@human-kit/svelte-components 1.0.0-alpha.12 → 1.0.0-alpha.14
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/checkbox/root/checkbox-root.svelte +22 -2
- package/dist/checkbox/root/checkbox-root.svelte.d.ts +4 -1
- package/dist/combobox/input/combobox-input.svelte +1 -0
- package/dist/combobox/list/combobox-listbox.svelte +1 -0
- package/dist/combobox/list/combobox-listbox.svelte.d.ts +1 -0
- package/dist/combobox/root/combobox-test.svelte +8 -2
- package/dist/combobox/root/combobox-test.svelte.d.ts +1 -0
- package/dist/combobox/root/combobox.svelte +16 -9
- package/dist/listbox/item/listbox-item.svelte +24 -2
- package/dist/listbox/root/listbox.svelte +14 -2
- package/dist/listbox/root/listbox.svelte.d.ts +2 -0
- package/dist/table/IMPLEMENTATION_NOTES.md +2 -1
- package/dist/table/PLAN.md +440 -17
- package/dist/table/TODO.md +39 -1
- package/dist/table/cell/table-cell.svelte +86 -79
- package/dist/table/checkbox/table-checkbox-test.svelte +7 -0
- package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +3 -1
- package/dist/table/checkbox/table-checkbox.svelte +55 -30
- package/dist/table/index.d.ts +1 -1
- package/dist/table/root/context.d.ts +16 -1
- package/dist/table/root/context.js +199 -24
- package/dist/table/root/table-root.svelte +30 -0
- package/dist/table/root/table-root.svelte.d.ts +4 -1
- package/dist/table/root/table-test.svelte +29 -0
- package/dist/table/root/table-test.svelte.d.ts +5 -1
- package/dist/table/row/table-row.svelte +44 -67
- package/dist/table/utils/handle-body-keydown.d.ts +13 -0
- package/dist/table/utils/handle-body-keydown.js +67 -0
- package/package.json +1 -1
package/dist/table/PLAN.md
CHANGED
|
@@ -390,7 +390,7 @@ Responsibilities:
|
|
|
390
390
|
- drag and drop
|
|
391
391
|
- async loading / load more
|
|
392
392
|
- API pública dinámica con `items` y `columns`
|
|
393
|
-
- row
|
|
393
|
+
- row links / `href`-style navigation semantics
|
|
394
394
|
- typeahead
|
|
395
395
|
- focus management para elementos interactivos dentro de `Cell`
|
|
396
396
|
- focus management for interactive elements inside `Cell`
|
|
@@ -406,22 +406,23 @@ Responsibilities:
|
|
|
406
406
|
|
|
407
407
|
## Advanced Feature Matrix
|
|
408
408
|
|
|
409
|
-
| Feature | Main Complexity
|
|
410
|
-
| ---------------------------------- |
|
|
411
|
-
| Column resizing | width state, handles, pointer + keyboard, persistence
|
|
412
|
-
| Drag and drop | reorder, drop targets, SR + keyboard + pointer
|
|
413
|
-
| Async loading / load more | scroll state, sentinel rows, partial states
|
|
414
|
-
| Dynamic `items` / `columns` API | collection, stable ids, render functions, memoization
|
|
415
|
-
| Row actions /
|
|
416
|
-
|
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
421
|
-
|
|
|
422
|
-
|
|
|
423
|
-
|
|
|
424
|
-
|
|
|
409
|
+
| Feature | Main Complexity | Risk | Recommendation |
|
|
410
|
+
| ---------------------------------- | -------------------------------------------------------- | ----------- | ------------------ |
|
|
411
|
+
| Column resizing | width state, handles, pointer + keyboard, persistence | high | next planned phase |
|
|
412
|
+
| Drag and drop | reorder, drop targets, SR + keyboard + pointer | very high | keep out of v1 |
|
|
413
|
+
| Async loading / load more | scroll state, sentinel rows, partial states | high | keep out of v1 |
|
|
414
|
+
| Dynamic `items` / `columns` API | collection, stable ids, render functions, memoization | high | defer |
|
|
415
|
+
| Row actions / `onRowAction` | action-selection conflicts across mouse and keyboard | medium/high | next planned phase |
|
|
416
|
+
| Row links / `href` semantics | HTML limitations, router integration, native link parity | high | defer |
|
|
417
|
+
| Interactive content inside `Cell` | focus handoff between grid and nested controls | very high | keep out of v1 |
|
|
418
|
+
| Typeahead | depends on stable collection and consistent `textValue` | medium | defer |
|
|
419
|
+
| Nested headers / column groups | spans, navigation, and complex semantics | high | keep out of v1 |
|
|
420
|
+
| Cell selection | changes the entire interaction model | high | keep out of v1 |
|
|
421
|
+
| Full `selectionBehavior="replace"` | modifiers and fine-grained focus/selection semantics | medium/high | defer |
|
|
422
|
+
| Virtualization | strong decoupling between collection and DOM | very high | keep out of v1 |
|
|
423
|
+
| Integrated select-all | useful UX but depends on mature selection behavior | medium | phase 2 |
|
|
424
|
+
| Complex `colSpan` / `rowSpan` | breaks the rectangular grid model | high | defer |
|
|
425
|
+
| Navigable footer | adds another region to the focus model | medium | avoid in v1 |
|
|
425
426
|
|
|
426
427
|
## Proposed Internal Architecture
|
|
427
428
|
|
|
@@ -919,6 +920,428 @@ Minimum regression coverage:
|
|
|
919
920
|
- tests cover critical behavior
|
|
920
921
|
- the implementation leaves real room for future phases without breaking the API
|
|
921
922
|
|
|
923
|
+
## Phase 3: Row Actions and Disabled Behavior Plan
|
|
924
|
+
|
|
925
|
+
### Goal
|
|
926
|
+
|
|
927
|
+
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:
|
|
928
|
+
|
|
929
|
+
- `Table.Root` remains the single owner of interaction state
|
|
930
|
+
- `Table.Row` and `Table.Cell` remain semantic wrappers over native table elements
|
|
931
|
+
- selection and actions are treated as related but distinct interactions
|
|
932
|
+
- disabled state becomes more explicit so selection-only disabling does not accidentally disable focus or actions
|
|
933
|
+
|
|
934
|
+
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.
|
|
935
|
+
|
|
936
|
+
### Reference Behavior
|
|
937
|
+
|
|
938
|
+
The intended behavioral reference is React Aria's collection model for selection and item actions:
|
|
939
|
+
|
|
940
|
+
- `selectionBehavior="toggle"` keeps action as the primary row press interaction until the user enters a selection state
|
|
941
|
+
- `selectionBehavior="replace"` makes selection the primary pointer interaction and uses double click for row actions
|
|
942
|
+
- keyboard behavior separates selection from action more strictly:
|
|
943
|
+
- `Space` is selection-oriented
|
|
944
|
+
- `Enter` is action-oriented when actions are available
|
|
945
|
+
|
|
946
|
+
The goal is functional parity for the supported cases, not a byte-for-byte clone of RAC internals.
|
|
947
|
+
|
|
948
|
+
### Public API Recommendation
|
|
949
|
+
|
|
950
|
+
#### `Table.Root`
|
|
951
|
+
|
|
952
|
+
Add the following props:
|
|
953
|
+
|
|
954
|
+
- `onRowAction?: (id: TableSelectionKey) => void`
|
|
955
|
+
- `disabledBehavior?: 'selection' | 'all'`
|
|
956
|
+
|
|
957
|
+
Prerequisite:
|
|
958
|
+
|
|
959
|
+
- `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.
|
|
960
|
+
|
|
961
|
+
Defaults:
|
|
962
|
+
|
|
963
|
+
- `onRowAction` default: `undefined`
|
|
964
|
+
- `disabledBehavior` default: `'all'`
|
|
965
|
+
|
|
966
|
+
Rationale:
|
|
967
|
+
|
|
968
|
+
- `onRowAction` belongs at the collection root because the component is built around row ids and centralized interaction state
|
|
969
|
+
- `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
|
|
970
|
+
|
|
971
|
+
#### No New `rowPressBehavior` Prop
|
|
972
|
+
|
|
973
|
+
This phase should not add a new `rowPressBehavior` prop.
|
|
974
|
+
|
|
975
|
+
Reasons:
|
|
976
|
+
|
|
977
|
+
- the desired behavior is already derivable from the combination of:
|
|
978
|
+
- `onRowAction`
|
|
979
|
+
- `selectionMode`
|
|
980
|
+
- `selectionBehavior`
|
|
981
|
+
- React Aria already establishes a recognizable interaction contract based on those inputs
|
|
982
|
+
- a separate `rowPressBehavior` prop would create redundant states and undocumented invalid combinations
|
|
983
|
+
|
|
984
|
+
### Interaction Model
|
|
985
|
+
|
|
986
|
+
#### Core Principle
|
|
987
|
+
|
|
988
|
+
There are now three distinct row-level concepts:
|
|
989
|
+
|
|
990
|
+
1. row focus
|
|
991
|
+
2. row selection
|
|
992
|
+
3. row action
|
|
993
|
+
|
|
994
|
+
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.
|
|
995
|
+
|
|
996
|
+
#### Pointer Behavior Without `onRowAction`
|
|
997
|
+
|
|
998
|
+
When `onRowAction` is not provided, the component should preserve the current selection semantics:
|
|
999
|
+
|
|
1000
|
+
- `selectionMode="none"`: row/cell click does nothing selection-related
|
|
1001
|
+
- `selectionBehavior="toggle"`: row/cell click toggles selection
|
|
1002
|
+
- `selectionBehavior="replace"`: row/cell click replaces selection according to current replace-mode rules
|
|
1003
|
+
|
|
1004
|
+
This preserves backwards compatibility.
|
|
1005
|
+
|
|
1006
|
+
#### Pointer Behavior With `onRowAction`
|
|
1007
|
+
|
|
1008
|
+
##### `selectionMode="none"`
|
|
1009
|
+
|
|
1010
|
+
Regardless of `selectionBehavior`:
|
|
1011
|
+
|
|
1012
|
+
- single click on row or cell executes `onRowAction(id)`
|
|
1013
|
+
- double click does not have special meaning beyond the browser's normal click sequence
|
|
1014
|
+
- no selection state is changed
|
|
1015
|
+
|
|
1016
|
+
##### `selectionBehavior="toggle"` with selection enabled
|
|
1017
|
+
|
|
1018
|
+
When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
|
|
1019
|
+
|
|
1020
|
+
- if the table currently has no selected rows:
|
|
1021
|
+
- single click executes `onRowAction(id)`
|
|
1022
|
+
- click does not change row selection
|
|
1023
|
+
- if the table currently has at least one selected row:
|
|
1024
|
+
- single click on a row follows toggle selection semantics
|
|
1025
|
+
- click does not execute `onRowAction`
|
|
1026
|
+
|
|
1027
|
+
This matches the RAC notion that action is the default press interaction until the user has entered a selection workflow.
|
|
1028
|
+
|
|
1029
|
+
Important documentation note:
|
|
1030
|
+
|
|
1031
|
+
- this behavior changes dynamically based on whether the table currently has an active selection
|
|
1032
|
+
- that dynamic switch is powerful but can also surprise consumers and end users if it is not documented clearly
|
|
1033
|
+
- docs should call this out explicitly and describe it as an intentional RAC-aligned interaction model rather than a bug or inconsistency
|
|
1034
|
+
- 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
|
|
1035
|
+
|
|
1036
|
+
##### `selectionBehavior="replace"` with selection enabled
|
|
1037
|
+
|
|
1038
|
+
When `selectionMode` is `single` or `multiple` and `onRowAction` exists:
|
|
1039
|
+
|
|
1040
|
+
- single click selects the row using replace-mode semantics
|
|
1041
|
+
- double click executes `onRowAction(id)`
|
|
1042
|
+
- the first click of the double-click sequence still performs selection
|
|
1043
|
+
|
|
1044
|
+
Callback ordering requirement:
|
|
1045
|
+
|
|
1046
|
+
- because the first click in the double-click sequence performs selection, `onSelectionChange` must fire before `onRowAction`
|
|
1047
|
+
- docs should state this ordering explicitly so consumers do not assume the action callback is the first observable event in the interaction
|
|
1048
|
+
|
|
1049
|
+
This is the clearest and most familiar desktop-style interaction model for replace mode.
|
|
1050
|
+
|
|
1051
|
+
### Keyboard Model
|
|
1052
|
+
|
|
1053
|
+
#### Rows and Cells Without `onRowAction`
|
|
1054
|
+
|
|
1055
|
+
Keep existing behavior:
|
|
1056
|
+
|
|
1057
|
+
- `Enter` and `Space` continue to use selection behavior when appropriate
|
|
1058
|
+
|
|
1059
|
+
#### Rows and Cells With `onRowAction`
|
|
1060
|
+
|
|
1061
|
+
Apply the following contract in body rows:
|
|
1062
|
+
|
|
1063
|
+
- `Enter` executes `onRowAction(id)` when the row is actionable
|
|
1064
|
+
- `Space` performs selection when selection is allowed for that row
|
|
1065
|
+
- arrow keys keep their existing focus/navigation behavior
|
|
1066
|
+
|
|
1067
|
+
This keyboard split applies even when pointer behavior differs between `toggle` and `replace`.
|
|
1068
|
+
|
|
1069
|
+
#### Detailed Keyboard Rules
|
|
1070
|
+
|
|
1071
|
+
| State | `Enter` | `Space` |
|
|
1072
|
+
| ------------------------------------------ | -------------------------- | -------------------------- |
|
|
1073
|
+
| `selectionMode="none"` + `onRowAction` | action | no-op |
|
|
1074
|
+
| `selectionMode="single"` + `onRowAction` | action | selection |
|
|
1075
|
+
| `selectionMode="multiple"` + `onRowAction` | action | selection |
|
|
1076
|
+
| any mode without `onRowAction` | current selection behavior | current selection behavior |
|
|
1077
|
+
|
|
1078
|
+
Notes:
|
|
1079
|
+
|
|
1080
|
+
- in `replace` mode, `Space` must not be treated as action even though pointer uses double click for action
|
|
1081
|
+
- `Ctrl/Cmd+Space` in `multiple` + `replace` should preserve the existing non-contiguous selection behavior
|
|
1082
|
+
- `Shift+ArrowUp/Down` in `replace` should continue to extend selection; `onRowAction` must not interfere with that contract
|
|
1083
|
+
|
|
1084
|
+
### Checkbox Interaction Rules
|
|
1085
|
+
|
|
1086
|
+
`Table.Checkbox` remains the explicit selection affordance.
|
|
1087
|
+
|
|
1088
|
+
Rules:
|
|
1089
|
+
|
|
1090
|
+
- checkbox interactions must never trigger `onRowAction`
|
|
1091
|
+
- checkbox interaction should continue to stop propagation so row presses are not synthesized accidentally
|
|
1092
|
+
- when `disabledBehavior="selection"`, the checkbox is disabled even if the row remains actionable
|
|
1093
|
+
- checkbox semantics remain selection-only, even when `selectionBehavior="replace"`
|
|
1094
|
+
|
|
1095
|
+
This preserves a clean mental model: checkbox equals selection, row press may mean action or selection depending on state.
|
|
1096
|
+
|
|
1097
|
+
### `disabledBehavior` Semantics
|
|
1098
|
+
|
|
1099
|
+
#### `'all'`
|
|
1100
|
+
|
|
1101
|
+
This is the current effective behavior and should remain the default.
|
|
1102
|
+
|
|
1103
|
+
When a row is disabled by `disabledKeys` or `Table.Row isDisabled` and `disabledBehavior="all"`:
|
|
1104
|
+
|
|
1105
|
+
- row cannot be selected
|
|
1106
|
+
- row cannot trigger `onRowAction`
|
|
1107
|
+
- row is skipped by focus navigation
|
|
1108
|
+
- row should not be tabbable directly
|
|
1109
|
+
- body cells in that row should not be tabbable directly
|
|
1110
|
+
- checkbox is disabled
|
|
1111
|
+
|
|
1112
|
+
#### `'selection'`
|
|
1113
|
+
|
|
1114
|
+
When a row is disabled and `disabledBehavior="selection"`:
|
|
1115
|
+
|
|
1116
|
+
- row cannot be selected
|
|
1117
|
+
- row cannot be added to or removed from selection by click
|
|
1118
|
+
- row cannot be selected by `Space`
|
|
1119
|
+
- row cannot be selected via replace-mode arrow synchronization
|
|
1120
|
+
- checkbox is disabled
|
|
1121
|
+
- row can still receive focus
|
|
1122
|
+
- row can still participate in keyboard navigation
|
|
1123
|
+
- row can still trigger `onRowAction`
|
|
1124
|
+
|
|
1125
|
+
This mode means disabled-for-selection, not disabled-for-interaction.
|
|
1126
|
+
|
|
1127
|
+
### Disabled State Matrix
|
|
1128
|
+
|
|
1129
|
+
| Condition | Focusable | Selectable | Actionable |
|
|
1130
|
+
| --------------------------------------------- | --------- | ------------------------- | ------------------------------ |
|
|
1131
|
+
| enabled row | yes | yes, per selection config | yes, when `onRowAction` exists |
|
|
1132
|
+
| disabled row + `disabledBehavior="all"` | no | no | no |
|
|
1133
|
+
| disabled row + `disabledBehavior="selection"` | yes | no | yes, when `onRowAction` exists |
|
|
1134
|
+
|
|
1135
|
+
### Recommended Internal Refactor
|
|
1136
|
+
|
|
1137
|
+
#### Split the Existing Disabled Model
|
|
1138
|
+
|
|
1139
|
+
The current `isRowDisabled()` helper is too coarse for the new behavior. Internally, the root context should introduce separate predicates, or equivalent derived logic, for:
|
|
1140
|
+
|
|
1141
|
+
- row disabled for focus/navigation
|
|
1142
|
+
- row disabled for selection
|
|
1143
|
+
- row disabled for action
|
|
1144
|
+
|
|
1145
|
+
The public API does not need to expose all of these separately, but the internal model should.
|
|
1146
|
+
|
|
1147
|
+
Recommended internal helpers:
|
|
1148
|
+
|
|
1149
|
+
- `isRowSelectionDisabled(id, localDisabled?)`
|
|
1150
|
+
- `isRowActionDisabled(id, localDisabled?)`
|
|
1151
|
+
- `isRowInteractionDisabled(id, localDisabled?)`
|
|
1152
|
+
- `canRowReceiveFocus(id, localDisabled?)`
|
|
1153
|
+
|
|
1154
|
+
These names are illustrative; exact naming can be refined during implementation.
|
|
1155
|
+
|
|
1156
|
+
#### Split the Existing Press Pipeline
|
|
1157
|
+
|
|
1158
|
+
The current `pressRow()` API is selection-oriented. This phase should replace that single concept with a more explicit pipeline.
|
|
1159
|
+
|
|
1160
|
+
Recommended root-level methods:
|
|
1161
|
+
|
|
1162
|
+
- `performRowAction(id)`
|
|
1163
|
+
- `pressRowSelection(id, interaction)`
|
|
1164
|
+
- `pressRow(id, source, interaction)` as a coordinator, or equivalent separate handlers
|
|
1165
|
+
|
|
1166
|
+
The coordinator should decide behavior using:
|
|
1167
|
+
|
|
1168
|
+
- presence of `onRowAction`
|
|
1169
|
+
- `selectionMode`
|
|
1170
|
+
- `selectionBehavior`
|
|
1171
|
+
- whether there is an active selection
|
|
1172
|
+
- whether the row is disabled for selection or for all interactions
|
|
1173
|
+
- whether the source was pointer single click, pointer double click, `Enter`, or `Space`
|
|
1174
|
+
|
|
1175
|
+
### Affected Parts
|
|
1176
|
+
|
|
1177
|
+
#### `root/context.ts`
|
|
1178
|
+
|
|
1179
|
+
Will need to own:
|
|
1180
|
+
|
|
1181
|
+
- `onRowAction`
|
|
1182
|
+
- `disabledBehavior`
|
|
1183
|
+
- selection-disabled vs action-disabled resolution
|
|
1184
|
+
- row action dispatch helpers
|
|
1185
|
+
- press classification helpers
|
|
1186
|
+
|
|
1187
|
+
#### `root/table-root.svelte`
|
|
1188
|
+
|
|
1189
|
+
Will need to:
|
|
1190
|
+
|
|
1191
|
+
- accept and sync the new props into context
|
|
1192
|
+
- expose any new root-level data attributes if useful for styling/debugging
|
|
1193
|
+
|
|
1194
|
+
#### `row/table-row.svelte`
|
|
1195
|
+
|
|
1196
|
+
Will need to:
|
|
1197
|
+
|
|
1198
|
+
- update row tabbability based on `disabledBehavior`
|
|
1199
|
+
- handle `Enter` as action when available
|
|
1200
|
+
- handle `Space` as selection when available
|
|
1201
|
+
- ensure row-level keydown no longer assumes `Enter` and `Space` are always equivalent
|
|
1202
|
+
|
|
1203
|
+
#### `cell/table-cell.svelte`
|
|
1204
|
+
|
|
1205
|
+
Will need to:
|
|
1206
|
+
|
|
1207
|
+
- classify pointer click behavior differently when `onRowAction` exists
|
|
1208
|
+
- support double-click action in `replace` mode
|
|
1209
|
+
- align keydown behavior with the row contract so focus target does not change semantics
|
|
1210
|
+
|
|
1211
|
+
#### `checkbox/table-checkbox.svelte`
|
|
1212
|
+
|
|
1213
|
+
Will need to:
|
|
1214
|
+
|
|
1215
|
+
- disable itself from selection-only disabled rows
|
|
1216
|
+
- keep stopping propagation so row actions are not accidentally fired
|
|
1217
|
+
- preserve explicit selection behavior independently from row-action semantics
|
|
1218
|
+
|
|
1219
|
+
### Event Contract Recommendation
|
|
1220
|
+
|
|
1221
|
+
For the first release of this feature, `onRowAction` should receive only the row id:
|
|
1222
|
+
|
|
1223
|
+
```ts
|
|
1224
|
+
onRowAction?: (id: TableSelectionKey) => void;
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
Reasons:
|
|
1228
|
+
|
|
1229
|
+
- aligns with the current root-level API style (`onSelectionChange`, `onSortChange`)
|
|
1230
|
+
- keeps the surface simple while the interaction model is still stabilizing
|
|
1231
|
+
- avoids prematurely freezing an event-detail contract that may need more nuance later
|
|
1232
|
+
|
|
1233
|
+
If consumers later need trigger metadata, a future non-breaking addition could evolve this to an object payload or add a second callback.
|
|
1234
|
+
|
|
1235
|
+
### Data Attribute Plan
|
|
1236
|
+
|
|
1237
|
+
This phase should add row-level state markers for styling and debugging:
|
|
1238
|
+
|
|
1239
|
+
- `data-actionable="true"` when the row can trigger `onRowAction`
|
|
1240
|
+
- `data-disabled-behavior="selection" | "all"` on `Table.Root`
|
|
1241
|
+
- `data-selection-disabled="true"` on rows/cells when selection is blocked but action remains available
|
|
1242
|
+
|
|
1243
|
+
`data-actionable` should be treated as required for this phase rather than optional.
|
|
1244
|
+
|
|
1245
|
+
Reasons:
|
|
1246
|
+
|
|
1247
|
+
- consumers need a reliable styling hook for actionable rows
|
|
1248
|
+
- cursor styling depends on this (`cursor: pointer` vs default)
|
|
1249
|
+
- once row actions exist, actionable state is part of the public styling contract rather than an internal implementation detail
|
|
1250
|
+
|
|
1251
|
+
### Accessibility Plan
|
|
1252
|
+
|
|
1253
|
+
Requirements for this phase:
|
|
1254
|
+
|
|
1255
|
+
- rows that are action-enabled but selection-disabled must still expose coherent keyboard interaction
|
|
1256
|
+
- `aria-disabled` should only reflect non-interactive rows under `disabledBehavior="all"`
|
|
1257
|
+
- rows disabled only for selection should not be presented as fully disabled if they remain actionable and focusable
|
|
1258
|
+
- checkbox disabled state must remain announced correctly
|
|
1259
|
+
|
|
1260
|
+
Explicit decision:
|
|
1261
|
+
|
|
1262
|
+
- rows under `disabledBehavior="selection"` should not add compensating `aria-roledescription` just to explain partial disabled state
|
|
1263
|
+
- the row should remain announced according to its normal table/grid semantics
|
|
1264
|
+
- the disabled checkbox remains the primary assistive-technology signal that selection is unavailable
|
|
1265
|
+
- docs should explain that selection-only disabled rows are still actionable and focusable, while accessibility semantics stay conservative rather than inventing a custom roledescription
|
|
1266
|
+
|
|
1267
|
+
This is important because reusing the current all-or-nothing `aria-disabled` contract under `disabledBehavior="selection"` would misrepresent the row to assistive technology.
|
|
1268
|
+
|
|
1269
|
+
### Behavior Matrix
|
|
1270
|
+
|
|
1271
|
+
#### Pointer Summary
|
|
1272
|
+
|
|
1273
|
+
| `selectionMode` | `selectionBehavior` | `onRowAction` | row click | row double click |
|
|
1274
|
+
| --------------------- | --------------------- | ---------------------------- | ----------------- | ---------------------- |
|
|
1275
|
+
| `none` | `toggle` or `replace` | no | no-op | no-op |
|
|
1276
|
+
| `none` | `toggle` or `replace` | yes | action | same as click sequence |
|
|
1277
|
+
| `single` / `multiple` | `toggle` | no | selection toggle | same as click sequence |
|
|
1278
|
+
| `single` / `multiple` | `toggle` | yes, no active selection | action | same as click sequence |
|
|
1279
|
+
| `single` / `multiple` | `toggle` | yes, active selection exists | selection toggle | same as click sequence |
|
|
1280
|
+
| `single` / `multiple` | `replace` | no | replace selection | same as click sequence |
|
|
1281
|
+
| `single` / `multiple` | `replace` | yes | replace selection | action |
|
|
1282
|
+
|
|
1283
|
+
#### Keyboard Summary
|
|
1284
|
+
|
|
1285
|
+
| `selectionMode` | `onRowAction` | `Enter` | `Space` |
|
|
1286
|
+
| --------------------- | ------------- | --------------------------- | --------------------------- |
|
|
1287
|
+
| `none` | no | no-op | no-op |
|
|
1288
|
+
| `none` | yes | action | no-op |
|
|
1289
|
+
| `single` / `multiple` | no | existing selection behavior | existing selection behavior |
|
|
1290
|
+
| `single` / `multiple` | yes | action | selection |
|
|
1291
|
+
|
|
1292
|
+
### Testing Plan
|
|
1293
|
+
|
|
1294
|
+
Minimum regression coverage:
|
|
1295
|
+
|
|
1296
|
+
- `selectionMode="none"` + `onRowAction` triggers action on row click
|
|
1297
|
+
- basic pointer action tests should land before double-click support so the action pipeline is validated incrementally
|
|
1298
|
+
- `selectionBehavior="toggle"` + `onRowAction` triggers action on click when selection is empty
|
|
1299
|
+
- `selectionBehavior="toggle"` + `onRowAction` toggles selection on click once a selection exists
|
|
1300
|
+
- `selectionBehavior="replace"` + `onRowAction` selects on click and acts on double click
|
|
1301
|
+
- `Enter` triggers action and `Space` triggers selection when both are available
|
|
1302
|
+
- `disabledBehavior="selection"` disables checkbox and selection changes but still allows action
|
|
1303
|
+
- `disabledBehavior="all"` blocks focus, selection, and action
|
|
1304
|
+
- disabled rows under `selection` are skipped by selection sync but not by pure focus navigation
|
|
1305
|
+
- disabled row + `disabledBehavior="selection"` + `onRowAction` + `Enter` triggers action
|
|
1306
|
+
- checkbox never triggers `onRowAction`
|
|
1307
|
+
- existing replace-mode selection extension (`Shift+Arrow`, `Ctrl/Cmd+Space`) remains intact
|
|
1308
|
+
|
|
1309
|
+
### Documentation Plan
|
|
1310
|
+
|
|
1311
|
+
Docs and README updates should include:
|
|
1312
|
+
|
|
1313
|
+
- a new section explaining the difference between row actions and row selection
|
|
1314
|
+
- examples for:
|
|
1315
|
+
- action-only rows (`selectionMode="none"`)
|
|
1316
|
+
- mixed action + selection in `toggle`
|
|
1317
|
+
- mixed action + selection in `replace`
|
|
1318
|
+
- `disabledBehavior="selection"`
|
|
1319
|
+
- an explicit note that in `toggle` mode the meaning of row click changes when a selection becomes active
|
|
1320
|
+
- an explicit note that in `replace` mode double click emits callbacks in the order `onSelectionChange` then `onRowAction`
|
|
1321
|
+
- an explicit note that this phase does not implement full row link semantics
|
|
1322
|
+
|
|
1323
|
+
### Explicit Non-Goals
|
|
1324
|
+
|
|
1325
|
+
This phase should not attempt to solve:
|
|
1326
|
+
|
|
1327
|
+
- `href`, `target`, or router-aware row navigation APIs
|
|
1328
|
+
- native-link-equivalent semantics for rows
|
|
1329
|
+
- nested buttons, links, inputs, or menus inside arbitrary body cells
|
|
1330
|
+
- touch-specific long-press selection mode switching
|
|
1331
|
+
|
|
1332
|
+
These can be revisited later, but they should not block the collection-level row action API.
|
|
1333
|
+
|
|
1334
|
+
### Recommended Implementation Order
|
|
1335
|
+
|
|
1336
|
+
1. Add `onRowAction` and `disabledBehavior` to `Table.Root` and root context.
|
|
1337
|
+
2. Refactor disabled-state helpers into selection-vs-action-aware logic.
|
|
1338
|
+
3. Refactor row press handling so action and selection are separate pathways, and land basic pointer single-click action tests immediately.
|
|
1339
|
+
4. Update `Table.Row` and `Table.Cell` keyboard semantics (`Enter` vs `Space`).
|
|
1340
|
+
5. Add pointer double-click support for `replace` mode only after the basic action pipeline is covered by tests.
|
|
1341
|
+
6. Update `Table.Checkbox` for selection-only disabled rows.
|
|
1342
|
+
7. Add regression tests for the full matrix, including callback ordering and disabled-row keyboard action coverage.
|
|
1343
|
+
8. Document examples and caveats, especially the dynamic `toggle`-mode switch, callback ordering in `replace`, and the non-goal of row link semantics.
|
|
1344
|
+
|
|
922
1345
|
## Recommended Next Step
|
|
923
1346
|
|
|
924
1347
|
Turn this plan into a more concrete API specification, part by part and prop by prop, before creating implementation files.
|
package/dist/table/TODO.md
CHANGED
|
@@ -61,6 +61,9 @@ Ship a stable `Table` v1 with keyboard navigation, row selection, sorting, docum
|
|
|
61
61
|
- [x] [S][P2][Area: Behavior][Owner: Unassigned][Target: Done] Confirm whether disabled body rows should remain keyboard-focusable or be skipped by navigation.
|
|
62
62
|
- [ ] [S][P2][Area: API][Owner: Unassigned][Target: TBD] Decide whether `Table.Column` should hard-enforce a single `Table.ColumnHeaderCell` child.
|
|
63
63
|
- [ ] [S][P2][Area: API][Owner: Unassigned][Target: TBD] Decide whether clipboard-related behavior should remain fully browser-native in v1 or be deferred behind a future explicit cell-selection model.
|
|
64
|
+
- [x] [C][P1][Area: API][Owner: Unassigned][Target: Done] Add `onRowAction?: (id: TableSelectionKey) => void` to `Table.Root` and document its collection-level semantics across `selectionMode` and `selectionBehavior`.
|
|
65
|
+
- [x] [C][P1][Area: API][Owner: Unassigned][Target: Done] Add `disabledBehavior?: 'selection' | 'all'` to `Table.Root` with default `'all'`, and document that `'selection'` disables selection while preserving focus and row actions.
|
|
66
|
+
- [x] [S][P1][Area: API][Owner: Unassigned][Target: Done] Treat stable `selectionBehavior?: 'toggle' | 'replace'` support in `Table.Root` as an explicit prerequisite for the row-actions phase rather than an implicit assumption.
|
|
64
67
|
|
|
65
68
|
### Features
|
|
66
69
|
|
|
@@ -74,6 +77,7 @@ Ship a stable `Table` v1 with keyboard navigation, row selection, sorting, docum
|
|
|
74
77
|
- [ ] [C][P2][Area: Features][Owner: Unassigned][Target: TBD] Add inline cell and row editing primitives — define an opt-in editing model that can coexist with current focus, selection, and keyboard navigation contracts.
|
|
75
78
|
- [ ] [M][P2][Area: Features][Owner: Unassigned][Target: TBD] Add column action primitives — expose a path for header menus, quick sort/filter actions, and future column management UI without requiring consumers to hand-roll header action composition every time.
|
|
76
79
|
- [ ] [M][P3][Area: Features][Owner: Unassigned][Target: TBD] Add row actions patterns — document or expose a composable pattern for common trailing actions columns so selection, row press, and nested interactive controls do not conflict.
|
|
80
|
+
- [x] [C][P1][Area: Features][Owner: Unassigned][Target: Done] Implement collection-level row actions in `Table.Root` so body rows can trigger `onRowAction` using RAC-style interaction rules without introducing a separate `rowPressBehavior` prop.
|
|
77
81
|
|
|
78
82
|
### Tests
|
|
79
83
|
|
|
@@ -81,12 +85,21 @@ Ship a stable `Table` v1 with keyboard navigation, row selection, sorting, docum
|
|
|
81
85
|
- [x] [S][P2][Area: Tests][Owner: Unassigned][Target: Done] Add test for full sort cycle via keyboard (ascending → descending) and verify no way to clear sort with keyboard is intentional.
|
|
82
86
|
- [x] [S][P2][Area: Tests][Owner: Unassigned][Target: Done] Add test verifying disabled rows are not selected when arrow-navigated in `replace` mode.
|
|
83
87
|
- [x] [S][P2][Area: Tests][Owner: Unassigned][Target: Done] Add tests covering `selectionMode` transitions after mount, including collapsing multiple selections to one on `single`.
|
|
88
|
+
- [x] [C][P1][Area: Tests][Owner: Unassigned][Target: Done] Add the full row action matrix to `Table.Root` tests: click/action in `selectionMode="none"`, toggle-mode action when selection is empty, toggle-mode selection when selection is active, and replace-mode double-click actions.
|
|
89
|
+
- [x] [M][P1][Area: Tests][Owner: Unassigned][Target: Done] Add keyboard interaction tests proving `Enter` triggers `onRowAction` and `Space` triggers selection when both behaviors are available.
|
|
90
|
+
- [x] [M][P1][Area: Tests][Owner: Unassigned][Target: Done] Add `disabledBehavior` regression tests covering `'selection'` vs `'all'` for row focusability, checkbox disabled state, row action availability, and selection blocking.
|
|
91
|
+
- [x] [S][P1][Area: Tests][Owner: Unassigned][Target: Done] Add an explicit edge-case test proving a disabled row under `disabledBehavior="selection"` still fires `onRowAction` on `Enter` while remaining non-selectable.
|
|
92
|
+
- [x] [S][P1][Area: Tests][Owner: Unassigned][Target: Done] Add a callback-ordering test proving `selectionBehavior="replace"` double click fires `onSelectionChange` before `onRowAction`.
|
|
84
93
|
|
|
85
94
|
### Docs
|
|
86
95
|
|
|
87
96
|
- [ ] [C][P2][Area: Docs][Owner: Unassigned][Target: TBD] Add richer styling examples and sorting guidance to the docs page.
|
|
88
97
|
- [x] [C][P3][Area: Docs][Owner: Unassigned][Target: Done] Document that `Table.Column` is a logical-only wrapper (no DOM output) prominently in the README anatomy section.
|
|
89
98
|
- [x] [S][P2][Area: Docs][Owner: Unassigned][Target: Done] Document `selectionMode="none"` normalization behavior and clarify that selection is cleared internally when selection is disabled.
|
|
99
|
+
- [ ] [C][P1][Area: Docs][Owner: Unassigned][Target: TBD] Document row actions vs row selection in the Table README and docs page, including the different pointer rules for `toggle` and `replace` when `onRowAction` is provided.
|
|
100
|
+
- [ ] [M][P1][Area: Docs][Owner: Unassigned][Target: TBD] Document `disabledBehavior="selection"` and clarify that it disables selection only, not row actions or focus.
|
|
101
|
+
- [ ] [S][P1][Area: Docs][Owner: Unassigned][Target: TBD] Call out explicitly that `toggle` mode changes row-click behavior dynamically once an active selection exists, and treat this as an intentional RAC-style model rather than an accidental inconsistency.
|
|
102
|
+
- [ ] [S][P1][Area: Docs][Owner: Unassigned][Target: TBD] Document callback ordering for `replace`-mode double click: `onSelectionChange` fires before `onRowAction`.
|
|
90
103
|
|
|
91
104
|
### Selection Checkbox
|
|
92
105
|
|
|
@@ -94,7 +107,32 @@ Ship a stable `Table` v1 with keyboard navigation, row selection, sorting, docum
|
|
|
94
107
|
- [ ] [M][P1][Area: Accessibility][Owner: Unassigned][Target: TBD] Validate `Table.Checkbox` screen reader announcements across NVDA and VoiceOver — verify that `aria-checked="mixed"` transitions in the header checkbox and row selection toggles are announced correctly.
|
|
95
108
|
- [ ] [M][P1][Area: Correctness][Owner: Unassigned][Target: TBD] Document or resolve `Table.Checkbox` bypass of `pressRow()` — the checkbox always calls `toggleRowSelection()` directly, ignoring `selectionBehavior="replace"` semantics (Shift+click range, Ctrl+click toggle). This is intentional for checkbox UX but undocumented and inconsistent with row-click behavior.
|
|
96
109
|
- [ ] [M][P1][Area: Correctness][Owner: Unassigned][Target: TBD] Add dev-time structural validation for `Table.Checkbox` placement — warn when header includes a selection checkbox column but body rows do not, or when the checkbox is placed in a footer cell where it has no behavior.
|
|
110
|
+
- [x] [M][P1][Area: Correctness][Owner: Unassigned][Target: Done] Ensure `Table.Checkbox` respects `disabledBehavior="selection"` by disabling explicit selection controls without suppressing row actions on the rest of the row.
|
|
111
|
+
|
|
112
|
+
### Row Actions
|
|
113
|
+
|
|
114
|
+
- [x] [C][P1][Area: Correctness][Owner: Unassigned][Target: Done] Refactor the current `pressRow()` pipeline so selection and row actions are distinct internal pathways rather than a single selection-oriented press abstraction.
|
|
115
|
+
- [x] [C][P1][Area: Correctness][Owner: Unassigned][Target: Done] Split row disabled-state helpers into selection-disabled vs action-disabled semantics so `disabledBehavior="selection"` does not incorrectly suppress focus or actions.
|
|
116
|
+
- [x] [M][P1][Area: Behavior][Owner: Unassigned][Target: Done] Preserve existing `replace`-mode selection extension (`Shift+Arrow`, `Ctrl/Cmd+Space`, click replace) while layering `onRowAction` on top.
|
|
117
|
+
- [x] [M][P1][Area: Accessibility][Owner: Unassigned][Target: Done] Revisit `aria-disabled` semantics for rows under `disabledBehavior="selection"` so actionable rows are not announced as fully disabled.
|
|
118
|
+
- [x] [M][P1][Area: DX][Owner: Unassigned][Target: Done] Evaluate whether row/cell data attributes should expose actionable and selection-disabled states for styling and debugging once `onRowAction` ships.
|
|
119
|
+
- [x] [S][P1][Area: DX][Owner: Unassigned][Target: Done] Make `data-actionable` part of the required styling contract for actionable rows so consumers can reliably style cursor and affordances.
|
|
97
120
|
|
|
98
121
|
### Code Quality
|
|
99
122
|
|
|
100
|
-
- [
|
|
123
|
+
- [x] [C][P3][Area: Code Quality][Owner: Unassigned][Target: Done] Remove unused keyboard/click handlers from `Table.Cell` when rendering in footer scope — currently handlers are bound but short-circuit via guards.
|
|
124
|
+
- [x] [M][P2][Area: Code Quality][Owner: Unassigned][Target: Done] Extract shared keyboard handler between `Table.Row` and `Table.Cell` — both components duplicate ~60 lines of nearly identical `handleKeyDown` logic (ArrowUp/Down/Left/Right, Home/End, Ctrl+Home/End, Ctrl+A, Enter, Space) with only minor differences in Home/End targets.
|
|
125
|
+
- [x] [S][P2][Area: Code Quality][Owner: Unassigned][Target: Done] Add a defensive `isRowDisabled` guard inside `pressRow()` in context — currently callers (Row, Cell) check disabled state before calling, but `pressRow` itself does not validate, so a direct call bypasses the check.
|
|
126
|
+
|
|
127
|
+
### Bugs / Phase 3
|
|
128
|
+
|
|
129
|
+
- [x] [M][P1][Area: Correctness][Owner: Unassigned][Target: Done] Fix `keyboard-space` ignoring interaction modifiers in `pressRow()` — the `keyboard-space` branch calls `toggleRowSelection(id)` directly, discarding `shiftKey`/`ctrlKey`/`metaKey`. This breaks `Shift+Space` (should extend selection) and `Ctrl+Space` semantics in `replace` + `multiple` mode. Should route through `pressRowSelection(id, interaction)` instead.
|
|
130
|
+
|
|
131
|
+
### Accessibility / Phase 3
|
|
132
|
+
|
|
133
|
+
- [x] [S][P2][Area: Accessibility][Owner: Unassigned][Target: Done] Clarify ARIA semantics for rows under `disabledBehavior="selection"` without a checkbox — a selection-disabled but actionable row has no `aria-disabled` (correct) but also no ARIA hint that selection is unavailable; the checkbox disabled state announces it, but rows without a checkbox column have no signal for assistive technology.
|
|
134
|
+
|
|
135
|
+
### Performance / Phase 3
|
|
136
|
+
|
|
137
|
+
- [x] [S][P2][Area: Performance][Owner: Unassigned][Target: Done] Use O(1) id-to-token lookup in `isColumnSortable()` — currently iterates all columns with `Array.from(columns.values()).some(...)` instead of using `getColumnRegistrationById(columnId)?.allowsSorting`.
|
|
138
|
+
- [x] [S][P2][Area: Performance][Owner: Unassigned][Target: Done] Cache `getVisibleColumnWidths()` result — it filters `getColumnWidths()` on every call but is used in the render path of `Table.Root` for `managedTableWidth` derivation; should share a cache invalidated alongside `columnWidthsCache`.
|