@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,15 @@
1
+ /**
2
+ * `@thomasfosterau/components/canvas` — a low-level toolkit for vector-editing
3
+ * surfaces: pan, zoom, a grid, and pointer gestures reported in canvas
4
+ * coordinates. The application defines its element types, its tools and its
5
+ * renderer.
6
+ *
7
+ * A separate entry point so it never sits on the always-loaded path of a
8
+ * consumer that does not use it.
9
+ *
10
+ * @module
11
+ */
12
+ export { default as VectorCanvas } from './VectorCanvas.svelte';
13
+ export { applyTransform, diffTransform, emptyHistory, HISTORY_LIMIT, invertTransform, recordEdit, redo, undo, type History, type HistoryStep, } from './history.ts';
14
+ export type { CanvasElement, ElementId, GridConfig, HistoryEntry, Point, Rect, Transform, VectorDocument, Viewport, } from './types.ts';
15
+ export { boundingBox, canvasToScreen, distance, generateId, panBy, pointInRect, pointNearLine, rotatePoint, screenToCanvas, snapToGrid, sortByZIndex, zoomAbout, } from './utils.ts';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@thomasfosterau/components/canvas` — a low-level toolkit for vector-editing
3
+ * surfaces: pan, zoom, a grid, and pointer gestures reported in canvas
4
+ * coordinates. The application defines its element types, its tools and its
5
+ * renderer.
6
+ *
7
+ * A separate entry point so it never sits on the always-loaded path of a
8
+ * consumer that does not use it.
9
+ *
10
+ * @module
11
+ */
12
+ export { default as VectorCanvas } from './VectorCanvas.svelte';
13
+ export { applyTransform, diffTransform, emptyHistory, HISTORY_LIMIT, invertTransform, recordEdit, redo, undo, } from "./history.js";
14
+ export { boundingBox, canvasToScreen, distance, generateId, panBy, pointInRect, pointNearLine, rotatePoint, screenToCanvas, snapToGrid, sortByZIndex, zoomAbout, } from "./utils.js";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * VectorCanvas core types — the low-level primitives a vector-editing surface
3
+ * is built from (a floor plan, a seating chart, a garden's bed layout).
4
+ *
5
+ * Shaped after ProseMirror's split: a plain-data document, a transform
6
+ * describing a change to it, and a view that renders the document and reports
7
+ * interactions. The application defines its own element types by extending
8
+ * {@link CanvasElement} and supplies the renderer; nothing here knows what a
9
+ * bed or a table is.
10
+ *
11
+ * @module @thomasfosterau/components/canvas/types
12
+ */
13
+ /** Stable identity for a canvas element. */
14
+ export type ElementId = string;
15
+ /** A point in canvas coordinates — the document's own space, not the screen's. */
16
+ export interface Point {
17
+ readonly x: number;
18
+ readonly y: number;
19
+ }
20
+ /** What every element carries, whatever the application adds on top. */
21
+ export interface CanvasElement {
22
+ readonly id: ElementId;
23
+ /** The application's discriminator; the renderer switches on it. */
24
+ readonly type: string;
25
+ readonly position: Point;
26
+ /** Degrees, clockwise. */
27
+ readonly rotation: number;
28
+ /** Paint order; higher draws later. */
29
+ readonly zIndex: number;
30
+ readonly strokeColor: string;
31
+ readonly strokeWidth: number;
32
+ readonly fillColor: string;
33
+ /** 0–1. */
34
+ readonly opacity: number;
35
+ /** Whether edits are refused; the view still renders it. */
36
+ readonly locked: boolean;
37
+ readonly visible?: boolean | undefined;
38
+ readonly label?: string | undefined;
39
+ /** The application's own per-element data. */
40
+ readonly metadata?: Readonly<Record<string, unknown>> | undefined;
41
+ }
42
+ /** Pan and zoom — where the document sits under the viewport. */
43
+ export interface Viewport {
44
+ readonly offsetX: number;
45
+ readonly offsetY: number;
46
+ /** 1 is 100%. */
47
+ readonly zoom: number;
48
+ }
49
+ /** The background grid, and whether it captures positions. */
50
+ export interface GridConfig {
51
+ readonly visible: boolean;
52
+ /** Cell size in canvas units. */
53
+ readonly size: number;
54
+ readonly snap: boolean;
55
+ /** A CSS colour — a theme token in practice. */
56
+ readonly color: string;
57
+ }
58
+ /** The whole editable document. */
59
+ export interface VectorDocument<T extends CanvasElement = CanvasElement> {
60
+ /** Schema version, so a stored document can be migrated. */
61
+ readonly version: string;
62
+ readonly metadata: Readonly<Record<string, unknown>>;
63
+ readonly viewport: Viewport;
64
+ readonly grid: GridConfig;
65
+ readonly elements: readonly T[];
66
+ }
67
+ /**
68
+ * A change to the document, as data. Carrying the previous elements alongside
69
+ * the new ones is what makes a transform invertible — undo is the same
70
+ * structure read backwards, rather than a second code path.
71
+ */
72
+ export interface Transform<T extends CanvasElement = CanvasElement> {
73
+ readonly type: 'add' | 'update' | 'delete' | 'batch';
74
+ readonly elementIds: readonly ElementId[];
75
+ readonly elements?: readonly T[] | undefined;
76
+ readonly previousElements?: readonly T[] | undefined;
77
+ }
78
+ /** One entry of an undo stack. */
79
+ export interface HistoryEntry<T extends CanvasElement = CanvasElement> {
80
+ readonly transform: Transform<T>;
81
+ /** Epoch milliseconds. */
82
+ readonly timestamp: number;
83
+ readonly description?: string | undefined;
84
+ }
85
+ /** An axis-aligned box in canvas coordinates. */
86
+ export interface Rect {
87
+ readonly x: number;
88
+ readonly y: number;
89
+ readonly width: number;
90
+ readonly height: number;
91
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * VectorCanvas core types — the low-level primitives a vector-editing surface
3
+ * is built from (a floor plan, a seating chart, a garden's bed layout).
4
+ *
5
+ * Shaped after ProseMirror's split: a plain-data document, a transform
6
+ * describing a change to it, and a view that renders the document and reports
7
+ * interactions. The application defines its own element types by extending
8
+ * {@link CanvasElement} and supplies the renderer; nothing here knows what a
9
+ * bed or a table is.
10
+ *
11
+ * @module @thomasfosterau/components/canvas/types
12
+ */
13
+ export {};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * VectorCanvas geometry — coordinate transforms and hit tests, as pure
3
+ * functions over the types in `./types.ts`.
4
+ *
5
+ * Pure and free of the component on purpose: these are what a unit test can
6
+ * pin without mounting anything, and what an application's own tools (a
7
+ * marquee select, a snap-to-neighbour) reuse rather than re-derive.
8
+ *
9
+ * @module @thomasfosterau/components/canvas/utils
10
+ */
11
+ import type { CanvasElement, ElementId, Point, Rect, Viewport } from './types.ts';
12
+ /**
13
+ * A fresh element id.
14
+ *
15
+ * `crypto.randomUUID` is present in every browser this library supports and in
16
+ * workerd; a `Date.now()` + `Math.random()` fallback for environments that are
17
+ * no longer targets would be worse than none — a duplicate id silently merges
18
+ * two elements.
19
+ */
20
+ export declare const generateId: () => ElementId;
21
+ /** Screen (viewport-relative) coordinates → canvas coordinates. */
22
+ export declare const screenToCanvas: (point: Point, viewport: Viewport) => Point;
23
+ /** Canvas coordinates → screen (viewport-relative) coordinates. */
24
+ export declare const canvasToScreen: (point: Point, viewport: Viewport) => Point;
25
+ /** The nearest grid intersection. */
26
+ export declare const snapToGrid: (point: Point, gridSize: number) => Point;
27
+ /** Euclidean distance between two points. */
28
+ export declare const distance: (from: Point, to: Point) => number;
29
+ /** Whether a point falls inside a box (edges included). */
30
+ export declare const pointInRect: (point: Point, rect: Rect) => boolean;
31
+ /**
32
+ * Whether a point is within `threshold` of a line SEGMENT — the projection is
33
+ * clamped to the segment, so a point far past an endpoint is not "near the
34
+ * line" the way it would be for an infinite one.
35
+ */
36
+ export declare const pointNearLine: (point: Point, start: Point, end: Point, threshold?: number) => boolean;
37
+ /** Paint order: lowest `zIndex` first. Returns a new array. */
38
+ export declare const sortByZIndex: <T extends CanvasElement>(elements: readonly T[]) => readonly T[];
39
+ /**
40
+ * The box enclosing every element's ORIGIN — not its drawn extent, which only
41
+ * the application's renderer knows. Enough for "centre the view on the
42
+ * selection"; not enough for "fit the selection exactly".
43
+ *
44
+ * Empty input has no box, which is why the result is an array of none or one
45
+ * rather than a nullable `Rect`.
46
+ */
47
+ export declare const boundingBox: (elements: readonly CanvasElement[]) => readonly Rect[];
48
+ /** A point rotated `degrees` clockwise about `centre`. */
49
+ export declare const rotatePoint: (point: Point, centre: Point, degrees: number) => Point;
50
+ /**
51
+ * The viewport after a pan of `dx`/`dy` SCREEN pixels.
52
+ *
53
+ * Screen pixels, not canvas units, and so not divided by the zoom: panning
54
+ * moves the document one-for-one under the cursor, which is what makes a drag
55
+ * feel attached to the thing being dragged at every zoom level.
56
+ */
57
+ export declare const panBy: (viewport: Viewport, dx: number, dy: number) => Viewport;
58
+ /**
59
+ * The viewport after zooming by `factor` about a screen point.
60
+ *
61
+ * About the POINTER, not the origin: the point under the cursor is the one the
62
+ * reader is looking at, and it stays where it is. Zooming about the origin
63
+ * instead sends whatever they were examining off the edge, which is the single
64
+ * most disorienting thing a canvas can do.
65
+ */
66
+ export declare const zoomAbout: (viewport: Viewport, pointer: Point, factor: number) => Viewport;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * VectorCanvas geometry — coordinate transforms and hit tests, as pure
3
+ * functions over the types in `./types.ts`.
4
+ *
5
+ * Pure and free of the component on purpose: these are what a unit test can
6
+ * pin without mounting anything, and what an application's own tools (a
7
+ * marquee select, a snap-to-neighbour) reuse rather than re-derive.
8
+ *
9
+ * @module @thomasfosterau/components/canvas/utils
10
+ */
11
+ /**
12
+ * A fresh element id.
13
+ *
14
+ * `crypto.randomUUID` is present in every browser this library supports and in
15
+ * workerd; a `Date.now()` + `Math.random()` fallback for environments that are
16
+ * no longer targets would be worse than none — a duplicate id silently merges
17
+ * two elements.
18
+ */
19
+ export const generateId = () => crypto.randomUUID();
20
+ /** Screen (viewport-relative) coordinates → canvas coordinates. */
21
+ export const screenToCanvas = (point, viewport) => ({
22
+ x: (point.x - viewport.offsetX) / viewport.zoom,
23
+ y: (point.y - viewport.offsetY) / viewport.zoom,
24
+ });
25
+ /** Canvas coordinates → screen (viewport-relative) coordinates. */
26
+ export const canvasToScreen = (point, viewport) => ({
27
+ x: point.x * viewport.zoom + viewport.offsetX,
28
+ y: point.y * viewport.zoom + viewport.offsetY,
29
+ });
30
+ /** The nearest grid intersection. */
31
+ export const snapToGrid = (point, gridSize) => ({
32
+ x: Math.round(point.x / gridSize) * gridSize,
33
+ y: Math.round(point.y / gridSize) * gridSize,
34
+ });
35
+ /** Euclidean distance between two points. */
36
+ export const distance = (from, to) => Math.hypot(to.x - from.x, to.y - from.y);
37
+ /** Whether a point falls inside a box (edges included). */
38
+ export const pointInRect = (point, rect) => point.x >= rect.x &&
39
+ point.x <= rect.x + rect.width &&
40
+ point.y >= rect.y &&
41
+ point.y <= rect.y + rect.height;
42
+ /**
43
+ * Whether a point is within `threshold` of a line SEGMENT — the projection is
44
+ * clamped to the segment, so a point far past an endpoint is not "near the
45
+ * line" the way it would be for an infinite one.
46
+ */
47
+ export const pointNearLine = (point, start, end, threshold = 5) => {
48
+ const dx = end.x - start.x;
49
+ const dy = end.y - start.y;
50
+ const lengthSquared = dx * dx + dy * dy;
51
+ if (lengthSquared === 0)
52
+ return distance(point, start) <= threshold;
53
+ const t = Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSquared));
54
+ return distance(point, { x: start.x + t * dx, y: start.y + t * dy }) <= threshold;
55
+ };
56
+ /** Paint order: lowest `zIndex` first. Returns a new array. */
57
+ export const sortByZIndex = (elements) => elements.toSorted((left, right) => left.zIndex - right.zIndex);
58
+ /**
59
+ * The box enclosing every element's ORIGIN — not its drawn extent, which only
60
+ * the application's renderer knows. Enough for "centre the view on the
61
+ * selection"; not enough for "fit the selection exactly".
62
+ *
63
+ * Empty input has no box, which is why the result is an array of none or one
64
+ * rather than a nullable `Rect`.
65
+ */
66
+ export const boundingBox = (elements) => {
67
+ if (elements.length === 0)
68
+ return [];
69
+ const xs = elements.map((element) => element.position.x);
70
+ const ys = elements.map((element) => element.position.y);
71
+ const minX = Math.min(...xs);
72
+ const minY = Math.min(...ys);
73
+ return [{ x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY }];
74
+ };
75
+ /** A point rotated `degrees` clockwise about `centre`. */
76
+ export const rotatePoint = (point, centre, degrees) => {
77
+ const radians = (degrees * Math.PI) / 180;
78
+ const cos = Math.cos(radians);
79
+ const sin = Math.sin(radians);
80
+ const dx = point.x - centre.x;
81
+ const dy = point.y - centre.y;
82
+ return { x: centre.x + dx * cos - dy * sin, y: centre.y + dx * sin + dy * cos };
83
+ };
84
+ /**
85
+ * The viewport after a pan of `dx`/`dy` SCREEN pixels.
86
+ *
87
+ * Screen pixels, not canvas units, and so not divided by the zoom: panning
88
+ * moves the document one-for-one under the cursor, which is what makes a drag
89
+ * feel attached to the thing being dragged at every zoom level.
90
+ */
91
+ export const panBy = (viewport, dx, dy) => ({
92
+ ...viewport,
93
+ offsetX: viewport.offsetX + dx,
94
+ offsetY: viewport.offsetY + dy,
95
+ });
96
+ /** The zoom bounds; below the first the grid is useless, above the second the marks are. */
97
+ const ZOOM_RANGE = { min: 0.1, max: 10 };
98
+ /**
99
+ * The viewport after zooming by `factor` about a screen point.
100
+ *
101
+ * About the POINTER, not the origin: the point under the cursor is the one the
102
+ * reader is looking at, and it stays where it is. Zooming about the origin
103
+ * instead sends whatever they were examining off the edge, which is the single
104
+ * most disorienting thing a canvas can do.
105
+ */
106
+ export const zoomAbout = (viewport, pointer, factor) => {
107
+ const zoom = Math.max(ZOOM_RANGE.min, Math.min(ZOOM_RANGE.max, viewport.zoom * factor));
108
+ const anchor = screenToCanvas(pointer, viewport);
109
+ return {
110
+ zoom,
111
+ offsetX: pointer.x - anchor.x * zoom,
112
+ offsetY: pointer.y - anchor.y * zoom,
113
+ };
114
+ };
@@ -0,0 +1,300 @@
1
+ <!-- @component
2
+ A line, area or bar chart drawn as plain SVG from a declarative series spec, with an always-present `sr-only` data table.
3
+ -->
4
+ <!--
5
+ Chart — a line, area or bar chart drawn as plain SVG from a declarative
6
+ series spec.
7
+
8
+ It takes no charting dependency, deliberately: scales over a bounded category
9
+ axis are arithmetic, and a charting library behind a package's entry point is
10
+ not worth the bytes when the whole of what it would supply is four functions.
11
+
12
+ Rules it enforces rather than leaves to the caller, because a chart that gets
13
+ them wrong is a chart that misleads:
14
+
15
+ - **One y axis.** Two measures of different scale are two charts. There is no
16
+ prop for a second scale, deliberately.
17
+ - **The zero line is drawn.** A bar chart whose baseline is not zero
18
+ exaggerates every difference on it; this one starts its domain at zero (or
19
+ at the minimum, when values go negative) and says so on the axis.
20
+ - **Identity is never colour alone.** Two or more series always get a legend;
21
+ four or fewer also get a direct label on their last point. The series colour
22
+ is on the mark, never on the text beside it.
23
+ - **The numbers are readable without seeing.** A `<table>` of the same data is
24
+ always in the DOM — visually hidden by default, revealed with `showTable` —
25
+ so a reader who cannot resolve the colours still has the figures.
26
+
27
+ Colour comes from the theme's four validated chart slots
28
+ (`--color-chart-1…4`, see `theme.css`). Slots are assigned by series ORDER and
29
+ never cycled: a fifth series is a design problem — fold it into an "other"
30
+ series, or facet — not a generated hue, so it draws in the neutral slot and
31
+ the legend says which one it is.
32
+
33
+ ## ARIA contract
34
+
35
+ The `<svg>` is `role="img"` named by `title`, so a screen reader is told it is
36
+ one picture rather than walked through several hundred unlabelled shapes. The
37
+ figures themselves are reached through the always-present `<table>`, captioned
38
+ by `tableLabel` (which defaults to `title`) with the categories as row headers
39
+ and the series as column headers. The legend, when there is one, is a list
40
+ named by `legendLabel`. Every one of those strings is a prop with an English
41
+ default; nothing here reads copy from a context.
42
+
43
+ Hand-rolled rather than built on a bits-ui primitive because bits-ui has no
44
+ chart: this is geometry and a table, not a focus-managed widget.
45
+ -->
46
+ <script lang="ts">let { series, kind = "line", title, description = "", height = 240, showTable = false, legendLabel = "Series", tableLabel = "", categoryLabel = "", formatValue = (value) => String(value), class: className } = $props();
47
+ /** The plot's own coordinate system; the SVG scales to its container. */
48
+ const WIDTH = 640;
49
+ const PADDING = {
50
+ top: 16,
51
+ right: 16,
52
+ bottom: 32,
53
+ left: 48
54
+ };
55
+ const caption = $derived(tableLabel === "" ? title : tableLabel);
56
+ const categories = $derived(series[0]?.points.map((point) => point.label) ?? []);
57
+ const values = $derived(series.flatMap((entry) => entry.points.map((point) => point.value)));
58
+ // The domain starts at zero unless the data goes below it. Growing the top by
59
+ // a twentieth keeps the highest mark off the frame without inventing a scale.
60
+ const max = $derived(Math.max(0, ...values) * 1.05 || 1);
61
+ const min = $derived(Math.min(0, ...values));
62
+ const plotWidth = WIDTH - PADDING.left - PADDING.right;
63
+ const plotHeight = $derived(height - PADDING.top - PADDING.bottom);
64
+ const y = $derived((value) => {
65
+ const span = max - min || 1;
66
+ return PADDING.top + plotHeight - (value - min) / span * plotHeight;
67
+ });
68
+ /** Category centres: a band per category, the mark on its midpoint. */
69
+ const x = $derived((index) => {
70
+ const band = plotWidth / Math.max(categories.length, 1);
71
+ return PADDING.left + band * index + band / 2;
72
+ });
73
+ /** Four validated slots, then the neutral one — never a generated hue. */
74
+ const colour = (index) => index < 4 ? `var(--color-chart-${index + 1})` : "var(--color-neutral)";
75
+ const path = $derived((entry) => entry.points.map((point, index) => `${index === 0 ? "M" : "L"}${x(index)} ${y(point.value)}`).join(" "));
76
+ const areaPath = $derived((entry) => entry.points.length === 0 ? "" : `${path(entry)} L${x(entry.points.length - 1)} ${y(min)} L${x(0)} ${y(min)} Z`);
77
+ /** Five gridlines, the bottom one being the zero (or minimum) rule. */
78
+ const ticks = $derived(Array.from({ length: 5 }, (_unused, step) => min + (max - min) / 4 * step).reverse());
79
+ // Bars share a category band: each series takes an equal slice of it, less a
80
+ // 2px gap so adjacent fills never touch and read as one block.
81
+ const barBand = $derived(plotWidth / Math.max(categories.length, 1));
82
+ const barWidth = $derived(Math.max(2, barBand * .7 / Math.max(series.length, 1) - 2));
83
+ /** The category the pointer is over; -1 is "nowhere". */
84
+ let hovered = $state(-1);
85
+ let plot = $state();
86
+ const track = (event) => {
87
+ if (plot === undefined || categories.length === 0) return;
88
+ const box = plot.getBoundingClientRect();
89
+ // The pointer is in CSS pixels and the plot in its own units; scale across
90
+ // rather than assuming they match, because the SVG is fluid.
91
+ const local = (event.clientX - box.left) / box.width * WIDTH;
92
+ const band = plotWidth / categories.length;
93
+ const index = Math.floor((local - PADDING.left) / band);
94
+ hovered = index >= 0 && index < categories.length ? index : -1;
95
+ };
96
+ export {};
97
+ </script>
98
+
99
+ <figure class={['not-prose space-y-2', className]}>
100
+ <figcaption class="space-y-1">
101
+ <p class="text-base font-semibold">{title}</p>
102
+ {#if description !== ''}
103
+ <p class="text-base-content/70 text-sm">{description}</p>
104
+ {/if}
105
+ </figcaption>
106
+
107
+ {#if series.length > 1}
108
+ <ul
109
+ aria-label={legendLabel}
110
+ class="text-base-content/80 flex flex-wrap gap-x-4 gap-y-1 text-sm"
111
+ >
112
+ {#each series as entry, index (entry.key)}
113
+ <li class="flex items-center gap-1.5">
114
+ <span
115
+ class="inline-block size-2.5 rounded-full"
116
+ style:background-color={colour(index)}
117
+ aria-hidden="true"
118
+ ></span>
119
+ {entry.label}
120
+ </li>
121
+ {/each}
122
+ </ul>
123
+ {/if}
124
+
125
+ <div class="relative">
126
+ <svg
127
+ bind:this={plot}
128
+ viewBox={`0 0 ${WIDTH} ${height}`}
129
+ class="h-auto w-full"
130
+ role="img"
131
+ aria-label={title}
132
+ onpointermove={track}
133
+ onpointerleave={() => {
134
+ hovered = -1;
135
+ }}
136
+ >
137
+ <!-- Grid and axes, recessive: they orient the eye, they are not the data. -->
138
+ {#each ticks as tick (tick)}
139
+ <line
140
+ x1={PADDING.left}
141
+ x2={WIDTH - PADDING.right}
142
+ y1={y(tick)}
143
+ y2={y(tick)}
144
+ class="stroke-base-content/10"
145
+ stroke-width="1"
146
+ />
147
+ <text
148
+ x={PADDING.left - 8}
149
+ y={y(tick) + 4}
150
+ text-anchor="end"
151
+ class="fill-base-content/50 text-[10px]"
152
+ >
153
+ {formatValue(Math.round(tick))}
154
+ </text>
155
+ {/each}
156
+ <line
157
+ x1={PADDING.left}
158
+ x2={WIDTH - PADDING.right}
159
+ y1={y(min)}
160
+ y2={y(min)}
161
+ class="stroke-base-content/30"
162
+ stroke-width="1"
163
+ />
164
+
165
+ {#each categories as category, index (category)}
166
+ <text
167
+ x={x(index)}
168
+ y={height - 10}
169
+ text-anchor="middle"
170
+ class="fill-base-content/50 text-[10px]"
171
+ >
172
+ {category}
173
+ </text>
174
+ {/each}
175
+
176
+ {#if hovered >= 0 && kind !== 'bar'}
177
+ <line
178
+ x1={x(hovered)}
179
+ x2={x(hovered)}
180
+ y1={PADDING.top}
181
+ y2={PADDING.top + plotHeight}
182
+ class="stroke-base-content/30"
183
+ stroke-width="1"
184
+ stroke-dasharray="3 3"
185
+ />
186
+ {/if}
187
+
188
+ {#each series as entry, index (entry.key)}
189
+ {#if kind === 'bar'}
190
+ {#each entry.points as point, pointIndex (point.label)}
191
+ <rect
192
+ x={x(pointIndex) -
193
+ (barWidth * series.length + 2 * (series.length - 1)) / 2 +
194
+ index * (barWidth + 2)}
195
+ y={Math.min(y(point.value), y(0))}
196
+ width={barWidth}
197
+ height={Math.max(1, Math.abs(y(point.value) - y(0)))}
198
+ rx="4"
199
+ fill={colour(index)}
200
+ opacity={hovered === -1 || hovered === pointIndex ? 1 : 0.45}
201
+ />
202
+ {/each}
203
+ {:else}
204
+ {#if kind === 'area'}
205
+ <path d={areaPath(entry)} fill={colour(index)} opacity="0.15" />
206
+ {/if}
207
+ <path
208
+ d={path(entry)}
209
+ fill="none"
210
+ stroke={colour(index)}
211
+ stroke-width="2"
212
+ stroke-linecap="round"
213
+ stroke-linejoin="round"
214
+ />
215
+ {#if hovered >= 0}
216
+ {@const marked = entry.points[hovered]}
217
+ {#if marked !== undefined}
218
+ <!-- The 2px surface ring is what keeps two markers that land on
219
+ the same value from reading as one mark. -->
220
+ <circle
221
+ cx={x(hovered)}
222
+ cy={y(marked.value)}
223
+ r="5"
224
+ fill={colour(index)}
225
+ class="stroke-base-100"
226
+ stroke-width="2"
227
+ />
228
+ {/if}
229
+ {/if}
230
+ {#if series.length > 1 && series.length <= 4}
231
+ {@const last = entry.points.at(-1)}
232
+ {#if last !== undefined}
233
+ <text
234
+ x={x(entry.points.length - 1) + 6}
235
+ y={y(last.value) + 3}
236
+ class="fill-base-content/70 text-[10px]"
237
+ >
238
+ {entry.label}
239
+ </text>
240
+ {/if}
241
+ {/if}
242
+ {/if}
243
+ {/each}
244
+ </svg>
245
+
246
+ {#if hovered >= 0}
247
+ <!-- Positioned over the plot in percentage terms, so it tracks the fluid
248
+ SVG rather than a width this component does not know. -->
249
+ <div
250
+ class="bg-base-100 border-base-300 rounded-box pointer-events-none absolute top-2 z-10 border p-2 text-xs shadow-md"
251
+ style:left={`calc(${(x(hovered) / WIDTH) * 100}% + 0.5rem)`}
252
+ role="status"
253
+ >
254
+ <p class="font-semibold">{categories[hovered]}</p>
255
+ <ul>
256
+ {#each series as entry, index (entry.key)}
257
+ {@const point = entry.points[hovered]}
258
+ {#if point !== undefined}
259
+ <li class="flex items-center gap-1.5">
260
+ <span
261
+ class="inline-block size-2 rounded-full"
262
+ style:background-color={colour(index)}
263
+ aria-hidden="true"
264
+ ></span>
265
+ <span class="text-base-content/70">{entry.label}</span>
266
+ <span class="font-medium">{formatValue(point.value)}</span>
267
+ </li>
268
+ {/if}
269
+ {/each}
270
+ </ul>
271
+ </div>
272
+ {/if}
273
+ </div>
274
+
275
+ <!-- The same figures as text. Always present; `showTable` only decides
276
+ whether sighted readers see it too. -->
277
+ <div class={showTable ? 'overflow-x-auto' : 'sr-only'}>
278
+ <table class="table table-sm">
279
+ <caption class="sr-only">{caption}</caption>
280
+ <thead>
281
+ <tr>
282
+ <th scope="col">{categories.length === 0 ? caption : categoryLabel}</th>
283
+ {#each series as entry (entry.key)}
284
+ <th scope="col">{entry.label}</th>
285
+ {/each}
286
+ </tr>
287
+ </thead>
288
+ <tbody>
289
+ {#each categories as category, index (category)}
290
+ <tr>
291
+ <th scope="row">{category}</th>
292
+ {#each series as entry (entry.key)}
293
+ <td>{formatValue(entry.points[index]?.value ?? 0)}</td>
294
+ {/each}
295
+ </tr>
296
+ {/each}
297
+ </tbody>
298
+ </table>
299
+ </div>
300
+ </figure>
@@ -0,0 +1,50 @@
1
+ import type { ChartKind, ChartSeries } from './types.ts';
2
+ interface Props {
3
+ /** One entry per series, sharing the first series' categories. */
4
+ series: readonly ChartSeries[];
5
+ /**
6
+ * The chart form.
7
+ * @defaultValue `'line'`
8
+ */
9
+ kind?: ChartKind | undefined;
10
+ /** Names the figure; rendered as its caption and its accessible name. */
11
+ title: string;
12
+ /**
13
+ * A sentence of context under the title.
14
+ * @defaultValue `''`
15
+ */
16
+ description?: string | undefined;
17
+ /**
18
+ * Plot height in CSS pixels; the width is fluid.
19
+ * @defaultValue `240`
20
+ */
21
+ height?: number | undefined;
22
+ /** Reveal the data table rather than leaving it to assistive tech. */
23
+ showTable?: boolean | undefined;
24
+ /**
25
+ * Names the series legend; only rendered when there are two or more.
26
+ * @defaultValue `'Series'`
27
+ */
28
+ legendLabel?: string | undefined;
29
+ /**
30
+ * Captions the data table. Empty means "use `title`".
31
+ * @defaultValue `''` (use `title`)
32
+ */
33
+ tableLabel?: string | undefined;
34
+ /**
35
+ * Heads the table's category column.
36
+ * @defaultValue `''`
37
+ */
38
+ categoryLabel?: string | undefined;
39
+ /**
40
+ * Rendered value text — a unit, a locale-formatted number.
41
+ * @defaultValue `String(value)`
42
+ */
43
+ formatValue?: ((value: number) => string) | undefined;
44
+ /** Extra classes, merged last. */
45
+ class?: string | undefined;
46
+ }
47
+ /** A line, area or bar chart drawn as plain SVG from a declarative series spec, with an always-present `sr-only` data table. */
48
+ declare const Chart: import("svelte").Component<Props, {}, "">;
49
+ type Chart = ReturnType<typeof Chart>;
50
+ export default Chart;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@thomasfosterau/components/charts` — one component, over a declarative
3
+ * series spec, drawn as plain SVG.
4
+ *
5
+ * A separate entry point so it never sits on the always-loaded path of a
6
+ * consumer that does not use it.
7
+ *
8
+ * @module
9
+ */
10
+ export { default as Chart } from './Chart.svelte';
11
+ export type { ChartKind, ChartPoint, ChartSeries } from './types.ts';