Skip to content

Commit

Permalink
chore(form): simplify and optimize conditional callback resolution a …
Browse files Browse the repository at this point in the history
…little bit
  • Loading branch information
bjoerge authored and hermanwikner committed Aug 11, 2022
1 parent fb78bb7 commit 6741dab
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 110 deletions.
4 changes: 3 additions & 1 deletion packages/@sanity/types/src/schema/asserters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export function isArraySchemaType(type: unknown): type is ArraySchemaType {
return type.jsonType === 'array'
}

export function isArrayOfObjectsSchemaType(type: unknown): type is ArraySchemaType {
export function isArrayOfObjectsSchemaType(
type: unknown
): type is ArraySchemaType<ObjectSchemaType> {
return isArraySchemaType(type) && type.of.every((memberType) => isObjectSchemaType(memberType))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable no-nested-ternary */
import {ConditionalProperty, CurrentUser} from '@sanity/types'

export interface ConditionalPropertyCallbackContext {
parent?: unknown
document?: Record<string, unknown>
currentUser: Omit<CurrentUser, 'role'> | null
value: unknown
}

export function callConditionalProperty(
property: ConditionalProperty,
context: ConditionalPropertyCallbackContext
) {
const {currentUser, document, parent, value} = context

if (typeof property === 'boolean' || property === undefined) {
return Boolean(property)
}

return (
property({
document: document as any,
parent,
value,
currentUser,
}) === true // note: we can't strictly "trust" the return value here, so the conditional property should probably be typed as unknown
)
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './resolveConditionalProperties'
export * from './callConditionalProperty'

This file was deleted.

98 changes: 47 additions & 51 deletions packages/sanity/src/form/store/formState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {FIXME} from '../types'
import {FormFieldPresence} from '../../presence'
import {isRecord} from '../../util'
import {PrimitiveFormNode, StateTree} from './types'
import {callConditionalProperties, callConditionalProperty} from './conditional-property'
import {callConditionalProperty} from './conditional-property'
import {MAX_FIELD_DEPTH} from './constants'
import {getItemType, getPrimitiveItemType} from './utils/getItemType'
import {
Expand Down Expand Up @@ -266,16 +266,14 @@ function prepareFieldMember(props: {
const scopedCollapsedPaths = parent.collapsedPaths?.children?.[field.name]
const scopedCollapsedFieldSets = parent.collapsedFieldSets?.children?.[field.name]

const {readOnly} = callConditionalProperties(
field.type,
{
const readOnly =
parent.readOnly ||
callConditionalProperty(field.type.readOnly, {
value: fieldValue,
parent: parent.value,
document: parent.document,
currentUser: parent.currentUser,
},
['readOnly']
)
})

const fieldState = prepareArrayOfObjectsInputState({
schemaType: field.type,
Expand All @@ -294,7 +292,7 @@ function prepareFieldMember(props: {
collapsedFieldSets: scopedCollapsedFieldSets,
level: fieldLevel,
path: fieldPath,
readOnly: props.parent.readOnly || readOnly,
readOnly,
})

if (fieldState === null) {
Expand Down Expand Up @@ -336,16 +334,14 @@ function prepareFieldMember(props: {
const scopedCollapsedPaths = parent.collapsedPaths?.children?.[field.name]
const scopedCollapsedFieldSets = parent.collapsedFieldSets?.children?.[field.name]

const {readOnly} = callConditionalProperties(
field.type,
{
const readOnly =
parent.readOnly ||
callConditionalProperty(field.type.readOnly, {
value: fieldValue,
parent: parent.value,
document: parent.document,
currentUser: parent.currentUser,
},
['readOnly']
)
})

const fieldState = prepareArrayOfPrimitivesInputState({
changed: isChangedValue(fieldValue, fieldComparisonValue),
Expand All @@ -364,7 +360,7 @@ function prepareFieldMember(props: {
collapsedFieldSets: scopedCollapsedFieldSets,
level: fieldLevel,
path: fieldPath,
readOnly: parent.readOnly || readOnly,
readOnly,
})

if (fieldState === null) {
Expand Down Expand Up @@ -393,29 +389,31 @@ function prepareFieldMember(props: {
const fieldComparisonValue = isRecord(parentComparisonValue)
? parentComparisonValue?.[field.name]
: undefined

const conditionalFieldContext = {
value: fieldValue,
parent: parent.value,
document: parent.document,
currentUser: parent.currentUser,
}

// note: we *only* want to call the conditional props here, as it's handled by the prepare<Object|Array>InputProps otherwise
const fieldConditionalProps = callConditionalProperties(
field.type,
{
value: fieldValue,
parent: parent.value,
document: parent.document,
currentUser: parent.currentUser,
},
['hidden', 'readOnly']
)
const hidden = callConditionalProperty(field.type.hidden, conditionalFieldContext)

if (fieldConditionalProps.hidden) {
if (hidden) {
return null
}

const readOnly =
parent.readOnly || callConditionalProperty(field.type.hidden, conditionalFieldContext)

const fieldState = preparePrimitiveInputState({
...parent,
comparisonValue: fieldComparisonValue,
value: fieldValue as boolean | string | number | undefined,
schemaType: field.type as PrimitiveSchemaType,
path: fieldPath,
readOnly: parent.readOnly || fieldConditionalProps.readOnly,
readOnly,
})

return {
Expand Down Expand Up @@ -479,16 +477,16 @@ function prepareObjectInputState<T>(
currentUser: props.currentUser,
}

const {hidden, readOnly} = callConditionalProperties(
props.schemaType,
conditionalFieldContext,
enableHiddenCheck ? ['hidden', 'readOnly'] : ['readOnly']
)
const hidden =
enableHiddenCheck && callConditionalProperty(props.schemaType.hidden, conditionalFieldContext)

if (hidden && enableHiddenCheck) {
if (hidden) {
return null
}

const readOnly =
props.readOnly || callConditionalProperty(props.schemaType.readOnly, conditionalFieldContext)

const schemaTypeGroupConfig = props.schemaType.groups || []
const defaultGroupName = (schemaTypeGroupConfig.find((g) => g.default) || ALL_FIELDS_GROUP)?.name

Expand All @@ -511,7 +509,7 @@ function prepareObjectInputState<T>(
const parentProps: RawState<ObjectSchemaType, unknown> = {
...props,
hidden,
readOnly: props.readOnly || readOnly,
readOnly,
}

// note: this is needed because not all object types gets a ´fieldsets´ property during schema parsing.
Expand Down Expand Up @@ -638,15 +636,16 @@ function prepareArrayOfPrimitivesInputState<T extends (boolean | string | number
document: props.document,
currentUser: props.currentUser,
}
const {hidden, readOnly} = callConditionalProperties(props.schemaType, conditionalFieldContext, [
'hidden',
'readOnly',
])

const hidden = callConditionalProperty(props.schemaType.hidden, conditionalFieldContext)

if (hidden) {
return null
}

const readOnly =
props.readOnly || callConditionalProperty(props.schemaType.readOnly, conditionalFieldContext)

// Todo: improve error handling at the parent level so that the value here is either undefined or an array
const items = Array.isArray(props.value) ? props.value : []

Expand All @@ -660,7 +659,7 @@ function prepareArrayOfPrimitivesInputState<T extends (boolean | string | number
return {
changed: members.some((m) => m.kind === 'item' && m.item.changed), // TODO: is this correct? There could be field and fieldsets here?
value: props.value as T,
readOnly: props.readOnly || readOnly,
readOnly,
schemaType: props.schemaType,
focused: isEqual(props.path, props.focusPath),
focusPath: trimChildPath(props.path, props.focusPath),
Expand All @@ -686,14 +685,13 @@ function prepareArrayOfObjectsInputState<T extends {_key: string}[]>(
document: props.document,
currentUser: props.currentUser,
}
const {hidden, readOnly} = callConditionalProperties(props.schemaType, conditionalFieldContext, [
'hidden',
'readOnly',
])
const hidden = callConditionalProperty(props.schemaType.hidden, conditionalFieldContext)

if (hidden) {
return null
}
const readOnly =
props.readOnly || callConditionalProperty(props.schemaType.readOnly, conditionalFieldContext)

// Todo: improve error handling at the parent level so that the value here is either undefined or an array
const items = Array.isArray(props.value) ? props.value : []
Expand All @@ -714,7 +712,7 @@ function prepareArrayOfObjectsInputState<T extends {_key: string}[]>(
return {
changed: members.some((m) => m.kind === 'item' && m.item.changed),
value: props.value as T,
readOnly: props.readOnly || readOnly,
readOnly,
schemaType: props.schemaType,
focused: isEqual(props.path, props.focusPath),
focusPath: trimChildPath(props.path, props.focusPath),
Expand Down Expand Up @@ -835,16 +833,14 @@ function prepareArrayOfPrimitivesMember(props: {
}
}

const {readOnly} = callConditionalProperties(
itemType,
{
const readOnly =
parent.readOnly ||
callConditionalProperty(itemType.readOnly, {
value: itemValue,
parent: parent.value,
document: parent.document,
currentUser: parent.currentUser,
},
['readOnly']
)
})

const item = preparePrimitiveInputState({
...parent,
Expand All @@ -853,7 +849,7 @@ function prepareArrayOfPrimitivesMember(props: {
level: itemLevel,
value: itemValue,
comparisonValue: itemComparisonValue,
readOnly: parent.readOnly || readOnly,
readOnly,
})

return {
Expand Down

0 comments on commit 6741dab

Please sign in to comment.