@human-kit/svelte-components 1.0.0-alpha.2 → 1.0.0-alpha.21
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/dist/FOCUS_STATE_CONTRACT.md +63 -0
- package/dist/FOCUS_STATE_REVIEW_TEMPLATE.md +70 -0
- package/dist/button/README.md +48 -0
- package/dist/button/TODO.md +13 -0
- package/dist/button/index.d.ts +5 -0
- package/dist/button/index.js +4 -0
- package/dist/button/index.parts.d.ts +1 -0
- package/dist/button/index.parts.js +1 -0
- package/dist/button/root/README.md +43 -0
- package/dist/button/root/button-root.svelte +393 -0
- package/dist/button/root/button-root.svelte.d.ts +21 -0
- package/dist/button/root/button-test.svelte +76 -0
- package/dist/button/root/button-test.svelte.d.ts +11 -0
- package/dist/calendar/README.md +2 -1
- package/dist/calendar/TODO.md +21 -107
- package/dist/calendar/body-cell/README.md +15 -0
- package/dist/calendar/body-cell/calendar-body-cell.svelte +116 -41
- package/dist/calendar/grid/README.md +13 -0
- package/dist/calendar/grid-body/README.md +13 -0
- package/dist/calendar/grid-header/README.md +13 -0
- package/dist/calendar/header-cell/README.md +14 -0
- package/dist/calendar/heading/README.md +13 -0
- package/dist/calendar/index.d.ts +3 -3
- package/dist/calendar/index.js +3 -3
- package/dist/calendar/root/README.md +24 -0
- package/dist/calendar/root/calendar-root-test.svelte +4 -0
- package/dist/calendar/root/calendar-root-test.svelte.d.ts +1 -0
- package/dist/calendar/root/calendar-root.svelte +3 -0
- package/dist/calendar/root/calendar-root.svelte.d.ts +1 -0
- package/dist/calendar/root/context.d.ts +4 -0
- package/dist/calendar/root/context.js +28 -25
- package/dist/calendar/root/date-utils.d.ts +1 -1
- package/dist/calendar/root/date-utils.js +16 -26
- package/dist/calendar/trigger-next/README.md +14 -0
- package/dist/calendar/trigger-next/calendar-trigger-next.svelte +9 -4
- package/dist/calendar/trigger-next/calendar-trigger-next.svelte.d.ts +2 -1
- package/dist/calendar/trigger-previous/README.md +14 -0
- package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte +9 -4
- package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte.d.ts +2 -1
- package/dist/checkbox/README.md +53 -0
- package/dist/checkbox/TODO.md +16 -0
- package/dist/checkbox/index.d.ts +6 -0
- package/dist/checkbox/index.js +6 -0
- package/dist/checkbox/index.parts.d.ts +2 -0
- package/dist/checkbox/index.parts.js +2 -0
- package/dist/checkbox/indicator/README.md +23 -0
- package/dist/checkbox/indicator/checkbox-indicator.svelte +43 -0
- package/dist/checkbox/indicator/checkbox-indicator.svelte.d.ts +10 -0
- package/dist/checkbox/root/README.md +47 -0
- package/dist/checkbox/root/checkbox-label-test.svelte +10 -0
- package/dist/checkbox/root/checkbox-label-test.svelte.d.ts +18 -0
- package/dist/checkbox/root/checkbox-root.svelte +386 -0
- package/dist/checkbox/root/checkbox-root.svelte.d.ts +29 -0
- package/dist/checkbox/root/checkbox-test.svelte +59 -0
- package/dist/checkbox/root/checkbox-test.svelte.d.ts +18 -0
- package/dist/checkbox/root/context.d.ts +21 -0
- package/dist/checkbox/root/context.js +15 -0
- package/dist/clock/README.md +75 -0
- package/dist/clock/axis/README.md +24 -0
- package/dist/clock/axis/clock-axis.svelte +37 -0
- package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
- package/dist/clock/index.d.ts +10 -0
- package/dist/clock/index.js +10 -0
- package/dist/clock/index.parts.d.ts +4 -0
- package/dist/clock/index.parts.js +4 -0
- package/dist/clock/root/README.md +38 -0
- package/dist/clock/root/clock-root-test.svelte +62 -0
- package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
- package/dist/clock/root/clock-root.svelte +329 -0
- package/dist/clock/root/clock-root.svelte.d.ts +25 -0
- package/dist/clock/root/context.d.ts +22 -0
- package/dist/clock/root/context.js +15 -0
- package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
- package/dist/clock/root/resolve-visible-columns.js +16 -0
- package/dist/clock/root/time-utils.d.ts +48 -0
- package/dist/clock/root/time-utils.js +314 -0
- package/dist/clock/root/wheel-options.d.ts +17 -0
- package/dist/clock/root/wheel-options.js +63 -0
- package/dist/clock/wheel-column/README.md +25 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
- package/dist/clock/wheel-item/README.md +17 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
- package/dist/combobox/README.md +8 -2
- package/dist/combobox/TODO.md +28 -175
- package/dist/combobox/button/README.md +8 -3
- package/dist/combobox/button/combobox-button-test.svelte +27 -0
- package/dist/combobox/button/combobox-button-test.svelte.d.ts +6 -0
- package/dist/combobox/button/combobox-button.svelte +10 -11
- package/dist/combobox/clear/README.md +21 -0
- package/dist/combobox/clear/combobox-clear-test.svelte +34 -0
- package/dist/combobox/clear/combobox-clear-test.svelte.d.ts +3 -0
- package/dist/combobox/clear/combobox-clear.svelte +61 -0
- package/dist/combobox/clear/combobox-clear.svelte.d.ts +9 -0
- package/dist/combobox/index.d.ts +5 -3
- package/dist/combobox/index.js +5 -3
- package/dist/combobox/index.parts.d.ts +2 -0
- package/dist/combobox/index.parts.js +2 -0
- package/dist/combobox/input/combobox-input.svelte +44 -12
- package/dist/combobox/item/combobox-item-implicit-text-test.svelte +1 -1
- package/dist/combobox/item/combobox-listboxitem.svelte +14 -11
- package/dist/combobox/item-indicator/combobox-item-indicator.svelte +4 -15
- package/dist/combobox/list/combobox-listbox.svelte +1 -0
- package/dist/combobox/list/combobox-listbox.svelte.d.ts +2 -1
- package/dist/combobox/popover/README.md +18 -4
- package/dist/combobox/popover/combobox-popover-props-test.svelte +38 -0
- package/dist/combobox/popover/combobox-popover-props-test.svelte.d.ts +11 -0
- package/dist/combobox/popover/combobox-popover.svelte +166 -23
- package/dist/combobox/popover/combobox-popover.svelte.d.ts +3 -3
- package/dist/combobox/popover/combobox-scrollable-list-test.svelte +23 -0
- package/dist/combobox/popover/combobox-scrollable-list-test.svelte.d.ts +18 -0
- package/dist/combobox/root/README.md +1 -0
- package/dist/combobox/root/combobox-multiselect-test.svelte +5 -3
- package/dist/combobox/root/combobox-multiselect-test.svelte.d.ts +1 -0
- package/dist/combobox/root/combobox-numeric-string-id-test.svelte +1 -1
- package/dist/combobox/root/combobox-test.svelte +23 -4
- package/dist/combobox/root/combobox-test.svelte.d.ts +2 -0
- package/dist/combobox/root/combobox.svelte +119 -13
- package/dist/combobox/root/combobox.svelte.d.ts +1 -0
- package/dist/combobox/root/context.d.ts +19 -1
- package/dist/combobox/tag-remove/combobox-tag-remove.svelte +3 -2
- package/dist/combobox/trigger/README.md +21 -0
- package/dist/combobox/trigger/combobox-trigger.svelte +56 -0
- package/dist/combobox/trigger/combobox-trigger.svelte.d.ts +9 -0
- package/dist/datepicker/README.md +100 -0
- package/dist/datepicker/TODO.md +28 -0
- package/dist/datepicker/calendar/README.md +19 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte +60 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte +65 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte.d.ts +10 -0
- package/dist/datepicker/index.d.ts +18 -0
- package/dist/datepicker/index.js +18 -0
- package/dist/datepicker/index.parts.d.ts +14 -0
- package/dist/datepicker/index.parts.js +14 -0
- package/dist/datepicker/input/README.md +15 -0
- package/dist/datepicker/input/date-picker-input.svelte +108 -0
- package/dist/datepicker/input/date-picker-input.svelte.d.ts +11 -0
- package/dist/datepicker/internal/strict-props.d.ts +2 -0
- package/dist/datepicker/internal/strict-props.js +28 -0
- package/dist/datepicker/popover/README.md +20 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte +57 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte.d.ts +3 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte +45 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte.d.ts +18 -0
- package/dist/datepicker/popover/date-picker-popover.svelte +87 -0
- package/dist/datepicker/popover/date-picker-popover.svelte.d.ts +7 -0
- package/dist/datepicker/root/README.md +38 -0
- package/dist/datepicker/root/context.d.ts +43 -0
- package/dist/datepicker/root/context.js +15 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte +24 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte +41 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte +54 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte.d.ts +8 -0
- package/dist/datepicker/root/date-picker-root.svelte +495 -0
- package/dist/datepicker/root/date-picker-root.svelte.d.ts +24 -0
- package/dist/datepicker/root/date-picker-test.svelte +86 -0
- package/dist/datepicker/root/date-picker-test.svelte.d.ts +13 -0
- package/dist/datepicker/root/date-utils.d.ts +17 -0
- package/dist/datepicker/root/date-utils.js +138 -0
- package/dist/datepicker/root/draft-evaluation.d.ts +13 -0
- package/dist/datepicker/root/draft-evaluation.js +56 -0
- package/dist/datepicker/root/focus-controller.d.ts +3 -0
- package/dist/datepicker/root/focus-controller.js +15 -0
- package/dist/datepicker/root/open-change.d.ts +5 -0
- package/dist/datepicker/root/open-change.js +13 -0
- package/dist/datepicker/root/open-controller.d.ts +7 -0
- package/dist/datepicker/root/open-controller.js +15 -0
- package/dist/datepicker/root/segment-controller.d.ts +8 -0
- package/dist/datepicker/root/segment-controller.js +53 -0
- package/dist/datepicker/root/segment-state.d.ts +18 -0
- package/dist/datepicker/root/segment-state.js +134 -0
- package/dist/datepicker/root/value-commit.d.ts +4 -0
- package/dist/datepicker/root/value-commit.js +8 -0
- package/dist/datepicker/segment/README.md +14 -0
- package/dist/datepicker/segment/date-picker-segment.svelte +319 -0
- package/dist/datepicker/segment/date-picker-segment.svelte.d.ts +9 -0
- package/dist/datepicker/trigger/README.md +14 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte +110 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte.d.ts +9 -0
- package/dist/dialog/content/dialog-content.svelte +6 -6
- package/dist/dialog/index.d.ts +3 -3
- package/dist/dialog/index.js +2 -2
- package/dist/dialog/root/context.d.ts +2 -1
- package/dist/dialog/root/dialog-root.svelte +9 -2
- package/dist/dialog/trigger/dialog-trigger.svelte +3 -0
- package/dist/hooks/use-virtual-focus.svelte.js +3 -1
- package/dist/index.d.ts +31 -17
- package/dist/index.js +31 -17
- package/dist/input/README.md +38 -0
- package/dist/input/TODO.md +12 -0
- package/dist/input/input-test.svelte +43 -0
- package/dist/input/input-test.svelte.d.ts +12 -0
- package/dist/input/input.svelte +151 -7
- package/dist/input/input.svelte.d.ts +8 -2
- package/dist/listbox/index.d.ts +3 -3
- package/dist/listbox/index.js +3 -3
- package/dist/listbox/item/README.md +2 -1
- package/dist/listbox/item/listbox-item.svelte +260 -6
- package/dist/listbox/item/listbox-item.svelte.d.ts +6 -0
- package/dist/listbox/root/context.d.ts +6 -0
- package/dist/listbox/root/context.js +23 -13
- package/dist/listbox/root/listbox-test.svelte +14 -2
- package/dist/listbox/root/listbox-test.svelte.d.ts +1 -0
- package/dist/listbox/root/listbox.svelte +49 -2
- package/dist/listbox/root/listbox.svelte.d.ts +4 -2
- package/dist/popover/README.md +10 -0
- package/dist/popover/content/README.md +11 -0
- package/dist/popover/content/popover-content-controlled-close-test.svelte +30 -0
- package/dist/popover/content/popover-content-controlled-close-test.svelte.d.ts +3 -0
- package/dist/popover/content/popover-content-standalone-test.svelte +28 -0
- package/dist/popover/content/popover-content-standalone-test.svelte.d.ts +6 -0
- package/dist/popover/content/popover-content-test.svelte +32 -2
- package/dist/popover/content/popover-content-test.svelte.d.ts +3 -1
- package/dist/popover/content/popover-content.svelte +315 -24
- package/dist/popover/content/popover-content.svelte.d.ts +5 -1
- package/dist/popover/index.d.ts +3 -3
- package/dist/popover/index.js +3 -5
- package/dist/popover/root/README.md +10 -15
- package/dist/popover/root/context.d.ts +16 -7
- package/dist/popover/root/context.js +0 -2
- package/dist/popover/root/focus-state.d.ts +4 -0
- package/dist/popover/root/focus-state.js +33 -0
- package/dist/popover/root/popover-root.svelte +90 -17
- package/dist/popover/root/popover-root.svelte.d.ts +2 -1
- package/dist/popover/root/popover-test.svelte +2 -1
- package/dist/popover/root/popover-test.svelte.d.ts +2 -1
- package/dist/popover/trigger/popover-trigger-button-root-test.svelte +17 -0
- package/dist/popover/trigger/popover-trigger-button-root-test.svelte.d.ts +18 -0
- package/dist/popover/trigger/popover-trigger-button.svelte +9 -7
- package/dist/popover/trigger/popover-trigger.svelte +17 -5
- package/dist/portal/portal.svelte +3 -1
- package/dist/primitives/click-outside.d.ts +1 -1
- package/dist/primitives/click-outside.js +1 -1
- package/dist/primitives/floating.js +12 -4
- package/dist/primitives/focus-trap.d.ts +7 -2
- package/dist/primitives/focus-trap.js +50 -17
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/input-modality.d.ts +7 -0
- package/dist/primitives/input-modality.js +125 -0
- package/dist/primitives/keyboard-navigation.d.ts +1 -0
- package/dist/primitives/keyboard-navigation.js +17 -0
- package/dist/table/IMPLEMENTATION_NOTES.md +9 -0
- package/dist/table/PLAN-HIDDEN-COLUMNS.md +152 -0
- package/dist/table/PLAN.md +1336 -0
- package/dist/table/README.md +143 -0
- package/dist/table/SELECTION_CHECKBOX_PLAN.md +234 -0
- package/dist/table/TODO.md +138 -0
- package/dist/table/body/README.md +39 -0
- package/dist/table/body/table-body-items-test.svelte +45 -0
- package/dist/table/body/table-body-items-test.svelte.d.ts +18 -0
- package/dist/table/body/table-body.svelte +171 -0
- package/dist/table/body/table-body.svelte.d.ts +45 -0
- package/dist/table/cell/README.md +27 -0
- package/dist/table/cell/table-cell.svelte +253 -0
- package/dist/table/cell/table-cell.svelte.d.ts +4 -0
- package/dist/table/checkbox/README.md +40 -0
- package/dist/table/checkbox/table-checkbox-test.svelte +170 -0
- package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +22 -0
- package/dist/table/checkbox/table-checkbox.svelte +235 -0
- package/dist/table/checkbox/table-checkbox.svelte.d.ts +4 -0
- package/dist/table/checkbox-indicator/README.md +31 -0
- package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte +15 -0
- package/dist/table/checkbox-indicator/table-checkbox-indicator.svelte.d.ts +4 -0
- package/dist/table/column/README.md +36 -0
- package/dist/table/column/table-column.svelte +79 -0
- package/dist/table/column/table-column.svelte.d.ts +4 -0
- package/dist/table/column-header-cell/README.md +30 -0
- package/dist/table/column-header-cell/table-column-header-cell.svelte +271 -0
- package/dist/table/column-header-cell/table-column-header-cell.svelte.d.ts +4 -0
- package/dist/table/column-resizer/README.md +33 -0
- package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte +57 -0
- package/dist/table/column-resizer/table-column-resizer-fixed-width-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte +52 -0
- package/dist/table/column-resizer/table-column-resizer-freeze-layout-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-narrow-min-width-test.svelte +76 -0
- package/dist/table/column-resizer/table-column-resizer-narrow-min-width-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte +64 -0
- package/dist/table/column-resizer/table-column-resizer-overflow-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte +67 -0
- package/dist/table/column-resizer/table-column-resizer-padded-container-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte +87 -0
- package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte +84 -0
- package/dist/table/column-resizer/table-column-resizer-selection-column-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-test.svelte +77 -0
- package/dist/table/column-resizer/table-column-resizer-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte +64 -0
- package/dist/table/column-resizer/table-column-resizer-three-column-relative-test.svelte.d.ts +3 -0
- package/dist/table/column-resizer/table-column-resizer.svelte +610 -0
- package/dist/table/column-resizer/table-column-resizer.svelte.d.ts +4 -0
- package/dist/table/empty-state/README.md +27 -0
- package/dist/table/empty-state/table-empty-state.svelte +33 -0
- package/dist/table/empty-state/table-empty-state.svelte.d.ts +4 -0
- package/dist/table/footer/README.md +26 -0
- package/dist/table/footer/table-footer.svelte +13 -0
- package/dist/table/footer/table-footer.svelte.d.ts +4 -0
- package/dist/table/header/README.md +26 -0
- package/dist/table/header/table-header.svelte +13 -0
- package/dist/table/header/table-header.svelte.d.ts +4 -0
- package/dist/table/index.d.ts +18 -0
- package/dist/table/index.js +17 -0
- package/dist/table/index.parts.d.ts +13 -0
- package/dist/table/index.parts.js +13 -0
- package/dist/table/root/README.md +66 -0
- package/dist/table/root/context.d.ts +233 -0
- package/dist/table/root/context.js +2153 -0
- package/dist/table/root/table-reorder-test.svelte +64 -0
- package/dist/table/root/table-reorder-test.svelte.d.ts +3 -0
- package/dist/table/root/table-root.svelte +561 -0
- package/dist/table/root/table-root.svelte.d.ts +4 -0
- package/dist/table/root/table-ssr-wrapper-column.svelte +48 -0
- package/dist/table/root/table-ssr-wrapper-column.svelte.d.ts +4 -0
- package/dist/table/root/table-ssr-wrapper-context.d.ts +11 -0
- package/dist/table/root/table-ssr-wrapper-context.js +13 -0
- package/dist/table/root/table-ssr-wrapper-test.svelte +57 -0
- package/dist/table/root/table-ssr-wrapper-test.svelte.d.ts +3 -0
- package/dist/table/root/table-test.svelte +206 -0
- package/dist/table/root/table-test.svelte.d.ts +29 -0
- package/dist/table/row/README.md +29 -0
- package/dist/table/row/table-row.svelte +244 -0
- package/dist/table/row/table-row.svelte.d.ts +4 -0
- package/dist/table/sort-trigger/README.md +45 -0
- package/dist/table/sort-trigger/table-sort-trigger.svelte +183 -0
- package/dist/table/sort-trigger/table-sort-trigger.svelte.d.ts +4 -0
- package/dist/table/types.d.ts +112 -0
- package/dist/table/types.js +1 -0
- package/dist/table/utils/handle-body-keydown.d.ts +13 -0
- package/dist/table/utils/handle-body-keydown.js +67 -0
- package/dist/table/utils/visually-hidden-style.d.ts +1 -0
- package/dist/table/utils/visually-hidden-style.js +1 -0
- package/dist/test-utils/focus-contract.d.ts +3 -0
- package/dist/test-utils/focus-contract.js +26 -0
- package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
- package/dist/timepicker/README.md +97 -0
- package/dist/timepicker/TODO.md +86 -0
- package/dist/timepicker/clock/README.md +14 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
- package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
- package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
- package/dist/timepicker/index.d.ts +14 -0
- package/dist/timepicker/index.js +14 -0
- package/dist/timepicker/index.parts.d.ts +8 -0
- package/dist/timepicker/index.parts.js +8 -0
- package/dist/timepicker/input/README.md +15 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/input/time-picker-input.svelte +109 -0
- package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
- package/dist/timepicker/internal/strict-props.d.ts +4 -0
- package/dist/timepicker/internal/strict-props.js +51 -0
- package/dist/timepicker/popover/README.md +20 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
- package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
- package/dist/timepicker/root/README.md +42 -0
- package/dist/timepicker/root/context.d.ts +51 -0
- package/dist/timepicker/root/context.js +15 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-root.svelte +625 -0
- package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
- package/dist/timepicker/root/time-picker-test.svelte +72 -0
- package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
- package/dist/timepicker/root/time-utils.d.ts +1 -0
- package/dist/timepicker/root/time-utils.js +3 -0
- package/dist/timepicker/segment/README.md +14 -0
- package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
- package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
- package/dist/timepicker/trigger/README.md +14 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
- package/dist/utils/date-only.d.ts +11 -0
- package/dist/utils/date-only.js +53 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +33 -2
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD007 MD010 MD060 -->
|
|
2
|
+
|
|
3
|
+
# Table Plan
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Design and implement a new public `Table` component for the Svelte library, using the React Aria Components usability model as the main reference, but adapting it to the repository conventions: part-based composition, centralized state in `Root`, typed context, Svelte 5 runes, colocated tests for each part, and component/part documentation.
|
|
8
|
+
|
|
9
|
+
## Product Goals
|
|
10
|
+
|
|
11
|
+
- Provide a composable and readable API for headless tables.
|
|
12
|
+
- Prioritize robust and predictable keyboard navigation.
|
|
13
|
+
- Support row selection and sorting in the first version.
|
|
14
|
+
- Keep the initial scope reasonable so the component is not blocked by advanced features.
|
|
15
|
+
- Establish an internal foundation that can be extended later.
|
|
16
|
+
|
|
17
|
+
## Repository References
|
|
18
|
+
|
|
19
|
+
### Patterns to Follow
|
|
20
|
+
|
|
21
|
+
- Namespace-style exports and named exports, as in `ListBox`.
|
|
22
|
+
- Explicit typed context in `root/context.ts`.
|
|
23
|
+
- Controlled/uncontrolled state pattern in `Root`.
|
|
24
|
+
- Colocated interaction and accessibility tests.
|
|
25
|
+
- Base component README plus a README for each public part.
|
|
26
|
+
|
|
27
|
+
### Relevant Components and Utilities
|
|
28
|
+
|
|
29
|
+
- `packages/svelte/src/lib/listbox`
|
|
30
|
+
- `packages/svelte/src/lib/calendar`
|
|
31
|
+
- `packages/svelte/src/lib/primitives/keyboard-navigation.ts`
|
|
32
|
+
- `packages/svelte/src/lib/FOCUS_STATE_CONTRACT.md`
|
|
33
|
+
- `packages/svelte/src/lib/test-utils/focus-contract.ts`
|
|
34
|
+
|
|
35
|
+
## Decisions Already Made
|
|
36
|
+
|
|
37
|
+
- The v1 public API will be static/composable, not dynamic.
|
|
38
|
+
- `Table` must support cell focus and row-derived focus state.
|
|
39
|
+
- `Table.Column` is added to the anatomy to solve column metadata.
|
|
40
|
+
- `Table.Column` is a logical component with no DOM output; it registers column metadata in context. `Table.ColumnHeaderCell` renders the `<th>`.
|
|
41
|
+
- `Table.EmptyState` is a dedicated part for the body empty state.
|
|
42
|
+
- V1 must include:
|
|
43
|
+
- base anatomy
|
|
44
|
+
- `Table.Footer`
|
|
45
|
+
- `Table.EmptyState`
|
|
46
|
+
- keyboard grid navigation
|
|
47
|
+
- row selection
|
|
48
|
+
- sorting
|
|
49
|
+
|
|
50
|
+
## V1 Scope
|
|
51
|
+
|
|
52
|
+
### Proposed Public Anatomy
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<Table.Root aria-label="Users">
|
|
56
|
+
<Table.Header>
|
|
57
|
+
<Table.Row>
|
|
58
|
+
<!-- Column es lógico (sin DOM), solo registra metadata -->
|
|
59
|
+
<!-- Column is logical (no DOM), it only registers metadata -->
|
|
60
|
+
<Table.Column id="email" isRowHeader>
|
|
61
|
+
<Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
|
|
62
|
+
</Table.Column>
|
|
63
|
+
<Table.Column id="group" allowsSorting>
|
|
64
|
+
<Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
|
|
65
|
+
</Table.Column>
|
|
66
|
+
</Table.Row>
|
|
67
|
+
</Table.Header>
|
|
68
|
+
|
|
69
|
+
<Table.Body>
|
|
70
|
+
<Table.Row id="danilo">
|
|
71
|
+
<Table.Cell>danilo@example.com</Table.Cell>
|
|
72
|
+
<Table.Cell>Developer</Table.Cell>
|
|
73
|
+
</Table.Row>
|
|
74
|
+
<Table.Row id="zahra">
|
|
75
|
+
<Table.Cell>zahra@example.com</Table.Cell>
|
|
76
|
+
<Table.Cell>Admin</Table.Cell>
|
|
77
|
+
</Table.Row>
|
|
78
|
+
|
|
79
|
+
<!-- Only shown when Body has no rows -->
|
|
80
|
+
<Table.EmptyState>No users found.</Table.EmptyState>
|
|
81
|
+
</Table.Body>
|
|
82
|
+
|
|
83
|
+
<Table.Footer>
|
|
84
|
+
<Table.Row>
|
|
85
|
+
<Table.Cell>Total</Table.Cell>
|
|
86
|
+
<Table.Cell>2 users</Table.Cell>
|
|
87
|
+
</Table.Row>
|
|
88
|
+
</Table.Footer>
|
|
89
|
+
</Table.Root>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Target Public Parts
|
|
93
|
+
|
|
94
|
+
- `Table.Root`
|
|
95
|
+
- `Table.Column` — logical, no DOM
|
|
96
|
+
- `Table.Header`
|
|
97
|
+
- `Table.Body`
|
|
98
|
+
- `Table.EmptyState`
|
|
99
|
+
- `Table.Footer`
|
|
100
|
+
- `Table.Row`
|
|
101
|
+
- `Table.ColumnHeaderCell`
|
|
102
|
+
- `Table.Cell`
|
|
103
|
+
|
|
104
|
+
## Semantics and Accessibility
|
|
105
|
+
|
|
106
|
+
### Semantic Model
|
|
107
|
+
|
|
108
|
+
- The table should behave as an interactive table, not only as a passive `<table>`.
|
|
109
|
+
- The primary reference is a `grid` model with directional navigation.
|
|
110
|
+
- `Table.Root` must require an accessible name through `aria-label` or `aria-labelledby`.
|
|
111
|
+
- There must be support for marking a column as `isRowHeader` to improve screen reader announcements.
|
|
112
|
+
|
|
113
|
+
### Recommended Semantic Rendering
|
|
114
|
+
|
|
115
|
+
- `Table.Root` should render `<table role="grid">`.
|
|
116
|
+
- `Table.Header` should render `<thead role="rowgroup">`.
|
|
117
|
+
- `Table.Body` should render `<tbody role="rowgroup">`.
|
|
118
|
+
- `Table.Footer` should render `<tfoot role="rowgroup">`.
|
|
119
|
+
- `Table.Row` should render `<tr role="row">`.
|
|
120
|
+
- `Table.ColumnHeaderCell` should render `<th role="columnheader">`.
|
|
121
|
+
- `Table.Cell` should render:
|
|
122
|
+
- `<th scope="row" role="rowheader">` when the associated column has `isRowHeader`
|
|
123
|
+
- `<td role="gridcell">` in all other cases
|
|
124
|
+
|
|
125
|
+
This combination preserves real HTML semantics while allowing interactive grid behavior without inventing a structure entirely based on `div`s.
|
|
126
|
+
|
|
127
|
+
### Focus Model
|
|
128
|
+
|
|
129
|
+
Two related levels will be implemented:
|
|
130
|
+
|
|
131
|
+
1. **DOM focus on cell/header cell**
|
|
132
|
+
- `Table.ColumnHeaderCell` and `Table.Cell` are the real navigation targets.
|
|
133
|
+
- This keeps the behavior close to React Aria Components.
|
|
134
|
+
|
|
135
|
+
2. **Row-derived state**
|
|
136
|
+
- `Table.Row` exposes derived states such as focused row, selected row, or disabled row.
|
|
137
|
+
- This helps with styling and simplifies the visual experience.
|
|
138
|
+
|
|
139
|
+
### Target Keyboard Support in V1
|
|
140
|
+
|
|
141
|
+
- `Tab` enters and leaves the grid.
|
|
142
|
+
- Arrow keys navigate between header cells and body cells.
|
|
143
|
+
- `Home` / `End` move to the start/end of the current row.
|
|
144
|
+
- `Ctrl/Cmd + Home` and `Ctrl/Cmd + End` may be evaluated as an enhancement if the 2D engine can support them without extra complexity.
|
|
145
|
+
- `Enter` / `Space` should trigger row selection when appropriate.
|
|
146
|
+
- Sortable headers should respond to keyboard input to change sort order.
|
|
147
|
+
|
|
148
|
+
### Recommended Tabbability Strategy
|
|
149
|
+
|
|
150
|
+
- `Table.Root` should not be tabbable under normal conditions.
|
|
151
|
+
- Only one `Table.ColumnHeaderCell` or `Table.Cell` should have `tabindex="0"` at a time.
|
|
152
|
+
- All other navigable cells should have `tabindex="-1"`.
|
|
153
|
+
- When entering with `Tab` from outside:
|
|
154
|
+
- if there is a previously focused cell, focus is restored there
|
|
155
|
+
- otherwise focus enters the first navigable header cell
|
|
156
|
+
- if no header is navigable, focus enters the first navigable body cell
|
|
157
|
+
- `Shift+Tab` and `Tab` should allow the browser to leave the grid naturally from the active cell.
|
|
158
|
+
|
|
159
|
+
This preserves the roving tabindex pattern and avoids making `Root` compete with cells as a focus target.
|
|
160
|
+
|
|
161
|
+
### Decision for `Footer`
|
|
162
|
+
|
|
163
|
+
- In v1, `Table.Footer` is mainly semantic/structural.
|
|
164
|
+
- It will not participate in the main grid focus flow unless a clear need appears during implementation.
|
|
165
|
+
|
|
166
|
+
## Key Types
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
/** Sort direction */
|
|
170
|
+
type SortDirection = 'ascending' | 'descending';
|
|
171
|
+
|
|
172
|
+
/** Active sort descriptor */
|
|
173
|
+
type SortDescriptor = {
|
|
174
|
+
column: string;
|
|
175
|
+
direction: SortDirection;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Row selection mode */
|
|
179
|
+
type SelectionMode = 'none' | 'single' | 'multiple';
|
|
180
|
+
|
|
181
|
+
/** Selectable key (Row ids) */
|
|
182
|
+
type SelectionKey = string | number;
|
|
183
|
+
|
|
184
|
+
/** Set of selected keys (Row ids) */
|
|
185
|
+
type SelectionSet = Set<SelectionKey>;
|
|
186
|
+
|
|
187
|
+
/** Coordinate within the 2D grid (global, header row 0 = row 0) */
|
|
188
|
+
type GridCoord = {
|
|
189
|
+
row: number;
|
|
190
|
+
col: number;
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Proposed V1 API
|
|
195
|
+
|
|
196
|
+
### `Table.Root`
|
|
197
|
+
|
|
198
|
+
Responsibilities:
|
|
199
|
+
|
|
200
|
+
- register columns, rows, and cells
|
|
201
|
+
- maintain the 2D focus cursor
|
|
202
|
+
- expose controlled/uncontrolled selection state
|
|
203
|
+
- expose controlled/uncontrolled sorting state
|
|
204
|
+
- coordinate ARIA attributes and focus/selection data attributes
|
|
205
|
+
|
|
206
|
+
Tentative API:
|
|
207
|
+
|
|
208
|
+
- `aria-label` / `aria-labelledby` — required accessible name
|
|
209
|
+
- `selectionMode?: SelectionMode` — default `'none'`
|
|
210
|
+
- `selectedKeys?: SelectionSet`
|
|
211
|
+
- `defaultSelectedKeys?: SelectionSet`
|
|
212
|
+
- `onSelectionChange?: (keys: SelectionSet) => void`
|
|
213
|
+
- `sortDescriptor?: SortDescriptor`
|
|
214
|
+
- `defaultSortDescriptor?: SortDescriptor`
|
|
215
|
+
- `onSortChange?: (descriptor: SortDescriptor) => void`
|
|
216
|
+
- `disabledKeys?: Set<SelectionKey>` — disabled row ids
|
|
217
|
+
- `children`
|
|
218
|
+
|
|
219
|
+
### `Table.Column`
|
|
220
|
+
|
|
221
|
+
> Logical component — does not render its own DOM element. Registers column metadata in `Root` context and wraps `ColumnHeaderCell`.
|
|
222
|
+
|
|
223
|
+
Responsibilities:
|
|
224
|
+
|
|
225
|
+
- define stable column identity
|
|
226
|
+
- register column metadata in context (sorting, row header)
|
|
227
|
+
- register column width constraints in context
|
|
228
|
+
- serve as the anchor for sorting and row header semantics
|
|
229
|
+
- serve as the anchor for width/resizing behavior
|
|
230
|
+
|
|
231
|
+
Tentative API:
|
|
232
|
+
|
|
233
|
+
- `id: string` — stable column identity
|
|
234
|
+
- `allowsSorting?: boolean`
|
|
235
|
+
- `isRowHeader?: boolean`
|
|
236
|
+
- `textValue?: string`
|
|
237
|
+
- `width?: number | string`
|
|
238
|
+
- `defaultWidth?: number | string`
|
|
239
|
+
- `minWidth?: number`
|
|
240
|
+
- `maxWidth?: number`
|
|
241
|
+
|
|
242
|
+
### `Table.Header`
|
|
243
|
+
|
|
244
|
+
Responsibilities:
|
|
245
|
+
|
|
246
|
+
- contain header rows
|
|
247
|
+
- coordinate header semantics
|
|
248
|
+
|
|
249
|
+
### `Table.Body`
|
|
250
|
+
|
|
251
|
+
Responsibilities:
|
|
252
|
+
|
|
253
|
+
- contain data rows
|
|
254
|
+
- coordinate `Table.EmptyState` visibility when there are no rows
|
|
255
|
+
|
|
256
|
+
Tentative API:
|
|
257
|
+
|
|
258
|
+
- `children`
|
|
259
|
+
|
|
260
|
+
> The empty state is handled through the dedicated `Table.EmptyState` part inside `Body`, not through a prop.
|
|
261
|
+
|
|
262
|
+
### `Table.EmptyState`
|
|
263
|
+
|
|
264
|
+
Responsibilities:
|
|
265
|
+
|
|
266
|
+
- render empty-state content when `Body` has no rows
|
|
267
|
+
- hide itself automatically when rows exist
|
|
268
|
+
- not participate in the grid navigation model
|
|
269
|
+
|
|
270
|
+
Tentative API:
|
|
271
|
+
|
|
272
|
+
- `children`
|
|
273
|
+
|
|
274
|
+
Recommended semantics:
|
|
275
|
+
|
|
276
|
+
- `Table.EmptyState` should be a convenience part that internally renders a row and an empty-state cell.
|
|
277
|
+
- Its output should be equivalent to:
|
|
278
|
+
|
|
279
|
+
```svelte
|
|
280
|
+
<tr role="row" data-empty>
|
|
281
|
+
<td role="gridcell" colspan={columnCount}>
|
|
282
|
+
{children}
|
|
283
|
+
</td>
|
|
284
|
+
</tr>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `colspan` should be resolved automatically from the number of registered columns.
|
|
288
|
+
- It should not be focusable or participate in 2D navigation.
|
|
289
|
+
- It should only be allowed inside `Table.Body`.
|
|
290
|
+
|
|
291
|
+
This avoids invalid markup inside `<tbody>` and keeps the API convenient for consumers.
|
|
292
|
+
|
|
293
|
+
### `Table.Footer`
|
|
294
|
+
|
|
295
|
+
Responsibilities:
|
|
296
|
+
|
|
297
|
+
- contain summary/metadata rows
|
|
298
|
+
- not interfere with the main v1 navigation model
|
|
299
|
+
|
|
300
|
+
### `Table.Row`
|
|
301
|
+
|
|
302
|
+
Responsibilities:
|
|
303
|
+
|
|
304
|
+
- row identity
|
|
305
|
+
- disabled state
|
|
306
|
+
- derived selection state
|
|
307
|
+
- derived focus/selection styling
|
|
308
|
+
|
|
309
|
+
Tentative API:
|
|
310
|
+
|
|
311
|
+
- `id`
|
|
312
|
+
- `isDisabled?: boolean`
|
|
313
|
+
- `textValue?: string`
|
|
314
|
+
|
|
315
|
+
### `Table.ColumnHeaderCell`
|
|
316
|
+
|
|
317
|
+
Responsibilities:
|
|
318
|
+
|
|
319
|
+
- focus target in the header
|
|
320
|
+
- sorting trigger when the column allows it
|
|
321
|
+
- apply `aria-sort` automatically from `Root.sortDescriptor` and `Column.allowsSorting`
|
|
322
|
+
- `aria-sort` values: `ascending` | `descending` | `none`
|
|
323
|
+
- host the resize affordance when the consumer composes a `Table.ColumnResizer` inside it
|
|
324
|
+
|
|
325
|
+
### `Table.ColumnResizer`
|
|
326
|
+
|
|
327
|
+
Responsibilities:
|
|
328
|
+
|
|
329
|
+
- interactive resize handle for the current `Table.Column`
|
|
330
|
+
- consume column identity from `Table.Column` context rather than matching by visual position
|
|
331
|
+
- support pointer drag and keyboard resizing
|
|
332
|
+
- expose resize state for styling through data attributes
|
|
333
|
+
|
|
334
|
+
Tentative API:
|
|
335
|
+
|
|
336
|
+
- `step?: number` — keyboard delta in px, default `16`
|
|
337
|
+
- `shiftStep?: number` — larger keyboard delta in px, default `48`
|
|
338
|
+
- `children?`
|
|
339
|
+
- `class?`
|
|
340
|
+
|
|
341
|
+
### `Table.Cell`
|
|
342
|
+
|
|
343
|
+
Responsibilities:
|
|
344
|
+
|
|
345
|
+
- focus target within the body
|
|
346
|
+
- reflect derived row selection state
|
|
347
|
+
- support simple textual content in v1
|
|
348
|
+
|
|
349
|
+
## Data Attributes by Part
|
|
350
|
+
|
|
351
|
+
| Part | Attribute | Values | Description |
|
|
352
|
+
| ------------------ | --------------------- | ---------------------------- | ---------------------------------------- |
|
|
353
|
+
| `Root` | `data-selection-mode` | `none`, `single`, `multiple` | Active selection mode |
|
|
354
|
+
| `Header` | `data-table-header` | `''` | Semantic marker |
|
|
355
|
+
| `Body` | `data-table-body` | `''` | Semantic marker |
|
|
356
|
+
| `Body` | `data-empty` | `''` | Present when Body has no rows |
|
|
357
|
+
| `Footer` | `data-table-footer` | `''` | Semantic marker |
|
|
358
|
+
| `Row` (body) | `data-focused` | `''` | The row contains the focused cell |
|
|
359
|
+
| `Row` (body) | `data-selected` | `''` | Selected row |
|
|
360
|
+
| `Row` (body) | `data-disabled` | `''` | Disabled row |
|
|
361
|
+
| `ColumnHeaderCell` | `data-focused` | `''` | Focused header cell |
|
|
362
|
+
| `ColumnHeaderCell` | `data-sortable` | `''` | The column allows sorting |
|
|
363
|
+
| `ColumnHeaderCell` | `data-sort-direction` | `ascending`, `descending` | Active sort direction |
|
|
364
|
+
| `Cell` | `data-focused` | `''` | Focused cell |
|
|
365
|
+
| `Cell` | `data-row-selected` | `''` | The row containing this cell is selected |
|
|
366
|
+
|
|
367
|
+
## ARIA Attributes by Part
|
|
368
|
+
|
|
369
|
+
| Part | Attribute | Value | Description |
|
|
370
|
+
| ------------------ | -------------------------------- | --------------------------------- | ------------------------------------------------ |
|
|
371
|
+
| `Root` | `role` | `grid` | Interactive table |
|
|
372
|
+
| `Root` | `aria-label` / `aria-labelledby` | string | Accessible name (required) |
|
|
373
|
+
| `Root` | `aria-multiselectable` | `true` | Present when `selectionMode='multiple'` |
|
|
374
|
+
| `Header` | `role` | `rowgroup` | Header row group |
|
|
375
|
+
| `Body` | `role` | `rowgroup` | Data row group |
|
|
376
|
+
| `Footer` | `role` | `rowgroup` | Footer row group |
|
|
377
|
+
| `Row` | `role` | `row` | Row |
|
|
378
|
+
| `Row` (body) | `aria-selected` | `true` / `false` | Selection state (when `selectionMode != 'none'`) |
|
|
379
|
+
| `Row` (body) | `aria-disabled` | `true` | Disabled row |
|
|
380
|
+
| `ColumnHeaderCell` | `role` | `columnheader` | Column header |
|
|
381
|
+
| `ColumnHeaderCell` | `aria-sort` | `ascending`, `descending`, `none` | Sort direction (only when `allowsSorting`) |
|
|
382
|
+
| `Cell` | `role` | `gridcell` or `rowheader` | `rowheader` if the column has `isRowHeader` |
|
|
383
|
+
| `EmptyState` | `role` | `row` + internal `gridcell` | Semantic, non-navigable empty-state row |
|
|
384
|
+
|
|
385
|
+
## What V1 Does Not Include
|
|
386
|
+
|
|
387
|
+
### Out of Scope
|
|
388
|
+
|
|
389
|
+
- drag and drop
|
|
390
|
+
- async loading / load more
|
|
391
|
+
- API pública dinámica con `items` y `columns`
|
|
392
|
+
- row links / `href`-style navigation semantics
|
|
393
|
+
- typeahead
|
|
394
|
+
- focus management para elementos interactivos dentro de `Cell`
|
|
395
|
+
- focus management for interactive elements inside `Cell`
|
|
396
|
+
- nested headers / grouped columns
|
|
397
|
+
- cell selection
|
|
398
|
+
- cell selection
|
|
399
|
+
- virtualización
|
|
400
|
+
- virtualization
|
|
401
|
+
- soporte complejo de `colSpan` / `rowSpan`
|
|
402
|
+
- complex `colSpan` / `rowSpan` support
|
|
403
|
+
- footer navegable como parte del grid principal
|
|
404
|
+
- footer navigation as part of the main grid
|
|
405
|
+
|
|
406
|
+
## Advanced Feature Matrix
|
|
407
|
+
|
|
408
|
+
| Feature | Main Complexity | Risk | Recommendation |
|
|
409
|
+
| ---------------------------------- | -------------------------------------------------------- | ----------- | ------------------ |
|
|
410
|
+
| Column resizing | width state, handles, pointer + keyboard, persistence | high | next planned phase |
|
|
411
|
+
| Drag and drop | reorder, drop targets, SR + keyboard + pointer | very high | keep out of v1 |
|
|
412
|
+
| Async loading / load more | scroll state, sentinel rows, partial states | high | keep out of v1 |
|
|
413
|
+
| Dynamic `items` / `columns` API | collection, stable ids, render functions, memoization | high | defer |
|
|
414
|
+
| Row actions / `onRowAction` | action-selection conflicts across mouse and keyboard | medium/high | next planned phase |
|
|
415
|
+
| Row links / `href` semantics | HTML limitations, router integration, native link parity | high | defer |
|
|
416
|
+
| Interactive content inside `Cell` | focus handoff between grid and nested controls | very high | keep out of v1 |
|
|
417
|
+
| Typeahead | depends on stable collection and consistent `textValue` | medium | defer |
|
|
418
|
+
| Nested headers / column groups | spans, navigation, and complex semantics | high | keep out of v1 |
|
|
419
|
+
| Cell selection | changes the entire interaction model | high | keep out of v1 |
|
|
420
|
+
| Full `selectionBehavior="replace"` | modifiers and fine-grained focus/selection semantics | medium/high | defer |
|
|
421
|
+
| Virtualization | strong decoupling between collection and DOM | very high | keep out of v1 |
|
|
422
|
+
| Integrated select-all | useful UX but depends on mature selection behavior | medium | phase 2 |
|
|
423
|
+
| Complex `colSpan` / `rowSpan` | breaks the rectangular grid model | high | defer |
|
|
424
|
+
| Navigable footer | adds another region to the focus model | medium | avoid in v1 |
|
|
425
|
+
|
|
426
|
+
## Proposed Internal Architecture
|
|
427
|
+
|
|
428
|
+
## Root State
|
|
429
|
+
|
|
430
|
+
Create `packages/svelte/src/lib/table/root/context.ts` with responsibilities for:
|
|
431
|
+
|
|
432
|
+
- column registration
|
|
433
|
+
- row registration
|
|
434
|
+
- cell registration by coordinate
|
|
435
|
+
- current focus resolution
|
|
436
|
+
- 2D navigation
|
|
437
|
+
- row selection state
|
|
438
|
+
- sorting state
|
|
439
|
+
- derived utilities for each part
|
|
440
|
+
|
|
441
|
+
## 2D Navigation Engine
|
|
442
|
+
|
|
443
|
+
It is not enough to depend only on `createKeyboardNavigation()` because it currently solves linear navigation. `Table` needs a dedicated engine able to:
|
|
444
|
+
|
|
445
|
+
- know rows and columns by index
|
|
446
|
+
- move focus in two dimensions
|
|
447
|
+
- distinguish header and body
|
|
448
|
+
- resolve missing or disabled cells
|
|
449
|
+
- derive the active row from the active cell
|
|
450
|
+
|
|
451
|
+
### Proposed Internal Interface
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
interface GridNavigation {
|
|
455
|
+
/** Currently focused coordinate (global, header row 0 = row 0) */
|
|
456
|
+
focusedCoord: GridCoord;
|
|
457
|
+
|
|
458
|
+
/** Moves focus in the given direction, skipping non-navigable cells */
|
|
459
|
+
move(direction: 'up' | 'down' | 'left' | 'right'): void;
|
|
460
|
+
|
|
461
|
+
/** Moves focus to the first cell in the current row */
|
|
462
|
+
moveToRowStart(): void;
|
|
463
|
+
|
|
464
|
+
/** Moves focus to the last cell in the current row */
|
|
465
|
+
moveToRowEnd(): void;
|
|
466
|
+
|
|
467
|
+
/** Moves focus to the first cell in the grid (header[0][0]) */
|
|
468
|
+
moveToGridStart(): void;
|
|
469
|
+
|
|
470
|
+
/** Moves focus to the last cell in the body */
|
|
471
|
+
moveToGridEnd(): void;
|
|
472
|
+
|
|
473
|
+
/** Registers a navigable cell in the grid */
|
|
474
|
+
register(coord: GridCoord, element: HTMLElement): void;
|
|
475
|
+
|
|
476
|
+
/** Unregisters a cell from the grid */
|
|
477
|
+
unregister(coord: GridCoord): void;
|
|
478
|
+
|
|
479
|
+
/** Checks whether a coordinate is navigable (exists and is not disabled) */
|
|
480
|
+
isNavigable(coord: GridCoord): boolean;
|
|
481
|
+
|
|
482
|
+
/** Returns the body row index from a global coordinate */
|
|
483
|
+
toBodyRowIndex(globalRow: number): number | null;
|
|
484
|
+
|
|
485
|
+
/** Applies DOM focus to the element at the given coordinate */
|
|
486
|
+
focusCell(coord: GridCoord): void;
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Key-to-Action Mapping
|
|
491
|
+
|
|
492
|
+
| Key | Action |
|
|
493
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
494
|
+
| `ArrowUp` | `move('up')` |
|
|
495
|
+
| `ArrowDown` | `move('down')` |
|
|
496
|
+
| `ArrowLeft` | `move('left')` |
|
|
497
|
+
| `ArrowRight` | `move('right')` |
|
|
498
|
+
| `Home` | `moveToRowStart()` |
|
|
499
|
+
| `End` | `moveToRowEnd()` |
|
|
500
|
+
| `Ctrl+Home` / `Cmd+Home` | `moveToGridStart()` |
|
|
501
|
+
| `Ctrl+End` / `Cmd+End` | `moveToGridEnd()` |
|
|
502
|
+
| `Tab` | Leaves the grid (focus to the next tabbable element) |
|
|
503
|
+
| `Shift+Tab` | Leaves the grid (focus to the previous tabbable element) |
|
|
504
|
+
| `Enter` / `Space` | Select row (body) or toggle sort (sortable header) |
|
|
505
|
+
|
|
506
|
+
## Selection
|
|
507
|
+
|
|
508
|
+
- V1 selection is row-based.
|
|
509
|
+
- Focus must not be equivalent to selection.
|
|
510
|
+
- `single` and `multiple` must work in both controlled and uncontrolled mode.
|
|
511
|
+
- Disabled rows must not be selectable.
|
|
512
|
+
|
|
513
|
+
## Sorting
|
|
514
|
+
|
|
515
|
+
- Sort state should live in `Root`.
|
|
516
|
+
- `Column` defines whether a column allows sorting.
|
|
517
|
+
- `ColumnHeaderCell` triggers sort changes.
|
|
518
|
+
- The component reflects state, but does not necessarily mutate data automatically; that remains the consumer's responsibility.
|
|
519
|
+
|
|
520
|
+
## Planned File Structure
|
|
521
|
+
|
|
522
|
+
### New Component Files
|
|
523
|
+
|
|
524
|
+
- `packages/svelte/src/lib/table/index.ts`
|
|
525
|
+
- `packages/svelte/src/lib/table/index.parts.ts`
|
|
526
|
+
- `packages/svelte/src/lib/table/README.md`
|
|
527
|
+
- `packages/svelte/src/lib/table/TODO.md`
|
|
528
|
+
|
|
529
|
+
### Root
|
|
530
|
+
|
|
531
|
+
- `packages/svelte/src/lib/table/root/table-root.svelte`
|
|
532
|
+
- `packages/svelte/src/lib/table/root/context.ts`
|
|
533
|
+
- `packages/svelte/src/lib/table/root/table-root.test.ts`
|
|
534
|
+
- `packages/svelte/src/lib/table/root/table-test.svelte`
|
|
535
|
+
- `packages/svelte/src/lib/table/root/README.md`
|
|
536
|
+
|
|
537
|
+
### Public Parts
|
|
538
|
+
|
|
539
|
+
- `packages/svelte/src/lib/table/column/table-column.svelte`
|
|
540
|
+
- `packages/svelte/src/lib/table/column/README.md`
|
|
541
|
+
- `packages/svelte/src/lib/table/column/table-column.test.ts`
|
|
542
|
+
|
|
543
|
+
## Phase 2: Column Resizing Plan
|
|
544
|
+
|
|
545
|
+
### Resize Goal
|
|
546
|
+
|
|
547
|
+
Add column resizing in a way that follows the React Aria Components mental model while preserving the repository's existing `Table` architecture: logical `Table.Column`, state in `Table.Root`, typed context, native table semantics, and composable parts.
|
|
548
|
+
|
|
549
|
+
### Functional Contract
|
|
550
|
+
|
|
551
|
+
- Resizing must target the column whose composition includes the resize handle.
|
|
552
|
+
- The resize handle must live inside the header composition for that column, not in a parallel list of handlers.
|
|
553
|
+
- The active column is resolved from `Table.Column` context, never by visual index guessing alone.
|
|
554
|
+
- Functional behavior should mirror RAC:
|
|
555
|
+
- a column opts into resizing via column metadata
|
|
556
|
+
- a dedicated resizer part provides the interactive affordance
|
|
557
|
+
- widths can be controlled or uncontrolled
|
|
558
|
+
- pointer and keyboard resizing are both supported
|
|
559
|
+
|
|
560
|
+
### Recommended Public Composition
|
|
561
|
+
|
|
562
|
+
```svelte
|
|
563
|
+
<Table.Root aria-label="Users" bind:columnWidths>
|
|
564
|
+
<Table.Header>
|
|
565
|
+
<Table.Row>
|
|
566
|
+
<Table.Column id="email" isRowHeader allowsSorting defaultWidth={280} minWidth={180}>
|
|
567
|
+
<Table.ColumnHeaderCell>
|
|
568
|
+
<span>Email</span>
|
|
569
|
+
<Table.ColumnResizer />
|
|
570
|
+
</Table.ColumnHeaderCell>
|
|
571
|
+
</Table.Column>
|
|
572
|
+
|
|
573
|
+
<Table.Column id="group" allowsSorting defaultWidth={180} minWidth={140}>
|
|
574
|
+
<Table.ColumnHeaderCell>
|
|
575
|
+
<span>Group</span>
|
|
576
|
+
<Table.ColumnResizer />
|
|
577
|
+
</Table.ColumnHeaderCell>
|
|
578
|
+
</Table.Column>
|
|
579
|
+
</Table.Row>
|
|
580
|
+
</Table.Header>
|
|
581
|
+
|
|
582
|
+
<Table.Body>
|
|
583
|
+
<!-- rows -->
|
|
584
|
+
</Table.Body>
|
|
585
|
+
</Table.Root>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### API Recommendation
|
|
589
|
+
|
|
590
|
+
#### Width Props on `Table.Column`
|
|
591
|
+
|
|
592
|
+
Add the following props:
|
|
593
|
+
|
|
594
|
+
- `width?: number | string`
|
|
595
|
+
- `defaultWidth?: number | string`
|
|
596
|
+
- `minWidth?: number`
|
|
597
|
+
- `maxWidth?: number`
|
|
598
|
+
|
|
599
|
+
Notes:
|
|
600
|
+
|
|
601
|
+
- `width` is the controlled width for the column.
|
|
602
|
+
- `defaultWidth` is the uncontrolled initial width.
|
|
603
|
+
- rendering `Table.ColumnResizer` is the public resize opt-in for the owning column.
|
|
604
|
+
|
|
605
|
+
#### Width State on `Table.Root`
|
|
606
|
+
|
|
607
|
+
Add root-level width state APIs:
|
|
608
|
+
|
|
609
|
+
- `columnWidths?: Map<string, number>`
|
|
610
|
+
- `defaultColumnWidths?: Map<string, number>`
|
|
611
|
+
- `onColumnWidthsChange?: (widths: Map<string, number>) => void`
|
|
612
|
+
- `onColumnResizeStart?: (columnId: string) => void`
|
|
613
|
+
- `onColumnResizeEnd?: (widths: Map<string, number>) => void`
|
|
614
|
+
|
|
615
|
+
Notes:
|
|
616
|
+
|
|
617
|
+
- Controlled/uncontrolled width state should mirror the existing `selectedKeys` and `sortDescriptor` contracts.
|
|
618
|
+
- Widths in root state should be normalized to px numbers even if consumer input allows string forms.
|
|
619
|
+
|
|
620
|
+
#### `Table.ColumnResizer` Part
|
|
621
|
+
|
|
622
|
+
Public part to place inside `Table.ColumnHeaderCell`.
|
|
623
|
+
|
|
624
|
+
Tentative props:
|
|
625
|
+
|
|
626
|
+
- `step?: number`
|
|
627
|
+
- `shiftStep?: number`
|
|
628
|
+
- `class?: string`
|
|
629
|
+
- `children?: Snippet`
|
|
630
|
+
|
|
631
|
+
No `columnId` prop should be needed; it must use `Table.Column` context.
|
|
632
|
+
|
|
633
|
+
### Why Not a Parallel `ColumnHandler`
|
|
634
|
+
|
|
635
|
+
This API should explicitly avoid a separate sibling structure like:
|
|
636
|
+
|
|
637
|
+
```svelte
|
|
638
|
+
<Table.Column id="email" />
|
|
639
|
+
<Table.Column id="group" />
|
|
640
|
+
<Table.ColumnHandler index={0} />
|
|
641
|
+
<Table.ColumnHandler index={1} />
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Reasons:
|
|
645
|
+
|
|
646
|
+
- position-based matching becomes fragile with dynamic columns
|
|
647
|
+
- it duplicates the concept of column identity
|
|
648
|
+
- it becomes harder to keep sorting, row-header semantics, and resizing anchored to the same column contract
|
|
649
|
+
- it does not follow the RAC model, where resizing is column-owned and the handle is colocated with the header content
|
|
650
|
+
|
|
651
|
+
### Width Model
|
|
652
|
+
|
|
653
|
+
#### Effective Width Resolution
|
|
654
|
+
|
|
655
|
+
For each registered column, compute the effective width from highest to lowest precedence:
|
|
656
|
+
|
|
657
|
+
1. `Table.Root.columnWidths.get(columnId)`
|
|
658
|
+
2. `Table.Column.width`
|
|
659
|
+
3. `Table.Root.defaultColumnWidths.get(columnId)`
|
|
660
|
+
4. `Table.Column.defaultWidth`
|
|
661
|
+
5. no explicit width
|
|
662
|
+
|
|
663
|
+
Then clamp the result against:
|
|
664
|
+
|
|
665
|
+
- `minWidth`
|
|
666
|
+
- `maxWidth`
|
|
667
|
+
|
|
668
|
+
#### Initial Implementation Constraint
|
|
669
|
+
|
|
670
|
+
For the first resizing implementation, normalize widths to px values.
|
|
671
|
+
|
|
672
|
+
- Accept `number` as px.
|
|
673
|
+
- Optionally accept `"123px"` and normalize it to `123`.
|
|
674
|
+
- Defer `%`, `fr`, and more advanced layout math until the base feature is stable.
|
|
675
|
+
|
|
676
|
+
This keeps the state model simple and reduces layout bugs.
|
|
677
|
+
|
|
678
|
+
### Rendering Strategy
|
|
679
|
+
|
|
680
|
+
The recommended implementation is to generate a `<colgroup>` inside `Table.Root` from the registered columns and the effective widths.
|
|
681
|
+
|
|
682
|
+
Reasons:
|
|
683
|
+
|
|
684
|
+
- widths apply consistently to both header and body cells
|
|
685
|
+
- native table layout remains intact
|
|
686
|
+
- it avoids pushing per-cell width styles into every `Table.Cell`
|
|
687
|
+
- it scales better as the feature grows
|
|
688
|
+
|
|
689
|
+
Planned approach:
|
|
690
|
+
|
|
691
|
+
- `Table.Root` renders a managed `<colgroup>` before children
|
|
692
|
+
- each registered column maps to one `<col>`
|
|
693
|
+
- effective width is applied to the `<col>`
|
|
694
|
+
- body and header cells keep their semantic markup unchanged
|
|
695
|
+
|
|
696
|
+
Fallback if `<colgroup>` proves insufficient for some cases:
|
|
697
|
+
|
|
698
|
+
- apply inline width/min-width styles to header cells and derived styles to body cells by column index
|
|
699
|
+
|
|
700
|
+
But `<colgroup>` should be the default strategy.
|
|
701
|
+
|
|
702
|
+
### Interaction Model
|
|
703
|
+
|
|
704
|
+
#### Pointer
|
|
705
|
+
|
|
706
|
+
- pointer down on `Table.ColumnResizer` starts resizing for its current column
|
|
707
|
+
- movement computes a new width in px relative to the starting header width
|
|
708
|
+
- width updates continuously during drag
|
|
709
|
+
- pointer up finalizes the interaction and calls `onColumnResizeEnd`
|
|
710
|
+
|
|
711
|
+
#### Keyboard
|
|
712
|
+
|
|
713
|
+
- the resizer is focusable
|
|
714
|
+
- `ArrowLeft` reduces width by `step`
|
|
715
|
+
- `ArrowRight` increases width by `step`
|
|
716
|
+
- `Shift+ArrowLeft` / `Shift+ArrowRight` use `shiftStep`
|
|
717
|
+
- resize keyboard handling should not hijack the existing table cell navigation when the resizer itself is not focused
|
|
718
|
+
|
|
719
|
+
### Accessibility Contract
|
|
720
|
+
|
|
721
|
+
`Table.ColumnResizer` should behave like a column separator/resizer control.
|
|
722
|
+
|
|
723
|
+
Recommended attributes:
|
|
724
|
+
|
|
725
|
+
- `role="separator"`
|
|
726
|
+
- `aria-orientation="vertical"`
|
|
727
|
+
- `aria-valuenow`
|
|
728
|
+
- `aria-valuemin`
|
|
729
|
+
- `aria-valuemax`
|
|
730
|
+
- accessible label derived from the current column, for example `Resize Email column`
|
|
731
|
+
|
|
732
|
+
Derived data attributes:
|
|
733
|
+
|
|
734
|
+
- `data-resizing`
|
|
735
|
+
- `data-focused`
|
|
736
|
+
- `data-focus-visible`
|
|
737
|
+
- `data-resizable-direction="right"`
|
|
738
|
+
|
|
739
|
+
### Internal Architecture Additions
|
|
740
|
+
|
|
741
|
+
#### `root/context.ts`
|
|
742
|
+
|
|
743
|
+
Extend column registration to include:
|
|
744
|
+
|
|
745
|
+
- `width`
|
|
746
|
+
- `defaultWidth`
|
|
747
|
+
- `minWidth`
|
|
748
|
+
- `maxWidth`
|
|
749
|
+
|
|
750
|
+
Add root-level APIs for:
|
|
751
|
+
|
|
752
|
+
- resolving effective column widths
|
|
753
|
+
- updating a column width by `columnId`
|
|
754
|
+
- starting/ending resize interactions
|
|
755
|
+
- reading resize state for a column
|
|
756
|
+
|
|
757
|
+
#### New Part Context Usage
|
|
758
|
+
|
|
759
|
+
`Table.ColumnResizer` should consume:
|
|
760
|
+
|
|
761
|
+
- `Table.Column` context for `columnId`
|
|
762
|
+
- `Table.Root` context for width state and resize actions
|
|
763
|
+
|
|
764
|
+
It should not require positional props like `index` or `for`.
|
|
765
|
+
|
|
766
|
+
### Planned File Additions
|
|
767
|
+
|
|
768
|
+
- `packages/svelte/src/lib/table/column-resizer/table-column-resizer.svelte`
|
|
769
|
+
- `packages/svelte/src/lib/table/column-resizer/README.md`
|
|
770
|
+
- `packages/svelte/src/lib/table/column-resizer/table-column-resizer.test.ts`
|
|
771
|
+
|
|
772
|
+
Planned touched files:
|
|
773
|
+
|
|
774
|
+
- `packages/svelte/src/lib/table/index.parts.ts`
|
|
775
|
+
- `packages/svelte/src/lib/table/index.ts`
|
|
776
|
+
- `packages/svelte/src/lib/table/root/context.ts`
|
|
777
|
+
- `packages/svelte/src/lib/table/root/table-root.svelte`
|
|
778
|
+
- `packages/svelte/src/lib/table/column/table-column.svelte`
|
|
779
|
+
- `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.svelte`
|
|
780
|
+
- `packages/svelte/src/lib/table/root/table-root.test.ts`
|
|
781
|
+
- `docs/src/routes/docs/table/+page.svelte`
|
|
782
|
+
|
|
783
|
+
### Testing Plan
|
|
784
|
+
|
|
785
|
+
Minimum regression coverage:
|
|
786
|
+
|
|
787
|
+
- renders a resize handle only for columns composed with `Table.ColumnResizer`
|
|
788
|
+
- dragging the resizer changes the associated column width only
|
|
789
|
+
- resizing one column does not corrupt neighboring column identity
|
|
790
|
+
- controlled `columnWidths` updates are reflected in the DOM
|
|
791
|
+
- uncontrolled `defaultWidth` is honored on mount
|
|
792
|
+
- keyboard resizing updates width in deterministic steps
|
|
793
|
+
- min/max constraints are enforced
|
|
794
|
+
- focus and pointer interactions on the resizer do not break table navigation
|
|
795
|
+
|
|
796
|
+
### Recommended Implementation Order
|
|
797
|
+
|
|
798
|
+
1. Add API fields to `Table.Column` and root context registration.
|
|
799
|
+
2. Add root width state and effective width resolution.
|
|
800
|
+
3. Render managed `<colgroup>` from `Table.Root`.
|
|
801
|
+
4. Add `Table.ColumnResizer` pointer interaction.
|
|
802
|
+
5. Add keyboard and ARIA support for the resizer.
|
|
803
|
+
6. Add docs/demo and controlled/uncontrolled tests.
|
|
804
|
+
|
|
805
|
+
### Non-Goals for the First Resize Release
|
|
806
|
+
|
|
807
|
+
- percentage/fr width math
|
|
808
|
+
- column resize persistence outside consumer-provided state
|
|
809
|
+
- multi-column proportional redistribution
|
|
810
|
+
- double-click auto-fit
|
|
811
|
+
- resize in nested/grouped headers
|
|
812
|
+
- resizable footer-specific behavior
|
|
813
|
+
- `packages/svelte/src/lib/table/header/table-header.svelte`
|
|
814
|
+
- `packages/svelte/src/lib/table/header/README.md`
|
|
815
|
+
- `packages/svelte/src/lib/table/header/table-header.test.ts`
|
|
816
|
+
- `packages/svelte/src/lib/table/body/table-body.svelte`
|
|
817
|
+
- `packages/svelte/src/lib/table/body/README.md`
|
|
818
|
+
- `packages/svelte/src/lib/table/body/table-body.test.ts`
|
|
819
|
+
- `packages/svelte/src/lib/table/empty-state/table-empty-state.svelte`
|
|
820
|
+
- `packages/svelte/src/lib/table/empty-state/README.md`
|
|
821
|
+
- `packages/svelte/src/lib/table/empty-state/table-empty-state.test.ts`
|
|
822
|
+
- `packages/svelte/src/lib/table/footer/table-footer.svelte`
|
|
823
|
+
- `packages/svelte/src/lib/table/footer/README.md`
|
|
824
|
+
- `packages/svelte/src/lib/table/footer/table-footer.test.ts`
|
|
825
|
+
- `packages/svelte/src/lib/table/row/table-row.svelte`
|
|
826
|
+
- `packages/svelte/src/lib/table/row/README.md`
|
|
827
|
+
- `packages/svelte/src/lib/table/row/table-row.test.ts`
|
|
828
|
+
- `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.svelte`
|
|
829
|
+
- `packages/svelte/src/lib/table/column-header-cell/README.md`
|
|
830
|
+
- `packages/svelte/src/lib/table/column-header-cell/table-column-header-cell.test.ts`
|
|
831
|
+
- `packages/svelte/src/lib/table/cell/table-cell.svelte`
|
|
832
|
+
- `packages/svelte/src/lib/table/cell/README.md`
|
|
833
|
+
- `packages/svelte/src/lib/table/cell/table-cell.test.ts`
|
|
834
|
+
|
|
835
|
+
### Package Integrations
|
|
836
|
+
|
|
837
|
+
- `packages/svelte/src/lib/index.ts`
|
|
838
|
+
- `packages/svelte/package.json`
|
|
839
|
+
- `docs/src/routes/docs/table/+page.svelte`
|
|
840
|
+
- `README.md`
|
|
841
|
+
- `.changeset/*`
|
|
842
|
+
|
|
843
|
+
## Implementation Strategy
|
|
844
|
+
|
|
845
|
+
### Phase 1: Public Contract
|
|
846
|
+
|
|
847
|
+
- finalize the public anatomy
|
|
848
|
+
- finalize prop naming
|
|
849
|
+
- define exact responsibilities for each part
|
|
850
|
+
- document minimal examples
|
|
851
|
+
|
|
852
|
+
### Phase 2: State and Navigation
|
|
853
|
+
|
|
854
|
+
- implement root context
|
|
855
|
+
- implement column/row/cell registration
|
|
856
|
+
- build 2D navigation
|
|
857
|
+
- add data attributes and focus contract
|
|
858
|
+
|
|
859
|
+
### Phase 3: Selection and Sorting
|
|
860
|
+
|
|
861
|
+
- add controlled/uncontrolled selection
|
|
862
|
+
- add disabled rows
|
|
863
|
+
- add per-column sorting
|
|
864
|
+
- validate keyboard behavior
|
|
865
|
+
|
|
866
|
+
### Phase 4: Docs and Tests
|
|
867
|
+
|
|
868
|
+
- tests for each public part
|
|
869
|
+
- integral harness
|
|
870
|
+
- base README and per-part READMEs
|
|
871
|
+
- docs demo page
|
|
872
|
+
- changeset
|
|
873
|
+
|
|
874
|
+
## Resize Testing Plan
|
|
875
|
+
|
|
876
|
+
### Minimum Cases
|
|
877
|
+
|
|
878
|
+
- correct semantic rendering
|
|
879
|
+
- required accessible label
|
|
880
|
+
- arrow-key navigation between cells
|
|
881
|
+
- row-level `Home` / `End`
|
|
882
|
+
- entering/leaving the grid with `Tab`
|
|
883
|
+
- correct focus-visible and focus attributes
|
|
884
|
+
- single selection
|
|
885
|
+
- multiple selection
|
|
886
|
+
- disabled rows
|
|
887
|
+
- sorting via keyboard and pointer
|
|
888
|
+
- empty state
|
|
889
|
+
- `Footer` renders without breaking main navigation
|
|
890
|
+
|
|
891
|
+
### Test Inspirations
|
|
892
|
+
|
|
893
|
+
- `ListBox` tests
|
|
894
|
+
- `Calendar` tests
|
|
895
|
+
- focus contract tests
|
|
896
|
+
|
|
897
|
+
## Main Risks
|
|
898
|
+
|
|
899
|
+
- without a clear 2D engine, the component may end up stuck between a passive table and an interactive grid
|
|
900
|
+
- mixing cell focus with row selection requires very explicit rules
|
|
901
|
+
- if `Footer` joins the focus flow too early, the model becomes unnecessarily complex
|
|
902
|
+
- allowing interactive content inside cells in v1 may break the keyboard experience
|
|
903
|
+
|
|
904
|
+
## V1 Success Criteria
|
|
905
|
+
|
|
906
|
+
- the public API is clear and consistent with the rest of the library
|
|
907
|
+
- keyboard navigation feels close to React Aria Components in the core cases
|
|
908
|
+
- sorting and selection work without ambiguity
|
|
909
|
+
- tests cover critical behavior
|
|
910
|
+
- the implementation leaves real room for future phases without breaking the API
|
|
911
|
+
|
|
912
|
+
## Phase 3: Row Actions and Disabled Behavior Plan
|
|
913
|
+
|
|
914
|
+
### Phase 3 Goal
|
|
915
|
+
|
|
916
|
+
Add row actions in a way that matches the React Aria Components mental model closely enough for consumers to predict behavior, while still fitting the existing `Table` architecture in this repository:
|
|
917
|
+
|
|
918
|
+
- `Table.Root` remains the single owner of interaction state
|
|
919
|
+
- `Table.Row` and `Table.Cell` remain semantic wrappers over native table elements
|
|
920
|
+
- selection and actions are treated as related but distinct interactions
|
|
921
|
+
- disabled state becomes more explicit so selection-only disabling does not accidentally disable focus or actions
|
|
922
|
+
|
|
923
|
+
This phase is specifically about `onRowAction` and `disabledBehavior`. It does not attempt to solve full row-as-link semantics or nested interactive controls inside arbitrary cells.
|
|
924
|
+
|
|
925
|
+
### Reference Behavior
|
|
926
|
+
|
|
927
|
+
The intended behavioral reference is React Aria's collection model for selection and item actions:
|
|
928
|
+
|
|
929
|
+
- `selectionBehavior="toggle"` keeps action as the primary row press interaction until the user enters a selection state
|
|
930
|
+
- `selectionBehavior="replace"` makes selection the primary pointer interaction and uses double click for row actions
|
|
931
|
+
- keyboard behavior separates selection from action more strictly:
|
|
932
|
+
- `Space` is selection-oriented
|
|
933
|
+
- `Enter` is action-oriented when actions are available
|
|
934
|
+
|
|
935
|
+
The goal is functional parity for the supported cases, not a byte-for-byte clone of RAC internals.
|
|
936
|
+
|
|
937
|
+
### Public API Recommendation
|
|
938
|
+
|
|
939
|
+
#### `Table.Root` Props
|
|
940
|
+
|
|
941
|
+
Add the following props:
|
|
942
|
+
|
|
943
|
+
- `onRowAction?: (id: TableSelectionKey) => void`
|
|
944
|
+
- `disabledBehavior?: 'selection' | 'all'`
|
|
945
|
+
|
|
946
|
+
Prerequisite:
|
|
947
|
+
|
|
948
|
+
- `selectionBehavior?: 'toggle' | 'replace'` must already exist and be part of the stable `Table.Root` contract for this phase to make sense. This phase depends on the existing selection-behavior model rather than introducing a parallel row-press API.
|
|
949
|
+
|
|
950
|
+
Defaults:
|
|
951
|
+
|
|
952
|
+
- `onRowAction` default: `undefined`
|
|
953
|
+
- `disabledBehavior` default: `'all'`
|
|
954
|
+
|
|
955
|
+
Rationale:
|
|
956
|
+
|
|
957
|
+
- `onRowAction` belongs at the collection root because the component is built around row ids and centralized interaction state
|
|
958
|
+
- `disabledBehavior` also belongs at the root because the current disabled model is collection-driven (`disabledKeys`) and should stay consistent across row-local and root-provided disabled state
|
|
959
|
+
|
|
960
|
+
#### No New `rowPressBehavior` Prop
|
|
961
|
+
|
|
962
|
+
This phase should not add a new `rowPressBehavior` prop.
|
|
963
|
+
|
|
964
|
+
Reasons:
|
|
965
|
+
|
|
966
|
+
- the desired behavior is already derivable from the combination of:
|
|
967
|
+
- `onRowAction`
|
|
968
|
+
- `selectionMode`
|
|
969
|
+
- `selectionBehavior`
|
|
970
|
+
- React Aria already establishes a recognizable interaction contract based on those inputs
|
|
971
|
+
- a separate `rowPressBehavior` prop would create redundant states and undocumented invalid combinations
|
|
972
|
+
|
|
973
|
+
### Phase 3 Interaction Model
|
|
974
|
+
|
|
975
|
+
#### Core Principle
|
|
976
|
+
|
|
977
|
+
There are now three distinct row-level concepts:
|
|
978
|
+
|
|
979
|
+
1. row focus
|
|
980
|
+
2. row selection
|
|
981
|
+
3. row action
|
|
982
|
+
|
|
983
|
+
The implementation must stop treating `pressRow()` as if it were synonymous with selection. A row press should first be classified, then routed to either selection logic, action logic, or both depending on the active mode.
|
|
984
|
+
|
|
985
|
+
#### Pointer Behavior Without `onRowAction`
|
|
986
|
+
|
|
987
|
+
When `onRowAction` is not provided, the component should preserve the current selection semantics:
|
|
988
|
+
|
|
989
|
+
- `selectionMode="none"`: row/cell click does nothing selection-related
|
|
990
|
+
- `selectionBehavior="toggle"`: row/cell click toggles selection
|
|
991
|
+
- `selectionBehavior="replace"`: row/cell click replaces selection according to current replace-mode rules
|
|
992
|
+
|
|
993
|
+
This preserves backwards compatibility.
|
|
994
|
+
|
|
995
|
+
#### Pointer Behavior With `onRowAction`
|
|
996
|
+
|
|
997
|
+
##### `selectionMode="none"`
|
|
998
|
+
|
|
999
|
+
Regardless of `selectionBehavior`:
|
|
1000
|
+
|
|
1001
|
+
- single click on row or cell executes `onRowAction(id)`
|
|
1002
|
+
- double click does not have special meaning beyond the browser's normal click sequence
|
|
1003
|
+
- no selection state is changed
|
|
1004
|
+
|
|
1005
|
+
##### `selectionBehavior="toggle"` with selection enabled
|
|
1006
|
+
|
|
1007
|
+
When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
|
|
1008
|
+
|
|
1009
|
+
- if the table currently has no selected rows:
|
|
1010
|
+
- single click executes `onRowAction(id)`
|
|
1011
|
+
- click does not change row selection
|
|
1012
|
+
- if the table currently has at least one selected row:
|
|
1013
|
+
- single click on a row follows toggle selection semantics
|
|
1014
|
+
- click does not execute `onRowAction`
|
|
1015
|
+
|
|
1016
|
+
This matches the RAC notion that action is the default press interaction until the user has entered a selection workflow.
|
|
1017
|
+
|
|
1018
|
+
Important documentation note:
|
|
1019
|
+
|
|
1020
|
+
- this behavior changes dynamically based on whether the table currently has an active selection
|
|
1021
|
+
- that dynamic switch is powerful but can also surprise consumers and end users if it is not documented clearly
|
|
1022
|
+
- docs should call this out explicitly and describe it as an intentional RAC-aligned interaction model rather than a bug or inconsistency
|
|
1023
|
+
- if future consumer feedback shows this is too implicit, a dedicated escape hatch can be evaluated later, but this phase should ship the RAC-style default first
|
|
1024
|
+
|
|
1025
|
+
##### `selectionBehavior="replace"` with selection enabled
|
|
1026
|
+
|
|
1027
|
+
When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
|
|
1028
|
+
|
|
1029
|
+
- single click selects the row using replace-mode semantics
|
|
1030
|
+
- double click executes `onRowAction(id)`
|
|
1031
|
+
- the first click of the double-click sequence still performs selection
|
|
1032
|
+
|
|
1033
|
+
Callback ordering requirement:
|
|
1034
|
+
|
|
1035
|
+
- because the first click in the double-click sequence performs selection, `onSelectionChange` must fire before `onRowAction`
|
|
1036
|
+
- docs should state this ordering explicitly so consumers do not assume the action callback is the first observable event in the interaction
|
|
1037
|
+
|
|
1038
|
+
This is the clearest and most familiar desktop-style interaction model for replace mode.
|
|
1039
|
+
|
|
1040
|
+
### Keyboard Model
|
|
1041
|
+
|
|
1042
|
+
#### Rows and Cells Without `onRowAction`
|
|
1043
|
+
|
|
1044
|
+
Keep existing behavior:
|
|
1045
|
+
|
|
1046
|
+
- `Enter` and `Space` continue to use selection behavior when appropriate
|
|
1047
|
+
|
|
1048
|
+
#### Rows and Cells With `onRowAction`
|
|
1049
|
+
|
|
1050
|
+
Apply the following contract in body rows:
|
|
1051
|
+
|
|
1052
|
+
- `Enter` executes `onRowAction(id)` when the row is actionable
|
|
1053
|
+
- `Space` performs selection when selection is allowed for that row
|
|
1054
|
+
- arrow keys keep their existing focus/navigation behavior
|
|
1055
|
+
|
|
1056
|
+
This keyboard split applies even when pointer behavior differs between `toggle` and `replace`.
|
|
1057
|
+
|
|
1058
|
+
#### Detailed Keyboard Rules
|
|
1059
|
+
|
|
1060
|
+
| State | `Enter` | `Space` |
|
|
1061
|
+
| ------------------------------------------ | -------------------------- | -------------------------- |
|
|
1062
|
+
| `selectionMode="none"` + `onRowAction` | action | no-op |
|
|
1063
|
+
| `selectionMode="single"` + `onRowAction` | action | selection |
|
|
1064
|
+
| `selectionMode="multiple"` + `onRowAction` | action | selection |
|
|
1065
|
+
| any mode without `onRowAction` | current selection behavior | current selection behavior |
|
|
1066
|
+
|
|
1067
|
+
Notes:
|
|
1068
|
+
|
|
1069
|
+
- in `replace` mode, `Space` must not be treated as action even though pointer uses double click for action
|
|
1070
|
+
- `Ctrl/Cmd+Space` in `multiple` + `replace` should preserve the existing non-contiguous selection behavior
|
|
1071
|
+
- `Shift+ArrowUp/Down` in `replace` should continue to extend selection; `onRowAction` must not interfere with that contract
|
|
1072
|
+
|
|
1073
|
+
### Checkbox Interaction Rules
|
|
1074
|
+
|
|
1075
|
+
`Table.Checkbox` remains the explicit selection affordance.
|
|
1076
|
+
|
|
1077
|
+
Rules:
|
|
1078
|
+
|
|
1079
|
+
- checkbox interactions must never trigger `onRowAction`
|
|
1080
|
+
- checkbox interaction should continue to stop propagation so row presses are not synthesized accidentally
|
|
1081
|
+
- when `disabledBehavior="selection"`, the checkbox is disabled even if the row remains actionable
|
|
1082
|
+
- checkbox semantics remain selection-only, even when `selectionBehavior="replace"`
|
|
1083
|
+
|
|
1084
|
+
This preserves a clean mental model: checkbox equals selection, row press may mean action or selection depending on state.
|
|
1085
|
+
|
|
1086
|
+
### `disabledBehavior` Semantics
|
|
1087
|
+
|
|
1088
|
+
#### `'all'`
|
|
1089
|
+
|
|
1090
|
+
This is the current effective behavior and should remain the default.
|
|
1091
|
+
|
|
1092
|
+
When a row is disabled by `disabledKeys` or `Table.Row isDisabled` and `disabledBehavior="all"`:
|
|
1093
|
+
|
|
1094
|
+
- row cannot be selected
|
|
1095
|
+
- row cannot trigger `onRowAction`
|
|
1096
|
+
- row is skipped by focus navigation
|
|
1097
|
+
- row should not be tabbable directly
|
|
1098
|
+
- body cells in that row should not be tabbable directly
|
|
1099
|
+
- checkbox is disabled
|
|
1100
|
+
|
|
1101
|
+
#### `'selection'`
|
|
1102
|
+
|
|
1103
|
+
When a row is disabled and `disabledBehavior="selection"`:
|
|
1104
|
+
|
|
1105
|
+
- row cannot be selected
|
|
1106
|
+
- row cannot be added to or removed from selection by click
|
|
1107
|
+
- row cannot be selected by `Space`
|
|
1108
|
+
- row cannot be selected via replace-mode arrow synchronization
|
|
1109
|
+
- checkbox is disabled
|
|
1110
|
+
- row can still receive focus
|
|
1111
|
+
- row can still participate in keyboard navigation
|
|
1112
|
+
- row can still trigger `onRowAction`
|
|
1113
|
+
|
|
1114
|
+
This mode means disabled-for-selection, not disabled-for-interaction.
|
|
1115
|
+
|
|
1116
|
+
### Disabled State Matrix
|
|
1117
|
+
|
|
1118
|
+
| Condition | Focusable | Selectable | Actionable |
|
|
1119
|
+
| --------------------------------------------- | --------- | ------------------------- | ------------------------------ |
|
|
1120
|
+
| enabled row | yes | yes, per selection config | yes, when `onRowAction` exists |
|
|
1121
|
+
| disabled row + `disabledBehavior="all"` | no | no | no |
|
|
1122
|
+
| disabled row + `disabledBehavior="selection"` | yes | no | yes, when `onRowAction` exists |
|
|
1123
|
+
|
|
1124
|
+
### Recommended Internal Refactor
|
|
1125
|
+
|
|
1126
|
+
#### Split the Existing Disabled Model
|
|
1127
|
+
|
|
1128
|
+
The current `isRowDisabled()` helper is too coarse for the new behavior. Internally, the root context should introduce separate predicates, or equivalent derived logic, for:
|
|
1129
|
+
|
|
1130
|
+
- row disabled for focus/navigation
|
|
1131
|
+
- row disabled for selection
|
|
1132
|
+
- row disabled for action
|
|
1133
|
+
|
|
1134
|
+
The public API does not need to expose all of these separately, but the internal model should.
|
|
1135
|
+
|
|
1136
|
+
Recommended internal helpers:
|
|
1137
|
+
|
|
1138
|
+
- `isRowSelectionDisabled(id, localDisabled?)`
|
|
1139
|
+
- `isRowActionDisabled(id, localDisabled?)`
|
|
1140
|
+
- `isRowInteractionDisabled(id, localDisabled?)`
|
|
1141
|
+
- `canRowReceiveFocus(id, localDisabled?)`
|
|
1142
|
+
|
|
1143
|
+
These names are illustrative; exact naming can be refined during implementation.
|
|
1144
|
+
|
|
1145
|
+
#### Split the Existing Press Pipeline
|
|
1146
|
+
|
|
1147
|
+
The current `pressRow()` API is selection-oriented. This phase should replace that single concept with a more explicit pipeline.
|
|
1148
|
+
|
|
1149
|
+
Recommended root-level methods:
|
|
1150
|
+
|
|
1151
|
+
- `performRowAction(id)`
|
|
1152
|
+
- `pressRowSelection(id, interaction)`
|
|
1153
|
+
- `pressRow(id, source, interaction)` as a coordinator, or equivalent separate handlers
|
|
1154
|
+
|
|
1155
|
+
The coordinator should decide behavior using:
|
|
1156
|
+
|
|
1157
|
+
- presence of `onRowAction`
|
|
1158
|
+
- `selectionMode`
|
|
1159
|
+
- `selectionBehavior`
|
|
1160
|
+
- whether there is an active selection
|
|
1161
|
+
- whether the row is disabled for selection or for all interactions
|
|
1162
|
+
- whether the source was pointer single click, pointer double click, `Enter`, or `Space`
|
|
1163
|
+
|
|
1164
|
+
### Affected Parts
|
|
1165
|
+
|
|
1166
|
+
#### `root/context.ts` Changes
|
|
1167
|
+
|
|
1168
|
+
Will need to own:
|
|
1169
|
+
|
|
1170
|
+
- `onRowAction`
|
|
1171
|
+
- `disabledBehavior`
|
|
1172
|
+
- selection-disabled vs action-disabled resolution
|
|
1173
|
+
- row action dispatch helpers
|
|
1174
|
+
- press classification helpers
|
|
1175
|
+
|
|
1176
|
+
#### `root/table-root.svelte`
|
|
1177
|
+
|
|
1178
|
+
Will need to:
|
|
1179
|
+
|
|
1180
|
+
- accept and sync the new props into context
|
|
1181
|
+
- expose any new root-level data attributes if useful for styling/debugging
|
|
1182
|
+
|
|
1183
|
+
#### `row/table-row.svelte`
|
|
1184
|
+
|
|
1185
|
+
Will need to:
|
|
1186
|
+
|
|
1187
|
+
- update row tabbability based on `disabledBehavior`
|
|
1188
|
+
- handle `Enter` as action when available
|
|
1189
|
+
- handle `Space` as selection when available
|
|
1190
|
+
- ensure row-level keydown no longer assumes `Enter` and `Space` are always equivalent
|
|
1191
|
+
|
|
1192
|
+
#### `cell/table-cell.svelte`
|
|
1193
|
+
|
|
1194
|
+
Will need to:
|
|
1195
|
+
|
|
1196
|
+
- classify pointer click behavior differently when `onRowAction` exists
|
|
1197
|
+
- support double-click action in `replace` mode
|
|
1198
|
+
- align keydown behavior with the row contract so focus target does not change semantics
|
|
1199
|
+
|
|
1200
|
+
#### `checkbox/table-checkbox.svelte`
|
|
1201
|
+
|
|
1202
|
+
Will need to:
|
|
1203
|
+
|
|
1204
|
+
- disable itself from selection-only disabled rows
|
|
1205
|
+
- keep stopping propagation so row actions are not accidentally fired
|
|
1206
|
+
- preserve explicit selection behavior independently from row-action semantics
|
|
1207
|
+
|
|
1208
|
+
### Event Contract Recommendation
|
|
1209
|
+
|
|
1210
|
+
For the first release of this feature, `onRowAction` should receive only the row id:
|
|
1211
|
+
|
|
1212
|
+
```ts
|
|
1213
|
+
onRowAction?: (id: TableSelectionKey) => void;
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
Reasons:
|
|
1217
|
+
|
|
1218
|
+
- aligns with the current root-level API style (`onSelectionChange`, `onSortChange`)
|
|
1219
|
+
- keeps the surface simple while the interaction model is still stabilizing
|
|
1220
|
+
- avoids prematurely freezing an event-detail contract that may need more nuance later
|
|
1221
|
+
|
|
1222
|
+
If consumers later need trigger metadata, a future non-breaking addition could evolve this to an object payload or add a second callback.
|
|
1223
|
+
|
|
1224
|
+
### Data Attribute Plan
|
|
1225
|
+
|
|
1226
|
+
This phase should add row-level state markers for styling and debugging:
|
|
1227
|
+
|
|
1228
|
+
- `data-actionable="true"` when the row can trigger `onRowAction`
|
|
1229
|
+
- `data-disabled-behavior="selection" | "all"` on `Table.Root`
|
|
1230
|
+
- `data-selection-disabled="true"` on rows/cells when selection is blocked but action remains available
|
|
1231
|
+
|
|
1232
|
+
`data-actionable` should be treated as required for this phase rather than optional.
|
|
1233
|
+
|
|
1234
|
+
Reasons:
|
|
1235
|
+
|
|
1236
|
+
- consumers need a reliable styling hook for actionable rows
|
|
1237
|
+
- cursor styling depends on this (`cursor: pointer` vs default)
|
|
1238
|
+
- once row actions exist, actionable state is part of the public styling contract rather than an internal implementation detail
|
|
1239
|
+
|
|
1240
|
+
### Accessibility Plan
|
|
1241
|
+
|
|
1242
|
+
Requirements for this phase:
|
|
1243
|
+
|
|
1244
|
+
- rows that are action-enabled but selection-disabled must still expose coherent keyboard interaction
|
|
1245
|
+
- `aria-disabled` should only reflect non-interactive rows under `disabledBehavior="all"`
|
|
1246
|
+
- rows disabled only for selection should not be presented as fully disabled if they remain actionable and focusable
|
|
1247
|
+
- checkbox disabled state must remain announced correctly
|
|
1248
|
+
|
|
1249
|
+
Explicit decision:
|
|
1250
|
+
|
|
1251
|
+
- rows under `disabledBehavior="selection"` should not add compensating `aria-roledescription` just to explain partial disabled state
|
|
1252
|
+
- the row should remain announced according to its normal table/grid semantics
|
|
1253
|
+
- the disabled checkbox remains the primary assistive-technology signal that selection is unavailable
|
|
1254
|
+
- docs should explain that selection-only disabled rows are still actionable and focusable, while accessibility semantics stay conservative rather than inventing a custom roledescription
|
|
1255
|
+
|
|
1256
|
+
This is important because reusing the current all-or-nothing `aria-disabled` contract under `disabledBehavior="selection"` would misrepresent the row to assistive technology.
|
|
1257
|
+
|
|
1258
|
+
### Behavior Matrix
|
|
1259
|
+
|
|
1260
|
+
#### Pointer Summary
|
|
1261
|
+
|
|
1262
|
+
| `selectionMode` | `selectionBehavior` | `onRowAction` | row click | row double click |
|
|
1263
|
+
| --------------------- | --------------------- | ---------------------------- | ----------------- | ---------------------- |
|
|
1264
|
+
| `none` | `toggle` or `replace` | no | no-op | no-op |
|
|
1265
|
+
| `none` | `toggle` or `replace` | yes | action | same as click sequence |
|
|
1266
|
+
| `single` / `multiple` | `toggle` | no | selection toggle | same as click sequence |
|
|
1267
|
+
| `single` / `multiple` | `toggle` | yes, no active selection | action | same as click sequence |
|
|
1268
|
+
| `single` / `multiple` | `toggle` | yes, active selection exists | selection toggle | same as click sequence |
|
|
1269
|
+
| `single` / `multiple` | `replace` | no | replace selection | same as click sequence |
|
|
1270
|
+
| `single` / `multiple` | `replace` | yes | replace selection | action |
|
|
1271
|
+
|
|
1272
|
+
#### Keyboard Summary
|
|
1273
|
+
|
|
1274
|
+
| `selectionMode` | `onRowAction` | `Enter` | `Space` |
|
|
1275
|
+
| --------------------- | ------------- | --------------------------- | --------------------------- |
|
|
1276
|
+
| `none` | no | no-op | no-op |
|
|
1277
|
+
| `none` | yes | action | no-op |
|
|
1278
|
+
| `single` / `multiple` | no | existing selection behavior | existing selection behavior |
|
|
1279
|
+
| `single` / `multiple` | yes | action | selection |
|
|
1280
|
+
|
|
1281
|
+
### Phase 3 Testing Plan
|
|
1282
|
+
|
|
1283
|
+
Minimum regression coverage:
|
|
1284
|
+
|
|
1285
|
+
- `selectionMode="none"` + `onRowAction` triggers action on row click
|
|
1286
|
+
- basic pointer action tests should land before double-click support so the action pipeline is validated incrementally
|
|
1287
|
+
- `selectionBehavior="toggle"` + `onRowAction` triggers action on click when selection is empty
|
|
1288
|
+
- `selectionBehavior="toggle"` + `onRowAction` toggles selection on click once a selection exists
|
|
1289
|
+
- `selectionBehavior="replace"` + `onRowAction` selects on click and acts on double click
|
|
1290
|
+
- `Enter` triggers action and `Space` triggers selection when both are available
|
|
1291
|
+
- `disabledBehavior="selection"` disables checkbox and selection changes but still allows action
|
|
1292
|
+
- `disabledBehavior="all"` blocks focus, selection, and action
|
|
1293
|
+
- disabled rows under `selection` are skipped by selection sync but not by pure focus navigation
|
|
1294
|
+
- disabled row + `disabledBehavior="selection"` + `onRowAction` + `Enter` triggers action
|
|
1295
|
+
- checkbox never triggers `onRowAction`
|
|
1296
|
+
- existing replace-mode selection extension (`Shift+Arrow`, `Ctrl/Cmd+Space`) remains intact
|
|
1297
|
+
|
|
1298
|
+
### Documentation Plan
|
|
1299
|
+
|
|
1300
|
+
Docs and README updates should include:
|
|
1301
|
+
|
|
1302
|
+
- a new section explaining the difference between row actions and row selection
|
|
1303
|
+
- examples for:
|
|
1304
|
+
- action-only rows (`selectionMode="none"`)
|
|
1305
|
+
- mixed action + selection in `toggle`
|
|
1306
|
+
- mixed action + selection in `replace`
|
|
1307
|
+
- `disabledBehavior="selection"`
|
|
1308
|
+
- an explicit note that in `toggle` mode the meaning of row click changes when a selection becomes active
|
|
1309
|
+
- an explicit note that in `replace` mode double click emits callbacks in the order `onSelectionChange` then `onRowAction`
|
|
1310
|
+
- an explicit note that this phase does not implement full row link semantics
|
|
1311
|
+
|
|
1312
|
+
### Explicit Non-Goals
|
|
1313
|
+
|
|
1314
|
+
This phase should not attempt to solve:
|
|
1315
|
+
|
|
1316
|
+
- `href`, `target`, or router-aware row navigation APIs
|
|
1317
|
+
- native-link-equivalent semantics for rows
|
|
1318
|
+
- nested buttons, links, inputs, or menus inside arbitrary body cells
|
|
1319
|
+
- touch-specific long-press selection mode switching
|
|
1320
|
+
|
|
1321
|
+
These can be revisited later, but they should not block the collection-level row action API.
|
|
1322
|
+
|
|
1323
|
+
### Phase 3 Recommended Implementation Order
|
|
1324
|
+
|
|
1325
|
+
1. Add `onRowAction` and `disabledBehavior` to `Table.Root` and root context.
|
|
1326
|
+
2. Refactor disabled-state helpers into selection-vs-action-aware logic.
|
|
1327
|
+
3. Refactor row press handling so action and selection are separate pathways, and land basic pointer single-click action tests immediately.
|
|
1328
|
+
4. Update `Table.Row` and `Table.Cell` keyboard semantics (`Enter` vs `Space`).
|
|
1329
|
+
5. Add pointer double-click support for `replace` mode only after the basic action pipeline is covered by tests.
|
|
1330
|
+
6. Update `Table.Checkbox` for selection-only disabled rows.
|
|
1331
|
+
7. Add regression tests for the full matrix, including callback ordering and disabled-row keyboard action coverage.
|
|
1332
|
+
8. Document examples and caveats, especially the dynamic `toggle`-mode switch, callback ordering in `replace`, and the non-goal of row link semantics.
|
|
1333
|
+
|
|
1334
|
+
## Recommended Next Step
|
|
1335
|
+
|
|
1336
|
+
Turn this plan into a more concrete API specification, part by part and prop by prop, before creating implementation files.
|