@astryxdesign/core 0.7.2 → 0.7.3-canary.0bf23bd
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/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +8 -4
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +7 -0
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +19 -24
- package/dist/ClickableCard/ClickableCard.d.ts +2 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +11 -9
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +11 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +23 -5
- package/dist/DateInput/DateInput.d.ts +4 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +27 -20
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +14 -8
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +14 -10
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +9 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +52 -10
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +34 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +15 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +21 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +375 -42
- package/dist/DropdownMenu/menuRegionStops.d.ts +2 -2
- package/dist/DropdownMenu/menuRegionStops.js +2 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +14 -12
- package/dist/Link/Link.d.ts +2 -3
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +11 -19
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +48 -15
- package/dist/MultiSelector/MultiSelector.d.ts +4 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts +2 -0
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts.map +1 -0
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.js +48 -0
- package/dist/MultiSelector/MultiSelector.js +25 -18
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +8 -4
- package/dist/Selector/Selector.d.ts +4 -4
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +23 -14
- package/dist/Selector/SelectorOption.d.ts.map +1 -1
- package/dist/Selector/SelectorOption.js +1 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -14
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +25 -17
- package/dist/Switch/switch.markers.stylex.d.ts +2 -0
- package/dist/Switch/switch.markers.stylex.d.ts.map +1 -1
- package/dist/Switch/switch.markers.stylex.js +6 -0
- package/dist/TimeInput/TimeInput.d.ts +2 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +8 -3
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +36 -14
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -2
- package/dist/TopNav/TopNav.d.ts.map +1 -1
- package/dist/TopNav/TopNav.js +2 -2
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +1 -0
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +14 -2
- package/dist/astryx.css +8 -2
- package/dist/hooks/useMenuPress.d.ts +11 -1
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +43 -5
- package/dist/utils/disabledInteraction.d.ts +21 -0
- package/dist/utils/disabledInteraction.d.ts.map +1 -0
- package/dist/utils/disabledInteraction.js +33 -0
- package/dist/utils/timeParser.d.ts +4 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +9 -2
- package/package.json +4 -4
- package/scripts/agent-doc-state.mjs +35 -8
- package/src/Banner/Banner.spec.md +37 -32
- package/src/BottomSheet/BottomSheet.spec.md +13 -6
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +8 -4
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +121 -51
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +26 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +6 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +44 -5
- package/src/CheckboxInput/CheckboxInput.tsx +16 -26
- package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +3 -3
- package/src/CheckboxList/CheckboxList.test.tsx +9 -6
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
- package/src/ClickableCard/ClickableCard.test.tsx +5 -4
- package/src/ClickableCard/ClickableCard.tsx +9 -9
- package/src/CodeBlock/CodeBlock.highlightStyles.test.tsx +87 -0
- package/src/CodeBlock/CodeBlock.tsx +11 -1
- package/src/ComplexSelector/ComplexSelector.focusableDisabled.test.tsx +76 -0
- package/src/ComplexSelector/ComplexSelector.tsx +21 -4
- package/src/ContextMenu/ContextMenu.test.tsx +41 -0
- package/src/DateInput/DateInput.doc.mjs +9 -8
- package/src/DateInput/DateInput.test.tsx +88 -13
- package/src/DateInput/DateInput.tsx +49 -21
- package/src/DateInput/DateInputTouch.test.tsx +22 -1
- package/src/DateInput/NativeDateField.test.tsx +29 -3
- package/src/DateInput/NativeDateField.tsx +20 -7
- package/src/DateInput/TouchDateField.tsx +24 -9
- package/src/DateRangeInput/DateRangeInput.spec.md +13 -13
- package/src/Dialog/Dialog.test.tsx +39 -0
- package/src/Dialog/Dialog.tsx +9 -4
- package/src/DropdownMenu/DropdownMenu.spec.md +33 -2
- package/src/DropdownMenu/DropdownMenu.tsx +79 -8
- package/src/DropdownMenu/DropdownMenuContext.public.test.ts +22 -0
- package/src/DropdownMenu/DropdownMenuContext.tsx +39 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +16 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +623 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +556 -99
- package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +267 -0
- package/src/DropdownMenu/menuRegionStops.ts +2 -2
- package/src/DropdownMenu/modules/useMenuPress.spec.md +9 -0
- package/src/FieldStatus/FieldStatus.spec.md +3 -2
- package/src/FileInput/FileInput.test.tsx +30 -7
- package/src/FileInput/FileInput.tsx +21 -11
- package/src/Link/Link.test.tsx +21 -7
- package/src/Link/Link.tsx +16 -24
- package/src/Link/__tests__/Link.a11y.states.ts +1 -1
- package/src/Markdown/Markdown.rerender.test.tsx +143 -0
- package/src/Markdown/Markdown.tsx +89 -35
- package/src/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.ts +71 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +5 -5
- package/src/MultiSelector/MultiSelector.focusableDisabled.test.tsx +121 -0
- package/src/MultiSelector/MultiSelector.test.tsx +49 -7
- package/src/MultiSelector/MultiSelector.tsx +53 -29
- package/src/Popover/Popover.test.tsx +15 -0
- package/src/Popover/Popover.tsx +19 -8
- package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +3 -2
- package/src/PowerSearch/PowerSearchToken.test.tsx +7 -5
- package/src/Selector/Selector.doc.mjs +4 -4
- package/src/Selector/Selector.focusableDisabled.test.tsx +127 -0
- package/src/Selector/Selector.spec.md +11 -11
- package/src/Selector/Selector.test.tsx +64 -6
- package/src/Selector/Selector.tsx +51 -21
- package/src/Selector/SelectorOption.tsx +4 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/Slider/Slider.spec.md +11 -11
- package/src/Slider/Slider.test.tsx +3 -3
- package/src/Slider/Slider.tsx +8 -15
- package/src/Switch/Switch.test.tsx +85 -8
- package/src/Switch/Switch.tsx +26 -16
- package/src/Switch/__tests__/Switch.a11y.states.ts +2 -2
- package/src/Switch/switch.markers.stylex.ts +4 -0
- package/src/Table/Table.spec.md +2 -0
- package/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.spec.md +244 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +5 -3
- package/src/Table/plugins/virtualization/useTableVirtualization.spec.md +266 -0
- package/src/TimeInput/TimeInput.doc.mjs +5 -3
- package/src/TimeInput/TimeInput.test.tsx +20 -0
- package/src/TimeInput/TimeInput.tsx +2 -1
- package/src/Token/Token.test.tsx +26 -4
- package/src/Token/Token.tsx +6 -2
- package/src/Tokenizer/Tokenizer.spec.md +28 -28
- package/src/Tooltip/Tooltip.test.tsx +51 -6
- package/src/Tooltip/Tooltip.tsx +41 -18
- package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +129 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +86 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +132 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +126 -0
- package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +74 -0
- package/src/Tooltip/useTooltip.test.tsx +23 -0
- package/src/Tooltip/useTooltip.tsx +22 -9
- package/src/TopNav/TopNav.tsx +11 -1
- package/src/TopNav/TopNavHeading.tsx +1 -0
- package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +74 -0
- package/src/Typeahead/Typeahead.test.tsx +89 -0
- package/src/Typeahead/Typeahead.tsx +15 -2
- package/src/hooks/useMenuPress.ts +45 -7
- package/src/utils/disabledInteraction.ts +40 -0
- package/src/utils/timeParser.test.ts +11 -0
- package/src/utils/timeParser.ts +9 -2
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: module
|
|
5
|
+
id: module:Table/useTableInfiniteScroll
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers:
|
|
13
|
+
[public-api, behavior, layout, theming, accessibility, interaction]
|
|
14
|
+
verified_by:
|
|
15
|
+
[
|
|
16
|
+
packages/core/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.test.tsx,
|
|
17
|
+
packages/core/src/Table/__tests__/TableInfiniteScroll.a11y.chromium.spec.ts,
|
|
18
|
+
packages/core/src/Table/Table.perf.test.tsx,
|
|
19
|
+
scripts/check-knowledge.mjs,
|
|
20
|
+
]
|
|
21
|
+
parent_component: component:Table
|
|
22
|
+
references:
|
|
23
|
+
[
|
|
24
|
+
architecture:component-theming-surface,
|
|
25
|
+
architecture:interaction-modality,
|
|
26
|
+
architecture:public-component-api,
|
|
27
|
+
architecture:react-component-runtime,
|
|
28
|
+
spec:AST-002,
|
|
29
|
+
spec:AST-009,
|
|
30
|
+
spec:AST-020,
|
|
31
|
+
]
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# useTableInfiniteScroll module contract
|
|
35
|
+
|
|
36
|
+
## Contract at a glance
|
|
37
|
+
|
|
38
|
+
| Area | Contract |
|
|
39
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
40
|
+
| Public contract | Candidate `useTableInfiniteScroll` hook returning one `TablePlugin` from controlled `hasMore`, controlled `isLoading`, and `onLoadMore`; root, reset, fallback, and look-ahead inputs remain unadmitted until their owner decisions pass. |
|
|
41
|
+
| Behavior | One end sentinel requests the next batch as it approaches the relevant block-scroll viewport, never overlaps requests, and rearms when loading completes while more data remains. |
|
|
42
|
+
| End-user impact | People can continue through incrementally loaded rows without duplicate requests, focus disruption, inaccessible progress, or a dead end when automatic observation is unavailable. |
|
|
43
|
+
| Builder impact | Builders own data, request state, errors, and appended rows. The plugin owns observation, duplicate suppression, progress/fallback presentation, and cleanup. |
|
|
44
|
+
| Compatibility/readiness | Additive, unreleased module. This draft is not implementation authority. Scroll-root ownership, request completion/reset, parent rendering-mode seam, fallback policy, loading/error composition, customization, and announcement evidence require owner decisions before promotion. |
|
|
45
|
+
| Review checks | Reject viewport assumptions, overlapping loads, stale callbacks, a permanently visible sentinel loop, unexplained focusable sentinels, progress announced on every observer tick, a mouse-only fallback, invalid table DOM, retained observers, or router/data-client dependencies. |
|
|
46
|
+
| Governing rules | `component:Table` FR9-FR12 and ORD1-ORD4 own plugin composition; `architecture:react-component-runtime` owns observer lifetime; `architecture:public-component-api` and `spec:AST-002` own public caller burden. |
|
|
47
|
+
|
|
48
|
+
This table is a review projection; the body below is authoritative.
|
|
49
|
+
|
|
50
|
+
## Intent
|
|
51
|
+
|
|
52
|
+
`useTableInfiniteScroll` connects incremental data loading to a data-driven Table
|
|
53
|
+
without making Table own a router, cache, query client, cursor format, or product data.
|
|
54
|
+
It owns an end sentinel, observation lifecycle, one-request-at-a-time behavior,
|
|
55
|
+
rearming, accessible progress, and a keyboard-operable fallback.
|
|
56
|
+
|
|
57
|
+
Infinite scroll is distinct from client pagination and virtualization. It appends data;
|
|
58
|
+
it does not choose the caller's page/cursor protocol or bound the number of rendered rows.
|
|
59
|
+
|
|
60
|
+
## Compatibility and migration
|
|
61
|
+
|
|
62
|
+
- Released default preserved: `yes`; Table behavior remains unchanged unless the new plugin is supplied.
|
|
63
|
+
- Compatibility class: additive public hook, types, documentation, and examples.
|
|
64
|
+
- Migration decision: `module:Table/useTableInfiniteScroll/DEC-1` after the open ownership decisions are approved.
|
|
65
|
+
|
|
66
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
67
|
+
|
|
68
|
+
## Ownership boundary
|
|
69
|
+
|
|
70
|
+
**Owns**
|
|
71
|
+
|
|
72
|
+
- The public hook, configuration, sentinel, observer lifecycle, and loading trigger boundary.
|
|
73
|
+
- Duplicate-request suppression, rearming after completion, and short-batch behavior.
|
|
74
|
+
- Default progress and manual fallback behavior, accessibility, performance, and evidence.
|
|
75
|
+
- Composition with Table's scroll wrapper, empty state, loading state, appended rows, other plugins, and the candidate children-mode warning/no-op, blocked on OQ8's parent contract decision.
|
|
76
|
+
|
|
77
|
+
**Does not own / non-goals**
|
|
78
|
+
|
|
79
|
+
- Aggregate `TablePlugin` protocol, transform order, or scroll-wrapper implementation — owned by `component:Table`.
|
|
80
|
+
- Product data, request/cursor/page state, cache policy, retry policy, or error content — owned by the caller or another module.
|
|
81
|
+
- Client pagination, server pagination, Relay integration, row virtualization, or column virtualization.
|
|
82
|
+
- Scroll restoration, URL state, routing, or prefetching.
|
|
83
|
+
- A new public Table theme target unless a separate approved decision establishes a distinct theme-author need.
|
|
84
|
+
- Caller-composed children-mode semantics or the parent rendering-mode contract. The candidate warning/no-op depends on OQ8; the module never interprets caller children or infers mode from DOM to append loading UI.
|
|
85
|
+
|
|
86
|
+
## Public API and concepts
|
|
87
|
+
|
|
88
|
+
The candidate minimum shape is:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
export interface UseTableInfiniteScrollConfig {
|
|
92
|
+
hasMore: boolean;
|
|
93
|
+
isLoading: boolean;
|
|
94
|
+
onLoadMore: () => void | Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function useTableInfiniteScroll<T extends Record<string, unknown>>(
|
|
98
|
+
config: UseTableInfiniteScrollConfig,
|
|
99
|
+
): TablePlugin<T>;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The final shape is blocked on OQ1-OQ8. Consumer docs own final signatures,
|
|
103
|
+
defaults, imports, and examples after promotion.
|
|
104
|
+
|
|
105
|
+
| Concept | Closed values or states | Meaning | Default | Owner | Stability |
|
|
106
|
+
| --------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------- | --------- |
|
|
107
|
+
| More-data state | `hasMore` true or false | Whether another load may be requested. | Controlled | Caller through this module | Candidate |
|
|
108
|
+
| Loading state | `isLoading` true or false | Whether a request is already in flight. | Controlled | Caller through this module | Candidate |
|
|
109
|
+
| Load request | `void` or promise-returning callback | Starts one caller-owned append operation without prescribing its data protocol. | No request while blocked | Caller through this module | Unsettled |
|
|
110
|
+
| Look-ahead | Optional non-negative distance, if admitted | Starts loading before the sentinel reaches the visible block-end edge. | Not yet admitted | Caller/module boundary | Unsettled |
|
|
111
|
+
| Sentinel | Observed, loading, exhausted, unavailable | Coordinates automatic loading without becoming unexplained table data or an accidental tab stop. | Derived | `module:Table/useTableInfiniteScroll` | Candidate |
|
|
112
|
+
| Manual fallback | Available or hidden | Provides an explicit load command when automatic observation is unavailable or unsuitable. | Unsettled | `module:Table/useTableInfiniteScroll` | Unsettled |
|
|
113
|
+
|
|
114
|
+
## Behavioral contract
|
|
115
|
+
|
|
116
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
117
|
+
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------- |
|
|
118
|
+
| FR1 | Stable configuration callbacks and unchanged primitive inputs preserve one stable `TablePlugin`. | Parent plugin identity and runtime architecture | Settled candidate |
|
|
119
|
+
| FR2 | The plugin contributes one end sentinel after the semantic `<table>`, inside the Table scroll-wrapper content. It never inserts non-row content inside `<table>`, `<thead>`, `<tbody>`, or `<tr>`. | Valid HTML and parent wrapper contract | Settled candidate |
|
|
120
|
+
| FR3 | Automatic loading is armed only when `hasMore` is true and `isLoading` is false. One visibility episode produces at most one active request. | Duplicate suppression | Settled candidate |
|
|
121
|
+
| FR4 | While a load is in flight, the module does not call `onLoadMore` again, including through repeated observation, manual activation, or rerender. Completion can make another call eligible only for the current configuration, subject to the controlled inputs and FR14. The caller remains solely responsible for suppressing stale product-data commits. | Controlled state and async ownership | Callback completion contract unsettled |
|
|
122
|
+
| FR5 | When a short append leaves the sentinel visible and `hasMore` remains true, completing the prior load can request the next batch without requiring the user to scroll away and back. | Continuous loading behavior | Settled outcome; mechanism free |
|
|
123
|
+
| FR6 | `hasMore=false` disconnects observation, removes the manual command, and presents no loading state. Changing back to true can arm a new request. | Exhausted-state boundary | Settled candidate |
|
|
124
|
+
| FR7 | An observer-unavailable environment preserves server rendering/hydration and exposes the approved manual fallback rather than throwing or silently dead-ending the user. | Progressive enhancement | Fallback policy unsettled |
|
|
125
|
+
| FR8 | Loading progress is shown and announced at most once per state transition, not once per observer callback. Error and retry UI remain caller-owned and do not render duplicate progress chrome. | Accessibility and module boundary | Composition surface unsettled |
|
|
126
|
+
| FR9 | The module does not call another plugin's callbacks, mutate selection/activation/expansion/sort/filter state, replace product rows, or reset scroll position. Preservation of caller-controlled state follows from that non-interference; caller data updates remain caller-owned. | Plugin independence | Settled candidate |
|
|
127
|
+
| FR10 | Empty data may still load when `hasMore=true`; default EmptyState and load affordance do not obscure each other or communicate contradictory states. | Empty/loading composition | Human layout decision |
|
|
128
|
+
| FR11 | Approved scroll-root or look-ahead changes reconnect observation without leaking the old observer or triggering from a detached sentinel. Unmount and node replacement release every observer and retained node. | `architecture:react-component-runtime` | Settled candidate |
|
|
129
|
+
| FR12 | The plugin remains data-protocol agnostic. `onLoadMore` receives no Table-owned page, cursor, Relay, router, cache, or request-client object. | Public ownership boundary | Settled candidate |
|
|
130
|
+
| FR13 | In children mode the module is a no-op and emits at most one deduplicated development warning. This candidate outcome depends on OQ8's missing parent contract; it does not establish a rendering-mode signal on the current Table wrapper transform or permit mode inference from DOM. | Missing `component:Table` dependency, OQ8 | Hold: parent contract decision required |
|
|
131
|
+
| FR14 | Reconfiguration during a load follows one approved caller-visible reset or React remount boundary. Settlement from a load started before that boundary neither calls `onLoadMore`, makes a call eligible, nor changes loading presentation for the current configuration. Caller response ownership remains unchanged. | Caller-visible reset and stale-completion safety | Human API decision |
|
|
132
|
+
|
|
133
|
+
### Transformation and precedence order
|
|
134
|
+
|
|
135
|
+
- **ORD1 — Exhausted/loading gate.** Resolve `hasMore` and `isLoading` before observing or exposing a manual command.
|
|
136
|
+
- **ORD2 — Current configuration.** Calls use the current `onLoadMore` and respect the approved reset/remount boundary; changing callback identity alone does not end an in-flight load or silently redefine product-data ownership.
|
|
137
|
+
- **ORD3 — One load at a time.** From invocation of `onLoadMore` until the approved completion condition, no observation or manual activation invokes it again, including reentrant activation from caller code. Synchronous throws and asynchronous settlement follow OQ2's completion rule.
|
|
138
|
+
- **ORD4 — Completion after reset.** FR14 excludes settlement from before a reset/remount from invoking or enabling a call or changing current loading presentation. Completion of a current-configuration load follows OQ2/OQ5 and the controlled inputs; the plugin never commits or rejects caller data.
|
|
139
|
+
- **ORD5 — Rearm.** Controlled completion and continued `hasMore` rearm the current sentinel, including when it remains visible.
|
|
140
|
+
- **ORD6 — Presentation.** Exhausted, loading, automatic, fallback, and error-adjacent states render one coherent after-table surface.
|
|
141
|
+
- **ORD7 — Parent pipeline.** In data mode the module appends to accumulated `afterTable` content and preserves prior wrapper props, styles, refs, and chrome; children mode returns prior props unchanged, subject to OQ8's parent contract dependency.
|
|
142
|
+
|
|
143
|
+
### Performance and resources
|
|
144
|
+
|
|
145
|
+
- **PR1 — One observer.** At most one `IntersectionObserver` and one observed sentinel exist per mounted plugin instance.
|
|
146
|
+
- **PR2 — No scroll listener.** Native observation, not a per-frame or per-scroll JavaScript listener, drives automatic loading.
|
|
147
|
+
- **PR3 — Stable resources.** Callback updates do not recreate observation when the approved root/look-ahead geometry and sentinel are unchanged.
|
|
148
|
+
- **PR4 — Complete cleanup.** Unmount, sentinel replacement, root replacement, exhaustion, and loading transitions release obsolete observation.
|
|
149
|
+
- **PR5 — Constant callback work.** An observer entry performs constant coordination work and never scans rows or columns.
|
|
150
|
+
|
|
151
|
+
## Accessibility contract
|
|
152
|
+
|
|
153
|
+
- **AR1 — Valid structure.** Sentinel, progress, and fallback UI stay outside the semantic table and do not appear as data rows or cells (WCAG 2.2 SC 1.3.1; DOM/accessibility-tree and browser evidence).
|
|
154
|
+
- **AR2 — Progress announcement.** Loading transitions use a non-interruptive status that does not repeat for observer churn or every appended row (WCAG 2.2 SC 4.1.3). Deterministic transition tests prove event count/order, and the approved implementation requires NVDA + Chrome on Windows and VoiceOver + Safari on macOS to prove spoken announcement timing under `spec:AST-009` FR4 and FR12.
|
|
155
|
+
- **AR3 — Operable fallback.** The approved fallback is a real keyboard-operable control with a localized name and visible focus; the sentinel itself is not an unexplained tab stop (WCAG 2.2 SC 2.1.1, 2.4.7, and 4.1.2; DOM/axe/real-browser evidence).
|
|
156
|
+
- **AR4 — Focus stability.** Appending rows does not move focus, reset focused descendants, or make the fallback disappear while it owns focus without a deterministic successor (WCAG 2.2 SC 2.4.3 and 2.4.7; real-browser keyboard evidence).
|
|
157
|
+
- **AR5 — Reduced motion.** Progress presentation does not require motion to communicate loading state and respects platform motion settings; real-browser visual evidence proves the supported states.
|
|
158
|
+
- **AR6 — Touch and zoom.** Automatic loading and fallback remain available without hover, precision pointing, or a fixed viewport size (WCAG 2.2 SC 1.4.10 and 2.1.1; responsive/touch real-browser evidence).
|
|
159
|
+
- **AR7 — Evidence boundaries.** Real AT evidence is limited to AR2's announcement claim or any later approved AT-dependent outcome; it supplements rather than replaces deterministic state, DOM, keyboard, and browser coverage.
|
|
160
|
+
|
|
161
|
+
## Design relationships
|
|
162
|
+
|
|
163
|
+
| Anatomy or state | Design requirement | Representation authority | Module contract |
|
|
164
|
+
| ------------------- | ------------------------------------------------------------------------------------- | ------------------------- | ----------------- |
|
|
165
|
+
| End sentinel | Supplies observation geometry without masquerading as table data or a control. | This module | FR2-FR7, AR1 |
|
|
166
|
+
| Loading progress | Communicates one active append operation without duplicating caller-owned chrome. | Unsettled pending OQ4 | FR3-FR8, AR2, AR5 |
|
|
167
|
+
| Manual load command | Provides an explicit fallback with normal control semantics and focus treatment. | Unsettled pending OQ3/OQ4 | FR7-FR10, AR3-AR4 |
|
|
168
|
+
| Exhausted state | Stops requesting and removes load affordances without adding an unsolicited message. | This module | FR6, ORD1, ORD5 |
|
|
169
|
+
| Error/retry | Remains outside the module unless a later loading/error composition contract owns it. | Caller or another module | FR8, non-goal |
|
|
170
|
+
|
|
171
|
+
The module adds no direct theme target. OQ3/OQ4 must select any progress/fallback component and then name its exact existing target or record a classified `none`; no delegation is promised yet. Sentinel geometry is implementation detail rather than theme API.
|
|
172
|
+
|
|
173
|
+
## Parent and system relationships
|
|
174
|
+
|
|
175
|
+
- `component:Table` owns the scroll-wrapper phase and accumulated `afterTable` slot. **Missing parent dependency (OQ8):** its current contract does not establish a data/children mode signal at that phase. The module remains on hold until the parent contract records the approved mode distinction or makes the invalid combination unrepresentable; FR13 then cites that owning claim. This draft neither supplies a field nor approves a parent API change.
|
|
176
|
+
- `architecture:react-component-runtime` owns observer acquisition, current-value synchronization, node replacement, StrictMode replay, and cleanup boundaries.
|
|
177
|
+
- `architecture:public-component-api` and `spec:AST-002` require a protocol-agnostic API whose inputs express caller-owned state rather than implementation clients.
|
|
178
|
+
- `spec:AST-009` and `spec:AST-020` own announcement evidence and the WCAG/evidence-layer boundary; AR2 is the only current real-AT-dependent claim.
|
|
179
|
+
- Pagination and virtualization remain separate modules. Infinite loading can compose with them only where their current contracts explicitly permit it.
|
|
180
|
+
|
|
181
|
+
## Verification map
|
|
182
|
+
|
|
183
|
+
| Contract | Verification | Representative states | Mutation or failure expectation |
|
|
184
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| FR1, FR3-FR7, FR14, ORD1-ORD5 | Deterministic observer events, callback count/order assertions, and controlled rerenders | Visible/hidden, loading, exhausted, short batch, reentrant activation, throw, async settle, reset/remount | Calls overlap, pre-reset completion invokes or enables a call or changes current loading presentation, short batches dead-end, observation throws, or rearm fails. |
|
|
186
|
+
| FR2, FR8-FR10, FR12-FR13, ORD6-ORD7 | Table render and plugin-composition tests against the parent rendering-mode contract once OQ8 is resolved | Data/children mode, empty/non-empty, wrapper chrome, loading, exhausted, caller error UI | Invalid DOM appears, children mode renders a sentinel, warning repeats, prior chrome is lost, caller state is mutated, or states contradict; absent parent authority keeps OQ8 on hold. |
|
|
187
|
+
| FR11, PR1-PR5 | Observer lifecycle tests under StrictMode, root/sentinel/look-ahead change, unmount, and no-op rerender | Every approved acquisition, replacement, and release path | More than one observer survives, detached nodes remain observed, or stable inputs churn resources. |
|
|
188
|
+
| AR1, AR3-AR7 | DOM/accessibility-tree assertions, axe, and real-browser keyboard/touch/zoom/reduced-motion/focus flows | Automatic, fallback, loading, append, exhaustion, observer unavailable | Sentinel becomes a row/tab stop, focus is lost, fallback is inoperable, or responsive/motion behavior carries required meaning. |
|
|
189
|
+
| AR2 | Deterministic status-transition count/order tests plus NVDA + Chrome on Windows and VoiceOver + Safari on macOS | Initial load, repeated observer entries, short batches, completion, exhaustion | Status repeats, arrives out of order, is skipped, or announces observer churn/appended rows instead of loading transitions. |
|
|
190
|
+
| Public API and docs | Public-subpath compile fixture, `.doc.mjs`, generated inventory, Storybook, and one generic async-cache recipe | Controlled state, promise/void callback, approved reset/root/fallback/look-ahead inputs | API leaks a client, exposes look-ahead without admission, cannot express the approved reset boundary, promises unsettled behavior, or permits contradictions. |
|
|
191
|
+
| Performance | Extend Table performance coverage with stable no-op, observer callback, and repeated append budgets | Large representative data and repeated batches | Stable rerenders recreate resources, callbacks scan rows, or appends exceed recorded budgets. |
|
|
192
|
+
| Structural ownership | `pnpm check:knowledge` | Draft module and parent backlink | A missing, orphaned, misnamed, wrong-kind, or duplicate module record passes validation. |
|
|
193
|
+
|
|
194
|
+
## Decision log
|
|
195
|
+
|
|
196
|
+
### DEC-1 — Infinite scroll owns loading coordination, not data
|
|
197
|
+
|
|
198
|
+
**Reference:** `module:Table/useTableInfiniteScroll/DEC-1`
|
|
199
|
+
**Decider:** pending owner approval
|
|
200
|
+
|
|
201
|
+
The module observes the end of rendered Table content and asks the caller for another batch. It owns at-most-one-in-flight loading, callback eligibility after completion/reset, rearming, and presentation under FR4/FR14 and ORD2-ORD5, not an internal coordination mechanism. The caller remains the sole owner of rows, request protocol, cache, cursor/page, stale-response suppression, errors, and retry policy.
|
|
202
|
+
|
|
203
|
+
Rejected: importing a router/query client, mutating Table data, or combining cursor,
|
|
204
|
+
server-pagination, Relay, and infinite-loading contracts.
|
|
205
|
+
|
|
206
|
+
### DEC-2 — The sentinel is after the table
|
|
207
|
+
|
|
208
|
+
**Reference:** `module:Table/useTableInfiniteScroll/DEC-2`
|
|
209
|
+
**Decider:** pending owner approval
|
|
210
|
+
|
|
211
|
+
Observation and progress UI use the parent scroll wrapper's `afterTable` slot. They never
|
|
212
|
+
insert non-row elements into semantic table structure or synthesize a fake data row.
|
|
213
|
+
|
|
214
|
+
Rejected: a `<div>` inside `<tbody>`, a sentinel `<tr>` announced as data, and a focusable
|
|
215
|
+
sentinel with no user command.
|
|
216
|
+
|
|
217
|
+
### DEC-3 — Automatic loading requires an operable fallback
|
|
218
|
+
|
|
219
|
+
**Reference:** `module:Table/useTableInfiniteScroll/DEC-3`
|
|
220
|
+
**Decider:** pending owner approval
|
|
221
|
+
|
|
222
|
+
Automatic observation is progressive enhancement. An approved fallback prevents a dead
|
|
223
|
+
end when observation is unsupported, disabled, or unsuitable and gives keyboard users an
|
|
224
|
+
explicit command without making the sentinel itself interactive.
|
|
225
|
+
|
|
226
|
+
Rejected: silently doing nothing without IntersectionObserver and making automatic loading
|
|
227
|
+
the only path.
|
|
228
|
+
|
|
229
|
+
## Open questions
|
|
230
|
+
|
|
231
|
+
- **OQ1 — Who owns the block-scroll root?** (`human-api`) Decide viewport default versus an explicit caller root/ref, and how Table nested in a vertical `ScrollableArea` identifies the correct root without brittle ancestor discovery.
|
|
232
|
+
- **OQ2 — What observable event completes a load?** (`human-api`) Decide whether controlled `isLoading`, a returned promise, or both permit another `onLoadMore` call after synchronous throw and asynchronous settlement.
|
|
233
|
+
- **OQ3 — When is manual fallback shown?** (`human-design`) Decide always-visible, observer-unavailable-only, user-selected, or another policy; define focus behavior when loading/exhaustion removes it.
|
|
234
|
+
- **OQ4 — What loading customization is public?** (`human-api`) Decide whether the module renders one fixed default, accepts `renderLoadingIndicator`, composes an existing loading-state module, or stays behavior-only while still meeting progress requirements.
|
|
235
|
+
- **OQ5 — How do error and retry compose?** (`human-api`) Keep error content caller-owned, but define how a failed request rearms automatic loading and avoids immediate retry loops or duplicate loading/error chrome.
|
|
236
|
+
- **OQ6 — Is caller-configurable look-ahead admitted?** (`human-api`) First prove a non-derivable caller distinction under `spec:AST-002`; if admitted, choose an intent-named distance API with explicit units/default rather than reusing browser `threshold` terminology, which conventionally means an intersection ratio. Keep it out of the candidate interface until then.
|
|
237
|
+
- **OQ7 — What is the caller-visible reset boundary?** (`human-api`) Decide an explicit caller-owned reset identity versus a documented React remount/key boundary when dataset/query configuration changes during a load. Pre-reset settlement neither invokes nor enables a call nor changes loading presentation for the current configuration; stale product-data suppression remains the caller's job.
|
|
238
|
+
- **OQ8 — Which parent contract distinguishes data from children mode?** (`human-api`) Decide a parent-owned rendering-mode signal at the wrapper transform versus making the invalid combination unrepresentable, preserving FR13's warning/no-op outcome for children mode. The chosen behavior belongs in `component:Table`, and FR13 must cite its approved owning claim before this module is promoted. This missing dependency remains on hold; no parent field or approval is assumed.
|
|
239
|
+
|
|
240
|
+
## Content boundary
|
|
241
|
+
|
|
242
|
+
This record does not duplicate consumer signatures/examples, the parent Table wrapper
|
|
243
|
+
protocol, general observer mechanics, current usage audits, implementation steps, or
|
|
244
|
+
pagination/virtualization/data-client contracts. It links to their canonical owners.
|
|
@@ -195,9 +195,11 @@ describe('useTableSelection', () => {
|
|
|
195
195
|
it('disables checkbox for disabled rows', () => {
|
|
196
196
|
render(<SelectionTable getIsItemEnabled={item => !item.isLocked} />);
|
|
197
197
|
const rowCheckboxes = screen.getAllByLabelText('Select row');
|
|
198
|
-
expect(rowCheckboxes[0]).not.
|
|
199
|
-
expect(rowCheckboxes[1]).not.
|
|
200
|
-
|
|
198
|
+
expect(rowCheckboxes[0]).not.toHaveAttribute('aria-disabled');
|
|
199
|
+
expect(rowCheckboxes[1]).not.toHaveAttribute('aria-disabled');
|
|
200
|
+
// Focusable disabled (spec:AST-085 FR1): aria-disabled, no native disabled.
|
|
201
|
+
expect(rowCheckboxes[2]).toHaveAttribute('aria-disabled', 'true');
|
|
202
|
+
expect(rowCheckboxes[2]).not.toHaveAttribute('disabled');
|
|
201
203
|
});
|
|
202
204
|
|
|
203
205
|
it('prepends selection <td> to each body row', () => {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: module
|
|
5
|
+
id: module:Table/useTableVirtualization
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers:
|
|
13
|
+
[
|
|
14
|
+
public-api,
|
|
15
|
+
behavior,
|
|
16
|
+
layout,
|
|
17
|
+
theming,
|
|
18
|
+
accessibility,
|
|
19
|
+
interaction,
|
|
20
|
+
performance,
|
|
21
|
+
react-runtime,
|
|
22
|
+
]
|
|
23
|
+
verified_by:
|
|
24
|
+
[
|
|
25
|
+
packages/core/src/Table/plugins/virtualization/useTableVirtualization.test.tsx,
|
|
26
|
+
packages/core/src/Table/__tests__/TableVirtualization.a11y.chromium.spec.ts,
|
|
27
|
+
packages/core/src/Table/Table.perf.test.tsx,
|
|
28
|
+
scripts/check-knowledge.mjs,
|
|
29
|
+
]
|
|
30
|
+
parent_component: component:Table
|
|
31
|
+
references:
|
|
32
|
+
[
|
|
33
|
+
architecture:component-theming-surface,
|
|
34
|
+
architecture:interaction-modality,
|
|
35
|
+
architecture:public-component-api,
|
|
36
|
+
architecture:react-component-runtime,
|
|
37
|
+
spec:AST-002,
|
|
38
|
+
spec:AST-009,
|
|
39
|
+
spec:AST-020,
|
|
40
|
+
spec:AST-025,
|
|
41
|
+
]
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# useTableVirtualization module contract
|
|
45
|
+
|
|
46
|
+
## Contract at a glance
|
|
47
|
+
|
|
48
|
+
| Area | Contract |
|
|
49
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
50
|
+
| Public contract | Candidate fixed-height row-windowing module with caller-owned data, one validated stable unique Table row identity, a vertical viewport, row height, and an explicit server-rendered initial range. OQ1 keeps parent-owned phases, a dedicated component, and an atomic props bundle open. Caller-configurable overscan is not admitted unless OQ12 proves a semantic need. |
|
|
51
|
+
| Behavior | Render one contiguous visible row window plus an internally bounded overscan and, when necessary, one separately bounded retained-focus row subtree, while preserving native table structure, full-table row metadata, scroll geometry, stable identity, and supported plugin/core appearance. |
|
|
52
|
+
| End-user impact | Large tables keep bounded DOM and responsive scrolling while preserving supported row state and keyboard focus. The accessibility tree exposes truthful full-table positions for mounted rows; this contract does not claim virtual-cursor discovery of unmounted rows. |
|
|
53
|
+
| Builder impact | Builders opt into a fixed-height contract, identify the vertical viewport, and supply stable row keys. Variable-height content and unsupported plugin combinations remain explicit rather than silently degrading. |
|
|
54
|
+
| Compatibility/readiness | Additive, unreleased virtualization capability; no automatic correction to existing nonvirtualized paginated rowIndexStart/rowCount callers. Any shared correction needs a separately approved Table authority/compatibility decision under OQ9. This draft is not implementation authority; parent integration, viewport, fixed height, SSR, focus, anchoring, plugin/appearance compatibility, and budgets remain owner decisions. |
|
|
55
|
+
| Review checks | Reject invalid table DOM, block rewrites of semantic elements, render-then-hide, mismatched/duplicate/index keys, incomplete full-table ARIA totals or positions, unenforceable fixed height, silent clipping/drift, lost focus, unbounded focus retention, spacer-relative striping/dividers, recycled uncontrolled state, scroll jumps, unsupported combinations that appear to work, or unmeasured performance claims. |
|
|
56
|
+
| Governing rules | `component:Table` owns shared plugin phases and row rendering; its DEC-1 and [`spec:AST-025`](../../../../../../docs/specs/AST-025/spec.md) FR18/FR19 constrain shared viewport ownership. `architecture:react-component-runtime` owns measurement/resource lifetime; `architecture:public-component-api` and `spec:AST-002` own public caller burden. |
|
|
57
|
+
|
|
58
|
+
This table is a review projection; the body below is authoritative.
|
|
59
|
+
|
|
60
|
+
## Intent
|
|
61
|
+
|
|
62
|
+
`useTableVirtualization` bounds the number of mounted body rows in a large data-driven
|
|
63
|
+
Table while preserving semantic table structure and the caller's row identities. It owns
|
|
64
|
+
visible-range calculation, overscan, spacer geometry, absolute row-position metadata,
|
|
65
|
+
focus retention, supported-plugin composition, resize/scroll coordination, performance
|
|
66
|
+
budgets, and implementation evidence.
|
|
67
|
+
|
|
68
|
+
Row virtualization is distinct from infinite loading and pagination. It limits mounted DOM
|
|
69
|
+
for data the caller already owns; it does not fetch, page, cache, or discard product data.
|
|
70
|
+
Column virtualization is a separate contract.
|
|
71
|
+
|
|
72
|
+
## Compatibility and migration
|
|
73
|
+
|
|
74
|
+
- Released default preserved: `yes` for the additive virtualization capability; absent opt-in, Table renders every supplied row and this module does not change existing nonvirtualized paginated `rowIndexStart`/`rowCount` output.
|
|
75
|
+
- Compatibility class: additive public capability plus the smallest approved parent integration. Full-table metadata is required for virtualized mode. Any shared correction affecting existing nonvirtualized callers requires a separately approved `component:Table` authority/compatibility decision; this module does not automatically authorize a global correction.
|
|
76
|
+
- Migration decision: `module:Table/useTableVirtualization/DEC-1` after the protocol and interaction questions are approved. OQ9 keeps the metadata integration and public compatibility scope explicit; any migration for existing callers belongs to the separately approved Table decision.
|
|
77
|
+
|
|
78
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
79
|
+
|
|
80
|
+
## Ownership boundary
|
|
81
|
+
|
|
82
|
+
**Owns**
|
|
83
|
+
|
|
84
|
+
- The public virtualization capability and its fixed-height row-window contract, independent of the API shape chosen in OQ1.
|
|
85
|
+
- Visible-range and overscan calculation, top/bottom spacer geometry, and vertical viewport coordination.
|
|
86
|
+
- Stable row identity through window changes and explicit DOM-recycling policy.
|
|
87
|
+
- Virtualized full-table `aria-rowindex`/`aria-rowcount`, focus retention, keyboard/assistive-technology behavior, and evidence; admitted detail-row representation remains with the parent/detail owner.
|
|
88
|
+
- Compatibility declarations for selection, sorting, filtering, activation, expansion/tree/grouping, sticky columns, context menus, and infinite loading.
|
|
89
|
+
- Resize/scroll resource lifetime and measurable performance budgets.
|
|
90
|
+
|
|
91
|
+
**Does not own / non-goals**
|
|
92
|
+
|
|
93
|
+
- Aggregate plugin protocol and shared render phases — owned by `component:Table`.
|
|
94
|
+
- A global correction or migration of existing nonvirtualized paginated `rowIndexStart`/`rowCount` callers; OQ9 requires a separate Table authority/compatibility decision for any such shared change.
|
|
95
|
+
- Expanded detail representation or expansion admission; the parent/detail owner owns the shape, and OQ6 must admit the combination before it is supported.
|
|
96
|
+
- Product data, fetching, caching, pagination, infinite loading, or server cursors.
|
|
97
|
+
- Column virtualization.
|
|
98
|
+
- Variable-height row measurement in v1 unless owner evidence admits it explicitly.
|
|
99
|
+
- Product scroll restoration, route state, or imperative navigation to arbitrary data not owned by the caller.
|
|
100
|
+
- Cell renderer correctness for unstable keys or content that violates the declared fixed-height contract.
|
|
101
|
+
- A new theme target. Spacer geometry is not theme anatomy, and visible rows use existing Table targets.
|
|
102
|
+
|
|
103
|
+
## Public API and concepts
|
|
104
|
+
|
|
105
|
+
No TypeScript shape is admitted yet. OQ1 chooses parent-owned phases, a dedicated
|
|
106
|
+
component, or one atomic Table-compatible props bundle. Whichever shape is approved,
|
|
107
|
+
the integration MUST:
|
|
108
|
+
|
|
109
|
+
- accept mutable-compatible `T[]` data supported by current `TableProps<T>`;
|
|
110
|
+
- keep data and the exact validated stable unique `idKey` together for window identity and Table reconciliation, so callers cannot accidentally omit or disagree with it;
|
|
111
|
+
- provide one canonical logical offset/count source from which ARIA metadata and `useTableRowIndex` derive their respective positions rather than requiring duplicate caller-maintained offsets; and
|
|
112
|
+
- define duplicate/invalid-key behavior before exposing the capability.
|
|
113
|
+
|
|
114
|
+
The current parent protocol cannot safely express data windowing, semantic spacer rows,
|
|
115
|
+
identity, and full-table metadata without an approved integration; OQ1 owns that boundary.
|
|
116
|
+
|
|
117
|
+
| Concept | Closed values or states | Meaning | Default | Owner | Stability |
|
|
118
|
+
| ------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------- | --------- |
|
|
119
|
+
| Data ownership | Complete caller-owned ordered `T[]` | Supplies the logical dataset from which mounted rows are derived. | Required | Caller through this module | Candidate |
|
|
120
|
+
| Row identity | Validated stable unique `string` or `number` key | Drives both window identity and Table reconciliation; duplicate/invalid keys follow one fail-safe policy. | Required | Parent/module boundary | Unsettled |
|
|
121
|
+
| Row measurement | Positive finite total row block size in CSS pixels | Defines deterministic geometry under an enforceable fixed-height content/appearance mode. | Required in v1 | `module:Table/useTableVirtualization` | Unsettled |
|
|
122
|
+
| Visible range | Contiguous half-open logical index interval | Names the rows intersecting the viewport before internal overscan. | Derived | `module:Table/useTableVirtualization` | Candidate |
|
|
123
|
+
| Focus island | Absent or one separately bounded retained row subtree | Keeps the currently focused row and, only if OQ6 admits it, owned detail mounted without stretching the visible range across the dataset. | Absent without remote focus | `module:Table/useTableVirtualization` | Unsettled |
|
|
124
|
+
| Overscan | Internally bounded non-negative row count | Absorbs ordinary scroll/keyboard movement without becoming a caller tuning API unless OQ12 admits it. | Internal | `module:Table/useTableVirtualization` | Unsettled |
|
|
125
|
+
| Spacer geometry | One or more inaccessible logical-space segments | Preserves full scroll extent around the visible range and optional focus island. | Derived | `module:Table/useTableVirtualization` | Candidate |
|
|
126
|
+
| Initial range | Explicit positive row count or other approved seed | Provides deterministic server/hydration output before a client viewport is measurable. | Unsettled | Caller/module boundary | Unsettled |
|
|
127
|
+
| Recycling policy | Stable keyed mount/unmount; no cross-key DOM reuse | Avoids moving uncontrolled/focused state between logical rows. | No cross-key recycling | `module:Table/useTableVirtualization` | Candidate |
|
|
128
|
+
| Appearance metadata | Absolute logical parity and logical-final-row state | Preserves striping/dividers independently of spacer/window DOM position. | Derived | Parent/module boundary | Unsettled |
|
|
129
|
+
|
|
130
|
+
## Behavioral contract
|
|
131
|
+
|
|
132
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
133
|
+
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
|
|
134
|
+
| FR1 | Stable callbacks, data identity, viewport, valid row height, initial-range inputs, and unchanged internal overscan preserve stable module output where their observable result is unchanged. | Parent identity and runtime architecture | Settled candidate |
|
|
135
|
+
| FR2 | Mounted data consists of one contiguous visible range plus internally bounded overscan and, when focus would otherwise unmount, at most one separately bounded retained-focus row subtree. Empty data mounts no data/spacer/focus-island row. | Window and focus bounds | Human interaction decision |
|
|
136
|
+
| FR3 | Spacer segments plus mounted row heights equal the full logical body height, including around an optional focus island. The same logical offset before/after a no-op render produces the same visible range without a jump. | Scroll continuity | Settled candidate |
|
|
137
|
+
| FR4 | Native `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, and `<td>` semantics remain valid. Spacer representation is absent from accessibility semantics and does not require block/absolute/role rewrites of structural table elements. | HTML and accessibility boundary | Architecture decision pending |
|
|
138
|
+
| FR5 | Virtualized full-table metadata counts the complete logical table, not just mounted rows, and absolutely indexes represented header/data rows and, if OQ6 admits expansion, every represented semantic detail row. Inaccessible spacers count as neither. The parent/detail owner defines the logical sequence and detail positions; semantic detail rows cannot be silently omitted. Without detail rows, data indices are `headerRowCount + logicalDataIndex + 1` and the total is `headerRowCount + logicalDataRowCount`; these formulas cannot omit admitted semantic detail rows. OQ9 governs integration and any separately approved shared compatibility correction. | WAI-ARIA row metadata; parent/detail ownership and compatibility | Parent API and detail admission decisions pending |
|
|
139
|
+
| FR6 | One validated stable unique canonical key drives both window identity and Table reconciliation. The approved integration makes that key inseparable from its data regardless of API shape. Duplicate/invalid keys disable virtualization and fall back to full rendering in all builds, with one development warning. | React identity and fail-safe behavior | Parent/API decision required |
|
|
140
|
+
| FR7 | A focused row that leaves the ordinary window becomes the one bounded focus island rather than stretching the contiguous range or moving focus to the document body. A focused owned detail subtree participates only if OQ6 admits its representation, metadata, and focus behavior. The island is released on blur, removal, or return to the ordinary range. | Keyboard/focus correctness | Human interaction and detail admission decisions |
|
|
141
|
+
| FR8 | Sorting/filtering/data replacement recompute current logical order and reconcile scroll/focus anchors according to one explicit policy. Stale geometry or prior indices cannot expose another row under a retained key. | Controlled data and identity | Scroll-anchor policy unsettled |
|
|
142
|
+
| FR9 | Sticky columns, selection, sorting, filtering, context menus, and `useTableRowIndex` preserve state/meaning for mounted rows. One parent logical-offset source drives ARIA and visible row numbering. Activation, expansion, tree, grouping, infinite loading, and custom rows are explicit compatibility decisions in OQ6; an unapproved activation contract is not an existing Table capability or a promise of supported composition. | Plugin composition | Compatibility matrix decision pending |
|
|
143
|
+
| FR10 | Server and first client render the same explicit initial range. Hydration does not render the full dataset then collapse it or render an empty body and repair it in an Effect. | SSR/hydration and runtime architecture | Initial-range policy unsettled |
|
|
144
|
+
| FR11 | Viewport resize, row-height/data-length change, and scroll events update the range without synchronous layout read/write loops. Stable no-op renders do not reconnect resources or reset scroll. | Runtime/performance boundary | Settled candidate |
|
|
145
|
+
| FR12 | Imperative scrolling, if admitted, targets a logical canonical key/index with defined alignment and unavailable-row behavior. It remains separate from activation, focus, navigation, and fetching. | Public responsibility boundary | Human API decision |
|
|
146
|
+
| FR13 | Completion proves bounded mounted data rows and recorded frame/render/memory budgets. Hiding offscreen mounted rows or rendering all rows before slicing is not virtualization. | Performance outcome | Settled candidate |
|
|
147
|
+
| FR14 | V1 accepts only a positive finite total row block size and an owner-approved fixed-height Table mode that defines text overflow, density/theme padding, custom cell content, interactive controls, plugin output, precedence, overflow detection, and explicit rejection/fallback. Invalid height disables virtualization and falls back to full rendering. | Enforceable geometry contract | Human API/design decision |
|
|
148
|
+
| FR15 | If striping or row dividers are supported, their parity/final-row treatment derives from absolute logical row metadata, not spacer-relative `:nth-child`/`:last-child`; middle windows match the equivalent full Table. Otherwise the options are explicitly rejected. | Core Table appearance compatibility | Parent appearance decision |
|
|
149
|
+
| FR16 | Accessibility-tree claims cover truthful metadata for mounted rows only. This contract does not claim that virtual-cursor users can discover unmounted rows; any admitted AT navigation/announcement outcome requires its own interaction and `spec:AST-009` matrix. | Evidence boundary | Settled limitation |
|
|
150
|
+
|
|
151
|
+
### Transformation and precedence order
|
|
152
|
+
|
|
153
|
+
- **ORD1 — Logical data and identity.** Approved sorting/filtering/data-order owners establish the logical `T[]`; the parent validates one canonical stable unique key and exposes one logical offset/count source. Any admitted semantic detail rows follow the parent/detail owner's sequence and position contract in FR5.
|
|
154
|
+
- **ORD2 — Admission inputs.** Validate viewport, positive finite row block size, approved fixed-height mode/content, dataset length, initial range, and internal overscan before calculating a window. Invalid identity/height falls back to full rendering.
|
|
155
|
+
- **ORD3 — Mounted set.** Derive the contiguous visible range from current scroll geometry, expand by internal overscan, and add at most one separately bounded focus island when required.
|
|
156
|
+
- **ORD4 — Semantic body.** Render inaccessible spacer segments, keyed data rows, any admitted semantic detail rows, and any focus island with FR5 full-table indices through the approved parent integration, regardless of API shape. Preserve valid table semantics and exclude spacers, not semantic detail rows, from row counts.
|
|
157
|
+
- **ORD5 — Element plugins.** Existing row/cell transforms run only for mounted logical data/focus-island rows unless their current contracts explicitly include spacer anatomy; `useTableRowIndex` consumes the same logical offset as ARIA metadata.
|
|
158
|
+
- **ORD6 — Core appearance.** Virtualized row rendering receives absolute logical parity/final-row metadata before striping/divider styles resolve, so spacers and window edges do not alter appearance.
|
|
159
|
+
- **ORD7 — State changes.** Data/order/height/viewport changes apply the approved anchor policy before exposing the next range; stale work cannot overwrite current geometry.
|
|
160
|
+
- **ORD8 — Parent protocol.** The module does not privately bypass BaseTable. Any new data/body/identity/appearance phase belongs to `component:Table` and must be approved before implementation; OQ1 does not require a phase-shaped API.
|
|
161
|
+
|
|
162
|
+
### Performance and resources
|
|
163
|
+
|
|
164
|
+
- **PR1 — Bounded DOM.** Mounted data is bounded by visible rows plus internal overscan and at most one separately bounded focus-retained row subtree, independent of total dataset length.
|
|
165
|
+
- **PR2 — Fixed-height arithmetic.** Correct range/geometry behavior uses the validated declared block size and viewport geometry and does not require per-row measurement or observer fan-out; diagnostics/fallback for contract violations remain an OQ10 outcome.
|
|
166
|
+
- **PR3 — One viewport subscription.** At most one scroll subscription and one resize/viewport observer exist per mounted instance; updates are scheduled without more than one pending frame of range work.
|
|
167
|
+
- **PR4 — Stable no-op.** Unchanged inputs and a scroll offset that remains within the current effective range do not rebuild every row or reset stable module output identity.
|
|
168
|
+
- **PR5 — Measured budgets.** Implementation records mounted row count, initial render cost, no-op rerender cost, scroll-frame work, memory trend, and supported-plugin overhead at representative 1k, 10k, and 100k logical rows.
|
|
169
|
+
- **PR6 — Cleanup.** Unmount and viewport replacement release listeners, observers, animation-frame work, retained elements, and stale geometry.
|
|
170
|
+
|
|
171
|
+
## Accessibility contract
|
|
172
|
+
|
|
173
|
+
- **AR1 — Semantic table.** Native table structure and header/cell associations remain intact; virtualization does not convert Table into an ARIA grid or generic block list.
|
|
174
|
+
- **AR2 — Full-table position exposure.** The browser accessibility tree exposes each represented semantic row's absolute full-table `aria-rowindex` and full-table `aria-rowcount` under FR5, including represented detail rows only if OQ6 admits expansion. Positions never use window-relative ordinals, and inaccessible spacers count as neither. This is a computed-semantics claim, not a promise that unmounted rows are discoverable by virtual cursor.
|
|
175
|
+
- **AR3 — Hidden spacers.** Spacer geometry has no accessible row/cell content, name, selection, current state, or focus stop.
|
|
176
|
+
- **AR4 — Focus retention.** One focused row may remain as the separately bounded focus island through range shifts; an owned detail subtree participates only under OQ6's admitted representation, counts, indices, positions, and focus contract. The island is released on blur/removal/return and never causes an unbounded range or silent focus move to the document body.
|
|
177
|
+
- **AR5 — Keyboard traversal.** Supported Table controls and row interactions remain reachable without requiring pointer scrolling or exposing thousands of tab stops.
|
|
178
|
+
- **AR6 — Announcements.** Scrolling does not generate live-region noise. Any explicit “row X of Y” claim uses the semantic row metadata rather than repeated status messages.
|
|
179
|
+
- **AR7 — Zoom, reflow, and fixed-height admission.** At supported zoom/text settings, the approved fixed-height mode cannot silently clip required content. Content or plugin output outside that mode follows the explicit rejection/full-render fallback chosen in OQ10.
|
|
180
|
+
- **AR8 — Evidence boundary.** DOM/accessibility-tree and real-browser keyboard tests prove static semantics, focus, and computed full-table metadata. Real AT is added only if an approved outcome depends on announcement, virtual-cursor advancement/discovery, or a recorded divergence under `spec:AST-009`; no such unmounted-row discovery claim exists in this draft.
|
|
181
|
+
|
|
182
|
+
## Design relationships
|
|
183
|
+
|
|
184
|
+
| Anatomy or state | Design requirement | Representation authority | Module contract |
|
|
185
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
|
186
|
+
| Mounted data row | Looks and behaves like the same logical row in a non-virtualized Table. | Existing row/cell targets and supported plugins | FR2-FR16, AR1-AR8 |
|
|
187
|
+
| Spacer segments | Preserve geometry without becoming visible/accessibility data anatomy. | This module; not a public theme target | FR3-FR5, AR1-AR3 |
|
|
188
|
+
| Vertical viewport | Preserves Table's shared scroll owner; nested owners obey nearest-effective-axis ownership and default chaining. | `component:Table/DEC-1` and [`spec:AST-025`](../../../../../../docs/specs/AST-025/spec.md) FR18/FR19; integration pending OQ2 | FR3, FR11, PR3 |
|
|
189
|
+
| Focus island | Keeps at most one focused row subtree mounted outside the ordinary window; detail participates only if OQ6 admits it. | This module plus the focused descendant/detail owner | FR2, FR5, FR7, AR2, AR4-AR5 |
|
|
190
|
+
| Logical row appearance | Preserves striping parity and final-row dividers from absolute logical metadata. | Parent Table appearance plus this module | FR15, ORD6 |
|
|
191
|
+
| Unsupported tall row | Uses the explicit rejection/full-render fallback rather than clipping under a fixed-height promise. | Parent/module boundary pending OQ10 | FR14, AR7 |
|
|
192
|
+
|
|
193
|
+
The module adds no new theming target. Data rows retain current Table targets. Spacer geometry
|
|
194
|
+
is structural implementation detail and cannot accept arbitrary theme paint or sizing.
|
|
195
|
+
|
|
196
|
+
## Parent and system relationships
|
|
197
|
+
|
|
198
|
+
- `component:Table` owns BaseTable's data/render phases, semantic body/header construction, canonical key reconciliation, row memoization, plugin ordering, core striping/divider appearance, existing body-relative `rowIndexStart`/`rowCount` behavior, and stable expansion/detail representation. This module requires an approved window/spacer/identity/appearance integration with FR5 full-table metadata. OQ9 keeps any shared correction for existing nonvirtualized paginated callers outside the additive module until a separate Table authority/compatibility decision approves it.
|
|
199
|
+
- `component:Table/DEC-1` keeps one Table-owned viewport with shared scroll behavior on the existing Scroll region and a private observed content box. [`spec:AST-025`](../../../../../../docs/specs/AST-025/spec.md) FR18 makes the nearest effective owner on the applicable axis win, permits orthogonal nesting, and requires deliberate same-axis nesting with chaining by default. FR19 lets Table integrate on its existing wrapper so column sizing, keyboard access, scroll shadows, sticky columns, and sticky headers share one viewport; it does not require a separate public ScrollableArea. An external owner is valid only when Table explicitly accepts and coordinates it. OQ2 chooses integration within these constraints, not a parallel scroll owner or overflow detector.
|
|
200
|
+
- `architecture:react-component-runtime` owns scroll/resize resource lifetime, current values, scheduling, node replacement, StrictMode replay, and cleanup.
|
|
201
|
+
- `architecture:public-component-api` and `spec:AST-002` require intent-named inputs and reject leaking an internal virtualizer or tuning knob without proven caller need.
|
|
202
|
+
- `spec:AST-009` and `spec:AST-020` own the evidence boundary. This draft claims computed accessibility-tree metadata for mounted rows, not AT discovery of unmounted rows.
|
|
203
|
+
- `useTableRowIndex` and every supported state plugin consume the same parent-owned canonical identity/logical offset rather than a caller-maintained duplicate. Expansion/tree/grouping/infinite-loading/custom-row behavior composes only if its owning current contract and OQ6 explicitly admit it; semantic detail rows then participate in FR5 counts/indices/positions without transferring representation ownership to virtualization.
|
|
204
|
+
|
|
205
|
+
## Verification map
|
|
206
|
+
|
|
207
|
+
| Contract | Verification | Representative states | Mutation or failure expectation |
|
|
208
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
209
|
+
| FR1-FR6, FR14-FR15, ORD1-ORD6 | Deterministic range/geometry/identity/appearance tests plus Table renders inspecting native DOM, keys, spacer semantics, full-table ARIA, striping, and dividers | Empty; first/middle/last window; focus island; duplicate keys; invalid height; striped/divided; admitted detail rows | Visible rows drop, geometry drifts, DOM is invalid, keys recycle, ARIA excludes represented headers/detail rows, fallback fails, or appearance shifts. |
|
|
210
|
+
| FR7-FR9, FR12, FR16, AR1-AR8 | Real-browser keyboard/accessibility-tree tests for scrolling, focus island, data replacement, zoom, controls, row index, and approved plugins | Focus in row/control; detail only if admitted; offscreen scroll; sort/filter; sticky/selection/activation/index | Focus/body/semantics/state/numbering fail, admitted detail counts/indices/positions are wrong, unmounted discovery is overclaimed, clipping is silent, or unsupported combos appear valid. |
|
|
211
|
+
| FR10, SSR/hydration | Server markup plus hydration tests at explicit initial ranges and viewport changes | Empty, smaller/larger than initial, changed client viewport | Full data renders then collapses, markup mismatches, initial rows disappear, or an Effect repairs known-wrong output. |
|
|
212
|
+
| FR11, PR2-PR4, PR6; OQ2 | Fake viewport/scroll/resize harness under StrictMode, no-op rerenders, viewport replacement, unmount, and rapid scroll, plus real-browser shared-owner and chaining integration | Stable, changed and detached viewport; repeated scroll events; orthogonal/deliberate same-axis nesting; sticky columns/headers | Resources leak/churn, stale work commits, scroll resets, synchronous layout loops occur, owners diverge, an uncoordinated external owner is accepted, or chaining fails. |
|
|
213
|
+
| FR13, PR1, PR5 | Performance suite and browser trace recording visible/overscan/focus-island DOM, commits, frame work, and memory at 1k/10k/100k logical rows | Core-only and every supported plugin/appearance combination | Mounted rows scale with total data, focus retention is unbounded, budgets regress, or claimed support adds unbounded work. |
|
|
214
|
+
| Public API and docs | Public-subpath compile/contract fixtures for the OQ1-approved phases, component, or atomic props bundle; `.doc.mjs`, generated inventory, Storybook, and fixed-height recipes | Mutable-compatible `T[]`, unique key, viewport, initial range, fixed-height mode, support matrix | Caller can omit/disagree on identity, readonly data mismatches Table, tuning leaks without admission, or docs imply unsupported behavior. |
|
|
215
|
+
| Compatibility scope; OQ9 | Representative nonvirtualized paginated `rowIndexStart`/`rowCount` fixtures alongside virtualized full-table metadata fixtures | Existing first/later pages and unknown totals; virtualized header/data and any admitted detail sequence | Additive virtualization silently changes existing callers or a shared correction proceeds without separate Table authority/compatibility approval. |
|
|
216
|
+
| Structural ownership | `pnpm check:knowledge` | Draft module and parent backlink | A missing, orphaned, misnamed, wrong-kind, or duplicate module record passes validation. |
|
|
217
|
+
|
|
218
|
+
## Decision log
|
|
219
|
+
|
|
220
|
+
### DEC-1 — V1 is fixed-height row virtualization
|
|
221
|
+
|
|
222
|
+
**Reference:** `module:Table/useTableVirtualization/DEC-1`
|
|
223
|
+
**Decider:** pending owner approval
|
|
224
|
+
|
|
225
|
+
V1 must prove bounded DOM and semantic correctness from one positive finite total row block size under an owner-approved fixed-height Table mode. That mode defines text overflow, density/theme padding, custom content/controls, plugin output, precedence, diagnostics, and full-render fallback. Variable-height measurement remains a separate contract.
|
|
226
|
+
|
|
227
|
+
Rejected: silently measuring some rows, assuming an average height, clipping arbitrary Table content without an admitted mode, and calling hidden or `content-visibility` rows virtualized while they remain mounted.
|
|
228
|
+
|
|
229
|
+
### DEC-2 — Preserve native table semantics
|
|
230
|
+
|
|
231
|
+
**Reference:** `module:Table/useTableVirtualization/DEC-2`
|
|
232
|
+
**Decider:** pending owner approval
|
|
233
|
+
|
|
234
|
+
Virtualization must use valid semantic table structure and FR5 full-table metadata: represented headers and data are counted/indexed, as are represented semantic detail rows if OQ6 admits expansion; inaccessible spacers are excluded. The parent/detail owner defines detail shape and positions. Reuse of existing body-relative `rowIndexStart`/`rowCount` is unresolved in OQ9; any shared correction affecting nonvirtualized paginated callers requires separate Table authority/compatibility approval, not automatic correction by this additive module. Structural elements remain native rather than block-positioned or role-replaced.
|
|
235
|
+
|
|
236
|
+
Rejected: incomplete full-table totals, `display:block` on `<table>/<tbody>/<tr>`, absolutely positioned generic row divs, and spacer rows announced as data.
|
|
237
|
+
|
|
238
|
+
### DEC-3 — Stable keys; no cross-row DOM recycling
|
|
239
|
+
|
|
240
|
+
**Reference:** `module:Table/useTableVirtualization/DEC-3`
|
|
241
|
+
**Decider:** pending owner approval
|
|
242
|
+
|
|
243
|
+
V1 uses one validated stable unique canonical key for both window identity and Table reconciliation. The approved integration makes that key inseparable from its data whether OQ1 selects parent-owned phases, a dedicated component, or an atomic props bundle. Duplicate/invalid keys disable virtualization and fall back to normal full rendering in every build, with one development warning. Rows outside the approved mounted set may unmount, but no keyed DOM/component instance is reused for another logical key.
|
|
244
|
+
|
|
245
|
+
Rejected: separate window/Table extractors, optional identity that falls back to window-local indices, duplicate keys that recycle multiple rows, and recycling uncontrolled input, focus, or component state across logical rows.
|
|
246
|
+
|
|
247
|
+
## Open questions
|
|
248
|
+
|
|
249
|
+
- **OQ1 — Which parent integration represents windowing, identity, metadata, and spacers?** (`human-api`) Choose parent-owned phases, a dedicated component, or one atomic Table-compatible props bundle. Whichever shape is chosen must keep mutable-compatible `T[]`, the exact validated unique `idKey`, logical offset/count, and semantic spacer ownership together so callers cannot omit or disagree with identity/metadata. No hook or return-object shape is admitted by this question.
|
|
250
|
+
- **OQ2 — Who owns the vertical viewport?** (`human-api`) Decide a Table-owned block viewport versus a caller-supplied ref explicitly accepted and coordinated by Table, subject to `component:Table/DEC-1` and [`spec:AST-025`](../../../../../../docs/specs/AST-025/spec.md) FR18/FR19. Preserve the shared scroll owner for column sizing, keyboard access, scroll shadows, sticky columns, and sticky headers; no separate public ScrollableArea is required. The nearest effective owner on the applicable axis wins; orthogonal nesting may coexist, and any same-axis nesting is deliberate in Table's contract and preserves chaining by default. An arbitrary external wrapper does not satisfy ownership without explicit Table coordination; do not create a parallel owner or overflow detector.
|
|
251
|
+
- **OQ3 — What is the initial SSR range?** (`human-api`) Decide whether an initial row count is required, has a documented default, or derives from explicit server-known geometry. Full-render-then-collapse and empty-then-repair are rejected.
|
|
252
|
+
- **OQ4 — How is the bounded focus island represented?** (`human-design`) Define at most one discontiguous retained row and, only if OQ6 admits it, its owner-defined detail subtree, spacer segmentation, release on blur/removal/return, scroll/focus behavior, and what happens when another off-window row receives focus. Detail shape remains the parent/detail owner's, not virtualization's.
|
|
253
|
+
- **OQ5 — What happens when data order changes?** (`human-api`) Decide start-offset, focused/active-key, first-visible-key, or another scroll-anchor policy for sorting, filtering, insertion, deletion, and replacement.
|
|
254
|
+
- **OQ6 — Which plugins and core appearance modes compose in v1?** (`human-api`) Confirm sticky columns, selection, sorting, filtering, context menus, and `useTableRowIndex` with one logical offset. Decide whether activation, expansion, tree, grouped rows, infinite loading, custom rows, striping, and each divider mode are supported, constrained, or rejected with dedicated evidence. Activation requires its own approved current module contract before composition can be admitted; an open draft supplies neither capability nor authority. Expansion admission requires the parent/detail owner to define detail representation, full-table row counts, absolute indices and positions for every represented semantic detail row, fixed-height geometry compatibility, and bounded focus-island behavior under OQ4. Neither silently omitting semantic detail rows nor mentioning retained detail admits expansion.
|
|
255
|
+
- **OQ7 — Is imperative `scrollToRow` public in v1?** (`human-api`) Admit it only if callers need an operation beyond setting scroll position, and define canonical key/index, alignment, unavailable-row, focus, and data-not-loaded behavior.
|
|
256
|
+
- **OQ8 — What objective budgets gate completion?** (`human-api`) Approve mounted-row, focus-island, render, frame, and memory budgets at representative data sizes so “smooth” cannot substitute for evidence.
|
|
257
|
+
- **OQ9 — How does the parent integrate full-table ARIA metadata, and what is the public compatibility scope?** (`human-api`) Define represented header indices/count, data offsets, any OQ6-admitted semantic detail counts/indices/positions, and spacer exclusion. Decide whether a virtualized-only metadata path is sufficient or a shared correction is necessary before reusing body-relative `rowIndexStart`/`rowCount`. Any shared correction affecting existing nonvirtualized paginated callers requires a separately approved Table authority/compatibility decision that defines their output, docs, and migration. This additive module does not choose or authorize that global correction.
|
|
258
|
+
- **OQ10 — What exactly constitutes fixed-height mode?** (`human-api`) Define positive-finite validation, total block-size semantics, `textOverflow`, density/theme padding precedence, allowed custom renderers/interactive content/plugins, overflow diagnostics, and the explicit rejection or full-render fallback. Native row height is a minimum, so a numeric prop alone is insufficient.
|
|
259
|
+
- **OQ11 — How are striping and dividers made logical-position-aware?** (`human-design`) If supported, replace spacer-relative `:nth-child`/`:last-child` meaning with parent-supplied absolute parity/final-data-row state while preserving ordinary non-virtualized CSS behavior and middle-window pixels.
|
|
260
|
+
- **OQ12 — Is caller-configurable overscan admitted?** (`human-api`) Keep overscan internal unless two non-derivable caller situations prove an API need under `spec:AST-002`; if admitted, define semantic outcome, bounds, invalid values, default, and decision burden rather than exposing an engine-tuning knob.
|
|
261
|
+
|
|
262
|
+
## Content boundary
|
|
263
|
+
|
|
264
|
+
This record does not duplicate consumer signatures/examples, the parent Table pipeline,
|
|
265
|
+
general virtualizer algorithms, current usage audits, implementation steps, data-fetching,
|
|
266
|
+
column virtualization, or variable-height contracts. It links to their canonical owners.
|