@thomasfosterau/components 0.0.0 → 0.1.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 (340) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/canvas/VectorCanvas.svelte +239 -0
  4. package/dist/canvas/VectorCanvas.svelte.d.ts +87 -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 +38 -0
  57. package/dist/context.svelte.d.ts +46 -0
  58. package/dist/context.svelte.js +51 -0
  59. package/dist/data/DataGrid.svelte +150 -0
  60. package/dist/data/DataGrid.svelte.d.ts +118 -0
  61. package/dist/data/DescriptionList.svelte +47 -0
  62. package/dist/data/DescriptionList.svelte.d.ts +26 -0
  63. package/dist/data/DescriptionRow.svelte +55 -0
  64. package/dist/data/DescriptionRow.svelte.d.ts +21 -0
  65. package/dist/data/DiffText.svelte +29 -0
  66. package/dist/data/DiffText.svelte.d.ts +19 -0
  67. package/dist/data/ItemList.svelte +80 -0
  68. package/dist/data/ItemList.svelte.d.ts +52 -0
  69. package/dist/data/LinkListItem.svelte +46 -0
  70. package/dist/data/LinkListItem.svelte.d.ts +21 -0
  71. package/dist/data/Pagination.svelte +136 -0
  72. package/dist/data/Pagination.svelte.d.ts +71 -0
  73. package/dist/data/PaginationFooter.svelte +41 -0
  74. package/dist/data/PaginationFooter.svelte.d.ts +59 -0
  75. package/dist/data/ResultCount.svelte +27 -0
  76. package/dist/data/ResultCount.svelte.d.ts +18 -0
  77. package/dist/data/Table.svelte +145 -0
  78. package/dist/data/Table.svelte.d.ts +75 -0
  79. package/dist/data/TreeList.svelte +41 -0
  80. package/dist/data/TreeList.svelte.d.ts +17 -0
  81. package/dist/data/TreeListItem.svelte +29 -0
  82. package/dist/data/TreeListItem.svelte.d.ts +10 -0
  83. package/dist/data/description-context.d.ts +33 -0
  84. package/dist/data/description-context.js +35 -0
  85. package/dist/data/diff.d.ts +14 -0
  86. package/dist/data/diff.js +10 -0
  87. package/dist/data/index.d.ts +35 -0
  88. package/dist/data/index.js +35 -0
  89. package/dist/data/list-item.d.ts +30 -0
  90. package/dist/data/list-item.js +19 -0
  91. package/dist/data/pagination.d.ts +81 -0
  92. package/dist/data/pagination.js +79 -0
  93. package/dist/data/table-types.d.ts +64 -0
  94. package/dist/data/table-types.js +35 -0
  95. package/dist/feedback/ConfirmDialog.svelte +79 -0
  96. package/dist/feedback/ConfirmDialog.svelte.d.ts +82 -0
  97. package/dist/feedback/Dialog.svelte +77 -0
  98. package/dist/feedback/Dialog.svelte.d.ts +37 -0
  99. package/dist/feedback/LoadingBoundary.svelte +61 -0
  100. package/dist/feedback/LoadingBoundary.svelte.d.ts +34 -0
  101. package/dist/feedback/ProgressBar.svelte +46 -0
  102. package/dist/feedback/ProgressBar.svelte.d.ts +28 -0
  103. package/dist/feedback/SegmentBar.svelte +83 -0
  104. package/dist/feedback/SegmentBar.svelte.d.ts +36 -0
  105. package/dist/feedback/StatTile.svelte +77 -0
  106. package/dist/feedback/StatTile.svelte.d.ts +49 -0
  107. package/dist/feedback/Stats.svelte +18 -0
  108. package/dist/feedback/Stats.svelte.d.ts +16 -0
  109. package/dist/feedback/Timeline.svelte +86 -0
  110. package/dist/feedback/Timeline.svelte.d.ts +52 -0
  111. package/dist/feedback/Toaster.svelte +78 -0
  112. package/dist/feedback/Toaster.svelte.d.ts +32 -0
  113. package/dist/feedback/index.d.ts +31 -0
  114. package/dist/feedback/index.js +27 -0
  115. package/dist/feedback/sizes.d.ts +27 -0
  116. package/dist/feedback/sizes.js +41 -0
  117. package/dist/feedback/toaster.d.ts +48 -0
  118. package/dist/feedback/toaster.js +29 -0
  119. package/dist/fields/Checkbox.svelte +33 -0
  120. package/dist/fields/Checkbox.svelte.d.ts +24 -0
  121. package/dist/fields/CheckboxGroup.svelte +41 -0
  122. package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
  123. package/dist/fields/Combobox.svelte +222 -0
  124. package/dist/fields/Combobox.svelte.d.ts +98 -0
  125. package/dist/fields/DateInput.svelte +31 -0
  126. package/dist/fields/DateInput.svelte.d.ts +29 -0
  127. package/dist/fields/FieldErrors.svelte +30 -0
  128. package/dist/fields/FieldErrors.svelte.d.ts +11 -0
  129. package/dist/fields/Fieldset.svelte +48 -0
  130. package/dist/fields/Fieldset.svelte.d.ts +26 -0
  131. package/dist/fields/FileInput.svelte +67 -0
  132. package/dist/fields/FileInput.svelte.d.ts +33 -0
  133. package/dist/fields/FormField.svelte +54 -0
  134. package/dist/fields/FormField.svelte.d.ts +29 -0
  135. package/dist/fields/ImageInput.svelte +87 -0
  136. package/dist/fields/ImageInput.svelte.d.ts +39 -0
  137. package/dist/fields/PushToggle.svelte +93 -0
  138. package/dist/fields/PushToggle.svelte.d.ts +55 -0
  139. package/dist/fields/RadioGroup.svelte +85 -0
  140. package/dist/fields/RadioGroup.svelte.d.ts +41 -0
  141. package/dist/fields/SearchInput.svelte +57 -0
  142. package/dist/fields/SearchInput.svelte.d.ts +33 -0
  143. package/dist/fields/Select.svelte +36 -0
  144. package/dist/fields/Select.svelte.d.ts +29 -0
  145. package/dist/fields/TextInput.svelte +26 -0
  146. package/dist/fields/TextInput.svelte.d.ts +30 -0
  147. package/dist/fields/Textarea.svelte +22 -0
  148. package/dist/fields/Textarea.svelte.d.ts +25 -0
  149. package/dist/fields/Toggle.svelte +72 -0
  150. package/dist/fields/Toggle.svelte.d.ts +32 -0
  151. package/dist/fields/VideoInput.svelte +35 -0
  152. package/dist/fields/VideoInput.svelte.d.ts +40 -0
  153. package/dist/fields/field-types.d.ts +51 -0
  154. package/dist/fields/field-types.js +60 -0
  155. package/dist/fields/index.d.ts +34 -0
  156. package/dist/fields/index.js +34 -0
  157. package/dist/formatting/DateTime.svelte +35 -0
  158. package/dist/formatting/DateTime.svelte.d.ts +26 -0
  159. package/dist/formatting/FormatList.svelte +31 -0
  160. package/dist/formatting/FormatList.svelte.d.ts +46 -0
  161. package/dist/formatting/Highlight.svelte +47 -0
  162. package/dist/formatting/Highlight.svelte.d.ts +15 -0
  163. package/dist/formatting/Number.svelte +35 -0
  164. package/dist/formatting/Number.svelte.d.ts +43 -0
  165. package/dist/formatting/RelativeTime.svelte +69 -0
  166. package/dist/formatting/RelativeTime.svelte.d.ts +26 -0
  167. package/dist/formatting/Year.svelte +39 -0
  168. package/dist/formatting/Year.svelte.d.ts +21 -0
  169. package/dist/formatting/index.d.ts +12 -0
  170. package/dist/formatting/index.js +14 -0
  171. package/dist/forms/EntityForm.svelte +177 -0
  172. package/dist/forms/EntityForm.svelte.d.ts +57 -0
  173. package/dist/forms/FormActions.svelte +29 -0
  174. package/dist/forms/FormActions.svelte.d.ts +37 -0
  175. package/dist/forms/FormErrors.svelte +21 -0
  176. package/dist/forms/FormErrors.svelte.d.ts +16 -0
  177. package/dist/forms/HiddenField.svelte +22 -0
  178. package/dist/forms/HiddenField.svelte.d.ts +11 -0
  179. package/dist/forms/SearchForm.svelte +43 -0
  180. package/dist/forms/SearchForm.svelte.d.ts +36 -0
  181. package/dist/forms/entity-form.d.ts +12 -0
  182. package/dist/forms/entity-form.js +12 -0
  183. package/dist/forms/index.d.ts +14 -0
  184. package/dist/forms/index.js +14 -0
  185. package/dist/hotkeys/HotkeyBadge.svelte +39 -0
  186. package/dist/hotkeys/HotkeyBadge.svelte.d.ts +25 -0
  187. package/dist/hotkeys/HotkeyHelpDialog.svelte +68 -0
  188. package/dist/hotkeys/HotkeyHelpDialog.svelte.d.ts +35 -0
  189. package/dist/hotkeys/HotkeyProvider.svelte +92 -0
  190. package/dist/hotkeys/HotkeyProvider.svelte.d.ts +4 -0
  191. package/dist/hotkeys/context.svelte.d.ts +47 -0
  192. package/dist/hotkeys/context.svelte.js +72 -0
  193. package/dist/hotkeys/index.d.ts +23 -0
  194. package/dist/hotkeys/index.js +22 -0
  195. package/dist/hotkeys/match.d.ts +65 -0
  196. package/dist/hotkeys/match.js +133 -0
  197. package/dist/hotkeys/registry.svelte.d.ts +55 -0
  198. package/dist/hotkeys/registry.svelte.js +110 -0
  199. package/dist/hotkeys/types.d.ts +72 -0
  200. package/dist/hotkeys/types.js +18 -0
  201. package/dist/index.d.ts +36 -0
  202. package/dist/index.js +36 -0
  203. package/dist/infobox/Infobox.svelte +79 -0
  204. package/dist/infobox/Infobox.svelte.d.ts +26 -0
  205. package/dist/infobox/InfoboxHeader.svelte +27 -0
  206. package/dist/infobox/InfoboxHeader.svelte.d.ts +11 -0
  207. package/dist/infobox/InfoboxImage.svelte +38 -0
  208. package/dist/infobox/InfoboxImage.svelte.d.ts +21 -0
  209. package/dist/infobox/InfoboxRow.svelte +50 -0
  210. package/dist/infobox/InfoboxRow.svelte.d.ts +25 -0
  211. package/dist/infobox/InfoboxSection.svelte +24 -0
  212. package/dist/infobox/InfoboxSection.svelte.d.ts +12 -0
  213. package/dist/infobox/index.d.ts +21 -0
  214. package/dist/infobox/index.js +20 -0
  215. package/dist/infobox/types.d.ts +86 -0
  216. package/dist/infobox/types.js +27 -0
  217. package/dist/layout/Breadcrumbs.svelte +52 -0
  218. package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
  219. package/dist/layout/DetailLayout.svelte +50 -0
  220. package/dist/layout/DetailLayout.svelte.d.ts +19 -0
  221. package/dist/layout/FilterBar.svelte +40 -0
  222. package/dist/layout/FilterBar.svelte.d.ts +12 -0
  223. package/dist/layout/Footer.svelte +32 -0
  224. package/dist/layout/Footer.svelte.d.ts +12 -0
  225. package/dist/layout/Main.svelte +30 -0
  226. package/dist/layout/Main.svelte.d.ts +15 -0
  227. package/dist/layout/Navbar.svelte +68 -0
  228. package/dist/layout/Navbar.svelte.d.ts +31 -0
  229. package/dist/layout/PageHeader.svelte +77 -0
  230. package/dist/layout/PageHeader.svelte.d.ts +33 -0
  231. package/dist/layout/Section.svelte +72 -0
  232. package/dist/layout/Section.svelte.d.ts +40 -0
  233. package/dist/layout/Sidebar.svelte +54 -0
  234. package/dist/layout/Sidebar.svelte.d.ts +27 -0
  235. package/dist/layout/Tabs.svelte +85 -0
  236. package/dist/layout/Tabs.svelte.d.ts +42 -0
  237. package/dist/layout/index.d.ts +29 -0
  238. package/dist/layout/index.js +29 -0
  239. package/dist/layout/types.d.ts +52 -0
  240. package/dist/layout/types.js +27 -0
  241. package/dist/links/ContextLinks.svelte +28 -0
  242. package/dist/links/ContextLinks.svelte.d.ts +20 -0
  243. package/dist/links/EmailLink.svelte +26 -0
  244. package/dist/links/EmailLink.svelte.d.ts +20 -0
  245. package/dist/links/index.d.ts +7 -0
  246. package/dist/links/index.js +7 -0
  247. package/dist/media/Figure.svelte +26 -0
  248. package/dist/media/Figure.svelte.d.ts +12 -0
  249. package/dist/media/Image.svelte +76 -0
  250. package/dist/media/Image.svelte.d.ts +41 -0
  251. package/dist/media/Video.svelte +65 -0
  252. package/dist/media/Video.svelte.d.ts +29 -0
  253. package/dist/media/image-dimensions.d.ts +28 -0
  254. package/dist/media/image-dimensions.js +34 -0
  255. package/dist/media/index.d.ts +10 -0
  256. package/dist/media/index.js +10 -0
  257. package/dist/search/CommandPalette.svelte +257 -0
  258. package/dist/search/CommandPalette.svelte.d.ts +59 -0
  259. package/dist/search/index.d.ts +13 -0
  260. package/dist/search/index.js +13 -0
  261. package/dist/search/types.d.ts +37 -0
  262. package/dist/search/types.js +20 -0
  263. package/dist/seo/Head.svelte +190 -0
  264. package/dist/seo/Head.svelte.d.ts +86 -0
  265. package/dist/seo/JsonLd.svelte +30 -0
  266. package/dist/seo/JsonLd.svelte.d.ts +20 -0
  267. package/dist/seo/index.d.ts +18 -0
  268. package/dist/seo/index.js +17 -0
  269. package/dist/seo/json-ld.d.ts +33 -0
  270. package/dist/seo/json-ld.js +29 -0
  271. package/dist/seo/og-image.d.ts +71 -0
  272. package/dist/seo/og-image.js +81 -0
  273. package/dist/seo/types.d.ts +43 -0
  274. package/dist/seo/types.js +6 -0
  275. package/dist/theme.css +161 -0
  276. package/dist/tips/PopoverTip.svelte +80 -0
  277. package/dist/tips/PopoverTip.svelte.d.ts +33 -0
  278. package/dist/tips/Tip.svelte +91 -0
  279. package/dist/tips/Tip.svelte.d.ts +25 -0
  280. package/dist/tips/index.d.ts +15 -0
  281. package/dist/tips/index.js +14 -0
  282. package/dist/tips/tip-stack.d.ts +25 -0
  283. package/dist/tips/tip-stack.js +37 -0
  284. package/dist/tips/tip-store.svelte.d.ts +74 -0
  285. package/dist/tips/tip-store.svelte.js +158 -0
  286. package/dist/tips/types.d.ts +46 -0
  287. package/dist/tips/types.js +6 -0
  288. package/dist/ui/Alert.svelte +61 -0
  289. package/dist/ui/Alert.svelte.d.ts +37 -0
  290. package/dist/ui/Badge.svelte +39 -0
  291. package/dist/ui/Badge.svelte.d.ts +32 -0
  292. package/dist/ui/Button.svelte +43 -0
  293. package/dist/ui/Button.svelte.d.ts +29 -0
  294. package/dist/ui/Card.svelte +49 -0
  295. package/dist/ui/Card.svelte.d.ts +44 -0
  296. package/dist/ui/Chip.svelte +58 -0
  297. package/dist/ui/Chip.svelte.d.ts +21 -0
  298. package/dist/ui/ConfirmButton.svelte +75 -0
  299. package/dist/ui/ConfirmButton.svelte.d.ts +48 -0
  300. package/dist/ui/Disclosure.svelte +39 -0
  301. package/dist/ui/Disclosure.svelte.d.ts +22 -0
  302. package/dist/ui/EmptyState.svelte +37 -0
  303. package/dist/ui/EmptyState.svelte.d.ts +22 -0
  304. package/dist/ui/ExternalLink.svelte +38 -0
  305. package/dist/ui/ExternalLink.svelte.d.ts +34 -0
  306. package/dist/ui/Heading.svelte +58 -0
  307. package/dist/ui/Heading.svelte.d.ts +22 -0
  308. package/dist/ui/Link.svelte +34 -0
  309. package/dist/ui/Link.svelte.d.ts +20 -0
  310. package/dist/ui/LinkButton.svelte +51 -0
  311. package/dist/ui/LinkButton.svelte.d.ts +33 -0
  312. package/dist/ui/Menu.svelte +54 -0
  313. package/dist/ui/Menu.svelte.d.ts +34 -0
  314. package/dist/ui/MenuItem.svelte +75 -0
  315. package/dist/ui/MenuItem.svelte.d.ts +21 -0
  316. package/dist/ui/MenuSeparator.svelte +26 -0
  317. package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
  318. package/dist/ui/Popover.svelte +63 -0
  319. package/dist/ui/Popover.svelte.d.ts +40 -0
  320. package/dist/ui/ShareButton.svelte +64 -0
  321. package/dist/ui/ShareButton.svelte.d.ts +39 -0
  322. package/dist/ui/Skeleton.svelte +43 -0
  323. package/dist/ui/Skeleton.svelte.d.ts +22 -0
  324. package/dist/ui/ThemeToggle.svelte +65 -0
  325. package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
  326. package/dist/ui/Tooltip.svelte +54 -0
  327. package/dist/ui/Tooltip.svelte.d.ts +35 -0
  328. package/dist/ui/VisuallyHidden.svelte +23 -0
  329. package/dist/ui/VisuallyHidden.svelte.d.ts +10 -0
  330. package/dist/ui/colour-token.d.ts +41 -0
  331. package/dist/ui/colour-token.js +49 -0
  332. package/dist/ui/index.d.ts +44 -0
  333. package/dist/ui/index.js +44 -0
  334. package/dist/ui/menu-context.d.ts +22 -0
  335. package/dist/ui/menu-context.js +26 -0
  336. package/dist/ui/slug.d.ts +19 -0
  337. package/dist/ui/slug.js +25 -0
  338. package/dist/ui/tones.d.ts +68 -0
  339. package/dist/ui/tones.js +150 -0
  340. package/package.json +25 -25
@@ -0,0 +1,118 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { Size } from '../ui/tones.ts';
4
+ import type { SortState, TableColumn } from './table-types.ts';
5
+ declare function $$render<T>(): {
6
+ props: Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
7
+ /** Every row; sorting, filtering and paging happen here. */
8
+ rows: readonly T[];
9
+ /** Column definitions: header, cell renderer, sort key. */
10
+ columns: readonly TableColumn<T>[];
11
+ /** Stable identity per row — the `{#each}` key. */
12
+ rowKey: (row: T) => string;
13
+ /** Offer the filter box. */
14
+ filterable?: boolean | undefined;
15
+ /**
16
+ * Offer the pager; every row renders when false.
17
+ * @defaultValue `true`
18
+ */
19
+ paginated?: boolean | undefined;
20
+ /**
21
+ * Rows per page until the reader picks another size.
22
+ * @defaultValue `10`
23
+ */
24
+ pageSize?: number | undefined;
25
+ /**
26
+ * The choices offered in the rows-per-page select.
27
+ * @defaultValue `[10, 25, 50, 100]`
28
+ */
29
+ pageSizeOptions?: readonly number[] | undefined;
30
+ /** The table's accessible name, rendered as its `<caption>`. */
31
+ caption?: string | undefined;
32
+ /** Keep the caption for assistive technology only. */
33
+ captionHidden?: boolean | undefined;
34
+ /** daisyUI size step. */
35
+ size?: Size | undefined;
36
+ /** Stripe alternate rows. */
37
+ zebra?: boolean | undefined;
38
+ /** Stick the header row while the body scrolls; see `Table`. */
39
+ pinRows?: boolean | undefined;
40
+ /** Which column starts sorted. Bindable; `undefined` is the caller's order. */
41
+ sort?: SortState | undefined;
42
+ /** Makes every row activatable by pointer and keyboard; see `Table`. */
43
+ onSelect?: ((row: T) => void) | undefined;
44
+ /** The line shown when no row matches. */
45
+ emptyMessage?: string | undefined;
46
+ /** Richer empty state, in place of `emptyMessage`. */
47
+ emptyState?: Snippet<[]> | undefined;
48
+ /**
49
+ * Accessible name of the filter box.
50
+ * @defaultValue `'Search'`
51
+ */
52
+ filterLabel?: string | undefined;
53
+ /**
54
+ * Placeholder in the filter box.
55
+ * @defaultValue `'Search…'`
56
+ */
57
+ filterPlaceholder?: string | undefined;
58
+ /**
59
+ * Label of the rows-per-page select.
60
+ * @defaultValue `'Rows per page'`
61
+ */
62
+ rowsPerPageLabel?: string | undefined;
63
+ /**
64
+ * The range line under the table.
65
+ * @defaultValue `"Showing <start> to <end> of <total>"`
66
+ */
67
+ showingLabel?: ((start: number, end: number, total: number) => string) | undefined;
68
+ /**
69
+ * The page status between the pager arms.
70
+ * @defaultValue `"Page <page> of <pages>"`
71
+ */
72
+ pageOfLabel?: ((page: number, pages: number) => string) | undefined;
73
+ /**
74
+ * Text of the previous-page control.
75
+ * @defaultValue `'Previous'`
76
+ */
77
+ previousLabel?: string | undefined;
78
+ /**
79
+ * Text of the next-page control.
80
+ * @defaultValue `'Next'`
81
+ */
82
+ nextLabel?: string | undefined;
83
+ /**
84
+ * Accessible name of the pager landmark.
85
+ * @defaultValue `'Pagination'`
86
+ */
87
+ paginationLabel?: string | undefined;
88
+ /** How a sortable column’s button is named; see `Table`. */
89
+ sortLabel?: ((column: TableColumn<T>) => string) | undefined;
90
+ /**
91
+ * Locale(s) for `Intl` formatting.
92
+ * @defaultValue the components context's `locale()`
93
+ */
94
+ locales?: string | readonly string[] | undefined;
95
+ };
96
+ exports: {};
97
+ bindings: "sort";
98
+ slots: {};
99
+ events: {};
100
+ };
101
+ declare class __sveltets_Render<T> {
102
+ props(): ReturnType<typeof $$render<T>>['props'];
103
+ events(): ReturnType<typeof $$render<T>>['events'];
104
+ slots(): ReturnType<typeof $$render<T>>['slots'];
105
+ bindings(): "sort";
106
+ exports(): {};
107
+ }
108
+ interface $$IsomorphicComponent {
109
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
110
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
111
+ } & ReturnType<__sveltets_Render<T>['exports']>;
112
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
113
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
114
+ }
115
+ /** A `Table` with client-side sorting, filtering across every column and paging, for a list that is already entirely in hand. */
116
+ declare const DataGrid: $$IsomorphicComponent;
117
+ type DataGrid<T> = InstanceType<typeof DataGrid<T>>;
118
+ export default DataGrid;
@@ -0,0 +1,47 @@
1
+ <!-- @component
2
+ Label/value pairs as a `grid`, a bordered `card`, `divided` rows or `inline` pairs.
3
+ -->
4
+ <!--
5
+ DescriptionList — label / value pairs, in the four shapes a detail page
6
+ wants them: a two-column `grid`, a bordered `card` with the labels in a
7
+ fixed gutter, `divided` rows, or `inline` pairs running across a line.
8
+
9
+ One component rather than the two the apps had. They had split it by how the
10
+ VALUE arrives — one took rows as strings, the other took them as markup —
11
+ which meant a page needing a formatted date in one row and a string in the
12
+ next had to choose a component per list. Here the value is a prop or a
13
+ snippet on the ROW, so both live in one list.
14
+
15
+ `mono` marks the values that are identifiers (a formula, an InChIKey, a
16
+ DOI) rather than sentences: those want a fixed-width face and a
17
+ break-anywhere rule, and the ones that are prose do not.
18
+
19
+ ARIA contract: a real `<dl>` of `<dt>`/`<dd>` pairs, which is what makes the
20
+ label / value association exist at all — a screen reader reads a `<dd>` as
21
+ the definition of the `<dt>` before it. The variant reaches the rows through
22
+ context (see `description-context.ts`), never through a class on the list
23
+ that the rows would have to guess at.
24
+ -->
25
+ <script lang="ts">import { setDescriptionListStyle } from "./description-context.js";
26
+ let { variant = "grid", mono = false, showEmpty = false, emptyLabel = "unrecorded", children, class: className, ...rest } = $props();
27
+ // Literal lookup table: Tailwind reads the class names out of the source,
28
+ // so a template string would emit no CSS at all.
29
+ const listClass = {
30
+ grid: "grid grid-cols-1 gap-3 sm:grid-cols-2",
31
+ card: "card card-border card-body bg-base-100 grid grid-cols-1 gap-x-6 gap-y-3 sm:grid-cols-[12rem_1fr]",
32
+ divided: "divide-base-300 divide-y",
33
+ inline: "flex flex-wrap gap-x-6 gap-y-2"
34
+ };
35
+ // A getter, not a snapshot: the rows re-read it, so a list whose `variant`
36
+ // changes re-styles the rows it already mounted.
37
+ setDescriptionListStyle(() => ({
38
+ variant,
39
+ mono,
40
+ showEmpty,
41
+ emptyLabel
42
+ }));
43
+ </script>
44
+
45
+ <dl {...rest} class={[listClass[variant], className]}>
46
+ {@render children()}
47
+ </dl>
@@ -0,0 +1,26 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type DescriptionListStyle } from './description-context.ts';
4
+ type Variant = DescriptionListStyle['variant'];
5
+ interface Props extends HTMLAttributes<HTMLElement> {
6
+ /**
7
+ * The layout.
8
+ * @defaultValue `'grid'`
9
+ */
10
+ variant?: Variant | undefined;
11
+ /** Render values in a fixed-width face that breaks anywhere. */
12
+ mono?: boolean | undefined;
13
+ /** Render rows whose value is empty, rather than dropping them. */
14
+ showEmpty?: boolean | undefined;
15
+ /**
16
+ * What an empty value reads as when `showEmpty`.
17
+ * @defaultValue `'unrecorded'`
18
+ */
19
+ emptyLabel?: string | undefined;
20
+ /** The rows — `DescriptionRow`s, or any other `<dt>`/`<dd>` pairs. */
21
+ children: Snippet<[]>;
22
+ }
23
+ /** Label/value pairs as a `grid`, a bordered `card`, `divided` rows or `inline` pairs. */
24
+ declare const DescriptionList: import("svelte").Component<Props, {}, "">;
25
+ type DescriptionList = ReturnType<typeof DescriptionList>;
26
+ export default DescriptionList;
@@ -0,0 +1,55 @@
1
+ <!-- @component
2
+ One label and its value inside a `DescriptionList`.
3
+ -->
4
+ <!--
5
+ DescriptionRow — one label and its value inside a `DescriptionList`.
6
+
7
+ A row with nothing in it renders NOTHING. An empty `<dd>` is a worse answer
8
+ than an absent row: it claims the page looked and found a blank, where the
9
+ absent row claims nothing at all. `showEmpty` is for the lists where the
10
+ absence is the point — a specimen record whose empty fields are what the
11
+ reader is checking — and it renders `emptyLabel` in the value's place.
12
+ Both settings come from the enclosing list by default and can be overridden
13
+ per row.
14
+
15
+ The `card` variant's `<dt>` and `<dd>` are siblings of every other row's,
16
+ directly in the list's two-column grid: a wrapper would break the alignment
17
+ the grid exists for. Every other variant wraps the pair, so the two stay
18
+ together when the rows wrap.
19
+
20
+ ARIA contract: the `<dt>`/`<dd>` pairing is the whole contract — nothing is
21
+ added to it, and nothing needs to be.
22
+ -->
23
+ <script lang="ts">import { getDescriptionListStyle } from "./description-context.js";
24
+ let { label, value, children, valueClass, labelClass, showEmpty, emptyLabel } = $props();
25
+ const style = getDescriptionListStyle();
26
+ const hasValue = $derived(children !== undefined || value !== undefined && String(value).trim() !== "");
27
+ const visible = $derived(hasValue || (showEmpty ?? style().showEmpty));
28
+ const placeholder = $derived(emptyLabel ?? style().emptyLabel);
29
+ const dtClass = $derived(["text-base-content/60 text-sm", labelClass]);
30
+ const ddClass = $derived([
31
+ "text-sm",
32
+ style().mono && "font-mono break-all",
33
+ !hasValue && "text-base-content/50 italic",
34
+ valueClass
35
+ ]);
36
+ </script>
37
+
38
+ {#snippet pair()}
39
+ <dt class={dtClass}>{label}</dt>
40
+ <dd class={ddClass}>
41
+ {#if children}{@render children()}{:else if hasValue}{value}{:else}{placeholder}{/if}
42
+ </dd>
43
+ {/snippet}
44
+
45
+ {#if visible}
46
+ {#if style().variant === 'card'}
47
+ {@render pair()}
48
+ {:else if style().variant === 'inline'}
49
+ <div class="flex items-baseline gap-2">{@render pair()}</div>
50
+ {:else if style().variant === 'divided'}
51
+ <div class="flex flex-col gap-0.5 py-2 sm:flex-row sm:gap-4">{@render pair()}</div>
52
+ {:else}
53
+ <div class="flex flex-col gap-0.5">{@render pair()}</div>
54
+ {/if}
55
+ {/if}
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** The term, rendered as the `<dt>`. */
4
+ label: string;
5
+ /** The value as text. Ignored when `children` is given. */
6
+ value?: string | number | undefined;
7
+ /** The value as markup — a formatted date, a link, a badge. */
8
+ children?: Snippet<[]> | undefined;
9
+ /** Extra classes on the `<dd>`. */
10
+ valueClass?: string | undefined;
11
+ /** Extra classes on the `<dt>`. */
12
+ labelClass?: string | undefined;
13
+ /** Override the list's `showEmpty` for this row. */
14
+ showEmpty?: boolean | undefined;
15
+ /** Override the list's `emptyLabel` for this row. */
16
+ emptyLabel?: string | undefined;
17
+ }
18
+ /** One label and its value inside a `DescriptionList`. */
19
+ declare const DescriptionRow: import("svelte").Component<Props, {}, "">;
20
+ type DescriptionRow = ReturnType<typeof DescriptionRow>;
21
+ export default DescriptionRow;
@@ -0,0 +1,29 @@
1
+ <!-- @component
2
+ A word-level diff rendered inline: inserted runs in `<ins>`, deleted runs in `<del>`, unchanged runs plain.
3
+ -->
4
+ <!--
5
+ DiffText — a word-level diff rendered inline: inserted runs in `<ins>`,
6
+ deleted runs in `<del>`, unchanged runs plain.
7
+
8
+ ARIA contract: `<ins>` and `<del>` are the contract. They are what carries
9
+ "added" and "removed" into the accessibility tree and into a copy of the
10
+ page; the colour and the strike-through are the sighted reader's copy of
11
+ the same information, never the only copy. Support for announcing the two
12
+ elements is uneven across screen readers, so `insertLabel` / `deleteLabel`
13
+ add a visually hidden word before a run when the diff is the point of the
14
+ page rather than a detail on it — off by default, because a forty-segment
15
+ diff with a prefix on every run is unreadable.
16
+
17
+ `separator` sits BETWEEN segments rather than inside them: a word-level
18
+ diff needs the space and a character-level one must not have it, and only
19
+ the caller knows which it produced. That is also why the template below is
20
+ written without line breaks between the runs — a newline in the markup is a
21
+ space in the output, and it would make the choice for them.
22
+ -->
23
+ <script lang="ts">import VisuallyHidden from "../ui/VisuallyHidden.svelte";
24
+ let { segments, separator = " ", insertLabel, deleteLabel, class: className, ...rest } = $props();
25
+ </script>
26
+
27
+ {#snippet run(segment: DiffSegment)}{#if segment.kind === 'insert'}<ins class="bg-success/20 text-success-content decoration-success underline">{#if insertLabel !== undefined}<VisuallyHidden>{insertLabel}</VisuallyHidden>{/if}{segment.text}</ins>{:else if segment.kind === 'delete'}<del class="bg-error/20 text-error-content">{#if deleteLabel !== undefined}<VisuallyHidden>{deleteLabel}</VisuallyHidden>{/if}{segment.text}</del>{:else}{segment.text}{/if}{/snippet}
28
+
29
+ <p {...rest} class={['leading-7', className]}>{#each segments as segment, index (index)}{#if index > 0}{separator}{/if}{@render run(segment)}{/each}</p>
@@ -0,0 +1,19 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { DiffSegment } from './diff.ts';
3
+ interface Props extends Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> {
4
+ /** The diff, in reading order. */
5
+ segments: readonly DiffSegment[];
6
+ /**
7
+ * Rendered between segments — a space for a word-level diff.
8
+ * @defaultValue `' '`
9
+ */
10
+ separator?: string | undefined;
11
+ /** A visually hidden word before each inserted run. */
12
+ insertLabel?: string | undefined;
13
+ /** A visually hidden word before each deleted run. */
14
+ deleteLabel?: string | undefined;
15
+ }
16
+ /** A word-level diff rendered inline: inserted runs in `<ins>`, deleted runs in `<del>`, unchanged runs plain. */
17
+ declare const DiffText: import("svelte").Component<Props, {}, "">;
18
+ type DiffText = ReturnType<typeof DiffText>;
19
+ export default DiffText;
@@ -0,0 +1,80 @@
1
+ <!-- @component
2
+ A list in one of four shapes — `compact` links, `stacked` rows, a `grid` or `cards` — with an optional feed mode for windowed streams.
3
+ -->
4
+ <!--
5
+ ItemList — the list every index page and detail section renders, in the four
6
+ shapes those pages actually use: a `compact` run of links, `stacked` rows
7
+ divided by rules, a responsive `grid`, or a grid of `cards`.
8
+
9
+ The row body is the caller's, through the `item` snippet, because row shape
10
+ varies per resource type; what this owns is the container, the keying and
11
+ the empty state — so an empty list cannot be forgotten in one page out of
12
+ twenty.
13
+
14
+ ARIA contract, two modes:
15
+
16
+ - Default: a plain `<ul>` of `<li>`s. The list structure is the semantics —
17
+ a screen reader announces "list, 12 items" — and nothing is added to it.
18
+ - `feed`: the ARIA feed pattern, for a stream whose items load as the
19
+ reader scrolls. The container takes `role="feed"`, an accessible name from
20
+ `label`, and `aria-busy` while more is loading; each item is an `<article>`
21
+ carrying `aria-posinset` / `aria-setsize`, so "article 7 of 40" is
22
+ announced even though only 20 are in the DOM, and `tabindex="-1"` so the
23
+ reader's Page Down lands somewhere.
24
+
25
+ Feed mode renders `<div role="feed">` with `<article>` children rather
26
+ than a list, deliberately: `feed` is not a role HTML permits on `<ul>`,
27
+ and forcing `role="article"` onto each `<li>` would throw away the list
28
+ semantics to get there. A feed is not a list, and the markup says so.
29
+ -->
30
+ <script lang="ts" generics="T">let { items, variant = "compact", getKey, item, empty, emptyMessage = "Nothing here yet.", feed = false, label, busy = false, class: className, ...rest } = $props();
31
+ // Literal lookup tables: Tailwind only generates the classes it can read
32
+ // verbatim in the source, so these can never become template strings.
33
+ const containerClass = {
34
+ compact: "space-y-1",
35
+ stacked: "divide-base-300 divide-y",
36
+ grid: "grid grid-cols-1 gap-3 sm:grid-cols-2",
37
+ cards: "grid grid-cols-1 gap-4 sm:grid-cols-2"
38
+ };
39
+ const itemClass = {
40
+ compact: "",
41
+ stacked: "py-3",
42
+ grid: "",
43
+ cards: "card card-border bg-base-100 p-4 shadow-sm"
44
+ };
45
+ const setSize = $derived(items.length);
46
+ export {};
47
+ </script>
48
+
49
+ {#if items.length === 0}
50
+ {#if empty}
51
+ {@render empty()}
52
+ {:else}
53
+ <p class="text-base-content/70 text-sm leading-6">{emptyMessage}</p>
54
+ {/if}
55
+ {:else if feed}
56
+ <div
57
+ {...rest}
58
+ role="feed"
59
+ aria-label={label}
60
+ aria-busy={busy}
61
+ class={[containerClass[variant], className]}
62
+ >
63
+ {#each items as entry, index (getKey(entry, index))}
64
+ <article
65
+ aria-posinset={index + 1}
66
+ aria-setsize={setSize}
67
+ tabindex="-1"
68
+ class={itemClass[variant]}
69
+ >
70
+ {@render item(entry, index)}
71
+ </article>
72
+ {/each}
73
+ </div>
74
+ {:else}
75
+ <ul {...rest} aria-label={label} class={[containerClass[variant], className]}>
76
+ {#each items as entry, index (getKey(entry, index))}
77
+ <li class={itemClass[variant]}>{@render item(entry, index)}</li>
78
+ {/each}
79
+ </ul>
80
+ {/if}
@@ -0,0 +1,52 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ declare function $$render<T>(): {
4
+ props: Omit<HTMLAttributes<HTMLElement>, "children"> & {
5
+ /** The items, each rendered by the `item` snippet. */
6
+ items: readonly T[];
7
+ /**
8
+ * The layout.
9
+ * @defaultValue `'compact'`
10
+ */
11
+ variant?: ("grid" | "compact" | "stacked" | "cards") | undefined;
12
+ /** Stable identity per item — the `{#each}` key. */
13
+ getKey: (item: T, index: number) => string;
14
+ /** One row's body. */
15
+ item: Snippet<[T, number]>;
16
+ /** Rendered in place of the list when there is nothing in it. */
17
+ empty?: Snippet<[]> | undefined;
18
+ /**
19
+ * The line shown when there is nothing and no `empty` snippet.
20
+ * @defaultValue `'Nothing here yet.'`
21
+ */
22
+ emptyMessage?: string | undefined;
23
+ /** Render as an ARIA feed. */
24
+ feed?: boolean | undefined;
25
+ /** The feed's accessible name. Required in `feed` mode to name the region. */
26
+ label?: string | undefined;
27
+ /** More items are loading — `aria-busy` on the feed. */
28
+ busy?: boolean | undefined;
29
+ };
30
+ exports: {};
31
+ bindings: "";
32
+ slots: {};
33
+ events: {};
34
+ };
35
+ declare class __sveltets_Render<T> {
36
+ props(): ReturnType<typeof $$render<T>>['props'];
37
+ events(): ReturnType<typeof $$render<T>>['events'];
38
+ slots(): ReturnType<typeof $$render<T>>['slots'];
39
+ bindings(): "";
40
+ exports(): {};
41
+ }
42
+ interface $$IsomorphicComponent {
43
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
44
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
45
+ } & ReturnType<__sveltets_Render<T>['exports']>;
46
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
47
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
48
+ }
49
+ /** A list in one of four shapes — `compact` links, `stacked` rows, a `grid` or `cards` — with an optional feed mode for windowed streams. */
50
+ declare const ItemList: $$IsomorphicComponent;
51
+ type ItemList<T> = InstanceType<typeof ItemList<T>>;
52
+ export default ItemList;
@@ -0,0 +1,46 @@
1
+ <!-- @component
2
+ The row body most lists want: a linked title, a line of context under it, and short badges.
3
+ -->
4
+ <!--
5
+ LinkListItem — the row body most lists want: a linked title, a line of
6
+ context under it, and short markers.
7
+
8
+ It renders the body only, never the `<li>` or the `<article>` around it,
9
+ because the container is `ItemList`'s decision (a list item in one mode, a
10
+ feed article in the other) and a component that rendered both would have to
11
+ know which.
12
+
13
+ ARIA contract: the title is a real link, resolved through the components
14
+ context, and it is the row's only interactive element — the subtitle and
15
+ the badges are text beside it, not a second target. `headingLevel` promotes
16
+ the title to a real heading for a list of sections a reader navigates by
17
+ headings; by default it is not one, because twenty `<h3>`s in a filter
18
+ result is worse for navigation than none.
19
+ -->
20
+ <script lang="ts">import Badge from "../ui/Badge.svelte";
21
+ import Link from "../ui/Link.svelte";
22
+ let { href, title, subtitle, badges, headingLevel, meta, class: className, ...rest } = $props();
23
+ </script>
24
+
25
+ <div {...rest} class={['flex flex-col gap-1', className]}>
26
+ {#if headingLevel === undefined}
27
+ <Link {href} variant="muted" class="font-medium">{title}</Link>
28
+ {:else}
29
+ <svelte:element this={`h${headingLevel}`} class="text-base font-medium">
30
+ <Link {href} variant="muted">{title}</Link>
31
+ </svelte:element>
32
+ {/if}
33
+ {#if subtitle !== undefined}
34
+ <p class="text-base-content/70 text-sm">{subtitle}</p>
35
+ {/if}
36
+ {#if meta}
37
+ <div class="text-base-content/60 text-sm">{@render meta()}</div>
38
+ {/if}
39
+ {#if badges !== undefined && badges.length > 0}
40
+ <div class="flex flex-wrap gap-1">
41
+ {#each badges as badge (badge)}
42
+ <Badge variant="outline" size="sm">{badge}</Badge>
43
+ {/each}
44
+ </div>
45
+ {/if}
46
+ </div>
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { HeadingLevel } from '../ui/tones.ts';
4
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children'> {
5
+ /** Where the title links to; resolved through the components context. */
6
+ href: string;
7
+ /** The linked title; rendered as a heading when `headingLevel` is set. */
8
+ title: string;
9
+ /** A line of context under the title — an owner, a parent, a date. */
10
+ subtitle?: string | undefined;
11
+ /** Short markers rendered as badges. */
12
+ badges?: readonly string[] | undefined;
13
+ /** Render the title as a real heading at this level. */
14
+ headingLevel?: HeadingLevel | undefined;
15
+ /** Extra detail under the subtitle — formatted dates, counts, a second link. */
16
+ meta?: Snippet<[]> | undefined;
17
+ }
18
+ /** The row body most lists want: a linked title, a line of context under it, and short badges. */
19
+ declare const LinkListItem: import("svelte").Component<Props, {}, "">;
20
+ type LinkListItem = ReturnType<typeof LinkListItem>;
21
+ export default LinkListItem;
@@ -0,0 +1,136 @@
1
+ <!-- @component
2
+ Offset-based page navigation: numbered links with gaps or a bare Previous/Next pair, as links (`href`) or callbacks (`onPage`).
3
+ -->
4
+ <!--
5
+ Pagination — one component for the five pagers the four apps had grown:
6
+ numbered page links with gaps, a bare Previous/Next pair, the link form and
7
+ the callback form.
8
+
9
+ Two axes, and that is the whole design. `variant` chooses how much is
10
+ drawn — `window` renders first / last / the current page's neighbours,
11
+ `prevNext` renders the two arms and nothing else, which is what a listing
12
+ that only knows "there is more" can honestly offer. `href` versus `onPage`
13
+ chooses what the controls ARE: given `href` they are real links, which keeps
14
+ Ctrl-click, middle-click and "copy link address" and works with no
15
+ JavaScript at all; given `onPage` they are buttons, for a pager over state
16
+ the URL does not carry. `href` wins when both are passed.
17
+
18
+ ARIA contract: a `<nav>` named by `label`, holding a list of controls. The
19
+ arms carry `rel="prev"` / `rel="next"`. An unavailable arm is an
20
+ `aria-disabled` `<span>` — never a link to nowhere, and never removed, since
21
+ a pager whose Previous disappears on page one slides Next under the reader's
22
+ cursor. The current page is a `<span>` with `aria-current="page"` rather
23
+ than a link to where you already are; every numbered control is named
24
+ "Page 8" through `pageLabel`, because a bare digit is not an accessible
25
+ name; and the gap marker is `aria-hidden`, because "…" is not information.
26
+
27
+ In `window` mode a single page renders nothing at all — there is nothing to
28
+ navigate — unless `showRange` asked for the range line, which is a fact
29
+ about the result and still true. In `prevNext` mode both arms stay, disabled:
30
+ that variant is drawn by a caller that wants its control bar to hold place.
31
+ -->
32
+ <script lang="ts">import { ChevronLeft, ChevronRight } from "@lucide/svelte";
33
+ import { getComponentsContext } from "../context.svelte.js";
34
+ import { buttonSize } from "../ui/tones.js";
35
+ import { pageWindow } from "./pagination.js";
36
+ let { offset, limit, total, variant = "window", href, onPage, siblings = 1, label = "Pagination", previousLabel = "Previous", nextLabel = "Next", pageLabel = (page) => `Page ${page}`, size = "sm", showRange = false, rangeLabel, statusLabel, locales = getComponentsContext().locale(), class: className, ...rest } = $props();
37
+ const context = getComponentsContext();
38
+ const view = $derived(pageWindow({
39
+ offset,
40
+ limit
41
+ }, total, siblings));
42
+ // The formatter and the formatted string are separate `$derived`s so the
43
+ // (relatively expensive) `Intl.NumberFormat` is not rebuilt when only the
44
+ // offset moves. Counts are quantities and get grouped; the page NUMBERS
45
+ // below are identifiers and stay bare.
46
+ const counts = $derived(new Intl.NumberFormat(locales));
47
+ const rangeText = $derived(rangeLabel === undefined ? `Showing ${counts.format(view.from)}–${counts.format(view.to)} of ${counts.format(total)}` : rangeLabel(view.from, view.to, total));
48
+ /** A target offset's href, resolved through the context. */
49
+ const linkTo = (target) => href === undefined ? undefined : context.href(href(target));
50
+ const itemClass = $derived(["btn join-item", buttonSize[size]]);
51
+ const inertClass = $derived(["btn btn-disabled join-item", buttonSize[size]]);
52
+ </script>
53
+
54
+ {#snippet armBody(text: string, leading: boolean)}
55
+ {#if leading}<ChevronLeft class="size-4" aria-hidden="true" />{/if}
56
+ <span>{text}</span>
57
+ {#if !leading}<ChevronRight class="size-4" aria-hidden="true" />{/if}
58
+ {/snippet}
59
+
60
+ {#snippet arm(target: number | undefined, text: string, rel: 'prev' | 'next', leading: boolean)}
61
+ <li>
62
+ {#if target === undefined}
63
+ <span class={inertClass} aria-disabled="true">{@render armBody(text, leading)}</span>
64
+ {:else if href !== undefined}
65
+ <a href={linkTo(target)} {rel} class={itemClass}>{@render armBody(text, leading)}</a>
66
+ {:else}
67
+ <button
68
+ type="button"
69
+ class={itemClass}
70
+ onclick={() => {
71
+ onPage?.(target);
72
+ }}
73
+ >
74
+ {@render armBody(text, leading)}
75
+ </button>
76
+ {/if}
77
+ </li>
78
+ {/snippet}
79
+
80
+ {#if variant === 'prevNext' || view.pageCount > 1}
81
+ <nav
82
+ {...rest}
83
+ aria-label={label}
84
+ class={['flex flex-col items-center gap-2 sm:flex-row sm:justify-between', className]}
85
+ >
86
+ {#if showRange}
87
+ <p class="text-base-content/70 text-sm tabular-nums">{rangeText}</p>
88
+ {/if}
89
+ <ul class="join">
90
+ {@render arm(view.previous, previousLabel, 'prev', true)}
91
+ {#if variant === 'prevNext'}
92
+ {#if statusLabel !== undefined}
93
+ <li>
94
+ <span class={['btn btn-ghost join-item pointer-events-none', buttonSize[size]]}>
95
+ {statusLabel(view.page, view.pageCount)}
96
+ </span>
97
+ </li>
98
+ {/if}
99
+ {:else}
100
+ {#each view.items as item (item.kind === 'page' ? item.page : `gap-${item.after}`)}
101
+ <li>
102
+ {#if item.kind === 'gap'}
103
+ <span class={inertClass} aria-hidden="true">…</span>
104
+ {:else if item.current}
105
+ <span class={['btn btn-active join-item', buttonSize[size]]} aria-current="page">
106
+ <span class="sr-only">{pageLabel(item.page)}</span>
107
+ <span aria-hidden="true">{item.page}</span>
108
+ </span>
109
+ {:else if href !== undefined}
110
+ <a href={linkTo(item.offset)} class={itemClass} aria-label={pageLabel(item.page)}>
111
+ <span aria-hidden="true">{item.page}</span>
112
+ </a>
113
+ {:else}
114
+ <button
115
+ type="button"
116
+ class={itemClass}
117
+ aria-label={pageLabel(item.page)}
118
+ onclick={() => {
119
+ onPage?.(item.offset);
120
+ }}
121
+ >
122
+ <span aria-hidden="true">{item.page}</span>
123
+ </button>
124
+ {/if}
125
+ </li>
126
+ {/each}
127
+ {/if}
128
+ {@render arm(view.next, nextLabel, 'next', false)}
129
+ </ul>
130
+ </nav>
131
+ {:else if showRange}
132
+ <!-- One page, so there is nothing to navigate — but the range line is a
133
+ fact about the result, not about the pager, and stays. A <nav> around
134
+ it alone would be a landmark with no controls in it. -->
135
+ <p {...rest} class={['text-base-content/70 text-sm tabular-nums', className]}>{rangeText}</p>
136
+ {/if}