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,469 @@
1
+ <!--
2
+ @component
3
+ ListInput - A component for handling multiple input values displayed as "chips".
4
+ Allows adding, removing, and editing multiple values with keyboard navigation.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <ListInput
9
+ name="tags"
10
+ placeholder="Add tag..."
11
+ values={['react', 'svelte']}
12
+ onchange={handleTagsChange}
13
+ />
14
+
15
+ <FormField label="Recipients">
16
+ <ListInput
17
+ name="recipients"
18
+ placeholder="Add email..."
19
+ validator={(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)}
20
+ errorMessage="Please enter a valid email address"
21
+ />
22
+ </FormField>
23
+ ```
24
+ -->
25
+ <script>
26
+ import { getContext } from "svelte"
27
+
28
+ const {
29
+ /** @type {string} - Additional CSS classes */
30
+ class: className = "",
31
+
32
+ /** @type {string} - HTML id for accessibility */
33
+ id = crypto.randomUUID(),
34
+
35
+ /** @type {string} - Input name */
36
+ name,
37
+
38
+ /** @type {string} - Placeholder text */
39
+ placeholder = "Add item...",
40
+
41
+ /** @type {string[]} - Array of values */
42
+ values = [],
43
+
44
+ /** @type {boolean} - Whether the input is disabled */
45
+ disabled = false,
46
+
47
+ /** @type {boolean} - Whether the input is required */
48
+ required = false,
49
+
50
+ /** @type {boolean} - Whether to allow duplicates */
51
+ allowDuplicates = false,
52
+
53
+ /** @type {string} - Character(s) that trigger adding a new item */
54
+ addOnKeys = "Enter,Tab,Comma",
55
+
56
+ /** @type {string} - Separator for pasting multiple values */
57
+ pasteSeparator = ",",
58
+
59
+ /** @type {number} - Maximum number of items */
60
+ maxItems,
61
+
62
+ /** @type {Function} - Validator function for new values */
63
+ validator,
64
+
65
+ /** @type {string} - Error message for invalid values */
66
+ errorMessage = "Invalid value",
67
+
68
+ /** @type {string} - ARIA label for the input */
69
+ ariaLabel,
70
+
71
+ /** @type {(event: CustomEvent) => void} - Change event handler */
72
+ onchange,
73
+ /** @type {(event: CustomEvent) => void} - Add event handler */
74
+ onadd,
75
+ /** @type {(event: CustomEvent) => void} - Remove event handler */
76
+ onremove,
77
+ /** @type {(event: CustomEvent) => void} - Focus event handler */
78
+ onfocus,
79
+ /** @type {(event: CustomEvent) => void} - Blur event handler */
80
+ onblur,
81
+ } = $props()
82
+
83
+ // Get form context if available
84
+ const formContext = getContext("form")
85
+
86
+ // Input state
87
+ let inputValue = $state("")
88
+ let itemValues = $state([...values])
89
+ let focusedIndex = $state(-1)
90
+ let inputEl
91
+ let isInvalid = $state(false)
92
+ let validationMessage = $state("")
93
+
94
+ // Register with form if available
95
+ let fieldApi = $state()
96
+
97
+ $effect(() => {
98
+ if (formContext && name) {
99
+ fieldApi = formContext.registerField(name, values)
100
+ }
101
+ })
102
+
103
+ // Update values when form field changes
104
+ $effect(() => {
105
+ if (fieldApi) {
106
+ const formValue = fieldApi.getValue()
107
+ if (formValue !== undefined && JSON.stringify(formValue) !== JSON.stringify(itemValues)) {
108
+ itemValues = [...formValue]
109
+ }
110
+ }
111
+ })
112
+
113
+ // Update internal values when prop changes
114
+ $effect(() => {
115
+ if (JSON.stringify(values) !== JSON.stringify(itemValues)) {
116
+ itemValues = [...values]
117
+ }
118
+ })
119
+
120
+ /**
121
+ * Validates a value
122
+ * @param {string} value - Value to validate
123
+ * @returns {boolean} - Whether the value is valid
124
+ */
125
+ function validateValue(value) {
126
+ if (!value.trim()) return false
127
+
128
+ if (!allowDuplicates && itemValues.includes(value.trim())) {
129
+ validationMessage = "Duplicate value"
130
+ return false
131
+ }
132
+
133
+ if (maxItems && itemValues.length >= maxItems) {
134
+ validationMessage = `Maximum of ${maxItems} items allowed`
135
+ return false
136
+ }
137
+
138
+ if (validator) {
139
+ const isValid = validator(value.trim())
140
+ if (!isValid) {
141
+ validationMessage = errorMessage
142
+ }
143
+ return isValid
144
+ }
145
+
146
+ return true
147
+ }
148
+
149
+ /**
150
+ * Adds a new item
151
+ * @param {string} value - Value to add
152
+ */
153
+ function addItem(value) {
154
+ const trimmedValue = value.trim()
155
+
156
+ if (!validateValue(trimmedValue)) {
157
+ isInvalid = true
158
+ return
159
+ }
160
+
161
+ isInvalid = false
162
+ validationMessage = ""
163
+
164
+ // Add the item
165
+ itemValues = [...itemValues, trimmedValue]
166
+ inputValue = ""
167
+
168
+ // Update form field if available
169
+ if (fieldApi) {
170
+ fieldApi.setValue(itemValues)
171
+ }
172
+
173
+ onchange?.(new CustomEvent("change", { detail: { values: itemValues } }))
174
+ onadd?.(new CustomEvent("add", { detail: { value: trimmedValue } }))
175
+ }
176
+
177
+ /**
178
+ * Removes an item
179
+ * @param {number} index - Index of the item to remove
180
+ */
181
+ function removeItem(index) {
182
+ if (index < 0 || index >= itemValues.length) return
183
+
184
+ const removedValue = itemValues[index]
185
+ itemValues = itemValues.filter((_, i) => i !== index)
186
+
187
+ // Update form field if available
188
+ if (fieldApi) {
189
+ fieldApi.setValue(itemValues)
190
+ }
191
+
192
+ // Update focused index
193
+ if (focusedIndex >= itemValues.length) {
194
+ focusedIndex = itemValues.length - 1
195
+ }
196
+
197
+ onchange?.(new CustomEvent("change", { detail: { values: itemValues } }))
198
+ onremove?.(new CustomEvent("remove", { detail: { value: removedValue, index } }))
199
+ }
200
+
201
+ /**
202
+ * Handles input keydown
203
+ * @param {KeyboardEvent} event - Keydown event
204
+ */
205
+ function handleKeydown(event) {
206
+ // Get add trigger keys
207
+ const triggerKeys = addOnKeys.split(",").map((k) => k.trim().toLowerCase())
208
+
209
+ // Check if key should trigger adding an item
210
+ const shouldAddItem =
211
+ (triggerKeys.includes("enter") && event.key === "Enter") ||
212
+ (triggerKeys.includes("tab") && event.key === "Tab") ||
213
+ (triggerKeys.includes("comma") && event.key === ",") ||
214
+ (triggerKeys.includes("space") && event.key === " ")
215
+
216
+ if (shouldAddItem && inputValue.trim()) {
217
+ event.preventDefault()
218
+ addItem(inputValue)
219
+ return
220
+ }
221
+
222
+ // Handle backspace to remove last item
223
+ if (event.key === "Backspace" && !inputValue && itemValues.length > 0) {
224
+ if (focusedIndex === -1) {
225
+ // Focus the last item first
226
+ focusedIndex = itemValues.length - 1
227
+ } else {
228
+ // Remove the focused item
229
+ removeItem(focusedIndex)
230
+ }
231
+ }
232
+
233
+ // Handle arrow keys for navigation
234
+ if (event.key === "ArrowLeft" && focusedIndex === -1 && !inputValue && itemValues.length > 0) {
235
+ focusedIndex = itemValues.length - 1
236
+ } else if (event.key === "ArrowLeft" && focusedIndex > 0) {
237
+ focusedIndex--
238
+ } else if (event.key === "ArrowRight" && focusedIndex < itemValues.length - 1) {
239
+ focusedIndex++
240
+ } else if (event.key === "ArrowRight" && focusedIndex === itemValues.length - 1) {
241
+ focusedIndex = -1
242
+ inputEl?.focus()
243
+ }
244
+
245
+ // Handle Escape to blur
246
+ if (event.key === "Escape") {
247
+ inputEl?.blur()
248
+ focusedIndex = -1
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Handles input change
254
+ * @param {Event} event - Input event
255
+ */
256
+ function handleInput(event) {
257
+ inputValue = event.target.value
258
+
259
+ // Clear validation state when typing
260
+ if (isInvalid) {
261
+ isInvalid = false
262
+ validationMessage = ""
263
+ }
264
+
265
+ // If the last character is a comma, add the item
266
+ if (inputValue.endsWith(",") && addOnKeys.includes("Comma")) {
267
+ const valueToAdd = inputValue.slice(0, -1)
268
+ if (valueToAdd.trim()) {
269
+ addItem(valueToAdd)
270
+ }
271
+ }
272
+
273
+ dispatch("input", { value: inputValue })
274
+ }
275
+
276
+ /**
277
+ * Handles paste event
278
+ * @param {ClipboardEvent} event - Paste event
279
+ */
280
+ function handlePaste(event) {
281
+ if (!pasteSeparator) return
282
+
283
+ const pastedText = event.clipboardData?.getData("text") || ""
284
+ if (!pastedText.includes(pasteSeparator)) return
285
+
286
+ // Prevent default paste
287
+ event.preventDefault()
288
+
289
+ // Split pasted text and add items
290
+ const pastedValues = pastedText
291
+ .split(pasteSeparator)
292
+ .map((v) => v.trim())
293
+ .filter((v) => v)
294
+
295
+ for (const value of pastedValues) {
296
+ addItem(value)
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Handles chip click
302
+ * @param {number} index - Chip index
303
+ */
304
+ function handleChipClick(index) {
305
+ focusedIndex = index
306
+ }
307
+
308
+ /**
309
+ * Handles input focus
310
+ */
311
+ function handleFocus() {
312
+ focusedIndex = -1
313
+ dispatch("focus")
314
+ }
315
+
316
+ /**
317
+ * Handles input blur
318
+ */
319
+ function handleBlur(event) {
320
+ // Add current value if not empty and not clicking on a chip
321
+ if (inputValue.trim() && !event.relatedTarget?.classList.contains("list-input-chip")) {
322
+ addItem(inputValue)
323
+ }
324
+
325
+ // Reset focused index after a short delay
326
+ // This allows chip click events to fire first
327
+ setTimeout(() => {
328
+ focusedIndex = -1
329
+ }, 100)
330
+
331
+ dispatch("blur")
332
+ }
333
+ </script>
334
+
335
+ <div
336
+ class="
337
+ list-input
338
+ {isInvalid ? 'list-input-invalid' : ''}
339
+ {disabled ? 'list-input-disabled' : ''}
340
+ {className}
341
+ "
342
+ >
343
+ <div
344
+ class="list-input-container"
345
+ onclick={() => inputEl?.focus()}
346
+ >
347
+ <!-- Chips -->
348
+ {#each itemValues as value, index}
349
+ <div
350
+ class="list-input-chip {focusedIndex === index ? 'list-input-chip-focused' : ''}"
351
+ tabindex="0"
352
+ onclick={() => handleChipClick(index)}
353
+ onkeydown={(e) => {
354
+ if (e.key === 'Backspace' || e.key === 'Delete') {
355
+ removeItem(index);
356
+ inputEl?.focus();
357
+ }
358
+ }}
359
+ >
360
+ <span class="list-input-chip-text">{value}</span>
361
+ <button
362
+ type="button"
363
+ class="list-input-chip-remove"
364
+ aria-label={`Remove ${value}`}
365
+ tabindex="-1"
366
+ onclick={(e) => {
367
+ e.stopPropagation();
368
+ e.preventDefault();
369
+ removeItem(index);
370
+ inputEl?.focus();
371
+ }}
372
+ >
373
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
374
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
375
+ </svg>
376
+ </button>
377
+ </div>
378
+ {/each}
379
+
380
+ <!-- Input -->
381
+ <input
382
+ {id}
383
+ {name}
384
+ type="text"
385
+ class="list-input-field"
386
+ {placeholder}
387
+ value={inputValue}
388
+ disabled={disabled || (maxItems && itemValues.length >= maxItems)}
389
+ aria-label={ariaLabel || name}
390
+ aria-invalid={isInvalid ? 'true' : undefined}
391
+ bind:this={inputEl}
392
+ oninput={handleInput}
393
+ onkeydown={handleKeydown}
394
+ onfocus={handleFocus}
395
+ onblur={handleBlur}
396
+ onpaste={handlePaste}
397
+ />
398
+ </div>
399
+
400
+ {#if isInvalid && validationMessage}
401
+ <div class="list-input-error" role="alert">
402
+ {validationMessage}
403
+ </div>
404
+ {/if}
405
+
406
+ <!-- Hidden input for form submission -->
407
+ <input
408
+ type="hidden"
409
+ {name}
410
+ value={JSON.stringify(itemValues)}
411
+ {required}
412
+ {disabled}
413
+ />
414
+ </div>
415
+
416
+ <style>
417
+ @reference "../../twintrinsic.css";
418
+
419
+ .list-input {
420
+ @apply w-full;
421
+ }
422
+
423
+ .list-input-container {
424
+ @apply flex flex-wrap items-center gap-2 p-2;
425
+ @apply bg-background dark:bg-background;
426
+ @apply border border-border dark:border-border rounded-md;
427
+ @apply focus-within:ring-2 focus-within:ring-primary-500 dark:focus-within:ring-primary-400 focus-within:border-primary-500 dark:focus-within:border-primary-400;
428
+ @apply transition-colors duration-200;
429
+ }
430
+
431
+ .list-input-field {
432
+ @apply flex-grow min-w-[80px] bg-transparent border-none outline-none p-1;
433
+ @apply text-text dark:text-text placeholder:text-muted dark:placeholder:text-muted;
434
+ }
435
+
436
+ .list-input-chip {
437
+ @apply flex items-center gap-1 px-2 py-1 rounded-md;
438
+ @apply bg-surface dark:bg-surface text-text dark:text-text;
439
+ @apply border border-border dark:border-border;
440
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
441
+ }
442
+
443
+ .list-input-chip-focused {
444
+ @apply bg-primary-100 dark:bg-primary-900 border-primary-300 dark:border-primary-700;
445
+ }
446
+
447
+ .list-input-chip-text {
448
+ @apply text-sm;
449
+ }
450
+
451
+ .list-input-chip-remove {
452
+ @apply p-0.5 rounded-full;
453
+ @apply text-muted dark:text-muted;
454
+ @apply hover:bg-hover dark:hover:bg-hover hover:text-text dark:hover:text-text;
455
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
456
+ }
457
+
458
+ .list-input-error {
459
+ @apply mt-1 text-xs text-error-600 dark:text-error-400;
460
+ }
461
+
462
+ .list-input-invalid .list-input-container {
463
+ @apply border-error-500 dark:border-error-400 focus-within:ring-error-500 dark:focus-within:ring-error-400 focus-within:border-error-500 dark:focus-within:border-error-400;
464
+ }
465
+
466
+ .list-input-disabled {
467
+ @apply opacity-60 pointer-events-none;
468
+ }
469
+ </style>
@@ -0,0 +1,70 @@
1
+ export default ListInput;
2
+ type ListInput = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * ListInput - A component for handling multiple input values displayed as "chips".
8
+ * Allows adding, removing, and editing multiple values with keyboard navigation.
9
+ *
10
+ * Usage:
11
+ * ```svelte
12
+ * <ListInput
13
+ * name="tags"
14
+ * placeholder="Add tag..."
15
+ * values={['react', 'svelte']}
16
+ * onchange={handleTagsChange}
17
+ * />
18
+ *
19
+ * <FormField label="Recipients">
20
+ * <ListInput
21
+ * name="recipients"
22
+ * placeholder="Add email..."
23
+ * validator={(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)}
24
+ * errorMessage="Please enter a valid email address"
25
+ * />
26
+ * </FormField>
27
+ * ```
28
+ */
29
+ declare const ListInput: import("svelte").Component<{
30
+ class?: string;
31
+ id?: any;
32
+ name: any;
33
+ placeholder?: string;
34
+ values?: any[];
35
+ disabled?: boolean;
36
+ required?: boolean;
37
+ allowDuplicates?: boolean;
38
+ addOnKeys?: string;
39
+ pasteSeparator?: string;
40
+ maxItems: any;
41
+ validator: any;
42
+ errorMessage?: string;
43
+ ariaLabel: any;
44
+ onchange: any;
45
+ onadd: any;
46
+ onremove: any;
47
+ onfocus: any;
48
+ onblur: any;
49
+ }, {}, "">;
50
+ type $$ComponentProps = {
51
+ class?: string;
52
+ id?: any;
53
+ name: any;
54
+ placeholder?: string;
55
+ values?: any[];
56
+ disabled?: boolean;
57
+ required?: boolean;
58
+ allowDuplicates?: boolean;
59
+ addOnKeys?: string;
60
+ pasteSeparator?: string;
61
+ maxItems: any;
62
+ validator: any;
63
+ errorMessage?: string;
64
+ ariaLabel: any;
65
+ onchange: any;
66
+ onadd: any;
67
+ onremove: any;
68
+ onfocus: any;
69
+ onblur: any;
70
+ };