@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,320 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
A pan-and-zoom SVG surface over a `VectorDocument`, with each element drawn by a renderer snippet the application supplies.
|
|
3
|
+
-->
|
|
4
|
+
<!--
|
|
5
|
+
VectorCanvas — a pan-and-zoom SVG surface over a {@link VectorDocument}, with
|
|
6
|
+
the elements drawn by a renderer the application supplies.
|
|
7
|
+
|
|
8
|
+
It owns the viewport and the pointer plumbing; it owns nothing about what the
|
|
9
|
+
elements MEAN. Selection, creation and dragging are the application's, reached
|
|
10
|
+
through `oninteract` — which is why a floor-plan editor and a planting plan
|
|
11
|
+
can share this without either one's vocabulary leaking into it.
|
|
12
|
+
|
|
13
|
+
## ARIA contract
|
|
14
|
+
|
|
15
|
+
The `<svg>` is `role="application"` named by `label` (default "Vector editing
|
|
16
|
+
canvas"). `application` is the honest role for a surface that interprets its
|
|
17
|
+
own input — it tells a screen reader to pass keys through rather than to read
|
|
18
|
+
the SVG as a picture — and it is a promise that the surface HANDLES those
|
|
19
|
+
keys, which § Keys keeps.
|
|
20
|
+
|
|
21
|
+
## Keys
|
|
22
|
+
|
|
23
|
+
When `interactive`, the `<svg>` is one Tab stop, and every keydown and keyup
|
|
24
|
+
on it goes to `onkey` FIRST. What a key means to the elements is the
|
|
25
|
+
application's, for the same reason a pointer gesture is. Only a key the
|
|
26
|
+
application left alone (did not `preventDefault`), and not a Ctrl, Alt or
|
|
27
|
+
Meta chord, falls through to the two bindings this component owns, because
|
|
28
|
+
it owns the viewport:
|
|
29
|
+
|
|
30
|
+
| Key | Does |
|
|
31
|
+
| ----------------------- | -------------------------------------------- |
|
|
32
|
+
| Arrow keys (`pan` tool) | pan by 40 screen pixels; Shift for 160 |
|
|
33
|
+
| `+` / `=`, `-` | zoom in / out about the centre of the view |
|
|
34
|
+
|
|
35
|
+
The application says which of its elements is focused through
|
|
36
|
+
`activedescendant` (an element id inside the surface), so DOM focus stays on
|
|
37
|
+
the `<svg>` and the arrow keys never scroll the page out from under the
|
|
38
|
+
reader.
|
|
39
|
+
|
|
40
|
+
## Two decisions worth stating
|
|
41
|
+
|
|
42
|
+
- The document prop is `doc`, not `document`: a prop called `document` shadows
|
|
43
|
+
the global inside a component that also reaches for the DOM, which is a bug
|
|
44
|
+
waiting for the next line of code.
|
|
45
|
+
- The viewport is REPORTED through `onviewportchange` rather than mutated
|
|
46
|
+
through `$bindable`, so the document stays the caller's to own and an undo
|
|
47
|
+
stack has something to undo.
|
|
48
|
+
|
|
49
|
+
Pointer events rather than mouse events: one code path covers mouse, touch and
|
|
50
|
+
pen, and `setPointerCapture` keeps a drag alive past the edge of the canvas.
|
|
51
|
+
|
|
52
|
+
Hand-rolled rather than built on a bits-ui primitive because there is no
|
|
53
|
+
primitive for this: bits-ui owns focus, keyboard and ARIA relationships
|
|
54
|
+
between named widgets, and a pan/zoom surface is none of those — it is raw
|
|
55
|
+
pointer geometry.
|
|
56
|
+
-->
|
|
57
|
+
<script lang="ts" generics="T extends CanvasElement">import { panBy, screenToCanvas, snapToGrid, sortByZIndex, zoomAbout } from "./utils.js";
|
|
58
|
+
let { doc, selected = new Set(), tool = "select", width = 800, height = 600, element, interactive = true, label = "Vector editing canvas", activedescendant = "", describedby = "", overlay, onkey, onfocuschange, onviewportchange, oninteract, class: className, ...rest } = $props();
|
|
59
|
+
// Scoped to this instance: two canvases on one page would otherwise share a
|
|
60
|
+
// `<pattern id>`, and the second would paint the first's grid.
|
|
61
|
+
const uid = $props.id();
|
|
62
|
+
const gridPatternId = `vector-canvas-grid-${uid}`;
|
|
63
|
+
let surface = $state();
|
|
64
|
+
/** -1 is "no pointer down"; the id is what `setPointerCapture` released. */
|
|
65
|
+
let dragging = $state(-1);
|
|
66
|
+
let panning = $state(false);
|
|
67
|
+
/**
|
|
68
|
+
* The pointer's last SCREEN position, so a pan is the difference between two
|
|
69
|
+
* positions we recorded. `MouseEvent.movementX` would say the same thing in
|
|
70
|
+
* one property, but its value is scaled by the device pixel ratio in some
|
|
71
|
+
* browsers and zeroed after a coalesced move in others — a pan that drifts
|
|
72
|
+
* on one machine and not another is not worth the line it saves.
|
|
73
|
+
*/
|
|
74
|
+
let lastScreen = $state({
|
|
75
|
+
x: 0,
|
|
76
|
+
y: 0
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Chromium's own double-click interval and its slop, in milliseconds and CSS
|
|
80
|
+
* pixels.
|
|
81
|
+
*/
|
|
82
|
+
const MULTI_PRESS_MS = 500;
|
|
83
|
+
const MULTI_PRESS_SLOP = 4;
|
|
84
|
+
/** The run of rapid presses at one spot: 1, then 2, then 3… */
|
|
85
|
+
let pressCount = 0;
|
|
86
|
+
let lastPress = {
|
|
87
|
+
time: 0,
|
|
88
|
+
x: 0,
|
|
89
|
+
y: 0
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The press count, counted HERE rather than read off `event.detail`.
|
|
93
|
+
*
|
|
94
|
+
* `MouseEvent.detail` carries the click count; `PointerEvent.detail` is 0,
|
|
95
|
+
* always, in every browser — and a component that listens on pointer events
|
|
96
|
+
* to cover mouse, touch and pen alike therefore never sees it. That is easy
|
|
97
|
+
* to miss, because a synthetic `pointerdown` dispatched by a test carries
|
|
98
|
+
* whatever `detail` the test puts on it: the check reads as working and does
|
|
99
|
+
* nothing at all in a browser. Counting it here also makes a double-TAP
|
|
100
|
+
* count, which `detail` would not have done on any input.
|
|
101
|
+
*/
|
|
102
|
+
const countPress = (event) => {
|
|
103
|
+
const rapid = event.timeStamp - lastPress.time < MULTI_PRESS_MS && Math.abs(event.clientX - lastPress.x) <= MULTI_PRESS_SLOP && Math.abs(event.clientY - lastPress.y) <= MULTI_PRESS_SLOP;
|
|
104
|
+
pressCount = rapid ? pressCount + 1 : 1;
|
|
105
|
+
lastPress = {
|
|
106
|
+
time: event.timeStamp,
|
|
107
|
+
x: event.clientX,
|
|
108
|
+
y: event.clientY
|
|
109
|
+
};
|
|
110
|
+
return pressCount;
|
|
111
|
+
};
|
|
112
|
+
const ordered = $derived(sortByZIndex(doc.elements));
|
|
113
|
+
const at = (event) => {
|
|
114
|
+
if (surface === undefined) return {
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0
|
|
117
|
+
};
|
|
118
|
+
const box = surface.getBoundingClientRect();
|
|
119
|
+
const point = screenToCanvas({
|
|
120
|
+
x: event.clientX - box.left,
|
|
121
|
+
y: event.clientY - box.top
|
|
122
|
+
}, doc.viewport);
|
|
123
|
+
return doc.grid.snap ? snapToGrid(point, doc.grid.size) : point;
|
|
124
|
+
};
|
|
125
|
+
const down = (event) => {
|
|
126
|
+
if (!interactive) return;
|
|
127
|
+
// Capture on the surface so a drag that leaves the element keeps reporting.
|
|
128
|
+
// Guarded rather than called bare: a test DOM (and an old browser) can
|
|
129
|
+
// have the element without the method, and a TypeError here would take
|
|
130
|
+
// the whole gesture with it.
|
|
131
|
+
if (typeof surface?.setPointerCapture === "function") surface.setPointerCapture(event.pointerId);
|
|
132
|
+
dragging = event.pointerId;
|
|
133
|
+
panning = tool === "pan";
|
|
134
|
+
lastScreen = {
|
|
135
|
+
x: event.clientX,
|
|
136
|
+
y: event.clientY
|
|
137
|
+
};
|
|
138
|
+
const detail = countPress(event);
|
|
139
|
+
if (!panning) oninteract?.({
|
|
140
|
+
phase: "down",
|
|
141
|
+
tool,
|
|
142
|
+
point: at(event),
|
|
143
|
+
detail,
|
|
144
|
+
native: event
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const move = (event) => {
|
|
148
|
+
if (!interactive || dragging !== event.pointerId) return;
|
|
149
|
+
if (panning) {
|
|
150
|
+
onviewportchange?.(panBy(doc.viewport, event.clientX - lastScreen.x, event.clientY - lastScreen.y));
|
|
151
|
+
lastScreen = {
|
|
152
|
+
x: event.clientX,
|
|
153
|
+
y: event.clientY
|
|
154
|
+
};
|
|
155
|
+
} else oninteract?.({
|
|
156
|
+
phase: "move",
|
|
157
|
+
tool,
|
|
158
|
+
point: at(event),
|
|
159
|
+
detail: 1,
|
|
160
|
+
native: event
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
const up = (event) => {
|
|
164
|
+
if (!interactive || dragging !== event.pointerId) return;
|
|
165
|
+
if (typeof surface?.releasePointerCapture === "function") {
|
|
166
|
+
surface.releasePointerCapture(event.pointerId);
|
|
167
|
+
}
|
|
168
|
+
if (!panning) oninteract?.({
|
|
169
|
+
phase: "up",
|
|
170
|
+
tool,
|
|
171
|
+
point: at(event),
|
|
172
|
+
detail: 1,
|
|
173
|
+
native: event
|
|
174
|
+
});
|
|
175
|
+
dragging = -1;
|
|
176
|
+
panning = false;
|
|
177
|
+
};
|
|
178
|
+
const wheel = (event) => {
|
|
179
|
+
if (!interactive || surface === undefined) return;
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
const box = surface.getBoundingClientRect();
|
|
182
|
+
const pointer = {
|
|
183
|
+
x: event.clientX - box.left,
|
|
184
|
+
y: event.clientY - box.top
|
|
185
|
+
};
|
|
186
|
+
onviewportchange?.(zoomAbout(doc.viewport, pointer, event.deltaY > 0 ? .9 : 1.1));
|
|
187
|
+
};
|
|
188
|
+
/** Screen pixels one arrow press pans by with the `pan` tool; Shift multiplies it. */
|
|
189
|
+
const PAN_STEP = 40;
|
|
190
|
+
const COARSE_PAN = 4;
|
|
191
|
+
/** Which way the view moves for each arrow: right reveals more of the right. */
|
|
192
|
+
const PAN_DIRECTIONS = new Map([
|
|
193
|
+
["ArrowLeft", {
|
|
194
|
+
x: 1,
|
|
195
|
+
y: 0
|
|
196
|
+
}],
|
|
197
|
+
["ArrowRight", {
|
|
198
|
+
x: -1,
|
|
199
|
+
y: 0
|
|
200
|
+
}],
|
|
201
|
+
["ArrowUp", {
|
|
202
|
+
x: 0,
|
|
203
|
+
y: 1
|
|
204
|
+
}],
|
|
205
|
+
["ArrowDown", {
|
|
206
|
+
x: 0,
|
|
207
|
+
y: -1
|
|
208
|
+
}]
|
|
209
|
+
]);
|
|
210
|
+
/** The zoom factor per key; `=` is `+` without the Shift. */
|
|
211
|
+
const ZOOM_KEYS = new Map([
|
|
212
|
+
["+", 1.1],
|
|
213
|
+
["=", 1.1],
|
|
214
|
+
["-", .9]
|
|
215
|
+
]);
|
|
216
|
+
/** The view's own keys, for whatever the application did not claim. */
|
|
217
|
+
const viewKey = (event) => {
|
|
218
|
+
const factor = ZOOM_KEYS.get(event.key);
|
|
219
|
+
if (factor !== undefined) {
|
|
220
|
+
onviewportchange?.(zoomAbout(doc.viewport, {
|
|
221
|
+
x: width / 2,
|
|
222
|
+
y: height / 2
|
|
223
|
+
}, factor));
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const direction = PAN_DIRECTIONS.get(event.key);
|
|
227
|
+
if (tool !== "pan" || direction === undefined) return false;
|
|
228
|
+
const step = PAN_STEP * (event.shiftKey ? COARSE_PAN : 1);
|
|
229
|
+
onviewportchange?.(panBy(doc.viewport, direction.x * step, direction.y * step));
|
|
230
|
+
return true;
|
|
231
|
+
};
|
|
232
|
+
const key = (event) => {
|
|
233
|
+
if (!interactive) return;
|
|
234
|
+
onkey?.(event);
|
|
235
|
+
if (event.type !== "keydown" || event.defaultPrevented) return;
|
|
236
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
237
|
+
if (viewKey(event)) event.preventDefault();
|
|
238
|
+
};
|
|
239
|
+
</script>
|
|
240
|
+
|
|
241
|
+
<div
|
|
242
|
+
class={['bg-base-100 relative overflow-hidden select-none', className]}
|
|
243
|
+
style:width="{width}px"
|
|
244
|
+
style:height="{height}px"
|
|
245
|
+
style:touch-action="none"
|
|
246
|
+
>
|
|
247
|
+
<!-- Svelte's a11y table files `application` under the non-interactive roles;
|
|
248
|
+
ARIA defines it as the role that takes focus and handles its own keys,
|
|
249
|
+
which is exactly what the tabindex and the key handlers are for. -->
|
|
250
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
251
|
+
<svg
|
|
252
|
+
{...rest}
|
|
253
|
+
bind:this={surface}
|
|
254
|
+
{width}
|
|
255
|
+
{height}
|
|
256
|
+
role="application"
|
|
257
|
+
aria-label={label}
|
|
258
|
+
aria-activedescendant={activedescendant === '' ? undefined : activedescendant}
|
|
259
|
+
aria-describedby={describedby === '' ? undefined : describedby}
|
|
260
|
+
tabindex={interactive ? 0 : undefined}
|
|
261
|
+
class="focus-visible:outline-primary cursor-crosshair focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2"
|
|
262
|
+
onkeydown={key}
|
|
263
|
+
onkeyup={key}
|
|
264
|
+
onfocus={() => onfocuschange?.(true)}
|
|
265
|
+
onblur={() => onfocuschange?.(false)}
|
|
266
|
+
onpointerdown={down}
|
|
267
|
+
onpointermove={move}
|
|
268
|
+
onpointerup={up}
|
|
269
|
+
onpointercancel={up}
|
|
270
|
+
onwheel={wheel}
|
|
271
|
+
>
|
|
272
|
+
<defs>
|
|
273
|
+
<pattern
|
|
274
|
+
id={gridPatternId}
|
|
275
|
+
width={doc.grid.size}
|
|
276
|
+
height={doc.grid.size}
|
|
277
|
+
patternUnits="userSpaceOnUse"
|
|
278
|
+
>
|
|
279
|
+
<path
|
|
280
|
+
d="M {doc.grid.size} 0 L 0 0 0 {doc.grid.size}"
|
|
281
|
+
fill="none"
|
|
282
|
+
stroke={doc.grid.color}
|
|
283
|
+
stroke-width="0.5"
|
|
284
|
+
/>
|
|
285
|
+
</pattern>
|
|
286
|
+
</defs>
|
|
287
|
+
|
|
288
|
+
<g
|
|
289
|
+
transform="translate({doc.viewport.offsetX},{doc.viewport.offsetY}) scale({doc.viewport.zoom})"
|
|
290
|
+
>
|
|
291
|
+
{#if doc.grid.visible}
|
|
292
|
+
<!-- Sized to swamp any reachable pan rather than tracked to the
|
|
293
|
+
viewport: a rect that has to be recomputed on every pan is a
|
|
294
|
+
reactive dependency for a decoration. -->
|
|
295
|
+
<rect x={-10000} y={-10000} width={20000} height={20000} fill="url(#{gridPatternId})" />
|
|
296
|
+
{/if}
|
|
297
|
+
|
|
298
|
+
{#each ordered as node (node.id)}
|
|
299
|
+
<g opacity={node.visible === false ? 0 : node.opacity}>
|
|
300
|
+
{#if element}
|
|
301
|
+
{@render element(node, selected.has(node.id))}
|
|
302
|
+
{:else}
|
|
303
|
+
<!-- The fallback is a marker, not a drawing: an application that
|
|
304
|
+
has not supplied a renderer should SEE that it has not. -->
|
|
305
|
+
<circle
|
|
306
|
+
cx={node.position.x}
|
|
307
|
+
cy={node.position.y}
|
|
308
|
+
r="5"
|
|
309
|
+
fill={node.fillColor}
|
|
310
|
+
stroke={selected.has(node.id) ? 'currentColor' : node.strokeColor}
|
|
311
|
+
stroke-width={node.strokeWidth}
|
|
312
|
+
/>
|
|
313
|
+
{/if}
|
|
314
|
+
</g>
|
|
315
|
+
{/each}
|
|
316
|
+
|
|
317
|
+
{@render overlay?.()}
|
|
318
|
+
</g>
|
|
319
|
+
</svg>
|
|
320
|
+
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { SVGAttributes } from 'svelte/elements';
|
|
3
|
+
import type { CanvasElement, Point, VectorDocument, Viewport } from './types.ts';
|
|
4
|
+
declare function $$render<T extends CanvasElement>(): {
|
|
5
|
+
props: Omit<SVGAttributes<SVGSVGElement>, "class" | "onfocus" | "onblur" | "onkeydown" | "onkeyup" | "height" | "width"> & {
|
|
6
|
+
/** The document to draw: its elements, viewport and grid. */
|
|
7
|
+
doc: VectorDocument<T>;
|
|
8
|
+
/**
|
|
9
|
+
* Which elements the application considers selected.
|
|
10
|
+
* @defaultValue an empty set
|
|
11
|
+
*/
|
|
12
|
+
selected?: ReadonlySet<string> | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* The application's active tool; `pan` is the one this component handles.
|
|
15
|
+
* @defaultValue `'select'`
|
|
16
|
+
*/
|
|
17
|
+
tool?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Viewport size in CSS pixels.
|
|
20
|
+
* @defaultValue `800`
|
|
21
|
+
*/
|
|
22
|
+
width?: number | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Viewport height in CSS pixels.
|
|
25
|
+
* @defaultValue `600`
|
|
26
|
+
*/
|
|
27
|
+
height?: number | undefined;
|
|
28
|
+
/** Draws one element, told whether it is selected. */
|
|
29
|
+
element?: Snippet<[T, boolean]> | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Whether pointer input is accepted at all.
|
|
32
|
+
* @defaultValue `true`
|
|
33
|
+
*/
|
|
34
|
+
interactive?: boolean | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Names the surface for assistive tech.
|
|
37
|
+
* @defaultValue `'Vector editing canvas'`
|
|
38
|
+
*/
|
|
39
|
+
label?: string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The id of the element that has keyboard focus inside the surface, for
|
|
42
|
+
* `aria-activedescendant`; `''` for none.
|
|
43
|
+
* @defaultValue `''`
|
|
44
|
+
*/
|
|
45
|
+
activedescendant?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The id of an element describing the surface's keys, for
|
|
48
|
+
* `aria-describedby`; `''` for none.
|
|
49
|
+
* @defaultValue `''`
|
|
50
|
+
*/
|
|
51
|
+
describedby?: string | undefined;
|
|
52
|
+
/** Drawn above every element, in canvas coordinates: a cursor, a guide. */
|
|
53
|
+
overlay?: Snippet<[]> | undefined;
|
|
54
|
+
/** Every keydown and keyup on the surface, before its own keys; `preventDefault` claims the key. */
|
|
55
|
+
onkey?: ((event: KeyboardEvent) => void) | undefined;
|
|
56
|
+
/** Whether the surface has keyboard focus, each time that changes. */
|
|
57
|
+
onfocuschange?: ((focused: boolean) => void) | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* The viewport after a pan or a zoom.
|
|
60
|
+
*
|
|
61
|
+
* `| undefined` on every callback here and below is the `$props()` seam
|
|
62
|
+
* under `exactOptionalPropertyTypes`: a caller holding an optional handler
|
|
63
|
+
* spreads it straight through, and `{...maybe}` hands over an explicit
|
|
64
|
+
* `undefined` that a bare `?:` would refuse.
|
|
65
|
+
*/
|
|
66
|
+
onviewportchange?: ((viewport: Viewport) => void) | undefined;
|
|
67
|
+
/** A pointer gesture, in canvas coordinates, snapped when the grid says so. */
|
|
68
|
+
oninteract?: ((event: {
|
|
69
|
+
readonly phase: "down" | "move" | "up";
|
|
70
|
+
readonly tool: string;
|
|
71
|
+
readonly point: Point;
|
|
72
|
+
/**
|
|
73
|
+
* How many presses this one is in a run of rapid presses at the same
|
|
74
|
+
* spot: 1 for a single press, 2 for the second of a double-press.
|
|
75
|
+
* Always 1 on `move` and `up`.
|
|
76
|
+
*/
|
|
77
|
+
readonly detail: number;
|
|
78
|
+
readonly native: PointerEvent;
|
|
79
|
+
}) => void) | undefined;
|
|
80
|
+
/** Merged onto the sized box that clips the surface, caller's last. */
|
|
81
|
+
class?: string | undefined;
|
|
82
|
+
};
|
|
83
|
+
exports: {};
|
|
84
|
+
bindings: "";
|
|
85
|
+
slots: {};
|
|
86
|
+
events: {};
|
|
87
|
+
};
|
|
88
|
+
declare class __sveltets_Render<T extends CanvasElement> {
|
|
89
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
90
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
91
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
92
|
+
bindings(): "";
|
|
93
|
+
exports(): {};
|
|
94
|
+
}
|
|
95
|
+
interface $$IsomorphicComponent {
|
|
96
|
+
new <T extends CanvasElement>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
97
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
98
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
99
|
+
<T extends CanvasElement>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
100
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
101
|
+
}
|
|
102
|
+
/** A pan-and-zoom SVG surface over a `VectorDocument`, with each element drawn by a renderer snippet the application supplies. */
|
|
103
|
+
declare const VectorCanvas: $$IsomorphicComponent;
|
|
104
|
+
type VectorCanvas<T extends CanvasElement> = InstanceType<typeof VectorCanvas<T>>;
|
|
105
|
+
export default VectorCanvas;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Undo and redo over a {@link VectorDocument}, as pure functions on the
|
|
3
|
+
* {@link Transform} and {@link HistoryEntry} types `./types.ts` already
|
|
4
|
+
* declares.
|
|
5
|
+
*
|
|
6
|
+
* Those two types were written for exactly this; `Transform`'s own comment is
|
|
7
|
+
* the specification — "carrying the previous elements alongside the new ones is
|
|
8
|
+
* what makes a transform invertible — undo is the same structure read
|
|
9
|
+
* backwards, rather than a second code path". This module takes it at its word,
|
|
10
|
+
* so there is no second structure to keep in step with the first.
|
|
11
|
+
*
|
|
12
|
+
* **Transforms, not snapshots.** A history of whole documents is bounded by the
|
|
13
|
+
* size of the PLAN; a history of transforms is bounded by the size of the
|
|
14
|
+
* EDITS. A plan with four hundred elements would otherwise cost four hundred
|
|
15
|
+
* elements per keystroke.
|
|
16
|
+
*
|
|
17
|
+
* @module @thomasfosterau/components/canvas/history
|
|
18
|
+
*/
|
|
19
|
+
import type { CanvasElement, HistoryEntry, Transform, VectorDocument } from './types.ts';
|
|
20
|
+
/**
|
|
21
|
+
* How many steps back an editing session can walk.
|
|
22
|
+
*
|
|
23
|
+
* Capped rather than unbounded: a plan editor is a long-lived page, and an
|
|
24
|
+
* unbounded stack is a leak that only shows up in the session that matters. A
|
|
25
|
+
* hundred is well past what a reader walks back by hand.
|
|
26
|
+
*/
|
|
27
|
+
export declare const HISTORY_LIMIT = 100;
|
|
28
|
+
/** The undo and redo stacks; the caller owns them, as it owns the document. */
|
|
29
|
+
export interface History<T extends CanvasElement = CanvasElement> {
|
|
30
|
+
/** Oldest first; the last entry is the one `undo` takes back. */
|
|
31
|
+
readonly past: readonly HistoryEntry<T>[];
|
|
32
|
+
/** Nearest first; the first entry is the one `redo` puts back. */
|
|
33
|
+
readonly future: readonly HistoryEntry<T>[];
|
|
34
|
+
}
|
|
35
|
+
/** A session with nothing to undo. */
|
|
36
|
+
export declare const emptyHistory: <T extends CanvasElement>() => History<T>;
|
|
37
|
+
/**
|
|
38
|
+
* A transform's meaning, for every one of the four `type`s:
|
|
39
|
+
* **every id in `elementIds` ends as its entry in `elements`, or absent when
|
|
40
|
+
* `elements` has none for it.**
|
|
41
|
+
*
|
|
42
|
+
* `add`, `update` and `delete` are the narrow cases of that one rule (absent
|
|
43
|
+
* before, present both, present before) and `batch` is the mixed one. Naming
|
|
44
|
+
* them separately is for the reader and for a history entry's `description`;
|
|
45
|
+
* the arithmetic below does not branch on `type` at all, which is why a
|
|
46
|
+
* mixed `batch` cannot be the case somebody forgot to handle.
|
|
47
|
+
*
|
|
48
|
+
* `previousElements` is not read here — it is what {@link invertTransform}
|
|
49
|
+
* needs, and reading it would make applying a transform depend on the document
|
|
50
|
+
* it was recorded against still being the current one.
|
|
51
|
+
*
|
|
52
|
+
* A re-added element is APPENDED rather than restored to its old index. Paint
|
|
53
|
+
* order is `zIndex`'s job (`sortByZIndex`), so the array's order carries no
|
|
54
|
+
* meaning to preserve.
|
|
55
|
+
*/
|
|
56
|
+
export declare const applyTransform: <T extends CanvasElement>(doc: VectorDocument<T>, transform: Transform<T>) => VectorDocument<T>;
|
|
57
|
+
/**
|
|
58
|
+
* The transform that undoes this one: the two element lists swapped, and
|
|
59
|
+
* `add` and `delete` exchanged so a history entry still reads as what it did.
|
|
60
|
+
*
|
|
61
|
+
* Both lists are written as arrays even when the original omitted one, so the
|
|
62
|
+
* result is a transform whose own inverse is the original — `invert(invert(t))`
|
|
63
|
+
* differs from `t` only in carrying `[]` where `t` carried nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare const invertTransform: <T extends CanvasElement>(transform: Transform<T>) => Transform<T>;
|
|
66
|
+
/**
|
|
67
|
+
* The history with one edit pushed on.
|
|
68
|
+
*
|
|
69
|
+
* Recording CLEARS the redo stack, which is the standard rule and the only
|
|
70
|
+
* honest one: once the document has diverged, the futures recorded against the
|
|
71
|
+
* old one describe a document that no longer exists.
|
|
72
|
+
*
|
|
73
|
+
* `timestamp` is a parameter rather than a `Date.now()` call inside, so this
|
|
74
|
+
* stays a pure function a test can pin.
|
|
75
|
+
*/
|
|
76
|
+
export declare const recordEdit: <T extends CanvasElement>(history: History<T>, transform: Transform<T>, description?: string, timestamp?: number) => History<T>;
|
|
77
|
+
/**
|
|
78
|
+
* The transform from one set of elements to another, both keyed by id.
|
|
79
|
+
*
|
|
80
|
+
* This is how a component that has just changed a document reports WHAT it
|
|
81
|
+
* changed without keeping a parallel log: snapshot the elements a gesture is
|
|
82
|
+
* about when it starts, read them again when it ends, and the difference is the
|
|
83
|
+
* history entry. A gesture that touched one element costs one element, however
|
|
84
|
+
* many pointer moves it took.
|
|
85
|
+
*
|
|
86
|
+
* The `type` is named from the two sets — absent before is an `add`, absent
|
|
87
|
+
* after a `delete`, a mix a `batch` — so an entry's `type` still reads as what
|
|
88
|
+
* the reader did even though {@link applyTransform} does not branch on it.
|
|
89
|
+
*/
|
|
90
|
+
export declare const diffTransform: <T extends CanvasElement>(before: readonly T[], after: readonly T[]) => Transform<T>;
|
|
91
|
+
/** A document and the history that goes with it, after a step in either direction. */
|
|
92
|
+
export interface HistoryStep<T extends CanvasElement = CanvasElement> {
|
|
93
|
+
readonly doc: VectorDocument<T>;
|
|
94
|
+
readonly history: History<T>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The document one step back, or nothing when there is nothing to undo.
|
|
98
|
+
*
|
|
99
|
+
* None-or-one rather than a nullable result, for `boundingBox`'s reason: the
|
|
100
|
+
* caller writes `const [step] = undo(...)` and the empty case needs no
|
|
101
|
+
* sentinel to remember to check.
|
|
102
|
+
*/
|
|
103
|
+
export declare const undo: <T extends CanvasElement>(doc: VectorDocument<T>, history: History<T>) => readonly HistoryStep<T>[];
|
|
104
|
+
/** The document one step forward, or nothing when there is nothing to redo. */
|
|
105
|
+
export declare const redo: <T extends CanvasElement>(doc: VectorDocument<T>, history: History<T>) => readonly HistoryStep<T>[];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Undo and redo over a {@link VectorDocument}, as pure functions on the
|
|
3
|
+
* {@link Transform} and {@link HistoryEntry} types `./types.ts` already
|
|
4
|
+
* declares.
|
|
5
|
+
*
|
|
6
|
+
* Those two types were written for exactly this; `Transform`'s own comment is
|
|
7
|
+
* the specification — "carrying the previous elements alongside the new ones is
|
|
8
|
+
* what makes a transform invertible — undo is the same structure read
|
|
9
|
+
* backwards, rather than a second code path". This module takes it at its word,
|
|
10
|
+
* so there is no second structure to keep in step with the first.
|
|
11
|
+
*
|
|
12
|
+
* **Transforms, not snapshots.** A history of whole documents is bounded by the
|
|
13
|
+
* size of the PLAN; a history of transforms is bounded by the size of the
|
|
14
|
+
* EDITS. A plan with four hundred elements would otherwise cost four hundred
|
|
15
|
+
* elements per keystroke.
|
|
16
|
+
*
|
|
17
|
+
* @module @thomasfosterau/components/canvas/history
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* How many steps back an editing session can walk.
|
|
21
|
+
*
|
|
22
|
+
* Capped rather than unbounded: a plan editor is a long-lived page, and an
|
|
23
|
+
* unbounded stack is a leak that only shows up in the session that matters. A
|
|
24
|
+
* hundred is well past what a reader walks back by hand.
|
|
25
|
+
*/
|
|
26
|
+
export const HISTORY_LIMIT = 100;
|
|
27
|
+
/** A session with nothing to undo. */
|
|
28
|
+
export const emptyHistory = () => ({ past: [], future: [] });
|
|
29
|
+
/**
|
|
30
|
+
* A transform's meaning, for every one of the four `type`s:
|
|
31
|
+
* **every id in `elementIds` ends as its entry in `elements`, or absent when
|
|
32
|
+
* `elements` has none for it.**
|
|
33
|
+
*
|
|
34
|
+
* `add`, `update` and `delete` are the narrow cases of that one rule (absent
|
|
35
|
+
* before, present both, present before) and `batch` is the mixed one. Naming
|
|
36
|
+
* them separately is for the reader and for a history entry's `description`;
|
|
37
|
+
* the arithmetic below does not branch on `type` at all, which is why a
|
|
38
|
+
* mixed `batch` cannot be the case somebody forgot to handle.
|
|
39
|
+
*
|
|
40
|
+
* `previousElements` is not read here — it is what {@link invertTransform}
|
|
41
|
+
* needs, and reading it would make applying a transform depend on the document
|
|
42
|
+
* it was recorded against still being the current one.
|
|
43
|
+
*
|
|
44
|
+
* A re-added element is APPENDED rather than restored to its old index. Paint
|
|
45
|
+
* order is `zIndex`'s job (`sortByZIndex`), so the array's order carries no
|
|
46
|
+
* meaning to preserve.
|
|
47
|
+
*/
|
|
48
|
+
export const applyTransform = (doc, transform) => {
|
|
49
|
+
const touched = new Set(transform.elementIds);
|
|
50
|
+
const next = new Map((transform.elements ?? []).map((element) => [element.id, element]));
|
|
51
|
+
const present = new Set(doc.elements.map((element) => element.id));
|
|
52
|
+
const kept = doc.elements.flatMap((element) => {
|
|
53
|
+
if (!touched.has(element.id))
|
|
54
|
+
return [element];
|
|
55
|
+
const replacement = next.get(element.id);
|
|
56
|
+
return replacement === undefined ? [] : [replacement];
|
|
57
|
+
});
|
|
58
|
+
const added = (transform.elements ?? []).filter((element) => !present.has(element.id));
|
|
59
|
+
return { ...doc, elements: [...kept, ...added] };
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The transform that undoes this one: the two element lists swapped, and
|
|
63
|
+
* `add` and `delete` exchanged so a history entry still reads as what it did.
|
|
64
|
+
*
|
|
65
|
+
* Both lists are written as arrays even when the original omitted one, so the
|
|
66
|
+
* result is a transform whose own inverse is the original — `invert(invert(t))`
|
|
67
|
+
* differs from `t` only in carrying `[]` where `t` carried nothing.
|
|
68
|
+
*/
|
|
69
|
+
export const invertTransform = (transform) => ({
|
|
70
|
+
type: transform.type === 'add' ? 'delete' : transform.type === 'delete' ? 'add' : transform.type,
|
|
71
|
+
elementIds: transform.elementIds,
|
|
72
|
+
elements: transform.previousElements ?? [],
|
|
73
|
+
previousElements: transform.elements ?? [],
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* The history with one edit pushed on.
|
|
77
|
+
*
|
|
78
|
+
* Recording CLEARS the redo stack, which is the standard rule and the only
|
|
79
|
+
* honest one: once the document has diverged, the futures recorded against the
|
|
80
|
+
* old one describe a document that no longer exists.
|
|
81
|
+
*
|
|
82
|
+
* `timestamp` is a parameter rather than a `Date.now()` call inside, so this
|
|
83
|
+
* stays a pure function a test can pin.
|
|
84
|
+
*/
|
|
85
|
+
export const recordEdit = (history, transform, description = '', timestamp = Date.now()) => {
|
|
86
|
+
const entry = {
|
|
87
|
+
transform,
|
|
88
|
+
timestamp,
|
|
89
|
+
...(description === '' ? {} : { description }),
|
|
90
|
+
};
|
|
91
|
+
const past = [...history.past, entry];
|
|
92
|
+
return { past: past.slice(Math.max(0, past.length - HISTORY_LIMIT)), future: [] };
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The transform from one set of elements to another, both keyed by id.
|
|
96
|
+
*
|
|
97
|
+
* This is how a component that has just changed a document reports WHAT it
|
|
98
|
+
* changed without keeping a parallel log: snapshot the elements a gesture is
|
|
99
|
+
* about when it starts, read them again when it ends, and the difference is the
|
|
100
|
+
* history entry. A gesture that touched one element costs one element, however
|
|
101
|
+
* many pointer moves it took.
|
|
102
|
+
*
|
|
103
|
+
* The `type` is named from the two sets — absent before is an `add`, absent
|
|
104
|
+
* after a `delete`, a mix a `batch` — so an entry's `type` still reads as what
|
|
105
|
+
* the reader did even though {@link applyTransform} does not branch on it.
|
|
106
|
+
*/
|
|
107
|
+
export const diffTransform = (before, after) => {
|
|
108
|
+
const gone = before.some((element) => !after.some((other) => other.id === element.id));
|
|
109
|
+
const fresh = after.some((element) => !before.some((other) => other.id === element.id));
|
|
110
|
+
const ids = [...new Set([...before, ...after].map((element) => element.id))];
|
|
111
|
+
return {
|
|
112
|
+
type: gone && fresh ? 'batch' : gone ? 'delete' : fresh ? 'add' : 'update',
|
|
113
|
+
elementIds: ids,
|
|
114
|
+
elements: after,
|
|
115
|
+
previousElements: before,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* The document one step back, or nothing when there is nothing to undo.
|
|
120
|
+
*
|
|
121
|
+
* None-or-one rather than a nullable result, for `boundingBox`'s reason: the
|
|
122
|
+
* caller writes `const [step] = undo(...)` and the empty case needs no
|
|
123
|
+
* sentinel to remember to check.
|
|
124
|
+
*/
|
|
125
|
+
export const undo = (doc, history) => {
|
|
126
|
+
const entry = history.past.at(-1);
|
|
127
|
+
if (entry === undefined)
|
|
128
|
+
return [];
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
doc: applyTransform(doc, invertTransform(entry.transform)),
|
|
132
|
+
history: { past: history.past.slice(0, -1), future: [entry, ...history.future] },
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
};
|
|
136
|
+
/** The document one step forward, or nothing when there is nothing to redo. */
|
|
137
|
+
export const redo = (doc, history) => {
|
|
138
|
+
const [entry, ...rest] = history.future;
|
|
139
|
+
if (entry === undefined)
|
|
140
|
+
return [];
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
doc: applyTransform(doc, entry.transform),
|
|
144
|
+
history: { past: [...history.past, entry], future: rest },
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
};
|