Skip to content

Commit d288752

Browse files
authored
fix: field schema map paths (#10852)
Continuation of #10638. Field paths within the schema map are not correct. For example, an unnamed tab containing a text field should have the schema path: - `_index-0.fieldWithinUnnamedTab` However, within the schema map that path is formatted as: - `fieldWithinUnnamedTab` The leading index in the first example _should_ exist, as this field is nested within another field, regardless of this field being unnamed. Otherwise, it would be impossible to traverse the schema and lookup this field. This discrepancy is not an issue in the admin panel because fields are _also_ provided the wrong schema paths. They can properly locate their schema within the schema map because they match despite being incorrect. Both are wrong and that's why it works. Here's comprehensive example of how field paths _should_ be formatted: ```ts { // ... fields: [ { // path: 'topLevelNamedField' // schemaPath: 'topLevelNamedField' // indexPath: '' name: 'topLevelNamedField', type: 'text', }, { // path: 'array' // schemaPath: 'array' // indexPath: '' name: 'array', type: 'array', fields: [ { // path: 'array.[n].fieldWithinArray' // schemaPath: 'array.fieldWithinArray' // indexPath: '' name: 'fieldWithinArray', type: 'text', }, { // path: 'array.[n].nestedArray' // schemaPath: 'array.nestedArray' // indexPath: '' name: 'nestedArray', type: 'array', fields: [ { // path: 'array.[n].nestedArray.[n].fieldWithinNestedArray' // schemaPath: 'array.nestedArray.fieldWithinNestedArray' // indexPath: '' name: 'fieldWithinNestedArray', type: 'text', }, ], }, { // path: 'array.[n]._index-2' // schemaPath: 'array._index-2' // indexPath: '2' type: 'row', fields: [ { // path: 'array.[n].fieldWithinRowWithinArray' // schemaPath: 'array._index-2.fieldWithinRowWithinArray' // indexPath: '' name: 'fieldWithinRowWithinArray', type: 'text', }, ], }, ], }, { // path: '_index-2' // schemaPath: '_index-2' // indexPath: '2' type: 'row', fields: [ { // path: 'fieldWithinRow' // schemaPath: '_index-2.fieldWithinRow' // indexPath: '' name: 'fieldWithinRow', type: 'text', }, ], }, { // path: '_index-3' // schemaPath: '_index-3' // indexPath: '3' type: 'tabs', tabs: [ { // path: '_index-3-0' // schemaPath: '_index-3-0' // indexPath: '3-0' label: 'Unnamed Tab', fields: [ { // path: 'fieldWithinUnnamedTab' // schemaPath: '_index-3-0.fieldWithinUnnamedTab' // indexPath: '' name: 'fieldWithinUnnamedTab', type: 'text', }, { // path: '_index-3-0-1' // schemaPath: '_index-3-0-1' // indexPath: '3-0-1' type: 'tabs', tabs: [ { // path: '_index-3-0-1-0' // schemaPath: '_index-3-0-1-0' // indexPath: '3-0-1-0' label: 'Nested Unnamed Tab', fields: [ { // path: 'fieldWithinNestedUnnamedTab' // schemaPath: '_index-3-0-1-0.fieldWithinNestedUnnamedTab' // indexPath: '' name: 'fieldWithinNestedUnnamedTab', type: 'text', }, ], }, ], }, ], }, { // path: 'namedTab' // schemaPath: '_index-3.namedTab' // indexPath: '' label: 'Named Tab', name: 'namedTab', fields: [ { // path: 'namedTab.fieldWithinNamedTab' // schemaPath: '_index-3.namedTab.fieldWithinNamedTab' // indexPath: '' name: 'fieldWithinNamedTab', type: 'text', }, ], }, ], }, ] } ```
1 parent 1c8d515 commit d288752

File tree

41 files changed

+1778
-899
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1778
-899
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ jobs:
239239
- dashboard
240240
- joins
241241
- field-error-states
242+
- field-paths
242243
- fields-relationship
243244
- fields__collections__Array
244245
- fields__collections__Blocks

packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@ const buildVersionField = ({
304304
field: tabAsField,
305305
index: tabIndex,
306306
parentIndexPath: indexPath,
307-
parentPath,
308-
parentSchemaPath,
307+
parentPath: path,
308+
parentSchemaPath: schemaPath,
309309
})
310310

311311
let tabFieldsPermissions: SanitizedFieldsPermissions = undefined
@@ -340,11 +340,7 @@ const buildVersionField = ({
340340
parentIndexPath: isNamedTab ? '' : tabIndexPath,
341341
parentIsLocalized: parentIsLocalized || tab.localized,
342342
parentPath: isNamedTab ? tabPath : 'name' in field ? path : parentPath,
343-
parentSchemaPath: isNamedTab
344-
? tabSchemaPath
345-
: 'name' in field
346-
? schemaPath
347-
: parentSchemaPath,
343+
parentSchemaPath: tabSchemaPath,
348344
req,
349345
selectedLocales,
350346
versionFromSiblingData: 'name' in tab ? valueFrom?.[tab.name] : valueFrom,
@@ -396,7 +392,7 @@ const buildVersionField = ({
396392
parentIndexPath: 'name' in field ? '' : indexPath,
397393
parentIsLocalized: parentIsLocalized || field.localized,
398394
parentPath: ('name' in field ? path : parentPath) + '.' + i,
399-
parentSchemaPath: 'name' in field ? schemaPath : parentSchemaPath,
395+
parentSchemaPath: schemaPath,
400396
req,
401397
selectedLocales,
402398
versionFromSiblingData: fromRow,
@@ -424,7 +420,7 @@ const buildVersionField = ({
424420
parentIndexPath: 'name' in field ? '' : indexPath,
425421
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
426422
parentPath: 'name' in field ? path : parentPath,
427-
parentSchemaPath: 'name' in field ? schemaPath : parentSchemaPath,
423+
parentSchemaPath: schemaPath,
428424
req,
429425
selectedLocales,
430426
versionFromSiblingData: valueFrom as object,
@@ -502,7 +498,7 @@ const buildVersionField = ({
502498
parentIndexPath: 'name' in field ? '' : indexPath,
503499
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
504500
parentPath: ('name' in field ? path : parentPath) + '.' + i,
505-
parentSchemaPath: ('name' in field ? schemaPath : parentSchemaPath) + '.' + toBlock.slug,
501+
parentSchemaPath: schemaPath + '.' + toBlock.slug,
506502
req,
507503
selectedLocales,
508504
versionFromSiblingData: fromRow,

packages/next/src/views/Version/RenderFieldsToDiff/utilities/getFieldPathsModified.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

packages/payload/src/admin/forms/Form.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type SupportedLanguages } from '@payloadcms/translations'
22

33
import type { SanitizedDocumentPermissions } from '../../auth/types.js'
4-
import type { Field, Option, Validate } from '../../fields/config/types.js'
4+
import type { Field, Option, TabAsField, Validate } from '../../fields/config/types.js'
55
import type { TypedLocale } from '../../index.js'
66
import type { DocumentPreferences } from '../../preferences/types.js'
77
import type { PayloadRequest, SelectType, Where } from '../../types/index.js'
@@ -60,7 +60,7 @@ export type FieldState = {
6060
* The fieldSchema may be part of the form state if `includeSchema: true` is passed to buildFormState.
6161
* This will never be in the form state of the client.
6262
*/
63-
fieldSchema?: Field
63+
fieldSchema?: Field | TabAsField
6464
filterOptions?: FilterOptionsResult
6565
initialValue?: unknown
6666
/**

packages/payload/src/fields/config/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,7 @@ export type FieldWithManyClient = RelationshipFieldClient | SelectFieldClient
19821982
export type FieldWithMaxDepth = RelationshipField | UploadField
19831983
export type FieldWithMaxDepthClient = JoinFieldClient | RelationshipFieldClient | UploadFieldClient
19841984

1985-
export function fieldHasSubFields<TField extends ClientField | Field>(
1985+
export function fieldHasSubFields<TField extends ClientField | Field | TabAsField>(
19861986
field: TField,
19871987
): field is TField & (TField extends ClientField ? FieldWithSubFieldsClient : FieldWithSubFields) {
19881988
return (
Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import type { ClientTab } from '../admin/types.js'
12
import type { ClientField, Field, Tab, TabAsFieldClient } from './config/types.js'
23

34
type Args = {
4-
field: ClientField | Field | Tab | TabAsFieldClient
5+
field: ClientField | ClientTab | Field | Tab | TabAsFieldClient
56
index: number
67
parentIndexPath: string
7-
parentPath: string
8+
/**
9+
* Needed to generate data paths. Omit if you only need schema paths, e.g. within field schema maps.
10+
*/
11+
parentPath?: string
812
parentSchemaPath: string
913
}
1014

@@ -29,45 +33,19 @@ export function getFieldPaths({
2933
field,
3034
index,
3135
parentIndexPath,
32-
parentPath,
33-
parentSchemaPath,
34-
}: Args): FieldPaths {
35-
if ('name' in field) {
36-
return {
37-
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
38-
path: `${parentPath ? parentPath + '.' : ''}${field.name}`,
39-
schemaPath: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${field.name}`,
40-
}
41-
}
42-
43-
const indexSuffix = `_index-${`${parentIndexPath ? parentIndexPath + '-' : ''}${index}`}`
44-
45-
return {
46-
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
47-
path: `${parentPath ? parentPath + '.' : ''}${indexSuffix}`,
48-
schemaPath: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
49-
}
50-
}
51-
52-
/**
53-
* @deprecated - will be removed in 4.0. Use `getFieldPaths` instead.
54-
*/
55-
export function getFieldPathsModified({
56-
field,
57-
index,
58-
parentIndexPath,
59-
parentPath,
36+
parentPath = '',
6037
parentSchemaPath,
6138
}: Args): FieldPaths {
6239
const parentPathSegments = parentPath.split('.')
6340

64-
const parentIsUnnamed = parentPathSegments[parentPathSegments.length - 1]!.startsWith('_index-')
41+
const parentPathIsUnnamed =
42+
parentPathSegments?.[parentPathSegments.length - 1]?.startsWith('_index-')
6543

66-
const parentWithoutIndex = parentIsUnnamed
44+
const parentWithoutIndex = parentPathIsUnnamed
6745
? parentPathSegments.slice(0, -1).join('.')
6846
: parentPath
6947

70-
const parentPathToUse = parentIsUnnamed ? parentWithoutIndex : parentPath
48+
const parentPathToUse = parentPathIsUnnamed ? parentWithoutIndex : parentPath
7149

7250
if ('name' in field) {
7351
return {
@@ -79,9 +57,16 @@ export function getFieldPathsModified({
7957

8058
const indexSuffix = `_index-${`${parentIndexPath ? parentIndexPath + '-' : ''}${index}`}`
8159

60+
const parentSchemaPathSegments = parentSchemaPath.split('.')
61+
62+
const parentSchemaPathIsUnnamed =
63+
parentSchemaPathSegments?.[parentSchemaPathSegments.length - 1]?.startsWith('_index-')
64+
8265
return {
8366
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
8467
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${indexSuffix}`,
85-
schemaPath: `${!parentIsUnnamed && parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
68+
schemaPath: parentSchemaPathIsUnnamed
69+
? `${parentSchemaPath}-${index}`
70+
: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
8671
}
8772
}

packages/payload/src/fields/hooks/afterChange/promise.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Block, Field, TabAsField } from '../../config/types.js'
77

88
import { MissingEditorProp } from '../../../errors/index.js'
99
import { fieldAffectsData, tabHasName } from '../../config/types.js'
10-
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
10+
import { getFieldPaths } from '../../getFieldPaths.js'
1111
import { traverseFields } from './traverseFields.js'
1212

1313
type Args = {

packages/payload/src/fields/hooks/afterRead/promise.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
1717
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
1818
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
1919
import { getDefaultValue } from '../../getDefaultValue.js'
20-
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
20+
import { getFieldPaths } from '../../getFieldPaths.js'
2121
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
2222
import { traverseFields } from './traverseFields.js'
2323
import { virtualFieldPopulationPromise } from './virtualFieldPopulationPromise.js'

packages/payload/src/fields/hooks/beforeChange/promise.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { type RequestContext, validateBlocksFilterOptions } from '../../../index
1010
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
1111
import { getTranslatedLabel } from '../../../utilities/getTranslatedLabel.js'
1212
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
13-
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
13+
import { getFieldPaths } from '../../getFieldPaths.js'
1414
import { getExistingRowDoc } from './getExistingRowDoc.js'
1515
import { traverseFields } from './traverseFields.js'
1616

packages/payload/src/fields/hooks/beforeDuplicate/promise.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { JsonObject, PayloadRequest } from '../../../types/index.js'
44
import type { Block, Field, FieldHookArgs, TabAsField } from '../../config/types.js'
55

66
import { fieldAffectsData, fieldShouldBeLocalized } from '../../config/types.js'
7-
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
7+
import { getFieldPaths } from '../../getFieldPaths.js'
88
import { traverseFields } from './traverseFields.js'
99

1010
type Args<T> = {

0 commit comments

Comments
 (0)