@charcuterie/ui 4.21.0 → 4.22.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/LICENSE +21 -0
- package/README.md +6 -6
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts +4 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts.map +1 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.js +3 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.js.map +1 -1
- package/dist/AdaptiveGrid/chooseColumns.d.ts +10 -2
- package/dist/AdaptiveGrid/chooseColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/chooseColumns.js +10 -3
- package/dist/AdaptiveGrid/chooseColumns.js.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +6 -2
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js +4 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -1
- package/dist/Overlay/useAnchoredOverlay.d.ts +2 -2
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +4 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js +3 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
- package/package.json +10 -8
- package/src/AdaptiveGrid/AdaptiveGrid.mdx +10 -0
- package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +17 -0
- package/src/AdaptiveGrid/AdaptiveGrid.test.tsx +36 -2
- package/src/AdaptiveGrid/AdaptiveGrid.tsx +6 -0
- package/src/AdaptiveGrid/chooseColumns.ts +29 -1
- package/src/AdaptiveGrid/useAdaptiveColumns.ts +13 -1
- package/src/CopyButton/CopyButton.test.tsx +25 -12
- package/src/VirtualizedGrid/VirtualizedGrid.tsx +6 -0
- package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +16 -3
- package/src/mdxReferences.test.ts +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Ghadyani
|
|
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
CHANGED
|
@@ -210,7 +210,7 @@ M5 spent an hour on the second and third of these, so M5b and M6 do not have to:
|
|
|
210
210
|
// the PROJECT ROOT — `ui` declares its siblings as `workspace:*`, and that
|
|
211
211
|
// descriptor cannot resolve outside charcuterie's own workspace. Bare keys, not
|
|
212
212
|
// `@charcuterie/ui/@charcuterie/tokens`: the scoped form yields a different
|
|
213
|
-
// locator string from the app's own dependency on the same directory, and
|
|
213
|
+
// locator string from the app's own dependency on the same directory, and pnpm
|
|
214
214
|
// rejects the pair as conflicting.
|
|
215
215
|
"resolutions": {
|
|
216
216
|
"@charcuterie/logic": "portal:../charcuterie/packages/logic",
|
|
@@ -233,7 +233,7 @@ TypeError: Cannot read properties of null (reading 'useRef')
|
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
which mentions neither symlinks nor React identity. Keep the line after publish: it costs
|
|
236
|
-
nothing with one copy, and it is the difference between a working `
|
|
236
|
+
nothing with one copy, and it is the difference between a working `pnpm link` session and
|
|
237
237
|
an hour of confusion.
|
|
238
238
|
|
|
239
239
|
## How a component is put together
|
|
@@ -256,9 +256,9 @@ and fails on any candidate Tailwind cannot generate.
|
|
|
256
256
|
## Tests
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
pnpm vitest run --project ui # Node: class maps, status switches, clamping, boundaries
|
|
260
|
+
pnpm vitest run --project ui-dom # chromium: Component.test.tsx — behaviour, keyboard, ARIA
|
|
261
|
+
pnpm vitest run --project storybook # chromium: every story renders, axe at test: "error"
|
|
262
262
|
```
|
|
263
263
|
|
|
264
264
|
**Stories are demos and carry no assertions**
|
|
@@ -319,7 +319,7 @@ rename breaks only the rendered page).
|
|
|
319
319
|
from effects, so before those run a `RovingFocus` has no active value and a
|
|
320
320
|
`SinglePicker` / `VisibilityGroup` has no selected key — only a pending one. Two of M4's
|
|
321
321
|
four bugs were exactly that, and the isolated story runner saw neither —
|
|
322
|
-
`
|
|
322
|
+
`pnpm smoke:storybook` did.
|
|
323
323
|
7. **If it declares `@container`, every `StoryCell` holding it needs `align="stretch"`.**
|
|
324
324
|
`container-type: inline-size` forbids the element from being sized by its own
|
|
325
325
|
contents, so a default (shrink-to-fit) cell collapses it to min-content and every
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { GridLayout } from "./chooseColumns.ts";
|
|
2
3
|
import type { BlockSizeResolver } from "./useAdaptiveColumns.ts";
|
|
3
4
|
export type AdaptiveGridProps = {
|
|
4
5
|
/**
|
|
@@ -35,6 +36,8 @@ export type AdaptiveGridProps = {
|
|
|
35
36
|
* would be worse than one that is an item out.
|
|
36
37
|
*/
|
|
37
38
|
itemCount?: number;
|
|
39
|
+
/** Fill the container with readable columns instead of spending height first. */
|
|
40
|
+
layout?: GridLayout;
|
|
38
41
|
/** The most columns the automatic answer will take. */
|
|
39
42
|
maxColumns?: number;
|
|
40
43
|
/** The narrowest a column may be, in CSS px. */
|
|
@@ -90,5 +93,5 @@ export type AdaptiveGridProps = {
|
|
|
90
93
|
* need every count to survive the scan; an inline style needs
|
|
91
94
|
* none.
|
|
92
95
|
*/
|
|
93
|
-
export declare const AdaptiveGrid: ({ blockSizeResolver, children, chromeBlockSize, className, columns, itemBlockSize, itemCount, maxColumns, minColumnInlineSize, storageKey, }: AdaptiveGridProps) => ReactNode;
|
|
96
|
+
export declare const AdaptiveGrid: ({ blockSizeResolver, children, chromeBlockSize, className, columns, itemBlockSize, itemCount, layout: gridLayout, maxColumns, minColumnInlineSize, storageKey, }: AdaptiveGridProps) => ReactNode;
|
|
94
97
|
//# sourceMappingURL=AdaptiveGrid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdaptiveGrid.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/AdaptiveGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"AdaptiveGrid.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/AdaptiveGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAGhE,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,QAAQ,EAAE,SAAS,CAAA;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,YAAY,GAAI,kKAY1B,iBAAiB,KAAG,SAyCtB,CAAA"}
|
|
@@ -48,12 +48,13 @@ import { useAdaptiveColumns } from "./useAdaptiveColumns.js";
|
|
|
48
48
|
* need every count to survive the scan; an inline style needs
|
|
49
49
|
* none.
|
|
50
50
|
*/
|
|
51
|
-
export const AdaptiveGrid = ({ blockSizeResolver, children, chromeBlockSize, className, columns, itemBlockSize, itemCount, maxColumns, minColumnInlineSize, storageKey, }) => {
|
|
51
|
+
export const AdaptiveGrid = ({ blockSizeResolver, children, chromeBlockSize, className, columns, itemBlockSize, itemCount, layout: gridLayout = "height-first", maxColumns, minColumnInlineSize, storageKey, }) => {
|
|
52
52
|
const layout = useAdaptiveColumns({
|
|
53
53
|
blockSizeResolver,
|
|
54
54
|
chromeBlockSize,
|
|
55
55
|
itemBlockSize,
|
|
56
56
|
itemCount: itemCount ?? Children.count(children),
|
|
57
|
+
layout: gridLayout,
|
|
57
58
|
maxAutoColumns: maxColumns,
|
|
58
59
|
minColumnInlineSize,
|
|
59
60
|
storageKey,
|
|
@@ -64,6 +65,7 @@ export const AdaptiveGrid = ({ blockSizeResolver, children, chromeBlockSize, cla
|
|
|
64
65
|
// a one-column cap is the same bug as no cap at all, upside down.
|
|
65
66
|
const contentMaxInlineSize = getContentMaxInlineSize({
|
|
66
67
|
columns: drawnColumns,
|
|
68
|
+
layout: gridLayout,
|
|
67
69
|
});
|
|
68
70
|
return (_jsx("div", { className: toClassName("flex justify-center", className), ref: layout.containerRef, children: _jsx("div", { className: "grid w-full gap-4 [&>*]:min-w-0", style: {
|
|
69
71
|
gridTemplateColumns: `repeat(${drawnColumns}, minmax(0, 1fr))`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdaptiveGrid.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/AdaptiveGrid.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"AdaptiveGrid.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/AdaptiveGrid.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAkD5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,SAAS,EACT,OAAO,EACP,aAAa,EACb,SAAS,EACT,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,UAAU,EACV,mBAAmB,EACnB,UAAU,GACQ,EAAa,EAAE;IACjC,MAAM,MAAM,GAAG,kBAAkB,CAAC;QAChC,iBAAiB;QACjB,eAAe;QACf,aAAa;QACb,SAAS,EAAE,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;QAChD,MAAM,EAAE,UAAU;QAClB,cAAc,EAAE,UAAU;QAC1B,mBAAmB;QACnB,UAAU;KACX,CAAC,CAAA;IAEF,MAAM,YAAY,GAAG,OAAO,IAAI,MAAM,CAAC,OAAO,CAAA;IAE9C,iEAAiE;IACjE,kEAAkE;IAClE,kEAAkE;IAClE,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;QACnD,OAAO,EAAE,YAAY;QACrB,MAAM,EAAE,UAAU;KACnB,CAAC,CAAA;IAEF,OAAO,CACL,cACE,SAAS,EAAE,WAAW,CACpB,qBAAqB,EACrB,SAAS,CACV,EACD,GAAG,EAAE,MAAM,CAAC,YAAY,YAExB,cACE,SAAS,EAAC,iCAAiC,EAC3C,KAAK,EAAE;gBACL,mBAAmB,EAAE,UAAU,YAAY,mBAAmB;gBAC9D,aAAa,EAAE,oBAAoB;aACpC,YAEA,QAAQ,GACL,GACF,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -98,11 +98,13 @@ export declare const DEFAULT_MIN_COLUMN_INLINE_SIZE_PX: number;
|
|
|
98
98
|
* `"auto"`, or the number of columns a person explicitly asked for.
|
|
99
99
|
*/
|
|
100
100
|
export type ColumnChoice = "auto" | number;
|
|
101
|
+
/** Fill is an explicit width-first layout for media galleries and fleets. */
|
|
102
|
+
export type GridLayout = "height-first" | "fill";
|
|
101
103
|
/**
|
|
102
104
|
* The column count the automatic rule wants, for one measured space
|
|
103
105
|
* and one set of items.
|
|
104
106
|
*/
|
|
105
|
-
export declare const chooseColumns: ({ availableBlockSize, availableInlineSize, chromeBlockSize, itemBlockSize, itemCount, maxColumns, minColumnInlineSize, }: {
|
|
107
|
+
export declare const chooseColumns: ({ availableBlockSize, availableInlineSize, chromeBlockSize, itemBlockSize, itemCount, gap, layout, maxColumns, minColumnInlineSize, }: {
|
|
106
108
|
/**
|
|
107
109
|
* The block size the layout has to spend, in CSS px — the
|
|
108
110
|
* viewport's, not the grid's.
|
|
@@ -153,6 +155,10 @@ export declare const chooseColumns: ({ availableBlockSize, availableInlineSize,
|
|
|
153
155
|
* finished loading would be worse than one that is an item out.
|
|
154
156
|
*/
|
|
155
157
|
itemCount: number;
|
|
158
|
+
/** Height-first by default; fill spends the available container width. */
|
|
159
|
+
layout?: GridLayout;
|
|
160
|
+
/** Space between tracks, in CSS px, used by fill. */
|
|
161
|
+
gap?: number;
|
|
156
162
|
/** @see DEFAULT_MAX_AUTO_COLUMNS */
|
|
157
163
|
maxColumns?: number;
|
|
158
164
|
/** @see DEFAULT_MIN_COLUMN_INLINE_SIZE_PX */
|
|
@@ -183,12 +189,14 @@ export declare const chooseColumns: ({ availableBlockSize, availableInlineSize,
|
|
|
183
189
|
* dense tables and an app of prose disagree about a measure and
|
|
184
190
|
* neither is wrong.
|
|
185
191
|
*/
|
|
186
|
-
export declare const getContentMaxInlineSize: ({ columnInlineSize, columns, gutterInlineSize, singleInlineSize, }: {
|
|
192
|
+
export declare const getContentMaxInlineSize: ({ columnInlineSize, columns, gutterInlineSize, layout, singleInlineSize, }: {
|
|
187
193
|
/** What one column is worth, once there are several. */
|
|
188
194
|
columnInlineSize?: string;
|
|
189
195
|
columns: number;
|
|
190
196
|
/** Slack added on top of a multi-column cap, for the gaps. */
|
|
191
197
|
gutterInlineSize?: string;
|
|
198
|
+
/** Fill uses all available width once there are multiple columns. */
|
|
199
|
+
layout?: GridLayout;
|
|
192
200
|
/** The cap when there is only one column. */
|
|
193
201
|
singleInlineSize?: string;
|
|
194
202
|
}) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chooseColumns.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/chooseColumns.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,iCAAiC,QACH,CAAA;AAE3C;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,
|
|
1
|
+
{"version":3,"file":"chooseColumns.d.ts","sourceRoot":"","sources":["../../src/AdaptiveGrid/chooseColumns.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,iCAAiC,QACH,CAAA;AAE3C;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,MAAM,CAAA;AAEhD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,uIAU3B;IACD;;;;;;;;OAQG;IACH,kBAAkB,EAAE,MAAM,CAAA;IAC1B;;;;;;;;;;;;OAYG;IACH,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;;;;;;OAUG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,KAAG,MA0CH,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,GAAI,4EAMrC;IACD,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qEAAqE;IACrE,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,KAAG,MAK+F,CAAA;AAEnG;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,iBAE9B;IACD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAG,YAAY,EAMf,CAAA"}
|
|
@@ -99,13 +99,18 @@ export const DEFAULT_MIN_COLUMN_INLINE_SIZE_PX = Number.parseFloat(containerQuer
|
|
|
99
99
|
* The column count the automatic rule wants, for one measured space
|
|
100
100
|
* and one set of items.
|
|
101
101
|
*/
|
|
102
|
-
export const chooseColumns = ({ availableBlockSize, availableInlineSize, chromeBlockSize = 0, itemBlockSize, itemCount, maxColumns = DEFAULT_MAX_AUTO_COLUMNS, minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX, }) => {
|
|
102
|
+
export const chooseColumns = ({ availableBlockSize, availableInlineSize, chromeBlockSize = 0, itemBlockSize, itemCount, gap = 16, layout = "height-first", maxColumns = DEFAULT_MAX_AUTO_COLUMNS, minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX, }) => {
|
|
103
103
|
// Inline size is a CAP, never a reason. This is the "don't go
|
|
104
104
|
// 9-wide just because you're on an ultrawide" half of the rule.
|
|
105
105
|
const inlineSizeCap = Math.max(1, Math.min(maxColumns, Math.floor(availableInlineSize / minColumnInlineSize)));
|
|
106
106
|
if (itemCount <= 1) {
|
|
107
107
|
return 1;
|
|
108
108
|
}
|
|
109
|
+
if (layout === "fill") {
|
|
110
|
+
const columnsThatFit = Math.max(1, Math.floor((availableInlineSize + gap) /
|
|
111
|
+
(minColumnInlineSize + gap)));
|
|
112
|
+
return Math.max(1, Math.min(maxColumns, itemCount, columnsThatFit));
|
|
113
|
+
}
|
|
109
114
|
// Block size is the reason. How many items stack in the space,
|
|
110
115
|
// then how many stacks that many items need.
|
|
111
116
|
const rowsThatFit = Math.max(1, Math.floor((availableBlockSize - chromeBlockSize) /
|
|
@@ -138,9 +143,11 @@ export const chooseColumns = ({ availableBlockSize, availableInlineSize, chromeB
|
|
|
138
143
|
* dense tables and an app of prose disagree about a measure and
|
|
139
144
|
* neither is wrong.
|
|
140
145
|
*/
|
|
141
|
-
export const getContentMaxInlineSize = ({ columnInlineSize = contentInlineSize.column, columns, gutterInlineSize = contentInlineSize.gutter, singleInlineSize = contentInlineSize.single, }) => columns <= 1
|
|
146
|
+
export const getContentMaxInlineSize = ({ columnInlineSize = contentInlineSize.column, columns, gutterInlineSize = contentInlineSize.gutter, layout = "height-first", singleInlineSize = contentInlineSize.single, }) => columns <= 1
|
|
142
147
|
? singleInlineSize
|
|
143
|
-
:
|
|
148
|
+
: layout === "fill"
|
|
149
|
+
? "none"
|
|
150
|
+
: `${Number.parseFloat(columnInlineSize) * columns + Number.parseFloat(gutterInlineSize)}rem`;
|
|
144
151
|
/**
|
|
145
152
|
* The choices a manual picker offers: `"auto"`, then every count up
|
|
146
153
|
* to `maxColumns`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chooseColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/chooseColumns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"chooseColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/chooseColumns.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;AAU3C;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,GAAG,CAAC,EACnB,aAAa,EACb,SAAS,EACT,GAAG,GAAG,EAAE,EACR,MAAM,GAAG,cAAc,EACvB,UAAU,GAAG,wBAAwB,EACrC,mBAAmB,GAAG,iCAAiC,GA4DxD,EAAU,EAAE;IACX,8DAA8D;IAC9D,gEAAgE;IAChE,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAC5B,CAAC,EACD,IAAI,CAAC,GAAG,CACN,UAAU,EACV,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,mBAAmB,CAAC,CACtD,CACF,CAAA;IAED,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAC7B,CAAC,EACD,IAAI,CAAC,KAAK,CACR,CAAC,mBAAmB,GAAG,GAAG,CAAC;YACzB,CAAC,mBAAmB,GAAG,GAAG,CAAC,CAC9B,CACF,CAAA;QACD,OAAO,IAAI,CAAC,GAAG,CACb,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,CAAC,CAChD,CAAA;IACH,CAAC;IAED,+DAA+D;IAC/D,6CAA6C;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAC1B,CAAC,EACD,IAAI,CAAC,KAAK,CACR,CAAC,kBAAkB,GAAG,eAAe,CAAC;QACpC,aAAa,CAChB,CACF,CAAA;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,CAAA;IAExD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAAA;AAC5D,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,EACtC,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,EAC3C,OAAO,EACP,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,EAC3C,MAAM,GAAG,cAAc,EACvB,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,GAW5C,EAAU,EAAE,CACX,OAAO,IAAI,CAAC;IACV,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,MAAM,KAAK,MAAM;QACjB,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,KAAK,CAAA;AAEnG;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,UAAU,GAAG,0BAA0B,GAGxC,EAAkB,EAAE,CAAC;IACpB,MAAM;IACN,GAAG,KAAK,CAAC,IAAI,CACX,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,EACnC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CACxB;CACF,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from "react";
|
|
2
|
-
import type { ColumnChoice } from "./chooseColumns.ts";
|
|
2
|
+
import type { ColumnChoice, GridLayout } from "./chooseColumns.ts";
|
|
3
3
|
/**
|
|
4
4
|
* The height-first column rule, wired to a real element.
|
|
5
5
|
*
|
|
@@ -96,7 +96,7 @@ export declare const readStoredChoice: ({ maxColumns, persistence, }: {
|
|
|
96
96
|
maxColumns?: number;
|
|
97
97
|
persistence: ColumnPersistence | undefined;
|
|
98
98
|
}) => ColumnChoice;
|
|
99
|
-
export declare const useAdaptiveColumns: <ContainerElement extends HTMLElement = HTMLDivElement>({ blockSizeResolver, chromeBlockSize, columnInlineSize, gutterInlineSize, itemBlockSize, itemCount, maxAutoColumns, maxManualColumns, minColumnInlineSize, persistence, singleInlineSize, storageKey, }: {
|
|
99
|
+
export declare const useAdaptiveColumns: <ContainerElement extends HTMLElement = HTMLDivElement>({ blockSizeResolver, chromeBlockSize, columnInlineSize, gutterInlineSize, gap, itemBlockSize, itemCount, layout, maxAutoColumns, maxManualColumns, minColumnInlineSize, persistence, singleInlineSize, storageKey, }: {
|
|
100
100
|
/** @see BlockSizeResolver. Defaults to the viewport. */
|
|
101
101
|
blockSizeResolver?: BlockSizeResolver;
|
|
102
102
|
/** Everything down the page that is not this grid, in CSS px. */
|
|
@@ -105,10 +105,14 @@ export declare const useAdaptiveColumns: <ContainerElement extends HTMLElement =
|
|
|
105
105
|
columnInlineSize?: string;
|
|
106
106
|
/** Override `contentInlineSize.gutter`. */
|
|
107
107
|
gutterInlineSize?: string;
|
|
108
|
+
/** Space between tracks for the fill layout. */
|
|
109
|
+
gap?: number;
|
|
108
110
|
/** One item's block size in CSS px. Measure it; err high. */
|
|
109
111
|
itemBlockSize: number;
|
|
110
112
|
/** How many items the grid is being asked to fit. */
|
|
111
113
|
itemCount: number;
|
|
114
|
+
/** Opt in to width-first media layout. */
|
|
115
|
+
layout?: GridLayout;
|
|
112
116
|
/** The most columns the automatic answer will take. */
|
|
113
117
|
maxAutoColumns?: number;
|
|
114
118
|
/** The most a person may ask for. One above the auto cap. */
|
|
@@ -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,
|
|
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,EACV,YAAY,EACZ,UAAU,EACX,MAAM,oBAAoB,CAAA;AAS3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,sNAeC;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,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,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;CAkH1C,CAAA"}
|
|
@@ -70,7 +70,7 @@ export const readStoredChoice = ({ maxColumns = DEFAULT_MAX_MANUAL_COLUMNS, pers
|
|
|
70
70
|
}
|
|
71
71
|
return parsed;
|
|
72
72
|
};
|
|
73
|
-
export const useAdaptiveColumns = ({ blockSizeResolver, chromeBlockSize, columnInlineSize, gutterInlineSize, itemBlockSize, itemCount, maxAutoColumns = DEFAULT_MAX_AUTO_COLUMNS, maxManualColumns = DEFAULT_MAX_MANUAL_COLUMNS, minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX, persistence, singleInlineSize, storageKey, }) => {
|
|
73
|
+
export const useAdaptiveColumns = ({ blockSizeResolver, chromeBlockSize, columnInlineSize, gutterInlineSize, gap, itemBlockSize, itemCount, layout = "height-first", maxAutoColumns = DEFAULT_MAX_AUTO_COLUMNS, maxManualColumns = DEFAULT_MAX_MANUAL_COLUMNS, minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX, persistence, singleInlineSize, storageKey, }) => {
|
|
74
74
|
const containerRef = useRef(null);
|
|
75
75
|
// Built once, so the `resize` subscription and the storage
|
|
76
76
|
// handle survive re-renders and StrictMode's double mount — the
|
|
@@ -132,6 +132,8 @@ export const useAdaptiveColumns = ({ blockSizeResolver, chromeBlockSize, columnI
|
|
|
132
132
|
chromeBlockSize,
|
|
133
133
|
itemBlockSize,
|
|
134
134
|
itemCount,
|
|
135
|
+
gap,
|
|
136
|
+
layout,
|
|
135
137
|
maxColumns: maxAutoColumns,
|
|
136
138
|
minColumnInlineSize,
|
|
137
139
|
});
|
|
@@ -145,6 +147,7 @@ export const useAdaptiveColumns = ({ blockSizeResolver, chromeBlockSize, columnI
|
|
|
145
147
|
columnInlineSize,
|
|
146
148
|
columns,
|
|
147
149
|
gutterInlineSize,
|
|
150
|
+
layout,
|
|
148
151
|
singleInlineSize,
|
|
149
152
|
}),
|
|
150
153
|
isLaidOut: availableInlineSize > 0,
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"useAdaptiveColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/useAdaptiveColumns.ts"],"names":[],"mappings":"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"}
|
|
@@ -132,10 +132,10 @@ export declare const useAnchoredOverlay: ({ anchorRef, isEscapeDismissable, isHe
|
|
|
132
132
|
clonedTrigger: ReactElement<Record<string, unknown>, string | import("react").JSXElementConstructor<any>> | null;
|
|
133
133
|
context: {
|
|
134
134
|
placement: Placement;
|
|
135
|
-
strategy: import("@floating-ui/
|
|
135
|
+
strategy: import("@floating-ui/react").Strategy;
|
|
136
136
|
x: number;
|
|
137
137
|
y: number;
|
|
138
|
-
middlewareData: import("@floating-ui/
|
|
138
|
+
middlewareData: import("@floating-ui/react").MiddlewareData;
|
|
139
139
|
isPositioned: boolean;
|
|
140
140
|
update: () => void;
|
|
141
141
|
floatingStyles: React.CSSProperties;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { GridLayout } from "../AdaptiveGrid/chooseColumns.ts";
|
|
2
3
|
import type { BlockSizeResolver } from "../AdaptiveGrid/useAdaptiveColumns.ts";
|
|
3
4
|
export type VirtualizedGridProps<Item> = {
|
|
4
5
|
/**
|
|
@@ -46,6 +47,8 @@ export type VirtualizedGridProps<Item> = {
|
|
|
46
47
|
* is one whose length a reader cannot count for itself.
|
|
47
48
|
*/
|
|
48
49
|
label?: string;
|
|
50
|
+
/** Opt in to width-first media layout. */
|
|
51
|
+
layout?: GridLayout;
|
|
49
52
|
/** The most columns the automatic answer will take. */
|
|
50
53
|
maxColumns?: number;
|
|
51
54
|
/** The narrowest a column may be, in CSS px. */
|
|
@@ -176,5 +179,5 @@ export type VirtualizedGridProps<Item> = {
|
|
|
176
179
|
* a cache — the same thing a routed tab does, and the reason a
|
|
177
180
|
* routed tab never had this bug.
|
|
178
181
|
*/
|
|
179
|
-
export declare const VirtualizedGrid: <Item>({ blockSizeResolver, chromeBlockSize, className, columns, gap, getItemKey, itemBlockSize, items, label, maxColumns, minColumnInlineSize, overscanRows, renderItem, storageKey, }: VirtualizedGridProps<Item>) => ReactNode;
|
|
182
|
+
export declare const VirtualizedGrid: <Item>({ blockSizeResolver, chromeBlockSize, className, columns, gap, getItemKey, itemBlockSize, items, label, layout: gridLayout, maxColumns, minColumnInlineSize, overscanRows, renderItem, storageKey, }: VirtualizedGridProps<Item>) => ReactNode;
|
|
180
183
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,EAAG,
|
|
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,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAClE,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,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,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,sMAgBpC,oBAAoB,CAAC,IAAI,CAAC,KAAG,SAmE/B,CAAA"}
|
|
@@ -113,12 +113,14 @@ import { WindowGrid } from "./WindowGrid.js";
|
|
|
113
113
|
* a cache — the same thing a routed tab does, and the reason a
|
|
114
114
|
* routed tab never had this bug.
|
|
115
115
|
*/
|
|
116
|
-
export const VirtualizedGrid = ({ blockSizeResolver, chromeBlockSize, className, columns, gap = DEFAULT_GRID_GAP_PX, getItemKey, itemBlockSize, items, label, maxColumns, minColumnInlineSize, overscanRows = 4, renderItem, storageKey, }) => {
|
|
116
|
+
export const VirtualizedGrid = ({ blockSizeResolver, chromeBlockSize, className, columns, gap = DEFAULT_GRID_GAP_PX, getItemKey, itemBlockSize, items, label, layout: gridLayout, maxColumns, minColumnInlineSize, overscanRows = 4, renderItem, storageKey, }) => {
|
|
117
117
|
const layout = useAdaptiveColumns({
|
|
118
118
|
blockSizeResolver,
|
|
119
119
|
chromeBlockSize,
|
|
120
120
|
itemBlockSize,
|
|
121
121
|
itemCount: items.length,
|
|
122
|
+
gap,
|
|
123
|
+
layout: gridLayout,
|
|
122
124
|
maxAutoColumns: maxColumns,
|
|
123
125
|
minColumnInlineSize,
|
|
124
126
|
storageKey,
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"VirtualizedGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIjD,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;AA6E7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,MAAM,EAAE,UAAU,EAClB,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,GAAG;QACH,MAAM,EAAE,UAAU;QAClB,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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcuterie/ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.22.0",
|
|
4
4
|
"description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,12 +56,6 @@
|
|
|
56
56
|
"dist",
|
|
57
57
|
"src"
|
|
58
58
|
],
|
|
59
|
-
"scripts": {
|
|
60
|
-
"build": "tsc -p tsconfig.build.json && node scripts/copyStyles.ts",
|
|
61
|
-
"prepack": "yarn build",
|
|
62
|
-
"test": "vitest",
|
|
63
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
-
},
|
|
65
59
|
"dependencies": {
|
|
66
60
|
"@charcuterie/logic": "2.10.0",
|
|
67
61
|
"@charcuterie/tokens": "1.10.2",
|
|
@@ -161,11 +155,19 @@
|
|
|
161
155
|
"storybook": "^10.3.6",
|
|
162
156
|
"tailwindcss": "^4.3.0",
|
|
163
157
|
"typescript": "^6.0.3",
|
|
164
|
-
"vitest": "^4.1.8"
|
|
158
|
+
"vitest": "^4.1.8",
|
|
159
|
+
"axe-core": "4.12.1",
|
|
160
|
+
"@vitest/browser": "^4.1.8",
|
|
161
|
+
"@storybook/addon-docs": "^10.3.6"
|
|
165
162
|
},
|
|
166
163
|
"repository": {
|
|
167
164
|
"type": "git",
|
|
168
165
|
"url": "git+https://github.com/Sawtaytoes/charcuterie.git",
|
|
169
166
|
"directory": "packages/ui"
|
|
167
|
+
},
|
|
168
|
+
"scripts": {
|
|
169
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copyStyles.ts",
|
|
170
|
+
"test": "vitest",
|
|
171
|
+
"typecheck": "tsc --noEmit"
|
|
170
172
|
}
|
|
171
173
|
}
|
|
@@ -16,6 +16,16 @@ across an ultrawide and nothing below the fold.
|
|
|
16
16
|
|
|
17
17
|
<Canvas of={AdaptiveGridStories.Playground} />
|
|
18
18
|
|
|
19
|
+
## Fill a media workspace
|
|
20
|
+
|
|
21
|
+
Opt in with `layout="fill"` when a gallery or printer fleet should spend the
|
|
22
|
+
available width even if its cards would fit in a taller stack. Set `maxColumns`
|
|
23
|
+
and `minColumnInlineSize` for the content. The shared 16px gap counts toward the
|
|
24
|
+
column floor. Multiple columns use the whole container; a lone column retains
|
|
25
|
+
its reading measure. The default remains height-first.
|
|
26
|
+
|
|
27
|
+
<Canvas of={AdaptiveGridStories.Fill} />
|
|
28
|
+
|
|
19
29
|
## Props
|
|
20
30
|
|
|
21
31
|
<Controls />
|
|
@@ -240,3 +240,20 @@ export const Interactive: Story = {
|
|
|
240
240
|
)
|
|
241
241
|
},
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
/** A small fleet and a gallery opt in to spending width first. */
|
|
245
|
+
export const Fill: Story = {
|
|
246
|
+
args: {
|
|
247
|
+
layout: "fill",
|
|
248
|
+
minColumnInlineSize: 300,
|
|
249
|
+
maxColumns: 6,
|
|
250
|
+
blockSizeResolver: fixedBlockSize(1920),
|
|
251
|
+
},
|
|
252
|
+
render: (gridProps) => (
|
|
253
|
+
<AdaptiveGrid {...gridProps}>
|
|
254
|
+
{NINE_ITEMS.slice(0, 6).map((label) => (
|
|
255
|
+
<Item key={label} label={label} />
|
|
256
|
+
))}
|
|
257
|
+
</AdaptiveGrid>
|
|
258
|
+
),
|
|
259
|
+
}
|
|
@@ -36,8 +36,13 @@ import * as stories from "./AdaptiveGrid.stories.tsx"
|
|
|
36
36
|
* all.
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
const {
|
|
40
|
-
|
|
39
|
+
const {
|
|
40
|
+
AllStates,
|
|
41
|
+
AllVariants,
|
|
42
|
+
Fill,
|
|
43
|
+
Playground,
|
|
44
|
+
Responsive,
|
|
45
|
+
} = composeStories(stories)
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* The grids this component drew.
|
|
@@ -241,3 +246,32 @@ test("the inline size is read from the container, not the window", async () => {
|
|
|
241
246
|
).toEqual([1, 2, 3])
|
|
242
247
|
})
|
|
243
248
|
})
|
|
249
|
+
|
|
250
|
+
test("fill uses the available container width and keeps its column floor", async () => {
|
|
251
|
+
const { canvasElement } = await mountAtInlineSize(
|
|
252
|
+
Fill,
|
|
253
|
+
1900,
|
|
254
|
+
)
|
|
255
|
+
await waitFor(async () => {
|
|
256
|
+
const grid = getGrid(canvasElement)
|
|
257
|
+
await expect(getTrackCount(grid)).toBe(6)
|
|
258
|
+
await expect(grid.style.maxInlineSize).toBe("none")
|
|
259
|
+
await expect(grid.getBoundingClientRect().width).toBe(
|
|
260
|
+
1900,
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
canvasElement.style.inlineSize = "700px"
|
|
264
|
+
await waitFor(
|
|
265
|
+
async () =>
|
|
266
|
+
await expect(
|
|
267
|
+
getTrackCount(getGrid(canvasElement)),
|
|
268
|
+
).toBe(2),
|
|
269
|
+
)
|
|
270
|
+
canvasElement.style.inlineSize = "384px"
|
|
271
|
+
await waitFor(
|
|
272
|
+
async () =>
|
|
273
|
+
await expect(
|
|
274
|
+
getTrackCount(getGrid(canvasElement)),
|
|
275
|
+
).toBe(1),
|
|
276
|
+
)
|
|
277
|
+
})
|
|
@@ -2,6 +2,7 @@ import type { ReactNode } from "react"
|
|
|
2
2
|
import { Children } from "react"
|
|
3
3
|
|
|
4
4
|
import { toClassName } from "../toClassName.ts"
|
|
5
|
+
import type { GridLayout } from "./chooseColumns.ts"
|
|
5
6
|
import { getContentMaxInlineSize } from "./chooseColumns.ts"
|
|
6
7
|
import type { BlockSizeResolver } from "./useAdaptiveColumns.ts"
|
|
7
8
|
import { useAdaptiveColumns } from "./useAdaptiveColumns.ts"
|
|
@@ -41,6 +42,8 @@ export type AdaptiveGridProps = {
|
|
|
41
42
|
* would be worse than one that is an item out.
|
|
42
43
|
*/
|
|
43
44
|
itemCount?: number
|
|
45
|
+
/** Fill the container with readable columns instead of spending height first. */
|
|
46
|
+
layout?: GridLayout
|
|
44
47
|
/** The most columns the automatic answer will take. */
|
|
45
48
|
maxColumns?: number
|
|
46
49
|
/** The narrowest a column may be, in CSS px. */
|
|
@@ -105,6 +108,7 @@ export const AdaptiveGrid = ({
|
|
|
105
108
|
columns,
|
|
106
109
|
itemBlockSize,
|
|
107
110
|
itemCount,
|
|
111
|
+
layout: gridLayout = "height-first",
|
|
108
112
|
maxColumns,
|
|
109
113
|
minColumnInlineSize,
|
|
110
114
|
storageKey,
|
|
@@ -114,6 +118,7 @@ export const AdaptiveGrid = ({
|
|
|
114
118
|
chromeBlockSize,
|
|
115
119
|
itemBlockSize,
|
|
116
120
|
itemCount: itemCount ?? Children.count(children),
|
|
121
|
+
layout: gridLayout,
|
|
117
122
|
maxAutoColumns: maxColumns,
|
|
118
123
|
minColumnInlineSize,
|
|
119
124
|
storageKey,
|
|
@@ -126,6 +131,7 @@ export const AdaptiveGrid = ({
|
|
|
126
131
|
// a one-column cap is the same bug as no cap at all, upside down.
|
|
127
132
|
const contentMaxInlineSize = getContentMaxInlineSize({
|
|
128
133
|
columns: drawnColumns,
|
|
134
|
+
layout: gridLayout,
|
|
129
135
|
})
|
|
130
136
|
|
|
131
137
|
return (
|
|
@@ -109,6 +109,9 @@ export const DEFAULT_MIN_COLUMN_INLINE_SIZE_PX =
|
|
|
109
109
|
*/
|
|
110
110
|
export type ColumnChoice = "auto" | number
|
|
111
111
|
|
|
112
|
+
/** Fill is an explicit width-first layout for media galleries and fleets. */
|
|
113
|
+
export type GridLayout = "height-first" | "fill"
|
|
114
|
+
|
|
112
115
|
/**
|
|
113
116
|
* The column count the automatic rule wants, for one measured space
|
|
114
117
|
* and one set of items.
|
|
@@ -119,6 +122,8 @@ export const chooseColumns = ({
|
|
|
119
122
|
chromeBlockSize = 0,
|
|
120
123
|
itemBlockSize,
|
|
121
124
|
itemCount,
|
|
125
|
+
gap = 16,
|
|
126
|
+
layout = "height-first",
|
|
122
127
|
maxColumns = DEFAULT_MAX_AUTO_COLUMNS,
|
|
123
128
|
minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX,
|
|
124
129
|
}: {
|
|
@@ -172,6 +177,10 @@ export const chooseColumns = ({
|
|
|
172
177
|
* finished loading would be worse than one that is an item out.
|
|
173
178
|
*/
|
|
174
179
|
itemCount: number
|
|
180
|
+
/** Height-first by default; fill spends the available container width. */
|
|
181
|
+
layout?: GridLayout
|
|
182
|
+
/** Space between tracks, in CSS px, used by fill. */
|
|
183
|
+
gap?: number
|
|
175
184
|
/** @see DEFAULT_MAX_AUTO_COLUMNS */
|
|
176
185
|
maxColumns?: number
|
|
177
186
|
/** @see DEFAULT_MIN_COLUMN_INLINE_SIZE_PX */
|
|
@@ -191,6 +200,20 @@ export const chooseColumns = ({
|
|
|
191
200
|
return 1
|
|
192
201
|
}
|
|
193
202
|
|
|
203
|
+
if (layout === "fill") {
|
|
204
|
+
const columnsThatFit = Math.max(
|
|
205
|
+
1,
|
|
206
|
+
Math.floor(
|
|
207
|
+
(availableInlineSize + gap) /
|
|
208
|
+
(minColumnInlineSize + gap),
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
return Math.max(
|
|
212
|
+
1,
|
|
213
|
+
Math.min(maxColumns, itemCount, columnsThatFit),
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
194
217
|
// Block size is the reason. How many items stack in the space,
|
|
195
218
|
// then how many stacks that many items need.
|
|
196
219
|
const rowsThatFit = Math.max(
|
|
@@ -235,6 +258,7 @@ export const getContentMaxInlineSize = ({
|
|
|
235
258
|
columnInlineSize = contentInlineSize.column,
|
|
236
259
|
columns,
|
|
237
260
|
gutterInlineSize = contentInlineSize.gutter,
|
|
261
|
+
layout = "height-first",
|
|
238
262
|
singleInlineSize = contentInlineSize.single,
|
|
239
263
|
}: {
|
|
240
264
|
/** What one column is worth, once there are several. */
|
|
@@ -242,12 +266,16 @@ export const getContentMaxInlineSize = ({
|
|
|
242
266
|
columns: number
|
|
243
267
|
/** Slack added on top of a multi-column cap, for the gaps. */
|
|
244
268
|
gutterInlineSize?: string
|
|
269
|
+
/** Fill uses all available width once there are multiple columns. */
|
|
270
|
+
layout?: GridLayout
|
|
245
271
|
/** The cap when there is only one column. */
|
|
246
272
|
singleInlineSize?: string
|
|
247
273
|
}): string =>
|
|
248
274
|
columns <= 1
|
|
249
275
|
? singleInlineSize
|
|
250
|
-
:
|
|
276
|
+
: layout === "fill"
|
|
277
|
+
? "none"
|
|
278
|
+
: `${Number.parseFloat(columnInlineSize) * columns + Number.parseFloat(gutterInlineSize)}rem`
|
|
251
279
|
|
|
252
280
|
/**
|
|
253
281
|
* The choices a manual picker offers: `"auto"`, then every count up
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
useSyncExternalStore,
|
|
8
8
|
} from "react"
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ColumnChoice,
|
|
12
|
+
GridLayout,
|
|
13
|
+
} from "./chooseColumns.ts"
|
|
11
14
|
import {
|
|
12
15
|
chooseColumns,
|
|
13
16
|
DEFAULT_MAX_AUTO_COLUMNS,
|
|
@@ -180,8 +183,10 @@ export const useAdaptiveColumns = <
|
|
|
180
183
|
chromeBlockSize,
|
|
181
184
|
columnInlineSize,
|
|
182
185
|
gutterInlineSize,
|
|
186
|
+
gap,
|
|
183
187
|
itemBlockSize,
|
|
184
188
|
itemCount,
|
|
189
|
+
layout = "height-first",
|
|
185
190
|
maxAutoColumns = DEFAULT_MAX_AUTO_COLUMNS,
|
|
186
191
|
maxManualColumns = DEFAULT_MAX_MANUAL_COLUMNS,
|
|
187
192
|
minColumnInlineSize = DEFAULT_MIN_COLUMN_INLINE_SIZE_PX,
|
|
@@ -197,10 +202,14 @@ export const useAdaptiveColumns = <
|
|
|
197
202
|
columnInlineSize?: string
|
|
198
203
|
/** Override `contentInlineSize.gutter`. */
|
|
199
204
|
gutterInlineSize?: string
|
|
205
|
+
/** Space between tracks for the fill layout. */
|
|
206
|
+
gap?: number
|
|
200
207
|
/** One item's block size in CSS px. Measure it; err high. */
|
|
201
208
|
itemBlockSize: number
|
|
202
209
|
/** How many items the grid is being asked to fit. */
|
|
203
210
|
itemCount: number
|
|
211
|
+
/** Opt in to width-first media layout. */
|
|
212
|
+
layout?: GridLayout
|
|
204
213
|
/** The most columns the automatic answer will take. */
|
|
205
214
|
maxAutoColumns?: number
|
|
206
215
|
/** The most a person may ask for. One above the auto cap. */
|
|
@@ -349,6 +358,8 @@ export const useAdaptiveColumns = <
|
|
|
349
358
|
chromeBlockSize,
|
|
350
359
|
itemBlockSize,
|
|
351
360
|
itemCount,
|
|
361
|
+
gap,
|
|
362
|
+
layout,
|
|
352
363
|
maxColumns: maxAutoColumns,
|
|
353
364
|
minColumnInlineSize,
|
|
354
365
|
})
|
|
@@ -364,6 +375,7 @@ export const useAdaptiveColumns = <
|
|
|
364
375
|
columnInlineSize,
|
|
365
376
|
columns,
|
|
366
377
|
gutterInlineSize,
|
|
378
|
+
layout,
|
|
367
379
|
singleInlineSize,
|
|
368
380
|
}),
|
|
369
381
|
isLaidOut: availableInlineSize > 0,
|
|
@@ -46,8 +46,12 @@ test("copies the value and confirms in place", async () => {
|
|
|
46
46
|
await expect(copy).toHaveBeenCalledWith("335590")
|
|
47
47
|
// The press and its proof are the same object — no toast, no
|
|
48
48
|
// second place to look.
|
|
49
|
+
//
|
|
50
|
+
// `findBy`: `copy` is async, so the label changes a render after
|
|
51
|
+
// the click resolves. A synchronous read lost that race in CI's
|
|
52
|
+
// ultrawide window (master run 37298376242).
|
|
49
53
|
await expect(
|
|
50
|
-
canvas.
|
|
54
|
+
await canvas.findByRole("button", { name: "Copied" }),
|
|
51
55
|
).toBeVisible()
|
|
52
56
|
})
|
|
53
57
|
|
|
@@ -73,7 +77,9 @@ test("says so when the clipboard refuses", async () => {
|
|
|
73
77
|
)
|
|
74
78
|
|
|
75
79
|
await expect(
|
|
76
|
-
canvas.
|
|
80
|
+
await canvas.findByRole("button", {
|
|
81
|
+
name: "Copy failed",
|
|
82
|
+
}),
|
|
77
83
|
).toBeVisible()
|
|
78
84
|
await expect(onCopy).toHaveBeenCalledWith(false, "335590")
|
|
79
85
|
})
|
|
@@ -103,26 +109,30 @@ test("tells the caller what happened, both ways", async () => {
|
|
|
103
109
|
test("announces the outcome in a status region", async () => {
|
|
104
110
|
const { canvas } = await mountStory(Confirmed)
|
|
105
111
|
|
|
106
|
-
await
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
await waitFor(async () => {
|
|
113
|
+
await expect(
|
|
114
|
+
canvas.getByRole("status"),
|
|
115
|
+
).toHaveTextContent("Copied to the clipboard.")
|
|
116
|
+
})
|
|
109
117
|
})
|
|
110
118
|
|
|
111
119
|
test("the failure says what to do instead", async () => {
|
|
112
120
|
const { canvas } = await mountStory(Refused)
|
|
113
121
|
|
|
114
|
-
await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
await waitFor(async () => {
|
|
123
|
+
await expect(
|
|
124
|
+
canvas.getByRole("status"),
|
|
125
|
+
).toHaveTextContent(
|
|
126
|
+
"The clipboard refused. Select the value and press Control C.",
|
|
127
|
+
)
|
|
128
|
+
})
|
|
119
129
|
})
|
|
120
130
|
|
|
121
131
|
/** The confirmation goes back on its own. */
|
|
122
132
|
test("returns to the resting label", async () => {
|
|
123
133
|
const { canvas } = await mountStory(
|
|
124
134
|
composeStory(
|
|
125
|
-
{ args: { confirmDuration:
|
|
135
|
+
{ args: { confirmDuration: 500, copy: () => true } },
|
|
126
136
|
meta,
|
|
127
137
|
),
|
|
128
138
|
)
|
|
@@ -130,8 +140,11 @@ test("returns to the resting label", async () => {
|
|
|
130
140
|
await userEvent.click(
|
|
131
141
|
canvas.getByRole("button", { name: "Copy" }),
|
|
132
142
|
)
|
|
143
|
+
// 500ms, not 20: on a loaded runner a 20ms confirmation can come
|
|
144
|
+
// and go before the test looks, which fails the read below for the
|
|
145
|
+
// opposite reason to the race above.
|
|
133
146
|
await expect(
|
|
134
|
-
canvas.
|
|
147
|
+
await canvas.findByRole("button", { name: "Copied" }),
|
|
135
148
|
).toBeVisible()
|
|
136
149
|
|
|
137
150
|
await waitFor(async () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
2
|
import { useLayoutEffect, useState } from "react"
|
|
3
3
|
|
|
4
|
+
import type { GridLayout } from "../AdaptiveGrid/chooseColumns.ts"
|
|
4
5
|
import type { BlockSizeResolver } from "../AdaptiveGrid/useAdaptiveColumns.ts"
|
|
5
6
|
import { useAdaptiveColumns } from "../AdaptiveGrid/useAdaptiveColumns.ts"
|
|
6
7
|
import { toClassName } from "../toClassName.ts"
|
|
@@ -62,6 +63,8 @@ export type VirtualizedGridProps<Item> = {
|
|
|
62
63
|
* is one whose length a reader cannot count for itself.
|
|
63
64
|
*/
|
|
64
65
|
label?: string
|
|
66
|
+
/** Opt in to width-first media layout. */
|
|
67
|
+
layout?: GridLayout
|
|
65
68
|
/** The most columns the automatic answer will take. */
|
|
66
69
|
maxColumns?: number
|
|
67
70
|
/** The narrowest a column may be, in CSS px. */
|
|
@@ -203,6 +206,7 @@ export const VirtualizedGrid = <Item,>({
|
|
|
203
206
|
itemBlockSize,
|
|
204
207
|
items,
|
|
205
208
|
label,
|
|
209
|
+
layout: gridLayout,
|
|
206
210
|
maxColumns,
|
|
207
211
|
minColumnInlineSize,
|
|
208
212
|
overscanRows = 4,
|
|
@@ -214,6 +218,8 @@ export const VirtualizedGrid = <Item,>({
|
|
|
214
218
|
chromeBlockSize,
|
|
215
219
|
itemBlockSize,
|
|
216
220
|
itemCount: items.length,
|
|
221
|
+
gap,
|
|
222
|
+
layout: gridLayout,
|
|
217
223
|
maxAutoColumns: maxColumns,
|
|
218
224
|
minColumnInlineSize,
|
|
219
225
|
storageKey,
|
|
@@ -349,11 +349,16 @@ test("inline markup in link text does not drop the document to raw source", asyn
|
|
|
349
349
|
// No raw source anywhere in the document.
|
|
350
350
|
const article = canvas.getByRole("article")
|
|
351
351
|
|
|
352
|
-
|
|
352
|
+
// The heading can paint before lower link decorations finish parsing.
|
|
353
|
+
// Retry the complete contract rather than treating that partial paint
|
|
354
|
+
// as the final document; all three source-marker assertions still apply.
|
|
355
|
+
await waitFor(async () => {
|
|
356
|
+
await expect(article.textContent).not.toContain("](")
|
|
353
357
|
|
|
354
|
-
|
|
358
|
+
await expect(article.textContent).not.toContain("**")
|
|
355
359
|
|
|
356
|
-
|
|
360
|
+
await expect(article.textContent).not.toContain("~~")
|
|
361
|
+
})
|
|
357
362
|
})
|
|
358
363
|
|
|
359
364
|
/**
|
|
@@ -420,6 +425,14 @@ test("a code span is smaller than the prose it sits in", async () => {
|
|
|
420
425
|
|
|
421
426
|
const article = canvas.getByRole("article")
|
|
422
427
|
|
|
428
|
+
// CodeMirror can finish its syntax decorations after the story mounts.
|
|
429
|
+
// Wait for the measured span itself rather than capturing a stale null.
|
|
430
|
+
await waitFor(() => {
|
|
431
|
+
expect(
|
|
432
|
+
article.querySelector(".cm-md-code"),
|
|
433
|
+
).not.toBeNull()
|
|
434
|
+
})
|
|
435
|
+
|
|
423
436
|
const code = article.querySelector(".cm-md-code")
|
|
424
437
|
|
|
425
438
|
await expect(code).not.toBeNull()
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and does not fail `vitest` — MDX resolves `of` at *runtime*, so a
|
|
7
7
|
* renamed story turns into `of={undefined}` and the docs page
|
|
8
8
|
* renders Storybook's "component failed to render" panel instead of
|
|
9
|
-
* the page. `
|
|
9
|
+
* the page. `pnpm smoke:storybook` catches it, which is good, but
|
|
10
10
|
* only after a full Storybook build; this catches it in under a
|
|
11
11
|
* second, which is the difference between noticing during the edit
|
|
12
12
|
* and noticing in CI.
|