@sanity/hierarchical-document-list 2.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -30
  3. package/dist/index.d.ts +170 -195
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +835 -6031
  6. package/dist/index.js.map +1 -1
  7. package/package.json +38 -77
  8. package/dist/index.d.mts +0 -240
  9. package/dist/index.mjs +0 -6433
  10. package/dist/index.mjs.map +0 -1
  11. package/sanity.json +0 -8
  12. package/src/TreeDeskStructure.tsx +0 -80
  13. package/src/TreeInputComponent.tsx +0 -41
  14. package/src/components/DeskWarning.tsx +0 -40
  15. package/src/components/DocumentInNode.tsx +0 -133
  16. package/src/components/DocumentPreviewStatus.tsx +0 -70
  17. package/src/components/NodeActions.tsx +0 -85
  18. package/src/components/NodeContentRenderer.tsx +0 -141
  19. package/src/components/PlaceholderDropzone.tsx +0 -45
  20. package/src/components/TreeEditor.tsx +0 -184
  21. package/src/components/TreeEditorErrorBoundary.tsx +0 -14
  22. package/src/components/TreeNodeRenderer.tsx +0 -37
  23. package/src/components/TreeNodeRendererScaffold.tsx +0 -193
  24. package/src/createDeskHierarchy.tsx +0 -110
  25. package/src/createHierarchicalSchemas.tsx +0 -151
  26. package/src/hooks/useAllItems.ts +0 -119
  27. package/src/hooks/useLocalTree.ts +0 -40
  28. package/src/hooks/useTreeOperations.ts +0 -25
  29. package/src/hooks/useTreeOperationsProvider.ts +0 -86
  30. package/src/index.ts +0 -25
  31. package/src/schemas/hierarchy.tree.ts +0 -19
  32. package/src/types.ts +0 -148
  33. package/src/utils/flatDataToTree.ts +0 -20
  34. package/src/utils/getAdjescentNodes.ts +0 -30
  35. package/src/utils/getCommonTreeProps.tsx +0 -28
  36. package/src/utils/getTreeHeight.ts +0 -8
  37. package/src/utils/gradientPatchAdapter.ts +0 -43
  38. package/src/utils/idUtils.ts +0 -7
  39. package/src/utils/injectNodeTypeInPatches.ts +0 -60
  40. package/src/utils/moveItemInArray.ts +0 -26
  41. package/src/utils/throwError.ts +0 -9
  42. package/src/utils/treeData.tsx +0 -119
  43. package/src/utils/treePatches.ts +0 -171
  44. package/v2-incompatible.js +0 -11
@@ -1,184 +0,0 @@
1
- import SortableTree, {FullTree, NodeData} from '@nosferatu500/react-sortable-tree'
2
- import {AddCircleIcon} from '@sanity/icons'
3
- import {Box, Button, Card, Flex, Spinner, Stack, Text, Tooltip} from '@sanity/ui'
4
- import * as React from 'react'
5
- import {useCallback, useMemo} from 'react'
6
- import {DndProvider} from 'react-dnd'
7
- import {HTML5Backend} from 'react-dnd-html5-backend'
8
- import {PatchEvent} from 'sanity'
9
- import useAllItems from '../hooks/useAllItems'
10
- import useLocalTree from '../hooks/useLocalTree'
11
- import {TreeOperationsContext} from '../hooks/useTreeOperations'
12
- import useTreeOperationsProvider from '../hooks/useTreeOperationsProvider'
13
- import {Optional, StoredTreeItem, TreeDeskStructureProps} from '../types'
14
- import getCommonTreeProps from '../utils/getCommonTreeProps'
15
- import getTreeHeight from '../utils/getTreeHeight'
16
- import {getUnaddedItems} from '../utils/treeData'
17
- import {HandleMovedNodeData} from '../utils/treePatches'
18
- import DocumentInNode from './DocumentInNode'
19
- import {TreeEditorErrorBoundary} from './TreeEditorErrorBoundary'
20
-
21
- /**
22
- * The loaded tree users interact with
23
- */
24
- const TreeEditor: React.FC<{
25
- tree: StoredTreeItem[]
26
- onChange: (patch: PatchEvent) => void
27
- options: Optional<TreeDeskStructureProps, 'documentId'>
28
- patchPrefix?: string
29
- }> = (props) => {
30
- const {status: allItemsStatus, allItems} = useAllItems(props.options)
31
- const unAddedItems = getUnaddedItems({tree: props.tree, allItems})
32
-
33
- const {localTree, handleVisibilityToggle} = useLocalTree({
34
- tree: props.tree,
35
- allItems
36
- })
37
-
38
- const operations = useTreeOperationsProvider({
39
- patchPrefix: props.patchPrefix,
40
- onChange: props.onChange,
41
- localTree
42
- })
43
-
44
- const [context, setContext] = React.useState<HTMLElement | null>(null)
45
- const [treeViewHeight, setTreeViewHeight] = React.useState<string>('')
46
-
47
- const updateTreeViewHeight = () => {
48
- const el = document.querySelector(`#${props.options.documentId} [data-known-size]`) as HTMLElement | null
49
- const rowHeight = Number(el?.dataset.knownSize || 51)
50
- setTreeViewHeight(getTreeHeight(localTree, rowHeight))
51
- }
52
-
53
- React.useEffect(() => {
54
- if (props.options.documentId) {
55
- setContext(document.getElementById(props.options.documentId))
56
- }
57
- }, [props.options.documentId])
58
-
59
- React.useEffect(() => {
60
- // Wait for dom to load before initial execution.
61
- setTimeout(updateTreeViewHeight)
62
- }, [])
63
-
64
- React.useEffect(() => {
65
- // Immediately update when changes are detected.
66
- updateTreeViewHeight()
67
- }, [props.options.documentId, localTree])
68
-
69
- const onMoveNode = useCallback(
70
- (data: NodeData & FullTree & any) =>
71
- operations.handleMovedNode(data as unknown as HandleMovedNodeData),
72
- [operations]
73
- )
74
-
75
- const treeProps = useMemo(
76
- () =>
77
- getCommonTreeProps({
78
- placeholder: {
79
- title: 'Add items from the list below'
80
- }
81
- }),
82
- []
83
- )
84
-
85
- const operationContext = useMemo(
86
- () => ({...operations, allItemsStatus}),
87
- [operations, allItemsStatus]
88
- )
89
-
90
- return (
91
- <TreeEditorErrorBoundary>
92
- {/*Use this Box-wrapper to get a context Element to prevent DndProvider to have to HTML% backend at the same time https://github.com/react-dnd/react-dnd/issues/186#issuecomment-978206387 */}
93
- <Box id={props.options.documentId}>
94
- {context ? (
95
- <DndProvider backend={HTML5Backend} options={{rootElement: context}}>
96
- <TreeOperationsContext.Provider value={operationContext}>
97
- <Stack space={4} paddingTop={4}>
98
- <Card
99
- style={{minHeight: treeViewHeight}}
100
- // Only include borderBottom if there's something to show in unadded items
101
- borderBottom={allItemsStatus !== 'success' || unAddedItems?.length > 0}
102
- >
103
- <SortableTree
104
- maxDepth={props.options.maxDepth}
105
- onChange={doNothingOnChange}
106
- onVisibilityToggle={handleVisibilityToggle}
107
- canDrop={canDrop}
108
- onMoveNode={onMoveNode}
109
- treeData={localTree}
110
- {...treeProps}
111
- />
112
- </Card>
113
-
114
- {allItemsStatus === 'success' && unAddedItems?.length > 0 && (
115
- <Stack space={1} paddingX={2} paddingTop={3}>
116
- <Stack space={2} paddingX={2} paddingBottom={3}>
117
- <Text size={2} as="h2" weight="semibold">
118
- Add more items
119
- </Text>
120
- <Text size={1} muted>
121
- Only published documents are shown.
122
- </Text>
123
- </Stack>
124
- {unAddedItems.map((item) => (
125
- <DocumentInNode
126
- key={item.publishedId || item.draftId}
127
- item={item}
128
- action={
129
- <Tooltip
130
- portal
131
- placement="left"
132
- content={
133
- <Box padding={2}>
134
- <Text size={1}>Add to list</Text>
135
- </Box>
136
- }
137
- >
138
- <Button
139
- onClick={() => {
140
- operations.addItem(item)
141
- }}
142
- mode="bleed"
143
- icon={AddCircleIcon}
144
- style={{cursor: 'pointer'}}
145
- />
146
- </Tooltip>
147
- }
148
- />
149
- ))}
150
- </Stack>
151
- )}
152
- {allItemsStatus === 'loading' && (
153
- <Flex padding={4} align={'center'} justify={'center'}>
154
- <Spinner size={3} muted />
155
- </Flex>
156
- )}
157
- {allItemsStatus === 'error' && (
158
- <Flex padding={4} align={'center'} justify={'center'}>
159
- <Text size={2} weight="semibold">
160
- Something went wrong when loading documents
161
- </Text>
162
- </Flex>
163
- )}
164
- </Stack>
165
- </TreeOperationsContext.Provider>
166
- </DndProvider>
167
- ) : null}
168
- </Box>
169
- </TreeEditorErrorBoundary>
170
- )
171
- }
172
-
173
- function canDrop({nextPath, prevPath}: any & NodeData) {
174
- const insideItself =
175
- nextPath.length >= prevPath.length &&
176
- prevPath.every((pathIndex: any, index: string | number) => nextPath[index] === pathIndex)
177
- return !insideItself
178
- }
179
-
180
- const doNothingOnChange = () => {
181
- // Do nothing. onMoveNode will do all the work
182
- }
183
-
184
- export default TreeEditor
@@ -1,14 +0,0 @@
1
- /**
2
- * react-sortable-tree emits a lot of random errors when dragging to invalid states,
3
- * even when drag-targets are disabled.
4
- *
5
- * This boundary is a workaround so users are not pestered with error-toasts for things
6
- * that have no functional impact.
7
- *
8
- * This boundry does NOT handle errors that happen in the React dnd
9
- * event handlers, so there is addtional workarounds in the
10
- * DnDManager.
11
- * */
12
- export const TreeEditorErrorBoundary = (props: any) => {
13
- return props.children
14
- }
@@ -1,37 +0,0 @@
1
- import * as React from 'react'
2
- import TreeNodeRendererScaffold from './TreeNodeRendererScaffold'
3
-
4
- /**
5
- * To prevent expand buttons from overflowing on the left, we add a minimum left padding to all entries
6
- */
7
- const BASE_LEFT_PADDING = 10
8
- const NESTING_PADDING = 14
9
-
10
- const TreeNodeRenderer: any = (props: any) => {
11
- const {children, lowerSiblingCounts, connectDropTarget, isOver, draggedNode, canDrop} = props
12
-
13
- // Construct the scaffold representing the structure of the tree
14
- const scaffoldBlockCount = lowerSiblingCounts.length
15
-
16
- return connectDropTarget(
17
- <div style={props.style}>
18
- <div
19
- style={{
20
- // prettier-ignore
21
- paddingLeft: `${BASE_LEFT_PADDING + (NESTING_PADDING * scaffoldBlockCount)}px`
22
- }}
23
- >
24
- {React.Children.map(children, (child) =>
25
- React.cloneElement(child as React.ReactElement<any>, {
26
- isOver,
27
- canDrop,
28
- draggedNode
29
- })
30
- )}
31
- </div>
32
- <TreeNodeRendererScaffold {...props} />
33
- </div>
34
- )
35
- }
36
-
37
- export default TreeNodeRenderer
@@ -1,193 +0,0 @@
1
- import {blue} from '@sanity/color'
2
- import * as React from 'react'
3
- import {createGlobalStyle} from 'styled-components'
4
-
5
- // Adapted from react-sortable-tree/src/tree-node.js
6
- const ScaffoldStyles = createGlobalStyle`
7
- .rst__lineBlock,
8
- .rst__absoluteLineBlock {
9
- height: 100%;
10
- position: relative;
11
- display: inline-block;
12
- --stroke-width: 3px;
13
- }
14
-
15
- .rst__absoluteLineBlock {
16
- position: absolute;
17
- top: 0;
18
- }
19
-
20
- /* Highlight line for pointing to dragged row destination
21
- ========================================================================== */
22
- /**
23
- * +--+--+
24
- * | | |
25
- * | | |
26
- * | | |
27
- * +--+--+
28
- */
29
- .rst__highlightLineVertical {
30
- z-index: 3;
31
- }
32
- .rst__highlightLineVertical::before {
33
- position: absolute;
34
- content: '';
35
- background-color: ${blue[400].hex};
36
- width: calc(var(--stroke-width) * 2);
37
- margin-left: calc(var(--stroke-width) * -1);
38
- left: 50%;
39
- top: 0;
40
- height: 100%;
41
- }
42
-
43
- @keyframes arrow-pulse {
44
- 0% {
45
- transform: translate(0, 0);
46
- opacity: 0;
47
- }
48
- 30% {
49
- transform: translate(0, 300%);
50
- opacity: 1;
51
- }
52
- 70% {
53
- transform: translate(0, 700%);
54
- opacity: 1;
55
- }
56
- 100% {
57
- transform: translate(0, 1000%);
58
- opacity: 0;
59
- }
60
- }
61
- .rst__highlightLineVertical::after {
62
- content: '';
63
- position: absolute;
64
- height: 0;
65
- margin-left: calc(var(--stroke-width) * -1);
66
- left: 50%;
67
- top: 0;
68
- border-left: var(--stroke-width) solid transparent;
69
- border-right: var(--stroke-width) solid transparent;
70
- border-top: var(--stroke-width) solid white;
71
- animation: arrow-pulse 1s infinite linear both;
72
- }
73
-
74
- /**
75
- * +-----+
76
- * | |
77
- * | +--+
78
- * | | |
79
- * +--+--+
80
- */
81
- .rst__highlightTopLeftCorner::before {
82
- z-index: 3;
83
- content: '';
84
- position: absolute;
85
- border-top: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
86
- border-left: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
87
- box-sizing: border-box;
88
- height: calc(50% + var(--stroke-width));
89
- top: 50%;
90
- margin-top: calc(var(--stroke-width) * -1);
91
- right: 0;
92
- width: calc(50% + var(--stroke-width));
93
- }
94
-
95
- /**
96
- * +--+--+
97
- * | | |
98
- * | | |
99
- * | +->|
100
- * +-----+
101
- */
102
- .rst__highlightBottomLeftCorner {
103
- z-index: 3;
104
- }
105
- .rst__highlightBottomLeftCorner::before {
106
- content: '';
107
- position: absolute;
108
- border-bottom: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
109
- border-left: solid calc(var(--stroke-width) * 2) ${blue[400].hex};
110
- box-sizing: border-box;
111
- height: calc(100% + var(--stroke-width));
112
- top: 0;
113
- right: calc(var(--stroke-width) * 3);
114
- width: calc(50% - calc(var(--stroke-width) * 2));
115
- }
116
-
117
- .rst__highlightBottomLeftCorner::after {
118
- content: '';
119
- position: absolute;
120
- height: 0;
121
- right: 0;
122
- top: 100%;
123
- margin-top: calc(var(--stroke-width) * -3);
124
- border-top: calc(var(--stroke-width) * 3) solid transparent;
125
- border-bottom: calc(var(--stroke-width) * 3) solid transparent;
126
- border-left: calc(var(--stroke-width) * 3) solid ${blue[400].hex};
127
- }
128
-
129
- .rst__unclickable {
130
- pointer-events: none;
131
- margin-top: -calc(var(--stroke-width) * 3);
132
- }
133
- `
134
-
135
- const TreeNodeRendererScaffold: React.FC<any> = (props) => {
136
- const {
137
- lowerSiblingCounts,
138
- scaffoldBlockPxWidth,
139
- listIndex,
140
- swapDepth,
141
- swapFrom,
142
- swapLength,
143
- treeIndex,
144
- } = props
145
-
146
- // Construct the scaffold representing the structure of the tree
147
- const scaffold: React.ReactNode[] = lowerSiblingCounts.map(
148
- (lowerSiblingCount: number, i: any) => {
149
- if (lowerSiblingCount < 0 || treeIndex === listIndex || i !== swapDepth) {
150
- return null
151
- }
152
-
153
- // This row has been shifted, and is at the depth of
154
- // the line pointing to the new destination
155
- let highlightLineClass = ''
156
-
157
- if (listIndex === (swapFrom || 0) + (swapLength || 0) - 1) {
158
- // This block is on the bottom (target) line
159
- // This block points at the target block (where the row will go when released)
160
- highlightLineClass = 'rst__highlightBottomLeftCorner'
161
- } else if (treeIndex === swapFrom) {
162
- // This block is on the top (source) line
163
- highlightLineClass = 'rst__highlightTopLeftCorner'
164
- } else {
165
- // This block is between the bottom and top
166
- highlightLineClass = 'rst__highlightLineVertical'
167
- }
168
-
169
- const style = {
170
- width: scaffoldBlockPxWidth,
171
- left: scaffoldBlockPxWidth * i,
172
- }
173
-
174
- return (
175
- <div
176
- key={i}
177
- style={style}
178
- className={`rst__unclickable rst__absoluteLineBlock ${highlightLineClass || ''}`}
179
- tabIndex={-1}
180
- />
181
- )
182
- }
183
- )
184
-
185
- return (
186
- <>
187
- {scaffold}
188
- <ScaffoldStyles />
189
- </>
190
- )
191
- }
192
-
193
- export default TreeNodeRendererScaffold
@@ -1,110 +0,0 @@
1
- import {AddIcon} from '@sanity/icons'
2
- import * as React from 'react'
3
- import type {ConfigContext} from 'sanity'
4
- import {StructureBuilder} from 'sanity/desk'
5
-
6
- import TreeDeskStructure from './TreeDeskStructure'
7
- import {TreeDeskStructureProps} from './types'
8
- import throwError from './utils/throwError'
9
-
10
- export interface TreeProps extends TreeDeskStructureProps {
11
- /**
12
- * Visible title above the tree.
13
- * Also used as the label in the desk list item.
14
- */
15
- title: string
16
-
17
- /**
18
- * Optional icon for rendering the item in the desk structure.
19
- */
20
- icon?: any
21
-
22
- context?: ConfigContext | any
23
- S?: StructureBuilder | any
24
- /**
25
- * Restrict document types that can be created.
26
- */
27
- creatableTypes?: string[]
28
- }
29
-
30
- const deskTreeValidator = (props: TreeProps): React.FC => {
31
- const {documentId, referenceTo} = props
32
- if (typeof documentId !== 'string' && !documentId) {
33
- throwError('invalidDocumentId')
34
- }
35
- if (!Array.isArray(referenceTo)) {
36
- throwError('invalidReferenceTo', `(documentId "${documentId}")`)
37
- }
38
-
39
- return (deskProps) => <TreeDeskStructure {...deskProps} options={props} />
40
- }
41
-
42
- export default function createDeskHierarchy(props: TreeProps) {
43
- const {documentId, referenceTo, referenceOptions, context, S, creatableTypes} = props
44
- if (!S || !context) {
45
- throw new Error('Invalid configuration. S or context props are undefined. ' +
46
- 'These props are available as function parameters when configuring structure, and must be passed along to createDeskHierarchy. ' +
47
- 'Confer the plugin README for example usage.')
48
- }
49
-
50
- const {schema} = context
51
-
52
- const safelyCreatableTypes =
53
- creatableTypes && !creatableTypes.some((type) => referenceTo.indexOf(type))
54
- ? creatableTypes
55
- : referenceTo
56
-
57
- let mainList = (
58
- referenceTo?.length === 1
59
- ? S.documentTypeList(referenceTo[0]).schemaType(referenceTo[0])
60
- : S.documentList().filter('_type in $types').params({types: referenceTo})
61
- )
62
- .id(documentId)
63
- .menuItems(
64
- (safelyCreatableTypes || []).map((schemaType) =>
65
- S.menuItem()
66
- .intent({
67
- type: 'create',
68
- params: {type: schemaType}
69
- })
70
- .title(`Create ${schema.get(schemaType)?.title}`)
71
- .icon(schema.get(schemaType)?.icon || AddIcon)
72
- )
73
- )
74
- .canHandleIntent((intent: string, c: Record<string, unknown>) => {
75
- // Can edit itself
76
- if (intent === 'edit' && c.id === props.documentId) {
77
- return true
78
- }
79
- // Can create & edit referenced document types
80
- if (safelyCreatableTypes.includes(c.type as string)) {
81
- return true
82
- }
83
- return false
84
- })
85
-
86
- if (referenceOptions?.filter) {
87
- mainList = mainList.filter(referenceOptions.filter)
88
- }
89
-
90
- if (referenceOptions?.filterParams) {
91
- mainList = mainList.params(referenceOptions.filterParams)
92
- }
93
-
94
- return S.listItem()
95
- .id(documentId)
96
- .title(props.title || documentId)
97
- .icon(props.icon)
98
- .child(
99
- Object.assign(
100
- mainList.serialize(),
101
- {
102
- type: 'component',
103
- component: deskTreeValidator(props),
104
- options: props,
105
- __preserveInstance: true
106
- },
107
- props.title ? {title: props.title} : {}
108
- )
109
- )
110
- }
@@ -1,151 +0,0 @@
1
- import * as React from 'react'
2
- import {ArraySchemaType} from 'sanity'
3
- import {DEFAULT_FIELD_KEY} from './TreeDeskStructure'
4
- import TreeInputComponent from './TreeInputComponent'
5
- import {TreeDeskStructureProps, TreeFieldSchema} from './types'
6
- import {
7
- INTERNAL_NODE_ARRAY_TYPE,
8
- INTERNAL_NODE_TYPE,
9
- INTERNAL_NODE_VALUE_TYPE,
10
- getSchemaTypeName
11
- } from './utils/injectNodeTypeInPatches'
12
- import throwError from './utils/throwError'
13
-
14
- type SchemaOptions = Omit<TreeDeskStructureProps, 'documentId' | 'maxDepth'>
15
-
16
- function createHierarchicalNodeValueType({
17
- referenceTo,
18
- referenceOptions,
19
- documentType
20
- }: SchemaOptions) {
21
- return {
22
- // when used inside the field, name & type are overwritten by createHierarchicalNodeType
23
- name: documentType ? getSchemaTypeName(documentType, 'nodeValue') : INTERNAL_NODE_VALUE_TYPE,
24
- type: 'object',
25
- title: `Hierarchical node value (${documentType})`,
26
-
27
- fields: [
28
- {name: 'docType', type: 'string'},
29
- {
30
- name: 'reference',
31
- type: 'reference',
32
- weak: true,
33
- to: referenceTo.map((type) => ({type})),
34
- options: referenceOptions
35
- }
36
- ]
37
- }
38
- }
39
-
40
- function createHierarchicalNodeType(options: SchemaOptions) {
41
- return {
42
- // name & type are overwritten by createHierarchicalField
43
- name: options.documentType
44
- ? getSchemaTypeName(options.documentType, 'node')
45
- : INTERNAL_NODE_TYPE,
46
- title: `Hierarchical node (${options.documentType})`,
47
- type: 'object',
48
- fields: [
49
- {name: 'parent', type: 'string'},
50
-
51
- options.documentType
52
- ? {name: 'value', type: getSchemaTypeName(options.documentType, 'nodeValue')}
53
- : // If no documentType is defined, use an anonymized inline object to avoid
54
- // having to define another custom schema type through the plugin
55
- {
56
- ...createHierarchicalNodeValueType(options),
57
- name: 'value',
58
- type: 'object'
59
- }
60
- ]
61
- }
62
- }
63
-
64
- function createHierarchicalArrayType(options: SchemaOptions) {
65
- return {
66
- // name & type are overwritten by createHierarchicalField
67
- name: options.documentType
68
- ? getSchemaTypeName(options.documentType, 'array')
69
- : INTERNAL_NODE_ARRAY_TYPE,
70
- title: `Hierarchical array of nodes (${options.documentType})`,
71
- type: 'array',
72
- of: [
73
- options.documentType
74
- ? {type: getSchemaTypeName(options.documentType, 'node')}
75
- : createHierarchicalNodeType(options)
76
- ]
77
- }
78
- }
79
-
80
- export function createHierarchicalField({name, title, options, ...rest}: TreeFieldSchema): Omit<
81
- ArraySchemaType,
82
- 'type' | 'jsonType' | 'of'
83
- > & {
84
- type: string
85
- inputComponent: React.FC<any>
86
- of?: any[]
87
- } {
88
- if (!Array.isArray(options?.referenceTo)) {
89
- throwError('invalidReferenceTo', `(field of name "${name}")`)
90
- }
91
-
92
- return {
93
- ...rest,
94
- name,
95
- title,
96
- inputComponent: TreeInputComponent,
97
- options,
98
- ...(options.documentType
99
- ? {type: getSchemaTypeName(options.documentType, 'array')}
100
- : {
101
- ...createHierarchicalArrayType(options),
102
- name
103
- })
104
- }
105
- }
106
-
107
- function createHierarchicalDocType(options: SchemaOptions) {
108
- return {
109
- name: options.documentType,
110
- title: 'Hierarchical tree',
111
- type: 'document',
112
- // The plugin needs to define a `schemaType` with liveEdit enabled so that
113
- // `useDocumentOperation` in TreeDeskStructure.tsx doesn't create drafts at every patch.
114
- liveEdit: true,
115
- fields: [
116
- createHierarchicalField({
117
- name: options.fieldKeyInDocument || DEFAULT_FIELD_KEY,
118
- title: 'Hierarchical Tree',
119
- options
120
- })
121
- ],
122
- preview: {
123
- select: {
124
- id: '_id',
125
- tree: 'tree'
126
- },
127
- prepare({id, tree}: {id: string; tree: unknown[]}): Record<string, string> {
128
- return {
129
- title: `Hierarchical documents (ID: ${id})`,
130
- subtitle: `${tree?.length || 0} document(s) in its list.`
131
- }
132
- }
133
- }
134
- }
135
- }
136
-
137
- export default function createHierarchicalSchemas(options: SchemaOptions) {
138
- if (!Array.isArray(options.referenceTo) || options.referenceTo.length <= 0) {
139
- throwError('invalidReferenceTo')
140
- }
141
- if (!options.documentType) {
142
- throwError('invalidDocumentType')
143
- }
144
-
145
- return [
146
- createHierarchicalDocType(options),
147
- createHierarchicalArrayType(options),
148
- createHierarchicalNodeType(options),
149
- createHierarchicalNodeValueType(options)
150
- ]
151
- }