chore: improves field types (#9172)

### What?
Ensures `path` is required and only present on the fields that expect it
(all fields except row).

Deprecates `useFieldComponents` and `FieldComponentsProvider` and
instead extends the RenderField component to account for all field
types. This also improves type safety within `RenderField`.

### Why?
`path` being optional just adds DX overhead and annoyance. 

### How?
Added `FieldPaths` type which is added to iterable field types. Placed
`path` back onto the ClientFieldBase type.
This commit is contained in:
Jarrod Flesch
2024-11-13 13:53:47 -05:00
committed by GitHub
parent cd95daf029
commit bcbca0e44a
68 changed files with 305 additions and 281 deletions

View File

@@ -19,6 +19,7 @@ export const LocaleSelector: React.FC<{
options: localeOptions, options: localeOptions,
}} }}
onChange={(value: string) => onChange(value)} onChange={(value: string) => onChange(value)}
path="locale"
/> />
) )
} }

View File

@@ -159,6 +159,7 @@ export const APIViewClient: React.FC = () => {
label: t('version:draft'), label: t('version:draft'),
}} }}
onChange={() => setDraft(!draft)} onChange={() => setDraft(!draft)}
path="draft"
/> />
)} )}
<CheckboxField <CheckboxField
@@ -167,6 +168,7 @@ export const APIViewClient: React.FC = () => {
label: t('authentication:authenticated'), label: t('authentication:authenticated'),
}} }}
onChange={() => setAuthenticated(!authenticated)} onChange={() => setAuthenticated(!authenticated)}
path="authenticated"
/> />
</div> </div>
{localeOptions && <LocaleSelector localeOptions={localeOptions} onChange={setLocale} />} {localeOptions && <LocaleSelector localeOptions={localeOptions} onChange={setLocale} />}
@@ -181,6 +183,7 @@ export const APIViewClient: React.FC = () => {
min: 0, min: 0,
}} }}
onChange={(value) => setDepth(value?.toString())} onChange={(value) => setDepth(value?.toString())}
path="depth"
/> />
</div> </div>
</Form> </Form>

View File

@@ -36,6 +36,7 @@ export const ToggleTheme: React.FC = () => {
], ],
}} }}
onChange={onChange} onChange={onChange}
path="theme"
value={autoMode ? 'auto' : theme} value={autoMode ? 'auto' : theme}
/> />
) )

View File

@@ -105,6 +105,7 @@ export const CreateFirstUserClient: React.FC<{
label: t('authentication:newPassword'), label: t('authentication:newPassword'),
required: true, required: true,
}} }}
path="password"
/> />
<ConfirmPasswordField /> <ConfirmPasswordField />
<RenderFields <RenderFields

View File

@@ -86,6 +86,7 @@ export const ForgotPasswordForm: React.FC = () => {
label: t('authentication:username'), label: t('authentication:username'),
required: true, required: true,
}} }}
path="username"
validate={(value) => validate={(value) =>
text(value, { text(value, {
name: 'username', name: 'username',
@@ -113,6 +114,7 @@ export const ForgotPasswordForm: React.FC = () => {
label: t('general:email'), label: t('general:email'),
required: true, required: true,
}} }}
path="email"
validate={(value) => validate={(value) =>
email(value, { email(value, {
name: 'email', name: 'email',

View File

@@ -25,6 +25,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('general:email'), label: t('general:email'),
required, required,
}} }}
path="email"
validate={email} validate={email}
/> />
) )
@@ -38,6 +39,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('authentication:username'), label: t('authentication:username'),
required, required,
}} }}
path="username"
validate={username} validate={username}
/> />
) )
@@ -51,6 +53,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('authentication:emailOrUsername'), label: t('authentication:emailOrUsername'),
required, required,
}} }}
path="username"
validate={(value, options) => { validate={(value, options) => {
const passesUsername = username(value, options) const passesUsername = username(value, options)
const passesEmail = email( const passesEmail = email(

View File

@@ -98,6 +98,7 @@ export const LoginForm: React.FC<{
label: t('general:password'), label: t('general:password'),
required: true, required: true,
}} }}
path="password"
/> />
</div> </div>
<Link <Link

View File

@@ -75,9 +75,6 @@ export const ResetPasswordForm: React.FC<Args> = ({ token }) => {
label: i18n.t('authentication:newPassword'), label: i18n.t('authentication:newPassword'),
required: true, required: true,
}} }}
indexPath=""
parentPath=""
parentSchemaPath=""
path="password" path="password"
schemaPath={`${userSlug}.password`} schemaPath={`${userSlug}.password`}
/> />
@@ -90,9 +87,6 @@ export const ResetPasswordForm: React.FC<Args> = ({ token }) => {
hidden: true, hidden: true,
}, },
}} }}
indexPath=""
parentPath={userSlug}
parentSchemaPath={userSlug}
path="token" path="token"
schemaPath={`${userSlug}.token`} schemaPath={`${userSlug}.token`}
value={token} value={token}

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -19,9 +20,9 @@ import type {
type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'> type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'>
type ArrayFieldBaseClientProps = { type ArrayFieldBaseClientProps = {
readonly path?: string
readonly validate?: ArrayFieldValidation readonly validate?: ArrayFieldValidation
} & Pick<ServerFieldBase, 'permissions'> } & FieldPaths &
Pick<ServerFieldBase, 'permissions'>
export type ArrayFieldClientProps = ArrayFieldBaseClientProps & export type ArrayFieldClientProps = ArrayFieldBaseClientProps &
ClientFieldBase<ArrayFieldClientWithoutType> ClientFieldBase<ArrayFieldClientWithoutType>

View File

@@ -1,11 +1,12 @@
import type { MarkOptional } from 'ts-essentials' import type { MarkOptional } from 'ts-essentials'
import type { BlocksField, BlocksFieldClient, ClientField } from '../../fields/config/types.js' import type { BlocksField, BlocksFieldClient } from '../../fields/config/types.js'
import type { BlocksFieldValidation } from '../../fields/validations.js' import type { BlocksFieldValidation } from '../../fields/validations.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js' import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -19,9 +20,9 @@ import type {
type BlocksFieldClientWithoutType = MarkOptional<BlocksFieldClient, 'type'> type BlocksFieldClientWithoutType = MarkOptional<BlocksFieldClient, 'type'>
type BlocksFieldBaseClientProps = { type BlocksFieldBaseClientProps = {
readonly path?: string
readonly validate?: BlocksFieldValidation readonly validate?: BlocksFieldValidation
} & Pick<ServerFieldBase, 'permissions'> } & FieldPaths &
Pick<ServerFieldBase, 'permissions'>
export type BlocksFieldClientProps = BlocksFieldBaseClientProps & export type BlocksFieldClientProps = BlocksFieldBaseClientProps &
ClientFieldBase<BlocksFieldClientWithoutType> ClientFieldBase<BlocksFieldClientWithoutType>

View File

@@ -24,7 +24,7 @@ type CheckboxFieldBaseClientProps = {
readonly id?: string readonly id?: string
readonly onChange?: (value: boolean) => void readonly onChange?: (value: boolean) => void
readonly partialChecked?: boolean readonly partialChecked?: boolean
readonly path?: string readonly path: string
readonly validate?: CheckboxFieldValidation readonly validate?: CheckboxFieldValidation
} }

View File

@@ -20,7 +20,7 @@ type CodeFieldClientWithoutType = MarkOptional<CodeFieldClient, 'type'>
type CodeFieldBaseClientProps = { type CodeFieldBaseClientProps = {
readonly autoComplete?: string readonly autoComplete?: string
readonly path?: string readonly path: string
readonly validate?: CodeFieldValidation readonly validate?: CodeFieldValidation
} }

View File

@@ -5,6 +5,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -15,9 +16,7 @@ import type {
FieldLabelServerComponent, FieldLabelServerComponent,
} from '../types.js' } from '../types.js'
type CollapsibleFieldBaseClientProps = { type CollapsibleFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'> type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'>

View File

@@ -19,7 +19,7 @@ import type {
type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'> type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'>
type DateFieldBaseClientProps = { type DateFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: DateFieldValidation readonly validate?: DateFieldValidation
} }

View File

@@ -19,7 +19,7 @@ import type {
type EmailFieldClientWithoutType = MarkOptional<EmailFieldClient, 'type'> type EmailFieldClientWithoutType = MarkOptional<EmailFieldClient, 'type'>
type EmailFieldBaseClientProps = { type EmailFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: EmailFieldValidation readonly validate?: EmailFieldValidation
} }

View File

@@ -5,6 +5,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -17,9 +18,7 @@ import type {
type GroupFieldClientWithoutType = MarkOptional<GroupFieldClient, 'type'> type GroupFieldClientWithoutType = MarkOptional<GroupFieldClient, 'type'>
export type GroupFieldBaseClientProps = { export type GroupFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
export type GroupFieldClientProps = ClientFieldBase<GroupFieldClientWithoutType> & export type GroupFieldClientProps = ClientFieldBase<GroupFieldClientWithoutType> &
GroupFieldBaseClientProps GroupFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ type HiddenFieldBaseClientProps = {
readonly field?: { readonly field?: {
readonly name?: string readonly name?: string
} & ClientField } & ClientField
readonly path: string
readonly value?: unknown readonly value?: unknown
} }

View File

@@ -19,7 +19,7 @@ import type {
type JSONFieldClientWithoutType = MarkOptional<JSONFieldClient, 'type'> type JSONFieldClientWithoutType = MarkOptional<JSONFieldClient, 'type'>
type JSONFieldBaseClientProps = { type JSONFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: JSONFieldValidation readonly validate?: JSONFieldValidation
} }

View File

@@ -17,15 +17,21 @@ import type {
type JoinFieldClientWithoutType = MarkOptional<JoinFieldClient, 'type'> type JoinFieldClientWithoutType = MarkOptional<JoinFieldClient, 'type'>
export type JoinFieldClientProps = { type JoinFieldBaseClientProps = {
path?: string readonly path: string
} & ClientFieldBase<JoinFieldClientWithoutType> }
export type JoinFieldClientProps = ClientFieldBase<JoinFieldClientWithoutType> &
JoinFieldBaseClientProps
export type JoinFieldServerProps = ServerFieldBase<JoinField> export type JoinFieldServerProps = ServerFieldBase<JoinField>
export type JoinFieldServerComponent = FieldServerComponent<JoinField> export type JoinFieldServerComponent = FieldServerComponent<JoinField>
export type JoinFieldClientComponent = FieldClientComponent<JoinFieldClientWithoutType> export type JoinFieldClientComponent = FieldClientComponent<
JoinFieldClientWithoutType,
JoinFieldBaseClientProps
>
export type JoinFieldLabelServerComponent = FieldLabelServerComponent<JoinField> export type JoinFieldLabelServerComponent = FieldLabelServerComponent<JoinField>

View File

@@ -20,7 +20,7 @@ type NumberFieldClientWithoutType = MarkOptional<NumberFieldClient, 'type'>
type NumberFieldBaseClientProps = { type NumberFieldBaseClientProps = {
readonly onChange?: (e: number) => void readonly onChange?: (e: number) => void
readonly path?: string readonly path: string
readonly validate?: NumberFieldValidation readonly validate?: NumberFieldValidation
} }

View File

@@ -19,7 +19,7 @@ import type {
type PointFieldClientWithoutType = MarkOptional<PointFieldClient, 'type'> type PointFieldClientWithoutType = MarkOptional<PointFieldClient, 'type'>
type PointFieldBaseClientProps = { type PointFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: PointFieldValidation readonly validate?: PointFieldValidation
} }

View File

@@ -24,7 +24,7 @@ type RadioFieldBaseClientProps = {
*/ */
readonly disableModifyingForm?: boolean readonly disableModifyingForm?: boolean
readonly onChange?: OnChange readonly onChange?: OnChange
readonly path?: string readonly path: string
readonly validate?: RadioFieldValidation readonly validate?: RadioFieldValidation
readonly value?: string readonly value?: string
} }

View File

@@ -19,7 +19,7 @@ import type {
type RelationshipFieldClientWithoutType = MarkOptional<RelationshipFieldClient, 'type'> type RelationshipFieldClientWithoutType = MarkOptional<RelationshipFieldClient, 'type'>
type RelationshipFieldBaseClientProps = { type RelationshipFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: RelationshipFieldValidation readonly validate?: RelationshipFieldValidation
} }

View File

@@ -27,7 +27,7 @@ type RichTextFieldBaseClientProps<
TAdapterProps = any, TAdapterProps = any,
TExtraProperties = object, TExtraProperties = object,
> = { > = {
readonly path?: string readonly path: string
readonly validate?: RichTextFieldValidation readonly validate?: RichTextFieldValidation
} }

View File

@@ -4,6 +4,7 @@ import type { RowField, RowFieldClient } from '../../fields/config/types.js'
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -20,9 +21,10 @@ type RowFieldClientWithoutType = MarkOptional<RowFieldClient, 'type'>
type RowFieldBaseClientProps = { type RowFieldBaseClientProps = {
readonly forceRender?: boolean readonly forceRender?: boolean
} & Pick<ServerFieldBase, 'permissions'> } & Omit<FieldPaths, 'path'> &
Pick<ServerFieldBase, 'permissions'>
export type RowFieldClientProps = ClientFieldBase<RowFieldClientWithoutType> & export type RowFieldClientProps = Omit<ClientFieldBase<RowFieldClientWithoutType>, 'path'> &
RowFieldBaseClientProps RowFieldBaseClientProps
export type RowFieldServerProps = ServerFieldBase<RowField, RowFieldClientWithoutType> export type RowFieldServerProps = ServerFieldBase<RowField, RowFieldClientWithoutType>

View File

@@ -20,7 +20,7 @@ type SelectFieldClientWithoutType = MarkOptional<SelectFieldClient, 'type'>
type SelectFieldBaseClientProps = { type SelectFieldBaseClientProps = {
readonly onChange?: (e: string | string[]) => void readonly onChange?: (e: string | string[]) => void
readonly path?: string readonly path: string
readonly validate?: SelectFieldValidation readonly validate?: SelectFieldValidation
readonly value?: string readonly value?: string
} }

View File

@@ -11,6 +11,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type { import type {
ClientFieldBase, ClientFieldBase,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerFieldBase, ServerFieldBase,
} from '../forms/Field.js' } from '../forms/Field.js'
@@ -25,7 +26,7 @@ export type ClientTab =
| ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>) | ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>)
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>) | ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
type TabsFieldBaseClientProps = {} & Pick<ServerFieldBase, 'permissions'> type TabsFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
type TabsFieldClientWithoutType = MarkOptional<TabsFieldClient, 'type'> type TabsFieldClientWithoutType = MarkOptional<TabsFieldClient, 'type'>

View File

@@ -22,7 +22,7 @@ type TextFieldClientWithoutType = MarkOptional<TextFieldClient, 'type'>
type TextFieldBaseClientProps = { type TextFieldBaseClientProps = {
readonly inputRef?: React.RefObject<HTMLInputElement> readonly inputRef?: React.RefObject<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement> readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path?: string readonly path: string
readonly validate?: TextFieldValidation readonly validate?: TextFieldValidation
} }

View File

@@ -22,7 +22,7 @@ type TextareaFieldClientWithoutType = MarkOptional<TextareaFieldClient, 'type'>
type TextareaFieldBaseClientProps = { type TextareaFieldBaseClientProps = {
readonly inputRef?: React.Ref<HTMLInputElement> readonly inputRef?: React.Ref<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement> readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path?: string readonly path: string
readonly validate?: TextareaFieldValidation readonly validate?: TextareaFieldValidation
} }

View File

@@ -1,9 +1,26 @@
import type { MarkOptional } from 'ts-essentials' import type { MarkOptional } from 'ts-essentials'
import type { UIField, UIFieldClient } from '../../fields/config/types.js' import type { UIField, UIFieldClient } from '../../fields/config/types.js'
import type { FieldClientComponent, FieldServerComponent } from '../types.js' import type {
ClientFieldBase,
FieldClientComponent,
FieldServerComponent,
ServerFieldBase,
} from '../types.js'
type UIFieldClientWithoutType = MarkOptional<UIFieldClient, 'type'> type UIFieldClientWithoutType = MarkOptional<UIFieldClient, 'type'>
export type UIFieldClientComponent = FieldClientComponent<UIFieldClientWithoutType>
type UIFieldBaseClientProps = {
readonly path: string
}
export type UIFieldClientProps = ClientFieldBase<UIFieldClientWithoutType> & UIFieldBaseClientProps
export type UIFieldServerProps = ServerFieldBase<UIField, UIFieldClientWithoutType>
export type UIFieldClientComponent = FieldClientComponent<
UIFieldClientWithoutType,
UIFieldBaseClientProps
>
export type UIFieldServerComponent = FieldServerComponent<UIField, UIFieldClientWithoutType> export type UIFieldServerComponent = FieldServerComponent<UIField, UIFieldClientWithoutType>

View File

@@ -19,7 +19,7 @@ import type {
type UploadFieldClientWithoutType = MarkOptional<UploadFieldClient, 'type'> type UploadFieldClientWithoutType = MarkOptional<UploadFieldClient, 'type'>
type UploadFieldBaseClientProps = { type UploadFieldBaseClientProps = {
readonly path?: string readonly path: string
readonly validate?: UploadFieldValidation readonly validate?: UploadFieldValidation
} }

View File

@@ -19,6 +19,19 @@ export type ClientComponentProps = {
customComponents: FormField['customComponents'] customComponents: FormField['customComponents']
field: ClientBlock | ClientField | ClientTab field: ClientBlock | ClientField | ClientTab
forceRender?: boolean forceRender?: boolean
readOnly?: boolean
renderedBlocks?: RenderedField[]
/**
* Used to extract field configs from a schemaMap.
* Does not include indexes.
*
* @default field.name
**/
schemaPath?: string
}
// TODO: maybe we can come up with a better name?
export type FieldPaths = {
/** /**
* @default '' * @default ''
*/ */
@@ -28,19 +41,27 @@ export type ClientComponentProps = {
*/ */
parentPath?: string parentPath?: string
/** /**
* @default ''' * The path built up to the point of the field
* excluding the field name.
*
* @default ''
*/ */
parentSchemaPath?: string parentSchemaPath?: string
/** /**
* A built up path to access FieldState in the form state.
* Nested fields will have a path that includes the parent field names
* if they are nested within a group, array, block or named tab.
*
* Collapsibles and unnamed tabs will have arbitrary paths
* that look like _index-0, _index-1, etc.
*
* Row fields will not have a path.
*
* @example 'parentGroupField.childTextField'
*
* @default field.name * @default field.name
*/ */
path?: string path: string
readOnly?: boolean
renderedBlocks?: RenderedField[]
/**
* @default field.name
**/
schemaPath?: string
} }
export type ServerComponentProps = { export type ServerComponentProps = {

View File

@@ -301,7 +301,12 @@ export type {
TextareaFieldServerProps, TextareaFieldServerProps,
} from './fields/Textarea.js' } from './fields/Textarea.js'
export type { UIFieldClientComponent, UIFieldServerComponent } from './fields/UI.js' export type {
UIFieldClientComponent,
UIFieldClientProps,
UIFieldServerComponent,
UIFieldServerProps,
} from './fields/UI.js'
export type { export type {
UploadFieldClientComponent, UploadFieldClientComponent,
@@ -351,6 +356,7 @@ export type {
ClientFieldBase, ClientFieldBase,
ClientFieldWithOptionalType, ClientFieldWithOptionalType,
FieldClientComponent, FieldClientComponent,
FieldPaths,
FieldServerComponent, FieldServerComponent,
ServerComponentProps, ServerComponentProps,
ServerFieldBase, ServerFieldBase,

View File

@@ -1,4 +1,9 @@
import type { ClientComponentProps, RichTextFieldClient, ServerComponentProps } from 'payload' import type {
ClientComponentProps,
FieldPaths,
RichTextFieldClient,
ServerComponentProps,
} from 'payload'
import React from 'react' import React from 'react'
@@ -13,6 +18,7 @@ export const RscEntryLexicalField: React.FC<
admin: LexicalFieldAdminProps admin: LexicalFieldAdminProps
sanitizedEditorConfig: SanitizedServerEditorConfig sanitizedEditorConfig: SanitizedServerEditorConfig
} & ClientComponentProps & } & ClientComponentProps &
Pick<FieldPaths, 'path'> &
ServerComponentProps ServerComponentProps
> = (args) => { > = (args) => {
const path = args.path ?? (args.clientField as RichTextFieldClient).name const path = args.path ?? (args.clientField as RichTextFieldClient).name
@@ -32,10 +38,7 @@ export const RscEntryLexicalField: React.FC<
featureClientSchemaMap={featureClientSchemaMap} featureClientSchemaMap={featureClientSchemaMap}
field={args.clientField as RichTextFieldClient} field={args.clientField as RichTextFieldClient}
forceRender={args.forceRender} forceRender={args.forceRender}
indexPath={args.indexPath}
lexicalEditorConfig={args.sanitizedEditorConfig.lexical} lexicalEditorConfig={args.sanitizedEditorConfig.lexical}
parentPath={args.parentPath}
parentSchemaPath={args.parentSchemaPath}
path={path} path={path}
permissions={args.permissions} permissions={args.permissions}
readOnly={args.readOnly} readOnly={args.readOnly}

View File

@@ -23,10 +23,7 @@ export interface EditorConfigContextType {
editorConfig: SanitizedClientEditorConfig editorConfig: SanitizedClientEditorConfig
editorContainerRef: React.RefObject<HTMLDivElement> editorContainerRef: React.RefObject<HTMLDivElement>
fieldProps: MarkRequired< fieldProps: MarkRequired<LexicalRichTextFieldProps, 'path' | 'schemaPath'>
LexicalRichTextFieldProps,
'indexPath' | 'parentPath' | 'parentSchemaPath' | 'path' | 'schemaPath'
>
focusedEditor: EditorConfigContextType | null focusedEditor: EditorConfigContextType | null
// Editor focus handling // Editor focus handling
focusEditor: (editorContext: EditorConfigContextType) => void focusEditor: (editorContext: EditorConfigContextType) => void

View File

@@ -1,13 +1,13 @@
import type {
ClientComponentProps,
ClientField,
Field,
FieldPaths,
RichTextFieldClient,
ServerComponentProps } from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { import { createClientFields, deepCopyObjectSimple } from 'payload'
type ClientComponentProps,
type ClientField,
createClientFields,
deepCopyObjectSimple,
type Field,
type RichTextFieldClient,
type ServerComponentProps,
} from 'payload'
import React from 'react' import React from 'react'
import type { AdapterArguments, RichTextCustomElement, RichTextCustomLeaf } from '../types.js' import type { AdapterArguments, RichTextCustomElement, RichTextCustomLeaf } from '../types.js'
@@ -22,15 +22,13 @@ export const RscEntrySlateField: React.FC<
{ {
args: AdapterArguments args: AdapterArguments
} & ClientComponentProps & } & ClientComponentProps &
Pick<FieldPaths, 'path'> &
ServerComponentProps ServerComponentProps
> = ({ > = ({
args, args,
clientField, clientField,
forceRender, forceRender,
i18n, i18n,
indexPath,
parentPath,
parentSchemaPath,
path, path,
payload, payload,
readOnly, readOnly,
@@ -191,9 +189,6 @@ export const RscEntrySlateField: React.FC<
componentMap={Object.fromEntries(componentMap)} componentMap={Object.fromEntries(componentMap)}
field={clientField as RichTextFieldClient} field={clientField as RichTextFieldClient}
forceRender={forceRender} forceRender={forceRender}
indexPath={indexPath}
parentPath={parentPath}
parentSchemaPath={parentSchemaPath}
path={path} path={path}
readOnly={readOnly} readOnly={readOnly}
renderedBlocks={renderedBlocks} renderedBlocks={renderedBlocks}

View File

@@ -29,7 +29,7 @@ export function EmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps)
const showUsernameField = Boolean(loginWithUsername) const showUsernameField = Boolean(loginWithUsername)
return ( return (
<Fragment> <div className={className}>
{showEmailField ? ( {showEmailField ? (
<EmailField <EmailField
field={{ field={{
@@ -40,9 +40,6 @@ export function EmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps)
label: t('general:email'), label: t('general:email'),
required: !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail), required: !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail),
}} }}
indexPath=""
parentPath=""
parentSchemaPath=""
path="email" path="email"
schemaPath="email" schemaPath="email"
validate={email} validate={email}
@@ -55,14 +52,11 @@ export function EmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps)
label: t('authentication:username'), label: t('authentication:username'),
required: loginWithUsername && loginWithUsername.requireUsername, required: loginWithUsername && loginWithUsername.requireUsername,
}} }}
indexPath=""
parentPath=""
parentSchemaPath=""
path="username" path="username"
schemaPath="username" schemaPath="username"
validate={username} validate={username}
/> />
)} )}
</Fragment> </div>
) )
} }

View File

@@ -233,10 +233,6 @@ export {
EntityVisibilityProvider, EntityVisibilityProvider,
useEntityVisibility, useEntityVisibility,
} from '../../providers/EntityVisibility/index.js' } from '../../providers/EntityVisibility/index.js'
export {
FieldComponentsProvider,
useFieldComponents,
} from '../../providers/FieldComponents/index.js'
export { UploadEditsProvider, useUploadEdits } from '../../providers/UploadEdits/index.js' export { UploadEditsProvider, useUploadEdits } from '../../providers/UploadEdits/index.js'
export { export {
ListDrawerContextProvider, ListDrawerContextProvider,

View File

@@ -46,13 +46,12 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
required, required,
}, },
forceRender = false, forceRender = false,
path: pathFromProps, path,
permissions, permissions,
readOnly, readOnly,
schemaPath: schemaPathFromProps, schemaPath: schemaPathFromProps,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const schemaPath = schemaPathFromProps ?? name const schemaPath = schemaPathFromProps ?? name
const minRows = (minRowsProp ?? required) ? 1 : 0 const minRows = (minRowsProp ?? required) ? 1 : 0

View File

@@ -47,13 +47,12 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
minRows: minRowsProp, minRows: minRowsProp,
required, required,
}, },
path: pathFromProps, path,
permissions, permissions,
readOnly, readOnly,
schemaPath: schemaPathFromProps, schemaPath: schemaPathFromProps,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const schemaPath = schemaPathFromProps ?? name const schemaPath = schemaPathFromProps ?? name
const minRows = (minRowsProp ?? required) ? 1 : 0 const minRows = (minRowsProp ?? required) ? 1 : 0

View File

@@ -43,11 +43,10 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
} = {} as CheckboxFieldClientProps['field'], } = {} as CheckboxFieldClientProps['field'],
onChange: onChangeFromProps, onChange: onChangeFromProps,
partialChecked, partialChecked,
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { uuid } = useForm() const { uuid } = useForm()

View File

@@ -36,11 +36,10 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
localized, localized,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const memoizedValidate = useCallback( const memoizedValidate = useCallback(
(value, options) => { (value, options) => {

View File

@@ -25,10 +25,10 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
const { const {
field, field,
field: { admin: { className, description, initCollapsed = false } = {}, fields, label } = {}, field: { admin: { className, description, initCollapsed = false } = {}, fields, label } = {},
indexPath = '', indexPath,
parentPath = '', parentPath,
parentSchemaPath = '', parentSchemaPath,
path = '', path,
permissions, permissions,
readOnly, readOnly,
} = props } = props

View File

@@ -26,11 +26,10 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
localized, localized,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { i18n } = useTranslation() const { i18n } = useTranslation()

View File

@@ -34,11 +34,10 @@ const EmailFieldComponent: EmailFieldClientComponent = (props) => {
localized, localized,
required, required,
} = {} as EmailFieldClientProps['field'], } = {} as EmailFieldClientProps['field'],
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { i18n } = useTranslation() const { i18n } = useTranslation()

View File

@@ -31,12 +31,11 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
fields, fields,
label, label,
}, },
path: pathFromProps, path,
permissions, permissions,
readOnly, readOnly,
schemaPath: schemaPathFromProps, schemaPath: schemaPathFromProps,
} = props } = props
const path = pathFromProps ?? name
const schemaPath = schemaPathFromProps ?? name const schemaPath = schemaPathFromProps ?? name
const { i18n } = useTranslation() const { i18n } = useTranslation()

View File

@@ -25,11 +25,10 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
localized, localized,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const [stringValue, setStringValue] = useState<string>() const [stringValue, setStringValue] = useState<string>()
const [jsonError, setJsonError] = useState<string>() const [jsonError, setJsonError] = useState<string>()

View File

@@ -23,11 +23,9 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
on, on,
required, required,
}, },
path: pathFromProps, path,
} = props } = props
const path = pathFromProps ?? name
const { id: docID } = useDocumentInfo() const { id: docID } = useDocumentInfo()
const { customComponents: { AfterInput, BeforeInput, Label } = {}, value } = const { customComponents: { AfterInput, BeforeInput, Label } = {}, value } =

View File

@@ -39,11 +39,10 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
required, required,
}, },
onChange: onChangeFromProps, onChange: onChangeFromProps,
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()

View File

@@ -33,10 +33,9 @@ const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => {
required, required,
} = {} as PasswordFieldProps['field'], } = {} as PasswordFieldProps['field'],
inputRef, inputRef,
path: pathFromProps, path,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { t } = useTranslation() const { t } = useTranslation()
const locale = useLocale() const locale = useLocale()

View File

@@ -27,7 +27,7 @@ export type PasswordFieldProps = {
/** /**
* @default field.name * @default field.name
*/ */
readonly path?: string readonly path: string
/** /**
* @default field.name * @default field.name
*/ */

View File

@@ -25,11 +25,10 @@ export const PointFieldComponent: PointFieldClientComponent = (props) => {
localized, localized,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()

View File

@@ -21,7 +21,6 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
const { const {
disableModifyingForm: disableModifyingFormFromProps, disableModifyingForm: disableModifyingFormFromProps,
field: { field: {
name,
admin: { admin: {
className, className,
description, description,
@@ -35,12 +34,11 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
required, required,
} = {} as RadioFieldClientProps['field'], } = {} as RadioFieldClientProps['field'],
onChange: onChangeFromProps, onChange: onChangeFromProps,
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
value: valueFromProps, value: valueFromProps,
} = props } = props
const path = pathFromProps ?? name
const { uuid } = useForm() const { uuid } = useForm()

View File

@@ -56,11 +56,10 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
relationTo, relationTo,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { config } = useConfig() const { config } = useConfig()

View File

@@ -46,11 +46,10 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
required, required,
}, },
onChange: onChangeFromProps, onChange: onChangeFromProps,
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const options = React.useMemo(() => formatOptions(optionsFromProps), [optionsFromProps]) const options = React.useMemo(() => formatOptions(optionsFromProps), [optionsFromProps])

View File

@@ -11,8 +11,8 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { isFieldRTL } from '../shared/index.js' import { isFieldRTL } from '../shared/index.js'
import './index.scss'
import { TextInput } from './Input.js' import { TextInput } from './Input.js'
import './index.scss'
export { TextInput, TextInputProps } export { TextInput, TextInputProps }
@@ -31,11 +31,10 @@ const TextFieldComponent: TextFieldClientComponent = (props) => {
required, required,
}, },
inputRef, inputRef,
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const locale = useLocale() const locale = useLocale()

View File

@@ -28,11 +28,10 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
minLength, minLength,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { i18n } = useTranslation() const { i18n } = useTranslation()

View File

@@ -27,11 +27,10 @@ export function UploadComponent(props: UploadFieldClientProps) {
relationTo, relationTo,
required, required,
}, },
path: pathFromProps, path,
readOnly, readOnly,
validate, validate,
} = props } = props
const path = pathFromProps ?? name
const { config } = useConfig() const { config } = useConfig()

View File

@@ -8,6 +8,8 @@ import type {
} from 'payload' } from 'payload'
import type React from 'react' import type React from 'react'
import type { ConfirmPasswordFieldProps } from './ConfirmPassword/index.js'
import { RowLabel } from '../forms/RowLabel/index.js' import { RowLabel } from '../forms/RowLabel/index.js'
import { ArrayField } from './Array/index.js' import { ArrayField } from './Array/index.js'
import { BlocksField } from './Blocks/index.js' import { BlocksField } from './Blocks/index.js'
@@ -41,7 +43,9 @@ import { UploadField } from './Upload/index.js'
export * from './shared/index.js' export * from './shared/index.js'
export type FieldTypesComponents = { export type FieldTypesComponents = {
[K in 'confirmPassword' | 'hidden' | 'password' | FieldTypes]: React.FC<ClientFieldBase> [K in 'hidden' | 'password' | FieldTypes]: React.FC<ClientFieldBase>
} & {
confirmPassword: React.FC<ConfirmPasswordFieldProps>
} }
export const fieldComponents: FieldTypesComponents = { export const fieldComponents: FieldTypesComponents = {

View File

@@ -59,9 +59,6 @@ export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
label: t('general:fallbackToDefaultLocale'), label: t('general:fallbackToDefaultLocale'),
}} }}
id={`field-${path.replace(/\./g, '__')}`} id={`field-${path.replace(/\./g, '__')}`}
indexPath=""
parentPath=""
parentSchemaPath=""
path={path} path={path}
schemaPath="" schemaPath=""
// onToggle={onChange} // onToggle={onChange}

View File

@@ -1,32 +1,39 @@
'use client' 'use client'
import type { ClientComponentProps, ClientField, FieldPermissions } from 'payload' import type { ClientComponentProps, ClientField, FieldPaths, FieldPermissions } from 'payload'
import React from 'react' import React from 'react'
import { ArrayField } from '../../fields/Array/index.js' import { ArrayField } from '../../fields/Array/index.js'
import { BlocksField } from '../../fields/Blocks/index.js' import { BlocksField } from '../../fields/Blocks/index.js'
import { CheckboxField } from '../../fields/Checkbox/index.js'
import { CodeField } from '../../fields/Code/index.js'
import { CollapsibleField } from '../../fields/Collapsible/index.js' import { CollapsibleField } from '../../fields/Collapsible/index.js'
import { DateTimeField } from '../../fields/DateTime/index.js'
import { EmailField } from '../../fields/Email/index.js'
import { GroupField } from '../../fields/Group/index.js' import { GroupField } from '../../fields/Group/index.js'
import { HiddenField } from '../../fields/Hidden/index.js' import { HiddenField } from '../../fields/Hidden/index.js'
import { JoinField } from '../../fields/Join/index.js'
import { JSONField } from '../../fields/JSON/index.js'
import { NumberField } from '../../fields/Number/index.js'
import { PointField } from '../../fields/Point/index.js'
import { RadioGroupField } from '../../fields/RadioGroup/index.js'
import { RelationshipField } from '../../fields/Relationship/index.js'
import { RichTextField } from '../../fields/RichText/index.js'
import { RowField } from '../../fields/Row/index.js' import { RowField } from '../../fields/Row/index.js'
import { SelectField } from '../../fields/Select/index.js'
import { TabsField } from '../../fields/Tabs/index.js' import { TabsField } from '../../fields/Tabs/index.js'
import { TextField } from '../../fields/Text/index.js'
import { TextareaField } from '../../fields/Textarea/index.js'
import { UIField } from '../../fields/UI/index.js'
import { UploadField } from '../../fields/Upload/index.js'
import { useFormFields } from '../../forms/Form/index.js' import { useFormFields } from '../../forms/Form/index.js'
import { useFieldComponents } from '../../providers/FieldComponents/index.js'
type RenderFieldProps = { type RenderFieldProps = {
clientFieldConfig: ClientField clientFieldConfig: ClientField
permissions: FieldPermissions permissions: FieldPermissions
} & Pick< } & FieldPaths &
ClientComponentProps, Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'>
| 'forceRender'
| 'indexPath'
| 'parentPath'
| 'parentSchemaPath'
| 'path'
| 'readOnly'
| 'schemaPath'
>
export function RenderField({ export function RenderField({
clientFieldConfig, clientFieldConfig,
@@ -39,59 +46,95 @@ export function RenderField({
readOnly, readOnly,
schemaPath, schemaPath,
}: RenderFieldProps) { }: RenderFieldProps) {
const fieldComponents = useFieldComponents()
const Field = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field) const Field = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
if (Field !== undefined) { if (Field !== undefined) {
return Field || null return Field || null
} }
const sharedProps: Pick< const baseFieldProps: Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'> = {
ClientComponentProps,
| 'forceRender'
| 'indexPath'
| 'parentPath'
| 'parentSchemaPath'
| 'path'
| 'readOnly'
| 'schemaPath'
> = {
forceRender, forceRender,
indexPath,
parentPath,
parentSchemaPath,
path,
readOnly, readOnly,
schemaPath, schemaPath,
} }
if (clientFieldConfig.admin?.hidden) { const iterableFieldProps = {
return <HiddenField field={clientFieldConfig} {...sharedProps} /> ...baseFieldProps,
indexPath,
parentPath,
parentSchemaPath,
permissions,
} }
const DefaultField = fieldComponents?.[clientFieldConfig?.type] if (clientFieldConfig.admin?.hidden) {
return <HiddenField {...baseFieldProps} field={clientFieldConfig} path={path} />
}
switch (clientFieldConfig.type) { switch (clientFieldConfig.type) {
// named fields with subfields
case 'array': case 'array':
return <ArrayField {...sharedProps} field={clientFieldConfig} permissions={permissions} /> return <ArrayField {...iterableFieldProps} field={clientFieldConfig} path={path} />
case 'blocks': case 'blocks':
return <BlocksField {...sharedProps} field={clientFieldConfig} permissions={permissions} /> return <BlocksField {...iterableFieldProps} field={clientFieldConfig} path={path} />
case 'checkbox':
return <CheckboxField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'code':
return <CodeField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'collapsible': case 'collapsible':
return ( return <CollapsibleField {...iterableFieldProps} field={clientFieldConfig} path={path} />
<CollapsibleField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
) case 'date':
return <DateTimeField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'email':
return <EmailField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'group': case 'group':
return <GroupField {...sharedProps} field={clientFieldConfig} permissions={permissions} /> return <GroupField {...iterableFieldProps} field={clientFieldConfig} path={path} />
case 'join':
return <JoinField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'json':
return <JSONField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'number':
return <NumberField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'point':
return <PointField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'radio':
return <RadioGroupField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'relationship':
return <RelationshipField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'richText':
return <RichTextField {...baseFieldProps} field={clientFieldConfig} path={path} />
// unnamed fields with subfields
case 'row': case 'row':
return <RowField {...sharedProps} field={clientFieldConfig} permissions={permissions} /> return <RowField {...iterableFieldProps} field={clientFieldConfig} />
case 'tabs':
return <TabsField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
default: case 'select':
return DefaultField ? <DefaultField field={clientFieldConfig} {...sharedProps} /> : null return <SelectField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'tabs':
return <TabsField {...iterableFieldProps} field={clientFieldConfig} path={path} />
case 'text':
return <TextField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'textarea':
return <TextareaField {...baseFieldProps} field={clientFieldConfig} path={path} />
case 'ui':
return <UIField />
case 'upload':
return <UploadField {...baseFieldProps} field={clientFieldConfig} path={path} />
} }
} }

View File

@@ -1,6 +1,7 @@
import type { import type {
ClientComponentProps, ClientComponentProps,
ClientField, ClientField,
FieldPaths,
FieldPermissions, FieldPermissions,
PayloadComponent, PayloadComponent,
ServerComponentProps, ServerComponentProps,
@@ -45,17 +46,21 @@ export const renderField: RenderFieldMethod = ({
? incomingPermissions?.[fieldConfig.name] ? incomingPermissions?.[fieldConfig.name]
: ({} as FieldPermissions) : ({} as FieldPermissions)
const clientProps: ClientComponentProps = { const clientProps: ClientComponentProps & Partial<FieldPaths> = {
customComponents: fieldState?.customComponents || {}, customComponents: fieldState?.customComponents || {},
field: clientField, field: clientField,
indexPath,
parentPath,
parentSchemaPath,
path, path,
readOnly: permissions?.[operation]?.permission === false, readOnly: permissions?.[operation]?.permission === false,
schemaPath, schemaPath,
} }
// fields with subfields
if (['array', 'blocks', 'collapsible', 'group', 'row', 'tabs'].includes(fieldConfig.type)) {
clientProps.indexPath = indexPath
clientProps.parentPath = parentPath
clientProps.parentSchemaPath = parentSchemaPath
}
const serverProps: ServerComponentProps = { const serverProps: ServerComponentProps = {
clientField, clientField,
data, data,

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { ClientFieldProps } from 'payload' import type { ClientFieldProps, FieldPaths } from 'payload'
import type { MarkOptional } from 'ts-essentials' import type { MarkOptional } from 'ts-essentials'
import React from 'react' import React from 'react'
@@ -7,10 +7,7 @@ import React from 'react'
import { WatchCondition } from './WatchCondition.js' import { WatchCondition } from './WatchCondition.js'
export const withCondition = < export const withCondition = <
P extends MarkOptional< P extends MarkOptional<FieldPaths & Pick<ClientFieldProps, 'field'>, 'indexPath' | 'path'>,
Pick<ClientFieldProps, 'field' | 'indexPath' | 'path'>,
'indexPath' | 'path'
>,
>( >(
Field: React.ComponentType<P>, Field: React.ComponentType<P>,
): React.FC<P> => { ): React.FC<P> => {

View File

@@ -1,22 +0,0 @@
'use client'
import React, { createContext, useContext } from 'react'
import type { FieldTypesComponents } from '../../fields/index.js'
export type IFieldComponentsContext = FieldTypesComponents
const FieldComponentsContext = createContext<IFieldComponentsContext>(null)
export const FieldComponentsProvider: React.FC<{
readonly children: React.ReactNode
readonly fieldComponents: FieldTypesComponents
}> = ({ children, fieldComponents }) => {
return (
<FieldComponentsContext.Provider value={fieldComponents}>
{children}
</FieldComponentsContext.Provider>
)
}
export const useFieldComponents = (): IFieldComponentsContext => useContext(FieldComponentsContext)

View File

@@ -18,13 +18,11 @@ import { LoadingOverlayProvider } from '../../elements/LoadingOverlay/index.js'
import { NavProvider } from '../../elements/Nav/context.js' import { NavProvider } from '../../elements/Nav/context.js'
import { StayLoggedInModal } from '../../elements/StayLoggedIn/index.js' import { StayLoggedInModal } from '../../elements/StayLoggedIn/index.js'
import { StepNavProvider } from '../../elements/StepNav/index.js' import { StepNavProvider } from '../../elements/StepNav/index.js'
import { fieldComponents } from '../../fields/index.js'
import { WindowInfoProvider } from '../../providers/WindowInfo/index.js' import { WindowInfoProvider } from '../../providers/WindowInfo/index.js'
import { AuthProvider } from '../Auth/index.js' import { AuthProvider } from '../Auth/index.js'
import { ClientFunctionProvider } from '../ClientFunction/index.js' import { ClientFunctionProvider } from '../ClientFunction/index.js'
import { ConfigProvider } from '../Config/index.js' import { ConfigProvider } from '../Config/index.js'
import { DocumentEventsProvider } from '../DocumentEvents/index.js' import { DocumentEventsProvider } from '../DocumentEvents/index.js'
import { FieldComponentsProvider } from '../FieldComponents/index.js'
import { LocaleProvider } from '../Locale/index.js' import { LocaleProvider } from '../Locale/index.js'
import { ParamsProvider } from '../Params/index.js' import { ParamsProvider } from '../Params/index.js'
import { PreferencesProvider } from '../Preferences/index.js' import { PreferencesProvider } from '../Preferences/index.js'
@@ -74,55 +72,53 @@ export const RootProvider: React.FC<Props> = ({
<ServerFunctionsProvider serverFunction={serverFunction}> <ServerFunctionsProvider serverFunction={serverFunction}>
<RouteCacheComponent> <RouteCacheComponent>
<ConfigProvider config={config}> <ConfigProvider config={config}>
<FieldComponentsProvider fieldComponents={fieldComponents}> <ClientFunctionProvider>
<ClientFunctionProvider> <TranslationProvider
<TranslationProvider dateFNSKey={dateFNSKey}
dateFNSKey={dateFNSKey} fallbackLang={fallbackLang}
fallbackLang={fallbackLang} language={languageCode}
language={languageCode} languageOptions={languageOptions}
languageOptions={languageOptions} switchLanguageServerAction={switchLanguageServerAction}
switchLanguageServerAction={switchLanguageServerAction} translations={translations}
translations={translations} >
<WindowInfoProvider
breakpoints={{
l: '(max-width: 1440px)',
m: '(max-width: 1024px)',
s: '(max-width: 768px)',
xs: '(max-width: 400px)',
}}
> >
<WindowInfoProvider <ScrollInfoProvider>
breakpoints={{ <SearchParamsProvider>
l: '(max-width: 1440px)', <ModalProvider classPrefix="payload" transTime={0} zIndex="var(--z-modal)">
m: '(max-width: 1024px)', <AuthProvider permissions={permissions} user={user}>
s: '(max-width: 768px)', <PreferencesProvider>
xs: '(max-width: 400px)', <ThemeProvider theme={theme}>
}} <ParamsProvider>
> <LocaleProvider>
<ScrollInfoProvider> <StepNavProvider>
<SearchParamsProvider> <LoadingOverlayProvider>
<ModalProvider classPrefix="payload" transTime={0} zIndex="var(--z-modal)"> <DocumentEventsProvider>
<AuthProvider permissions={permissions} user={user}> <NavProvider initialIsOpen={isNavOpen}>
<PreferencesProvider> {children}
<ThemeProvider theme={theme}> </NavProvider>
<ParamsProvider> </DocumentEventsProvider>
<LocaleProvider> </LoadingOverlayProvider>
<StepNavProvider> </StepNavProvider>
<LoadingOverlayProvider> </LocaleProvider>
<DocumentEventsProvider> </ParamsProvider>
<NavProvider initialIsOpen={isNavOpen}> </ThemeProvider>
{children} </PreferencesProvider>
</NavProvider> <ModalContainer />
</DocumentEventsProvider> <StayLoggedInModal />
</LoadingOverlayProvider> </AuthProvider>
</StepNavProvider> </ModalProvider>
</LocaleProvider> </SearchParamsProvider>
</ParamsProvider> </ScrollInfoProvider>
</ThemeProvider> </WindowInfoProvider>
</PreferencesProvider> </TranslationProvider>
<ModalContainer /> </ClientFunctionProvider>
<StayLoggedInModal />
</AuthProvider>
</ModalProvider>
</SearchParamsProvider>
</ScrollInfoProvider>
</WindowInfoProvider>
</TranslationProvider>
</ClientFunctionProvider>
</FieldComponentsProvider>
</ConfigProvider> </ConfigProvider>
</RouteCacheComponent> </RouteCacheComponent>
</ServerFunctionsProvider> </ServerFunctionsProvider>

View File

@@ -215,9 +215,6 @@ export const Auth: React.FC<Props> = (props) => {
admin: { disabled, readOnly: enableAPIKeyReadOnly }, admin: { disabled, readOnly: enableAPIKeyReadOnly },
label: t('authentication:enableAPIKey'), label: t('authentication:enableAPIKey'),
}} }}
indexPath=""
parentPath=""
parentSchemaPath=""
path="enableAPIKey" path="enableAPIKey"
schemaPath={`${collectionSlug}.enableAPIKey`} schemaPath={`${collectionSlug}.enableAPIKey`}
/> />
@@ -232,9 +229,6 @@ export const Auth: React.FC<Props> = (props) => {
admin: { disabled, readOnly }, admin: { disabled, readOnly },
label: t('authentication:verified'), label: t('authentication:verified'),
}} }}
indexPath=""
parentPath=""
parentSchemaPath=""
path="_verified" path="_verified"
schemaPath={`${collectionSlug}._verified`} schemaPath={`${collectionSlug}._verified`}
/> />

View File

@@ -4,22 +4,6 @@ import type { TextFieldClientComponent } from 'payload'
import { TextField } from '@payloadcms/ui' import { TextField } from '@payloadcms/ui'
import React from 'react' import React from 'react'
export const MyClientFieldComponent: TextFieldClientComponent = ({ export const MyClientFieldComponent: TextFieldClientComponent = (props) => {
field, return <TextField {...props} />
indexPath,
parentPath,
parentSchemaPath,
path,
schemaPath,
}) => {
return (
<TextField
field={field}
indexPath={indexPath}
parentPath={parentPath}
parentSchemaPath={parentSchemaPath}
path={path}
schemaPath={schemaPath}
/>
)
} }

View File

@@ -47,6 +47,7 @@ const CustomPassword: React.FC = () => {
label: 'Password', label: 'Password',
required: true, required: true,
}} }}
path="password"
validate={(value) => { validate={(value) => {
if (value && confirmValue) { if (value && confirmValue) {
return confirmValue === value ? true : 'Passwords must match!!!!' return confirmValue === value ? true : 'Passwords must match!!!!'