@placeholderco/placeholder-ui 1.0.12 → 2.1.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 (278) 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/ColorPicker.svelte +957 -0
  107. package/dist/form/ColorPicker.svelte.d.ts +78 -0
  108. package/dist/form/ComboBox.svelte +30 -20
  109. package/dist/form/ComboBox.svelte.d.ts +1 -1
  110. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  111. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  112. package/dist/form/ComboBoxMulti.svelte +38 -24
  113. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  114. package/dist/form/CronBuilder.svelte +84 -67
  115. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  116. package/dist/form/DatePicker.svelte +148 -195
  117. package/dist/form/DateRangePicker.svelte +101 -98
  118. package/dist/form/DateTimePicker.svelte +167 -208
  119. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  120. package/dist/form/FormGroup.svelte +33 -10
  121. package/dist/form/FormGroup.svelte.d.ts +12 -2
  122. package/dist/form/Number.svelte +70 -47
  123. package/dist/form/Number.svelte.d.ts +36 -6
  124. package/dist/form/Radio.svelte +18 -18
  125. package/dist/form/RadioGroup.svelte +23 -12
  126. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  127. package/dist/form/SegmentedControl.svelte +76 -56
  128. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  129. package/dist/form/Select.svelte +150 -233
  130. package/dist/form/Select.svelte.d.ts +29 -5
  131. package/dist/form/SelectMulti.svelte +163 -277
  132. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  133. package/dist/form/Slider.svelte +183 -269
  134. package/dist/form/Slider.svelte.d.ts +32 -17
  135. package/dist/form/StringArrayBuilder.svelte +249 -0
  136. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  137. package/dist/form/Switch.svelte +164 -106
  138. package/dist/form/Switch.svelte.d.ts +22 -3
  139. package/dist/form/TextArea.svelte +49 -29
  140. package/dist/form/TextArea.svelte.d.ts +23 -3
  141. package/dist/form/Textbox.svelte +69 -68
  142. package/dist/form/Textbox.svelte.d.ts +32 -4
  143. package/dist/form/TimePicker.svelte +42 -47
  144. package/dist/form/TimePicker.svelte.d.ts +12 -2
  145. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  146. package/dist/form/comboBoxCore.svelte.js +243 -0
  147. package/dist/icon/Icon.svelte +8 -3
  148. package/dist/icon/Icon.svelte.d.ts +8 -3
  149. package/dist/icon/file-type-bmp.svg +1 -0
  150. package/dist/icon/file-type-csv.svg +1 -0
  151. package/dist/icon/file-type-doc.svg +1 -0
  152. package/dist/icon/file-type-docx.svg +1 -0
  153. package/dist/icon/file-type-jpg.svg +1 -0
  154. package/dist/icon/file-type-pdf.svg +1 -0
  155. package/dist/icon/file-type-png.svg +1 -0
  156. package/dist/icon/file-type-ppt.svg +1 -0
  157. package/dist/icon/file-type-svg.svg +1 -0
  158. package/dist/icon/file-type-txt.svg +1 -0
  159. package/dist/icon/file-type-xls.svg +1 -0
  160. package/dist/icon/file-type-zip.svg +1 -0
  161. package/dist/icon/index.d.ts +166 -151
  162. package/dist/icon/index.js +166 -152
  163. package/dist/icon/logo-placeholder.svg +6 -0
  164. package/dist/icon/source-code.svg +1 -0
  165. package/dist/icon/sun-moon.svg +1 -0
  166. package/dist/icon/table.svg +6 -0
  167. package/dist/index.d.ts +146 -82
  168. package/dist/index.js +77 -78
  169. package/dist/layout/AppShell.svelte +12 -162
  170. package/dist/layout/AppShell.svelte.d.ts +6 -27
  171. package/dist/layout/CustomNavbar.svelte +9 -7
  172. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  173. package/dist/layout/Navbar.svelte +161 -129
  174. package/dist/layout/Navbar.svelte.d.ts +29 -15
  175. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  176. package/dist/layout/NavbarItemView.svelte +182 -0
  177. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  178. package/dist/layout/Sidenav.svelte +280 -341
  179. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  180. package/dist/models/ComboBoxItem.d.ts +2 -0
  181. package/dist/models/Employee.d.ts +53 -0
  182. package/dist/models/Employee.js +23 -0
  183. package/dist/models/Hyperlink.d.ts +3 -1
  184. package/dist/models/LinkCollectionItem.d.ts +3 -1
  185. package/dist/models/NavbarItem.d.ts +6 -2
  186. package/dist/theme.svelte.d.ts +0 -1
  187. package/dist/theme.svelte.js +46 -58
  188. package/dist/ui/Accordion.svelte +81 -242
  189. package/dist/ui/Accordion.svelte.d.ts +37 -14
  190. package/dist/ui/AccordionItem.svelte +303 -0
  191. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  192. package/dist/ui/ActionIcon.svelte +25 -21
  193. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  194. package/dist/ui/Badge.svelte +163 -171
  195. package/dist/ui/Badge.svelte.d.ts +13 -5
  196. package/dist/ui/Button.svelte +178 -205
  197. package/dist/ui/Button.svelte.d.ts +19 -2
  198. package/dist/ui/ButtonVariant.d.ts +15 -1
  199. package/dist/ui/ContextMenu.svelte +146 -0
  200. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  201. package/dist/ui/ContextMenuList.svelte +188 -0
  202. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  203. package/dist/ui/Dialog.svelte +183 -111
  204. package/dist/ui/Dialog.svelte.d.ts +21 -4
  205. package/dist/ui/Drawer.svelte +237 -184
  206. package/dist/ui/Drawer.svelte.d.ts +20 -2
  207. package/dist/ui/Dropdown.svelte +24 -14
  208. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  209. package/dist/ui/Dropzone.svelte +159 -57
  210. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  211. package/dist/ui/Link.svelte +12 -4
  212. package/dist/ui/Link.svelte.d.ts +8 -2
  213. package/dist/ui/Loader.svelte +23 -18
  214. package/dist/ui/Loader.svelte.d.ts +5 -2
  215. package/dist/ui/Logo.svelte +37 -0
  216. package/dist/ui/Logo.svelte.d.ts +16 -0
  217. package/dist/ui/MultiSortable.svelte +287 -0
  218. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  219. package/dist/ui/Pagination.svelte +15 -9
  220. package/dist/ui/Pagination.svelte.d.ts +6 -2
  221. package/dist/ui/Popover.svelte +43 -65
  222. package/dist/ui/Popover.svelte.d.ts +14 -5
  223. package/dist/ui/ProgressBar.svelte +28 -30
  224. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  225. package/dist/ui/SegmentedControl.svelte +0 -0
  226. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  227. package/dist/ui/Sortable.svelte +163 -0
  228. package/dist/ui/Sortable.svelte.d.ts +47 -0
  229. package/dist/ui/Table.svelte +383 -80
  230. package/dist/ui/Table.svelte.d.ts +43 -2
  231. package/dist/ui/Tabs.svelte +22 -18
  232. package/dist/ui/Tabs.svelte.d.ts +6 -2
  233. package/dist/ui/ThemeSwitcher.svelte +26 -7
  234. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  235. package/dist/ui/Tooltip.svelte +312 -180
  236. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  237. package/dist/uiTheme.svelte.d.ts +86 -0
  238. package/dist/uiTheme.svelte.js +214 -0
  239. package/dist/util/DateFunctions.js +1 -1
  240. package/dist/util/DragDrop.d.ts +50 -0
  241. package/dist/util/DragDrop.js +154 -0
  242. package/dist/util/NavigateTo.js +2 -2
  243. package/dist/util/dayjs.d.ts +4 -0
  244. package/dist/util/dayjs.js +15 -0
  245. package/package.json +66 -26
  246. package/dist/display/Alert.svelte +0 -179
  247. package/dist/display/Alert.svelte.d.ts +0 -13
  248. package/dist/form/FileInput.svelte +0 -235
  249. package/dist/form/FileInput.svelte.d.ts +0 -23
  250. package/dist/form/PasswordInput.svelte +0 -253
  251. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  252. package/dist/form/Rating.svelte +0 -235
  253. package/dist/form/Rating.svelte.d.ts +0 -21
  254. package/dist/styles/components.css +0 -199
  255. package/dist/styles/dark.css +0 -146
  256. package/dist/styles/index.css +0 -116
  257. package/dist/styles/reset.css +0 -110
  258. package/dist/styles/semantic.css +0 -86
  259. package/dist/styles/tokens.css +0 -203
  260. package/dist/styles/utilities.css +0 -523
  261. package/dist/ui/Breadcrumbs.svelte +0 -131
  262. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  263. package/dist/ui/LoadingOverlay.svelte +0 -53
  264. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  265. package/dist/ui/Progress.svelte +0 -191
  266. package/dist/ui/Progress.svelte.d.ts +0 -15
  267. package/dist/ui/RingProgress.svelte +0 -141
  268. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  269. package/dist/ui/Skeleton.svelte +0 -85
  270. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  271. package/dist/ui/Stepper.svelte +0 -355
  272. package/dist/ui/Stepper.svelte.d.ts +0 -24
  273. package/dist/ui/Timeline.svelte +0 -225
  274. package/dist/ui/Timeline.svelte.d.ts +0 -23
  275. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  276. /package/dist/{actions → util}/ClickOutside.js +0 -0
  277. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  278. /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';