@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,268 @@
1
+ <script lang="ts">
2
+ import type { TabsProps, TabsItem, TabsItemHandlers } from '../types/tabs.js'
3
+ import { ItemProxy } from '../types/item-proxy.js'
4
+ import { ListController } from '@rokkit/states'
5
+ import { navigator } from '@rokkit/actions'
6
+ import { untrack } from 'svelte'
7
+
8
+ let {
9
+ options = [],
10
+ fields: userFields,
11
+ value = $bindable(),
12
+ orientation = 'horizontal',
13
+ position = 'before',
14
+ align = 'start',
15
+ name = 'tabs',
16
+ editable = false,
17
+ placeholder = 'Select a tab to view its content.',
18
+ disabled = false,
19
+ class: className = '',
20
+ onchange,
21
+ onselect,
22
+ onadd,
23
+ onremove,
24
+ tabItem: tabItemSnippet,
25
+ tabPanel: tabPanelSnippet,
26
+ empty: emptySnippet
27
+ }: TabsProps = $props()
28
+
29
+ /** Content field name from user fields or default */
30
+ const contentField = $derived((userFields as Record<string, string> | undefined)?.content ?? 'content')
31
+
32
+ let controller = untrack(() => new ListController(options, value, userFields))
33
+ let containerRef: HTMLElement | null = $state(null)
34
+ let lastSyncedValue: unknown = value
35
+
36
+ $effect(() => {
37
+ controller.update(options)
38
+ })
39
+
40
+ // Sync controller focus when value changes externally
41
+ $effect(() => {
42
+ if (value !== lastSyncedValue) {
43
+ lastSyncedValue = value
44
+ controller.moveToValue(value)
45
+ }
46
+ })
47
+
48
+ // Focus the tab matching controller.focusedKey on navigator move events
49
+ $effect(() => {
50
+ if (!containerRef) return
51
+ const el = containerRef
52
+
53
+ function handleAction(event: Event) {
54
+ const detail = (event as CustomEvent).detail
55
+
56
+ if (detail.name === 'move') {
57
+ const key = controller.focusedKey
58
+ if (key) {
59
+ const target = el.querySelector(`[data-path="${key}"]`) as HTMLElement | null
60
+ if (target && target !== document.activeElement) {
61
+ target.focus()
62
+ }
63
+ }
64
+ }
65
+
66
+ if (detail.name === 'select') {
67
+ handleSelectAction()
68
+ }
69
+ }
70
+
71
+ el.addEventListener('action', handleAction)
72
+ return () => el.removeEventListener('action', handleAction)
73
+ })
74
+
75
+ /**
76
+ * Create an ItemProxy for the given item
77
+ */
78
+ function createProxy(item: TabsItem): ItemProxy {
79
+ return new ItemProxy(item, userFields)
80
+ }
81
+
82
+ /**
83
+ * Check if an item is currently selected
84
+ */
85
+ function isSelected(proxy: ItemProxy): boolean {
86
+ return proxy.itemValue === value
87
+ }
88
+
89
+ /**
90
+ * Handle tab selection via navigator select action
91
+ */
92
+ function handleSelectAction() {
93
+ const key = controller.focusedKey
94
+ if (!key) return
95
+
96
+ const proxy = controller.lookup.get(key)
97
+ if (!proxy) return
98
+
99
+ const itemProxy = createProxy(proxy.value)
100
+ selectTab(itemProxy)
101
+ }
102
+
103
+ /**
104
+ * Select a tab by its proxy
105
+ */
106
+ function selectTab(proxy: ItemProxy) {
107
+ if (proxy.disabled || disabled) return
108
+ const itemValue = proxy.itemValue
109
+ if (itemValue !== value) {
110
+ value = itemValue
111
+ lastSyncedValue = itemValue
112
+ controller.moveToValue(itemValue)
113
+ onchange?.(itemValue, proxy.original as TabsItem)
114
+ }
115
+ onselect?.(itemValue, proxy.original as TabsItem)
116
+ }
117
+
118
+ /**
119
+ * Handle keyboard events on individual tabs (Enter/Space)
120
+ */
121
+ function handleKeyDown(event: KeyboardEvent, proxy: ItemProxy) {
122
+ if (event.key === 'Enter' || event.key === ' ') {
123
+ event.preventDefault()
124
+ selectTab(proxy)
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Create handlers object for custom snippets
130
+ */
131
+ function createHandlers(proxy: ItemProxy): TabsItemHandlers {
132
+ return {
133
+ onclick: () => selectTab(proxy),
134
+ onkeydown: (event: KeyboardEvent) => handleKeyDown(event, proxy)
135
+ }
136
+ }
137
+
138
+ function handleAdd() {
139
+ onadd?.()
140
+ }
141
+
142
+ function handleRemove(proxy: ItemProxy) {
143
+ onremove?.(proxy.itemValue)
144
+ }
145
+
146
+ /**
147
+ * Get the panel content for a tab item
148
+ */
149
+ function getContent(item: TabsItem): unknown {
150
+ return item[contentField]
151
+ }
152
+ </script>
153
+
154
+ {#snippet defaultTabItem(proxy: ItemProxy, handlers: TabsItemHandlers, selected: boolean, key: string)}
155
+ <button
156
+ type="button"
157
+ data-tabs-trigger
158
+ data-path={key}
159
+ data-selected={selected || undefined}
160
+ data-disabled={proxy.disabled || undefined}
161
+ role="tab"
162
+ aria-selected={selected}
163
+ aria-label={proxy.label}
164
+ disabled={proxy.disabled || disabled}
165
+ onkeydown={handlers.onkeydown}
166
+ >
167
+ {#if proxy.icon}
168
+ <span data-tabs-icon class={proxy.icon} aria-hidden="true"></span>
169
+ {/if}
170
+ <span data-tabs-label>{proxy.text}</span>
171
+ {#if editable}
172
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
173
+ <span
174
+ data-tabs-remove
175
+ role="button"
176
+ tabindex="-1"
177
+ aria-label="Remove tab"
178
+ onclick={(e) => { e.stopPropagation(); handleRemove(proxy) }}
179
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.stopPropagation(); e.preventDefault(); handleRemove(proxy) } }}
180
+ >
181
+ <span class="i-lucide:x" aria-hidden="true"></span>
182
+ </span>
183
+ {/if}
184
+ </button>
185
+ {/snippet}
186
+
187
+ {#snippet defaultPanel(item: TabsItem)}
188
+ <div data-tabs-content>
189
+ {getContent(item)}
190
+ </div>
191
+ {/snippet}
192
+
193
+ {#snippet defaultEmpty()}
194
+ No tabs available.
195
+ {/snippet}
196
+
197
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
198
+ <div
199
+ bind:this={containerRef}
200
+ data-tabs
201
+ data-orientation={orientation}
202
+ data-position={position}
203
+ data-align={align}
204
+ data-disabled={disabled || undefined}
205
+ class={className || undefined}
206
+ aria-label={name}
207
+ use:navigator={{ wrapper: controller, orientation }}
208
+ >
209
+ {#if options.length === 0}
210
+ <div data-tabs-empty>
211
+ {#if emptySnippet}
212
+ {@render emptySnippet()}
213
+ {:else}
214
+ {@render defaultEmpty()}
215
+ {/if}
216
+ </div>
217
+ {:else}
218
+ <div data-tabs-list role="tablist" aria-orientation={orientation}>
219
+ {#each options as option, index (index)}
220
+ {@const proxy = createProxy(option)}
221
+ {@const selected = isSelected(proxy)}
222
+ {@const handlers = createHandlers(proxy)}
223
+ {@const key = String(index)}
224
+
225
+ {#if tabItemSnippet}
226
+ {@render tabItemSnippet(option, userFields ?? {}, handlers, selected)}
227
+ {:else}
228
+ {@render defaultTabItem(proxy, handlers, selected, key)}
229
+ {/if}
230
+ {/each}
231
+ {#if editable}
232
+ <button
233
+ type="button"
234
+ data-tabs-add
235
+ aria-label="Add tab"
236
+ onclick={handleAdd}
237
+ >
238
+ <span class="i-lucide:plus" aria-hidden="true"></span>
239
+ </button>
240
+ {/if}
241
+ </div>
242
+
243
+ {#each options as option, index (index)}
244
+ {@const proxy = createProxy(option)}
245
+ {@const active = isSelected(proxy)}
246
+
247
+ <div
248
+ data-tabs-panel
249
+ data-panel-active={active || undefined}
250
+ role="tabpanel"
251
+ id="tab-panel-{index}"
252
+ aria-labelledby="tab-{index}"
253
+ >
254
+ {#if tabPanelSnippet}
255
+ {@render tabPanelSnippet(option, userFields ?? {})}
256
+ {:else}
257
+ {@render defaultPanel(option)}
258
+ {/if}
259
+ </div>
260
+ {/each}
261
+
262
+ {#if value === undefined}
263
+ <div data-tabs-placeholder>
264
+ {placeholder}
265
+ </div>
266
+ {/if}
267
+ {/if}
268
+ </div>
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface TiltProps {
5
+ /** Maximum rotation angle in degrees (default: 10) */
6
+ maxRotation?: number
7
+ /** Whether to adjust brightness based on mouse Y position */
8
+ setBrightness?: boolean
9
+ /** CSS perspective value in pixels (default: 600) */
10
+ perspective?: number
11
+ /** Additional CSS class */
12
+ class?: string
13
+ children?: Snippet
14
+ }
15
+
16
+ const {
17
+ maxRotation = 10,
18
+ setBrightness = false,
19
+ perspective = 600,
20
+ class: className = '',
21
+ children
22
+ }: TiltProps = $props()
23
+
24
+ let width = $state(0)
25
+ let height = $state(0)
26
+
27
+ let rotateX = $state(0)
28
+ let rotateY = $state(0)
29
+ let brightness = $state(1)
30
+
31
+ /** Linear interpolation: maps value from [0, max] to [rangeMin, rangeMax] */
32
+ function lerp(value: number, max: number, rangeMin: number, rangeMax: number): number {
33
+ if (max === 0) return rangeMin
34
+ return rangeMin + (value / max) * (rangeMax - rangeMin)
35
+ }
36
+
37
+ function onMouseMove(e: MouseEvent) {
38
+ rotateY = lerp(e.offsetX, width, maxRotation, -maxRotation)
39
+ rotateX = lerp(e.offsetY, height, -maxRotation, maxRotation)
40
+
41
+ if (setBrightness) {
42
+ brightness = lerp(e.offsetY, height, 2.0, 1.0)
43
+ }
44
+ }
45
+
46
+ function onMouseLeave() {
47
+ rotateX = 0
48
+ rotateY = 0
49
+ brightness = 1
50
+ }
51
+ </script>
52
+
53
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
54
+ <div
55
+ data-tilt
56
+ data-tilt-brightness={setBrightness || undefined}
57
+ class={className || undefined}
58
+ style:--tilt-perspective="{perspective}px"
59
+ style:--tilt-rotate-x="{rotateX}deg"
60
+ style:--tilt-rotate-y="{rotateY}deg"
61
+ style:--tilt-brightness={brightness}
62
+ bind:clientWidth={width}
63
+ bind:clientHeight={height}
64
+ onmousemove={onMouseMove}
65
+ onmouseleave={onMouseLeave}
66
+ >
67
+ {@render children?.()}
68
+ </div>
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import type { TimelineProps } from '../types/timeline.js'
3
+ import { defaultTimelineFields, defaultTimelineIcons } from '../types/timeline.js'
4
+ import { ItemProxy } from '../types/item-proxy.js'
5
+
6
+ let {
7
+ items = [],
8
+ fields: userFields,
9
+ icons: userIcons,
10
+ class: className = '',
11
+ content
12
+ }: TimelineProps = $props()
13
+
14
+ const fields = $derived({ ...defaultTimelineFields, ...userFields })
15
+ const icons = $derived({ ...defaultTimelineIcons, ...userIcons })
16
+ </script>
17
+
18
+ <div data-timeline class={className || undefined} role="list">
19
+ {#each items as item, index (index)}
20
+ {@const proxy = new ItemProxy(item, fields)}
21
+ {@const text = proxy.text}
22
+ {@const icon = proxy.icon}
23
+ {@const description = proxy.description}
24
+ {@const completed = Boolean(item.completed)}
25
+ {@const active = Boolean(item.active)}
26
+
27
+ <div
28
+ data-timeline-item
29
+ data-completed={completed || undefined}
30
+ data-active={active || undefined}
31
+ role="listitem"
32
+ >
33
+ <div data-timeline-marker aria-hidden="true">
34
+ <div data-timeline-circle>
35
+ {#if completed}
36
+ <span class={icons.completed}></span>
37
+ {:else if icon}
38
+ <span class={icon}></span>
39
+ {:else}
40
+ {index + 1}
41
+ {/if}
42
+ </div>
43
+ {#if index < items.length - 1}
44
+ <div data-timeline-connector></div>
45
+ {/if}
46
+ </div>
47
+
48
+ <div data-timeline-body>
49
+ {#if text}
50
+ <div data-timeline-title>{text}</div>
51
+ {/if}
52
+ {#if description}
53
+ <div data-timeline-description>{description}</div>
54
+ {/if}
55
+ {#if content}
56
+ {@render content(item, index)}
57
+ {/if}
58
+ </div>
59
+ </div>
60
+ {/each}
61
+ </div>
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import type { ToggleProps, ToggleItem, ToggleItemHandlers } from '../types/toggle.js'
3
+ import { ItemProxy } from '../types/item-proxy.js'
4
+ import { ListController } from '@rokkit/states'
5
+ import { navigator } from '@rokkit/actions'
6
+ import { untrack } from 'svelte'
7
+
8
+ let {
9
+ options = [],
10
+ fields: userFields,
11
+ value = $bindable(),
12
+ onchange,
13
+ showLabels = true,
14
+ size = 'md',
15
+ disabled = false,
16
+ class: className = '',
17
+ item: itemSnippet
18
+ }: ToggleProps = $props()
19
+
20
+ let controller = untrack(() => new ListController(options, value, userFields))
21
+ let containerRef: HTMLElement | null = $state(null)
22
+ let lastSyncedValue: unknown = value
23
+
24
+ $effect(() => {
25
+ controller.update(options)
26
+ })
27
+
28
+ // Only sync controller focus when value prop changes externally,
29
+ // not after every navigator move (focus !== selection in radiogroups)
30
+ $effect(() => {
31
+ if (value !== lastSyncedValue) {
32
+ lastSyncedValue = value
33
+ controller.moveToValue(value)
34
+ }
35
+ })
36
+
37
+ // Focus the button matching controller.focusedKey on navigator move events
38
+ $effect(() => {
39
+ if (!containerRef) return
40
+ const el = containerRef
41
+
42
+ function handleAction(event: Event) {
43
+ const detail = (event as CustomEvent).detail
44
+ if (detail.name === 'move') {
45
+ const key = controller.focusedKey
46
+ if (key) {
47
+ const target = el.querySelector(`[data-path="${key}"]`) as HTMLElement | null
48
+ if (target && target !== document.activeElement) {
49
+ target.focus()
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ el.addEventListener('action', handleAction)
56
+ return () => el.removeEventListener('action', handleAction)
57
+ })
58
+
59
+ /**
60
+ * Create an ItemProxy for the given item
61
+ */
62
+ function createProxy(item: ToggleItem): ItemProxy {
63
+ return new ItemProxy(item, userFields)
64
+ }
65
+
66
+ /**
67
+ * Check if an item is currently selected
68
+ */
69
+ function isSelected(proxy: ItemProxy): boolean {
70
+ return proxy.itemValue === value
71
+ }
72
+
73
+ /**
74
+ * Handle item selection
75
+ */
76
+ function handleSelect(proxy: ItemProxy) {
77
+ if (proxy.disabled || disabled) return
78
+ const itemValue = proxy.itemValue
79
+ if (itemValue !== value) {
80
+ value = itemValue
81
+ lastSyncedValue = itemValue
82
+ controller.moveToValue(itemValue)
83
+ onchange?.(itemValue, proxy.original as ToggleItem)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Handle keyboard events on individual options (Enter/Space)
89
+ */
90
+ function handleKeyDown(event: KeyboardEvent, proxy: ItemProxy) {
91
+ if (event.key === 'Enter' || event.key === ' ') {
92
+ event.preventDefault()
93
+ handleSelect(proxy)
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Create handlers object for custom snippets
99
+ */
100
+ function createHandlers(proxy: ItemProxy): ToggleItemHandlers {
101
+ return {
102
+ onclick: () => handleSelect(proxy),
103
+ onkeydown: (event: KeyboardEvent) => handleKeyDown(event, proxy)
104
+ }
105
+ }
106
+ </script>
107
+
108
+ {#snippet defaultOption(proxy: ItemProxy, handlers: ToggleItemHandlers, selected: boolean, key: string)}
109
+ <button
110
+ type="button"
111
+ data-toggle-option
112
+ data-path={key}
113
+ data-selected={selected || undefined}
114
+ data-disabled={proxy.disabled || undefined}
115
+ role="radio"
116
+ aria-checked={selected}
117
+ aria-label={proxy.text}
118
+ title={proxy.description || proxy.text}
119
+ disabled={proxy.disabled || disabled}
120
+ onclick={handlers.onclick}
121
+ onkeydown={handlers.onkeydown}
122
+ >
123
+ {#if proxy.icon}
124
+ <span data-toggle-icon class={proxy.icon} aria-hidden="true"></span>
125
+ {/if}
126
+ {#if showLabels && proxy.text}
127
+ <span data-toggle-label>{proxy.text}</span>
128
+ {/if}
129
+ </button>
130
+ {/snippet}
131
+
132
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
133
+ <div
134
+ bind:this={containerRef}
135
+ data-toggle
136
+ data-toggle-size={size}
137
+ data-toggle-disabled={disabled || undefined}
138
+ data-toggle-labels={showLabels || undefined}
139
+ class={className || undefined}
140
+ role="radiogroup"
141
+ aria-label="Selection"
142
+ aria-disabled={disabled || undefined}
143
+ use:navigator={{ wrapper: controller, orientation: 'horizontal' }}
144
+ >
145
+ {#each options as option, index (index)}
146
+ {@const proxy = createProxy(option)}
147
+ {@const selected = isSelected(proxy)}
148
+ {@const handlers = createHandlers(proxy)}
149
+ {@const key = String(index)}
150
+
151
+ {#if itemSnippet}
152
+ {@render itemSnippet(option, userFields ?? {}, handlers, selected)}
153
+ {:else}
154
+ {@render defaultOption(proxy, handlers, selected, key)}
155
+ {/if}
156
+ {/each}
157
+ </div>