torch-glare 2.4.0 → 2.4.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 (264) hide show
  1. package/README.md +8 -0
  2. package/apps/lib/components/ActionButton.tsx +7 -12
  3. package/apps/lib/components/ActionsGroup.tsx +4 -15
  4. package/apps/lib/components/AlertDialog.tsx +173 -191
  5. package/apps/lib/components/Avatar.tsx +13 -16
  6. package/apps/lib/components/Badge.tsx +1 -3
  7. package/apps/lib/components/BadgeField.tsx +76 -71
  8. package/apps/lib/components/Breadcrumb.tsx +49 -85
  9. package/apps/lib/components/Button.tsx +20 -19
  10. package/apps/lib/components/ButtonGroup.tsx +31 -41
  11. package/apps/lib/components/Calendar.tsx +51 -29
  12. package/apps/lib/components/Card.tsx +2 -13
  13. package/apps/lib/components/ChartBlockTool.ts +17 -52
  14. package/apps/lib/components/Checkbox.tsx +9 -16
  15. package/apps/lib/components/ContextMenu.tsx +97 -96
  16. package/apps/lib/components/CountBadge.tsx +4 -6
  17. package/apps/lib/components/DataTable.tsx +196 -216
  18. package/apps/lib/components/DataViews/DataViewRadio.tsx +1 -6
  19. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +13 -47
  20. package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
  21. package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
  22. package/apps/lib/components/DataViews/FilterPanel.tsx +155 -149
  23. package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
  24. package/apps/lib/components/DataViews/InboxView.tsx +22 -68
  25. package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
  26. package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
  27. package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
  28. package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
  29. package/apps/lib/components/DataViews/TableView.tsx +8 -28
  30. package/apps/lib/components/DataViews/TreeView.tsx +12 -40
  31. package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
  32. package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
  33. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +30 -23
  34. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
  35. package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
  36. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
  37. package/apps/lib/components/DataViews/index.ts +26 -26
  38. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
  39. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
  40. package/apps/lib/components/DataViews/types.ts +104 -115
  41. package/apps/lib/components/DatePicker.tsx +198 -196
  42. package/apps/lib/components/Dialog.tsx +92 -105
  43. package/apps/lib/components/Divider.tsx +21 -24
  44. package/apps/lib/components/Drawer.tsx +79 -139
  45. package/apps/lib/components/DropdownMenu.tsx +131 -171
  46. package/apps/lib/components/FieldHint.tsx +4 -11
  47. package/apps/lib/components/Form.tsx +146 -155
  48. package/apps/lib/components/FormStepper.tsx +9 -39
  49. package/apps/lib/components/HeaderBar.tsx +1 -9
  50. package/apps/lib/components/ImageAttachment.tsx +111 -73
  51. package/apps/lib/components/InnerLabelField.tsx +20 -16
  52. package/apps/lib/components/Input.tsx +171 -172
  53. package/apps/lib/components/InputField.tsx +44 -45
  54. package/apps/lib/components/InputOTP.tsx +68 -69
  55. package/apps/lib/components/Label.tsx +31 -35
  56. package/apps/lib/components/LabelField.tsx +4 -5
  57. package/apps/lib/components/LabeledCheckBox.tsx +8 -12
  58. package/apps/lib/components/LabeledRadio.tsx +7 -5
  59. package/apps/lib/components/LinkButton.tsx +2 -9
  60. package/apps/lib/components/LoginButton.tsx +4 -9
  61. package/apps/lib/components/PasswordLevel.tsx +5 -5
  62. package/apps/lib/components/Popover.tsx +15 -33
  63. package/apps/lib/components/ProfileMenu.tsx +67 -72
  64. package/apps/lib/components/Radio.tsx +1 -5
  65. package/apps/lib/components/RadioCard.tsx +9 -28
  66. package/apps/lib/components/ScrollArea.tsx +45 -43
  67. package/apps/lib/components/SearchField.tsx +49 -39
  68. package/apps/lib/components/SearchableSelect.tsx +8 -14
  69. package/apps/lib/components/SearchableTable.tsx +108 -118
  70. package/apps/lib/components/SearchableTree.tsx +469 -0
  71. package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
  72. package/apps/lib/components/SectionBlock.tsx +4 -16
  73. package/apps/lib/components/Select.tsx +53 -80
  74. package/apps/lib/components/SimpleSelect.tsx +196 -187
  75. package/apps/lib/components/Skeleton.tsx +9 -12
  76. package/apps/lib/components/SlideDatePicker.tsx +215 -166
  77. package/apps/lib/components/SpinLoading.tsx +33 -49
  78. package/apps/lib/components/Stepper.tsx +13 -56
  79. package/apps/lib/components/Switch.tsx +8 -10
  80. package/apps/lib/components/TabFormItem.tsx +3 -12
  81. package/apps/lib/components/TabSwitch.tsx +7 -34
  82. package/apps/lib/components/Table.tsx +60 -75
  83. package/apps/lib/components/TableDnDWrapper.ts +15 -48
  84. package/apps/lib/components/TextEditor.tsx +93 -104
  85. package/apps/lib/components/Textarea.tsx +8 -12
  86. package/apps/lib/components/Timeline.tsx +21 -41
  87. package/apps/lib/components/Toast.tsx +83 -83
  88. package/apps/lib/components/Toggle.tsx +112 -114
  89. package/apps/lib/components/ToggleButton.tsx +1 -1
  90. package/apps/lib/components/Tooltip.tsx +24 -19
  91. package/apps/lib/components/TransparentLabel.tsx +61 -62
  92. package/apps/lib/components/TreeDropDown.tsx +86 -52
  93. package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
  94. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
  95. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
  96. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
  97. package/apps/lib/components/TreeFolder/icons.tsx +14 -17
  98. package/apps/lib/components/TreeFolder/index.ts +15 -8
  99. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
  100. package/apps/lib/components/TreeFolder/types.ts +32 -32
  101. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
  102. package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
  103. package/apps/lib/hooks/useClickOutside.ts +21 -20
  104. package/apps/lib/hooks/useDataViewsState.ts +86 -80
  105. package/apps/lib/hooks/useIsMobile.ts +12 -12
  106. package/apps/lib/hooks/useResize.ts +54 -65
  107. package/apps/lib/hooks/useTagSelection.ts +201 -201
  108. package/apps/lib/layouts/CNLayout.tsx +181 -140
  109. package/apps/lib/layouts/DataViewCard.tsx +4 -18
  110. package/apps/lib/layouts/FieldSection.tsx +2 -6
  111. package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
  112. package/apps/lib/providers/ThemeProvider.tsx +8 -23
  113. package/apps/lib/registry.json +1004 -0
  114. package/apps/lib/tsconfig.json +9 -0
  115. package/apps/lib/utils/cn.ts +1 -1
  116. package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
  117. package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
  118. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
  119. package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
  120. package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
  121. package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
  122. package/apps/lib/utils/dateFormat.ts +64 -54
  123. package/apps/lib/utils/markdownParser.ts +2 -7
  124. package/apps/lib/utils/resize.ts +21 -25
  125. package/apps/lib/utils/types.ts +13 -13
  126. package/dist/bin/index.js +8 -2
  127. package/dist/bin/index.js.map +1 -1
  128. package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
  129. package/dist/src/shared/getAvailableFiles.js +6 -1
  130. package/dist/src/shared/getAvailableFiles.js.map +1 -1
  131. package/dist/src/shared/loadRegistry.d.ts +7 -0
  132. package/dist/src/shared/loadRegistry.d.ts.map +1 -0
  133. package/dist/src/shared/loadRegistry.js +31 -0
  134. package/dist/src/shared/loadRegistry.js.map +1 -0
  135. package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
  136. package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
  137. package/dist/src/shared/resolveInstallPlan.js +39 -0
  138. package/dist/src/shared/resolveInstallPlan.js.map +1 -0
  139. package/dist/src/types/main.d.ts +24 -0
  140. package/dist/src/types/main.d.ts.map +1 -1
  141. package/docs/README.md +10 -2
  142. package/docs/components/action-button.md +30 -24
  143. package/docs/components/actions-group.md +26 -20
  144. package/docs/components/alert-dialog.md +45 -45
  145. package/docs/components/avatar.md +6 -2
  146. package/docs/components/badge-field.md +6 -2
  147. package/docs/components/badge.md +7 -1
  148. package/docs/components/breadcrumb.md +15 -40
  149. package/docs/components/button-group.md +13 -10
  150. package/docs/components/button.md +58 -33
  151. package/docs/components/calendar.md +26 -19
  152. package/docs/components/card.md +19 -16
  153. package/docs/components/chart-block-tool.md +128 -0
  154. package/docs/components/checkbox.md +16 -8
  155. package/docs/components/cn-layout.md +7 -1
  156. package/docs/components/context-menu.md +18 -25
  157. package/docs/components/count-badge.md +7 -1
  158. package/docs/components/data-table.md +45 -33
  159. package/docs/components/data-views-config-panel.md +15 -11
  160. package/docs/components/data-views-layout.md +13 -16
  161. package/docs/components/date-picker.md +30 -23
  162. package/docs/components/dialog.md +43 -42
  163. package/docs/components/divider.md +10 -4
  164. package/docs/components/drawer.md +8 -33
  165. package/docs/components/dropdown-menu.md +25 -34
  166. package/docs/components/field-hint.md +37 -25
  167. package/docs/components/field-section.md +9 -3
  168. package/docs/components/form-stepper.md +6 -0
  169. package/docs/components/form.md +17 -20
  170. package/docs/components/header-bar.md +6 -0
  171. package/docs/components/image-attachment.md +33 -41
  172. package/docs/components/inbox-view.md +16 -5
  173. package/docs/components/inner-label-field.md +34 -24
  174. package/docs/components/input-field.md +18 -10
  175. package/docs/components/input-otp.md +14 -11
  176. package/docs/components/input.md +22 -16
  177. package/docs/components/kanban-view.md +15 -5
  178. package/docs/components/label-field.md +32 -22
  179. package/docs/components/label.md +43 -32
  180. package/docs/components/labeled-check-box.md +16 -8
  181. package/docs/components/labeled-radio.md +18 -11
  182. package/docs/components/link-button.md +7 -1
  183. package/docs/components/login-button.md +8 -2
  184. package/docs/components/password-level.md +37 -24
  185. package/docs/components/popover.md +26 -23
  186. package/docs/components/profile-menu.md +38 -27
  187. package/docs/components/radio-card.md +16 -9
  188. package/docs/components/radio.md +16 -8
  189. package/docs/components/scroll-area.md +6 -2
  190. package/docs/components/search-field.md +14 -6
  191. package/docs/components/searchable-select.md +19 -14
  192. package/docs/components/searchable-table.md +18 -13
  193. package/docs/components/searchable-tree-dialog.md +190 -0
  194. package/docs/components/searchable-tree.md +200 -0
  195. package/docs/components/section-block.md +12 -4
  196. package/docs/components/select.md +23 -18
  197. package/docs/components/simple-select.md +14 -10
  198. package/docs/components/skeleton.md +43 -33
  199. package/docs/components/slide-date-picker.md +37 -29
  200. package/docs/components/spin-loading.md +32 -23
  201. package/docs/components/stepper.md +6 -0
  202. package/docs/components/switch.md +14 -6
  203. package/docs/components/tab-form-item.md +7 -1
  204. package/docs/components/tab-switch.md +13 -6
  205. package/docs/components/table-dnd-wrapper.md +105 -0
  206. package/docs/components/table-view.md +15 -5
  207. package/docs/components/table.md +28 -28
  208. package/docs/components/text-editor.md +19 -13
  209. package/docs/components/textarea.md +15 -7
  210. package/docs/components/timeline.md +6 -0
  211. package/docs/components/toast.md +35 -26
  212. package/docs/components/toggle-button.md +24 -21
  213. package/docs/components/toggle.md +23 -15
  214. package/docs/components/tooltip.md +42 -30
  215. package/docs/components/transparent-label.md +29 -20
  216. package/docs/components/tree-drop-down.md +31 -23
  217. package/docs/components/tree-sub-layout.md +6 -2
  218. package/docs/components/tree-view.md +16 -5
  219. package/docs/explanation/architecture.md +54 -0
  220. package/docs/explanation/design-system.md +52 -0
  221. package/docs/how-to/data-views-from-backend-response.md +6 -4
  222. package/docs/how-to/guides.md +24 -18
  223. package/docs/migration/changelog.md +38 -0
  224. package/docs/reference/cli.md +87 -0
  225. package/docs/reference/components.md +11 -12
  226. package/docs/reference/hooks.md +8 -8
  227. package/docs/reference/providers.md +22 -22
  228. package/docs/reference/tailwind-plugins.md +2 -2
  229. package/docs/reference/theme.md +85 -0
  230. package/docs/reference/types.md +4 -4
  231. package/docs/reference/utilities.md +19 -26
  232. package/docs/tutorials/building-first-form.md +20 -13
  233. package/docs/tutorials/component-composition.md +34 -50
  234. package/docs/tutorials/getting-started.md +2 -2
  235. package/docs/tutorials/theming-basics.md +13 -10
  236. package/package.json +31 -3
  237. package/dist/src/commands/block.d.ts +0 -5
  238. package/dist/src/commands/block.d.ts.map +0 -1
  239. package/dist/src/commands/block.js +0 -255
  240. package/dist/src/commands/block.js.map +0 -1
  241. package/dist/src/shared/configureFonts.d.ts +0 -6
  242. package/dist/src/shared/configureFonts.d.ts.map +0 -1
  243. package/dist/src/shared/configureFonts.js +0 -106
  244. package/dist/src/shared/configureFonts.js.map +0 -1
  245. package/dist/src/shared/configureGlobalCss.d.ts +0 -6
  246. package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
  247. package/dist/src/shared/configureGlobalCss.js +0 -154
  248. package/dist/src/shared/configureGlobalCss.js.map +0 -1
  249. package/dist/src/shared/configureTailwind.d.ts +0 -7
  250. package/dist/src/shared/configureTailwind.d.ts.map +0 -1
  251. package/dist/src/shared/configureTailwind.js +0 -163
  252. package/dist/src/shared/configureTailwind.js.map +0 -1
  253. package/dist/src/shared/detectFramework.d.ts +0 -23
  254. package/dist/src/shared/detectFramework.d.ts.map +0 -1
  255. package/dist/src/shared/detectFramework.js +0 -119
  256. package/dist/src/shared/detectFramework.js.map +0 -1
  257. package/dist/src/shared/installBaseUtils.d.ts +0 -5
  258. package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
  259. package/dist/src/shared/installBaseUtils.js +0 -79
  260. package/dist/src/shared/installBaseUtils.js.map +0 -1
  261. package/dist/src/shared/resolveAliases.d.ts +0 -24
  262. package/dist/src/shared/resolveAliases.d.ts.map +0 -1
  263. package/dist/src/shared/resolveAliases.js +0 -98
  264. package/dist/src/shared/resolveAliases.js.map +0 -1
@@ -1,11 +1,11 @@
1
- import type { TreeFolderBreadcrumb, TreeFolderMoveArgs, TreeFolderNode } from "./types"
1
+ import type { TreeFolderBreadcrumb, TreeFolderMoveArgs, TreeFolderNode } from "./types";
2
2
 
3
3
  export function findPath(roots: TreeFolderNode[], id: string): TreeFolderNode[] | null {
4
4
  for (const root of roots) {
5
- const trail = walkPath(root, id, [])
6
- if (trail) return trail
5
+ const trail = walkPath(root, id, []);
6
+ if (trail) return trail;
7
7
  }
8
- return null
8
+ return null;
9
9
  }
10
10
 
11
11
  function walkPath(
@@ -13,102 +13,97 @@ function walkPath(
13
13
  id: string,
14
14
  trail: TreeFolderNode[],
15
15
  ): TreeFolderNode[] | null {
16
- const next = [...trail, node]
17
- if (node.id === id) return next
16
+ const next = [...trail, node];
17
+ if (node.id === id) return next;
18
18
  if (node.children) {
19
19
  for (const c of node.children) {
20
- const found = walkPath(c, id, next)
21
- if (found) return found
20
+ const found = walkPath(c, id, next);
21
+ if (found) return found;
22
22
  }
23
23
  }
24
- return null
24
+ return null;
25
25
  }
26
26
 
27
27
  export function toBreadcrumb(path: TreeFolderNode[]): TreeFolderBreadcrumb {
28
- return path.map((n) => ({ id: n.id, name: n.name }))
28
+ return path.map((n) => ({ id: n.id, name: n.name }));
29
29
  }
30
30
 
31
31
  export function findNode(roots: TreeFolderNode[], id: string): TreeFolderNode | null {
32
32
  for (const root of roots) {
33
- if (root.id === id) return root
33
+ if (root.id === id) return root;
34
34
  if (root.children) {
35
- const inChild = findNode(root.children, id)
36
- if (inChild) return inChild
35
+ const inChild = findNode(root.children, id);
36
+ if (inChild) return inChild;
37
37
  }
38
38
  }
39
- return null
39
+ return null;
40
40
  }
41
41
 
42
42
  export function isAncestor(parent: TreeFolderNode, id: string): boolean {
43
- if (parent.id === id) return true
44
- if (!parent.children) return false
43
+ if (parent.id === id) return true;
44
+ if (!parent.children) return false;
45
45
  for (const c of parent.children) {
46
- if (isAncestor(c, id)) return true
46
+ if (isAncestor(c, id)) return true;
47
47
  }
48
- return false
48
+ return false;
49
49
  }
50
50
 
51
51
  export function descendantIds(node: TreeFolderNode, includeSelf = false): Set<string> {
52
- const out = new Set<string>()
53
- if (includeSelf) out.add(node.id)
52
+ const out = new Set<string>();
53
+ if (includeSelf) out.add(node.id);
54
54
  const walk = (n: TreeFolderNode) => {
55
- if (!n.children) return
55
+ if (!n.children) return;
56
56
  for (const c of n.children) {
57
- out.add(c.id)
58
- walk(c)
57
+ out.add(c.id);
58
+ walk(c);
59
59
  }
60
- }
61
- walk(node)
62
- return out
60
+ };
61
+ walk(node);
62
+ return out;
63
63
  }
64
64
 
65
- export function applyMove(
66
- roots: TreeFolderNode[],
67
- args: TreeFolderMoveArgs,
68
- ): TreeFolderNode[] {
69
- const dragSet = new Set(args.dragIds)
65
+ export function applyMove(roots: TreeFolderNode[], args: TreeFolderMoveArgs): TreeFolderNode[] {
66
+ const dragSet = new Set(args.dragIds);
70
67
 
71
68
  if (args.parentId) {
72
69
  for (const id of args.dragIds) {
73
- const node = findNode(roots, id)
74
- if (node && isAncestor(node, args.parentId)) return roots
70
+ const node = findNode(roots, id);
71
+ if (node && isAncestor(node, args.parentId)) return roots;
75
72
  }
76
73
  }
77
74
 
78
- const extracted: TreeFolderNode[] = []
75
+ const extracted: TreeFolderNode[] = [];
79
76
 
80
77
  const extract = (records: TreeFolderNode[]): TreeFolderNode[] =>
81
78
  records
82
79
  .map((n) => {
83
- const next: TreeFolderNode = n.children
84
- ? { ...n, children: extract(n.children) }
85
- : n
80
+ const next: TreeFolderNode = n.children ? { ...n, children: extract(n.children) } : n;
86
81
  if (dragSet.has(n.id)) {
87
- extracted.push(next)
88
- return null
82
+ extracted.push(next);
83
+ return null;
89
84
  }
90
- return next
85
+ return next;
91
86
  })
92
- .filter((n): n is TreeFolderNode => n !== null)
87
+ .filter((n): n is TreeFolderNode => n !== null);
93
88
 
94
- const pruned = extract(roots)
89
+ const pruned = extract(roots);
95
90
 
96
91
  if (args.parentId == null) {
97
- const at = Math.max(0, Math.min(args.index, pruned.length))
98
- return [...pruned.slice(0, at), ...extracted, ...pruned.slice(at)]
92
+ const at = Math.max(0, Math.min(args.index, pruned.length));
93
+ return [...pruned.slice(0, at), ...extracted, ...pruned.slice(at)];
99
94
  }
100
95
 
101
96
  const insert = (records: TreeFolderNode[]): TreeFolderNode[] =>
102
97
  records.map((n) => {
103
98
  if (n.id === args.parentId) {
104
- const kids = n.children ? [...n.children] : []
105
- const at = Math.max(0, Math.min(args.index, kids.length))
106
- kids.splice(at, 0, ...extracted)
107
- return { ...n, children: kids }
99
+ const kids = n.children ? [...n.children] : [];
100
+ const at = Math.max(0, Math.min(args.index, kids.length));
101
+ kids.splice(at, 0, ...extracted);
102
+ return { ...n, children: kids };
108
103
  }
109
- if (n.children) return { ...n, children: insert(n.children) }
110
- return n
111
- })
104
+ if (n.children) return { ...n, children: insert(n.children) };
105
+ return n;
106
+ });
112
107
 
113
- return insert(pruned)
108
+ return insert(pruned);
114
109
  }
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react"
1
+ import type { ReactNode } from "react";
2
2
 
3
3
  export type TreeFolderNodeType =
4
4
  | "folder"
@@ -13,49 +13,49 @@ export type TreeFolderNodeType =
13
13
  | "link"
14
14
  | "section"
15
15
  | "container"
16
- | (string & {})
16
+ | (string & Record<never, never>);
17
17
 
18
18
  export type TreeFolderNode = {
19
- id: string
20
- name: string
21
- type?: TreeFolderNodeType
22
- icon?: ReactNode
23
- meta?: ReactNode
24
- disabled?: boolean
25
- draggable?: boolean
26
- droppable?: boolean
27
- data?: unknown
28
- children?: TreeFolderNode[]
29
- }
19
+ id: string;
20
+ name: string;
21
+ type?: TreeFolderNodeType;
22
+ icon?: ReactNode;
23
+ meta?: ReactNode;
24
+ disabled?: boolean;
25
+ draggable?: boolean;
26
+ droppable?: boolean;
27
+ data?: unknown;
28
+ children?: TreeFolderNode[];
29
+ };
30
30
 
31
31
  export type TreeFolderMoveArgs = {
32
- dragIds: string[]
33
- parentId: string | null
34
- index: number
35
- }
32
+ dragIds: string[];
33
+ parentId: string | null;
34
+ index: number;
35
+ };
36
36
 
37
37
  export type TreeFolderIconResolver = (
38
38
  node: TreeFolderNode,
39
39
  state: { isOpen: boolean; isInternal: boolean; isSelected: boolean },
40
- ) => ReactNode
40
+ ) => ReactNode;
41
41
 
42
- export type TreeFolderBreadcrumb = Array<{ id: string; name: string }>
42
+ export type TreeFolderBreadcrumb = Array<{ id: string; name: string }>;
43
43
 
44
44
  // ---------------------------------------------------------------------------
45
45
  // Visible-row + DnD primitives.
46
46
  // ---------------------------------------------------------------------------
47
47
 
48
48
  export type TreeFolderVisibleRow = {
49
- node: TreeFolderNode
50
- level: number
49
+ node: TreeFolderNode;
50
+ level: number;
51
51
  /** Resolved parent id (null for roots). */
52
- parentId: string | null
52
+ parentId: string | null;
53
53
  /** Position of this node among its siblings in the source tree (0-based). */
54
- childIndex: number
54
+ childIndex: number;
55
55
  /** True when the node is internal AND open. */
56
- isOpen: boolean
56
+ isOpen: boolean;
57
57
  /** True for internal nodes (any node with non-empty children). */
58
- isInternal: boolean
58
+ isInternal: boolean;
59
59
  /**
60
60
  * Connector-line geometry. `isLastChild` toggles the row's own bend between L
61
61
  * (last) and T (non-last). `ancestorHasMoreSiblings[d]` is true when the
@@ -63,15 +63,15 @@ export type TreeFolderVisibleRow = {
63
63
  * controls whether the vertical guide renders in that ancestor's gutter.
64
64
  * Length === `level`.
65
65
  */
66
- isLastChild: boolean
67
- ancestorHasMoreSiblings: boolean[]
68
- }
66
+ isLastChild: boolean;
67
+ ancestorHasMoreSiblings: boolean[];
68
+ };
69
69
 
70
70
  /** Where the pointer is dropping relative to the hovered row. */
71
- export type TreeFolderDropPosition = "before" | "after" | "inside"
71
+ export type TreeFolderDropPosition = "before" | "after" | "inside";
72
72
 
73
73
  export type TreeFolderDropTarget = {
74
74
  /** The id whose `parentId` the drop will resolve to. */
75
- rowId: string
76
- position: TreeFolderDropPosition
77
- }
75
+ rowId: string;
76
+ position: TreeFolderDropPosition;
77
+ };
@@ -1,50 +1,46 @@
1
- "use client"
1
+ "use client";
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from "react"
4
- import { isAncestor } from "./treeFolderUtils"
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { isAncestor } from "./treeFolderUtils";
5
5
  import type {
6
6
  TreeFolderDropPosition,
7
7
  TreeFolderDropTarget,
8
8
  TreeFolderMoveArgs,
9
9
  TreeFolderNode,
10
10
  TreeFolderVisibleRow,
11
- } from "./types"
11
+ } from "./types";
12
12
 
13
- type RowLookup = Map<string, TreeFolderVisibleRow>
13
+ type RowLookup = Map<string, TreeFolderVisibleRow>;
14
14
 
15
- const SCROLL_EDGE_PX = 32
16
- const SCROLL_SPEED_PX_PER_FRAME = 8
17
- const DRAG_DATA_MIME = "application/x-treefolder-ids"
15
+ const SCROLL_EDGE_PX = 32;
16
+ const SCROLL_SPEED_PX_PER_FRAME = 8;
17
+ const DRAG_DATA_MIME = "application/x-treefolder-ids";
18
18
 
19
19
  export type UseTreeFolderDnDOptions = {
20
- data: TreeFolderNode[]
21
- rowsById: RowLookup
22
- scrollContainerRef: React.RefObject<HTMLDivElement | null>
23
- enabled: boolean
24
- onMove?: (args: TreeFolderMoveArgs) => void
20
+ data: TreeFolderNode[];
21
+ rowsById: RowLookup;
22
+ scrollContainerRef: React.RefObject<HTMLDivElement | null>;
23
+ enabled: boolean;
24
+ onMove?: (args: TreeFolderMoveArgs) => void;
25
25
  /**
26
26
  * Predicate. Return false to forbid the drop. Defaults to allow except for
27
27
  * dropping a node into itself or a descendant.
28
28
  */
29
- canDrop?: (args: {
30
- dragIds: string[]
31
- parentId: string | null
32
- index: number
33
- }) => boolean
34
- }
29
+ canDrop?: (args: { dragIds: string[]; parentId: string | null; index: number }) => boolean;
30
+ };
35
31
 
36
32
  export type UseTreeFolderDnDResult = {
37
- dragIds: string[]
38
- dropTarget: TreeFolderDropTarget | null
33
+ dragIds: string[];
34
+ dropTarget: TreeFolderDropTarget | null;
39
35
  getRowDragHandlers: (rowId: string) => {
40
- draggable: boolean
41
- onDragStart: (e: React.DragEvent<HTMLElement>) => void
42
- onDragEnd: (e: React.DragEvent<HTMLElement>) => void
43
- onDragOver: (e: React.DragEvent<HTMLElement>) => void
44
- onDragLeave: (e: React.DragEvent<HTMLElement>) => void
45
- onDrop: (e: React.DragEvent<HTMLElement>) => void
46
- }
47
- }
36
+ draggable: boolean;
37
+ onDragStart: (e: React.DragEvent<HTMLElement>) => void;
38
+ onDragEnd: (e: React.DragEvent<HTMLElement>) => void;
39
+ onDragOver: (e: React.DragEvent<HTMLElement>) => void;
40
+ onDragLeave: (e: React.DragEvent<HTMLElement>) => void;
41
+ onDrop: (e: React.DragEvent<HTMLElement>) => void;
42
+ };
43
+ };
48
44
 
49
45
  export function useTreeFolderDnD({
50
46
  data,
@@ -54,108 +50,108 @@ export function useTreeFolderDnD({
54
50
  onMove,
55
51
  canDrop,
56
52
  }: UseTreeFolderDnDOptions): UseTreeFolderDnDResult {
57
- const [dragIds, setDragIds] = useState<string[]>([])
58
- const [dropTarget, setDropTarget] = useState<TreeFolderDropTarget | null>(null)
53
+ const [dragIds, setDragIds] = useState<string[]>([]);
54
+ const [dropTarget, setDropTarget] = useState<TreeFolderDropTarget | null>(null);
59
55
 
60
56
  // Auto-scroll loop while the user is dragging near a container edge.
61
- const autoScrollDeltaRef = useRef(0)
62
- const autoScrollRafRef = useRef<number | null>(null)
57
+ const autoScrollDeltaRef = useRef(0);
58
+ const autoScrollRafRef = useRef<number | null>(null);
63
59
 
64
60
  const stopAutoScroll = useCallback(() => {
65
- autoScrollDeltaRef.current = 0
61
+ autoScrollDeltaRef.current = 0;
66
62
  if (autoScrollRafRef.current != null) {
67
- cancelAnimationFrame(autoScrollRafRef.current)
68
- autoScrollRafRef.current = null
63
+ cancelAnimationFrame(autoScrollRafRef.current);
64
+ autoScrollRafRef.current = null;
69
65
  }
70
- }, [])
66
+ }, []);
71
67
 
72
68
  const tickAutoScroll = useCallback(() => {
73
- const el = scrollContainerRef.current
74
- const delta = autoScrollDeltaRef.current
69
+ const el = scrollContainerRef.current;
70
+ const delta = autoScrollDeltaRef.current;
75
71
  if (!el || delta === 0) {
76
- autoScrollRafRef.current = null
77
- return
72
+ autoScrollRafRef.current = null;
73
+ return;
78
74
  }
79
- el.scrollTop += delta
80
- autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll)
81
- }, [scrollContainerRef])
75
+ el.scrollTop += delta;
76
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
77
+ }, [scrollContainerRef]);
82
78
 
83
79
  const maybeAutoScroll = useCallback(
84
80
  (clientY: number) => {
85
- const el = scrollContainerRef.current
86
- if (!el) return
87
- const rect = el.getBoundingClientRect()
88
- let delta = 0
89
- if (clientY - rect.top < SCROLL_EDGE_PX) delta = -SCROLL_SPEED_PX_PER_FRAME
90
- else if (rect.bottom - clientY < SCROLL_EDGE_PX) delta = SCROLL_SPEED_PX_PER_FRAME
91
- autoScrollDeltaRef.current = delta
81
+ const el = scrollContainerRef.current;
82
+ if (!el) return;
83
+ const rect = el.getBoundingClientRect();
84
+ let delta = 0;
85
+ if (clientY - rect.top < SCROLL_EDGE_PX) delta = -SCROLL_SPEED_PX_PER_FRAME;
86
+ else if (rect.bottom - clientY < SCROLL_EDGE_PX) delta = SCROLL_SPEED_PX_PER_FRAME;
87
+ autoScrollDeltaRef.current = delta;
92
88
  if (delta !== 0 && autoScrollRafRef.current == null) {
93
- autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll)
89
+ autoScrollRafRef.current = requestAnimationFrame(tickAutoScroll);
94
90
  } else if (delta === 0) {
95
- stopAutoScroll()
91
+ stopAutoScroll();
96
92
  }
97
93
  },
98
94
  [scrollContainerRef, stopAutoScroll, tickAutoScroll],
99
- )
95
+ );
100
96
 
101
- useEffect(() => stopAutoScroll, [stopAutoScroll])
97
+ useEffect(() => stopAutoScroll, [stopAutoScroll]);
102
98
 
103
99
  const computePosition = useCallback(
104
100
  (target: HTMLElement, clientY: number, isInternal: boolean): TreeFolderDropPosition => {
105
- const rect = target.getBoundingClientRect()
106
- const offset = clientY - rect.top
107
- const ratio = offset / rect.height
101
+ const rect = target.getBoundingClientRect();
102
+ const offset = clientY - rect.top;
103
+ const ratio = offset / rect.height;
108
104
  // Leaves can't accept "inside" drops — squash that zone into before/after.
109
- if (!isInternal) return ratio < 0.5 ? "before" : "after"
110
- if (ratio < 0.25) return "before"
111
- if (ratio > 0.75) return "after"
112
- return "inside"
105
+ if (!isInternal) return ratio < 0.5 ? "before" : "after";
106
+ if (ratio < 0.25) return "before";
107
+ if (ratio > 0.75) return "after";
108
+ return "inside";
113
109
  },
114
110
  [],
115
- )
111
+ );
116
112
 
117
113
  const resolveMoveArgs = useCallback(
118
114
  (drag: string[], drop: TreeFolderDropTarget): TreeFolderMoveArgs | null => {
119
- const targetRow = rowsById.get(drop.rowId)
120
- if (!targetRow) return null
115
+ const targetRow = rowsById.get(drop.rowId);
116
+ if (!targetRow) return null;
121
117
 
122
- let parentId: string | null
123
- let index: number
118
+ let parentId: string | null;
119
+ let index: number;
124
120
 
125
121
  if (drop.position === "inside") {
126
- parentId = targetRow.node.id
122
+ parentId = targetRow.node.id;
127
123
  // Drop at the end of the target's children.
128
- index = targetRow.node.children?.length ?? 0
124
+ index = targetRow.node.children?.length ?? 0;
129
125
  } else {
130
- parentId = targetRow.parentId
131
- index = drop.position === "before" ? targetRow.childIndex : targetRow.childIndex + 1
126
+ parentId = targetRow.parentId;
127
+ index = drop.position === "before" ? targetRow.childIndex : targetRow.childIndex + 1;
132
128
  }
133
129
 
134
130
  // Reject: dropping into self or any descendant.
135
131
  for (const id of drag) {
136
- const node = rowsById.get(id)?.node
137
- if (!node) continue
138
- if (parentId && (node.id === parentId || isAncestor(node, parentId))) return null
132
+ const node = rowsById.get(id)?.node;
133
+ if (!node) continue;
134
+ if (parentId && (node.id === parentId || isAncestor(node, parentId))) return null;
139
135
  }
140
136
 
141
- if (canDrop && !canDrop({ dragIds: drag, parentId, index })) return null
137
+ if (canDrop && !canDrop({ dragIds: drag, parentId, index })) return null;
142
138
 
143
139
  // No-op safety: skipping the equivalent of "drop where you already are".
144
140
  if (drag.length === 1) {
145
- const onlyDrag = rowsById.get(drag[0])
141
+ const onlyDrag = rowsById.get(drag[0]);
146
142
  if (
147
143
  onlyDrag &&
148
144
  onlyDrag.parentId === parentId &&
149
145
  (onlyDrag.childIndex === index || onlyDrag.childIndex + 1 === index)
150
146
  ) {
151
- return null
147
+ return null;
152
148
  }
153
149
  }
154
150
 
155
- return { dragIds: drag, parentId, index }
151
+ return { dragIds: drag, parentId, index };
156
152
  },
157
153
  [rowsById, canDrop],
158
- )
154
+ );
159
155
 
160
156
  const getRowDragHandlers = useCallback(
161
157
  (rowId: string) => {
@@ -167,77 +163,75 @@ export function useTreeFolderDnD({
167
163
  onDragOver: () => {},
168
164
  onDragLeave: () => {},
169
165
  onDrop: () => {},
170
- }
166
+ };
171
167
  }
172
168
  return {
173
169
  draggable: true,
174
170
  onDragStart: (e: React.DragEvent<HTMLElement>) => {
175
- const ids = [rowId]
176
- setDragIds(ids)
177
- e.dataTransfer.effectAllowed = "move"
171
+ const ids = [rowId];
172
+ setDragIds(ids);
173
+ e.dataTransfer.effectAllowed = "move";
178
174
  try {
179
- e.dataTransfer.setData(DRAG_DATA_MIME, ids.join(","))
175
+ e.dataTransfer.setData(DRAG_DATA_MIME, ids.join(","));
180
176
  // Required for Firefox to initiate the drag at all.
181
- e.dataTransfer.setData("text/plain", ids.join(","))
177
+ e.dataTransfer.setData("text/plain", ids.join(","));
182
178
  } catch {
183
179
  // setData can throw in some sandboxed contexts; ignore.
184
180
  }
185
181
  },
186
182
  onDragEnd: () => {
187
- setDragIds([])
188
- setDropTarget(null)
189
- stopAutoScroll()
183
+ setDragIds([]);
184
+ setDropTarget(null);
185
+ stopAutoScroll();
190
186
  },
191
187
  onDragOver: (e: React.DragEvent<HTMLElement>) => {
192
- if (dragIds.length === 0) return
188
+ if (dragIds.length === 0) return;
193
189
  // Reject self-drops upfront so the cursor reflects "no drop" outside the tree.
194
190
  if (dragIds.includes(rowId)) {
195
- e.dataTransfer.dropEffect = "none"
196
- return
191
+ e.dataTransfer.dropEffect = "none";
192
+ return;
197
193
  }
198
- const row = rowsById.get(rowId)
199
- if (!row) return
194
+ const row = rowsById.get(rowId);
195
+ if (!row) return;
200
196
  // Reject dropping onto a descendant of any dragged node.
201
197
  for (const dragId of dragIds) {
202
- const dragNode = rowsById.get(dragId)?.node
198
+ const dragNode = rowsById.get(dragId)?.node;
203
199
  if (dragNode && isAncestor(dragNode, rowId)) {
204
- e.dataTransfer.dropEffect = "none"
205
- return
200
+ e.dataTransfer.dropEffect = "none";
201
+ return;
206
202
  }
207
203
  }
208
- e.preventDefault()
209
- e.dataTransfer.dropEffect = "move"
210
- const position = computePosition(e.currentTarget, e.clientY, row.isInternal)
204
+ e.preventDefault();
205
+ e.dataTransfer.dropEffect = "move";
206
+ const position = computePosition(e.currentTarget, e.clientY, row.isInternal);
211
207
  // Only setState when something actually changed (avoids re-render storms).
212
208
  setDropTarget((prev) =>
213
- prev && prev.rowId === rowId && prev.position === position
214
- ? prev
215
- : { rowId, position },
216
- )
217
- maybeAutoScroll(e.clientY)
209
+ prev && prev.rowId === rowId && prev.position === position ? prev : { rowId, position },
210
+ );
211
+ maybeAutoScroll(e.clientY);
218
212
  },
219
213
  onDragLeave: () => {
220
214
  // No-op: onDragOver on the next row will overwrite dropTarget. Clearing
221
215
  // here would flicker the indicator during normal row-to-row hovering.
222
216
  },
223
217
  onDrop: (e: React.DragEvent<HTMLElement>) => {
224
- if (dragIds.length === 0) return
225
- e.preventDefault()
226
- const row = rowsById.get(rowId)
218
+ if (dragIds.length === 0) return;
219
+ e.preventDefault();
220
+ const row = rowsById.get(rowId);
227
221
  if (!row) {
228
- setDragIds([])
229
- setDropTarget(null)
230
- stopAutoScroll()
231
- return
222
+ setDragIds([]);
223
+ setDropTarget(null);
224
+ stopAutoScroll();
225
+ return;
232
226
  }
233
- const position = computePosition(e.currentTarget, e.clientY, row.isInternal)
234
- const moveArgs = resolveMoveArgs(dragIds, { rowId, position })
235
- if (moveArgs) onMove?.(moveArgs)
236
- setDragIds([])
237
- setDropTarget(null)
238
- stopAutoScroll()
227
+ const position = computePosition(e.currentTarget, e.clientY, row.isInternal);
228
+ const moveArgs = resolveMoveArgs(dragIds, { rowId, position });
229
+ if (moveArgs) onMove?.(moveArgs);
230
+ setDragIds([]);
231
+ setDropTarget(null);
232
+ stopAutoScroll();
239
233
  },
240
- }
234
+ };
241
235
  },
242
236
  [
243
237
  enabled,
@@ -249,13 +243,13 @@ export function useTreeFolderDnD({
249
243
  maybeAutoScroll,
250
244
  stopAutoScroll,
251
245
  ],
252
- )
246
+ );
253
247
 
254
248
  // If the data tree changes mid-drag, clear stale targets.
255
249
  useEffect(() => {
256
- if (dragIds.length === 0) return
257
- setDropTarget((prev) => (prev && rowsById.has(prev.rowId) ? prev : null))
258
- }, [data, rowsById, dragIds.length])
250
+ if (dragIds.length === 0) return;
251
+ setDropTarget((prev) => (prev && rowsById.has(prev.rowId) ? prev : null));
252
+ }, [data, rowsById, dragIds.length]);
259
253
 
260
- return { dragIds, dropTarget, getRowDragHandlers }
254
+ return { dragIds, dropTarget, getRowDragHandlers };
261
255
  }