@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,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* data — the tables, lists and pagers an index page and a detail page are
|
|
3
|
+
* built from.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes of list run through this family, and the split is worth stating
|
|
6
|
+
* once. `Table` and `ItemList` present a window a caller already chose:
|
|
7
|
+
* they never decide which rows exist, so they serve a server-paged collection
|
|
8
|
+
* of 40,000 as happily as a list of four. `DataGrid` is the other shape —
|
|
9
|
+
* it sorts, filters and pages a bounded set the page already holds, entirely
|
|
10
|
+
* on the client — and it composes the first two rather than duplicating them.
|
|
11
|
+
*
|
|
12
|
+
* Every optional prop in this family is declared `T | undefined`, not bare
|
|
13
|
+
* `T`: the workspace compiles with `exactOptionalPropertyTypes`, under which
|
|
14
|
+
* `prop?: T` rejects an explicitly passed `undefined`, and a caller
|
|
15
|
+
* forwarding its own optional prop straight through would not compile.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export { default as DataGrid } from './DataGrid.svelte';
|
|
20
|
+
export { default as DescriptionList } from './DescriptionList.svelte';
|
|
21
|
+
export { default as DescriptionRow } from './DescriptionRow.svelte';
|
|
22
|
+
export { default as DiffText } from './DiffText.svelte';
|
|
23
|
+
export { default as ItemList } from './ItemList.svelte';
|
|
24
|
+
export { default as LinkListItem } from './LinkListItem.svelte';
|
|
25
|
+
export { default as Pagination } from './Pagination.svelte';
|
|
26
|
+
export { default as PaginationFooter } from './PaginationFooter.svelte';
|
|
27
|
+
export { default as ResultCount } from './ResultCount.svelte';
|
|
28
|
+
export { default as Table } from './Table.svelte';
|
|
29
|
+
export { default as TreeList } from './TreeList.svelte';
|
|
30
|
+
export { default as TreeListItem } from './TreeListItem.svelte';
|
|
31
|
+
export * from './description-context.ts';
|
|
32
|
+
export * from './diff.ts';
|
|
33
|
+
export * from './list-item.ts';
|
|
34
|
+
export * from './pagination.ts';
|
|
35
|
+
export * from './table-types.ts';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* data — the tables, lists and pagers an index page and a detail page are
|
|
3
|
+
* built from.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes of list run through this family, and the split is worth stating
|
|
6
|
+
* once. `Table` and `ItemList` present a window a caller already chose:
|
|
7
|
+
* they never decide which rows exist, so they serve a server-paged collection
|
|
8
|
+
* of 40,000 as happily as a list of four. `DataGrid` is the other shape —
|
|
9
|
+
* it sorts, filters and pages a bounded set the page already holds, entirely
|
|
10
|
+
* on the client — and it composes the first two rather than duplicating them.
|
|
11
|
+
*
|
|
12
|
+
* Every optional prop in this family is declared `T | undefined`, not bare
|
|
13
|
+
* `T`: the workspace compiles with `exactOptionalPropertyTypes`, under which
|
|
14
|
+
* `prop?: T` rejects an explicitly passed `undefined`, and a caller
|
|
15
|
+
* forwarding its own optional prop straight through would not compile.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export { default as DataGrid } from './DataGrid.svelte';
|
|
20
|
+
export { default as DescriptionList } from './DescriptionList.svelte';
|
|
21
|
+
export { default as DescriptionRow } from './DescriptionRow.svelte';
|
|
22
|
+
export { default as DiffText } from './DiffText.svelte';
|
|
23
|
+
export { default as ItemList } from './ItemList.svelte';
|
|
24
|
+
export { default as LinkListItem } from './LinkListItem.svelte';
|
|
25
|
+
export { default as Pagination } from './Pagination.svelte';
|
|
26
|
+
export { default as PaginationFooter } from './PaginationFooter.svelte';
|
|
27
|
+
export { default as ResultCount } from './ResultCount.svelte';
|
|
28
|
+
export { default as Table } from './Table.svelte';
|
|
29
|
+
export { default as TreeList } from './TreeList.svelte';
|
|
30
|
+
export { default as TreeListItem } from './TreeListItem.svelte';
|
|
31
|
+
export * from "./description-context.js";
|
|
32
|
+
export * from "./diff.js";
|
|
33
|
+
export * from "./list-item.js";
|
|
34
|
+
export * from "./pagination.js";
|
|
35
|
+
export * from "./table-types.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row shape an index page maps its data into before handing it to
|
|
3
|
+
* `ItemList`.
|
|
4
|
+
*
|
|
5
|
+
* A type, not a resource: the caller's own mappers turn a wire document into
|
|
6
|
+
* it, which is what keeps every API's shape out of this package. It lives in
|
|
7
|
+
* a `.ts` module rather than a `<script module>` block so a mapper — and the
|
|
8
|
+
* mapper's unit test — can import it without reaching through a component.
|
|
9
|
+
*
|
|
10
|
+
* Both optional members are declared `?: T | undefined` rather than bare
|
|
11
|
+
* `?: T`. The workspace compiles under `exactOptionalPropertyTypes`, where
|
|
12
|
+
* `{ subtitle: undefined }` is not the same thing as an absent key: with the
|
|
13
|
+
* narrow declaration every mapper reading a possibly-absent attribute has to
|
|
14
|
+
* spread the member conditionally, which is three lines and a comment at
|
|
15
|
+
* every call site. Widening it here is what removes them.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
/** One row of a `LinkListItem`-shaped list. */
|
|
20
|
+
export interface LinkListItemData {
|
|
21
|
+
/** Stable identity — the list's `{#each}` key. */
|
|
22
|
+
readonly id: string;
|
|
23
|
+
/** Where the row's title links to; resolved through the components context. */
|
|
24
|
+
readonly href: string;
|
|
25
|
+
readonly title: string;
|
|
26
|
+
/** A line of context under the title — an owner, a parent, a date. */
|
|
27
|
+
readonly subtitle?: string | undefined;
|
|
28
|
+
/** Short markers rendered as badges (a visibility state, a kind). */
|
|
29
|
+
readonly badges?: readonly string[] | undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row shape an index page maps its data into before handing it to
|
|
3
|
+
* `ItemList`.
|
|
4
|
+
*
|
|
5
|
+
* A type, not a resource: the caller's own mappers turn a wire document into
|
|
6
|
+
* it, which is what keeps every API's shape out of this package. It lives in
|
|
7
|
+
* a `.ts` module rather than a `<script module>` block so a mapper — and the
|
|
8
|
+
* mapper's unit test — can import it without reaching through a component.
|
|
9
|
+
*
|
|
10
|
+
* Both optional members are declared `?: T | undefined` rather than bare
|
|
11
|
+
* `?: T`. The workspace compiles under `exactOptionalPropertyTypes`, where
|
|
12
|
+
* `{ subtitle: undefined }` is not the same thing as an absent key: with the
|
|
13
|
+
* narrow declaration every mapper reading a possibly-absent attribute has to
|
|
14
|
+
* spread the member conditionally, which is three lines and a comment at
|
|
15
|
+
* every call site. Widening it here is what removes them.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offset / limit pagination arithmetic, and the href that carries it.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions, deliberately: the window a pager renders (first page, last
|
|
5
|
+
* page, the current page's neighbours, the gaps between them) is arithmetic
|
|
6
|
+
* with a dozen edge cases — an offset past the end, a zero limit, a total of
|
|
7
|
+
* zero — and arithmetic is far cheaper to pin in a unit test than in a
|
|
8
|
+
* rendered component. `Pagination` is then markup over {@link pageWindow}.
|
|
9
|
+
*
|
|
10
|
+
* Ported from an Effect implementation with `Option` replaced by
|
|
11
|
+
* `undefined` and the query Schema left behind in the app that needs it:
|
|
12
|
+
* decoding an app's own URL is the app's job, and a component library that
|
|
13
|
+
* owned it would drag a validation library onto every consumer's page.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/** The JSON:API offset query parameter — the default {@link pageHref} writes. */
|
|
18
|
+
export declare const PAGE_OFFSET_PARAM = "page[offset]";
|
|
19
|
+
/** The JSON:API limit query parameter — the default {@link pageHref} writes. */
|
|
20
|
+
export declare const PAGE_LIMIT_PARAM = "page[limit]";
|
|
21
|
+
/** A resolved page position. */
|
|
22
|
+
export interface Page {
|
|
23
|
+
readonly offset: number;
|
|
24
|
+
readonly limit: number;
|
|
25
|
+
}
|
|
26
|
+
/** The query-parameter names {@link pageHref} writes the position into. */
|
|
27
|
+
export interface PageParamNames {
|
|
28
|
+
readonly offset?: string | undefined;
|
|
29
|
+
readonly limit?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the href for `page` from `base` (a path, optionally with a query
|
|
33
|
+
* string and a fragment).
|
|
34
|
+
*
|
|
35
|
+
* Every other query parameter is preserved and the page parameters are
|
|
36
|
+
* replaced — paging through a filtered list that silently dropped the filter
|
|
37
|
+
* is the bug this shape exists to prevent. A relative `base` yields a
|
|
38
|
+
* relative href: the absolute origin below is a parsing scaffold, never part
|
|
39
|
+
* of the result.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pageHref(base: string, page: Page, names?: PageParamNames): string;
|
|
42
|
+
/** One entry in a rendered page window: a page link, or the gap before one. */
|
|
43
|
+
export type PageWindowItem = {
|
|
44
|
+
readonly kind: 'page';
|
|
45
|
+
/** 1-based page number, as rendered. */
|
|
46
|
+
readonly page: number;
|
|
47
|
+
/** The offset that page starts at. */
|
|
48
|
+
readonly offset: number;
|
|
49
|
+
readonly current: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: 'gap'; /** The page number the gap follows. */
|
|
52
|
+
readonly after: number;
|
|
53
|
+
};
|
|
54
|
+
/** Everything a pager needs to draw itself, from one position and a total. */
|
|
55
|
+
export interface PageWindow {
|
|
56
|
+
/** 1-based current page. */
|
|
57
|
+
readonly page: number;
|
|
58
|
+
/** Total pages; at least 1, even with no rows. */
|
|
59
|
+
readonly pageCount: number;
|
|
60
|
+
/** 1-based index of the first row shown, or 0 when there are no rows. */
|
|
61
|
+
readonly from: number;
|
|
62
|
+
/** 1-based index of the last row shown, or 0 when there are no rows. */
|
|
63
|
+
readonly to: number;
|
|
64
|
+
/** Offset of the previous page, or `undefined` on the first. */
|
|
65
|
+
readonly previous: number | undefined;
|
|
66
|
+
/** Offset of the next page, or `undefined` on the last. */
|
|
67
|
+
readonly next: number | undefined;
|
|
68
|
+
/** First page, last page, and `siblings` pages either side of the current. */
|
|
69
|
+
readonly items: readonly PageWindowItem[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compute the window of page links to render around `page`: the first and
|
|
73
|
+
* last pages always, plus `siblings` pages either side of the current one,
|
|
74
|
+
* with a gap marker wherever the sequence skips.
|
|
75
|
+
*
|
|
76
|
+
* Tolerant by construction — a zero or negative limit, a negative offset and
|
|
77
|
+
* an offset past the end all resolve to a real page rather than throwing,
|
|
78
|
+
* because every one of them arrives from a hand-edited URL sooner or later
|
|
79
|
+
* and a 500 is a worse answer than the first page.
|
|
80
|
+
*/
|
|
81
|
+
export declare function pageWindow(page: Page, total: number, siblings?: number): PageWindow;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offset / limit pagination arithmetic, and the href that carries it.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions, deliberately: the window a pager renders (first page, last
|
|
5
|
+
* page, the current page's neighbours, the gaps between them) is arithmetic
|
|
6
|
+
* with a dozen edge cases — an offset past the end, a zero limit, a total of
|
|
7
|
+
* zero — and arithmetic is far cheaper to pin in a unit test than in a
|
|
8
|
+
* rendered component. `Pagination` is then markup over {@link pageWindow}.
|
|
9
|
+
*
|
|
10
|
+
* Ported from an Effect implementation with `Option` replaced by
|
|
11
|
+
* `undefined` and the query Schema left behind in the app that needs it:
|
|
12
|
+
* decoding an app's own URL is the app's job, and a component library that
|
|
13
|
+
* owned it would drag a validation library onto every consumer's page.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/** The JSON:API offset query parameter — the default {@link pageHref} writes. */
|
|
18
|
+
export const PAGE_OFFSET_PARAM = 'page[offset]';
|
|
19
|
+
/** The JSON:API limit query parameter — the default {@link pageHref} writes. */
|
|
20
|
+
export const PAGE_LIMIT_PARAM = 'page[limit]';
|
|
21
|
+
/**
|
|
22
|
+
* Build the href for `page` from `base` (a path, optionally with a query
|
|
23
|
+
* string and a fragment).
|
|
24
|
+
*
|
|
25
|
+
* Every other query parameter is preserved and the page parameters are
|
|
26
|
+
* replaced — paging through a filtered list that silently dropped the filter
|
|
27
|
+
* is the bug this shape exists to prevent. A relative `base` yields a
|
|
28
|
+
* relative href: the absolute origin below is a parsing scaffold, never part
|
|
29
|
+
* of the result.
|
|
30
|
+
*/
|
|
31
|
+
export function pageHref(base, page, names = {}) {
|
|
32
|
+
const url = new URL(base, 'http://pagination.invalid');
|
|
33
|
+
url.searchParams.set(names.offset ?? PAGE_OFFSET_PARAM, String(page.offset));
|
|
34
|
+
url.searchParams.set(names.limit ?? PAGE_LIMIT_PARAM, String(page.limit));
|
|
35
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compute the window of page links to render around `page`: the first and
|
|
39
|
+
* last pages always, plus `siblings` pages either side of the current one,
|
|
40
|
+
* with a gap marker wherever the sequence skips.
|
|
41
|
+
*
|
|
42
|
+
* Tolerant by construction — a zero or negative limit, a negative offset and
|
|
43
|
+
* an offset past the end all resolve to a real page rather than throwing,
|
|
44
|
+
* because every one of them arrives from a hand-edited URL sooner or later
|
|
45
|
+
* and a 500 is a worse answer than the first page.
|
|
46
|
+
*/
|
|
47
|
+
export function pageWindow(page, total, siblings = 1) {
|
|
48
|
+
const limit = Math.max(1, Math.floor(page.limit));
|
|
49
|
+
const offset = Math.max(0, Math.floor(page.offset));
|
|
50
|
+
const rows = Math.max(0, total);
|
|
51
|
+
const pageCount = Math.max(1, Math.ceil(rows / limit));
|
|
52
|
+
const current = Math.min(pageCount, Math.floor(offset / limit) + 1);
|
|
53
|
+
const wanted = [];
|
|
54
|
+
const want = (n) => {
|
|
55
|
+
if (n >= 1 && n <= pageCount && !wanted.includes(n))
|
|
56
|
+
wanted.push(n);
|
|
57
|
+
};
|
|
58
|
+
want(1);
|
|
59
|
+
for (let n = current - siblings; n <= current + siblings; n += 1)
|
|
60
|
+
want(n);
|
|
61
|
+
want(pageCount);
|
|
62
|
+
wanted.sort((left, right) => left - right);
|
|
63
|
+
const items = [];
|
|
64
|
+
for (const [index, n] of wanted.entries()) {
|
|
65
|
+
const previous = index > 0 ? wanted[index - 1] : undefined;
|
|
66
|
+
if (previous !== undefined && n - previous > 1)
|
|
67
|
+
items.push({ kind: 'gap', after: previous });
|
|
68
|
+
items.push({ kind: 'page', page: n, offset: (n - 1) * limit, current: n === current });
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
page: current,
|
|
72
|
+
pageCount,
|
|
73
|
+
from: rows > 0 ? (current - 1) * limit + 1 : 0,
|
|
74
|
+
to: rows > 0 ? Math.min(rows, current * limit) : 0,
|
|
75
|
+
previous: current > 1 ? (current - 2) * limit : undefined,
|
|
76
|
+
next: current < pageCount ? current * limit : undefined,
|
|
77
|
+
items,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The column spec `Table` and `DataGrid` share.
|
|
3
|
+
*
|
|
4
|
+
* Declarative data rather than a table engine. The apps this is consolidated
|
|
5
|
+
* from were built on `@tanstack/svelte-table` — a column helper, a
|
|
6
|
+
* `createTable` instance, four row-model factories and a `FlexRender`
|
|
7
|
+
* indirection — which is ~40 kB of client JavaScript for tables whose whole
|
|
8
|
+
* job is to show rows, sort one column and page. The spec below is data, and
|
|
9
|
+
* the sorting / filtering / paging live in `DataGrid`'s own derivations.
|
|
10
|
+
* Reach for an engine again when something needs column resizing, grouping
|
|
11
|
+
* or virtualisation; none of those are on the map.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { Snippet } from 'svelte';
|
|
16
|
+
/**
|
|
17
|
+
* One column of a table.
|
|
18
|
+
*
|
|
19
|
+
* `value` is required and does triple duty: it is the cell's text, the key
|
|
20
|
+
* the column sorts on, and the corpus `DataGrid`'s filter searches. One
|
|
21
|
+
* accessor for all three is what stops a grid that sorts a column by one
|
|
22
|
+
* thing and shows another — the failure mode of a spec where they are
|
|
23
|
+
* separate. `cell` layers richer markup (a link, a badge, a formatted date)
|
|
24
|
+
* over that same value.
|
|
25
|
+
*/
|
|
26
|
+
export interface TableColumn<T> {
|
|
27
|
+
/** Stable id — the `{#each}` key and the sort state's handle. */
|
|
28
|
+
readonly key: string;
|
|
29
|
+
/** The `<th>` text, already in the caller's language. */
|
|
30
|
+
readonly header: string;
|
|
31
|
+
/** The cell's text, its sort key, and what a filter matches against. */
|
|
32
|
+
readonly value: (row: T) => string | number;
|
|
33
|
+
/** Richer cell markup; `value`'s result is rendered when omitted. */
|
|
34
|
+
readonly cell?: Snippet<[T]> | undefined;
|
|
35
|
+
/** Whether the header offers a sort control on this column. */
|
|
36
|
+
readonly sortable?: boolean | undefined;
|
|
37
|
+
/** Right-align numeric columns. */
|
|
38
|
+
readonly align?: 'start' | 'end' | undefined;
|
|
39
|
+
/** Extra classes on this column's header and body cells. */
|
|
40
|
+
readonly class?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Which column a table is sorted by, and which way.
|
|
44
|
+
*
|
|
45
|
+
* Unsorted is `undefined`, not a `SortState` with an empty key: "no column"
|
|
46
|
+
* is the absence of a sort, and modelling it as a sentinel key means every
|
|
47
|
+
* reader of the state has to know the sentinel.
|
|
48
|
+
*/
|
|
49
|
+
export interface SortState {
|
|
50
|
+
/** The `key` of the sorted {@link TableColumn}. */
|
|
51
|
+
readonly key: string;
|
|
52
|
+
readonly direction: 'asc' | 'desc';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The next state in the three-step cycle a sortable header runs:
|
|
56
|
+
* unsorted → ascending → descending → unsorted.
|
|
57
|
+
*
|
|
58
|
+
* A pure function rather than a method on the component so `DataGrid` and a
|
|
59
|
+
* server-sorted caller cycle identically, and so the cycle is unit-testable
|
|
60
|
+
* without a DOM.
|
|
61
|
+
*/
|
|
62
|
+
export declare function nextSortState(current: SortState | undefined, key: string): SortState | undefined;
|
|
63
|
+
/** What a column's `<th>` announces in `aria-sort`. */
|
|
64
|
+
export declare function columnAriaSort(sort: SortState | undefined, key: string): 'ascending' | 'descending' | 'none';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The column spec `Table` and `DataGrid` share.
|
|
3
|
+
*
|
|
4
|
+
* Declarative data rather than a table engine. The apps this is consolidated
|
|
5
|
+
* from were built on `@tanstack/svelte-table` — a column helper, a
|
|
6
|
+
* `createTable` instance, four row-model factories and a `FlexRender`
|
|
7
|
+
* indirection — which is ~40 kB of client JavaScript for tables whose whole
|
|
8
|
+
* job is to show rows, sort one column and page. The spec below is data, and
|
|
9
|
+
* the sorting / filtering / paging live in `DataGrid`'s own derivations.
|
|
10
|
+
* Reach for an engine again when something needs column resizing, grouping
|
|
11
|
+
* or virtualisation; none of those are on the map.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The next state in the three-step cycle a sortable header runs:
|
|
17
|
+
* unsorted → ascending → descending → unsorted.
|
|
18
|
+
*
|
|
19
|
+
* A pure function rather than a method on the component so `DataGrid` and a
|
|
20
|
+
* server-sorted caller cycle identically, and so the cycle is unit-testable
|
|
21
|
+
* without a DOM.
|
|
22
|
+
*/
|
|
23
|
+
export function nextSortState(current, key) {
|
|
24
|
+
if (current === undefined || current.key !== key)
|
|
25
|
+
return { key, direction: 'asc' };
|
|
26
|
+
if (current.direction === 'asc')
|
|
27
|
+
return { key, direction: 'desc' };
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** What a column's `<th>` announces in `aria-sort`. */
|
|
31
|
+
export function columnAriaSort(sort, key) {
|
|
32
|
+
if (sort === undefined || sort.key !== key)
|
|
33
|
+
return 'none';
|
|
34
|
+
return sort.direction === 'asc' ? 'ascending' : 'descending';
|
|
35
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A destructive action behind a modal confirmation, run either as a callback (`onConfirm`) or as a progressive-enhancement POST (`form`).
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
ConfirmDialog — a destructive action behind a modal confirmation, in the two
|
|
6
|
+
shapes the app repos actually needed.
|
|
7
|
+
|
|
8
|
+
**Callback mode** (`onConfirm`) is the client-side one: the promise runs,
|
|
9
|
+
the confirm button goes busy, a rejection is shown inside the still-open
|
|
10
|
+
dialog so the reader can retry or cancel, and a resolution closes it and
|
|
11
|
+
optionally navigates through the components context.
|
|
12
|
+
|
|
13
|
+
**Form mode** (`form`) is the progressive-enhancement one: the confirm
|
|
14
|
+
button is a real `type="submit"` inside a real `<form method="POST">`, so
|
|
15
|
+
the action posts and the server redirects even with scripting off. The SvelteKit `enhance` action is passed IN by the
|
|
16
|
+
app (`enhance: (node) => enhance(node)`); importing `$app/forms` here would
|
|
17
|
+
tie this package to SvelteKit. It is applied as an attachment, so a caller
|
|
18
|
+
that omits it simply gets the unenhanced form.
|
|
19
|
+
|
|
20
|
+
Pass exactly one of the two. With neither, the confirm button does nothing
|
|
21
|
+
— there is no action to take.
|
|
22
|
+
|
|
23
|
+
ARIA contract: `Dialog`'s, plus a failure surfaced as an `Alert` with
|
|
24
|
+
`live="assertive"` inside the dialog (the reader pressed confirm and
|
|
25
|
+
nothing visible happened, so the reason has to interrupt). Both buttons are
|
|
26
|
+
`disabled` while the callback is in flight and the confirm button swaps its
|
|
27
|
+
text to `pendingLabel` rather than emptying itself, so its accessible name
|
|
28
|
+
never disappears mid-press.
|
|
29
|
+
-->
|
|
30
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
31
|
+
import Alert from "../ui/Alert.svelte";
|
|
32
|
+
import Button from "../ui/Button.svelte";
|
|
33
|
+
import Dialog from "./Dialog.svelte";
|
|
34
|
+
let { open = $bindable(false), title, description, confirmLabel = "Delete", cancelLabel = "Cancel", pendingLabel = "Working…", failureMessage = "That didn’t work. Try again.", closeLabel = "Close", tone = "error", size = "md", onConfirm, form, redirectTo, class: className, trigger, children } = $props();
|
|
35
|
+
const context = getComponentsContext();
|
|
36
|
+
let pending = $state(false);
|
|
37
|
+
let errorMessage = $state(undefined);
|
|
38
|
+
const confirm = async () => {
|
|
39
|
+
if (onConfirm === undefined) return;
|
|
40
|
+
pending = true;
|
|
41
|
+
errorMessage = undefined;
|
|
42
|
+
try {
|
|
43
|
+
await onConfirm();
|
|
44
|
+
open = false;
|
|
45
|
+
if (redirectTo !== undefined) await context.navigate(context.href(redirectTo));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
errorMessage = error instanceof Error ? error.message : failureMessage;
|
|
48
|
+
} finally {
|
|
49
|
+
pending = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** The app's `enhance`, as an attachment so omitting it is a no-op. */
|
|
53
|
+
const enhanceForm = (node) => form?.enhance?.(node);
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<Dialog bind:open {title} {description} {closeLabel} {size} class={className} {trigger}>
|
|
57
|
+
{#if children}
|
|
58
|
+
{@render children()}
|
|
59
|
+
{/if}
|
|
60
|
+
{#if errorMessage !== undefined}
|
|
61
|
+
<Alert tone="error" live="assertive">{errorMessage}</Alert>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
{#snippet actions({ close })}
|
|
65
|
+
<Button variant="ghost" disabled={pending} onclick={close}>{cancelLabel}</Button>
|
|
66
|
+
{#if form === undefined}
|
|
67
|
+
<Button {tone} loading={pending} onclick={confirm}>
|
|
68
|
+
{pending ? pendingLabel : confirmLabel}
|
|
69
|
+
</Button>
|
|
70
|
+
{:else}
|
|
71
|
+
<form method={form.method ?? 'POST'} action={form.action} {@attach enhanceForm}>
|
|
72
|
+
{#each Object.entries(form.fields ?? {}) as [name, value] (name)}
|
|
73
|
+
<input type="hidden" {name} {value} />
|
|
74
|
+
{/each}
|
|
75
|
+
<Button {tone} type="submit">{confirmLabel}</Button>
|
|
76
|
+
</form>
|
|
77
|
+
{/if}
|
|
78
|
+
{/snippet}
|
|
79
|
+
</Dialog>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { Tone } from '../ui/tones.ts';
|
|
3
|
+
import type { DialogSize } from './sizes.ts';
|
|
4
|
+
/** A progressive-enhancement POST: the confirm button submits this form. */
|
|
5
|
+
export interface ConfirmForm {
|
|
6
|
+
/** The form's `action` — a SvelteKit named action (`?/delete`) or a path. */
|
|
7
|
+
readonly action: string;
|
|
8
|
+
/** Always `POST`; spelled out because a destructive GET is a bug. */
|
|
9
|
+
readonly method?: 'POST' | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Extra fields to post, rendered as hidden inputs INSIDE the form. They
|
|
12
|
+
* live here rather than in `children` because `children` renders in the
|
|
13
|
+
* dialog body, which is outside the form element.
|
|
14
|
+
*/
|
|
15
|
+
readonly fields?: Readonly<Record<string, string>> | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The app's own `use:enhance`, applied to the `<form>` element. Returns
|
|
18
|
+
* its teardown, if it has one. Omit for a plain, unenhanced POST.
|
|
19
|
+
*/
|
|
20
|
+
readonly enhance?: ((form: HTMLFormElement) => void | (() => void)) | undefined;
|
|
21
|
+
}
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Whether the dialog is shown. Bindable. */
|
|
24
|
+
open?: boolean | undefined;
|
|
25
|
+
/** Heading — the dialog's accessible name. */
|
|
26
|
+
title: string;
|
|
27
|
+
/** What confirming does, in the reader's terms. */
|
|
28
|
+
description?: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Text of the confirm button.
|
|
31
|
+
* @defaultValue `'Delete'`
|
|
32
|
+
*/
|
|
33
|
+
confirmLabel?: string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Text of the cancel button.
|
|
36
|
+
* @defaultValue `'Cancel'`
|
|
37
|
+
*/
|
|
38
|
+
cancelLabel?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* The confirm button's text while the callback is in flight.
|
|
41
|
+
* @defaultValue `'Working…'`
|
|
42
|
+
*/
|
|
43
|
+
pendingLabel?: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Shown when the callback rejects with something that is not an `Error`.
|
|
46
|
+
* @defaultValue `'That didn’t work. Try again.'`
|
|
47
|
+
*/
|
|
48
|
+
failureMessage?: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Accessible name of the corner close button.
|
|
51
|
+
* @defaultValue `'Close'`
|
|
52
|
+
*/
|
|
53
|
+
closeLabel?: string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Colour of the confirm button.
|
|
56
|
+
* @defaultValue `'error'`
|
|
57
|
+
*/
|
|
58
|
+
tone?: Tone | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Width of the dialog.
|
|
61
|
+
* @defaultValue `'md'`
|
|
62
|
+
*/
|
|
63
|
+
size?: DialogSize | undefined;
|
|
64
|
+
/** Client-side confirmation. Mutually exclusive with `form`. */
|
|
65
|
+
onConfirm?: (() => Promise<unknown>) | undefined;
|
|
66
|
+
/** Progressive-enhancement POST. Mutually exclusive with `onConfirm`. */
|
|
67
|
+
form?: ConfirmForm | undefined;
|
|
68
|
+
/** Navigated to through the components context once `onConfirm` resolves. */
|
|
69
|
+
redirectTo?: string | undefined;
|
|
70
|
+
/** Extra classes, merged last. */
|
|
71
|
+
class?: string | undefined;
|
|
72
|
+
/** The opening control — the caller's own element, spreading `props`. */
|
|
73
|
+
trigger?: Snippet<[{
|
|
74
|
+
props: Record<string, unknown>;
|
|
75
|
+
}]> | undefined;
|
|
76
|
+
/** Extra body content: a list of what will be destroyed, a reason field. */
|
|
77
|
+
children?: Snippet<[]> | undefined;
|
|
78
|
+
}
|
|
79
|
+
/** A destructive action behind a modal confirmation, run either as a callback (`onConfirm`) or as a progressive-enhancement POST (`form`). */
|
|
80
|
+
declare const ConfirmDialog: import("svelte").Component<Props, {}, "open">;
|
|
81
|
+
type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
|
|
82
|
+
export default ConfirmDialog;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A modal on bits-ui’s `Dialog`, wearing daisyUI’s `modal-box`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Dialog — a modal on bits-ui's `Dialog`, wearing daisyUI's `modal-box`.
|
|
6
|
+
|
|
7
|
+
ARIA contract: everything that makes a modal a modal is bits-ui's — the
|
|
8
|
+
focus trap, the initial focus move, `aria-modal`, the `aria-labelledby` /
|
|
9
|
+
`aria-describedby` wiring to `Dialog.Title` and `Dialog.Description`,
|
|
10
|
+
Escape and outside-click dismissal, the scroll lock, and returning focus
|
|
11
|
+
to whatever opened it. None of that is re-implemented here, because the
|
|
12
|
+
hand-rolled version is the one that is always subtly wrong: the native
|
|
13
|
+
`<dialog>` the app repos used gets the trap right but leaves the title
|
|
14
|
+
wiring and the focus return to the call site, and every call site did it
|
|
15
|
+
differently.
|
|
16
|
+
|
|
17
|
+
`title` is required: a dialog with no accessible name is announced as
|
|
18
|
+
"dialog" and nothing else. `description` is optional but is what turns
|
|
19
|
+
"Delete garden?" into a sentence an assistive-technology user can act on.
|
|
20
|
+
|
|
21
|
+
`trigger` receives the props to spread, so an existing `Button` keeps its
|
|
22
|
+
own markup and its own accessible name; `actions` receives `close`, so the
|
|
23
|
+
action row's buttons can dismiss without reaching for the bindable `open`.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import { X } from "@lucide/svelte";
|
|
26
|
+
import { Dialog } from "bits-ui";
|
|
27
|
+
import Button from "../ui/Button.svelte";
|
|
28
|
+
import VisuallyHidden from "../ui/VisuallyHidden.svelte";
|
|
29
|
+
import { dialogSize } from "./sizes.js";
|
|
30
|
+
let { open = $bindable(false), title, description, closeLabel = "Close", size = "md", class: className, trigger, actions, children } = $props();
|
|
31
|
+
const close = () => {
|
|
32
|
+
open = false;
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<Dialog.Root bind:open>
|
|
37
|
+
{#if trigger}
|
|
38
|
+
<Dialog.Trigger>
|
|
39
|
+
{#snippet child({ props })}
|
|
40
|
+
{@render trigger({ props })}
|
|
41
|
+
{/snippet}
|
|
42
|
+
</Dialog.Trigger>
|
|
43
|
+
{/if}
|
|
44
|
+
<Dialog.Portal>
|
|
45
|
+
<!-- daisyUI's `modal-backdrop` is a child of an open `.modal`; this is
|
|
46
|
+
the same scrim as an element bits-ui can animate and dismiss from. -->
|
|
47
|
+
<Dialog.Overlay class="bg-base-300/60 fixed inset-0 z-40" />
|
|
48
|
+
<Dialog.Content
|
|
49
|
+
class={[
|
|
50
|
+
'modal-box fixed top-1/2 left-1/2 z-50 max-h-[calc(100svh-2rem)] w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2',
|
|
51
|
+
dialogSize[size],
|
|
52
|
+
className,
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
<div class="flex items-start justify-between gap-4">
|
|
56
|
+
<Dialog.Title class="text-lg font-semibold">{title}</Dialog.Title>
|
|
57
|
+
<Dialog.Close>
|
|
58
|
+
{#snippet child({ props })}
|
|
59
|
+
<Button {...props} variant="ghost" size="sm" class="btn-square -mt-1 -mr-1">
|
|
60
|
+
<X class="size-4" aria-hidden="true" />
|
|
61
|
+
<VisuallyHidden>{closeLabel}</VisuallyHidden>
|
|
62
|
+
</Button>
|
|
63
|
+
{/snippet}
|
|
64
|
+
</Dialog.Close>
|
|
65
|
+
</div>
|
|
66
|
+
{#if description !== undefined}
|
|
67
|
+
<Dialog.Description class="text-base-content/70 mt-1 text-sm leading-6">
|
|
68
|
+
{description}
|
|
69
|
+
</Dialog.Description>
|
|
70
|
+
{/if}
|
|
71
|
+
<div class="py-4">{@render children()}</div>
|
|
72
|
+
{#if actions}
|
|
73
|
+
<div class="modal-action">{@render actions({ close })}</div>
|
|
74
|
+
{/if}
|
|
75
|
+
</Dialog.Content>
|
|
76
|
+
</Dialog.Portal>
|
|
77
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Dialog } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { type DialogSize } from './sizes.ts';
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Whether the dialog is shown. Bindable. */
|
|
6
|
+
open?: boolean | undefined;
|
|
7
|
+
/** Heading — the dialog's accessible name. */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Supporting line under the heading — the accessible description. */
|
|
10
|
+
description?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Accessible name of the corner close button.
|
|
13
|
+
* @defaultValue `'Close'`
|
|
14
|
+
*/
|
|
15
|
+
closeLabel?: string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Width step of the `modal-box`.
|
|
18
|
+
* @defaultValue `'md'`
|
|
19
|
+
*/
|
|
20
|
+
size?: DialogSize | undefined;
|
|
21
|
+
/** Extra classes on the `modal-box`. */
|
|
22
|
+
class?: string | undefined;
|
|
23
|
+
/** The opening control — the caller's own element, spreading `props`. */
|
|
24
|
+
trigger?: Snippet<[{
|
|
25
|
+
props: Record<string, unknown>;
|
|
26
|
+
}]> | undefined;
|
|
27
|
+
/** Buttons for the `modal-action` row. */
|
|
28
|
+
actions?: Snippet<[{
|
|
29
|
+
close: () => void;
|
|
30
|
+
}]> | undefined;
|
|
31
|
+
/** The dialog body, under the title and description. */
|
|
32
|
+
children: Snippet<[]>;
|
|
33
|
+
}
|
|
34
|
+
/** A modal on bits-ui’s `Dialog`, wearing daisyUI’s `modal-box`. */
|
|
35
|
+
declare const Dialog: import("svelte").Component<Props, {}, "open">;
|
|
36
|
+
type Dialog = ReturnType<typeof Dialog>;
|
|
37
|
+
export default Dialog;
|