@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,43 @@
|
|
|
1
|
+
import type { HTMLDataAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends Omit<HTMLDataAttributes, 'value'> {
|
|
3
|
+
/** The number to format; the raw value also goes in the `value` attribute. */
|
|
4
|
+
value: number | bigint;
|
|
5
|
+
/**
|
|
6
|
+
* Locale(s) for `Intl` formatting.
|
|
7
|
+
* @defaultValue the components context's `locale()`
|
|
8
|
+
*/
|
|
9
|
+
locales?: string | readonly string[] | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* `Intl.NumberFormat` `style`.
|
|
12
|
+
* @defaultValue `'decimal'`
|
|
13
|
+
*/
|
|
14
|
+
style?: Intl.NumberFormatOptions['style'] | undefined;
|
|
15
|
+
/** The unit when `style` is `unit` (`kilometer`, `percent`). */
|
|
16
|
+
unit?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* `Intl.NumberFormat` `unitDisplay`.
|
|
19
|
+
* @defaultValue `'short'`
|
|
20
|
+
*/
|
|
21
|
+
unitDisplay?: Intl.NumberFormatOptions['unitDisplay'] | undefined;
|
|
22
|
+
/** ISO 4217 code when `style` is `currency`. */
|
|
23
|
+
currency?: string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* `Intl.NumberFormat` `currencyDisplay`.
|
|
26
|
+
* @defaultValue `'symbol'`
|
|
27
|
+
*/
|
|
28
|
+
currencyDisplay?: Intl.NumberFormatOptions['currencyDisplay'] | undefined;
|
|
29
|
+
/** `Intl.NumberFormat` `maximumFractionDigits`. */
|
|
30
|
+
maximumFractionDigits?: number | undefined;
|
|
31
|
+
/** `Intl.NumberFormat` `minimumFractionDigits`. */
|
|
32
|
+
minimumFractionDigits?: number | undefined;
|
|
33
|
+
/** `Intl.NumberFormat` `signDisplay`. */
|
|
34
|
+
signDisplay?: Intl.NumberFormatOptions['signDisplay'] | undefined;
|
|
35
|
+
/** `Intl.NumberFormat` `notation`. */
|
|
36
|
+
notation?: Intl.NumberFormatOptions['notation'] | undefined;
|
|
37
|
+
/** Raw `Intl.NumberFormatOptions`; wins over the individual style props when given. */
|
|
38
|
+
options?: Intl.NumberFormatOptions | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** A `<data>` element whose text is the `Intl.NumberFormat` rendering and whose `value` attribute keeps the raw number. */
|
|
41
|
+
declare const Number: import("svelte").Component<Props, {}, "">;
|
|
42
|
+
type Number = ReturnType<typeof Number>;
|
|
43
|
+
export default Number;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
"3 days ago" / "in 2 hours" via `Intl.RelativeTimeFormat`, in the largest unit whose magnitude is at least one.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
RelativeTime — "3 days ago" / "in 2 hours" via `Intl.RelativeTimeFormat`,
|
|
6
|
+
picking the largest unit (seconds → years) whose magnitude is at least one.
|
|
7
|
+
|
|
8
|
+
ARIA contract: none beyond native `<time>` semantics. The absolute instant
|
|
9
|
+
is never hidden — it is both the machine-readable `datetime` and the
|
|
10
|
+
human-readable `title`, so a reader who hovers (or a tool that reads
|
|
11
|
+
`title`) gets the precise moment the relative text is glossing over.
|
|
12
|
+
-->
|
|
13
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
14
|
+
let { value, now, locales = getComponentsContext().locale(), numeric = "auto", style = "long", ...rest } = $props();
|
|
15
|
+
const UNITS = [
|
|
16
|
+
{
|
|
17
|
+
unit: "year",
|
|
18
|
+
ms: 365.25 * 24 * 60 * 60 * 1e3
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
unit: "month",
|
|
22
|
+
ms: 30.44 * 24 * 60 * 60 * 1e3
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
unit: "week",
|
|
26
|
+
ms: 7 * 24 * 60 * 60 * 1e3
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
unit: "day",
|
|
30
|
+
ms: 24 * 60 * 60 * 1e3
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
unit: "hour",
|
|
34
|
+
ms: 60 * 60 * 1e3
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
unit: "minute",
|
|
38
|
+
ms: 60 * 1e3
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
unit: "second",
|
|
42
|
+
ms: 1e3
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
const instant = $derived(new Date(value));
|
|
46
|
+
const reference = $derived(new Date(now ?? Date.now()));
|
|
47
|
+
const diffMs = $derived(instant.getTime() - reference.getTime());
|
|
48
|
+
// Largest unit whose magnitude rounds to at least one; falls back to
|
|
49
|
+
// seconds for anything smaller (e.g. "0 seconds ago" / "in 0 seconds").
|
|
50
|
+
const picked = $derived(UNITS.find((candidate) => Math.abs(diffMs) >= candidate.ms) ?? UNITS[UNITS.length - 1]);
|
|
51
|
+
const roundedValue = $derived(Math.round(diffMs / picked.ms));
|
|
52
|
+
// The formatter and the formatted string are separate `$derived` values so
|
|
53
|
+
// constructing the (relatively expensive) `Intl.RelativeTimeFormat` is
|
|
54
|
+
// skipped when only `value`/`now` change. See PERF-CPU-003 in
|
|
55
|
+
// docs/specs/performance-invariants.md.
|
|
56
|
+
const format = $derived(new Intl.RelativeTimeFormat(locales, {
|
|
57
|
+
numeric,
|
|
58
|
+
style
|
|
59
|
+
}));
|
|
60
|
+
const formatted = $derived(format.format(roundedValue, picked.unit));
|
|
61
|
+
const iso = $derived(instant.toISOString());
|
|
62
|
+
const absoluteFormat = $derived(new Intl.DateTimeFormat(locales, {
|
|
63
|
+
dateStyle: "full",
|
|
64
|
+
timeStyle: "short"
|
|
65
|
+
}));
|
|
66
|
+
const absolute = $derived(absoluteFormat.format(instant));
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<time {...rest} datetime={iso} title={absolute}>{formatted}</time>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HTMLTimeAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLTimeAttributes {
|
|
3
|
+
/** The instant: a `Date`, epoch milliseconds or an ISO string. */
|
|
4
|
+
value: Date | number | string;
|
|
5
|
+
/** The instant "now" is measured from; defaults to the current wall clock at render. */
|
|
6
|
+
now?: Date | number | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Locale(s) for `Intl` formatting.
|
|
9
|
+
* @defaultValue the components context's `locale()`
|
|
10
|
+
*/
|
|
11
|
+
locales?: string | readonly string[] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* `Intl.RelativeTimeFormat` `numeric`; `auto` allows "yesterday".
|
|
14
|
+
* @defaultValue `'auto'`
|
|
15
|
+
*/
|
|
16
|
+
numeric?: Intl.RelativeTimeFormatOptions['numeric'] | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* `Intl.RelativeTimeFormat` `style`.
|
|
19
|
+
* @defaultValue `'long'`
|
|
20
|
+
*/
|
|
21
|
+
style?: Intl.RelativeTimeFormatOptions['style'] | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** "3 days ago" / "in 2 hours" via `Intl.RelativeTimeFormat`, in the largest unit whose magnitude is at least one. */
|
|
24
|
+
declare const RelativeTime: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type RelativeTime = ReturnType<typeof RelativeTime>;
|
|
26
|
+
export default RelativeTime;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A `<time>` element carrying just a year, for dates whose precision really is a year.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Year — a `<time>` element carrying just a year: `datetime="1925"`, and
|
|
6
|
+
`Intl.DateTimeFormat`'s locale rendering of the year (with its era, when
|
|
7
|
+
asked) as the text. For publication dates, lifespans and anything else whose
|
|
8
|
+
precision really is a year.
|
|
9
|
+
|
|
10
|
+
ARIA contract: none beyond the native `<time>` semantics. `datetime` is a
|
|
11
|
+
valid HTML year string (four or more digits), so the value stays
|
|
12
|
+
machine-readable at the precision it actually has rather than claiming a
|
|
13
|
+
midnight-on-1-January instant.
|
|
14
|
+
|
|
15
|
+
A `number` is a CALENDAR YEAR (`1925`), not an epoch timestamp — that is
|
|
16
|
+
what a caller holding "the year" has. A `Date` or string is read as an
|
|
17
|
+
instant and its UTC year is taken, so a date at midnight UTC never renders
|
|
18
|
+
as the previous year in a timezone west of Greenwich. Years before 1 CE have
|
|
19
|
+
no HTML year string, so for those `datetime` falls back to the ISO instant.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
22
|
+
let { value, locales = getComponentsContext().locale(), era, representation = "numeric", ...rest } = $props();
|
|
23
|
+
const instant = $derived.by(() => {
|
|
24
|
+
if (typeof value !== "number") return new Date(value);
|
|
25
|
+
const date = new Date(Date.UTC(2e3, 0, 1));
|
|
26
|
+
date.setUTCFullYear(value);
|
|
27
|
+
return date;
|
|
28
|
+
});
|
|
29
|
+
const format = $derived(new Intl.DateTimeFormat(locales, {
|
|
30
|
+
year: representation,
|
|
31
|
+
era,
|
|
32
|
+
timeZone: "UTC"
|
|
33
|
+
}));
|
|
34
|
+
const formatted = $derived(format.format(instant));
|
|
35
|
+
const year = $derived(instant.getUTCFullYear());
|
|
36
|
+
const datetime = $derived(year >= 1 ? String(year).padStart(4, "0") : instant.toISOString());
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<time {...rest} {datetime}>{formatted}</time>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HTMLTimeAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLTimeAttributes {
|
|
3
|
+
/** A calendar year, or an instant (its UTC year is used). */
|
|
4
|
+
value: Date | number | string;
|
|
5
|
+
/**
|
|
6
|
+
* Locale(s) for `Intl` formatting.
|
|
7
|
+
* @defaultValue the components context's `locale()`
|
|
8
|
+
*/
|
|
9
|
+
locales?: string | readonly string[] | undefined;
|
|
10
|
+
/** Append the era: `1925 AD`. */
|
|
11
|
+
era?: Intl.DateTimeFormatOptions['era'] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Four digits or two.
|
|
14
|
+
* @defaultValue `'numeric'`
|
|
15
|
+
*/
|
|
16
|
+
representation?: 'numeric' | '2-digit' | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** A `<time>` element carrying just a year, for dates whose precision really is a year. */
|
|
19
|
+
declare const Year: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Year = ReturnType<typeof Year>;
|
|
21
|
+
export default Year;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* formatting — locale-aware rendering of dates, numbers, lists and search
|
|
3
|
+
* highlights. See the package README for the family reference.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
export { default as DateTime } from './DateTime.svelte';
|
|
8
|
+
export { default as NumberFormat } from './Number.svelte';
|
|
9
|
+
export { default as FormatList } from './FormatList.svelte';
|
|
10
|
+
export { default as RelativeTime } from './RelativeTime.svelte';
|
|
11
|
+
export { default as Year } from './Year.svelte';
|
|
12
|
+
export { default as Highlight, tokenise } from './Highlight.svelte';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* formatting — locale-aware rendering of dates, numbers, lists and search
|
|
3
|
+
* highlights. See the package README for the family reference.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
export { default as DateTime } from './DateTime.svelte';
|
|
8
|
+
// `NumberFormat`, not `Number`: a named export called `Number` would shadow
|
|
9
|
+
// the global constructor for anyone importing it by that name.
|
|
10
|
+
export { default as NumberFormat } from './Number.svelte';
|
|
11
|
+
export { default as FormatList } from './FormatList.svelte';
|
|
12
|
+
export { default as RelativeTime } from './RelativeTime.svelte';
|
|
13
|
+
export { default as Year } from './Year.svelte';
|
|
14
|
+
export { default as Highlight, tokenise } from './Highlight.svelte';
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders an `@thomasfosterau/effect-forms-svelte` `defineEntityForm` definition through this library’s field primitives.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
EntityForm — the generic renderer for an `@thomasfosterau/effect-forms-svelte`
|
|
6
|
+
`defineEntityForm` definition: it walks `definition.entriesFor(mode)` and
|
|
7
|
+
renders each entry through this library's field primitives —
|
|
8
|
+
`FormField` + `TextInput` / `DateInput` / `Textarea` / `Select` for scalars
|
|
9
|
+
and selects, and the entry's own component for a bespoke widget (a picker,
|
|
10
|
+
a contributor list), bridged through the widget contract below.
|
|
11
|
+
|
|
12
|
+
The one component of this package that touches Effect, which is why it is a
|
|
13
|
+
separate entry point (`@thomasfosterau/components/entity-form`) with
|
|
14
|
+
`@thomasfosterau/effect-forms-svelte` an OPTIONAL peer: a consumer that
|
|
15
|
+
never imports this entry never installs the form library or `effect`. The
|
|
16
|
+
renderer itself imports only the store (`createForm`) and types.
|
|
17
|
+
|
|
18
|
+
**Seeding.** `definition`, `mode`, `initial` and `initialRows` configure the
|
|
19
|
+
store ONCE, at mount: a form never changes entity or mode mid-flight, and an
|
|
20
|
+
edit page's values must land in the SSR HTML. There is no `$effect`
|
|
21
|
+
re-seeding, so a value typed before hydration is never wiped by a late seed.
|
|
22
|
+
A create form whose command carries a client id gets one minted here, once
|
|
23
|
+
per mount, so a retried submission stays idempotent.
|
|
24
|
+
|
|
25
|
+
**The widget contract.** A widget entry receives, in this order: its
|
|
26
|
+
definition's `props()`, then the caller's `fieldProps[key]`, then the bridge
|
|
27
|
+
— `name`, `value`, `onChange`, `errors`. The bridge is spread LAST, so
|
|
28
|
+
neither of the first two can sever the store binding. A `value` widget
|
|
29
|
+
bridges a string field of the store; a `rows` widget bridges a row list held
|
|
30
|
+
here and serialises it itself (its own hidden input).
|
|
31
|
+
|
|
32
|
+
**Submission.** `enhance(form)` supplies the spreadable `<form>` attributes.
|
|
33
|
+
It defaults to the store's own `form.props` (POST + its progressive
|
|
34
|
+
enhancement); a SvelteKit app passes its `$app/forms`-backed `enhanceForm`,
|
|
35
|
+
which this package cannot import.
|
|
36
|
+
|
|
37
|
+
Every string that is not the definition's own (labels, submit labels and
|
|
38
|
+
option labels are its message thunks) is a prop with an English default.
|
|
39
|
+
-->
|
|
40
|
+
<script lang="ts">import { createForm } from "@thomasfosterau/effect-forms-svelte";
|
|
41
|
+
import DateInput from "../fields/DateInput.svelte";
|
|
42
|
+
import FormField from "../fields/FormField.svelte";
|
|
43
|
+
import Select from "../fields/Select.svelte";
|
|
44
|
+
import TextInput from "../fields/TextInput.svelte";
|
|
45
|
+
import Textarea from "../fields/Textarea.svelte";
|
|
46
|
+
import FormActions from "./FormActions.svelte";
|
|
47
|
+
import FormErrors from "./FormErrors.svelte";
|
|
48
|
+
let { definition, mode, cancelHref, action, initial = {}, initialRows = {}, fieldProps = {}, enhance = (form) => form.props, cancelLabel = "Cancel", pendingLabel = "Saving…", selectPlaceholder = "Choose…", requiredLabel, class: className, ...rest } = $props();
|
|
49
|
+
// Configure-once, at mount (see the header): these reads are deliberately
|
|
50
|
+
// not reactive.
|
|
51
|
+
// svelte-ignore state_referenced_locally
|
|
52
|
+
const configured = {
|
|
53
|
+
definition,
|
|
54
|
+
mode,
|
|
55
|
+
initial,
|
|
56
|
+
initialRows
|
|
57
|
+
};
|
|
58
|
+
const baseDefaults = configured.mode === "create" ? configured.definition.createDefaults : configured.definition.updateDefaults;
|
|
59
|
+
const defaults = {
|
|
60
|
+
...baseDefaults,
|
|
61
|
+
...configured.initial
|
|
62
|
+
};
|
|
63
|
+
if (configured.mode === "create" && configured.definition.clientId) {
|
|
64
|
+
defaults["id"] = crypto.randomUUID();
|
|
65
|
+
}
|
|
66
|
+
const form = createForm({
|
|
67
|
+
schema: configured.mode === "create" ? configured.definition.createInput : configured.definition.updateInput,
|
|
68
|
+
defaults
|
|
69
|
+
});
|
|
70
|
+
const entries = configured.definition.entriesFor(configured.mode);
|
|
71
|
+
// `defineEntityForm` validated every entry key against the derived
|
|
72
|
+
// all-string input schema, which the path-typed accessor cannot see.
|
|
73
|
+
const handle = (key) => form.field(key);
|
|
74
|
+
const getField = (key) => handle(key).value ?? "";
|
|
75
|
+
const setField = (key, next) => {
|
|
76
|
+
handle(key).value = next;
|
|
77
|
+
};
|
|
78
|
+
let rows = $state(Object.fromEntries(entries.filter((entry) => entry._tag === "widget" && entry.bridge === "rows").map((entry) => [entry.key, [...configured.initialRows[entry.key] ?? []]])));
|
|
79
|
+
const bridge = (entry) => entry.bridge === "rows" ? {
|
|
80
|
+
name: entry.key,
|
|
81
|
+
value: rows[entry.key] ?? [],
|
|
82
|
+
onChange: (next) => {
|
|
83
|
+
rows[entry.key] = next;
|
|
84
|
+
},
|
|
85
|
+
errors: handle(entry.key).visibleErrors
|
|
86
|
+
} : {
|
|
87
|
+
name: entry.key,
|
|
88
|
+
value: getField(entry.key),
|
|
89
|
+
onChange: (next) => setField(entry.key, next),
|
|
90
|
+
errors: handle(entry.key).visibleErrors
|
|
91
|
+
};
|
|
92
|
+
const selectOptions = (entry) => (configured.definition.fieldFor(entry.key).options ?? []).map((value) => ({
|
|
93
|
+
value,
|
|
94
|
+
label: entry.options.optionLabels[value]?.() ?? value
|
|
95
|
+
}));
|
|
96
|
+
const inputMode = (entry) => entry.options.inputmode ?? (configured.definition.fieldFor(entry.key).kind === "number" ? "numeric" : undefined);
|
|
97
|
+
const showHiddenId = configured.mode === "update" || configured.definition.clientId;
|
|
98
|
+
const submitLabel = $derived(mode === "create" ? definition.submit.create() : definition.submit.update());
|
|
99
|
+
const gated = $derived(entries.some((entry) => entry._tag === "widget" && entry.options.gate === true && getField(entry.key) === ""));
|
|
100
|
+
const rootErrors = $derived(form.errors._errors ?? []);
|
|
101
|
+
const fieldId = (key) => `entity-form-${key}`;
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<form {...rest} {...enhance(form)} {action} class={['space-y-4', className]}>
|
|
105
|
+
{#if showHiddenId}
|
|
106
|
+
<input type="hidden" name="id" value={getField('id')} />
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
<FormErrors messages={rootErrors} />
|
|
110
|
+
|
|
111
|
+
{#each entries as entry (entry.key)}
|
|
112
|
+
{#if entry._tag === 'widget'}
|
|
113
|
+
<entry.component {...entry.props()} {...fieldProps[entry.key] ?? {}} {...bridge(entry)} />
|
|
114
|
+
{:else if entry._tag === 'select'}
|
|
115
|
+
<FormField
|
|
116
|
+
id={fieldId(entry.key)}
|
|
117
|
+
label={entry.options.label()}
|
|
118
|
+
required={entry.options.required === true}
|
|
119
|
+
errors={handle(entry.key).visibleErrors}
|
|
120
|
+
{requiredLabel}
|
|
121
|
+
>
|
|
122
|
+
{#snippet children(control)}
|
|
123
|
+
<Select
|
|
124
|
+
{...control}
|
|
125
|
+
name={entry.key}
|
|
126
|
+
options={selectOptions(entry)}
|
|
127
|
+
placeholder={selectPlaceholder}
|
|
128
|
+
bind:value={() => getField(entry.key), (next: string) => setField(entry.key, next)}
|
|
129
|
+
/>
|
|
130
|
+
{/snippet}
|
|
131
|
+
</FormField>
|
|
132
|
+
{:else}
|
|
133
|
+
<FormField
|
|
134
|
+
id={fieldId(entry.key)}
|
|
135
|
+
label={entry.options.label()}
|
|
136
|
+
required={entry.options.required === true}
|
|
137
|
+
errors={handle(entry.key).visibleErrors}
|
|
138
|
+
{requiredLabel}
|
|
139
|
+
>
|
|
140
|
+
{#snippet children(control)}
|
|
141
|
+
{#if entry.multiline}
|
|
142
|
+
<Textarea
|
|
143
|
+
{...control}
|
|
144
|
+
name={entry.key}
|
|
145
|
+
placeholder={entry.options.placeholder?.()}
|
|
146
|
+
bind:value={() => getField(entry.key), (next: string) => setField(entry.key, next)}
|
|
147
|
+
/>
|
|
148
|
+
{:else if entry.options.type === 'date'}
|
|
149
|
+
<DateInput
|
|
150
|
+
{...control}
|
|
151
|
+
name={entry.key}
|
|
152
|
+
bind:value={() => getField(entry.key), (next: string) => setField(entry.key, next)}
|
|
153
|
+
/>
|
|
154
|
+
{:else}
|
|
155
|
+
<TextInput
|
|
156
|
+
{...control}
|
|
157
|
+
name={entry.key}
|
|
158
|
+
type={entry.options.type === 'url' ? 'url' : 'text'}
|
|
159
|
+
inputmode={inputMode(entry)}
|
|
160
|
+
placeholder={entry.options.placeholder?.()}
|
|
161
|
+
bind:value={() => getField(entry.key), (next: string) => setField(entry.key, next)}
|
|
162
|
+
/>
|
|
163
|
+
{/if}
|
|
164
|
+
{/snippet}
|
|
165
|
+
</FormField>
|
|
166
|
+
{/if}
|
|
167
|
+
{/each}
|
|
168
|
+
|
|
169
|
+
<FormActions
|
|
170
|
+
{submitLabel}
|
|
171
|
+
{pendingLabel}
|
|
172
|
+
pending={form.submitting}
|
|
173
|
+
disabled={gated}
|
|
174
|
+
{cancelHref}
|
|
175
|
+
{cancelLabel}
|
|
176
|
+
/>
|
|
177
|
+
</form>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createForm, type EntityFormDefinition } from '@thomasfosterau/effect-forms-svelte';
|
|
2
|
+
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
3
|
+
type Store = ReturnType<typeof createForm>;
|
|
4
|
+
interface Props extends Omit<HTMLFormAttributes, 'action' | 'method' | 'children'> {
|
|
5
|
+
/** The form definition from `defineEntityForm`. */
|
|
6
|
+
definition: EntityFormDefinition;
|
|
7
|
+
/** Which of the definition’s entry sets to render. */
|
|
8
|
+
mode: 'create' | 'update';
|
|
9
|
+
/** Where Cancel goes. Omitted renders no cancel link. */
|
|
10
|
+
cancelHref?: string | undefined;
|
|
11
|
+
/** A named action (`?/addItem`). Omitted posts to the page's default action. */
|
|
12
|
+
action?: string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* All-string initial values: an edit page's current values, or a create page's pre-filled FK.
|
|
15
|
+
* @defaultValue `{}`
|
|
16
|
+
*/
|
|
17
|
+
initial?: Readonly<Record<string, string>> | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Initial rows per `rows` widget key (a contributor list's current credits).
|
|
20
|
+
* @defaultValue `{}`
|
|
21
|
+
*/
|
|
22
|
+
initialRows?: Readonly<Record<string, readonly unknown[]>> | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Render-time extra props per widget key (a picker's load-resolved `options`).
|
|
25
|
+
* @defaultValue `{}`
|
|
26
|
+
*/
|
|
27
|
+
fieldProps?: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* The spreadable `<form>` attributes for this store.
|
|
30
|
+
* @defaultValue the store's own `form.props`
|
|
31
|
+
*/
|
|
32
|
+
enhance?: ((form: Store) => Record<string | symbol, unknown>) | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Text of the cancel link.
|
|
35
|
+
* @defaultValue `'Cancel'`
|
|
36
|
+
*/
|
|
37
|
+
cancelLabel?: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Submit button text while the form is submitting.
|
|
40
|
+
* @defaultValue `'Saving…'`
|
|
41
|
+
*/
|
|
42
|
+
pendingLabel?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The empty option of a select entry.
|
|
45
|
+
* @defaultValue `'Choose…'`
|
|
46
|
+
*/
|
|
47
|
+
selectPlaceholder?: string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Read out after a required field’s label.
|
|
50
|
+
* @defaultValue `'(required)'` (from `FormField`)
|
|
51
|
+
*/
|
|
52
|
+
requiredLabel?: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
/** Renders an `@thomasfosterau/effect-forms-svelte` `defineEntityForm` definition through this library’s field primitives. */
|
|
55
|
+
declare const EntityForm: import("svelte").Component<Props, {}, "">;
|
|
56
|
+
type EntityForm = ReturnType<typeof EntityForm>;
|
|
57
|
+
export default EntityForm;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The submit and cancel row that closes a form.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FormActions — the submit / cancel row that closes a form.
|
|
6
|
+
|
|
7
|
+
ARIA contract: the submit control is a real `type="submit"` button, so
|
|
8
|
+
Enter in any field still submits the form. While `pending` it is disabled
|
|
9
|
+
AND `aria-busy="true"` (disabled alone says "you may not"; busy says "it is
|
|
10
|
+
already happening") and its label swaps to `pendingLabel` — the button
|
|
11
|
+
keeps a name throughout, which a spinner-only state would not.
|
|
12
|
+
|
|
13
|
+
Cancel is a LINK, not a button: it navigates away, and a reader should be
|
|
14
|
+
able to see where to before they commit to it.
|
|
15
|
+
-->
|
|
16
|
+
<script lang="ts">import Button from "../ui/Button.svelte";
|
|
17
|
+
import LinkButton from "../ui/LinkButton.svelte";
|
|
18
|
+
let { submitLabel = "Save", pendingLabel = "Saving…", pending = false, disabled = false, tone = "primary", cancelHref, cancelLabel = "Cancel", children, class: className, ...rest } = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div {...rest} class={['flex flex-wrap items-center gap-3 pt-2', className]}>
|
|
22
|
+
<Button type="submit" {tone} loading={pending} {disabled}>
|
|
23
|
+
{pending ? pendingLabel : submitLabel}
|
|
24
|
+
</Button>
|
|
25
|
+
{#if cancelHref !== undefined}
|
|
26
|
+
<LinkButton href={cancelHref} variant="ghost" disabled={pending}>{cancelLabel}</LinkButton>
|
|
27
|
+
{/if}
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Tone } from '../ui/tones.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
/**
|
|
6
|
+
* Idle label of the submit button.
|
|
7
|
+
* @defaultValue `'Save'`
|
|
8
|
+
*/
|
|
9
|
+
submitLabel?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Label while the form is in flight.
|
|
12
|
+
* @defaultValue `'Saving…'`
|
|
13
|
+
*/
|
|
14
|
+
pendingLabel?: string | undefined;
|
|
15
|
+
/** The form is being submitted. */
|
|
16
|
+
pending?: boolean | undefined;
|
|
17
|
+
/** Extra disable condition — a required picker still empty, say. */
|
|
18
|
+
disabled?: boolean | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Colour of the submit button.
|
|
21
|
+
* @defaultValue `'primary'`
|
|
22
|
+
*/
|
|
23
|
+
tone?: Tone | undefined;
|
|
24
|
+
/** Where cancel goes. Omit for a form with no cancel affordance. */
|
|
25
|
+
cancelHref?: string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Text of the cancel control.
|
|
28
|
+
* @defaultValue `'Cancel'`
|
|
29
|
+
*/
|
|
30
|
+
cancelLabel?: string | undefined;
|
|
31
|
+
/** Extra controls, rendered after cancel. */
|
|
32
|
+
children?: Snippet<[]> | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** The submit and cancel row that closes a form. */
|
|
35
|
+
declare const FormActions: import("svelte").Component<Props, {}, "">;
|
|
36
|
+
type FormActions = ReturnType<typeof FormActions>;
|
|
37
|
+
export default FormActions;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The whole-form messages that belong to the submission rather than to any one field.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FormErrors — the whole-form messages: the ones that belong to the
|
|
6
|
+
submission rather than to any one field ("That name is already taken",
|
|
7
|
+
"Your session expired").
|
|
8
|
+
|
|
9
|
+
ARIA contract: one `Alert` per message, each `live="assertive"`. Assertive
|
|
10
|
+
is right here and would be wrong for page furniture: these appear in
|
|
11
|
+
response to something the reader just did, and the next thing they will do
|
|
12
|
+
is try again. Nothing is rendered while the list is empty, so the alert is
|
|
13
|
+
inserted — and therefore announced — exactly when there is something to say.
|
|
14
|
+
-->
|
|
15
|
+
<script lang="ts">import Alert from "../ui/Alert.svelte";
|
|
16
|
+
let { messages, tone = "error", class: className } = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#each messages ?? [] as message, index (index)}
|
|
20
|
+
<Alert {tone} live="assertive" class={className}>{message}</Alert>
|
|
21
|
+
{/each}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StatusTone } from '../ui/tones.ts';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Whole-form messages. */
|
|
4
|
+
messages?: readonly string[] | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Colour of the alert.
|
|
7
|
+
* @defaultValue `'error'`
|
|
8
|
+
*/
|
|
9
|
+
tone?: StatusTone | undefined;
|
|
10
|
+
/** Extra classes, merged last. */
|
|
11
|
+
class?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** The whole-form messages that belong to the submission rather than to any one field. */
|
|
14
|
+
declare const FormErrors: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type FormErrors = ReturnType<typeof FormErrors>;
|
|
16
|
+
export default FormErrors;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A fixed value posted with the form and never edited.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
HiddenField — a fixed value posted with the form and never edited: the
|
|
6
|
+
parent id a create form hangs its row off, a command discriminator, a
|
|
7
|
+
redirect target.
|
|
8
|
+
|
|
9
|
+
It is a component rather than a bare `<input type="hidden">` so that a form
|
|
10
|
+
described declaratively (a list of fields) can include one without dropping
|
|
11
|
+
out of that description, and so the value passes through one place that
|
|
12
|
+
stringifies it.
|
|
13
|
+
|
|
14
|
+
ARIA contract: none, deliberately — a hidden input is not in the
|
|
15
|
+
accessibility tree, and anything a reader needs to KNOW about does not
|
|
16
|
+
belong in one.
|
|
17
|
+
-->
|
|
18
|
+
<script lang="ts">let { name, value, ...rest } = $props();
|
|
19
|
+
export {};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<input {...rest} type="hidden" {name} value={String(value)} />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends Omit<HTMLInputAttributes, 'type' | 'value'> {
|
|
3
|
+
/** The posted field name. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** The posted value. */
|
|
6
|
+
value: string | number;
|
|
7
|
+
}
|
|
8
|
+
/** A fixed value posted with the form and never edited. */
|
|
9
|
+
declare const HiddenField: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type HiddenField = ReturnType<typeof HiddenField>;
|
|
11
|
+
export default HiddenField;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The site search box: a `<search>` landmark around a GET form that works without JavaScript.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
SearchForm — the site search box: a `<search>` landmark around a GET form
|
|
6
|
+
that works with no JavaScript at all.
|
|
7
|
+
|
|
8
|
+
Method GET on purpose. A search is a request for a REPRESENTATION, so its
|
|
9
|
+
result should be a URL people can bookmark, share and go back to; a POST
|
|
10
|
+
would make the results page un-linkable and re-submit on refresh.
|
|
11
|
+
|
|
12
|
+
ARIA contract: `<search>` is the landmark (so "go to search" reaches it
|
|
13
|
+
without an `aria-label` per instance), the visible label is visually hidden
|
|
14
|
+
but real — `for`/`id`, not a `placeholder`, which disappears the moment
|
|
15
|
+
someone types and is not a name at all — and the submit button carries its
|
|
16
|
+
own text rather than an icon alone.
|
|
17
|
+
|
|
18
|
+
`action` resolves through the components context, so an app with a base path
|
|
19
|
+
or a locale prefix gets the right URL without this file knowing about either.
|
|
20
|
+
-->
|
|
21
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
22
|
+
import Button from "../ui/Button.svelte";
|
|
23
|
+
let { action, name = "q", value = $bindable(""), label = "Search", submitLabel = "Search", placeholder, required = true, class: className, ...rest } = $props();
|
|
24
|
+
const context = getComponentsContext();
|
|
25
|
+
const resolved = $derived(context.href(action));
|
|
26
|
+
const uid = $props.id();
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<search class={['block', className]}>
|
|
30
|
+
<form {...rest} action={resolved} method="get" class="join w-full">
|
|
31
|
+
<label class="sr-only" for={uid}>{label}</label>
|
|
32
|
+
<input
|
|
33
|
+
id={uid}
|
|
34
|
+
class="input join-item w-full"
|
|
35
|
+
type="search"
|
|
36
|
+
{name}
|
|
37
|
+
{placeholder}
|
|
38
|
+
{required}
|
|
39
|
+
bind:value
|
|
40
|
+
/>
|
|
41
|
+
<Button type="submit" tone="primary" class="join-item">{submitLabel}</Button>
|
|
42
|
+
</form>
|
|
43
|
+
</search>
|