@placeholderco/placeholder-ui 1.0.12 → 2.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 (276) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +86 -135
  99. package/dist/form/Autocomplete.svelte.d.ts +6 -8
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +59 -54
  103. package/dist/form/Checkbox.svelte.d.ts +14 -5
  104. package/dist/form/Chips.svelte +29 -13
  105. package/dist/form/Chips.svelte.d.ts +16 -2
  106. package/dist/form/ComboBox.svelte +30 -20
  107. package/dist/form/ComboBox.svelte.d.ts +1 -1
  108. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  109. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  110. package/dist/form/ComboBoxMulti.svelte +38 -24
  111. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  112. package/dist/form/CronBuilder.svelte +84 -67
  113. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  114. package/dist/form/DatePicker.svelte +148 -195
  115. package/dist/form/DateRangePicker.svelte +101 -98
  116. package/dist/form/DateTimePicker.svelte +167 -208
  117. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  118. package/dist/form/FormGroup.svelte +33 -10
  119. package/dist/form/FormGroup.svelte.d.ts +12 -2
  120. package/dist/form/Number.svelte +70 -47
  121. package/dist/form/Number.svelte.d.ts +36 -6
  122. package/dist/form/Radio.svelte +18 -18
  123. package/dist/form/RadioGroup.svelte +23 -12
  124. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  125. package/dist/form/SegmentedControl.svelte +76 -56
  126. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  127. package/dist/form/Select.svelte +150 -233
  128. package/dist/form/Select.svelte.d.ts +29 -5
  129. package/dist/form/SelectMulti.svelte +163 -277
  130. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  131. package/dist/form/Slider.svelte +183 -269
  132. package/dist/form/Slider.svelte.d.ts +32 -17
  133. package/dist/form/StringArrayBuilder.svelte +249 -0
  134. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  135. package/dist/form/Switch.svelte +164 -106
  136. package/dist/form/Switch.svelte.d.ts +22 -3
  137. package/dist/form/TextArea.svelte +49 -29
  138. package/dist/form/TextArea.svelte.d.ts +23 -3
  139. package/dist/form/Textbox.svelte +69 -68
  140. package/dist/form/Textbox.svelte.d.ts +32 -4
  141. package/dist/form/TimePicker.svelte +42 -47
  142. package/dist/form/TimePicker.svelte.d.ts +12 -2
  143. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  144. package/dist/form/comboBoxCore.svelte.js +243 -0
  145. package/dist/icon/Icon.svelte +8 -3
  146. package/dist/icon/Icon.svelte.d.ts +8 -3
  147. package/dist/icon/file-type-bmp.svg +1 -0
  148. package/dist/icon/file-type-csv.svg +1 -0
  149. package/dist/icon/file-type-doc.svg +1 -0
  150. package/dist/icon/file-type-docx.svg +1 -0
  151. package/dist/icon/file-type-jpg.svg +1 -0
  152. package/dist/icon/file-type-pdf.svg +1 -0
  153. package/dist/icon/file-type-png.svg +1 -0
  154. package/dist/icon/file-type-ppt.svg +1 -0
  155. package/dist/icon/file-type-svg.svg +1 -0
  156. package/dist/icon/file-type-txt.svg +1 -0
  157. package/dist/icon/file-type-xls.svg +1 -0
  158. package/dist/icon/file-type-zip.svg +1 -0
  159. package/dist/icon/index.d.ts +166 -151
  160. package/dist/icon/index.js +166 -152
  161. package/dist/icon/logo-placeholder.svg +6 -0
  162. package/dist/icon/source-code.svg +1 -0
  163. package/dist/icon/sun-moon.svg +1 -0
  164. package/dist/icon/table.svg +6 -0
  165. package/dist/index.d.ts +87 -26
  166. package/dist/index.js +25 -28
  167. package/dist/layout/AppShell.svelte +12 -162
  168. package/dist/layout/AppShell.svelte.d.ts +6 -27
  169. package/dist/layout/CustomNavbar.svelte +9 -7
  170. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  171. package/dist/layout/Navbar.svelte +161 -129
  172. package/dist/layout/Navbar.svelte.d.ts +29 -15
  173. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  174. package/dist/layout/NavbarItemView.svelte +182 -0
  175. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  176. package/dist/layout/Sidenav.svelte +280 -341
  177. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  178. package/dist/models/ComboBoxItem.d.ts +2 -0
  179. package/dist/models/Employee.d.ts +53 -0
  180. package/dist/models/Employee.js +23 -0
  181. package/dist/models/Hyperlink.d.ts +3 -1
  182. package/dist/models/LinkCollectionItem.d.ts +3 -1
  183. package/dist/models/NavbarItem.d.ts +6 -2
  184. package/dist/theme.svelte.d.ts +0 -1
  185. package/dist/theme.svelte.js +46 -58
  186. package/dist/ui/Accordion.svelte +81 -242
  187. package/dist/ui/Accordion.svelte.d.ts +37 -14
  188. package/dist/ui/AccordionItem.svelte +303 -0
  189. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  190. package/dist/ui/ActionIcon.svelte +25 -21
  191. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  192. package/dist/ui/Badge.svelte +163 -171
  193. package/dist/ui/Badge.svelte.d.ts +13 -5
  194. package/dist/ui/Button.svelte +178 -205
  195. package/dist/ui/Button.svelte.d.ts +19 -2
  196. package/dist/ui/ButtonVariant.d.ts +15 -1
  197. package/dist/ui/ContextMenu.svelte +146 -0
  198. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  199. package/dist/ui/ContextMenuList.svelte +188 -0
  200. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  201. package/dist/ui/Dialog.svelte +183 -111
  202. package/dist/ui/Dialog.svelte.d.ts +21 -4
  203. package/dist/ui/Drawer.svelte +237 -184
  204. package/dist/ui/Drawer.svelte.d.ts +20 -2
  205. package/dist/ui/Dropdown.svelte +24 -14
  206. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  207. package/dist/ui/Dropzone.svelte +159 -57
  208. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  209. package/dist/ui/Link.svelte +12 -4
  210. package/dist/ui/Link.svelte.d.ts +8 -2
  211. package/dist/ui/Loader.svelte +23 -18
  212. package/dist/ui/Loader.svelte.d.ts +5 -2
  213. package/dist/ui/Logo.svelte +37 -0
  214. package/dist/ui/Logo.svelte.d.ts +16 -0
  215. package/dist/ui/MultiSortable.svelte +287 -0
  216. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  217. package/dist/ui/Pagination.svelte +15 -9
  218. package/dist/ui/Pagination.svelte.d.ts +6 -2
  219. package/dist/ui/Popover.svelte +43 -65
  220. package/dist/ui/Popover.svelte.d.ts +14 -5
  221. package/dist/ui/ProgressBar.svelte +28 -30
  222. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  223. package/dist/ui/SegmentedControl.svelte +0 -0
  224. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  225. package/dist/ui/Sortable.svelte +163 -0
  226. package/dist/ui/Sortable.svelte.d.ts +47 -0
  227. package/dist/ui/Table.svelte +383 -80
  228. package/dist/ui/Table.svelte.d.ts +43 -2
  229. package/dist/ui/Tabs.svelte +22 -18
  230. package/dist/ui/Tabs.svelte.d.ts +6 -2
  231. package/dist/ui/ThemeSwitcher.svelte +26 -7
  232. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  233. package/dist/ui/Tooltip.svelte +312 -180
  234. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  235. package/dist/uiTheme.svelte.d.ts +86 -0
  236. package/dist/uiTheme.svelte.js +214 -0
  237. package/dist/util/DateFunctions.js +1 -1
  238. package/dist/util/DragDrop.d.ts +50 -0
  239. package/dist/util/DragDrop.js +154 -0
  240. package/dist/util/NavigateTo.js +2 -2
  241. package/dist/util/dayjs.d.ts +4 -0
  242. package/dist/util/dayjs.js +15 -0
  243. package/package.json +66 -26
  244. package/dist/display/Alert.svelte +0 -179
  245. package/dist/display/Alert.svelte.d.ts +0 -13
  246. package/dist/form/FileInput.svelte +0 -235
  247. package/dist/form/FileInput.svelte.d.ts +0 -23
  248. package/dist/form/PasswordInput.svelte +0 -253
  249. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  250. package/dist/form/Rating.svelte +0 -235
  251. package/dist/form/Rating.svelte.d.ts +0 -21
  252. package/dist/styles/components.css +0 -199
  253. package/dist/styles/dark.css +0 -146
  254. package/dist/styles/index.css +0 -116
  255. package/dist/styles/reset.css +0 -110
  256. package/dist/styles/semantic.css +0 -86
  257. package/dist/styles/tokens.css +0 -203
  258. package/dist/styles/utilities.css +0 -523
  259. package/dist/ui/Breadcrumbs.svelte +0 -131
  260. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  261. package/dist/ui/LoadingOverlay.svelte +0 -53
  262. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  263. package/dist/ui/Progress.svelte +0 -191
  264. package/dist/ui/Progress.svelte.d.ts +0 -15
  265. package/dist/ui/RingProgress.svelte +0 -141
  266. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  267. package/dist/ui/Skeleton.svelte +0 -85
  268. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  269. package/dist/ui/Stepper.svelte +0 -355
  270. package/dist/ui/Stepper.svelte.d.ts +0 -24
  271. package/dist/ui/Timeline.svelte +0 -225
  272. package/dist/ui/Timeline.svelte.d.ts +0 -23
  273. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  274. /package/dist/{actions → util}/ClickOutside.js +0 -0
  275. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  276. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -0,0 +1,396 @@
1
+ <script lang="ts">
2
+ import Icon from '../../icon/Icon.svelte';
3
+ import { iconChevronRight, iconFolder, iconFile } from '../../icon/index.js';
4
+ import { draggable, droppable, type DragContext } from '../../util/DragDrop.js';
5
+ import type { FileTreeNode } from './FileTreeNode.js';
6
+ import type { Snippet } from 'svelte';
7
+ import { fly } from 'svelte/transition';
8
+ import Self from './FileTreeItem.svelte';
9
+
10
+ export type DropZone = 'before' | 'inside' | 'after' | null;
11
+
12
+ interface Props {
13
+ node: FileTreeNode;
14
+ depth: number;
15
+ dragType: string;
16
+ selectedId?: string;
17
+ /** When false, the row cannot be dragged and won't accept drops */
18
+ isDraggable?: boolean;
19
+ /** Called when this row is clicked (selection) */
20
+ onSelect?: (node: FileTreeNode) => void;
21
+ /** Called when this row is double-clicked */
22
+ onDoubleClick?: (node: FileTreeNode) => void;
23
+ /** Called when the user wants to expand/collapse a folder */
24
+ onToggle?: (node: FileTreeNode) => void;
25
+ /** Called when a drag drops onto/around this row. Position 'inside' is only valid for folders. */
26
+ onMove?: (sourceId: string, targetId: string, position: 'before' | 'inside' | 'after') => void;
27
+ /** Called when this row is right-clicked */
28
+ onContextMenu?: (node: FileTreeNode, event: MouseEvent) => void;
29
+ /** Optional snippet to customize node label area */
30
+ nodeContent?: Snippet<[FileTreeNode]>;
31
+ /** Optional snippet rendering a trailing action bar for the row */
32
+ nodeActions?: Snippet<[FileTreeNode]>;
33
+ /** When the action bar appears */
34
+ nodeActionsTrigger?: 'selected' | 'hover' | 'always';
35
+ /** Where the action bar sits: pinned to the row end, or beside the label */
36
+ nodeActionsPlacement?: 'end' | 'beside';
37
+ /** When false, folders can't be collapsed and always render expanded */
38
+ collapsible?: boolean;
39
+ /** When true, the label gets a title attribute (native tooltip) with the node name */
40
+ showTitles?: boolean;
41
+ /** True while any row in the tree is being dragged */
42
+ dragging?: boolean;
43
+ /** Notifies the tree when this row starts/ends dragging */
44
+ onDragState?: (dragging: boolean) => void;
45
+ }
46
+
47
+ let {
48
+ node,
49
+ depth,
50
+ dragType,
51
+ selectedId,
52
+ isDraggable = true,
53
+ onSelect,
54
+ onDoubleClick,
55
+ onToggle,
56
+ onMove,
57
+ onContextMenu,
58
+ nodeContent,
59
+ nodeActions,
60
+ nodeActionsTrigger = 'selected',
61
+ nodeActionsPlacement = 'end',
62
+ collapsible = true,
63
+ showTitles = false,
64
+ dragging = false,
65
+ onDragState
66
+ }: Props = $props();
67
+
68
+ let dropZone = $state<DropZone>(null);
69
+ let hovered = $state(false);
70
+ let hoverExpandTimer: ReturnType<typeof setTimeout> | null = null;
71
+
72
+ const showActions = $derived(
73
+ !!nodeActions &&
74
+ !dragging &&
75
+ (nodeActionsTrigger === 'always' ||
76
+ (nodeActionsTrigger === 'selected' && selectedId === node.id) ||
77
+ (nodeActionsTrigger === 'hover' && hovered))
78
+ );
79
+
80
+ const isFolder = $derived(node.type === 'folder');
81
+ const hasChildren = $derived(isFolder && (node.children?.length ?? 0) > 0);
82
+ const expanded = $derived(!collapsible || !!node.expanded);
83
+
84
+ interface TreePayload {
85
+ sourceId: string;
86
+ }
87
+
88
+ function computeZone(e: DragEvent, el: HTMLElement): DropZone {
89
+ const rect = el.getBoundingClientRect();
90
+ const y = e.clientY - rect.top;
91
+ const h = rect.height;
92
+ if (!isFolder) {
93
+ return y < h / 2 ? 'before' : 'after';
94
+ }
95
+ // Folders have three zones: top quarter, middle (into), bottom quarter
96
+ if (y < h * 0.25) return 'before';
97
+ if (y > h * 0.75) return 'after';
98
+ return 'inside';
99
+ }
100
+
101
+ function clearHoverExpand() {
102
+ if (hoverExpandTimer) {
103
+ clearTimeout(hoverExpandTimer);
104
+ hoverExpandTimer = null;
105
+ }
106
+ }
107
+
108
+ function scheduleHoverExpand() {
109
+ if (!isFolder || expanded) return;
110
+ clearHoverExpand();
111
+ hoverExpandTimer = setTimeout(() => {
112
+ onToggle?.(node);
113
+ hoverExpandTimer = null;
114
+ }, 600);
115
+ }
116
+ </script>
117
+
118
+ <div class="ft-node-wrap" style="--depth: {depth}">
119
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
120
+ <div
121
+ class="ft-row-wrap"
122
+ class:selected={selectedId === node.id}
123
+ class:actions-beside={nodeActionsPlacement === 'beside'}
124
+ onmouseenter={() => (hovered = true)}
125
+ onmouseleave={() => (hovered = false)}
126
+ >
127
+ <button
128
+ type="button"
129
+ class="ft-row"
130
+ class:drop-before={dropZone === 'before'}
131
+ class:drop-after={dropZone === 'after'}
132
+ class:drop-inside={dropZone === 'inside'}
133
+ onclick={() => {
134
+ onSelect?.(node);
135
+ // Expand on row click, but never collapse — collapsing is chevron-only.
136
+ if (isFolder && collapsible && !expanded) onToggle?.(node);
137
+ }}
138
+ ondblclick={() => onDoubleClick?.(node)}
139
+ oncontextmenu={(e) => onContextMenu?.(node, e)}
140
+ use:draggable={{
141
+ payload: { sourceId: node.id } satisfies TreePayload,
142
+ type: dragType,
143
+ source: node.id,
144
+ disabled: !isDraggable,
145
+ onDragStart: () => onDragState?.(true),
146
+ onDragEnd: () => {
147
+ dropZone = null;
148
+ clearHoverExpand();
149
+ onDragState?.(false);
150
+ }
151
+ }}
152
+ use:droppable={{
153
+ accepts: dragType,
154
+ disabled: !isDraggable,
155
+ canDrop: (ctx: DragContext<TreePayload>) => ctx.payload.sourceId !== node.id,
156
+ onDragOver: (_, e) => {
157
+ const zone = computeZone(e, e.currentTarget as HTMLElement);
158
+ if (zone !== dropZone) {
159
+ dropZone = zone;
160
+ if (zone === 'inside') scheduleHoverExpand();
161
+ else clearHoverExpand();
162
+ }
163
+ },
164
+ onDragLeave: () => {
165
+ dropZone = null;
166
+ clearHoverExpand();
167
+ },
168
+ onDrop: (ctx: DragContext<TreePayload>) => {
169
+ const zone = dropZone;
170
+ dropZone = null;
171
+ clearHoverExpand();
172
+ if (!zone) return;
173
+ onMove?.(ctx.payload.sourceId, node.id, zone);
174
+ }
175
+ }}
176
+ >
177
+ <span class="ft-indent">
178
+ {#each Array(depth) as _, i (i)}
179
+ <span class="ft-indent-line"></span>
180
+ {/each}
181
+ </span>
182
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
183
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
184
+ <span
185
+ class="ft-chevron"
186
+ class:invisible={!isFolder || !collapsible}
187
+ class:open={expanded}
188
+ onclick={(e) => {
189
+ if (!isFolder || !collapsible) return;
190
+ e.stopPropagation();
191
+ onToggle?.(node);
192
+ }}
193
+ ondblclick={(e) => {
194
+ if (isFolder && collapsible) e.stopPropagation();
195
+ }}
196
+ >
197
+ <Icon svg={iconChevronRight} size="14px" />
198
+ </span>
199
+ <span class="ft-icon">
200
+ <Icon svg={node.icon ?? (isFolder ? iconFolder : iconFile)} size="16px" />
201
+ </span>
202
+ {#if nodeContent}
203
+ {@render nodeContent(node)}
204
+ {:else}
205
+ <span class="ft-label" title={showTitles ? node.name : undefined}>{node.name}</span>
206
+ {/if}
207
+ </button>
208
+
209
+ {#if showActions}
210
+ <div class="ft-actions" transition:fly={{ x: 6, duration: 180 }}>
211
+ {@render nodeActions?.(node)}
212
+ </div>
213
+ {/if}
214
+ </div>
215
+
216
+ {#if isFolder && expanded && node.children}
217
+ <div class="ft-children">
218
+ {#each node.children as child (child.id)}
219
+ <Self
220
+ node={child}
221
+ depth={depth + 1}
222
+ {dragType}
223
+ {selectedId}
224
+ {isDraggable}
225
+ {onSelect}
226
+ {onDoubleClick}
227
+ {onToggle}
228
+ {onMove}
229
+ {onContextMenu}
230
+ {nodeContent}
231
+ {nodeActions}
232
+ {nodeActionsTrigger}
233
+ {nodeActionsPlacement}
234
+ {collapsible}
235
+ {showTitles}
236
+ {dragging}
237
+ {onDragState}
238
+ />
239
+ {/each}
240
+ </div>
241
+ {/if}
242
+ </div>
243
+
244
+ <style>
245
+ .ft-node-wrap {
246
+ display: contents;
247
+ }
248
+
249
+ .ft-row-wrap {
250
+ position: relative;
251
+ display: flex;
252
+ align-items: center;
253
+ border-left: 3px solid transparent;
254
+ border-radius: 0 3px 3px 0;
255
+ transition: background-color 0.15s ease;
256
+ }
257
+
258
+ .ft-row-wrap:hover {
259
+ background-color: rgba(var(--ui-primary-rgbc), 0.08);
260
+ }
261
+
262
+ :global(.dark) .ft-row-wrap:hover {
263
+ background-color: rgba(var(--ui-primary-rgbc), 0.2);
264
+ }
265
+
266
+ .ft-row-wrap.selected {
267
+ background-color: rgba(var(--ui-accent-rgbc), 0.2);
268
+ border-left-color: var(--ui-accent);
269
+ }
270
+
271
+ :global(.dark) .ft-row-wrap.selected {
272
+ background-color: rgba(var(--ui-primary-rgbc), 0.3);
273
+ border-left-color: var(--ui-primary);
274
+ }
275
+
276
+ .ft-row {
277
+ position: relative;
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 0.25rem;
281
+ flex: 1 1 auto;
282
+ min-width: 0;
283
+ padding: 0.375rem 0.5rem;
284
+ background: none;
285
+ border: 0;
286
+ color: var(--text-color);
287
+ font-size: 0.875rem;
288
+ font-weight: 500;
289
+ text-align: left;
290
+ cursor: pointer;
291
+ user-select: none;
292
+ transition: color 0.15s ease;
293
+ }
294
+
295
+ /* 'beside' placement: the row shrinks to its content so the actions sit
296
+ immediately after the label instead of at the far right of the row. */
297
+ .ft-row-wrap.actions-beside .ft-row {
298
+ flex: 0 1 auto;
299
+ }
300
+
301
+ .ft-row-wrap.actions-beside .ft-label {
302
+ flex: 0 1 auto;
303
+ }
304
+
305
+ .ft-actions {
306
+ display: flex;
307
+ align-items: center;
308
+ flex-shrink: 0;
309
+ gap: 0.125rem;
310
+ padding-right: 0.5rem;
311
+ padding-left: 0.25rem;
312
+ }
313
+
314
+ .ft-indent {
315
+ display: inline-flex;
316
+ flex-shrink: 0;
317
+ }
318
+
319
+ .ft-indent-line {
320
+ display: inline-block;
321
+ width: 14px;
322
+ height: 100%;
323
+ border-left: 1px solid var(--border-color);
324
+ margin-right: 2px;
325
+ }
326
+
327
+ .ft-chevron {
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ width: 16px;
332
+ flex-shrink: 0;
333
+ cursor: pointer;
334
+ border-radius: 3px;
335
+ transition: transform 0.12s ease;
336
+ }
337
+
338
+ .ft-chevron:not(.invisible):hover {
339
+ background-color: rgba(var(--ui-primary-rgbc), 0.15);
340
+ }
341
+
342
+ .ft-chevron.open {
343
+ transform: rotate(90deg);
344
+ }
345
+
346
+ .ft-chevron.invisible {
347
+ visibility: hidden;
348
+ }
349
+
350
+ .ft-icon {
351
+ display: inline-flex;
352
+ flex-shrink: 0;
353
+ color: var(--ui-primary);
354
+ }
355
+
356
+ :global(.dark) .ft-icon {
357
+ color: var(--ui-accent);
358
+ }
359
+
360
+ .ft-label {
361
+ flex: 1;
362
+ min-width: 0;
363
+ overflow: hidden;
364
+ text-overflow: ellipsis;
365
+ white-space: nowrap;
366
+ }
367
+
368
+ .ft-row.drop-before::before,
369
+ .ft-row.drop-after::after {
370
+ content: '';
371
+ position: absolute;
372
+ left: 0;
373
+ right: 0;
374
+ height: 2px;
375
+ background: var(--ui-tertiary);
376
+ pointer-events: none;
377
+ }
378
+
379
+ .ft-row.drop-before::before {
380
+ top: -1px;
381
+ }
382
+
383
+ .ft-row.drop-after::after {
384
+ bottom: -1px;
385
+ }
386
+
387
+ .ft-row.drop-inside {
388
+ outline: 2px dashed var(--ui-tertiary);
389
+ outline-offset: -2px;
390
+ }
391
+
392
+ .ft-children {
393
+ display: flex;
394
+ flex-direction: column;
395
+ }
396
+ </style>
@@ -0,0 +1,40 @@
1
+ import type { FileTreeNode } from './FileTreeNode.js';
2
+ import type { Snippet } from 'svelte';
3
+ export type DropZone = 'before' | 'inside' | 'after' | null;
4
+ interface Props {
5
+ node: FileTreeNode;
6
+ depth: number;
7
+ dragType: string;
8
+ selectedId?: string;
9
+ /** When false, the row cannot be dragged and won't accept drops */
10
+ isDraggable?: boolean;
11
+ /** Called when this row is clicked (selection) */
12
+ onSelect?: (node: FileTreeNode) => void;
13
+ /** Called when this row is double-clicked */
14
+ onDoubleClick?: (node: FileTreeNode) => void;
15
+ /** Called when the user wants to expand/collapse a folder */
16
+ onToggle?: (node: FileTreeNode) => void;
17
+ /** Called when a drag drops onto/around this row. Position 'inside' is only valid for folders. */
18
+ onMove?: (sourceId: string, targetId: string, position: 'before' | 'inside' | 'after') => void;
19
+ /** Called when this row is right-clicked */
20
+ onContextMenu?: (node: FileTreeNode, event: MouseEvent) => void;
21
+ /** Optional snippet to customize node label area */
22
+ nodeContent?: Snippet<[FileTreeNode]>;
23
+ /** Optional snippet rendering a trailing action bar for the row */
24
+ nodeActions?: Snippet<[FileTreeNode]>;
25
+ /** When the action bar appears */
26
+ nodeActionsTrigger?: 'selected' | 'hover' | 'always';
27
+ /** Where the action bar sits: pinned to the row end, or beside the label */
28
+ nodeActionsPlacement?: 'end' | 'beside';
29
+ /** When false, folders can't be collapsed and always render expanded */
30
+ collapsible?: boolean;
31
+ /** When true, the label gets a title attribute (native tooltip) with the node name */
32
+ showTitles?: boolean;
33
+ /** True while any row in the tree is being dragged */
34
+ dragging?: boolean;
35
+ /** Notifies the tree when this row starts/ends dragging */
36
+ onDragState?: (dragging: boolean) => void;
37
+ }
38
+ declare const FileTreeItem: import("svelte").Component<Props, {}, "">;
39
+ type FileTreeItem = ReturnType<typeof FileTreeItem>;
40
+ export default FileTreeItem;
@@ -0,0 +1,46 @@
1
+ export type FileTreeNodeType = 'file' | 'folder';
2
+ export interface FileTreeNode {
3
+ /** Unique identifier within the tree */
4
+ id: string;
5
+ /** Display label */
6
+ name: string;
7
+ type: FileTreeNodeType;
8
+ /** Present on folders. Absent (or empty) on files. */
9
+ children?: FileTreeNode[];
10
+ /** Persisted open/closed state for folders */
11
+ expanded?: boolean;
12
+ /** Optional custom icon (raw SVG string). Overrides the default file/folder icon. */
13
+ icon?: string;
14
+ /** Arbitrary application data ignored by the component */
15
+ data?: unknown;
16
+ }
17
+ export interface FileTreeDropTarget {
18
+ /** Parent id — null for tree root */
19
+ parentId: string | null;
20
+ /** Insertion index within parent.children. -1 means append at end (used when dropping INTO an empty folder). */
21
+ index: number;
22
+ }
23
+ interface Found {
24
+ node: FileTreeNode;
25
+ parent: FileTreeNode[];
26
+ index: number;
27
+ }
28
+ export declare function findNode(nodes: FileTreeNode[], id: string): Found | null;
29
+ export declare function findParent(nodes: FileTreeNode[], id: string): FileTreeNode | null;
30
+ /** Returns the chain of nodes from the root down to (and including) `id`, or null if not found. */
31
+ export declare function findPath(nodes: FileTreeNode[], id: string): FileTreeNode[] | null;
32
+ /**
33
+ * Expands every ancestor folder of `id` so the node is visible. Mutates the tree in place.
34
+ * Returns true if any folder's expanded state changed.
35
+ */
36
+ export declare function expandTo(nodes: FileTreeNode[], id: string): boolean;
37
+ /** True if `ancestorId` is `id` or an ancestor of `id` in the tree. */
38
+ export declare function isAncestor(nodes: FileTreeNode[], ancestorId: string, id: string): boolean;
39
+ /**
40
+ * Moves the node identified by `sourceId` to `target`.
41
+ * Returns a new tree array; the input is not mutated.
42
+ * Returns the original tree unchanged if the move is illegal
43
+ * (source missing, target inside source's subtree, etc.).
44
+ */
45
+ export declare function moveNode(tree: FileTreeNode[], sourceId: string, target: FileTreeDropTarget): FileTreeNode[];
46
+ export {};
@@ -0,0 +1,115 @@
1
+ export function findNode(nodes, id) {
2
+ for (let i = 0; i < nodes.length; i++) {
3
+ const n = nodes[i];
4
+ if (n.id === id)
5
+ return { node: n, parent: nodes, index: i };
6
+ if (n.children) {
7
+ const found = findNode(n.children, id);
8
+ if (found)
9
+ return found;
10
+ }
11
+ }
12
+ return null;
13
+ }
14
+ export function findParent(nodes, id) {
15
+ for (const n of nodes) {
16
+ if (n.children?.some((c) => c.id === id))
17
+ return n;
18
+ if (n.children) {
19
+ const found = findParent(n.children, id);
20
+ if (found)
21
+ return found;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ /** Returns the chain of nodes from the root down to (and including) `id`, or null if not found. */
27
+ export function findPath(nodes, id) {
28
+ for (const n of nodes) {
29
+ if (n.id === id)
30
+ return [n];
31
+ if (n.children) {
32
+ const sub = findPath(n.children, id);
33
+ if (sub)
34
+ return [n, ...sub];
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Expands every ancestor folder of `id` so the node is visible. Mutates the tree in place.
41
+ * Returns true if any folder's expanded state changed.
42
+ */
43
+ export function expandTo(nodes, id) {
44
+ const path = findPath(nodes, id);
45
+ if (!path)
46
+ return false;
47
+ let changed = false;
48
+ for (const n of path.slice(0, -1)) {
49
+ if (n.type === 'folder' && !n.expanded) {
50
+ n.expanded = true;
51
+ changed = true;
52
+ }
53
+ }
54
+ return changed;
55
+ }
56
+ /** True if `ancestorId` is `id` or an ancestor of `id` in the tree. */
57
+ export function isAncestor(nodes, ancestorId, id) {
58
+ const found = findNode(nodes, ancestorId);
59
+ if (!found)
60
+ return false;
61
+ const stack = [found.node];
62
+ while (stack.length) {
63
+ const n = stack.pop();
64
+ if (n.id === id)
65
+ return true;
66
+ if (n.children)
67
+ stack.push(...n.children);
68
+ }
69
+ return false;
70
+ }
71
+ /**
72
+ * Moves the node identified by `sourceId` to `target`.
73
+ * Returns a new tree array; the input is not mutated.
74
+ * Returns the original tree unchanged if the move is illegal
75
+ * (source missing, target inside source's subtree, etc.).
76
+ */
77
+ export function moveNode(tree, sourceId, target) {
78
+ if (target.parentId && isAncestor(tree, sourceId, target.parentId)) {
79
+ return tree;
80
+ }
81
+ const clone = cloneTree(tree);
82
+ const found = findNode(clone, sourceId);
83
+ if (!found)
84
+ return tree;
85
+ const [removed] = found.parent.splice(found.index, 1);
86
+ let destChildren;
87
+ if (target.parentId === null) {
88
+ destChildren = clone;
89
+ }
90
+ else {
91
+ const destParent = findNode(clone, target.parentId);
92
+ if (!destParent || destParent.node.type !== 'folder') {
93
+ // roll back
94
+ found.parent.splice(found.index, 0, removed);
95
+ return tree;
96
+ }
97
+ if (!destParent.node.children)
98
+ destParent.node.children = [];
99
+ destChildren = destParent.node.children;
100
+ }
101
+ // Adjust index if removing from same parent shifted positions
102
+ let insertAt = target.index;
103
+ if (target.index === -1)
104
+ insertAt = destChildren.length;
105
+ else if (found.parent === destChildren && found.index < target.index)
106
+ insertAt--;
107
+ destChildren.splice(insertAt, 0, removed);
108
+ return clone;
109
+ }
110
+ function cloneTree(nodes) {
111
+ return nodes.map((n) => ({
112
+ ...n,
113
+ children: n.children ? cloneTree(n.children) : n.children
114
+ }));
115
+ }
@@ -0,0 +1,5 @@
1
+ export { default as FileTree } from './FileTree.svelte';
2
+ export { default as FileTreeItem } from './FileTreeItem.svelte';
3
+ export type { FileTreeProps, FileTreeDropEvent } from './FileTree.svelte';
4
+ export type { FileTreeNode, FileTreeNodeType, FileTreeDropTarget } from './FileTreeNode.js';
5
+ export { findNode, findParent, findPath, expandTo, isAncestor, moveNode } from './FileTreeNode.js';
@@ -0,0 +1,3 @@
1
+ export { default as FileTree } from './FileTree.svelte';
2
+ export { default as FileTreeItem } from './FileTreeItem.svelte';
3
+ export { findNode, findParent, findPath, expandTo, isAncestor, moveNode } from './FileTreeNode.js';