sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
166 lines (142 loc) • 5.34 kB
text/typescript
import {isKeySegment, type Path} from '@sanity/types'
import {castArray} from 'lodash'
import {isMemberArrayOfObjects, isMemberObject} from '../../members/object/fields/asserters'
import {ALL_FIELDS_GROUP} from '../constants'
import {
type ArrayOfObjectsFormNode,
type ArrayOfObjectsItemMember,
type BaseFormNode,
type FieldMember,
type FieldSetMember,
type ObjectFormNode,
type ObjectMember,
} from '../types'
import {isArrayOfObjectsFormNode, isObjectFormNode} from '../types/asserters'
/** @internal */
export interface ExpandPathOperation {
type: 'expandPath'
path: Path
}
/** @internal */
export interface ExpandFieldSetOperation {
type: 'expandFieldSet'
path: Path
}
/** @internal */
export interface SetActiveGroupOperation {
type: 'setSelectedGroup'
path: Path
groupName: string
}
/** @internal */
export type ExpandOperation =
| ExpandPathOperation
| ExpandFieldSetOperation
| SetActiveGroupOperation
function hasAllMembers<T extends BaseFormNode>(value: T): value is HasAllMembers<T> {
return '_allMembers' in value && Array.isArray(value._allMembers)
}
/**
* This takes a form state and returns a list of operations required to open a node at a particular path
* @param node - The base form node (i.e. the form state node for the _document_)
* @param path - The path to open
*
* @internal
*/
export function getExpandOperations(node: BaseFormNode, path: Path): ExpandOperation[] {
return [
// make sure to expand all intermediate paths
...path.map((p, i): ExpandPathOperation => ({type: 'expandPath', path: path.slice(0, i + 1)})),
// make sure to expand all fieldsets and selects the groups that includes the intermediate nodes
...getFieldsetAndFieldGroupOperations(node, path),
]
}
function getFieldsetAndFieldGroupOperations(node: BaseFormNode, path: Path) {
if (path.length === 0) {
return []
}
if (isObjectFormNode(node) && hasAllMembers(node)) {
return getObjectFieldsetAndFieldGroupOperations(node, path)
}
if (isArrayOfObjectsFormNode(node)) {
return getArrayFieldsetAndFieldGroupOperations(node, path)
}
return []
}
type HasAllMembers<T> = T & {_allMembers: ObjectMember[]}
function getObjectFieldsetAndFieldGroupOperations(
node: HasAllMembers<ObjectFormNode>,
path: Path,
): (ExpandFieldSetOperation | SetActiveGroupOperation)[] {
if (path.length === 0) {
return []
}
// extract the field name for the current level we're looking at
const [fieldName, ...tail] = path
const fieldsetMember = node._allMembers.find(
(member): member is FieldSetMember =>
member.kind === 'fieldSet' &&
member.fieldSet.members.some(
(field): field is FieldMember => field.kind === 'field' && field.name === fieldName,
),
)
// if we found the field in a fieldset we need to recurse into this fieldset's members, otherwise we can use the node's members
const members = fieldsetMember
? fieldsetMember.fieldSet.members
: // Note: we need to use the internal `_allMembers` array here instead of members since hidden/collapsed members are omitted from members
node._allMembers
// look for the field inside the members array
const fieldMember = members.find(
(member): member is FieldMember =>
member !== null && member.kind === 'field' && member.name === fieldName,
)
// Group handling
const schemaField = node.schemaType.fields.find((field) => field.name === fieldName)
const selectedGroupName = node.groups.find((group) => group.selected)?.name
const defaultGroupName = (node.schemaType.groups || []).find((group) => group.default)?.name
const inSelectedGroup =
selectedGroupName &&
(selectedGroupName === ALL_FIELDS_GROUP.name ||
(schemaField && castArray(schemaField.group).includes(selectedGroupName)))
const ops: (ExpandFieldSetOperation | SetActiveGroupOperation)[] = []
if (!inSelectedGroup) {
ops.push({
type: 'setSelectedGroup',
path: node.path,
groupName: defaultGroupName || ALL_FIELDS_GROUP.name,
})
}
if (fieldsetMember) {
// the field is inside a fieldset, make sure we expand it too
ops.push({type: 'expandFieldSet', path: fieldsetMember.fieldSet.path})
}
if (fieldMember && hasAllMembers(fieldMember.field)) {
if (isMemberArrayOfObjects(fieldMember)) {
ops.push(...getArrayFieldsetAndFieldGroupOperations(fieldMember.field, tail))
} else if (isMemberObject(fieldMember)) {
ops.push(...getObjectFieldsetAndFieldGroupOperations(fieldMember.field, tail))
}
}
return ops
}
function getArrayFieldsetAndFieldGroupOperations(
state: ArrayOfObjectsFormNode,
path: Path,
): (ExpandFieldSetOperation | SetActiveGroupOperation)[] {
if (path.length === 0) {
return []
}
// start at the root and make sure all groups/paths are expanded/activated along the way
const [segment, ...rest] = path
if (!isKeySegment(segment)) {
throw new Error('Expected path segment to be an object with a _key property')
}
const foundMember = state.members.find(
(member): member is ArrayOfObjectsItemMember => member.key === segment._key,
)
if (!foundMember) {
// tried to open a member that does not exist in the form state - it's likely hidden
return []
}
return getFieldsetAndFieldGroupOperations(foundMember.item, rest)
}