fix(ui): executes filterOptions on the server (#5335)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ClientValidate, Field } from '../../fields/config/types.js'
|
||||
import type { Where } from '../../types/index.js'
|
||||
|
||||
export type Data = {
|
||||
[key: string]: any
|
||||
@@ -11,11 +12,16 @@ export type Row = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type FilterOptionsResult = {
|
||||
[relation: string]: Where | boolean
|
||||
}
|
||||
|
||||
export type FormField = {
|
||||
disableFormData?: boolean
|
||||
errorMessage?: string
|
||||
errorPaths?: Set<string>
|
||||
fieldSchema?: Field
|
||||
filterOptions?: FilterOptionsResult
|
||||
initialValue: unknown
|
||||
passesCondition?: boolean
|
||||
rows?: Row[]
|
||||
|
||||
@@ -23,9 +23,10 @@ export type {
|
||||
DescriptionComponent,
|
||||
DescriptionFunction,
|
||||
} from './forms/FieldDescription.js'
|
||||
export type { Data, FormField, FormState, Row } from './forms/Form.js'
|
||||
export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js'
|
||||
export type { LabelProps } from './forms/Label.js'
|
||||
export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js'
|
||||
|
||||
export type {
|
||||
AdminViewComponent,
|
||||
AdminViewProps,
|
||||
|
||||
@@ -32,7 +32,8 @@ export type ServerOnlyGlobalAdminProperties = keyof Pick<
|
||||
export type ServerOnlyLivePreviewProperties = keyof Pick<LivePreviewConfig, 'url'>
|
||||
|
||||
export type ServerOnlyFieldProperties =
|
||||
| 'editor'
|
||||
| 'editor' // This is a `richText` only property
|
||||
| 'filterOptions' // This is a `relationship` and `upload` only property
|
||||
| 'label'
|
||||
| keyof Pick<FieldBase, 'access' | 'defaultValue' | 'hooks' | 'validate'>
|
||||
|
||||
@@ -79,8 +80,8 @@ export const sanitizeField = (f: Field) => {
|
||||
'validate',
|
||||
'defaultValue',
|
||||
'label',
|
||||
// This is a `richText` only property
|
||||
'editor',
|
||||
'filterOptions', // This is a `relationship` and `upload` only property
|
||||
'editor', // This is a `richText` only property
|
||||
// `fields`
|
||||
// `blocks`
|
||||
// `tabs`
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { FilterOptions } from 'payload/types'
|
||||
|
||||
import equal from 'deep-equal'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js'
|
||||
|
||||
import { useAllFormFields } from '../../forms/Form/context.js'
|
||||
import getSiblingData from '../../forms/Form/getSiblingData.js'
|
||||
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues.js'
|
||||
import { getFilterOptionsQuery } from '../../forms/fields/getFilterOptionsQuery.js'
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
|
||||
type Args = {
|
||||
filterOptions: FilterOptions
|
||||
filterOptionsResult: FilterOptionsResult
|
||||
path: string
|
||||
relationTo: string | string[]
|
||||
setFilterOptionsResult: (optionFilters: FilterOptionsResult) => void
|
||||
}
|
||||
|
||||
export const GetFilterOptions = ({
|
||||
filterOptions,
|
||||
filterOptionsResult,
|
||||
path,
|
||||
relationTo,
|
||||
setFilterOptionsResult,
|
||||
}: Args): null => {
|
||||
const [fields] = useAllFormFields()
|
||||
const { id } = useDocumentInfo()
|
||||
const { user } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
const data = reduceFieldsToValues(fields, true)
|
||||
const siblingData = getSiblingData(fields, path)
|
||||
|
||||
const getFilterOptions = async () => {
|
||||
const newFilterOptionsResult = await getFilterOptionsQuery(filterOptions, {
|
||||
id,
|
||||
data,
|
||||
relationTo,
|
||||
siblingData,
|
||||
user,
|
||||
})
|
||||
|
||||
if (!equal(newFilterOptionsResult, filterOptionsResult)) {
|
||||
setFilterOptionsResult(newFilterOptionsResult)
|
||||
}
|
||||
}
|
||||
getFilterOptions()
|
||||
}, [
|
||||
fields,
|
||||
filterOptions,
|
||||
id,
|
||||
relationTo,
|
||||
user,
|
||||
path,
|
||||
filterOptionsResult,
|
||||
setFilterOptionsResult,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { FilterOptionsResult, SanitizedCollectionConfig } from 'payload/types'
|
||||
import type React from 'react'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js'
|
||||
|
||||
export type ListDrawerProps = {
|
||||
collectionSlugs: string[]
|
||||
customHeader?: React.ReactNode
|
||||
|
||||
@@ -21,6 +21,8 @@ export { RenderFields } from '../forms/RenderFields/index.js'
|
||||
export { useRowLabel } from '../forms/RowLabel/Context/index.js'
|
||||
export { default as FormSubmit } from '../forms/Submit/index.js'
|
||||
export { default as Submit } from '../forms/Submit/index.js'
|
||||
export { buildStateFromSchema } from '../forms/buildStateFromSchema/index.js'
|
||||
export type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js'
|
||||
export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js'
|
||||
export { CheckboxInput } from '../forms/fields/Checkbox/Input.js'
|
||||
export { default as Checkbox } from '../forms/fields/Checkbox/index.js'
|
||||
@@ -38,16 +40,14 @@ export { default as Text } from '../forms/fields/Text/index.js'
|
||||
export type { Props as TextFieldProps } from '../forms/fields/Text/types.js'
|
||||
export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js'
|
||||
export { default as Textarea } from '../forms/fields/Textarea/index.js'
|
||||
|
||||
export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js'
|
||||
|
||||
export { default as UploadField } from '../forms/fields/Upload/index.js'
|
||||
|
||||
export { fieldTypes } from '../forms/fields/index.js'
|
||||
export { fieldBaseClass } from '../forms/fields/shared.js'
|
||||
export { useField } from '../forms/useField/index.js'
|
||||
export type { FieldType, Options } from '../forms/useField/types.js'
|
||||
export { default as buildStateFromSchema } from '../forms/utilities/buildStateFromSchema/index.js'
|
||||
export type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js'
|
||||
export { withCondition } from '../forms/withCondition/index.js'
|
||||
|
||||
export { buildComponentMap } from '../utilities/buildComponentMap/index.js'
|
||||
|
||||
@@ -11,6 +11,7 @@ import ObjectIdImport from 'bson-objectid'
|
||||
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types'
|
||||
import { getDefaultValue } from 'payload/utilities'
|
||||
|
||||
import { getFilterOptionsQuery } from './getFilterOptionsQuery.js'
|
||||
import { iterateFields } from './iterateFields.js'
|
||||
|
||||
const ObjectId = (ObjectIdImport.default ||
|
||||
@@ -92,6 +93,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
const validate = operation === 'update' ? field.validate : undefined
|
||||
|
||||
const fieldState: FormField = {
|
||||
errorPaths: new Set(),
|
||||
fieldSchema: includeSchema ? field : undefined,
|
||||
@@ -375,6 +377,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
}
|
||||
|
||||
case 'relationship': {
|
||||
if (typeof field.filterOptions === 'function') {
|
||||
const query = await getFilterOptionsQuery(field.filterOptions, {
|
||||
id,
|
||||
data: fullData,
|
||||
relationTo: field.relationTo,
|
||||
siblingData: data,
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
fieldState.filterOptions = query
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
const relationshipValue = Array.isArray(valueWithDefault)
|
||||
? valueWithDefault.map((relationship) => {
|
||||
@@ -433,6 +447,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
}
|
||||
|
||||
case 'upload': {
|
||||
if (typeof field.filterOptions === 'function') {
|
||||
const query = await getFilterOptionsQuery(field.filterOptions, {
|
||||
id,
|
||||
data: fullData,
|
||||
relationTo: field.relationTo,
|
||||
siblingData: data,
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
fieldState.filterOptions = query
|
||||
}
|
||||
|
||||
const relationshipValue =
|
||||
valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault
|
||||
? valueWithDefault.id
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
import buildStateFromSchema from './index.js'
|
||||
|
||||
describe('Form - buildStateFromSchema', () => {
|
||||
@@ -5,8 +5,11 @@ export const getFilterOptionsQuery = async (
|
||||
options: Omit<FilterOptionsProps, 'relationTo'> & { relationTo: string | string[] },
|
||||
): Promise<{ [collection: string]: Where }> => {
|
||||
const { relationTo } = options
|
||||
|
||||
const relations = Array.isArray(relationTo) ? relationTo : [relationTo]
|
||||
|
||||
const query = {}
|
||||
|
||||
if (typeof filterOptions !== 'undefined') {
|
||||
await Promise.all(
|
||||
relations.map(async (relation) => {
|
||||
@@ -14,9 +17,11 @@ export const getFilterOptionsQuery = async (
|
||||
typeof filterOptions === 'function'
|
||||
? await filterOptions({ ...options, relationTo: relation })
|
||||
: filterOptions
|
||||
|
||||
if (query[relation] === true) {
|
||||
query[relation] = {}
|
||||
}
|
||||
|
||||
// this is an ugly way to prevent results from being returned
|
||||
if (query[relation] === false) {
|
||||
query[relation] = { id: { exists: false } }
|
||||
@@ -24,5 +29,6 @@ export const getFilterOptionsQuery = async (
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export type BuildFormStateArgs = {
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
export const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
const { id, data: fullData = {}, fieldSchema, operation, preferences, req } = args
|
||||
|
||||
if (fieldSchema) {
|
||||
@@ -49,5 +49,3 @@ const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export default buildStateFromSchema
|
||||
@@ -7,9 +7,8 @@ import qs from 'qs'
|
||||
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||
|
||||
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
||||
import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types.js'
|
||||
import type { GetResults, Option, Props, Value } from './types.js'
|
||||
|
||||
import { GetFilterOptions } from '../../../elements/GetFilterOptions/index.js'
|
||||
import ReactSelect from '../../../elements/ReactSelect/index.js'
|
||||
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js'
|
||||
import { useAuth } from '../../../providers/Auth/index.js'
|
||||
@@ -57,7 +56,6 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
|
||||
const relationTo = 'relationTo' in props ? props?.relationTo : undefined
|
||||
const hasMany = 'hasMany' in props ? props?.hasMany : undefined
|
||||
const filterOptions = 'filterOptions' in props ? props?.filterOptions : undefined
|
||||
const sortOptions = 'sortOptions' in props ? props?.sortOptions : undefined
|
||||
const isSortable = 'isSortable' in props ? props?.isSortable : true
|
||||
const allowCreate = 'allowCreate' in props ? props?.allowCreate : true
|
||||
@@ -71,7 +69,6 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1)
|
||||
const [lastLoadedPage, setLastLoadedPage] = useState<Record<string, number>>({})
|
||||
const [errorLoading, setErrorLoading] = useState('')
|
||||
const [filterOptionsResult, setFilterOptionsResult] = useState<FilterOptionsResult>()
|
||||
const [search, setSearch] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [hasLoadedFirstPage, setHasLoadedFirstPage] = useState(false)
|
||||
@@ -87,7 +84,9 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { initialValue, path, setValue, showError, value } = useField<Value | Value[]>({
|
||||
const { filterOptions, initialValue, path, setValue, showError, value } = useField<
|
||||
Value | Value[]
|
||||
>({
|
||||
path: pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
@@ -123,7 +122,8 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
|
||||
if (!errorLoading) {
|
||||
await relationsToFetch.reduce(async (priorRelation, relation) => {
|
||||
const relationFilterOption = filterOptionsResult?.[relation]
|
||||
const relationFilterOption = filterOptions?.[relation]
|
||||
|
||||
let lastLoadedPageToUse
|
||||
if (search !== searchArg) {
|
||||
lastLoadedPageToUse = 1
|
||||
@@ -246,7 +246,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
lastLoadedPage,
|
||||
collections,
|
||||
locale,
|
||||
filterOptionsResult,
|
||||
filterOptions,
|
||||
serverURL,
|
||||
sortOptions,
|
||||
api,
|
||||
@@ -362,7 +362,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
setEnableWordBoundarySearch(!isIdOnly)
|
||||
}, [relationTo, collections])
|
||||
|
||||
// When (`relationTo` || `filterOptionsResult` || `locale`) changes, reset component
|
||||
// When (`relationTo` || `filterOptions` || `locale`) changes, reset component
|
||||
// Note - effect should not run on first run
|
||||
useEffect(() => {
|
||||
if (firstRun.current) {
|
||||
@@ -374,7 +374,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
setLastFullyLoadedRelation(-1)
|
||||
setLastLoadedPage({})
|
||||
setHasLoadedFirstPage(false)
|
||||
}, [relationTo, filterOptionsResult, locale])
|
||||
}, [relationTo, filterOptions, locale])
|
||||
|
||||
const onSave = useCallback<DocumentDrawerProps['onSave']>(
|
||||
(args) => {
|
||||
@@ -435,15 +435,6 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
>
|
||||
{Error}
|
||||
{Label}
|
||||
<GetFilterOptions
|
||||
{...{
|
||||
filterOptions,
|
||||
filterOptionsResult,
|
||||
path,
|
||||
relationTo,
|
||||
setFilterOptionsResult,
|
||||
}}
|
||||
/>
|
||||
{!errorLoading && (
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<ReactSelect
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import type { Where } from 'payload/types'
|
||||
|
||||
import type { FormFieldBase } from '../shared.js'
|
||||
|
||||
@@ -59,7 +58,3 @@ export type GetResults = (args: {
|
||||
sort?: boolean
|
||||
value?: Value | Value[]
|
||||
}) => Promise<void>
|
||||
|
||||
export type FilterOptionsResult = {
|
||||
[relation: string]: Where | boolean
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import type { SanitizedCollectionConfig, UploadField } from 'payload/types'
|
||||
import type { FilterOptionsResult, SanitizedCollectionConfig, UploadField } from 'payload/types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
||||
import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js'
|
||||
import type { FilterOptionsResult } from '../Relationship/types.js'
|
||||
|
||||
import { Button } from '../../../elements/Button/index.js'
|
||||
import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js'
|
||||
@@ -23,7 +22,7 @@ const baseClass = 'upload'
|
||||
export type UploadInputProps = FormFieldBase & {
|
||||
api?: string
|
||||
collection?: SanitizedCollectionConfig
|
||||
filterOptions?: UploadField['filterOptions']
|
||||
filterOptions?: FilterOptionsResult
|
||||
onChange?: (e) => void
|
||||
relationTo?: UploadField['relationTo']
|
||||
serverURL?: string
|
||||
@@ -34,12 +33,12 @@ export type UploadInputProps = FormFieldBase & {
|
||||
export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
const {
|
||||
Description,
|
||||
|
||||
Error,
|
||||
Label: LabelFromProps,
|
||||
api = '/api',
|
||||
className,
|
||||
collection,
|
||||
filterOptions,
|
||||
label,
|
||||
onChange,
|
||||
readOnly,
|
||||
@@ -59,7 +58,6 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
const [file, setFile] = useState(undefined)
|
||||
const [missingFile, setMissingFile] = useState(false)
|
||||
const [collectionSlugs] = useState([collection?.slug])
|
||||
const [filterOptionsResult, setFilterOptionsResult] = useState<FilterOptionsResult>()
|
||||
|
||||
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
||||
collectionSlug: collectionSlugs[0],
|
||||
@@ -67,7 +65,7 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
|
||||
const [ListDrawer, ListDrawerToggler, { closeDrawer: closeListDrawer }] = useListDrawer({
|
||||
collectionSlugs,
|
||||
filterOptions: filterOptionsResult,
|
||||
filterOptions,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -131,15 +129,6 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{/* <GetFilterOptions
|
||||
{...{
|
||||
filterOptions,
|
||||
filterOptionsResult,
|
||||
path,
|
||||
relationTo,
|
||||
setFilterOptionsResult,
|
||||
}}
|
||||
/> */}
|
||||
{Error}
|
||||
{Label}
|
||||
{collection?.upload && (
|
||||
|
||||
@@ -15,7 +15,6 @@ const Upload: React.FC<Props> = (props) => {
|
||||
Error,
|
||||
Label: LabelFromProps,
|
||||
className,
|
||||
filterOptions,
|
||||
label,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
@@ -45,7 +44,7 @@ const Upload: React.FC<Props> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path, setValue, showError, value } = useField<string>({
|
||||
const { filterOptions, path, setValue, showError, value } = useField<string>({
|
||||
path: pathFromProps,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FieldPermissions, User } from 'payload/auth'
|
||||
import type { User } from 'payload/auth'
|
||||
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
|
||||
import type {
|
||||
ArrayField,
|
||||
@@ -51,6 +51,21 @@ export type FormFieldBase = {
|
||||
validate?: Validate
|
||||
width?: string
|
||||
} & (
|
||||
| {
|
||||
// For `array` fields
|
||||
label?: RowLabel
|
||||
labels?: ArrayField['labels']
|
||||
maxRows?: ArrayField['maxRows']
|
||||
minRows?: ArrayField['minRows']
|
||||
}
|
||||
| {
|
||||
// For `blocks` fields
|
||||
blocks?: ReducedBlock[]
|
||||
labels?: BlockField['labels']
|
||||
maxRows?: BlockField['maxRows']
|
||||
minRows?: BlockField['minRows']
|
||||
slug?: string
|
||||
}
|
||||
| {
|
||||
// For `code` fields
|
||||
editorOptions?: CodeField['admin']['editorOptions']
|
||||
@@ -68,11 +83,25 @@ export type FormFieldBase = {
|
||||
// For `json` fields
|
||||
editorOptions?: JSONField['admin']['editorOptions']
|
||||
}
|
||||
| {
|
||||
// For `number` fields
|
||||
hasMany?: boolean
|
||||
max?: number
|
||||
maxRows?: number
|
||||
min?: number
|
||||
step?: number
|
||||
}
|
||||
| {
|
||||
// For `radio` fields
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
options?: Option[]
|
||||
}
|
||||
| {
|
||||
// For `relationship` fields
|
||||
allowCreate?: RelationshipField['admin']['allowCreate']
|
||||
relationTo?: RelationshipField['relationTo']
|
||||
sortOptions?: RelationshipField['admin']['sortOptions']
|
||||
}
|
||||
| {
|
||||
// For `richText` fields
|
||||
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
|
||||
@@ -90,36 +119,6 @@ export type FormFieldBase = {
|
||||
// For `upload` fields
|
||||
relationTo?: UploadField['relationTo']
|
||||
}
|
||||
| {
|
||||
allowCreate?: RelationshipField['admin']['allowCreate']
|
||||
filterOptions?: RelationshipField['filterOptions']
|
||||
// For `relationship` fields
|
||||
relationTo?: RelationshipField['relationTo']
|
||||
sortOptions?: RelationshipField['admin']['sortOptions']
|
||||
}
|
||||
| {
|
||||
blocks?: ReducedBlock[]
|
||||
labels?: BlockField['labels']
|
||||
maxRows?: BlockField['maxRows']
|
||||
minRows?: BlockField['minRows']
|
||||
// For `blocks` fields
|
||||
slug?: string
|
||||
}
|
||||
| {
|
||||
hasMany?: boolean
|
||||
max?: number
|
||||
maxRows?: number
|
||||
min?: number
|
||||
// For `number` fields
|
||||
step?: number
|
||||
}
|
||||
| {
|
||||
label?: RowLabel
|
||||
labels?: ArrayField['labels']
|
||||
maxRows?: ArrayField['maxRows']
|
||||
// For `array` fields
|
||||
minRows?: ArrayField['minRows']
|
||||
}
|
||||
| {
|
||||
tabs?: MappedTab[]
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
||||
|
||||
const { getData, getDataByPath, getSiblingData, setModified } = useForm()
|
||||
|
||||
const filterOptions = field?.filterOptions
|
||||
const value = field?.value as T
|
||||
const initialValue = field?.initialValue as T
|
||||
const valid = typeof field?.valid === 'boolean' ? field.valid : true
|
||||
@@ -79,6 +80,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
||||
const result: FieldType<T> = useMemo(
|
||||
() => ({
|
||||
errorMessage: field?.errorMessage,
|
||||
filterOptions,
|
||||
formProcessing: processing,
|
||||
formSubmitted: submitted,
|
||||
initialValue,
|
||||
@@ -106,6 +108,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
||||
schemaPath,
|
||||
readOnly,
|
||||
permissions,
|
||||
filterOptions,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClientValidate, FieldPermissions, Row } from 'payload/types'
|
||||
import type { ClientValidate, FieldPermissions, FilterOptionsResult, Row } from 'payload/types'
|
||||
|
||||
export type Options = {
|
||||
disableFormData?: boolean
|
||||
@@ -12,6 +12,7 @@ export type Options = {
|
||||
|
||||
export type FieldType<T> = {
|
||||
errorMessage?: string
|
||||
filterOptions?: FilterOptionsResult
|
||||
formProcessing: boolean
|
||||
formSubmitted: boolean
|
||||
initialValue?: T
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FormState, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js'
|
||||
import type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js'
|
||||
|
||||
export const getFormState = async (args: {
|
||||
apiRoute: SanitizedConfig['routes']['api']
|
||||
|
||||
@@ -21,6 +21,17 @@ export const PostsCollection: CollectionConfig = {
|
||||
{
|
||||
name: 'relationship',
|
||||
type: 'relationship',
|
||||
filterOptions: ({ id }) => {
|
||||
return {
|
||||
where: [
|
||||
{
|
||||
id: {
|
||||
not_equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
relationTo: ['posts'],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"./packages/graphql/src"
|
||||
],
|
||||
"@payload-config": [
|
||||
"./test/access-control/config.ts"
|
||||
"./test/_community/config.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user