@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,54 @@
1
+ <!-- @component
2
+ A daisyUI `menu` as either a labelled navigation list (`mode="nav"`) or a bits-ui dropdown (`mode="dropdown"`).
3
+ -->
4
+ <!--
5
+ Menu — one component over the two things daisyUI calls a `menu`, because a
6
+ call site writes the same `<MenuItem>` list for both.
7
+
8
+ `mode="nav"` is site navigation: a `<nav>` landmark around a `<ul
9
+ class="menu">` of links, where the current page marks itself with
10
+ `aria-current="page"`. Links, list, landmark — no widget, no roving focus,
11
+ no Escape. Everything stays reachable with nothing but Tab, which is the
12
+ correct behaviour for navigation and the wrong behaviour for a menu.
13
+
14
+ `mode="dropdown"` is an application menu: bits-ui's `DropdownMenu`, which
15
+ owns `aria-haspopup`/`aria-expanded` on the trigger, arrow-key roving
16
+ focus, typeahead, Escape to close and focus return. The daisyUI `menu`
17
+ styling is applied to a `<ul>` INSIDE the content; that list and its items'
18
+ `<li>` wrappers carry `role="none"` so the presentation layer does not come
19
+ between `role="menu"` and its `role="menuitem"` children in the
20
+ accessibility tree.
21
+
22
+ The mode reaches `MenuItem` through context rather than a prop, so the two
23
+ can never be set inconsistently. See `menu-context.ts`.
24
+ -->
25
+ <script lang="ts">import { DropdownMenu } from "bits-ui";
26
+ import { setMenuMode } from "./menu-context.js";
27
+ let { mode = "nav", label, open = $bindable(false), side = "bottom", align = "end", class: className, trigger, children } = $props();
28
+ setMenuMode(() => mode);
29
+ </script>
30
+
31
+ {#if mode === 'dropdown'}
32
+ <DropdownMenu.Root bind:open>
33
+ {#if trigger}
34
+ <DropdownMenu.Trigger>
35
+ {#snippet child({ props })}
36
+ {@render trigger({ props })}
37
+ {/snippet}
38
+ </DropdownMenu.Trigger>
39
+ {/if}
40
+ <DropdownMenu.Portal>
41
+ <DropdownMenu.Content {side} {align} sideOffset={8} aria-label={label} class="z-50">
42
+ <ul role="none" class={['menu bg-base-100 rounded-box w-56 p-2 shadow-md', className]}>
43
+ {@render children()}
44
+ </ul>
45
+ </DropdownMenu.Content>
46
+ </DropdownMenu.Portal>
47
+ </DropdownMenu.Root>
48
+ {:else}
49
+ <nav aria-label={label}>
50
+ <ul class={['menu w-full gap-1 p-0', className]}>
51
+ {@render children()}
52
+ </ul>
53
+ </nav>
54
+ {/if}
@@ -0,0 +1,34 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /**
4
+ * `nav` renders a navigation list; `dropdown` a menu behind `trigger`.
5
+ * @defaultValue `'nav'`
6
+ */
7
+ mode?: 'dropdown' | 'nav' | undefined;
8
+ /** Accessible name of the navigation landmark / the open menu. */
9
+ label: string;
10
+ /** Whether the dropdown is open. Bindable; ignored in `nav` mode. */
11
+ open?: boolean | undefined;
12
+ /**
13
+ * Which side of the trigger the dropdown opens on.
14
+ * @defaultValue `'bottom'`
15
+ */
16
+ side?: 'top' | 'right' | 'bottom' | 'left' | undefined;
17
+ /**
18
+ * Alignment against the trigger.
19
+ * @defaultValue `'end'`
20
+ */
21
+ align?: 'start' | 'center' | 'end' | undefined;
22
+ /** Extra classes on the `<ul class="menu">`. */
23
+ class?: string | undefined;
24
+ /** The dropdown's trigger — the caller's own element, spreading `props`. */
25
+ trigger?: Snippet<[{
26
+ props: Record<string, unknown>;
27
+ }]> | undefined;
28
+ /** The `MenuItem`s. */
29
+ children: Snippet<[]>;
30
+ }
31
+ /** A daisyUI `menu` as either a labelled navigation list (`mode="nav"`) or a bits-ui dropdown (`mode="dropdown"`). */
32
+ declare const Menu: import("svelte").Component<Props, {}, "open">;
33
+ type Menu = ReturnType<typeof Menu>;
34
+ export default Menu;
@@ -0,0 +1,75 @@
1
+ <!-- @component
2
+ One entry of a `Menu`, in whichever mode the enclosing `Menu` declared.
3
+ -->
4
+ <!--
5
+ MenuItem — one entry of a `Menu`, in whichever of the two modes the
6
+ enclosing `Menu` declared.
7
+
8
+ In `nav` mode it is a plain `<li><a>`: the current page takes daisyUI's
9
+ `menu-active` AND `aria-current="page"`, because the highlight is a
10
+ colour and the attribute is what says so out loud. `current` overrides the
11
+ context's `isCurrent(href)`, which is what an app without a router-aware
12
+ context uses.
13
+
14
+ In `dropdown` mode it renders through bits-ui's `DropdownMenu.Item` `child`
15
+ snippet, so the element is still the caller's own `<a>` or `<button>` —
16
+ carrying bits-ui's `role="menuitem"`, its focus management and its
17
+ selection handling — rather than a `<div>` pretending to be one. An item
18
+ with an `href` is a link and must stay a link (middle-click, "open in new
19
+ tab"); an item without one is a button.
20
+
21
+ ARIA contract: the `<li>` wrapper is `role="none"` in dropdown mode so it
22
+ does not sit between `role="menu"` and `role="menuitem"`. `disabled` is
23
+ bits-ui's in dropdown mode (skipped by arrow keys, not activatable) and
24
+ `aria-disabled` + no `href` in nav mode, where there is no widget to skip.
25
+ The `icon` snippet is decorative — the label carries the meaning.
26
+ -->
27
+ <script lang="ts">import { DropdownMenu } from "bits-ui";
28
+ import { getComponentsContext } from "../context.svelte.js";
29
+ import { getMenuMode } from "./menu-context.js";
30
+ let { href, onSelect, current, disabled = false, icon, class: className, children } = $props();
31
+ const context = getComponentsContext();
32
+ const mode = getMenuMode();
33
+ const resolved = $derived(href === undefined ? undefined : context.href(href));
34
+ // bits-ui's `onSelect` is not optional-friendly under
35
+ // `exactOptionalPropertyTypes`; a stable wrapper is simpler than a spread.
36
+ const select = () => onSelect?.();
37
+ const isCurrent = $derived(current ?? (resolved === undefined ? false : context.isCurrent(resolved)));
38
+ </script>
39
+
40
+ {#snippet label()}
41
+ {#if icon}<span class="size-5 shrink-0" aria-hidden="true">{@render icon()}</span>{/if}
42
+ {@render children()}
43
+ {/snippet}
44
+
45
+ {#if mode() === 'dropdown'}
46
+ <li role="none" class={className}>
47
+ <DropdownMenu.Item {disabled} onSelect={select}>
48
+ {#snippet child({ props })}
49
+ {#if resolved === undefined}
50
+ <button {...props} type="button">{@render label()}</button>
51
+ {:else}
52
+ <a {...props} href={resolved} aria-current={isCurrent ? 'page' : undefined}>
53
+ {@render label()}
54
+ </a>
55
+ {/if}
56
+ {/snippet}
57
+ </DropdownMenu.Item>
58
+ </li>
59
+ {:else}
60
+ <li class={className}>
61
+ {#if resolved === undefined}
62
+ <button type="button" {disabled} onclick={select}>{@render label()}</button>
63
+ {:else}
64
+ <a
65
+ href={disabled ? undefined : resolved}
66
+ aria-disabled={disabled ? 'true' : undefined}
67
+ aria-current={isCurrent ? 'page' : undefined}
68
+ class:menu-active={isCurrent}
69
+ onclick={select}
70
+ >
71
+ {@render label()}
72
+ </a>
73
+ {/if}
74
+ </li>
75
+ {/if}
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Where the item navigates. Omitted, the item is a button. */
4
+ href?: string | undefined;
5
+ /** Called when the item is chosen. */
6
+ onSelect?: (() => void) | undefined;
7
+ /** Whether this item is the current page; defaults to the context's answer. */
8
+ current?: boolean | undefined;
9
+ /** Shown but not activatable. */
10
+ disabled?: boolean | undefined;
11
+ /** Leading icon. Decorative. */
12
+ icon?: Snippet<[]> | undefined;
13
+ /** Extra classes on the `<li>`. */
14
+ class?: string | undefined;
15
+ /** The item's content: a link or button in `nav` mode, the label in `dropdown` mode. */
16
+ children: Snippet<[]>;
17
+ }
18
+ /** One entry of a `Menu`, in whichever mode the enclosing `Menu` declared. */
19
+ declare const MenuItem: import("svelte").Component<Props, {}, "">;
20
+ type MenuItem = ReturnType<typeof MenuItem>;
21
+ export default MenuItem;
@@ -0,0 +1,26 @@
1
+ <!-- @component
2
+ A rule between groups of `MenuItem`s.
3
+ -->
4
+ <!--
5
+ MenuSeparator — a rule between groups of `MenuItem`s.
6
+
7
+ ARIA contract: a real `role="separator"`, which is what tells a screen
8
+ reader the grouping exists at all; a bare styled `<li>` is a line sighted
9
+ readers can see and nobody else can. In dropdown mode the role comes from
10
+ bits-ui's own separator (which also marks it non-focusable for the menu's
11
+ arrow-key navigation), wrapped in a `role="none"` `<li>` so the
12
+ presentational list item stays out of the accessibility tree.
13
+ -->
14
+ <script lang="ts">import { DropdownMenu } from "bits-ui";
15
+ import { getMenuMode } from "./menu-context.js";
16
+ let { class: className } = $props();
17
+ const mode = getMenuMode();
18
+ </script>
19
+
20
+ {#if mode() === 'dropdown'}
21
+ <li role="none">
22
+ <DropdownMenu.Separator class={['divider my-1 h-px', className]} />
23
+ </li>
24
+ {:else}
25
+ <li role="separator" class={['divider my-1 h-px', className]}></li>
26
+ {/if}
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ /** Extra classes, merged last. */
3
+ class?: string | undefined;
4
+ }
5
+ /** A rule between groups of `MenuItem`s. */
6
+ declare const MenuSeparator: import("svelte").Component<Props, {}, "">;
7
+ type MenuSeparator = ReturnType<typeof MenuSeparator>;
8
+ export default MenuSeparator;
@@ -0,0 +1,63 @@
1
+ <!-- @component
2
+ A small floating panel anchored to a trigger, on bits-ui’s `Popover`.
3
+ -->
4
+ <!--
5
+ Popover — a small floating panel anchored to a trigger, on bits-ui's
6
+ `Popover`.
7
+
8
+ Unlike a tooltip, a popover holds INTERACTIVE content: it takes focus, it
9
+ closes on Escape and on an outside click, and it returns focus to the
10
+ trigger. All of that is bits-ui's — it is precisely the hand-rolled part
11
+ that is always subtly wrong.
12
+
13
+ ARIA contract: bits-ui gives the trigger `aria-haspopup="dialog"` and
14
+ `aria-expanded` and wires `aria-controls` to the panel, but it leaves the
15
+ panel's own role to the caller — so `role="dialog"` is set here, where the
16
+ focus trap and the Escape handler that make the role true already are. When
17
+ `title` is set the panel is `aria-labelledby` that heading, so it announces
18
+ what it is on open; without a title, label it from the call site. The close button is
19
+ a labelled control, not a bare glyph.
20
+
21
+ Both snippets take the caller's own element: `trigger` receives the props
22
+ to spread, so an existing `Button` keeps its own markup.
23
+ -->
24
+ <script lang="ts">import { X } from "@lucide/svelte";
25
+ import { Popover } from "bits-ui";
26
+ import Button from "./Button.svelte";
27
+ import VisuallyHidden from "./VisuallyHidden.svelte";
28
+ let { open = $bindable(false), side = "bottom", align = "center", sideOffset = 8, title, closeLabel = "Close", class: className, trigger, children } = $props();
29
+ const titleId = $props.id();
30
+ </script>
31
+
32
+ <Popover.Root bind:open>
33
+ <Popover.Trigger>
34
+ {#snippet child({ props })}
35
+ {@render trigger({ props })}
36
+ {/snippet}
37
+ </Popover.Trigger>
38
+ <Popover.Portal>
39
+ <Popover.Content
40
+ {side}
41
+ {align}
42
+ {sideOffset}
43
+ role="dialog"
44
+ aria-labelledby={title === undefined ? undefined : titleId}
45
+ class={['card card-border bg-base-100 rounded-box z-50 w-72 p-4 shadow-md', className]}
46
+ >
47
+ {#if closeLabel !== undefined}
48
+ <Popover.Close>
49
+ {#snippet child({ props })}
50
+ <Button {...props} variant="ghost" size="sm" class="absolute top-2 right-2 btn-square">
51
+ <X class="size-4" aria-hidden="true" />
52
+ <VisuallyHidden>{closeLabel}</VisuallyHidden>
53
+ </Button>
54
+ {/snippet}
55
+ </Popover.Close>
56
+ {/if}
57
+ {#if title !== undefined}
58
+ <p id={titleId} class="mb-2 pr-8 font-semibold">{title}</p>
59
+ {/if}
60
+ {@render children()}
61
+ </Popover.Content>
62
+ </Popover.Portal>
63
+ </Popover.Root>
@@ -0,0 +1,40 @@
1
+ import { Popover } from 'bits-ui';
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ /** Whether the panel is open. Bindable. */
5
+ open?: boolean | undefined;
6
+ /**
7
+ * Which side of the trigger the panel opens on.
8
+ * @defaultValue `'bottom'`
9
+ */
10
+ side?: 'top' | 'right' | 'bottom' | 'left' | undefined;
11
+ /**
12
+ * Alignment against the trigger along that side.
13
+ * @defaultValue `'center'`
14
+ */
15
+ align?: 'start' | 'center' | 'end' | undefined;
16
+ /**
17
+ * Distance in pixels from the trigger.
18
+ * @defaultValue `8`
19
+ */
20
+ sideOffset?: number | undefined;
21
+ /** Heading for the panel; becomes its accessible name. */
22
+ title?: string | undefined;
23
+ /**
24
+ * Accessible name of the close button. Omit to hide the button.
25
+ * @defaultValue `'Close'`
26
+ */
27
+ closeLabel?: string | undefined;
28
+ /** Extra classes on the panel. */
29
+ class?: string | undefined;
30
+ /** The trigger — the caller's own element, spreading `props` onto it. */
31
+ trigger: Snippet<[{
32
+ props: Record<string, unknown>;
33
+ }]>;
34
+ /** The panel body, under `title` when one is given. */
35
+ children: Snippet<[]>;
36
+ }
37
+ /** A small floating panel anchored to a trigger, on bits-ui’s `Popover`. */
38
+ declare const Popover: import("svelte").Component<Props, {}, "open">;
39
+ type Popover = ReturnType<typeof Popover>;
40
+ export default Popover;
@@ -0,0 +1,64 @@
1
+ <!-- @component
2
+ Hands a title, a URL and some text to the platform share sheet.
3
+ -->
4
+ <!--
5
+ ShareButton — hands a title, a URL and some text to the platform share
6
+ sheet.
7
+
8
+ It renders NOTHING where the Web Share API is absent — every desktop
9
+ Firefox, and the server. A button that opens nothing is worse than no
10
+ button, and the caller cannot know which it has before hydration, so the
11
+ decision is made here and made after mount: reading `navigator.share` during
12
+ SSR would render a button the client then has to remove, which is a layout
13
+ shift on every page that carries one.
14
+
15
+ `canShare`, where the browser has it, is consulted with the SAME payload
16
+ that will be shared — the original implementation tested one
17
+ object and shared another. Where it is missing the only way to find out is
18
+ to try, and the rejection path handles "no". A share sheet the reader
19
+ dismisses also rejects; `onError` sees both, because the API does not
20
+ distinguish them.
21
+
22
+ ARIA contract: a plain `Button`, so nothing beyond its label. The icon is
23
+ decorative.
24
+ -->
25
+ <script lang="ts">import { Share2 } from "@lucide/svelte";
26
+ import Button from "./Button.svelte";
27
+ let { title, text, url, label = "Share", tone, variant = "ghost", size = "sm", class: className, onShare, onError, children } = $props();
28
+ /**
29
+ * Whether this browser can share at all. Set from an effect, never at
30
+ * module or render scope: `navigator` does not exist on the server.
31
+ */
32
+ let shareable = $state(false);
33
+ $effect(() => {
34
+ shareable = typeof navigator !== "undefined" && typeof navigator.share === "function";
35
+ });
36
+ const payload = $derived({
37
+ ...title === undefined ? {} : { title },
38
+ ...text === undefined ? {} : { text },
39
+ ...url === undefined ? {} : { url }
40
+ });
41
+ const share = async () => {
42
+ if (typeof navigator.canShare === "function" && !navigator.canShare(payload)) {
43
+ onError?.(new Error("This browser cannot share that payload."));
44
+ return;
45
+ }
46
+ try {
47
+ await navigator.share(payload);
48
+ onShare?.(payload);
49
+ } catch (error) {
50
+ onError?.(error);
51
+ }
52
+ };
53
+ </script>
54
+
55
+ {#if shareable}
56
+ <Button {tone} {variant} {size} class={className} onclick={share}>
57
+ {#if children}
58
+ {@render children()}
59
+ {:else}
60
+ <Share2 class="size-4" aria-hidden="true" />
61
+ {label}
62
+ {/if}
63
+ </Button>
64
+ {/if}
@@ -0,0 +1,39 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ButtonVariant, Size, Tone } from './tones.ts';
3
+ interface Props {
4
+ /** Share sheet title. */
5
+ title?: string | undefined;
6
+ /** Body text offered alongside the link. */
7
+ text?: string | undefined;
8
+ /** The URL to share; the current page when omitted. */
9
+ url?: string | undefined;
10
+ /**
11
+ * The button's text.
12
+ * @defaultValue `'Share'`
13
+ */
14
+ label?: string | undefined;
15
+ /** Semantic colour. */
16
+ tone?: Tone | undefined;
17
+ /**
18
+ * daisyUI button style.
19
+ * @defaultValue `'ghost'`
20
+ */
21
+ variant?: ButtonVariant | undefined;
22
+ /**
23
+ * daisyUI size step.
24
+ * @defaultValue `'sm'`
25
+ */
26
+ size?: Size | undefined;
27
+ /** Extra classes, merged last. */
28
+ class?: string | undefined;
29
+ /** The share sheet completed. */
30
+ onShare?: ((data: ShareData) => void) | undefined;
31
+ /** The share sheet failed, or the reader dismissed it. */
32
+ onError?: ((error: unknown) => void) | undefined;
33
+ /** Replaces the label and icon entirely. */
34
+ children?: Snippet<[]> | undefined;
35
+ }
36
+ /** Hands a title, a URL and some text to the platform share sheet. */
37
+ declare const ShareButton: import("svelte").Component<Props, {}, "">;
38
+ type ShareButton = ReturnType<typeof ShareButton>;
39
+ export default ShareButton;
@@ -0,0 +1,43 @@
1
+ <!-- @component
2
+ daisyUI `skeleton` placeholders — a block, a circle or lines of text — for a surface whose data has not arrived.
3
+ -->
4
+ <!--
5
+ Skeleton — daisyUI `skeleton` placeholders for a surface whose data has not
6
+ arrived. Three shapes: a `block` (one box, sized by the caller's `class`), a
7
+ `circle` (an avatar), or `text` (`lines` rows of decreasing width, so the
8
+ shimmer reads as a paragraph rather than a bar).
9
+
10
+ ARIA contract: the shimmer conveys "loading" by animation alone, which is
11
+ nothing to a screen reader, so the wrapper is a polite live region
12
+ (`role="status"`) marked `aria-busy="true"` and named by `label`, and every
13
+ shimmering box is `aria-hidden`. The label is an `aria-label` rather than
14
+ visually hidden text because `status` does not take its name from its
15
+ content — and it is the ONLY thing announced, since the fake paragraph
16
+ shapes must not be read as content.
17
+ -->
18
+ <script lang="ts">let { variant = "block", lines = 3, label = "Loading", class: className, ...rest } = $props();
19
+ const widths = [
20
+ "w-full",
21
+ "w-11/12",
22
+ "w-4/5",
23
+ "w-3/4",
24
+ "w-2/3",
25
+ "w-1/2"
26
+ ];
27
+ const rows = $derived(Array.from({ length: Math.max(1, lines) }, (_, index) => widths[index % widths.length] ?? "w-full"));
28
+ export {};
29
+ </script>
30
+
31
+ <div {...rest} role="status" aria-busy="true" aria-label={label} class={className}>
32
+ {#if variant === 'text'}
33
+ <div class="flex flex-col gap-2" aria-hidden="true">
34
+ {#each rows as width, index (index)}
35
+ <div class={['skeleton h-4', width]}></div>
36
+ {/each}
37
+ </div>
38
+ {:else if variant === 'circle'}
39
+ <div class="skeleton size-12 shrink-0 rounded-full" aria-hidden="true"></div>
40
+ {:else}
41
+ <div class="skeleton h-24 w-full" aria-hidden="true"></div>
42
+ {/if}
43
+ </div>
@@ -0,0 +1,22 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props extends HTMLAttributes<HTMLDivElement> {
3
+ /**
4
+ * The placeholder shape.
5
+ * @defaultValue `'block'`
6
+ */
7
+ variant?: 'block' | 'text' | 'circle' | undefined;
8
+ /**
9
+ * Rows for the `text` variant.
10
+ * @defaultValue `3`
11
+ */
12
+ lines?: number | undefined;
13
+ /**
14
+ * What assistive technology announces in place of the shimmer.
15
+ * @defaultValue `'Loading'`
16
+ */
17
+ label?: string | undefined;
18
+ }
19
+ /** daisyUI `skeleton` placeholders — a block, a circle or lines of text — for a surface whose data has not arrived. */
20
+ declare const Skeleton: import("svelte").Component<Props, {}, "">;
21
+ type Skeleton = ReturnType<typeof Skeleton>;
22
+ export default Skeleton;
@@ -0,0 +1,65 @@
1
+ <!-- @component
2
+ The light/dark switch: sets `data-theme` on the document element and remembers the choice in `localStorage`.
3
+ -->
4
+ <!--
5
+ ThemeToggle — the light / dark switch: a daisyUI `swap` label around a
6
+ checkbox, which writes the chosen theme name to
7
+ `document.documentElement.dataset.theme` and remembers it in
8
+ `localStorage`.
9
+
10
+ Generic on purpose. The two theme NAMES are props, because this library
11
+ ships tokens rather than a brand and an app may call its themes anything;
12
+ `storageKey` is a prop for the same reason.
13
+
14
+ **The pre-paint script is the app's job.** This component can only run
15
+ after hydration, so a returning visitor whose choice was dark would flash
16
+ light first. The fix is three lines the app inlines in its document head,
17
+ before any stylesheet, reading the same key:
18
+
19
+ ```html
20
+ <script>
21
+ try {
22
+ const t = localStorage.getItem('theme');
23
+ if (t) document.documentElement.dataset.theme = t;
24
+ } catch {}
25
+ </script>
26
+ ```
27
+
28
+ Every `localStorage` access here is wrapped: it throws outright in a private
29
+ window and with site data blocked, and a remembered theme is a convenience
30
+ that must never be able to stop the control rendering.
31
+
32
+ ARIA contract: a real checkbox with `role="switch"` — on/off, not
33
+ checked/unchecked — named by `label`. Both icons are `aria-hidden`; they
34
+ are the visual half of the state the switch already announces. Note that
35
+ daisyUI's `theme-controller` class is deliberately NOT used: it applies a
36
+ theme through CSS scoped to the checkbox, which would fight with the
37
+ `data-theme` this component sets on the document element.
38
+ -->
39
+ <script lang="ts">import { Moon, Sun } from "@lucide/svelte";
40
+ let { lightTheme, darkTheme, storageKey = "theme", label = "Toggle dark mode", value = $bindable(), class: className } = $props();
41
+ // Read the remembered choice after mount: `localStorage` does not exist on
42
+ // the server, and a value read during render would not survive hydration.
43
+ $effect(() => {
44
+ try {
45
+ const stored = globalThis.localStorage?.getItem(storageKey);
46
+ if (stored === darkTheme || stored === lightTheme) value = stored;
47
+ } catch {}
48
+ });
49
+ const active = $derived(value ?? lightTheme);
50
+ const dark = $derived(active === darkTheme);
51
+ const onchange = (event) => {
52
+ const next = event.currentTarget.checked ? darkTheme : lightTheme;
53
+ value = next;
54
+ document.documentElement.dataset.theme = next;
55
+ try {
56
+ globalThis.localStorage?.setItem(storageKey, next);
57
+ } catch {}
58
+ };
59
+ </script>
60
+
61
+ <label class={['swap swap-rotate btn btn-ghost btn-circle', className]}>
62
+ <input type="checkbox" role="switch" checked={dark} aria-label={label} {onchange} />
63
+ <Sun class="swap-off size-5" aria-hidden="true" />
64
+ <Moon class="swap-on size-5" aria-hidden="true" />
65
+ </label>
@@ -0,0 +1,24 @@
1
+ interface Props {
2
+ /** The theme name applied when the switch is off. */
3
+ lightTheme: string;
4
+ /** The theme name applied when the switch is on. */
5
+ darkTheme: string;
6
+ /**
7
+ * `localStorage` key the choice is remembered under.
8
+ * @defaultValue `'theme'`
9
+ */
10
+ storageKey?: string | undefined;
11
+ /**
12
+ * Accessible name of the switch.
13
+ * @defaultValue `'Toggle dark mode'`
14
+ */
15
+ label?: string | undefined;
16
+ /** The active theme name. Bindable; defaults to `lightTheme`. */
17
+ value?: string | undefined;
18
+ /** Extra classes, merged last. */
19
+ class?: string | undefined;
20
+ }
21
+ /** The light/dark switch: sets `data-theme` on the document element and remembers the choice in `localStorage`. */
22
+ declare const ThemeToggle: import("svelte").Component<Props, {}, "value">;
23
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
24
+ export default ThemeToggle;
@@ -0,0 +1,54 @@
1
+ <!-- @component
2
+ Supplementary text on hover and on keyboard focus, on bits-ui’s `Tooltip`.
3
+ -->
4
+ <!--
5
+ Tooltip — supplementary text on hover and on keyboard focus, on bits-ui's
6
+ `Tooltip`.
7
+
8
+ Provider-free by design. bits-ui's `Tooltip.Root` requires a
9
+ `Tooltip.Provider` ancestor (it reads the shared open-delay state from
10
+ context and throws without it), which means every consuming app would have
11
+ to remember to mount one in its root layout before a single tooltip works.
12
+ This component mounts its own, so `<Tooltip text="…">{trigger}</Tooltip>` is
13
+ the whole API. The cost is that the "skip the delay for the next tooltip"
14
+ behaviour is per-tooltip rather than app-wide, which is a fair trade for a
15
+ primitive that always works.
16
+
17
+ ARIA contract: bits-ui wires `aria-describedby` from the trigger to the
18
+ content — DESCRIBED, not labelled, because a tooltip supplements a name it
19
+ must never be the only source of. An icon-only control still needs its own
20
+ accessible name (`VisuallyHidden`, or `aria-label`); a tooltip is not it,
21
+ since a tooltip is unreachable to touch users entirely.
22
+
23
+ The trigger renders through bits-ui's `child` snippet, so the caller's own
24
+ element keeps its semantics instead of being wrapped in another button.
25
+ -->
26
+ <script lang="ts">import { Tooltip } from "bits-ui";
27
+ let { text, content, side = "top", sideOffset = 6, openDelay = 400, open = $bindable(false), class: className, children } = $props();
28
+ </script>
29
+
30
+ <Tooltip.Provider delayDuration={openDelay}>
31
+ <Tooltip.Root bind:open>
32
+ <Tooltip.Trigger>
33
+ {#snippet child({ props })}
34
+ {@render children({ props })}
35
+ {/snippet}
36
+ </Tooltip.Trigger>
37
+ <Tooltip.Portal>
38
+ <Tooltip.Content
39
+ {side}
40
+ {sideOffset}
41
+ class={[
42
+ 'bg-neutral text-neutral-content rounded-box z-50 max-w-xs px-3 py-1.5 text-sm shadow-md',
43
+ className,
44
+ ]}
45
+ >
46
+ {#if content}
47
+ {@render content()}
48
+ {:else if text !== undefined}
49
+ {text}
50
+ {/if}
51
+ </Tooltip.Content>
52
+ </Tooltip.Portal>
53
+ </Tooltip.Root>
54
+ </Tooltip.Provider>