@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,26 @@
1
+ <!-- @component
2
+ A `mailto:` link whose subject, body, cc and bcc are correctly encoded.
3
+ -->
4
+ <!--
5
+ EmailLink — a `mailto:` anchor, its query string (`subject`/`body`/`cc`/
6
+ `bcc`) built with `URLSearchParams` so every value is correctly encoded.
7
+
8
+ ARIA contract: none beyond the native `<a>` — the visible text (the address
9
+ by default, or `children` when given) is the accessible name.
10
+ -->
11
+ <script lang="ts">let { email, subject, body, cc, bcc, children, class: className, ...rest } = $props();
12
+ const href = $derived.by(() => {
13
+ const params = new URLSearchParams();
14
+ if (subject) params.set("subject", subject);
15
+ if (body) params.set("body", body);
16
+ if (cc) params.set("cc", cc);
17
+ if (bcc) params.set("bcc", bcc);
18
+ const query = params.toString();
19
+ return `mailto:${email}${query ? `?${query}` : ""}`;
20
+ });
21
+ export {};
22
+ </script>
23
+
24
+ <a {...rest} {href} class={['link link-primary link-hover', className]}>
25
+ {#if children}{@render children()}{:else}{email}{/if}
26
+ </a>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLAnchorAttributes, 'children'> {
4
+ /** The address; also the link text unless `children` replaces it. */
5
+ email: string;
6
+ /** Pre-filled subject line. */
7
+ subject?: string | undefined;
8
+ /** Pre-filled message body. */
9
+ body?: string | undefined;
10
+ /** Address to copy. */
11
+ cc?: string | undefined;
12
+ /** Address to blind-copy. */
13
+ bcc?: string | undefined;
14
+ /** Replaces the link text; defaults to the address itself. */
15
+ children?: Snippet<[]> | undefined;
16
+ }
17
+ /** A `mailto:` link whose subject, body, cc and bcc are correctly encoded. */
18
+ declare const EmailLink: import("svelte").Component<Props, {}, "">;
19
+ type EmailLink = ReturnType<typeof EmailLink>;
20
+ export default EmailLink;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * links — see the package README for the family reference.
3
+ *
4
+ * @module
5
+ */
6
+ export { default as EmailLink } from './EmailLink.svelte';
7
+ export { default as ContextLinks, type ContextLinkItem } from './ContextLinks.svelte';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * links — see the package README for the family reference.
3
+ *
4
+ * @module
5
+ */
6
+ export { default as EmailLink } from './EmailLink.svelte';
7
+ export { default as ContextLinks } from './ContextLinks.svelte';
@@ -0,0 +1,26 @@
1
+ <!-- @component
2
+ A `<figure>` with an optional `<figcaption>`, for media that needs a caption.
3
+ -->
4
+ <!--
5
+ Figure — a `<figure>` with an optional `<figcaption>`, for media that needs
6
+ a caption associated with it (an image, a video, a diagram).
7
+
8
+ ARIA contract: none beyond the native grouping — `<figure>`/`<figcaption>`
9
+ already associate the caption with its content for assistive technology.
10
+ -->
11
+ <script lang="ts">let { caption, children, class: className, ...rest } = $props();
12
+ export {};
13
+ </script>
14
+
15
+ <figure {...rest} class={className}>
16
+ {@render children()}
17
+ {#if caption}
18
+ <figcaption>
19
+ {#if typeof caption === 'string'}
20
+ {caption}
21
+ {:else}
22
+ {@render caption()}
23
+ {/if}
24
+ </figcaption>
25
+ {/if}
26
+ </figure>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLAttributes<HTMLElement> {
4
+ /** Rendered as `<figcaption>`; a plain string or a snippet for richer markup. */
5
+ caption?: string | Snippet<[]> | undefined;
6
+ /** The image, video or diagram the caption describes. */
7
+ children: Snippet<[]>;
8
+ }
9
+ /** A `<figure>` with an optional `<figcaption>`, for media that needs a caption. */
10
+ declare const Figure: import("svelte").Component<Props, {}, "">;
11
+ type Figure = ReturnType<typeof Figure>;
12
+ export default Figure;
@@ -0,0 +1,76 @@
1
+ <!-- @component
2
+ A responsive `<img>` from a URL or an uploaded image id, optionally wrapped in `<picture>`. `decoding` defaults to `'async'`.
3
+ -->
4
+ <!--
5
+ Image — a responsive `<img>`, optionally wrapped in `<picture>`.
6
+
7
+ Two ways to get a `srcset`: pass `src` directly for a plain image, or pass
8
+ `id` (+ optionally `base`/`widths`) for a width-bucketed rendition served
9
+ by an image-transform route (`${base}/${id}?w=${w}`). Requesting an arbitrary width
10
+ would defeat that bucketing (every distinct width is a distinct transform
11
+ and a distinct cache entry), so `widths` is a fixed list, not a free number.
12
+
13
+ `sources` adds art-direction / format-negotiation `<source>` elements ahead
14
+ of the `<img>` fallback, wrapping everything in `<picture>` — each source's
15
+ own `srcset` is the caller's (e.g. an AVIF/WebP rendition list), unrelated
16
+ to the `id`-bucketing above.
17
+
18
+ `width`/`height` reserve the box before the bytes arrive — the single most
19
+ visible layout-shift regression a media-heavy page can ship — so pass them
20
+ whenever they are known.
21
+
22
+ ARIA contract: `alt` is required and not defaulted — a caller with no
23
+ meaningful text must pass `""` (decorative) explicitly, which is a
24
+ different, deliberate claim from having forgotten it.
25
+ -->
26
+ <script lang="ts">const DEFAULT_WIDTHS = [
27
+ 320,
28
+ 640,
29
+ 960,
30
+ 1280,
31
+ 1920
32
+ ];
33
+ let { alt, src, id, base = "/images", widths = DEFAULT_WIDTHS, sources, width, height, sizes = "100vw", eager = false, decoding = "async", ...rest } = $props();
34
+ const bucketedHref = $derived(id === undefined ? undefined : `${base}/${id}`);
35
+ const bucketedSrcset = $derived(bucketedHref === undefined ? undefined : widths.map((w) => `${bucketedHref}?w=${w} ${w}w`).join(", "));
36
+ // The largest bucket as the plain `src`: the fallback a browser without
37
+ // `srcset` support (and every crawler) fetches.
38
+ const fallbackWidth = $derived(widths[widths.length - 1]);
39
+ const resolvedSrc = $derived(src ?? (bucketedHref === undefined ? undefined : `${bucketedHref}?w=${fallbackWidth}`));
40
+ const loading = $derived(eager ? "eager" : "lazy");
41
+ const fetchpriority = $derived(eager ? "high" : undefined);
42
+ export {};
43
+ </script>
44
+
45
+ {#if sources && sources.length > 0}
46
+ <picture>
47
+ {#each sources as source (source.srcset)}
48
+ <source srcset={source.srcset} type={source.type} media={source.media} />
49
+ {/each}
50
+ <img
51
+ {...rest}
52
+ src={resolvedSrc}
53
+ srcset={bucketedSrcset}
54
+ {sizes}
55
+ {alt}
56
+ {width}
57
+ {height}
58
+ {loading}
59
+ {fetchpriority}
60
+ {decoding}
61
+ />
62
+ </picture>
63
+ {:else}
64
+ <img
65
+ {...rest}
66
+ src={resolvedSrc}
67
+ srcset={bucketedSrcset}
68
+ {sizes}
69
+ {alt}
70
+ {width}
71
+ {height}
72
+ {loading}
73
+ {fetchpriority}
74
+ {decoding}
75
+ />
76
+ {/if}
@@ -0,0 +1,41 @@
1
+ import type { HTMLImgAttributes } from 'svelte/elements';
2
+ interface ImageSource {
3
+ srcset: string;
4
+ type?: string | undefined;
5
+ media?: string | undefined;
6
+ }
7
+ interface Props extends HTMLImgAttributes {
8
+ /** Alternative text; `''` only where the image is decoration. */
9
+ alt: string;
10
+ /** A plain image URL. Mutually exclusive with `id`; `src` wins if both are given. */
11
+ src?: string | undefined;
12
+ /** An uploaded image's id — combined with `base`/`widths` into a bucketed `srcset`. */
13
+ id?: string | undefined;
14
+ /**
15
+ * Route prefix the bucketed rendition lives under.
16
+ * @defaultValue `'/images'`
17
+ */
18
+ base?: string | undefined;
19
+ /**
20
+ * The width buckets the transform route serves. Must match its own list.
21
+ * @defaultValue `[320, 640, 960, 1280, 1920]`
22
+ */
23
+ widths?: readonly number[] | undefined;
24
+ /** Extra `<source>` elements (art direction / format negotiation) ahead of the `<img>` fallback. Wraps everything in `<picture>`. */
25
+ sources?: readonly ImageSource[] | undefined;
26
+ /** Intrinsic width; reserves the box against layout shift. */
27
+ width?: number | string | undefined;
28
+ /** Intrinsic height; reserves the box against layout shift. */
29
+ height?: number | string | undefined;
30
+ /**
31
+ * The `sizes` hint.
32
+ * @defaultValue `'100vw'`
33
+ */
34
+ sizes?: string | undefined;
35
+ /** Eager-load this image. Use for the one image above the fold; everything else stays lazy. */
36
+ eager?: boolean | undefined;
37
+ }
38
+ /** A responsive `<img>` from a URL or an uploaded image id, optionally wrapped in `<picture>`. `decoding` defaults to `'async'`. */
39
+ declare const Image: import("svelte").Component<Props, {}, "">;
40
+ type Image = ReturnType<typeof Image>;
41
+ export default Image;
@@ -0,0 +1,65 @@
1
+ <!-- @component
2
+ An HLS video player (Cloudflare Stream shape) with a placeholder while the upload is still pending or processing.
3
+ -->
4
+ <!--
5
+ Video — the player for an HLS-served video (Cloudflare Stream shape:
6
+ manifest URL + poster + an upload lifecycle).
7
+
8
+ Deliberately a plain `<video>` with the HLS manifest as its source and the
9
+ generated thumbnail as its poster. That plays natively in Safari and on
10
+ iOS; everywhere else it needs an MSE shim (e.g. hls.js), which is an
11
+ app-side concern and stays out of this package — it is a real dependency
12
+ weight that must never land in the always-loaded closure of a package every
13
+ page imports. A caller that wants universal playback attaches the shim to
14
+ the element handed to `onReady`.
15
+
16
+ While the upload is still pending/processing there is no manifest at all,
17
+ so the component renders the poster (or a neutral placeholder) with the
18
+ state spelled out via `statusLabel` rather than an empty player that
19
+ silently fails.
20
+
21
+ ARIA contract: `title` is the player's accessible name (`aria-label`); the
22
+ non-playable placeholder's `statusLabel` is a `badge-neutral`, visible text,
23
+ not conveyed through colour alone.
24
+ -->
25
+ <script lang="ts">let { hlsUrl, posterUrl, title, width, height, uploadState = "ready", statusLabel, onReady, captionsSrc, class: className } = $props();
26
+ let element = $state();
27
+ const playable = $derived(uploadState === "ready" && hlsUrl !== undefined);
28
+ $effect(() => {
29
+ if (element && playable) onReady?.(element);
30
+ });
31
+ </script>
32
+
33
+ <div class={['bg-base-200 rounded-box overflow-hidden', className]}>
34
+ {#if playable}
35
+ <video
36
+ bind:this={element}
37
+ controls
38
+ preload="none"
39
+ poster={posterUrl}
40
+ {width}
41
+ {height}
42
+ aria-label={title}
43
+ class="aspect-video h-auto w-full max-w-full"
44
+ >
45
+ <source src={hlsUrl} type="application/x-mpegURL" />
46
+ {#if captionsSrc}
47
+ <track kind="captions" src={captionsSrc} />
48
+ {:else}
49
+ <!-- A `<track>` is required for a caption-less video to be valid; an
50
+ empty default track is the honest statement that none exists yet,
51
+ rather than silently shipping a video with no captions option. -->
52
+ <track kind="captions" />
53
+ {/if}
54
+ </video>
55
+ {:else}
56
+ <div class="aspect-video relative flex w-full items-center justify-center">
57
+ {#if posterUrl}
58
+ <img src={posterUrl} alt="" class="h-full w-full object-cover opacity-60" />
59
+ {/if}
60
+ {#if statusLabel}
61
+ <span class="badge badge-neutral absolute">{statusLabel}</span>
62
+ {/if}
63
+ </div>
64
+ {/if}
65
+ </div>
@@ -0,0 +1,29 @@
1
+ interface Props {
2
+ /** The HLS manifest URL; absent until the encode finishes. */
3
+ hlsUrl?: string | undefined;
4
+ /** The poster frame. */
5
+ posterUrl?: string | undefined;
6
+ /** Accessible name for the player. */
7
+ title: string;
8
+ /** Intrinsic dimensions, when known — reserves the box against layout shift. */
9
+ width?: number | undefined;
10
+ /** Intrinsic height, when known. */
11
+ height?: number | undefined;
12
+ /**
13
+ * Where the upload is in its lifecycle.
14
+ * @defaultValue `'ready'`
15
+ */
16
+ uploadState?: 'pending' | 'processing' | 'ready' | 'failed' | undefined;
17
+ /** Copy for the non-playable placeholder state, supplied by the app (this package holds no messages). */
18
+ statusLabel?: string | undefined;
19
+ /** Called with the mounted `<video>` element once it is playable, for a caller attaching an MSE shim. */
20
+ onReady?: ((element: HTMLVideoElement) => void) | undefined;
21
+ /** A captions track URL (WebVTT). When absent, an empty track is still rendered — see below. */
22
+ captionsSrc?: string | undefined;
23
+ /** Extra classes, merged last. */
24
+ class?: string | undefined;
25
+ }
26
+ /** An HLS video player (Cloudflare Stream shape) with a placeholder while the upload is still pending or processing. */
27
+ declare const Video: import("svelte").Component<Props, {}, "">;
28
+ type Video = ReturnType<typeof Video>;
29
+ export default Video;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Reading an image file's intrinsic pixel dimensions in the browser.
3
+ *
4
+ * A form that uploads an image wants its `width` / `height` alongside the
5
+ * binary, so the page that later renders it can reserve the box on first
6
+ * paint instead of reflowing when it loads — and the browser can decode the
7
+ * file for free the moment it is chosen, where a server would have to.
8
+ * `fields/ImageInput` measures with this; a caller with its own file control
9
+ * uses it directly.
10
+ *
11
+ * @module
12
+ */
13
+ /** An image's intrinsic size, in pixels. */
14
+ export interface ImageDimensions {
15
+ readonly width: number;
16
+ readonly height: number;
17
+ }
18
+ /** Thrown by {@link imageDimensions} where there is no `createImageBitmap` (on a server). */
19
+ export declare class ImageDimensionsUnavailable extends Error {
20
+ readonly name = "ImageDimensionsUnavailable";
21
+ }
22
+ /**
23
+ * Decode `file` with `createImageBitmap` and return its pixel size.
24
+ *
25
+ * Rejects with {@link ImageDimensionsUnavailable} outside a browser, and with
26
+ * the browser's own error when the file does not decode as an image.
27
+ */
28
+ export declare function imageDimensions(file: Blob): Promise<ImageDimensions>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Reading an image file's intrinsic pixel dimensions in the browser.
3
+ *
4
+ * A form that uploads an image wants its `width` / `height` alongside the
5
+ * binary, so the page that later renders it can reserve the box on first
6
+ * paint instead of reflowing when it loads — and the browser can decode the
7
+ * file for free the moment it is chosen, where a server would have to.
8
+ * `fields/ImageInput` measures with this; a caller with its own file control
9
+ * uses it directly.
10
+ *
11
+ * @module
12
+ */
13
+ /** Thrown by {@link imageDimensions} where there is no `createImageBitmap` (on a server). */
14
+ export class ImageDimensionsUnavailable extends Error {
15
+ name = 'ImageDimensionsUnavailable';
16
+ }
17
+ /**
18
+ * Decode `file` with `createImageBitmap` and return its pixel size.
19
+ *
20
+ * Rejects with {@link ImageDimensionsUnavailable} outside a browser, and with
21
+ * the browser's own error when the file does not decode as an image.
22
+ */
23
+ export async function imageDimensions(file) {
24
+ if (typeof globalThis.createImageBitmap !== 'function') {
25
+ throw new ImageDimensionsUnavailable('imageDimensions needs createImageBitmap, which only a browser has.');
26
+ }
27
+ const bitmap = await globalThis.createImageBitmap(file);
28
+ try {
29
+ return { width: bitmap.width, height: bitmap.height };
30
+ }
31
+ finally {
32
+ bitmap.close();
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * media — images, video and figures. See the package README for the family
3
+ * reference.
4
+ *
5
+ * @module
6
+ */
7
+ export { default as Image } from './Image.svelte';
8
+ export { default as Video } from './Video.svelte';
9
+ export { default as Figure } from './Figure.svelte';
10
+ export { imageDimensions, ImageDimensionsUnavailable, type ImageDimensions, } from './image-dimensions.ts';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * media — images, video and figures. See the package README for the family
3
+ * reference.
4
+ *
5
+ * @module
6
+ */
7
+ export { default as Image } from './Image.svelte';
8
+ export { default as Video } from './Video.svelte';
9
+ export { default as Figure } from './Figure.svelte';
10
+ export { imageDimensions, ImageDimensionsUnavailable, } from "./image-dimensions.js";
@@ -0,0 +1,257 @@
1
+ <!-- @component
2
+ The ⌘K / Ctrl+K command palette: bits-ui’s `Command` inside this library’s `Dialog`.
3
+ -->
4
+ <!--
5
+ CommandPalette — the ⌘K / Ctrl+K palette: bits-ui's `Command` inside this
6
+ library's `Dialog`.
7
+
8
+ Two sources of rows, either of which may be absent:
9
+
10
+ - **The hotkey registry**, when one is mounted above. `paletteActions` is
11
+ already role-filtered and in registration order, and each row shows its
12
+ trigger as a `HotkeyBadge`, so the palette teaches the keyboard while it
13
+ is used with the mouse. The palette also binds its open state to the
14
+ registry's `paletteOpen`, which is what a registered `$mod+k` action
15
+ toggles.
16
+ - **`items` and `onSearch`**, which need no registry at all. That is what
17
+ makes this usable in an app that wants a palette but not a hotkey system.
18
+
19
+ The search lifecycle is `fields/Combobox`'s, for the same reasons: a
20
+ keystroke does not start a request, `debounceMs` of quiet does; each request
21
+ gets an `AbortController` and starting a new one aborts the last, so a slow
22
+ first response cannot overwrite a fast second one; and the previous rows
23
+ stay on screen while the next request is in flight, because clearing on
24
+ every keystroke makes the list strobe and costs the reader the row they were
25
+ about to pick.
26
+
27
+ `shouldFilter` is off exactly when `onSearch` is given: async rows are
28
+ already the server's answer to the query, and bits-ui re-filtering them
29
+ would hide rows whose match is in a field the label doesn't show. Without
30
+ `onSearch`, bits-ui's own scoring is better than anything here. Registry and
31
+ `items` rows are filtered locally by label in the async case, so they do not
32
+ outlive the query either.
33
+
34
+ ARIA contract: `Dialog`'s (bits-ui: `aria-modal`, the focus trap, Escape,
35
+ the focus return) plus `Command`'s — the input is a `combobox` owning a
36
+ `listbox`, each row an `option`, `aria-activedescendant` follows the arrow
37
+ keys and Enter selects. `label` names the command menu for assistive
38
+ technology and also titles the dialog. The pending row is
39
+ `Command.Loading`, named by `loadingLabel`, so a reader is told the list is
40
+ still settling instead of hearing an empty list.
41
+ -->
42
+ <script lang="ts">import { Command } from "bits-ui";
43
+ import { untrack } from "svelte";
44
+ import { getComponentsContext } from "../context.svelte.js";
45
+ import Dialog from "../feedback/Dialog.svelte";
46
+ import HotkeyBadge from "../hotkeys/HotkeyBadge.svelte";
47
+ import { tryGetHotkeyRegistry } from "../hotkeys/context.svelte.js";
48
+ import Badge from "../ui/Badge.svelte";
49
+ import { defaultCommandGroups } from "./types.js";
50
+ let { open = $bindable(false), label = "Command palette", placeholder = "Type a command or search…", loadingLabel = "Searching…", emptyLabel = "No results", closeLabel = "Close", groups = defaultCommandGroups, items, onSearch, debounceMs = 150, onSelect, item } = $props();
51
+ const context = getComponentsContext();
52
+ const registry = tryGetHotkeyRegistry();
53
+ let query = $state("");
54
+ const trimmedQuery = $derived(query.trim());
55
+ // ── Open state ───────────────────────────────────────────────────
56
+ //
57
+ // With a registry, `paletteOpen` and `open` are two halves of one value:
58
+ // the registered `$mod+k` action writes the registry, a caller may write
59
+ // the prop, and both have to end up agreeing. The trackers below make the
60
+ // sync converge — whichever side changed is copied to the other and both
61
+ // trackers are updated, so the effect's own writes find nothing to do on
62
+ // the re-run.
63
+ let lastOpen = open;
64
+ let lastPaletteOpen = registry?.paletteOpen ?? open;
65
+ $effect(() => {
66
+ if (registry === undefined) return;
67
+ const fromProp = open;
68
+ const fromRegistry = registry.paletteOpen;
69
+ if (fromProp !== lastOpen) {
70
+ lastOpen = fromProp;
71
+ lastPaletteOpen = fromProp;
72
+ registry.paletteOpen = fromProp;
73
+ } else if (fromRegistry !== lastPaletteOpen) {
74
+ lastPaletteOpen = fromRegistry;
75
+ lastOpen = fromRegistry;
76
+ open = fromRegistry;
77
+ }
78
+ });
79
+ // bits-ui's `Dialog` moves focus to the first tabbable child as it opens,
80
+ // which here is the dialog's own close button. A palette has to open with
81
+ // the cursor in its query box — anything else costs the reader a Tab before
82
+ // they can type, and leaves the arrow keys (which `Command` handles on the
83
+ // input) doing nothing. Hence the frame's delay: focusing before the
84
+ // dialog's own focus move would simply be undone.
85
+ let input = $state(null);
86
+ $effect(() => {
87
+ const element = input;
88
+ if (!open || element === null) return;
89
+ const frame = requestAnimationFrame(() => element.focus());
90
+ return () => cancelAnimationFrame(frame);
91
+ });
92
+ const setOpen = (next) => {
93
+ open = next;
94
+ // Closing clears the query: a palette that reopens on the last search is
95
+ // a palette that shows yesterday's answer to today's question.
96
+ if (!next) query = "";
97
+ };
98
+ /** In async mode this component owns filtering, so local rows filter here. */
99
+ const matchesQuery = (candidate) => {
100
+ if (onSearch === undefined || trimmedQuery === "") return true;
101
+ return candidate.label.toLowerCase().includes(trimmedQuery.toLowerCase());
102
+ };
103
+ const registryRows = $derived((registry?.paletteActions ?? []).map((entry) => ({
104
+ item: {
105
+ id: entry.action.id,
106
+ label: entry.action.label,
107
+ group: entry.action.group,
108
+ run: entry.action.run
109
+ },
110
+ trigger: entry.action.trigger
111
+ })));
112
+ const staticRows = $derived((items ?? []).map((candidate) => ({
113
+ item: candidate,
114
+ trigger: undefined
115
+ })));
116
+ let results = $state([]);
117
+ const resultRows = $derived(results.map((candidate) => ({
118
+ item: candidate,
119
+ trigger: undefined
120
+ })));
121
+ const rows = $derived([
122
+ ...registryRows,
123
+ ...staticRows,
124
+ ...resultRows
125
+ ].filter((row) => matchesQuery(row.item)));
126
+ const sections = $derived(groups.map((group) => ({
127
+ group,
128
+ rows: rows.filter((row) => row.item.group === group.id)
129
+ })).filter((section) => section.rows.length > 0));
130
+ // ── Async search ─────────────────────────────────────────────────
131
+ let pending = $state(false);
132
+ let timer;
133
+ let inFlight;
134
+ const search = (text) => {
135
+ if (onSearch === undefined) return;
136
+ inFlight?.abort();
137
+ const own = new AbortController();
138
+ inFlight = own;
139
+ pending = true;
140
+ void onSearch(text, own.signal).then((next) => {
141
+ // A superseded request must not write: `inFlight` is the identity
142
+ // check, `signal.aborted` catches the caller aborting its own work.
143
+ if (own.signal.aborted || inFlight !== own) return;
144
+ results = next;
145
+ pending = false;
146
+ }).catch(() => {
147
+ if (own.signal.aborted || inFlight !== own) return;
148
+ pending = false;
149
+ });
150
+ };
151
+ const cancel = () => {
152
+ if (timer !== undefined) clearTimeout(timer);
153
+ timer = undefined;
154
+ inFlight?.abort();
155
+ inFlight = undefined;
156
+ };
157
+ const emit = (text) => {
158
+ if (onSearch === undefined) return;
159
+ if (text === "") {
160
+ cancel();
161
+ pending = false;
162
+ results = [];
163
+ return;
164
+ }
165
+ if (timer !== undefined) clearTimeout(timer);
166
+ timer = setTimeout(() => {
167
+ timer = undefined;
168
+ search(text);
169
+ }, debounceMs);
170
+ };
171
+ // Emit only while the palette is open; closing it emits `''`, which resets
172
+ // the rows and cancels anything in flight.
173
+ $effect(() => {
174
+ const text = open ? trimmedQuery : "";
175
+ untrack(() => emit(text));
176
+ });
177
+ $effect(() => () => cancel());
178
+ const isEmpty = $derived(trimmedQuery.length > 0 && !pending && sections.length === 0);
179
+ // ── Activation ───────────────────────────────────────────────────
180
+ const activate = (candidate) => {
181
+ setOpen(false);
182
+ onSelect?.(candidate);
183
+ if (candidate.run !== undefined) void candidate.run();
184
+ if (candidate.href !== undefined) void context.navigate(context.href(candidate.href));
185
+ };
186
+ </script>
187
+
188
+ {#snippet row(entry: Row)}
189
+ <Command.Item
190
+ value={`${entry.item.group}:${entry.item.id}`}
191
+ keywords={[entry.item.label]}
192
+ onSelect={() => activate(entry.item)}
193
+ class="data-selected:bg-base-200 flex cursor-pointer items-center justify-between gap-4 rounded px-3 py-2"
194
+ >
195
+ {#if item}
196
+ {@render item(entry.item)}
197
+ {:else}
198
+ <span class="flex min-w-0 items-baseline gap-2">
199
+ <span class="truncate text-sm">{entry.item.label}</span>
200
+ {#if entry.item.hint !== undefined}
201
+ <span class="text-base-content/60 truncate text-xs">{entry.item.hint}</span>
202
+ {/if}
203
+ </span>
204
+ {#if entry.trigger !== undefined}
205
+ <HotkeyBadge trigger={entry.trigger} />
206
+ {:else if entry.item.badge !== undefined}
207
+ <Badge size="sm">{entry.item.badge}</Badge>
208
+ {/if}
209
+ {/if}
210
+ </Command.Item>
211
+ {/snippet}
212
+
213
+ <Dialog bind:open={() => open, setOpen} title={label} {closeLabel}>
214
+ <Command.Root shouldFilter={onSearch === undefined} loop {label} class="flex flex-col">
215
+ <Command.Input
216
+ bind:ref={input}
217
+ bind:value={query}
218
+ {placeholder}
219
+ class="input border-base-300 w-full rounded-none border-0 border-b focus:outline-none"
220
+ />
221
+ <Command.List class="max-h-96 overflow-x-hidden overflow-y-auto pt-2">
222
+ <Command.Viewport>
223
+ {#each sections as section (section.group.id)}
224
+ <Command.Group value={section.group.id}>
225
+ <Command.GroupHeading
226
+ class="text-base-content/70 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide uppercase"
227
+ >
228
+ {section.group.label}
229
+ </Command.GroupHeading>
230
+ <Command.GroupItems>
231
+ {#each section.rows as entry (`${entry.item.group}:${entry.item.id}`)}
232
+ {@render row(entry)}
233
+ {/each}
234
+ </Command.GroupItems>
235
+ </Command.Group>
236
+ {/each}
237
+
238
+ {#if pending && trimmedQuery.length > 0}
239
+ <!-- Through `child` so `aria-label` lands AFTER bits-ui's own
240
+ props: `Command.Loading` hard-codes the English "Loading...",
241
+ and in this library the name of anything is a prop. -->
242
+ <Command.Loading>
243
+ {#snippet child({ props })}
244
+ <div {...props} aria-label={loadingLabel} class="text-base-content/60 px-3 py-2 text-sm">
245
+ {loadingLabel}
246
+ </div>
247
+ {/snippet}
248
+ </Command.Loading>
249
+ {/if}
250
+
251
+ {#if isEmpty}
252
+ <p class="text-base-content/60 px-3 py-6 text-center text-sm">{emptyLabel}</p>
253
+ {/if}
254
+ </Command.Viewport>
255
+ </Command.List>
256
+ </Command.Root>
257
+ </Dialog>