@svgrid/grid 1.2.34 → 2.0.1
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/README.md +6 -5
- package/dist/SvAccordion.svelte +109 -0
- package/dist/SvAccordion.svelte.d.ts +28 -0
- package/dist/SvAlert.svelte +92 -0
- package/dist/SvAlert.svelte.d.ts +27 -0
- package/dist/SvAutoComplete.svelte +47 -18
- package/dist/SvAutoComplete.svelte.d.ts +2 -5
- package/dist/SvAvatar.svelte +75 -0
- package/dist/SvAvatar.svelte.d.ts +14 -0
- package/dist/SvAvatarGroup.svelte +48 -0
- package/dist/SvAvatarGroup.svelte.d.ts +16 -0
- package/dist/SvBadge.svelte +54 -0
- package/dist/SvBadge.svelte.d.ts +23 -0
- package/dist/SvBreadcrumb.svelte +88 -0
- package/dist/SvBreadcrumb.svelte.d.ts +23 -0
- package/dist/SvButtonGroup.svelte +121 -0
- package/dist/SvButtonGroup.svelte.d.ts +16 -0
- package/dist/SvCalendar.svelte +72 -9
- package/dist/SvCalendar.svelte.d.ts +34 -1
- package/dist/SvCard.svelte +71 -0
- package/dist/SvCard.svelte.d.ts +28 -0
- package/dist/SvCarousel.svelte +134 -0
- package/dist/SvCarousel.svelte.d.ts +28 -0
- package/dist/SvCheckBox.svelte +36 -21
- package/dist/SvCheckBox.svelte.d.ts +2 -5
- package/dist/SvChip.svelte +88 -0
- package/dist/SvChip.svelte.d.ts +31 -0
- package/dist/SvCircularProgress.svelte +109 -0
- package/dist/SvCircularProgress.svelte.d.ts +31 -0
- package/dist/SvColorInput.svelte +54 -27
- package/dist/SvColorInput.svelte.d.ts +10 -6
- package/dist/SvComboBox.svelte +119 -33
- package/dist/SvComboBox.svelte.d.ts +18 -6
- package/dist/SvCommand.svelte +197 -0
- package/dist/SvCommand.svelte.d.ts +26 -0
- package/dist/SvContextMenu.svelte +96 -0
- package/dist/SvContextMenu.svelte.d.ts +25 -0
- package/dist/SvCountryInput.svelte +56 -21
- package/dist/SvCountryInput.svelte.d.ts +9 -5
- package/dist/SvDateRangeInput.svelte +256 -0
- package/dist/SvDateRangeInput.svelte.d.ts +37 -0
- package/dist/SvDateTimePicker.svelte +57 -20
- package/dist/SvDateTimePicker.svelte.d.ts +12 -4
- package/dist/SvDivider.svelte +61 -0
- package/dist/SvDivider.svelte.d.ts +22 -0
- package/dist/SvDrawer.svelte +226 -0
- package/dist/SvDrawer.svelte.d.ts +43 -0
- package/dist/SvDropDownList.svelte +114 -35
- package/dist/SvDropDownList.svelte.d.ts +7 -6
- package/dist/SvDurationInput.svelte +108 -0
- package/dist/SvDurationInput.svelte.d.ts +15 -0
- package/dist/SvEmptyState.svelte +59 -0
- package/dist/SvEmptyState.svelte.d.ts +25 -0
- package/dist/SvField.svelte +85 -0
- package/dist/SvField.svelte.d.ts +30 -0
- package/dist/SvFileUpload.svelte +169 -0
- package/dist/SvFileUpload.svelte.d.ts +24 -0
- package/dist/SvForm.svelte +7 -1
- package/dist/SvForm.svelte.d.ts +3 -0
- package/dist/SvGauge.svelte +7 -1
- package/dist/SvGauge.svelte.d.ts +11 -0
- package/dist/SvGrid.controller.svelte.d.ts +6 -0
- package/dist/SvGrid.controller.svelte.js +64 -9
- package/dist/SvGrid.css +33 -0
- package/dist/SvGrid.svelte +116 -3
- package/dist/SvGrid.types.d.ts +366 -0
- package/dist/SvGridBoard.svelte +2281 -0
- package/dist/SvGridBoard.svelte.d.ts +31 -0
- package/dist/SvGridDropdown.svelte +2 -1
- package/dist/SvGridSelect.svelte +255 -0
- package/dist/SvGridSelect.svelte.d.ts +32 -0
- package/dist/SvGroupCell.svelte +109 -0
- package/dist/SvGroupCell.svelte.d.ts +41 -0
- package/dist/SvListBox.svelte +123 -36
- package/dist/SvListBox.svelte.d.ts +12 -8
- package/dist/SvMaskedInput.svelte +53 -13
- package/dist/SvMaskedInput.svelte.d.ts +16 -6
- package/dist/SvMenu.svelte +101 -0
- package/dist/SvMenu.svelte.d.ts +26 -0
- package/dist/SvMenuList.svelte +143 -0
- package/dist/SvMenuList.svelte.d.ts +24 -0
- package/dist/SvModal.svelte +220 -0
- package/dist/SvModal.svelte.d.ts +31 -0
- package/dist/SvMultiSelect.svelte +277 -0
- package/dist/SvMultiSelect.svelte.d.ts +31 -0
- package/dist/SvNavPane.svelte +314 -0
- package/dist/SvNavPane.svelte.d.ts +59 -0
- package/dist/SvNumberInput.svelte +23 -9
- package/dist/SvNumberInput.svelte.d.ts +2 -0
- package/dist/SvOtpInput.svelte +157 -0
- package/dist/SvOtpInput.svelte.d.ts +16 -0
- package/dist/SvPagination.svelte +100 -0
- package/dist/SvPagination.svelte.d.ts +16 -0
- package/dist/SvPasswordInput.svelte +42 -12
- package/dist/SvPasswordInput.svelte.d.ts +13 -6
- package/dist/SvPhoneInput.svelte +43 -11
- package/dist/SvPhoneInput.svelte.d.ts +10 -11
- package/dist/SvPopover.svelte +127 -0
- package/dist/SvPopover.svelte.d.ts +35 -0
- package/dist/SvProgress.svelte +116 -0
- package/dist/SvProgress.svelte.d.ts +32 -0
- package/dist/SvRadioGroup.svelte +40 -22
- package/dist/SvRadioGroup.svelte.d.ts +2 -10
- package/dist/SvRating.svelte +51 -30
- package/dist/SvRating.svelte.d.ts +10 -7
- package/dist/SvRichText.svelte +192 -0
- package/dist/SvRichText.svelte.d.ts +16 -0
- package/dist/SvRowGroupPanel.svelte +147 -0
- package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
- package/dist/SvScrollArea.svelte +50 -0
- package/dist/SvScrollArea.svelte.d.ts +22 -0
- package/dist/SvSkeleton.svelte +76 -0
- package/dist/SvSkeleton.svelte.d.ts +25 -0
- package/dist/SvSlider.svelte +41 -9
- package/dist/SvSlider.svelte.d.ts +4 -4
- package/dist/SvSparkline.svelte +77 -0
- package/dist/SvSparkline.svelte.d.ts +28 -0
- package/dist/SvSplitter.svelte +138 -0
- package/dist/SvSplitter.svelte.d.ts +32 -0
- package/dist/SvStat.svelte +86 -0
- package/dist/SvStat.svelte.d.ts +30 -0
- package/dist/SvStepper.svelte +91 -0
- package/dist/SvStepper.svelte.d.ts +24 -0
- package/dist/SvSwitchButton.svelte +39 -24
- package/dist/SvSwitchButton.svelte.d.ts +2 -5
- package/dist/SvTabs.svelte +147 -28
- package/dist/SvTabs.svelte.d.ts +16 -4
- package/dist/SvTagsInput.svelte +59 -22
- package/dist/SvTagsInput.svelte.d.ts +9 -4
- package/dist/SvTextArea.svelte +113 -0
- package/dist/SvTextArea.svelte.d.ts +18 -0
- package/dist/SvTextInput.svelte +115 -0
- package/dist/SvTextInput.svelte.d.ts +20 -0
- package/dist/SvTimePicker.svelte +19 -5
- package/dist/SvTimePicker.svelte.d.ts +12 -0
- package/dist/SvTimeline.svelte +75 -0
- package/dist/SvTimeline.svelte.d.ts +24 -0
- package/dist/SvToaster.svelte +124 -0
- package/dist/SvToaster.svelte.d.ts +10 -0
- package/dist/SvToggleButton.svelte +37 -12
- package/dist/SvToggleButton.svelte.d.ts +2 -4
- package/dist/SvTooltip.svelte +113 -0
- package/dist/SvTooltip.svelte.d.ts +22 -0
- package/dist/SvTour.svelte +151 -0
- package/dist/SvTour.svelte.d.ts +25 -0
- package/dist/SvTree.svelte +286 -44
- package/dist/SvTree.svelte.d.ts +26 -9
- package/dist/SvTreeSelect.svelte +222 -0
- package/dist/SvTreeSelect.svelte.d.ts +24 -0
- package/dist/a11y/dismissable.d.ts +32 -0
- package/dist/a11y/dismissable.js +54 -0
- package/dist/a11y/focus-trap.d.ts +39 -0
- package/dist/a11y/focus-trap.js +142 -0
- package/dist/a11y/live-region.d.ts +8 -0
- package/dist/a11y/live-region.js +60 -0
- package/dist/a11y/scroll-lock.d.ts +4 -0
- package/dist/a11y/scroll-lock.js +41 -0
- package/dist/a11y.d.ts +3 -1
- package/dist/a11y.js +1 -1
- package/dist/avatar.d.ts +10 -0
- package/dist/avatar.js +22 -0
- package/dist/builtin-editors.d.ts +5 -0
- package/dist/builtin-editors.js +55 -0
- package/dist/cdn/svgrid.js +16021 -8234
- package/dist/cdn/svgrid.svelte-external.js +14542 -6764
- package/dist/columns.js +3 -2
- package/dist/core.d.ts +7 -1
- package/dist/countries.d.ts +12 -0
- package/dist/countries.js +24 -0
- package/dist/createAccordion.svelte.d.ts +71 -0
- package/dist/createAccordion.svelte.js +99 -0
- package/dist/createAutocomplete.svelte.d.ts +13 -4
- package/dist/createAutocomplete.svelte.js +11 -1
- package/dist/createButtonGroup.svelte.d.ts +62 -0
- package/dist/createButtonGroup.svelte.js +154 -0
- package/dist/createCalendar.svelte.d.ts +5 -0
- package/dist/createCalendar.svelte.js +2 -0
- package/dist/createCheckbox.svelte.d.ts +9 -17
- package/dist/createCheckbox.svelte.js +28 -1
- package/dist/createColorInput.svelte.d.ts +12 -22
- package/dist/createColorInput.svelte.js +13 -0
- package/dist/createCombobox.svelte.d.ts +16 -5
- package/dist/createCombobox.svelte.js +13 -2
- package/dist/createCountryInput.svelte.d.ts +14 -3
- package/dist/createCountryInput.svelte.js +12 -2
- package/dist/createDateTimePicker.svelte.d.ts +17 -2
- package/dist/createDateTimePicker.svelte.js +12 -2
- package/dist/createDropdownList.svelte.d.ts +14 -5
- package/dist/createDropdownList.svelte.js +50 -1
- package/dist/createFileUpload.svelte.d.ts +68 -0
- package/dist/createFileUpload.svelte.js +122 -0
- package/dist/createListbox.svelte.d.ts +10 -1
- package/dist/createListbox.svelte.js +48 -1
- package/dist/createMaskedInput.svelte.d.ts +11 -2
- package/dist/createMaskedInput.svelte.js +11 -1
- package/dist/createNumberInput.svelte.d.ts +1 -0
- package/dist/createNumberInput.svelte.js +1 -0
- package/dist/createPasswordInput.svelte.d.ts +19 -21
- package/dist/createPasswordInput.svelte.js +16 -4
- package/dist/createPhoneInput.svelte.d.ts +26 -7
- package/dist/createPhoneInput.svelte.js +24 -3
- package/dist/createPopoverSelect.svelte.d.ts +86 -0
- package/dist/createPopoverSelect.svelte.js +196 -0
- package/dist/createRadioGroup.svelte.d.ts +9 -0
- package/dist/createRadioGroup.svelte.js +11 -1
- package/dist/createRating.svelte.d.ts +11 -0
- package/dist/createRating.svelte.js +12 -1
- package/dist/createSlider.svelte.d.ts +2 -0
- package/dist/createSlider.svelte.js +16 -5
- package/dist/createSplitter.svelte.d.ts +67 -0
- package/dist/createSplitter.svelte.js +99 -0
- package/dist/createSwitch.svelte.d.ts +9 -16
- package/dist/createSwitch.svelte.js +28 -1
- package/dist/createTabs.svelte.d.ts +7 -0
- package/dist/createTabs.svelte.js +17 -2
- package/dist/createTagsInput.svelte.d.ts +13 -1
- package/dist/createTagsInput.svelte.js +12 -3
- package/dist/createToggle.svelte.d.ts +9 -0
- package/dist/createToggle.svelte.js +12 -1
- package/dist/createTree.svelte.d.ts +19 -0
- package/dist/createTree.svelte.js +108 -15
- package/dist/debounce.d.ts +11 -0
- package/dist/debounce.js +30 -0
- package/dist/duration.d.ts +15 -0
- package/dist/duration.js +51 -0
- package/dist/editor-contract.d.ts +62 -7
- package/dist/editor-contract.js +45 -5
- package/dist/editor-registry.d.ts +67 -0
- package/dist/editor-registry.js +38 -0
- package/dist/editors/cell-editors.d.ts +1 -1
- package/dist/editors/cell-editors.js +5 -0
- package/dist/fuzzy.d.ts +3 -0
- package/dist/fuzzy.js +41 -0
- package/dist/grid-select.d.ts +9 -0
- package/dist/grid-select.js +8 -0
- package/dist/index.d.ts +79 -6
- package/dist/index.js +78 -4
- package/dist/keyboard-handlers.js +19 -0
- package/dist/list-option.d.ts +36 -0
- package/dist/list-option.js +68 -0
- package/dist/otp.d.ts +16 -0
- package/dist/otp.js +26 -0
- package/dist/paginate.d.ts +20 -0
- package/dist/paginate.js +40 -0
- package/dist/popover.d.ts +15 -1
- package/dist/popover.js +29 -3
- package/dist/recurrence.d.ts +36 -0
- package/dist/recurrence.js +90 -0
- package/dist/server-data-source.d.ts +89 -0
- package/dist/server-data-source.js +4 -0
- package/dist/server-group-model.d.ts +98 -0
- package/dist/server-group-model.js +263 -0
- package/dist/toast-store.svelte.d.ts +41 -0
- package/dist/toast-store.svelte.js +113 -0
- package/dist/tree-select.d.ts +19 -0
- package/dist/tree-select.js +40 -0
- package/dist/ui-app.types.d.ts +80 -0
- package/dist/ui-app.types.js +7 -0
- package/dist/validators.d.ts +43 -0
- package/dist/validators.js +53 -0
- package/dist/virtual.d.ts +35 -0
- package/dist/virtual.js +34 -0
- package/package.json +1 -1
- package/src/SvAccordion.svelte +109 -0
- package/src/SvAccordion.test.ts +66 -0
- package/src/SvAlert.svelte +92 -0
- package/src/SvAutoComplete.svelte +47 -18
- package/src/SvAvatar.svelte +75 -0
- package/src/SvAvatarGroup.svelte +48 -0
- package/src/SvBadge.svelte +54 -0
- package/src/SvBreadcrumb.svelte +88 -0
- package/src/SvButtonGroup.svelte +121 -0
- package/src/SvButtonGroup.test.ts +72 -0
- package/src/SvCalendar.svelte +72 -9
- package/src/SvCard.svelte +71 -0
- package/src/SvCarousel.svelte +134 -0
- package/src/SvCheckBox.svelte +36 -21
- package/src/SvChip.svelte +88 -0
- package/src/SvCircularProgress.svelte +109 -0
- package/src/SvColorInput.svelte +54 -27
- package/src/SvComboBox.svelte +119 -33
- package/src/SvCommand.svelte +197 -0
- package/src/SvContextMenu.svelte +96 -0
- package/src/SvCountryInput.svelte +56 -21
- package/src/SvDateRangeInput.svelte +256 -0
- package/src/SvDateRangeInput.test.ts +80 -0
- package/src/SvDateTimePicker.svelte +57 -20
- package/src/SvDivider.svelte +61 -0
- package/src/SvDrawer.svelte +226 -0
- package/src/SvDrawer.test.ts +125 -0
- package/src/SvDropDownList.svelte +114 -35
- package/src/SvDurationInput.svelte +108 -0
- package/src/SvEmptyState.svelte +59 -0
- package/src/SvField.svelte +85 -0
- package/src/SvFileUpload.svelte +169 -0
- package/src/SvFileUpload.test.ts +59 -0
- package/src/SvForm.svelte +7 -1
- package/src/SvGauge.svelte +7 -1
- package/src/SvGrid.controller.svelte.ts +68 -9
- package/src/SvGrid.css +33 -0
- package/src/SvGrid.svelte +116 -3
- package/src/SvGrid.types.ts +374 -0
- package/src/SvGridBoard.svelte +2281 -0
- package/src/SvGridDropdown.svelte +2 -1
- package/src/SvGridSelect.svelte +255 -0
- package/src/SvGridSelect.test.ts +100 -0
- package/src/SvGroupCell.svelte +109 -0
- package/src/SvListBox.svelte +123 -36
- package/src/SvMaskedInput.svelte +53 -13
- package/src/SvMenu.svelte +101 -0
- package/src/SvMenu.test.ts +73 -0
- package/src/SvMenuList.svelte +143 -0
- package/src/SvModal.svelte +220 -0
- package/src/SvMultiSelect.svelte +277 -0
- package/src/SvMultiSelect.test.ts +167 -0
- package/src/SvNavPane.svelte +314 -0
- package/src/SvNavPane.test.ts +70 -0
- package/src/SvNumberInput.svelte +23 -9
- package/src/SvOtpInput.svelte +157 -0
- package/src/SvPagination.svelte +100 -0
- package/src/SvPasswordInput.svelte +42 -12
- package/src/SvPhoneInput.svelte +43 -11
- package/src/SvPopover.svelte +127 -0
- package/src/SvProgress.svelte +116 -0
- package/src/SvRadioGroup.svelte +40 -22
- package/src/SvRating.svelte +51 -30
- package/src/SvRichText.svelte +192 -0
- package/src/SvRowGroupPanel.svelte +147 -0
- package/src/SvScrollArea.svelte +50 -0
- package/src/SvSkeleton.svelte +76 -0
- package/src/SvSlider.svelte +41 -9
- package/src/SvSparkline.svelte +77 -0
- package/src/SvSplitter.svelte +138 -0
- package/src/SvSplitter.test.ts +55 -0
- package/src/SvStat.svelte +86 -0
- package/src/SvStepper.svelte +91 -0
- package/src/SvSwitchButton.svelte +39 -24
- package/src/SvTabs.svelte +147 -28
- package/src/SvTagsInput.svelte +59 -22
- package/src/SvTextArea.svelte +113 -0
- package/src/SvTextInput.svelte +115 -0
- package/src/SvTimePicker.svelte +19 -5
- package/src/SvTimeline.svelte +75 -0
- package/src/SvToaster.svelte +124 -0
- package/src/SvToaster.test.ts +63 -0
- package/src/SvToggleButton.svelte +37 -12
- package/src/SvTooltip.svelte +113 -0
- package/src/SvTour.svelte +151 -0
- package/src/SvTree.svelte +286 -44
- package/src/SvTreeSelect.svelte +222 -0
- package/src/SvTreeSelect.test.ts +87 -0
- package/src/a11y/dismissable.test.ts +95 -0
- package/src/a11y/dismissable.ts +94 -0
- package/src/a11y/focus-trap.test.ts +110 -0
- package/src/a11y/focus-trap.ts +134 -0
- package/src/a11y/live-region.test.ts +40 -0
- package/src/a11y/live-region.ts +65 -0
- package/src/a11y/scroll-lock.test.ts +38 -0
- package/src/a11y/scroll-lock.ts +41 -0
- package/src/a11y.ts +3 -1
- package/src/avatar.test.ts +32 -0
- package/src/avatar.ts +21 -0
- package/src/board.test.ts +927 -0
- package/src/builtin-editors.grid.test.ts +85 -0
- package/src/builtin-editors.test.ts +58 -0
- package/src/builtin-editors.ts +56 -0
- package/src/columns.ts +3 -2
- package/src/core.ts +10 -0
- package/src/countries.ts +25 -0
- package/src/createAccordion.svelte.ts +126 -0
- package/src/createAutocomplete.svelte.ts +18 -1
- package/src/createButtonGroup.svelte.ts +166 -0
- package/src/createCalendar.svelte.ts +6 -0
- package/src/createCheckbox.svelte.ts +23 -1
- package/src/createColorInput.svelte.ts +20 -0
- package/src/createCombobox.svelte.ts +22 -2
- package/src/createCountryInput.svelte.ts +21 -2
- package/src/createDateTimePicker.svelte.ts +23 -2
- package/src/createDropdownList.svelte.ts +31 -2
- package/src/createFileUpload.svelte.ts +144 -0
- package/src/createListbox.svelte.ts +35 -3
- package/src/createMaskedInput.svelte.ts +18 -1
- package/src/createNumberInput.svelte.ts +2 -0
- package/src/createPasswordInput.svelte.ts +31 -5
- package/src/createPhoneInput.svelte.ts +38 -4
- package/src/createPopoverSelect.svelte.ts +208 -0
- package/src/createRadioGroup.svelte.ts +23 -1
- package/src/createRating.svelte.ts +26 -1
- package/src/createSlider.svelte.ts +14 -3
- package/src/createSplitter.svelte.ts +119 -0
- package/src/createSwitch.svelte.ts +26 -3
- package/src/createTabs.svelte.ts +20 -3
- package/src/createTagsInput.svelte.ts +24 -3
- package/src/createToggle.svelte.ts +25 -2
- package/src/createTree.svelte.ts +104 -14
- package/src/debounce.test.ts +50 -0
- package/src/debounce.ts +41 -0
- package/src/duration.test.ts +41 -0
- package/src/duration.ts +51 -0
- package/src/editor-contract.ts +87 -8
- package/src/editor-registry.grid.test.ts +98 -0
- package/src/editor-registry.test.ts +86 -0
- package/src/editor-registry.ts +106 -0
- package/src/editors/cell-editors.ts +10 -1
- package/src/fuzzy.test.ts +40 -0
- package/src/fuzzy.ts +39 -0
- package/src/grid-select.test.ts +28 -0
- package/src/grid-select.ts +20 -0
- package/src/index.ts +150 -5
- package/src/keyboard-handlers.ts +20 -0
- package/src/list-option.ts +74 -0
- package/src/list-power.test.ts +71 -0
- package/src/otp.test.ts +28 -0
- package/src/otp.ts +28 -0
- package/src/overlays.test.ts +85 -0
- package/src/paginate.test.ts +41 -0
- package/src/paginate.ts +61 -0
- package/src/phone-valid.test.ts +26 -0
- package/src/popover.ts +31 -3
- package/src/progress.test.ts +55 -0
- package/src/recurrence.test.ts +91 -0
- package/src/recurrence.ts +111 -0
- package/src/server-data-source.ts +102 -0
- package/src/server-group-model.test.ts +294 -0
- package/src/server-group-model.ts +370 -0
- package/src/svgrid.responsive.test.ts +72 -0
- package/src/toast-store.svelte.ts +148 -0
- package/src/toast-store.test.ts +86 -0
- package/src/tree-edit.test.ts +49 -0
- package/src/tree-filter.test.ts +56 -0
- package/src/tree-select.test.ts +48 -0
- package/src/tree-select.ts +62 -0
- package/src/ui-app.test.ts +130 -0
- package/src/ui-app.types.ts +88 -0
- package/src/ui-display.test.ts +79 -0
- package/src/ui-inputs.test.ts +1 -1
- package/src/ui-status.test.ts +141 -0
- package/src/ui-tier1.test.ts +142 -0
- package/src/ui-tier2.test.ts +124 -0
- package/src/ui-tier3.test.ts +139 -0
- package/src/validators.test.ts +58 -0
- package/src/validators.ts +105 -0
- package/src/virtual.test.ts +72 -0
- package/src/virtual.ts +67 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type { BreadcrumbItem } from './ui-app.types'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
/**
|
|
7
|
+
* SvBreadcrumb - a navigation trail. Each item is a link (`href`), a button
|
|
8
|
+
* (`onClick`), or plain text; the last item is the current page and is never
|
|
9
|
+
* interactive. Long trails collapse the middle to an expandable ellipsis.
|
|
10
|
+
* Parity: Smart breadcrumb.
|
|
11
|
+
*
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <SvBreadcrumb items={[{label:'Home', href:'/'}, {label:'Orders', href:'/orders'}, {label:'#1024'}]} />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { BreadcrumbItem } from './ui-app.types'
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
items: ReadonlyArray<BreadcrumbItem>
|
|
20
|
+
/** Separator between crumbs. Default a chevron. */
|
|
21
|
+
separator?: string
|
|
22
|
+
/** Collapse the middle when there are more than this many crumbs. */
|
|
23
|
+
maxItems?: number
|
|
24
|
+
ariaLabel?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { items, separator = '/', maxItems, ariaLabel = 'Breadcrumb' }: Props = $props()
|
|
28
|
+
|
|
29
|
+
let expanded = $state(false)
|
|
30
|
+
|
|
31
|
+
// When collapsed, show the first crumb, an ellipsis, and the last two.
|
|
32
|
+
const collapsed = $derived(!!maxItems && !expanded && items.length > maxItems)
|
|
33
|
+
type Display = { item: BreadcrumbItem; index: number } | 'ellipsis'
|
|
34
|
+
const display = $derived<Display[]>(
|
|
35
|
+
collapsed
|
|
36
|
+
? [
|
|
37
|
+
{ item: items[0]!, index: 0 },
|
|
38
|
+
'ellipsis',
|
|
39
|
+
...items.slice(-2).map((item, i) => ({ item, index: items.length - 2 + i })),
|
|
40
|
+
]
|
|
41
|
+
: items.map((item, index) => ({ item, index })),
|
|
42
|
+
)
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<nav class="sv-bc" aria-label={ariaLabel}>
|
|
46
|
+
<ol class="sv-bc__list">
|
|
47
|
+
{#each display as entry, i (entry === 'ellipsis' ? `e${i}` : entry.index)}
|
|
48
|
+
{#if i > 0}<li class="sv-bc__sep" aria-hidden="true">{separator}</li>{/if}
|
|
49
|
+
{#if entry === 'ellipsis'}
|
|
50
|
+
<li>
|
|
51
|
+
<button type="button" class="sv-bc__ellipsis" aria-label="Show collapsed crumbs" onclick={() => (expanded = true)}>…</button>
|
|
52
|
+
</li>
|
|
53
|
+
{:else}
|
|
54
|
+
{@const isLast = entry.index === items.length - 1}
|
|
55
|
+
<li class="sv-bc__item" class:is-current={isLast}>
|
|
56
|
+
{#if isLast}
|
|
57
|
+
<span aria-current="page">{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</span>
|
|
58
|
+
{:else if entry.item.href}
|
|
59
|
+
<a class="sv-bc__link" href={entry.item.href}>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</a>
|
|
60
|
+
{:else if entry.item.onClick}
|
|
61
|
+
<button type="button" class="sv-bc__link sv-bc__btn" onclick={entry.item.onClick}>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</button>
|
|
62
|
+
{:else}
|
|
63
|
+
<span>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</span>
|
|
64
|
+
{/if}
|
|
65
|
+
</li>
|
|
66
|
+
{/if}
|
|
67
|
+
{/each}
|
|
68
|
+
</ol>
|
|
69
|
+
</nav>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
.sv-bc__list { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; list-style: none; margin: 0; padding: 0; font-size: 13px; }
|
|
73
|
+
.sv-bc__sep { color: var(--sg-muted, #94a3b8); user-select: none; }
|
|
74
|
+
.sv-bc__item { color: var(--sg-muted, #64748b); }
|
|
75
|
+
.sv-bc__item.is-current { color: var(--sg-fg, #0f172a); font-weight: 600; }
|
|
76
|
+
.sv-bc__icon { margin-inline-end: 4px; }
|
|
77
|
+
.sv-bc__link {
|
|
78
|
+
color: var(--sg-muted, #64748b); text-decoration: none; border-radius: 4px;
|
|
79
|
+
display: inline-flex; align-items: center;
|
|
80
|
+
}
|
|
81
|
+
.sv-bc__link:hover { color: var(--sg-accent, #2563eb); text-decoration: underline; }
|
|
82
|
+
.sv-bc__btn { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
|
|
83
|
+
.sv-bc__ellipsis {
|
|
84
|
+
background: var(--sg-row-hover-bg, #f1f5f9); border: 0; border-radius: 5px; cursor: pointer;
|
|
85
|
+
color: var(--sg-muted, #64748b); padding: 0 7px; height: 20px; line-height: 1; font: inherit;
|
|
86
|
+
}
|
|
87
|
+
.sv-bc__ellipsis:hover { color: var(--sg-fg, #0f172a); }
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type { ButtonGroupItem, ButtonGroupMode, ButtonGroupValue } from './createButtonGroup.svelte'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
/**
|
|
7
|
+
* SvButtonGroup - a segmented button bar with single-select (radio semantics),
|
|
8
|
+
* multi-select (toggle set) or plain action buttons. Parity: Smart
|
|
9
|
+
* `smart-button-group`. Controlled via `value` + `onChange`; roving tabindex +
|
|
10
|
+
* arrow-key navigation come from the headless `createButtonGroup` core.
|
|
11
|
+
*
|
|
12
|
+
* Part of the editor kit: carries the shared contract (label, hint, error
|
|
13
|
+
* validation, size, `dir`/RTL) via `<SvField>`.
|
|
14
|
+
*/
|
|
15
|
+
import SvField from './SvField.svelte'
|
|
16
|
+
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
17
|
+
import { createButtonGroup, type ButtonGroupItem, type ButtonGroupMode, type ButtonGroupValue } from './createButtonGroup.svelte'
|
|
18
|
+
|
|
19
|
+
type Props = SvEditorProps & {
|
|
20
|
+
items: ReadonlyArray<ButtonGroupItem>
|
|
21
|
+
value?: ButtonGroupValue
|
|
22
|
+
onChange?: (value: string | number | Array<string | number>) => void
|
|
23
|
+
/** 'single' (radio), 'multiple' (toggle set), or 'none' (action buttons). */
|
|
24
|
+
mode?: ButtonGroupMode
|
|
25
|
+
orientation?: 'horizontal' | 'vertical'
|
|
26
|
+
/** 'solid' segmented fill (default) or 'outline' pills. */
|
|
27
|
+
variant?: 'solid' | 'outline'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
items,
|
|
32
|
+
value = null,
|
|
33
|
+
onChange,
|
|
34
|
+
mode = 'single',
|
|
35
|
+
orientation = 'horizontal',
|
|
36
|
+
variant = 'solid',
|
|
37
|
+
disabled = false,
|
|
38
|
+
size = 'md',
|
|
39
|
+
name,
|
|
40
|
+
ariaLabel,
|
|
41
|
+
invalid = false,
|
|
42
|
+
required = false,
|
|
43
|
+
error,
|
|
44
|
+
label,
|
|
45
|
+
hint,
|
|
46
|
+
dir,
|
|
47
|
+
id,
|
|
48
|
+
}: Props = $props()
|
|
49
|
+
|
|
50
|
+
const autoId = nextEditorId('sv-bg')
|
|
51
|
+
const uid = $derived(id ?? autoId)
|
|
52
|
+
|
|
53
|
+
const g = createButtonGroup({
|
|
54
|
+
items: () => items,
|
|
55
|
+
value: () => value,
|
|
56
|
+
onChange: (v) => onChange?.(v),
|
|
57
|
+
mode: () => mode,
|
|
58
|
+
orientation: () => orientation,
|
|
59
|
+
disabled: () => disabled,
|
|
60
|
+
dir: () => dir,
|
|
61
|
+
id: () => uid,
|
|
62
|
+
invalid: () => invalid,
|
|
63
|
+
required: () => required,
|
|
64
|
+
error: () => error,
|
|
65
|
+
hint: () => hint,
|
|
66
|
+
ariaLabel: () => ariaLabel ?? label,
|
|
67
|
+
})
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
71
|
+
<div
|
|
72
|
+
class="sv-bg sv-bg--{orientation} sv-bg--{variant} sv-bg--{size}"
|
|
73
|
+
class:is-disabled={disabled}
|
|
74
|
+
class:is-invalid={invalid}
|
|
75
|
+
{...g.rootProps()}
|
|
76
|
+
>
|
|
77
|
+
{#each items as item, i (item.value)}
|
|
78
|
+
<button
|
|
79
|
+
class="sv-bg__btn"
|
|
80
|
+
class:is-selected={g.isSelected(item.value)}
|
|
81
|
+
{...g.buttonProps(i)}
|
|
82
|
+
>{item.label ?? item.value}</button>
|
|
83
|
+
{/each}
|
|
84
|
+
{#if name}
|
|
85
|
+
{#each g.selectedValues as v (v)}<input type="hidden" {name} value={v} />{/each}
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
88
|
+
</SvField>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.sv-bg {
|
|
92
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
93
|
+
--_border: var(--sg-input-border, var(--sg-border, #cbd5e1));
|
|
94
|
+
--_radius: var(--sg-radius, 8px);
|
|
95
|
+
display: inline-flex; background: var(--sg-input-bg, #fff);
|
|
96
|
+
border: 1px solid var(--_border); border-radius: var(--_radius); overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
.sv-bg--vertical { flex-direction: column; }
|
|
99
|
+
.sv-bg.is-disabled { opacity: 0.6; }
|
|
100
|
+
.sv-bg.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
101
|
+
.sv-bg:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
102
|
+
.sv-bg.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
103
|
+
|
|
104
|
+
.sv-bg__btn {
|
|
105
|
+
background: none; border: 0; font: inherit; font-weight: 600; color: var(--sg-fg, #0f172a);
|
|
106
|
+
cursor: pointer; white-space: nowrap; transition: background 0.12s, color 0.12s;
|
|
107
|
+
}
|
|
108
|
+
.sv-bg--sm .sv-bg__btn { padding: 5px 11px; font-size: 12px; }
|
|
109
|
+
.sv-bg--md .sv-bg__btn { padding: 7px 14px; font-size: 13px; }
|
|
110
|
+
.sv-bg--lg .sv-bg__btn { padding: 10px 18px; font-size: 15px; }
|
|
111
|
+
/* Segment dividers use a logical border so the bar mirrors under RTL. */
|
|
112
|
+
.sv-bg--horizontal .sv-bg__btn + .sv-bg__btn { border-inline-start: 1px solid var(--_border); }
|
|
113
|
+
.sv-bg--vertical .sv-bg__btn + .sv-bg__btn { border-top: 1px solid var(--_border); }
|
|
114
|
+
.sv-bg__btn:hover:not(:disabled):not(.is-selected) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
115
|
+
.sv-bg__btn:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: -2px; }
|
|
116
|
+
.sv-bg__btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
117
|
+
|
|
118
|
+
.sv-bg--solid .sv-bg__btn.is-selected { background: var(--_accent); color: var(--sg-on-accent, #fff); }
|
|
119
|
+
.sv-bg--solid .sv-bg__btn.is-selected + .sv-bg__btn, .sv-bg--solid .sv-bg__btn.is-selected { border-color: var(--_accent); }
|
|
120
|
+
.sv-bg--outline .sv-bg__btn.is-selected { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 12%, transparent); }
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for SvButtonGroup: single-select radio semantics, multi-select
|
|
3
|
+
* toggle set, roving tabindex, and the shared editor contract wiring.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import { mount, unmount, flushSync } from 'svelte'
|
|
7
|
+
import SvButtonGroup from './SvButtonGroup.svelte'
|
|
8
|
+
|
|
9
|
+
function mountBg(props: Record<string, unknown>) {
|
|
10
|
+
const target = document.createElement('div')
|
|
11
|
+
document.body.appendChild(target)
|
|
12
|
+
const app = mount(SvButtonGroup, { target, props: props as any })
|
|
13
|
+
flushSync()
|
|
14
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const items = [
|
|
18
|
+
{ value: 'day', label: 'Day' },
|
|
19
|
+
{ value: 'week', label: 'Week' },
|
|
20
|
+
{ value: 'month', label: 'Month' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
describe('SvButtonGroup', () => {
|
|
24
|
+
it('single mode uses radiogroup/radio semantics and checks the value', () => {
|
|
25
|
+
const { target, destroy } = mountBg({ items, value: 'week', mode: 'single' })
|
|
26
|
+
try {
|
|
27
|
+
const group = target.querySelector<HTMLElement>('.sv-bg')!
|
|
28
|
+
expect(group.getAttribute('role')).toBe('radiogroup')
|
|
29
|
+
const btns = [...target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')]
|
|
30
|
+
expect(btns.map((b) => b.getAttribute('role'))).toEqual(['radio', 'radio', 'radio'])
|
|
31
|
+
expect(btns[1].getAttribute('aria-checked')).toBe('true')
|
|
32
|
+
// Roving tabindex: only the checked/active button is tabbable.
|
|
33
|
+
expect(btns[1].tabIndex).toBe(0)
|
|
34
|
+
expect(btns[0].tabIndex).toBe(-1)
|
|
35
|
+
} finally { destroy() }
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('single mode emits a scalar on click', () => {
|
|
39
|
+
let got: unknown
|
|
40
|
+
const { target, destroy } = mountBg({ items, value: 'day', mode: 'single', onChange: (v: unknown) => (got = v) })
|
|
41
|
+
try {
|
|
42
|
+
const btns = target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')
|
|
43
|
+
btns[2].click()
|
|
44
|
+
flushSync()
|
|
45
|
+
expect(got).toBe('month')
|
|
46
|
+
} finally { destroy() }
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('multiple mode toggles a set and uses aria-pressed', () => {
|
|
50
|
+
let got: unknown
|
|
51
|
+
const { target, destroy } = mountBg({ items, value: ['day'], mode: 'multiple', onChange: (v: unknown) => (got = v) })
|
|
52
|
+
try {
|
|
53
|
+
const group = target.querySelector<HTMLElement>('.sv-bg')!
|
|
54
|
+
expect(group.getAttribute('role')).toBe('group')
|
|
55
|
+
const btns = target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')
|
|
56
|
+
expect(btns[0].getAttribute('aria-pressed')).toBe('true')
|
|
57
|
+
btns[1].click()
|
|
58
|
+
flushSync()
|
|
59
|
+
expect(got).toEqual(['day', 'week'])
|
|
60
|
+
} finally { destroy() }
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('wires label + aria-invalid via the editor contract', () => {
|
|
64
|
+
const { target, destroy } = mountBg({ items, value: null, mode: 'single', label: 'View', invalid: true, error: 'Pick one' })
|
|
65
|
+
try {
|
|
66
|
+
const group = target.querySelector<HTMLElement>('.sv-bg')!
|
|
67
|
+
expect(group.getAttribute('aria-invalid')).toBe('true')
|
|
68
|
+
expect(target.querySelector('.sv-field__label')!.textContent).toContain('View')
|
|
69
|
+
expect(target.querySelector('.sv-field__error')!.textContent).toBe('Pick one')
|
|
70
|
+
} finally { destroy() }
|
|
71
|
+
})
|
|
72
|
+
})
|
package/src/SvCalendar.svelte
CHANGED
|
@@ -16,20 +16,29 @@
|
|
|
16
16
|
* Parity target: Smart `smart-calendar` (selectionModes, min/max, restricted /
|
|
17
17
|
* important dates, week numbers, firstDayOfWeek, drill navigation, keyboard).
|
|
18
18
|
*/
|
|
19
|
-
import { untrack } from 'svelte'
|
|
19
|
+
import { untrack, type Snippet } from 'svelte'
|
|
20
|
+
import { resolveMessages, type EditorDir } from './editor-contract'
|
|
20
21
|
import {
|
|
21
22
|
createCalendar,
|
|
22
23
|
type CalendarValue,
|
|
23
24
|
type CalendarPreset,
|
|
24
25
|
type CalendarAnimation,
|
|
26
|
+
type CalendarDayState,
|
|
25
27
|
type DisplayMode,
|
|
26
28
|
} from './createCalendar.svelte'
|
|
27
29
|
import type { SelectionMode } from './datetime/date-selection'
|
|
28
30
|
import type { RestrictOptions } from './datetime/date-restrict'
|
|
29
31
|
import type { DateLike } from './datetime/date-core'
|
|
32
|
+
import type { RecurrenceRule } from './recurrence'
|
|
30
33
|
|
|
31
34
|
type NameFormat = 'narrow' | 'short' | 'long'
|
|
32
35
|
|
|
36
|
+
/** User-facing strings (localizable via `messages`). */
|
|
37
|
+
type CalendarMessages = { label: string; shortcuts: string; today: string; clear: string; prev: string; next: string }
|
|
38
|
+
const DEFAULT_MESSAGES: CalendarMessages = {
|
|
39
|
+
label: 'Calendar', shortcuts: 'Shortcuts', today: 'Today', clear: 'Clear', prev: 'Previous', next: 'Next',
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
type Props = {
|
|
34
43
|
/** Selected value(s). Single Date for one/zeroOrOne, array for multi modes. */
|
|
35
44
|
value?: CalendarValue
|
|
@@ -72,6 +81,16 @@
|
|
|
72
81
|
dateTooltip?: (date: Date) => string | null | undefined
|
|
73
82
|
/** One-click shortcuts (e.g. Today, Last 7 days) shown in a side rail. */
|
|
74
83
|
presets?: ReadonlyArray<CalendarPreset>
|
|
84
|
+
/** Text direction (rtl mirrors layout + flips the nav arrows; auto inherits). */
|
|
85
|
+
dir?: EditorDir
|
|
86
|
+
/** Override the built-in strings (group/shortcut labels, footer buttons). */
|
|
87
|
+
messages?: Partial<CalendarMessages>
|
|
88
|
+
/** Repeat pattern(s): matching days get an indicator + `recurring` day-state. */
|
|
89
|
+
recurrence?: RecurrenceRule | ReadonlyArray<RecurrenceRule> | null
|
|
90
|
+
/** Render rich content inside each day cell (events, dots, badges). Receives
|
|
91
|
+
* the date + its day-state. Switches the month grid to a taller,
|
|
92
|
+
* top-aligned layout so there's room for the content. */
|
|
93
|
+
day?: Snippet<[Date, CalendarDayState]>
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
let {
|
|
@@ -101,8 +120,18 @@
|
|
|
101
120
|
wheelNavigation = false,
|
|
102
121
|
dateTooltip,
|
|
103
122
|
presets,
|
|
123
|
+
dir,
|
|
124
|
+
messages,
|
|
125
|
+
recurrence = null,
|
|
126
|
+
day,
|
|
104
127
|
}: Props = $props()
|
|
105
128
|
|
|
129
|
+
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
130
|
+
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
131
|
+
// With several month panels side by side, leading/trailing days of adjacent
|
|
132
|
+
// months overlap visually, so hide them by default when months > 1.
|
|
133
|
+
const hideOther = $derived(hideOtherMonthDays || months > 1)
|
|
134
|
+
|
|
106
135
|
// The headless core owns all reactive state, selection, navigation, keyboard
|
|
107
136
|
// and ARIA. Reactive inputs are passed as getters; callbacks as closures.
|
|
108
137
|
const cal = createCalendar({
|
|
@@ -124,6 +153,7 @@
|
|
|
124
153
|
dayNameFormat: () => dayNameFormat,
|
|
125
154
|
monthNameFormat: () => monthNameFormat,
|
|
126
155
|
dateTooltip: () => dateTooltip,
|
|
156
|
+
recurrence: () => recurrence,
|
|
127
157
|
})
|
|
128
158
|
|
|
129
159
|
// --- View-change animation (framework-native Web Animations API) -----------
|
|
@@ -181,12 +211,14 @@
|
|
|
181
211
|
class:sv-cal--disabled={disabled}
|
|
182
212
|
class:sv-cal--with-presets={presets && presets.length}
|
|
183
213
|
class:sv-cal--band={selectionMode === 'range' || selectionMode === 'week'}
|
|
214
|
+
class:sv-cal--rich={!!day}
|
|
184
215
|
role="group"
|
|
185
|
-
aria-label=
|
|
216
|
+
aria-label={M.label}
|
|
186
217
|
aria-disabled={disabled}
|
|
218
|
+
dir={resolvedDir}
|
|
187
219
|
>
|
|
188
220
|
{#if presets && presets.length}
|
|
189
|
-
<div class="sv-cal__presets" role="group" aria-label=
|
|
221
|
+
<div class="sv-cal__presets" role="group" aria-label={M.shortcuts}>
|
|
190
222
|
{#each presets as p, i (i)}
|
|
191
223
|
<button class="sv-cal__preset" {...cal.presetProps(p)}>{p.label}</button>
|
|
192
224
|
{/each}
|
|
@@ -223,7 +255,7 @@
|
|
|
223
255
|
{#if weekNumbers}<span class="sv-cal__wk" aria-hidden="true">{week[0]?.week}</span>{/if}
|
|
224
256
|
{#each week as cell (cell.date.getTime())}
|
|
225
257
|
{@const st = cal.dayState(cell.date, panel.month)}
|
|
226
|
-
{#if st.outside &&
|
|
258
|
+
{#if st.outside && hideOther}
|
|
227
259
|
<span class="sv-cal__day sv-cal__day--empty" role="gridcell" aria-hidden="true"></span>
|
|
228
260
|
{:else}
|
|
229
261
|
<button
|
|
@@ -234,8 +266,9 @@
|
|
|
234
266
|
class:is-important={st.important}
|
|
235
267
|
class:is-preview={st.preview}
|
|
236
268
|
class:is-focused={st.focused}
|
|
269
|
+
class:is-recurring={st.recurring}
|
|
237
270
|
{...cal.dayProps(cell, panel.month)}
|
|
238
|
-
>{cell.date.getDate()}</button>
|
|
271
|
+
><span class="sv-cal__daynum">{cell.date.getDate()}</span>{#if day}<span class="sv-cal__daybody">{@render day(cell.date, st)}</span>{/if}</button>
|
|
239
272
|
{/if}
|
|
240
273
|
{/each}
|
|
241
274
|
</div>
|
|
@@ -271,9 +304,9 @@
|
|
|
271
304
|
|
|
272
305
|
{#if footer}
|
|
273
306
|
<div class="sv-cal__footer">
|
|
274
|
-
<button type="button" class="sv-cal__foot-btn" onclick={cal.goToday} disabled={disabled}>
|
|
307
|
+
<button type="button" class="sv-cal__foot-btn" onclick={cal.goToday} disabled={disabled}>{M.today}</button>
|
|
275
308
|
{#if cal.canClear}
|
|
276
|
-
<button type="button" class="sv-cal__foot-btn" onclick={cal.clearSelection} disabled={disabled}>
|
|
309
|
+
<button type="button" class="sv-cal__foot-btn" onclick={cal.clearSelection} disabled={disabled}>{M.clear}</button>
|
|
277
310
|
{/if}
|
|
278
311
|
</div>
|
|
279
312
|
{/if}
|
|
@@ -310,13 +343,15 @@
|
|
|
310
343
|
/* Quick-shortcut rail (Today / Last 7 days / ...) shown when `presets` is set. */
|
|
311
344
|
.sv-cal__presets {
|
|
312
345
|
display: flex; flex-direction: column; gap: 2px;
|
|
313
|
-
padding-
|
|
346
|
+
padding-inline-end: 10px; border-inline-end: 1px solid var(--_border);
|
|
314
347
|
min-width: 118px; align-self: stretch;
|
|
315
348
|
}
|
|
316
349
|
.sv-cal__preset {
|
|
317
|
-
text-align:
|
|
350
|
+
text-align: start; padding: 6px 10px; font: inherit; font-size: 12px; font-weight: 550;
|
|
318
351
|
background: none; border: 0; border-radius: var(--_radius); color: inherit; cursor: pointer; white-space: nowrap;
|
|
319
352
|
}
|
|
353
|
+
/* Under RTL the prev/next chevron glyphs must point the other way. */
|
|
354
|
+
.sv-cal[dir='rtl'] .sv-cal__nav svg { transform: scaleX(-1); }
|
|
320
355
|
.sv-cal__preset:hover:not(:disabled) { background: var(--_hover); color: var(--_accent); }
|
|
321
356
|
.sv-cal__preset:disabled { opacity: 0.5; cursor: default; }
|
|
322
357
|
|
|
@@ -369,6 +404,34 @@
|
|
|
369
404
|
content: ''; position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
|
|
370
405
|
width: 4px; height: 4px; border-radius: 50%; background: var(--sg-danger, #dc2626);
|
|
371
406
|
}
|
|
407
|
+
/* Repeat-pattern indicator: a small ring in the top-inline-end corner
|
|
408
|
+
(distinct from the is-important bottom dot). */
|
|
409
|
+
.sv-cal__day.is-recurring::before {
|
|
410
|
+
content: ''; position: absolute; top: 4px; inset-inline-end: 4px;
|
|
411
|
+
width: 5px; height: 5px; border-radius: 50%; pointer-events: none;
|
|
412
|
+
border: 1.5px solid color-mix(in srgb, var(--_accent) 75%, transparent);
|
|
413
|
+
}
|
|
414
|
+
.sv-cal:not(.sv-cal--rich) .sv-cal__day.is-selected.is-recurring::before { border-color: var(--sg-on-accent, #fff); }
|
|
415
|
+
|
|
416
|
+
/* --- Rich month grid: enabled by a `day` snippet. Cells fill the column width
|
|
417
|
+
and become taller / top-aligned so the snippet content (events, badges) has
|
|
418
|
+
room - turning the picker into an event-calendar surface. --- */
|
|
419
|
+
.sv-cal--rich .sv-cal__weekdays, .sv-cal--rich .sv-cal__week { grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
420
|
+
.sv-cal--rich .sv-cal__weekdays:has(.sv-cal__wk-head), .sv-cal--rich .sv-cal__week:has(.sv-cal__wk) { grid-template-columns: 26px repeat(7, minmax(0, 1fr)); }
|
|
421
|
+
.sv-cal--rich .sv-cal__weekday { justify-items: start; padding-inline-start: 6px; }
|
|
422
|
+
.sv-cal--rich .sv-cal__day {
|
|
423
|
+
width: auto; height: auto; min-height: 90px; display: flex; flex-direction: column;
|
|
424
|
+
align-items: stretch; gap: 3px; padding: 4px 5px 6px; text-align: start; border-radius: 8px;
|
|
425
|
+
}
|
|
426
|
+
.sv-cal--rich .sv-cal__day.is-today { box-shadow: none; }
|
|
427
|
+
.sv-cal--rich .sv-cal__day.is-selected { background: color-mix(in srgb, var(--_accent) 9%, transparent); color: inherit; }
|
|
428
|
+
.sv-cal--rich .sv-cal__daynum {
|
|
429
|
+
align-self: flex-start; flex: none; width: 24px; height: 24px; display: grid; place-items: center;
|
|
430
|
+
border-radius: 50%; font-size: 12.5px; font-weight: 600;
|
|
431
|
+
}
|
|
432
|
+
.sv-cal--rich .sv-cal__day.is-today .sv-cal__daynum { background: var(--_accent); color: var(--sg-on-accent, #fff); }
|
|
433
|
+
.sv-cal--rich .sv-cal__daybody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
|
|
434
|
+
.sv-cal--rich .sv-cal__day.is-important::after { display: none; }
|
|
372
435
|
.sv-cal__day.is-preview { background: color-mix(in srgb, var(--_accent) 16%, transparent); border-radius: 0; }
|
|
373
436
|
.sv-cal__day.is-selected {
|
|
374
437
|
background: var(--_accent); color: var(--sg-on-accent, #fff); font-weight: 650;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvCard - a themed surface container with an optional header (title +
|
|
4
|
+
* subtitle or a custom `header` snippet), body, and footer. The building block
|
|
5
|
+
* for dashboards and Studio-generated app screens. Parity: Smart card.
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <SvCard title="Revenue" subtitle="Last 30 days">
|
|
9
|
+
* <Chart .../>
|
|
10
|
+
* {#snippet footer()}<a href="/reports">View report</a>{/snippet}
|
|
11
|
+
* </SvCard>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
title?: string
|
|
18
|
+
subtitle?: string
|
|
19
|
+
/** Lift + accent border on hover (for clickable cards). */
|
|
20
|
+
hoverable?: boolean
|
|
21
|
+
/** Remove body padding (e.g. a card wrapping a full-bleed grid/table). */
|
|
22
|
+
flush?: boolean
|
|
23
|
+
/** Custom header content; overrides title/subtitle when provided. */
|
|
24
|
+
header?: Snippet
|
|
25
|
+
children?: Snippet
|
|
26
|
+
footer?: Snippet
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { title, subtitle, hoverable = false, flush = false, header, children, footer }: Props = $props()
|
|
30
|
+
const hasHeader = $derived(!!header || !!title || !!subtitle)
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="sv-card" class:is-hoverable={hoverable}>
|
|
34
|
+
{#if hasHeader}
|
|
35
|
+
<div class="sv-card__header">
|
|
36
|
+
{#if header}
|
|
37
|
+
{@render header()}
|
|
38
|
+
{:else}
|
|
39
|
+
{#if title}<h3 class="sv-card__title">{title}</h3>{/if}
|
|
40
|
+
{#if subtitle}<p class="sv-card__subtitle">{subtitle}</p>{/if}
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
43
|
+
{/if}
|
|
44
|
+
<div class="sv-card__body" class:is-flush={flush}>{@render children?.()}</div>
|
|
45
|
+
{#if footer}<div class="sv-card__footer">{@render footer()}</div>{/if}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.sv-card {
|
|
50
|
+
display: flex; flex-direction: column;
|
|
51
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
52
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius-lg, 12px);
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
transition: box-shadow 0.16s, border-color 0.16s, transform 0.16s;
|
|
55
|
+
}
|
|
56
|
+
.sv-card.is-hoverable:hover {
|
|
57
|
+
border-color: color-mix(in srgb, var(--sg-accent, #2563eb) 45%, var(--sg-border, #e2e8f0));
|
|
58
|
+
box-shadow: 0 10px 30px -12px rgba(15, 23, 42, 0.28);
|
|
59
|
+
transform: translateY(-1px);
|
|
60
|
+
}
|
|
61
|
+
@media (prefers-reduced-motion: reduce) { .sv-card.is-hoverable:hover { transform: none; } }
|
|
62
|
+
.sv-card__header { padding: 14px 16px; border-bottom: 1px solid var(--sg-border, #e2e8f0); }
|
|
63
|
+
.sv-card__title { margin: 0; font-size: 14.5px; font-weight: 650; }
|
|
64
|
+
.sv-card__subtitle { margin: 2px 0 0; font-size: 12.5px; color: var(--sg-muted, #64748b); }
|
|
65
|
+
.sv-card__body { padding: 16px; flex: 1; font-size: 13.5px; line-height: 1.55; }
|
|
66
|
+
.sv-card__body.is-flush { padding: 0; }
|
|
67
|
+
.sv-card__footer {
|
|
68
|
+
padding: 12px 16px; border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
69
|
+
font-size: 13px; color: var(--sg-muted, #64748b);
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvCarousel - a slideshow: a sliding track with prev/next arrows, dot
|
|
4
|
+
* indicators, optional autoplay (pauses on hover/focus) and swipe. Slides are
|
|
5
|
+
* rendered by the `slide` snippet (index in, your content out). Parity: Smart
|
|
6
|
+
* carousel.
|
|
7
|
+
*
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <SvCarousel count={imgs.length} autoplay={4000}>
|
|
10
|
+
* {#snippet slide(i)}<img src={imgs[i]} alt="" />{/snippet}
|
|
11
|
+
* </SvCarousel>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
count: number
|
|
18
|
+
slide?: Snippet<[number]>
|
|
19
|
+
/** Active slide index (bindable). */
|
|
20
|
+
current?: number
|
|
21
|
+
/** Autoplay interval in ms; 0 = off. */
|
|
22
|
+
autoplay?: number
|
|
23
|
+
loop?: boolean
|
|
24
|
+
arrows?: boolean
|
|
25
|
+
dots?: boolean
|
|
26
|
+
ariaLabel?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
count,
|
|
31
|
+
slide,
|
|
32
|
+
current = $bindable(0),
|
|
33
|
+
autoplay = 0,
|
|
34
|
+
loop = true,
|
|
35
|
+
arrows = true,
|
|
36
|
+
dots = true,
|
|
37
|
+
ariaLabel = 'Carousel',
|
|
38
|
+
}: Props = $props()
|
|
39
|
+
|
|
40
|
+
function go(i: number) {
|
|
41
|
+
if (count === 0) return
|
|
42
|
+
current = loop ? (i + count) % count : Math.max(0, Math.min(i, count - 1))
|
|
43
|
+
}
|
|
44
|
+
const next = () => go(current + 1)
|
|
45
|
+
const prev = () => go(current - 1)
|
|
46
|
+
|
|
47
|
+
// Autoplay, paused while hovered/focused.
|
|
48
|
+
let paused = $state(false)
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (!autoplay || paused || count < 2) return
|
|
51
|
+
const t = setInterval(next, autoplay)
|
|
52
|
+
return () => clearInterval(t)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Swipe.
|
|
56
|
+
let dragging = $state(false)
|
|
57
|
+
let startX = 0
|
|
58
|
+
let dx = $state(0)
|
|
59
|
+
function down(e: PointerEvent) {
|
|
60
|
+
dragging = true
|
|
61
|
+
startX = e.clientX
|
|
62
|
+
;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
|
|
63
|
+
}
|
|
64
|
+
function move(e: PointerEvent) { if (dragging) dx = e.clientX - startX }
|
|
65
|
+
function up() {
|
|
66
|
+
if (!dragging) return
|
|
67
|
+
dragging = false
|
|
68
|
+
if (Math.abs(dx) > 50) (dx < 0 ? next() : prev())
|
|
69
|
+
dx = 0
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<div
|
|
74
|
+
class="sv-carousel"
|
|
75
|
+
role="group"
|
|
76
|
+
aria-roledescription="carousel"
|
|
77
|
+
aria-label={ariaLabel}
|
|
78
|
+
onpointerenter={() => (paused = true)}
|
|
79
|
+
onpointerleave={() => (paused = false)}
|
|
80
|
+
onfocusin={() => (paused = true)}
|
|
81
|
+
onfocusout={() => (paused = false)}
|
|
82
|
+
>
|
|
83
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
84
|
+
<div class="sv-carousel__viewport" onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
|
|
85
|
+
<div class="sv-carousel__track" class:is-dragging={dragging} style:transform={`translateX(calc(${-current * 100}% + ${dx}px))`}>
|
|
86
|
+
{#each Array(count) as _, i (i)}
|
|
87
|
+
<div class="sv-carousel__slide" role="group" aria-roledescription="slide" aria-label={`${i + 1} of ${count}`} aria-hidden={i !== current}>
|
|
88
|
+
{@render slide?.(i)}
|
|
89
|
+
</div>
|
|
90
|
+
{/each}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{#if arrows && count > 1}
|
|
95
|
+
<button type="button" class="sv-carousel__arrow is-prev" aria-label="Previous slide" onclick={prev} disabled={!loop && current === 0}>‹</button>
|
|
96
|
+
<button type="button" class="sv-carousel__arrow is-next" aria-label="Next slide" onclick={next} disabled={!loop && current === count - 1}>›</button>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
{#if dots && count > 1}
|
|
100
|
+
<div class="sv-carousel__dots">
|
|
101
|
+
{#each Array(count) as _, i (i)}
|
|
102
|
+
<button type="button" class="sv-carousel__dot" class:is-active={i === current} aria-label={`Go to slide ${i + 1}`} aria-current={i === current ? 'true' : undefined} onclick={() => go(i)}></button>
|
|
103
|
+
{/each}
|
|
104
|
+
</div>
|
|
105
|
+
{/if}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
.sv-carousel { position: relative; border-radius: 12px; overflow: hidden; }
|
|
110
|
+
.sv-carousel__viewport { overflow: hidden; touch-action: pan-y; }
|
|
111
|
+
.sv-carousel__track { display: flex; transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
112
|
+
.sv-carousel__track.is-dragging { transition: none; }
|
|
113
|
+
.sv-carousel__slide { flex: 0 0 100%; min-width: 0; }
|
|
114
|
+
.sv-carousel__slide :global(img) { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
115
|
+
|
|
116
|
+
.sv-carousel__arrow {
|
|
117
|
+
position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
|
|
118
|
+
width: 34px; height: 34px; display: grid; place-items: center; border-radius: 50%;
|
|
119
|
+
background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
|
|
120
|
+
border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 20px; line-height: 1;
|
|
121
|
+
box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
|
|
122
|
+
}
|
|
123
|
+
.sv-carousel__arrow.is-prev { inset-inline-start: 10px; }
|
|
124
|
+
.sv-carousel__arrow.is-next { inset-inline-end: 10px; }
|
|
125
|
+
.sv-carousel__arrow:hover:not(:disabled) { background: var(--sg-bg, #fff); }
|
|
126
|
+
.sv-carousel__arrow:disabled { opacity: 0.4; cursor: default; }
|
|
127
|
+
|
|
128
|
+
.sv-carousel__dots { position: absolute; inset-inline: 0; bottom: 10px; display: flex; justify-content: center; gap: 6px; z-index: 2; }
|
|
129
|
+
.sv-carousel__dot {
|
|
130
|
+
width: 8px; height: 8px; border-radius: 50%; border: 0; cursor: pointer; padding: 0;
|
|
131
|
+
background: color-mix(in srgb, var(--sg-fg, #0f172a) 30%, var(--sg-bg, #fff)); transition: background 0.15s, width 0.15s;
|
|
132
|
+
}
|
|
133
|
+
.sv-carousel__dot.is-active { width: 20px; border-radius: 4px; background: var(--sg-accent, #2563eb); }
|
|
134
|
+
</style>
|