@svgrid/grid 1.2.34 → 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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* focus-trap - keep Tab / Shift+Tab focus inside a container while it is active,
|
|
3
|
+
* move focus in on activate, and restore it on release. Shared by SvModal,
|
|
4
|
+
* SvDrawer, SvMenu and any dialog-like overlay so the WAI-ARIA dialog focus
|
|
5
|
+
* behaviour lives in ONE place instead of being re-hand-rolled per component.
|
|
6
|
+
*
|
|
7
|
+
* Framework-free (DOM only): call `createFocusTrap(el).activate()` from a
|
|
8
|
+
* component `$effect` and `release()` from its cleanup.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* $effect(() => {
|
|
12
|
+
* if (!open || !panel) return
|
|
13
|
+
* const trap = createFocusTrap(panel)
|
|
14
|
+
* trap.activate()
|
|
15
|
+
* return () => trap.release()
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Selector for elements that can, in principle, hold keyboard focus. */
|
|
21
|
+
export const FOCUSABLE_SELECTOR = [
|
|
22
|
+
'a[href]',
|
|
23
|
+
'area[href]',
|
|
24
|
+
'button:not([disabled])',
|
|
25
|
+
'input:not([disabled])',
|
|
26
|
+
'select:not([disabled])',
|
|
27
|
+
'textarea:not([disabled])',
|
|
28
|
+
'iframe',
|
|
29
|
+
'audio[controls]',
|
|
30
|
+
'video[controls]',
|
|
31
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
32
|
+
'[tabindex]',
|
|
33
|
+
].join(',')
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether `el` is visible enough to be Tab-focusable. Detects the common,
|
|
37
|
+
* layout-independent ways of hiding (disabled / hidden attr / aria-hidden /
|
|
38
|
+
* inline display:none|visibility:hidden) so the check is deterministic in both
|
|
39
|
+
* real browsers and jsdom. Class-based `display:none` in a real browser is
|
|
40
|
+
* additionally caught via client rects when layout is available.
|
|
41
|
+
*/
|
|
42
|
+
function isVisible(el: HTMLElement): boolean {
|
|
43
|
+
if (el.hasAttribute('disabled')) return false
|
|
44
|
+
if (el.getAttribute('aria-hidden') === 'true') return false
|
|
45
|
+
if (el.hidden || el.closest('[hidden]')) return false
|
|
46
|
+
const style = el.style
|
|
47
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false
|
|
48
|
+
// Only trust layout when the environment actually lays out (real browsers).
|
|
49
|
+
if (typeof el.getClientRects === 'function') {
|
|
50
|
+
const rects = el.getClientRects()
|
|
51
|
+
// jsdom reports 0 rects for everything, so 0 alone is not "hidden" there;
|
|
52
|
+
// treat 0 rects as hidden only when SOME element in the doc has layout.
|
|
53
|
+
if (rects.length === 0 && document.body.getClientRects().length > 0) return false
|
|
54
|
+
}
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Visible, Tab-focusable descendants of `container`, in DOM order. Elements
|
|
59
|
+
* with `tabindex="-1"` are excluded (script-focusable, not Tab-reachable). */
|
|
60
|
+
export function getFocusable(container: HTMLElement): HTMLElement[] {
|
|
61
|
+
const nodes = Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))
|
|
62
|
+
return nodes.filter((el) => {
|
|
63
|
+
const ti = el.getAttribute('tabindex')
|
|
64
|
+
if (ti !== null && Number(ti) < 0) return false
|
|
65
|
+
return isVisible(el)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type FocusTrapOptions = {
|
|
70
|
+
/** Where focus goes on activate: the first focusable (default), the container
|
|
71
|
+
* itself, a specific element, or a getter resolved at activate time. */
|
|
72
|
+
initialFocus?: 'first' | 'container' | HTMLElement | (() => HTMLElement | null)
|
|
73
|
+
/** Restore focus to the previously-focused element on release. Default true. */
|
|
74
|
+
returnFocus?: boolean
|
|
75
|
+
/** Called on Escape while active. Dismissal is usually owned by the layer
|
|
76
|
+
* manager, so most consumers leave this unset. */
|
|
77
|
+
onEscape?: (e: KeyboardEvent) => void
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type FocusTrap = { activate: () => void; release: () => void }
|
|
81
|
+
|
|
82
|
+
/** Create a focus trap for `container`. Inert until `activate()`. */
|
|
83
|
+
export function createFocusTrap(
|
|
84
|
+
container: HTMLElement,
|
|
85
|
+
options: FocusTrapOptions = {},
|
|
86
|
+
): FocusTrap {
|
|
87
|
+
const { initialFocus = 'first', returnFocus = true } = options
|
|
88
|
+
let previouslyFocused: HTMLElement | null = null
|
|
89
|
+
let active = false
|
|
90
|
+
|
|
91
|
+
function resolveInitial(): HTMLElement | null {
|
|
92
|
+
const f = initialFocus
|
|
93
|
+
if (f === 'container') return container
|
|
94
|
+
if (f === 'first') return getFocusable(container)[0] ?? container
|
|
95
|
+
if (typeof f === 'function') return f()
|
|
96
|
+
return f
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function onKeydown(e: KeyboardEvent) {
|
|
100
|
+
if (!active) return
|
|
101
|
+
if (e.key === 'Escape' && options.onEscape) { options.onEscape(e); return }
|
|
102
|
+
if (e.key !== 'Tab') return
|
|
103
|
+
const f = getFocusable(container)
|
|
104
|
+
if (!f.length) { e.preventDefault(); container.focus(); return }
|
|
105
|
+
const first = f[0]!
|
|
106
|
+
const last = f[f.length - 1]!
|
|
107
|
+
const activeEl = document.activeElement
|
|
108
|
+
// Focus escaped the container entirely (programmatic blur, etc.): pull back.
|
|
109
|
+
if (!container.contains(activeEl)) { e.preventDefault(); first.focus(); return }
|
|
110
|
+
if (e.shiftKey && activeEl === first) { e.preventDefault(); last.focus() }
|
|
111
|
+
else if (!e.shiftKey && activeEl === last) { e.preventDefault(); first.focus() }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function activate() {
|
|
115
|
+
if (active) return
|
|
116
|
+
active = true
|
|
117
|
+
previouslyFocused = document.activeElement as HTMLElement | null
|
|
118
|
+
// Guarantee the container is a focus fallback even without focusable children.
|
|
119
|
+
if (!container.hasAttribute('tabindex')) container.setAttribute('tabindex', '-1')
|
|
120
|
+
document.addEventListener('keydown', onKeydown, true)
|
|
121
|
+
// Focus on the next microtask so a just-opened panel is in the DOM first.
|
|
122
|
+
queueMicrotask(() => { if (active) resolveInitial()?.focus() })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function release() {
|
|
126
|
+
if (!active) return
|
|
127
|
+
active = false
|
|
128
|
+
document.removeEventListener('keydown', onKeydown, true)
|
|
129
|
+
if (returnFocus) previouslyFocused?.focus?.()
|
|
130
|
+
previouslyFocused = null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { activate, release }
|
|
134
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { _resetLiveRegions, announce } from './live-region'
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
_resetLiveRegions()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
describe('announce', () => {
|
|
9
|
+
it('creates a polite status region and sets its text', async () => {
|
|
10
|
+
announce('3 rows selected')
|
|
11
|
+
const region = document.querySelector('[aria-live="polite"]')
|
|
12
|
+
expect(region).not.toBeNull()
|
|
13
|
+
expect(region!.getAttribute('role')).toBe('status')
|
|
14
|
+
await Promise.resolve()
|
|
15
|
+
expect(region!.textContent).toBe('3 rows selected')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('creates a separate assertive alert region', async () => {
|
|
19
|
+
announce('error!', { assertive: true })
|
|
20
|
+
const region = document.querySelector('[aria-live="assertive"]')
|
|
21
|
+
expect(region).not.toBeNull()
|
|
22
|
+
expect(region!.getAttribute('role')).toBe('alert')
|
|
23
|
+
await Promise.resolve()
|
|
24
|
+
expect(region!.textContent).toBe('error!')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('reuses the same region across calls', async () => {
|
|
28
|
+
announce('first')
|
|
29
|
+
announce('second')
|
|
30
|
+
const regions = document.querySelectorAll('[aria-live="polite"]')
|
|
31
|
+
expect(regions).toHaveLength(1)
|
|
32
|
+
await Promise.resolve()
|
|
33
|
+
expect(regions[0]!.textContent).toBe('second')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('ignores empty messages', () => {
|
|
37
|
+
announce('')
|
|
38
|
+
expect(document.querySelector('[aria-live]')).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* live-region - a single shared pair of visually-hidden ARIA live regions
|
|
3
|
+
* (polite + assertive) appended to <body> on first use, so any component can
|
|
4
|
+
* push a transient message to screen readers via `announce()` without each one
|
|
5
|
+
* shipping its own live region. Used for things like "3 rows selected",
|
|
6
|
+
* "copied", "no results".
|
|
7
|
+
*/
|
|
8
|
+
let politeEl: HTMLElement | null = null
|
|
9
|
+
let assertiveEl: HTMLElement | null = null
|
|
10
|
+
|
|
11
|
+
function make(mode: 'polite' | 'assertive'): HTMLElement {
|
|
12
|
+
const el = document.createElement('div')
|
|
13
|
+
el.setAttribute('aria-live', mode)
|
|
14
|
+
el.setAttribute('aria-atomic', 'true')
|
|
15
|
+
el.setAttribute('role', mode === 'assertive' ? 'alert' : 'status')
|
|
16
|
+
// Visually hidden, but still exposed to assistive technology.
|
|
17
|
+
Object.assign(el.style, {
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
width: '1px',
|
|
20
|
+
height: '1px',
|
|
21
|
+
margin: '-1px',
|
|
22
|
+
border: '0',
|
|
23
|
+
padding: '0',
|
|
24
|
+
overflow: 'hidden',
|
|
25
|
+
clip: 'rect(0 0 0 0)',
|
|
26
|
+
clipPath: 'inset(50%)',
|
|
27
|
+
whiteSpace: 'nowrap',
|
|
28
|
+
})
|
|
29
|
+
document.body.appendChild(el)
|
|
30
|
+
return el
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AnnounceOptions = {
|
|
34
|
+
/** Interrupt the user immediately (role="alert"). Default polite. */
|
|
35
|
+
assertive?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Announce `message` to screen readers. Empty messages are ignored. */
|
|
39
|
+
export function announce(message: string, options: AnnounceOptions = {}): void {
|
|
40
|
+
if (typeof document === 'undefined' || !message) return
|
|
41
|
+
const assertive = options.assertive ?? false
|
|
42
|
+
let el = assertive ? assertiveEl : politeEl
|
|
43
|
+
// Recreate when missing OR when a prior DOM reset detached the cached node,
|
|
44
|
+
// so a stale reference can never swallow an announcement silently.
|
|
45
|
+
if (!el || !el.isConnected) {
|
|
46
|
+
el = make(assertive ? 'assertive' : 'polite')
|
|
47
|
+
if (assertive) assertiveEl = el
|
|
48
|
+
else politeEl = el
|
|
49
|
+
}
|
|
50
|
+
// Clear, then set on the next microtask, so an identical consecutive message
|
|
51
|
+
// still triggers a fresh announcement.
|
|
52
|
+
el.textContent = ''
|
|
53
|
+
const target = el
|
|
54
|
+
queueMicrotask(() => {
|
|
55
|
+
target.textContent = message
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Test helper: tear the shared live regions down. */
|
|
60
|
+
export function _resetLiveRegions(): void {
|
|
61
|
+
politeEl?.remove()
|
|
62
|
+
assertiveEl?.remove()
|
|
63
|
+
politeEl = null
|
|
64
|
+
assertiveEl = null
|
|
65
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { lockScroll, scrollLockDepth } from './scroll-lock'
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
// Each test balances its own locks; just reset the inline styles.
|
|
6
|
+
document.body.style.overflow = ''
|
|
7
|
+
document.body.style.paddingRight = ''
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('lockScroll', () => {
|
|
11
|
+
it('hides body overflow while locked and restores it on unlock', () => {
|
|
12
|
+
document.body.style.overflow = 'auto'
|
|
13
|
+
const unlock = lockScroll()
|
|
14
|
+
expect(document.body.style.overflow).toBe('hidden')
|
|
15
|
+
unlock()
|
|
16
|
+
expect(document.body.style.overflow).toBe('auto')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('ref-counts nested locks: only the last unlock restores', () => {
|
|
20
|
+
document.body.style.overflow = 'scroll'
|
|
21
|
+
const a = lockScroll()
|
|
22
|
+
const b = lockScroll()
|
|
23
|
+
expect(scrollLockDepth()).toBe(2)
|
|
24
|
+
a()
|
|
25
|
+
expect(document.body.style.overflow).toBe('hidden') // still locked by b
|
|
26
|
+
b()
|
|
27
|
+
expect(document.body.style.overflow).toBe('scroll')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('unlock is idempotent', () => {
|
|
31
|
+
document.body.style.overflow = 'auto'
|
|
32
|
+
const unlock = lockScroll()
|
|
33
|
+
unlock()
|
|
34
|
+
unlock()
|
|
35
|
+
expect(scrollLockDepth()).toBe(0)
|
|
36
|
+
expect(document.body.style.overflow).toBe('auto')
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scroll-lock - stop the document scrolling behind an overlay, with REF-COUNTING
|
|
3
|
+
* so nested overlays (a dialog that opens a menu) never fight over
|
|
4
|
+
* `body.style.overflow`. The first lock records the original values; the last
|
|
5
|
+
* unlock restores them. Also pads for the vanishing scrollbar so the page
|
|
6
|
+
* doesn't shift when the lock engages.
|
|
7
|
+
*/
|
|
8
|
+
let locks = 0
|
|
9
|
+
let previousOverflow = ''
|
|
10
|
+
let previousPaddingRight = ''
|
|
11
|
+
|
|
12
|
+
/** Lock document scroll. Returns an idempotent unlock function. */
|
|
13
|
+
export function lockScroll(): () => void {
|
|
14
|
+
if (typeof document === 'undefined') return () => {}
|
|
15
|
+
const body = document.body
|
|
16
|
+
if (locks === 0) {
|
|
17
|
+
previousOverflow = body.style.overflow
|
|
18
|
+
previousPaddingRight = body.style.paddingRight
|
|
19
|
+
// Compensate for the scrollbar width so fixed/full-width layout stays put.
|
|
20
|
+
const sbw = window.innerWidth - document.documentElement.clientWidth
|
|
21
|
+
if (sbw > 0) {
|
|
22
|
+
const current = parseFloat(getComputedStyle(body).paddingRight) || 0
|
|
23
|
+
body.style.paddingRight = `${current + sbw}px`
|
|
24
|
+
}
|
|
25
|
+
body.style.overflow = 'hidden'
|
|
26
|
+
}
|
|
27
|
+
locks++
|
|
28
|
+
let released = false
|
|
29
|
+
return () => {
|
|
30
|
+
if (released) return
|
|
31
|
+
released = true
|
|
32
|
+
locks = Math.max(0, locks - 1)
|
|
33
|
+
if (locks === 0) {
|
|
34
|
+
body.style.overflow = previousOverflow
|
|
35
|
+
body.style.paddingRight = previousPaddingRight
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Test helper: current lock depth. */
|
|
41
|
+
export function scrollLockDepth(): number { return locks }
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
getGridCellA11yProps,
|
|
4
|
-
getGridHeaderA11yProps,
|
|
5
|
-
getGridRootA11yProps,
|
|
6
|
-
getGridRowA11yProps,
|
|
7
|
-
} from './a11y'
|
|
8
|
-
|
|
9
|
-
function attrsToHtml(attrs: Record<string, string | number | boolean>) {
|
|
10
|
-
return Object.entries(attrs)
|
|
11
|
-
.map(([key, value]) => `${key}="${String(value)}"`)
|
|
12
|
-
.join(' ')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('a11y contract rendering', () => {
|
|
16
|
-
it('builds expected headless markup attributes', () => {
|
|
17
|
-
const root = getGridRootA11yProps()
|
|
18
|
-
const header = getGridHeaderA11yProps({
|
|
19
|
-
sortable: true,
|
|
20
|
-
sortDirection: 'descending',
|
|
21
|
-
})
|
|
22
|
-
const row = getGridRowA11yProps(1)
|
|
23
|
-
const cell = getGridCellA11yProps({
|
|
24
|
-
rowIndex: 1,
|
|
25
|
-
colIndex: 2,
|
|
26
|
-
selected: true,
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const html = [
|
|
30
|
-
`<table ${attrsToHtml(root)}><thead>`,
|
|
31
|
-
`<tr ${attrsToHtml(row)}>`,
|
|
32
|
-
`<th ${attrsToHtml(header)}>Age</th>`,
|
|
33
|
-
`</tr></thead><tbody>`,
|
|
34
|
-
`<tr ${attrsToHtml(row)}><td ${attrsToHtml(cell)}>42</td></tr>`,
|
|
35
|
-
`</tbody></table>`,
|
|
36
|
-
].join('')
|
|
37
|
-
|
|
38
|
-
expect(html).toContain('role="grid"')
|
|
39
|
-
expect(html).toContain('role="columnheader"')
|
|
40
|
-
expect(html).toContain('aria-sort="descending"')
|
|
41
|
-
expect(html).toContain('aria-selected="true"')
|
|
42
|
-
expect(html).toContain('aria-colindex="2"')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('creates stable active descendant ids', async () => {
|
|
46
|
-
const { getGridCellDomId } = await import('./a11y')
|
|
47
|
-
expect(getGridCellDomId('svgrid', 3, 4)).toBe('svgrid_cell_3_4')
|
|
48
|
-
})
|
|
49
|
-
})
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
getGridCellA11yProps,
|
|
4
|
+
getGridHeaderA11yProps,
|
|
5
|
+
getGridRootA11yProps,
|
|
6
|
+
getGridRowA11yProps,
|
|
7
|
+
} from './a11y'
|
|
8
|
+
|
|
9
|
+
function attrsToHtml(attrs: Record<string, string | number | boolean>) {
|
|
10
|
+
return Object.entries(attrs)
|
|
11
|
+
.map(([key, value]) => `${key}="${String(value)}"`)
|
|
12
|
+
.join(' ')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('a11y contract rendering', () => {
|
|
16
|
+
it('builds expected headless markup attributes', () => {
|
|
17
|
+
const root = getGridRootA11yProps()
|
|
18
|
+
const header = getGridHeaderA11yProps({
|
|
19
|
+
sortable: true,
|
|
20
|
+
sortDirection: 'descending',
|
|
21
|
+
})
|
|
22
|
+
const row = getGridRowA11yProps(1)
|
|
23
|
+
const cell = getGridCellA11yProps({
|
|
24
|
+
rowIndex: 1,
|
|
25
|
+
colIndex: 2,
|
|
26
|
+
selected: true,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const html = [
|
|
30
|
+
`<table ${attrsToHtml(root)}><thead>`,
|
|
31
|
+
`<tr ${attrsToHtml(row)}>`,
|
|
32
|
+
`<th ${attrsToHtml(header)}>Age</th>`,
|
|
33
|
+
`</tr></thead><tbody>`,
|
|
34
|
+
`<tr ${attrsToHtml(row)}><td ${attrsToHtml(cell)}>42</td></tr>`,
|
|
35
|
+
`</tbody></table>`,
|
|
36
|
+
].join('')
|
|
37
|
+
|
|
38
|
+
expect(html).toContain('role="grid"')
|
|
39
|
+
expect(html).toContain('role="columnheader"')
|
|
40
|
+
expect(html).toContain('aria-sort="descending"')
|
|
41
|
+
expect(html).toContain('aria-selected="true"')
|
|
42
|
+
expect(html).toContain('aria-colindex="2"')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('creates stable active descendant ids', async () => {
|
|
46
|
+
const { getGridCellDomId } = await import('./a11y')
|
|
47
|
+
expect(getGridCellDomId('svgrid', 3, 4)).toBe('svgrid_cell_3_4')
|
|
48
|
+
})
|
|
49
|
+
})
|
package/src/a11y.test.ts
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
getGridCellA11yProps,
|
|
4
|
-
getGridHeaderA11yProps,
|
|
5
|
-
getGridRootA11yProps,
|
|
6
|
-
getGridRowA11yProps,
|
|
7
|
-
} from './a11y'
|
|
8
|
-
|
|
9
|
-
describe('a11y helpers', () => {
|
|
10
|
-
it('returns grid role props', () => {
|
|
11
|
-
expect(
|
|
12
|
-
getGridRootA11yProps({
|
|
13
|
-
activeDescendantId: 'svgrid_cell_0_0',
|
|
14
|
-
rowCount: 10,
|
|
15
|
-
colCount: 5,
|
|
16
|
-
}),
|
|
17
|
-
).toEqual({
|
|
18
|
-
role: 'grid',
|
|
19
|
-
tabindex: 0,
|
|
20
|
-
'aria-activedescendant': 'svgrid_cell_0_0',
|
|
21
|
-
'aria-rowcount': 10,
|
|
22
|
-
'aria-colcount': 5,
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('returns header sort semantics', () => {
|
|
27
|
-
expect(
|
|
28
|
-
getGridHeaderA11yProps({
|
|
29
|
-
sortable: true,
|
|
30
|
-
sortDirection: 'ascending',
|
|
31
|
-
}),
|
|
32
|
-
).toEqual({
|
|
33
|
-
role: 'columnheader',
|
|
34
|
-
'aria-sort': 'ascending',
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns row and cell indices for accessibility metadata', () => {
|
|
39
|
-
expect(getGridRowA11yProps(2)).toEqual({
|
|
40
|
-
role: 'row',
|
|
41
|
-
'aria-rowindex': 2,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
expect(
|
|
45
|
-
getGridCellA11yProps({
|
|
46
|
-
id: 'svgrid_cell_2_3',
|
|
47
|
-
rowIndex: 2,
|
|
48
|
-
colIndex: 3,
|
|
49
|
-
selected: true,
|
|
50
|
-
}),
|
|
51
|
-
).toEqual({
|
|
52
|
-
role: 'gridcell',
|
|
53
|
-
id: 'svgrid_cell_2_3',
|
|
54
|
-
'aria-colindex': 3,
|
|
55
|
-
'aria-rowindex': 2,
|
|
56
|
-
'aria-selected': true,
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
getGridCellA11yProps,
|
|
4
|
+
getGridHeaderA11yProps,
|
|
5
|
+
getGridRootA11yProps,
|
|
6
|
+
getGridRowA11yProps,
|
|
7
|
+
} from './a11y'
|
|
8
|
+
|
|
9
|
+
describe('a11y helpers', () => {
|
|
10
|
+
it('returns grid role props', () => {
|
|
11
|
+
expect(
|
|
12
|
+
getGridRootA11yProps({
|
|
13
|
+
activeDescendantId: 'svgrid_cell_0_0',
|
|
14
|
+
rowCount: 10,
|
|
15
|
+
colCount: 5,
|
|
16
|
+
}),
|
|
17
|
+
).toEqual({
|
|
18
|
+
role: 'grid',
|
|
19
|
+
tabindex: 0,
|
|
20
|
+
'aria-activedescendant': 'svgrid_cell_0_0',
|
|
21
|
+
'aria-rowcount': 10,
|
|
22
|
+
'aria-colcount': 5,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('returns header sort semantics', () => {
|
|
27
|
+
expect(
|
|
28
|
+
getGridHeaderA11yProps({
|
|
29
|
+
sortable: true,
|
|
30
|
+
sortDirection: 'ascending',
|
|
31
|
+
}),
|
|
32
|
+
).toEqual({
|
|
33
|
+
role: 'columnheader',
|
|
34
|
+
'aria-sort': 'ascending',
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns row and cell indices for accessibility metadata', () => {
|
|
39
|
+
expect(getGridRowA11yProps(2)).toEqual({
|
|
40
|
+
role: 'row',
|
|
41
|
+
'aria-rowindex': 2,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(
|
|
45
|
+
getGridCellA11yProps({
|
|
46
|
+
id: 'svgrid_cell_2_3',
|
|
47
|
+
rowIndex: 2,
|
|
48
|
+
colIndex: 3,
|
|
49
|
+
selected: true,
|
|
50
|
+
}),
|
|
51
|
+
).toEqual({
|
|
52
|
+
role: 'gridcell',
|
|
53
|
+
id: 'svgrid_cell_2_3',
|
|
54
|
+
'aria-colindex': 3,
|
|
55
|
+
'aria-rowindex': 2,
|
|
56
|
+
'aria-selected': true,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
package/src/a11y.ts
CHANGED
|
@@ -1,59 +1,61 @@
|
|
|
1
|
-
export type GridSortDirection = 'ascending' | 'descending' | 'none' | 'other'
|
|
2
|
-
|
|
3
|
-
export type GridColumnA11yInput = {
|
|
4
|
-
sortable?: boolean
|
|
5
|
-
sortDirection?: GridSortDirection
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type GridCellA11yInput = {
|
|
9
|
-
colIndex?: number
|
|
10
|
-
rowIndex?: number
|
|
11
|
-
selected?: boolean
|
|
12
|
-
id?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getGridRootA11yProps(input: {
|
|
16
|
-
activeDescendantId?: string | null
|
|
17
|
-
rowCount?: number
|
|
18
|
-
colCount?: number
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...(
|
|
53
|
-
'aria-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
export type GridSortDirection = 'ascending' | 'descending' | 'none' | 'other'
|
|
2
|
+
|
|
3
|
+
export type GridColumnA11yInput = {
|
|
4
|
+
sortable?: boolean
|
|
5
|
+
sortDirection?: GridSortDirection
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type GridCellA11yInput = {
|
|
9
|
+
colIndex?: number
|
|
10
|
+
rowIndex?: number
|
|
11
|
+
selected?: boolean
|
|
12
|
+
id?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getGridRootA11yProps(input: {
|
|
16
|
+
activeDescendantId?: string | null
|
|
17
|
+
rowCount?: number
|
|
18
|
+
colCount?: number
|
|
19
|
+
/** Use the treegrid role (expandable server group / tree rows are present). */
|
|
20
|
+
treegrid?: boolean
|
|
21
|
+
} = {}) {
|
|
22
|
+
return {
|
|
23
|
+
role: input.treegrid ? 'treegrid' : 'grid',
|
|
24
|
+
tabindex: 0,
|
|
25
|
+
...(input.activeDescendantId
|
|
26
|
+
? { 'aria-activedescendant': input.activeDescendantId }
|
|
27
|
+
: {}),
|
|
28
|
+
...(input.rowCount !== undefined ? { 'aria-rowcount': input.rowCount } : {}),
|
|
29
|
+
...(input.colCount !== undefined ? { 'aria-colcount': input.colCount } : {}),
|
|
30
|
+
} as const
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getGridHeaderA11yProps(input: GridColumnA11yInput = {}) {
|
|
34
|
+
const { sortable = false, sortDirection = 'none' } = input
|
|
35
|
+
return {
|
|
36
|
+
role: 'columnheader',
|
|
37
|
+
...(sortable ? { 'aria-sort': sortDirection } : {}),
|
|
38
|
+
} as const
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getGridRowA11yProps(rowIndex?: number) {
|
|
42
|
+
return {
|
|
43
|
+
role: 'row',
|
|
44
|
+
...(rowIndex !== undefined ? { 'aria-rowindex': rowIndex } : {}),
|
|
45
|
+
} as const
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getGridCellA11yProps(input: GridCellA11yInput = {}) {
|
|
49
|
+
const { colIndex, rowIndex, selected = false, id } = input
|
|
50
|
+
return {
|
|
51
|
+
role: 'gridcell',
|
|
52
|
+
...(id ? { id } : {}),
|
|
53
|
+
...(colIndex !== undefined ? { 'aria-colindex': colIndex } : {}),
|
|
54
|
+
...(rowIndex !== undefined ? { 'aria-rowindex': rowIndex } : {}),
|
|
55
|
+
'aria-selected': selected,
|
|
56
|
+
} as const
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getGridCellDomId(baseId: string, rowIndex: number, colIndex: number) {
|
|
60
|
+
return `${baseId}_cell_${rowIndex}_${colIndex}`
|
|
61
|
+
}
|