@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,47 @@
1
+ /**
2
+ * Svelte context plumbing for the hotkey registry, plus the declarative
3
+ * `registerHotkeys` helper that pages and shared page components call to
4
+ * contribute page-scoped actions.
5
+ *
6
+ * @module
7
+ */
8
+ import { HotkeyRegistry, type HotkeyRegistryOptions } from './registry.svelte.ts';
9
+ import type { CommandAction, HotkeyScope } from './types.ts';
10
+ /** Raised by {@link getHotkeyRegistry} when no registry has been created above. */
11
+ export declare class HotkeyRegistryMissing extends Error {
12
+ readonly name = "HotkeyRegistryMissing";
13
+ }
14
+ /**
15
+ * Create the registry and place it in context. Call once, from the root
16
+ * layout, during component initialisation.
17
+ */
18
+ export declare function createHotkeyRegistry(options: HotkeyRegistryOptions): HotkeyRegistry;
19
+ /**
20
+ * Read the registry from context, or `undefined` when none was created.
21
+ *
22
+ * This is the seam that lets `CommandPalette` work in an app that never
23
+ * mounted a registry: it falls back to its `items` prop. Components that
24
+ * cannot work without one (`HotkeyProvider`, `HotkeyHelpDialog`) call
25
+ * {@link getHotkeyRegistry} instead, so the failure is loud.
26
+ */
27
+ export declare function tryGetHotkeyRegistry(): HotkeyRegistry | undefined;
28
+ /** Read the registry from context. Throws when none was created. */
29
+ export declare function getHotkeyRegistry(): HotkeyRegistry;
30
+ /**
31
+ * Declaratively register actions for the lifetime of the calling component.
32
+ * Registration is reactive: the `actions` thunk re-runs when its dependencies
33
+ * change (a resource's `canEdit` resolving, say), replacing the previous
34
+ * registration.
35
+ *
36
+ * ```svelte
37
+ * registerHotkeys(() =>
38
+ * post.canEdit
39
+ * ? [{ id: 'post.edit', label: 'Edit post', trigger: 'e', group: 'actions',
40
+ * run: () => goto(`/posts/${post.id}/edit`) }]
41
+ * : [],
42
+ * );
43
+ * ```
44
+ */
45
+ export declare function registerHotkeys(actions: () => readonly CommandAction[], options?: {
46
+ scope?: HotkeyScope | undefined;
47
+ }): void;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Svelte context plumbing for the hotkey registry, plus the declarative
3
+ * `registerHotkeys` helper that pages and shared page components call to
4
+ * contribute page-scoped actions.
5
+ *
6
+ * @module
7
+ */
8
+ import { getContext, setContext, untrack } from 'svelte';
9
+ import { HotkeyRegistry } from "./registry.svelte.js";
10
+ const CONTEXT_KEY = Symbol('@thomasfosterau/components:hotkey-registry');
11
+ /** Raised by {@link getHotkeyRegistry} when no registry has been created above. */
12
+ export class HotkeyRegistryMissing extends Error {
13
+ name = 'HotkeyRegistryMissing';
14
+ }
15
+ /**
16
+ * Create the registry and place it in context. Call once, from the root
17
+ * layout, during component initialisation.
18
+ */
19
+ export function createHotkeyRegistry(options) {
20
+ const registry = new HotkeyRegistry(options);
21
+ setContext(CONTEXT_KEY, registry);
22
+ return registry;
23
+ }
24
+ /**
25
+ * Read the registry from context, or `undefined` when none was created.
26
+ *
27
+ * This is the seam that lets `CommandPalette` work in an app that never
28
+ * mounted a registry: it falls back to its `items` prop. Components that
29
+ * cannot work without one (`HotkeyProvider`, `HotkeyHelpDialog`) call
30
+ * {@link getHotkeyRegistry} instead, so the failure is loud.
31
+ */
32
+ export function tryGetHotkeyRegistry() {
33
+ return getContext(CONTEXT_KEY);
34
+ }
35
+ /** Read the registry from context. Throws when none was created. */
36
+ export function getHotkeyRegistry() {
37
+ const registry = tryGetHotkeyRegistry();
38
+ if (!registry) {
39
+ throw new HotkeyRegistryMissing('HotkeyRegistry not found in context. ' +
40
+ 'createHotkeyRegistry() must be called by an ancestor (the root layout).');
41
+ }
42
+ return registry;
43
+ }
44
+ /**
45
+ * Declaratively register actions for the lifetime of the calling component.
46
+ * Registration is reactive: the `actions` thunk re-runs when its dependencies
47
+ * change (a resource's `canEdit` resolving, say), replacing the previous
48
+ * registration.
49
+ *
50
+ * ```svelte
51
+ * registerHotkeys(() =>
52
+ * post.canEdit
53
+ * ? [{ id: 'post.edit', label: 'Edit post', trigger: 'e', group: 'actions',
54
+ * run: () => goto(`/posts/${post.id}/edit`) }]
55
+ * : [],
56
+ * );
57
+ * ```
58
+ */
59
+ export function registerHotkeys(actions, options = {}) {
60
+ const registry = getHotkeyRegistry();
61
+ const scope = options.scope ?? 'page';
62
+ $effect(() => {
63
+ // Track only the `actions` thunk's own reactive inputs. `register()`
64
+ // reads *and* writes the registry's `#registrations` `$state`; left
65
+ // tracked, that read-then-write makes the effect re-trigger itself
66
+ // forever (`effect_update_depth_exceeded`, which crashed hydration on
67
+ // every page). `untrack` isolates the register call so the effect re-runs
68
+ // only when `actions()` changes, not when the array it writes does.
69
+ const batch = actions();
70
+ return untrack(() => registry.register(scope, batch));
71
+ });
72
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * hotkeys — the registry that answers "what can the viewer do right now", and
3
+ * the two surfaces that read it back.
4
+ *
5
+ * An app creates one registry in its root layout (`createHotkeyRegistry`),
6
+ * renders `HotkeyProvider` once to mount the key listener, and registers
7
+ * actions from anywhere below with `registerHotkeys` — global ones from the
8
+ * layout, page ones from the page, torn down when it unmounts. `search/`'s
9
+ * `CommandPalette` and `HotkeyHelpDialog` then list exactly what is live.
10
+ *
11
+ * Everything user-facing is a prop: the registry stores the labels the app
12
+ * registered, and the two components take their own chrome copy
13
+ * (`title`, `closeLabel`, `commandLabel`, …) with English defaults.
14
+ *
15
+ * The family is re-exported from the root barrel and is also its own entry,
16
+ * `@thomasfosterau/components/hotkeys`, so a consumer can
17
+ * `import('@thomasfosterau/components/hotkeys')` to load `HotkeyHelpDialog`
18
+ * lazily, on the first `?`, instead of in its entry chunk.
19
+ *
20
+ * @module
21
+ */
22
+ export { HotkeyRegistryMissing, createHotkeyRegistry, getHotkeyRegistry, registerHotkeys, tryGetHotkeyRegistry, } from './context.svelte.ts';
23
+ export { HotkeyRegistry, type HotkeyRegistryOptions } from './registry.svelte.ts';
24
+ export { default as HotkeyBadge } from './HotkeyBadge.svelte';
25
+ export { default as HotkeyHelpDialog } from './HotkeyHelpDialog.svelte';
26
+ export { default as HotkeyProvider } from './HotkeyProvider.svelte';
27
+ export { SEQUENCE_TIMEOUT_MS, hasChordModifier, isApplePlatform, isEditableTarget, isSequenceKey, matchChord, matchSequence, triggerDisplay, type SequenceMatch, type TriggerDisplay, } from './match.ts';
28
+ export type { CommandAction, CommandActionGroup, HotkeyChord, HotkeyScope, HotkeySequence, HotkeyTrigger, RegisteredAction, } from './types.ts';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * hotkeys — the registry that answers "what can the viewer do right now", and
3
+ * the two surfaces that read it back.
4
+ *
5
+ * An app creates one registry in its root layout (`createHotkeyRegistry`),
6
+ * renders `HotkeyProvider` once to mount the key listener, and registers
7
+ * actions from anywhere below with `registerHotkeys` — global ones from the
8
+ * layout, page ones from the page, torn down when it unmounts. `search/`'s
9
+ * `CommandPalette` and `HotkeyHelpDialog` then list exactly what is live.
10
+ *
11
+ * Everything user-facing is a prop: the registry stores the labels the app
12
+ * registered, and the two components take their own chrome copy
13
+ * (`title`, `closeLabel`, `commandLabel`, …) with English defaults.
14
+ *
15
+ * The family is re-exported from the root barrel and is also its own entry,
16
+ * `@thomasfosterau/components/hotkeys`, so a consumer can
17
+ * `import('@thomasfosterau/components/hotkeys')` to load `HotkeyHelpDialog`
18
+ * lazily, on the first `?`, instead of in its entry chunk.
19
+ *
20
+ * @module
21
+ */
22
+ export { HotkeyRegistryMissing, createHotkeyRegistry, getHotkeyRegistry, registerHotkeys, tryGetHotkeyRegistry, } from "./context.svelte.js";
23
+ export { HotkeyRegistry } from "./registry.svelte.js";
24
+ export { default as HotkeyBadge } from './HotkeyBadge.svelte';
25
+ export { default as HotkeyHelpDialog } from './HotkeyHelpDialog.svelte';
26
+ export { default as HotkeyProvider } from './HotkeyProvider.svelte';
27
+ export { SEQUENCE_TIMEOUT_MS, hasChordModifier, isApplePlatform, isEditableTarget, isSequenceKey, matchChord, matchSequence, triggerDisplay, } from "./match.js";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure key-matching helpers for the hotkey registry. No Svelte, no DOM
3
+ * mutation — just functions over `KeyboardEvent`s, trigger grammars and the
4
+ * sequence buffer, so the matching rules stay testable and `HotkeyProvider`
5
+ * stays thin.
6
+ *
7
+ * Trigger model:
8
+ *
9
+ * - **Modifier chords** (`'$mod+k'`) — held together. `$mod` is ⌘ on macOS
10
+ * and Ctrl elsewhere. Matched on their own; never buffered.
11
+ * - **Plain keys / sequences** (`'e'`, `'/'`, `['g', 'p']`) — pressed in
12
+ * order with no modifier held. A plain single key is just a sequence of
13
+ * length one, so matching is uniform.
14
+ *
15
+ * @module
16
+ */
17
+ import type { CommandAction, HotkeyTrigger, RegisteredAction } from './types.ts';
18
+ /** Sequence buffer entries expire after this many milliseconds. */
19
+ export declare const SEQUENCE_TIMEOUT_MS = 1000;
20
+ /** Whether the runtime platform treats `$mod` as ⌘ (macOS / iOS). */
21
+ export declare function isApplePlatform(): boolean;
22
+ /**
23
+ * Whether a keyboard event's target is an editable surface — a form field or
24
+ * a contenteditable region (which covers a rich-text editor's root). Hotkeys
25
+ * without `allowInEditable` stay inert there so typing never fires actions.
26
+ */
27
+ export declare function isEditableTarget(target: EventTarget | null): boolean;
28
+ /** Whether an event carries a chord modifier (⌘ / Ctrl / Alt). */
29
+ export declare function hasChordModifier(event: KeyboardEvent): boolean;
30
+ /**
31
+ * Whether an event's key can participate in a sequence: a single printable
32
+ * character pressed without chord modifiers.
33
+ */
34
+ export declare function isSequenceKey(event: KeyboardEvent): boolean;
35
+ /**
36
+ * Find the action whose modifier chord matches the event. `actions` must
37
+ * already be in precedence order (page before global, later registrations
38
+ * first).
39
+ */
40
+ export declare function matchChord(event: KeyboardEvent, actions: readonly RegisteredAction[]): CommandAction | undefined;
41
+ /** The result of `matchSequence`: the action a key buffer completes, and whether it could still grow into another. */
42
+ export interface SequenceMatch {
43
+ /** The action whose sequence the buffer completes, if any. */
44
+ exact: CommandAction | undefined;
45
+ /** Whether the buffer is a strict prefix of at least one sequence. */
46
+ isPrefix: boolean;
47
+ }
48
+ /**
49
+ * Match a candidate key buffer against the active plain-key triggers.
50
+ * `actions` must already be in precedence order.
51
+ */
52
+ export declare function matchSequence(buffer: readonly string[], actions: readonly RegisteredAction[]): SequenceMatch;
53
+ /** A trigger split into the keys a `HotkeyBadge` renders, and whether they are pressed in turn or together. */
54
+ export interface TriggerDisplay {
55
+ /**
56
+ * Keys to render as `<kbd>` chips, in order. `'$mod'` is left un-resolved
57
+ * so the rendering component can substitute the platform-appropriate
58
+ * label.
59
+ */
60
+ keys: readonly string[];
61
+ /** True when keys are pressed one after another rather than held together. */
62
+ sequential: boolean;
63
+ }
64
+ /** Project a trigger into its display form for help overlays and badges. */
65
+ export declare function triggerDisplay(trigger: HotkeyTrigger): TriggerDisplay;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Pure key-matching helpers for the hotkey registry. No Svelte, no DOM
3
+ * mutation — just functions over `KeyboardEvent`s, trigger grammars and the
4
+ * sequence buffer, so the matching rules stay testable and `HotkeyProvider`
5
+ * stays thin.
6
+ *
7
+ * Trigger model:
8
+ *
9
+ * - **Modifier chords** (`'$mod+k'`) — held together. `$mod` is ⌘ on macOS
10
+ * and Ctrl elsewhere. Matched on their own; never buffered.
11
+ * - **Plain keys / sequences** (`'e'`, `'/'`, `['g', 'p']`) — pressed in
12
+ * order with no modifier held. A plain single key is just a sequence of
13
+ * length one, so matching is uniform.
14
+ *
15
+ * @module
16
+ */
17
+ /** Sequence buffer entries expire after this many milliseconds. */
18
+ export const SEQUENCE_TIMEOUT_MS = 1000;
19
+ /** Whether the runtime platform treats `$mod` as ⌘ (macOS / iOS). */
20
+ export function isApplePlatform() {
21
+ // `globalThis.navigator` rather than the bare identifier: on a runtime
22
+ // without one, a direct reference would throw before the guard could run.
23
+ if (typeof globalThis.navigator === 'undefined')
24
+ return false;
25
+ return /mac|iphone|ipad|ipod/i.test(globalThis.navigator.platform);
26
+ }
27
+ /**
28
+ * Whether a keyboard event's target is an editable surface — a form field or
29
+ * a contenteditable region (which covers a rich-text editor's root). Hotkeys
30
+ * without `allowInEditable` stay inert there so typing never fires actions.
31
+ */
32
+ export function isEditableTarget(target) {
33
+ if (!(target instanceof HTMLElement))
34
+ return false;
35
+ return target.matches('input, textarea, select') || target.isContentEditable;
36
+ }
37
+ /** Whether a trigger is in the modifier-chord form (`'$mod+…'`). */
38
+ function isModifierChord(trigger) {
39
+ return typeof trigger === 'string' && trigger.includes('+');
40
+ }
41
+ /** Normalise a plain-key trigger to its sequence form. `undefined` for a chord. */
42
+ function asSequence(trigger) {
43
+ if (typeof trigger === 'string') {
44
+ return isModifierChord(trigger) ? undefined : [trigger];
45
+ }
46
+ return trigger;
47
+ }
48
+ /** Whether the event matches a `'$mod+key'` chord. */
49
+ // Modifier matching is a flat run of guard comparisons (mod / alt / shift /
50
+ // cross-platform exclusion); each early return is one rule of the chord
51
+ // grammar, and extracting them would obscure the contract.
52
+ function eventMatchesChord(event, chord) {
53
+ const parts = chord.split('+');
54
+ const key = parts.at(-1) ?? '';
55
+ // A plain array, not a `Set`: a chord carries at most three modifiers, so
56
+ // three `includes` scans beat building a hashed collection on every
57
+ // keystroke.
58
+ const modifiers = parts.slice(0, -1);
59
+ const wantsMod = modifiers.includes('$mod');
60
+ const wantsAlt = modifiers.includes('alt');
61
+ const wantsShift = modifiers.includes('shift');
62
+ const apple = isApplePlatform();
63
+ const modPressed = apple ? event.metaKey : event.ctrlKey;
64
+ // The non-$mod modifier must not also be held, so e.g. Ctrl+K on macOS
65
+ // doesn't trigger a ⌘K hotkey.
66
+ const otherModPressed = apple ? event.ctrlKey : event.metaKey;
67
+ if (wantsMod !== modPressed || otherModPressed)
68
+ return false;
69
+ if (wantsAlt !== event.altKey)
70
+ return false;
71
+ if (wantsShift && !event.shiftKey)
72
+ return false;
73
+ return event.key.toLowerCase() === key.toLowerCase();
74
+ }
75
+ /** Whether an event carries a chord modifier (⌘ / Ctrl / Alt). */
76
+ export function hasChordModifier(event) {
77
+ return event.metaKey || event.ctrlKey || event.altKey;
78
+ }
79
+ /**
80
+ * Whether an event's key can participate in a sequence: a single printable
81
+ * character pressed without chord modifiers.
82
+ */
83
+ export function isSequenceKey(event) {
84
+ return !hasChordModifier(event) && event.key.length === 1;
85
+ }
86
+ /**
87
+ * Find the action whose modifier chord matches the event. `actions` must
88
+ * already be in precedence order (page before global, later registrations
89
+ * first).
90
+ */
91
+ export function matchChord(event, actions) {
92
+ return actions.find(({ action }) => typeof action.trigger === 'string' &&
93
+ action.trigger.includes('+') &&
94
+ eventMatchesChord(event, action.trigger))?.action;
95
+ }
96
+ // A single-pass scan that classifies every trigger as exact-match / prefix /
97
+ // miss in one loop; splitting it into separate passes would be simpler per
98
+ // function but iterate the registrations three times per keystroke.
99
+ /**
100
+ * Match a candidate key buffer against the active plain-key triggers.
101
+ * `actions` must already be in precedence order.
102
+ */
103
+ export function matchSequence(buffer, actions) {
104
+ let exact;
105
+ let isPrefix = false;
106
+ for (const { action } of actions) {
107
+ const trigger = action.trigger;
108
+ if (trigger === undefined)
109
+ continue;
110
+ const sequence = asSequence(trigger);
111
+ if (sequence === undefined)
112
+ continue;
113
+ if (sequence.length === buffer.length) {
114
+ if (exact === undefined && sequence.every((key, i) => key === buffer[i]))
115
+ exact = action;
116
+ }
117
+ else if (sequence.length > buffer.length && buffer.every((key, i) => key === sequence[i])) {
118
+ isPrefix = true;
119
+ }
120
+ }
121
+ return { exact, isPrefix };
122
+ }
123
+ /** Project a trigger into its display form for help overlays and badges. */
124
+ export function triggerDisplay(trigger) {
125
+ if (typeof trigger === 'string') {
126
+ if (isModifierChord(trigger)) {
127
+ return {
128
+ keys: trigger.split('+').map((part) => (part === '$mod' ? part : part.toUpperCase())),
129
+ sequential: false,
130
+ };
131
+ }
132
+ return { keys: [trigger.toUpperCase()], sequential: false };
133
+ }
134
+ return { keys: trigger.map((key) => key.toUpperCase()), sequential: true };
135
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The reactive hotkey / command registry.
3
+ *
4
+ * One instance lives in the app's Svelte context. The root layout registers
5
+ * `global` actions; pages register `page` actions that are torn down when the
6
+ * page unmounts. The registry is the single source of truth for three
7
+ * consumers:
8
+ *
9
+ * - `HotkeyProvider` — matches keyboard events against active triggers.
10
+ * - `CommandPalette` — lists palette-visible actions.
11
+ * - `HotkeyHelpDialog` — lists actions that carry triggers.
12
+ *
13
+ * Role gating happens here: actions registered with `requiresAdmin` are
14
+ * dropped from every read surface when the viewer is not an administrator, so
15
+ * non-admin users never see (or can fire) them.
16
+ *
17
+ * @module
18
+ */
19
+ import type { CommandAction, HotkeyScope, RegisteredAction } from './types.ts';
20
+ /** What `createHotkeyRegistry` needs from the app: the admin check and the dev flag. */
21
+ export interface HotkeyRegistryOptions {
22
+ /** Whether the current viewer is an administrator. Read lazily so it can be reactive. */
23
+ isAdmin: () => boolean;
24
+ /**
25
+ * Whether this is a dev build. When true, registering a batch whose trigger
26
+ * shadows an existing one logs a console warning. An app wires this to its
27
+ * bundler's dev flag (SvelteKit's `$app/environment` `dev`); defaults to
28
+ * `false`.
29
+ */
30
+ dev?: boolean | undefined;
31
+ }
32
+ /** The reactive set of registered hotkey actions that `HotkeyProvider`, `CommandPalette` and `HotkeyHelpDialog` read; page registrations win over global ones. */
33
+ export declare class HotkeyRegistry {
34
+ #private;
35
+ /** Whether the command palette is open. Bound by `CommandPalette`. */
36
+ paletteOpen: boolean;
37
+ /** Whether the hotkey help overlay is open. Bound by `HotkeyHelpDialog`. */
38
+ helpOpen: boolean;
39
+ constructor(options: HotkeyRegistryOptions);
40
+ /**
41
+ * Register a batch of actions under a scope. Returns an unregister
42
+ * function; callers are expected to invoke it on unmount (the
43
+ * `registerHotkeys` helper wires this to `$effect` cleanup).
44
+ */
45
+ register(scope: HotkeyScope, actions: readonly CommandAction[]): () => void;
46
+ /**
47
+ * Role-filtered active registrations in match-precedence order: page scope
48
+ * before global, later registrations first within a scope.
49
+ */
50
+ readonly active: readonly RegisteredAction[];
51
+ /** Active actions that should appear in the command palette, in registration order. */
52
+ readonly paletteActions: readonly RegisteredAction[];
53
+ /** Active actions that carry a keyboard trigger, for the help overlay. */
54
+ readonly hotkeys: readonly RegisteredAction[];
55
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The reactive hotkey / command registry.
3
+ *
4
+ * One instance lives in the app's Svelte context. The root layout registers
5
+ * `global` actions; pages register `page` actions that are torn down when the
6
+ * page unmounts. The registry is the single source of truth for three
7
+ * consumers:
8
+ *
9
+ * - `HotkeyProvider` — matches keyboard events against active triggers.
10
+ * - `CommandPalette` — lists palette-visible actions.
11
+ * - `HotkeyHelpDialog` — lists actions that carry triggers.
12
+ *
13
+ * Role gating happens here: actions registered with `requiresAdmin` are
14
+ * dropped from every read surface when the viewer is not an administrator, so
15
+ * non-admin users never see (or can fire) them.
16
+ *
17
+ * @module
18
+ */
19
+ /**
20
+ * Whether two triggers denote the same keystroke. A structural comparison
21
+ * rather than a JSON round-trip: a chord is a string and a sequence is an
22
+ * array, so the two forms never collide.
23
+ */
24
+ function sameTrigger(self, that) {
25
+ return typeof self === 'string' || typeof that === 'string'
26
+ ? self === that
27
+ : self.length === that.length && self.every((key, index) => key === that[index]);
28
+ }
29
+ /** A trigger rendered for the dev warning below. */
30
+ function formatTrigger(trigger) {
31
+ return typeof trigger === 'string' ? `"${trigger}"` : `["${trigger.join('", "')}"]`;
32
+ }
33
+ /** Dev-only console warning when a new registration shadows an existing trigger. */
34
+ function warnOnTriggerConflicts(added, existing) {
35
+ for (const entry of added) {
36
+ const trigger = entry.action.trigger;
37
+ if (trigger === undefined)
38
+ continue;
39
+ const conflict = existing.find((other) => other.action.trigger !== undefined && sameTrigger(other.action.trigger, trigger));
40
+ if (conflict) {
41
+ // A dev-build diagnostic: it has to reach the browser console the
42
+ // developer is looking at, which is why this is the one `console` call
43
+ // in the package.
44
+ console.warn(`[hotkeys] Trigger ${formatTrigger(trigger)} of "${entry.action.id}" ` +
45
+ `shadows "${conflict.action.id}" (${conflict.scope}). ` +
46
+ `Page-scope and later registrations win.`);
47
+ }
48
+ }
49
+ }
50
+ /** Page scope sorts before global scope. */
51
+ const scopeRank = (entry) => (entry.scope === 'page' ? 0 : 1);
52
+ /**
53
+ * Match precedence: page scope first, then the most recent registration.
54
+ * A plain comparator — the composed `Order` this was ported from is the only
55
+ * thing that changed.
56
+ */
57
+ const byMatchPrecedence = (a, b) => scopeRank(a) - scopeRank(b) || b.order - a.order;
58
+ /** Ascending registration order (earlier registrations first). */
59
+ const byOrder = (a, b) => a.order - b.order;
60
+ /** The reactive set of registered hotkey actions that `HotkeyProvider`, `CommandPalette` and `HotkeyHelpDialog` read; page registrations win over global ones. */
61
+ export class HotkeyRegistry {
62
+ #registrations = $state([]);
63
+ #nextOrder = 0;
64
+ #isAdmin;
65
+ #dev;
66
+ /** Whether the command palette is open. Bound by `CommandPalette`. */
67
+ paletteOpen = $state(false);
68
+ /** Whether the hotkey help overlay is open. Bound by `HotkeyHelpDialog`. */
69
+ helpOpen = $state(false);
70
+ constructor(options) {
71
+ this.#isAdmin = options.isAdmin;
72
+ this.#dev = options.dev ?? false;
73
+ }
74
+ /**
75
+ * Register a batch of actions under a scope. Returns an unregister
76
+ * function; callers are expected to invoke it on unmount (the
77
+ * `registerHotkeys` helper wires this to `$effect` cleanup).
78
+ */
79
+ register(scope, actions) {
80
+ const added = actions.map((action) => ({ scope, action, order: this.#nextOrder++ }));
81
+ if (this.#dev)
82
+ warnOnTriggerConflicts(added, this.#registrations);
83
+ this.#registrations = [...this.#registrations, ...added];
84
+ return () => {
85
+ // Remove by the stable monotonic `order`, NOT object identity. `$state`
86
+ // deep-proxies `#registrations`, so entries read back here are proxies
87
+ // that never `===` the raw objects captured in `added` — a `Set(added)`
88
+ // membership test would match nothing and remove nothing, leaking the
89
+ // batch. `registerHotkeys` wires this closure to `$effect` cleanup, so
90
+ // every reactive re-registration would otherwise accumulate duplicates.
91
+ const orders = new Set(added.map((entry) => entry.order));
92
+ this.#registrations = this.#registrations.filter((entry) => !orders.has(entry.order));
93
+ };
94
+ }
95
+ /**
96
+ * Role-filtered active registrations in match-precedence order: page scope
97
+ * before global, later registrations first within a scope.
98
+ */
99
+ active = $derived(this.#registrations
100
+ .filter((entry) => !entry.action.requiresAdmin || this.#isAdmin())
101
+ .toSorted(byMatchPrecedence));
102
+ /** Active actions that should appear in the command palette, in registration order. */
103
+ paletteActions = $derived(this.#registrations
104
+ .filter((entry) => (!entry.action.requiresAdmin || this.#isAdmin()) && entry.action.paletteVisible !== false)
105
+ .toSorted(byOrder));
106
+ /** Active actions that carry a keyboard trigger, for the help overlay. */
107
+ hotkeys = $derived(this.#registrations
108
+ .filter((entry) => (!entry.action.requiresAdmin || this.#isAdmin()) && entry.action.trigger !== undefined)
109
+ .toSorted(byOrder));
110
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The registration contract shared by the hotkey registry and the command
3
+ * palette.
4
+ *
5
+ * A {@link CommandAction} is the unit of both systems: a labelled, runnable
6
+ * action that may carry a keyboard trigger (making it a hotkey) and may
7
+ * appear in the command palette (making it discoverable). Pages and the
8
+ * global shell register actions through the registry; the palette and the
9
+ * help overlay read the same registrations back, so there is exactly one
10
+ * source of truth for "what can the viewer do right now".
11
+ *
12
+ * Every optional member is declared `T | undefined`: the workspace compiles
13
+ * with `exactOptionalPropertyTypes`, and these objects are written as plain
14
+ * literals by application code that forwards its own optional values.
15
+ *
16
+ * @module
17
+ */
18
+ /**
19
+ * A single-keystroke trigger.
20
+ *
21
+ * Grammar: zero or more `+`-separated modifiers followed by a key, e.g.
22
+ * `'$mod+k'`, `'/'`, `'?'`, `'e'`. `$mod` resolves to ⌘ on macOS and Ctrl
23
+ * elsewhere. Shift is never written explicitly — shifted characters (like
24
+ * `?`) already arrive shifted in `KeyboardEvent.key`.
25
+ */
26
+ export type HotkeyChord = string;
27
+ /**
28
+ * A multi-keystroke trigger pressed in order, e.g. `['g', 'p']`. Only plain
29
+ * (unmodified) printable keys participate in sequences.
30
+ */
31
+ export type HotkeySequence = readonly string[];
32
+ /** A hotkey trigger: a chord pressed together (`'$mod+k'`) or a sequence pressed in turn (`['g', 'p']`). */
33
+ export type HotkeyTrigger = HotkeyChord | HotkeySequence;
34
+ /** The command-palette section a `CommandAction` is listed under. Drives section headings and ordering. */
35
+ export type CommandActionGroup = 'actions' | 'navigation';
36
+ /**
37
+ * Where a hotkey registration lives: `global` for the app shell, `page` for the mounted page. Page registrations are torn down when the
38
+ * registering page unmounts and win over global registrations on trigger
39
+ * conflicts.
40
+ */
41
+ export type HotkeyScope = 'global' | 'page';
42
+ /** One action the hotkey registry and the command palette offer: an id, a label, what it runs and, optionally, its trigger. */
43
+ export interface CommandAction {
44
+ /** Stable unique id, e.g. `'nav.posts'`, `'posts.create'`. */
45
+ id: string;
46
+ /** Human-readable label. An i18n app resolves its message at registration. */
47
+ label: string;
48
+ /** What runs on activation (navigate, focus, open a dialog, …). */
49
+ run: () => void | Promise<void>;
50
+ /** Palette section. */
51
+ group: CommandActionGroup;
52
+ /** Optional keyboard trigger. Actions without one are palette-only. */
53
+ trigger?: HotkeyTrigger | undefined;
54
+ /**
55
+ * When true the registry drops the action entirely for non-admin viewers —
56
+ * it neither fires as a hotkey nor appears in the palette.
57
+ */
58
+ requiresAdmin?: boolean | undefined;
59
+ /** Set false to keep an action hotkey-only (hidden from the palette). */
60
+ paletteVisible?: boolean | undefined;
61
+ /**
62
+ * Allow the trigger to fire while focus is inside an editable element
63
+ * (input / textarea / contenteditable). Reserved for the palette opener;
64
+ * everything else stays inert while typing.
65
+ */
66
+ allowInEditable?: boolean | undefined;
67
+ }
68
+ /** A registration entry: an action plus the scope it was registered under. */
69
+ export interface RegisteredAction {
70
+ /** The scope it was registered under. */
71
+ scope: HotkeyScope;
72
+ /** The action as registered. */
73
+ action: CommandAction;
74
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The registration contract shared by the hotkey registry and the command
3
+ * palette.
4
+ *
5
+ * A {@link CommandAction} is the unit of both systems: a labelled, runnable
6
+ * action that may carry a keyboard trigger (making it a hotkey) and may
7
+ * appear in the command palette (making it discoverable). Pages and the
8
+ * global shell register actions through the registry; the palette and the
9
+ * help overlay read the same registrations back, so there is exactly one
10
+ * source of truth for "what can the viewer do right now".
11
+ *
12
+ * Every optional member is declared `T | undefined`: the workspace compiles
13
+ * with `exactOptionalPropertyTypes`, and these objects are written as plain
14
+ * literals by application code that forwards its own optional values.
15
+ *
16
+ * @module
17
+ */
18
+ export {};