@thomasfosterau/components 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +241 -56
- package/dist/canvas/VectorCanvas.svelte +320 -0
- package/dist/canvas/VectorCanvas.svelte.d.ts +105 -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 +40 -0
- package/dist/context.svelte.d.ts +46 -0
- package/dist/context.svelte.js +51 -0
- package/dist/data/ChatBubble.svelte +48 -0
- package/dist/data/ChatBubble.svelte.d.ts +33 -0
- package/dist/data/DataGrid.svelte +150 -0
- package/dist/data/DataGrid.svelte.d.ts +118 -0
- package/dist/data/DescriptionList.svelte +53 -0
- package/dist/data/DescriptionList.svelte.d.ts +26 -0
- package/dist/data/DescriptionRow.svelte +56 -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/MessageThread.svelte +29 -0
- package/dist/data/MessageThread.svelte.d.ts +17 -0
- package/dist/data/Pagination.svelte +136 -0
- package/dist/data/Pagination.svelte.d.ts +79 -0
- package/dist/data/PaginationFooter.svelte +42 -0
- package/dist/data/PaginationFooter.svelte.d.ts +61 -0
- package/dist/data/ResultCount.svelte +27 -0
- package/dist/data/ResultCount.svelte.d.ts +18 -0
- package/dist/data/Table.svelte +151 -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 +39 -0
- package/dist/data/description-context.js +36 -0
- package/dist/data/diff.d.ts +14 -0
- package/dist/data/diff.js +10 -0
- package/dist/data/index.d.ts +37 -0
- package/dist/data/index.js +37 -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 +71 -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 +47 -0
- package/dist/feedback/Dialog.svelte.d.ts +36 -0
- package/dist/feedback/DialogFrame.svelte +64 -0
- package/dist/feedback/DialogFrame.svelte.d.ts +23 -0
- package/dist/feedback/Drawer.svelte +43 -0
- package/dist/feedback/Drawer.svelte.d.ts +43 -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 +37 -0
- package/dist/feedback/StatTile.svelte +77 -0
- package/dist/feedback/StatTile.svelte.d.ts +50 -0
- package/dist/feedback/Stats.svelte +18 -0
- package/dist/feedback/Stats.svelte.d.ts +16 -0
- package/dist/feedback/Steps.svelte +72 -0
- package/dist/feedback/Steps.svelte.d.ts +61 -0
- package/dist/feedback/Timeline.svelte +86 -0
- package/dist/feedback/Timeline.svelte.d.ts +55 -0
- package/dist/feedback/Toaster.svelte +78 -0
- package/dist/feedback/Toaster.svelte.d.ts +32 -0
- package/dist/feedback/index.d.ts +34 -0
- package/dist/feedback/index.js +29 -0
- package/dist/feedback/sizes.d.ts +41 -0
- package/dist/feedback/sizes.js +69 -0
- package/dist/feedback/toaster.d.ts +48 -0
- package/dist/feedback/toaster.js +29 -0
- package/dist/fields/Calendar.svelte +60 -0
- package/dist/fields/Calendar.svelte.d.ts +11 -0
- package/dist/fields/CalendarMonths.svelte +86 -0
- package/dist/fields/CalendarMonths.svelte.d.ts +30 -0
- package/dist/fields/Checkbox.svelte +33 -0
- package/dist/fields/Checkbox.svelte.d.ts +30 -0
- package/dist/fields/CheckboxGroup.svelte +41 -0
- package/dist/fields/CheckboxGroup.svelte.d.ts +40 -0
- package/dist/fields/Combobox.svelte +229 -0
- package/dist/fields/Combobox.svelte.d.ts +133 -0
- package/dist/fields/DateField.svelte +37 -0
- package/dist/fields/DateField.svelte.d.ts +20 -0
- package/dist/fields/DateInput.svelte +31 -0
- package/dist/fields/DateInput.svelte.d.ts +35 -0
- package/dist/fields/DatePicker.svelte +47 -0
- package/dist/fields/DatePicker.svelte.d.ts +25 -0
- package/dist/fields/DateRangePicker.svelte +70 -0
- package/dist/fields/DateRangePicker.svelte.d.ts +43 -0
- package/dist/fields/Dropzone.svelte +232 -0
- package/dist/fields/Dropzone.svelte.d.ts +103 -0
- package/dist/fields/FieldErrors.svelte +30 -0
- package/dist/fields/FieldErrors.svelte.d.ts +11 -0
- package/dist/fields/Fieldset.svelte +53 -0
- package/dist/fields/Fieldset.svelte.d.ts +27 -0
- package/dist/fields/FileInput.svelte +68 -0
- package/dist/fields/FileInput.svelte.d.ts +39 -0
- package/dist/fields/FormField.svelte +78 -0
- package/dist/fields/FormField.svelte.d.ts +29 -0
- package/dist/fields/ImageInput.svelte +87 -0
- package/dist/fields/ImageInput.svelte.d.ts +45 -0
- package/dist/fields/MultiSelect.svelte +112 -0
- package/dist/fields/MultiSelect.svelte.d.ts +75 -0
- package/dist/fields/NumberField.svelte +190 -0
- package/dist/fields/NumberField.svelte.d.ts +76 -0
- package/dist/fields/PasswordInput.svelte +95 -0
- package/dist/fields/PasswordInput.svelte.d.ts +58 -0
- package/dist/fields/PickerPopover.svelte +47 -0
- package/dist/fields/PickerPopover.svelte.d.ts +16 -0
- package/dist/fields/PinInput.svelte +83 -0
- package/dist/fields/PinInput.svelte.d.ts +66 -0
- package/dist/fields/PushToggle.svelte +93 -0
- package/dist/fields/PushToggle.svelte.d.ts +55 -0
- package/dist/fields/RadioGroup.svelte +88 -0
- package/dist/fields/RadioGroup.svelte.d.ts +41 -0
- package/dist/fields/RangeCalendar.svelte +51 -0
- package/dist/fields/RangeCalendar.svelte.d.ts +23 -0
- package/dist/fields/Rating.svelte +127 -0
- package/dist/fields/Rating.svelte.d.ts +86 -0
- package/dist/fields/SearchInput.svelte +58 -0
- package/dist/fields/SearchInput.svelte.d.ts +33 -0
- package/dist/fields/SegmentGroup.svelte +91 -0
- package/dist/fields/SegmentGroup.svelte.d.ts +20 -0
- package/dist/fields/Select.svelte +36 -0
- package/dist/fields/Select.svelte.d.ts +35 -0
- package/dist/fields/Slider.svelte +194 -0
- package/dist/fields/Slider.svelte.d.ts +100 -0
- package/dist/fields/TagsInput.svelte +256 -0
- package/dist/fields/TagsInput.svelte.d.ts +102 -0
- package/dist/fields/TextInput.svelte +26 -0
- package/dist/fields/TextInput.svelte.d.ts +36 -0
- package/dist/fields/Textarea.svelte +22 -0
- package/dist/fields/Textarea.svelte.d.ts +31 -0
- package/dist/fields/TimeField.svelte +30 -0
- package/dist/fields/TimeField.svelte.d.ts +19 -0
- package/dist/fields/Toggle.svelte +72 -0
- package/dist/fields/Toggle.svelte.d.ts +38 -0
- package/dist/fields/VideoInput.svelte +35 -0
- package/dist/fields/VideoInput.svelte.d.ts +46 -0
- package/dist/fields/calendar-props.d.ts +119 -0
- package/dist/fields/calendar-props.js +51 -0
- package/dist/fields/dates.d.ts +28 -0
- package/dist/fields/dates.js +26 -0
- package/dist/fields/field-types.d.ts +80 -0
- package/dist/fields/field-types.js +92 -0
- package/dist/fields/file-validation.d.ts +30 -0
- package/dist/fields/file-validation.js +51 -0
- package/dist/fields/index.d.ts +43 -0
- package/dist/fields/index.js +42 -0
- package/dist/fields/latest-search.svelte.d.ts +38 -0
- package/dist/fields/latest-search.svelte.js +85 -0
- package/dist/fields/number-format.d.ts +52 -0
- package/dist/fields/number-format.js +89 -0
- package/dist/fields/segmented-field.d.ts +128 -0
- package/dist/fields/segmented-field.js +52 -0
- package/dist/fields/slider-keys.d.ts +21 -0
- package/dist/fields/slider-keys.js +37 -0
- package/dist/formatting/Countdown.svelte +110 -0
- package/dist/formatting/Countdown.svelte.d.ts +35 -0
- package/dist/formatting/DateTime.svelte +35 -0
- package/dist/formatting/DateTime.svelte.d.ts +26 -0
- package/dist/formatting/Duration.svelte +18 -0
- package/dist/formatting/Duration.svelte.d.ts +20 -0
- package/dist/formatting/FormatList.svelte +31 -0
- package/dist/formatting/FormatList.svelte.d.ts +46 -0
- package/dist/formatting/Highlight.svelte +51 -0
- package/dist/formatting/Highlight.svelte.d.ts +19 -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/duration.d.ts +44 -0
- package/dist/formatting/duration.js +125 -0
- package/dist/formatting/index.d.ts +15 -0
- package/dist/formatting/index.js +17 -0
- package/dist/forms/EntityForm.svelte +185 -0
- package/dist/forms/EntityForm.svelte.d.ts +66 -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 +28 -0
- package/dist/hotkeys/index.js +27 -0
- package/dist/hotkeys/match.d.ts +65 -0
- package/dist/hotkeys/match.js +135 -0
- package/dist/hotkeys/registry.svelte.d.ts +55 -0
- package/dist/hotkeys/registry.svelte.js +110 -0
- package/dist/hotkeys/types.d.ts +74 -0
- package/dist/hotkeys/types.js +18 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +37 -0
- package/dist/infobox/Infobox.svelte +79 -0
- package/dist/infobox/Infobox.svelte.d.ts +33 -0
- package/dist/infobox/InfoboxHeader.svelte +35 -0
- package/dist/infobox/InfoboxHeader.svelte.d.ts +18 -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/AppShell.svelte +50 -0
- package/dist/layout/AppShell.svelte.d.ts +28 -0
- package/dist/layout/Breadcrumbs.svelte +52 -0
- package/dist/layout/Breadcrumbs.svelte.d.ts +18 -0
- package/dist/layout/Cluster.svelte +28 -0
- package/dist/layout/Cluster.svelte.d.ts +27 -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/Fits.svelte +27 -0
- package/dist/layout/Fits.svelte.d.ts +13 -0
- package/dist/layout/Flow.svelte +46 -0
- package/dist/layout/Flow.svelte.d.ts +16 -0
- package/dist/layout/Footer.svelte +32 -0
- package/dist/layout/Footer.svelte.d.ts +12 -0
- package/dist/layout/Grid.svelte +23 -0
- package/dist/layout/Grid.svelte.d.ts +26 -0
- package/dist/layout/HStack.svelte +12 -0
- package/dist/layout/HStack.svelte.d.ts +34 -0
- package/dist/layout/Hero.svelte +76 -0
- package/dist/layout/Hero.svelte.d.ts +36 -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/Spacer.svelte +12 -0
- package/dist/layout/Spacer.svelte.d.ts +6 -0
- package/dist/layout/Tabs.svelte +85 -0
- package/dist/layout/Tabs.svelte.d.ts +42 -0
- package/dist/layout/VStack.svelte +12 -0
- package/dist/layout/VStack.svelte.d.ts +29 -0
- package/dist/layout/index.d.ts +38 -0
- package/dist/layout/index.js +38 -0
- package/dist/layout/space.d.ts +44 -0
- package/dist/layout/space.js +76 -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 +40 -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 +72 -0
- package/dist/seo/og-image.js +82 -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 +26 -0
- package/dist/tips/tip-stack.js +37 -0
- package/dist/tips/tip-store.svelte.d.ts +75 -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/Accordion.svelte +75 -0
- package/dist/ui/Accordion.svelte.d.ts +50 -0
- package/dist/ui/AccordionItem.svelte +73 -0
- package/dist/ui/AccordionItem.svelte.d.ts +18 -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 +44 -0
- package/dist/ui/Button.svelte.d.ts +29 -0
- package/dist/ui/ButtonGroup.svelte +24 -0
- package/dist/ui/ButtonGroup.svelte.d.ts +17 -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/ContextMenu.svelte +75 -0
- package/dist/ui/ContextMenu.svelte.d.ts +25 -0
- package/dist/ui/Disclosure.svelte +46 -0
- package/dist/ui/Disclosure.svelte.d.ts +22 -0
- package/dist/ui/Divider.svelte +47 -0
- package/dist/ui/Divider.svelte.d.ts +28 -0
- package/dist/ui/DropdownMenu.svelte +44 -0
- package/dist/ui/DropdownMenu.svelte.d.ts +30 -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 +24 -0
- package/dist/ui/Menu.svelte.d.ts +13 -0
- package/dist/ui/MenuCheckboxItem.svelte +33 -0
- package/dist/ui/MenuCheckboxItem.svelte.d.ts +20 -0
- package/dist/ui/MenuItem.svelte +73 -0
- package/dist/ui/MenuItem.svelte.d.ts +21 -0
- package/dist/ui/MenuRadioGroup.svelte +25 -0
- package/dist/ui/MenuRadioGroup.svelte.d.ts +20 -0
- package/dist/ui/MenuRadioItem.svelte +20 -0
- package/dist/ui/MenuRadioItem.svelte.d.ts +15 -0
- package/dist/ui/MenuSeparator.svelte +27 -0
- package/dist/ui/MenuSeparator.svelte.d.ts +8 -0
- package/dist/ui/MenuSub.svelte +24 -0
- package/dist/ui/MenuSub.svelte.d.ts +15 -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/Spinner.svelte +41 -0
- package/dist/ui/Spinner.svelte.d.ts +35 -0
- package/dist/ui/Stack.svelte +36 -0
- package/dist/ui/Stack.svelte.d.ts +48 -0
- package/dist/ui/ThemeToggle.svelte +65 -0
- package/dist/ui/ThemeToggle.svelte.d.ts +24 -0
- package/dist/ui/ToggleButton.svelte +58 -0
- package/dist/ui/ToggleButton.svelte.d.ts +45 -0
- package/dist/ui/ToggleGroup.svelte +142 -0
- package/dist/ui/ToggleGroup.svelte.d.ts +86 -0
- package/dist/ui/Toolbar.svelte +40 -0
- package/dist/ui/Toolbar.svelte.d.ts +20 -0
- package/dist/ui/ToolbarButton.svelte +29 -0
- package/dist/ui/ToolbarButton.svelte.d.ts +21 -0
- package/dist/ui/ToolbarLink.svelte +21 -0
- package/dist/ui/ToolbarLink.svelte.d.ts +12 -0
- package/dist/ui/ToolbarSeparator.svelte +22 -0
- package/dist/ui/ToolbarSeparator.svelte.d.ts +12 -0
- package/dist/ui/ToolbarToggleGroup.svelte +66 -0
- package/dist/ui/ToolbarToggleGroup.svelte.d.ts +52 -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/accordion-context.d.ts +26 -0
- package/dist/ui/accordion-context.js +23 -0
- package/dist/ui/colour-token.d.ts +41 -0
- package/dist/ui/colour-token.js +49 -0
- package/dist/ui/context-menu.d.ts +13 -0
- package/dist/ui/context-menu.js +13 -0
- package/dist/ui/index.d.ts +63 -0
- package/dist/ui/index.js +62 -0
- package/dist/ui/menu-context.d.ts +20 -0
- package/dist/ui/menu-context.js +22 -0
- package/dist/ui/menu-parts/CheckboxItemPart.svelte +26 -0
- package/dist/ui/menu-parts/CheckboxItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/ItemPart.svelte +29 -0
- package/dist/ui/menu-parts/ItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/RadioGroupPart.svelte +24 -0
- package/dist/ui/menu-parts/RadioGroupPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/RadioItemPart.svelte +22 -0
- package/dist/ui/menu-parts/RadioItemPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/SeparatorPart.svelte +16 -0
- package/dist/ui/menu-parts/SeparatorPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/SubPart.svelte +28 -0
- package/dist/ui/menu-parts/SubPart.svelte.d.ts +5 -0
- package/dist/ui/menu-parts/menu-parts.d.ts +104 -0
- package/dist/ui/menu-parts/menu-parts.js +31 -0
- package/dist/ui/menu-parts/parts.d.ts +12 -0
- package/dist/ui/menu-parts/parts.js +27 -0
- package/dist/ui/slug.d.ts +19 -0
- package/dist/ui/slug.js +25 -0
- package/dist/ui/toggle-classes.d.ts +39 -0
- package/dist/ui/toggle-classes.js +36 -0
- package/dist/ui/tones.d.ts +103 -0
- package/dist/ui/tones.js +240 -0
- package/package.json +41 -25
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The label, hint and errors frame around one control, handing it the `FieldControl` wiring.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FormField — the label + hint + errors frame around one control.
|
|
6
|
+
|
|
7
|
+
The control is a snippet that receives a `FieldControl` (`id`, `labelId`,
|
|
8
|
+
`describedBy`, `invalid`, `required`) to spread onto the primitive, so the
|
|
9
|
+
label's `for`, the control's `aria-describedby` and `aria-invalid`, and the
|
|
10
|
+
error region's `role="alert"` all stay wired together from a single `id`.
|
|
11
|
+
Neither side has to know the other's markup, which is what lets a caller
|
|
12
|
+
drop any of the field primitives — or their own `<input>` — into the slot.
|
|
13
|
+
|
|
14
|
+
ARIA contract: `<label for>` names the control (so clicking the text focuses
|
|
15
|
+
it). `<label for>` only reaches a labelable element (`input`, `select`,
|
|
16
|
+
`textarea`, `button`, `meter`, `output`, `progress`), so the label also
|
|
17
|
+
carries an id — `labelId` — for a role-based control (a slider thumb, a
|
|
18
|
+
`group` / `radiogroup` root) to point `aria-labelledby` at, and a click on
|
|
19
|
+
the label is forwarded to such a control: it focuses the control itself if
|
|
20
|
+
it is focusable, else its first focusable descendant (a roving group's
|
|
21
|
+
current tab stop). `describedBy` lists the hint's id then the error region's id, in that
|
|
22
|
+
reading order, and is empty when there is neither; `invalid` is true exactly
|
|
23
|
+
when there is at least one error. The required marker is a `*` that is
|
|
24
|
+
`aria-hidden` beside a visually-hidden `requiredLabel`, both inside the
|
|
25
|
+
`<label>`, so the accessible name reads "Scientific name (required)" rather
|
|
26
|
+
than "Scientific name star".
|
|
27
|
+
|
|
28
|
+
`errors` is a LIST. A field that fails several rules says so; collapsing
|
|
29
|
+
them to the first message is a decision the call site should make, not one
|
|
30
|
+
this frame makes for it.
|
|
31
|
+
-->
|
|
32
|
+
<script lang="ts">import FieldErrors from "./FieldErrors.svelte";
|
|
33
|
+
let { id: idProp, label, hint, errors = [], required = false, requiredLabel = "(required)", children, class: className, ...rest } = $props();
|
|
34
|
+
const uid = $props.id();
|
|
35
|
+
const id = $derived(idProp ?? uid);
|
|
36
|
+
const labelId = $derived(`${id}-label`);
|
|
37
|
+
const hintId = $derived(`${id}-hint`);
|
|
38
|
+
const errorId = $derived(`${id}-error`);
|
|
39
|
+
const invalid = $derived(errors.length > 0);
|
|
40
|
+
const control = $derived({
|
|
41
|
+
id,
|
|
42
|
+
labelId,
|
|
43
|
+
describedBy: [hint === undefined ? "" : hintId, invalid ? errorId : ""].filter((part) => part !== "").join(" "),
|
|
44
|
+
invalid,
|
|
45
|
+
required
|
|
46
|
+
});
|
|
47
|
+
// The elements a `<label for>` can name; the platform handles those.
|
|
48
|
+
const LABELABLE = "button, input, meter, output, progress, select, textarea";
|
|
49
|
+
// `<label for>` does nothing for a role-based control, so forward the click:
|
|
50
|
+
// focus the control if it is focusable, else its first focusable descendant.
|
|
51
|
+
const forwardClick = () => {
|
|
52
|
+
const control = document.getElementById(id);
|
|
53
|
+
if (control === null || control.matches(LABELABLE)) return;
|
|
54
|
+
const focusable = (element) => element.tabIndex >= 0 && !element.hasAttribute("disabled");
|
|
55
|
+
const target = focusable(control) ? control : Array.from(control.querySelectorAll("*")).find(focusable);
|
|
56
|
+
target?.focus();
|
|
57
|
+
};
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div {...rest} class={['fieldset', className]}>
|
|
61
|
+
<!-- The click handler restores what `<label for>` does natively — a pointer
|
|
62
|
+
shortcut to the control — for a control `for` cannot reach. Keyboard
|
|
63
|
+
users reach the control itself with Tab, as with any label, so there is
|
|
64
|
+
no key handler to add and the label stays non-interactive. -->
|
|
65
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
66
|
+
<label class="label" for={id} id={labelId} onclick={forwardClick}>
|
|
67
|
+
<span>{label}</span>
|
|
68
|
+
{#if required}
|
|
69
|
+
<span class="text-error" aria-hidden="true">*</span>
|
|
70
|
+
<span class="sr-only">{requiredLabel}</span>
|
|
71
|
+
{/if}
|
|
72
|
+
</label>
|
|
73
|
+
{@render children(control)}
|
|
74
|
+
{#if hint !== undefined}
|
|
75
|
+
<p class="fieldset-label" id={hintId}>{hint}</p>
|
|
76
|
+
{/if}
|
|
77
|
+
<FieldErrors {errors} id={errorId} />
|
|
78
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { FieldControl } from './field-types.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
/** The control's `id`. Minted when omitted. */
|
|
6
|
+
id?: string | undefined;
|
|
7
|
+
/** Visible label; becomes the control's accessible name. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Guidance rendered under the control. */
|
|
10
|
+
hint?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Validation messages; any at all marks the control invalid.
|
|
13
|
+
* @defaultValue `[]`
|
|
14
|
+
*/
|
|
15
|
+
errors?: readonly string[] | undefined;
|
|
16
|
+
/** Marks the control required and reads `requiredLabel` after the label. */
|
|
17
|
+
required?: boolean | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Read out after the label when `required`.
|
|
20
|
+
* @defaultValue `'(required)'`
|
|
21
|
+
*/
|
|
22
|
+
requiredLabel?: string | undefined;
|
|
23
|
+
/** The control; receives the `FieldControl` props to spread. */
|
|
24
|
+
children: Snippet<[FieldControl]>;
|
|
25
|
+
}
|
|
26
|
+
/** The label, hint and errors frame around one control, handing it the `FieldControl` wiring. */
|
|
27
|
+
declare const FormField: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type FormField = ReturnType<typeof FormField>;
|
|
29
|
+
export default FormField;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A `FileInput` that also measures the chosen image and previews it.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
ImageInput — a `FileInput` that also measures the picture and previews it.
|
|
6
|
+
|
|
7
|
+
It exists for one thing a server cannot do cheaply: read a file's INTRINSIC
|
|
8
|
+
DIMENSIONS. `createImageBitmap` (via `media/image-dimensions.ts`) decodes it in the browser the moment it is
|
|
9
|
+
chosen, so the form has `width` / `height` to submit alongside the binary and
|
|
10
|
+
the page that later renders the image can reserve its box on first paint
|
|
11
|
+
instead of reflowing when it loads.
|
|
12
|
+
|
|
13
|
+
The preview is an object URL, so the file never leaves the browser until the
|
|
14
|
+
form is submitted. The URL is revoked as soon as it is replaced and again on
|
|
15
|
+
teardown — an un-revoked object URL pins the whole file in memory for the
|
|
16
|
+
life of the document.
|
|
17
|
+
|
|
18
|
+
A decode failure means the browser could not read the file as an image; the
|
|
19
|
+
server's own media-type gate will refuse it too, so the dimensions are left
|
|
20
|
+
unset rather than blocking the submission.
|
|
21
|
+
|
|
22
|
+
ARIA contract: `FileInput`'s. The preview `<img>` is named by `previewAlt`,
|
|
23
|
+
and the measured size is a `<figcaption>` rather than an `aria-label`,
|
|
24
|
+
because it is information about the file that a sighted reader gets too.
|
|
25
|
+
-->
|
|
26
|
+
<script lang="ts">import { imageDimensions } from "../media/image-dimensions.js";
|
|
27
|
+
import FileInput from "./FileInput.svelte";
|
|
28
|
+
let { file = $bindable(), width = $bindable(), height = $bindable(), previewAlt = "Preview of the selected image", accept = "image/jpeg,image/png,image/gif,image/webp,image/avif", name, class: className, ...rest } = $props();
|
|
29
|
+
let previewUrl = $state();
|
|
30
|
+
let measuring = $state(false);
|
|
31
|
+
// Revoke the PREVIOUS url whenever this one changes, and the last one when
|
|
32
|
+
// the component goes away. Reading `previewUrl` inside the effect is what
|
|
33
|
+
// makes the cleanup run on every change rather than only on teardown.
|
|
34
|
+
$effect(() => {
|
|
35
|
+
const url = previewUrl;
|
|
36
|
+
return () => {
|
|
37
|
+
if (url !== undefined) URL.revokeObjectURL(url);
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const measure = async (chosen) => {
|
|
41
|
+
measuring = true;
|
|
42
|
+
try {
|
|
43
|
+
({width, height} = await imageDimensions(chosen));
|
|
44
|
+
} catch {
|
|
45
|
+
width = undefined;
|
|
46
|
+
height = undefined;
|
|
47
|
+
} finally {
|
|
48
|
+
measuring = false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const select = (chosen) => {
|
|
52
|
+
if (chosen === undefined) {
|
|
53
|
+
previewUrl = undefined;
|
|
54
|
+
width = undefined;
|
|
55
|
+
height = undefined;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
previewUrl = URL.createObjectURL(chosen);
|
|
59
|
+
void measure(chosen);
|
|
60
|
+
};
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<div class={['flex flex-col gap-2', className]}>
|
|
64
|
+
<FileInput {...rest} {name} {accept} bind:file onSelect={select} />
|
|
65
|
+
|
|
66
|
+
{#if previewUrl !== undefined}
|
|
67
|
+
<figure>
|
|
68
|
+
<!-- The caller's own file, not a served image, so a plain `<img>`. -->
|
|
69
|
+
<img src={previewUrl} alt={previewAlt} class="rounded-box border-base-300 max-h-64 border" />
|
|
70
|
+
<figcaption class="fieldset-label mt-1">
|
|
71
|
+
{#if measuring}
|
|
72
|
+
|
|
73
|
+
{:else if width !== undefined && height !== undefined}
|
|
74
|
+
{width} × {height}
|
|
75
|
+
{/if}
|
|
76
|
+
</figcaption>
|
|
77
|
+
</figure>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
<!-- The measured dimensions ride the same multipart body as the binary. -->
|
|
81
|
+
{#if name !== undefined && width !== undefined}
|
|
82
|
+
<input type="hidden" name="width" value={width} />
|
|
83
|
+
{/if}
|
|
84
|
+
{#if name !== undefined && height !== undefined}
|
|
85
|
+
<input type="hidden" name="height" value={height} />
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
/** Measured intrinsic width, in pixels. */
|
|
7
|
+
width?: number | undefined;
|
|
8
|
+
/** Measured intrinsic height, in pixels. */
|
|
9
|
+
height?: number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Alternative text for the local preview.
|
|
12
|
+
* @defaultValue `'Preview of the selected image'`
|
|
13
|
+
*/
|
|
14
|
+
previewAlt?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The `accept` filter passed to the file input.
|
|
17
|
+
* @defaultValue `'image/jpeg,image/png,image/gif,image/webp,image/avif'`
|
|
18
|
+
*/
|
|
19
|
+
accept?: string | undefined;
|
|
20
|
+
/** Largest accepted file, in bytes; a larger file is rejected with `tooLargeMessage`. */
|
|
21
|
+
maxBytes?: number | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Shown when the chosen file exceeds `maxBytes`.
|
|
24
|
+
* @defaultValue `'That file is too large.'` (from `FileInput`)
|
|
25
|
+
*/
|
|
26
|
+
tooLargeMessage?: string | undefined;
|
|
27
|
+
/** Called with the accepted file, or `undefined` when the selection is cleared. */
|
|
28
|
+
onSelect?: ((file: File | undefined) => void) | undefined;
|
|
29
|
+
/** daisyUI size step. */
|
|
30
|
+
size?: FieldSize | undefined;
|
|
31
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
32
|
+
invalid?: boolean | undefined;
|
|
33
|
+
/** `FieldControl`: space-separated ids for `aria-describedby`. */
|
|
34
|
+
describedBy?: string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* `FieldControl`: the visible label's id. Not used — the file input is
|
|
37
|
+
* labelable, so `<label for>` names it; passed through to `FileInput`,
|
|
38
|
+
* which drops it.
|
|
39
|
+
*/
|
|
40
|
+
labelId?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** A `FileInput` that also measures the chosen image and previews it. */
|
|
43
|
+
declare const ImageInput: import("svelte").Component<Props, {}, "height" | "width" | "file">;
|
|
44
|
+
type ImageInput = ReturnType<typeof ImageInput>;
|
|
45
|
+
export default ImageInput;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Several choices from a known list in a compact trigger that summarises them, on bits-ui's `Select` (`type="multiple"`).
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
MultiSelect — any number of options from a medium-sized known list: the
|
|
6
|
+
trigger summarises the selection ("Full sun, Well-drained, +1"), a listbox
|
|
7
|
+
popover holds the options with checkmarks. Built on bits-ui's `Select`
|
|
8
|
+
with `type="multiple"`, bound to a `string[]`.
|
|
9
|
+
|
|
10
|
+
When to use which: `CheckboxGroup` puts every option on screen (right for
|
|
11
|
+
about seven or fewer); `Select` is the native single-choice `<select>`;
|
|
12
|
+
`MultiSelect` is for a longer list where several may be chosen. It takes
|
|
13
|
+
the same `options` shape as `Select`, so the two swap at the call site. It
|
|
14
|
+
is a separate component rather than a mode on `Select` because `Select`'s
|
|
15
|
+
point is to be the native control, with no script and the platform picker.
|
|
16
|
+
|
|
17
|
+
ARIA contract (APG select-only combobox / listbox): bits-ui gives the
|
|
18
|
+
trigger `role="combobox"`, `aria-haspopup="listbox"`, `aria-expanded` and
|
|
19
|
+
`aria-controls`; the content `role="listbox"` with `aria-multiselectable`;
|
|
20
|
+
each option `role="option"` with `aria-selected` (and `aria-disabled`,
|
|
21
|
+
which bits-ui 2.20 leaves out, added here); typeahead (fed from
|
|
22
|
+
`options` through bits-ui's `items`), Home / End, and Escape / Tab closing
|
|
23
|
+
with focus back on the trigger. Selecting an option keeps the listbox open.
|
|
24
|
+
This file adds the trigger's name (the `FieldControl` id goes on the
|
|
25
|
+
trigger, so `FormField`'s `<label for>` names it), `aria-describedby`,
|
|
26
|
+
`aria-invalid`, `aria-required`, the summary text and the `aria-hidden`
|
|
27
|
+
check icon.
|
|
28
|
+
|
|
29
|
+
The summary is plain text, not removable badges: focusable children inside
|
|
30
|
+
a button-like trigger are an accessibility trap.
|
|
31
|
+
|
|
32
|
+
`values` is always kept in `options` order, whatever order the reader
|
|
33
|
+
clicked in, so the summary is stable. `name` posts one hidden input per
|
|
34
|
+
value, in option order (and nothing at all while none is selected).
|
|
35
|
+
-->
|
|
36
|
+
<script lang="ts">import { Check } from "@lucide/svelte";
|
|
37
|
+
import { Select } from "bits-ui";
|
|
38
|
+
import { selectSize, toFieldOptions } from "./field-types.js";
|
|
39
|
+
let { options, values = $bindable([]), placeholder = "Select…", name, maxShown = 2, formatOverflow = (hidden) => `+${hidden}`, option, portal = true, disabled = false, size = "md", id, labelId: _labelId, describedBy = "", invalid = false, required = false, class: className, ...rest } = $props();
|
|
40
|
+
const uid = $props.id();
|
|
41
|
+
const triggerId = $derived(id ?? uid);
|
|
42
|
+
const entries = $derived(toFieldOptions(options));
|
|
43
|
+
const items = $derived(entries.map((entry) => ({
|
|
44
|
+
...entry,
|
|
45
|
+
disabled: entry.disabled === true
|
|
46
|
+
})));
|
|
47
|
+
const selectedEntries = $derived(entries.filter((entry) => values.includes(entry.value)));
|
|
48
|
+
const selectedLabels = $derived(selectedEntries.map((entry) => entry.label));
|
|
49
|
+
const summary = $derived(selectedLabels.length <= maxShown ? selectedLabels.join(", ") : [...selectedLabels.slice(0, maxShown), formatOverflow(selectedLabels.length - maxShown)].join(", "));
|
|
50
|
+
const triggerText = $derived(selectedLabels.length === 0 ? placeholder : summary);
|
|
51
|
+
const ariaDescribedBy = $derived(describedBy === "" ? undefined : describedBy);
|
|
52
|
+
/** Keep `values` in option order, whatever order they were clicked in. */
|
|
53
|
+
function setValues(next) {
|
|
54
|
+
values = entries.map((entry) => entry.value).filter((value) => next.includes(value));
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#snippet optionBody(entry: FieldOption, selected: boolean)}
|
|
59
|
+
<Check class={['size-4 shrink-0', !selected && 'invisible']} aria-hidden="true" />
|
|
60
|
+
{#if option}
|
|
61
|
+
{@render option(entry, { selected })}
|
|
62
|
+
{:else}
|
|
63
|
+
<span>{entry.label}</span>
|
|
64
|
+
{/if}
|
|
65
|
+
{/snippet}
|
|
66
|
+
|
|
67
|
+
<div {...rest} class={['w-full', className]}>
|
|
68
|
+
<Select.Root
|
|
69
|
+
type="multiple"
|
|
70
|
+
bind:value={() => values, setValues}
|
|
71
|
+
{items}
|
|
72
|
+
{disabled}
|
|
73
|
+
{required}
|
|
74
|
+
>
|
|
75
|
+
<Select.Trigger
|
|
76
|
+
id={triggerId}
|
|
77
|
+
aria-describedby={ariaDescribedBy}
|
|
78
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
79
|
+
aria-required={required ? 'true' : undefined}
|
|
80
|
+
class={['select w-full text-start', selectSize[size], invalid && 'select-error']}
|
|
81
|
+
>
|
|
82
|
+
<span class={['truncate', selectedLabels.length === 0 && 'text-base-content/60']}>
|
|
83
|
+
{triggerText}
|
|
84
|
+
</span>
|
|
85
|
+
</Select.Trigger>
|
|
86
|
+
<Select.Portal disabled={!portal}>
|
|
87
|
+
<Select.Content
|
|
88
|
+
sideOffset={4}
|
|
89
|
+
class="menu bg-base-100 rounded-box border-base-300 z-50 max-h-72 w-(--bits-select-anchor-width) min-w-48 overflow-auto border p-2 shadow-lg"
|
|
90
|
+
>
|
|
91
|
+
<Select.Viewport>
|
|
92
|
+
{#each entries as entry (entry.value)}
|
|
93
|
+
<Select.Item
|
|
94
|
+
value={entry.value}
|
|
95
|
+
label={entry.label}
|
|
96
|
+
disabled={entry.disabled === true}
|
|
97
|
+
aria-disabled={entry.disabled === true ? 'true' : undefined}
|
|
98
|
+
class="rounded-field data-highlighted:bg-base-200 data-disabled:opacity-50 flex cursor-pointer items-center gap-2 px-3 py-2"
|
|
99
|
+
>
|
|
100
|
+
{#snippet children({ selected })}{@render optionBody(entry, selected)}{/snippet}
|
|
101
|
+
</Select.Item>
|
|
102
|
+
{/each}
|
|
103
|
+
</Select.Viewport>
|
|
104
|
+
</Select.Content>
|
|
105
|
+
</Select.Portal>
|
|
106
|
+
</Select.Root>
|
|
107
|
+
{#if name !== undefined}
|
|
108
|
+
{#each selectedEntries as entry (entry.value)}
|
|
109
|
+
<input type="hidden" {name} value={entry.value} {disabled} />
|
|
110
|
+
{/each}
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type FieldOption, type FieldOptionInput, type FieldSize } from './field-types.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'id'> {
|
|
5
|
+
/** The options; bare strings are both value and label. */
|
|
6
|
+
options: readonly FieldOptionInput[];
|
|
7
|
+
/**
|
|
8
|
+
* The selected values, in option order. Bindable.
|
|
9
|
+
* @defaultValue `[]`
|
|
10
|
+
*/
|
|
11
|
+
values?: string[] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Trigger text while nothing is selected.
|
|
14
|
+
* @defaultValue `'Select…'`
|
|
15
|
+
*/
|
|
16
|
+
placeholder?: string | undefined;
|
|
17
|
+
/** Posts each selected value under this name, one hidden input per value. */
|
|
18
|
+
name?: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Show at most this many labels in the trigger, then the overflow count.
|
|
21
|
+
* @defaultValue `2`
|
|
22
|
+
*/
|
|
23
|
+
maxShown?: number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Formats the overflow count, e.g. `(n) => \`+${n} more\``.
|
|
26
|
+
* @defaultValue `(n) => \`+${n}\``
|
|
27
|
+
*/
|
|
28
|
+
formatOverflow?: ((hidden: number) => string) | undefined;
|
|
29
|
+
/** Renders one option's body (an icon, a description); defaults to its label. */
|
|
30
|
+
option?: Snippet<[FieldOption, {
|
|
31
|
+
selected: boolean;
|
|
32
|
+
}]> | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Render the listbox into `<body>`.
|
|
35
|
+
* @defaultValue `true`
|
|
36
|
+
*/
|
|
37
|
+
portal?: boolean | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Disables the trigger.
|
|
40
|
+
* @defaultValue `false`
|
|
41
|
+
*/
|
|
42
|
+
disabled?: boolean | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* daisyUI size step of the trigger.
|
|
45
|
+
* @defaultValue `'md'`
|
|
46
|
+
*/
|
|
47
|
+
size?: FieldSize | undefined;
|
|
48
|
+
/** `FieldControl`: the trigger's `id`, which `FormField`'s label points at. Minted when omitted. */
|
|
49
|
+
id?: string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* `FieldControl`: the visible label's id. Not used — the trigger is a
|
|
52
|
+
* `<button>`, so the `FormField`'s `<label for>` already names it; taken
|
|
53
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
54
|
+
*/
|
|
55
|
+
labelId?: string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
58
|
+
* @defaultValue `''`
|
|
59
|
+
*/
|
|
60
|
+
describedBy?: string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* `FieldControl`: the field is in error — sets `aria-invalid` and `select-error`.
|
|
63
|
+
* @defaultValue `false`
|
|
64
|
+
*/
|
|
65
|
+
invalid?: boolean | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* `FieldControl`: sets `aria-required` on the trigger.
|
|
68
|
+
* @defaultValue `false`
|
|
69
|
+
*/
|
|
70
|
+
required?: boolean | undefined;
|
|
71
|
+
}
|
|
72
|
+
/** Several choices from a known list in a compact trigger that summarises them, on bits-ui's `Select` (`type="multiple"`). */
|
|
73
|
+
declare const MultiSelect: import("svelte").Component<Props, {}, "values">;
|
|
74
|
+
type MultiSelect = ReturnType<typeof MultiSelect>;
|
|
75
|
+
export default MultiSelect;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
An exact number the reader types or nudges, shown locale-formatted (units, percent, currency) with − / + stepper buttons: the APG spinbutton.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
NumberField — a `role="spinbutton"` text input showing its value through
|
|
6
|
+
`Intl.NumberFormat`, plus − / + buttons, stepping clamped to `min` / `max`.
|
|
7
|
+
|
|
8
|
+
Hand-rolled, and why that is acceptable here (AGENTS.md asks for the
|
|
9
|
+
reason): bits-ui 2.20 has no number field, and the APG spinbutton pattern is
|
|
10
|
+
one input plus two buttons — no popover, no roving focus, no ARIA
|
|
11
|
+
relationship beyond `aria-controls` — so every part of the contract is
|
|
12
|
+
small and pinned by `tests/fields/NumberField.test.ts`. It replaces the
|
|
13
|
+
native `<input type="number">` for callers who need what that cannot do:
|
|
14
|
+
locale formatting and parsing ("1.234,5" in `de-DE`), a `number` value
|
|
15
|
+
rather than a string, no accidental scroll-wheel changes (there is no wheel
|
|
16
|
+
handling at all), and stepper buttons that are labelled and styleable.
|
|
17
|
+
|
|
18
|
+
Editing: while focused the input holds the text as typed. On blur or Enter
|
|
19
|
+
it is parsed with the locale's separators (`number-format.ts`), clamped to
|
|
20
|
+
`min` / `max`, rounded to the step's or the format's precision, written to
|
|
21
|
+
`value`, re-formatted, and passed to `onCommit`. Unparseable text reverts
|
|
22
|
+
to the last valid value; clearing the field commits `undefined`.
|
|
23
|
+
|
|
24
|
+
ARIA contract (APG spinbutton): the input is `role="spinbutton"` with
|
|
25
|
+
`aria-valuenow`, `aria-valuemin` / `aria-valuemax` when set, and
|
|
26
|
+
`aria-valuetext` — the formatted string, so "45%" is read rather than
|
|
27
|
+
"0.45" — plus `inputmode` (`numeric` when the format has no fraction
|
|
28
|
+
digits, else `decimal`) and the `FieldControl` wiring. The − / + are real
|
|
29
|
+
buttons named by `decrementLabel` / `incrementLabel`, `aria-controls` the
|
|
30
|
+
input, `tabindex="-1"` so the field stays one tab stop, and disabled at the
|
|
31
|
+
bounds. Keys: Arrow Up / Down ± `step`, Page Up / Down ± `largeStep`,
|
|
32
|
+
Home / End to `min` / `max` (when set), Enter commits.
|
|
33
|
+
|
|
34
|
+
Press-and-hold on − / + auto-repeats: one step on press, then after
|
|
35
|
+
`REPEAT_DELAY_MS` it repeats, speeding up to `REPEAT_MIN_MS`, as GTK's spin
|
|
36
|
+
button, WinUI's NumberBox and SwiftUI's Stepper do. The pointer is captured,
|
|
37
|
+
so moving off the button keeps the capture and `pointerup` /
|
|
38
|
+
`pointercancel` / `lostpointercapture` (or reaching a bound) stops it. A
|
|
39
|
+
pointer press never moves focus off the input. A click with no pointer
|
|
40
|
+
press before it (assistive technology activating the button) steps once.
|
|
41
|
+
|
|
42
|
+
`name` posts the raw number through a hidden input, never the formatted
|
|
43
|
+
text, so a server never parses "1.234,5 cm".
|
|
44
|
+
-->
|
|
45
|
+
<script lang="ts">import { Minus, Plus } from "@lucide/svelte";
|
|
46
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
47
|
+
import { inputButtonSize, inputSize } from "./field-types.js";
|
|
48
|
+
import { clamp, formatNumber, parseNumber, roundTo, valuePrecision } from "./number-format.js";
|
|
49
|
+
/** How long a press is held before it starts repeating. */
|
|
50
|
+
const REPEAT_DELAY_MS = 400;
|
|
51
|
+
/** The first repeat interval; each repeat shortens it towards `REPEAT_MIN_MS`. */
|
|
52
|
+
const REPEAT_START_MS = 150;
|
|
53
|
+
const REPEAT_MIN_MS = 40;
|
|
54
|
+
let { value = $bindable(), min, max, step = 1, largeStep, formatOptions, locales = getComponentsContext().locale(), showStepper = true, decrementLabel = "Decrease", incrementLabel = "Increase", onCommit, name, size = "md", disabled = false, id: idProp, describedBy = "", labelId: _labelId, invalid = false, class: className, onkeydown, onblur, ...rest } = $props();
|
|
55
|
+
const uid = $props.id();
|
|
56
|
+
const id = $derived(idProp ?? uid);
|
|
57
|
+
const formatted = $derived(formatNumber(value, locales, formatOptions));
|
|
58
|
+
/** The input's text: the formatted value, overwritten by typing until the next commit. */
|
|
59
|
+
let text = $derived(formatted);
|
|
60
|
+
const precision = $derived(valuePrecision(step, locales, formatOptions));
|
|
61
|
+
const atMin = $derived(value !== undefined && min !== undefined && value <= min);
|
|
62
|
+
const atMax = $derived(value !== undefined && max !== undefined && value >= max);
|
|
63
|
+
let input = $state(null);
|
|
64
|
+
let repeat;
|
|
65
|
+
/** Write a new value: clamp, round, re-format, and tell `onCommit`. */
|
|
66
|
+
function commit(next) {
|
|
67
|
+
value = next === undefined ? undefined : roundTo(clamp(next, min, max), precision);
|
|
68
|
+
text = formatNumber(value, locales, formatOptions);
|
|
69
|
+
onCommit?.(value);
|
|
70
|
+
}
|
|
71
|
+
function commitText() {
|
|
72
|
+
if (text === formatted) return;
|
|
73
|
+
const parsed = parseNumber(text, locales, formatOptions);
|
|
74
|
+
if (parsed !== undefined && Number.isNaN(parsed)) {
|
|
75
|
+
text = formatted;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
commit(parsed);
|
|
79
|
+
}
|
|
80
|
+
/** Move by `delta`; from empty, land on `min` (or 0) rather than stepping. */
|
|
81
|
+
function stepBy(delta) {
|
|
82
|
+
commit(value === undefined ? min ?? 0 : value + delta);
|
|
83
|
+
}
|
|
84
|
+
const keySteps = {
|
|
85
|
+
ArrowUp: () => step,
|
|
86
|
+
ArrowDown: () => -step,
|
|
87
|
+
PageUp: () => largeStep ?? step * 10,
|
|
88
|
+
PageDown: () => -(largeStep ?? step * 10)
|
|
89
|
+
};
|
|
90
|
+
function handleKeydown(event) {
|
|
91
|
+
const delta = keySteps[event.key]?.();
|
|
92
|
+
if (delta !== undefined) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
stepBy(delta);
|
|
95
|
+
} else if (event.key === "Home" && min !== undefined) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
commit(min);
|
|
98
|
+
} else if (event.key === "End" && max !== undefined) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
commit(max);
|
|
101
|
+
} else if (event.key === "Enter") {
|
|
102
|
+
commitText();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function stopRepeat() {
|
|
106
|
+
clearTimeout(repeat);
|
|
107
|
+
repeat = undefined;
|
|
108
|
+
}
|
|
109
|
+
/** Step now, then keep stepping while held, faster each time, until a bound. */
|
|
110
|
+
function startRepeat(event, delta) {
|
|
111
|
+
if (event.button !== 0 || disabled) return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
input?.focus();
|
|
114
|
+
try {
|
|
115
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
116
|
+
} catch {}
|
|
117
|
+
stepBy(delta);
|
|
118
|
+
const tick = (interval) => {
|
|
119
|
+
repeat = setTimeout(() => {
|
|
120
|
+
if (delta > 0 && atMax || delta < 0 && atMin) return stopRepeat();
|
|
121
|
+
stepBy(delta);
|
|
122
|
+
tick(Math.max(REPEAT_MIN_MS, interval * .8));
|
|
123
|
+
}, interval);
|
|
124
|
+
};
|
|
125
|
+
stopRepeat();
|
|
126
|
+
repeat = setTimeout(() => tick(REPEAT_START_MS), REPEAT_DELAY_MS - REPEAT_START_MS);
|
|
127
|
+
}
|
|
128
|
+
/** A click that no pointer press preceded (`detail === 0`): assistive technology, step once. */
|
|
129
|
+
function clickStep(event, delta) {
|
|
130
|
+
if (event.detail === 0) stepBy(delta);
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
{#snippet stepper(delta: number, label: string, bounded: boolean)}
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
tabindex="-1"
|
|
138
|
+
aria-controls={id}
|
|
139
|
+
disabled={disabled || bounded}
|
|
140
|
+
class={['btn btn-square btn-ghost', inputButtonSize[size]]}
|
|
141
|
+
onpointerdown={(event) => startRepeat(event, delta)}
|
|
142
|
+
onpointerup={stopRepeat}
|
|
143
|
+
onpointercancel={stopRepeat}
|
|
144
|
+
onlostpointercapture={stopRepeat}
|
|
145
|
+
onclick={(event) => clickStep(event, delta)}
|
|
146
|
+
>
|
|
147
|
+
{#if delta < 0}
|
|
148
|
+
<Minus class="size-4" aria-hidden="true" />
|
|
149
|
+
{:else}
|
|
150
|
+
<Plus class="size-4" aria-hidden="true" />
|
|
151
|
+
{/if}
|
|
152
|
+
<span class="sr-only">{label}</span>
|
|
153
|
+
</button>
|
|
154
|
+
{/snippet}
|
|
155
|
+
|
|
156
|
+
<div class={['input w-full pe-1', inputSize[size], invalid && 'input-error', className]}>
|
|
157
|
+
<input
|
|
158
|
+
{...rest}
|
|
159
|
+
bind:this={input}
|
|
160
|
+
{id}
|
|
161
|
+
type="text"
|
|
162
|
+
role="spinbutton"
|
|
163
|
+
autocomplete="off"
|
|
164
|
+
inputmode={formatOptions?.maximumFractionDigits === 0 ? 'numeric' : 'decimal'}
|
|
165
|
+
{disabled}
|
|
166
|
+
aria-valuenow={value}
|
|
167
|
+
aria-valuemin={min}
|
|
168
|
+
aria-valuemax={max}
|
|
169
|
+
aria-valuetext={value === undefined ? undefined : formatted}
|
|
170
|
+
aria-describedby={describedBy === '' ? undefined : describedBy}
|
|
171
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
172
|
+
bind:value={text}
|
|
173
|
+
onkeydown={(event) => {
|
|
174
|
+
handleKeydown(event);
|
|
175
|
+
onkeydown?.(event);
|
|
176
|
+
}}
|
|
177
|
+
onblur={(event) => {
|
|
178
|
+
stopRepeat();
|
|
179
|
+
commitText();
|
|
180
|
+
onblur?.(event);
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
{#if showStepper}
|
|
184
|
+
{@render stepper(-step, decrementLabel, atMin)}
|
|
185
|
+
{@render stepper(step, incrementLabel, atMax)}
|
|
186
|
+
{/if}
|
|
187
|
+
{#if name !== undefined}
|
|
188
|
+
<input type="hidden" {name} value={value === undefined ? '' : String(value)} {disabled} />
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|