@charcuterie/ui 4.21.0 → 4.22.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -6
  3. package/dist/AdaptiveGrid/AdaptiveGrid.d.ts +4 -1
  4. package/dist/AdaptiveGrid/AdaptiveGrid.d.ts.map +1 -1
  5. package/dist/AdaptiveGrid/AdaptiveGrid.js +3 -1
  6. package/dist/AdaptiveGrid/AdaptiveGrid.js.map +1 -1
  7. package/dist/AdaptiveGrid/chooseColumns.d.ts +10 -2
  8. package/dist/AdaptiveGrid/chooseColumns.d.ts.map +1 -1
  9. package/dist/AdaptiveGrid/chooseColumns.js +10 -3
  10. package/dist/AdaptiveGrid/chooseColumns.js.map +1 -1
  11. package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +6 -2
  12. package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -1
  13. package/dist/AdaptiveGrid/useAdaptiveColumns.js +4 -1
  14. package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -1
  15. package/dist/Overlay/useAnchoredOverlay.d.ts +2 -2
  16. package/dist/Picker/Picker.d.ts +15 -0
  17. package/dist/Picker/Picker.d.ts.map +1 -1
  18. package/dist/Picker/Picker.js +26 -4
  19. package/dist/Picker/Picker.js.map +1 -1
  20. package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +4 -1
  21. package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
  22. package/dist/VirtualizedGrid/VirtualizedGrid.js +3 -1
  23. package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
  24. package/package.json +10 -8
  25. package/src/AdaptiveGrid/AdaptiveGrid.mdx +10 -0
  26. package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +17 -0
  27. package/src/AdaptiveGrid/AdaptiveGrid.test.tsx +36 -2
  28. package/src/AdaptiveGrid/AdaptiveGrid.tsx +6 -0
  29. package/src/AdaptiveGrid/chooseColumns.ts +29 -1
  30. package/src/AdaptiveGrid/useAdaptiveColumns.ts +13 -1
  31. package/src/CopyButton/CopyButton.test.tsx +25 -12
  32. package/src/Picker/Picker.mdx +14 -0
  33. package/src/Picker/Picker.stories.tsx +46 -0
  34. package/src/Picker/Picker.test.tsx +91 -2
  35. package/src/Picker/Picker.tsx +43 -4
  36. package/src/VirtualizedGrid/VirtualizedGrid.tsx +6 -0
  37. package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +16 -3
  38. 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 Yarn
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 `yarn link` session and
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
- yarn vitest run --project ui # Node: class maps, status switches, clamping, boundaries
260
- yarn vitest run --project ui-dom # chromium: Component.test.tsx — behaviour, keyboard, ARIA
261
- yarn vitest run --project storybook # chromium: every story renders, axe at test: "error"
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
- `yarn smoke:storybook` did.
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;AAKtC,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,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,8IAW1B,iBAAiB,KAAG,SAuCtB,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;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgD5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,SAAS,EACT,OAAO,EACP,aAAa,EACb,SAAS,EACT,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,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;KACtB,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"}
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,0HAQ3B;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,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,KAAG,MA4BH,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,GAAI,oEAKrC;IACD,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,KAAG,MAG6F,CAAA;AAEjG;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,iBAE9B;IACD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAG,YAAY,EAMf,CAAA"}
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
- : `${Number.parseFloat(columnInlineSize) * columns + Number.parseFloat(gutterInlineSize)}rem`;
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;AAO3C;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,GAAG,CAAC,EACnB,aAAa,EACb,SAAS,EACT,UAAU,GAAG,wBAAwB,EACrC,mBAAmB,GAAG,iCAAiC,GAwDxD,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,+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,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,GAS5C,EAAU,EAAE,CACX,OAAO,IAAI,CAAC;IACV,CAAC,CAAC,gBAAgB;IAClB,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,KAAK,CAAA;AAEjG;;;;;;;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
+ {"version":3,"file":"chooseColumns.js","sourceRoot":"","sources":["../../src/AdaptiveGrid/chooseColumns.ts"],"names":[],"mappings":"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"}
@@ -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,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
+ {"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":"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"}
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/utils").Strategy;
135
+ strategy: import("@floating-ui/react").Strategy;
136
136
  x: number;
137
137
  y: number;
138
- middlewareData: import("@floating-ui/core").MiddlewareData;
138
+ middlewareData: import("@floating-ui/react").MiddlewareData;
139
139
  isPositioned: boolean;
140
140
  update: () => void;
141
141
  floatingStyles: React.CSSProperties;
@@ -84,6 +84,21 @@ export type PickerProps = Omit<ComponentPropsWithRef<"button">, "disabled" | "on
84
84
  * mux-magic both moved to it while `id` was still being replaced — but
85
85
  * `id` is no longer a trap. Everything else on the button (`data-*`,
86
86
  * `onClick`, `form`, …) passes straight through.
87
+ *
88
+ * ### A stretched trigger reads like a select, not a button
89
+ *
90
+ * `Button` centers its content, which is right for an action and wrong
91
+ * for a value. Inside a `Field` (a `flex-col`, so the child stretches)
92
+ * or under `w-full`, the trigger used to become a bar with the value
93
+ * floating in the middle and the chevron beside it. The value now sits
94
+ * at the inline start, in a span that grows into the spare width and
95
+ * ends in an ellipsis when there is too little, and the chevron sits at
96
+ * the inline end. `justify-between` is passed through `className`, which
97
+ * `toClassName` merges over `Button`'s `justify-center` rather than
98
+ * leaving the two to stylesheet order — and the growing span makes the
99
+ * result correct even if a caller's `className` overrides it. A trigger
100
+ * at its content width has no spare width to distribute, so it renders
101
+ * exactly as it did before.
87
102
  */
88
103
  export declare const Picker: ({ appearance, className, iconEnd, intent, isDisabled, label, onChange, options, placement, placeholder, size, value, ...buttonProps }: PickerProps) => ReactNode;
89
104
  //# sourceMappingURL=Picker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Picker.d.ts","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAA;AAwBtC,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,GAAG,UAAU,GAAG,OAAO,CAClC,GAAG;IACF,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,MAAM,GAAI,uIAcpB,WAAW,KAAG,SAoDhB,CAAA"}
1
+ {"version":3,"file":"Picker.d.ts","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAIzD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAA;AAwBtC,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,GAAG,UAAU,GAAG,OAAO,CAClC,GAAG;IACF,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,MAAM,GAAI,uIAcpB,WAAW,KAAG,SA2EhB,CAAA"}
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useVisibility } from "@charcuterie/logic";
3
3
  import { Button } from "../Button/Button.js";
4
4
  import { Listbox } from "../Listbox/Listbox.js";
5
+ import { toClassName } from "../toClassName.js";
5
6
  /**
6
7
  * A constant element rather than a component, for the same reason
7
8
  * `Select` inlines its own: a second component in this file trips the
@@ -62,6 +63,21 @@ const CHEVRON_DOWN = (_jsx("svg", { "aria-hidden": "true", className: "size-4",
62
63
  * mux-magic both moved to it while `id` was still being replaced — but
63
64
  * `id` is no longer a trap. Everything else on the button (`data-*`,
64
65
  * `onClick`, `form`, …) passes straight through.
66
+ *
67
+ * ### A stretched trigger reads like a select, not a button
68
+ *
69
+ * `Button` centers its content, which is right for an action and wrong
70
+ * for a value. Inside a `Field` (a `flex-col`, so the child stretches)
71
+ * or under `w-full`, the trigger used to become a bar with the value
72
+ * floating in the middle and the chevron beside it. The value now sits
73
+ * at the inline start, in a span that grows into the spare width and
74
+ * ends in an ellipsis when there is too little, and the chevron sits at
75
+ * the inline end. `justify-between` is passed through `className`, which
76
+ * `toClassName` merges over `Button`'s `justify-center` rather than
77
+ * leaving the two to stylesheet order — and the growing span makes the
78
+ * result correct even if a caller's `className` overrides it. A trigger
79
+ * at its content width has no spare width to distribute, so it renders
80
+ * exactly as it did before.
65
81
  */
66
82
  export const Picker = ({ appearance = "outline", className, iconEnd = CHEVRON_DOWN, intent = "neutral", isDisabled = false, label, onChange, options, placement, placeholder, size, value, ...buttonProps }) => {
67
83
  const { hide, isVisible, toggle } = useVisibility();
@@ -73,6 +89,14 @@ export const Picker = ({ appearance = "outline", className, iconEnd = CHEVRON_DO
73
89
  ? currentOption.label
74
90
  : undefined));
75
91
  const triggerText = currentText ?? placeholder ?? "";
92
+ const triggerContent = currentOption === undefined
93
+ ? placeholder
94
+ : currentOption.label;
95
+ // No content, no span: an empty flex item would still earn a `gap`
96
+ // before the chevron and widen a content-width trigger.
97
+ const hasTriggerContent = triggerContent !== undefined &&
98
+ triggerContent !== null &&
99
+ triggerContent !== "";
76
100
  return (_jsx(Listbox
77
101
  // A disabled control must not open, and hiding the panel is a
78
102
  // truer way to say that than trusting the trigger's click alone —
@@ -81,11 +105,9 @@ export const Picker = ({ appearance = "outline", className, iconEnd = CHEVRON_DO
81
105
  // A disabled control must not open, and hiding the panel is a
82
106
  // truer way to say that than trusting the trigger's click alone —
83
107
  // Escape and outside-press both route through `onDismiss`.
84
- isVisible: isVisible && !isDisabled, onDismiss: hide, onSelect: onChange, options: options, placement: placement, selectedValue: value, trigger: _jsx(Button, { ...buttonProps, appearance: appearance, "aria-label": `${label}: ${triggerText}`, className: className, iconEnd: iconEnd, intent: intent, isDisabled: isDisabled, onClick: (clickEvent) => {
108
+ isVisible: isVisible && !isDisabled, onDismiss: hide, onSelect: onChange, options: options, placement: placement, selectedValue: value, trigger: _jsx(Button, { ...buttonProps, appearance: appearance, "aria-label": `${label}: ${triggerText}`, className: toClassName("justify-between", className), iconEnd: iconEnd, intent: intent, isDisabled: isDisabled, onClick: (clickEvent) => {
85
109
  buttonProps.onClick?.(clickEvent);
86
110
  toggle();
87
- }, size: size, type: "button", children: currentOption === undefined
88
- ? placeholder
89
- : currentOption.label }) }));
111
+ }, size: size, type: "button", children: hasTriggerContent ? (_jsx("span", { className: "-mx-1 min-w-0 grow overflow-x-clip px-1 text-start text-ellipsis whitespace-nowrap", children: triggerContent })) : null }) }));
90
112
  };
91
113
  //# sourceMappingURL=Picker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Picker.js","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAWlD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAG7C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAwB,CAAA;AAIhD;;;;GAIG;AACH,MAAM,YAAY,GAAG,CACnB,6BACc,MAAM,EAClB,SAAS,EAAC,QAAQ,EAClB,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,KAAK,EAChB,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,IAAI,EACjB,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,4BAA4B,YAElC,eAAM,CAAC,EAAC,cAAc,GAAG,GACrB,CACP,CAAA;AAgCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,UAAU,GAAG,SAAS,EACtB,SAAS,EACT,OAAO,GAAG,YAAY,EACtB,MAAM,GAAG,SAAS,EAClB,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,IAAI,EACJ,KAAK,EACL,GAAG,WAAW,EACF,EAAa,EAAE;IAC3B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAAA;IAEnD,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAChC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,CACnC,CAAA;IAED,MAAM,WAAW,GACf,aAAa,KAAK,SAAS;QACzB,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS;YACxB,CAAC,OAAO,aAAa,CAAC,KAAK,KAAK,QAAQ;gBACtC,CAAC,CAAC,aAAa,CAAC,KAAK;gBACrB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAErB,MAAM,WAAW,GAAG,WAAW,IAAI,WAAW,IAAI,EAAE,CAAA;IAEpD,OAAO,CACL,KAAC,OAAO;IACN,8DAA8D;IAC9D,kEAAkE;IAClE,2DAA2D;;QAF3D,8DAA8D;QAC9D,kEAAkE;QAClE,2DAA2D;QAC3D,SAAS,EAAE,SAAS,IAAI,CAAC,UAAU,EACnC,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,KAAK,EACpB,OAAO,EACL,KAAC,MAAM,OACD,WAAW,EACf,UAAU,EAAE,UAAU,gBACV,GAAG,KAAK,KAAK,WAAW,EAAE,EACtC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE;gBACtB,WAAW,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAA;gBAEjC,MAAM,EAAE,CAAA;YACV,CAAC,EACD,IAAI,EAAE,IAAI,EACV,IAAI,EAAC,QAAQ,YAEZ,aAAa,KAAK,SAAS;gBAC1B,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,aAAa,CAAC,KAAK,GAChB,GAEX,CACH,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Picker.js","sourceRoot":"","sources":["../../src/Picker/Picker.tsx"],"names":[],"mappings":"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"}
@@ -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,kLAepC,oBAAoB,CAAC,IAAI,CAAC,KAAG,SAiE/B,CAAA"}
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;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"}
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.21.0",
3
+ "version": "4.22.1",
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 { AllStates, AllVariants, Playground, Responsive } =
40
- composeStories(stories)
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
- : `${Number.parseFloat(columnInlineSize) * columns + Number.parseFloat(gutterInlineSize)}rem`
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 { ColumnChoice } from "./chooseColumns.ts"
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.getByRole("button", { name: "Copied" }),
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.getByRole("button", { name: "Copy failed" }),
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 expect(
107
- canvas.getByRole("status"),
108
- ).toHaveTextContent("Copied to the clipboard.")
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 expect(
115
- canvas.getByRole("status"),
116
- ).toHaveTextContent(
117
- "The clipboard refused. Select the value and press Control C.",
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: 20, copy: () => true } },
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.getByRole("button", { name: "Copied" }),
147
+ await canvas.findByRole("button", { name: "Copied" }),
135
148
  ).toBeVisible()
136
149
 
137
150
  await waitFor(async () => {
@@ -77,6 +77,20 @@ all flush; they are all the same trap.
77
77
 
78
78
  <Canvas of={PickerStories.NextToText} />
79
79
 
80
+ ## A stretched trigger reads like a select
81
+
82
+ `Picker` is a `Button`, and a `Button` centers its content. Inside a `Field` — a `flex-col`,
83
+ so the trigger stretches to the column — or under `w-full`, that used to give a full-width bar
84
+ with the value floating in the middle. The trigger now lays its value out at the **inline
85
+ start** and the chevron at the **inline end**, and a value wider than the trigger truncates
86
+ with an ellipsis rather than pushing the chevron out. The full value stays in the accessible
87
+ name and in the open list.
88
+
89
+ The width still comes from the container: a trigger the parent does not stretch keeps its
90
+ content width and renders exactly as before.
91
+
92
+ <Canvas of={PickerStories.Responsive} />
93
+
80
94
  ## `id` does not survive; `data-testid` does
81
95
 
82
96
  `useAnchoredOverlay` overwrites the trigger's `id` with a generated one so the portalled
@@ -10,6 +10,7 @@ import {
10
10
  } from "../argTypes.storyHelpers.ts"
11
11
  import { Badge } from "../Badge/Badge.tsx"
12
12
  import {
13
+ ContainerBoard,
13
14
  StoryRow,
14
15
  StorySection,
15
16
  } from "../board.storyHelpers.tsx"
@@ -258,3 +259,48 @@ export const NextToText: Story = {
258
259
  </div>
259
260
  ),
260
261
  }
262
+
263
+ /**
264
+ * One value long enough to outgrow the narrowest container, so the
265
+ * board shows the truncation as well as the stretch.
266
+ */
267
+ const SUBTITLES: PickerOption[] = [
268
+ { label: "Off", value: "off" },
269
+ {
270
+ label: "English (SDH, signs and songs, full dialogue)",
271
+ value: "eng-sdh",
272
+ },
273
+ ]
274
+
275
+ /**
276
+ * Inside a `Field` the trigger stretches to the container, the way a
277
+ * select does: the value sits at the inline start, the chevron at the
278
+ * inline end, and a value wider than the container truncates rather
279
+ * than pushing the chevron out.
280
+ */
281
+ export const Responsive: Story = {
282
+ render: () => (
283
+ <ContainerBoard>
284
+ {(width) => (
285
+ <div className="flex flex-col gap-4">
286
+ <Field label={`Language at ${width}`}>
287
+ <Picker
288
+ label={`Language at ${width}`}
289
+ onChange={() => undefined}
290
+ options={LANGUAGES}
291
+ value="eng"
292
+ />
293
+ </Field>
294
+ <Field label={`Subtitles at ${width}`}>
295
+ <Picker
296
+ label={`Subtitles at ${width}`}
297
+ onChange={() => undefined}
298
+ options={SUBTITLES}
299
+ value="eng-sdh"
300
+ />
301
+ </Field>
302
+ </div>
303
+ )}
304
+ </ContainerBoard>
305
+ ),
306
+ }
@@ -7,8 +7,13 @@ import { mountStory } from "../mountStory.testHelpers.ts"
7
7
  import { expectAgentDrivable } from "../testing/index.ts"
8
8
  import * as stories from "./Picker.stories.tsx"
9
9
 
10
- const { AllStates, Playground, Interactive, NextToText } =
11
- composeStories(stories)
10
+ const {
11
+ AllStates,
12
+ Playground,
13
+ Interactive,
14
+ NextToText,
15
+ Responsive,
16
+ } = composeStories(stories)
12
17
 
13
18
  test("the trigger's accessible name carries the current value", async () => {
14
19
  const { canvas, canvasElement } =
@@ -108,3 +113,87 @@ test("a rich option label keeps a plain-string trigger via textValue", async ()
108
113
 
109
114
  await expectNoAxeViolations(canvasElement)
110
115
  })
116
+
117
+ test("a stretched trigger puts the value at the start and the chevron at the end", async () => {
118
+ const { canvas, canvasElement } =
119
+ await mountStory(Responsive)
120
+
121
+ const trigger = expectAgentDrivable(canvas, {
122
+ name: "Language at 34rem: English",
123
+ role: "button",
124
+ })
125
+
126
+ const valueElement = canvas
127
+ .getAllByText("English")
128
+ .find((element: HTMLElement) =>
129
+ trigger.contains(element),
130
+ ) as HTMLElement
131
+
132
+ // The text's own box, not the span's: the span grows to fill the
133
+ // trigger, so its box would sit at the start even with centered text.
134
+ const valueRange = document.createRange()
135
+
136
+ valueRange.selectNodeContents(valueElement)
137
+
138
+ const valueBox = valueRange.getBoundingClientRect()
139
+ const triggerBox = trigger.getBoundingClientRect()
140
+ const chevronBox = (
141
+ trigger.querySelector("svg") as SVGElement
142
+ ).getBoundingClientRect()
143
+
144
+ const triggerStyle = getComputedStyle(trigger)
145
+ const contentStart =
146
+ triggerBox.left +
147
+ Number.parseFloat(triggerStyle.borderInlineStartWidth) +
148
+ Number.parseFloat(triggerStyle.paddingInlineStart)
149
+ const contentEnd =
150
+ triggerBox.right -
151
+ Number.parseFloat(triggerStyle.borderInlineEndWidth) -
152
+ Number.parseFloat(triggerStyle.paddingInlineEnd)
153
+
154
+ // The `Field` stretched the trigger well past its content, so a
155
+ // centered value would start far from the inline start.
156
+ await expect(
157
+ triggerBox.width - valueBox.width - chevronBox.width,
158
+ ).toBeGreaterThan(200)
159
+ await expect(
160
+ Math.abs(valueBox.left - contentStart),
161
+ ).toBeLessThan(1)
162
+ await expect(
163
+ Math.abs(chevronBox.right - contentEnd),
164
+ ).toBeLessThan(1)
165
+
166
+ await expectNoAxeViolations(canvasElement)
167
+ })
168
+
169
+ test("a value wider than the trigger truncates and keeps the chevron inside", async () => {
170
+ const { canvas } = await mountStory(Responsive)
171
+
172
+ const trigger = expectAgentDrivable(canvas, {
173
+ name: "Subtitles at 15rem: English (SDH, signs and songs, full dialogue)",
174
+ role: "button",
175
+ })
176
+
177
+ const valueElement = canvas
178
+ .getAllByText(
179
+ "English (SDH, signs and songs, full dialogue)",
180
+ )
181
+ .find((element: HTMLElement) =>
182
+ trigger.contains(element),
183
+ ) as HTMLElement
184
+
185
+ const triggerBox = trigger.getBoundingClientRect()
186
+ const chevronBox = (
187
+ trigger.querySelector("svg") as SVGElement
188
+ ).getBoundingClientRect()
189
+
190
+ await expect(valueElement.scrollWidth).toBeGreaterThan(
191
+ valueElement.clientWidth,
192
+ )
193
+ await expect(trigger.scrollWidth).toBe(
194
+ trigger.clientWidth,
195
+ )
196
+ await expect(chevronBox.right).toBeLessThanOrEqual(
197
+ triggerBox.right,
198
+ )
199
+ })
@@ -13,6 +13,7 @@ import { Button } from "../Button/Button.tsx"
13
13
  import type { IntentAppearance } from "../intentStyles.ts"
14
14
  import type { ListboxItem } from "../Listbox/Listbox.tsx"
15
15
  import { Listbox } from "../Listbox/Listbox.tsx"
16
+ import { toClassName } from "../toClassName.ts"
16
17
 
17
18
  export type PickerOption = ListboxItem
18
19
 
@@ -122,6 +123,21 @@ export type PickerProps = Omit<
122
123
  * mux-magic both moved to it while `id` was still being replaced — but
123
124
  * `id` is no longer a trap. Everything else on the button (`data-*`,
124
125
  * `onClick`, `form`, …) passes straight through.
126
+ *
127
+ * ### A stretched trigger reads like a select, not a button
128
+ *
129
+ * `Button` centers its content, which is right for an action and wrong
130
+ * for a value. Inside a `Field` (a `flex-col`, so the child stretches)
131
+ * or under `w-full`, the trigger used to become a bar with the value
132
+ * floating in the middle and the chevron beside it. The value now sits
133
+ * at the inline start, in a span that grows into the spare width and
134
+ * ends in an ellipsis when there is too little, and the chevron sits at
135
+ * the inline end. `justify-between` is passed through `className`, which
136
+ * `toClassName` merges over `Button`'s `justify-center` rather than
137
+ * leaving the two to stylesheet order — and the growing span makes the
138
+ * result correct even if a caller's `className` overrides it. A trigger
139
+ * at its content width has no spare width to distribute, so it renders
140
+ * exactly as it did before.
125
141
  */
126
142
  export const Picker = ({
127
143
  appearance = "outline",
@@ -154,6 +170,18 @@ export const Picker = ({
154
170
 
155
171
  const triggerText = currentText ?? placeholder ?? ""
156
172
 
173
+ const triggerContent =
174
+ currentOption === undefined
175
+ ? placeholder
176
+ : currentOption.label
177
+
178
+ // No content, no span: an empty flex item would still earn a `gap`
179
+ // before the chevron and widen a content-width trigger.
180
+ const hasTriggerContent =
181
+ triggerContent !== undefined &&
182
+ triggerContent !== null &&
183
+ triggerContent !== ""
184
+
157
185
  return (
158
186
  <Listbox
159
187
  // A disabled control must not open, and hiding the panel is a
@@ -170,7 +198,10 @@ export const Picker = ({
170
198
  {...buttonProps}
171
199
  appearance={appearance}
172
200
  aria-label={`${label}: ${triggerText}`}
173
- className={className}
201
+ className={toClassName(
202
+ "justify-between",
203
+ className,
204
+ )}
174
205
  iconEnd={iconEnd}
175
206
  intent={intent}
176
207
  isDisabled={isDisabled}
@@ -182,9 +213,17 @@ export const Picker = ({
182
213
  size={size}
183
214
  type="button"
184
215
  >
185
- {currentOption === undefined
186
- ? placeholder
187
- : currentOption.label}
216
+ {hasTriggerContent ? (
217
+ // Not `truncate`. Its `overflow: hidden` clips the
218
+ // anti-aliased edge pixels of glyphs and a rich label's
219
+ // badge, so a content-width trigger stopped rendering
220
+ // pixel-identically to the unwrapped one. `overflow-x-clip`
221
+ // leaves the block axis alone, and `-mx-1 px-1` moves the
222
+ // inline clip edge 4px outward without moving the text.
223
+ <span className="-mx-1 min-w-0 grow overflow-x-clip px-1 text-start text-ellipsis whitespace-nowrap">
224
+ {triggerContent}
225
+ </span>
226
+ ) : null}
188
227
  </Button>
189
228
  }
190
229
  />
@@ -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
- await expect(article.textContent).not.toContain("](")
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
- await expect(article.textContent).not.toContain("**")
358
+ await expect(article.textContent).not.toContain("**")
355
359
 
356
- await expect(article.textContent).not.toContain("~~")
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. `yarn smoke:storybook` catches it, which is good, but
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.