@sanity/assist
Version:
You create the instructions; Sanity AI Assist does the rest.
135 lines (121 loc) • 4.49 kB
text/typescript
/* eslint-disable max-depth */
import {
type ArrayOfObjectsFormNode,
type ArrayOfObjectsItemMember,
type ArrayOfPrimitivesFormNode,
type DocumentFormNode,
type FieldsetState,
isObjectSchemaType,
type ObjectFormNode,
type Path,
pathToString,
type SchemaType,
} from 'sanity'
const DEFAULT_MAX_DEPTH = 8
const ABSOLUTE_MAX_DEPTH = 50
export interface ConditionalMemberState {
path: string
hidden: boolean
readOnly: boolean
}
interface ConditionalMemberInnerState extends ConditionalMemberState {
conditional: boolean
}
/**
* This is used to statically determine the state of the functions on the server-side.
* Paths which has a schema with conditional config should be considered hidden: true and/or readOnly: true
* Only conditional paths are included, as static props can be determined from schema.
*
* Returns paths that has conditional hidden or readOnly schema config (function) and that.
* Form-state does not contain hidden members.
*
* Note:
* * If a parent path is hidden, no child paths are included
* * If a parent path is readOnly, no child paths are included
* * If a path is hidden, it is not included; only conditionally visible paths will be returned, with hidden: false
*/
export function getConditionalMembers(
docState: DocumentFormNode,
maxDepth = DEFAULT_MAX_DEPTH,
): ConditionalMemberState[] {
const doc: ConditionalMemberInnerState = {
path: '',
hidden: false,
readOnly: !!docState.readOnly,
conditional: isConditional(docState.schemaType),
}
return (
[doc, ...extractConditionalPaths(docState, Math.min(maxDepth, ABSOLUTE_MAX_DEPTH))]
.filter((v) => v.conditional)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({conditional, ...state}) => ({...state}))
)
}
function isConditional(schemaType: SchemaType) {
return typeof schemaType.hidden === 'function' || typeof schemaType.readOnly === 'function'
}
function conditionalState(memberState: {
path: Path
schemaType: SchemaType
readOnly?: boolean
}): ConditionalMemberInnerState {
return {
path: pathToString(memberState.path),
readOnly: !!memberState.readOnly,
hidden: false, // if its in members, its not hidden
conditional: isConditional(memberState.schemaType),
}
}
function extractConditionalPaths(
node: ObjectFormNode | FieldsetState,
maxDepth: number,
): ConditionalMemberInnerState[] {
if (node.path.length >= maxDepth) {
return []
}
return node.members.reduce<ConditionalMemberInnerState[]>((acc, member) => {
if (member.kind === 'error') {
return acc
}
if (member.kind === 'field') {
const schemaType = member.field.schemaType
if (schemaType.jsonType === 'object') {
const innerFields = member.field.readOnly
? []
: extractConditionalPaths(member.field as ObjectFormNode, maxDepth)
return [...acc, conditionalState(member.field), ...innerFields]
} else if (schemaType.jsonType === 'array') {
const array = member.field as ArrayOfObjectsFormNode | ArrayOfPrimitivesFormNode
let arrayPaths: ConditionalMemberInnerState[] = []
const isObjectsArray = array.members.some(
(m) => m.kind === 'item' && isObjectSchemaType(m.item.schemaType),
)
if (!array.readOnly) {
for (const arrayMember of array.members) {
if (arrayMember.kind === 'error') {
continue
}
const innerFields =
isObjectsArray && !arrayMember.item.readOnly
? extractConditionalPaths((arrayMember as ArrayOfObjectsItemMember).item, maxDepth)
: []
arrayPaths = [...arrayPaths, conditionalState(arrayMember.item), ...innerFields]
}
}
return [...acc, conditionalState(array), ...arrayPaths]
}
return [...acc, conditionalState(member.field)]
} else if (member.kind === 'fieldSet') {
const conditionalFieldset = !!(node as ObjectFormNode).schemaType?.fieldsets?.some(
(f) => !f.single && f.name === member.fieldSet.name && typeof f.hidden === 'function',
)
const innerFields = extractConditionalPaths(member.fieldSet, maxDepth).map((f) => ({
...f,
// if fieldset is conditional, visible fields must also be considered conditional
conditional: conditionalFieldset ?? f.conditional,
}))
return [...acc, ...innerFields]
}
return acc
}, [])
}