@charcuterie/ui 3.32.0 → 3.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +16 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js +1 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +35 -0
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js +37 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
- package/package.json +1 -1
- package/src/AdaptiveGrid/useAdaptiveColumns.ts +17 -0
- package/src/VirtualizedGrid/VirtualizedGrid.mdx +21 -0
- package/src/VirtualizedGrid/VirtualizedGrid.stories.tsx +64 -0
- package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +122 -3
- package/src/VirtualizedGrid/VirtualizedGrid.tsx +37 -1
|
@@ -148,6 +148,22 @@ export declare const useAdaptiveColumns: <ContainerElement extends HTMLElement =
|
|
|
148
148
|
* ⚠️ Never the element `contentMaxInlineSize` is applied to.
|
|
149
149
|
*/
|
|
150
150
|
containerRef: RefObject<ContainerElement | null>;
|
|
151
|
+
/**
|
|
152
|
+
* Has the container been given a box yet?
|
|
153
|
+
*
|
|
154
|
+
* False in two situations that look identical from here and are
|
|
155
|
+
* both "no measurement has happened": the frame before
|
|
156
|
+
* `ResizeObserver` delivers its first record, and any time the
|
|
157
|
+
* container sits inside a `display: none` ancestor — which is
|
|
158
|
+
* what the `hidden` attribute is, and what a tab panel, a
|
|
159
|
+
* collapsed disclosure and a closed `<details>` all use.
|
|
160
|
+
*
|
|
161
|
+
* The distinction matters because a zero inline size is not a
|
|
162
|
+
* measurement of zero. It is the absence of one, and a consumer
|
|
163
|
+
* that treats it as a number lays out for a 0px container and
|
|
164
|
+
* then reads the correction as a change.
|
|
165
|
+
*/
|
|
166
|
+
isLaidOut: boolean;
|
|
151
167
|
/** What was picked, `"auto"` included. */
|
|
152
168
|
choice: ColumnChoice;
|
|
153
169
|
setChoice: (choice: ColumnChoice) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAdaptiveColumns.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/useAdaptiveColumns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAStD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAChC,iBAgBF,CAAA;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACxC,KAAK,MAAM,KACV,iBAeD,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,8BAG9B;IACD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAC3C,KAAG,YAkBH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,gBAAgB,SAAS,WAAW,GAAG,cAAc,EACrD,yMAaC;IACD,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAG;IACF,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAA;IACnB,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,YAAY,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAChD,0CAA0C;IAC1C,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"useAdaptiveColumns.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/useAdaptiveColumns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAStD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAChC,iBAgBF,CAAA;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACxC,KAAK,MAAM,KACV,iBAeD,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,8BAG9B;IACD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAC3C,KAAG,YAkBH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,gBAAgB,SAAS,WAAW,GAAG,cAAc,EACrD,yMAaC;IACD,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAG;IACF,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAA;IACnB,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,YAAY,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAChD;;;;;;;;;;;;;;OAcG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB,0CAA0C;IAC1C,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;CA+G1C,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAdaptiveColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/useAdaptiveColumns.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,SAAS,EACT,MAAM,EACN,QAAQ,EACR,oBAAoB,GACrB,MAAM,OAAO,CAAA;AAGd,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAuE3B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACpC,GAAsB,EAAE,CAAC,CAAC;IACxB,GAAG,EAAE,GAAG,EAAE,CACR,OAAO,MAAM,KAAK,WAAW;QAC3B,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,MAAM,CAAC,WAAW;IACxB,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;QACtB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAE3C,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChD,CAAC,CAAA;IACH,CAAC;CACF,CAAC,CAAA;AAEJ;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAC3C,GAAW,EACQ,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,GAAG,EAAE;QACT,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;QACpD,CAAC;IACH,CAAC;CACF,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,UAAU,GAAG,0BAA0B,EACvC,WAAW,GAIZ,EAAgB,EAAE;IACjB,MAAM,MAAM,GAAG,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;IAE1C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACzC,OAAO,MAAM,CAAA;IACf,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAE7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;QACtC,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAEhC,EACA,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,cAAc,GAAG,wBAAwB,EACzC,gBAAgB,GAAG,0BAA0B,EAC7C,mBAAmB,GAAG,iCAAiC,EACvD,WAAW,EACX,gBAAgB,EAChB,UAAU,GAqCX,
|
|
1
|
+
{"version":3,"file":"useAdaptiveColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/useAdaptiveColumns.ts"],"names":[],"mappings":"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"}
|
|
@@ -140,6 +140,41 @@ export type VirtualizedGridProps<Item> = {
|
|
|
140
140
|
* answer: the browser's `Ctrl+F` only searches what is mounted, and
|
|
141
141
|
* so does "select all". A page that needs either owes its users a
|
|
142
142
|
* search field of its own.
|
|
143
|
+
*
|
|
144
|
+
* ### Nothing is mounted until there is a box to measure
|
|
145
|
+
*
|
|
146
|
+
* `display: none` gives an element no boxes at all, so every
|
|
147
|
+
* measurement taken through one reads zero — and zero is the
|
|
148
|
+
* absence of a measurement, not a measurement of nothing. A grid
|
|
149
|
+
* inside a `hidden` tab panel, a collapsed disclosure or a closed
|
|
150
|
+
* `<details>` therefore measures a 0px container (one column) and
|
|
151
|
+
* 0px-tall rows, and the virtualizer stores those as facts.
|
|
152
|
+
*
|
|
153
|
+
* Revealing the panel corrects all of them at once, and the
|
|
154
|
+
* virtualizer reads a correction as a **resize**. That is the one
|
|
155
|
+
* event it is entitled to move the scroll position for: an item
|
|
156
|
+
* above the viewport that grows must be compensated for, or the
|
|
157
|
+
* page slides under the reader. So it compensates — for a growth
|
|
158
|
+
* that never happened — and scrolls the shared scroll owner down
|
|
159
|
+
* by roughly the distance from the top of the region to the top of
|
|
160
|
+
* the grid.
|
|
161
|
+
*
|
|
162
|
+
* Whether the compensation lands is a race between the
|
|
163
|
+
* `ResizeObserver` delivering the real row heights and the layout
|
|
164
|
+
* effect correcting `scrollMargin`, so the symptom is intermittent
|
|
165
|
+
* and its size varies. Docket's Triage bar is where it was found:
|
|
166
|
+
* one switch moved `Main` 266px, another 190px, another 1,190px,
|
|
167
|
+
* and the tab bar went off the top of the screen. The cause is not
|
|
168
|
+
* the tab bar, and fixing it there would have left every other
|
|
169
|
+
* host of a hidden grid broken.
|
|
170
|
+
*
|
|
171
|
+
* `isLaidOut` is the gate. While the container has no box the
|
|
172
|
+
* windowed grid is not mounted, so there is nothing to measure
|
|
173
|
+
* wrongly and no virtualizer to mislead; it mounts when the box
|
|
174
|
+
* arrives and measures once, correctly. The cost is that a
|
|
175
|
+
* revealed grid re-measures from its estimate instead of resuming
|
|
176
|
+
* a cache — the same thing a routed tab does, and the reason a
|
|
177
|
+
* routed tab never had this bug.
|
|
143
178
|
*/
|
|
144
179
|
export declare const VirtualizedGrid: <Item>({ blockSizeResolver, chromeBlockSize, className, columns, gap, getItemKey, itemBlockSize, items, label, maxColumns, minColumnInlineSize, overscanRows, renderItem, storageKey, }: VirtualizedGridProps<Item>) => ReactNode;
|
|
145
180
|
//# sourceMappingURL=VirtualizedGrid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualizedGrid.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAY9E,MAAM,MAAM,oBAAoB,CAAC,IAAI,IAAI;IACvC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,KACV,MAAM,GAAG,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB,kCAAkC;IAClC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qBAAqB;IACrB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAA;IACpD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"VirtualizedGrid.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAY9E,MAAM,MAAM,oBAAoB,CAAC,IAAI,IAAI;IACvC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,KACV,MAAM,GAAG,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB,kCAAkC;IAClC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qBAAqB;IACrB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAA;IACpD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,EAAG,kLAepC,oBAAoB,CAAC,IAAI,CAAC,KAAG,SAiE/B,CAAA"}
|
|
@@ -77,6 +77,41 @@ import { WindowGrid } from "./WindowGrid.js";
|
|
|
77
77
|
* answer: the browser's `Ctrl+F` only searches what is mounted, and
|
|
78
78
|
* so does "select all". A page that needs either owes its users a
|
|
79
79
|
* search field of its own.
|
|
80
|
+
*
|
|
81
|
+
* ### Nothing is mounted until there is a box to measure
|
|
82
|
+
*
|
|
83
|
+
* `display: none` gives an element no boxes at all, so every
|
|
84
|
+
* measurement taken through one reads zero — and zero is the
|
|
85
|
+
* absence of a measurement, not a measurement of nothing. A grid
|
|
86
|
+
* inside a `hidden` tab panel, a collapsed disclosure or a closed
|
|
87
|
+
* `<details>` therefore measures a 0px container (one column) and
|
|
88
|
+
* 0px-tall rows, and the virtualizer stores those as facts.
|
|
89
|
+
*
|
|
90
|
+
* Revealing the panel corrects all of them at once, and the
|
|
91
|
+
* virtualizer reads a correction as a **resize**. That is the one
|
|
92
|
+
* event it is entitled to move the scroll position for: an item
|
|
93
|
+
* above the viewport that grows must be compensated for, or the
|
|
94
|
+
* page slides under the reader. So it compensates — for a growth
|
|
95
|
+
* that never happened — and scrolls the shared scroll owner down
|
|
96
|
+
* by roughly the distance from the top of the region to the top of
|
|
97
|
+
* the grid.
|
|
98
|
+
*
|
|
99
|
+
* Whether the compensation lands is a race between the
|
|
100
|
+
* `ResizeObserver` delivering the real row heights and the layout
|
|
101
|
+
* effect correcting `scrollMargin`, so the symptom is intermittent
|
|
102
|
+
* and its size varies. Docket's Triage bar is where it was found:
|
|
103
|
+
* one switch moved `Main` 266px, another 190px, another 1,190px,
|
|
104
|
+
* and the tab bar went off the top of the screen. The cause is not
|
|
105
|
+
* the tab bar, and fixing it there would have left every other
|
|
106
|
+
* host of a hidden grid broken.
|
|
107
|
+
*
|
|
108
|
+
* `isLaidOut` is the gate. While the container has no box the
|
|
109
|
+
* windowed grid is not mounted, so there is nothing to measure
|
|
110
|
+
* wrongly and no virtualizer to mislead; it mounts when the box
|
|
111
|
+
* arrives and measures once, correctly. The cost is that a
|
|
112
|
+
* revealed grid re-measures from its estimate instead of resuming
|
|
113
|
+
* a cache — the same thing a routed tab does, and the reason a
|
|
114
|
+
* routed tab never had this bug.
|
|
80
115
|
*/
|
|
81
116
|
export const VirtualizedGrid = ({ blockSizeResolver, chromeBlockSize, className, columns, gap = DEFAULT_GRID_GAP_PX, getItemKey, itemBlockSize, items, label, maxColumns, minColumnInlineSize, overscanRows = 4, renderItem, storageKey, }) => {
|
|
82
117
|
const layout = useAdaptiveColumns({
|
|
@@ -115,6 +150,7 @@ export const VirtualizedGrid = ({ blockSizeResolver, chromeBlockSize, className,
|
|
|
115
150
|
renderItem,
|
|
116
151
|
rowCount,
|
|
117
152
|
};
|
|
118
|
-
return (_jsx("div", { className: toClassName("flex justify-center", className), ref: layout.containerRef, children:
|
|
153
|
+
return (_jsx("div", { className: toClassName("flex justify-center", className), ref: layout.containerRef, children: !layout.isLaidOut ? null : scrollOwner?.kind ===
|
|
154
|
+
"element" ? (_jsx(ElementGrid, { ...gridProps, scrollOwner: scrollOwner })) : scrollOwner?.kind === "window" ? (_jsx(WindowGrid, { ...gridProps })) : null }));
|
|
119
155
|
};
|
|
120
156
|
//# sourceMappingURL=VirtualizedGrid.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualizedGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAK5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAkB,CAAA;AA2E7C
|
|
1
|
+
{"version":3,"file":"VirtualizedGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAK5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAkB,CAAA;AA2E7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAQ,EACrC,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,OAAO,EACP,GAAG,GAAG,mBAAmB,EACzB,UAAU,EACV,aAAa,EACb,KAAK,EACL,KAAK,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,GAAG,CAAC,EAChB,UAAU,EACV,UAAU,GACiB,EAAa,EAAE;IAC1C,MAAM,MAAM,GAAG,kBAAkB,CAAC;QAChC,iBAAiB;QACjB,eAAe;QACf,aAAa;QACb,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,cAAc,EAAE,UAAU;QAC1B,mBAAmB;QACnB,UAAU;KACX,CAAC,CAAA;IAEF,MAAM,YAAY,GAAG,OAAO,IAAI,MAAM,CAAC,OAAO,CAAA;IAE9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAA;IAEvD;;;;;OAKG;IACH,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GACjC,QAAQ,CAAqB,IAAI,CAAC,CAAA;IAEpC,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAA;QAE3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAA;IAC1C,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;IAEzB,MAAM,SAAS,GAA0B;QACvC,YAAY;QACZ,GAAG;QACH,UAAU;QACV,aAAa;QACb,KAAK;QACL,KAAK;QACL,YAAY;QACZ,UAAU;QACV,QAAQ;KACT,CAAA;IAED,OAAO,CACL,cACE,SAAS,EAAE,WAAW,CACpB,qBAAqB,EACrB,SAAS,CACV,EACD,GAAG,EAAE,MAAM,CAAC,YAAY,YAEvB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI;YAC3C,SAAS,CAAC,CAAC,CAAC,CACZ,KAAC,WAAW,OACN,SAAS,EACb,WAAW,EAAE,WAAW,GACxB,CACH,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CACnC,KAAC,UAAU,OAAK,SAAS,GAAI,CAC9B,CAAC,CAAC,CAAC,IAAI,GACJ,CACP,CAAA;AACH,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -240,6 +240,22 @@ export const useAdaptiveColumns = <
|
|
|
240
240
|
* ⚠️ Never the element `contentMaxInlineSize` is applied to.
|
|
241
241
|
*/
|
|
242
242
|
containerRef: RefObject<ContainerElement | null>
|
|
243
|
+
/**
|
|
244
|
+
* Has the container been given a box yet?
|
|
245
|
+
*
|
|
246
|
+
* False in two situations that look identical from here and are
|
|
247
|
+
* both "no measurement has happened": the frame before
|
|
248
|
+
* `ResizeObserver` delivers its first record, and any time the
|
|
249
|
+
* container sits inside a `display: none` ancestor — which is
|
|
250
|
+
* what the `hidden` attribute is, and what a tab panel, a
|
|
251
|
+
* collapsed disclosure and a closed `<details>` all use.
|
|
252
|
+
*
|
|
253
|
+
* The distinction matters because a zero inline size is not a
|
|
254
|
+
* measurement of zero. It is the absence of one, and a consumer
|
|
255
|
+
* that treats it as a number lays out for a 0px container and
|
|
256
|
+
* then reads the correction as a change.
|
|
257
|
+
*/
|
|
258
|
+
isLaidOut: boolean
|
|
243
259
|
/** What was picked, `"auto"` included. */
|
|
244
260
|
choice: ColumnChoice
|
|
245
261
|
setChoice: (choice: ColumnChoice) => void
|
|
@@ -350,6 +366,7 @@ export const useAdaptiveColumns = <
|
|
|
350
366
|
gutterInlineSize,
|
|
351
367
|
singleInlineSize,
|
|
352
368
|
}),
|
|
369
|
+
isLaidOut: availableInlineSize > 0,
|
|
353
370
|
setChoice,
|
|
354
371
|
}
|
|
355
372
|
}
|
|
@@ -59,6 +59,27 @@ wrong and every row is offset by the chrome above it.
|
|
|
59
59
|
|
|
60
60
|
<Canvas of={VirtualizedGridStories.InShellScrollRegion} />
|
|
61
61
|
|
|
62
|
+
## It waits for a box before it measures anything
|
|
63
|
+
|
|
64
|
+
`display: none` gives an element no boxes, and the `hidden` attribute is `display: none`.
|
|
65
|
+
So a grid inside a hidden tab panel, a collapsed disclosure or a closed `<details>` cannot
|
|
66
|
+
be measured at all: its container reads 0px wide and every mounted row reads 0px tall.
|
|
67
|
+
|
|
68
|
+
That is the absence of a measurement, not a measurement of zero, and the difference is not
|
|
69
|
+
academic. Revealing the panel corrects every one of those numbers at once, and a
|
|
70
|
+
virtualizer is entitled to read a correction as a **resize** — the one event it may move
|
|
71
|
+
the scroll position for. It then compensates for growth that never happened and scrolls the
|
|
72
|
+
page. Docket's Triage bar is where this was found: switching tabs moved `Main` by up to
|
|
73
|
+
1,190px and took the tab bar off the top of the screen.
|
|
74
|
+
|
|
75
|
+
So the grid mounts nothing until its container has a box. Put a grid in a tab panel and the
|
|
76
|
+
panel holds no rows while it is put away; the rows arrive with the box and are measured once,
|
|
77
|
+
correctly. The cost is that a revealed grid re-measures from its estimate rather than
|
|
78
|
+
resuming a cache, which is exactly what a routed tab already does.
|
|
79
|
+
|
|
80
|
+
<Canvas of={VirtualizedGridStories.InHiddenTabPanel} />
|
|
81
|
+
|
|
82
|
+
|
|
62
83
|
## The column rule is inherited, not reimplemented
|
|
63
84
|
|
|
64
85
|
<Canvas of={VirtualizedGridStories.AllVariants} />
|
|
@@ -10,6 +10,7 @@ import { Header } from "../Header/Header.tsx"
|
|
|
10
10
|
import { Main } from "../Main/Main.tsx"
|
|
11
11
|
import { SegmentedControl } from "../SegmentedControl/SegmentedControl.tsx"
|
|
12
12
|
import { Shell } from "../Shell/Shell.tsx"
|
|
13
|
+
import { Tabs } from "../Tabs/Tabs.tsx"
|
|
13
14
|
import { VirtualizedGrid } from "./VirtualizedGrid.tsx"
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -130,6 +131,69 @@ export const InShellScrollRegion: Story = {
|
|
|
130
131
|
),
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Two grids in a tab bar, one of them hidden — the shape that found
|
|
136
|
+
* the scroll bug.
|
|
137
|
+
*
|
|
138
|
+
* A `hidden` panel is `display: none`, so the grid inside it has no
|
|
139
|
+
* box: its container measures 0px wide (one column) and every
|
|
140
|
+
* mounted row measures 0px tall. Revealing the panel corrects all
|
|
141
|
+
* of that at once, and a virtualizer is entitled to read a
|
|
142
|
+
* correction as a resize and compensate the scroll position for it.
|
|
143
|
+
*
|
|
144
|
+
* Docket's Triage bar is where it showed: clicking a tab scrolled
|
|
145
|
+
* `Main` down 266px and put the tab bar off the top of the screen.
|
|
146
|
+
* So the claim this story exists to hold is one number — **switching
|
|
147
|
+
* tabs leaves `Main.scrollTop` where it was.**
|
|
148
|
+
*
|
|
149
|
+
* The chrome above the grid is not decoration either. The false
|
|
150
|
+
* adjustment is roughly the distance from the top of the scroll
|
|
151
|
+
* region to the top of the grid, so a story with the grid flush
|
|
152
|
+
* against the top of `Main` would have had nothing to measure and
|
|
153
|
+
* would have passed while the bug was still there.
|
|
154
|
+
*/
|
|
155
|
+
export const InHiddenTabPanel: Story = {
|
|
156
|
+
parameters: { layout: "fullscreen" },
|
|
157
|
+
render: (gridProps) => (
|
|
158
|
+
<Shell contentWidth="full">
|
|
159
|
+
<Header heading="Bays" />
|
|
160
|
+
|
|
161
|
+
<Main>
|
|
162
|
+
<p className="text-content-secondary text-sm">
|
|
163
|
+
Switching tabs must not move the page.
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
<Tabs
|
|
167
|
+
label="Bay views"
|
|
168
|
+
tabs={[
|
|
169
|
+
{
|
|
170
|
+
content: (
|
|
171
|
+
<VirtualizedGrid
|
|
172
|
+
{...gridProps}
|
|
173
|
+
label="Loaded bays"
|
|
174
|
+
/>
|
|
175
|
+
),
|
|
176
|
+
key: "loaded",
|
|
177
|
+
label: "Loaded",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
content: (
|
|
181
|
+
<VirtualizedGrid
|
|
182
|
+
{...gridProps}
|
|
183
|
+
items={MANY_ITEMS.slice(0, 48)}
|
|
184
|
+
label="Reserved bays"
|
|
185
|
+
/>
|
|
186
|
+
),
|
|
187
|
+
key: "reserved",
|
|
188
|
+
label: "Reserved",
|
|
189
|
+
},
|
|
190
|
+
]}
|
|
191
|
+
/>
|
|
192
|
+
</Main>
|
|
193
|
+
</Shell>
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
|
|
133
197
|
/**
|
|
134
198
|
* The same 2,000 items at three window heights.
|
|
135
199
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { composeStories } from "@storybook/react"
|
|
2
|
-
import { expect, waitFor } from "storybook/test"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
3
|
import { test } from "vitest"
|
|
4
4
|
|
|
5
5
|
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
@@ -31,10 +31,17 @@ import * as stories from "./VirtualizedGrid.stories.tsx"
|
|
|
31
31
|
* and "list, 40 items" on a list of 2,000.
|
|
32
32
|
* - **An empty list reserves no space.** The state a filter lands
|
|
33
33
|
* on.
|
|
34
|
+
* - **A hidden panel measures nothing, and revealing it does not
|
|
35
|
+
* scroll.** The regression that took Docket's Triage tab bar off
|
|
36
|
+
* the top of the screen.
|
|
34
37
|
*/
|
|
35
38
|
|
|
36
|
-
const {
|
|
37
|
-
|
|
39
|
+
const {
|
|
40
|
+
AllStates,
|
|
41
|
+
Default,
|
|
42
|
+
InHiddenTabPanel,
|
|
43
|
+
InShellScrollRegion,
|
|
44
|
+
} = composeStories(stories)
|
|
38
45
|
|
|
39
46
|
const getLists = (canvasElement: HTMLElement) => [
|
|
40
47
|
...canvasElement.querySelectorAll<HTMLElement>("ul"),
|
|
@@ -205,6 +212,118 @@ test("a grid inside Shell follows Main's one scroll region", async () => {
|
|
|
205
212
|
).toBe("0px")
|
|
206
213
|
})
|
|
207
214
|
|
|
215
|
+
test("a grid inside a hidden panel mounts nothing and reveals without scrolling", async () => {
|
|
216
|
+
const { canvasElement } = await mountStory(
|
|
217
|
+
InHiddenTabPanel,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const main =
|
|
221
|
+
canvasElement.querySelector<HTMLElement>("main")
|
|
222
|
+
|
|
223
|
+
if (!main) {
|
|
224
|
+
throw new Error("The tab story has no Main element.")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await waitFor(async () => {
|
|
228
|
+
await expect(
|
|
229
|
+
getRowIndexes(canvasElement).length,
|
|
230
|
+
).toBeGreaterThan(0)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const hiddenPanel = canvasElement.querySelector(
|
|
234
|
+
'[role="tabpanel"][hidden]',
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if (!hiddenPanel) {
|
|
238
|
+
throw new Error("Both tab panels are showing.")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
* The direct observable, and the one the fix is: `display: none`
|
|
243
|
+
* gives an element no box, so nothing inside it can be measured.
|
|
244
|
+
* A grid that mounted its rows anyway would record a 0px-wide
|
|
245
|
+
* container and 0px-tall rows as facts.
|
|
246
|
+
*/
|
|
247
|
+
/*
|
|
248
|
+
* THE ASSERTION THAT DISCRIMINATES, and it is the cause rather
|
|
249
|
+
* than the symptom on purpose.
|
|
250
|
+
*
|
|
251
|
+
* `display: none` gives an element no box, so nothing inside it
|
|
252
|
+
* can be measured. A grid that mounted its rows anyway records a
|
|
253
|
+
* 0px-wide container and 0px-tall rows as facts, and the
|
|
254
|
+
* virtualizer then reads the correction as a resize and
|
|
255
|
+
* compensates the scroll position for it.
|
|
256
|
+
*
|
|
257
|
+
* Whether that compensation actually lands is a RACE — between
|
|
258
|
+
* the `ResizeObserver` delivering the real row heights and the
|
|
259
|
+
* layout effect correcting `scrollMargin`. In Docket it landed
|
|
260
|
+
* and moved `Main` by 266px, 190px and 1,190px on three
|
|
261
|
+
* different switches; in this story it does not land at all. So
|
|
262
|
+
* the symptom is not what is asserted here. This is: with
|
|
263
|
+
* nothing mounted, the race has no entrants.
|
|
264
|
+
*/
|
|
265
|
+
await expect(
|
|
266
|
+
hiddenPanel.querySelectorAll("ul").length,
|
|
267
|
+
).toBe(0)
|
|
268
|
+
|
|
269
|
+
const tabs = [
|
|
270
|
+
...canvasElement.querySelectorAll<HTMLElement>(
|
|
271
|
+
'[role="tab"]',
|
|
272
|
+
),
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
const nextTab = tabs.find(
|
|
276
|
+
(tab) => tab.getAttribute("aria-selected") === "false",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if (!nextTab) {
|
|
280
|
+
throw new Error("No unselected tab to switch to.")
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await expect(main.scrollTop).toBe(0)
|
|
284
|
+
|
|
285
|
+
await userEvent.click(nextTab)
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The list inside the panel that is SHOWING. Scoping matters:
|
|
289
|
+
* without the gate the hidden panel still holds a `<ul>`, and a
|
|
290
|
+
* query that took the first one in the canvas would watch the
|
|
291
|
+
* panel that was just put away and settle before the revealed
|
|
292
|
+
* one had measured anything.
|
|
293
|
+
*/
|
|
294
|
+
const getShownList = () => {
|
|
295
|
+
const shown = canvasElement.querySelector(
|
|
296
|
+
'[role="tabpanel"]:not([hidden])',
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
const list = shown?.querySelector<HTMLElement>("ul")
|
|
300
|
+
|
|
301
|
+
if (!list) {
|
|
302
|
+
throw new Error("The shown panel has no grid.")
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return list
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Wait for the revealed grid to have MEASURED, not merely to
|
|
309
|
+
// have mounted — the adjustment, when it happens, happens on the
|
|
310
|
+
// virtualizer's resize path.
|
|
311
|
+
await waitFor(async () => {
|
|
312
|
+
await expect(
|
|
313
|
+
Number(
|
|
314
|
+
getShownList().style.paddingBlockEnd.replace(
|
|
315
|
+
"px",
|
|
316
|
+
"",
|
|
317
|
+
),
|
|
318
|
+
),
|
|
319
|
+
).toBeGreaterThan(0)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// The symptom, kept as a guard rather than as the proof. It is
|
|
323
|
+
// free, and it is the sentence a reader of this file wants.
|
|
324
|
+
await expect(main.scrollTop).toBe(0)
|
|
325
|
+
})
|
|
326
|
+
|
|
208
327
|
test("a screen reader is told the real length", async () => {
|
|
209
328
|
const { canvasElement } = await mountStory(Default)
|
|
210
329
|
|
|
@@ -157,6 +157,41 @@ export type VirtualizedGridProps<Item> = {
|
|
|
157
157
|
* answer: the browser's `Ctrl+F` only searches what is mounted, and
|
|
158
158
|
* so does "select all". A page that needs either owes its users a
|
|
159
159
|
* search field of its own.
|
|
160
|
+
*
|
|
161
|
+
* ### Nothing is mounted until there is a box to measure
|
|
162
|
+
*
|
|
163
|
+
* `display: none` gives an element no boxes at all, so every
|
|
164
|
+
* measurement taken through one reads zero — and zero is the
|
|
165
|
+
* absence of a measurement, not a measurement of nothing. A grid
|
|
166
|
+
* inside a `hidden` tab panel, a collapsed disclosure or a closed
|
|
167
|
+
* `<details>` therefore measures a 0px container (one column) and
|
|
168
|
+
* 0px-tall rows, and the virtualizer stores those as facts.
|
|
169
|
+
*
|
|
170
|
+
* Revealing the panel corrects all of them at once, and the
|
|
171
|
+
* virtualizer reads a correction as a **resize**. That is the one
|
|
172
|
+
* event it is entitled to move the scroll position for: an item
|
|
173
|
+
* above the viewport that grows must be compensated for, or the
|
|
174
|
+
* page slides under the reader. So it compensates — for a growth
|
|
175
|
+
* that never happened — and scrolls the shared scroll owner down
|
|
176
|
+
* by roughly the distance from the top of the region to the top of
|
|
177
|
+
* the grid.
|
|
178
|
+
*
|
|
179
|
+
* Whether the compensation lands is a race between the
|
|
180
|
+
* `ResizeObserver` delivering the real row heights and the layout
|
|
181
|
+
* effect correcting `scrollMargin`, so the symptom is intermittent
|
|
182
|
+
* and its size varies. Docket's Triage bar is where it was found:
|
|
183
|
+
* one switch moved `Main` 266px, another 190px, another 1,190px,
|
|
184
|
+
* and the tab bar went off the top of the screen. The cause is not
|
|
185
|
+
* the tab bar, and fixing it there would have left every other
|
|
186
|
+
* host of a hidden grid broken.
|
|
187
|
+
*
|
|
188
|
+
* `isLaidOut` is the gate. While the container has no box the
|
|
189
|
+
* windowed grid is not mounted, so there is nothing to measure
|
|
190
|
+
* wrongly and no virtualizer to mislead; it mounts when the box
|
|
191
|
+
* arrives and measures once, correctly. The cost is that a
|
|
192
|
+
* revealed grid re-measures from its estimate instead of resuming
|
|
193
|
+
* a cache — the same thing a routed tab does, and the reason a
|
|
194
|
+
* routed tab never had this bug.
|
|
160
195
|
*/
|
|
161
196
|
export const VirtualizedGrid = <Item,>({
|
|
162
197
|
blockSizeResolver,
|
|
@@ -227,7 +262,8 @@ export const VirtualizedGrid = <Item,>({
|
|
|
227
262
|
)}
|
|
228
263
|
ref={layout.containerRef}
|
|
229
264
|
>
|
|
230
|
-
{scrollOwner?.kind ===
|
|
265
|
+
{!layout.isLaidOut ? null : scrollOwner?.kind ===
|
|
266
|
+
"element" ? (
|
|
231
267
|
<ElementGrid
|
|
232
268
|
{...gridProps}
|
|
233
269
|
scrollOwner={scrollOwner}
|