@thomasfosterau/components 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -56
  3. package/dist/canvas/VectorCanvas.svelte +320 -0
  4. package/dist/canvas/VectorCanvas.svelte.d.ts +105 -0
  5. package/dist/canvas/history.d.ts +105 -0
  6. package/dist/canvas/history.js +147 -0
  7. package/dist/canvas/index.d.ts +15 -0
  8. package/dist/canvas/index.js +14 -0
  9. package/dist/canvas/types.d.ts +91 -0
  10. package/dist/canvas/types.js +13 -0
  11. package/dist/canvas/utils.d.ts +66 -0
  12. package/dist/canvas/utils.js +114 -0
  13. package/dist/charts/Chart.svelte +300 -0
  14. package/dist/charts/Chart.svelte.d.ts +50 -0
  15. package/dist/charts/index.d.ts +11 -0
  16. package/dist/charts/index.js +10 -0
  17. package/dist/charts/types.d.ts +28 -0
  18. package/dist/charts/types.js +6 -0
  19. package/dist/citations/BookCitation.svelte +53 -0
  20. package/dist/citations/BookCitation.svelte.d.ts +41 -0
  21. package/dist/citations/ChapterCitation.svelte +38 -0
  22. package/dist/citations/ChapterCitation.svelte.d.ts +50 -0
  23. package/dist/citations/Citation.svelte +47 -0
  24. package/dist/citations/Citation.svelte.d.ts +24 -0
  25. package/dist/citations/CreditList.svelte +69 -0
  26. package/dist/citations/CreditList.svelte.d.ts +47 -0
  27. package/dist/citations/DOILink.svelte +28 -0
  28. package/dist/citations/DOILink.svelte.d.ts +19 -0
  29. package/dist/citations/Footnote.svelte +39 -0
  30. package/dist/citations/Footnote.svelte.d.ts +17 -0
  31. package/dist/citations/FootnoteList.svelte +36 -0
  32. package/dist/citations/FootnoteList.svelte.d.ts +21 -0
  33. package/dist/citations/ISBNLink.svelte +29 -0
  34. package/dist/citations/ISBNLink.svelte.d.ts +19 -0
  35. package/dist/citations/ISSNLink.svelte +29 -0
  36. package/dist/citations/ISSNLink.svelte.d.ts +19 -0
  37. package/dist/citations/Publisher.svelte +34 -0
  38. package/dist/citations/Publisher.svelte.d.ts +14 -0
  39. package/dist/citations/ReferenceList.svelte +53 -0
  40. package/dist/citations/ReferenceList.svelte.d.ts +21 -0
  41. package/dist/citations/ReportCitation.svelte +41 -0
  42. package/dist/citations/ReportCitation.svelte.d.ts +36 -0
  43. package/dist/citations/ScholarlyArticleCitation.svelte +64 -0
  44. package/dist/citations/ScholarlyArticleCitation.svelte.d.ts +41 -0
  45. package/dist/citations/ThesisCitation.svelte +35 -0
  46. package/dist/citations/ThesisCitation.svelte.d.ts +29 -0
  47. package/dist/citations/bibliography.svelte.d.ts +55 -0
  48. package/dist/citations/bibliography.svelte.js +78 -0
  49. package/dist/citations/citation.d.ts +71 -0
  50. package/dist/citations/citation.js +121 -0
  51. package/dist/citations/csl.d.ts +102 -0
  52. package/dist/citations/csl.js +20 -0
  53. package/dist/citations/index.d.ts +31 -0
  54. package/dist/citations/index.js +36 -0
  55. package/dist/citations/list-format.d.ts +34 -0
  56. package/dist/citations/list-format.js +40 -0
  57. package/dist/context.svelte.d.ts +46 -0
  58. package/dist/context.svelte.js +51 -0
  59. package/dist/data/ChatBubble.svelte +48 -0
  60. package/dist/data/ChatBubble.svelte.d.ts +33 -0
  61. package/dist/data/DataGrid.svelte +150 -0
  62. package/dist/data/DataGrid.svelte.d.ts +118 -0
  63. package/dist/data/DescriptionList.svelte +53 -0
  64. package/dist/data/DescriptionList.svelte.d.ts +26 -0
  65. package/dist/data/DescriptionRow.svelte +56 -0
  66. package/dist/data/DescriptionRow.svelte.d.ts +21 -0
  67. package/dist/data/DiffText.svelte +29 -0
  68. package/dist/data/DiffText.svelte.d.ts +19 -0
  69. package/dist/data/ItemList.svelte +80 -0
  70. package/dist/data/ItemList.svelte.d.ts +52 -0
  71. package/dist/data/LinkListItem.svelte +46 -0
  72. package/dist/data/LinkListItem.svelte.d.ts +21 -0
  73. package/dist/data/MessageThread.svelte +29 -0
  74. package/dist/data/MessageThread.svelte.d.ts +17 -0
  75. package/dist/data/Pagination.svelte +136 -0
  76. package/dist/data/Pagination.svelte.d.ts +79 -0
  77. package/dist/data/PaginationFooter.svelte +42 -0
  78. package/dist/data/PaginationFooter.svelte.d.ts +61 -0
  79. package/dist/data/ResultCount.svelte +27 -0
  80. package/dist/data/ResultCount.svelte.d.ts +18 -0
  81. package/dist/data/Table.svelte +151 -0
  82. package/dist/data/Table.svelte.d.ts +75 -0
  83. package/dist/data/TreeList.svelte +41 -0
  84. package/dist/data/TreeList.svelte.d.ts +17 -0
  85. package/dist/data/TreeListItem.svelte +29 -0
  86. package/dist/data/TreeListItem.svelte.d.ts +10 -0
  87. package/dist/data/description-context.d.ts +39 -0
  88. package/dist/data/description-context.js +36 -0
  89. package/dist/data/diff.d.ts +14 -0
  90. package/dist/data/diff.js +10 -0
  91. package/dist/data/index.d.ts +37 -0
  92. package/dist/data/index.js +37 -0
  93. package/dist/data/list-item.d.ts +30 -0
  94. package/dist/data/list-item.js +19 -0
  95. package/dist/data/pagination.d.ts +81 -0
  96. package/dist/data/pagination.js +79 -0
  97. package/dist/data/table-types.d.ts +71 -0
  98. package/dist/data/table-types.js +35 -0
  99. package/dist/feedback/ConfirmDialog.svelte +79 -0
  100. package/dist/feedback/ConfirmDialog.svelte.d.ts +82 -0
  101. package/dist/feedback/Dialog.svelte +47 -0
  102. package/dist/feedback/Dialog.svelte.d.ts +36 -0
  103. package/dist/feedback/DialogFrame.svelte +64 -0
  104. package/dist/feedback/DialogFrame.svelte.d.ts +23 -0
  105. package/dist/feedback/Drawer.svelte +43 -0
  106. package/dist/feedback/Drawer.svelte.d.ts +43 -0
  107. package/dist/feedback/LoadingBoundary.svelte +61 -0
  108. package/dist/feedback/LoadingBoundary.svelte.d.ts +34 -0
  109. package/dist/feedback/ProgressBar.svelte +46 -0
  110. package/dist/feedback/ProgressBar.svelte.d.ts +28 -0
  111. package/dist/feedback/SegmentBar.svelte +83 -0
  112. package/dist/feedback/SegmentBar.svelte.d.ts +37 -0
  113. package/dist/feedback/StatTile.svelte +77 -0
  114. package/dist/feedback/StatTile.svelte.d.ts +50 -0
  115. package/dist/feedback/Stats.svelte +18 -0
  116. package/dist/feedback/Stats.svelte.d.ts +16 -0
  117. package/dist/feedback/Steps.svelte +72 -0
  118. package/dist/feedback/Steps.svelte.d.ts +61 -0
  119. package/dist/feedback/Timeline.svelte +86 -0
  120. package/dist/feedback/Timeline.svelte.d.ts +55 -0
  121. package/dist/feedback/Toaster.svelte +78 -0
  122. package/dist/feedback/Toaster.svelte.d.ts +32 -0
  123. package/dist/feedback/index.d.ts +34 -0
  124. package/dist/feedback/index.js +29 -0
  125. package/dist/feedback/sizes.d.ts +41 -0
  126. package/dist/feedback/sizes.js +69 -0
  127. package/dist/feedback/toaster.d.ts +48 -0
  128. package/dist/feedback/toaster.js +29 -0
  129. package/dist/fields/Calendar.svelte +60 -0
  130. package/dist/fields/Calendar.svelte.d.ts +11 -0
  131. package/dist/fields/CalendarMonths.svelte +86 -0
  132. package/dist/fields/CalendarMonths.svelte.d.ts +30 -0
  133. package/dist/fields/Checkbox.svelte +33 -0
  134. package/dist/fields/Checkbox.svelte.d.ts +30 -0
  135. package/dist/fields/CheckboxGroup.svelte +41 -0
  136. package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
  137. package/dist/fields/Combobox.svelte +229 -0
  138. package/dist/fields/Combobox.svelte.d.ts +133 -0
  139. package/dist/fields/DateField.svelte +37 -0
  140. package/dist/fields/DateField.svelte.d.ts +20 -0
  141. package/dist/fields/DateInput.svelte +31 -0
  142. package/dist/fields/DateInput.svelte.d.ts +35 -0
  143. package/dist/fields/DatePicker.svelte +47 -0
  144. package/dist/fields/DatePicker.svelte.d.ts +25 -0
  145. package/dist/fields/DateRangePicker.svelte +70 -0
  146. package/dist/fields/DateRangePicker.svelte.d.ts +43 -0
  147. package/dist/fields/Dropzone.svelte +232 -0
  148. package/dist/fields/Dropzone.svelte.d.ts +103 -0
  149. package/dist/fields/FieldErrors.svelte +30 -0
  150. package/dist/fields/FieldErrors.svelte.d.ts +11 -0
  151. package/dist/fields/Fieldset.svelte +53 -0
  152. package/dist/fields/Fieldset.svelte.d.ts +27 -0
  153. package/dist/fields/FileInput.svelte +68 -0
  154. package/dist/fields/FileInput.svelte.d.ts +39 -0
  155. package/dist/fields/FormField.svelte +78 -0
  156. package/dist/fields/FormField.svelte.d.ts +29 -0
  157. package/dist/fields/ImageInput.svelte +87 -0
  158. package/dist/fields/ImageInput.svelte.d.ts +45 -0
  159. package/dist/fields/MultiSelect.svelte +112 -0
  160. package/dist/fields/MultiSelect.svelte.d.ts +75 -0
  161. package/dist/fields/NumberField.svelte +190 -0
  162. package/dist/fields/NumberField.svelte.d.ts +76 -0
  163. package/dist/fields/PasswordInput.svelte +95 -0
  164. package/dist/fields/PasswordInput.svelte.d.ts +58 -0
  165. package/dist/fields/PickerPopover.svelte +47 -0
  166. package/dist/fields/PickerPopover.svelte.d.ts +16 -0
  167. package/dist/fields/PinInput.svelte +83 -0
  168. package/dist/fields/PinInput.svelte.d.ts +66 -0
  169. package/dist/fields/PushToggle.svelte +93 -0
  170. package/dist/fields/PushToggle.svelte.d.ts +55 -0
  171. package/dist/fields/RadioGroup.svelte +88 -0
  172. package/dist/fields/RadioGroup.svelte.d.ts +41 -0
  173. package/dist/fields/RangeCalendar.svelte +51 -0
  174. package/dist/fields/RangeCalendar.svelte.d.ts +23 -0
  175. package/dist/fields/Rating.svelte +127 -0
  176. package/dist/fields/Rating.svelte.d.ts +86 -0
  177. package/dist/fields/SearchInput.svelte +58 -0
  178. package/dist/fields/SearchInput.svelte.d.ts +33 -0
  179. package/dist/fields/SegmentGroup.svelte +91 -0
  180. package/dist/fields/SegmentGroup.svelte.d.ts +20 -0
  181. package/dist/fields/Select.svelte +36 -0
  182. package/dist/fields/Select.svelte.d.ts +35 -0
  183. package/dist/fields/Slider.svelte +194 -0
  184. package/dist/fields/Slider.svelte.d.ts +100 -0
  185. package/dist/fields/TagsInput.svelte +256 -0
  186. package/dist/fields/TagsInput.svelte.d.ts +102 -0
  187. package/dist/fields/TextInput.svelte +26 -0
  188. package/dist/fields/TextInput.svelte.d.ts +36 -0
  189. package/dist/fields/Textarea.svelte +22 -0
  190. package/dist/fields/Textarea.svelte.d.ts +31 -0
  191. package/dist/fields/TimeField.svelte +30 -0
  192. package/dist/fields/TimeField.svelte.d.ts +19 -0
  193. package/dist/fields/Toggle.svelte +72 -0
  194. package/dist/fields/Toggle.svelte.d.ts +38 -0
  195. package/dist/fields/VideoInput.svelte +35 -0
  196. package/dist/fields/VideoInput.svelte.d.ts +46 -0
  197. package/dist/fields/calendar-props.d.ts +119 -0
  198. package/dist/fields/calendar-props.js +51 -0
  199. package/dist/fields/dates.d.ts +28 -0
  200. package/dist/fields/dates.js +26 -0
  201. package/dist/fields/field-types.d.ts +80 -0
  202. package/dist/fields/field-types.js +92 -0
  203. package/dist/fields/file-validation.d.ts +30 -0
  204. package/dist/fields/file-validation.js +51 -0
  205. package/dist/fields/index.d.ts +43 -0
  206. package/dist/fields/index.js +42 -0
  207. package/dist/fields/latest-search.svelte.d.ts +38 -0
  208. package/dist/fields/latest-search.svelte.js +85 -0
  209. package/dist/fields/number-format.d.ts +52 -0
  210. package/dist/fields/number-format.js +89 -0
  211. package/dist/fields/segmented-field.d.ts +128 -0
  212. package/dist/fields/segmented-field.js +52 -0
  213. package/dist/fields/slider-keys.d.ts +21 -0
  214. package/dist/fields/slider-keys.js +37 -0
  215. package/dist/formatting/Countdown.svelte +110 -0
  216. package/dist/formatting/Countdown.svelte.d.ts +35 -0
  217. package/dist/formatting/DateTime.svelte +35 -0
  218. package/dist/formatting/DateTime.svelte.d.ts +26 -0
  219. package/dist/formatting/Duration.svelte +18 -0
  220. package/dist/formatting/Duration.svelte.d.ts +20 -0
  221. package/dist/formatting/FormatList.svelte +31 -0
  222. package/dist/formatting/FormatList.svelte.d.ts +46 -0
  223. package/dist/formatting/Highlight.svelte +51 -0
  224. package/dist/formatting/Highlight.svelte.d.ts +19 -0
  225. package/dist/formatting/Number.svelte +35 -0
  226. package/dist/formatting/Number.svelte.d.ts +43 -0
  227. package/dist/formatting/RelativeTime.svelte +69 -0
  228. package/dist/formatting/RelativeTime.svelte.d.ts +26 -0
  229. package/dist/formatting/Year.svelte +39 -0
  230. package/dist/formatting/Year.svelte.d.ts +21 -0
  231. package/dist/formatting/duration.d.ts +44 -0
  232. package/dist/formatting/duration.js +125 -0
  233. package/dist/formatting/index.d.ts +15 -0
  234. package/dist/formatting/index.js +17 -0
  235. package/dist/forms/EntityForm.svelte +185 -0
  236. package/dist/forms/EntityForm.svelte.d.ts +66 -0
  237. package/dist/forms/FormActions.svelte +29 -0
  238. package/dist/forms/FormActions.svelte.d.ts +37 -0
  239. package/dist/forms/FormErrors.svelte +21 -0
  240. package/dist/forms/FormErrors.svelte.d.ts +16 -0
  241. package/dist/forms/HiddenField.svelte +22 -0
  242. package/dist/forms/HiddenField.svelte.d.ts +11 -0
  243. package/dist/forms/SearchForm.svelte +43 -0
  244. package/dist/forms/SearchForm.svelte.d.ts +36 -0
  245. package/dist/forms/entity-form.d.ts +12 -0
  246. package/dist/forms/entity-form.js +12 -0
  247. package/dist/forms/index.d.ts +14 -0
  248. package/dist/forms/index.js +14 -0
  249. package/dist/hotkeys/HotkeyBadge.svelte +39 -0
  250. package/dist/hotkeys/HotkeyBadge.svelte.d.ts +25 -0
  251. package/dist/hotkeys/HotkeyHelpDialog.svelte +68 -0
  252. package/dist/hotkeys/HotkeyHelpDialog.svelte.d.ts +35 -0
  253. package/dist/hotkeys/HotkeyProvider.svelte +92 -0
  254. package/dist/hotkeys/HotkeyProvider.svelte.d.ts +4 -0
  255. package/dist/hotkeys/context.svelte.d.ts +47 -0
  256. package/dist/hotkeys/context.svelte.js +72 -0
  257. package/dist/hotkeys/index.d.ts +28 -0
  258. package/dist/hotkeys/index.js +27 -0
  259. package/dist/hotkeys/match.d.ts +65 -0
  260. package/dist/hotkeys/match.js +135 -0
  261. package/dist/hotkeys/registry.svelte.d.ts +55 -0
  262. package/dist/hotkeys/registry.svelte.js +110 -0
  263. package/dist/hotkeys/types.d.ts +74 -0
  264. package/dist/hotkeys/types.js +18 -0
  265. package/dist/index.d.ts +37 -0
  266. package/dist/index.js +37 -0
  267. package/dist/infobox/Infobox.svelte +79 -0
  268. package/dist/infobox/Infobox.svelte.d.ts +33 -0
  269. package/dist/infobox/InfoboxHeader.svelte +35 -0
  270. package/dist/infobox/InfoboxHeader.svelte.d.ts +18 -0
  271. package/dist/infobox/InfoboxImage.svelte +38 -0
  272. package/dist/infobox/InfoboxImage.svelte.d.ts +21 -0
  273. package/dist/infobox/InfoboxRow.svelte +50 -0
  274. package/dist/infobox/InfoboxRow.svelte.d.ts +25 -0
  275. package/dist/infobox/InfoboxSection.svelte +24 -0
  276. package/dist/infobox/InfoboxSection.svelte.d.ts +12 -0
  277. package/dist/infobox/index.d.ts +21 -0
  278. package/dist/infobox/index.js +20 -0
  279. package/dist/infobox/types.d.ts +86 -0
  280. package/dist/infobox/types.js +27 -0
  281. package/dist/layout/AppShell.svelte +50 -0
  282. package/dist/layout/AppShell.svelte.d.ts +28 -0
  283. package/dist/layout/Breadcrumbs.svelte +52 -0
  284. package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
  285. package/dist/layout/Cluster.svelte +28 -0
  286. package/dist/layout/Cluster.svelte.d.ts +27 -0
  287. package/dist/layout/DetailLayout.svelte +50 -0
  288. package/dist/layout/DetailLayout.svelte.d.ts +19 -0
  289. package/dist/layout/FilterBar.svelte +40 -0
  290. package/dist/layout/FilterBar.svelte.d.ts +12 -0
  291. package/dist/layout/Fits.svelte +27 -0
  292. package/dist/layout/Fits.svelte.d.ts +13 -0
  293. package/dist/layout/Flow.svelte +46 -0
  294. package/dist/layout/Flow.svelte.d.ts +16 -0
  295. package/dist/layout/Footer.svelte +32 -0
  296. package/dist/layout/Footer.svelte.d.ts +12 -0
  297. package/dist/layout/Grid.svelte +23 -0
  298. package/dist/layout/Grid.svelte.d.ts +26 -0
  299. package/dist/layout/HStack.svelte +12 -0
  300. package/dist/layout/HStack.svelte.d.ts +34 -0
  301. package/dist/layout/Hero.svelte +76 -0
  302. package/dist/layout/Hero.svelte.d.ts +36 -0
  303. package/dist/layout/Main.svelte +30 -0
  304. package/dist/layout/Main.svelte.d.ts +15 -0
  305. package/dist/layout/Navbar.svelte +68 -0
  306. package/dist/layout/Navbar.svelte.d.ts +31 -0
  307. package/dist/layout/PageHeader.svelte +77 -0
  308. package/dist/layout/PageHeader.svelte.d.ts +33 -0
  309. package/dist/layout/Section.svelte +72 -0
  310. package/dist/layout/Section.svelte.d.ts +40 -0
  311. package/dist/layout/Sidebar.svelte +54 -0
  312. package/dist/layout/Sidebar.svelte.d.ts +27 -0
  313. package/dist/layout/Spacer.svelte +12 -0
  314. package/dist/layout/Spacer.svelte.d.ts +6 -0
  315. package/dist/layout/Tabs.svelte +85 -0
  316. package/dist/layout/Tabs.svelte.d.ts +42 -0
  317. package/dist/layout/VStack.svelte +12 -0
  318. package/dist/layout/VStack.svelte.d.ts +29 -0
  319. package/dist/layout/index.d.ts +38 -0
  320. package/dist/layout/index.js +38 -0
  321. package/dist/layout/space.d.ts +44 -0
  322. package/dist/layout/space.js +76 -0
  323. package/dist/layout/types.d.ts +52 -0
  324. package/dist/layout/types.js +27 -0
  325. package/dist/links/ContextLinks.svelte +28 -0
  326. package/dist/links/ContextLinks.svelte.d.ts +20 -0
  327. package/dist/links/EmailLink.svelte +26 -0
  328. package/dist/links/EmailLink.svelte.d.ts +20 -0
  329. package/dist/links/index.d.ts +7 -0
  330. package/dist/links/index.js +7 -0
  331. package/dist/media/Figure.svelte +26 -0
  332. package/dist/media/Figure.svelte.d.ts +12 -0
  333. package/dist/media/Image.svelte +76 -0
  334. package/dist/media/Image.svelte.d.ts +41 -0
  335. package/dist/media/Video.svelte +65 -0
  336. package/dist/media/Video.svelte.d.ts +29 -0
  337. package/dist/media/image-dimensions.d.ts +28 -0
  338. package/dist/media/image-dimensions.js +34 -0
  339. package/dist/media/index.d.ts +10 -0
  340. package/dist/media/index.js +10 -0
  341. package/dist/search/CommandPalette.svelte +257 -0
  342. package/dist/search/CommandPalette.svelte.d.ts +59 -0
  343. package/dist/search/index.d.ts +13 -0
  344. package/dist/search/index.js +13 -0
  345. package/dist/search/types.d.ts +40 -0
  346. package/dist/search/types.js +20 -0
  347. package/dist/seo/Head.svelte +190 -0
  348. package/dist/seo/Head.svelte.d.ts +86 -0
  349. package/dist/seo/JsonLd.svelte +30 -0
  350. package/dist/seo/JsonLd.svelte.d.ts +20 -0
  351. package/dist/seo/index.d.ts +18 -0
  352. package/dist/seo/index.js +17 -0
  353. package/dist/seo/json-ld.d.ts +33 -0
  354. package/dist/seo/json-ld.js +29 -0
  355. package/dist/seo/og-image.d.ts +72 -0
  356. package/dist/seo/og-image.js +82 -0
  357. package/dist/seo/types.d.ts +43 -0
  358. package/dist/seo/types.js +6 -0
  359. package/dist/theme.css +161 -0
  360. package/dist/tips/PopoverTip.svelte +80 -0
  361. package/dist/tips/PopoverTip.svelte.d.ts +33 -0
  362. package/dist/tips/Tip.svelte +91 -0
  363. package/dist/tips/Tip.svelte.d.ts +25 -0
  364. package/dist/tips/index.d.ts +15 -0
  365. package/dist/tips/index.js +14 -0
  366. package/dist/tips/tip-stack.d.ts +26 -0
  367. package/dist/tips/tip-stack.js +37 -0
  368. package/dist/tips/tip-store.svelte.d.ts +75 -0
  369. package/dist/tips/tip-store.svelte.js +158 -0
  370. package/dist/tips/types.d.ts +46 -0
  371. package/dist/tips/types.js +6 -0
  372. package/dist/ui/Accordion.svelte +75 -0
  373. package/dist/ui/Accordion.svelte.d.ts +50 -0
  374. package/dist/ui/AccordionItem.svelte +73 -0
  375. package/dist/ui/AccordionItem.svelte.d.ts +18 -0
  376. package/dist/ui/Alert.svelte +61 -0
  377. package/dist/ui/Alert.svelte.d.ts +37 -0
  378. package/dist/ui/Badge.svelte +39 -0
  379. package/dist/ui/Badge.svelte.d.ts +32 -0
  380. package/dist/ui/Button.svelte +44 -0
  381. package/dist/ui/Button.svelte.d.ts +29 -0
  382. package/dist/ui/ButtonGroup.svelte +24 -0
  383. package/dist/ui/ButtonGroup.svelte.d.ts +17 -0
  384. package/dist/ui/Card.svelte +49 -0
  385. package/dist/ui/Card.svelte.d.ts +44 -0
  386. package/dist/ui/Chip.svelte +58 -0
  387. package/dist/ui/Chip.svelte.d.ts +21 -0
  388. package/dist/ui/ConfirmButton.svelte +75 -0
  389. package/dist/ui/ConfirmButton.svelte.d.ts +48 -0
  390. package/dist/ui/ContextMenu.svelte +75 -0
  391. package/dist/ui/ContextMenu.svelte.d.ts +25 -0
  392. package/dist/ui/Disclosure.svelte +46 -0
  393. package/dist/ui/Disclosure.svelte.d.ts +22 -0
  394. package/dist/ui/Divider.svelte +47 -0
  395. package/dist/ui/Divider.svelte.d.ts +28 -0
  396. package/dist/ui/DropdownMenu.svelte +44 -0
  397. package/dist/ui/DropdownMenu.svelte.d.ts +30 -0
  398. package/dist/ui/EmptyState.svelte +37 -0
  399. package/dist/ui/EmptyState.svelte.d.ts +22 -0
  400. package/dist/ui/ExternalLink.svelte +38 -0
  401. package/dist/ui/ExternalLink.svelte.d.ts +34 -0
  402. package/dist/ui/Heading.svelte +58 -0
  403. package/dist/ui/Heading.svelte.d.ts +22 -0
  404. package/dist/ui/Link.svelte +34 -0
  405. package/dist/ui/Link.svelte.d.ts +20 -0
  406. package/dist/ui/LinkButton.svelte +51 -0
  407. package/dist/ui/LinkButton.svelte.d.ts +33 -0
  408. package/dist/ui/Menu.svelte +24 -0
  409. package/dist/ui/Menu.svelte.d.ts +13 -0
  410. package/dist/ui/MenuCheckboxItem.svelte +33 -0
  411. package/dist/ui/MenuCheckboxItem.svelte.d.ts +20 -0
  412. package/dist/ui/MenuItem.svelte +73 -0
  413. package/dist/ui/MenuItem.svelte.d.ts +21 -0
  414. package/dist/ui/MenuRadioGroup.svelte +25 -0
  415. package/dist/ui/MenuRadioGroup.svelte.d.ts +20 -0
  416. package/dist/ui/MenuRadioItem.svelte +20 -0
  417. package/dist/ui/MenuRadioItem.svelte.d.ts +15 -0
  418. package/dist/ui/MenuSeparator.svelte +27 -0
  419. package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
  420. package/dist/ui/MenuSub.svelte +24 -0
  421. package/dist/ui/MenuSub.svelte.d.ts +15 -0
  422. package/dist/ui/Popover.svelte +63 -0
  423. package/dist/ui/Popover.svelte.d.ts +40 -0
  424. package/dist/ui/ShareButton.svelte +64 -0
  425. package/dist/ui/ShareButton.svelte.d.ts +39 -0
  426. package/dist/ui/Skeleton.svelte +43 -0
  427. package/dist/ui/Skeleton.svelte.d.ts +22 -0
  428. package/dist/ui/Spinner.svelte +41 -0
  429. package/dist/ui/Spinner.svelte.d.ts +35 -0
  430. package/dist/ui/Stack.svelte +36 -0
  431. package/dist/ui/Stack.svelte.d.ts +48 -0
  432. package/dist/ui/ThemeToggle.svelte +65 -0
  433. package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
  434. package/dist/ui/ToggleButton.svelte +58 -0
  435. package/dist/ui/ToggleButton.svelte.d.ts +45 -0
  436. package/dist/ui/ToggleGroup.svelte +142 -0
  437. package/dist/ui/ToggleGroup.svelte.d.ts +86 -0
  438. package/dist/ui/Toolbar.svelte +40 -0
  439. package/dist/ui/Toolbar.svelte.d.ts +20 -0
  440. package/dist/ui/ToolbarButton.svelte +29 -0
  441. package/dist/ui/ToolbarButton.svelte.d.ts +21 -0
  442. package/dist/ui/ToolbarLink.svelte +21 -0
  443. package/dist/ui/ToolbarLink.svelte.d.ts +12 -0
  444. package/dist/ui/ToolbarSeparator.svelte +22 -0
  445. package/dist/ui/ToolbarSeparator.svelte.d.ts +12 -0
  446. package/dist/ui/ToolbarToggleGroup.svelte +66 -0
  447. package/dist/ui/ToolbarToggleGroup.svelte.d.ts +52 -0
  448. package/dist/ui/Tooltip.svelte +54 -0
  449. package/dist/ui/Tooltip.svelte.d.ts +35 -0
  450. package/dist/ui/VisuallyHidden.svelte +23 -0
  451. package/dist/ui/VisuallyHidden.svelte.d.ts +10 -0
  452. package/dist/ui/accordion-context.d.ts +26 -0
  453. package/dist/ui/accordion-context.js +23 -0
  454. package/dist/ui/colour-token.d.ts +41 -0
  455. package/dist/ui/colour-token.js +49 -0
  456. package/dist/ui/context-menu.d.ts +13 -0
  457. package/dist/ui/context-menu.js +13 -0
  458. package/dist/ui/index.d.ts +63 -0
  459. package/dist/ui/index.js +62 -0
  460. package/dist/ui/menu-context.d.ts +20 -0
  461. package/dist/ui/menu-context.js +22 -0
  462. package/dist/ui/menu-parts/CheckboxItemPart.svelte +26 -0
  463. package/dist/ui/menu-parts/CheckboxItemPart.svelte.d.ts +5 -0
  464. package/dist/ui/menu-parts/ItemPart.svelte +29 -0
  465. package/dist/ui/menu-parts/ItemPart.svelte.d.ts +5 -0
  466. package/dist/ui/menu-parts/RadioGroupPart.svelte +24 -0
  467. package/dist/ui/menu-parts/RadioGroupPart.svelte.d.ts +5 -0
  468. package/dist/ui/menu-parts/RadioItemPart.svelte +22 -0
  469. package/dist/ui/menu-parts/RadioItemPart.svelte.d.ts +5 -0
  470. package/dist/ui/menu-parts/SeparatorPart.svelte +16 -0
  471. package/dist/ui/menu-parts/SeparatorPart.svelte.d.ts +5 -0
  472. package/dist/ui/menu-parts/SubPart.svelte +28 -0
  473. package/dist/ui/menu-parts/SubPart.svelte.d.ts +5 -0
  474. package/dist/ui/menu-parts/menu-parts.d.ts +104 -0
  475. package/dist/ui/menu-parts/menu-parts.js +31 -0
  476. package/dist/ui/menu-parts/parts.d.ts +12 -0
  477. package/dist/ui/menu-parts/parts.js +27 -0
  478. package/dist/ui/slug.d.ts +19 -0
  479. package/dist/ui/slug.js +25 -0
  480. package/dist/ui/toggle-classes.d.ts +39 -0
  481. package/dist/ui/toggle-classes.js +36 -0
  482. package/dist/ui/tones.d.ts +103 -0
  483. package/dist/ui/tones.js +240 -0
  484. package/package.json +41 -25
@@ -0,0 +1,80 @@
1
+ <!-- @component
2
+ A `Tip` anchored beside the trigger it is about, which renders inside it.
3
+ -->
4
+ <!--
5
+ PopoverTip — a `Tip` anchored to the thing it is talking about: the
6
+ caller's own trigger renders inside, and the bubble floats beside it.
7
+
8
+ Built on bits-ui's `Popover` rather than the hand-measured `position: fixed`
9
+ span the old copy used (a layout read per scroll frame, page-wide, for a
10
+ bubble that never moves relative to what it points at) — bits-ui's
11
+ `Popover.Content` computes and keeps that position via Floating UI, and
12
+ handles focus containment and outside-click dismissal for free.
13
+
14
+ `open` is NOT bound: it is a pure function of the tip's own state
15
+ (`available` and not yet dismissed/completed), read back from the store. A
16
+ tip shows because the store's rules say it may, never because the reader
17
+ clicked the trigger — so the trigger element has its bits-ui click/keydown
18
+ toggle handlers stripped (the `onclick`/`onkeydown` overrides below); it
19
+ keeps only the id/ref wiring `Popover.Content` needs to float against it.
20
+ Otherwise a click on an ordinary anchor button could pop open an EMPTY
21
+ bubble for a tip the store has not decided to show yet (`Tip`'s own
22
+ `{#if visible}` gate would render nothing inside).
23
+
24
+ Escape closing is arbitrated by the shared `TipStack`, not by bits-ui's own
25
+ (unrelated) escape-layer stacking order: `onEscapeKeydown` blocks the
26
+ default close unless this tip is the page's topmost, so two tips opened
27
+ from two different `PopoverTip`s still resolve "which one does Escape
28
+ close" the same way two `Tip`s do. Whatever closes the popover — Escape
29
+ when topmost, or a click outside it — is treated as a dismissal and fed
30
+ back into the store via `onOpenChange`, so the store and the floating
31
+ layer never disagree about whether the tip is showing.
32
+
33
+ `Popover.Content` is `forceMount`ed: it stays in the DOM whether or not
34
+ `open` is true, with bits-ui only toggling its floating position/listeners.
35
+ That is what lets `Tip` — mounted unconditionally inside it — run its own
36
+ `shouldDisplay`/`markShown` effect and flip the store to `available` in the
37
+ first place. Without `forceMount`, `Content` would itself wait for `open`
38
+ before rendering `Tip` at all, and `open` here is derived FROM what `Tip`
39
+ decides — a deadlock neither side could break.
40
+ -->
41
+ <script lang="ts">import { Popover } from "bits-ui";
42
+ import Tip from "./Tip.svelte";
43
+ import { soloTipStack } from "./tip-stack.js";
44
+ let { config, store, stack = soloTipStack, placement = "top", children, dismissLabel = "Dismiss", class: className } = $props();
45
+ const placementSide = {
46
+ top: "top",
47
+ bottom: "bottom",
48
+ left: "left",
49
+ right: "right"
50
+ };
51
+ /** True exactly when `Tip` would render itself, read back from the store. */
52
+ const open = $derived(store.getState(config.id).status === "available");
53
+ const handleOpenChange = (next) => {
54
+ if (!next) store.dismiss(config.id);
55
+ };
56
+ const handleEscapeKeydown = (event) => {
57
+ if (!stack.isTopmost(config.id)) event.preventDefault();
58
+ };
59
+ </script>
60
+
61
+ <Popover.Root {open} onOpenChange={handleOpenChange}>
62
+ <Popover.Trigger>
63
+ {#snippet child({ props })}
64
+ <span {...props} onclick={undefined} onkeydown={undefined} class="contents">
65
+ {@render children()}
66
+ </span>
67
+ {/snippet}
68
+ </Popover.Trigger>
69
+ <Popover.Portal>
70
+ <Popover.Content
71
+ forceMount
72
+ side={placementSide[placement]}
73
+ sideOffset={8}
74
+ onEscapeKeydown={handleEscapeKeydown}
75
+ class={['z-50 w-64', className]}
76
+ >
77
+ <Tip {config} {store} {stack} {dismissLabel} />
78
+ </Popover.Content>
79
+ </Popover.Portal>
80
+ </Popover.Root>
@@ -0,0 +1,33 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type TipStack } from './tip-stack.ts';
3
+ import type { TipStore } from './tip-store.svelte.ts';
4
+ import type { TipConfig, TipPlacement } from './types.ts';
5
+ interface Props {
6
+ /** The tip’s id, copy and actions. */
7
+ config: TipConfig;
8
+ /** The store that decides whether this shows and remembers that it did. */
9
+ store: TipStore;
10
+ /**
11
+ * Shared with the page’s other tips so Escape closes only the top one.
12
+ * @defaultValue `soloTipStack` (shares Escape with no other tip)
13
+ */
14
+ stack?: TipStack | undefined;
15
+ /**
16
+ * Which side of the trigger the bubble sits on.
17
+ * @defaultValue `'top'`
18
+ */
19
+ placement?: TipPlacement | undefined;
20
+ /** The trigger — a button, a field, whatever the tip is about. */
21
+ children: Snippet<[]>;
22
+ /**
23
+ * Accessible name of the tip's dismiss button.
24
+ * @defaultValue `'Dismiss'`
25
+ */
26
+ dismissLabel?: string | undefined;
27
+ /** Extra classes, merged last. */
28
+ class?: string | undefined;
29
+ }
30
+ /** A `Tip` anchored beside the trigger it is about, which renders inside it. */
31
+ declare const PopoverTip: import("svelte").Component<Props, {}, "">;
32
+ type PopoverTip = ReturnType<typeof PopoverTip>;
33
+ export default PopoverTip;
@@ -0,0 +1,91 @@
1
+ <!-- @component
2
+ One piece of inline guidance that shows only when its store allows and closes for good once dismissed or acted on.
3
+ -->
4
+ <!--
5
+ Tip — one piece of inline guidance: a title, an optional sentence, and the
6
+ actions it offers. Shows itself only if the store says it may, records that
7
+ it did, and closes for good when the reader dismisses it or takes an
8
+ action.
9
+
10
+ A daisyUI `alert` rather than a hand-rolled card — no hard-coded colours, no
11
+ hand-drawn arrow (that belongs to `PopoverTip`, where a tip is actually
12
+ anchored to something an arrow can point at; an inline tip sits in the flow
13
+ and has nothing to point at).
14
+
15
+ `role="status"` with `aria-live="polite"`: a tip is an offer, not an
16
+ interruption, so it is announced at the next pause rather than immediately.
17
+
18
+ The Escape-to-dismiss listener is hand-rolled rather than left to bits-ui:
19
+ this component has no bits-ui primitive backing it (it is a plain `alert`,
20
+ not a dialog or popover), and the key has to be scoped to "only the topmost
21
+ tip on the page" via the shared `TipStack` — a concern bits-ui's own
22
+ Escape handling knows nothing about. `PopoverTip` wires the same stack into
23
+ its bits-ui `Popover` instead, once there is a floating layer to hand it to.
24
+ -->
25
+ <script lang="ts">import { soloTipStack } from "./tip-stack.js";
26
+ let { config, store, stack = soloTipStack, dismissLabel = "Dismiss", class: className } = $props();
27
+ let visible = $state(false);
28
+ // The decision is made on mount, not during render: `markShown` writes, and a
29
+ // write inside a derivation would run again on every re-render.
30
+ $effect(() => {
31
+ if (!store.shouldDisplay(config.id)) return;
32
+ visible = true;
33
+ store.markShown(config.id);
34
+ });
35
+ $effect(() => {
36
+ if (!visible) return;
37
+ stack.push(config.id);
38
+ const onKey = (event) => {
39
+ if (event.key !== "Escape" || !stack.isTopmost(config.id)) return;
40
+ event.preventDefault();
41
+ dismiss();
42
+ };
43
+ globalThis.addEventListener("keydown", onKey);
44
+ return () => {
45
+ stack.pop(config.id);
46
+ globalThis.removeEventListener("keydown", onKey);
47
+ };
48
+ });
49
+ const dismiss = () => {
50
+ store.dismiss(config.id);
51
+ visible = false;
52
+ };
53
+ /**
54
+ * Taking an action COMPLETES the tip rather than dismissing it. The two are
55
+ * different facts — "they did the thing" and "they made it go away" — and
56
+ * the store keeps them apart so a later "show me what I skipped" can tell.
57
+ */
58
+ const act = async (action) => {
59
+ await action.handler();
60
+ store.complete(config.id);
61
+ visible = false;
62
+ };
63
+ </script>
64
+
65
+ {#if visible}
66
+ <div class={['alert alert-info alert-soft items-start', className]} role="status" aria-live="polite">
67
+ <div class="flex-1 space-y-2">
68
+ <p class="font-semibold">{config.title}</p>
69
+ {#if config.message !== undefined && config.message !== ''}
70
+ <p class="text-sm">{config.message}</p>
71
+ {/if}
72
+ {#if config.actions !== undefined && config.actions.length > 0}
73
+ <div class="flex flex-wrap gap-2">
74
+ {#each config.actions as action (action.id)}
75
+ <button type="button" class="btn btn-sm" onclick={() => act(action)}>
76
+ {action.title}
77
+ </button>
78
+ {/each}
79
+ </div>
80
+ {/if}
81
+ </div>
82
+ <button
83
+ type="button"
84
+ class="btn btn-ghost btn-sm btn-circle"
85
+ onclick={dismiss}
86
+ aria-label={dismissLabel}
87
+ >
88
+ ✕
89
+ </button>
90
+ </div>
91
+ {/if}
@@ -0,0 +1,25 @@
1
+ import { type TipStack } from './tip-stack.ts';
2
+ import type { TipStore } from './tip-store.svelte.ts';
3
+ import type { TipConfig } from './types.ts';
4
+ interface Props {
5
+ /** The tip’s id, copy and actions. */
6
+ config: TipConfig;
7
+ /** The store that decides whether this shows and remembers that it did. */
8
+ store: TipStore;
9
+ /**
10
+ * Shared with the page's other tips so Escape closes only the top one.
11
+ * @defaultValue `soloTipStack` (shares Escape with no other tip)
12
+ */
13
+ stack?: TipStack | undefined;
14
+ /**
15
+ * Accessible name of the dismiss button.
16
+ * @defaultValue `'Dismiss'`
17
+ */
18
+ dismissLabel?: string | undefined;
19
+ /** Extra classes, merged last. */
20
+ class?: string | undefined;
21
+ }
22
+ /** One piece of inline guidance that shows only when its store allows and closes for good once dismissed or acted on. */
23
+ declare const Tip: import("svelte").Component<Props, {}, "">;
24
+ type Tip = ReturnType<typeof Tip>;
25
+ export default Tip;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@thomasfosterau/components/tips` — a separate entry point so it never sits on
3
+ * the always-loaded path of a consumer that does not use it.
4
+ *
5
+ * TipKit — contextual, dismissible guidance: an inline tip, a tip anchored to
6
+ * its trigger, the store that decides when either may show, and the stack
7
+ * that decides which one Escape closes.
8
+ *
9
+ * @module
10
+ */
11
+ export { default as PopoverTip } from './PopoverTip.svelte';
12
+ export { default as Tip } from './Tip.svelte';
13
+ export { createTipStack, soloTipStack, type TipStack } from './tip-stack.ts';
14
+ export { browserTipStorage, createTipStore, memoryTipStorage, type TipStorage, type TipStoreOptions, type TipStore, } from './tip-store.svelte.ts';
15
+ export type { TipAction, TipConfig, TipFrequency, TipPlacement, TipRule, TipState, TipStatus, } from './types.ts';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@thomasfosterau/components/tips` — a separate entry point so it never sits on
3
+ * the always-loaded path of a consumer that does not use it.
4
+ *
5
+ * TipKit — contextual, dismissible guidance: an inline tip, a tip anchored to
6
+ * its trigger, the store that decides when either may show, and the stack
7
+ * that decides which one Escape closes.
8
+ *
9
+ * @module
10
+ */
11
+ export { default as PopoverTip } from './PopoverTip.svelte';
12
+ export { default as Tip } from './Tip.svelte';
13
+ export { createTipStack, soloTipStack } from "./tip-stack.js";
14
+ export { browserTipStorage, createTipStore, memoryTipStorage, } from "./tip-store.svelte.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The tip stack: which tips are on screen, most recent last.
3
+ *
4
+ * It exists for one keystroke. Escape must close the tip the reader is looking
5
+ * at — the last one to appear — and not every tip on the page at once. Each
6
+ * visible tip asks the stack whether it is on top before acting on the key.
7
+ *
8
+ * @module
9
+ */
10
+ /** Which tips are on screen, most recent last, so Escape closes only the topmost one. */
11
+ export interface TipStack {
12
+ /** Mark a tip visible, moving it to the top if it was already there. */
13
+ push: (id: string) => void;
14
+ /** Mark a tip no longer visible. */
15
+ pop: (id: string) => void;
16
+ /** Whether this tip is the one Escape should close. */
17
+ isTopmost: (id: string) => boolean;
18
+ }
19
+ /**
20
+ * A fresh stack. Create one per app (or per test) and hand it to the tips; a
21
+ * module-level singleton would make two mounted apps — or two tests — share a
22
+ * stack, which is exactly the bug this is meant to prevent.
23
+ */
24
+ export declare const createTipStack: () => TipStack;
25
+ /** A stack that always says yes — for a tip mounted without one. */
26
+ export declare const soloTipStack: TipStack;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The tip stack: which tips are on screen, most recent last.
3
+ *
4
+ * It exists for one keystroke. Escape must close the tip the reader is looking
5
+ * at — the last one to appear — and not every tip on the page at once. Each
6
+ * visible tip asks the stack whether it is on top before acting on the key.
7
+ *
8
+ * @module
9
+ */
10
+ /**
11
+ * A fresh stack. Create one per app (or per test) and hand it to the tips; a
12
+ * module-level singleton would make two mounted apps — or two tests — share a
13
+ * stack, which is exactly the bug this is meant to prevent.
14
+ */
15
+ export const createTipStack = () => {
16
+ const stack = [];
17
+ return {
18
+ push: (id) => {
19
+ const index = stack.indexOf(id);
20
+ if (index !== -1)
21
+ stack.splice(index, 1);
22
+ stack.push(id);
23
+ },
24
+ pop: (id) => {
25
+ const index = stack.indexOf(id);
26
+ if (index !== -1)
27
+ stack.splice(index, 1);
28
+ },
29
+ isTopmost: (id) => stack.length > 0 && stack[stack.length - 1] === id,
30
+ };
31
+ };
32
+ /** A stack that always says yes — for a tip mounted without one. */
33
+ export const soloTipStack = {
34
+ push: () => { },
35
+ pop: () => { },
36
+ isTopmost: () => true,
37
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The tip store: which tips exist, which have been seen, and whether one may
3
+ * show right now.
4
+ *
5
+ * This is a plain object with `$state` inside it, so a tip re-renders when
6
+ * its state changes rather than when a promise resolves. Rules are
7
+ * synchronous for the same reason a network round-trip would show a tip a
8
+ * beat after the page did, while the reader is already doing something else:
9
+ * await first, then declare the tip.
10
+ *
11
+ * Storage is optional and every access is guarded: a private window can have
12
+ * `localStorage` present and throwing, and a tip system that breaks the page
13
+ * it is meant to explain is worse than one that forgets.
14
+ *
15
+ * @module
16
+ */
17
+ import type { TipConfig, TipState } from './types.ts';
18
+ /**
19
+ * The subset of the DOM `Storage` interface a tip store reads and writes, so a
20
+ * test can pass a plain object.
21
+ *
22
+ * The signatures are the DOM `Storage` interface's own, `null` included: this
23
+ * type exists so `globalThis.localStorage` satisfies it structurally.
24
+ */
25
+ export interface TipStorage {
26
+ getItem: (key: string) => string | null;
27
+ setItem: (key: string, value: string) => void;
28
+ removeItem: (key: string) => void;
29
+ }
30
+ /** What `createTipStore` returns: registers tips, decides whether each may show, and records what the reader did with it. */
31
+ export interface TipStore {
32
+ /** Declare a tip. Re-registering the same id replaces its configuration. */
33
+ register: (config: TipConfig) => void;
34
+ /** Whether this tip may show right now. */
35
+ shouldDisplay: (id: string) => boolean;
36
+ /** What is remembered about this tip. */
37
+ getState: (id: string) => TipState;
38
+ /** Record that it has just been shown. */
39
+ markShown: (id: string) => void;
40
+ /** The reader closed it: never show it again. */
41
+ dismiss: (id: string) => void;
42
+ /** The reader took its action: never show it again. */
43
+ complete: (id: string) => void;
44
+ /** Forget every tip's state — the "show me the tour again" button. */
45
+ resetAll: () => void;
46
+ }
47
+ /**
48
+ * Storage that forgets when the page does.
49
+ *
50
+ * The fallback for a browser that refuses persistence, and the right storage
51
+ * for a test or a Storybook story — a tip dismissed while reviewing one story
52
+ * must not be invisible in the next reviewer's session.
53
+ */
54
+ export declare const memoryTipStorage: () => TipStorage;
55
+ /**
56
+ * `globalThis.localStorage` where it exists and is usable, and
57
+ * {@link memoryTipStorage} where it does not — the server, and a browser that
58
+ * refuses storage.
59
+ */
60
+ export declare const browserTipStorage: () => TipStorage;
61
+ /** Options for {@link createTipStore}. */
62
+ export interface TipStoreOptions {
63
+ /** Defaults to {@link browserTipStorage}. */
64
+ readonly storage?: TipStorage | undefined;
65
+ /** The storage key prefix each tip's entry is written under. */
66
+ readonly prefix?: string | undefined;
67
+ }
68
+ /**
69
+ * Build a tip store over the given storage.
70
+ *
71
+ * Call once, high in the tree, and hand it to the tips (or put it in a context
72
+ * of the app's own). The in-memory state is what drives re-rendering; storage
73
+ * is where it survives a reload.
74
+ */
75
+ export declare const createTipStore: (options?: TipStoreOptions) => TipStore;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The tip store: which tips exist, which have been seen, and whether one may
3
+ * show right now.
4
+ *
5
+ * This is a plain object with `$state` inside it, so a tip re-renders when
6
+ * its state changes rather than when a promise resolves. Rules are
7
+ * synchronous for the same reason a network round-trip would show a tip a
8
+ * beat after the page did, while the reader is already doing something else:
9
+ * await first, then declare the tip.
10
+ *
11
+ * Storage is optional and every access is guarded: a private window can have
12
+ * `localStorage` present and throwing, and a tip system that breaks the page
13
+ * it is meant to explain is worse than one that forgets.
14
+ *
15
+ * @module
16
+ */
17
+ const DEFAULT_PREFIX = 'tips:';
18
+ /** Milliseconds a frequency has to elapse before a tip may reappear. */
19
+ const INTERVALS = {
20
+ hourly: 60 * 60 * 1000,
21
+ daily: 24 * 60 * 60 * 1000,
22
+ weekly: 7 * 24 * 60 * 60 * 1000,
23
+ };
24
+ /**
25
+ * Storage that forgets when the page does.
26
+ *
27
+ * The fallback for a browser that refuses persistence, and the right storage
28
+ * for a test or a Storybook story — a tip dismissed while reviewing one story
29
+ * must not be invisible in the next reviewer's session.
30
+ */
31
+ export const memoryTipStorage = () => {
32
+ const memory = new Map();
33
+ return {
34
+ getItem: (key) => memory.get(key) ?? null,
35
+ setItem: (key, value) => {
36
+ memory.set(key, value);
37
+ },
38
+ removeItem: (key) => {
39
+ memory.delete(key);
40
+ },
41
+ };
42
+ };
43
+ /**
44
+ * `globalThis.localStorage` where it exists and is usable, and
45
+ * {@link memoryTipStorage} where it does not — the server, and a browser that
46
+ * refuses storage.
47
+ */
48
+ export const browserTipStorage = () => {
49
+ try {
50
+ const storage = globalThis.localStorage;
51
+ // Presence is not permission: Safari's private mode throws on write.
52
+ storage.setItem('tips:probe', '1');
53
+ storage.removeItem('tips:probe');
54
+ return storage;
55
+ }
56
+ catch {
57
+ return memoryTipStorage();
58
+ }
59
+ };
60
+ /**
61
+ * Build a tip store over the given storage.
62
+ *
63
+ * Call once, high in the tree, and hand it to the tips (or put it in a context
64
+ * of the app's own). The in-memory state is what drives re-rendering; storage
65
+ * is where it survives a reload.
66
+ */
67
+ export const createTipStore = (options = {}) => {
68
+ const storage = options.storage ?? browserTipStorage();
69
+ const prefix = options.prefix ?? DEFAULT_PREFIX;
70
+ const configs = new Map();
71
+ const states = $state(new Map());
72
+ const read = (id) => {
73
+ const live = states.get(id);
74
+ if (live !== undefined)
75
+ return live;
76
+ let stored = { id, status: 'pending', displayCount: 0 };
77
+ try {
78
+ const raw = storage.getItem(`${prefix}${id}`);
79
+ // Trusted only for its shape: it is this app's own write, and a corrupt
80
+ // entry falls back to "never seen" rather than throwing at the reader.
81
+ if (raw !== null)
82
+ stored = JSON.parse(raw);
83
+ }
84
+ catch {
85
+ /* unreadable or malformed — treat the tip as unseen */
86
+ }
87
+ states.set(id, stored);
88
+ return stored;
89
+ };
90
+ const write = (next) => {
91
+ states.set(next.id, next);
92
+ try {
93
+ storage.setItem(`${prefix}${next.id}`, JSON.stringify(next));
94
+ }
95
+ catch {
96
+ /* storage refused; the in-memory state still drives this session */
97
+ }
98
+ };
99
+ const frequencyAllows = (config, state) => {
100
+ if (state.lastShown === undefined)
101
+ return true;
102
+ const frequency = config.frequency ?? 'immediate';
103
+ if (frequency === 'immediate')
104
+ return true;
105
+ if (frequency === 'once')
106
+ return false;
107
+ const since = Date.now() - new Date(state.lastShown).getTime();
108
+ return since >= (INTERVALS[frequency] ?? 0);
109
+ };
110
+ return {
111
+ register: (config) => {
112
+ configs.set(config.id, config);
113
+ },
114
+ getState: read,
115
+ shouldDisplay: (id) => {
116
+ const config = configs.get(id);
117
+ // An unregistered tip shows nothing: a typo in an id should be an absent
118
+ // tip, not a tip with no rules.
119
+ if (config === undefined)
120
+ return false;
121
+ const state = read(id);
122
+ if (state.status === 'dismissed' || state.status === 'completed')
123
+ return false;
124
+ if (config.maxDisplayCount !== undefined && state.displayCount >= config.maxDisplayCount) {
125
+ return false;
126
+ }
127
+ if (!frequencyAllows(config, state))
128
+ return false;
129
+ return (config.rules ?? []).every((rule) => rule.evaluate());
130
+ },
131
+ markShown: (id) => {
132
+ const state = read(id);
133
+ write({
134
+ ...state,
135
+ status: 'available',
136
+ displayCount: state.displayCount + 1,
137
+ lastShown: new Date().toISOString(),
138
+ });
139
+ },
140
+ dismiss: (id) => {
141
+ write({ ...read(id), status: 'dismissed', dismissedAt: new Date().toISOString() });
142
+ },
143
+ complete: (id) => {
144
+ write({ ...read(id), status: 'completed', completedAt: new Date().toISOString() });
145
+ },
146
+ resetAll: () => {
147
+ for (const id of [...states.keys(), ...configs.keys()]) {
148
+ states.delete(id);
149
+ try {
150
+ storage.removeItem(`${prefix}${id}`);
151
+ }
152
+ catch {
153
+ /* storage refused; the in-memory reset still stands */
154
+ }
155
+ }
156
+ },
157
+ };
158
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * TipKit types — contextual, dismissible guidance, after Apple's TipKit.
3
+ *
4
+ * @module
5
+ */
6
+ /** How often a tip may reappear once it has been shown. */
7
+ export type TipFrequency = 'immediate' | 'hourly' | 'daily' | 'weekly' | 'once';
8
+ /** Where a tip has got to in its life. */
9
+ export type TipStatus = 'pending' | 'available' | 'dismissed' | 'completed';
10
+ /** Which side of its anchor a popover tip sits on. */
11
+ export type TipPlacement = 'top' | 'bottom' | 'left' | 'right';
12
+ /** A condition that must hold for a tip to show. */
13
+ export interface TipRule {
14
+ readonly id: string;
15
+ /** Synchronous on purpose — see the note in `tip-store.svelte.ts`. */
16
+ readonly evaluate: () => boolean;
17
+ }
18
+ /** Something the reader can do from the tip. */
19
+ export interface TipAction {
20
+ readonly id: string;
21
+ readonly title: string;
22
+ readonly handler: () => void | Promise<void>;
23
+ }
24
+ /** A tip, as declared by the page that owns it. */
25
+ export interface TipConfig {
26
+ readonly id: string;
27
+ readonly title: string;
28
+ readonly message?: string | undefined;
29
+ readonly actions?: readonly TipAction[] | undefined;
30
+ /** Every rule must hold. */
31
+ readonly rules?: readonly TipRule[] | undefined;
32
+ /** Defaults to `immediate` — shown every time the page is visited. */
33
+ readonly frequency?: TipFrequency | undefined;
34
+ /** Never shown more than this many times, whatever the frequency says. */
35
+ readonly maxDisplayCount?: number | undefined;
36
+ }
37
+ /** What is remembered about a tip between visits. */
38
+ export interface TipState {
39
+ readonly id: string;
40
+ readonly status: TipStatus;
41
+ readonly displayCount: number;
42
+ /** ISO 8601; absent until the tip has been shown once. */
43
+ readonly lastShown?: string | undefined;
44
+ readonly dismissedAt?: string | undefined;
45
+ readonly completedAt?: string | undefined;
46
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * TipKit types — contextual, dismissible guidance, after Apple's TipKit.
3
+ *
4
+ * @module
5
+ */
6
+ export {};