@svgrid/grid 1.2.33 → 2.0.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/README.md +6 -5
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- 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 +2376 -2343
- package/dist/SvGrid.svelte +2764 -2651
- 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 +666 -665
- 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 +16022 -8235
- package/dist/cdn/svgrid.svelte-external.js +14543 -6765
- 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 +85 -85
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- 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 +2553 -2494
- package/src/SvGrid.css +2376 -2343
- package/src/SvGrid.svelte +2764 -2651
- package/src/SvGrid.types.ts +944 -570
- package/src/SvGridBoard.svelte +2281 -0
- package/src/SvGridDropdown.svelte +666 -665
- 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.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -59
- package/src/avatar.test.ts +32 -0
- package/src/avatar.ts +21 -0
- package/src/board.test.ts +927 -0
- package/src/build-api.ts +798 -798
- 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/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +3 -2
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1101
- 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/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- 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/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- 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/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/flex-render.ts +3 -3
- 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 +463 -318
- package/src/keyboard-handlers.ts +20 -0
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-option.ts +74 -0
- package/src/list-power.test.ts +71 -0
- package/src/menus.ts +582 -582
- package/src/merge-objects.ts +48 -48
- 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/render-component.ts +28 -28
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -311
- package/src/server-group-model.test.ts +294 -0
- package/src/server-group-model.ts +370 -0
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +304 -304
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +439 -439
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.responsive.test.ts +72 -0
- package/src/svgrid.wrapper.test.ts +40 -40
- 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
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/src/merge-objects.ts
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Merges objects together while keeping their getters alive.
|
|
3
|
-
* Taken from SolidJS:
|
|
4
|
-
* https://github.com/solidjs/solid/blob/24abc825c0996fd2bc8c1de1491efe9a7e743aff/packages/solid/src/server/rendering.ts#L82-L115
|
|
5
|
-
*/
|
|
6
|
-
export function mergeObjects<T>(source: T): T
|
|
7
|
-
export function mergeObjects<T, U>(source: T, source1: U): T & U
|
|
8
|
-
export function mergeObjects<T, U, V>(
|
|
9
|
-
source: T,
|
|
10
|
-
source1: U,
|
|
11
|
-
source2: V,
|
|
12
|
-
): T & U & V
|
|
13
|
-
export function mergeObjects<T, U, V, W>(
|
|
14
|
-
source: T,
|
|
15
|
-
source1: U,
|
|
16
|
-
source2: V,
|
|
17
|
-
source3: W,
|
|
18
|
-
): T & U & V & W
|
|
19
|
-
export function mergeObjects(...sources: any): any {
|
|
20
|
-
const target = {}
|
|
21
|
-
|
|
22
|
-
for (let source of sources) {
|
|
23
|
-
if (typeof source === 'function') source = source()
|
|
24
|
-
|
|
25
|
-
if (source) {
|
|
26
|
-
const descriptors = Object.getOwnPropertyDescriptors(source)
|
|
27
|
-
|
|
28
|
-
for (const key in descriptors) {
|
|
29
|
-
if (key in target) continue
|
|
30
|
-
|
|
31
|
-
Object.defineProperty(target, key, {
|
|
32
|
-
enumerable: true,
|
|
33
|
-
get() {
|
|
34
|
-
for (let i = sources.length - 1; i >= 0; i--) {
|
|
35
|
-
let scopedSource = sources[i]
|
|
36
|
-
if (typeof scopedSource === 'function') scopedSource = scopedSource()
|
|
37
|
-
|
|
38
|
-
const value = (scopedSource || {})[key]
|
|
39
|
-
if (value !== undefined) return value
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return target
|
|
48
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Merges objects together while keeping their getters alive.
|
|
3
|
+
* Taken from SolidJS:
|
|
4
|
+
* https://github.com/solidjs/solid/blob/24abc825c0996fd2bc8c1de1491efe9a7e743aff/packages/solid/src/server/rendering.ts#L82-L115
|
|
5
|
+
*/
|
|
6
|
+
export function mergeObjects<T>(source: T): T
|
|
7
|
+
export function mergeObjects<T, U>(source: T, source1: U): T & U
|
|
8
|
+
export function mergeObjects<T, U, V>(
|
|
9
|
+
source: T,
|
|
10
|
+
source1: U,
|
|
11
|
+
source2: V,
|
|
12
|
+
): T & U & V
|
|
13
|
+
export function mergeObjects<T, U, V, W>(
|
|
14
|
+
source: T,
|
|
15
|
+
source1: U,
|
|
16
|
+
source2: V,
|
|
17
|
+
source3: W,
|
|
18
|
+
): T & U & V & W
|
|
19
|
+
export function mergeObjects(...sources: any): any {
|
|
20
|
+
const target = {}
|
|
21
|
+
|
|
22
|
+
for (let source of sources) {
|
|
23
|
+
if (typeof source === 'function') source = source()
|
|
24
|
+
|
|
25
|
+
if (source) {
|
|
26
|
+
const descriptors = Object.getOwnPropertyDescriptors(source)
|
|
27
|
+
|
|
28
|
+
for (const key in descriptors) {
|
|
29
|
+
if (key in target) continue
|
|
30
|
+
|
|
31
|
+
Object.defineProperty(target, key, {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get() {
|
|
34
|
+
for (let i = sources.length - 1; i >= 0; i--) {
|
|
35
|
+
let scopedSource = sources[i]
|
|
36
|
+
if (typeof scopedSource === 'function') scopedSource = scopedSource()
|
|
37
|
+
|
|
38
|
+
const value = (scopedSource || {})[key]
|
|
39
|
+
if (value !== undefined) return value
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return target
|
|
48
|
+
}
|
package/src/otp.test.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { sanitizeOtp, otpCells, isOtpComplete } from './otp'
|
|
3
|
+
|
|
4
|
+
describe('sanitizeOtp', () => {
|
|
5
|
+
it('keeps only digits in numeric mode and clamps to length', () => {
|
|
6
|
+
expect(sanitizeOtp('12ab34-56', 4, true)).toBe('1234')
|
|
7
|
+
})
|
|
8
|
+
it('keeps non-whitespace in text mode', () => {
|
|
9
|
+
expect(sanitizeOtp('a b\tc d', 3, false)).toBe('abc')
|
|
10
|
+
})
|
|
11
|
+
it('clamps to length', () => {
|
|
12
|
+
expect(sanitizeOtp('123456789', 6, true)).toBe('123456')
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('otpCells', () => {
|
|
17
|
+
it('pads to exactly length cells', () => {
|
|
18
|
+
expect(otpCells('12', 4)).toEqual(['1', '2', '', ''])
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('isOtpComplete', () => {
|
|
23
|
+
it('is true only when every cell is filled', () => {
|
|
24
|
+
expect(isOtpComplete('1234', 4)).toBe(true)
|
|
25
|
+
expect(isOtpComplete('12', 4)).toBe(false)
|
|
26
|
+
expect(isOtpComplete('', 4)).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
})
|
package/src/otp.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* otp - pure helpers behind <SvOtpInput>: sanitize/spread a raw string across N
|
|
3
|
+
* segments (used on paste and per-cell input) and detect completion.
|
|
4
|
+
* Framework-free + pure so they are unit-tested directly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Keep only the characters allowed for `numeric` mode (digits) or any
|
|
9
|
+
* non-whitespace otherwise, then clamp to `length`. Returns the cleaned string
|
|
10
|
+
* (<= length chars).
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeOtp(raw: string, length: number, numeric: boolean): string {
|
|
13
|
+
const kept = numeric ? raw.replace(/\D/g, '') : raw.replace(/\s/g, '')
|
|
14
|
+
return kept.slice(0, Math.max(0, length))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Split a value into exactly `length` single-char cells (padding with ''). */
|
|
18
|
+
export function otpCells(value: string, length: number): string[] {
|
|
19
|
+
const cells: string[] = []
|
|
20
|
+
for (let i = 0; i < length; i++) cells[i] = value[i] ?? ''
|
|
21
|
+
return cells
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Whether `value` fills every cell (a joined OTP has no internal gaps, so an
|
|
25
|
+
* exact-length string is complete). */
|
|
26
|
+
export function isOtpComplete(value: string, length: number): boolean {
|
|
27
|
+
return value.length === length
|
|
28
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the overlay trio: SvModal (dialog semantics, Escape /
|
|
3
|
+
* backdrop close), SvPopover (click open + outside close) and SvTooltip
|
|
4
|
+
* (focus-triggered, aria-describedby wiring).
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it, vi, afterEach } from 'vitest'
|
|
7
|
+
import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
|
|
8
|
+
import SvModal from './SvModal.svelte'
|
|
9
|
+
import SvPopover from './SvPopover.svelte'
|
|
10
|
+
import SvTooltip from './SvTooltip.svelte'
|
|
11
|
+
|
|
12
|
+
const body = createRawSnippet(() => ({ render: () => `<p class="body">hello</p>` }))
|
|
13
|
+
const anchorBtn = createRawSnippet(() => ({ render: () => `<button class="anchor">open</button>` }))
|
|
14
|
+
|
|
15
|
+
function mountC(Comp: any, props: Record<string, unknown>) {
|
|
16
|
+
const target = document.createElement('div')
|
|
17
|
+
document.body.appendChild(target)
|
|
18
|
+
const app = mount(Comp, { target, props: props as any })
|
|
19
|
+
flushSync()
|
|
20
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
21
|
+
}
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
document.querySelectorAll('.sv-modal__backdrop, .sv-pop__panel, .sv-tip').forEach((n) => n.remove())
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('SvModal', () => {
|
|
27
|
+
it('renders a labelled modal dialog when open', () => {
|
|
28
|
+
const { destroy } = mountC(SvModal, { open: true, title: 'Edit', children: body })
|
|
29
|
+
try {
|
|
30
|
+
const dialog = document.querySelector<HTMLElement>('.sv-modal')!
|
|
31
|
+
expect(dialog.getAttribute('role')).toBe('dialog')
|
|
32
|
+
expect(dialog.getAttribute('aria-modal')).toBe('true')
|
|
33
|
+
const titleId = dialog.getAttribute('aria-labelledby')!
|
|
34
|
+
expect(document.getElementById(titleId)!.textContent).toBe('Edit')
|
|
35
|
+
expect(document.querySelector('.sv-modal .body')!.textContent).toBe('hello')
|
|
36
|
+
} finally { destroy() }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Escape and the close button both fire onClose', () => {
|
|
40
|
+
let closed = 0
|
|
41
|
+
const { destroy } = mountC(SvModal, { open: true, title: 'X', onClose: () => (closed++), children: body })
|
|
42
|
+
try {
|
|
43
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
44
|
+
flushSync()
|
|
45
|
+
expect(closed).toBe(1)
|
|
46
|
+
} finally { destroy() }
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('SvPopover', () => {
|
|
51
|
+
it('opens the panel on anchor click and closes on outside pointerdown', () => {
|
|
52
|
+
const { target, destroy } = mountC(SvPopover, { anchor: anchorBtn, children: body })
|
|
53
|
+
try {
|
|
54
|
+
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
55
|
+
target.querySelector<HTMLElement>('.anchor')!.click()
|
|
56
|
+
flushSync()
|
|
57
|
+
const panel = document.querySelector<HTMLElement>('.sv-pop__panel')!
|
|
58
|
+
expect(panel).not.toBeNull()
|
|
59
|
+
expect(panel.getAttribute('role')).toBe('dialog')
|
|
60
|
+
// Outside click closes.
|
|
61
|
+
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
|
|
62
|
+
flushSync()
|
|
63
|
+
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
64
|
+
} finally { destroy() }
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('SvTooltip', () => {
|
|
69
|
+
it('shows after the delay on focus and wires aria-describedby', () => {
|
|
70
|
+
vi.useFakeTimers()
|
|
71
|
+
try {
|
|
72
|
+
const child = createRawSnippet(() => ({ render: () => `<button class="t">i</button>` }))
|
|
73
|
+
const { target, destroy } = mountC(SvTooltip, { text: 'Tip text', delay: 200, children: child })
|
|
74
|
+
const anchor = target.querySelector<HTMLElement>('.sv-tip__anchor')!
|
|
75
|
+
anchor.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
|
|
76
|
+
vi.advanceTimersByTime(210)
|
|
77
|
+
flushSync()
|
|
78
|
+
const tip = document.querySelector<HTMLElement>('.sv-tip')!
|
|
79
|
+
expect(tip).not.toBeNull()
|
|
80
|
+
expect(tip.getAttribute('role')).toBe('tooltip')
|
|
81
|
+
expect(anchor.getAttribute('aria-describedby')).toBe(tip.id)
|
|
82
|
+
destroy()
|
|
83
|
+
} finally { vi.useRealTimers() }
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { paginationRange } from './paginate'
|
|
3
|
+
|
|
4
|
+
describe('paginationRange', () => {
|
|
5
|
+
it('returns an empty list for zero pages', () => {
|
|
6
|
+
expect(paginationRange({ page: 1, pageCount: 0 })).toEqual([])
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('shows every page when the count is small', () => {
|
|
10
|
+
expect(paginationRange({ page: 3, pageCount: 5 })).toEqual([1, 2, 3, 4, 5])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('adds a trailing ellipsis near the start', () => {
|
|
14
|
+
expect(paginationRange({ page: 1, pageCount: 10 })).toEqual([1, 2, 'ellipsis-right', 10])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('adds ellipses on both sides in the middle', () => {
|
|
18
|
+
expect(paginationRange({ page: 5, pageCount: 10 })).toEqual([
|
|
19
|
+
1, 'ellipsis-left', 4, 5, 6, 'ellipsis-right', 10,
|
|
20
|
+
])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('adds a leading ellipsis near the end', () => {
|
|
24
|
+
expect(paginationRange({ page: 10, pageCount: 10 })).toEqual([1, 'ellipsis-left', 9, 10])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('collapses a single-page gap to that page number, not an ellipsis', () => {
|
|
28
|
+
// Between boundary 1 and sibling-start 3 there is exactly one page (2): show it.
|
|
29
|
+
expect(paginationRange({ page: 4, pageCount: 8 })).toEqual([1, 2, 3, 4, 5, 'ellipsis-right', 8])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('clamps an out-of-range page', () => {
|
|
33
|
+
expect(paginationRange({ page: 99, pageCount: 5 })).toEqual([1, 2, 3, 4, 5])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('honours a larger siblingCount', () => {
|
|
37
|
+
expect(paginationRange({ page: 6, pageCount: 12, siblingCount: 2 })).toEqual([
|
|
38
|
+
1, 'ellipsis-left', 4, 5, 6, 7, 8, 'ellipsis-right', 12,
|
|
39
|
+
])
|
|
40
|
+
})
|
|
41
|
+
})
|
package/src/paginate.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paginate - the pure page-range algorithm behind <SvPagination>. Given the
|
|
3
|
+
* current page and total page count, returns the sequence of page numbers and
|
|
4
|
+
* ellipsis markers to render (boundary pages + a window of siblings around the
|
|
5
|
+
* current page, with gaps collapsed to an ellipsis). Framework-free + pure so
|
|
6
|
+
* it is unit-tested on its own.
|
|
7
|
+
*/
|
|
8
|
+
export type PaginationItem = number | 'ellipsis-left' | 'ellipsis-right'
|
|
9
|
+
|
|
10
|
+
export type PaginationRangeOptions = {
|
|
11
|
+
/** 1-based current page. */
|
|
12
|
+
page: number
|
|
13
|
+
/** Total number of pages. */
|
|
14
|
+
pageCount: number
|
|
15
|
+
/** Pages shown on each side of the current page. Default 1. */
|
|
16
|
+
siblingCount?: number
|
|
17
|
+
/** Pages pinned at each end. Default 1. */
|
|
18
|
+
boundaryCount?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi)
|
|
22
|
+
|
|
23
|
+
/** Build the page/ellipsis sequence (e.g. `[1, 'ellipsis-left', 4, 5, 6, 'ellipsis-right', 20]`). */
|
|
24
|
+
export function paginationRange(options: PaginationRangeOptions): PaginationItem[] {
|
|
25
|
+
const { pageCount } = options
|
|
26
|
+
if (pageCount <= 0) return []
|
|
27
|
+
const siblingCount = Math.max(0, options.siblingCount ?? 1)
|
|
28
|
+
const boundaryCount = Math.max(1, options.boundaryCount ?? 1)
|
|
29
|
+
const page = clamp(options.page, 1, pageCount)
|
|
30
|
+
|
|
31
|
+
// If ellipsis could hide at most one page, just show every page.
|
|
32
|
+
if (pageCount <= boundaryCount * 2 + siblingCount * 2 + 3) {
|
|
33
|
+
return Array.from({ length: pageCount }, (_, i) => i + 1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pages = new Set<number>()
|
|
37
|
+
// End boundaries (both ends).
|
|
38
|
+
for (let i = 1; i <= boundaryCount && i <= pageCount; i++) {
|
|
39
|
+
pages.add(i)
|
|
40
|
+
pages.add(pageCount - i + 1)
|
|
41
|
+
}
|
|
42
|
+
// Siblings around the current page.
|
|
43
|
+
for (let i = page - siblingCount; i <= page + siblingCount; i++) {
|
|
44
|
+
if (i >= 1 && i <= pageCount) pages.add(i)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sorted = [...pages].sort((a, b) => a - b)
|
|
48
|
+
const result: PaginationItem[] = []
|
|
49
|
+
let prev = 0
|
|
50
|
+
for (const p of sorted) {
|
|
51
|
+
if (p - prev === 2) {
|
|
52
|
+
// Exactly one page in the gap: show it instead of an ellipsis.
|
|
53
|
+
result.push(prev + 1)
|
|
54
|
+
} else if (p - prev > 2) {
|
|
55
|
+
result.push(p <= page ? 'ellipsis-left' : 'ellipsis-right')
|
|
56
|
+
}
|
|
57
|
+
result.push(p)
|
|
58
|
+
prev = p
|
|
59
|
+
}
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-country phone length validation (phoneDigitsValid): exact lengths, ranges,
|
|
3
|
+
* unknown countries (any non-empty), and empty handling.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import { phoneDigitsValid } from './countries'
|
|
7
|
+
|
|
8
|
+
describe('phoneDigitsValid', () => {
|
|
9
|
+
it('checks exact national lengths', () => {
|
|
10
|
+
expect(phoneDigitsValid('US', '4155551234')).toBe(true) // 10
|
|
11
|
+
expect(phoneDigitsValid('US', '415555')).toBe(false)
|
|
12
|
+
expect(phoneDigitsValid('IN', '9876543210')).toBe(true) // 10
|
|
13
|
+
expect(phoneDigitsValid('SG', '81234567')).toBe(true) // 8
|
|
14
|
+
expect(phoneDigitsValid('SG', '8123456')).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
it('accepts ranges', () => {
|
|
17
|
+
expect(phoneDigitsValid('DE', '1701234567')).toBe(true) // 10 in [10,11]
|
|
18
|
+
expect(phoneDigitsValid('DE', '17012345678')).toBe(true) // 11
|
|
19
|
+
expect(phoneDigitsValid('DE', '170')).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
it('ignores non-digits and unknown countries', () => {
|
|
22
|
+
expect(phoneDigitsValid('US', '(415) 555-1234')).toBe(true)
|
|
23
|
+
expect(phoneDigitsValid('ZZ', '12345')).toBe(true) // unknown -> any non-empty
|
|
24
|
+
expect(phoneDigitsValid('ZZ', '')).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
})
|
package/src/popover.ts
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
* any popover panel actually consumes.
|
|
17
17
|
*/
|
|
18
18
|
export const PANEL_THEME_VARS = [
|
|
19
|
-
'--sg-accent', '--sg-bg', '--sg-fg', '--sg-
|
|
20
|
-
'--sg-header-bg', '--sg-row-hover-bg', '--sg-
|
|
21
|
-
'--sg-
|
|
19
|
+
'--sg-accent', '--sg-on-accent', '--sg-bg', '--sg-fg', '--sg-muted', '--sg-border',
|
|
20
|
+
'--sg-header-bg', '--sg-header-fg', '--sg-row-hover-bg', '--sg-row-alt-bg',
|
|
21
|
+
'--sg-selection-bg', '--sg-selection-fg', '--sg-input-bg', '--sg-input-border',
|
|
22
|
+
'--sg-danger', '--sg-focus-ring', '--sg-radius', '--sg-font',
|
|
23
|
+
'--sg-invalid-bg', '--sg-invalid-border', '--sg-invalid-fg',
|
|
24
|
+
'--sg-rating-on', '--sg-rating-empty', '--sg-rating-hover',
|
|
22
25
|
] as const
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -40,6 +43,31 @@ export function portalToBody(node: HTMLElement, vars: ReadonlyArray<string> = PA
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Svelte action: play a short enter animation when a portalled panel mounts
|
|
48
|
+
* (dropdowns, popovers, dialogs). Slides from the trigger side + fades/scales in.
|
|
49
|
+
* A no-op under `prefers-reduced-motion` and in environments without the Web
|
|
50
|
+
* Animations API (jsdom), so tests and reduced-motion users are unaffected.
|
|
51
|
+
*
|
|
52
|
+
* `use:popIn={{ up: rect.openUpward }}` - pass `up` for panels that flipped above
|
|
53
|
+
* their trigger so the slide direction matches.
|
|
54
|
+
*/
|
|
55
|
+
export function popIn(node: HTMLElement, param: { up?: boolean; duration?: number; scale?: number } = {}) {
|
|
56
|
+
if (typeof node.animate !== 'function') return
|
|
57
|
+
const reduce = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
58
|
+
if (reduce) return
|
|
59
|
+
const dy = param.up ? 6 : -6
|
|
60
|
+
const s = param.scale ?? 0.97
|
|
61
|
+
node.animate(
|
|
62
|
+
[
|
|
63
|
+
{ opacity: 0, transform: `translateY(${dy}px) scale(${s})` },
|
|
64
|
+
{ opacity: 1, transform: 'translateY(0) scale(1)' },
|
|
65
|
+
],
|
|
66
|
+
{ duration: param.duration ?? 140, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' },
|
|
67
|
+
)
|
|
68
|
+
return {}
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
export type AnchoredRect = { top: number; left: number; width: number; openUpward: boolean }
|
|
44
72
|
|
|
45
73
|
export type AnchorOptions = {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for SvProgress + SvCircularProgress: progressbar ARIA,
|
|
3
|
+
* determinate fill width / ring offset, and indeterminate (no valuenow).
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import { mount, unmount, flushSync } from 'svelte'
|
|
7
|
+
import SvProgress from './SvProgress.svelte'
|
|
8
|
+
import SvCircularProgress from './SvCircularProgress.svelte'
|
|
9
|
+
|
|
10
|
+
function mountC(Comp: any, props: Record<string, unknown>) {
|
|
11
|
+
const target = document.createElement('div')
|
|
12
|
+
document.body.appendChild(target)
|
|
13
|
+
const app = mount(Comp, { target, props: props as any })
|
|
14
|
+
flushSync()
|
|
15
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('SvProgress', () => {
|
|
19
|
+
it('exposes progressbar ARIA + fill width for a determinate value', () => {
|
|
20
|
+
const { target, destroy } = mountC(SvProgress, { value: 40, max: 200, showLabel: true })
|
|
21
|
+
try {
|
|
22
|
+
const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
|
|
23
|
+
expect(bar.getAttribute('aria-valuemin')).toBe('0')
|
|
24
|
+
expect(bar.getAttribute('aria-valuemax')).toBe('200')
|
|
25
|
+
expect(bar.getAttribute('aria-valuenow')).toBe('40')
|
|
26
|
+
expect(target.querySelector<HTMLElement>('.sv-prog__fill')!.style.width).toBe('20%')
|
|
27
|
+
expect(target.querySelector('.sv-prog__label')!.textContent).toBe('20%')
|
|
28
|
+
} finally { destroy() }
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('indeterminate drops valuenow and animates', () => {
|
|
32
|
+
const { target, destroy } = mountC(SvProgress, { indeterminate: true })
|
|
33
|
+
try {
|
|
34
|
+
const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
|
|
35
|
+
expect(bar.getAttribute('aria-valuenow')).toBeNull()
|
|
36
|
+
expect(target.querySelector('.sv-prog__fill.is-indeterminate')).not.toBeNull()
|
|
37
|
+
} finally { destroy() }
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('SvCircularProgress', () => {
|
|
42
|
+
it('sets the ring dashoffset from the value', () => {
|
|
43
|
+
const { target, destroy } = mountC(SvCircularProgress, { value: 75, size: 48, thickness: 4, showLabel: true })
|
|
44
|
+
try {
|
|
45
|
+
const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
|
|
46
|
+
expect(bar.getAttribute('aria-valuenow')).toBe('75')
|
|
47
|
+
const arc = target.querySelector<SVGCircleElement>('.sv-circ__arc')!
|
|
48
|
+
const circ = 2 * Math.PI * ((48 - 4) / 2)
|
|
49
|
+
expect(Number(arc.getAttribute('stroke-dasharray'))).toBeCloseTo(circ, 3)
|
|
50
|
+
// 75% -> 25% of the circumference remains as offset.
|
|
51
|
+
expect(Number(arc.getAttribute('stroke-dashoffset'))).toBeCloseTo(circ * 0.25, 3)
|
|
52
|
+
expect(target.querySelector('.sv-circ__label')!.textContent).toBe('75%')
|
|
53
|
+
} finally { destroy() }
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { matchesRecurrence, expandRecurrence, type RecurrenceRule } from './recurrence'
|
|
3
|
+
|
|
4
|
+
// Fixed reference dates (local time). 2026-07-01 is a Wednesday.
|
|
5
|
+
const D = (s: string) => new Date(`${s}T00:00:00`)
|
|
6
|
+
|
|
7
|
+
describe('matchesRecurrence - weekly', () => {
|
|
8
|
+
const weekdays: RecurrenceRule = { freq: 'weekly', weekdays: [1, 2, 3, 4, 5] } // Mon..Fri
|
|
9
|
+
it('matches weekdays, not weekends', () => {
|
|
10
|
+
expect(matchesRecurrence(D('2026-07-06'), weekdays)).toBe(true) // Monday
|
|
11
|
+
expect(matchesRecurrence(D('2026-07-10'), weekdays)).toBe(true) // Friday
|
|
12
|
+
expect(matchesRecurrence(D('2026-07-11'), weekdays)).toBe(false) // Saturday
|
|
13
|
+
expect(matchesRecurrence(D('2026-07-12'), weekdays)).toBe(false) // Sunday
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('every other Monday with an interval + anchor', () => {
|
|
17
|
+
const r: RecurrenceRule = { freq: 'weekly', weekdays: [1], interval: 2, from: D('2026-07-06') }
|
|
18
|
+
expect(matchesRecurrence(D('2026-07-06'), r)).toBe(true) // anchor Monday
|
|
19
|
+
expect(matchesRecurrence(D('2026-07-13'), r)).toBe(false) // next Monday (odd week)
|
|
20
|
+
expect(matchesRecurrence(D('2026-07-20'), r)).toBe(true) // +2 weeks
|
|
21
|
+
expect(matchesRecurrence(D('2026-07-07'), r)).toBe(false) // Tuesday
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("falls back to the anchor's weekday when no weekdays given", () => {
|
|
25
|
+
const r: RecurrenceRule = { freq: 'weekly', from: D('2026-07-01') } // Wednesday
|
|
26
|
+
expect(matchesRecurrence(D('2026-07-08'), r)).toBe(true) // Wednesday
|
|
27
|
+
expect(matchesRecurrence(D('2026-07-09'), r)).toBe(false) // Thursday
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('matchesRecurrence - monthly / yearly / daily', () => {
|
|
32
|
+
it('monthly on a day-of-month', () => {
|
|
33
|
+
const r: RecurrenceRule = { freq: 'monthly', day: 1 }
|
|
34
|
+
expect(matchesRecurrence(D('2026-07-01'), r)).toBe(true)
|
|
35
|
+
expect(matchesRecurrence(D('2026-08-01'), r)).toBe(true)
|
|
36
|
+
expect(matchesRecurrence(D('2026-08-02'), r)).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('monthly with an interval + anchor (every 3 months, the 15th)', () => {
|
|
40
|
+
const r: RecurrenceRule = { freq: 'monthly', day: 15, interval: 3, from: D('2026-01-15') }
|
|
41
|
+
expect(matchesRecurrence(D('2026-01-15'), r)).toBe(true)
|
|
42
|
+
expect(matchesRecurrence(D('2026-02-15'), r)).toBe(false)
|
|
43
|
+
expect(matchesRecurrence(D('2026-04-15'), r)).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('yearly on a month + day', () => {
|
|
47
|
+
const r: RecurrenceRule = { freq: 'yearly', month: 11, day: 25 } // Dec 25
|
|
48
|
+
expect(matchesRecurrence(D('2026-12-25'), r)).toBe(true)
|
|
49
|
+
expect(matchesRecurrence(D('2027-12-25'), r)).toBe(true)
|
|
50
|
+
expect(matchesRecurrence(D('2026-12-24'), r)).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('daily every N days from an anchor', () => {
|
|
54
|
+
const r: RecurrenceRule = { freq: 'daily', interval: 3, from: D('2026-07-01') }
|
|
55
|
+
expect(matchesRecurrence(D('2026-07-01'), r)).toBe(true)
|
|
56
|
+
expect(matchesRecurrence(D('2026-07-02'), r)).toBe(false)
|
|
57
|
+
expect(matchesRecurrence(D('2026-07-04'), r)).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('matchesRecurrence - bounds + combining', () => {
|
|
62
|
+
it('respects from / until', () => {
|
|
63
|
+
const r: RecurrenceRule = { freq: 'daily', from: D('2026-07-05'), until: D('2026-07-08') }
|
|
64
|
+
expect(matchesRecurrence(D('2026-07-04'), r)).toBe(false) // before
|
|
65
|
+
expect(matchesRecurrence(D('2026-07-06'), r)).toBe(true) // inside
|
|
66
|
+
expect(matchesRecurrence(D('2026-07-09'), r)).toBe(false) // after until
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('matches if ANY rule in a list matches', () => {
|
|
70
|
+
const rules: RecurrenceRule[] = [
|
|
71
|
+
{ freq: 'weekly', weekdays: [1] }, // Mondays
|
|
72
|
+
{ freq: 'monthly', day: 1 }, // the 1st
|
|
73
|
+
]
|
|
74
|
+
expect(matchesRecurrence(D('2026-07-06'), rules)).toBe(true) // Monday
|
|
75
|
+
expect(matchesRecurrence(D('2026-07-01'), rules)).toBe(true) // the 1st (a Wednesday)
|
|
76
|
+
expect(matchesRecurrence(D('2026-07-02'), rules)).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('returns false for empty / nullish rules', () => {
|
|
80
|
+
expect(matchesRecurrence(D('2026-07-01'), null)).toBe(false)
|
|
81
|
+
expect(matchesRecurrence(D('2026-07-01'), [])).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('expandRecurrence', () => {
|
|
86
|
+
it('lists the matching dates in a range', () => {
|
|
87
|
+
const r: RecurrenceRule = { freq: 'weekly', weekdays: [1] } // Mondays
|
|
88
|
+
const dates = expandRecurrence(r, D('2026-07-01'), D('2026-07-31')).map((d) => d.getDate())
|
|
89
|
+
expect(dates).toEqual([6, 13, 20, 27])
|
|
90
|
+
})
|
|
91
|
+
})
|