@thomasfosterau/components 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +817 -2
- package/dist/canvas/VectorCanvas.svelte +239 -0
- package/dist/canvas/VectorCanvas.svelte.d.ts +87 -0
- package/dist/canvas/history.d.ts +105 -0
- package/dist/canvas/history.js +147 -0
- package/dist/canvas/index.d.ts +15 -0
- package/dist/canvas/index.js +14 -0
- package/dist/canvas/types.d.ts +91 -0
- package/dist/canvas/types.js +13 -0
- package/dist/canvas/utils.d.ts +66 -0
- package/dist/canvas/utils.js +114 -0
- package/dist/charts/Chart.svelte +300 -0
- package/dist/charts/Chart.svelte.d.ts +50 -0
- package/dist/charts/index.d.ts +11 -0
- package/dist/charts/index.js +10 -0
- package/dist/charts/types.d.ts +28 -0
- package/dist/charts/types.js +6 -0
- package/dist/citations/BookCitation.svelte +53 -0
- package/dist/citations/BookCitation.svelte.d.ts +41 -0
- package/dist/citations/ChapterCitation.svelte +38 -0
- package/dist/citations/ChapterCitation.svelte.d.ts +50 -0
- package/dist/citations/Citation.svelte +47 -0
- package/dist/citations/Citation.svelte.d.ts +24 -0
- package/dist/citations/CreditList.svelte +69 -0
- package/dist/citations/CreditList.svelte.d.ts +47 -0
- package/dist/citations/DOILink.svelte +28 -0
- package/dist/citations/DOILink.svelte.d.ts +19 -0
- package/dist/citations/Footnote.svelte +39 -0
- package/dist/citations/Footnote.svelte.d.ts +17 -0
- package/dist/citations/FootnoteList.svelte +36 -0
- package/dist/citations/FootnoteList.svelte.d.ts +21 -0
- package/dist/citations/ISBNLink.svelte +29 -0
- package/dist/citations/ISBNLink.svelte.d.ts +19 -0
- package/dist/citations/ISSNLink.svelte +29 -0
- package/dist/citations/ISSNLink.svelte.d.ts +19 -0
- package/dist/citations/Publisher.svelte +34 -0
- package/dist/citations/Publisher.svelte.d.ts +14 -0
- package/dist/citations/ReferenceList.svelte +53 -0
- package/dist/citations/ReferenceList.svelte.d.ts +21 -0
- package/dist/citations/ReportCitation.svelte +41 -0
- package/dist/citations/ReportCitation.svelte.d.ts +36 -0
- package/dist/citations/ScholarlyArticleCitation.svelte +64 -0
- package/dist/citations/ScholarlyArticleCitation.svelte.d.ts +41 -0
- package/dist/citations/ThesisCitation.svelte +35 -0
- package/dist/citations/ThesisCitation.svelte.d.ts +29 -0
- package/dist/citations/bibliography.svelte.d.ts +55 -0
- package/dist/citations/bibliography.svelte.js +78 -0
- package/dist/citations/citation.d.ts +71 -0
- package/dist/citations/citation.js +121 -0
- package/dist/citations/csl.d.ts +102 -0
- package/dist/citations/csl.js +20 -0
- package/dist/citations/index.d.ts +31 -0
- package/dist/citations/index.js +36 -0
- package/dist/citations/list-format.d.ts +34 -0
- package/dist/citations/list-format.js +38 -0
- package/dist/context.svelte.d.ts +46 -0
- package/dist/context.svelte.js +51 -0
- package/dist/data/DataGrid.svelte +150 -0
- package/dist/data/DataGrid.svelte.d.ts +118 -0
- package/dist/data/DescriptionList.svelte +47 -0
- package/dist/data/DescriptionList.svelte.d.ts +26 -0
- package/dist/data/DescriptionRow.svelte +55 -0
- package/dist/data/DescriptionRow.svelte.d.ts +21 -0
- package/dist/data/DiffText.svelte +29 -0
- package/dist/data/DiffText.svelte.d.ts +19 -0
- package/dist/data/ItemList.svelte +80 -0
- package/dist/data/ItemList.svelte.d.ts +52 -0
- package/dist/data/LinkListItem.svelte +46 -0
- package/dist/data/LinkListItem.svelte.d.ts +21 -0
- package/dist/data/Pagination.svelte +136 -0
- package/dist/data/Pagination.svelte.d.ts +71 -0
- package/dist/data/PaginationFooter.svelte +41 -0
- package/dist/data/PaginationFooter.svelte.d.ts +59 -0
- package/dist/data/ResultCount.svelte +27 -0
- package/dist/data/ResultCount.svelte.d.ts +18 -0
- package/dist/data/Table.svelte +145 -0
- package/dist/data/Table.svelte.d.ts +75 -0
- package/dist/data/TreeList.svelte +41 -0
- package/dist/data/TreeList.svelte.d.ts +17 -0
- package/dist/data/TreeListItem.svelte +29 -0
- package/dist/data/TreeListItem.svelte.d.ts +10 -0
- package/dist/data/description-context.d.ts +33 -0
- package/dist/data/description-context.js +35 -0
- package/dist/data/diff.d.ts +14 -0
- package/dist/data/diff.js +10 -0
- package/dist/data/index.d.ts +35 -0
- package/dist/data/index.js +35 -0
- package/dist/data/list-item.d.ts +30 -0
- package/dist/data/list-item.js +19 -0
- package/dist/data/pagination.d.ts +81 -0
- package/dist/data/pagination.js +79 -0
- package/dist/data/table-types.d.ts +64 -0
- package/dist/data/table-types.js +35 -0
- package/dist/feedback/ConfirmDialog.svelte +79 -0
- package/dist/feedback/ConfirmDialog.svelte.d.ts +82 -0
- package/dist/feedback/Dialog.svelte +77 -0
- package/dist/feedback/Dialog.svelte.d.ts +37 -0
- package/dist/feedback/LoadingBoundary.svelte +61 -0
- package/dist/feedback/LoadingBoundary.svelte.d.ts +34 -0
- package/dist/feedback/ProgressBar.svelte +46 -0
- package/dist/feedback/ProgressBar.svelte.d.ts +28 -0
- package/dist/feedback/SegmentBar.svelte +83 -0
- package/dist/feedback/SegmentBar.svelte.d.ts +36 -0
- package/dist/feedback/StatTile.svelte +77 -0
- package/dist/feedback/StatTile.svelte.d.ts +49 -0
- package/dist/feedback/Stats.svelte +18 -0
- package/dist/feedback/Stats.svelte.d.ts +16 -0
- package/dist/feedback/Timeline.svelte +86 -0
- package/dist/feedback/Timeline.svelte.d.ts +52 -0
- package/dist/feedback/Toaster.svelte +78 -0
- package/dist/feedback/Toaster.svelte.d.ts +32 -0
- package/dist/feedback/index.d.ts +31 -0
- package/dist/feedback/index.js +27 -0
- package/dist/feedback/sizes.d.ts +27 -0
- package/dist/feedback/sizes.js +41 -0
- package/dist/feedback/toaster.d.ts +48 -0
- package/dist/feedback/toaster.js +29 -0
- package/dist/fields/Checkbox.svelte +33 -0
- package/dist/fields/Checkbox.svelte.d.ts +24 -0
- package/dist/fields/CheckboxGroup.svelte +41 -0
- package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
- package/dist/fields/Combobox.svelte +222 -0
- package/dist/fields/Combobox.svelte.d.ts +98 -0
- package/dist/fields/DateInput.svelte +31 -0
- package/dist/fields/DateInput.svelte.d.ts +29 -0
- package/dist/fields/FieldErrors.svelte +30 -0
- package/dist/fields/FieldErrors.svelte.d.ts +11 -0
- package/dist/fields/Fieldset.svelte +48 -0
- package/dist/fields/Fieldset.svelte.d.ts +26 -0
- package/dist/fields/FileInput.svelte +67 -0
- package/dist/fields/FileInput.svelte.d.ts +33 -0
- package/dist/fields/FormField.svelte +54 -0
- package/dist/fields/FormField.svelte.d.ts +29 -0
- package/dist/fields/ImageInput.svelte +87 -0
- package/dist/fields/ImageInput.svelte.d.ts +39 -0
- package/dist/fields/PushToggle.svelte +93 -0
- package/dist/fields/PushToggle.svelte.d.ts +55 -0
- package/dist/fields/RadioGroup.svelte +85 -0
- package/dist/fields/RadioGroup.svelte.d.ts +41 -0
- package/dist/fields/SearchInput.svelte +57 -0
- package/dist/fields/SearchInput.svelte.d.ts +33 -0
- package/dist/fields/Select.svelte +36 -0
- package/dist/fields/Select.svelte.d.ts +29 -0
- package/dist/fields/TextInput.svelte +26 -0
- package/dist/fields/TextInput.svelte.d.ts +30 -0
- package/dist/fields/Textarea.svelte +22 -0
- package/dist/fields/Textarea.svelte.d.ts +25 -0
- package/dist/fields/Toggle.svelte +72 -0
- package/dist/fields/Toggle.svelte.d.ts +32 -0
- package/dist/fields/VideoInput.svelte +35 -0
- package/dist/fields/VideoInput.svelte.d.ts +40 -0
- package/dist/fields/field-types.d.ts +51 -0
- package/dist/fields/field-types.js +60 -0
- package/dist/fields/index.d.ts +34 -0
- package/dist/fields/index.js +34 -0
- package/dist/formatting/DateTime.svelte +35 -0
- package/dist/formatting/DateTime.svelte.d.ts +26 -0
- package/dist/formatting/FormatList.svelte +31 -0
- package/dist/formatting/FormatList.svelte.d.ts +46 -0
- package/dist/formatting/Highlight.svelte +47 -0
- package/dist/formatting/Highlight.svelte.d.ts +15 -0
- package/dist/formatting/Number.svelte +35 -0
- package/dist/formatting/Number.svelte.d.ts +43 -0
- package/dist/formatting/RelativeTime.svelte +69 -0
- package/dist/formatting/RelativeTime.svelte.d.ts +26 -0
- package/dist/formatting/Year.svelte +39 -0
- package/dist/formatting/Year.svelte.d.ts +21 -0
- package/dist/formatting/index.d.ts +12 -0
- package/dist/formatting/index.js +14 -0
- package/dist/forms/EntityForm.svelte +177 -0
- package/dist/forms/EntityForm.svelte.d.ts +57 -0
- package/dist/forms/FormActions.svelte +29 -0
- package/dist/forms/FormActions.svelte.d.ts +37 -0
- package/dist/forms/FormErrors.svelte +21 -0
- package/dist/forms/FormErrors.svelte.d.ts +16 -0
- package/dist/forms/HiddenField.svelte +22 -0
- package/dist/forms/HiddenField.svelte.d.ts +11 -0
- package/dist/forms/SearchForm.svelte +43 -0
- package/dist/forms/SearchForm.svelte.d.ts +36 -0
- package/dist/forms/entity-form.d.ts +12 -0
- package/dist/forms/entity-form.js +12 -0
- package/dist/forms/index.d.ts +14 -0
- package/dist/forms/index.js +14 -0
- package/dist/hotkeys/HotkeyBadge.svelte +39 -0
- package/dist/hotkeys/HotkeyBadge.svelte.d.ts +25 -0
- package/dist/hotkeys/HotkeyHelpDialog.svelte +68 -0
- package/dist/hotkeys/HotkeyHelpDialog.svelte.d.ts +35 -0
- package/dist/hotkeys/HotkeyProvider.svelte +92 -0
- package/dist/hotkeys/HotkeyProvider.svelte.d.ts +4 -0
- package/dist/hotkeys/context.svelte.d.ts +47 -0
- package/dist/hotkeys/context.svelte.js +72 -0
- package/dist/hotkeys/index.d.ts +23 -0
- package/dist/hotkeys/index.js +22 -0
- package/dist/hotkeys/match.d.ts +65 -0
- package/dist/hotkeys/match.js +133 -0
- package/dist/hotkeys/registry.svelte.d.ts +55 -0
- package/dist/hotkeys/registry.svelte.js +110 -0
- package/dist/hotkeys/types.d.ts +72 -0
- package/dist/hotkeys/types.js +18 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +36 -0
- package/dist/infobox/Infobox.svelte +79 -0
- package/dist/infobox/Infobox.svelte.d.ts +26 -0
- package/dist/infobox/InfoboxHeader.svelte +27 -0
- package/dist/infobox/InfoboxHeader.svelte.d.ts +11 -0
- package/dist/infobox/InfoboxImage.svelte +38 -0
- package/dist/infobox/InfoboxImage.svelte.d.ts +21 -0
- package/dist/infobox/InfoboxRow.svelte +50 -0
- package/dist/infobox/InfoboxRow.svelte.d.ts +25 -0
- package/dist/infobox/InfoboxSection.svelte +24 -0
- package/dist/infobox/InfoboxSection.svelte.d.ts +12 -0
- package/dist/infobox/index.d.ts +21 -0
- package/dist/infobox/index.js +20 -0
- package/dist/infobox/types.d.ts +86 -0
- package/dist/infobox/types.js +27 -0
- package/dist/layout/Breadcrumbs.svelte +52 -0
- package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
- package/dist/layout/DetailLayout.svelte +50 -0
- package/dist/layout/DetailLayout.svelte.d.ts +19 -0
- package/dist/layout/FilterBar.svelte +40 -0
- package/dist/layout/FilterBar.svelte.d.ts +12 -0
- package/dist/layout/Footer.svelte +32 -0
- package/dist/layout/Footer.svelte.d.ts +12 -0
- package/dist/layout/Main.svelte +30 -0
- package/dist/layout/Main.svelte.d.ts +15 -0
- package/dist/layout/Navbar.svelte +68 -0
- package/dist/layout/Navbar.svelte.d.ts +31 -0
- package/dist/layout/PageHeader.svelte +77 -0
- package/dist/layout/PageHeader.svelte.d.ts +33 -0
- package/dist/layout/Section.svelte +72 -0
- package/dist/layout/Section.svelte.d.ts +40 -0
- package/dist/layout/Sidebar.svelte +54 -0
- package/dist/layout/Sidebar.svelte.d.ts +27 -0
- package/dist/layout/Tabs.svelte +85 -0
- package/dist/layout/Tabs.svelte.d.ts +42 -0
- package/dist/layout/index.d.ts +29 -0
- package/dist/layout/index.js +29 -0
- package/dist/layout/types.d.ts +52 -0
- package/dist/layout/types.js +27 -0
- package/dist/links/ContextLinks.svelte +28 -0
- package/dist/links/ContextLinks.svelte.d.ts +20 -0
- package/dist/links/EmailLink.svelte +26 -0
- package/dist/links/EmailLink.svelte.d.ts +20 -0
- package/dist/links/index.d.ts +7 -0
- package/dist/links/index.js +7 -0
- package/dist/media/Figure.svelte +26 -0
- package/dist/media/Figure.svelte.d.ts +12 -0
- package/dist/media/Image.svelte +76 -0
- package/dist/media/Image.svelte.d.ts +41 -0
- package/dist/media/Video.svelte +65 -0
- package/dist/media/Video.svelte.d.ts +29 -0
- package/dist/media/image-dimensions.d.ts +28 -0
- package/dist/media/image-dimensions.js +34 -0
- package/dist/media/index.d.ts +10 -0
- package/dist/media/index.js +10 -0
- package/dist/search/CommandPalette.svelte +257 -0
- package/dist/search/CommandPalette.svelte.d.ts +59 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.js +13 -0
- package/dist/search/types.d.ts +37 -0
- package/dist/search/types.js +20 -0
- package/dist/seo/Head.svelte +190 -0
- package/dist/seo/Head.svelte.d.ts +86 -0
- package/dist/seo/JsonLd.svelte +30 -0
- package/dist/seo/JsonLd.svelte.d.ts +20 -0
- package/dist/seo/index.d.ts +18 -0
- package/dist/seo/index.js +17 -0
- package/dist/seo/json-ld.d.ts +33 -0
- package/dist/seo/json-ld.js +29 -0
- package/dist/seo/og-image.d.ts +71 -0
- package/dist/seo/og-image.js +81 -0
- package/dist/seo/types.d.ts +43 -0
- package/dist/seo/types.js +6 -0
- package/dist/theme.css +161 -0
- package/dist/tips/PopoverTip.svelte +80 -0
- package/dist/tips/PopoverTip.svelte.d.ts +33 -0
- package/dist/tips/Tip.svelte +91 -0
- package/dist/tips/Tip.svelte.d.ts +25 -0
- package/dist/tips/index.d.ts +15 -0
- package/dist/tips/index.js +14 -0
- package/dist/tips/tip-stack.d.ts +25 -0
- package/dist/tips/tip-stack.js +37 -0
- package/dist/tips/tip-store.svelte.d.ts +74 -0
- package/dist/tips/tip-store.svelte.js +158 -0
- package/dist/tips/types.d.ts +46 -0
- package/dist/tips/types.js +6 -0
- package/dist/ui/Alert.svelte +61 -0
- package/dist/ui/Alert.svelte.d.ts +37 -0
- package/dist/ui/Badge.svelte +39 -0
- package/dist/ui/Badge.svelte.d.ts +32 -0
- package/dist/ui/Button.svelte +43 -0
- package/dist/ui/Button.svelte.d.ts +29 -0
- package/dist/ui/Card.svelte +49 -0
- package/dist/ui/Card.svelte.d.ts +44 -0
- package/dist/ui/Chip.svelte +58 -0
- package/dist/ui/Chip.svelte.d.ts +21 -0
- package/dist/ui/ConfirmButton.svelte +75 -0
- package/dist/ui/ConfirmButton.svelte.d.ts +48 -0
- package/dist/ui/Disclosure.svelte +39 -0
- package/dist/ui/Disclosure.svelte.d.ts +22 -0
- package/dist/ui/EmptyState.svelte +37 -0
- package/dist/ui/EmptyState.svelte.d.ts +22 -0
- package/dist/ui/ExternalLink.svelte +38 -0
- package/dist/ui/ExternalLink.svelte.d.ts +34 -0
- package/dist/ui/Heading.svelte +58 -0
- package/dist/ui/Heading.svelte.d.ts +22 -0
- package/dist/ui/Link.svelte +34 -0
- package/dist/ui/Link.svelte.d.ts +20 -0
- package/dist/ui/LinkButton.svelte +51 -0
- package/dist/ui/LinkButton.svelte.d.ts +33 -0
- package/dist/ui/Menu.svelte +54 -0
- package/dist/ui/Menu.svelte.d.ts +34 -0
- package/dist/ui/MenuItem.svelte +75 -0
- package/dist/ui/MenuItem.svelte.d.ts +21 -0
- package/dist/ui/MenuSeparator.svelte +26 -0
- package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
- package/dist/ui/Popover.svelte +63 -0
- package/dist/ui/Popover.svelte.d.ts +40 -0
- package/dist/ui/ShareButton.svelte +64 -0
- package/dist/ui/ShareButton.svelte.d.ts +39 -0
- package/dist/ui/Skeleton.svelte +43 -0
- package/dist/ui/Skeleton.svelte.d.ts +22 -0
- package/dist/ui/ThemeToggle.svelte +65 -0
- package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
- package/dist/ui/Tooltip.svelte +54 -0
- package/dist/ui/Tooltip.svelte.d.ts +35 -0
- package/dist/ui/VisuallyHidden.svelte +23 -0
- package/dist/ui/VisuallyHidden.svelte.d.ts +10 -0
- package/dist/ui/colour-token.d.ts +41 -0
- package/dist/ui/colour-token.js +49 -0
- package/dist/ui/index.d.ts +44 -0
- package/dist/ui/index.js +44 -0
- package/dist/ui/menu-context.d.ts +22 -0
- package/dist/ui/menu-context.js +26 -0
- package/dist/ui/slug.d.ts +19 -0
- package/dist/ui/slug.js +25 -0
- package/dist/ui/tones.d.ts +68 -0
- package/dist/ui/tones.js +150 -0
- package/package.json +101 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends Omit<HTMLFormAttributes, 'action' | 'method'> {
|
|
3
|
+
/** Where the search submits — an app path, resolved through the context. */
|
|
4
|
+
action: string;
|
|
5
|
+
/**
|
|
6
|
+
* The query parameter's name.
|
|
7
|
+
* @defaultValue `'q'`
|
|
8
|
+
*/
|
|
9
|
+
name?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The query. Bindable.
|
|
12
|
+
* @defaultValue `''`
|
|
13
|
+
*/
|
|
14
|
+
value?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Accessible name of the field.
|
|
17
|
+
* @defaultValue `'Search'`
|
|
18
|
+
*/
|
|
19
|
+
label?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Text of the submit button.
|
|
22
|
+
* @defaultValue `'Search'`
|
|
23
|
+
*/
|
|
24
|
+
submitLabel?: string | undefined;
|
|
25
|
+
/** Placeholder in the field. */
|
|
26
|
+
placeholder?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Refuse to submit an empty query.
|
|
29
|
+
* @defaultValue `true`
|
|
30
|
+
*/
|
|
31
|
+
required?: boolean | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** The site search box: a `<search>` landmark around a GET form that works without JavaScript. */
|
|
34
|
+
declare const SearchForm: import("svelte").Component<Props, {}, "value">;
|
|
35
|
+
type SearchForm = ReturnType<typeof SearchForm>;
|
|
36
|
+
export default SearchForm;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@thomasfosterau/components/entity-form` — the renderer for
|
|
3
|
+
* `@thomasfosterau/effect-forms-svelte`'s `defineEntityForm` definitions.
|
|
4
|
+
*
|
|
5
|
+
* A separate entry point, and the only one that reaches Effect:
|
|
6
|
+
* `@thomasfosterau/effect-forms-svelte` (and, through it, `effect`) is an
|
|
7
|
+
* OPTIONAL peer of this package, needed exactly when this entry is imported.
|
|
8
|
+
* Deliberately NOT re-exported from the `forms` family or the root barrel.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
export { default as EntityForm } from './EntityForm.svelte';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@thomasfosterau/components/entity-form` — the renderer for
|
|
3
|
+
* `@thomasfosterau/effect-forms-svelte`'s `defineEntityForm` definitions.
|
|
4
|
+
*
|
|
5
|
+
* A separate entry point, and the only one that reaches Effect:
|
|
6
|
+
* `@thomasfosterau/effect-forms-svelte` (and, through it, `effect`) is an
|
|
7
|
+
* OPTIONAL peer of this package, needed exactly when this entry is imported.
|
|
8
|
+
* Deliberately NOT re-exported from the `forms` family or the root barrel.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
export { default as EntityForm } from './EntityForm.svelte';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forms — the pieces of a form that are not one control: the whole-form error
|
|
3
|
+
* list, the submit row, a hidden command field, and the site search form.
|
|
4
|
+
*
|
|
5
|
+
* Everything field-level lives in `fields/`. The split is by who owns the
|
|
6
|
+
* message: a `FormErrors` message has no field to attach to, and a
|
|
7
|
+
* `FormActions` row belongs to the form rather than to anything in it.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export { default as FormActions } from './FormActions.svelte';
|
|
12
|
+
export { default as FormErrors } from './FormErrors.svelte';
|
|
13
|
+
export { default as HiddenField } from './HiddenField.svelte';
|
|
14
|
+
export { default as SearchForm } from './SearchForm.svelte';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forms — the pieces of a form that are not one control: the whole-form error
|
|
3
|
+
* list, the submit row, a hidden command field, and the site search form.
|
|
4
|
+
*
|
|
5
|
+
* Everything field-level lives in `fields/`. The split is by who owns the
|
|
6
|
+
* message: a `FormErrors` message has no field to attach to, and a
|
|
7
|
+
* `FormActions` row belongs to the form rather than to anything in it.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export { default as FormActions } from './FormActions.svelte';
|
|
12
|
+
export { default as FormErrors } from './FormErrors.svelte';
|
|
13
|
+
export { default as HiddenField } from './HiddenField.svelte';
|
|
14
|
+
export { default as SearchForm } from './SearchForm.svelte';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A hotkey trigger as daisyUI `kbd` chips: ⌘ K for `'$mod+k'`, G then P for `['g', 'p']`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
HotkeyBadge — a hotkey trigger rendered as daisyUI `kbd` chips: ⌘ K for
|
|
6
|
+
`'$mod+k'`, G then P for `['g', 'p']`. Used by the command palette's rows
|
|
7
|
+
and by the hotkey help overlay.
|
|
8
|
+
|
|
9
|
+
`$mod` resolves to ⌘ / Ctrl only AFTER hydration. The server cannot know the
|
|
10
|
+
reader's platform, so server-rendered output always shows `controlLabel` and
|
|
11
|
+
a Mac corrects itself on mount; resolving it during render instead would be
|
|
12
|
+
a hydration mismatch on every Mac. That is why `apple` is `$state` written
|
|
13
|
+
from an `$effect` rather than a `$derived`.
|
|
14
|
+
|
|
15
|
+
ARIA contract: the chips are `<kbd>` elements, which is the element for user
|
|
16
|
+
input, and the separator between sequential keys is real text
|
|
17
|
+
(`thenLabel`) — not a bare "→" — so "G then P" is announced as an
|
|
18
|
+
instruction rather than as two loose letters. The badge contributes no name
|
|
19
|
+
of its own: it is read as part of the row it labels.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import { isApplePlatform, triggerDisplay } from "./match.js";
|
|
22
|
+
let { trigger, commandLabel = "⌘", controlLabel = "Ctrl", thenLabel = "then", class: className, ...rest } = $props();
|
|
23
|
+
const display = $derived(triggerDisplay(trigger));
|
|
24
|
+
// Resolved client-side to avoid an SSR/client hydration mismatch.
|
|
25
|
+
let apple = $state(false);
|
|
26
|
+
$effect(() => {
|
|
27
|
+
apple = isApplePlatform();
|
|
28
|
+
});
|
|
29
|
+
const modLabel = $derived(apple ? commandLabel : controlLabel);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<span {...rest} class={['inline-flex shrink-0 items-center gap-1', className]}>
|
|
33
|
+
{#each display.keys as key, index (index)}
|
|
34
|
+
{#if index > 0 && display.sequential}
|
|
35
|
+
<span class="text-base-content/50 text-xs">{thenLabel}</span>
|
|
36
|
+
{/if}
|
|
37
|
+
<kbd class="kbd kbd-sm">{key === '$mod' ? modLabel : key}</kbd>
|
|
38
|
+
{/each}
|
|
39
|
+
</span>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { HotkeyTrigger } from './types.ts';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
/** The trigger to render — a chord (`'$mod+k'`) or a sequence (`['g','p']`). */
|
|
5
|
+
trigger: HotkeyTrigger;
|
|
6
|
+
/**
|
|
7
|
+
* What `$mod` reads as on Apple platforms.
|
|
8
|
+
* @defaultValue `'⌘'`
|
|
9
|
+
*/
|
|
10
|
+
commandLabel?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* What `$mod` reads as everywhere else, and during server rendering.
|
|
13
|
+
* @defaultValue `'Ctrl'`
|
|
14
|
+
*/
|
|
15
|
+
controlLabel?: string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Read between the keys of a sequence.
|
|
18
|
+
* @defaultValue `'then'`
|
|
19
|
+
*/
|
|
20
|
+
thenLabel?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** A hotkey trigger as daisyUI `kbd` chips: ⌘ K for `'$mod+k'`, G then P for `['g', 'p']`. */
|
|
23
|
+
declare const HotkeyBadge: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type HotkeyBadge = ReturnType<typeof HotkeyBadge>;
|
|
25
|
+
export default HotkeyBadge;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The `?` overlay listing every hotkey the viewer can press right now, grouped into this page and everywhere.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
HotkeyHelpDialog — the `?` overlay: every hotkey the current viewer can
|
|
6
|
+
press right now, grouped into "This page" and "Everywhere", with
|
|
7
|
+
platform-aware key chips.
|
|
8
|
+
|
|
9
|
+
The list is read live from the registry rather than passed in, so it can
|
|
10
|
+
never drift from what `HotkeyProvider` would actually match: role-gated
|
|
11
|
+
actions are already absent, and a page's registrations disappear with the
|
|
12
|
+
page. Opening and closing is the registry's `helpOpen`, which is what a
|
|
13
|
+
registered action toggles.
|
|
14
|
+
|
|
15
|
+
ARIA contract: it is this library's `Dialog`, so bits-ui owns the modal —
|
|
16
|
+
`aria-modal`, the focus trap, Escape, the focus return — and `title` is the
|
|
17
|
+
dialog's accessible name. Each section is a `<section>` with a heading, and
|
|
18
|
+
each hotkey a list item pairing the action's label with its `HotkeyBadge`,
|
|
19
|
+
so the reading order is "what it does, then what to press".
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import Dialog from "../feedback/Dialog.svelte";
|
|
22
|
+
import { getHotkeyRegistry } from "./context.svelte.js";
|
|
23
|
+
import HotkeyBadge from "./HotkeyBadge.svelte";
|
|
24
|
+
let { title = "Keyboard shortcuts", closeLabel = "Close", emptyLabel = "No keyboard shortcuts are available on this page.", groupLabels = {
|
|
25
|
+
page: "This page",
|
|
26
|
+
global: "Everywhere"
|
|
27
|
+
}, commandLabel, controlLabel, thenLabel } = $props();
|
|
28
|
+
const registry = getHotkeyRegistry();
|
|
29
|
+
const sections = $derived([{
|
|
30
|
+
heading: groupLabels.page,
|
|
31
|
+
entries: registry.hotkeys.filter((entry) => entry.scope === "page")
|
|
32
|
+
}, {
|
|
33
|
+
heading: groupLabels.global,
|
|
34
|
+
entries: registry.hotkeys.filter((entry) => entry.scope === "global")
|
|
35
|
+
}].filter((section) => section.entries.length > 0));
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#snippet hotkeyRow(entry: RegisteredAction)}
|
|
39
|
+
<li class="flex items-center justify-between gap-4 py-1.5">
|
|
40
|
+
<span class="text-base-content text-sm">{entry.action.label}</span>
|
|
41
|
+
{#if entry.action.trigger !== undefined}
|
|
42
|
+
<HotkeyBadge trigger={entry.action.trigger} {commandLabel} {controlLabel} {thenLabel} />
|
|
43
|
+
{/if}
|
|
44
|
+
</li>
|
|
45
|
+
{/snippet}
|
|
46
|
+
|
|
47
|
+
<Dialog
|
|
48
|
+
bind:open={() => registry.helpOpen, (open) => (registry.helpOpen = open)}
|
|
49
|
+
{title}
|
|
50
|
+
{closeLabel}
|
|
51
|
+
>
|
|
52
|
+
{#if registry.hotkeys.length === 0}
|
|
53
|
+
<p class="text-base-content/70 text-sm leading-6">{emptyLabel}</p>
|
|
54
|
+
{:else}
|
|
55
|
+
{#each sections as section (section.heading)}
|
|
56
|
+
<section class="mt-4 first:mt-0">
|
|
57
|
+
<h3 class="text-base-content/70 text-xs font-semibold tracking-wide uppercase">
|
|
58
|
+
{section.heading}
|
|
59
|
+
</h3>
|
|
60
|
+
<ul class="divide-base-300 mt-2 divide-y">
|
|
61
|
+
{#each section.entries as entry (entry.action.id)}
|
|
62
|
+
{@render hotkeyRow(entry)}
|
|
63
|
+
{/each}
|
|
64
|
+
</ul>
|
|
65
|
+
</section>
|
|
66
|
+
{/each}
|
|
67
|
+
{/if}
|
|
68
|
+
</Dialog>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/**
|
|
3
|
+
* The dialog's accessible name.
|
|
4
|
+
* @defaultValue `'Keyboard shortcuts'`
|
|
5
|
+
*/
|
|
6
|
+
title?: string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Accessible name of the corner close button.
|
|
9
|
+
* @defaultValue `'Close'`
|
|
10
|
+
*/
|
|
11
|
+
closeLabel?: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Shown when nothing with a trigger is registered.
|
|
14
|
+
* @defaultValue `'No keyboard shortcuts are available on this page.'`
|
|
15
|
+
*/
|
|
16
|
+
emptyLabel?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Section headings, in render order.
|
|
19
|
+
* @defaultValue `{ page: 'This page', global: 'Everywhere' }`
|
|
20
|
+
*/
|
|
21
|
+
groupLabels?: {
|
|
22
|
+
page: string;
|
|
23
|
+
global: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/** What `$mod` reads as on Apple platforms. */
|
|
26
|
+
commandLabel?: string | undefined;
|
|
27
|
+
/** What `$mod` reads as everywhere else. */
|
|
28
|
+
controlLabel?: string | undefined;
|
|
29
|
+
/** Read between the keys of a sequence. */
|
|
30
|
+
thenLabel?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** The `?` overlay listing every hotkey the viewer can press right now, grouped into this page and everywhere. */
|
|
33
|
+
declare const HotkeyHelpDialog: import("svelte").Component<Props, {}, "">;
|
|
34
|
+
type HotkeyHelpDialog = ReturnType<typeof HotkeyHelpDialog>;
|
|
35
|
+
export default HotkeyHelpDialog;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The single global `keydown` listener that drives the hotkey registry; hotkeys never fire while the reader is typing.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
HotkeyProvider — the single global `keydown` listener that drives the hotkey
|
|
6
|
+
registry. Owns the sequence buffer (for `g p`-style triggers) and enforces
|
|
7
|
+
the editable-target guard, so hotkeys never fire while the reader is typing
|
|
8
|
+
in a form field or a rich-text editor.
|
|
9
|
+
|
|
10
|
+
Render once, from the root layout, inside the component that called
|
|
11
|
+
`createHotkeyRegistry()`. It renders nothing.
|
|
12
|
+
|
|
13
|
+
ARIA contract: none of its own — it has no markup. What it owes assistive
|
|
14
|
+
technology is negative: it must never steal a keystroke from a control that
|
|
15
|
+
needs it, which is what the editable-target guard and the
|
|
16
|
+
"`paletteOpen` / `helpOpen` suppress everything but `allowInEditable`" rule
|
|
17
|
+
are for. bits-ui owns keyboard interaction inside its own dialogs.
|
|
18
|
+
|
|
19
|
+
Not on a bits-ui primitive because there is no primitive for this: it is a
|
|
20
|
+
document-level key router over an app-owned registry, not a widget.
|
|
21
|
+
-->
|
|
22
|
+
<script lang="ts">import { getHotkeyRegistry } from "./context.svelte.js";
|
|
23
|
+
import { SEQUENCE_TIMEOUT_MS, hasChordModifier, isEditableTarget, isSequenceKey, matchChord, matchSequence } from "./match.js";
|
|
24
|
+
const registry = getHotkeyRegistry();
|
|
25
|
+
let sequenceBuffer = [];
|
|
26
|
+
let sequenceTimer;
|
|
27
|
+
function clearSequence() {
|
|
28
|
+
sequenceBuffer = [];
|
|
29
|
+
if (sequenceTimer !== undefined) clearTimeout(sequenceTimer);
|
|
30
|
+
sequenceTimer = undefined;
|
|
31
|
+
}
|
|
32
|
+
function armSequenceTimeout() {
|
|
33
|
+
if (sequenceTimer !== undefined) clearTimeout(sequenceTimer);
|
|
34
|
+
sequenceTimer = setTimeout(clearSequence, SEQUENCE_TIMEOUT_MS);
|
|
35
|
+
}
|
|
36
|
+
function activate(action, event) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
clearSequence();
|
|
39
|
+
void action.run();
|
|
40
|
+
}
|
|
41
|
+
// The keyboard event router. The chord / sequence / suppression branching is
|
|
42
|
+
// inherently branchy; splitting it would scatter the buffer state machine
|
|
43
|
+
// across helpers and hurt readability more than the branch count does.
|
|
44
|
+
function onKeydown(event) {
|
|
45
|
+
// While a hotkey-owned dialog is open, or focus sits in an editable
|
|
46
|
+
// element, only `allowInEditable` actions (the palette toggle) may fire —
|
|
47
|
+
// bits-ui owns keyboard interaction inside its dialogs, and typing must
|
|
48
|
+
// never trigger actions.
|
|
49
|
+
const suppressed = registry.paletteOpen || registry.helpOpen || isEditableTarget(event.target);
|
|
50
|
+
const actions = suppressed ? registry.active.filter((entry) => entry.action.allowInEditable) : registry.active;
|
|
51
|
+
if (hasChordModifier(event)) {
|
|
52
|
+
const action = matchChord(event, actions);
|
|
53
|
+
if (action !== undefined) activate(action, event);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Plain keys: ignore non-printable keys (Escape, arrows, bare modifiers,
|
|
57
|
+
// F-keys) entirely — they neither fire nor disturb a pending sequence.
|
|
58
|
+
if (!isSequenceKey(event)) return;
|
|
59
|
+
if (suppressed) {
|
|
60
|
+
clearSequence();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Try extending the pending sequence first, then restarting from this key
|
|
64
|
+
// alone, so a stray prefix never swallows a fresh trigger.
|
|
65
|
+
let hit;
|
|
66
|
+
for (const candidate of [[...sequenceBuffer, event.key], [event.key]]) {
|
|
67
|
+
const match = matchSequence(candidate, actions);
|
|
68
|
+
if (match.exact !== undefined || match.isPrefix) {
|
|
69
|
+
hit = {
|
|
70
|
+
candidate,
|
|
71
|
+
match
|
|
72
|
+
};
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (hit === undefined) {
|
|
77
|
+
clearSequence();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (hit.match.exact !== undefined) {
|
|
81
|
+
activate(hit.match.exact, event);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
sequenceBuffer = hit.candidate;
|
|
85
|
+
armSequenceTimeout();
|
|
86
|
+
}
|
|
87
|
+
$effect(() => () => {
|
|
88
|
+
if (sequenceTimer !== undefined) clearTimeout(sequenceTimer);
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<svelte:window onkeydown={onKeydown} />
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The single global `keydown` listener that drives the hotkey registry; hotkeys never fire while the reader is typing. */
|
|
2
|
+
declare const HotkeyProvider: import("svelte").Component<Record<string, never>, {}, "">;
|
|
3
|
+
type HotkeyProvider = ReturnType<typeof HotkeyProvider>;
|
|
4
|
+
export default HotkeyProvider;
|
|
@@ -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,23 @@
|
|
|
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
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export { HotkeyRegistryMissing, createHotkeyRegistry, getHotkeyRegistry, registerHotkeys, tryGetHotkeyRegistry, } from './context.svelte.ts';
|
|
18
|
+
export { HotkeyRegistry, type HotkeyRegistryOptions } from './registry.svelte.ts';
|
|
19
|
+
export { default as HotkeyBadge } from './HotkeyBadge.svelte';
|
|
20
|
+
export { default as HotkeyHelpDialog } from './HotkeyHelpDialog.svelte';
|
|
21
|
+
export { default as HotkeyProvider } from './HotkeyProvider.svelte';
|
|
22
|
+
export { SEQUENCE_TIMEOUT_MS, hasChordModifier, isApplePlatform, isEditableTarget, isSequenceKey, matchChord, matchSequence, triggerDisplay, type SequenceMatch, type TriggerDisplay, } from './match.ts';
|
|
23
|
+
export type { CommandAction, CommandActionGroup, HotkeyChord, HotkeyScope, HotkeySequence, HotkeyTrigger, RegisteredAction, } from './types.ts';
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export { HotkeyRegistryMissing, createHotkeyRegistry, getHotkeyRegistry, registerHotkeys, tryGetHotkeyRegistry, } from "./context.svelte.js";
|
|
18
|
+
export { HotkeyRegistry } from "./registry.svelte.js";
|
|
19
|
+
export { default as HotkeyBadge } from './HotkeyBadge.svelte';
|
|
20
|
+
export { default as HotkeyHelpDialog } from './HotkeyHelpDialog.svelte';
|
|
21
|
+
export { default as HotkeyProvider } from './HotkeyProvider.svelte';
|
|
22
|
+
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;
|