@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,22 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A daisyUI `textarea` with a bindable `value` and `FieldControl` wiring. `rows` defaults to `4`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Textarea — daisyUI `textarea` with a bindable `value` and the same
|
|
6
|
+
`FieldControl` wiring as `TextInput`.
|
|
7
|
+
|
|
8
|
+
ARIA contract: a native `<textarea>`; the component adds `aria-describedby`
|
|
9
|
+
and `aria-invalid` and nothing else.
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">import { textareaSize } from "./field-types.js";
|
|
12
|
+
let { value = $bindable(""), size = "md", invalid = false, describedBy = "", rows = 4, class: className, ...rest } = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<textarea
|
|
16
|
+
{...rest}
|
|
17
|
+
{rows}
|
|
18
|
+
bind:value
|
|
19
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
20
|
+
aria-describedby={describedBy === '' ? undefined : describedBy}
|
|
21
|
+
class={['textarea w-full', textareaSize[size], invalid && 'textarea-error', className]}
|
|
22
|
+
></textarea>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
2
|
+
import { type FieldSize } from './field-types.ts';
|
|
3
|
+
interface Props extends Omit<HTMLTextareaAttributes, 'value'> {
|
|
4
|
+
/**
|
|
5
|
+
* The text. Bindable.
|
|
6
|
+
* @defaultValue `''`
|
|
7
|
+
*/
|
|
8
|
+
value?: string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* daisyUI size step.
|
|
11
|
+
* @defaultValue `'md'`
|
|
12
|
+
*/
|
|
13
|
+
size?: FieldSize | undefined;
|
|
14
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
15
|
+
invalid?: boolean | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
18
|
+
* @defaultValue `''`
|
|
19
|
+
*/
|
|
20
|
+
describedBy?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** A daisyUI `textarea` with a bindable `value` and `FieldControl` wiring. `rows` defaults to `4`. */
|
|
23
|
+
declare const Textarea: import("svelte").Component<Props, {}, "value">;
|
|
24
|
+
type Textarea = ReturnType<typeof Textarea>;
|
|
25
|
+
export default Textarea;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A switch for an on/off setting that takes effect immediately, rather than a checkbox submitted with a form.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Toggle — a switch: an on/off setting that takes effect immediately, rather
|
|
6
|
+
than a checkbox whose value is submitted with a form.
|
|
7
|
+
|
|
8
|
+
ARIA contract: a native `<input type="checkbox">` carrying `role="switch"`,
|
|
9
|
+
so the platform supplies the keyboard (Space) and the checked state while
|
|
10
|
+
the role tells assistive technology to announce "on"/"off" rather than
|
|
11
|
+
"ticked". The visible `label` is inside the `<label>` and so IS the
|
|
12
|
+
accessible name; `description` is deliberately outside it and reached
|
|
13
|
+
through `aria-describedby`, because a paragraph of explanation inside the
|
|
14
|
+
label would be read as part of the name every time focus lands.
|
|
15
|
+
|
|
16
|
+
Why the control is NOT `bind:checked`: the browser flips a checkbox the
|
|
17
|
+
instant it is clicked, but `onChange` may refuse the change — the
|
|
18
|
+
registration failed, the reader denied a permission, the server said no.
|
|
19
|
+
So the switch reports an INTENTION, and whatever `onChange` resolves to is
|
|
20
|
+
written back onto the control. Without that write-back a refused change
|
|
21
|
+
leaves the switch showing a state that is not true until the next reload.
|
|
22
|
+
A thrown `onChange` is treated as a refusal for the same reason, and left
|
|
23
|
+
for the caller to report — a switch is not the place to render an error.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import { toggleSize } from "./field-types.js";
|
|
26
|
+
let { checked = $bindable(false), label, description, onChange, size = "md", invalid = false, describedBy = "", disabled = false, class: className, ...rest } = $props();
|
|
27
|
+
const uid = $props.id();
|
|
28
|
+
const descriptionId = `${uid}-description`;
|
|
29
|
+
let pending = $state(false);
|
|
30
|
+
const described = $derived([description === undefined ? "" : descriptionId, describedBy].filter((part) => part !== "").join(" "));
|
|
31
|
+
const settle = async (control) => {
|
|
32
|
+
const next = control.checked;
|
|
33
|
+
if (onChange === undefined) {
|
|
34
|
+
checked = next;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (pending) {
|
|
38
|
+
control.checked = checked;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
pending = true;
|
|
42
|
+
try {
|
|
43
|
+
checked = await onChange(next);
|
|
44
|
+
} catch {} finally {
|
|
45
|
+
pending = false;
|
|
46
|
+
control.checked = checked;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div class={['flex flex-col gap-1', className]}>
|
|
52
|
+
<label class="label cursor-pointer justify-start gap-3">
|
|
53
|
+
<input
|
|
54
|
+
{...rest}
|
|
55
|
+
type="checkbox"
|
|
56
|
+
role="switch"
|
|
57
|
+
{checked}
|
|
58
|
+
disabled={disabled || pending}
|
|
59
|
+
aria-busy={pending ? 'true' : undefined}
|
|
60
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
61
|
+
aria-describedby={described === '' ? undefined : described}
|
|
62
|
+
onchange={(event) => {
|
|
63
|
+
void settle(event.currentTarget);
|
|
64
|
+
}}
|
|
65
|
+
class={['toggle', toggleSize[size], invalid && 'toggle-error']}
|
|
66
|
+
/>
|
|
67
|
+
<span>{label}</span>
|
|
68
|
+
</label>
|
|
69
|
+
{#if description !== undefined}
|
|
70
|
+
<p id={descriptionId} class="fieldset-label">{description}</p>
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type FieldSize } from './field-types.ts';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'type' | 'checked' | 'size' | 'onchange'> {
|
|
4
|
+
/** Whether the switch is on. Bindable. */
|
|
5
|
+
checked?: boolean | undefined;
|
|
6
|
+
/** Text beside the switch; becomes its accessible name. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Explanation under the switch, reached through `aria-describedby`. */
|
|
9
|
+
description?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Apply the change. Whatever it resolves to is what the switch ends up
|
|
12
|
+
* showing, so returning `false` from a request for `true` rejects it.
|
|
13
|
+
* Omitted, the switch is uncontrolled and simply follows the click.
|
|
14
|
+
*/
|
|
15
|
+
onChange?: ((next: boolean) => Promise<boolean> | boolean) | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* daisyUI size step.
|
|
18
|
+
* @defaultValue `'md'`
|
|
19
|
+
*/
|
|
20
|
+
size?: FieldSize | undefined;
|
|
21
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
22
|
+
invalid?: boolean | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
25
|
+
* @defaultValue `''`
|
|
26
|
+
*/
|
|
27
|
+
describedBy?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** A switch for an on/off setting that takes effect immediately, rather than a checkbox submitted with a form. */
|
|
30
|
+
declare const Toggle: import("svelte").Component<Props, {}, "checked">;
|
|
31
|
+
type Toggle = ReturnType<typeof Toggle>;
|
|
32
|
+
export default Toggle;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A `FileInput` plus the progress readout for an upload the caller performs.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
VideoInput — a `FileInput` plus the progress readout for an upload the
|
|
6
|
+
CALLER performs.
|
|
7
|
+
|
|
8
|
+
The division is deliberate and is the same one `FileInput` states: this owns
|
|
9
|
+
the SELECTION and the PROGRESS UI, the caller owns the transfer. It receives
|
|
10
|
+
the chosen file through `onSelect`, drives the upload however it likes —
|
|
11
|
+
resumable, direct-to-storage, whatever the provider needs — and reports back
|
|
12
|
+
by writing `progress`. A tus or S3 client is a real dependency and belongs
|
|
13
|
+
in the app, not in a package every page imports.
|
|
14
|
+
|
|
15
|
+
ARIA contract: `FileInput`'s, plus a native `<progress>` named by
|
|
16
|
+
`progressLabel`. The bar appears only while a transfer is actually in
|
|
17
|
+
flight (0 < progress < 100), so a reader is not handed a permanently
|
|
18
|
+
stalled-looking control.
|
|
19
|
+
-->
|
|
20
|
+
<script lang="ts">import FileInput from "./FileInput.svelte";
|
|
21
|
+
let { file = $bindable(), progress = $bindable(0), progressLabel = "Upload progress", accept = "video/*", onSelect, class: className, ...rest } = $props();
|
|
22
|
+
const uploading = $derived(file !== undefined && progress > 0 && progress < 100);
|
|
23
|
+
const select = (chosen) => {
|
|
24
|
+
progress = 0;
|
|
25
|
+
onSelect?.(chosen);
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class={['flex flex-col gap-2', className]}>
|
|
30
|
+
<FileInput {...rest} {accept} bind:file onSelect={select} />
|
|
31
|
+
{#if uploading}
|
|
32
|
+
<progress class="progress progress-primary w-full" value={progress} max="100" aria-label={progressLabel}
|
|
33
|
+
></progress>
|
|
34
|
+
{/if}
|
|
35
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { FieldSize } from './field-types.ts';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'type' | 'value' | 'files' | 'size'> {
|
|
4
|
+
/** The chosen file. */
|
|
5
|
+
file?: File | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Transfer progress, 0–100. The caller writes this as its upload advances.
|
|
8
|
+
* @defaultValue `0`
|
|
9
|
+
*/
|
|
10
|
+
progress?: number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Accessible name of the progress bar.
|
|
13
|
+
* @defaultValue `'Upload progress'`
|
|
14
|
+
*/
|
|
15
|
+
progressLabel?: string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The `accept` filter passed to the file input.
|
|
18
|
+
* @defaultValue `'video/*'`
|
|
19
|
+
*/
|
|
20
|
+
accept?: string | undefined;
|
|
21
|
+
/** Largest accepted file, in bytes; a larger file is rejected with `tooLargeMessage`. */
|
|
22
|
+
maxBytes?: number | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Shown when the chosen file exceeds `maxBytes`.
|
|
25
|
+
* @defaultValue `'That file is too large.'` (from `FileInput`)
|
|
26
|
+
*/
|
|
27
|
+
tooLargeMessage?: string | undefined;
|
|
28
|
+
/** Called with the accepted file — the caller starts the transfer here. */
|
|
29
|
+
onSelect?: ((file: File | undefined) => void) | undefined;
|
|
30
|
+
/** daisyUI size step. */
|
|
31
|
+
size?: FieldSize | undefined;
|
|
32
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
33
|
+
invalid?: boolean | undefined;
|
|
34
|
+
/** `FieldControl`: space-separated ids for `aria-describedby`. */
|
|
35
|
+
describedBy?: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** A `FileInput` plus the progress readout for an upload the caller performs. */
|
|
38
|
+
declare const VideoInput: import("svelte").Component<Props, {}, "progress" | "file">;
|
|
39
|
+
type VideoInput = ReturnType<typeof VideoInput>;
|
|
40
|
+
export default VideoInput;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control props `<FormField>` hands its `children` snippet, and every
|
|
3
|
+
* field primitive accepts by spread: `<TextInput {...control} bind:value />`.
|
|
4
|
+
* Carrying the ids this way is what keeps `aria-describedby` and
|
|
5
|
+
* `aria-invalid` on the control in step with the hint and error paragraphs
|
|
6
|
+
* `FormField` renders, without either side knowing the other's markup.
|
|
7
|
+
*
|
|
8
|
+
* The daisyUI size tables live here for the same reason `ui/tones.ts` holds
|
|
9
|
+
* the button ones: Tailwind only generates classes it can see verbatim, so a
|
|
10
|
+
* `input-${size}` template string emits nothing at build time. Every field
|
|
11
|
+
* component looks a complete class name up rather than building one.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/** Wiring a field primitive receives from its enclosing `FormField`. */
|
|
16
|
+
export interface FieldControl {
|
|
17
|
+
/** The control's `id`; the `FormField` label points at it. */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
/** Space-separated ids of the hint / error paragraphs; empty when neither is shown. */
|
|
20
|
+
readonly describedBy: string;
|
|
21
|
+
/** The field currently shows an error. */
|
|
22
|
+
readonly invalid: boolean;
|
|
23
|
+
readonly required: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Size steps shared by the field primitives (daisyUI has no `xl` field). */
|
|
26
|
+
export type FieldSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
27
|
+
/** One entry of a `Select`, `RadioGroup` or `CheckboxGroup`. */
|
|
28
|
+
export interface FieldOption {
|
|
29
|
+
readonly value: string;
|
|
30
|
+
readonly label: string;
|
|
31
|
+
readonly disabled?: boolean | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* What an options prop accepts: full options, or bare strings for the common
|
|
35
|
+
* case where the value and the label are the same word.
|
|
36
|
+
*/
|
|
37
|
+
export type FieldOptionInput = FieldOption | string;
|
|
38
|
+
/** Widen the bare-string shorthand into full {@link FieldOption}s. */
|
|
39
|
+
export declare function toFieldOptions(options: readonly FieldOptionInput[]): readonly FieldOption[];
|
|
40
|
+
/** Literal daisyUI class per `FieldSize` for `TextInput`; a lookup table so Tailwind's scanner sees every class. */
|
|
41
|
+
export declare const inputSize: Record<FieldSize, string>;
|
|
42
|
+
/** Literal daisyUI class per `FieldSize` for `Textarea`; a lookup table so Tailwind's scanner sees every class. */
|
|
43
|
+
export declare const textareaSize: Record<FieldSize, string>;
|
|
44
|
+
/** Literal daisyUI class per `FieldSize` for `Select`; a lookup table so Tailwind's scanner sees every class. */
|
|
45
|
+
export declare const selectSize: Record<FieldSize, string>;
|
|
46
|
+
/** Literal daisyUI class per `FieldSize` for `Checkbox`; a lookup table so Tailwind's scanner sees every class. */
|
|
47
|
+
export declare const checkboxSize: Record<FieldSize, string>;
|
|
48
|
+
/** Literal daisyUI class per `FieldSize` for `Toggle`; a lookup table so Tailwind's scanner sees every class. */
|
|
49
|
+
export declare const toggleSize: Record<FieldSize, string>;
|
|
50
|
+
/** Literal daisyUI class per `FieldSize` for `FileInput`; a lookup table so Tailwind's scanner sees every class. */
|
|
51
|
+
export declare const fileInputSize: Record<FieldSize, string>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control props `<FormField>` hands its `children` snippet, and every
|
|
3
|
+
* field primitive accepts by spread: `<TextInput {...control} bind:value />`.
|
|
4
|
+
* Carrying the ids this way is what keeps `aria-describedby` and
|
|
5
|
+
* `aria-invalid` on the control in step with the hint and error paragraphs
|
|
6
|
+
* `FormField` renders, without either side knowing the other's markup.
|
|
7
|
+
*
|
|
8
|
+
* The daisyUI size tables live here for the same reason `ui/tones.ts` holds
|
|
9
|
+
* the button ones: Tailwind only generates classes it can see verbatim, so a
|
|
10
|
+
* `input-${size}` template string emits nothing at build time. Every field
|
|
11
|
+
* component looks a complete class name up rather than building one.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/** Widen the bare-string shorthand into full {@link FieldOption}s. */
|
|
16
|
+
export function toFieldOptions(options) {
|
|
17
|
+
return options.map((option) => typeof option === 'string' ? { value: option, label: option } : option);
|
|
18
|
+
}
|
|
19
|
+
/** Literal daisyUI class per `FieldSize` for `TextInput`; a lookup table so Tailwind's scanner sees every class. */
|
|
20
|
+
export const inputSize = {
|
|
21
|
+
xs: 'input-xs',
|
|
22
|
+
sm: 'input-sm',
|
|
23
|
+
md: 'input-md',
|
|
24
|
+
lg: 'input-lg',
|
|
25
|
+
};
|
|
26
|
+
/** Literal daisyUI class per `FieldSize` for `Textarea`; a lookup table so Tailwind's scanner sees every class. */
|
|
27
|
+
export const textareaSize = {
|
|
28
|
+
xs: 'textarea-xs',
|
|
29
|
+
sm: 'textarea-sm',
|
|
30
|
+
md: 'textarea-md',
|
|
31
|
+
lg: 'textarea-lg',
|
|
32
|
+
};
|
|
33
|
+
/** Literal daisyUI class per `FieldSize` for `Select`; a lookup table so Tailwind's scanner sees every class. */
|
|
34
|
+
export const selectSize = {
|
|
35
|
+
xs: 'select-xs',
|
|
36
|
+
sm: 'select-sm',
|
|
37
|
+
md: 'select-md',
|
|
38
|
+
lg: 'select-lg',
|
|
39
|
+
};
|
|
40
|
+
/** Literal daisyUI class per `FieldSize` for `Checkbox`; a lookup table so Tailwind's scanner sees every class. */
|
|
41
|
+
export const checkboxSize = {
|
|
42
|
+
xs: 'checkbox-xs',
|
|
43
|
+
sm: 'checkbox-sm',
|
|
44
|
+
md: 'checkbox-md',
|
|
45
|
+
lg: 'checkbox-lg',
|
|
46
|
+
};
|
|
47
|
+
/** Literal daisyUI class per `FieldSize` for `Toggle`; a lookup table so Tailwind's scanner sees every class. */
|
|
48
|
+
export const toggleSize = {
|
|
49
|
+
xs: 'toggle-xs',
|
|
50
|
+
sm: 'toggle-sm',
|
|
51
|
+
md: 'toggle-md',
|
|
52
|
+
lg: 'toggle-lg',
|
|
53
|
+
};
|
|
54
|
+
/** Literal daisyUI class per `FieldSize` for `FileInput`; a lookup table so Tailwind's scanner sees every class. */
|
|
55
|
+
export const fileInputSize = {
|
|
56
|
+
xs: 'file-input-xs',
|
|
57
|
+
sm: 'file-input-sm',
|
|
58
|
+
md: 'file-input-md',
|
|
59
|
+
lg: 'file-input-lg',
|
|
60
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fields — form controls wired through `FormField`'s `FieldControl` contract.
|
|
3
|
+
*
|
|
4
|
+
* The shape every control in here shares: `FormField` (or `Fieldset`) owns
|
|
5
|
+
* the label, the hint and the errors, derives one `id` into a `describedBy`
|
|
6
|
+
* and an `invalid` flag, and hands that `FieldControl` to its `children`
|
|
7
|
+
* snippet. A control spreads it — `<TextInput {...control} bind:value />` —
|
|
8
|
+
* and so carries `aria-describedby`, `aria-invalid` and `required` without
|
|
9
|
+
* either side knowing the other's markup.
|
|
10
|
+
*
|
|
11
|
+
* Optional props are declared `T | undefined` rather than `prop?: T`: the
|
|
12
|
+
* workspace compiles with `exactOptionalPropertyTypes`, under which a caller
|
|
13
|
+
* forwarding its own optional prop would not compile otherwise.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export { default as Checkbox } from './Checkbox.svelte';
|
|
18
|
+
export { default as CheckboxGroup } from './CheckboxGroup.svelte';
|
|
19
|
+
export { default as Combobox } from './Combobox.svelte';
|
|
20
|
+
export { default as DateInput } from './DateInput.svelte';
|
|
21
|
+
export { default as FieldErrors } from './FieldErrors.svelte';
|
|
22
|
+
export { default as Fieldset } from './Fieldset.svelte';
|
|
23
|
+
export { default as FileInput } from './FileInput.svelte';
|
|
24
|
+
export { default as FormField } from './FormField.svelte';
|
|
25
|
+
export { default as ImageInput } from './ImageInput.svelte';
|
|
26
|
+
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
27
|
+
export { default as SearchInput } from './SearchInput.svelte';
|
|
28
|
+
export { default as Select } from './Select.svelte';
|
|
29
|
+
export { default as Textarea } from './Textarea.svelte';
|
|
30
|
+
export { default as TextInput } from './TextInput.svelte';
|
|
31
|
+
export { default as Toggle } from './Toggle.svelte';
|
|
32
|
+
export { default as PushToggle, type PushDeclineReason, type PushEnableResult, } from './PushToggle.svelte';
|
|
33
|
+
export { default as VideoInput } from './VideoInput.svelte';
|
|
34
|
+
export * from './field-types.ts';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fields — form controls wired through `FormField`'s `FieldControl` contract.
|
|
3
|
+
*
|
|
4
|
+
* The shape every control in here shares: `FormField` (or `Fieldset`) owns
|
|
5
|
+
* the label, the hint and the errors, derives one `id` into a `describedBy`
|
|
6
|
+
* and an `invalid` flag, and hands that `FieldControl` to its `children`
|
|
7
|
+
* snippet. A control spreads it — `<TextInput {...control} bind:value />` —
|
|
8
|
+
* and so carries `aria-describedby`, `aria-invalid` and `required` without
|
|
9
|
+
* either side knowing the other's markup.
|
|
10
|
+
*
|
|
11
|
+
* Optional props are declared `T | undefined` rather than `prop?: T`: the
|
|
12
|
+
* workspace compiles with `exactOptionalPropertyTypes`, under which a caller
|
|
13
|
+
* forwarding its own optional prop would not compile otherwise.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export { default as Checkbox } from './Checkbox.svelte';
|
|
18
|
+
export { default as CheckboxGroup } from './CheckboxGroup.svelte';
|
|
19
|
+
export { default as Combobox } from './Combobox.svelte';
|
|
20
|
+
export { default as DateInput } from './DateInput.svelte';
|
|
21
|
+
export { default as FieldErrors } from './FieldErrors.svelte';
|
|
22
|
+
export { default as Fieldset } from './Fieldset.svelte';
|
|
23
|
+
export { default as FileInput } from './FileInput.svelte';
|
|
24
|
+
export { default as FormField } from './FormField.svelte';
|
|
25
|
+
export { default as ImageInput } from './ImageInput.svelte';
|
|
26
|
+
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
27
|
+
export { default as SearchInput } from './SearchInput.svelte';
|
|
28
|
+
export { default as Select } from './Select.svelte';
|
|
29
|
+
export { default as Textarea } from './Textarea.svelte';
|
|
30
|
+
export { default as TextInput } from './TextInput.svelte';
|
|
31
|
+
export { default as Toggle } from './Toggle.svelte';
|
|
32
|
+
export { default as PushToggle, } from './PushToggle.svelte';
|
|
33
|
+
export { default as VideoInput } from './VideoInput.svelte';
|
|
34
|
+
export * from "./field-types.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A `<time>` element with the ISO instant in `datetime` and the `Intl.DateTimeFormat` rendering as its text.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
DateTime — a `<time>` element carrying the ISO instant in `datetime` and
|
|
6
|
+
`Intl.DateTimeFormat`'s locale rendering as its text.
|
|
7
|
+
|
|
8
|
+
ARIA contract: none beyond the native `<time>` semantics — the `datetime`
|
|
9
|
+
attribute is what makes the value machine-readable; a screen reader still
|
|
10
|
+
announces the formatted text content (or the `children` snippet, when
|
|
11
|
+
given), never the ISO string.
|
|
12
|
+
|
|
13
|
+
`options` (raw `Intl.DateTimeFormatOptions`) wins over `dateStyle`/
|
|
14
|
+
`timeStyle` when both are supplied, so a caller that needs a field-level
|
|
15
|
+
combination (`{ year: 'numeric', month: 'long' }`) isn't stuck with the
|
|
16
|
+
coarse style presets.
|
|
17
|
+
-->
|
|
18
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
19
|
+
let { value, locales = getComponentsContext().locale(), dateStyle = "medium", timeStyle, options, children, ...rest } = $props();
|
|
20
|
+
const instant = $derived(new Date(value));
|
|
21
|
+
// The formatter and the formatted string are separate `$derived` values so
|
|
22
|
+
// constructing the (relatively expensive) `Intl.DateTimeFormat` is skipped
|
|
23
|
+
// when only `value` changes, not when locale/options change. See
|
|
24
|
+
// PERF-CPU-003 in docs/specs/performance-invariants.md.
|
|
25
|
+
const format = $derived(new Intl.DateTimeFormat(locales, options ?? {
|
|
26
|
+
dateStyle,
|
|
27
|
+
timeStyle
|
|
28
|
+
}));
|
|
29
|
+
const formatted = $derived(format.format(instant));
|
|
30
|
+
const iso = $derived(instant.toISOString());
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<time {...rest} datetime={iso}>
|
|
34
|
+
{#if children}{@render children()}{:else}{formatted}{/if}
|
|
35
|
+
</time>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLTimeAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLTimeAttributes, 'children'> {
|
|
4
|
+
/** The instant: a `Date`, epoch milliseconds or an ISO string. */
|
|
5
|
+
value: Date | number | string;
|
|
6
|
+
/**
|
|
7
|
+
* Locale(s) for `Intl` formatting.
|
|
8
|
+
* @defaultValue the components context's `locale()`
|
|
9
|
+
*/
|
|
10
|
+
locales?: string | readonly string[] | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* `Intl.DateTimeFormat` `dateStyle`.
|
|
13
|
+
* @defaultValue `'medium'`
|
|
14
|
+
*/
|
|
15
|
+
dateStyle?: Intl.DateTimeFormatOptions['dateStyle'] | undefined;
|
|
16
|
+
/** `Intl.DateTimeFormat` `timeStyle`; omitted, no time is shown. */
|
|
17
|
+
timeStyle?: Intl.DateTimeFormatOptions['timeStyle'] | undefined;
|
|
18
|
+
/** Raw `Intl.DateTimeFormatOptions`; wins over `dateStyle`/`timeStyle` when given. */
|
|
19
|
+
options?: Intl.DateTimeFormatOptions | undefined;
|
|
20
|
+
/** Replaces the formatted text; the `datetime` attribute stays machine-readable. */
|
|
21
|
+
children?: Snippet<[]> | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** A `<time>` element with the ISO instant in `datetime` and the `Intl.DateTimeFormat` rendering as its text. */
|
|
24
|
+
declare const DateTime: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type DateTime = ReturnType<typeof DateTime>;
|
|
26
|
+
export default DateTime;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders `items` through `Intl.ListFormat`, with each item drawn by the `children` snippet and the joining words from the locale.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FormatList<T> — renders `items` through `Intl.ListFormat`, letting the
|
|
6
|
+
`children` snippet render each element while literal text ("and", ", ")
|
|
7
|
+
comes from the locale's own conjunction/disjunction rules.
|
|
8
|
+
|
|
9
|
+
No wrapping element of its own — it emits `children(item)` and text nodes
|
|
10
|
+
in sequence, so the caller controls the surrounding markup entirely.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts" generics="T">import { getComponentsContext } from "../context.svelte.js";
|
|
13
|
+
let { items, style = "long", type = "conjunction", locales = getComponentsContext().locale(), children } = $props();
|
|
14
|
+
const format = $derived(new Intl.ListFormat(locales, {
|
|
15
|
+
style,
|
|
16
|
+
type
|
|
17
|
+
}));
|
|
18
|
+
// `Intl.ListFormat` only formats strings, so format the items' *indices*
|
|
19
|
+
// and map each "element" part back to the real item by index — that way
|
|
20
|
+
// the snippet gets the original `T`, not its stringified form.
|
|
21
|
+
const indices = $derived(items.map((_, index) => index.toString()));
|
|
22
|
+
const parts = $derived(format.formatToParts(indices));
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#each parts as part, i (i)}
|
|
26
|
+
{#if part.type === 'element'}
|
|
27
|
+
{@render children(items[Number.parseInt(part.value, 10)] as T)}
|
|
28
|
+
{:else}
|
|
29
|
+
{part.value}
|
|
30
|
+
{/if}
|
|
31
|
+
{/each}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
declare function $$render<T>(): {
|
|
3
|
+
props: {
|
|
4
|
+
/** The items to join, each rendered by `children`. */
|
|
5
|
+
items: readonly T[];
|
|
6
|
+
/**
|
|
7
|
+
* `Intl.ListFormat` style.
|
|
8
|
+
* @defaultValue `'long'`
|
|
9
|
+
*/
|
|
10
|
+
style?: Intl.ListFormatOptions["style"] | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* `Intl.ListFormat` type.
|
|
13
|
+
* @defaultValue `'conjunction'`
|
|
14
|
+
*/
|
|
15
|
+
type?: Intl.ListFormatOptions["type"] | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Locale(s) for `Intl` formatting.
|
|
18
|
+
* @defaultValue the components context's `locale()`
|
|
19
|
+
*/
|
|
20
|
+
locales?: string | readonly string[] | undefined;
|
|
21
|
+
/** Renders one item. */
|
|
22
|
+
children: Snippet<[T]>;
|
|
23
|
+
};
|
|
24
|
+
exports: {};
|
|
25
|
+
bindings: "";
|
|
26
|
+
slots: {};
|
|
27
|
+
events: {};
|
|
28
|
+
};
|
|
29
|
+
declare class __sveltets_Render<T> {
|
|
30
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
31
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
32
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
33
|
+
bindings(): "";
|
|
34
|
+
exports(): {};
|
|
35
|
+
}
|
|
36
|
+
interface $$IsomorphicComponent {
|
|
37
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
38
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
39
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
40
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
41
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
42
|
+
}
|
|
43
|
+
/** Renders `items` through `Intl.ListFormat`, with each item drawn by the `children` snippet and the joining words from the locale. */
|
|
44
|
+
declare const FormatList: $$IsomorphicComponent;
|
|
45
|
+
type FormatList<T> = InstanceType<typeof FormatList<T>>;
|
|
46
|
+
export default FormatList;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Best-effort client-side highlighting of query tokens in a search result.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Highlight — best-effort client-side token highlight for search results.
|
|
6
|
+
|
|
7
|
+
Splits `query` on whitespace, escapes regex metacharacters, and wraps
|
|
8
|
+
case-insensitive matches in `<mark>`. Intentionally naive: no stemming, no
|
|
9
|
+
FTS-aware tokenisation, no diacritic folding — the authoritative match is
|
|
10
|
+
whatever server-side search produced; this is a UX nicety, and its highlights should not be read as
|
|
11
|
+
"this is exactly what full-text search matched".
|
|
12
|
+
-->
|
|
13
|
+
<script lang="ts" module>function escapeRegex(value) {
|
|
14
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15
|
+
}
|
|
16
|
+
export function tokenise(text, query) {
|
|
17
|
+
const tokens = query.trim().split(/\s+/).filter((token) => token.length > 0).map(escapeRegex);
|
|
18
|
+
if (tokens.length === 0 || text.length === 0) {
|
|
19
|
+
return [{
|
|
20
|
+
text,
|
|
21
|
+
match: false
|
|
22
|
+
}];
|
|
23
|
+
}
|
|
24
|
+
// `String.prototype.split` with a single capturing group interleaves the
|
|
25
|
+
// captured matches into the result at odd indices: [before, match,
|
|
26
|
+
// between, match, …]. Read `match` from that index parity rather than
|
|
27
|
+
// re-testing each segment — testing each part against a *global* regex
|
|
28
|
+
// would leave `lastIndex` advanced on non-matching segments, which can
|
|
29
|
+
// misclassify later segments and drop highlights. Map first (parity is
|
|
30
|
+
// meaningful on the raw split array), then filter empties.
|
|
31
|
+
const pattern = new RegExp(`(${tokens.join("|")})`, "gi");
|
|
32
|
+
return text.split(pattern).map((part, index) => ({
|
|
33
|
+
text: part,
|
|
34
|
+
match: index % 2 === 1
|
|
35
|
+
})).filter((segment) => segment.text.length > 0);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script lang="ts">const { text, query } = $props();
|
|
40
|
+
const segments = $derived(tokenise(text, query));
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#each segments as segment, i (i)}
|
|
44
|
+
{#if segment.match}
|
|
45
|
+
<mark class="bg-warning/30 text-base-content rounded-sm px-0.5">{segment.text}</mark>
|
|
46
|
+
{:else}{segment.text}{/if}
|
|
47
|
+
{/each}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Segment {
|
|
2
|
+
text: string;
|
|
3
|
+
match: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function tokenise(text: string, query: string): Segment[];
|
|
6
|
+
interface Props {
|
|
7
|
+
/** The full text to render. */
|
|
8
|
+
text: string;
|
|
9
|
+
/** The user's search query. Tokens are highlighted within `text`. */
|
|
10
|
+
query: string;
|
|
11
|
+
}
|
|
12
|
+
/** Best-effort client-side highlighting of query tokens in a search result. */
|
|
13
|
+
declare const Highlight: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Highlight = ReturnType<typeof Highlight>;
|
|
15
|
+
export default Highlight;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A `<data>` element whose text is the `Intl.NumberFormat` rendering and whose `value` attribute keeps the raw number.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
NumberFormat — a `<data>` element whose text is `Intl.NumberFormat`'s
|
|
6
|
+
rendering and whose `value` attribute is the raw number/bigint, so the
|
|
7
|
+
machine-readable figure survives locale formatting.
|
|
8
|
+
|
|
9
|
+
Exported as `NumberFormat` (not `Number`) to avoid shadowing the global.
|
|
10
|
+
|
|
11
|
+
ARIA contract: none beyond native `<data>` semantics — `value` carries the
|
|
12
|
+
machine-readable figure, the rendered text carries the locale-formatted one.
|
|
13
|
+
-->
|
|
14
|
+
<script lang="ts">import { getComponentsContext } from "../context.svelte.js";
|
|
15
|
+
let { value, locales = getComponentsContext().locale(), style = "decimal", unit, unitDisplay = "short", currency, currencyDisplay = "symbol", maximumFractionDigits, minimumFractionDigits, signDisplay, notation, options, ...rest } = $props();
|
|
16
|
+
// The formatter and the formatted string are separate `$derived` values so
|
|
17
|
+
// constructing the (relatively expensive) `Intl.NumberFormat` is skipped
|
|
18
|
+
// when only `value` changes. See PERF-CPU-003 in
|
|
19
|
+
// docs/specs/performance-invariants.md.
|
|
20
|
+
const format = $derived(new Intl.NumberFormat(locales, options ?? {
|
|
21
|
+
style,
|
|
22
|
+
unit,
|
|
23
|
+
unitDisplay,
|
|
24
|
+
currency,
|
|
25
|
+
currencyDisplay,
|
|
26
|
+
maximumFractionDigits,
|
|
27
|
+
minimumFractionDigits,
|
|
28
|
+
signDisplay,
|
|
29
|
+
notation
|
|
30
|
+
}));
|
|
31
|
+
const formatted = $derived(format.format(value));
|
|
32
|
+
const raw = $derived(String(value));
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<data {...rest} value={raw}>{formatted}</data>
|