@thomasfosterau/components 0.0.0 → 0.2.0

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 (484) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -56
  3. package/dist/canvas/VectorCanvas.svelte +320 -0
  4. package/dist/canvas/VectorCanvas.svelte.d.ts +105 -0
  5. package/dist/canvas/history.d.ts +105 -0
  6. package/dist/canvas/history.js +147 -0
  7. package/dist/canvas/index.d.ts +15 -0
  8. package/dist/canvas/index.js +14 -0
  9. package/dist/canvas/types.d.ts +91 -0
  10. package/dist/canvas/types.js +13 -0
  11. package/dist/canvas/utils.d.ts +66 -0
  12. package/dist/canvas/utils.js +114 -0
  13. package/dist/charts/Chart.svelte +300 -0
  14. package/dist/charts/Chart.svelte.d.ts +50 -0
  15. package/dist/charts/index.d.ts +11 -0
  16. package/dist/charts/index.js +10 -0
  17. package/dist/charts/types.d.ts +28 -0
  18. package/dist/charts/types.js +6 -0
  19. package/dist/citations/BookCitation.svelte +53 -0
  20. package/dist/citations/BookCitation.svelte.d.ts +41 -0
  21. package/dist/citations/ChapterCitation.svelte +38 -0
  22. package/dist/citations/ChapterCitation.svelte.d.ts +50 -0
  23. package/dist/citations/Citation.svelte +47 -0
  24. package/dist/citations/Citation.svelte.d.ts +24 -0
  25. package/dist/citations/CreditList.svelte +69 -0
  26. package/dist/citations/CreditList.svelte.d.ts +47 -0
  27. package/dist/citations/DOILink.svelte +28 -0
  28. package/dist/citations/DOILink.svelte.d.ts +19 -0
  29. package/dist/citations/Footnote.svelte +39 -0
  30. package/dist/citations/Footnote.svelte.d.ts +17 -0
  31. package/dist/citations/FootnoteList.svelte +36 -0
  32. package/dist/citations/FootnoteList.svelte.d.ts +21 -0
  33. package/dist/citations/ISBNLink.svelte +29 -0
  34. package/dist/citations/ISBNLink.svelte.d.ts +19 -0
  35. package/dist/citations/ISSNLink.svelte +29 -0
  36. package/dist/citations/ISSNLink.svelte.d.ts +19 -0
  37. package/dist/citations/Publisher.svelte +34 -0
  38. package/dist/citations/Publisher.svelte.d.ts +14 -0
  39. package/dist/citations/ReferenceList.svelte +53 -0
  40. package/dist/citations/ReferenceList.svelte.d.ts +21 -0
  41. package/dist/citations/ReportCitation.svelte +41 -0
  42. package/dist/citations/ReportCitation.svelte.d.ts +36 -0
  43. package/dist/citations/ScholarlyArticleCitation.svelte +64 -0
  44. package/dist/citations/ScholarlyArticleCitation.svelte.d.ts +41 -0
  45. package/dist/citations/ThesisCitation.svelte +35 -0
  46. package/dist/citations/ThesisCitation.svelte.d.ts +29 -0
  47. package/dist/citations/bibliography.svelte.d.ts +55 -0
  48. package/dist/citations/bibliography.svelte.js +78 -0
  49. package/dist/citations/citation.d.ts +71 -0
  50. package/dist/citations/citation.js +121 -0
  51. package/dist/citations/csl.d.ts +102 -0
  52. package/dist/citations/csl.js +20 -0
  53. package/dist/citations/index.d.ts +31 -0
  54. package/dist/citations/index.js +36 -0
  55. package/dist/citations/list-format.d.ts +34 -0
  56. package/dist/citations/list-format.js +40 -0
  57. package/dist/context.svelte.d.ts +46 -0
  58. package/dist/context.svelte.js +51 -0
  59. package/dist/data/ChatBubble.svelte +48 -0
  60. package/dist/data/ChatBubble.svelte.d.ts +33 -0
  61. package/dist/data/DataGrid.svelte +150 -0
  62. package/dist/data/DataGrid.svelte.d.ts +118 -0
  63. package/dist/data/DescriptionList.svelte +53 -0
  64. package/dist/data/DescriptionList.svelte.d.ts +26 -0
  65. package/dist/data/DescriptionRow.svelte +56 -0
  66. package/dist/data/DescriptionRow.svelte.d.ts +21 -0
  67. package/dist/data/DiffText.svelte +29 -0
  68. package/dist/data/DiffText.svelte.d.ts +19 -0
  69. package/dist/data/ItemList.svelte +80 -0
  70. package/dist/data/ItemList.svelte.d.ts +52 -0
  71. package/dist/data/LinkListItem.svelte +46 -0
  72. package/dist/data/LinkListItem.svelte.d.ts +21 -0
  73. package/dist/data/MessageThread.svelte +29 -0
  74. package/dist/data/MessageThread.svelte.d.ts +17 -0
  75. package/dist/data/Pagination.svelte +136 -0
  76. package/dist/data/Pagination.svelte.d.ts +79 -0
  77. package/dist/data/PaginationFooter.svelte +42 -0
  78. package/dist/data/PaginationFooter.svelte.d.ts +61 -0
  79. package/dist/data/ResultCount.svelte +27 -0
  80. package/dist/data/ResultCount.svelte.d.ts +18 -0
  81. package/dist/data/Table.svelte +151 -0
  82. package/dist/data/Table.svelte.d.ts +75 -0
  83. package/dist/data/TreeList.svelte +41 -0
  84. package/dist/data/TreeList.svelte.d.ts +17 -0
  85. package/dist/data/TreeListItem.svelte +29 -0
  86. package/dist/data/TreeListItem.svelte.d.ts +10 -0
  87. package/dist/data/description-context.d.ts +39 -0
  88. package/dist/data/description-context.js +36 -0
  89. package/dist/data/diff.d.ts +14 -0
  90. package/dist/data/diff.js +10 -0
  91. package/dist/data/index.d.ts +37 -0
  92. package/dist/data/index.js +37 -0
  93. package/dist/data/list-item.d.ts +30 -0
  94. package/dist/data/list-item.js +19 -0
  95. package/dist/data/pagination.d.ts +81 -0
  96. package/dist/data/pagination.js +79 -0
  97. package/dist/data/table-types.d.ts +71 -0
  98. package/dist/data/table-types.js +35 -0
  99. package/dist/feedback/ConfirmDialog.svelte +79 -0
  100. package/dist/feedback/ConfirmDialog.svelte.d.ts +82 -0
  101. package/dist/feedback/Dialog.svelte +47 -0
  102. package/dist/feedback/Dialog.svelte.d.ts +36 -0
  103. package/dist/feedback/DialogFrame.svelte +64 -0
  104. package/dist/feedback/DialogFrame.svelte.d.ts +23 -0
  105. package/dist/feedback/Drawer.svelte +43 -0
  106. package/dist/feedback/Drawer.svelte.d.ts +43 -0
  107. package/dist/feedback/LoadingBoundary.svelte +61 -0
  108. package/dist/feedback/LoadingBoundary.svelte.d.ts +34 -0
  109. package/dist/feedback/ProgressBar.svelte +46 -0
  110. package/dist/feedback/ProgressBar.svelte.d.ts +28 -0
  111. package/dist/feedback/SegmentBar.svelte +83 -0
  112. package/dist/feedback/SegmentBar.svelte.d.ts +37 -0
  113. package/dist/feedback/StatTile.svelte +77 -0
  114. package/dist/feedback/StatTile.svelte.d.ts +50 -0
  115. package/dist/feedback/Stats.svelte +18 -0
  116. package/dist/feedback/Stats.svelte.d.ts +16 -0
  117. package/dist/feedback/Steps.svelte +72 -0
  118. package/dist/feedback/Steps.svelte.d.ts +61 -0
  119. package/dist/feedback/Timeline.svelte +86 -0
  120. package/dist/feedback/Timeline.svelte.d.ts +55 -0
  121. package/dist/feedback/Toaster.svelte +78 -0
  122. package/dist/feedback/Toaster.svelte.d.ts +32 -0
  123. package/dist/feedback/index.d.ts +34 -0
  124. package/dist/feedback/index.js +29 -0
  125. package/dist/feedback/sizes.d.ts +41 -0
  126. package/dist/feedback/sizes.js +69 -0
  127. package/dist/feedback/toaster.d.ts +48 -0
  128. package/dist/feedback/toaster.js +29 -0
  129. package/dist/fields/Calendar.svelte +60 -0
  130. package/dist/fields/Calendar.svelte.d.ts +11 -0
  131. package/dist/fields/CalendarMonths.svelte +86 -0
  132. package/dist/fields/CalendarMonths.svelte.d.ts +30 -0
  133. package/dist/fields/Checkbox.svelte +33 -0
  134. package/dist/fields/Checkbox.svelte.d.ts +30 -0
  135. package/dist/fields/CheckboxGroup.svelte +41 -0
  136. package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
  137. package/dist/fields/Combobox.svelte +229 -0
  138. package/dist/fields/Combobox.svelte.d.ts +133 -0
  139. package/dist/fields/DateField.svelte +37 -0
  140. package/dist/fields/DateField.svelte.d.ts +20 -0
  141. package/dist/fields/DateInput.svelte +31 -0
  142. package/dist/fields/DateInput.svelte.d.ts +35 -0
  143. package/dist/fields/DatePicker.svelte +47 -0
  144. package/dist/fields/DatePicker.svelte.d.ts +25 -0
  145. package/dist/fields/DateRangePicker.svelte +70 -0
  146. package/dist/fields/DateRangePicker.svelte.d.ts +43 -0
  147. package/dist/fields/Dropzone.svelte +232 -0
  148. package/dist/fields/Dropzone.svelte.d.ts +103 -0
  149. package/dist/fields/FieldErrors.svelte +30 -0
  150. package/dist/fields/FieldErrors.svelte.d.ts +11 -0
  151. package/dist/fields/Fieldset.svelte +53 -0
  152. package/dist/fields/Fieldset.svelte.d.ts +27 -0
  153. package/dist/fields/FileInput.svelte +68 -0
  154. package/dist/fields/FileInput.svelte.d.ts +39 -0
  155. package/dist/fields/FormField.svelte +78 -0
  156. package/dist/fields/FormField.svelte.d.ts +29 -0
  157. package/dist/fields/ImageInput.svelte +87 -0
  158. package/dist/fields/ImageInput.svelte.d.ts +45 -0
  159. package/dist/fields/MultiSelect.svelte +112 -0
  160. package/dist/fields/MultiSelect.svelte.d.ts +75 -0
  161. package/dist/fields/NumberField.svelte +190 -0
  162. package/dist/fields/NumberField.svelte.d.ts +76 -0
  163. package/dist/fields/PasswordInput.svelte +95 -0
  164. package/dist/fields/PasswordInput.svelte.d.ts +58 -0
  165. package/dist/fields/PickerPopover.svelte +47 -0
  166. package/dist/fields/PickerPopover.svelte.d.ts +16 -0
  167. package/dist/fields/PinInput.svelte +83 -0
  168. package/dist/fields/PinInput.svelte.d.ts +66 -0
  169. package/dist/fields/PushToggle.svelte +93 -0
  170. package/dist/fields/PushToggle.svelte.d.ts +55 -0
  171. package/dist/fields/RadioGroup.svelte +88 -0
  172. package/dist/fields/RadioGroup.svelte.d.ts +41 -0
  173. package/dist/fields/RangeCalendar.svelte +51 -0
  174. package/dist/fields/RangeCalendar.svelte.d.ts +23 -0
  175. package/dist/fields/Rating.svelte +127 -0
  176. package/dist/fields/Rating.svelte.d.ts +86 -0
  177. package/dist/fields/SearchInput.svelte +58 -0
  178. package/dist/fields/SearchInput.svelte.d.ts +33 -0
  179. package/dist/fields/SegmentGroup.svelte +91 -0
  180. package/dist/fields/SegmentGroup.svelte.d.ts +20 -0
  181. package/dist/fields/Select.svelte +36 -0
  182. package/dist/fields/Select.svelte.d.ts +35 -0
  183. package/dist/fields/Slider.svelte +194 -0
  184. package/dist/fields/Slider.svelte.d.ts +100 -0
  185. package/dist/fields/TagsInput.svelte +256 -0
  186. package/dist/fields/TagsInput.svelte.d.ts +102 -0
  187. package/dist/fields/TextInput.svelte +26 -0
  188. package/dist/fields/TextInput.svelte.d.ts +36 -0
  189. package/dist/fields/Textarea.svelte +22 -0
  190. package/dist/fields/Textarea.svelte.d.ts +31 -0
  191. package/dist/fields/TimeField.svelte +30 -0
  192. package/dist/fields/TimeField.svelte.d.ts +19 -0
  193. package/dist/fields/Toggle.svelte +72 -0
  194. package/dist/fields/Toggle.svelte.d.ts +38 -0
  195. package/dist/fields/VideoInput.svelte +35 -0
  196. package/dist/fields/VideoInput.svelte.d.ts +46 -0
  197. package/dist/fields/calendar-props.d.ts +119 -0
  198. package/dist/fields/calendar-props.js +51 -0
  199. package/dist/fields/dates.d.ts +28 -0
  200. package/dist/fields/dates.js +26 -0
  201. package/dist/fields/field-types.d.ts +80 -0
  202. package/dist/fields/field-types.js +92 -0
  203. package/dist/fields/file-validation.d.ts +30 -0
  204. package/dist/fields/file-validation.js +51 -0
  205. package/dist/fields/index.d.ts +43 -0
  206. package/dist/fields/index.js +42 -0
  207. package/dist/fields/latest-search.svelte.d.ts +38 -0
  208. package/dist/fields/latest-search.svelte.js +85 -0
  209. package/dist/fields/number-format.d.ts +52 -0
  210. package/dist/fields/number-format.js +89 -0
  211. package/dist/fields/segmented-field.d.ts +128 -0
  212. package/dist/fields/segmented-field.js +52 -0
  213. package/dist/fields/slider-keys.d.ts +21 -0
  214. package/dist/fields/slider-keys.js +37 -0
  215. package/dist/formatting/Countdown.svelte +110 -0
  216. package/dist/formatting/Countdown.svelte.d.ts +35 -0
  217. package/dist/formatting/DateTime.svelte +35 -0
  218. package/dist/formatting/DateTime.svelte.d.ts +26 -0
  219. package/dist/formatting/Duration.svelte +18 -0
  220. package/dist/formatting/Duration.svelte.d.ts +20 -0
  221. package/dist/formatting/FormatList.svelte +31 -0
  222. package/dist/formatting/FormatList.svelte.d.ts +46 -0
  223. package/dist/formatting/Highlight.svelte +51 -0
  224. package/dist/formatting/Highlight.svelte.d.ts +19 -0
  225. package/dist/formatting/Number.svelte +35 -0
  226. package/dist/formatting/Number.svelte.d.ts +43 -0
  227. package/dist/formatting/RelativeTime.svelte +69 -0
  228. package/dist/formatting/RelativeTime.svelte.d.ts +26 -0
  229. package/dist/formatting/Year.svelte +39 -0
  230. package/dist/formatting/Year.svelte.d.ts +21 -0
  231. package/dist/formatting/duration.d.ts +44 -0
  232. package/dist/formatting/duration.js +125 -0
  233. package/dist/formatting/index.d.ts +15 -0
  234. package/dist/formatting/index.js +17 -0
  235. package/dist/forms/EntityForm.svelte +185 -0
  236. package/dist/forms/EntityForm.svelte.d.ts +66 -0
  237. package/dist/forms/FormActions.svelte +29 -0
  238. package/dist/forms/FormActions.svelte.d.ts +37 -0
  239. package/dist/forms/FormErrors.svelte +21 -0
  240. package/dist/forms/FormErrors.svelte.d.ts +16 -0
  241. package/dist/forms/HiddenField.svelte +22 -0
  242. package/dist/forms/HiddenField.svelte.d.ts +11 -0
  243. package/dist/forms/SearchForm.svelte +43 -0
  244. package/dist/forms/SearchForm.svelte.d.ts +36 -0
  245. package/dist/forms/entity-form.d.ts +12 -0
  246. package/dist/forms/entity-form.js +12 -0
  247. package/dist/forms/index.d.ts +14 -0
  248. package/dist/forms/index.js +14 -0
  249. package/dist/hotkeys/HotkeyBadge.svelte +39 -0
  250. package/dist/hotkeys/HotkeyBadge.svelte.d.ts +25 -0
  251. package/dist/hotkeys/HotkeyHelpDialog.svelte +68 -0
  252. package/dist/hotkeys/HotkeyHelpDialog.svelte.d.ts +35 -0
  253. package/dist/hotkeys/HotkeyProvider.svelte +92 -0
  254. package/dist/hotkeys/HotkeyProvider.svelte.d.ts +4 -0
  255. package/dist/hotkeys/context.svelte.d.ts +47 -0
  256. package/dist/hotkeys/context.svelte.js +72 -0
  257. package/dist/hotkeys/index.d.ts +28 -0
  258. package/dist/hotkeys/index.js +27 -0
  259. package/dist/hotkeys/match.d.ts +65 -0
  260. package/dist/hotkeys/match.js +135 -0
  261. package/dist/hotkeys/registry.svelte.d.ts +55 -0
  262. package/dist/hotkeys/registry.svelte.js +110 -0
  263. package/dist/hotkeys/types.d.ts +74 -0
  264. package/dist/hotkeys/types.js +18 -0
  265. package/dist/index.d.ts +37 -0
  266. package/dist/index.js +37 -0
  267. package/dist/infobox/Infobox.svelte +79 -0
  268. package/dist/infobox/Infobox.svelte.d.ts +33 -0
  269. package/dist/infobox/InfoboxHeader.svelte +35 -0
  270. package/dist/infobox/InfoboxHeader.svelte.d.ts +18 -0
  271. package/dist/infobox/InfoboxImage.svelte +38 -0
  272. package/dist/infobox/InfoboxImage.svelte.d.ts +21 -0
  273. package/dist/infobox/InfoboxRow.svelte +50 -0
  274. package/dist/infobox/InfoboxRow.svelte.d.ts +25 -0
  275. package/dist/infobox/InfoboxSection.svelte +24 -0
  276. package/dist/infobox/InfoboxSection.svelte.d.ts +12 -0
  277. package/dist/infobox/index.d.ts +21 -0
  278. package/dist/infobox/index.js +20 -0
  279. package/dist/infobox/types.d.ts +86 -0
  280. package/dist/infobox/types.js +27 -0
  281. package/dist/layout/AppShell.svelte +50 -0
  282. package/dist/layout/AppShell.svelte.d.ts +28 -0
  283. package/dist/layout/Breadcrumbs.svelte +52 -0
  284. package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
  285. package/dist/layout/Cluster.svelte +28 -0
  286. package/dist/layout/Cluster.svelte.d.ts +27 -0
  287. package/dist/layout/DetailLayout.svelte +50 -0
  288. package/dist/layout/DetailLayout.svelte.d.ts +19 -0
  289. package/dist/layout/FilterBar.svelte +40 -0
  290. package/dist/layout/FilterBar.svelte.d.ts +12 -0
  291. package/dist/layout/Fits.svelte +27 -0
  292. package/dist/layout/Fits.svelte.d.ts +13 -0
  293. package/dist/layout/Flow.svelte +46 -0
  294. package/dist/layout/Flow.svelte.d.ts +16 -0
  295. package/dist/layout/Footer.svelte +32 -0
  296. package/dist/layout/Footer.svelte.d.ts +12 -0
  297. package/dist/layout/Grid.svelte +23 -0
  298. package/dist/layout/Grid.svelte.d.ts +26 -0
  299. package/dist/layout/HStack.svelte +12 -0
  300. package/dist/layout/HStack.svelte.d.ts +34 -0
  301. package/dist/layout/Hero.svelte +76 -0
  302. package/dist/layout/Hero.svelte.d.ts +36 -0
  303. package/dist/layout/Main.svelte +30 -0
  304. package/dist/layout/Main.svelte.d.ts +15 -0
  305. package/dist/layout/Navbar.svelte +68 -0
  306. package/dist/layout/Navbar.svelte.d.ts +31 -0
  307. package/dist/layout/PageHeader.svelte +77 -0
  308. package/dist/layout/PageHeader.svelte.d.ts +33 -0
  309. package/dist/layout/Section.svelte +72 -0
  310. package/dist/layout/Section.svelte.d.ts +40 -0
  311. package/dist/layout/Sidebar.svelte +54 -0
  312. package/dist/layout/Sidebar.svelte.d.ts +27 -0
  313. package/dist/layout/Spacer.svelte +12 -0
  314. package/dist/layout/Spacer.svelte.d.ts +6 -0
  315. package/dist/layout/Tabs.svelte +85 -0
  316. package/dist/layout/Tabs.svelte.d.ts +42 -0
  317. package/dist/layout/VStack.svelte +12 -0
  318. package/dist/layout/VStack.svelte.d.ts +29 -0
  319. package/dist/layout/index.d.ts +38 -0
  320. package/dist/layout/index.js +38 -0
  321. package/dist/layout/space.d.ts +44 -0
  322. package/dist/layout/space.js +76 -0
  323. package/dist/layout/types.d.ts +52 -0
  324. package/dist/layout/types.js +27 -0
  325. package/dist/links/ContextLinks.svelte +28 -0
  326. package/dist/links/ContextLinks.svelte.d.ts +20 -0
  327. package/dist/links/EmailLink.svelte +26 -0
  328. package/dist/links/EmailLink.svelte.d.ts +20 -0
  329. package/dist/links/index.d.ts +7 -0
  330. package/dist/links/index.js +7 -0
  331. package/dist/media/Figure.svelte +26 -0
  332. package/dist/media/Figure.svelte.d.ts +12 -0
  333. package/dist/media/Image.svelte +76 -0
  334. package/dist/media/Image.svelte.d.ts +41 -0
  335. package/dist/media/Video.svelte +65 -0
  336. package/dist/media/Video.svelte.d.ts +29 -0
  337. package/dist/media/image-dimensions.d.ts +28 -0
  338. package/dist/media/image-dimensions.js +34 -0
  339. package/dist/media/index.d.ts +10 -0
  340. package/dist/media/index.js +10 -0
  341. package/dist/search/CommandPalette.svelte +257 -0
  342. package/dist/search/CommandPalette.svelte.d.ts +59 -0
  343. package/dist/search/index.d.ts +13 -0
  344. package/dist/search/index.js +13 -0
  345. package/dist/search/types.d.ts +40 -0
  346. package/dist/search/types.js +20 -0
  347. package/dist/seo/Head.svelte +190 -0
  348. package/dist/seo/Head.svelte.d.ts +86 -0
  349. package/dist/seo/JsonLd.svelte +30 -0
  350. package/dist/seo/JsonLd.svelte.d.ts +20 -0
  351. package/dist/seo/index.d.ts +18 -0
  352. package/dist/seo/index.js +17 -0
  353. package/dist/seo/json-ld.d.ts +33 -0
  354. package/dist/seo/json-ld.js +29 -0
  355. package/dist/seo/og-image.d.ts +72 -0
  356. package/dist/seo/og-image.js +82 -0
  357. package/dist/seo/types.d.ts +43 -0
  358. package/dist/seo/types.js +6 -0
  359. package/dist/theme.css +161 -0
  360. package/dist/tips/PopoverTip.svelte +80 -0
  361. package/dist/tips/PopoverTip.svelte.d.ts +33 -0
  362. package/dist/tips/Tip.svelte +91 -0
  363. package/dist/tips/Tip.svelte.d.ts +25 -0
  364. package/dist/tips/index.d.ts +15 -0
  365. package/dist/tips/index.js +14 -0
  366. package/dist/tips/tip-stack.d.ts +26 -0
  367. package/dist/tips/tip-stack.js +37 -0
  368. package/dist/tips/tip-store.svelte.d.ts +75 -0
  369. package/dist/tips/tip-store.svelte.js +158 -0
  370. package/dist/tips/types.d.ts +46 -0
  371. package/dist/tips/types.js +6 -0
  372. package/dist/ui/Accordion.svelte +75 -0
  373. package/dist/ui/Accordion.svelte.d.ts +50 -0
  374. package/dist/ui/AccordionItem.svelte +73 -0
  375. package/dist/ui/AccordionItem.svelte.d.ts +18 -0
  376. package/dist/ui/Alert.svelte +61 -0
  377. package/dist/ui/Alert.svelte.d.ts +37 -0
  378. package/dist/ui/Badge.svelte +39 -0
  379. package/dist/ui/Badge.svelte.d.ts +32 -0
  380. package/dist/ui/Button.svelte +44 -0
  381. package/dist/ui/Button.svelte.d.ts +29 -0
  382. package/dist/ui/ButtonGroup.svelte +24 -0
  383. package/dist/ui/ButtonGroup.svelte.d.ts +17 -0
  384. package/dist/ui/Card.svelte +49 -0
  385. package/dist/ui/Card.svelte.d.ts +44 -0
  386. package/dist/ui/Chip.svelte +58 -0
  387. package/dist/ui/Chip.svelte.d.ts +21 -0
  388. package/dist/ui/ConfirmButton.svelte +75 -0
  389. package/dist/ui/ConfirmButton.svelte.d.ts +48 -0
  390. package/dist/ui/ContextMenu.svelte +75 -0
  391. package/dist/ui/ContextMenu.svelte.d.ts +25 -0
  392. package/dist/ui/Disclosure.svelte +46 -0
  393. package/dist/ui/Disclosure.svelte.d.ts +22 -0
  394. package/dist/ui/Divider.svelte +47 -0
  395. package/dist/ui/Divider.svelte.d.ts +28 -0
  396. package/dist/ui/DropdownMenu.svelte +44 -0
  397. package/dist/ui/DropdownMenu.svelte.d.ts +30 -0
  398. package/dist/ui/EmptyState.svelte +37 -0
  399. package/dist/ui/EmptyState.svelte.d.ts +22 -0
  400. package/dist/ui/ExternalLink.svelte +38 -0
  401. package/dist/ui/ExternalLink.svelte.d.ts +34 -0
  402. package/dist/ui/Heading.svelte +58 -0
  403. package/dist/ui/Heading.svelte.d.ts +22 -0
  404. package/dist/ui/Link.svelte +34 -0
  405. package/dist/ui/Link.svelte.d.ts +20 -0
  406. package/dist/ui/LinkButton.svelte +51 -0
  407. package/dist/ui/LinkButton.svelte.d.ts +33 -0
  408. package/dist/ui/Menu.svelte +24 -0
  409. package/dist/ui/Menu.svelte.d.ts +13 -0
  410. package/dist/ui/MenuCheckboxItem.svelte +33 -0
  411. package/dist/ui/MenuCheckboxItem.svelte.d.ts +20 -0
  412. package/dist/ui/MenuItem.svelte +73 -0
  413. package/dist/ui/MenuItem.svelte.d.ts +21 -0
  414. package/dist/ui/MenuRadioGroup.svelte +25 -0
  415. package/dist/ui/MenuRadioGroup.svelte.d.ts +20 -0
  416. package/dist/ui/MenuRadioItem.svelte +20 -0
  417. package/dist/ui/MenuRadioItem.svelte.d.ts +15 -0
  418. package/dist/ui/MenuSeparator.svelte +27 -0
  419. package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
  420. package/dist/ui/MenuSub.svelte +24 -0
  421. package/dist/ui/MenuSub.svelte.d.ts +15 -0
  422. package/dist/ui/Popover.svelte +63 -0
  423. package/dist/ui/Popover.svelte.d.ts +40 -0
  424. package/dist/ui/ShareButton.svelte +64 -0
  425. package/dist/ui/ShareButton.svelte.d.ts +39 -0
  426. package/dist/ui/Skeleton.svelte +43 -0
  427. package/dist/ui/Skeleton.svelte.d.ts +22 -0
  428. package/dist/ui/Spinner.svelte +41 -0
  429. package/dist/ui/Spinner.svelte.d.ts +35 -0
  430. package/dist/ui/Stack.svelte +36 -0
  431. package/dist/ui/Stack.svelte.d.ts +48 -0
  432. package/dist/ui/ThemeToggle.svelte +65 -0
  433. package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
  434. package/dist/ui/ToggleButton.svelte +58 -0
  435. package/dist/ui/ToggleButton.svelte.d.ts +45 -0
  436. package/dist/ui/ToggleGroup.svelte +142 -0
  437. package/dist/ui/ToggleGroup.svelte.d.ts +86 -0
  438. package/dist/ui/Toolbar.svelte +40 -0
  439. package/dist/ui/Toolbar.svelte.d.ts +20 -0
  440. package/dist/ui/ToolbarButton.svelte +29 -0
  441. package/dist/ui/ToolbarButton.svelte.d.ts +21 -0
  442. package/dist/ui/ToolbarLink.svelte +21 -0
  443. package/dist/ui/ToolbarLink.svelte.d.ts +12 -0
  444. package/dist/ui/ToolbarSeparator.svelte +22 -0
  445. package/dist/ui/ToolbarSeparator.svelte.d.ts +12 -0
  446. package/dist/ui/ToolbarToggleGroup.svelte +66 -0
  447. package/dist/ui/ToolbarToggleGroup.svelte.d.ts +52 -0
  448. package/dist/ui/Tooltip.svelte +54 -0
  449. package/dist/ui/Tooltip.svelte.d.ts +35 -0
  450. package/dist/ui/VisuallyHidden.svelte +23 -0
  451. package/dist/ui/VisuallyHidden.svelte.d.ts +10 -0
  452. package/dist/ui/accordion-context.d.ts +26 -0
  453. package/dist/ui/accordion-context.js +23 -0
  454. package/dist/ui/colour-token.d.ts +41 -0
  455. package/dist/ui/colour-token.js +49 -0
  456. package/dist/ui/context-menu.d.ts +13 -0
  457. package/dist/ui/context-menu.js +13 -0
  458. package/dist/ui/index.d.ts +63 -0
  459. package/dist/ui/index.js +62 -0
  460. package/dist/ui/menu-context.d.ts +20 -0
  461. package/dist/ui/menu-context.js +22 -0
  462. package/dist/ui/menu-parts/CheckboxItemPart.svelte +26 -0
  463. package/dist/ui/menu-parts/CheckboxItemPart.svelte.d.ts +5 -0
  464. package/dist/ui/menu-parts/ItemPart.svelte +29 -0
  465. package/dist/ui/menu-parts/ItemPart.svelte.d.ts +5 -0
  466. package/dist/ui/menu-parts/RadioGroupPart.svelte +24 -0
  467. package/dist/ui/menu-parts/RadioGroupPart.svelte.d.ts +5 -0
  468. package/dist/ui/menu-parts/RadioItemPart.svelte +22 -0
  469. package/dist/ui/menu-parts/RadioItemPart.svelte.d.ts +5 -0
  470. package/dist/ui/menu-parts/SeparatorPart.svelte +16 -0
  471. package/dist/ui/menu-parts/SeparatorPart.svelte.d.ts +5 -0
  472. package/dist/ui/menu-parts/SubPart.svelte +28 -0
  473. package/dist/ui/menu-parts/SubPart.svelte.d.ts +5 -0
  474. package/dist/ui/menu-parts/menu-parts.d.ts +104 -0
  475. package/dist/ui/menu-parts/menu-parts.js +31 -0
  476. package/dist/ui/menu-parts/parts.d.ts +12 -0
  477. package/dist/ui/menu-parts/parts.js +27 -0
  478. package/dist/ui/slug.d.ts +19 -0
  479. package/dist/ui/slug.js +25 -0
  480. package/dist/ui/toggle-classes.d.ts +39 -0
  481. package/dist/ui/toggle-classes.js +36 -0
  482. package/dist/ui/tones.d.ts +103 -0
  483. package/dist/ui/tones.js +240 -0
  484. package/package.json +41 -25
@@ -0,0 +1,35 @@
1
+ import type { HTMLSelectAttributes } from 'svelte/elements';
2
+ import { type FieldOptionInput, type FieldSize } from './field-types.ts';
3
+ interface Props extends Omit<HTMLSelectAttributes, 'value' | 'size'> {
4
+ /** Full options, or bare strings when value and label are the same. */
5
+ options: readonly FieldOptionInput[];
6
+ /**
7
+ * The selected option’s value; `''` selects the placeholder. Bindable.
8
+ * @defaultValue `''`
9
+ */
10
+ value?: string | undefined;
11
+ /** Disabled first option, shown while nothing is selected. */
12
+ placeholder?: string | undefined;
13
+ /**
14
+ * daisyUI size step.
15
+ * @defaultValue `'md'`
16
+ */
17
+ size?: FieldSize | undefined;
18
+ /** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
19
+ invalid?: boolean | undefined;
20
+ /**
21
+ * `FieldControl`: space-separated ids for `aria-describedby`.
22
+ * @defaultValue `''`
23
+ */
24
+ describedBy?: string | undefined;
25
+ /**
26
+ * `FieldControl`: the visible label's id. Not used — this control is
27
+ * labelable, so the `FormField`'s `<label for>` already names it; taken
28
+ * here so a spread `FieldControl` does not leak it as an attribute.
29
+ */
30
+ labelId?: string | undefined;
31
+ }
32
+ /** A native `<select>` in daisyUI styling over a list of options, with an optional placeholder and `FieldControl` wiring. */
33
+ declare const Select: import("svelte").Component<Props, {}, "value">;
34
+ type Select = ReturnType<typeof Select>;
35
+ export default Select;
@@ -0,0 +1,194 @@
1
+ <!-- @component
2
+ A number, or a `[low, high]` range, picked from a bounded, stepped scale by dragging or with the arrow keys, on bits-ui's `Slider`.
3
+ -->
4
+ <!--
5
+ Slider — one component, two modes. `type="single"` binds a `number`;
6
+ `type="multiple"` binds a `number[]` with one thumb per entry. Built on
7
+ bits-ui's `Slider` (pointer drag, arrow / Home / End keys, non-crossing
8
+ thumbs) and wired through the `FieldControl` contract like every other
9
+ control in `fields/`.
10
+
11
+ Why not `TextInput type="range"` or daisyUI's `range` on a native input: a
12
+ native range has one thumb, no ticks and no consistent value readout, and
13
+ the keyboard model across two thumbs is exactly what AGENTS.md reserves for
14
+ bits-ui. The daisyUI `range` CLASSES are still the colour and size
15
+ vocabulary: `rangeTone` sets `color` (the fill, read as `currentColor`) and
16
+ `--range-thumb` (the thumb face), and `rangeSize` sets `--range-thumb-size`,
17
+ which the thumb and the track are sized from. The `range` class itself is
18
+ not used — it styles a native input's pseudo-elements.
19
+
20
+ ARIA contract (APG slider and multi-thumb slider): every thumb is
21
+ `role="slider"` with `aria-valuemin/max/now` and `aria-orientation` from
22
+ bits-ui. This file adds `aria-valuetext` from `formatValue`, the accessible
23
+ name, `aria-describedby` / `aria-invalid` from `FieldControl`, Page Up /
24
+ Page Down (bits-ui 2.20 handles only the arrows, Home and End; a page is a
25
+ tenth of the scale, at least one step), and one hidden `<input>` per thumb
26
+ under `name`, so a plain form posts the value.
27
+
28
+ Naming: a `<label for>` cannot name a `role="slider"` span, so the `id` goes
29
+ on the first thumb (the second is `${id}-1`) and the name comes from
30
+ `aria-labelledby={labelId}` — the visible label's id, which `FormField`
31
+ hands its control as `FieldControl.labelId` (#179). In range mode each
32
+ thumb is labelled by the label AND itself (`aria-labelledby="label thumb"`
33
+ with `aria-label` = its `thumbLabels` entry), so the second thumb reads
34
+ "Hours of sun Maximum". Outside a `FormField`, pass `label` instead; it
35
+ becomes the thumbs' `aria-label`.
36
+
37
+ The visible readout is `aria-hidden`: the same text is each thumb's
38
+ `aria-valuetext`, and announcing it twice would be noise.
39
+
40
+ Known limitation: bits-ui 2.20 gives every thumb the slider's global
41
+ `aria-valuemin` / `aria-valuemax` rather than its neighbour's value, so in
42
+ range mode a thumb over-reports how far it can move (it still cannot cross:
43
+ `autoSort` is left on). Not hand-patched, so a bits-ui fix lands for free.
44
+
45
+ `required` is accepted so `{...control}` spreads cleanly, but a slider
46
+ always has a value, so it changes nothing.
47
+ -->
48
+ <script lang="ts">import { Slider } from "bits-ui";
49
+ import { getComponentsContext } from "../context.svelte.js";
50
+ import { rangeTone } from "../ui/tones.js";
51
+ import { rangeSize } from "./field-types.js";
52
+ import { clampToNeighbours, pageStep } from "./slider-keys.js";
53
+ let { type = "single", value = $bindable(), min = 0, max = 100, step = 1, orientation = "horizontal", ticks = false, formatValue, locales = getComponentsContext().locale(), showValue = true, thumbLabels = ["Minimum", "Maximum"], onCommit, name, disabled = false, tone = "primary", size = "md", label, id: idProp, labelId, describedBy = "", invalid = false, required: _required = false, class: className, ...rest } = $props();
54
+ const uid = $props.id();
55
+ const id = $derived(idProp ?? uid);
56
+ const multiple = $derived(type === "multiple");
57
+ const vertical = $derived(orientation === "vertical");
58
+ /** The bound value, normalised to one entry per thumb. */
59
+ const values = $derived.by(() => {
60
+ if (multiple) return Array.isArray(value) ? value : [min, max];
61
+ return [typeof value === "number" ? value : min];
62
+ });
63
+ const numberFormat = $derived(new Intl.NumberFormat(locales));
64
+ const format = $derived(formatValue ?? ((n) => numberFormat.format(n)));
65
+ const readout = $derived(values.map((n) => format(n)).join(" – "));
66
+ // bits-ui wants a mutable array; ours is readonly at the API.
67
+ const steps = $derived(typeof step === "number" ? step : [...step]);
68
+ /** What both `Slider.Root` branches share; only `type` and the bound value differ. */
69
+ const rootProps = $derived({
70
+ onValueCommit: commit,
71
+ min,
72
+ max,
73
+ step: steps,
74
+ orientation,
75
+ disabled,
76
+ class: ["relative flex touch-none items-center select-none data-[disabled]:opacity-30", vertical ? "h-full w-(--range-thumb-size) flex-col" : "h-(--range-thumb-size) w-full"]
77
+ });
78
+ function thumbId(index) {
79
+ return index === 0 ? id : `${id}-${index}`;
80
+ }
81
+ function thumbLabel(index) {
82
+ return thumbLabels[index] ?? `${index + 1}`;
83
+ }
84
+ function ariaLabel(index) {
85
+ if (labelId !== undefined) return multiple ? thumbLabel(index) : undefined;
86
+ if (!multiple) return label;
87
+ return label === undefined ? thumbLabel(index) : `${label} ${thumbLabel(index)}`;
88
+ }
89
+ function ariaLabelledBy(index) {
90
+ if (labelId === undefined) return undefined;
91
+ return multiple ? `${labelId} ${thumbId(index)}` : labelId;
92
+ }
93
+ function commit(next) {
94
+ onCommit?.(next);
95
+ }
96
+ /** Page Up / Page Down: a tenth of the scale (at least one step), clamped between the neighbouring thumbs. */
97
+ function onThumbKeydown(event, index) {
98
+ if (disabled || event.key !== "PageUp" && event.key !== "PageDown") return;
99
+ event.preventDefault();
100
+ const scale = {
101
+ min,
102
+ max,
103
+ step
104
+ };
105
+ const current = values[index] ?? min;
106
+ const next = clampToNeighbours(values, index, pageStep(current, event.key === "PageUp" ? 1 : -1, scale), scale);
107
+ if (next === current) return;
108
+ const updated = values.map((n, i) => i === index ? next : n);
109
+ value = multiple ? updated : next;
110
+ commit(multiple ? updated : next);
111
+ }
112
+ </script>
113
+
114
+ {#snippet parts({
115
+ thumbItems,
116
+ tickItems,
117
+ }: {
118
+ thumbItems: { index: number }[];
119
+ tickItems: { index: number }[];
120
+ })}
121
+ <span
122
+ class={[
123
+ 'relative grow overflow-hidden rounded-full bg-base-300',
124
+ vertical ? 'h-full w-[calc(var(--range-thumb-size)*0.5)]' : 'h-[calc(var(--range-thumb-size)*0.5)] w-full',
125
+ ]}
126
+ >
127
+ <Slider.Range class={['bg-current', vertical ? 'w-full' : 'h-full']} />
128
+ </span>
129
+ {#if ticks}
130
+ {#each tickItems as { index } (index)}
131
+ <Slider.Tick {index} class="size-1 rounded-full bg-base-content/30" />
132
+ {/each}
133
+ {/if}
134
+ {#each thumbItems as { index } (index)}
135
+ <Slider.Thumb
136
+ {index}
137
+ id={thumbId(index)}
138
+ aria-label={ariaLabel(index)}
139
+ aria-labelledby={ariaLabelledBy(index)}
140
+ aria-valuetext={format(values[index] ?? min)}
141
+ aria-describedby={describedBy === '' ? undefined : describedBy}
142
+ aria-invalid={invalid ? 'true' : undefined}
143
+ onkeydown={(event: KeyboardEvent) => onThumbKeydown(event, index)}
144
+ class="size-(--range-thumb-size) cursor-grab rounded-full border-4 border-current bg-(--range-thumb) shadow-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current data-[active]:cursor-grabbing data-[disabled]:cursor-not-allowed"
145
+ />
146
+ {/each}
147
+ {/snippet}
148
+
149
+ <span
150
+ {...rest}
151
+ class={[
152
+ 'inline-flex items-center gap-3',
153
+ vertical ? 'h-48 flex-col' : 'w-full',
154
+ rangeTone[invalid ? 'error' : tone],
155
+ rangeSize[size],
156
+ className,
157
+ ]}
158
+ >
159
+ {#if multiple}
160
+ <Slider.Root
161
+ type="multiple"
162
+ bind:value={
163
+ () => [...values],
164
+ (next: number[]) => {
165
+ value = next;
166
+ }
167
+ }
168
+ {...rootProps}
169
+ >
170
+ {#snippet children(snippetProps)}{@render parts(snippetProps)}{/snippet}
171
+ </Slider.Root>
172
+ {:else}
173
+ <Slider.Root
174
+ type="single"
175
+ bind:value={
176
+ () => values[0] ?? min,
177
+ (next: number) => {
178
+ value = next;
179
+ }
180
+ }
181
+ {...rootProps}
182
+ >
183
+ {#snippet children(snippetProps)}{@render parts(snippetProps)}{/snippet}
184
+ </Slider.Root>
185
+ {/if}
186
+ {#if showValue}
187
+ <span aria-hidden="true" class="text-sm whitespace-nowrap text-base-content tabular-nums">{readout}</span>
188
+ {/if}
189
+ {#if name !== undefined}
190
+ {#each values as entry, index (index)}
191
+ <input type="hidden" {name} value={entry} {disabled} />
192
+ {/each}
193
+ {/if}
194
+ </span>
@@ -0,0 +1,100 @@
1
+ import { Slider } from 'bits-ui';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type Tone } from '../ui/tones.ts';
4
+ import { type FieldSize } from './field-types.ts';
5
+ interface SliderBaseProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'dir' | 'onchange' | 'children'> {
6
+ /**
7
+ * The lowest value.
8
+ * @defaultValue `0`
9
+ */
10
+ min?: number | undefined;
11
+ /**
12
+ * The highest value.
13
+ * @defaultValue `100`
14
+ */
15
+ max?: number | undefined;
16
+ /**
17
+ * Increment between values; an array gives explicit snap points.
18
+ * @defaultValue `1`
19
+ */
20
+ step?: number | readonly number[] | undefined;
21
+ /**
22
+ * Track direction.
23
+ * @defaultValue `'horizontal'`
24
+ */
25
+ orientation?: 'horizontal' | 'vertical' | undefined;
26
+ /**
27
+ * Draw a tick per step.
28
+ * @defaultValue `false`
29
+ */
30
+ ticks?: boolean | undefined;
31
+ /** Format a value for each thumb's `aria-valuetext` and the visible readout, e.g. `(v) => \`${v} cm\``. Falls back to `Intl.NumberFormat(locales)`. */
32
+ formatValue?: ((value: number) => string) | undefined;
33
+ /** BCP 47 locale(s) for the default formatter; falls back to the context's `locale()`. */
34
+ locales?: string | readonly string[] | undefined;
35
+ /**
36
+ * Show the formatted value(s) beside the track.
37
+ * @defaultValue `true`
38
+ */
39
+ showValue?: boolean | undefined;
40
+ /**
41
+ * Accessible name of each thumb in range mode, in order.
42
+ * @defaultValue `['Minimum', 'Maximum']`
43
+ */
44
+ thumbLabels?: readonly string[] | undefined;
45
+ /** Fired once per finished interaction (pointer-up, or a key press), for work that should not run per pixel. */
46
+ onCommit?: ((value: number | readonly number[]) => void) | undefined;
47
+ /** Posts the value under this name: one hidden input per thumb. */
48
+ name?: string | undefined;
49
+ /**
50
+ * Disables every thumb (and the hidden inputs, so nothing posts).
51
+ * @defaultValue `false`
52
+ */
53
+ disabled?: boolean | undefined;
54
+ /**
55
+ * daisyUI tone of the fill and thumbs; `invalid` overrides it with `error`.
56
+ * @defaultValue `'primary'`
57
+ */
58
+ tone?: Tone | undefined;
59
+ /**
60
+ * daisyUI size step (the thumb diameter; the track is half of it).
61
+ * @defaultValue `'md'`
62
+ */
63
+ size?: FieldSize | undefined;
64
+ /** Accessible name, for a slider outside a `FormField`; ignored when `labelId` is set. */
65
+ label?: string | undefined;
66
+ /** `FieldControl`: the first thumb's `id` (the others are `${id}-1`, …). Minted when omitted. */
67
+ id?: string | undefined;
68
+ /** `FieldControl`: the visible label's id; every thumb is `aria-labelledby` it. */
69
+ labelId?: string | undefined;
70
+ /**
71
+ * `FieldControl`: space-separated ids for each thumb's `aria-describedby`.
72
+ * @defaultValue `''`
73
+ */
74
+ describedBy?: string | undefined;
75
+ /**
76
+ * `FieldControl`: the field is in error — sets `aria-invalid` and the `error` tone.
77
+ * @defaultValue `false`
78
+ */
79
+ invalid?: boolean | undefined;
80
+ /** `FieldControl`: accepted for the spread; a slider always has a value, so it has no effect. */
81
+ required?: boolean | undefined;
82
+ }
83
+ type Props = (SliderBaseProps & {
84
+ /**
85
+ * One thumb, binding a `number`.
86
+ * @defaultValue `'single'`
87
+ */
88
+ type?: 'single' | undefined;
89
+ /** The value. Bindable; defaults to `min`. */
90
+ value?: number | undefined;
91
+ }) | (SliderBaseProps & {
92
+ /** One thumb per entry of `value`, binding a `number[]`. */
93
+ type: 'multiple';
94
+ /** The values, low to high. Bindable; defaults to `[min, max]`. */
95
+ value?: number[] | undefined;
96
+ });
97
+ /** A number, or a `[low, high]` range, picked from a bounded, stepped scale by dragging or with the arrow keys, on bits-ui's `Slider`. */
98
+ declare const Slider: import("svelte").Component<Props, {}, "value">;
99
+ type Slider = ReturnType<typeof Slider>;
100
+ export default Slider;
@@ -0,0 +1,256 @@
1
+ <!-- @component
2
+ An open-ended list of short strings the reader types — tags, aliases, keywords — each a removable chip, with optional suggestions.
3
+ -->
4
+ <!--
5
+ TagsInput — type and press Enter (or a delimiter such as `,`) to commit a
6
+ tag; each tag renders as a chip with a remove button; Backspace in an empty
7
+ input reaches back into the tags. Optional suggestions, from a static list
8
+ or an async search, appear in a listbox.
9
+
10
+ Hand-rolled, and why (AGENTS.md: bits-ui owns interaction): bits-ui 2.20
11
+ has no tags input, and the token list plus one text input is a small
12
+ keyboard model, pinned key by key in `tests/fields/TagsInput.test.ts`. The
13
+ SUGGESTION half is not hand-rolled: with `suggestions` or `onSearch` the
14
+ text input is bits-ui's `Combobox` input, and the async lifecycle
15
+ (debounce, latest-wins, stale-while-revalidate) is the one `Combobox`
16
+ uses, `latest-search.svelte.ts`. Without either, no bits-ui is rendered.
17
+
18
+ ARIA contract (a composition — there is no single APG pattern): a
19
+ `role="list"` of tags, each `role="listitem"` holding its text and a
20
+ remove `<button>` named by `removeLabel(tag)`; then the text input, which
21
+ carries the `FieldControl` id (so `FormField`'s `<label for>` names it),
22
+ `aria-describedby` and `aria-invalid`, and is a combobox
23
+ (`aria-expanded`, `aria-controls`, `aria-activedescendant`) when
24
+ suggestions are on. The tag list is not folded into the description: adds
25
+ and removes reach assistive technology through a polite live region
26
+ (`addedMessage` / `removedMessage`).
27
+
28
+ Keyboard, in the text input: Enter or a delimiter commits the text (or, with
29
+ a suggestion highlighted, that suggestion; Enter on an empty input is left
30
+ alone so a wrapping form can submit); Backspace with empty text focuses the
31
+ last tag's remove button, and a second Backspace removes it; Arrow Left with
32
+ the caret at the start does the same move; Arrow Up / Down walk the
33
+ suggestions; a paste containing delimiters is split into one tag each. On a
34
+ remove button: Arrow Left / Right move between tags (Right past the last
35
+ returns to the input); Delete / Backspace / Enter / Space remove the tag.
36
+ Focus is never lost after a removal: it lands on the tag that took its
37
+ place, else the one before, else the input.
38
+
39
+ `required` means "at least one committed tag": while there are none, the
40
+ text input carries a custom validity error (`requiredMessage`), so the
41
+ browser blocks submission even when uncommitted text sits in the input;
42
+ `aria-required` is set throughout. `max` disables the input at the cap. `name`
43
+ posts one hidden input per tag.
44
+ -->
45
+ <script lang="ts">import { X } from "@lucide/svelte";
46
+ import { Combobox } from "bits-ui";
47
+ import { tick } from "svelte";
48
+ import { inputSize } from "./field-types.js";
49
+ import { LatestSearch } from "./latest-search.svelte.js";
50
+ let { values = $bindable([]), name, delimiters = [","], normalise = (text) => text.trim() === "" ? undefined : text.trim(), allowDuplicates = false, max, suggestions, onSearch, debounceMs = 150, placeholder = "Add a tag", removeLabel = (tag) => `Remove ${tag}`, addedMessage = (tag) => `${tag} added`, removedMessage = (tag) => `${tag} removed`, tag: tagBody, disabled = false, size = "md", id: idProp, labelId: _labelId, describedBy = "", invalid = false, required = false, requiredMessage = "Add at least one tag.", class: className, ...rest } = $props();
51
+ const uid = $props.id();
52
+ const inputId = $derived(idProp ?? uid);
53
+ let text = $state("");
54
+ let announcement = $state("");
55
+ let open = $state(false);
56
+ /** The suggestion bits-ui currently has under the cursor, if any. */
57
+ let highlighted = $state();
58
+ let input = $state(null);
59
+ const removeButtons = $state([]);
60
+ const searcher = new LatestSearch(() => onSearch, () => debounceMs);
61
+ $effect(() => () => searcher.dispose());
62
+ const suggesting = $derived(suggestions !== undefined || onSearch !== undefined);
63
+ const full = $derived(max !== undefined && values.length >= max);
64
+ const present = $derived(new Set(values.map((value) => value.toLowerCase())));
65
+ const visible = $derived.by(() => {
66
+ const pool = onSearch === undefined ? suggestions ?? [] : searcher.results;
67
+ const needle = text.trim().toLowerCase();
68
+ return pool.filter((entry) => (allowDuplicates || !present.has(entry.toLowerCase())) && (onSearch !== undefined || entry.toLowerCase().includes(needle)));
69
+ });
70
+ /** What both the plain and the combobox input carry. */
71
+ const inputProps = $derived({
72
+ id: inputId,
73
+ placeholder,
74
+ autocomplete: "off",
75
+ disabled: disabled || full,
76
+ "aria-required": required ? "true" : undefined,
77
+ "aria-describedby": describedBy === "" ? undefined : describedBy,
78
+ "aria-invalid": invalid ? "true" : undefined,
79
+ class: "min-w-24 grow"
80
+ });
81
+ // Validity follows the committed tags, not the text: typed-but-uncommitted
82
+ // text must not satisfy `required`.
83
+ $effect(() => {
84
+ input?.setCustomValidity(required && values.length === 0 ? requiredMessage : "");
85
+ });
86
+ /** Add `raw` as a tag if `normalise`, the duplicate rule and `max` allow it. */
87
+ function add(raw) {
88
+ if (full) return false;
89
+ const candidate = normalise(raw);
90
+ if (candidate === undefined) return false;
91
+ if (!allowDuplicates && present.has(candidate.toLowerCase())) return false;
92
+ values = [...values, candidate];
93
+ announcement = addedMessage(candidate);
94
+ return true;
95
+ }
96
+ function clearText() {
97
+ text = "";
98
+ if (input !== null) input.value = "";
99
+ searcher.clear();
100
+ open = false;
101
+ }
102
+ function commitText() {
103
+ if (add(text)) clearText();
104
+ }
105
+ async function remove(index) {
106
+ const removed = values[index];
107
+ if (removed === undefined) return;
108
+ values = values.filter((_, at) => at !== index);
109
+ announcement = removedMessage(removed);
110
+ await tick();
111
+ (removeButtons[index] ?? removeButtons[index - 1] ?? input)?.focus();
112
+ }
113
+ function focusTag(index) {
114
+ (removeButtons[index] ?? input)?.focus();
115
+ }
116
+ function typed(event) {
117
+ text = event.currentTarget.value;
118
+ if (!suggesting) return;
119
+ open = text.trim() !== "";
120
+ if (onSearch === undefined) return;
121
+ if (open) searcher.schedule(text);
122
+ else searcher.clear();
123
+ }
124
+ /** Enter commits the text, unless a suggestion is highlighted (bits-ui's) or there is no text (the form's). */
125
+ function enterKey(event) {
126
+ if (open && highlighted !== undefined || text.trim() === "") return;
127
+ event.preventDefault();
128
+ commitText();
129
+ }
130
+ /** Backspace in an empty input, or Arrow Left at its start, moves into the tags. */
131
+ function reachesTags(event) {
132
+ if (values.length === 0) return false;
133
+ if (event.key === "Backspace") return text === "";
134
+ const target = event.currentTarget;
135
+ return event.key === "ArrowLeft" && target.selectionStart === 0 && target.selectionEnd === 0;
136
+ }
137
+ function inputKeydown(event) {
138
+ // An IME uses Enter (and may use the delimiter keys) to confirm a candidate.
139
+ if (event.isComposing) return;
140
+ if (event.key === "Enter") {
141
+ enterKey(event);
142
+ } else if (delimiters.includes(event.key)) {
143
+ event.preventDefault();
144
+ commitText();
145
+ } else if (reachesTags(event)) {
146
+ event.preventDefault();
147
+ focusTag(values.length - 1);
148
+ }
149
+ }
150
+ function paste(event) {
151
+ const pasted = event.clipboardData?.getData("text/plain") ?? "";
152
+ const separators = [...delimiters, "\n"];
153
+ if (!separators.some((separator) => pasted.includes(separator))) return;
154
+ event.preventDefault();
155
+ // Splice the paste into the caret / selection, as a native paste would, then split.
156
+ const target = event.currentTarget;
157
+ const start = target.selectionStart ?? text.length;
158
+ const end = target.selectionEnd ?? text.length;
159
+ const pieces = separators.reduce((parts, separator) => parts.flatMap((part) => part.split(separator)), [`${text.slice(0, start)}${pasted}${text.slice(end)}`]);
160
+ for (const piece of pieces) add(piece);
161
+ clearText();
162
+ }
163
+ const removeKeys = new Set(["Delete", "Backspace"]);
164
+ function tagKeydown(event, index) {
165
+ if (removeKeys.has(event.key)) {
166
+ event.preventDefault();
167
+ void remove(index);
168
+ } else if (event.key === "ArrowLeft") {
169
+ event.preventDefault();
170
+ focusTag(Math.max(0, index - 1));
171
+ } else if (event.key === "ArrowRight") {
172
+ event.preventDefault();
173
+ focusTag(index + 1);
174
+ }
175
+ }
176
+ /** A suggestion was chosen. bits-ui writes the item's label into the input after this returns, so clear on the next tick. */
177
+ function pick(next) {
178
+ if (next === "") return;
179
+ add(next);
180
+ void tick().then(clearText);
181
+ }
182
+ </script>
183
+
184
+ <div
185
+ {...rest}
186
+ class={[
187
+ 'input h-auto min-h-(--size) w-full flex-wrap items-center gap-1 py-1',
188
+ inputSize[size],
189
+ invalid && 'input-error',
190
+ className,
191
+ ]}
192
+ >
193
+ {#if values.length > 0}
194
+ <ul role="list" class="flex flex-wrap gap-1">
195
+ {#each values as value, index (`${index}:${value}`)}
196
+ <li class="badge badge-soft gap-1 pe-0">
197
+ {#if tagBody}{@render tagBody(value)}{:else}<span>{value}</span>{/if}
198
+ <button
199
+ bind:this={removeButtons[index]}
200
+ type="button"
201
+ {disabled}
202
+ class="btn btn-circle btn-ghost btn-xs"
203
+ onclick={() => void remove(index)}
204
+ onkeydown={(event) => tagKeydown(event, index)}
205
+ >
206
+ <X class="size-3" aria-hidden="true" />
207
+ <span class="sr-only">{removeLabel(value)}</span>
208
+ </button>
209
+ </li>
210
+ {/each}
211
+ </ul>
212
+ {/if}
213
+
214
+ {#if suggesting}
215
+ <Combobox.Root
216
+ type="single"
217
+ bind:open
218
+ bind:value={() => '', pick}
219
+ inputValue={text}
220
+ {disabled}
221
+ >
222
+ <Combobox.Input {...inputProps} bind:ref={input} oninput={typed} onkeydown={inputKeydown} onpaste={paste} />
223
+ <Combobox.Portal>
224
+ <Combobox.Content
225
+ sideOffset={4}
226
+ class="menu bg-base-100 rounded-box border-base-300 z-50 max-h-72 min-w-48 overflow-auto border p-2 shadow-md"
227
+ >
228
+ <Combobox.Viewport>
229
+ {#each visible as entry (entry)}
230
+ <Combobox.Item
231
+ value={entry}
232
+ label={entry}
233
+ onHighlight={() => (highlighted = entry)}
234
+ onUnhighlight={() => {
235
+ if (highlighted === entry) highlighted = undefined;
236
+ }}
237
+ class="rounded-field data-highlighted:bg-base-200 cursor-pointer px-3 py-2"
238
+ >
239
+ {entry}
240
+ </Combobox.Item>
241
+ {/each}
242
+ </Combobox.Viewport>
243
+ </Combobox.Content>
244
+ </Combobox.Portal>
245
+ </Combobox.Root>
246
+ {:else}
247
+ <input {...inputProps} bind:this={input} type="text" oninput={typed} onkeydown={inputKeydown} onpaste={paste} />
248
+ {/if}
249
+
250
+ {#if name !== undefined}
251
+ {#each values as value, index (`${index}:${value}`)}
252
+ <input type="hidden" {name} {value} {disabled} />
253
+ {/each}
254
+ {/if}
255
+ <p class="sr-only" aria-live="polite">{announcement}</p>
256
+ </div>
@@ -0,0 +1,102 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type FieldSize } from './field-types.ts';
4
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'id'> {
5
+ /**
6
+ * The tags. Bindable.
7
+ * @defaultValue `[]`
8
+ */
9
+ values?: string[] | undefined;
10
+ /** Posts each tag under this name, one hidden input per tag. */
11
+ name?: string | undefined;
12
+ /**
13
+ * Characters that commit the current text as a tag, besides Enter; a paste is split on them too.
14
+ * @defaultValue `[',']`
15
+ */
16
+ delimiters?: readonly string[] | undefined;
17
+ /**
18
+ * Normalise or refuse a candidate; return `undefined` to refuse it.
19
+ * @defaultValue trims, and refuses an empty string
20
+ */
21
+ normalise?: ((text: string) => string | undefined) | undefined;
22
+ /**
23
+ * Allow a tag already present (compared case-insensitively after `normalise`).
24
+ * @defaultValue `false`
25
+ */
26
+ allowDuplicates?: boolean | undefined;
27
+ /** Cap on the number of tags; the input disables at the cap. */
28
+ max?: number | undefined;
29
+ /** Static suggestions, filtered by the typed text. */
30
+ suggestions?: readonly string[] | undefined;
31
+ /** Async suggestions, debounced and latest-wins; `signal` aborts a superseded search. */
32
+ onSearch?: ((query: string, signal: AbortSignal) => Promise<readonly string[]>) | undefined;
33
+ /**
34
+ * Quiet time before `onSearch` runs.
35
+ * @defaultValue `150`
36
+ */
37
+ debounceMs?: number | undefined;
38
+ /**
39
+ * Placeholder in the text input.
40
+ * @defaultValue `'Add a tag'`
41
+ */
42
+ placeholder?: string | undefined;
43
+ /**
44
+ * Accessible name of a tag's remove button.
45
+ * @defaultValue `(tag) => \`Remove ${tag}\``
46
+ */
47
+ removeLabel?: ((tag: string) => string) | undefined;
48
+ /**
49
+ * Live-region text after a tag is added.
50
+ * @defaultValue `(tag) => \`${tag} added\``
51
+ */
52
+ addedMessage?: ((tag: string) => string) | undefined;
53
+ /**
54
+ * Live-region text after a tag is removed.
55
+ * @defaultValue `(tag) => \`${tag} removed\``
56
+ */
57
+ removedMessage?: ((tag: string) => string) | undefined;
58
+ /** Renders one tag's body; defaults to its text. */
59
+ tag?: Snippet<[string]> | undefined;
60
+ /**
61
+ * Disables the input and every remove button.
62
+ * @defaultValue `false`
63
+ */
64
+ disabled?: boolean | undefined;
65
+ /**
66
+ * daisyUI size step.
67
+ * @defaultValue `'md'`
68
+ */
69
+ size?: FieldSize | undefined;
70
+ /** `FieldControl`: the text input's `id`, which `FormField`'s label points at. Minted when omitted. */
71
+ id?: string | undefined;
72
+ /**
73
+ * `FieldControl`: the visible label's id. Not used — the text input is
74
+ * labelable, so the `FormField`'s `<label for>` already names it; taken
75
+ * here so a spread `FieldControl` does not leak it as an attribute.
76
+ */
77
+ labelId?: string | undefined;
78
+ /**
79
+ * `FieldControl`: space-separated ids for the input's `aria-describedby`.
80
+ * @defaultValue `''`
81
+ */
82
+ describedBy?: string | undefined;
83
+ /**
84
+ * `FieldControl`: the field is in error — sets `aria-invalid` and `input-error`.
85
+ * @defaultValue `false`
86
+ */
87
+ invalid?: boolean | undefined;
88
+ /**
89
+ * `FieldControl`: at least one tag is required.
90
+ * @defaultValue `false`
91
+ */
92
+ required?: boolean | undefined;
93
+ /**
94
+ * The validation message while `required` and no tag has been committed.
95
+ * @defaultValue `'Add at least one tag.'`
96
+ */
97
+ requiredMessage?: string | undefined;
98
+ }
99
+ /** An open-ended list of short strings the reader types — tags, aliases, keywords — each a removable chip, with optional suggestions. */
100
+ declare const TagsInput: import("svelte").Component<Props, {}, "values">;
101
+ type TagsInput = ReturnType<typeof TagsInput>;
102
+ export default TagsInput;