@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,43 @@
|
|
|
1
|
+
import type { DateValue } from '@internationalized/date';
|
|
2
|
+
import { DateRangePicker } from 'bits-ui';
|
|
3
|
+
import { type PickerProps, type SegmentedFieldProps } from './segmented-field.ts';
|
|
4
|
+
/** A start–end span; either end `undefined` while incomplete. */
|
|
5
|
+
interface DateRangeValue {
|
|
6
|
+
start: DateValue | undefined;
|
|
7
|
+
end: DateValue | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface Props extends Omit<SegmentedFieldProps, 'name'>, PickerProps {
|
|
10
|
+
/** The span. Bindable. */
|
|
11
|
+
value?: DateRangeValue | undefined;
|
|
12
|
+
/** Earliest valid date. */
|
|
13
|
+
minValue?: DateValue | undefined;
|
|
14
|
+
/** Latest valid date. */
|
|
15
|
+
maxValue?: DateValue | undefined;
|
|
16
|
+
/** Shortest span in days. */
|
|
17
|
+
minDays?: number | undefined;
|
|
18
|
+
/** Longest span in days. */
|
|
19
|
+
maxDays?: number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Close the popover once the span is complete.
|
|
22
|
+
* @defaultValue `true`
|
|
23
|
+
*/
|
|
24
|
+
closeOnRangeSelect?: boolean | undefined;
|
|
25
|
+
/** Form field name of the start date (ISO), posted through a hidden input. */
|
|
26
|
+
startName?: string | undefined;
|
|
27
|
+
/** Form field name of the end date (ISO), posted through a hidden input. */
|
|
28
|
+
endName?: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Name of the start half.
|
|
31
|
+
* @defaultValue `'Start date'`
|
|
32
|
+
*/
|
|
33
|
+
startLabel?: string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Name of the end half.
|
|
36
|
+
* @defaultValue `'End date'`
|
|
37
|
+
*/
|
|
38
|
+
endLabel?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Two segmented date fields, a start and an end, with a range-calendar popover, on bits-ui `DateRangePicker`. */
|
|
41
|
+
declare const DateRangePicker: import("svelte").Component<Props, {}, "value" | "open">;
|
|
42
|
+
type DateRangePicker = ReturnType<typeof DateRangePicker>;
|
|
43
|
+
export default DateRangePicker;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Choose one or many files by dropping them on a target or through a "Choose files" button, with a removable file list and explained rejections; never uploads.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Dropzone — a drop target plus a real "Choose files" button, a list of the
|
|
6
|
+
chosen files (name, size, optional thumbnail) each with a remove button,
|
|
7
|
+
and client-side `accept` / `maxBytes` / `maxFiles` checks whose rejections
|
|
8
|
+
are explained. It binds a `File[]` and never uploads: the transfer is the
|
|
9
|
+
caller's, as with `FileInput`.
|
|
10
|
+
|
|
11
|
+
Hand-rolled, and why (AGENTS.md): no bits-ui primitive exists for file
|
|
12
|
+
drops, and the contract is small — one button, one list, one alert —
|
|
13
|
+
pinned by `tests/fields/Dropzone.test.ts`. The checks are the pure helpers
|
|
14
|
+
in `file-validation.ts`, shared with `FileInput`.
|
|
15
|
+
|
|
16
|
+
A real `<input type="file">` sits inside, visually hidden, carrying
|
|
17
|
+
`name`, `accept`, `multiple` and `required`. Every change to `files` — a
|
|
18
|
+
pick, a drop, a removal, the caller's own write — is synced into it through
|
|
19
|
+
a `DataTransfer`, so a plain form POST posts exactly the listed files.
|
|
20
|
+
A drop is NOT filtered by the browser against `accept`, which is why the
|
|
21
|
+
checks run on drops too.
|
|
22
|
+
|
|
23
|
+
ARIA contract (no APG pattern; these are the guarantees):
|
|
24
|
+
- The drop area is never the only way in. The "Choose files" `<button>`
|
|
25
|
+
opens the native picker, and it carries the `FieldControl` id, so
|
|
26
|
+
`FormField`'s `<label for>` names it, plus `aria-describedby`
|
|
27
|
+
(`describedBy`, then the rejection alert). It takes no `aria-invalid` —
|
|
28
|
+
the `button` role does not support it — so `invalid` reaches assistive
|
|
29
|
+
technology through `FormField`'s error text in that description. The drop
|
|
30
|
+
area itself is not focusable; `promptLabel` sits in front of the button.
|
|
31
|
+
- Drag-over is visual only (`data-dragging`), never announced.
|
|
32
|
+
- The files are a `<ul>` of `<li>`; each remove button is named by
|
|
33
|
+
`removeLabel(file)`. After a removal, focus moves to the next remove
|
|
34
|
+
button, else the previous one, else the choose button.
|
|
35
|
+
- Rejections from the last selection render in one `role="alert"`
|
|
36
|
+
region (`FieldErrors`), cleared by the next selection.
|
|
37
|
+
- A polite live region says how many files are selected after each
|
|
38
|
+
selection or drop, since a drop otherwise produces no announcement.
|
|
39
|
+
|
|
40
|
+
Previews: an image file gets an object-URL thumbnail when `previews` is
|
|
41
|
+
on; every URL is revoked when its file leaves the list and when the
|
|
42
|
+
component is destroyed.
|
|
43
|
+
-->
|
|
44
|
+
<script lang="ts">import { tick, untrack } from "svelte";
|
|
45
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
46
|
+
import { getComponentsContext } from "../context.svelte.js";
|
|
47
|
+
import FieldErrors from "./FieldErrors.svelte";
|
|
48
|
+
import { acceptMatches, exceedsSize, formatFileSize } from "./file-validation.js";
|
|
49
|
+
let { files = $bindable([]), accept, maxBytes, maxFiles, multiple = true, name, onSelect, previews = true, promptLabel = "Drop files here or", chooseLabel = "Choose files", removeLabel = (file) => `Remove ${file.name}`, tooLargeMessage = (file) => `${file.name} is too large.`, wrongTypeMessage = (file) => `${file.name} is not an accepted file type.`, tooManyMessage = (max) => `Choose at most ${max} files.`, selectedMessage = (count) => count === 1 ? "1 file selected" : `${count} files selected`, locales = getComponentsContext().locale(), file: fileRow, disabled = false, id: idProp, labelId: _labelId, describedBy = "", invalid = false, required = false, class: className, ...rest } = $props();
|
|
50
|
+
const uid = $props.id();
|
|
51
|
+
const buttonId = $derived(idProp ?? uid);
|
|
52
|
+
const errorsId = $derived(`${uid}-errors`);
|
|
53
|
+
let input = $state(null);
|
|
54
|
+
let chooseButton = $state(null);
|
|
55
|
+
let root = $state(null);
|
|
56
|
+
let dragging = $state(false);
|
|
57
|
+
let rejections = $state([]);
|
|
58
|
+
let announcement = $state("");
|
|
59
|
+
const previewUrls = new SvelteMap();
|
|
60
|
+
const cap = $derived(multiple ? maxFiles : 1);
|
|
61
|
+
const messages = $derived(rejectionMessages(rejections));
|
|
62
|
+
const described = $derived([describedBy, messages.length > 0 ? errorsId : ""].filter((part) => part !== "").join(" "));
|
|
63
|
+
function rejectionMessages(list) {
|
|
64
|
+
const out = list.filter((rejection) => rejection.reason !== "too-many").map((rejection) => rejection.reason === "too-large" ? tooLargeMessage(rejection.file) : wrongTypeMessage(rejection.file));
|
|
65
|
+
if (cap !== undefined && list.some((rejection) => rejection.reason === "too-many")) {
|
|
66
|
+
out.push(tooManyMessage(cap));
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/** Which rule, if any, refuses `candidate` once `taken` files are already in. */
|
|
71
|
+
function refusal(candidate, taken) {
|
|
72
|
+
if (!acceptMatches(candidate, accept)) return "wrong-type";
|
|
73
|
+
if (exceedsSize(candidate, maxBytes)) return "too-large";
|
|
74
|
+
if (cap !== undefined && taken >= cap) return "too-many";
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Check a pick or a drop, keep what passes, explain what does not. */
|
|
78
|
+
function take(incoming) {
|
|
79
|
+
const base = multiple ? files : [];
|
|
80
|
+
const accepted = [];
|
|
81
|
+
const rejected = [];
|
|
82
|
+
for (const candidate of incoming) {
|
|
83
|
+
const reason = refusal(candidate, base.length + accepted.length);
|
|
84
|
+
if (reason === undefined) accepted.push(candidate);
|
|
85
|
+
else rejected.push({
|
|
86
|
+
file: candidate,
|
|
87
|
+
reason
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (accepted.length > 0 || !multiple) files = [...base, ...accepted];
|
|
91
|
+
rejections = rejected;
|
|
92
|
+
announcement = selectedMessage(files.length);
|
|
93
|
+
onSelect?.(accepted, rejected);
|
|
94
|
+
}
|
|
95
|
+
async function remove(index) {
|
|
96
|
+
files = files.filter((_, at) => at !== index);
|
|
97
|
+
announcement = selectedMessage(files.length);
|
|
98
|
+
// Let the list re-render before choosing where focus goes.
|
|
99
|
+
await tick();
|
|
100
|
+
const left = [...root?.querySelectorAll("[data-dropzone-remove]") ?? []];
|
|
101
|
+
(left[index] ?? left[index - 1] ?? chooseButton)?.focus();
|
|
102
|
+
}
|
|
103
|
+
/** Mirror `files` into the native input, so a form POST sends exactly the listed files. */
|
|
104
|
+
function syncInput(current) {
|
|
105
|
+
if (input === null) return;
|
|
106
|
+
const transfer = new DataTransfer();
|
|
107
|
+
for (const entry of current) transfer.items.add(entry);
|
|
108
|
+
input.files = transfer.files;
|
|
109
|
+
}
|
|
110
|
+
$effect(() => {
|
|
111
|
+
const current = files;
|
|
112
|
+
untrack(() => {
|
|
113
|
+
for (const entry of current) {
|
|
114
|
+
if (previews && entry.type.startsWith("image/") && !previewUrls.has(entry)) {
|
|
115
|
+
previewUrls.set(entry, URL.createObjectURL(entry));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const [entry, url] of previewUrls) {
|
|
119
|
+
if (!current.includes(entry)) {
|
|
120
|
+
URL.revokeObjectURL(url);
|
|
121
|
+
previewUrls.delete(entry);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
syncInput(current);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
$effect(() => () => {
|
|
128
|
+
for (const url of previewUrls.values()) URL.revokeObjectURL(url);
|
|
129
|
+
});
|
|
130
|
+
function dragOver(event) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
if (disabled) return;
|
|
133
|
+
dragging = true;
|
|
134
|
+
if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "copy";
|
|
135
|
+
}
|
|
136
|
+
function dragLeave(event) {
|
|
137
|
+
const target = event.currentTarget;
|
|
138
|
+
if (event.relatedTarget instanceof Node && target.contains(event.relatedTarget)) return;
|
|
139
|
+
dragging = false;
|
|
140
|
+
}
|
|
141
|
+
function drop(event) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
dragging = false;
|
|
144
|
+
if (disabled) return;
|
|
145
|
+
take([...event.dataTransfer?.files ?? []]);
|
|
146
|
+
}
|
|
147
|
+
function picked(event) {
|
|
148
|
+
const chosen = [...event.currentTarget.files ?? []];
|
|
149
|
+
// The native input now holds only the new pick; `take` decides the list,
|
|
150
|
+
// and the sync effect writes it back.
|
|
151
|
+
take(chosen);
|
|
152
|
+
syncInput(files);
|
|
153
|
+
}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
{#snippet row(entry: File, index: number)}
|
|
157
|
+
{@const preview = previewUrls.get(entry)}
|
|
158
|
+
{#if preview !== undefined}
|
|
159
|
+
<img src={preview} alt="" class="rounded-field size-10 object-cover" />
|
|
160
|
+
{/if}
|
|
161
|
+
<span class="min-w-0 grow truncate">{entry.name}</span>
|
|
162
|
+
<span class="text-base-content/70 text-sm tabular-nums">{formatFileSize(entry.size, locales)}</span>
|
|
163
|
+
<button
|
|
164
|
+
data-dropzone-remove
|
|
165
|
+
type="button"
|
|
166
|
+
{disabled}
|
|
167
|
+
class="btn btn-ghost btn-sm"
|
|
168
|
+
onclick={() => void remove(index)}
|
|
169
|
+
>
|
|
170
|
+
{removeLabel(entry)}
|
|
171
|
+
</button>
|
|
172
|
+
{/snippet}
|
|
173
|
+
|
|
174
|
+
<div
|
|
175
|
+
{...rest}
|
|
176
|
+
bind:this={root}
|
|
177
|
+
data-dragging={dragging ? '' : undefined}
|
|
178
|
+
ondragenter={dragOver}
|
|
179
|
+
ondragover={dragOver}
|
|
180
|
+
ondragleave={dragLeave}
|
|
181
|
+
ondrop={drop}
|
|
182
|
+
class={[
|
|
183
|
+
'rounded-box bg-base-100 border-2 border-dashed p-4',
|
|
184
|
+
invalid || messages.length > 0 ? 'border-error' : 'border-base-300',
|
|
185
|
+
'data-[dragging]:border-primary data-[dragging]:bg-base-200',
|
|
186
|
+
disabled && 'opacity-50',
|
|
187
|
+
className,
|
|
188
|
+
]}
|
|
189
|
+
>
|
|
190
|
+
<div class="flex flex-wrap items-center justify-center gap-2 py-4 text-center">
|
|
191
|
+
<span>{promptLabel}</span>
|
|
192
|
+
<button
|
|
193
|
+
bind:this={chooseButton}
|
|
194
|
+
id={buttonId}
|
|
195
|
+
type="button"
|
|
196
|
+
{disabled}
|
|
197
|
+
aria-describedby={described === '' ? undefined : described}
|
|
198
|
+
class="btn btn-sm"
|
|
199
|
+
onclick={() => input?.click()}
|
|
200
|
+
>
|
|
201
|
+
{chooseLabel}
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
<input
|
|
205
|
+
bind:this={input}
|
|
206
|
+
type="file"
|
|
207
|
+
class="sr-only"
|
|
208
|
+
tabindex="-1"
|
|
209
|
+
aria-hidden="true"
|
|
210
|
+
{name}
|
|
211
|
+
{accept}
|
|
212
|
+
{multiple}
|
|
213
|
+
{required}
|
|
214
|
+
{disabled}
|
|
215
|
+
onchange={picked}
|
|
216
|
+
/>
|
|
217
|
+
{#if files.length > 0}
|
|
218
|
+
<ul class="mt-2 flex flex-col gap-2">
|
|
219
|
+
{#each files as entry, index (entry)}
|
|
220
|
+
<li class="flex items-center gap-3">
|
|
221
|
+
{#if fileRow}
|
|
222
|
+
{@render fileRow(entry, { remove: () => void remove(index) })}
|
|
223
|
+
{:else}
|
|
224
|
+
{@render row(entry, index)}
|
|
225
|
+
{/if}
|
|
226
|
+
</li>
|
|
227
|
+
{/each}
|
|
228
|
+
</ul>
|
|
229
|
+
{/if}
|
|
230
|
+
<FieldErrors errors={messages} id={errorsId} />
|
|
231
|
+
<p class="sr-only" aria-live="polite">{announcement}</p>
|
|
232
|
+
</div>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type FileRejection } from './file-validation.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'ondrop' | 'id'> {
|
|
5
|
+
/**
|
|
6
|
+
* The chosen files. Bindable.
|
|
7
|
+
* @defaultValue `[]`
|
|
8
|
+
*/
|
|
9
|
+
files?: File[] | undefined;
|
|
10
|
+
/** The native `accept` syntax (`image/*`, `.pdf`, `application/json`); also enforced on drop. */
|
|
11
|
+
accept?: string | undefined;
|
|
12
|
+
/** Per-file size cap, in bytes. Omitted, there is no cap. */
|
|
13
|
+
maxBytes?: number | undefined;
|
|
14
|
+
/** Cap on the number of files; extra files are rejected with `tooManyMessage`. */
|
|
15
|
+
maxFiles?: number | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Allow more than one file. Off, a new choice replaces the current file.
|
|
18
|
+
* @defaultValue `true`
|
|
19
|
+
*/
|
|
20
|
+
multiple?: boolean | undefined;
|
|
21
|
+
/** Posts the files under this name through the inner native input. */
|
|
22
|
+
name?: string | undefined;
|
|
23
|
+
/** Called after each pick or drop with the files accepted and the ones refused. */
|
|
24
|
+
onSelect?: ((accepted: readonly File[], rejected: readonly FileRejection[]) => void) | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Show a thumbnail for image files.
|
|
27
|
+
* @defaultValue `true`
|
|
28
|
+
*/
|
|
29
|
+
previews?: boolean | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Text in front of the choose button.
|
|
32
|
+
* @defaultValue `'Drop files here or'`
|
|
33
|
+
*/
|
|
34
|
+
promptLabel?: string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The choose button's text.
|
|
37
|
+
* @defaultValue `'Choose files'`
|
|
38
|
+
*/
|
|
39
|
+
chooseLabel?: string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Accessible name of a file's remove button.
|
|
42
|
+
* @defaultValue `(file) => \`Remove ${file.name}\``
|
|
43
|
+
*/
|
|
44
|
+
removeLabel?: ((file: File) => string) | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Shown for a file over `maxBytes`.
|
|
47
|
+
* @defaultValue `(file) => \`${file.name} is too large.\``
|
|
48
|
+
*/
|
|
49
|
+
tooLargeMessage?: ((file: File) => string) | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Shown for a file `accept` refuses.
|
|
52
|
+
* @defaultValue `(file) => \`${file.name} is not an accepted file type.\``
|
|
53
|
+
*/
|
|
54
|
+
wrongTypeMessage?: ((file: File) => string) | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Shown once when a selection would pass `maxFiles`.
|
|
57
|
+
* @defaultValue `(max) => \`Choose at most ${max} files.\``
|
|
58
|
+
*/
|
|
59
|
+
tooManyMessage?: ((max: number) => string) | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Live-region text after a selection or drop.
|
|
62
|
+
* @defaultValue `(count) => count === 1 ? '1 file selected' : \`${count} files selected\``
|
|
63
|
+
*/
|
|
64
|
+
selectedMessage?: ((count: number) => string) | undefined;
|
|
65
|
+
/** BCP 47 locale(s) for the file sizes; falls back to the context's `locale()`. */
|
|
66
|
+
locales?: string | readonly string[] | undefined;
|
|
67
|
+
/** Replaces a file row's body, e.g. to add upload progress the caller tracks. */
|
|
68
|
+
file?: Snippet<[File, {
|
|
69
|
+
remove: () => void;
|
|
70
|
+
}]> | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Disables the button, the drop target and the remove buttons.
|
|
73
|
+
* @defaultValue `false`
|
|
74
|
+
*/
|
|
75
|
+
disabled?: boolean | undefined;
|
|
76
|
+
/** `FieldControl`: the choose button's `id`, which `FormField`'s label points at. Minted when omitted. */
|
|
77
|
+
id?: string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* `FieldControl`: the visible label's id. Not used — the choose button is
|
|
80
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
81
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
82
|
+
*/
|
|
83
|
+
labelId?: string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* `FieldControl`: space-separated ids for the button's `aria-describedby`.
|
|
86
|
+
* @defaultValue `''`
|
|
87
|
+
*/
|
|
88
|
+
describedBy?: string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* `FieldControl`: the field is in error — draws the error border (the message reaches the button through `describedBy`).
|
|
91
|
+
* @defaultValue `false`
|
|
92
|
+
*/
|
|
93
|
+
invalid?: boolean | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* `FieldControl`: at least one file is required (on the native input).
|
|
96
|
+
* @defaultValue `false`
|
|
97
|
+
*/
|
|
98
|
+
required?: boolean | undefined;
|
|
99
|
+
}
|
|
100
|
+
/** Choose one or many files by dropping them on a target or through a "Choose files" button, with a removable file list and explained rejections; never uploads. */
|
|
101
|
+
declare const Dropzone: import("svelte").Component<Props, {}, "files">;
|
|
102
|
+
type Dropzone = ReturnType<typeof Dropzone>;
|
|
103
|
+
export default Dropzone;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The validation messages under one control: one `<p>` per message inside a single container.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FieldErrors — the validation messages under one control: one `<p>` per
|
|
6
|
+
message inside a single container.
|
|
7
|
+
|
|
8
|
+
ARIA contract: the container is the live region (`role="alert"`), not each
|
|
9
|
+
paragraph — a field that fails three rules should interrupt the reader once
|
|
10
|
+
and then read all three, which is what one region holding three paragraphs
|
|
11
|
+
does and what three regions do not. It carries the `id` a control points
|
|
12
|
+
`aria-describedby` at, so the messages are also read on demand when focus
|
|
13
|
+
reaches the control.
|
|
14
|
+
|
|
15
|
+
Nothing is rendered while `errors` is empty. That is deliberate: a live
|
|
16
|
+
region has to be IN the document before its content changes for the change
|
|
17
|
+
to be announced, and inserting a populated region announces it, so the
|
|
18
|
+
cheapest correct thing is to mount it only when there is something to say.
|
|
19
|
+
-->
|
|
20
|
+
<script lang="ts">let { errors, id, class: className, ...rest } = $props();
|
|
21
|
+
export {};
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
{#if errors.length > 0}
|
|
25
|
+
<div {...rest} {id} role="alert" class={className}>
|
|
26
|
+
{#each errors as message, index (index)}
|
|
27
|
+
<p class="fieldset-label text-error">{message}</p>
|
|
28
|
+
{/each}
|
|
29
|
+
</div>
|
|
30
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Messages to render, one paragraph each. */
|
|
4
|
+
errors: readonly string[];
|
|
5
|
+
/** The id a control's `aria-describedby` points at. */
|
|
6
|
+
id?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/** The validation messages under one control: one `<p>` per message inside a single container. */
|
|
9
|
+
declare const FieldErrors: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type FieldErrors = ReturnType<typeof FieldErrors>;
|
|
11
|
+
export default FieldErrors;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
The `FormField` frame — legend, hint, errors — for a group of controls rather than one.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
Fieldset — the same frame as `FormField`, for a GROUP of controls rather
|
|
6
|
+
than one.
|
|
7
|
+
|
|
8
|
+
A group of related checkboxes or radios is a `<fieldset>` with a `<legend>`,
|
|
9
|
+
not a label pointing at one input: there is no single control for a `for`
|
|
10
|
+
attribute to name, and the legend is what assistive technology repeats as it
|
|
11
|
+
moves between the members. So the hint and the errors hang off the
|
|
12
|
+
`<fieldset>` itself through `aria-describedby`. There is no `aria-invalid`:
|
|
13
|
+
the implicit `group` role does not support it, so the invalid state is a
|
|
14
|
+
`data-invalid` hook for styling and the error region is what announces it.
|
|
15
|
+
|
|
16
|
+
The `children` snippet receives a `FieldsetControl`, the `<legend>`'s id as
|
|
17
|
+
`labelId`, for a role-based control inside the group to point
|
|
18
|
+
`aria-labelledby` at.
|
|
19
|
+
|
|
20
|
+
`CheckboxGroup` is built on this. `RadioGroup` is not — bits-ui's radio
|
|
21
|
+
group renders its own `role="radiogroup"` element, and a role inside a
|
|
22
|
+
`<fieldset>` would be named by nothing, so it names itself from a heading it
|
|
23
|
+
renders instead. That is the one place the two frames diverge.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">import FieldErrors from "./FieldErrors.svelte";
|
|
26
|
+
let { legend, hint, errors = [], required = false, requiredLabel = "(required)", children, class: className, ...rest } = $props();
|
|
27
|
+
const uid = $props.id();
|
|
28
|
+
const labelId = `${uid}-legend`;
|
|
29
|
+
const hintId = `${uid}-hint`;
|
|
30
|
+
const errorId = `${uid}-error`;
|
|
31
|
+
const invalid = $derived(errors.length > 0);
|
|
32
|
+
const describedBy = $derived([hint === undefined ? "" : hintId, invalid ? errorId : ""].filter((part) => part !== "").join(" "));
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<fieldset
|
|
36
|
+
{...rest}
|
|
37
|
+
aria-describedby={describedBy === '' ? undefined : describedBy}
|
|
38
|
+
data-invalid={invalid ? '' : undefined}
|
|
39
|
+
class={['fieldset', className]}
|
|
40
|
+
>
|
|
41
|
+
<legend class="fieldset-legend" id={labelId}>
|
|
42
|
+
{legend}
|
|
43
|
+
{#if required}
|
|
44
|
+
<span class="text-error" aria-hidden="true">*</span>
|
|
45
|
+
<span class="sr-only">{requiredLabel}</span>
|
|
46
|
+
{/if}
|
|
47
|
+
</legend>
|
|
48
|
+
{@render children({ labelId })}
|
|
49
|
+
{#if hint !== undefined}
|
|
50
|
+
<p class="fieldset-label" id={hintId}>{hint}</p>
|
|
51
|
+
{/if}
|
|
52
|
+
<FieldErrors {errors} id={errorId} />
|
|
53
|
+
</fieldset>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { FieldsetControl } from './field-types.ts';
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLFieldSetElement>, 'children'> {
|
|
5
|
+
/** The group's `<legend>`; names every control inside it. */
|
|
6
|
+
legend: string;
|
|
7
|
+
/** Guidance rendered under the legend. */
|
|
8
|
+
hint?: string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Validation messages; any at all marks the group invalid.
|
|
11
|
+
* @defaultValue `[]`
|
|
12
|
+
*/
|
|
13
|
+
errors?: readonly string[] | undefined;
|
|
14
|
+
/** Marks the group required and reads `requiredLabel` after the legend. */
|
|
15
|
+
required?: boolean | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Read out after the legend when `required`.
|
|
18
|
+
* @defaultValue `'(required)'`
|
|
19
|
+
*/
|
|
20
|
+
requiredLabel?: string | undefined;
|
|
21
|
+
/** The grouped controls; receives the legend's id as `labelId`. */
|
|
22
|
+
children: Snippet<[FieldsetControl]>;
|
|
23
|
+
}
|
|
24
|
+
/** The `FormField` frame — legend, hint, errors — for a group of controls rather than one. */
|
|
25
|
+
declare const Fieldset: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type Fieldset = ReturnType<typeof Fieldset>;
|
|
27
|
+
export default Fieldset;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A native file input in daisyUI `file-input` styling, with the chosen `File` bindable and a client-side size cap.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
FileInput — a native `<input type="file">` in daisyUI `file-input` styling,
|
|
6
|
+
with the chosen `File` exposed as a bindable prop and a client-side size cap.
|
|
7
|
+
|
|
8
|
+
It does NOT upload anything. The transfer belongs to the caller — a form
|
|
9
|
+
POST, a direct-to-storage PUT, a resumable client — and a component every
|
|
10
|
+
page imports has no business carrying one. `ImageInput` and `VideoInput`
|
|
11
|
+
build on this: the selection and the messaging are shared, the transfer is
|
|
12
|
+
not.
|
|
13
|
+
|
|
14
|
+
`maxBytes` is enforced here as well as on the server because a file rejected
|
|
15
|
+
after the transfer is a uniquely expensive way to say no. An over-size file
|
|
16
|
+
is cleared from the control rather than left sitting in it, so what the
|
|
17
|
+
control shows and what `file` holds cannot disagree.
|
|
18
|
+
|
|
19
|
+
ARIA contract: a native file input. `aria-invalid` is set when the field is
|
|
20
|
+
invalid OR the chosen file was too large, and the too-large message is an
|
|
21
|
+
`alert` region the input's `aria-describedby` also points at, so the reason
|
|
22
|
+
is announced and is available again on focus.
|
|
23
|
+
-->
|
|
24
|
+
<script lang="ts">import FieldErrors from "./FieldErrors.svelte";
|
|
25
|
+
import { fileInputSize } from "./field-types.js";
|
|
26
|
+
import { exceedsSize } from "./file-validation.js";
|
|
27
|
+
let { file = $bindable(), accept, maxBytes, tooLargeMessage = "That file is too large.", onSelect, size = "md", invalid = false, describedBy = "", labelId: _labelId, class: className, ...rest } = $props();
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
const tooLargeId = `${uid}-too-large`;
|
|
30
|
+
let tooLarge = $state(false);
|
|
31
|
+
const described = $derived([describedBy, tooLarge ? tooLargeId : ""].filter((part) => part !== "").join(" "));
|
|
32
|
+
const choose = (control) => {
|
|
33
|
+
const chosen = control.files?.[0];
|
|
34
|
+
tooLarge = false;
|
|
35
|
+
if (chosen === undefined) {
|
|
36
|
+
file = undefined;
|
|
37
|
+
onSelect?.(undefined);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (exceedsSize(chosen, maxBytes)) {
|
|
41
|
+
tooLarge = true;
|
|
42
|
+
// Clear the control too: leaving the name of a file that was refused
|
|
43
|
+
// showing in the field is how someone submits and wonders what happened.
|
|
44
|
+
control.value = "";
|
|
45
|
+
file = undefined;
|
|
46
|
+
onSelect?.(undefined);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
file = chosen;
|
|
50
|
+
onSelect?.(chosen);
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<input
|
|
55
|
+
{...rest}
|
|
56
|
+
type="file"
|
|
57
|
+
{accept}
|
|
58
|
+
onchange={(event) => choose(event.currentTarget)}
|
|
59
|
+
aria-invalid={invalid || tooLarge ? 'true' : undefined}
|
|
60
|
+
aria-describedby={described === '' ? undefined : described}
|
|
61
|
+
class={[
|
|
62
|
+
'file-input w-full',
|
|
63
|
+
fileInputSize[size],
|
|
64
|
+
(invalid || tooLarge) && 'file-input-error',
|
|
65
|
+
className,
|
|
66
|
+
]}
|
|
67
|
+
/>
|
|
68
|
+
<FieldErrors errors={tooLarge ? [tooLargeMessage] : []} id={tooLargeId} />
|
|
@@ -0,0 +1,39 @@
|
|
|
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; `undefined` when the control is empty. */
|
|
5
|
+
file?: File | undefined;
|
|
6
|
+
/** `accept` attribute value. */
|
|
7
|
+
accept?: string | undefined;
|
|
8
|
+
/** Largest file to accept, in bytes. Omitted, there is no cap. */
|
|
9
|
+
maxBytes?: number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Shown when the chosen file exceeds `maxBytes`.
|
|
12
|
+
* @defaultValue `'That file is too large.'`
|
|
13
|
+
*/
|
|
14
|
+
tooLargeMessage?: string | undefined;
|
|
15
|
+
/** Called with the accepted file, or `undefined` when the choice cleared it. */
|
|
16
|
+
onSelect?: ((file: File | undefined) => void) | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* daisyUI size step.
|
|
19
|
+
* @defaultValue `'md'`
|
|
20
|
+
*/
|
|
21
|
+
size?: FieldSize | undefined;
|
|
22
|
+
/** `FieldControl`: the field is in error — sets `aria-invalid` and the error style. */
|
|
23
|
+
invalid?: boolean | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* `FieldControl`: space-separated ids for `aria-describedby`.
|
|
26
|
+
* @defaultValue `''`
|
|
27
|
+
*/
|
|
28
|
+
describedBy?: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* `FieldControl`: the visible label's id. Not used — this control is
|
|
31
|
+
* labelable, so the `FormField`'s `<label for>` already names it; taken
|
|
32
|
+
* here so a spread `FieldControl` does not leak it as an attribute.
|
|
33
|
+
*/
|
|
34
|
+
labelId?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** A native file input in daisyUI `file-input` styling, with the chosen `File` bindable and a client-side size cap. */
|
|
37
|
+
declare const FileInput: import("svelte").Component<Props, {}, "file">;
|
|
38
|
+
type FileInput = ReturnType<typeof FileInput>;
|
|
39
|
+
export default FileInput;
|