@sanity/hierarchical-document-list 1.1.0 → 2.0.1

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 (120) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +95 -59
  3. package/dist/index.d.ts +240 -0
  4. package/dist/index.esm.js +151 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +151 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +82 -51
  9. package/sanity.json +2 -6
  10. package/src/TreeDeskStructure.tsx +80 -0
  11. package/src/TreeInputComponent.tsx +41 -0
  12. package/src/components/DeskWarning.tsx +40 -0
  13. package/src/components/DocumentInNode.tsx +133 -0
  14. package/src/components/DocumentPreviewStatus.tsx +70 -0
  15. package/src/components/NodeActions.tsx +85 -0
  16. package/src/components/NodeContentRenderer.tsx +141 -0
  17. package/src/components/PlaceholderDropzone.tsx +45 -0
  18. package/src/components/TreeEditor.tsx +167 -0
  19. package/{lib/components/TreeEditorErrorBoundary.d.ts → src/components/TreeEditorErrorBoundary.tsx} +2 -4
  20. package/src/components/TreeNodeRenderer.tsx +37 -0
  21. package/src/components/TreeNodeRendererScaffold.tsx +193 -0
  22. package/src/createDeskHierarchy.tsx +110 -0
  23. package/src/createHierarchicalSchemas.tsx +151 -0
  24. package/src/hooks/useAllItems.ts +119 -0
  25. package/src/hooks/useLocalTree.ts +40 -0
  26. package/src/hooks/useTreeOperations.ts +25 -0
  27. package/src/hooks/useTreeOperationsProvider.ts +86 -0
  28. package/src/index.ts +25 -0
  29. package/src/schemas/hierarchy.tree.ts +19 -0
  30. package/src/types.ts +148 -0
  31. package/src/utils/flatDataToTree.ts +20 -0
  32. package/src/utils/getAdjescentNodes.ts +30 -0
  33. package/src/utils/getCommonTreeProps.tsx +28 -0
  34. package/src/utils/getTreeHeight.ts +10 -0
  35. package/src/utils/gradientPatchAdapter.ts +43 -0
  36. package/src/utils/idUtils.ts +7 -0
  37. package/src/utils/injectNodeTypeInPatches.ts +60 -0
  38. package/src/utils/moveItemInArray.ts +26 -0
  39. package/src/utils/throwError.ts +9 -0
  40. package/src/utils/treeData.tsx +119 -0
  41. package/src/utils/treePatches.ts +171 -0
  42. package/v2-incompatible.js +11 -0
  43. package/.husky/commit-msg +0 -4
  44. package/.husky/pre-commit +0 -4
  45. package/.idea/hierarchical-document-list.iml +0 -11
  46. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  47. package/.idea/misc.xml +0 -6
  48. package/.idea/modules.xml +0 -8
  49. package/.idea/prettier.xml +0 -7
  50. package/.idea/vcs.xml +0 -6
  51. package/CHANGELOG.md +0 -15
  52. package/commitlint.config.js +0 -3
  53. package/lib/TreeDeskStructure.d.ts +0 -8
  54. package/lib/TreeDeskStructure.js +0 -96
  55. package/lib/TreeInputComponent.d.ts +0 -19
  56. package/lib/TreeInputComponent.js +0 -50
  57. package/lib/components/DeskWarning.d.ts +0 -6
  58. package/lib/components/DeskWarning.js +0 -46
  59. package/lib/components/DocumentInNode.d.ts +0 -11
  60. package/lib/components/DocumentInNode.js +0 -81
  61. package/lib/components/DocumentPreviewStatus.d.ts +0 -7
  62. package/lib/components/DocumentPreviewStatus.js +0 -39
  63. package/lib/components/NodeActions.d.ts +0 -10
  64. package/lib/components/NodeActions.js +0 -61
  65. package/lib/components/NodeContentRenderer.d.ts +0 -8
  66. package/lib/components/NodeContentRenderer.js +0 -105
  67. package/lib/components/PlaceholderDropzone.d.ts +0 -9
  68. package/lib/components/PlaceholderDropzone.js +0 -30
  69. package/lib/components/SuppressedDnDManager.d.ts +0 -2
  70. package/lib/components/SuppressedDnDManager.js +0 -59
  71. package/lib/components/TreeEditor.d.ts +0 -12
  72. package/lib/components/TreeEditor.js +0 -74
  73. package/lib/components/TreeEditorErrorBoundary.js +0 -74
  74. package/lib/components/TreeNodeRenderer.d.ts +0 -3
  75. package/lib/components/TreeNodeRenderer.js +0 -59
  76. package/lib/components/TreeNodeRendererScaffold.d.ts +0 -4
  77. package/lib/components/TreeNodeRendererScaffold.js +0 -44
  78. package/lib/createDeskHierarchy.d.ts +0 -14
  79. package/lib/createDeskHierarchy.js +0 -84
  80. package/lib/createHierarchicalSchemas.d.ts +0 -98
  81. package/lib/createHierarchicalSchemas.js +0 -138
  82. package/lib/hooks/useAllItems.d.ts +0 -7
  83. package/lib/hooks/useAllItems.js +0 -119
  84. package/lib/hooks/useLocalTree.d.ts +0 -17
  85. package/lib/hooks/useLocalTree.js +0 -59
  86. package/lib/hooks/useTreeOperations.d.ts +0 -9
  87. package/lib/hooks/useTreeOperations.js +0 -39
  88. package/lib/hooks/useTreeOperationsProvider.d.ts +0 -14
  89. package/lib/hooks/useTreeOperationsProvider.js +0 -85
  90. package/lib/index.d.ts +0 -3
  91. package/lib/index.js +0 -12
  92. package/lib/schemas/hierarchy.tree.d.ts +0 -13
  93. package/lib/schemas/hierarchy.tree.js +0 -19
  94. package/lib/types.d.ts +0 -128
  95. package/lib/types.js +0 -2
  96. package/lib/utils/flatDataToTree.d.ts +0 -6
  97. package/lib/utils/flatDataToTree.js +0 -26
  98. package/lib/utils/getAdjescentNodes.d.ts +0 -12
  99. package/lib/utils/getAdjescentNodes.js +0 -19
  100. package/lib/utils/getCommonTreeProps.d.ts +0 -7
  101. package/lib/utils/getCommonTreeProps.js +0 -33
  102. package/lib/utils/getTreeHeight.d.ts +0 -3
  103. package/lib/utils/getTreeHeight.js +0 -11
  104. package/lib/utils/gradientPatchAdapter.d.ts +0 -4
  105. package/lib/utils/gradientPatchAdapter.js +0 -40
  106. package/lib/utils/idUtils.d.ts +0 -2
  107. package/lib/utils/idUtils.js +0 -13
  108. package/lib/utils/injectNodeTypeInPatches.d.ts +0 -12
  109. package/lib/utils/injectNodeTypeInPatches.js +0 -59
  110. package/lib/utils/moveItemInArray.d.ts +0 -5
  111. package/lib/utils/moveItemInArray.js +0 -26
  112. package/lib/utils/throwError.d.ts +0 -7
  113. package/lib/utils/throwError.js +0 -12
  114. package/lib/utils/treeData.d.ts +0 -18
  115. package/lib/utils/treeData.js +0 -118
  116. package/lib/utils/treePatches.d.ts +0 -15
  117. package/lib/utils/treePatches.js +0 -171
  118. package/lint-staged.config.js +0 -4
  119. package/screenshot-1.jpg +0 -0
  120. package/tsconfig.json +0 -20
@@ -0,0 +1,141 @@
1
+ import {isDescendant} from '@nosferatu500/react-sortable-tree'
2
+ import {cyan, gray, red} from '@sanity/color'
3
+ import {ChevronDownIcon, ChevronRightIcon, DragHandleIcon} from '@sanity/icons'
4
+ import {Box, Button, Flex, Spinner} from '@sanity/ui'
5
+ import * as React from 'react'
6
+ import styled from 'styled-components'
7
+
8
+ const Root = styled.div`
9
+ // Adapted from react-sortable-tree/style.css
10
+ &[data-landing='true'] > *,
11
+ &[data-cancel='true'] > * {
12
+ opacity: 0 !important;
13
+ }
14
+ &[data-landing='true']::before,
15
+ &[data-cancel='true']::before {
16
+ background-color: ${cyan[50].hex};
17
+ border: 2px dashed ${gray[400].hex};
18
+ border-radius: 3px;
19
+ content: '';
20
+ position: absolute;
21
+ top: 0;
22
+ right: 0;
23
+ bottom: 0;
24
+ left: 0;
25
+ z-index: -1;
26
+ }
27
+
28
+ &[data-cancel='true']::before {
29
+ background-color: ${red[50].hex};
30
+ }
31
+ `
32
+
33
+ /**
34
+ * Customization of react-sortable-tree's default node.
35
+ * Created in order to use Sanity UI for styles.
36
+ * Reference: https://github.com/frontend-collective/react-sortable-tree/blob/master/src/node-renderer-default.js
37
+ */
38
+ const NodeContentRenderer: any = (props: any) => {
39
+ const {node, path, treeIndex, canDrag = false} = props
40
+ const nodeTitle = node.title
41
+ const Handle = React.useMemo(() => {
42
+ if (!canDrag) {
43
+ return null
44
+ }
45
+ if (typeof node.children === 'function' && node.expanded) {
46
+ // Show a loading symbol on the handle when the children are expanded
47
+ // and yet still defined by a function (a callback to fetch the children)
48
+ return <Spinner />
49
+ }
50
+
51
+ const BtnElement = (
52
+ <div>
53
+ <Button
54
+ mode="bleed"
55
+ paddingX={0}
56
+ paddingY={1}
57
+ style={{
58
+ cursor: node.publishedId ? 'grab' : 'default',
59
+ fontSize: '1.5625rem'
60
+ }}
61
+ data-ui="DragHandleButton"
62
+ data-drag-handle={canDrag}
63
+ disabled={!node.publishedId}
64
+ >
65
+ <DragHandleIcon style={{marginBottom: '-0.1em'}} />
66
+ </Button>
67
+ </div>
68
+ )
69
+
70
+ // Don't allow editors to drag invalid documents
71
+ if (!node.publishedId) {
72
+ return BtnElement
73
+ }
74
+
75
+ // Show the handle used to initiate a drag-and-drop
76
+ return props.connectDragSource(BtnElement, {
77
+ dropEffect: 'copy'
78
+ })
79
+ }, [canDrag, node, typeof node.children === 'function'])
80
+
81
+ const isDraggedDescendant = props.draggedNode && isDescendant(props.draggedNode, node)
82
+ const isLandingPadActive = !props.didDrop && props.isDragging
83
+
84
+ return (
85
+ <Box style={{position: 'relative'}}>
86
+ {props.toggleChildrenVisibility &&
87
+ node.children &&
88
+ (node.children.length > 0 || typeof node.children === 'function') && (
89
+ <div
90
+ style={{
91
+ position: 'absolute',
92
+ left: '-2px',
93
+ top: '40%',
94
+ transform: 'translate(-100%, -50%)'
95
+ }}
96
+ >
97
+ <Button
98
+ aria-label={node.expanded ? 'Collapse' : 'Expand'}
99
+ icon={
100
+ node.expanded ? (
101
+ <ChevronDownIcon color={gray[200].hex} />
102
+ ) : (
103
+ <ChevronRightIcon color={gray[200].hex} />
104
+ )
105
+ }
106
+ mode="bleed"
107
+ fontSize={2}
108
+ padding={1}
109
+ type="button"
110
+ onClick={() =>
111
+ props.toggleChildrenVisibility?.({
112
+ node,
113
+ path,
114
+ treeIndex
115
+ })
116
+ }
117
+ />
118
+ </div>
119
+ )}
120
+
121
+ {props.connectDragPreview(
122
+ <div>
123
+ <Root
124
+ data-landing={isLandingPadActive}
125
+ data-cancel={isLandingPadActive && !props.canDrop}
126
+ style={{
127
+ opacity: isDraggedDescendant ? 0.5 : 1
128
+ }}
129
+ >
130
+ <Flex align="center">
131
+ {Handle}
132
+ {typeof nodeTitle === 'function' ? nodeTitle(props) : nodeTitle}
133
+ </Flex>
134
+ </Root>
135
+ </div>
136
+ )}
137
+ </Box>
138
+ )
139
+ }
140
+
141
+ export default NodeContentRenderer
@@ -0,0 +1,45 @@
1
+ import {Box, Card, CardTone, Stack, Text} from '@sanity/ui'
2
+ import * as React from 'react'
3
+
4
+ const PlaceholderDropzone: React.FC<
5
+ {
6
+ tone?: CardTone
7
+ title: string
8
+ subtitle?: string
9
+ } & any
10
+ > = (props) => {
11
+ const isValid = props.isOver && props.canDrop
12
+ const isInvalid = props.isOver && !props.canDrop
13
+ let tone: CardTone = 'transparent'
14
+ if (isValid) {
15
+ tone = 'positive'
16
+ }
17
+ if (isInvalid) {
18
+ tone = 'caution'
19
+ }
20
+ return (
21
+ <Box padding={3}>
22
+ <Card
23
+ padding={5}
24
+ radius={2}
25
+ border
26
+ tone={tone}
27
+ style={{
28
+ borderStyle: props.isOver ? undefined : 'dashed',
29
+ }}
30
+ >
31
+ <Stack space={2} style={{textAlign: 'center'}}>
32
+ <Text size={2} as="h2" muted>
33
+ {!props.isOver && props.title}
34
+ {isValid && 'Drop here'}
35
+ {isInvalid && 'Invalid location or element'}
36
+ </Text>
37
+ {props.subtitle && <Text size={1}>{props.subtitle}</Text>}
38
+ {props.children}
39
+ </Stack>
40
+ </Card>
41
+ </Box>
42
+ )
43
+ }
44
+
45
+ export default PlaceholderDropzone
@@ -0,0 +1,167 @@
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
+
46
+ React.useEffect(() => {
47
+ if (props.options.documentId) {
48
+ setContext(document.getElementById(props.options.documentId))
49
+ }
50
+ }, [props.options.documentId])
51
+
52
+ const onMoveNode = useCallback(
53
+ (data: NodeData & FullTree & any) =>
54
+ operations.handleMovedNode(data as unknown as HandleMovedNodeData),
55
+ [operations]
56
+ )
57
+
58
+ const treeProps = useMemo(
59
+ () =>
60
+ getCommonTreeProps({
61
+ placeholder: {
62
+ title: 'Add items from the list below'
63
+ }
64
+ }),
65
+ []
66
+ )
67
+
68
+ const operationContext = useMemo(
69
+ () => ({...operations, allItemsStatus}),
70
+ [operations, allItemsStatus]
71
+ )
72
+
73
+ return (
74
+ <TreeEditorErrorBoundary>
75
+ {/*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 */}
76
+ <Box id={props.options.documentId}>
77
+ {context ? (
78
+ <DndProvider backend={HTML5Backend} options={{rootElement: context}}>
79
+ <TreeOperationsContext.Provider value={operationContext}>
80
+ <Stack space={4} paddingTop={4}>
81
+ <Card
82
+ style={{minHeight: getTreeHeight(localTree)}}
83
+ // Only include borderBottom if there's something to show in unadded items
84
+ borderBottom={allItemsStatus !== 'success' || unAddedItems?.length > 0}
85
+ >
86
+ <SortableTree
87
+ maxDepth={props.options.maxDepth}
88
+ onChange={doNothingOnChange}
89
+ onVisibilityToggle={handleVisibilityToggle}
90
+ canDrop={canDrop}
91
+ onMoveNode={onMoveNode}
92
+ treeData={localTree}
93
+ {...treeProps}
94
+ />
95
+ </Card>
96
+
97
+ {allItemsStatus === 'success' && unAddedItems?.length > 0 && (
98
+ <Stack space={1} paddingX={2} paddingTop={3}>
99
+ <Stack space={2} paddingX={2} paddingBottom={3}>
100
+ <Text size={2} as="h2" weight="semibold">
101
+ Add more items
102
+ </Text>
103
+ <Text size={1} muted>
104
+ Only published documents are shown.
105
+ </Text>
106
+ </Stack>
107
+ {unAddedItems.map((item) => (
108
+ <DocumentInNode
109
+ key={item.publishedId || item.draftId}
110
+ item={item}
111
+ action={
112
+ <Tooltip
113
+ portal
114
+ placement="left"
115
+ content={
116
+ <Box padding={2}>
117
+ <Text size={1}>Add to list</Text>
118
+ </Box>
119
+ }
120
+ >
121
+ <Button
122
+ onClick={() => {
123
+ operations.addItem(item)
124
+ }}
125
+ mode="bleed"
126
+ icon={AddCircleIcon}
127
+ style={{cursor: 'pointer'}}
128
+ />
129
+ </Tooltip>
130
+ }
131
+ />
132
+ ))}
133
+ </Stack>
134
+ )}
135
+ {allItemsStatus === 'loading' && (
136
+ <Flex padding={4} align={'center'} justify={'center'}>
137
+ <Spinner size={3} muted />
138
+ </Flex>
139
+ )}
140
+ {allItemsStatus === 'error' && (
141
+ <Flex padding={4} align={'center'} justify={'center'}>
142
+ <Text size={2} weight="semibold">
143
+ Something went wrong when loading documents
144
+ </Text>
145
+ </Flex>
146
+ )}
147
+ </Stack>
148
+ </TreeOperationsContext.Provider>
149
+ </DndProvider>
150
+ ) : null}
151
+ </Box>
152
+ </TreeEditorErrorBoundary>
153
+ )
154
+ }
155
+
156
+ function canDrop({nextPath, prevPath}: any & NodeData) {
157
+ const insideItself =
158
+ nextPath.length >= prevPath.length &&
159
+ prevPath.every((pathIndex: any, index: string | number) => nextPath[index] === pathIndex)
160
+ return !insideItself
161
+ }
162
+
163
+ const doNothingOnChange = () => {
164
+ // Do nothing. onMoveNode will do all the work
165
+ }
166
+
167
+ export default TreeEditor
@@ -1,4 +1,3 @@
1
- import * as React from 'react';
2
1
  /**
3
2
  * react-sortable-tree emits a lot of random errors when dragging to invalid states,
4
3
  * even when drag-targets are disabled.
@@ -10,7 +9,6 @@ import * as React from 'react';
10
9
  * event handlers, so there is addtional workarounds in the
11
10
  * DnDManager.
12
11
  * */
13
- export declare class TreeEditorErrorBoundary extends React.Component {
14
- componentDidCatch(): void;
15
- render(): React.ReactNode;
12
+ export const TreeEditorErrorBoundary = (props: any) => {
13
+ return props.children
16
14
  }
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,193 @@
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