@charcuterie/ui 3.28.0 → 3.28.2

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 (46) hide show
  1. package/dist/Main/Main.js +1 -1
  2. package/dist/Main/Main.js.map +1 -1
  3. package/dist/Rail/Rail.js +1 -1
  4. package/dist/Rail/Rail.js.map +1 -1
  5. package/dist/Shell/Shell.js +1 -1
  6. package/dist/Shell/Shell.js.map +1 -1
  7. package/dist/VirtualizedGrid/ElementGrid.d.ts +7 -0
  8. package/dist/VirtualizedGrid/ElementGrid.d.ts.map +1 -0
  9. package/dist/VirtualizedGrid/ElementGrid.js +19 -0
  10. package/dist/VirtualizedGrid/ElementGrid.js.map +1 -0
  11. package/dist/VirtualizedGrid/GridRows.d.ts +4 -0
  12. package/dist/VirtualizedGrid/GridRows.d.ts.map +1 -0
  13. package/dist/VirtualizedGrid/GridRows.js +40 -0
  14. package/dist/VirtualizedGrid/GridRows.js.map +1 -0
  15. package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +6 -8
  16. package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
  17. package/dist/VirtualizedGrid/VirtualizedGrid.js +30 -87
  18. package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
  19. package/dist/VirtualizedGrid/WindowGrid.d.ts +3 -0
  20. package/dist/VirtualizedGrid/WindowGrid.d.ts.map +1 -0
  21. package/dist/VirtualizedGrid/WindowGrid.js +20 -0
  22. package/dist/VirtualizedGrid/WindowGrid.js.map +1 -0
  23. package/dist/VirtualizedGrid/virtualizedGridScroll.d.ts +25 -0
  24. package/dist/VirtualizedGrid/virtualizedGridScroll.d.ts.map +1 -0
  25. package/dist/VirtualizedGrid/virtualizedGridScroll.js +50 -0
  26. package/dist/VirtualizedGrid/virtualizedGridScroll.js.map +1 -0
  27. package/dist/VirtualizedGrid/virtualizedGridTypes.d.ts +27 -0
  28. package/dist/VirtualizedGrid/virtualizedGridTypes.d.ts.map +1 -0
  29. package/dist/VirtualizedGrid/virtualizedGridTypes.js +2 -0
  30. package/dist/VirtualizedGrid/virtualizedGridTypes.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/Main/Main.tsx +1 -1
  33. package/src/Rail/Rail.tsx +1 -1
  34. package/src/Shell/Shell.mdx +8 -0
  35. package/src/Shell/Shell.stories.tsx +24 -0
  36. package/src/Shell/Shell.test.tsx +29 -0
  37. package/src/Shell/Shell.tsx +1 -1
  38. package/src/VirtualizedGrid/ElementGrid.tsx +45 -0
  39. package/src/VirtualizedGrid/GridRows.tsx +90 -0
  40. package/src/VirtualizedGrid/VirtualizedGrid.mdx +9 -8
  41. package/src/VirtualizedGrid/VirtualizedGrid.stories.tsx +26 -0
  42. package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +60 -1
  43. package/src/VirtualizedGrid/VirtualizedGrid.tsx +42 -142
  44. package/src/VirtualizedGrid/WindowGrid.tsx +39 -0
  45. package/src/VirtualizedGrid/virtualizedGridScroll.ts +69 -0
  46. package/src/VirtualizedGrid/virtualizedGridTypes.ts +32 -0
package/dist/Main/Main.js CHANGED
@@ -118,6 +118,6 @@ export const Main = ({ children, className, contentWidth, id, ...mainProps }) =>
118
118
  const maxInlineSize = toMaxInlineSize(contentWidth ??
119
119
  shell?.contentWidth ??
120
120
  DEFAULT_CONTENT_WIDTH);
121
- return (_jsx("main", { ...mainProps, className: toClassName("col-start-1 row-start-3 min-w-0 md:col-start-2 md:row-start-2", className), id: id ?? shell?.mainId ?? fallbackId, tabIndex: -1, children: _jsx("div", { className: "@container mx-auto flex w-full min-w-0 flex-col gap-6 wrap-anywhere px-4 py-6 sm:px-6", style: { maxInlineSize }, children: children }) }));
121
+ return (_jsx("main", { ...mainProps, className: toClassName("charcuterie-scrollbar col-start-1 row-start-3 min-h-0 min-w-0 overflow-y-auto md:col-start-2 md:row-start-2", className), id: id ?? shell?.mainId ?? fallbackId, tabIndex: -1, children: _jsx("div", { className: "@container mx-auto flex w-full min-w-0 flex-col gap-6 wrap-anywhere px-4 py-6 sm:px-6", style: { maxInlineSize }, children: children }) }));
122
122
  };
123
123
  //# sourceMappingURL=Main.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Main.js","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAGL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,EAAE,EACF,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAEtC,iEAAiE;IACjE,2DAA2D;IAC3D,MAAM,UAAU,GAAG,WAAW,EAAE,CAAA;IAEhC,MAAM,aAAa,GAAG,eAAe,CACnC,YAAY;QACV,KAAK,EAAE,YAAY;QACnB,qBAAqB,CACxB,CAAA;IAED,OAAO,CACL,kBACM,SAAS,EACb,SAAS,EAAE,WAAW,CACpB,+DAA+D,EAC/D,SAAS,CACV,EACD,EAAE,EAAE,EAAE,IAAI,KAAK,EAAE,MAAM,IAAI,UAAU,EACrC,QAAQ,EAAE,CAAC,CAAC,YAaZ,cACE,SAAS,EAAC,uFAAuF,EACjG,KAAK,EAAE,EAAE,aAAa,EAAE,YAEvB,QAAQ,GACL,GACD,CACR,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Main.js","sourceRoot":"","sources":["../../src/Main/Main.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAGL,UAAU,GACX,MAAM,OAAO,CAAA;AAEd,OAAO,EAEL,qBAAqB,EACrB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,EAAE,EACF,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IAEtC,iEAAiE;IACjE,2DAA2D;IAC3D,MAAM,UAAU,GAAG,WAAW,EAAE,CAAA;IAEhC,MAAM,aAAa,GAAG,eAAe,CACnC,YAAY;QACV,KAAK,EAAE,YAAY;QACnB,qBAAqB,CACxB,CAAA;IAED,OAAO,CACL,kBACM,SAAS,EACb,SAAS,EAAE,WAAW,CACpB,6GAA6G,EAC7G,SAAS,CACV,EACD,EAAE,EAAE,EAAE,IAAI,KAAK,EAAE,MAAM,IAAI,UAAU,EACrC,QAAQ,EAAE,CAAC,CAAC,YAaZ,cACE,SAAS,EAAC,uFAAuF,EACjG,KAAK,EAAE,EAAE,aAAa,EAAE,YAEvB,QAAQ,GACL,GACD,CACR,CAAA;AACH,CAAC,CAAA"}
package/dist/Rail/Rail.js CHANGED
@@ -57,7 +57,7 @@ const LANDMARK_ELEMENT = {
57
57
  */
58
58
  export const Rail = ({ children, className, label, landmark = "complementary", side = "start", ...railProps }) => {
59
59
  const Element = LANDMARK_ELEMENT[landmark];
60
- return (_jsx(Element, { ...railProps, "aria-label": label, className: toClassName("flex min-w-0 gap-2 overflow-x-auto border-border-subtle p-4 md:row-start-2 md:w-64 md:flex-col md:overflow-x-visible", side === "start"
60
+ return (_jsx(Element, { ...railProps, "aria-label": label, className: toClassName("flex min-w-0 gap-2 overflow-x-auto border-border-subtle p-4 md:row-start-2 md:min-h-0 md:w-64 md:flex-col md:overflow-x-visible", side === "start"
61
61
  ? "row-start-2 border-b md:col-start-1 md:border-b-0 md:border-e"
62
62
  : "row-start-4 border-t md:col-start-3 md:border-t-0 md:border-s", className), children: children }));
63
63
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Rail.js","sourceRoot":"","sources":["../../src/Rail/Rail.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY/C,MAAM,gBAAgB,GAGlB;IACF,aAAa,EAAE,OAAO;IACtB,UAAU,EAAE,KAAK;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,GAAG,eAAe,EAC1B,IAAI,GAAG,OAAO,EACd,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAE1C,OAAO,CACL,KAAC,OAAO,OACF,SAAS,gBACD,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,sHAAsH,EACtH,IAAI,KAAK,OAAO;YACd,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,+DAA+D,EACnE,SAAS,CACV,YAEA,QAAQ,GACD,CACX,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Rail.js","sourceRoot":"","sources":["../../src/Rail/Rail.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY/C,MAAM,gBAAgB,GAGlB;IACF,aAAa,EAAE,OAAO;IACtB,UAAU,EAAE,KAAK;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,GAAG,eAAe,EAC1B,IAAI,GAAG,OAAO,EACd,GAAG,SAAS,EACF,EAAa,EAAE;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAE1C,OAAO,CACL,KAAC,OAAO,OACF,SAAS,gBACD,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,iIAAiI,EACjI,IAAI,KAAK,OAAO;YACd,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,+DAA+D,EACnE,SAAS,CACV,YAEA,QAAQ,GACD,CACX,CAAA;AACH,CAAC,CAAA"}
@@ -111,6 +111,6 @@ import { ShellContext } from "./shellContext.js";
111
111
  export const Shell = ({ children, className, contentWidth = DEFAULT_CONTENT_WIDTH, skipLinkLabel = "Skip to main content", ...divProps }) => {
112
112
  const mainId = useUniqueId();
113
113
  const shellContext = useMemo(() => ({ contentWidth, mainId }), [contentWidth, mainId]);
114
- return (_jsx(ShellContext.Provider, { value: shellContext, children: _jsxs("div", { ...divProps, className: toClassName("relative grid min-h-dvh grid-cols-1 grid-rows-[auto_auto_1fr_auto] overflow-x-clip bg-surface-base text-content-primary md:grid-cols-[auto_minmax(0,1fr)_auto] md:grid-rows-[auto_1fr]", className), children: [_jsx("a", { className: "fixed top-0 start-0 z-[var(--layer-toast)] m-3 -translate-y-24 rounded-md border border-border-subtle bg-surface-raised px-3 py-2 font-medium text-content-primary text-sm shadow-medium focus-visible:translate-y-0", href: `#${mainId}`, children: skipLinkLabel }), children] }) }));
114
+ return (_jsx(ShellContext.Provider, { value: shellContext, children: _jsxs("div", { ...divProps, className: toClassName("relative grid h-dvh grid-cols-1 grid-rows-[auto_auto_minmax(0,1fr)_auto] overflow-x-clip bg-surface-base text-content-primary md:grid-cols-[auto_minmax(0,1fr)_auto] md:grid-rows-[auto_minmax(0,1fr)]", className), children: [_jsx("a", { className: "fixed top-0 start-0 z-[var(--layer-toast)] m-3 -translate-y-24 rounded-md border border-border-subtle bg-surface-raised px-3 py-2 font-medium text-content-primary text-sm shadow-medium focus-visible:translate-y-0", href: `#${mainId}`, children: skipLinkLabel }), children] }) }));
115
115
  };
116
116
  //# sourceMappingURL=Shell.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Shell.js","sourceRoot":"","sources":["../../src/Shell/Shell.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAGL,OAAO,GACR,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAEL,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAOhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,YAAY,GAAG,qBAAqB,EACpC,aAAa,GAAG,sBAAsB,EACtC,GAAG,QAAQ,EACA,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAChC,CAAC,YAAY,EAAE,MAAM,CAAC,CACvB,CAAA;IAED,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YACxC,kBACM,QAAQ,EACZ,SAAS,EAAE,WAAW,CACpB,wLAAwL,EACxL,SAAS,CACV,aAcD,YACE,SAAS,EAAC,sNAAsN,EAChO,IAAI,EAAE,IAAI,MAAM,EAAE,YAEjB,aAAa,GACZ,EAEH,QAAQ,IACL,GACgB,CACzB,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Shell.js","sourceRoot":"","sources":["../../src/Shell/Shell.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAGL,OAAO,GACR,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAEL,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAOhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,YAAY,GAAG,qBAAqB,EACpC,aAAa,GAAG,sBAAsB,EACtC,GAAG,QAAQ,EACA,EAAa,EAAE;IAC1B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAChC,CAAC,YAAY,EAAE,MAAM,CAAC,CACvB,CAAA;IAED,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YACxC,kBACM,QAAQ,EACZ,SAAS,EAAE,WAAW,CACpB,wMAAwM,EACxM,SAAS,CACV,aAcD,YACE,SAAS,EAAC,sNAAsN,EAChO,IAAI,EAAE,IAAI,MAAM,EAAE,YAEjB,aAAa,GACZ,EAEH,QAAQ,IACL,GACgB,CACzB,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ import type { GridWindowProps, ScrollOwner } from "./virtualizedGridTypes.ts";
2
+ export declare const ElementGrid: <Item>(props: GridWindowProps<Item> & {
3
+ scrollOwner: Extract<ScrollOwner, {
4
+ kind: "element";
5
+ }>;
6
+ }) => import("react").JSX.Element;
7
+ //# sourceMappingURL=ElementGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ElementGrid.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/ElementGrid.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAElC,eAAO,MAAM,WAAW,GAAI,IAAI,EAC9B,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IAC7B,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC,CAAA;CACvD,gCA+BF,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useVirtualizer } from "@tanstack/react-virtual";
3
+ import { useCallback } from "react";
4
+ import { GridRows } from "./GridRows.js";
5
+ import { useScrollMargin } from "./virtualizedGridScroll.js";
6
+ export const ElementGrid = (props) => {
7
+ const { gridElement, scrollMargin } = useScrollMargin(props.scrollOwner);
8
+ const measureRow = useCallback((element) => element.getBoundingClientRect().height + props.gap, [props.gap]);
9
+ const rowVirtualizer = useVirtualizer({
10
+ count: props.rowCount,
11
+ estimateSize: () => props.itemBlockSize + props.gap,
12
+ getScrollElement: () => props.scrollOwner.element,
13
+ measureElement: measureRow,
14
+ overscan: props.overscanRows,
15
+ scrollMargin,
16
+ });
17
+ return (_jsx(GridRows, { ...props, gridElement: gridElement, measureElement: rowVirtualizer.measureElement, scrollMargin: scrollMargin, totalSize: rowVirtualizer.getTotalSize(), virtualRows: rowVirtualizer.getVirtualItems() }));
18
+ };
19
+ //# sourceMappingURL=ElementGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ElementGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/ElementGrid.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAgB,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAM5D,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,KAEC,EACD,EAAE;IACF,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,eAAe,CACnD,KAAK,CAAC,WAAW,CAClB,CAAA;IAED,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,OAAgB,EAAE,EAAE,CACnB,OAAO,CAAC,qBAAqB,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,EACpD,CAAC,KAAK,CAAC,GAAG,CAAC,CACZ,CAAA;IAED,MAAM,cAAc,GAAG,cAAc,CAAC;QACpC,KAAK,EAAE,KAAK,CAAC,QAAQ;QACrB,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,GAAG;QACnD,gBAAgB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO;QACjD,cAAc,EAAE,UAAU;QAC1B,QAAQ,EAAE,KAAK,CAAC,YAAY;QAC5B,YAAY;KACb,CAAC,CAAA;IAEF,OAAO,CACL,KAAC,QAAQ,OACH,KAAK,EACT,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,cAAc,CAAC,cAAc,EAC7C,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,cAAc,CAAC,YAAY,EAAE,EACxC,WAAW,EAAE,cAAc,CAAC,eAAe,EAAE,GAC7C,CACH,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { GridRowsProps } from "./virtualizedGridTypes.ts";
2
+ /** The shared markup. Only the scroll observer differs between the two owners. */
3
+ export declare const GridRows: <Item>({ drawnColumns, gap, getItemKey, gridElement, items, label, measureElement, renderItem, scrollMargin, totalSize, virtualRows, }: GridRowsProps<Item>) => import("react").JSX.Element;
4
+ //# sourceMappingURL=GridRows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GridRows.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/GridRows.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE9D,kFAAkF;AAClF,eAAO,MAAM,QAAQ,GAAI,IAAI,EAAG,iIAY7B,aAAa,CAAC,IAAI,CAAC,gCAyErB,CAAA"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { getContentMaxInlineSize } from "../AdaptiveGrid/chooseColumns.js";
3
+ /** The shared markup. Only the scroll observer differs between the two owners. */
4
+ export const GridRows = ({ drawnColumns, gap, getItemKey, gridElement, items, label, measureElement, renderItem, scrollMargin, totalSize, virtualRows, }) => {
5
+ const firstRow = virtualRows[0];
6
+ const lastRow = virtualRows[virtualRows.length - 1];
7
+ // Everything scrolled past, and everything still to come. The two
8
+ // numbers that stand in for the rows that do not exist.
9
+ const paddingBlockStart = firstRow?.start ?? 0;
10
+ const paddingBlockEnd = lastRow
11
+ ? Math.max(0, totalSize - lastRow.end)
12
+ : 0;
13
+ return (_jsx("ul", { "aria-label": label, className: "grid w-full list-none p-0 [&>*]:min-w-0", ref: gridElement, style: {
14
+ gap: `${gap}px`,
15
+ gridTemplateColumns: `repeat(${drawnColumns}, minmax(0, 1fr))`,
16
+ maxInlineSize: getContentMaxInlineSize({
17
+ columns: drawnColumns,
18
+ }),
19
+ // Offset by `scrollMargin` because `start` is measured
20
+ // from the top of the scroll owner, and this padding is
21
+ // measured from the top of the grid.
22
+ paddingBlockEnd: `${paddingBlockEnd}px`,
23
+ paddingBlockStart: `${Math.max(0, paddingBlockStart - scrollMargin)}px`,
24
+ }, children: virtualRows.flatMap((row) => {
25
+ const firstIndex = row.index * drawnColumns;
26
+ return items
27
+ .slice(firstIndex, firstIndex + drawnColumns)
28
+ .map((item, offset) => {
29
+ const index = firstIndex + offset;
30
+ return (_jsx("li", { "aria-posinset": index + 1, "aria-setsize": items.length, "data-index": row.index,
31
+ // Measured on the leading cell alone. A CSS grid
32
+ // stretches every cell to its row's height, so
33
+ // one is enough — and measuring all of them
34
+ // would have the virtualizer record the same row
35
+ // once per column.
36
+ ref: offset === 0 ? measureElement : undefined, children: renderItem(item, index) }, getItemKey?.(item, index) ?? index));
37
+ });
38
+ }) }));
39
+ };
40
+ //# sourceMappingURL=GridRows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GridRows.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/GridRows.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAA;AAG1E,kFAAkF;AAClF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAQ,EAC9B,YAAY,EACZ,GAAG,EACH,UAAU,EACV,WAAW,EACX,KAAK,EACL,KAAK,EACL,cAAc,EACd,UAAU,EACV,YAAY,EACZ,SAAS,EACT,WAAW,GACS,EAAE,EAAE;IACxB,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAC/B,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAEnD,kEAAkE;IAClE,wDAAwD;IACxD,MAAM,iBAAiB,GAAG,QAAQ,EAAE,KAAK,IAAI,CAAC,CAAA;IAE9C,MAAM,eAAe,GAAG,OAAO;QAC7B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC;QACtC,CAAC,CAAC,CAAC,CAAA;IAEL,OAAO,CACL,2BACc,KAAK,EACjB,SAAS,EAAC,yCAAyC,EACnD,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE;YACL,GAAG,EAAE,GAAG,GAAG,IAAI;YACf,mBAAmB,EAAE,UAAU,YAAY,mBAAmB;YAC9D,aAAa,EAAE,uBAAuB,CAAC;gBACrC,OAAO,EAAE,YAAY;aACtB,CAAC;YACF,uDAAuD;YACvD,wDAAwD;YACxD,qCAAqC;YACrC,eAAe,EAAE,GAAG,eAAe,IAAI;YACvC,iBAAiB,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC,IAAI;SACxE,YAEA,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAC3B,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,GAAG,YAAY,CAAA;YAE3C,OAAO,KAAK;iBACT,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,YAAY,CAAC;iBAC5C,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBACpB,MAAM,KAAK,GAAG,UAAU,GAAG,MAAM,CAAA;gBAEjC,OAAO,CACL,8BACiB,KAAK,GAAG,CAAC,kBACV,KAAK,CAAC,MAAM,gBAcd,GAAG,CAAC,KAAK;oBAErB,iDAAiD;oBACjD,+CAA+C;oBAC/C,4CAA4C;oBAC5C,iDAAiD;oBACjD,mBAAmB;oBACnB,GAAG,EACD,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,YAG1C,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,IAVnB,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,CAWpC,CACN,CAAA;YACH,CAAC,CAAC,CAAA;QACN,CAAC,CAAC,GACC,CACN,CAAA;AACH,CAAC,CAAA"}
@@ -123,15 +123,13 @@ export type VirtualizedGridProps<Item> = {
123
123
  * idea of the distance from one row to the next and the browser's
124
124
  * the same number.
125
125
  *
126
- * ### Why the window and not a scroll box of its own
126
+ * ### It follows the page's existing scroll owner
127
127
  *
128
- * `useWindowVirtualizer`, so the page scrolls the way every other
129
- * page in the fleet scrolls — the header leaves, the scrollbar is
130
- * the browser's, `Ctrl+End` works. A component that grew its own
131
- * `overflow: auto` well would trap the wheel and strand the page
132
- * header above it. The cost is `scrollMargin`: the virtualizer has
133
- * to be told how far down the document the grid starts, which is
134
- * measured below.
128
+ * A standalone grid follows the browser window. Inside `Shell`, it
129
+ * follows `Main`, which is the shell's one vertical scroll region.
130
+ * It never creates a scroll box of its own. The cost is
131
+ * `scrollMargin`: the virtualizer has to be told how far down its
132
+ * scroll owner the grid starts, which is measured below.
135
133
  *
136
134
  * ### What windowing costs, and what is done about it
137
135
  *
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedGrid.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAK9E,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,EAAG,kLAepC,oBAAoB,CAAC,IAAI,CAAC,KAAG,SAkK/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,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,EAAG,kLAepC,oBAAoB,CAAC,IAAI,CAAC,KAAG,SAgE/B,CAAA"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useWindowVirtualizer } from "@tanstack/react-virtual";
3
- import { useCallback, useLayoutEffect, useRef, useState, } from "react";
4
- import { getContentMaxInlineSize } from "../AdaptiveGrid/chooseColumns.js";
2
+ import { useLayoutEffect, useState } from "react";
5
3
  import { useAdaptiveColumns } from "../AdaptiveGrid/useAdaptiveColumns.js";
6
4
  import { toClassName } from "../toClassName.js";
5
+ import { ElementGrid } from "./ElementGrid.js";
7
6
  import { DEFAULT_GRID_GAP_PX } from "./gridGap.js";
7
+ import { findScrollOwner } from "./virtualizedGridScroll.js";
8
+ import { WindowGrid } from "./WindowGrid.js";
8
9
  /**
9
10
  * `AdaptiveGrid`'s layout, with only the rows you can see mounted.
10
11
  *
@@ -59,15 +60,13 @@ import { DEFAULT_GRID_GAP_PX } from "./gridGap.js";
59
60
  * idea of the distance from one row to the next and the browser's
60
61
  * the same number.
61
62
  *
62
- * ### Why the window and not a scroll box of its own
63
+ * ### It follows the page's existing scroll owner
63
64
  *
64
- * `useWindowVirtualizer`, so the page scrolls the way every other
65
- * page in the fleet scrolls — the header leaves, the scrollbar is
66
- * the browser's, `Ctrl+End` works. A component that grew its own
67
- * `overflow: auto` well would trap the wheel and strand the page
68
- * header above it. The cost is `scrollMargin`: the virtualizer has
69
- * to be told how far down the document the grid starts, which is
70
- * measured below.
65
+ * A standalone grid follows the browser window. Inside `Shell`, it
66
+ * follows `Main`, which is the shell's one vertical scroll region.
67
+ * It never creates a scroll box of its own. The cost is
68
+ * `scrollMargin`: the virtualizer has to be told how far down its
69
+ * scroll owner the grid starts, which is measured below.
71
70
  *
72
71
  * ### What windowing costs, and what is done about it
73
72
  *
@@ -90,88 +89,32 @@ export const VirtualizedGrid = ({ blockSizeResolver, chromeBlockSize, className,
90
89
  storageKey,
91
90
  });
92
91
  const drawnColumns = columns ?? layout.columns;
93
- // Folded from what is DRAWN rather than read off the hook, so an
94
- // override widens the page cap with it — the same rule
95
- // `AdaptiveGrid` follows, and wrong in the same way if it does
96
- // not.
97
- const contentMaxInlineSize = getContentMaxInlineSize({
98
- columns: drawnColumns,
99
- });
100
92
  const rowCount = Math.ceil(items.length / drawnColumns);
101
93
  /**
102
- * How far down the document the grid starts.
103
- *
104
- * `useWindowVirtualizer` measures against the document, so
105
- * without this every row is placed as though the grid began at
106
- * the top of the page, and the whole list sits shifted by the
107
- * height of the header above it.
108
- *
109
- * State and not a ref, because it feeds `scrollMargin` and a
110
- * change has to re-render. Measured on every commit rather than
111
- * on a dependency list: a filter row that grows a line moves the
112
- * grid down the page, and nothing in this component's own props
113
- * would report that.
94
+ * Main owns vertical scrolling inside an app Shell. Older pages
95
+ * and a standalone grid still use the window. Resolve the owner
96
+ * from the actual tree rather than making every app wire a ref
97
+ * between two Charcuterie components.
114
98
  */
115
- const gridElement = useRef(null);
116
- const [scrollMargin, setScrollMargin] = useState(0);
99
+ const [scrollOwner, setScrollOwner] = useState(null);
117
100
  useLayoutEffect(() => {
118
- const element = gridElement.current;
101
+ const element = layout.containerRef.current;
119
102
  if (!element) {
120
103
  return;
121
104
  }
122
- const next = element.getBoundingClientRect().top + window.scrollY;
123
- setScrollMargin((current) => current === next ? current : next);
124
- });
125
- /**
126
- * A row's height, plus the gap that follows it.
127
- *
128
- * The gap is added here rather than left to the estimate because
129
- * it is real distance the next row starts after: leave it out
130
- * and the virtualizer's offsets drift by one gap per row, which
131
- * on a list of two thousand is a scrollbar off by half a page.
132
- */
133
- const measureRow = useCallback((element) => element.getBoundingClientRect().height + gap, [gap]);
134
- const rowVirtualizer = useWindowVirtualizer({
135
- count: rowCount,
136
- estimateSize: () => itemBlockSize + gap,
137
- measureElement: measureRow,
138
- overscan: overscanRows,
139
- scrollMargin,
140
- });
141
- const virtualRows = rowVirtualizer.getVirtualItems();
142
- const firstRow = virtualRows[0];
143
- const lastRow = virtualRows[virtualRows.length - 1];
144
- // Everything scrolled past, and everything still to come. The two
145
- // numbers that stand in for the rows that do not exist.
146
- const paddingBlockStart = firstRow?.start ?? 0;
147
- const paddingBlockEnd = lastRow
148
- ? Math.max(0, rowVirtualizer.getTotalSize() - lastRow.end)
149
- : 0;
150
- return (_jsx("div", { className: toClassName("flex justify-center", className), ref: layout.containerRef, children: _jsx("ul", { "aria-label": label, className: "grid w-full list-none p-0 [&>*]:min-w-0", ref: gridElement, style: {
151
- gap: `${gap}px`,
152
- gridTemplateColumns: `repeat(${drawnColumns}, minmax(0, 1fr))`,
153
- maxInlineSize: contentMaxInlineSize,
154
- // Offset by `scrollMargin` because `start` is measured
155
- // from the top of the document, and this padding is
156
- // measured from the top of the grid.
157
- paddingBlockEnd: `${paddingBlockEnd}px`,
158
- paddingBlockStart: `${Math.max(0, paddingBlockStart - scrollMargin)}px`,
159
- }, children: virtualRows.flatMap((row) => {
160
- const firstIndex = row.index * drawnColumns;
161
- return items
162
- .slice(firstIndex, firstIndex + drawnColumns)
163
- .map((item, offset) => {
164
- const index = firstIndex + offset;
165
- return (_jsx("li", { "aria-posinset": index + 1, "aria-setsize": items.length, "data-index": row.index,
166
- // Measured on the leading cell alone. A CSS grid
167
- // stretches every cell to its row's height, so
168
- // one is enough — and measuring all of them
169
- // would have the virtualizer record the same row
170
- // once per column.
171
- ref: offset === 0
172
- ? rowVirtualizer.measureElement
173
- : undefined, children: renderItem(item, index) }, getItemKey?.(item, index) ?? index));
174
- });
175
- }) }) }));
105
+ setScrollOwner(findScrollOwner(element));
106
+ }, [layout.containerRef]);
107
+ const gridProps = {
108
+ drawnColumns,
109
+ gap,
110
+ getItemKey,
111
+ itemBlockSize,
112
+ items,
113
+ label,
114
+ overscanRows,
115
+ renderItem,
116
+ rowCount,
117
+ };
118
+ return (_jsx("div", { className: toClassName("flex justify-center", className), ref: layout.containerRef, children: scrollOwner?.kind === "element" ? (_jsx(ElementGrid, { ...gridProps, scrollOwner: scrollOwner })) : scrollOwner?.kind === "window" ? (_jsx(WindowGrid, { ...gridProps })) : null }));
176
119
  };
177
120
  //# sourceMappingURL=VirtualizedGrid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/VirtualizedGrid.tsx"],"names":[],"mappings":"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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;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,WAAW,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CACjC,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"}
@@ -0,0 +1,3 @@
1
+ import type { GridWindowProps } from "./virtualizedGridTypes.ts";
2
+ export declare const WindowGrid: <Item>(props: GridWindowProps<Item>) => import("react").JSX.Element;
3
+ //# sourceMappingURL=WindowGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WindowGrid.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/WindowGrid.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAEhE,eAAO,MAAM,UAAU,GAAI,IAAI,EAC7B,OAAO,eAAe,CAAC,IAAI,CAAC,gCA8B7B,CAAA"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useWindowVirtualizer } from "@tanstack/react-virtual";
3
+ import { useCallback } from "react";
4
+ import { GridRows } from "./GridRows.js";
5
+ import { useScrollMargin } from "./virtualizedGridScroll.js";
6
+ export const WindowGrid = (props) => {
7
+ const { gridElement, scrollMargin } = useScrollMargin({
8
+ kind: "window",
9
+ });
10
+ const measureRow = useCallback((element) => element.getBoundingClientRect().height + props.gap, [props.gap]);
11
+ const rowVirtualizer = useWindowVirtualizer({
12
+ count: props.rowCount,
13
+ estimateSize: () => props.itemBlockSize + props.gap,
14
+ measureElement: measureRow,
15
+ overscan: props.overscanRows,
16
+ scrollMargin,
17
+ });
18
+ return (_jsx(GridRows, { ...props, gridElement: gridElement, measureElement: rowVirtualizer.measureElement, scrollMargin: scrollMargin, totalSize: rowVirtualizer.getTotalSize(), virtualRows: rowVirtualizer.getVirtualItems() }));
19
+ };
20
+ //# sourceMappingURL=WindowGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WindowGrid.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/WindowGrid.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAgB,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAG5D,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,KAA4B,EAC5B,EAAE;IACF,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,eAAe,CAAC;QACpD,IAAI,EAAE,QAAQ;KACf,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,OAAgB,EAAE,EAAE,CACnB,OAAO,CAAC,qBAAqB,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,EACpD,CAAC,KAAK,CAAC,GAAG,CAAC,CACZ,CAAA;IAED,MAAM,cAAc,GAAG,oBAAoB,CAAC;QAC1C,KAAK,EAAE,KAAK,CAAC,QAAQ;QACrB,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,GAAG;QACnD,cAAc,EAAE,UAAU;QAC1B,QAAQ,EAAE,KAAK,CAAC,YAAY;QAC5B,YAAY;KACb,CAAC,CAAA;IAEF,OAAO,CACL,KAAC,QAAQ,OACH,KAAK,EACT,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,cAAc,CAAC,cAAc,EAC7C,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,cAAc,CAAC,YAAY,EAAE,EACxC,WAAW,EAAE,cAAc,CAAC,eAAe,EAAE,GAC7C,CACH,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,25 @@
1
+ import type { ScrollOwner } from "./virtualizedGridTypes.ts";
2
+ /**
3
+ * The closest ancestor that owns vertical scrolling.
4
+ *
5
+ * The computed value is enough. An `overflow-y:auto` ancestor can
6
+ * be shorter than its contents only AFTER this grid installs its
7
+ * virtual spacer, so requiring `scrollHeight > clientHeight` here
8
+ * would miss the owner on the first commit and create the exact
9
+ * document scrollbar this lookup prevents.
10
+ */
11
+ export declare const findScrollOwner: (element: HTMLElement) => ScrollOwner;
12
+ /**
13
+ * The offset from the scroll owner's content origin to the grid.
14
+ *
15
+ * Both formulas stay stable while their owner scrolls: the grid's
16
+ * viewport-relative top falls by the same amount that `scrollY` or
17
+ * `scrollTop` rises. The element path subtracts its border because
18
+ * a scroll offset starts at the padding edge, not at the outer
19
+ * border returned by `getBoundingClientRect()`.
20
+ */
21
+ export declare const useScrollMargin: (scrollOwner: ScrollOwner) => {
22
+ gridElement: import("react").RefObject<HTMLUListElement | null>;
23
+ scrollMargin: number;
24
+ };
25
+ //# sourceMappingURL=virtualizedGridScroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualizedGridScroll.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/virtualizedGridScroll.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,WAAW,KACnB,WAcF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,WAAW;;;CA2BzB,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+ /**
3
+ * The closest ancestor that owns vertical scrolling.
4
+ *
5
+ * The computed value is enough. An `overflow-y:auto` ancestor can
6
+ * be shorter than its contents only AFTER this grid installs its
7
+ * virtual spacer, so requiring `scrollHeight > clientHeight` here
8
+ * would miss the owner on the first commit and create the exact
9
+ * document scrollbar this lookup prevents.
10
+ */
11
+ export const findScrollOwner = (element) => {
12
+ let ancestor = element.parentElement;
13
+ while (ancestor) {
14
+ const overflow = getComputedStyle(ancestor).overflowY;
15
+ if (overflow === "auto" || overflow === "scroll") {
16
+ return { element: ancestor, kind: "element" };
17
+ }
18
+ ancestor = ancestor.parentElement;
19
+ }
20
+ return { kind: "window" };
21
+ };
22
+ /**
23
+ * The offset from the scroll owner's content origin to the grid.
24
+ *
25
+ * Both formulas stay stable while their owner scrolls: the grid's
26
+ * viewport-relative top falls by the same amount that `scrollY` or
27
+ * `scrollTop` rises. The element path subtracts its border because
28
+ * a scroll offset starts at the padding edge, not at the outer
29
+ * border returned by `getBoundingClientRect()`.
30
+ */
31
+ export const useScrollMargin = (scrollOwner) => {
32
+ const gridElement = useRef(null);
33
+ const [scrollMargin, setScrollMargin] = useState(0);
34
+ useLayoutEffect(() => {
35
+ const element = gridElement.current;
36
+ if (!element) {
37
+ return;
38
+ }
39
+ const gridTop = element.getBoundingClientRect().top;
40
+ const next = scrollOwner.kind === "window"
41
+ ? gridTop + window.scrollY
42
+ : gridTop -
43
+ scrollOwner.element.getBoundingClientRect().top -
44
+ scrollOwner.element.clientTop +
45
+ scrollOwner.element.scrollTop;
46
+ setScrollMargin((current) => current === next ? current : next);
47
+ });
48
+ return { gridElement, scrollMargin };
49
+ };
50
+ //# sourceMappingURL=virtualizedGridScroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualizedGridScroll.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/virtualizedGridScroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIzD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,OAAoB,EACP,EAAE;IACf,IAAI,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAA;IAEpC,OAAO,QAAQ,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAA;QAErD,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;QAC/C,CAAC;QAED,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAA;IACnC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAC3B,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,WAAwB,EACxB,EAAE;IACF,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAA;IAClD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEnD,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAA;QAEnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAA;QACnD,MAAM,IAAI,GACR,WAAW,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO;YAC1B,CAAC,CAAC,OAAO;gBACP,WAAW,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG;gBAC/C,WAAW,CAAC,OAAO,CAAC,SAAS;gBAC7B,WAAW,CAAC,OAAO,CAAC,SAAS,CAAA;QAEnC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAClC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAA;AACtC,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ import type { VirtualItem } from "@tanstack/react-virtual";
2
+ import type { ReactNode, RefObject } from "react";
3
+ export type ScrollOwner = {
4
+ kind: "element";
5
+ element: HTMLElement;
6
+ } | {
7
+ kind: "window";
8
+ };
9
+ export type GridWindowProps<Item> = {
10
+ drawnColumns: number;
11
+ gap: number;
12
+ getItemKey?: (item: Item, index: number) => string | number;
13
+ itemBlockSize: number;
14
+ items: readonly Item[];
15
+ label?: string;
16
+ overscanRows: number;
17
+ renderItem: (item: Item, index: number) => ReactNode;
18
+ rowCount: number;
19
+ };
20
+ export type GridRowsProps<Item> = Omit<GridWindowProps<Item>, "itemBlockSize" | "overscanRows" | "rowCount"> & {
21
+ gridElement: RefObject<HTMLUListElement | null>;
22
+ measureElement: (element: Element | null) => void;
23
+ scrollMargin: number;
24
+ totalSize: number;
25
+ virtualRows: VirtualItem[];
26
+ };
27
+ //# sourceMappingURL=virtualizedGridTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualizedGridTypes.d.ts","sourceRoot":"","sources":["../../src/VirtualizedGrid/virtualizedGridTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAEtB,MAAM,MAAM,eAAe,CAAC,IAAI,IAAI;IAClC,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,KACV,MAAM,GAAG,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAA;IACpD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,IAAI,IAAI,IAAI,CACpC,eAAe,CAAC,IAAI,CAAC,EACrB,eAAe,GAAG,cAAc,GAAG,UAAU,CAC9C,GAAG;IACF,WAAW,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAC/C,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAA;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,WAAW,EAAE,CAAA;CAC3B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=virtualizedGridTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualizedGridTypes.js","sourceRoot":"","sources":["../../src/VirtualizedGrid/virtualizedGridTypes.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "3.28.0",
3
+ "version": "3.28.2",
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",
package/src/Main/Main.tsx CHANGED
@@ -146,7 +146,7 @@ export const Main = ({
146
146
  <main
147
147
  {...mainProps}
148
148
  className={toClassName(
149
- "col-start-1 row-start-3 min-w-0 md:col-start-2 md:row-start-2",
149
+ "charcuterie-scrollbar col-start-1 row-start-3 min-h-0 min-w-0 overflow-y-auto md:col-start-2 md:row-start-2",
150
150
  className,
151
151
  )}
152
152
  id={id ?? shell?.mainId ?? fallbackId}
package/src/Rail/Rail.tsx CHANGED
@@ -89,7 +89,7 @@ export const Rail = ({
89
89
  {...railProps}
90
90
  aria-label={label}
91
91
  className={toClassName(
92
- "flex min-w-0 gap-2 overflow-x-auto border-border-subtle p-4 md:row-start-2 md:w-64 md:flex-col md:overflow-x-visible",
92
+ "flex min-w-0 gap-2 overflow-x-auto border-border-subtle p-4 md:row-start-2 md:min-h-0 md:w-64 md:flex-col md:overflow-x-visible",
93
93
  side === "start"
94
94
  ? "row-start-2 border-b md:col-start-1 md:border-b-0 md:border-e"
95
95
  : "row-start-4 border-t md:col-start-3 md:border-t-0 md:border-s",
@@ -127,6 +127,14 @@ rail that only changes *position* uses a media query and nothing else.
127
127
 
128
128
  <Canvas of={ShellStories.Responsive} />
129
129
 
130
+ ## The page content scrolls; the frame stays available
131
+
132
+ The shell fills the viewport. `Main` is the only vertical scroll region, so a long page does
133
+ not scroll the header or either rail away. This keeps navigation actions available, including
134
+ an app's rail-collapse control. The rail is not a second vertical scroll area.
135
+
136
+ <Canvas of={ShellStories.Scrollable} />
137
+
130
138
  ## It does not scroll sideways, and that is a test
131
139
 
132
140
  Plex Channels' narrow view scrolls left and right as wide as its content area. The usual
@@ -10,6 +10,7 @@ import {
10
10
  PageContent,
11
11
  RailDetail,
12
12
  RailNavigation,
13
+ ScrollFiller,
13
14
  } from "../shell.storyHelpers.tsx"
14
15
  import { Shell } from "./Shell.tsx"
15
16
 
@@ -183,3 +184,26 @@ export const Interactive: Story = {
183
184
  </Shell>
184
185
  ),
185
186
  }
187
+
188
+ /** A long page leaves the header and rail in place. */
189
+ export const Scrollable: Story = {
190
+ render: (shellProps) => (
191
+ <Shell {...shellProps}>
192
+ <Header
193
+ actions={<HeaderSchemeToggle />}
194
+ heading="Rip Deck"
195
+ />
196
+
197
+ <Rail label="Sections" landmark="navigation">
198
+ <RailNavigation />
199
+ </Rail>
200
+
201
+ <Main>
202
+ <PageContent />
203
+ <div style={{ minBlockSize: "150vh" }}>
204
+ <ScrollFiller />
205
+ </div>
206
+ </Main>
207
+ </Shell>
208
+ ),
209
+ }