twintrinsic 0.0.1

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 (212) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +150 -0
  3. package/dist/App/App.svelte +54 -0
  4. package/dist/App/App.svelte.d.ts +65 -0
  5. package/dist/Section.svelte +25 -0
  6. package/dist/Section.svelte.d.ts +34 -0
  7. package/dist/actions/clickOutside.d.ts +9 -0
  8. package/dist/actions/clickOutside.js +19 -0
  9. package/dist/actions/index.d.ts +1 -0
  10. package/dist/actions/index.js +1 -0
  11. package/dist/components/Accordion/Accordion.svelte +75 -0
  12. package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
  13. package/dist/components/Accordion/AccordionItem.svelte +150 -0
  14. package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
  15. package/dist/components/App/App.story.md +8 -0
  16. package/dist/components/App/App.story.svelte +170 -0
  17. package/dist/components/App/App.story.svelte.d.ts +22 -0
  18. package/dist/components/App/App.svelte +77 -0
  19. package/dist/components/App/App.svelte.d.ts +66 -0
  20. package/dist/components/App/Split.svelte +346 -0
  21. package/dist/components/App/Split.svelte.d.ts +54 -0
  22. package/dist/components/App/index.d.ts +2 -0
  23. package/dist/components/App/index.js +3 -0
  24. package/dist/components/AppHeader/AppHeader.svelte +439 -0
  25. package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
  26. package/dist/components/Avatar/Avatar.svelte +300 -0
  27. package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
  28. package/dist/components/Avatar/AvatarGroup.svelte +185 -0
  29. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
  30. package/dist/components/Badge/Badge.svelte +186 -0
  31. package/dist/components/Badge/Badge.svelte.d.ts +51 -0
  32. package/dist/components/BottomBar/BottomBar.svelte +146 -0
  33. package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
  34. package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
  35. package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
  36. package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
  37. package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
  38. package/dist/components/Button/Button.svelte +252 -0
  39. package/dist/components/Button/Button.svelte.d.ts +80 -0
  40. package/dist/components/Button/ButtonGroup.svelte +127 -0
  41. package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
  42. package/dist/components/Card/Card.svelte +152 -0
  43. package/dist/components/Card/Card.svelte.d.ts +55 -0
  44. package/dist/components/Carousel/Carousel.svelte +461 -0
  45. package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
  46. package/dist/components/Carousel/CarouselItem.svelte +149 -0
  47. package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
  48. package/dist/components/Chip/Chip.svelte +288 -0
  49. package/dist/components/Chip/Chip.svelte.d.ts +71 -0
  50. package/dist/components/Chip/ChipGroup.svelte +190 -0
  51. package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
  52. package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
  53. package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
  54. package/dist/components/CodeBlock/index.d.ts +1 -0
  55. package/dist/components/CodeBlock/index.js +1 -0
  56. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
  57. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
  58. package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
  59. package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
  60. package/dist/components/Combobox/Combobox.svelte +279 -0
  61. package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
  62. package/dist/components/Container/Container.svelte +45 -0
  63. package/dist/components/Container/Container.svelte.d.ts +36 -0
  64. package/dist/components/DataTable/DataTable.svelte +879 -0
  65. package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
  66. package/dist/components/Form/AutoComplete.svelte +357 -0
  67. package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
  68. package/dist/components/Form/Calendar.svelte +429 -0
  69. package/dist/components/Form/Calendar.svelte.d.ts +53 -0
  70. package/dist/components/Form/Checkbox.svelte +196 -0
  71. package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
  72. package/dist/components/Form/ColorPicker.svelte +396 -0
  73. package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
  74. package/dist/components/Form/Combobox.svelte +645 -0
  75. package/dist/components/Form/Combobox.svelte.d.ts +93 -0
  76. package/dist/components/Form/Dropdown.svelte +773 -0
  77. package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
  78. package/dist/components/Form/FileUpload.svelte +796 -0
  79. package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
  80. package/dist/components/Form/FloatLabel.svelte +245 -0
  81. package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
  82. package/dist/components/Form/Form.svelte +281 -0
  83. package/dist/components/Form/Form.svelte.d.ts +54 -0
  84. package/dist/components/Form/FormField.svelte +218 -0
  85. package/dist/components/Form/FormField.svelte.d.ts +47 -0
  86. package/dist/components/Form/Input.svelte +340 -0
  87. package/dist/components/Form/Input.svelte.d.ts +79 -0
  88. package/dist/components/Form/InputSwitch.svelte +189 -0
  89. package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
  90. package/dist/components/Form/InvalidState.svelte +97 -0
  91. package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
  92. package/dist/components/Form/Knob.svelte +537 -0
  93. package/dist/components/Form/Knob.svelte.d.ts +78 -0
  94. package/dist/components/Form/ListInput.svelte +469 -0
  95. package/dist/components/Form/ListInput.svelte.d.ts +70 -0
  96. package/dist/components/Form/Listbox.svelte +513 -0
  97. package/dist/components/Form/Listbox.svelte.d.ts +74 -0
  98. package/dist/components/Form/NumberInput.svelte +452 -0
  99. package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
  100. package/dist/components/Form/Radio.svelte +192 -0
  101. package/dist/components/Form/Radio.svelte.d.ts +53 -0
  102. package/dist/components/Form/RadioGroup.svelte +155 -0
  103. package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
  104. package/dist/components/Form/Rating.svelte +380 -0
  105. package/dist/components/Form/Rating.svelte.d.ts +64 -0
  106. package/dist/components/Form/Select.svelte +436 -0
  107. package/dist/components/Form/Select.svelte.d.ts +49 -0
  108. package/dist/components/Form/SelectGroup.svelte +34 -0
  109. package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
  110. package/dist/components/Form/Slider.svelte +622 -0
  111. package/dist/components/Form/Slider.svelte.d.ts +73 -0
  112. package/dist/components/Form/Switch.svelte +192 -0
  113. package/dist/components/Form/Switch.svelte.d.ts +46 -0
  114. package/dist/components/Form/TextInput.svelte +274 -0
  115. package/dist/components/Form/TextInput.svelte.d.ts +74 -0
  116. package/dist/components/Form/Textarea.svelte +207 -0
  117. package/dist/components/Form/Textarea.svelte.d.ts +62 -0
  118. package/dist/components/Icon/Icon.svelte +140 -0
  119. package/dist/components/Icon/Icon.svelte.d.ts +25 -0
  120. package/dist/components/Icon/index.d.ts +1 -0
  121. package/dist/components/Icon/index.js +1 -0
  122. package/dist/components/Lazy/Lazy.svelte +158 -0
  123. package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
  124. package/dist/components/Masonry/Masonry.svelte +299 -0
  125. package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
  126. package/dist/components/Menu/Menu/Menu.svelte +65 -0
  127. package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
  128. package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
  129. package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
  130. package/dist/components/Modal/Modal.svelte +334 -0
  131. package/dist/components/Modal/Modal.svelte.d.ts +55 -0
  132. package/dist/components/Panel/Card.svelte +141 -0
  133. package/dist/components/Panel/Card.svelte.d.ts +52 -0
  134. package/dist/components/Panel/Hero/Hero.story.md +9 -0
  135. package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
  136. package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
  137. package/dist/components/Panel/Hero/Hero.svelte +24 -0
  138. package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
  139. package/dist/components/Panel/LazyPanel.svelte +110 -0
  140. package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
  141. package/dist/components/Panel/Panel.svelte +205 -0
  142. package/dist/components/Panel/Panel.svelte.d.ts +23 -0
  143. package/dist/components/Progress/Progress.svelte +220 -0
  144. package/dist/components/Progress/Progress.svelte.d.ts +61 -0
  145. package/dist/components/Separator/Separator.svelte +109 -0
  146. package/dist/components/Separator/Separator.svelte.d.ts +35 -0
  147. package/dist/components/Sidebar/Sidebar.svelte +213 -0
  148. package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
  149. package/dist/components/Skeleton/Skeleton.svelte +170 -0
  150. package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
  151. package/dist/components/Stepper/Stepper.svelte +111 -0
  152. package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
  153. package/dist/components/Stepper/StepperStep.svelte +369 -0
  154. package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
  155. package/dist/components/Table/Table.svelte +167 -0
  156. package/dist/components/Table/Table.svelte.d.ts +56 -0
  157. package/dist/components/Table/TableBody.svelte +41 -0
  158. package/dist/components/Table/TableBody.svelte.d.ts +33 -0
  159. package/dist/components/Table/TableCell.svelte +76 -0
  160. package/dist/components/Table/TableCell.svelte.d.ts +36 -0
  161. package/dist/components/Table/TableHead.svelte +41 -0
  162. package/dist/components/Table/TableHead.svelte.d.ts +32 -0
  163. package/dist/components/Table/TableHeader.svelte +148 -0
  164. package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
  165. package/dist/components/Table/TableRow.svelte +99 -0
  166. package/dist/components/Table/TableRow.svelte.d.ts +40 -0
  167. package/dist/components/Tabs/Tab.svelte +145 -0
  168. package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
  169. package/dist/components/Tabs/TabList.svelte +60 -0
  170. package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
  171. package/dist/components/Tabs/TabPanel.svelte +118 -0
  172. package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
  173. package/dist/components/Tabs/Tabs.svelte +287 -0
  174. package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
  175. package/dist/components/Tag/Tag.svelte +260 -0
  176. package/dist/components/Tag/Tag.svelte.d.ts +54 -0
  177. package/dist/components/Tag/TagGroup.svelte +147 -0
  178. package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
  179. package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
  180. package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
  181. package/dist/components/Timeline/Timeline.svelte +144 -0
  182. package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
  183. package/dist/components/Timeline/TimelineItem.svelte +391 -0
  184. package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
  185. package/dist/components/Toast/Toast.svelte +313 -0
  186. package/dist/components/Toast/Toast.svelte.d.ts +44 -0
  187. package/dist/components/Toast/toastStore.d.ts +40 -0
  188. package/dist/components/Toast/toastStore.js +293 -0
  189. package/dist/components/Tooltip/Tooltip.svelte +282 -0
  190. package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
  191. package/dist/components/Tree/Tree.svelte +129 -0
  192. package/dist/components/Tree/Tree.svelte.d.ts +61 -0
  193. package/dist/components/Tree/TreeNode.svelte +332 -0
  194. package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
  195. package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
  196. package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
  197. package/dist/components/icons/twintrinsic-source.svg +73 -0
  198. package/dist/components/icons/twintrinsic.svg +38 -0
  199. package/dist/docs/EventsTable.svelte +86 -0
  200. package/dist/docs/EventsTable.svelte.d.ts +27 -0
  201. package/dist/docs/PropsTable.svelte +103 -0
  202. package/dist/docs/PropsTable.svelte.d.ts +28 -0
  203. package/dist/docs/index.d.ts +2 -0
  204. package/dist/docs/index.js +2 -0
  205. package/dist/helpers/detectLanguage.d.ts +6 -0
  206. package/dist/helpers/detectLanguage.js +60 -0
  207. package/dist/helpers/index.d.ts +1 -0
  208. package/dist/helpers/index.js +1 -0
  209. package/dist/index.d.ts +86 -0
  210. package/dist/index.js +94 -0
  211. package/dist/twintrinsic.css +347 -0
  212. package/package.json +98 -0
@@ -0,0 +1,513 @@
1
+ <!--
2
+ @component
3
+ Listbox - A component for selecting one or multiple options from a list.
4
+ Provides accessible keyboard navigation, filtering, and customization options.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <Listbox
9
+ name="users"
10
+ options={users}
11
+ optionLabel="name"
12
+ optionValue="id"
13
+ />
14
+
15
+ <Listbox
16
+ name="colors"
17
+ options={colors}
18
+ multiple
19
+ filter
20
+ />
21
+
22
+ <FormField label="Select a category">
23
+ <Listbox
24
+ name="category"
25
+ options={categories}
26
+ optionIcon="icon"
27
+ />
28
+ </FormField>
29
+ ```
30
+ -->
31
+ <script>
32
+ import { getContext, onMount } from "svelte"
33
+
34
+ const {
35
+ /** @type {string} - Additional CSS classes */
36
+ class: className = "",
37
+
38
+ /** @type {string} - HTML id for accessibility */
39
+ id = crypto.randomUUID(),
40
+
41
+ /** @type {string} - Input name */
42
+ name,
43
+
44
+ /** @type {Array} - Options to display */
45
+ options = [],
46
+
47
+ /** @type {any} - Selected value(s) */
48
+ value,
49
+
50
+ /** @type {boolean} - Whether multiple selection is allowed */
51
+ multiple = false,
52
+
53
+ /** @type {string} - Property name for option label */
54
+ optionLabel = "label",
55
+
56
+ /** @type {string} - Property name for option value */
57
+ optionValue = "value",
58
+
59
+ /** @type {string} - Property name for option icon */
60
+ optionIcon,
61
+
62
+ /** @type {boolean} - Whether the listbox is disabled */
63
+ disabled = false,
64
+
65
+ /** @type {boolean} - Whether the listbox is required */
66
+ required = false,
67
+
68
+ /** @type {boolean} - Whether to filter options by typing */
69
+ filter = false,
70
+
71
+ /** @type {string} - Placeholder for filter input */
72
+ filterPlaceholder = "Search...",
73
+
74
+ /** @type {number} - Maximum height of the listbox */
75
+ maxHeight = 300,
76
+
77
+ /** @type {boolean} - Whether to show a checkbox for multiple selection */
78
+ showCheckbox = true,
79
+
80
+ /** @type {string} - ARIA label for accessibility */
81
+ ariaLabel,
82
+
83
+ /** @type {(event: CustomEvent) => void} - Change event handler */
84
+ onchange,
85
+ /** @type {(event: CustomEvent) => void} - Filter event handler */
86
+ onfilter,
87
+ } = $props()
88
+
89
+ // Get form context if available
90
+ const formContext = getContext("form")
91
+
92
+ // Component state
93
+ let selectedValues = $state(multiple ? [] : null)
94
+ let filterValue = $state("")
95
+ let highlightedIndex = $state(0)
96
+ let listboxElement = $state()
97
+ let filterInputElement = $state()
98
+
99
+ // Register with form if available
100
+ let fieldApi = $state()
101
+
102
+ $effect(() => {
103
+ if (formContext && name) {
104
+ fieldApi = formContext.registerField(name, value)
105
+ }
106
+ })
107
+
108
+ // Update value when form field changes
109
+ $effect(() => {
110
+ if (fieldApi) {
111
+ const formValue = fieldApi.getValue()
112
+ if (formValue !== undefined && JSON.stringify(formValue) !== JSON.stringify(selectedValues)) {
113
+ selectedValues = formValue
114
+ }
115
+ }
116
+ })
117
+
118
+ // Initialize selected values from prop
119
+ $effect(() => {
120
+ if (value !== undefined) {
121
+ selectedValues = value
122
+ }
123
+ })
124
+
125
+ /**
126
+ * Gets the display label for an option
127
+ * @param {Object|string} option - Option to get label for
128
+ * @returns {string} - Display label
129
+ */
130
+ function getOptionLabel(option) {
131
+ if (!option) return ""
132
+
133
+ if (typeof option === "object") {
134
+ return option[optionLabel] || ""
135
+ }
136
+
137
+ return option.toString()
138
+ }
139
+
140
+ /**
141
+ * Gets the value for an option
142
+ * @param {Object|string} option - Option to get value for
143
+ * @returns {any} - Option value
144
+ */
145
+ function getOptionValue(option) {
146
+ if (!option) return null
147
+
148
+ if (typeof option === "object") {
149
+ return option[optionValue]
150
+ }
151
+
152
+ return option
153
+ }
154
+
155
+ /**
156
+ * Gets the icon for an option
157
+ * @param {Object} option - Option to get icon for
158
+ * @returns {string} - Icon HTML
159
+ */
160
+ function getOptionIcon(option) {
161
+ if (!option || !optionIcon || typeof option !== "object") return null
162
+
163
+ return option[optionIcon]
164
+ }
165
+
166
+ /**
167
+ * Checks if an option is selected
168
+ * @param {Object|string} option - Option to check
169
+ * @returns {boolean} - Whether the option is selected
170
+ */
171
+ function isOptionSelected(option) {
172
+ const value = getOptionValue(option)
173
+
174
+ if (multiple) {
175
+ return (
176
+ Array.isArray(selectedValues) &&
177
+ selectedValues.some((v) => (typeof v === "object" ? v[optionValue] === value : v === value))
178
+ )
179
+ }
180
+
181
+ return (
182
+ selectedValues === value ||
183
+ (typeof selectedValues === "object" && selectedValues && selectedValues[optionValue] === value)
184
+ )
185
+ }
186
+
187
+ /**
188
+ * Filters options based on input value
189
+ * @returns {Array} - Filtered options
190
+ */
191
+ function filterOptions() {
192
+ if (!filter || !filterValue) return options
193
+
194
+ return options.filter((option) => {
195
+ const label = getOptionLabel(option).toLowerCase()
196
+ return label.includes(filterValue.toLowerCase())
197
+ })
198
+ }
199
+
200
+ /**
201
+ * Selects an option
202
+ * @param {Object|string} option - Option to select
203
+ */
204
+ function selectOption(option) {
205
+ if (disabled) return
206
+
207
+ const value = getOptionValue(option)
208
+
209
+ if (multiple) {
210
+ if (isOptionSelected(option)) {
211
+ // Remove from selection
212
+ selectedValues = Array.isArray(selectedValues)
213
+ ? selectedValues.filter((v) =>
214
+ typeof v === "object" ? v[optionValue] !== value : v !== value
215
+ )
216
+ : []
217
+ } else {
218
+ // Add to selection
219
+ selectedValues = Array.isArray(selectedValues) ? [...selectedValues, value] : [value]
220
+ }
221
+ } else {
222
+ // Single selection
223
+ selectedValues = value
224
+ }
225
+
226
+ // Update form field if available
227
+ if (fieldApi) {
228
+ fieldApi.setValue(selectedValues)
229
+ }
230
+
231
+ onchange?.(new CustomEvent("change", { detail: { value: selectedValues } }))
232
+ }
233
+
234
+ /**
235
+ * Handles keydown events
236
+ * @param {KeyboardEvent} event - Keydown event
237
+ */
238
+ function handleKeydown(event) {
239
+ if (disabled) return
240
+
241
+ const filteredOptions = filterOptions()
242
+
243
+ switch (event.key) {
244
+ case "ArrowDown":
245
+ event.preventDefault()
246
+ highlightedIndex = (highlightedIndex + 1) % filteredOptions.length
247
+ scrollOptionIntoView()
248
+ break
249
+
250
+ case "ArrowUp":
251
+ event.preventDefault()
252
+ highlightedIndex = (highlightedIndex - 1 + filteredOptions.length) % filteredOptions.length
253
+ scrollOptionIntoView()
254
+ break
255
+
256
+ case "Enter":
257
+ case " ":
258
+ if (!filter || event.target !== filterInputElement) {
259
+ event.preventDefault()
260
+ if (filteredOptions[highlightedIndex]) {
261
+ selectOption(filteredOptions[highlightedIndex])
262
+ }
263
+ }
264
+ break
265
+
266
+ case "Home":
267
+ event.preventDefault()
268
+ highlightedIndex = 0
269
+ scrollOptionIntoView()
270
+ break
271
+
272
+ case "End":
273
+ event.preventDefault()
274
+ highlightedIndex = filteredOptions.length - 1
275
+ scrollOptionIntoView()
276
+ break
277
+
278
+ case "Tab":
279
+ // Allow normal tab behavior
280
+ break
281
+
282
+ default:
283
+ // If filter is not enabled, use type-ahead
284
+ if (!filter && event.key.length === 1) {
285
+ const char = event.key.toLowerCase()
286
+ const matchingIndex = filteredOptions.findIndex(
287
+ (option, index) =>
288
+ index > highlightedIndex && getOptionLabel(option).toLowerCase().startsWith(char)
289
+ )
290
+
291
+ if (matchingIndex !== -1) {
292
+ highlightedIndex = matchingIndex
293
+ scrollOptionIntoView()
294
+ } else {
295
+ // Try from the beginning
296
+ const firstMatchingIndex = filteredOptions.findIndex((option) =>
297
+ getOptionLabel(option).toLowerCase().startsWith(char)
298
+ )
299
+
300
+ if (firstMatchingIndex !== -1) {
301
+ highlightedIndex = firstMatchingIndex
302
+ scrollOptionIntoView()
303
+ }
304
+ }
305
+ }
306
+ break
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Scrolls the highlighted option into view
312
+ */
313
+ function scrollOptionIntoView() {
314
+ if (!listboxElement) return
315
+
316
+ const highlightedOption = listboxElement.querySelector(`[data-index="${highlightedIndex}"]`)
317
+ if (highlightedOption) {
318
+ highlightedOption.scrollIntoView({ block: "nearest" })
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Handles filter input
324
+ * @param {Event} event - Input event
325
+ */
326
+ function handleFilterInput(event) {
327
+ filterValue = event.target.value
328
+ highlightedIndex = 0
329
+
330
+ onfilter?.(new CustomEvent("filter", { detail: { filter: filterValue } }))
331
+ }
332
+
333
+ // Focus the listbox on mount
334
+ onMount(() => {
335
+ if (filter && filterInputElement) {
336
+ filterInputElement.focus()
337
+ } else if (listboxElement) {
338
+ listboxElement.focus()
339
+ }
340
+ })
341
+
342
+ // Computed filtered options
343
+ const filteredOptions = $derived(filterOptions())
344
+ </script>
345
+
346
+ <div class="listbox-container {className}">
347
+ {#if filter}
348
+ <div class="listbox-filter">
349
+ <input
350
+ type="text"
351
+ class="listbox-filter-input"
352
+ placeholder={filterPlaceholder}
353
+ value={filterValue}
354
+ oninput={handleFilterInput}
355
+ onkeydown={handleKeydown}
356
+ bind:this={filterInputElement}
357
+ {disabled}
358
+ aria-controls={`${id}-listbox`}
359
+ />
360
+ </div>
361
+ {/if}
362
+
363
+ <div
364
+ id="{id}-listbox"
365
+ class="listbox"
366
+ style="max-height: {maxHeight}px;"
367
+ tabindex={disabled ? undefined : 0}
368
+ role="listbox"
369
+ aria-multiselectable={multiple ? 'true' : undefined}
370
+ aria-label={ariaLabel || name}
371
+ aria-disabled={disabled ? 'true' : undefined}
372
+ onkeydown={handleKeydown}
373
+ bind:this={listboxElement}
374
+ >
375
+ {#if filteredOptions.length > 0}
376
+ <ul class="listbox-options">
377
+ {#each filteredOptions as option, index}
378
+ {@const isHighlighted = index === highlightedIndex}
379
+ {@const isSelected = isOptionSelected(option)}
380
+
381
+ <li
382
+ class="
383
+ listbox-option
384
+ {isHighlighted ? 'listbox-option-highlighted' : ''}
385
+ {isSelected ? 'listbox-option-selected' : ''}
386
+ "
387
+ role="option"
388
+ aria-selected={isSelected ? 'true' : 'false'}
389
+ data-index={index}
390
+ onclick={() => selectOption(option)}
391
+ onmouseenter={() => highlightedIndex = index}
392
+ >
393
+ <div class="listbox-option-content">
394
+ {#if multiple && showCheckbox}
395
+ <div class="listbox-checkbox">
396
+ {#if isSelected}
397
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
398
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
399
+ </svg>
400
+ {/if}
401
+ </div>
402
+ {/if}
403
+
404
+ {#if optionIcon && getOptionIcon(option)}
405
+ <div class="listbox-option-icon">
406
+ {@html getOptionIcon(option)}
407
+ </div>
408
+ {/if}
409
+
410
+ <div class="listbox-option-label">
411
+ {getOptionLabel(option)}
412
+ </div>
413
+ </div>
414
+ </li>
415
+ {/each}
416
+ </ul>
417
+ {:else}
418
+ <div class="listbox-empty">No options available</div>
419
+ {/if}
420
+ </div>
421
+
422
+ <!-- Hidden input for form submission -->
423
+ <input
424
+ type="hidden"
425
+ {id}
426
+ {name}
427
+ value={JSON.stringify(selectedValues)}
428
+ {required}
429
+ {disabled}
430
+ />
431
+ </div>
432
+
433
+ <style>
434
+ @reference "../../twintrinsic.css";
435
+
436
+ .listbox-container {
437
+ @apply w-full;
438
+ }
439
+
440
+ .listbox-filter {
441
+ @apply mb-2;
442
+ }
443
+
444
+ .listbox-filter-input {
445
+ @apply w-full px-3 py-2;
446
+ @apply bg-background dark:bg-background;
447
+ @apply border border-border dark:border-border rounded-md;
448
+ @apply text-text dark:text-text placeholder:text-muted dark:placeholder:text-muted;
449
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 focus:border-primary-500 dark:focus:border-primary-400;
450
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
451
+ @apply transition-colors duration-200;
452
+ }
453
+
454
+ .listbox {
455
+ @apply w-full overflow-y-auto;
456
+ @apply bg-background dark:bg-background;
457
+ @apply border border-border dark:border-border rounded-md;
458
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
459
+ @apply transition-colors duration-200;
460
+ }
461
+
462
+ .listbox[aria-disabled="true"] {
463
+ @apply opacity-50 cursor-not-allowed;
464
+ }
465
+
466
+ .listbox-options {
467
+ @apply py-1;
468
+ }
469
+
470
+ .listbox-option {
471
+ @apply px-3 py-2 cursor-pointer;
472
+ @apply text-text dark:text-text;
473
+ @apply hover:bg-hover dark:hover:bg-hover;
474
+ @apply transition-colors duration-150;
475
+ }
476
+
477
+ .listbox-option-content {
478
+ @apply flex items-center gap-2;
479
+ }
480
+
481
+ .listbox-option-highlighted {
482
+ @apply bg-hover dark:bg-hover;
483
+ }
484
+
485
+ .listbox-option-selected {
486
+ @apply bg-primary-50 dark:bg-primary-900/20;
487
+ @apply text-primary-700 dark:text-primary-300;
488
+ }
489
+
490
+ .listbox-checkbox {
491
+ @apply w-4 h-4 flex-shrink-0;
492
+ @apply border border-border dark:border-border rounded;
493
+ @apply flex items-center justify-center;
494
+ @apply bg-background dark:bg-background;
495
+ }
496
+
497
+ .listbox-option-selected .listbox-checkbox {
498
+ @apply bg-primary-500 dark:bg-primary-400 border-primary-500 dark:border-primary-400;
499
+ @apply text-white dark:text-white;
500
+ }
501
+
502
+ .listbox-option-icon {
503
+ @apply flex-shrink-0 w-5 h-5;
504
+ }
505
+
506
+ .listbox-option-label {
507
+ @apply flex-grow truncate;
508
+ }
509
+
510
+ .listbox-empty {
511
+ @apply px-3 py-4 text-muted dark:text-muted text-center;
512
+ }
513
+ </style>
@@ -0,0 +1,74 @@
1
+ export default Listbox;
2
+ type Listbox = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * Listbox - A component for selecting one or multiple options from a list.
8
+ * Provides accessible keyboard navigation, filtering, and customization options.
9
+ *
10
+ * Usage:
11
+ * ```svelte
12
+ * <Listbox
13
+ * name="users"
14
+ * options={users}
15
+ * optionLabel="name"
16
+ * optionValue="id"
17
+ * />
18
+ *
19
+ * <Listbox
20
+ * name="colors"
21
+ * options={colors}
22
+ * multiple
23
+ * filter
24
+ * />
25
+ *
26
+ * <FormField label="Select a category">
27
+ * <Listbox
28
+ * name="category"
29
+ * options={categories}
30
+ * optionIcon="icon"
31
+ * />
32
+ * </FormField>
33
+ * ```
34
+ */
35
+ declare const Listbox: import("svelte").Component<{
36
+ class?: string;
37
+ id?: any;
38
+ name: any;
39
+ options?: any[];
40
+ value: any;
41
+ multiple?: boolean;
42
+ optionLabel?: string;
43
+ optionValue?: string;
44
+ optionIcon: any;
45
+ disabled?: boolean;
46
+ required?: boolean;
47
+ filter?: boolean;
48
+ filterPlaceholder?: string;
49
+ maxHeight?: number;
50
+ showCheckbox?: boolean;
51
+ ariaLabel: any;
52
+ onchange: any;
53
+ onfilter: any;
54
+ }, {}, "">;
55
+ type $$ComponentProps = {
56
+ class?: string;
57
+ id?: any;
58
+ name: any;
59
+ options?: any[];
60
+ value: any;
61
+ multiple?: boolean;
62
+ optionLabel?: string;
63
+ optionValue?: string;
64
+ optionIcon: any;
65
+ disabled?: boolean;
66
+ required?: boolean;
67
+ filter?: boolean;
68
+ filterPlaceholder?: string;
69
+ maxHeight?: number;
70
+ showCheckbox?: boolean;
71
+ ariaLabel: any;
72
+ onchange: any;
73
+ onfilter: any;
74
+ };