@octanejs/window 0.0.1
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/LICENSE.md +21 -0
- package/README.md +89 -0
- package/UPSTREAM.md +116 -0
- package/package.json +68 -0
- package/src/components/grid/Grid.tsx +329 -0
- package/src/components/grid/types.ts +334 -0
- package/src/components/grid/useGridCallbackRef.ts +16 -0
- package/src/components/grid/useGridRef.ts +12 -0
- package/src/components/list/List.tsx +216 -0
- package/src/components/list/isDynamicRowHeight.ts +10 -0
- package/src/components/list/types.ts +214 -0
- package/src/components/list/useDynamicRowHeight.ts +170 -0
- package/src/components/list/useListCallbackRef.ts +16 -0
- package/src/components/list/useListRef.ts +12 -0
- package/src/constants.ts +3 -0
- package/src/core/createCachedBounds.ts +65 -0
- package/src/core/getEstimatedSize.ts +25 -0
- package/src/core/getOffsetForIndex.ts +75 -0
- package/src/core/getStartStopIndices.ts +68 -0
- package/src/core/types.ts +14 -0
- package/src/core/useCachedBounds.ts +29 -0
- package/src/core/useIsRtl.ts +27 -0
- package/src/core/useItemSize.ts +33 -0
- package/src/core/useVirtualizer.ts +269 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +10 -0
- package/src/hooks/useMemoizedObject.ts +14 -0
- package/src/hooks/useResizeObserver.ts +98 -0
- package/src/hooks/useStableCallback.ts +39 -0
- package/src/index.ts +20 -0
- package/src/internal.ts +37 -0
- package/src/types.ts +5 -0
- package/src/utils/adjustScrollOffsetForRtl.ts +35 -0
- package/src/utils/areArraysEqual.ts +13 -0
- package/src/utils/arePropsEqual.ts +19 -0
- package/src/utils/assert.ts +10 -0
- package/src/utils/colors/getContrastColor.ts +47 -0
- package/src/utils/colors/stringToColor.ts +13 -0
- package/src/utils/debug.ts +13 -0
- package/src/utils/getRTLOffsetType.ts +46 -0
- package/src/utils/getScrollbarSize.ts +23 -0
- package/src/utils/isRtl.ts +12 -0
- package/src/utils/parseNumericStyleValue.ts +17 -0
- package/src/utils/shallowCompare.ts +26 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Brian Vaughn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @octanejs/window
|
|
2
|
+
|
|
3
|
+
The exact [`react-window@2.3.0`](https://github.com/bvaughn/react-window) API for
|
|
4
|
+
the [Octane](https://github.com/octanejs/octane) renderer. Change the package
|
|
5
|
+
import and keep the current v2 `List`, `Grid`, sizing, callback, ref, and
|
|
6
|
+
imperative-scrolling contracts.
|
|
7
|
+
|
|
8
|
+
```diff
|
|
9
|
+
-import { List } from 'react-window';
|
|
10
|
+
+import { List } from '@octanejs/window';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Grid, List, type CellComponentProps, type RowComponentProps } from '@octanejs/window';
|
|
15
|
+
|
|
16
|
+
function Row({ index, style, ariaAttributes }: RowComponentProps) {
|
|
17
|
+
return <div {...ariaAttributes} style={style}>{'Row ' + index}</div>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Cell({ columnIndex, rowIndex, style, ariaAttributes }: CellComponentProps) {
|
|
21
|
+
return <div {...ariaAttributes} style={style}>{`${rowIndex}:${columnIndex}`}</div>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function VirtualizedData() @{
|
|
25
|
+
<section>
|
|
26
|
+
<List
|
|
27
|
+
defaultHeight={300}
|
|
28
|
+
rowComponent={Row}
|
|
29
|
+
rowCount={10_000}
|
|
30
|
+
rowHeight={32}
|
|
31
|
+
rowProps={{}}
|
|
32
|
+
style={{ height: 300 }}
|
|
33
|
+
/>
|
|
34
|
+
<Grid
|
|
35
|
+
cellComponent={Cell}
|
|
36
|
+
cellProps={{}}
|
|
37
|
+
columnCount={100}
|
|
38
|
+
columnWidth={100}
|
|
39
|
+
defaultHeight={300}
|
|
40
|
+
defaultWidth={600}
|
|
41
|
+
rowCount={10_000}
|
|
42
|
+
rowHeight={32}
|
|
43
|
+
style={{ height: 300, width: 600 }}
|
|
44
|
+
/>
|
|
45
|
+
</section>
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The package includes all eight v2 runtime exports and all eight public type
|
|
50
|
+
exports from the pinned release. Fixed, percentage, function, and dynamic row
|
|
51
|
+
sizes; overscan; RTL grids; ARIA attributes; custom root tags; imperative refs;
|
|
52
|
+
SSR; and hydration are covered by executable parity evidence.
|
|
53
|
+
|
|
54
|
+
## Version boundary
|
|
55
|
+
|
|
56
|
+
This package targets the current `react-window` v2 API. The older v1
|
|
57
|
+
`FixedSizeList`, `VariableSizeList`, `FixedSizeGrid`, and `VariableSizeGrid`
|
|
58
|
+
names are not exports of `react-window@2.3.0` and are intentionally not added
|
|
59
|
+
here. Applications still using those names need a v1-specific migration before
|
|
60
|
+
changing imports.
|
|
61
|
+
|
|
62
|
+
## Renderer differences
|
|
63
|
+
|
|
64
|
+
Consumer-visible rendering, keyed state, scrolling, measurement, and final DOM
|
|
65
|
+
match the React package. Two renderer-internal observations differ:
|
|
66
|
+
|
|
67
|
+
- Octane reserves the second raw function-component invocation argument for its
|
|
68
|
+
internal block ABI. Tests and instrumentation should assert component props,
|
|
69
|
+
not React's undocumented `undefined` second argument.
|
|
70
|
+
- After keyed reordering, sibling effect order and equal-prop rerender counts
|
|
71
|
+
can differ. Do not depend on sibling effect ordering; keyed state and DOM
|
|
72
|
+
identity are preserved.
|
|
73
|
+
|
|
74
|
+
The pinned source, npm declaration bundle, license, test inventory, allowed
|
|
75
|
+
adaptations, and executable pristine/adapted/type/differential/SSR/hydration
|
|
76
|
+
lanes are recorded in [`UPSTREAM.md`](./UPSTREAM.md) and
|
|
77
|
+
[`audit/react-parity.json`](./audit/react-parity.json).
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT — contains source derived from
|
|
82
|
+
[react-window](https://github.com/bvaughn/react-window) (MIT, © Brian Vaughn),
|
|
83
|
+
adapted for Octane.
|
|
84
|
+
|
|
85
|
+
## Status
|
|
86
|
+
|
|
87
|
+
Current scope and verification status are tracked in the generated
|
|
88
|
+
[bindings status table](../../docs/bindings-status.md), sourced from
|
|
89
|
+
[`status.json`](./status.json).
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# react-window upstream contract
|
|
2
|
+
|
|
3
|
+
## Immutable pin
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Package | `react-window` |
|
|
8
|
+
| Version | `2.3.0` |
|
|
9
|
+
| Repository | `https://github.com/bvaughn/react-window.git` |
|
|
10
|
+
| Annotated tag | `2.3.0` (`c8f17487…`) |
|
|
11
|
+
| Dereferenced tag commit | `4d9eebbb510262b3b7e95463cf49a10de53ea77d` |
|
|
12
|
+
| npm integrity | `sha512-FW6TIpaOH646k51X7yE+LSCWGkt5Pfsnc1fVyq/sCI9h0pTqmMiBXM04pzFKg3Bt7NGkeV6kqbU8d/QjmFS7Ug==` |
|
|
13
|
+
| npm shasum | `92fefee75b7de56a31204dfffc492b84136e4783` |
|
|
14
|
+
| npm tarball SHA-256 | `c62b0568794a8cf5f523fa6fd68f83261cfdc9bb7578e918ca2ae1181fc44623` |
|
|
15
|
+
| Canonical tag archive SHA-256 | `d0b66c0138c6355051a75086ce0681aa5880249c0d14f1e9759185daee16e452` |
|
|
16
|
+
| License | MIT, copyright Brian Vaughn |
|
|
17
|
+
|
|
18
|
+
The byte-exact tagged `lib/` tree, published npm declaration bundle, repository
|
|
19
|
+
package metadata, and license are vendored under `upstream/` for provenance and
|
|
20
|
+
parity evidence. They are audit inputs only and must remain excluded from the
|
|
21
|
+
published package.
|
|
22
|
+
|
|
23
|
+
Run `pnpm --dir packages/window upstream:verify` to verify all 58 vendored
|
|
24
|
+
artifacts, the exact file set, the published declaration bundle, the 14 upstream
|
|
25
|
+
test artifacts and their 117 test registrations, package metadata, and the
|
|
26
|
+
complete root export inventory.
|
|
27
|
+
|
|
28
|
+
## Public v2.3.0 surface
|
|
29
|
+
|
|
30
|
+
Runtime exports: `Grid`, `List`, `getScrollbarSize`, `useDynamicRowHeight`,
|
|
31
|
+
`useGridCallbackRef`, `useGridRef`, `useListCallbackRef`, and `useListRef`.
|
|
32
|
+
|
|
33
|
+
Type exports: `Align`, `CellComponentProps`, `DynamicRowHeight`,
|
|
34
|
+
`GridImperativeAPI`, `GridProps`, `ListImperativeAPI`, `ListProps`, and
|
|
35
|
+
`RowComponentProps`.
|
|
36
|
+
|
|
37
|
+
The v1 `FixedSizeList`, `VariableSizeList`, `FixedSizeGrid`, and
|
|
38
|
+
`VariableSizeGrid` names are not part of this pin and are intentionally outside
|
|
39
|
+
the binding contract.
|
|
40
|
+
|
|
41
|
+
## Source boundary and module disposition
|
|
42
|
+
|
|
43
|
+
The immutable boundary is every file under upstream `lib/`, plus `src/constants.ts`,
|
|
44
|
+
`vitest.setup.js`, `package.json`, and the MIT license. Production modules are
|
|
45
|
+
ported source-correspondently under `src/`; test modules are either executed
|
|
46
|
+
byte-exact in the pristine lane or generated into `tests/upstream/` using the
|
|
47
|
+
audited transformations in `audit/adapted-transformations.json`. Upstream test
|
|
48
|
+
utilities remain byte-locked inputs and are mapped to the adapted utilities by
|
|
49
|
+
the generator. No vendored file is published.
|
|
50
|
+
|
|
51
|
+
| Upstream module class | Octane disposition | Evidence |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `lib/components/**`, `lib/core/**`, `lib/hooks/**`, `lib/utils/**` production files | Source-correspondent port in `src/**`; React imports and renderer syntax are the only framework adaptations | `audit/adapted-transformations.json`, `tests/audit/adapted.test.mjs` |
|
|
54
|
+
| `lib/**/*.test.{ts,tsx}` | Unchanged pristine execution and generated adapted execution | `audit/pristine-runtime.json`, `audit/adapted-runtime.json` |
|
|
55
|
+
| `lib/utils/test/**`, `vitest.setup.js` | Unchanged pristine support; source-correspondent adapted support | `upstream/SHA256SUMS`, `tests/generate-adapted-tests.mjs` |
|
|
56
|
+
| Published declaration bundle | Unchanged pristine oracle; identical shared assertion program targets Octane source | `audit/type-contract.json`, `typetests/**` |
|
|
57
|
+
| Repository metadata and license | Vendored provenance only | `tests/audit/upstream.test.mjs` |
|
|
58
|
+
|
|
59
|
+
## Export crosswalk
|
|
60
|
+
|
|
61
|
+
| Public export | Kind | Classification | Octane evidence |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `Grid` | runtime | Exact component port | upstream Grid suites, runtime, differential, SSR, hydration |
|
|
64
|
+
| `List` | runtime | Exact component port | upstream List suites, runtime, differential, SSR, hydration |
|
|
65
|
+
| `getScrollbarSize` | runtime | Source-identical utility | upstream inventory and shared type contract |
|
|
66
|
+
| `useDynamicRowHeight` | runtime | Exact hook port with compiler-slot plumbing | upstream hook suite, runtime dynamic-height suite |
|
|
67
|
+
| `useGridCallbackRef` | runtime | Exact public React hook signature; Octane state slot internally | shared types and grid initializer runtime case |
|
|
68
|
+
| `useGridRef` | runtime | Exact public React hook signature; Octane ref slot internally | shared types and grid initializer runtime case |
|
|
69
|
+
| `useListCallbackRef` | runtime | Exact public React hook signature; Octane state slot internally | shared types and list initializer runtime case |
|
|
70
|
+
| `useListRef` | runtime | Exact public React hook signature; Octane ref slot internally | shared types and list initializer runtime case |
|
|
71
|
+
| `Align` | type | Exact published declaration | shared type contract group `utilities` |
|
|
72
|
+
| `CellComponentProps` | type | Exact published declaration | shared type contract group `component-props` |
|
|
73
|
+
| `DynamicRowHeight` | type | Exact published declaration | shared type contract group `dynamic-height` |
|
|
74
|
+
| `GridImperativeAPI` | type | Exact published declaration | shared type contract groups `ref-hooks`, `imperative-api` |
|
|
75
|
+
| `GridProps` | type | Exact published declaration | shared type contract groups `list-grid-props`, `forbidden-generated-props` |
|
|
76
|
+
| `ListImperativeAPI` | type | Exact published declaration | shared type contract groups `ref-hooks`, `imperative-api` |
|
|
77
|
+
| `ListProps` | type | Exact published declaration | shared type contract groups `list-grid-props`, `forbidden-generated-props` |
|
|
78
|
+
| `RowComponentProps` | type | Exact published declaration | shared type contract group `component-props` |
|
|
79
|
+
|
|
80
|
+
## Upstream test crosswalk
|
|
81
|
+
|
|
82
|
+
Every row runs unchanged in `react-window-pristine` and as a generator-produced,
|
|
83
|
+
audited adaptation in `react-window-adapted`; the inventories prove all 117
|
|
84
|
+
registered cases are unique and executed.
|
|
85
|
+
|
|
86
|
+
| Upstream test file | Pristine disposition | Adapted disposition |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `components/grid/Grid.test.tsx` | byte-exact | generated framework adaptation |
|
|
89
|
+
| `components/list/List.test.tsx` | byte-exact | generated framework adaptation |
|
|
90
|
+
| `components/list/useDynamicRowHeight.test.ts` | byte-exact | generated framework adaptation |
|
|
91
|
+
| `core/createCachedBounds.test.ts` | byte-exact | generated import adaptation |
|
|
92
|
+
| `core/getEstimatedSize.test.ts` | byte-exact | generated import adaptation |
|
|
93
|
+
| `core/getOffsetForIndex.test.ts` | byte-exact | generated import adaptation |
|
|
94
|
+
| `core/getStartStopIndices.test.ts` | byte-exact | generated import adaptation |
|
|
95
|
+
| `core/useCachedBounds.test.ts` | byte-exact | generated hook adaptation |
|
|
96
|
+
| `core/useVirtualizer.test.ts` | byte-exact | generated hook adaptation |
|
|
97
|
+
| `hooks/useMemoizedObject.test.ts` | byte-exact | generated hook adaptation |
|
|
98
|
+
| `hooks/useResizeObserver.test.ts` | byte-exact | generated hook adaptation |
|
|
99
|
+
| `hooks/useStableCallback.test.tsx` | byte-exact | generated hook adaptation |
|
|
100
|
+
| `utils/parseNumericStyleValue.test.ts` | byte-exact | generated import adaptation |
|
|
101
|
+
| `utils/shallowCompare.test.ts` | byte-exact | generated import adaptation |
|
|
102
|
+
|
|
103
|
+
## Port-authored evidence crosswalk
|
|
104
|
+
|
|
105
|
+
| Authored evidence | Classification | React-to-Octane pairing / citation |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `tests/runtime/{grid,list,dynamic-height}.test.ts` | Adapted consumer behavior | Supplements the exact upstream suites with Octane-native DOM, imperative API, initializer, and measurement coverage |
|
|
108
|
+
| `tests/differential/parity.test.ts` | Differential | One shared TSX fixture is compiled for React and Octane; the rig compares serialized DOM after native scrolling |
|
|
109
|
+
| `tests/ssr/ssr.test.ts` | Adapted SSR | Exercises the same public `List`/`Grid` defaults pinned by the declaration and upstream component suites |
|
|
110
|
+
| `tests/hydration.test.ts` | Adapted hydration | Adopts nodes emitted by the SSR fixture, then proves live ResizeObserver and scrolling behavior |
|
|
111
|
+
| `typetests/parity.test-d.ts` | Paired type oracle | Identical assertion groups compile against the npm declaration and Octane source; `audit/type-contract.json` forbids source transforms |
|
|
112
|
+
| `typetests/negative/missing-coordinate.test-d.ts` | Paired negative type control | Both compilers reject the same incomplete Grid coordinate |
|
|
113
|
+
| `tests/audit/upstream.test.mjs` | Provenance audit | Pins the exact file/export/test inventory in both directions |
|
|
114
|
+
| `tests/audit/adapted.test.mjs` | Adaptation audit | Regenerates and byte-compares every adapted upstream test/source mapping with mutation controls |
|
|
115
|
+
| `tests/audit/types.test.mjs` | Type-evidence audit | Accounts for every assertion group and proves skipped/deleted/unauthorized mutations fail |
|
|
116
|
+
| `tests/feasibility/renderer-boundary.test.ts` | Framework-boundary characterization | Documents the two reviewed renderer ABI/scheduling divergences recorded in the manifest |
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octanejs/window",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.22.2"
|
|
8
|
+
},
|
|
9
|
+
"octane": {
|
|
10
|
+
"hookSlots": {
|
|
11
|
+
"manual": [
|
|
12
|
+
"src"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"description": "Exact react-window 2.3.0 binding for Octane: virtualized List and Grid components, dynamic row measurement, imperative scrolling, SSR, and hydration.",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Dominic Gannaway",
|
|
19
|
+
"email": "dg@domgan.com"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/octanejs/octane.git",
|
|
27
|
+
"directory": "packages/window"
|
|
28
|
+
},
|
|
29
|
+
"main": "src/index.ts",
|
|
30
|
+
"module": "src/index.ts",
|
|
31
|
+
"types": "src/index.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"src",
|
|
34
|
+
"README.md",
|
|
35
|
+
"UPSTREAM.md",
|
|
36
|
+
"LICENSE.md"
|
|
37
|
+
],
|
|
38
|
+
"exports": {
|
|
39
|
+
".": "./src/index.ts"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@types/react": "^19.2.17"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"octane": "0.1.35"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
49
|
+
"@testing-library/react": "16.3.2",
|
|
50
|
+
"@tsrx/react": "^0.2.56",
|
|
51
|
+
"esbuild": "^0.28.1",
|
|
52
|
+
"react": "^19.2.7",
|
|
53
|
+
"react-dom": "^19.2.7",
|
|
54
|
+
"react-window": "2.3.0",
|
|
55
|
+
"vitest": "^4.1.10",
|
|
56
|
+
"@octanejs/testing-library": "0.1.32",
|
|
57
|
+
"octane": "0.1.35"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"adapted:generate": "node tests/generate-adapted-tests.mjs",
|
|
61
|
+
"test": "pnpm upstream:verify && pnpm test:audit && node ../../scripts/react-parity/harness.mjs run-required --manifest packages/window/audit/react-parity.json",
|
|
62
|
+
"upstream:verify": "node tests/audit/verify-upstream.mjs",
|
|
63
|
+
"test:audit": "node --test tests/audit/*.test.mjs",
|
|
64
|
+
"test:types:pristine": "tsc --noEmit -p typetests/tsconfig.pristine.json",
|
|
65
|
+
"test:types:adapted": "tsrx-tsc --noEmit -p typetests/tsconfig.adapted.json",
|
|
66
|
+
"typecheck": "tsrx-tsc --noEmit -p tsconfig.json"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { createElement, memo, useEffect, useImperativeHandle, useMemo, useState } from 'octane';
|
|
5
|
+
import { useIsRtl } from '../../core/useIsRtl.js';
|
|
6
|
+
import { useVirtualizer } from '../../core/useVirtualizer.js';
|
|
7
|
+
import { useMemoizedObject } from '../../hooks/useMemoizedObject.js';
|
|
8
|
+
import type { Align, TagNames } from '../../types.js';
|
|
9
|
+
import { arePropsEqual } from '../../utils/arePropsEqual.js';
|
|
10
|
+
import type { GridProps } from './types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders data with many rows and columns.
|
|
14
|
+
*
|
|
15
|
+
* ℹ️ Unlike `List` rows, `Grid` cell sizes must be known ahead of time.
|
|
16
|
+
* Either static sizes or something that can be derived (from the data in `CellProps`) without rendering.
|
|
17
|
+
*/
|
|
18
|
+
export function Grid<CellProps extends object, TagName extends TagNames = 'div'>({
|
|
19
|
+
cellComponent: CellComponentProp,
|
|
20
|
+
cellProps: cellPropsUnstable,
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
columnCount,
|
|
24
|
+
columnKey,
|
|
25
|
+
columnWidth,
|
|
26
|
+
defaultHeight = 0,
|
|
27
|
+
defaultWidth = 0,
|
|
28
|
+
dir,
|
|
29
|
+
gridRef,
|
|
30
|
+
onCellsRendered,
|
|
31
|
+
onResize,
|
|
32
|
+
overscanCount = 3,
|
|
33
|
+
rowCount,
|
|
34
|
+
rowHeight,
|
|
35
|
+
rowKey,
|
|
36
|
+
style,
|
|
37
|
+
tagName = 'div' as TagName,
|
|
38
|
+
...rest
|
|
39
|
+
}: GridProps<CellProps, TagName>) {
|
|
40
|
+
const cellProps = useMemoizedObject(cellPropsUnstable);
|
|
41
|
+
const CellComponent = useMemo(
|
|
42
|
+
() => memo(CellComponentProp as any, arePropsEqual as any) as typeof CellComponentProp,
|
|
43
|
+
[CellComponentProp],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const [element, setElement] = useState<HTMLDivElement | null>(null);
|
|
47
|
+
|
|
48
|
+
const isRtl = useIsRtl(element, dir);
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
getCellBounds: getColumnBounds,
|
|
52
|
+
getEstimatedSize: getEstimatedWidth,
|
|
53
|
+
startIndexOverscan: columnStartIndexOverscan,
|
|
54
|
+
startIndexVisible: columnStartIndexVisible,
|
|
55
|
+
scrollToIndex: scrollToColumnIndex,
|
|
56
|
+
stopIndexOverscan: columnStopIndexOverscan,
|
|
57
|
+
stopIndexVisible: columnStopIndexVisible,
|
|
58
|
+
} = useVirtualizer({
|
|
59
|
+
containerElement: element,
|
|
60
|
+
containerStyle: style,
|
|
61
|
+
defaultContainerSize: defaultWidth,
|
|
62
|
+
direction: 'horizontal',
|
|
63
|
+
isRtl,
|
|
64
|
+
itemCount: columnCount,
|
|
65
|
+
itemProps: cellProps,
|
|
66
|
+
itemSize: columnWidth,
|
|
67
|
+
onResize,
|
|
68
|
+
overscanCount,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
getCellBounds: getRowBounds,
|
|
73
|
+
getEstimatedSize: getEstimatedHeight,
|
|
74
|
+
startIndexOverscan: rowStartIndexOverscan,
|
|
75
|
+
startIndexVisible: rowStartIndexVisible,
|
|
76
|
+
scrollToIndex: scrollToRowIndex,
|
|
77
|
+
stopIndexOverscan: rowStopIndexOverscan,
|
|
78
|
+
stopIndexVisible: rowStopIndexVisible,
|
|
79
|
+
} = useVirtualizer({
|
|
80
|
+
containerElement: element,
|
|
81
|
+
containerStyle: style,
|
|
82
|
+
defaultContainerSize: defaultHeight,
|
|
83
|
+
direction: 'vertical',
|
|
84
|
+
itemCount: rowCount,
|
|
85
|
+
itemProps: cellProps,
|
|
86
|
+
itemSize: rowHeight,
|
|
87
|
+
onResize,
|
|
88
|
+
overscanCount,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
useImperativeHandle(
|
|
92
|
+
gridRef,
|
|
93
|
+
() => ({
|
|
94
|
+
get element() {
|
|
95
|
+
return element;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
scrollToCell({
|
|
99
|
+
behavior = 'auto',
|
|
100
|
+
columnAlign = 'auto',
|
|
101
|
+
columnIndex,
|
|
102
|
+
rowAlign = 'auto',
|
|
103
|
+
rowIndex,
|
|
104
|
+
}: {
|
|
105
|
+
behavior?: ScrollBehavior;
|
|
106
|
+
columnAlign?: Align;
|
|
107
|
+
columnIndex: number;
|
|
108
|
+
rowAlign?: Align;
|
|
109
|
+
rowIndex: number;
|
|
110
|
+
}) {
|
|
111
|
+
const left = scrollToColumnIndex({
|
|
112
|
+
align: columnAlign,
|
|
113
|
+
containerScrollOffset: element?.scrollLeft ?? 0,
|
|
114
|
+
index: columnIndex,
|
|
115
|
+
});
|
|
116
|
+
const top = scrollToRowIndex({
|
|
117
|
+
align: rowAlign,
|
|
118
|
+
containerScrollOffset: element?.scrollTop ?? 0,
|
|
119
|
+
index: rowIndex,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (typeof element?.scrollTo === 'function') {
|
|
123
|
+
element.scrollTo({
|
|
124
|
+
behavior,
|
|
125
|
+
left,
|
|
126
|
+
top,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
scrollToColumn({
|
|
132
|
+
align = 'auto',
|
|
133
|
+
behavior = 'auto',
|
|
134
|
+
index,
|
|
135
|
+
}: {
|
|
136
|
+
align?: Align;
|
|
137
|
+
behavior?: ScrollBehavior;
|
|
138
|
+
index: number;
|
|
139
|
+
}) {
|
|
140
|
+
const left = scrollToColumnIndex({
|
|
141
|
+
align,
|
|
142
|
+
containerScrollOffset: element?.scrollLeft ?? 0,
|
|
143
|
+
index,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (typeof element?.scrollTo === 'function') {
|
|
147
|
+
element.scrollTo({
|
|
148
|
+
behavior,
|
|
149
|
+
left,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
scrollToRow({
|
|
155
|
+
align = 'auto',
|
|
156
|
+
behavior = 'auto',
|
|
157
|
+
index,
|
|
158
|
+
}: {
|
|
159
|
+
align?: Align;
|
|
160
|
+
behavior?: ScrollBehavior;
|
|
161
|
+
index: number;
|
|
162
|
+
}) {
|
|
163
|
+
const top = scrollToRowIndex({
|
|
164
|
+
align,
|
|
165
|
+
containerScrollOffset: element?.scrollTop ?? 0,
|
|
166
|
+
index,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (typeof element?.scrollTo === 'function') {
|
|
170
|
+
element.scrollTo({
|
|
171
|
+
behavior,
|
|
172
|
+
top,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
[element, scrollToColumnIndex, scrollToRowIndex],
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (
|
|
182
|
+
columnStartIndexOverscan >= 0 &&
|
|
183
|
+
columnStopIndexOverscan >= 0 &&
|
|
184
|
+
rowStartIndexOverscan >= 0 &&
|
|
185
|
+
rowStopIndexOverscan >= 0 &&
|
|
186
|
+
onCellsRendered
|
|
187
|
+
) {
|
|
188
|
+
onCellsRendered(
|
|
189
|
+
{
|
|
190
|
+
columnStartIndex: columnStartIndexVisible,
|
|
191
|
+
columnStopIndex: columnStopIndexVisible,
|
|
192
|
+
rowStartIndex: rowStartIndexVisible,
|
|
193
|
+
rowStopIndex: rowStopIndexVisible,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
columnStartIndex: columnStartIndexOverscan,
|
|
197
|
+
columnStopIndex: columnStopIndexOverscan,
|
|
198
|
+
rowStartIndex: rowStartIndexOverscan,
|
|
199
|
+
rowStopIndex: rowStopIndexOverscan,
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}, [
|
|
204
|
+
onCellsRendered,
|
|
205
|
+
columnStartIndexOverscan,
|
|
206
|
+
columnStartIndexVisible,
|
|
207
|
+
columnStopIndexOverscan,
|
|
208
|
+
columnStopIndexVisible,
|
|
209
|
+
rowStartIndexOverscan,
|
|
210
|
+
rowStartIndexVisible,
|
|
211
|
+
rowStopIndexOverscan,
|
|
212
|
+
rowStopIndexVisible,
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
const cells = useMemo(() => {
|
|
216
|
+
const children: unknown[] = [];
|
|
217
|
+
if (columnCount > 0 && rowCount > 0) {
|
|
218
|
+
for (let rowIndex = rowStartIndexOverscan; rowIndex <= rowStopIndexOverscan; rowIndex++) {
|
|
219
|
+
const rowBounds = getRowBounds(rowIndex);
|
|
220
|
+
|
|
221
|
+
const columns: unknown[] = [];
|
|
222
|
+
|
|
223
|
+
for (
|
|
224
|
+
let columnIndex = columnStartIndexOverscan;
|
|
225
|
+
columnIndex <= columnStopIndexOverscan;
|
|
226
|
+
columnIndex++
|
|
227
|
+
) {
|
|
228
|
+
const columnBounds = getColumnBounds(columnIndex);
|
|
229
|
+
|
|
230
|
+
columns.push(
|
|
231
|
+
<CellComponent
|
|
232
|
+
{...(cellProps as CellProps)}
|
|
233
|
+
ariaAttributes={{
|
|
234
|
+
'aria-colindex': columnIndex + 1,
|
|
235
|
+
role: 'gridcell',
|
|
236
|
+
}}
|
|
237
|
+
columnIndex={columnIndex}
|
|
238
|
+
key={
|
|
239
|
+
columnKey
|
|
240
|
+
? columnKey({
|
|
241
|
+
columnIndex,
|
|
242
|
+
data: cellProps,
|
|
243
|
+
rowIndex,
|
|
244
|
+
})
|
|
245
|
+
: columnIndex
|
|
246
|
+
}
|
|
247
|
+
rowIndex={rowIndex}
|
|
248
|
+
style={{
|
|
249
|
+
position: 'absolute',
|
|
250
|
+
left: isRtl ? undefined : 0,
|
|
251
|
+
right: isRtl ? 0 : undefined,
|
|
252
|
+
transform: `translate(${isRtl ? -columnBounds.scrollOffset : columnBounds.scrollOffset}px, ${rowBounds.scrollOffset}px)`,
|
|
253
|
+
height: rowBounds.size,
|
|
254
|
+
width: columnBounds.size,
|
|
255
|
+
}}
|
|
256
|
+
/>,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
children.push(
|
|
261
|
+
<div
|
|
262
|
+
key={
|
|
263
|
+
rowKey
|
|
264
|
+
? rowKey({
|
|
265
|
+
data: cellProps,
|
|
266
|
+
rowIndex,
|
|
267
|
+
})
|
|
268
|
+
: rowIndex
|
|
269
|
+
}
|
|
270
|
+
role="row"
|
|
271
|
+
aria-rowindex={rowIndex + 1}
|
|
272
|
+
>
|
|
273
|
+
{columns}
|
|
274
|
+
</div>,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return children;
|
|
279
|
+
}, [
|
|
280
|
+
CellComponent,
|
|
281
|
+
cellProps,
|
|
282
|
+
columnCount,
|
|
283
|
+
columnKey,
|
|
284
|
+
columnStartIndexOverscan,
|
|
285
|
+
columnStopIndexOverscan,
|
|
286
|
+
getColumnBounds,
|
|
287
|
+
getRowBounds,
|
|
288
|
+
isRtl,
|
|
289
|
+
rowCount,
|
|
290
|
+
rowKey,
|
|
291
|
+
rowStartIndexOverscan,
|
|
292
|
+
rowStopIndexOverscan,
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const sizingElement = (
|
|
296
|
+
<div
|
|
297
|
+
aria-hidden
|
|
298
|
+
style={{
|
|
299
|
+
height: getEstimatedHeight(),
|
|
300
|
+
width: getEstimatedWidth(),
|
|
301
|
+
zIndex: -1,
|
|
302
|
+
}}
|
|
303
|
+
></div>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return createElement(
|
|
307
|
+
tagName,
|
|
308
|
+
{
|
|
309
|
+
'aria-colcount': columnCount,
|
|
310
|
+
'aria-rowcount': rowCount,
|
|
311
|
+
role: 'grid',
|
|
312
|
+
...rest,
|
|
313
|
+
className,
|
|
314
|
+
dir,
|
|
315
|
+
ref: setElement,
|
|
316
|
+
style: {
|
|
317
|
+
position: 'relative',
|
|
318
|
+
maxHeight: '100%',
|
|
319
|
+
maxWidth: '100%',
|
|
320
|
+
flexGrow: 1,
|
|
321
|
+
overflow: 'auto',
|
|
322
|
+
...style,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
cells,
|
|
326
|
+
children,
|
|
327
|
+
sizingElement,
|
|
328
|
+
);
|
|
329
|
+
}
|