@rokkit/ui 1.0.0-next.125 → 1.0.0-next.128

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 (154) hide show
  1. package/README.md +198 -101
  2. package/package.json +42 -34
  3. package/src/components/BreadCrumbs.svelte +90 -0
  4. package/src/components/Button.svelte +93 -0
  5. package/src/components/ButtonGroup.svelte +18 -0
  6. package/src/components/Card.svelte +61 -0
  7. package/src/components/Carousel.svelte +174 -0
  8. package/src/components/Code.svelte +189 -0
  9. package/src/components/Connector.svelte +46 -0
  10. package/src/components/FloatingAction.svelte +334 -0
  11. package/src/components/FloatingNavigation.svelte +235 -0
  12. package/src/components/Grid.svelte +128 -0
  13. package/src/components/ItemContent.svelte +25 -0
  14. package/src/components/LazyTree.svelte +165 -0
  15. package/src/components/List.svelte +188 -0
  16. package/src/components/Menu.svelte +270 -0
  17. package/src/components/MultiSelect.svelte +369 -0
  18. package/src/components/PaletteManager.svelte +364 -0
  19. package/src/components/Pill.svelte +83 -0
  20. package/src/components/ProgressBar.svelte +31 -0
  21. package/src/components/Range.svelte +330 -0
  22. package/src/components/Rating.svelte +101 -0
  23. package/src/components/Reveal.svelte +58 -0
  24. package/src/components/SearchFilter.svelte +88 -0
  25. package/src/components/Select.svelte +396 -0
  26. package/src/{Shine.svelte → components/Shine.svelte} +29 -21
  27. package/src/components/Stepper.svelte +172 -0
  28. package/src/components/Switch.svelte +75 -0
  29. package/src/components/Table.svelte +242 -0
  30. package/src/components/Tabs.svelte +192 -0
  31. package/src/components/Tilt.svelte +68 -0
  32. package/src/components/Timeline.svelte +61 -0
  33. package/src/components/Toggle.svelte +93 -0
  34. package/src/components/Toolbar.svelte +308 -0
  35. package/src/components/ToolbarGroup.svelte +17 -0
  36. package/src/components/Tree.svelte +144 -0
  37. package/src/components/UploadFileStatus.svelte +83 -0
  38. package/src/components/UploadProgress.svelte +131 -0
  39. package/src/components/UploadTarget.svelte +124 -0
  40. package/src/components/index.ts +38 -0
  41. package/src/index.ts +46 -0
  42. package/src/types/button.ts +86 -0
  43. package/src/types/code.ts +46 -0
  44. package/src/types/floating-action.ts +123 -0
  45. package/src/types/floating-navigation.ts +80 -0
  46. package/src/types/index.ts +55 -0
  47. package/src/types/list.ts +200 -0
  48. package/src/types/menu.ts +95 -0
  49. package/src/types/palette.ts +160 -0
  50. package/src/types/range.ts +51 -0
  51. package/src/types/search-filter.ts +67 -0
  52. package/src/types/select.ts +176 -0
  53. package/src/types/switch.ts +68 -0
  54. package/src/types/table.ts +210 -0
  55. package/src/types/tabs.ts +103 -0
  56. package/src/types/timeline.ts +53 -0
  57. package/src/types/toggle.ts +68 -0
  58. package/src/types/toolbar.ts +164 -0
  59. package/src/types/tree.ts +250 -0
  60. package/src/types/upload-file-status.ts +45 -0
  61. package/src/types/upload-progress.ts +111 -0
  62. package/src/types/upload-target.ts +68 -0
  63. package/src/utils/palette.ts +582 -0
  64. package/src/utils/shiki.ts +122 -0
  65. package/src/utils/upload.js +128 -0
  66. package/dist/constants.d.ts +0 -2
  67. package/dist/index.d.ts +0 -41
  68. package/dist/lib/fields.d.ts +0 -16
  69. package/dist/lib/form.d.ts +0 -95
  70. package/dist/lib/index.d.ts +0 -6
  71. package/dist/lib/layout.d.ts +0 -7
  72. package/dist/lib/nested.d.ts +0 -48
  73. package/dist/lib/schema.d.ts +0 -7
  74. package/dist/lib/select.d.ts +0 -8
  75. package/dist/lib/tree.d.ts +0 -9
  76. package/dist/tree/List.spec.svelte.d.ts +0 -1
  77. package/dist/tree/Node.spec.svelte.d.ts +0 -1
  78. package/dist/tree/Root.spec.svelte.d.ts +0 -1
  79. package/dist/types.d.ts +0 -5
  80. package/dist/wrappers/index.d.ts +0 -3
  81. package/src/Accordion.svelte +0 -118
  82. package/src/BreadCrumbs.svelte +0 -32
  83. package/src/Button.svelte +0 -57
  84. package/src/Calendar.svelte +0 -93
  85. package/src/Card.svelte +0 -45
  86. package/src/Carousel.svelte +0 -49
  87. package/src/CheckBox.svelte +0 -56
  88. package/src/Connector.svelte +0 -40
  89. package/src/DropDown.svelte +0 -68
  90. package/src/DropSearch.svelte +0 -37
  91. package/src/Fillable.svelte +0 -19
  92. package/src/GraphPaper.svelte +0 -43
  93. package/src/Icon.svelte +0 -81
  94. package/src/Item.svelte +0 -25
  95. package/src/Link.svelte +0 -21
  96. package/src/List.svelte +0 -89
  97. package/src/ListBody.svelte +0 -43
  98. package/src/Message.svelte +0 -11
  99. package/src/MultiSelect.svelte +0 -48
  100. package/src/NestedList.svelte +0 -78
  101. package/src/NestedPaginator.svelte +0 -63
  102. package/src/Node.svelte +0 -76
  103. package/src/Overlay.svelte +0 -21
  104. package/src/PageNavigator.svelte +0 -94
  105. package/src/PickOne.svelte +0 -60
  106. package/src/Pill.svelte +0 -41
  107. package/src/ProgressBar.svelte +0 -21
  108. package/src/ProgressDots.svelte +0 -53
  109. package/src/RadioGroup.svelte +0 -52
  110. package/src/Range.svelte +0 -45
  111. package/src/RangeMinMax.svelte +0 -124
  112. package/src/RangeSlider.svelte +0 -79
  113. package/src/RangeTick.svelte +0 -28
  114. package/src/Rating.svelte +0 -95
  115. package/src/ResponsiveGrid.svelte +0 -88
  116. package/src/Scrollable.svelte +0 -7
  117. package/src/Select.svelte +0 -114
  118. package/src/Separator.svelte +0 -1
  119. package/src/Slider.svelte +0 -14
  120. package/src/SlidingColumns.svelte +0 -50
  121. package/src/Stage.svelte +0 -41
  122. package/src/Stepper.svelte +0 -66
  123. package/src/Summary.svelte +0 -22
  124. package/src/Switch.svelte +0 -106
  125. package/src/TableCell.svelte +0 -51
  126. package/src/TableHeaderCell.svelte +0 -54
  127. package/src/Tabs.svelte +0 -176
  128. package/src/Tilt.svelte +0 -66
  129. package/src/Toggle.svelte +0 -58
  130. package/src/ToggleThemeMode.svelte +0 -23
  131. package/src/Tree.svelte +0 -80
  132. package/src/TreeTable.svelte +0 -171
  133. package/src/ValidationReport.svelte +0 -23
  134. package/src/constants.js +0 -4
  135. package/src/index.js +0 -48
  136. package/src/lib/fields.js +0 -118
  137. package/src/lib/form.js +0 -72
  138. package/src/lib/index.js +0 -13
  139. package/src/lib/layout.js +0 -63
  140. package/src/lib/nested.js +0 -192
  141. package/src/lib/schema.js +0 -32
  142. package/src/lib/select.js +0 -38
  143. package/src/lib/tree.js +0 -22
  144. package/src/tree/List.spec.svelte.js +0 -84
  145. package/src/tree/List.svelte +0 -78
  146. package/src/tree/Node.spec.svelte.js +0 -104
  147. package/src/tree/Node.svelte +0 -80
  148. package/src/tree/Root.spec.svelte.js +0 -63
  149. package/src/tree/Root.svelte +0 -81
  150. package/src/types.js +0 -9
  151. package/src/wrappers/Category.svelte +0 -27
  152. package/src/wrappers/Section.svelte +0 -16
  153. package/src/wrappers/Wrapper.svelte +0 -12
  154. package/src/wrappers/index.js +0 -3
@@ -0,0 +1,369 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MultiSelect — Trigger + dropdown with List-style flatView content.
4
+ *
5
+ * Same architecture as Select but with toggle selection (dropdown stays open),
6
+ * checkbox indicators, and tags display in trigger.
7
+ *
8
+ * Data attributes:
9
+ * data-select / data-multiselect — root container (both for theme compat)
10
+ * data-select-trigger — trigger button
11
+ * data-select-value — selected value display area
12
+ * data-select-tags — tags container
13
+ * data-select-tag — individual tag
14
+ * data-select-tag-text — tag text
15
+ * data-select-tag-remove — tag remove button
16
+ * data-select-count — count indicator when exceeding maxDisplay
17
+ * data-select-placeholder — placeholder text
18
+ * data-select-arrow — dropdown arrow icon
19
+ * data-select-dropdown — dropdown container
20
+ * data-select-option — option items
21
+ * data-select-checkbox — checkbox indicator
22
+ * data-select-group-label — group header (non-interactive)
23
+ * data-select-group-icon — icon inside group label
24
+ * data-select-divider — divider between groups
25
+ * data-path, data-selected, data-checked, data-disabled, data-open, data-size
26
+ */
27
+ // @ts-nocheck
28
+ import type { ProxyItem } from '@rokkit/states'
29
+ import { Wrapper, ProxyTree } from '@rokkit/states'
30
+ import { Navigator, Trigger } from '@rokkit/actions'
31
+ import { DEFAULT_STATE_ICONS, resolveSnippet, ITEM_SNIPPET, GROUP_SNIPPET } from '@rokkit/core'
32
+ import ItemContent from './ItemContent.svelte'
33
+
34
+ interface MultiSelectIcons {
35
+ opened?: string
36
+ closed?: string
37
+ checked?: string
38
+ remove?: string
39
+ }
40
+
41
+ let {
42
+ items = [],
43
+ fields = {},
44
+ value = $bindable<unknown[]>([]),
45
+ selected = $bindable<unknown[]>([]),
46
+ placeholder = 'Select...',
47
+ size = 'md',
48
+ disabled = false,
49
+ maxDisplay = 3,
50
+ align = 'start',
51
+ direction = 'down',
52
+ icons: userIcons = {} as MultiSelectIcons,
53
+ onchange,
54
+ class: className = '',
55
+ ...snippets
56
+ }: {
57
+ items?: unknown[]
58
+ fields?: Record<string, string>
59
+ value?: unknown[]
60
+ selected?: unknown[]
61
+ placeholder?: string
62
+ size?: string
63
+ disabled?: boolean
64
+ maxDisplay?: number
65
+ align?: 'start' | 'end'
66
+ direction?: 'up' | 'down'
67
+ icons?: MultiSelectIcons
68
+ onchange?: (values: unknown[], items: unknown[]) => void
69
+ class?: string
70
+ [key: string]: unknown
71
+ } = $props()
72
+
73
+ const icons = $derived({ ...DEFAULT_STATE_ICONS.selector, ...DEFAULT_STATE_ICONS.checkbox, ...DEFAULT_STATE_ICONS.action, ...userIcons })
74
+
75
+ // ─── Dropdown state ───────────────────────────────────────────────────────
76
+
77
+ let isOpen = $state(false)
78
+ let selectRef = $state<HTMLElement | null>(null)
79
+ let triggerRef = $state<HTMLElement | null>(null)
80
+ let dropdownRef = $state<HTMLElement | null>(null)
81
+
82
+ // ─── Pre-process items ────────────────────────────────────────────────────
83
+
84
+ const childrenField = $derived(fields?.children || 'children')
85
+
86
+ // Force groups expanded + disabled (non-navigable labels)
87
+ const processedItems = $derived(
88
+ items.map((item) => {
89
+ const children = item[childrenField]
90
+ if (Array.isArray(children) && children.length > 0) {
91
+ return { ...item, expanded: true, disabled: true }
92
+ }
93
+ return item
94
+ })
95
+ )
96
+
97
+ // ─── Wrapper ──────────────────────────────────────────────────────────────
98
+
99
+ function handleSelect(extractedValue: unknown, proxy: ProxyItem) {
100
+ if (proxy.disabled) return
101
+ toggleItemSelection(extractedValue)
102
+ }
103
+
104
+ const proxyTree = $derived(new ProxyTree(processedItems, fields))
105
+ const wrapper = $derived(new Wrapper(proxyTree, { onselect: handleSelect }))
106
+
107
+ // Override cancel/blur to close dropdown
108
+ $effect(() => {
109
+ const w = wrapper
110
+ w.cancel = () => {
111
+ isOpen = false
112
+ triggerRef?.focus()
113
+ }
114
+ w.blur = () => {
115
+ isOpen = false
116
+ }
117
+ })
118
+
119
+ // When wrapper recreates while open, focus first item
120
+ $effect(() => {
121
+ const _w = wrapper
122
+ if (isOpen) _w.first(null)
123
+ })
124
+
125
+ // ─── Selection logic ──────────────────────────────────────────────────────
126
+
127
+ function isItemSelected(extractedValue: unknown): boolean {
128
+ return (value ?? []).some((v) => v === extractedValue)
129
+ }
130
+
131
+ function toggleItemSelection(extractedValue: unknown) {
132
+ const currentValues = value ?? []
133
+ const alreadySelected = currentValues.some((v) => v === extractedValue)
134
+
135
+ let newValues: unknown[]
136
+ let newItems: unknown[]
137
+
138
+ if (alreadySelected) {
139
+ newValues = currentValues.filter((v) => v !== extractedValue)
140
+ // Rebuild selected items from remaining values
141
+ newItems = []
142
+ for (const [, proxy] of wrapper.lookup) {
143
+ if (!proxy.hasChildren && newValues.some((v) => v === proxy.value)) {
144
+ newItems.push(proxy.original)
145
+ }
146
+ }
147
+ } else {
148
+ newValues = [...currentValues, extractedValue]
149
+ // Rebuild selected items from lookup to include all values
150
+ newItems = []
151
+ for (const [, proxy] of wrapper.lookup) {
152
+ if (!proxy.hasChildren && newValues.some((v) => v === proxy.value)) {
153
+ newItems.push(proxy.original)
154
+ }
155
+ }
156
+ }
157
+
158
+ value = newValues
159
+ selected = newItems
160
+ onchange?.(newValues, newItems)
161
+ }
162
+
163
+ function removeTag(extractedValue: unknown) {
164
+ const currentValues = value ?? []
165
+ const newValues = currentValues.filter((v) => v !== extractedValue)
166
+ const newItems: unknown[] = []
167
+ for (const [, proxy] of wrapper.lookup) {
168
+ if (!proxy.hasChildren && newValues.some((v) => v === proxy.value)) {
169
+ newItems.push(proxy.original)
170
+ }
171
+ }
172
+ value = newValues
173
+ selected = newItems
174
+ onchange?.(newValues, newItems)
175
+ }
176
+
177
+ // ─── Selected items for tags display ──────────────────────────────────────
178
+
179
+ const selectedProxies = $derived.by(() => {
180
+ const vals = value ?? []
181
+ if (vals.length === 0) return []
182
+ const result: ProxyItem[] = []
183
+ for (const [, proxy] of wrapper.lookup) {
184
+ if (!proxy.hasChildren && vals.some((v) => v === proxy.value)) {
185
+ result.push(proxy)
186
+ }
187
+ }
188
+ return result
189
+ })
190
+
191
+ // ─── Trigger action ───────────────────────────────────────────────────────
192
+
193
+ $effect(() => {
194
+ if (!triggerRef || !selectRef || disabled) return
195
+ const t = new Trigger(triggerRef, selectRef, {
196
+ isOpen: () => isOpen,
197
+ onopen: () => {
198
+ isOpen = true
199
+ requestAnimationFrame(() => wrapper.first(null))
200
+ },
201
+ onclose: () => { isOpen = false },
202
+ onlast: () => requestAnimationFrame(() => wrapper.last(null))
203
+ })
204
+ return () => t.destroy()
205
+ })
206
+
207
+ // ─── Navigator on dropdown ────────────────────────────────────────────────
208
+
209
+ $effect(() => {
210
+ if (!isOpen || !dropdownRef) return
211
+ const dir = getComputedStyle(dropdownRef).direction || 'ltr'
212
+ const nav = new Navigator(dropdownRef, wrapper, { dir })
213
+ return () => nav.destroy()
214
+ })
215
+
216
+ // DOM focus sync
217
+ $effect(() => {
218
+ const key = wrapper.focusedKey
219
+ if (!isOpen || !dropdownRef || !key) return
220
+ requestAnimationFrame(() => {
221
+ const target = dropdownRef?.querySelector(`[data-path="${key}"]`) as HTMLElement | null
222
+ if (target && target !== document.activeElement) {
223
+ target.focus()
224
+ target.scrollIntoView?.({ block: 'nearest' })
225
+ }
226
+ })
227
+ })
228
+
229
+ // ─── Helpers ──────────────────────────────────────────────────────────────
230
+
231
+ const groupDividers = $derived.by(() => {
232
+ const set = new Set<string>()
233
+ let foundFirst = false
234
+ for (const node of wrapper.flatView) {
235
+ if (node.hasChildren) {
236
+ if (foundFirst) set.add(node.key)
237
+ foundFirst = true
238
+ }
239
+ }
240
+ return set
241
+ })
242
+ </script>
243
+
244
+ {#snippet defaultOptionContent(proxy: ProxyItem)}
245
+ <ItemContent {proxy} />
246
+ {/snippet}
247
+
248
+ {#snippet defaultGroupContent(proxy: ProxyItem)}
249
+ {#if proxy.get('icon')}
250
+ <span data-select-group-icon class={proxy.get('icon')} aria-hidden="true"></span>
251
+ {/if}
252
+ <span>{proxy.label}</span>
253
+ {/snippet}
254
+
255
+ <div
256
+ bind:this={selectRef}
257
+ data-select
258
+ data-multiselect
259
+ data-open={isOpen || undefined}
260
+ data-size={size}
261
+ data-disabled={disabled || undefined}
262
+ data-align={align}
263
+ data-direction={direction}
264
+ class={className || undefined}
265
+ >
266
+ <button
267
+ bind:this={triggerRef}
268
+ type="button"
269
+ data-select-trigger
270
+ {disabled}
271
+ aria-haspopup="listbox"
272
+ aria-expanded={isOpen}
273
+ >
274
+ <span data-select-value>
275
+ {#if selectedProxies.length > 0}
276
+ {#if selectedProxies.length <= maxDisplay}
277
+ <span data-select-tags>
278
+ {#each selectedProxies as proxy (proxy.value)}
279
+ <span data-select-tag>
280
+ <span data-select-tag-text>{proxy.label}</span>
281
+ <span
282
+ role="button"
283
+ tabindex="0"
284
+ data-select-tag-remove
285
+ aria-label="Remove {proxy.label}"
286
+ onclick={(e) => {
287
+ e.stopPropagation()
288
+ removeTag(proxy.value)
289
+ }}
290
+ onkeydown={(e) => {
291
+ if (e.key === 'Enter' || e.key === ' ') {
292
+ e.preventDefault()
293
+ e.stopPropagation()
294
+ removeTag(proxy.value)
295
+ }
296
+ }}
297
+ >
298
+ <span class={icons.remove} aria-hidden="true"></span>
299
+ </span>
300
+ </span>
301
+ {/each}
302
+ </span>
303
+ {:else}
304
+ <span data-select-count>{selectedProxies.length} selected</span>
305
+ {/if}
306
+ {:else}
307
+ <span data-select-placeholder>{placeholder}</span>
308
+ {/if}
309
+ </span>
310
+ <span data-select-arrow class={icons.opened} aria-hidden="true"></span>
311
+ </button>
312
+
313
+ {#if isOpen}
314
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
315
+ <div
316
+ bind:this={dropdownRef}
317
+ data-select-dropdown
318
+ role="listbox"
319
+ aria-multiselectable="true"
320
+ aria-orientation="vertical"
321
+ >
322
+ {#each wrapper.flatView as node (node.key)}
323
+ {@const proxy = node.proxy}
324
+ {@const sel = !node.hasChildren && isItemSelected(proxy.value)}
325
+ {@const content = resolveSnippet(snippets as Record<string, unknown>, proxy, node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET)}
326
+
327
+ {#if node.type === 'separator'}
328
+ <hr data-select-separator />
329
+ {:else if node.hasChildren}
330
+ {#if groupDividers.has(node.key)}
331
+ <div data-select-divider></div>
332
+ {/if}
333
+ <div data-select-group-label role="presentation">
334
+ {#if content}
335
+ {@render content(proxy)}
336
+ {:else}
337
+ {@render defaultGroupContent(proxy)}
338
+ {/if}
339
+ </div>
340
+ {:else}
341
+ <button
342
+ type="button"
343
+ data-select-option
344
+ data-path={node.key}
345
+ data-level={node.level}
346
+ data-selected={sel || undefined}
347
+ data-disabled={proxy.disabled || undefined}
348
+ role="option"
349
+ aria-selected={sel}
350
+ aria-label={proxy.label}
351
+ disabled={proxy.disabled || disabled}
352
+ tabindex="-1"
353
+ >
354
+ <span data-select-checkbox data-checked={sel || undefined}>
355
+ {#if sel}
356
+ <span class={icons.checked} aria-hidden="true"></span>
357
+ {/if}
358
+ </span>
359
+ {#if content}
360
+ {@render content(proxy)}
361
+ {:else}
362
+ {@render defaultOptionContent(proxy)}
363
+ {/if}
364
+ </button>
365
+ {/if}
366
+ {/each}
367
+ </div>
368
+ {/if}
369
+ </div>