@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,35 @@
|
|
|
1
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
2
|
+
import { type FieldOptionInput, type FieldSize } from './field-types.ts';
|
|
3
|
+
interface Props extends Omit<HTMLSelectAttributes, 'value' | 'size'> {
|
|
4
|
+
/** Full options, or bare strings when value and label are the same. */
|
|
5
|
+
options: readonly FieldOptionInput[];
|
|
6
|
+
/**
|
|
7
|
+
* The selected option’s value; `''` selects the placeholder. Bindable.
|
|
8
|
+
* @defaultValue `''`
|
|
9
|
+
*/
|
|
10
|
+
value?: string | undefined;
|
|
11
|
+
/** Disabled first option, shown while nothing is selected. */
|
|
12
|
+
placeholder?: string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* daisyUI size step.
|
|
15
|
+
* @defaultValue `'md'`
|
|
16
|
+
*/
|
|
17
|
+
size?: FieldSize | undefined;
|
|
18
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
19
|
+
invalid?: boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
22
|
+
* @defaultValue `''`
|
|
23
|
+
*/
|
|
24
|
+
describedBy?: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* `FieldControl`: the visible label's id. Not used — this control is
|
|
27
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
28
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
29
|
+
*/
|
|
30
|
+
labelId?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** A native `<select>` in daisyUI styling over a list of options, with an optional placeholder and `FieldControl` wiring. */
|
|
33
|
+
declare const Select: import("svelte").Component<Props, {}, "value">;
|
|
34
|
+
type Select = ReturnType<typeof Select>;
|
|
35
|
+
export default Select;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A number, or a `[low, high]` range, picked from a bounded, stepped scale by dragging or with the arrow keys, on bits-ui's `Slider`.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Slider — one component, two modes. `type="single"` binds a `number`;
|
|
6
|
+
`type="multiple"` binds a `number[]` with one thumb per entry. Built on
|
|
7
|
+
bits-ui's `Slider` (pointer drag, arrow / Home / End keys, non-crossing
|
|
8
|
+
thumbs) and wired through the `FieldControl` contract like every other
|
|
9
|
+
control in `fields/`.
|
|
10
|
+
|
|
11
|
+
Why not `TextInput type="range"` or daisyUI's `range` on a native input: a
|
|
12
|
+
native range has one thumb, no ticks and no consistent value readout, and
|
|
13
|
+
the keyboard model across two thumbs is exactly what AGENTS.md reserves for
|
|
14
|
+
bits-ui. The daisyUI `range` CLASSES are still the colour and size
|
|
15
|
+
vocabulary: `rangeTone` sets `color` (the fill, read as `currentColor`) and
|
|
16
|
+
`--range-thumb` (the thumb face), and `rangeSize` sets `--range-thumb-size`,
|
|
17
|
+
which the thumb and the track are sized from. The `range` class itself is
|
|
18
|
+
not used — it styles a native input's pseudo-elements.
|
|
19
|
+
|
|
20
|
+
ARIA contract (APG slider and multi-thumb slider): every thumb is
|
|
21
|
+
`role="slider"` with `aria-valuemin/max/now` and `aria-orientation` from
|
|
22
|
+
bits-ui. This file adds `aria-valuetext` from `formatValue`, the accessible
|
|
23
|
+
name, `aria-describedby` / `aria-invalid` from `FieldControl`, Page Up /
|
|
24
|
+
Page Down (bits-ui 2.20 handles only the arrows, Home and End; a page is a
|
|
25
|
+
tenth of the scale, at least one step), and one hidden `<input>` per thumb
|
|
26
|
+
under `name`, so a plain form posts the value.
|
|
27
|
+
|
|
28
|
+
Naming: a `<label for>` cannot name a `role="slider"` span, so the `id` goes
|
|
29
|
+
on the first thumb (the second is `${id}-1`) and the name comes from
|
|
30
|
+
`aria-labelledby={labelId}` — the visible label's id, which `FormField`
|
|
31
|
+
hands its control as `FieldControl.labelId` (#179). In range mode each
|
|
32
|
+
thumb is labelled by the label AND itself (`aria-labelledby="label thumb"`
|
|
33
|
+
with `aria-label` = its `thumbLabels` entry), so the second thumb reads
|
|
34
|
+
"Hours of sun Maximum". Outside a `FormField`, pass `label` instead; it
|
|
35
|
+
becomes the thumbs' `aria-label`.
|
|
36
|
+
|
|
37
|
+
The visible readout is `aria-hidden`: the same text is each thumb's
|
|
38
|
+
`aria-valuetext`, and announcing it twice would be noise.
|
|
39
|
+
|
|
40
|
+
Known limitation: bits-ui 2.20 gives every thumb the slider's global
|
|
41
|
+
`aria-valuemin` / `aria-valuemax` rather than its neighbour's value, so in
|
|
42
|
+
range mode a thumb over-reports how far it can move (it still cannot cross:
|
|
43
|
+
`autoSort` is left on). Not hand-patched, so a bits-ui fix lands for free.
|
|
44
|
+
|
|
45
|
+
`required` is accepted so `{...control}` spreads cleanly, but a slider
|
|
46
|
+
always has a value, so it changes nothing.
|
|
47
|
+
-->
|
|
48
|
+
<script lang="ts">import { Slider } from "bits-ui";
|
|
49
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
50
|
+
import { rangeTone } from "../ui/tones.js";
|
|
51
|
+
import { rangeSize } from "./field-types.js";
|
|
52
|
+
import { clampToNeighbours, pageStep } from "./slider-keys.js";
|
|
53
|
+
let { type = "single", value = $bindable(), min = 0, max = 100, step = 1, orientation = "horizontal", ticks = false, formatValue, locales = getComponentsContext().locale(), showValue = true, thumbLabels = ["Minimum", "Maximum"], onCommit, name, disabled = false, tone = "primary", size = "md", label, id: idProp, labelId, describedBy = "", invalid = false, required: _required = false, class: className, ...rest } = $props();
|
|
54
|
+
const uid = $props.id();
|
|
55
|
+
const id = $derived(idProp ?? uid);
|
|
56
|
+
const multiple = $derived(type === "multiple");
|
|
57
|
+
const vertical = $derived(orientation === "vertical");
|
|
58
|
+
/** The bound value, normalised to one entry per thumb. */
|
|
59
|
+
const values = $derived.by(() => {
|
|
60
|
+
if (multiple) return Array.isArray(value) ? value : [min, max];
|
|
61
|
+
return [typeof value === "number" ? value : min];
|
|
62
|
+
});
|
|
63
|
+
const numberFormat = $derived(new Intl.NumberFormat(locales));
|
|
64
|
+
const format = $derived(formatValue ?? ((n) => numberFormat.format(n)));
|
|
65
|
+
const readout = $derived(values.map((n) => format(n)).join(" – "));
|
|
66
|
+
// bits-ui wants a mutable array; ours is readonly at the API.
|
|
67
|
+
const steps = $derived(typeof step === "number" ? step : [...step]);
|
|
68
|
+
/** What both `Slider.Root` branches share; only `type` and the bound value differ. */
|
|
69
|
+
const rootProps = $derived({
|
|
70
|
+
onValueCommit: commit,
|
|
71
|
+
min,
|
|
72
|
+
max,
|
|
73
|
+
step: steps,
|
|
74
|
+
orientation,
|
|
75
|
+
disabled,
|
|
76
|
+
class: ["relative flex touch-none items-center select-none data-[disabled]:opacity-30", vertical ? "h-full w-(--range-thumb-size) flex-col" : "h-(--range-thumb-size) w-full"]
|
|
77
|
+
});
|
|
78
|
+
function thumbId(index) {
|
|
79
|
+
return index === 0 ? id : `${id}-${index}`;
|
|
80
|
+
}
|
|
81
|
+
function thumbLabel(index) {
|
|
82
|
+
return thumbLabels[index] ?? `${index + 1}`;
|
|
83
|
+
}
|
|
84
|
+
function ariaLabel(index) {
|
|
85
|
+
if (labelId !== undefined) return multiple ? thumbLabel(index) : undefined;
|
|
86
|
+
if (!multiple) return label;
|
|
87
|
+
return label === undefined ? thumbLabel(index) : `${label} ${thumbLabel(index)}`;
|
|
88
|
+
}
|
|
89
|
+
function ariaLabelledBy(index) {
|
|
90
|
+
if (labelId === undefined) return undefined;
|
|
91
|
+
return multiple ? `${labelId} ${thumbId(index)}` : labelId;
|
|
92
|
+
}
|
|
93
|
+
function commit(next) {
|
|
94
|
+
onCommit?.(next);
|
|
95
|
+
}
|
|
96
|
+
/** Page Up / Page Down: a tenth of the scale (at least one step), clamped between the neighbouring thumbs. */
|
|
97
|
+
function onThumbKeydown(event, index) {
|
|
98
|
+
if (disabled || event.key !== "PageUp" && event.key !== "PageDown") return;
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
const scale = {
|
|
101
|
+
min,
|
|
102
|
+
max,
|
|
103
|
+
step
|
|
104
|
+
};
|
|
105
|
+
const current = values[index] ?? min;
|
|
106
|
+
const next = clampToNeighbours(values, index, pageStep(current, event.key === "PageUp" ? 1 : -1, scale), scale);
|
|
107
|
+
if (next === current) return;
|
|
108
|
+
const updated = values.map((n, i) => i === index ? next : n);
|
|
109
|
+
value = multiple ? updated : next;
|
|
110
|
+
commit(multiple ? updated : next);
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
{#snippet parts({
|
|
115
|
+
thumbItems,
|
|
116
|
+
tickItems,
|
|
117
|
+
}: {
|
|
118
|
+
thumbItems: { index: number }[];
|
|
119
|
+
tickItems: { index: number }[];
|
|
120
|
+
})}
|
|
121
|
+
<span
|
|
122
|
+
class={[
|
|
123
|
+
'relative grow overflow-hidden rounded-full bg-base-300',
|
|
124
|
+
vertical ? 'h-full w-[calc(var(--range-thumb-size)*0.5)]' : 'h-[calc(var(--range-thumb-size)*0.5)] w-full',
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
<Slider.Range class={['bg-current', vertical ? 'w-full' : 'h-full']} />
|
|
128
|
+
</span>
|
|
129
|
+
{#if ticks}
|
|
130
|
+
{#each tickItems as { index } (index)}
|
|
131
|
+
<Slider.Tick {index} class="size-1 rounded-full bg-base-content/30" />
|
|
132
|
+
{/each}
|
|
133
|
+
{/if}
|
|
134
|
+
{#each thumbItems as { index } (index)}
|
|
135
|
+
<Slider.Thumb
|
|
136
|
+
{index}
|
|
137
|
+
id={thumbId(index)}
|
|
138
|
+
aria-label={ariaLabel(index)}
|
|
139
|
+
aria-labelledby={ariaLabelledBy(index)}
|
|
140
|
+
aria-valuetext={format(values[index] ?? min)}
|
|
141
|
+
aria-describedby={describedBy === '' ? undefined : describedBy}
|
|
142
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
143
|
+
onkeydown={(event: KeyboardEvent) => onThumbKeydown(event, index)}
|
|
144
|
+
class="size-(--range-thumb-size) cursor-grab rounded-full border-4 border-current bg-(--range-thumb) shadow-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current data-[active]:cursor-grabbing data-[disabled]:cursor-not-allowed"
|
|
145
|
+
/>
|
|
146
|
+
{/each}
|
|
147
|
+
{/snippet}
|
|
148
|
+
|
|
149
|
+
<span
|
|
150
|
+
{...rest}
|
|
151
|
+
class={[
|
|
152
|
+
'inline-flex items-center gap-3',
|
|
153
|
+
vertical ? 'h-48 flex-col' : 'w-full',
|
|
154
|
+
rangeTone[invalid ? 'error' : tone],
|
|
155
|
+
rangeSize[size],
|
|
156
|
+
className,
|
|
157
|
+
]}
|
|
158
|
+
>
|
|
159
|
+
{#if multiple}
|
|
160
|
+
<Slider.Root
|
|
161
|
+
type="multiple"
|
|
162
|
+
bind:value={
|
|
163
|
+
() => [...values],
|
|
164
|
+
(next: number[]) => {
|
|
165
|
+
value = next;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
{...rootProps}
|
|
169
|
+
>
|
|
170
|
+
{#snippet children(snippetProps)}{@render parts(snippetProps)}{/snippet}
|
|
171
|
+
</Slider.Root>
|
|
172
|
+
{:else}
|
|
173
|
+
<Slider.Root
|
|
174
|
+
type="single"
|
|
175
|
+
bind:value={
|
|
176
|
+
() => values[0] ?? min,
|
|
177
|
+
(next: number) => {
|
|
178
|
+
value = next;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
{...rootProps}
|
|
182
|
+
>
|
|
183
|
+
{#snippet children(snippetProps)}{@render parts(snippetProps)}{/snippet}
|
|
184
|
+
</Slider.Root>
|
|
185
|
+
{/if}
|
|
186
|
+
{#if showValue}
|
|
187
|
+
<span aria-hidden="true" class="text-sm whitespace-nowrap text-base-content tabular-nums">{readout}</span>
|
|
188
|
+
{/if}
|
|
189
|
+
{#if name !== undefined}
|
|
190
|
+
{#each values as entry, index (index)}
|
|
191
|
+
<input type="hidden" {name} value={entry} {disabled} />
|
|
192
|
+
{/each}
|
|
193
|
+
{/if}
|
|
194
|
+
</span>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Slider } from 'bits-ui';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../ui/tones.ts';
|
|
4
|
+
import { type FieldSize } from './field-types.ts';
|
|
5
|
+
interface SliderBaseProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'dir' | 'onchange' | 'children'> {
|
|
6
|
+
/**
|
|
7
|
+
* The lowest value.
|
|
8
|
+
* @defaultValue `0`
|
|
9
|
+
*/
|
|
10
|
+
min?: number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The highest value.
|
|
13
|
+
* @defaultValue `100`
|
|
14
|
+
*/
|
|
15
|
+
max?: number | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Increment between values; an array gives explicit snap points.
|
|
18
|
+
* @defaultValue `1`
|
|
19
|
+
*/
|
|
20
|
+
step?: number | readonly number[] | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Track direction.
|
|
23
|
+
* @defaultValue `'horizontal'`
|
|
24
|
+
*/
|
|
25
|
+
orientation?: 'horizontal' | 'vertical' | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Draw a tick per step.
|
|
28
|
+
* @defaultValue `false`
|
|
29
|
+
*/
|
|
30
|
+
ticks?: boolean | undefined;
|
|
31
|
+
/** Format a value for each thumb's `aria-valuetext` and the visible readout, e.g. `(v) => \`${v} cm\``. Falls back to `Intl.NumberFormat(locales)`. */
|
|
32
|
+
formatValue?: ((value: number) => string) | undefined;
|
|
33
|
+
/** BCP 47 locale(s) for the default formatter; falls back to the context's `locale()`. */
|
|
34
|
+
locales?: string | readonly string[] | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Show the formatted value(s) beside the track.
|
|
37
|
+
* @defaultValue `true`
|
|
38
|
+
*/
|
|
39
|
+
showValue?: boolean | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Accessible name of each thumb in range mode, in order.
|
|
42
|
+
* @defaultValue `['Minimum', 'Maximum']`
|
|
43
|
+
*/
|
|
44
|
+
thumbLabels?: readonly string[] | undefined;
|
|
45
|
+
/** Fired once per finished interaction (pointer-up, or a key press), for work that should not run per pixel. */
|
|
46
|
+
onCommit?: ((value: number | readonly number[]) => void) | undefined;
|
|
47
|
+
/** Posts the value under this name: one hidden input per thumb. */
|
|
48
|
+
name?: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Disables every thumb (and the hidden inputs, so nothing posts).
|
|
51
|
+
* @defaultValue `false`
|
|
52
|
+
*/
|
|
53
|
+
disabled?: boolean | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* daisyUI tone of the fill and thumbs; `invalid` overrides it with `error`.
|
|
56
|
+
* @defaultValue `'primary'`
|
|
57
|
+
*/
|
|
58
|
+
tone?: Tone | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* daisyUI size step (the thumb diameter; the track is half of it).
|
|
61
|
+
* @defaultValue `'md'`
|
|
62
|
+
*/
|
|
63
|
+
size?: FieldSize | undefined;
|
|
64
|
+
/** Accessible name, for a slider outside a `FormField`; ignored when `labelId` is set. */
|
|
65
|
+
label?: string | undefined;
|
|
66
|
+
/** `FieldControl`: the first thumb's `id` (the others are `${id}-1`, …). Minted when omitted. */
|
|
67
|
+
id?: string | undefined;
|
|
68
|
+
/** `FieldControl`: the visible label's id; every thumb is `aria-labelledby` it. */
|
|
69
|
+
labelId?: string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* `FieldControl`: space-separated ids for each thumb's `aria-describedby`.
|
|
72
|
+
* @defaultValue `''`
|
|
73
|
+
*/
|
|
74
|
+
describedBy?: string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* `FieldControl`: the field is in error — sets `aria-invalid` and the `error` tone.
|
|
77
|
+
* @defaultValue `false`
|
|
78
|
+
*/
|
|
79
|
+
invalid?: boolean | undefined;
|
|
80
|
+
/** `FieldControl`: accepted for the spread; a slider always has a value, so it has no effect. */
|
|
81
|
+
required?: boolean | undefined;
|
|
82
|
+
}
|
|
83
|
+
type Props = (SliderBaseProps & {
|
|
84
|
+
/**
|
|
85
|
+
* One thumb, binding a `number`.
|
|
86
|
+
* @defaultValue `'single'`
|
|
87
|
+
*/
|
|
88
|
+
type?: 'single' | undefined;
|
|
89
|
+
/** The value. Bindable; defaults to `min`. */
|
|
90
|
+
value?: number | undefined;
|
|
91
|
+
}) | (SliderBaseProps & {
|
|
92
|
+
/** One thumb per entry of `value`, binding a `number[]`. */
|
|
93
|
+
type: 'multiple';
|
|
94
|
+
/** The values, low to high. Bindable; defaults to `[min, max]`. */
|
|
95
|
+
value?: number[] | undefined;
|
|
96
|
+
});
|
|
97
|
+
/** A number, or a `[low, high]` range, picked from a bounded, stepped scale by dragging or with the arrow keys, on bits-ui's `Slider`. */
|
|
98
|
+
declare const Slider: import("svelte").Component<Props, {}, "value">;
|
|
99
|
+
type Slider = ReturnType<typeof Slider>;
|
|
100
|
+
export default Slider;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
An open-ended list of short strings the reader types — tags, aliases, keywords — each a removable chip, with optional suggestions.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
TagsInput — type and press Enter (or a delimiter such as `,`) to commit a
|
|
6
|
+
tag; each tag renders as a chip with a remove button; Backspace in an empty
|
|
7
|
+
input reaches back into the tags. Optional suggestions, from a static list
|
|
8
|
+
or an async search, appear in a listbox.
|
|
9
|
+
|
|
10
|
+
Hand-rolled, and why (AGENTS.md: bits-ui owns interaction): bits-ui 2.20
|
|
11
|
+
has no tags input, and the token list plus one text input is a small
|
|
12
|
+
keyboard model, pinned key by key in `tests/fields/TagsInput.test.ts`. The
|
|
13
|
+
SUGGESTION half is not hand-rolled: with `suggestions` or `onSearch` the
|
|
14
|
+
text input is bits-ui's `Combobox` input, and the async lifecycle
|
|
15
|
+
(debounce, latest-wins, stale-while-revalidate) is the one `Combobox`
|
|
16
|
+
uses, `latest-search.svelte.ts`. Without either, no bits-ui is rendered.
|
|
17
|
+
|
|
18
|
+
ARIA contract (a composition — there is no single APG pattern): a
|
|
19
|
+
`role="list"` of tags, each `role="listitem"` holding its text and a
|
|
20
|
+
remove `<button>` named by `removeLabel(tag)`; then the text input, which
|
|
21
|
+
carries the `FieldControl` id (so `FormField`'s `<label for>` names it),
|
|
22
|
+
`aria-describedby` and `aria-invalid`, and is a combobox
|
|
23
|
+
(`aria-expanded`, `aria-controls`, `aria-activedescendant`) when
|
|
24
|
+
suggestions are on. The tag list is not folded into the description: adds
|
|
25
|
+
and removes reach assistive technology through a polite live region
|
|
26
|
+
(`addedMessage` / `removedMessage`).
|
|
27
|
+
|
|
28
|
+
Keyboard, in the text input: Enter or a delimiter commits the text (or, with
|
|
29
|
+
a suggestion highlighted, that suggestion; Enter on an empty input is left
|
|
30
|
+
alone so a wrapping form can submit); Backspace with empty text focuses the
|
|
31
|
+
last tag's remove button, and a second Backspace removes it; Arrow Left with
|
|
32
|
+
the caret at the start does the same move; Arrow Up / Down walk the
|
|
33
|
+
suggestions; a paste containing delimiters is split into one tag each. On a
|
|
34
|
+
remove button: Arrow Left / Right move between tags (Right past the last
|
|
35
|
+
returns to the input); Delete / Backspace / Enter / Space remove the tag.
|
|
36
|
+
Focus is never lost after a removal: it lands on the tag that took its
|
|
37
|
+
place, else the one before, else the input.
|
|
38
|
+
|
|
39
|
+
`required` means "at least one committed tag": while there are none, the
|
|
40
|
+
text input carries a custom validity error (`requiredMessage`), so the
|
|
41
|
+
browser blocks submission even when uncommitted text sits in the input;
|
|
42
|
+
`aria-required` is set throughout. `max` disables the input at the cap. `name`
|
|
43
|
+
posts one hidden input per tag.
|
|
44
|
+
-->
|
|
45
|
+
<script lang="ts">import { X } from "@lucide/svelte";
|
|
46
|
+
import { Combobox } from "bits-ui";
|
|
47
|
+
import { tick } from "svelte";
|
|
48
|
+
import { inputSize } from "./field-types.js";
|
|
49
|
+
import { LatestSearch } from "./latest-search.svelte.js";
|
|
50
|
+
let { values = $bindable([]), name, delimiters = [","], normalise = (text) => text.trim() === "" ? undefined : text.trim(), allowDuplicates = false, max, suggestions, onSearch, debounceMs = 150, placeholder = "Add a tag", removeLabel = (tag) => `Remove ${tag}`, addedMessage = (tag) => `${tag} added`, removedMessage = (tag) => `${tag} removed`, tag: tagBody, disabled = false, size = "md", id: idProp, labelId: _labelId, describedBy = "", invalid = false, required = false, requiredMessage = "Add at least one tag.", class: className, ...rest } = $props();
|
|
51
|
+
const uid = $props.id();
|
|
52
|
+
const inputId = $derived(idProp ?? uid);
|
|
53
|
+
let text = $state("");
|
|
54
|
+
let announcement = $state("");
|
|
55
|
+
let open = $state(false);
|
|
56
|
+
/** The suggestion bits-ui currently has under the cursor, if any. */
|
|
57
|
+
let highlighted = $state();
|
|
58
|
+
let input = $state(null);
|
|
59
|
+
const removeButtons = $state([]);
|
|
60
|
+
const searcher = new LatestSearch(() => onSearch, () => debounceMs);
|
|
61
|
+
$effect(() => () => searcher.dispose());
|
|
62
|
+
const suggesting = $derived(suggestions !== undefined || onSearch !== undefined);
|
|
63
|
+
const full = $derived(max !== undefined && values.length >= max);
|
|
64
|
+
const present = $derived(new Set(values.map((value) => value.toLowerCase())));
|
|
65
|
+
const visible = $derived.by(() => {
|
|
66
|
+
const pool = onSearch === undefined ? suggestions ?? [] : searcher.results;
|
|
67
|
+
const needle = text.trim().toLowerCase();
|
|
68
|
+
return pool.filter((entry) => (allowDuplicates || !present.has(entry.toLowerCase())) && (onSearch !== undefined || entry.toLowerCase().includes(needle)));
|
|
69
|
+
});
|
|
70
|
+
/** What both the plain and the combobox input carry. */
|
|
71
|
+
const inputProps = $derived({
|
|
72
|
+
id: inputId,
|
|
73
|
+
placeholder,
|
|
74
|
+
autocomplete: "off",
|
|
75
|
+
disabled: disabled || full,
|
|
76
|
+
"aria-required": required ? "true" : undefined,
|
|
77
|
+
"aria-describedby": describedBy === "" ? undefined : describedBy,
|
|
78
|
+
"aria-invalid": invalid ? "true" : undefined,
|
|
79
|
+
class: "min-w-24 grow"
|
|
80
|
+
});
|
|
81
|
+
// Validity follows the committed tags, not the text: typed-but-uncommitted
|
|
82
|
+
// text must not satisfy `required`.
|
|
83
|
+
$effect(() => {
|
|
84
|
+
input?.setCustomValidity(required && values.length === 0 ? requiredMessage : "");
|
|
85
|
+
});
|
|
86
|
+
/** Add `raw` as a tag if `normalise`, the duplicate rule and `max` allow it. */
|
|
87
|
+
function add(raw) {
|
|
88
|
+
if (full) return false;
|
|
89
|
+
const candidate = normalise(raw);
|
|
90
|
+
if (candidate === undefined) return false;
|
|
91
|
+
if (!allowDuplicates && present.has(candidate.toLowerCase())) return false;
|
|
92
|
+
values = [...values, candidate];
|
|
93
|
+
announcement = addedMessage(candidate);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function clearText() {
|
|
97
|
+
text = "";
|
|
98
|
+
if (input !== null) input.value = "";
|
|
99
|
+
searcher.clear();
|
|
100
|
+
open = false;
|
|
101
|
+
}
|
|
102
|
+
function commitText() {
|
|
103
|
+
if (add(text)) clearText();
|
|
104
|
+
}
|
|
105
|
+
async function remove(index) {
|
|
106
|
+
const removed = values[index];
|
|
107
|
+
if (removed === undefined) return;
|
|
108
|
+
values = values.filter((_, at) => at !== index);
|
|
109
|
+
announcement = removedMessage(removed);
|
|
110
|
+
await tick();
|
|
111
|
+
(removeButtons[index] ?? removeButtons[index - 1] ?? input)?.focus();
|
|
112
|
+
}
|
|
113
|
+
function focusTag(index) {
|
|
114
|
+
(removeButtons[index] ?? input)?.focus();
|
|
115
|
+
}
|
|
116
|
+
function typed(event) {
|
|
117
|
+
text = event.currentTarget.value;
|
|
118
|
+
if (!suggesting) return;
|
|
119
|
+
open = text.trim() !== "";
|
|
120
|
+
if (onSearch === undefined) return;
|
|
121
|
+
if (open) searcher.schedule(text);
|
|
122
|
+
else searcher.clear();
|
|
123
|
+
}
|
|
124
|
+
/** Enter commits the text, unless a suggestion is highlighted (bits-ui's) or there is no text (the form's). */
|
|
125
|
+
function enterKey(event) {
|
|
126
|
+
if (open && highlighted !== undefined || text.trim() === "") return;
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
commitText();
|
|
129
|
+
}
|
|
130
|
+
/** Backspace in an empty input, or Arrow Left at its start, moves into the tags. */
|
|
131
|
+
function reachesTags(event) {
|
|
132
|
+
if (values.length === 0) return false;
|
|
133
|
+
if (event.key === "Backspace") return text === "";
|
|
134
|
+
const target = event.currentTarget;
|
|
135
|
+
return event.key === "ArrowLeft" && target.selectionStart === 0 && target.selectionEnd === 0;
|
|
136
|
+
}
|
|
137
|
+
function inputKeydown(event) {
|
|
138
|
+
// An IME uses Enter (and may use the delimiter keys) to confirm a candidate.
|
|
139
|
+
if (event.isComposing) return;
|
|
140
|
+
if (event.key === "Enter") {
|
|
141
|
+
enterKey(event);
|
|
142
|
+
} else if (delimiters.includes(event.key)) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
commitText();
|
|
145
|
+
} else if (reachesTags(event)) {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
focusTag(values.length - 1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function paste(event) {
|
|
151
|
+
const pasted = event.clipboardData?.getData("text/plain") ?? "";
|
|
152
|
+
const separators = [...delimiters, "\n"];
|
|
153
|
+
if (!separators.some((separator) => pasted.includes(separator))) return;
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
// Splice the paste into the caret / selection, as a native paste would, then split.
|
|
156
|
+
const target = event.currentTarget;
|
|
157
|
+
const start = target.selectionStart ?? text.length;
|
|
158
|
+
const end = target.selectionEnd ?? text.length;
|
|
159
|
+
const pieces = separators.reduce((parts, separator) => parts.flatMap((part) => part.split(separator)), [`${text.slice(0, start)}${pasted}${text.slice(end)}`]);
|
|
160
|
+
for (const piece of pieces) add(piece);
|
|
161
|
+
clearText();
|
|
162
|
+
}
|
|
163
|
+
const removeKeys = new Set(["Delete", "Backspace"]);
|
|
164
|
+
function tagKeydown(event, index) {
|
|
165
|
+
if (removeKeys.has(event.key)) {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
void remove(index);
|
|
168
|
+
} else if (event.key === "ArrowLeft") {
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
focusTag(Math.max(0, index - 1));
|
|
171
|
+
} else if (event.key === "ArrowRight") {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
focusTag(index + 1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** A suggestion was chosen. bits-ui writes the item's label into the input after this returns, so clear on the next tick. */
|
|
177
|
+
function pick(next) {
|
|
178
|
+
if (next === "") return;
|
|
179
|
+
add(next);
|
|
180
|
+
void tick().then(clearText);
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<div
|
|
185
|
+
{...rest}
|
|
186
|
+
class={[
|
|
187
|
+
'input h-auto min-h-(--size) w-full flex-wrap items-center gap-1 py-1',
|
|
188
|
+
inputSize[size],
|
|
189
|
+
invalid && 'input-error',
|
|
190
|
+
className,
|
|
191
|
+
]}
|
|
192
|
+
>
|
|
193
|
+
{#if values.length > 0}
|
|
194
|
+
<ul role="list" class="flex flex-wrap gap-1">
|
|
195
|
+
{#each values as value, index (`${index}:${value}`)}
|
|
196
|
+
<li class="badge badge-soft gap-1 pe-0">
|
|
197
|
+
{#if tagBody}{@render tagBody(value)}{:else}<span>{value}</span>{/if}
|
|
198
|
+
<button
|
|
199
|
+
bind:this={removeButtons[index]}
|
|
200
|
+
type="button"
|
|
201
|
+
{disabled}
|
|
202
|
+
class="btn btn-circle btn-ghost btn-xs"
|
|
203
|
+
onclick={() => void remove(index)}
|
|
204
|
+
onkeydown={(event) => tagKeydown(event, index)}
|
|
205
|
+
>
|
|
206
|
+
<X class="size-3" aria-hidden="true" />
|
|
207
|
+
<span class="sr-only">{removeLabel(value)}</span>
|
|
208
|
+
</button>
|
|
209
|
+
</li>
|
|
210
|
+
{/each}
|
|
211
|
+
</ul>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
214
|
+
{#if suggesting}
|
|
215
|
+
<Combobox.Root
|
|
216
|
+
type="single"
|
|
217
|
+
bind:open
|
|
218
|
+
bind:value={() => '', pick}
|
|
219
|
+
inputValue={text}
|
|
220
|
+
{disabled}
|
|
221
|
+
>
|
|
222
|
+
<Combobox.Input {...inputProps} bind:ref={input} oninput={typed} onkeydown={inputKeydown} onpaste={paste} />
|
|
223
|
+
<Combobox.Portal>
|
|
224
|
+
<Combobox.Content
|
|
225
|
+
sideOffset={4}
|
|
226
|
+
class="menu bg-base-100 rounded-box border-base-300 z-50 max-h-72 min-w-48 overflow-auto border p-2 shadow-md"
|
|
227
|
+
>
|
|
228
|
+
<Combobox.Viewport>
|
|
229
|
+
{#each visible as entry (entry)}
|
|
230
|
+
<Combobox.Item
|
|
231
|
+
value={entry}
|
|
232
|
+
label={entry}
|
|
233
|
+
onHighlight={() => (highlighted = entry)}
|
|
234
|
+
onUnhighlight={() => {
|
|
235
|
+
if (highlighted === entry) highlighted = undefined;
|
|
236
|
+
}}
|
|
237
|
+
class="rounded-field data-highlighted:bg-base-200 cursor-pointer px-3 py-2"
|
|
238
|
+
>
|
|
239
|
+
{entry}
|
|
240
|
+
</Combobox.Item>
|
|
241
|
+
{/each}
|
|
242
|
+
</Combobox.Viewport>
|
|
243
|
+
</Combobox.Content>
|
|
244
|
+
</Combobox.Portal>
|
|
245
|
+
</Combobox.Root>
|
|
246
|
+
{:else}
|
|
247
|
+
<input {...inputProps} bind:this={input} type="text" oninput={typed} onkeydown={inputKeydown} onpaste={paste} />
|
|
248
|
+
{/if}
|
|
249
|
+
|
|
250
|
+
{#if name !== undefined}
|
|
251
|
+
{#each values as value, index (`${index}:${value}`)}
|
|
252
|
+
<input type="hidden" {name} {value} {disabled} />
|
|
253
|
+
{/each}
|
|
254
|
+
{/if}
|
|
255
|
+
<p class="sr-only" aria-live="polite">{announcement}</p>
|
|
256
|
+
</div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type FieldSize } from './field-types.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'id'> {
|
|
5
|
+
/**
|
|
6
|
+
* The tags. Bindable.
|
|
7
|
+
* @defaultValue `[]`
|
|
8
|
+
*/
|
|
9
|
+
values?: string[] | undefined;
|
|
10
|
+
/** Posts each tag under this name, one hidden input per tag. */
|
|
11
|
+
name?: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Characters that commit the current text as a tag, besides Enter; a paste is split on them too.
|
|
14
|
+
* @defaultValue `[',']`
|
|
15
|
+
*/
|
|
16
|
+
delimiters?: readonly string[] | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Normalise or refuse a candidate; return `undefined` to refuse it.
|
|
19
|
+
* @defaultValue trims, and refuses an empty string
|
|
20
|
+
*/
|
|
21
|
+
normalise?: ((text: string) => string | undefined) | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Allow a tag already present (compared case-insensitively after `normalise`).
|
|
24
|
+
* @defaultValue `false`
|
|
25
|
+
*/
|
|
26
|
+
allowDuplicates?: boolean | undefined;
|
|
27
|
+
/** Cap on the number of tags; the input disables at the cap. */
|
|
28
|
+
max?: number | undefined;
|
|
29
|
+
/** Static suggestions, filtered by the typed text. */
|
|
30
|
+
suggestions?: readonly string[] | undefined;
|
|
31
|
+
/** Async suggestions, debounced and latest-wins; `signal` aborts a superseded search. */
|
|
32
|
+
onSearch?: ((query: string, signal: AbortSignal) => Promise<readonly string[]>) | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Quiet time before `onSearch` runs.
|
|
35
|
+
* @defaultValue `150`
|
|
36
|
+
*/
|
|
37
|
+
debounceMs?: number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Placeholder in the text input.
|
|
40
|
+
* @defaultValue `'Add a tag'`
|
|
41
|
+
*/
|
|
42
|
+
placeholder?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Accessible name of a tag's remove button.
|
|
45
|
+
* @defaultValue `(tag) => \`Remove ${tag}\``
|
|
46
|
+
*/
|
|
47
|
+
removeLabel?: ((tag: string) => string) | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Live-region text after a tag is added.
|
|
50
|
+
* @defaultValue `(tag) => \`${tag} added\``
|
|
51
|
+
*/
|
|
52
|
+
addedMessage?: ((tag: string) => string) | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Live-region text after a tag is removed.
|
|
55
|
+
* @defaultValue `(tag) => \`${tag} removed\``
|
|
56
|
+
*/
|
|
57
|
+
removedMessage?: ((tag: string) => string) | undefined;
|
|
58
|
+
/** Renders one tag's body; defaults to its text. */
|
|
59
|
+
tag?: Snippet<[string]> | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Disables the input and every remove button.
|
|
62
|
+
* @defaultValue `false`
|
|
63
|
+
*/
|
|
64
|
+
disabled?: boolean | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* daisyUI size step.
|
|
67
|
+
* @defaultValue `'md'`
|
|
68
|
+
*/
|
|
69
|
+
size?: FieldSize | undefined;
|
|
70
|
+
/** `FieldControl`: the text input's `id`, which `FormField`'s label points at. Minted when omitted. */
|
|
71
|
+
id?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* `FieldControl`: the visible label's id. Not used — the text input is
|
|
74
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
75
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
76
|
+
*/
|
|
77
|
+
labelId?: string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* `FieldControl`: space-separated ids for the input's `aria-describedby`.
|
|
80
|
+
* @defaultValue `''`
|
|
81
|
+
*/
|
|
82
|
+
describedBy?: string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* `FieldControl`: the field is in error — sets `aria-invalid` and `input-error`.
|
|
85
|
+
* @defaultValue `false`
|
|
86
|
+
*/
|
|
87
|
+
invalid?: boolean | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* `FieldControl`: at least one tag is required.
|
|
90
|
+
* @defaultValue `false`
|
|
91
|
+
*/
|
|
92
|
+
required?: boolean | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* The validation message while `required` and no tag has been committed.
|
|
95
|
+
* @defaultValue `'Add at least one tag.'`
|
|
96
|
+
*/
|
|
97
|
+
requiredMessage?: string | undefined;
|
|
98
|
+
}
|
|
99
|
+
/** An open-ended list of short strings the reader types — tags, aliases, keywords — each a removable chip, with optional suggestions. */
|
|
100
|
+
declare const TagsInput: import("svelte").Component<Props, {}, "values">;
|
|
101
|
+
type TagsInput = ReturnType<typeof TagsInput>;
|
|
102
|
+
export default TagsInput;
|