@rokkit/ui 1.0.0-next.124 → 1.0.0-next.127

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 (146) hide show
  1. package/README.md +198 -101
  2. package/package.json +52 -34
  3. package/src/components/BreadCrumbs.svelte +82 -0
  4. package/src/components/Button.svelte +87 -0
  5. package/src/components/ButtonGroup.svelte +18 -0
  6. package/src/components/Card.svelte +61 -0
  7. package/src/components/Carousel.svelte +169 -0
  8. package/src/components/Code.svelte +185 -0
  9. package/src/components/Connector.svelte +46 -0
  10. package/src/components/FloatingAction.svelte +331 -0
  11. package/src/components/FloatingNavigation.svelte +228 -0
  12. package/src/components/ItemContent.svelte +24 -0
  13. package/src/components/List.svelte +476 -0
  14. package/src/components/Menu.svelte +421 -0
  15. package/src/components/MultiSelect.svelte +521 -0
  16. package/src/components/PaletteManager.svelte +354 -0
  17. package/src/components/Pill.svelte +78 -0
  18. package/src/components/ProgressBar.svelte +31 -0
  19. package/src/components/Range.svelte +325 -0
  20. package/src/components/Rating.svelte +91 -0
  21. package/src/components/Reveal.svelte +58 -0
  22. package/src/components/SearchFilter.svelte +80 -0
  23. package/src/components/Select.svelte +585 -0
  24. package/src/{Shine.svelte → components/Shine.svelte} +29 -21
  25. package/src/components/Stepper.svelte +169 -0
  26. package/src/components/Switch.svelte +75 -0
  27. package/src/components/Table.svelte +243 -0
  28. package/src/components/Tabs.svelte +268 -0
  29. package/src/components/Tilt.svelte +68 -0
  30. package/src/components/Timeline.svelte +61 -0
  31. package/src/components/Toggle.svelte +157 -0
  32. package/src/components/Toolbar.svelte +307 -0
  33. package/src/components/ToolbarGroup.svelte +17 -0
  34. package/src/components/Tree.svelte +613 -0
  35. package/src/components/index.ts +33 -0
  36. package/src/index.ts +41 -0
  37. package/src/types/button.ts +83 -0
  38. package/src/types/code.ts +46 -0
  39. package/src/types/floating-action.ts +118 -0
  40. package/src/types/floating-navigation.ts +68 -0
  41. package/src/types/index.ts +53 -0
  42. package/src/types/item-proxy.ts +358 -0
  43. package/src/types/list.ts +196 -0
  44. package/src/types/menu.ts +195 -0
  45. package/src/types/palette.ts +143 -0
  46. package/src/types/range.ts +51 -0
  47. package/src/types/search-filter.ts +67 -0
  48. package/src/types/select.ts +206 -0
  49. package/src/types/switch.ts +64 -0
  50. package/src/types/table.ts +210 -0
  51. package/src/types/tabs.ts +124 -0
  52. package/src/types/timeline.ts +51 -0
  53. package/src/types/toggle.ts +109 -0
  54. package/src/types/toolbar.ts +164 -0
  55. package/src/types/tree.ts +259 -0
  56. package/src/utils/palette.ts +582 -0
  57. package/src/utils/shiki.ts +122 -0
  58. package/dist/constants.d.ts +0 -2
  59. package/dist/index.d.ts +0 -41
  60. package/dist/lib/fields.d.ts +0 -16
  61. package/dist/lib/form.d.ts +0 -95
  62. package/dist/lib/index.d.ts +0 -6
  63. package/dist/lib/layout.d.ts +0 -7
  64. package/dist/lib/nested.d.ts +0 -48
  65. package/dist/lib/schema.d.ts +0 -7
  66. package/dist/lib/select.d.ts +0 -8
  67. package/dist/lib/tree.d.ts +0 -9
  68. package/dist/tree/List.spec.svelte.d.ts +0 -1
  69. package/dist/tree/Node.spec.svelte.d.ts +0 -1
  70. package/dist/tree/Root.spec.svelte.d.ts +0 -1
  71. package/dist/types.d.ts +0 -5
  72. package/dist/wrappers/index.d.ts +0 -3
  73. package/src/Accordion.svelte +0 -118
  74. package/src/BreadCrumbs.svelte +0 -32
  75. package/src/Button.svelte +0 -57
  76. package/src/Calendar.svelte +0 -93
  77. package/src/Card.svelte +0 -45
  78. package/src/Carousel.svelte +0 -49
  79. package/src/CheckBox.svelte +0 -56
  80. package/src/Connector.svelte +0 -40
  81. package/src/DropDown.svelte +0 -68
  82. package/src/DropSearch.svelte +0 -37
  83. package/src/Fillable.svelte +0 -19
  84. package/src/GraphPaper.svelte +0 -43
  85. package/src/Icon.svelte +0 -81
  86. package/src/Item.svelte +0 -25
  87. package/src/Link.svelte +0 -21
  88. package/src/List.svelte +0 -89
  89. package/src/ListBody.svelte +0 -43
  90. package/src/Message.svelte +0 -11
  91. package/src/MultiSelect.svelte +0 -48
  92. package/src/NestedList.svelte +0 -78
  93. package/src/NestedPaginator.svelte +0 -63
  94. package/src/Node.svelte +0 -76
  95. package/src/Overlay.svelte +0 -21
  96. package/src/PageNavigator.svelte +0 -94
  97. package/src/PickOne.svelte +0 -60
  98. package/src/Pill.svelte +0 -41
  99. package/src/ProgressBar.svelte +0 -21
  100. package/src/ProgressDots.svelte +0 -53
  101. package/src/RadioGroup.svelte +0 -52
  102. package/src/Range.svelte +0 -45
  103. package/src/RangeMinMax.svelte +0 -124
  104. package/src/RangeSlider.svelte +0 -79
  105. package/src/RangeTick.svelte +0 -28
  106. package/src/Rating.svelte +0 -95
  107. package/src/ResponsiveGrid.svelte +0 -88
  108. package/src/Scrollable.svelte +0 -7
  109. package/src/Select.svelte +0 -114
  110. package/src/Separator.svelte +0 -1
  111. package/src/Slider.svelte +0 -14
  112. package/src/SlidingColumns.svelte +0 -50
  113. package/src/Stage.svelte +0 -41
  114. package/src/Stepper.svelte +0 -66
  115. package/src/Summary.svelte +0 -22
  116. package/src/Switch.svelte +0 -106
  117. package/src/TableCell.svelte +0 -51
  118. package/src/TableHeaderCell.svelte +0 -54
  119. package/src/Tabs.svelte +0 -176
  120. package/src/Tilt.svelte +0 -66
  121. package/src/Toggle.svelte +0 -58
  122. package/src/ToggleThemeMode.svelte +0 -23
  123. package/src/Tree.svelte +0 -80
  124. package/src/TreeTable.svelte +0 -171
  125. package/src/ValidationReport.svelte +0 -23
  126. package/src/constants.js +0 -4
  127. package/src/index.js +0 -48
  128. package/src/lib/fields.js +0 -118
  129. package/src/lib/form.js +0 -72
  130. package/src/lib/index.js +0 -13
  131. package/src/lib/layout.js +0 -63
  132. package/src/lib/nested.js +0 -192
  133. package/src/lib/schema.js +0 -32
  134. package/src/lib/select.js +0 -38
  135. package/src/lib/tree.js +0 -22
  136. package/src/tree/List.spec.svelte.js +0 -84
  137. package/src/tree/List.svelte +0 -78
  138. package/src/tree/Node.spec.svelte.js +0 -104
  139. package/src/tree/Node.svelte +0 -80
  140. package/src/tree/Root.spec.svelte.js +0 -63
  141. package/src/tree/Root.svelte +0 -81
  142. package/src/types.js +0 -9
  143. package/src/wrappers/Category.svelte +0 -27
  144. package/src/wrappers/Section.svelte +0 -16
  145. package/src/wrappers/Wrapper.svelte +0 -12
  146. package/src/wrappers/index.js +0 -3
@@ -0,0 +1,613 @@
1
+ <script lang="ts">
2
+ import type {
3
+ TreeProps,
4
+ TreeItem,
5
+ TreeItemSnippet,
6
+ TreeItemHandlers,
7
+ TreeFields,
8
+ TreeLineType,
9
+ TreeStateIcons
10
+ } from '../types/tree.js'
11
+ import {
12
+ defaultTreeFields,
13
+ defaultTreeStateIcons,
14
+ getLineTypes,
15
+ getNodeKey,
16
+ getSnippet
17
+ } from '../types/tree.js'
18
+ import { ItemProxy } from '../types/item-proxy.js'
19
+ import Connector from './Connector.svelte'
20
+ import ItemContent from './ItemContent.svelte'
21
+ import { NestedController } from '@rokkit/states'
22
+ import { navigator } from '@rokkit/actions'
23
+ import { untrack } from 'svelte'
24
+
25
+ let {
26
+ items = [],
27
+ fields: userFields,
28
+ value = $bindable(),
29
+ size = 'md',
30
+ showLines = true,
31
+ multiselect = false,
32
+ expanded = $bindable({}),
33
+ selected = $bindable([]),
34
+ expandAll = false,
35
+ active,
36
+ icons: userIcons,
37
+ onselect,
38
+ onselectedchange,
39
+ onexpandedchange,
40
+ ontoggle,
41
+ onloadchildren,
42
+ class: className = '',
43
+ item: itemSnippet,
44
+ toggle: toggleSnippet,
45
+ connector: connectorSnippet,
46
+ ...snippets
47
+ }: TreeProps & { [key: string]: TreeItemSnippet | unknown } = $props()
48
+
49
+ // Merge fields with defaults
50
+ const fields = $derived<TreeFields>({ ...defaultTreeFields, ...userFields })
51
+ const icons = $derived<TreeStateIcons>({ ...defaultTreeStateIcons, ...userIcons })
52
+
53
+ /**
54
+ * Create an ItemProxy for the given item
55
+ */
56
+ function createProxy(item: TreeItem): ItemProxy {
57
+ return new ItemProxy(item, fields)
58
+ }
59
+
60
+ // ─── NestedController for keyboard navigation ───────────────────
61
+
62
+ let controller = untrack(() => new NestedController(items, value, userFields, { multiselect }))
63
+ let treeRef = $state<HTMLElement | null>(null)
64
+ let loadingPaths = $state(new Set<string>())
65
+ let loadVersion = $state(0)
66
+
67
+ /**
68
+ * Get expanded state for a node key from the expanded prop.
69
+ * Default to expandAll setting when not explicitly set.
70
+ */
71
+ function getExpandedState(nodeKey: string): boolean {
72
+ const externalKeys = Object.keys(expanded)
73
+ if (externalKeys.length > 0) {
74
+ if (nodeKey in expanded) return expanded[nodeKey]
75
+ }
76
+ return expandAll
77
+ }
78
+
79
+ /**
80
+ * Sync expansion state: expanded prop + expandAll → controller.expandedKeys
81
+ */
82
+ function syncExpandedToController() {
83
+ for (const [key, proxy] of controller.lookup.entries()) {
84
+ if (!proxy.hasChildren) continue
85
+ const itemProxy = createProxy(proxy.value)
86
+ const nodeKey = getNodeKey(itemProxy)
87
+ const shouldExpand = getExpandedState(nodeKey)
88
+ if (shouldExpand) {
89
+ controller.expandedKeys.add(key)
90
+ } else {
91
+ controller.expandedKeys.delete(key)
92
+ }
93
+ }
94
+ }
95
+
96
+ // Sync on init
97
+ syncExpandedToController()
98
+
99
+ $effect(() => {
100
+ controller.update(items)
101
+ syncExpandedToController()
102
+ })
103
+
104
+ // Sync expanded prop / expandAll changes → controller
105
+ $effect(() => {
106
+ void expanded
107
+ void expandAll
108
+ syncExpandedToController()
109
+ })
110
+
111
+ /**
112
+ * Derive expanded prop from controller.expandedKeys (pathKey → nodeKey mapping)
113
+ */
114
+ function deriveExpandedFromController(): Record<string, boolean> {
115
+ const result: Record<string, boolean> = {}
116
+ function walk(treeItems: TreeItem[], pathPrefix: string) {
117
+ treeItems.forEach((item, index) => {
118
+ const proxy = createProxy(item)
119
+ if (!proxy.hasChildren) return
120
+ const pathKey = pathPrefix ? `${pathPrefix}-${index}` : String(index)
121
+ const nodeKey = getNodeKey(proxy)
122
+ result[nodeKey] = controller.expandedKeys.has(pathKey)
123
+ if (controller.expandedKeys.has(pathKey)) {
124
+ walk(proxy.children as TreeItem[], pathKey)
125
+ }
126
+ })
127
+ }
128
+ walk(items, '')
129
+ return result
130
+ }
131
+
132
+ // Focus the element matching controller.focusedKey on navigator action events
133
+ $effect(() => {
134
+ if (!treeRef) return
135
+ const el = treeRef
136
+
137
+ function onAction(event: Event) {
138
+ const detail = (event as CustomEvent).detail
139
+
140
+ if (detail.name === 'move') {
141
+ const key = controller.focusedKey
142
+ if (key) {
143
+ const target = el.querySelector(`[data-path="${key}"]`) as HTMLElement | null
144
+ if (target && target !== document.activeElement) {
145
+ target.focus()
146
+ target.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })
147
+ }
148
+ }
149
+ }
150
+
151
+ if (detail.name === 'select') {
152
+ handleSelectAction()
153
+ syncSelectedFromController()
154
+ }
155
+
156
+ if (detail.name === 'toggle') {
157
+ // Controller already toggled expandedKeys. Derive the expanded prop.
158
+ handleToggleAction()
159
+ }
160
+ }
161
+
162
+ el.addEventListener('action', onAction)
163
+ return () => el.removeEventListener('action', onAction)
164
+ })
165
+
166
+ /**
167
+ * Handle the navigator's select action (Enter/Space or click on data-path item)
168
+ */
169
+ function handleSelectAction() {
170
+ const key = controller.focusedKey
171
+ if (!key) return
172
+
173
+ const proxy = controller.lookup.get(key)
174
+ if (!proxy) return
175
+
176
+ const itemProxy = createProxy(proxy.value)
177
+ const href = itemProxy.get<string>('href')
178
+ if (!href) {
179
+ onselect?.(itemProxy.itemValue, proxy.value as TreeItem)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Handle the navigator's toggle action (ArrowLeft collapse / ArrowRight expand)
185
+ */
186
+ function handleToggleAction() {
187
+ const key = controller.focusedKey
188
+ if (!key) return
189
+
190
+ const proxy = controller.lookup.get(key)
191
+ if (!proxy) return
192
+
193
+ const itemProxy = createProxy(proxy.value)
194
+ const isExpanded = controller.expandedKeys.has(key)
195
+ const newExpanded = deriveExpandedFromController()
196
+ expanded = newExpanded
197
+ onexpandedchange?.(newExpanded)
198
+ ontoggle?.(itemProxy.itemValue, proxy.value as TreeItem, isExpanded)
199
+ }
200
+
201
+ // ─── Lazy loading helpers ──────────────────────────────────────
202
+
203
+ /**
204
+ * Load children for a lazy node (children: true → children: [...]).
205
+ * Returns true if children were loaded successfully.
206
+ */
207
+ async function loadLazyChildren(pathKey: string): Promise<boolean> {
208
+ const proxy = controller.lookup.get(pathKey)
209
+ if (!proxy) return false
210
+ const itemProxy = createProxy(proxy.value)
211
+ if (!itemProxy.canLoadChildren || !onloadchildren) return false
212
+
213
+ loadingPaths = new Set([...loadingPaths, pathKey])
214
+ try {
215
+ const children = await onloadchildren(itemProxy.itemValue, proxy.value as TreeItem)
216
+ const childrenField = fields.children ?? 'children'
217
+ ;(proxy.value as Record<string, unknown>)[childrenField] = children
218
+ controller.update(items)
219
+ syncExpandedToController()
220
+ loadVersion++
221
+ } catch {
222
+ loadingPaths = new Set([...loadingPaths].filter((p) => p !== pathKey))
223
+ return false
224
+ }
225
+ loadingPaths = new Set([...loadingPaths].filter((p) => p !== pathKey))
226
+ return true
227
+ }
228
+
229
+ /**
230
+ * Toggle expansion of a node by its path key (for toggle button clicks)
231
+ */
232
+ async function toggleNodeByKey(pathKey: string) {
233
+ // Collapsing — toggle normally
234
+ if (controller.expandedKeys.has(pathKey)) {
235
+ controller.toggleExpansion(pathKey)
236
+ const proxy = controller.lookup.get(pathKey)
237
+ if (!proxy) return
238
+ const itemProxy = createProxy(proxy.value)
239
+ const newExpanded = deriveExpandedFromController()
240
+ expanded = newExpanded
241
+ onexpandedchange?.(newExpanded)
242
+ ontoggle?.(itemProxy.itemValue, proxy.value as TreeItem, false)
243
+ return
244
+ }
245
+
246
+ // Expanding — check if lazy load is needed
247
+ const proxy = controller.lookup.get(pathKey)
248
+ let lazyLoaded = false
249
+ if (proxy) {
250
+ const itemProxy = createProxy(proxy.value)
251
+ if (itemProxy.canLoadChildren && onloadchildren) {
252
+ const loaded = await loadLazyChildren(pathKey)
253
+ if (!loaded) return // Error — stay collapsed
254
+ lazyLoaded = true
255
+ }
256
+ }
257
+
258
+ // After lazy load, syncExpandedToController may have already expanded the node
259
+ // (e.g. when expandAll=true). Only toggle if not already expanded.
260
+ if (!lazyLoaded || !controller.expandedKeys.has(pathKey)) {
261
+ controller.toggleExpansion(pathKey)
262
+ }
263
+ const updatedProxy = controller.lookup.get(pathKey)
264
+ if (!updatedProxy) return
265
+ const updatedItemProxy = createProxy(updatedProxy.value)
266
+ const isExpanded = controller.expandedKeys.has(pathKey)
267
+ const newExpanded = deriveExpandedFromController()
268
+ expanded = newExpanded
269
+ onexpandedchange?.(newExpanded)
270
+ ontoggle?.(updatedItemProxy.itemValue, updatedProxy.value as TreeItem, isExpanded)
271
+ }
272
+
273
+ /**
274
+ * Sync DOM focus to controller state
275
+ */
276
+ function handleFocusIn(event: FocusEvent) {
277
+ const target = event.target as HTMLElement
278
+ if (!target) return
279
+ const path = target.dataset.path
280
+ if (path !== undefined) {
281
+ controller.moveTo(path)
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Handle keyboard events the navigator doesn't cover:
287
+ * - Enter/Space on link items: let native <a> behavior through
288
+ * - ArrowRight on lazy nodes: trigger async load before expand
289
+ */
290
+ function handleTreeKeyDown(event: KeyboardEvent) {
291
+ // ArrowRight on lazy node: intercept before navigator
292
+ if (event.key === 'ArrowRight') {
293
+ const key = controller.focusedKey
294
+ if (!key) return
295
+ const proxy = controller.lookup.get(key)
296
+ if (!proxy) return
297
+ const itemProxy = createProxy(proxy.value)
298
+ if (itemProxy.canLoadChildren && onloadchildren) {
299
+ event.preventDefault()
300
+ event.stopPropagation()
301
+ loadLazyChildren(key).then((loaded) => {
302
+ if (loaded) {
303
+ controller.expand(key)
304
+ handleToggleAction()
305
+ const target = treeRef?.querySelector(`[data-path="${key}"]`) as HTMLElement
306
+ target?.focus()
307
+ }
308
+ })
309
+ return
310
+ }
311
+ }
312
+
313
+ if (event.key !== 'Enter' && event.key !== ' ') return
314
+
315
+ const key = controller.focusedKey
316
+ if (!key) return
317
+
318
+ const proxy = controller.lookup.get(key)
319
+ if (!proxy) return
320
+
321
+ const itemProxy = createProxy(proxy.value)
322
+ const href = itemProxy.get<string>('href')
323
+ if (href) {
324
+ event.stopPropagation()
325
+ }
326
+ }
327
+
328
+ // ─── Multi-selection helpers ────────────────────────────────────
329
+
330
+ /**
331
+ * Sync the selected bindable prop from controller.selected
332
+ */
333
+ function syncSelectedFromController() {
334
+ if (!multiselect) return
335
+ selected = [...controller.selected]
336
+ onselectedchange?.(selected)
337
+ }
338
+
339
+ /**
340
+ * Check if an item is in the current selection (for data-selected attribute)
341
+ */
342
+ function isItemSelected(pathKey: string): boolean {
343
+ if (!multiselect) return false
344
+ return controller.selectedKeys.has(pathKey)
345
+ }
346
+
347
+ // ─── Check active ──────────────────────────────────────────────
348
+
349
+ /**
350
+ * Check if an item is currently active/selected
351
+ */
352
+ function checkIsActive(proxy: ItemProxy): boolean {
353
+ if (active !== undefined) {
354
+ return proxy.itemValue === active
355
+ }
356
+ return value !== undefined && proxy.itemValue === value
357
+ }
358
+
359
+ /**
360
+ * Handle item click (for button items)
361
+ */
362
+ function handleItemClick(proxy: ItemProxy) {
363
+ value = proxy.itemValue
364
+ onselect?.(proxy.itemValue, proxy.original as TreeItem)
365
+ }
366
+
367
+ /**
368
+ * Create handlers object for custom snippets
369
+ */
370
+ function createHandlers(proxy: ItemProxy, pathKey: string): TreeItemHandlers {
371
+ return {
372
+ onclick: () => handleItemClick(proxy),
373
+ ontoggle: () => toggleNodeByKey(pathKey),
374
+ onkeydown: (event: KeyboardEvent) => {
375
+ if (event.key === 'Enter' || event.key === ' ') {
376
+ event.preventDefault()
377
+ event.stopPropagation()
378
+ handleItemClick(proxy)
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ // ==========================================================================
385
+ // Flattened tree structure for rendering
386
+ // ==========================================================================
387
+
388
+ interface FlatNode {
389
+ proxy: ItemProxy
390
+ level: number
391
+ lineTypes: TreeLineType[]
392
+ path: string
393
+ isLast: boolean
394
+ isExpandable: boolean
395
+ }
396
+
397
+ /**
398
+ * Flatten the tree into a list of visible nodes with their line types.
399
+ * Reads expansion state from controller.expandedKeys.
400
+ */
401
+ function flattenTree(
402
+ treeItems: TreeItem[],
403
+ level: number = 0,
404
+ types: TreeLineType[] = [],
405
+ pathPrefix: string = ''
406
+ ): FlatNode[] {
407
+ const result: FlatNode[] = []
408
+
409
+ treeItems.forEach((item, index) => {
410
+ const proxy = createProxy(item)
411
+ const isLast = index === treeItems.length - 1
412
+ const nodeType: 'child' | 'last' = isLast ? 'last' : 'child'
413
+ const path = pathPrefix ? `${pathPrefix}-${index}` : String(index)
414
+ const isExpandable = proxy.hasChildren || proxy.canLoadChildren
415
+
416
+ const connectors = getLineTypes(isExpandable, types, nodeType)
417
+
418
+ result.push({
419
+ proxy,
420
+ level,
421
+ lineTypes: connectors,
422
+ path,
423
+ isLast,
424
+ isExpandable
425
+ })
426
+
427
+ // If expanded (read from controller), recurse into children
428
+ if (proxy.hasChildren && controller.expandedKeys.has(path)) {
429
+ const childNodes = flattenTree(proxy.children as TreeItem[], level + 1, connectors, path)
430
+ result.push(...childNodes)
431
+ }
432
+ })
433
+
434
+ return result
435
+ }
436
+
437
+
438
+ const flatNodes = $derived((void loadVersion, flattenTree(items)))
439
+
440
+ /**
441
+ * Resolve which snippet to use for an item
442
+ */
443
+ function resolveItemSnippet(proxy: ItemProxy): TreeItemSnippet | null {
444
+ const snippetName = proxy.snippetName
445
+ if (snippetName) {
446
+ const namedSnippet = getSnippet(snippets, snippetName)
447
+ if (namedSnippet) {
448
+ return namedSnippet as TreeItemSnippet
449
+ }
450
+ }
451
+ return itemSnippet ?? null
452
+ }
453
+ </script>
454
+
455
+ {#snippet defaultToggle(isExpanded: boolean, hasChildren: boolean, stateIcons: TreeStateIcons)}
456
+ <span data-tree-toggle data-tree-has-children={hasChildren || undefined}>
457
+ {#if hasChildren}
458
+ {#if isExpanded}
459
+ <span class={stateIcons.opened} aria-hidden="true"></span>
460
+ {:else}
461
+ <span class={stateIcons.closed} aria-hidden="true"></span>
462
+ {/if}
463
+ {/if}
464
+ </span>
465
+ {/snippet}
466
+
467
+ {#snippet defaultItem(
468
+ proxy: ItemProxy,
469
+ handlers: TreeItemHandlers,
470
+ isActive: boolean,
471
+ _isExpanded: boolean,
472
+ _level: number,
473
+ _path: string
474
+ )}
475
+ {@const href = proxy.get<string>('href')}
476
+ {#if href}
477
+ <a
478
+ {href}
479
+ data-tree-item-content
480
+ data-path={_path}
481
+ data-active={isActive || undefined}
482
+ aria-label={proxy.label}
483
+ aria-current={isActive ? 'page' : undefined}
484
+ >
485
+ <ItemContent {proxy} />
486
+ </a>
487
+ {:else}
488
+ <button
489
+ type="button"
490
+ data-tree-item-content
491
+ data-path={_path}
492
+ data-active={isActive || undefined}
493
+ aria-label={proxy.label}
494
+ aria-pressed={isActive}
495
+ onkeydown={handlers.onkeydown}
496
+ >
497
+ <ItemContent {proxy} />
498
+ </button>
499
+ {/if}
500
+ {/snippet}
501
+
502
+ {#snippet renderNode(node: FlatNode)}
503
+ {@const proxy = node.proxy}
504
+ {@const isActive = checkIsActive(proxy)}
505
+ {@const nodeSelected = isItemSelected(node.path)}
506
+ {@const isExpanded = controller.expandedKeys.has(node.path)}
507
+ {@const isLoading = loadingPaths.has(node.path)}
508
+ {@const handlers = createHandlers(proxy, node.path)}
509
+ {@const customSnippet = resolveItemSnippet(proxy)}
510
+
511
+ <div
512
+ data-tree-node
513
+ data-tree-path={node.path}
514
+ data-tree-level={node.level}
515
+ data-tree-expanded={isExpanded || undefined}
516
+ data-tree-has-children={node.isExpandable || undefined}
517
+ data-tree-loading={isLoading || undefined}
518
+ data-active={isActive || undefined}
519
+ data-selected={nodeSelected || undefined}
520
+ role="treeitem"
521
+ aria-expanded={node.isExpandable ? isExpanded : undefined}
522
+ aria-selected={multiselect ? nodeSelected : isActive}
523
+ aria-busy={isLoading || undefined}
524
+ aria-level={node.level + 1}
525
+ >
526
+ <div data-tree-node-row>
527
+ <!-- Tree lines/connectors -->
528
+ {#if showLines}
529
+ {#each node.lineTypes as lineType, lineIndex (lineIndex)}
530
+ {#if lineType === 'icon'}
531
+ <!-- Render toggle icon instead of connector -->
532
+ <button
533
+ type="button"
534
+ data-tree-toggle-btn
535
+ onclick={() => toggleNodeByKey(node.path)}
536
+ aria-label={isLoading ? 'Loading' : isExpanded ? 'Collapse' : 'Expand'}
537
+ tabindex={-1}
538
+ >
539
+ {#if isLoading}
540
+ <span data-tree-spinner aria-hidden="true"></span>
541
+ {:else if toggleSnippet}
542
+ {@render toggleSnippet(isExpanded, node.isExpandable, icons)}
543
+ {:else}
544
+ {@render defaultToggle(isExpanded, node.isExpandable, icons)}
545
+ {/if}
546
+ </button>
547
+ {:else if connectorSnippet}
548
+ {@render connectorSnippet(lineType)}
549
+ {:else}
550
+ <Connector type={lineType} />
551
+ {/if}
552
+ {/each}
553
+ {:else}
554
+ <!-- No lines - just indent and toggle -->
555
+ <span data-tree-indent style="width: {node.level * 1.25}rem"></span>
556
+ <button
557
+ type="button"
558
+ data-tree-toggle-btn
559
+ onclick={() => toggleNodeByKey(node.path)}
560
+ aria-label={isLoading ? 'Loading' : isExpanded ? 'Collapse' : 'Expand'}
561
+ tabindex={-1}
562
+ >
563
+ {#if isLoading}
564
+ <span data-tree-spinner aria-hidden="true"></span>
565
+ {:else if toggleSnippet}
566
+ {@render toggleSnippet(isExpanded, node.isExpandable, icons)}
567
+ {:else}
568
+ {@render defaultToggle(isExpanded, node.isExpandable, icons)}
569
+ {/if}
570
+ </button>
571
+ {/if}
572
+
573
+ <!-- Node content -->
574
+ {#if customSnippet}
575
+ <svelte:boundary>
576
+ {@render customSnippet(
577
+ proxy.original as TreeItem,
578
+ fields,
579
+ handlers,
580
+ isActive,
581
+ isExpanded,
582
+ node.level
583
+ )}
584
+ {#snippet failed()}
585
+ {@render defaultItem(proxy, handlers, isActive, isExpanded, node.level, node.path)}
586
+ {/snippet}
587
+ </svelte:boundary>
588
+ {:else}
589
+ {@render defaultItem(proxy, handlers, isActive, isExpanded, node.level, node.path)}
590
+ {/if}
591
+ </div>
592
+ </div>
593
+ {/snippet}
594
+
595
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
596
+ <div
597
+ bind:this={treeRef}
598
+ data-tree
599
+ data-size={size}
600
+ data-show-lines={showLines || undefined}
601
+ data-multiselect={multiselect || undefined}
602
+ class={className || undefined}
603
+ role="tree"
604
+ aria-label="Tree"
605
+ aria-multiselectable={multiselect || undefined}
606
+ onkeydown={handleTreeKeyDown}
607
+ onfocusin={handleFocusIn}
608
+ use:navigator={{ wrapper: controller, orientation: 'vertical', nested: true, typeahead: true }}
609
+ >
610
+ {#each flatNodes as node (node.path)}
611
+ {@render renderNode(node)}
612
+ {/each}
613
+ </div>
@@ -0,0 +1,33 @@
1
+ // UI Components
2
+ export { default as Button } from './Button.svelte'
3
+ export { default as ButtonGroup } from './ButtonGroup.svelte'
4
+ export { default as Code } from './Code.svelte'
5
+ export { default as Menu } from './Menu.svelte'
6
+ export { default as Select } from './Select.svelte'
7
+ export { default as MultiSelect } from './MultiSelect.svelte'
8
+ export { default as Toolbar } from './Toolbar.svelte'
9
+ export { default as ToolbarGroup } from './ToolbarGroup.svelte'
10
+ export { default as Tabs } from './Tabs.svelte'
11
+ export { default as Toggle } from './Toggle.svelte'
12
+ export { default as List } from './List.svelte'
13
+ export { default as Tree } from './Tree.svelte'
14
+ export { default as Connector } from './Connector.svelte'
15
+ export { default as ItemContent } from './ItemContent.svelte'
16
+ export { default as FloatingAction } from './FloatingAction.svelte'
17
+ export { default as PaletteManager } from './PaletteManager.svelte'
18
+ export { default as Tilt } from './Tilt.svelte'
19
+ export { default as Shine } from './Shine.svelte'
20
+ export { default as BreadCrumbs } from './BreadCrumbs.svelte'
21
+ export { default as Card } from './Card.svelte'
22
+ export { default as ProgressBar } from './ProgressBar.svelte'
23
+ export { default as Carousel } from './Carousel.svelte'
24
+ export { default as Pill } from './Pill.svelte'
25
+ export { default as Rating } from './Rating.svelte'
26
+ export { default as Stepper } from './Stepper.svelte'
27
+ export { default as Switch } from './Switch.svelte'
28
+ export { default as Table } from './Table.svelte'
29
+ export { default as SearchFilter } from './SearchFilter.svelte'
30
+ export { default as Range } from './Range.svelte'
31
+ export { default as Timeline } from './Timeline.svelte'
32
+ export { default as Reveal } from './Reveal.svelte'
33
+ export { default as FloatingNavigation } from './FloatingNavigation.svelte'
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Components
2
+ export {
3
+ Button,
4
+ ButtonGroup,
5
+ Code,
6
+ Menu,
7
+ Select,
8
+ MultiSelect,
9
+ Toolbar,
10
+ ToolbarGroup,
11
+ Tabs,
12
+ Toggle,
13
+ List,
14
+ Tree,
15
+ FloatingAction,
16
+ FloatingNavigation,
17
+ PaletteManager,
18
+ Tilt,
19
+ Shine,
20
+ BreadCrumbs,
21
+ Card,
22
+ ProgressBar,
23
+ Carousel,
24
+ Pill,
25
+ Rating,
26
+ Connector,
27
+ Stepper,
28
+ Switch,
29
+ Table,
30
+ SearchFilter,
31
+ Range,
32
+ Timeline,
33
+ Reveal
34
+ } from './components/index.js'
35
+
36
+ // Utilities
37
+ export { highlightCode, preloadHighlighter, getSupportedLanguages } from './utils/shiki.js'
38
+ export * from './utils/palette.js'
39
+
40
+ // Types
41
+ export * from './types/index.js'