@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.
- package/README.md +198 -101
- package/package.json +52 -34
- package/src/components/BreadCrumbs.svelte +82 -0
- package/src/components/Button.svelte +87 -0
- package/src/components/ButtonGroup.svelte +18 -0
- package/src/components/Card.svelte +61 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +185 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/FloatingAction.svelte +331 -0
- package/src/components/FloatingNavigation.svelte +228 -0
- package/src/components/ItemContent.svelte +24 -0
- package/src/components/List.svelte +476 -0
- package/src/components/Menu.svelte +421 -0
- package/src/components/MultiSelect.svelte +521 -0
- package/src/components/PaletteManager.svelte +354 -0
- package/src/components/Pill.svelte +78 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +325 -0
- package/src/components/Rating.svelte +91 -0
- package/src/components/Reveal.svelte +58 -0
- package/src/components/SearchFilter.svelte +80 -0
- package/src/components/Select.svelte +585 -0
- package/src/{Shine.svelte → components/Shine.svelte} +29 -21
- package/src/components/Stepper.svelte +169 -0
- package/src/components/Switch.svelte +75 -0
- package/src/components/Table.svelte +243 -0
- package/src/components/Tabs.svelte +268 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +157 -0
- package/src/components/Toolbar.svelte +307 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tree.svelte +613 -0
- package/src/components/index.ts +33 -0
- package/src/index.ts +41 -0
- package/src/types/button.ts +83 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +118 -0
- package/src/types/floating-navigation.ts +68 -0
- package/src/types/index.ts +53 -0
- package/src/types/item-proxy.ts +358 -0
- package/src/types/list.ts +196 -0
- package/src/types/menu.ts +195 -0
- package/src/types/palette.ts +143 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +206 -0
- package/src/types/switch.ts +64 -0
- package/src/types/table.ts +210 -0
- package/src/types/tabs.ts +124 -0
- package/src/types/timeline.ts +51 -0
- package/src/types/toggle.ts +109 -0
- package/src/types/toolbar.ts +164 -0
- package/src/types/tree.ts +259 -0
- package/src/utils/palette.ts +582 -0
- package/src/utils/shiki.ts +122 -0
- package/dist/constants.d.ts +0 -2
- package/dist/index.d.ts +0 -41
- package/dist/lib/fields.d.ts +0 -16
- package/dist/lib/form.d.ts +0 -95
- package/dist/lib/index.d.ts +0 -6
- package/dist/lib/layout.d.ts +0 -7
- package/dist/lib/nested.d.ts +0 -48
- package/dist/lib/schema.d.ts +0 -7
- package/dist/lib/select.d.ts +0 -8
- package/dist/lib/tree.d.ts +0 -9
- package/dist/tree/List.spec.svelte.d.ts +0 -1
- package/dist/tree/Node.spec.svelte.d.ts +0 -1
- package/dist/tree/Root.spec.svelte.d.ts +0 -1
- package/dist/types.d.ts +0 -5
- package/dist/wrappers/index.d.ts +0 -3
- package/src/Accordion.svelte +0 -118
- package/src/BreadCrumbs.svelte +0 -32
- package/src/Button.svelte +0 -57
- package/src/Calendar.svelte +0 -93
- package/src/Card.svelte +0 -45
- package/src/Carousel.svelte +0 -49
- package/src/CheckBox.svelte +0 -56
- package/src/Connector.svelte +0 -40
- package/src/DropDown.svelte +0 -68
- package/src/DropSearch.svelte +0 -37
- package/src/Fillable.svelte +0 -19
- package/src/GraphPaper.svelte +0 -43
- package/src/Icon.svelte +0 -81
- package/src/Item.svelte +0 -25
- package/src/Link.svelte +0 -21
- package/src/List.svelte +0 -89
- package/src/ListBody.svelte +0 -43
- package/src/Message.svelte +0 -11
- package/src/MultiSelect.svelte +0 -48
- package/src/NestedList.svelte +0 -78
- package/src/NestedPaginator.svelte +0 -63
- package/src/Node.svelte +0 -76
- package/src/Overlay.svelte +0 -21
- package/src/PageNavigator.svelte +0 -94
- package/src/PickOne.svelte +0 -60
- package/src/Pill.svelte +0 -41
- package/src/ProgressBar.svelte +0 -21
- package/src/ProgressDots.svelte +0 -53
- package/src/RadioGroup.svelte +0 -52
- package/src/Range.svelte +0 -45
- package/src/RangeMinMax.svelte +0 -124
- package/src/RangeSlider.svelte +0 -79
- package/src/RangeTick.svelte +0 -28
- package/src/Rating.svelte +0 -95
- package/src/ResponsiveGrid.svelte +0 -88
- package/src/Scrollable.svelte +0 -7
- package/src/Select.svelte +0 -114
- package/src/Separator.svelte +0 -1
- package/src/Slider.svelte +0 -14
- package/src/SlidingColumns.svelte +0 -50
- package/src/Stage.svelte +0 -41
- package/src/Stepper.svelte +0 -66
- package/src/Summary.svelte +0 -22
- package/src/Switch.svelte +0 -106
- package/src/TableCell.svelte +0 -51
- package/src/TableHeaderCell.svelte +0 -54
- package/src/Tabs.svelte +0 -176
- package/src/Tilt.svelte +0 -66
- package/src/Toggle.svelte +0 -58
- package/src/ToggleThemeMode.svelte +0 -23
- package/src/Tree.svelte +0 -80
- package/src/TreeTable.svelte +0 -171
- package/src/ValidationReport.svelte +0 -23
- package/src/constants.js +0 -4
- package/src/index.js +0 -48
- package/src/lib/fields.js +0 -118
- package/src/lib/form.js +0 -72
- package/src/lib/index.js +0 -13
- package/src/lib/layout.js +0 -63
- package/src/lib/nested.js +0 -192
- package/src/lib/schema.js +0 -32
- package/src/lib/select.js +0 -38
- package/src/lib/tree.js +0 -22
- package/src/tree/List.spec.svelte.js +0 -84
- package/src/tree/List.svelte +0 -78
- package/src/tree/Node.spec.svelte.js +0 -104
- package/src/tree/Node.svelte +0 -80
- package/src/tree/Root.spec.svelte.js +0 -63
- package/src/tree/Root.svelte +0 -81
- package/src/types.js +0 -9
- package/src/wrappers/Category.svelte +0 -27
- package/src/wrappers/Section.svelte +0 -16
- package/src/wrappers/Wrapper.svelte +0 -12
- package/src/wrappers/index.js +0 -3
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
MultiSelectProps,
|
|
4
|
+
SelectItem,
|
|
5
|
+
SelectItemSnippet,
|
|
6
|
+
SelectItemHandlers,
|
|
7
|
+
SelectStateIcons
|
|
8
|
+
} from '../types/select.js'
|
|
9
|
+
import { getSnippet, defaultSelectStateIcons } from '../types/select.js'
|
|
10
|
+
import { ItemProxy } from '../types/item-proxy.js'
|
|
11
|
+
import ItemContent from './ItemContent.svelte'
|
|
12
|
+
import { ListController } from '@rokkit/states'
|
|
13
|
+
import { navigator } from '@rokkit/actions'
|
|
14
|
+
import { untrack } from 'svelte'
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
options = [],
|
|
18
|
+
fields: userFields,
|
|
19
|
+
value = $bindable<unknown[]>([]),
|
|
20
|
+
selected = $bindable<SelectItem[]>([]),
|
|
21
|
+
placeholder = 'Select...',
|
|
22
|
+
size = 'md',
|
|
23
|
+
align = 'left',
|
|
24
|
+
direction = 'down',
|
|
25
|
+
maxRows = 5,
|
|
26
|
+
maxDisplay = 3,
|
|
27
|
+
disabled = false,
|
|
28
|
+
onchange,
|
|
29
|
+
class: className = '',
|
|
30
|
+
icons: userIcons,
|
|
31
|
+
item: itemSnippet,
|
|
32
|
+
groupLabel: groupLabelSnippet,
|
|
33
|
+
selectedValues: selectedValuesSnippet,
|
|
34
|
+
...snippets
|
|
35
|
+
}: MultiSelectProps & { [key: string]: SelectItemSnippet | unknown } = $props()
|
|
36
|
+
|
|
37
|
+
// Merge icons with defaults
|
|
38
|
+
const icons = $derived<SelectStateIcons>({ ...defaultSelectStateIcons, ...userIcons })
|
|
39
|
+
|
|
40
|
+
// Normalize alignment value
|
|
41
|
+
const normalizedAlign = $derived(align === 'left' || align === 'start' ? 'left' : 'right')
|
|
42
|
+
|
|
43
|
+
// Default row heights by size (used until we measure actual height)
|
|
44
|
+
const defaultRowHeight = $derived(size === 'sm' ? 28 : size === 'lg' ? 40 : 34)
|
|
45
|
+
|
|
46
|
+
// Measured row height (updated when dropdown opens)
|
|
47
|
+
let measuredRowHeight = $state<number | null>(null)
|
|
48
|
+
|
|
49
|
+
// Use measured height if available, otherwise fall back to default
|
|
50
|
+
const maxHeight = $derived(maxRows * (measuredRowHeight ?? defaultRowHeight))
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create an ItemProxy for the given item
|
|
54
|
+
*/
|
|
55
|
+
function createProxy(item: SelectItem): ItemProxy {
|
|
56
|
+
return new ItemProxy(item, userFields)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Flatten options into navigable items for the controller ────
|
|
60
|
+
|
|
61
|
+
/** Flat array of raw selectable items (for controller) */
|
|
62
|
+
const flatItems = $derived.by(() => {
|
|
63
|
+
const items: SelectItem[] = []
|
|
64
|
+
for (const option of options) {
|
|
65
|
+
const proxy = createProxy(option)
|
|
66
|
+
if (proxy.hasChildren) {
|
|
67
|
+
for (const child of proxy.children) {
|
|
68
|
+
items.push(child as SelectItem)
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
items.push(option)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return items
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/** Map from raw item → flat index key (for data-path). Uses Map to support primitives. */
|
|
78
|
+
const itemPathMap = $derived.by(() => {
|
|
79
|
+
const map = new Map<unknown, string>()
|
|
80
|
+
flatItems.forEach((item, index) => {
|
|
81
|
+
map.set(item, String(index))
|
|
82
|
+
})
|
|
83
|
+
return map
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ─── Controller + Navigator ────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
let isOpen = $state(false)
|
|
89
|
+
let selectRef = $state<HTMLDivElement | null>(null)
|
|
90
|
+
let dropdownRef = $state<HTMLDivElement | null>(null)
|
|
91
|
+
|
|
92
|
+
let controller = untrack(() => new ListController(flatItems, undefined, userFields))
|
|
93
|
+
|
|
94
|
+
$effect(() => {
|
|
95
|
+
controller.update(flatItems)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Find selected items based on current value (extracted primitives)
|
|
99
|
+
const selectedItems = $derived.by(() => {
|
|
100
|
+
if (!value || value.length === 0) return [] as { proxy: ItemProxy; original: SelectItem }[]
|
|
101
|
+
return flatItems
|
|
102
|
+
.filter((item) => {
|
|
103
|
+
const proxy = createProxy(item)
|
|
104
|
+
const extracted = proxy.itemValue
|
|
105
|
+
return value.some((v) => v === extracted)
|
|
106
|
+
})
|
|
107
|
+
.map((item) => ({ proxy: createProxy(item), original: item }))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// Focus the element matching controller.focusedKey on navigator action events
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (!dropdownRef) return
|
|
113
|
+
const el = dropdownRef
|
|
114
|
+
|
|
115
|
+
function onAction(event: Event) {
|
|
116
|
+
const detail = (event as CustomEvent).detail
|
|
117
|
+
|
|
118
|
+
if (detail.name === 'move') {
|
|
119
|
+
const key = controller.focusedKey
|
|
120
|
+
if (key) {
|
|
121
|
+
const target = el.querySelector(`[data-path="${key}"]`) as HTMLElement | null
|
|
122
|
+
if (target && target !== document.activeElement) {
|
|
123
|
+
target.focus()
|
|
124
|
+
target.scrollIntoView?.({ block: 'nearest' })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (detail.name === 'select') {
|
|
130
|
+
handleSelectAction()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
el.addEventListener('action', onAction)
|
|
135
|
+
return () => el.removeEventListener('action', onAction)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Handle the navigator's select action — toggle selection (don't close)
|
|
140
|
+
*/
|
|
141
|
+
function handleSelectAction() {
|
|
142
|
+
const key = controller.focusedKey
|
|
143
|
+
if (!key) return
|
|
144
|
+
|
|
145
|
+
const proxy = controller.lookup.get(key)
|
|
146
|
+
if (!proxy) return
|
|
147
|
+
|
|
148
|
+
const item = proxy.value as SelectItem
|
|
149
|
+
toggleItemSelection(item)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sync DOM focus to controller state
|
|
154
|
+
*/
|
|
155
|
+
function handleFocusIn(event: FocusEvent) {
|
|
156
|
+
const target = event.target as HTMLElement
|
|
157
|
+
if (!target) return
|
|
158
|
+
const path = target.dataset.path
|
|
159
|
+
if (path !== undefined) {
|
|
160
|
+
controller.moveTo(path)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── Selection logic ───────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
function toggleItemSelection(item: SelectItem) {
|
|
167
|
+
const proxy = createProxy(item)
|
|
168
|
+
if (proxy.disabled) return
|
|
169
|
+
|
|
170
|
+
const extracted = proxy.itemValue
|
|
171
|
+
const isAlreadySelected = (value ?? []).some((v) => v === extracted)
|
|
172
|
+
|
|
173
|
+
let newValues: unknown[]
|
|
174
|
+
let newItems: SelectItem[]
|
|
175
|
+
|
|
176
|
+
if (isAlreadySelected) {
|
|
177
|
+
newValues = (value ?? []).filter((v) => v !== extracted)
|
|
178
|
+
newItems = selectedItems
|
|
179
|
+
.filter((si) => si.proxy.itemValue !== extracted)
|
|
180
|
+
.map((si) => si.original)
|
|
181
|
+
} else {
|
|
182
|
+
newValues = [...(value ?? []), extracted]
|
|
183
|
+
newItems = [...selectedItems.map((si) => si.original), item]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
value = newValues
|
|
187
|
+
selected = newItems
|
|
188
|
+
onchange?.(newValues, newItems)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function removeItem(item: { proxy: ItemProxy; original: SelectItem }) {
|
|
192
|
+
const extracted = item.proxy.itemValue
|
|
193
|
+
const newValues = (value ?? []).filter((v) => v !== extracted)
|
|
194
|
+
const newItems = selectedItems
|
|
195
|
+
.filter((si) => si.proxy.itemValue !== extracted)
|
|
196
|
+
.map((si) => si.original)
|
|
197
|
+
|
|
198
|
+
value = newValues
|
|
199
|
+
selected = newItems
|
|
200
|
+
onchange?.(newValues, newItems)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Dropdown open/close ───────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
function toggleDropdown() {
|
|
206
|
+
if (disabled) return
|
|
207
|
+
if (isOpen) {
|
|
208
|
+
closeDropdown()
|
|
209
|
+
} else {
|
|
210
|
+
openDropdown()
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function openDropdown() {
|
|
215
|
+
if (disabled || isOpen) return
|
|
216
|
+
isOpen = true
|
|
217
|
+
controller.moveFirst()
|
|
218
|
+
requestAnimationFrame(() => {
|
|
219
|
+
measureRowHeight()
|
|
220
|
+
focusCurrentItem()
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function closeDropdown() {
|
|
225
|
+
isOpen = false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function focusCurrentItem() {
|
|
229
|
+
if (!dropdownRef || !controller.focusedKey) return
|
|
230
|
+
const target = dropdownRef.querySelector(
|
|
231
|
+
`[data-path="${controller.focusedKey}"]`
|
|
232
|
+
) as HTMLElement | null
|
|
233
|
+
if (target) {
|
|
234
|
+
target.focus()
|
|
235
|
+
target.scrollIntoView?.({ block: 'nearest' })
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function measureRowHeight() {
|
|
240
|
+
const dropdown = selectRef?.querySelector('[data-select-dropdown]')
|
|
241
|
+
if (dropdown) {
|
|
242
|
+
const firstOption = dropdown.querySelector('[data-select-option]')
|
|
243
|
+
if (firstOption) {
|
|
244
|
+
const height = firstOption.getBoundingClientRect().height
|
|
245
|
+
if (height > 0) {
|
|
246
|
+
measuredRowHeight = height
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ─── Trigger keyboard handling ─────────────────────────────────
|
|
253
|
+
|
|
254
|
+
function handleTriggerKeyDown(event: KeyboardEvent) {
|
|
255
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
256
|
+
event.preventDefault()
|
|
257
|
+
openDropdown()
|
|
258
|
+
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
259
|
+
event.preventDefault()
|
|
260
|
+
toggleDropdown()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── Escape + click-outside ────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
function handleEscapeKey(event: KeyboardEvent) {
|
|
267
|
+
if (!isOpen) return
|
|
268
|
+
if (event.key === 'Escape') {
|
|
269
|
+
event.preventDefault()
|
|
270
|
+
closeDropdown()
|
|
271
|
+
const trigger = selectRef?.querySelector('[data-select-trigger]') as HTMLElement | undefined
|
|
272
|
+
trigger?.focus()
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleClickOutside(event: MouseEvent) {
|
|
277
|
+
if (selectRef && !selectRef.contains(event.target as Node)) {
|
|
278
|
+
closeDropdown()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
$effect(() => {
|
|
283
|
+
if (isOpen) {
|
|
284
|
+
document.addEventListener('click', handleClickOutside, true)
|
|
285
|
+
document.addEventListener('keydown', handleEscapeKey)
|
|
286
|
+
}
|
|
287
|
+
return () => {
|
|
288
|
+
document.removeEventListener('click', handleClickOutside, true)
|
|
289
|
+
document.removeEventListener('keydown', handleEscapeKey)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// ─── Snippet + rendering helpers ───────────────────────────────
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Handle direct Enter/Space on an option.
|
|
297
|
+
* Stops propagation to prevent navigator from double-handling.
|
|
298
|
+
*/
|
|
299
|
+
function handleItemToggle(item: SelectItem) {
|
|
300
|
+
toggleItemSelection(item)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create handlers object for custom snippets
|
|
305
|
+
*/
|
|
306
|
+
function createHandlers(item: SelectItem): SelectItemHandlers {
|
|
307
|
+
return {
|
|
308
|
+
onclick: () => handleItemToggle(item),
|
|
309
|
+
onkeydown: (event: KeyboardEvent) => {
|
|
310
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
311
|
+
event.preventDefault()
|
|
312
|
+
event.stopPropagation()
|
|
313
|
+
handleItemToggle(item)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Resolve which snippet to use for an item
|
|
321
|
+
*/
|
|
322
|
+
function resolveItemSnippet(proxy: ItemProxy): SelectItemSnippet | null {
|
|
323
|
+
const snippetName = proxy.snippetName
|
|
324
|
+
if (snippetName) {
|
|
325
|
+
const namedSnippet = getSnippet(snippets, snippetName)
|
|
326
|
+
if (namedSnippet) {
|
|
327
|
+
return namedSnippet as SelectItemSnippet
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return itemSnippet ?? null
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Check if an item is currently selected
|
|
335
|
+
*/
|
|
336
|
+
function isSelected(proxy: ItemProxy): boolean {
|
|
337
|
+
const extracted = proxy.itemValue
|
|
338
|
+
return (value ?? []).some((v) => v === extracted)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function shouldShowDivider(optionIndex: number, isGroup: boolean): boolean {
|
|
342
|
+
return isGroup && optionIndex > 0
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get the data-path key for a raw item
|
|
347
|
+
*/
|
|
348
|
+
function getPathKey(item: SelectItem): string | undefined {
|
|
349
|
+
return itemPathMap.get(item)
|
|
350
|
+
}
|
|
351
|
+
</script>
|
|
352
|
+
|
|
353
|
+
{#snippet defaultOption(proxy: ItemProxy, handlers: SelectItemHandlers, isItemSelected: boolean, pathKey: string | undefined)}
|
|
354
|
+
<button
|
|
355
|
+
type="button"
|
|
356
|
+
data-select-option
|
|
357
|
+
data-path={pathKey}
|
|
358
|
+
data-disabled={proxy.disabled || undefined}
|
|
359
|
+
data-selected={isItemSelected || undefined}
|
|
360
|
+
role="option"
|
|
361
|
+
aria-selected={isItemSelected}
|
|
362
|
+
disabled={proxy.disabled}
|
|
363
|
+
aria-label={proxy.label}
|
|
364
|
+
onkeydown={handlers.onkeydown}
|
|
365
|
+
>
|
|
366
|
+
<span data-select-checkbox data-checked={isItemSelected || undefined}>
|
|
367
|
+
{#if isItemSelected}
|
|
368
|
+
<span class={icons.checked} aria-hidden="true"></span>
|
|
369
|
+
{/if}
|
|
370
|
+
</span>
|
|
371
|
+
<ItemContent {proxy} />
|
|
372
|
+
</button>
|
|
373
|
+
{/snippet}
|
|
374
|
+
|
|
375
|
+
{#snippet defaultGroupLabel(proxy: ItemProxy)}
|
|
376
|
+
<div data-select-group-label role="presentation">
|
|
377
|
+
{#if proxy.icon}
|
|
378
|
+
<span data-select-group-icon class={proxy.icon} aria-hidden="true"></span>
|
|
379
|
+
{/if}
|
|
380
|
+
<span>{proxy.text}</span>
|
|
381
|
+
</div>
|
|
382
|
+
{/snippet}
|
|
383
|
+
|
|
384
|
+
{#snippet renderOption(item: SelectItem, proxy: ItemProxy, pathKey: string | undefined)}
|
|
385
|
+
{@const customSnippet = resolveItemSnippet(proxy)}
|
|
386
|
+
{@const handlers = createHandlers(item)}
|
|
387
|
+
{@const isItemSelected = isSelected(proxy)}
|
|
388
|
+
{#if customSnippet}
|
|
389
|
+
<div
|
|
390
|
+
data-select-option
|
|
391
|
+
data-select-option-custom
|
|
392
|
+
data-path={pathKey}
|
|
393
|
+
data-disabled={proxy.disabled || undefined}
|
|
394
|
+
data-selected={isItemSelected || undefined}
|
|
395
|
+
>
|
|
396
|
+
<svelte:boundary>
|
|
397
|
+
{@render customSnippet(item, proxy.fields, handlers, isItemSelected)}
|
|
398
|
+
{#snippet failed()}
|
|
399
|
+
{@render defaultOption(proxy, handlers, isItemSelected, pathKey)}
|
|
400
|
+
{/snippet}
|
|
401
|
+
</svelte:boundary>
|
|
402
|
+
</div>
|
|
403
|
+
{:else}
|
|
404
|
+
{@render defaultOption(proxy, handlers, isItemSelected, pathKey)}
|
|
405
|
+
{/if}
|
|
406
|
+
{/snippet}
|
|
407
|
+
|
|
408
|
+
{#snippet renderGroupLabel(proxy: ItemProxy)}
|
|
409
|
+
{#if groupLabelSnippet}
|
|
410
|
+
<svelte:boundary>
|
|
411
|
+
{@render groupLabelSnippet(proxy.original as SelectItem, proxy.fields)}
|
|
412
|
+
{#snippet failed()}
|
|
413
|
+
{@render defaultGroupLabel(proxy)}
|
|
414
|
+
{/snippet}
|
|
415
|
+
</svelte:boundary>
|
|
416
|
+
{:else}
|
|
417
|
+
{@render defaultGroupLabel(proxy)}
|
|
418
|
+
{/if}
|
|
419
|
+
{/snippet}
|
|
420
|
+
|
|
421
|
+
<div
|
|
422
|
+
bind:this={selectRef}
|
|
423
|
+
data-select
|
|
424
|
+
data-multiselect
|
|
425
|
+
data-open={isOpen || undefined}
|
|
426
|
+
data-size={size}
|
|
427
|
+
data-disabled={disabled || undefined}
|
|
428
|
+
data-align={normalizedAlign}
|
|
429
|
+
data-direction={direction}
|
|
430
|
+
class={className || undefined}
|
|
431
|
+
>
|
|
432
|
+
<button
|
|
433
|
+
type="button"
|
|
434
|
+
data-select-trigger
|
|
435
|
+
{disabled}
|
|
436
|
+
aria-haspopup="listbox"
|
|
437
|
+
aria-expanded={isOpen}
|
|
438
|
+
onclick={toggleDropdown}
|
|
439
|
+
onkeydown={handleTriggerKeyDown}
|
|
440
|
+
>
|
|
441
|
+
<span data-select-value>
|
|
442
|
+
{#if selectedValuesSnippet && selectedItems.length > 0}
|
|
443
|
+
{@render selectedValuesSnippet(
|
|
444
|
+
selectedItems.map((item) => item.original),
|
|
445
|
+
selectedItems[0]?.proxy.fields ?? {}
|
|
446
|
+
)}
|
|
447
|
+
{:else if selectedItems.length > 0}
|
|
448
|
+
{#if selectedItems.length <= maxDisplay}
|
|
449
|
+
<span data-select-tags>
|
|
450
|
+
{#each selectedItems as item (item.proxy.itemValue)}
|
|
451
|
+
<span data-select-tag>
|
|
452
|
+
<span data-select-tag-text>{item.proxy.text}</span>
|
|
453
|
+
<span
|
|
454
|
+
role="button"
|
|
455
|
+
tabindex="0"
|
|
456
|
+
data-select-tag-remove
|
|
457
|
+
aria-label="Remove {item.proxy.text}"
|
|
458
|
+
onclick={(e) => {
|
|
459
|
+
e.stopPropagation()
|
|
460
|
+
removeItem(item)
|
|
461
|
+
}}
|
|
462
|
+
onkeydown={(e) => {
|
|
463
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
464
|
+
e.preventDefault()
|
|
465
|
+
e.stopPropagation()
|
|
466
|
+
removeItem(item)
|
|
467
|
+
}
|
|
468
|
+
}}
|
|
469
|
+
>
|
|
470
|
+
<span class={icons.remove} aria-hidden="true"></span>
|
|
471
|
+
</span>
|
|
472
|
+
</span>
|
|
473
|
+
{/each}
|
|
474
|
+
</span>
|
|
475
|
+
{:else}
|
|
476
|
+
<span data-select-count>{selectedItems.length} selected</span>
|
|
477
|
+
{/if}
|
|
478
|
+
{:else}
|
|
479
|
+
<span data-select-placeholder>{placeholder}</span>
|
|
480
|
+
{/if}
|
|
481
|
+
</span>
|
|
482
|
+
<span data-select-arrow class={icons.opened} aria-hidden="true"></span>
|
|
483
|
+
</button>
|
|
484
|
+
|
|
485
|
+
{#if isOpen}
|
|
486
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
487
|
+
<div
|
|
488
|
+
bind:this={dropdownRef}
|
|
489
|
+
data-select-dropdown
|
|
490
|
+
role="listbox"
|
|
491
|
+
aria-multiselectable="true"
|
|
492
|
+
aria-orientation="vertical"
|
|
493
|
+
style="max-height: {maxHeight}px"
|
|
494
|
+
onfocusin={handleFocusIn}
|
|
495
|
+
use:navigator={{ wrapper: controller, orientation: 'vertical' }}
|
|
496
|
+
>
|
|
497
|
+
{#each options as option, optionIndex (optionIndex)}
|
|
498
|
+
{@const proxy = createProxy(option)}
|
|
499
|
+
|
|
500
|
+
{#if proxy.hasChildren}
|
|
501
|
+
{#if shouldShowDivider(optionIndex, true)}
|
|
502
|
+
<div data-select-divider role="separator"></div>
|
|
503
|
+
{/if}
|
|
504
|
+
|
|
505
|
+
<div data-select-group>
|
|
506
|
+
{@render renderGroupLabel(proxy)}
|
|
507
|
+
|
|
508
|
+
{#each proxy.children as child, childIndex (childIndex)}
|
|
509
|
+
{@const childProxy = proxy.createChildProxy(child)}
|
|
510
|
+
{@const pathKey = getPathKey(child as SelectItem)}
|
|
511
|
+
{@render renderOption(child as SelectItem, childProxy, pathKey)}
|
|
512
|
+
{/each}
|
|
513
|
+
</div>
|
|
514
|
+
{:else}
|
|
515
|
+
{@const pathKey = getPathKey(option)}
|
|
516
|
+
{@render renderOption(option, proxy, pathKey)}
|
|
517
|
+
{/if}
|
|
518
|
+
{/each}
|
|
519
|
+
</div>
|
|
520
|
+
{/if}
|
|
521
|
+
</div>
|