gridsmith-ui 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/charts.js +3 -3
  2. package/dist/charts.js.map +1 -1
  3. package/dist/chat.js +4 -4
  4. package/dist/{chunk-CIVSNXFG.js → chunk-5PDTHKW3.js} +17 -9
  5. package/dist/chunk-5PDTHKW3.js.map +1 -0
  6. package/dist/{chunk-XGSQOMEY.js → chunk-EQDR57AM.js} +4 -4
  7. package/dist/{chunk-XGSQOMEY.js.map → chunk-EQDR57AM.js.map} +1 -1
  8. package/dist/{chunk-EMCSARQO.js → chunk-ILIODA7W.js} +3 -3
  9. package/dist/{chunk-EMCSARQO.js.map → chunk-ILIODA7W.js.map} +1 -1
  10. package/dist/{chunk-OENJCGTJ.js → chunk-JN37OVTP.js} +4 -4
  11. package/dist/{chunk-OENJCGTJ.js.map → chunk-JN37OVTP.js.map} +1 -1
  12. package/dist/{chunk-4KVFH5KC.js → chunk-M2QRNBDQ.js} +4 -3
  13. package/dist/chunk-M2QRNBDQ.js.map +1 -0
  14. package/dist/{chunk-Z63OSN64.js → chunk-NCIZQYXQ.js} +7 -4
  15. package/dist/chunk-NCIZQYXQ.js.map +1 -0
  16. package/dist/{chunk-PVTGMCVR.js → chunk-XYDIBW3J.js} +4 -4
  17. package/dist/{chunk-PVTGMCVR.js.map → chunk-XYDIBW3J.js.map} +1 -1
  18. package/dist/data-grid.js +3 -3
  19. package/dist/editor.js +3 -3
  20. package/dist/editor.js.map +1 -1
  21. package/dist/index.d.ts +146 -53
  22. package/dist/index.js +437 -156
  23. package/dist/index.js.map +1 -1
  24. package/dist/kanban.js +3 -3
  25. package/dist/resizable.d.ts +8 -1
  26. package/dist/resizable.js +35 -15
  27. package/dist/resizable.js.map +1 -1
  28. package/dist/roots.css +164 -2
  29. package/dist/tailwind.css +14 -0
  30. package/dist/utils.js +1 -1
  31. package/dist/virtualized.js +1 -1
  32. package/dist-static/tailwind.css +14 -0
  33. package/package.json +1 -1
  34. package/dist/chunk-4KVFH5KC.js.map +0 -1
  35. package/dist/chunk-CIVSNXFG.js.map +0 -1
  36. package/dist/chunk-Z63OSN64.js.map +0 -1
package/dist/kanban.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Badge } from './chunk-PVTGMCVR.js';
2
- import { useTextOverflow, Tooltip } from './chunk-EMCSARQO.js';
1
+ import { Badge } from './chunk-XYDIBW3J.js';
2
+ import { useTextOverflow, Tooltip } from './chunk-ILIODA7W.js';
3
3
  import './chunk-7Q46JXSK.js';
4
4
  import './chunk-WH2FUFAC.js';
5
5
  import { Icon } from './chunk-4LDYR7QP.js';
6
- import { cn } from './chunk-4KVFH5KC.js';
6
+ import { cn } from './chunk-M2QRNBDQ.js';
7
7
  import { useState, useRef, useLayoutEffect, useMemo } from 'react';
8
8
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCorners, DragOverlay, useDroppable } from '@dnd-kit/core';
9
9
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable';
@@ -73,6 +73,13 @@ interface SplitViewProps {
73
73
  collapseBelow?: number;
74
74
  /** Below this width of the layout itself (px) the aside becomes a toggle. */
75
75
  asideCollapseBelow?: number;
76
+ /**
77
+ * The narrowest the open item's pane may be beside an aside (px). When the list and the aside would leave it less,
78
+ * the aside becomes its toggle and Drawer, so the item keeps the room its content needs: the aside is the secondary
79
+ * content (round 27, R27-13, the owner: "which information is more important"). An item that holds a wide table or a
80
+ * card grid may need more: pass it.
81
+ */
82
+ detailMinWidth?: number;
76
83
  /** A bordered, rounded surface (inside a page with padding) instead of edge to edge (filling <main>). */
77
84
  bordered?: boolean;
78
85
  /** "fill": the page root, flush to the shell, h-full against main, each pane owns its inset. "page" (default): inside a PageContainer under a PageHeader. */
@@ -90,6 +97,6 @@ interface SplitViewProps {
90
97
  * The page owns the selection and assembles the contents; keyboard movement
91
98
  * through the list is the list's job (StackedList selectedId).
92
99
  */
93
- declare function SplitView({ list, detail, aside, asideTitle, detailOpen, onDetailClose, detailMode, closedDetail, closeLabel, backLabel, listWidth, listMinWidth, asideWidth, collapseBelow, asideCollapseBelow, bordered, frame, "aria-label": ariaLabel, className, }: SplitViewProps): react_jsx_runtime.JSX.Element;
100
+ declare function SplitView({ list, detail, aside, asideTitle, detailOpen, onDetailClose, detailMode, closedDetail, closeLabel, backLabel, listWidth, listMinWidth, asideWidth, collapseBelow, asideCollapseBelow, detailMinWidth, bordered, frame, "aria-label": ariaLabel, className, }: SplitViewProps): react_jsx_runtime.JSX.Element;
94
101
 
95
102
  export { ResizableHandle, ResizablePanel, ResizablePanelGroup, SplitView, type SplitViewFrame, type SplitViewProps };
package/dist/resizable.js CHANGED
@@ -1,12 +1,12 @@
1
- import { useCompact, useAppShell, Button, Drawer } from './chunk-CIVSNXFG.js';
1
+ import { useCompact, useElementWidth, useBelow, useAppShell, Button, Drawer } from './chunk-5PDTHKW3.js';
2
2
  import { isTabStop, tabStops, rememberFocus } from './chunk-JXANCXK6.js';
3
3
  import { SplitPaneCtx } from './chunk-TKVTZWBN.js';
4
4
  import { useBreakpoint } from './chunk-CRQ6KCRD.js';
5
- import './chunk-EMCSARQO.js';
5
+ import './chunk-ILIODA7W.js';
6
6
  import './chunk-7Q46JXSK.js';
7
7
  import './chunk-WH2FUFAC.js';
8
8
  import { Icon } from './chunk-4LDYR7QP.js';
9
- import { cn } from './chunk-4KVFH5KC.js';
9
+ import { cn } from './chunk-M2QRNBDQ.js';
10
10
  import { Group, Panel, Separator } from 'react-resizable-panels';
11
11
  import { createContext, useContext, useRef, useState, useLayoutEffect } from 'react';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -89,6 +89,7 @@ function ResizableHandle({
89
89
  }
90
90
  );
91
91
  }
92
+ var THRESHOLD_BAND = 17;
92
93
  function SplitView({
93
94
  list,
94
95
  detail,
@@ -105,25 +106,36 @@ function SplitView({
105
106
  asideWidth = 25,
106
107
  collapseBelow = 768,
107
108
  asideCollapseBelow = 1024,
109
+ detailMinWidth = 480,
108
110
  bordered = false,
109
111
  frame = "page",
110
112
  "aria-label": ariaLabel,
111
113
  className
112
114
  }) {
113
115
  const rootRef = useRef(null);
114
- const compact = useCompact(rootRef, collapseBelow);
115
- const asideCollapsed = useCompact(rootRef, asideCollapseBelow);
116
+ const compact = useCompact(rootRef, collapseBelow, THRESHOLD_BAND);
117
+ const layoutWidth = useElementWidth(rootRef);
118
+ const [shares, setShares] = useState({});
119
+ const listShare = shares.list ?? listWidth;
120
+ const asideShare = shares.aside ?? asideWidth;
121
+ const itemShare = (100 - listShare - asideShare) / 100;
122
+ const itemBesideAside = aside ? layoutWidth * itemShare : 0;
123
+ const itemSqueezed = useBelow(itemBesideAside, detailMinWidth, THRESHOLD_BAND * Math.max(0, itemShare));
124
+ const asideCollapsed = useBelow(layoutWidth, asideCollapseBelow, THRESHOLD_BAND) || itemSqueezed;
116
125
  const [asideWantsOpen, setAsideWantsOpen] = useState(false);
117
126
  const hiding = closedDetail === "hidden";
118
127
  const showAside = !!aside && (!hiding || detailOpen);
119
128
  if (hiding && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);
129
+ if (compact && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);
120
130
  const asideOpen = asideCollapsed && asideWantsOpen;
121
131
  const region = ariaLabel ? { role: "region", "aria-label": ariaLabel } : {};
122
132
  const isFill = frame === "fill";
123
133
  const shell = useAppShell();
124
134
  const fillStyle = isFill && shell?.hasFooter ? { height: "var(--ds-main-height, 100%)" } : void 0;
135
+ const itemSurface = isFill ? "bg-[var(--ds-bg-surface)]" : void 0;
125
136
  const surface = cn(
126
- "h-full min-h-0 w-full bg-[var(--ds-bg-surface)]",
137
+ "h-full min-h-0 w-full",
138
+ !isFill && "bg-[var(--ds-bg-surface)]",
127
139
  isFill && "rounded-none border-0",
128
140
  bordered && !isFill && "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden",
129
141
  className
@@ -162,10 +174,17 @@ function SplitView({
162
174
  };
163
175
  if (compact) {
164
176
  const showDetail2 = detailMode === "stack" && detailOpen;
165
- return /* @__PURE__ */ jsx("div", { ref: rootRef, "data-split-view": showDetail2 ? "detail" : "list", "data-frame": frame, ...region, className: cn(surface, "flex flex-col"), style: fillStyle, children: showDetail2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
166
- /* @__PURE__ */ jsx("div", { className: "shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "arrow-left", size: 14 }), onClick: onDetailClose, children: backLabel }) }),
167
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: detail }) })
168
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: list }) }) });
177
+ const phoneAside = showDetail2 && !!aside;
178
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-split-view": showDetail2 ? "detail" : "list", "data-frame": frame, ...region, className: cn(surface, "flex flex-col"), style: fillStyle, children: [
179
+ showDetail2 ? /* @__PURE__ */ jsxs("div", { className: cn("flex-1 min-h-0 flex flex-col", itemSurface), children: [
180
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between gap-ds-2 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", children: [
181
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "arrow-left", size: 14 }), onClick: onDetailClose, children: backLabel }),
182
+ phoneAside && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "info", size: 14 }), onClick: () => setAsideWantsOpen(true), "aria-expanded": asideWantsOpen, children: asideTitle })
183
+ ] }),
184
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: detail }) })
185
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: list }) }),
186
+ phoneAside && /* @__PURE__ */ jsx(Drawer, { open: asideWantsOpen, onClose: () => setAsideWantsOpen(false), title: asideTitle, position: "right", children: aside })
187
+ ] });
169
188
  }
170
189
  const asideButton = showAside && asideCollapsed ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", iconLeft: /* @__PURE__ */ jsx(Icon, { name: "info", size: 14 }), onClick: () => setAsideWantsOpen(true), "aria-expanded": asideOpen, children: asideTitle }) : null;
171
190
  const closeButton = hiding && onDetailClose ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": closeLabel, onClick: onDetailClose, children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) }) : null;
@@ -181,23 +200,24 @@ function SplitView({
181
200
  direction: "horizontal",
182
201
  stackOnMobile: false,
183
202
  className: "h-full",
184
- onLayoutChanged: hiding ? (layout) => {
185
- if (layout.detail != null && layout.list != null) setListPct(layout.list);
186
- } : void 0,
203
+ onLayoutChanged: (layout) => {
204
+ setShares((prev) => prev.list === (layout.list ?? prev.list) && prev.aside === (layout.aside ?? prev.aside) ? prev : { list: layout.list ?? prev.list, aside: layout.aside ?? prev.aside });
205
+ if (hiding && layout.detail != null && layout.list != null) setListPct(layout.list);
206
+ },
187
207
  children: [
188
208
  /* @__PURE__ */ jsx(ResizablePanel, { id: "list", defaultSize: `${hiding ? detailOpen ? listPct : 100 : listWidth}%`, minSize: `${listMinWidth}%`, children: /* @__PURE__ */ jsx("div", { "data-pane": "list", className: "h-full min-h-0 overflow-y-auto", onFocus: hiding ? (e) => {
189
209
  lastInList.current = e.target;
190
210
  } : void 0, children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: list }) }) }),
191
211
  showDetail && /* @__PURE__ */ jsxs(Fragment, { children: [
192
212
  /* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
193
- /* @__PURE__ */ jsx(ResizablePanel, { id: "detail", minSize: "30%", children: /* @__PURE__ */ jsxs("div", { ref: paneRef, "data-pane": "detail", className: "h-full min-h-0 flex flex-col", onKeyDown: hiding ? onPaneKeyDown : void 0, children: [
213
+ /* @__PURE__ */ jsx(ResizablePanel, { id: "detail", minSize: "30%", children: /* @__PURE__ */ jsxs("div", { ref: paneRef, "data-pane": "detail", className: cn("h-full min-h-0 flex flex-col", itemSurface), onKeyDown: hiding ? onPaneKeyDown : void 0, children: [
194
214
  paneBar,
195
215
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: detail }) })
196
216
  ] }) })
197
217
  ] }),
198
218
  showAside && !asideCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
199
219
  /* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
200
- /* @__PURE__ */ jsx(ResizablePanel, { id: "aside", defaultSize: `${asideWidth}%`, minSize: "15%", children: /* @__PURE__ */ jsx("div", { "data-pane": "aside", className: "h-full min-h-0 overflow-y-auto p-ds-4", children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: aside }) }) })
220
+ /* @__PURE__ */ jsx(ResizablePanel, { id: "aside", defaultSize: `${asideWidth}%`, minSize: "15%", children: /* @__PURE__ */ jsx("div", { "data-pane": "aside", className: cn("h-full min-h-0 overflow-y-auto p-ds-4", itemSurface), children: /* @__PURE__ */ jsx(SplitPaneCtx, { value: true, children: aside }) }) })
201
221
  ] })
202
222
  ]
203
223
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/ResizablePanels.tsx","../../../showcase/src/components/SplitView.tsx"],"names":["PanelGroup","PanelResizeHandle","showDetail","jsx"],"mappings":"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","file":"resizable.js","sourcesContent":["import {\n Panel,\n Group as PanelGroup,\n Separator as PanelResizeHandle,\n} from \"react-resizable-panels\";\nimport { createContext, useContext, type ComponentProps } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { Icon } from \"../lib/icons\";\n\ntype PanelGroupProps = ComponentProps<typeof PanelGroup>;\n\ntype Direction = \"horizontal\" | \"vertical\";\nconst DirectionCtx = createContext<Direction>(\"horizontal\");\nconst MobileCtx = createContext(false);\n\nexport interface ResizablePanelGroupProps\n extends Omit<PanelGroupProps, \"className\" | \"orientation\"> {\n direction?: Direction;\n stackOnMobile?: boolean;\n className?: string;\n /** Map of panel id → percentage (0–100). Must be an object, never an array. Panels need matching id props. */\n defaultLayout?: { [panelId: string]: number };\n /** Called after resize ends. Receives { [panelId]: number }, not number[]. */\n onLayoutChanged?: (layout: { [panelId: string]: number }) => void;\n}\n\nexport function ResizablePanelGroup({\n className,\n direction = \"horizontal\",\n stackOnMobile = true,\n ...props\n}: ResizablePanelGroupProps) {\n const isMobile = useBreakpoint(\"sm\");\n const shouldStack = stackOnMobile && isMobile && direction === \"horizontal\";\n const effectiveDirection = shouldStack ? \"vertical\" : direction;\n\n return (\n <DirectionCtx.Provider value={effectiveDirection}>\n <MobileCtx.Provider value={isMobile}>\n <PanelGroup\n className={cn(\n \"flex h-full w-full\",\n effectiveDirection === \"vertical\" && \"flex-col\",\n className,\n )}\n orientation={effectiveDirection}\n {...props}\n />\n </MobileCtx.Provider>\n </DirectionCtx.Provider>\n );\n}\n\ntype PanelProps = ComponentProps<typeof Panel>;\n\nexport interface ResizablePanelProps extends Omit<PanelProps, \"className\"> {\n className?: string;\n id?: string;\n /** number = px. \"30%\" or \"30\" = percent. \"28rem\" / \"400px\" also valid. */\n defaultSize?: number | string;\n minSize?: number | string;\n maxSize?: number | string;\n collapsible?: boolean;\n}\n\nexport function ResizablePanel({ className, ...props }: ResizablePanelProps) {\n return <Panel className={cn(\"\", className)} {...props} />;\n}\n\ntype PanelResizeHandleProps = ComponentProps<typeof PanelResizeHandle>;\n\nexport interface ResizableHandleProps\n extends Omit<PanelResizeHandleProps, \"className\"> {\n withHandle?: boolean;\n className?: string;\n}\n\nexport function ResizableHandle({\n withHandle,\n className,\n ...props\n}: ResizableHandleProps) {\n const direction = useContext(DirectionCtx);\n const isMobile = useContext(MobileCtx);\n const isVertical = direction === \"vertical\";\n\n return (\n <PanelResizeHandle\n className={cn(\n \"relative flex items-center justify-center bg-[var(--ds-border-default)]\",\n isVertical\n ? cn(\n \"h-[var(--ds-border-width)] w-full after:absolute after:left-0 after:right-0 after:content-['']\",\n isMobile ? \"after:-top-3 after:-bottom-3\" : \"after:-top-1 after:-bottom-1\"\n )\n : cn(\n \"w-[var(--ds-border-width)] after:absolute after:inset-y-0 after:content-['']\",\n isMobile ? \"after:-left-3 after:-right-3\" : \"after:-left-1 after:-right-1\"\n ),\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"[&[data-resize-handle-state=drag]]:bg-[var(--ds-accent-primary)]\",\n \"[&[data-resize-handle-state=hover]]:bg-[var(--ds-accent-primary)]\",\n className,\n )}\n {...props}\n >\n {withHandle && (\n <div\n className={cn(\n \"z-10 flex items-center justify-center rounded-[var(--ds-radius-sm)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"bg-[var(--ds-bg-surface)]\",\n isVertical\n ? (isMobile ? \"h-4 w-8\" : \"h-3 w-4\")\n : (isMobile ? \"h-8 w-4\" : \"h-4 w-3\"),\n )}\n >\n <Icon\n name=\"grip-vertical\"\n size={isMobile ? 12 : 10}\n className={cn(\n \"text-[var(--ds-text-muted)]\",\n isVertical && \"rotate-90\",\n )}\n />\n </div>\n )}\n </PanelResizeHandle>\n );\n}\n","import { useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useCompact } from \"../lib/use-compact\";\nimport { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"./ResizablePanels\";\nimport { Button } from \"./Button\";\nimport { Drawer } from \"./Drawer\";\nimport { Icon } from \"../lib/icons\";\nimport { useAppShell } from \"./AppShell\";\nimport { SplitPaneCtx } from \"../lib/surface-context\";\nimport { isTabStop, rememberFocus, tabStops, type RememberedFocus } from \"../lib/focus\";\n\nexport type SplitViewFrame = \"fill\" | \"page\";\n\nexport interface SplitViewProps {\n /** The list (an inbox, a queue): it scrolls on its own. */\n list: ReactNode;\n /** The selected item (a thread, a record): it fills the height and scrolls on its own. */\n detail: ReactNode;\n /** Context beside the detail (a contact, properties). The pane pads it (16 px, like the Drawer it collapses into), so pass content with no padding classes (round 8). Collapses to a toggle that opens it in a Drawer when the layout is narrower than `asideCollapseBelow`. */\n aside?: ReactNode;\n /** Names the aside's toggle and Drawer. */\n asideTitle?: string;\n /**\n * Below `collapseBelow` the list shows alone; with `detailOpen` the detail replaces it, with a Back button that calls\n * `onDetailClose` (detailMode \"stack\"). The page owns the selection, so it owns this flag.\n */\n detailOpen?: boolean;\n onDetailClose?: () => void;\n /** \"stack\" (default): the detail stacks over the list below the collapse width. \"route\": the app navigates to the detail's own route instead, and this renders the list alone there. */\n detailMode?: \"stack\" | \"route\";\n /**\n * \"shown\" (default): above the collapse width the detail pane is always there — an inbox's reading pane may show a\n * placeholder. \"hidden\" (Sprint 25.7, C3): `detailOpen` decides at every width. Closed, the list takes the whole\n * width; open, the pane appears beside it at the width the user last gave it, with a close control that calls\n * `onDetailClose`. For boards, timelines and lists of records. The list stays mounted, so a board or a timeline keeps\n * its scroll and zoom.\n */\n closedDetail?: \"shown\" | \"hidden\";\n /** The open pane's close control, named for a screen reader (closedDetail=\"hidden\"). Default \"Close\". */\n closeLabel?: string;\n backLabel?: string;\n /** Percent of the width the list takes; the divider is draggable. */\n listWidth?: number;\n listMinWidth?: number;\n asideWidth?: number;\n /** Below this width of the layout itself (px) the list and detail stop sitting side by side. */\n collapseBelow?: number;\n /** Below this width of the layout itself (px) the aside becomes a toggle. */\n asideCollapseBelow?: number;\n /** A bordered, rounded surface (inside a page with padding) instead of edge to edge (filling <main>). */\n bordered?: boolean;\n /** \"fill\": the page root, flush to the shell, h-full against main, each pane owns its inset. \"page\" (default): inside a PageContainer under a PageHeader. */\n frame?: SplitViewFrame;\n \"aria-label\"?: string;\n className?: string;\n}\n\n/**\n * The reading-pane composition (Sprint 25.2, R3): one surface with dividers —\n * list, detail and an optional aside — that fills the height it is given (an\n * AppShell's <main> already gives it) and never three cards with gaps. The\n * panels compose ResizablePanelGroup, so the dividers drag. Below the collapse\n * width, measured on the layout itself, the list shows alone and the detail\n * stacks over it (or the app routes to it); the aside collapses to a toggle.\n * The page owns the selection and assembles the contents; keyboard movement\n * through the list is the list's job (StackedList selectedId).\n */\nexport function SplitView({\n list,\n detail,\n aside,\n asideTitle = \"Details\",\n detailOpen = false,\n onDetailClose,\n detailMode = \"stack\",\n closedDetail = \"shown\",\n closeLabel = \"Close\",\n backLabel = \"Back\",\n listWidth = 35,\n listMinWidth = 25,\n asideWidth = 25,\n collapseBelow = 768,\n asideCollapseBelow = 1024,\n bordered = false,\n frame = \"page\",\n \"aria-label\": ariaLabel,\n className,\n}: SplitViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompact(rootRef, collapseBelow);\n const asideCollapsed = useCompact(rootRef, asideCollapseBelow);\n const [asideWantsOpen, setAsideWantsOpen] = useState(false);\n const hiding = closedDetail === \"hidden\";\n // Hidden: the aside belongs to the open item, so it (its pane, its toggle, its Drawer) goes with the pane, and a\n // Drawer left open does not come back with the next item (render-time state adjustment).\n const showAside = !!aside && (!hiding || detailOpen);\n if (hiding && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);\n const asideOpen = asideCollapsed && asideWantsOpen;\n const region = ariaLabel ? { role: \"region\" as const, \"aria-label\": ariaLabel } : {};\n // fill: the page root, flush to the shell on every side (no border, no radius, no padding of its own); h-full\n // resolves against <main> because the shell is a flex column down to it. page: inside a PageContainer under a\n // PageHeader, where `bordered` draws the surface. Each pane owns its own inset either way.\n const isFill = frame === \"fill\";\n // In a shell with an app footer the children wrapper has no definite height, so h-full cannot resolve; the shell\n // publishes main's height and fill takes it (the footer then sits below the fold, as it must under a full pane).\n const shell = useAppShell();\n const fillStyle = isFill && shell?.hasFooter ? { height: \"var(--ds-main-height, 100%)\" } : undefined;\n const surface = cn(\n \"h-full min-h-0 w-full bg-[var(--ds-bg-surface)]\",\n isFill && \"rounded-none border-0\",\n bordered && !isFill && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden\",\n className,\n );\n\n // closedDetail=\"hidden\" (C3): the pane opens beside the list at the width the user last gave it; closing it returns\n // focus to the item the pane shows, or its neighbour if that item is gone (D14(e)): the list's selected item, else\n // the last list item that took focus, checked on each render while the pane is open, so switching items is followed.\n // A selection kept in the URL changes the path, so the item is remembered whatever the route does. With no item\n // (opened on load, or from outside the list) focus goes to the list's first Tab stop. No overlay layer: an\n // assistant opened earlier keeps Escape.\n const [listPct, setListPct] = useState(listWidth);\n const paneRef = useRef<HTMLDivElement>(null);\n const shown = useRef<RememberedFocus | null>(null);\n const lastInList = useRef<HTMLElement | null>(null);\n const wasOpen = useRef(false);\n useLayoutEffect(() => {\n if (!hiding) return;\n const listPane = rootRef.current?.querySelector<HTMLElement>('[data-pane=\"list\"]') ?? null;\n if (detailOpen) {\n wasOpen.current = true;\n const selected = listPane?.querySelector<HTMLElement>('[aria-current=\"true\"], [aria-selected=\"true\"]');\n const last = lastInList.current;\n const item = (selected && (isTabStop(selected) ? selected : tabStops(selected)[0]))\n || (last?.isConnected && listPane?.contains(last) ? last : null);\n if (item && item !== shown.current?.el) shown.current = rememberFocus(item, { ignoreRoute: true });\n return;\n }\n if (!wasOpen.current) return;\n wasOpen.current = false;\n const r = shown.current;\n shown.current = null;\n const active = document.activeElement;\n if (active && active !== document.body && active.isConnected) return;\n const first = listPane ? tabStops(listPane)[0] : undefined;\n if (!r?.restore(first)) first?.focus();\n });\n const onPaneKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !onDetailClose) return;\n const t = e.target as HTMLElement;\n // the pane's own DOM only — a portalled popup inside it bubbles here in React's tree — and never from a field\n if (!paneRef.current?.contains(t) || t.closest(\"input, textarea, select, [contenteditable='true']\")) return;\n e.preventDefault();\n onDetailClose();\n };\n\n if (compact) {\n const showDetail = detailMode === \"stack\" && detailOpen;\n return (\n <div ref={rootRef} data-split-view={showDetail ? \"detail\" : \"list\"} data-frame={frame} {...region} className={cn(surface, \"flex flex-col\")} style={fillStyle}>\n {showDetail ? (\n <>\n <div className=\"shrink-0 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"arrow-left\" size={14} />} onClick={onDetailClose}>{backLabel}</Button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </>\n ) : (\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{list}</SplitPaneCtx></div>\n )}\n </div>\n );\n }\n\n const asideButton = showAside && asideCollapsed\n ? <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"info\" size={14} />} onClick={() => setAsideWantsOpen(true)} aria-expanded={asideOpen}>{asideTitle}</Button>\n : null;\n const closeButton = hiding && onDetailClose\n ? <Button variant=\"ghost\" size=\"icon-sm\" aria-label={closeLabel} onClick={onDetailClose}><Icon name=\"close\" size={14} /></Button>\n : null;\n const paneBar = asideButton || closeButton ? (\n <div className=\"shrink-0 flex items-center justify-end gap-ds-1 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n {asideButton}\n {closeButton}\n </div>\n ) : null;\n const showDetail = !hiding || detailOpen;\n\n return (\n <div ref={rootRef} data-split-view={hiding && !detailOpen ? \"list\" : \"split\"} data-frame={frame} {...region} className={surface} style={fillStyle}>\n <ResizablePanelGroup\n direction=\"horizontal\"\n stackOnMobile={false}\n className=\"h-full\"\n // the width the user gives the list is kept, so a closed pane reopens at it\n onLayoutChanged={hiding ? (layout) => { if (layout.detail != null && layout.list != null) setListPct(layout.list); } : undefined}\n >\n <ResizablePanel id=\"list\" defaultSize={`${hiding ? (detailOpen ? listPct : 100) : listWidth}%`} minSize={`${listMinWidth}%`}>\n <div data-pane=\"list\" className=\"h-full min-h-0 overflow-y-auto\" onFocus={hiding ? (e) => { lastInList.current = e.target; } : undefined}>\n <SplitPaneCtx value={true}>{list}</SplitPaneCtx>\n </div>\n </ResizablePanel>\n {showDetail && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"detail\" minSize=\"30%\">\n <div ref={paneRef} data-pane=\"detail\" className=\"h-full min-h-0 flex flex-col\" onKeyDown={hiding ? onPaneKeyDown : undefined}>\n {paneBar}\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </div>\n </ResizablePanel>\n </>\n )}\n {showAside && !asideCollapsed && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"aside\" defaultSize={`${asideWidth}%`} minSize=\"15%\">\n <div data-pane=\"aside\" className=\"h-full min-h-0 overflow-y-auto p-ds-4\"><SplitPaneCtx value={true}>{aside}</SplitPaneCtx></div>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n {showAside && asideCollapsed && (\n <Drawer open={asideOpen} onClose={() => setAsideWantsOpen(false)} title={asideTitle} position=\"right\">\n {aside}\n </Drawer>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/ResizablePanels.tsx","../../../showcase/src/components/SplitView.tsx"],"names":["PanelGroup","PanelResizeHandle","showDetail","jsx"],"mappings":";;;;;;;;;;;;;AAaA,IAAM,YAAA,GAAe,cAAyB,YAAY,CAAA;AAC1D,IAAM,SAAA,GAAY,cAAc,KAAK,CAAA;AAa9B,SAAS,mBAAA,CAAoB;AAAA,EAClC,SAAA;AAAA,EACA,SAAA,GAAY,YAAA;AAAA,EACZ,aAAA,GAAgB,IAAA;AAAA,EAChB,GAAG;AACL,CAAA,EAA6B;AAC3B,EAAA,MAAM,QAAA,GAAW,cAAc,IAAI,CAAA;AACnC,EAAA,MAAM,WAAA,GAAc,aAAA,IAAiB,QAAA,IAAY,SAAA,KAAc,YAAA;AAC/D,EAAA,MAAM,kBAAA,GAAqB,cAAc,UAAA,GAAa,SAAA;AAEtD,EAAA,uBACE,GAAA,CAAC,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EAAO,kBAAA,EAC5B,QAAA,kBAAA,GAAA,CAAC,SAAA,CAAU,QAAA,EAAV,EAAmB,KAAA,EAAO,QAAA,EACzB,QAAA,kBAAA,GAAA;AAAA,IAACA,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,oBAAA;AAAA,QACA,uBAAuB,UAAA,IAAc,UAAA;AAAA,QACrC;AAAA,OACF;AAAA,MACA,WAAA,EAAa,kBAAA;AAAA,MACZ,GAAG;AAAA;AAAA,KAER,CAAA,EACF,CAAA;AAEJ;AAcO,SAAS,cAAA,CAAe,EAAE,SAAA,EAAW,GAAG,OAAM,EAAwB;AAC3E,EAAA,uBAAO,GAAA,CAAC,SAAM,SAAA,EAAW,EAAA,CAAG,IAAI,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO,CAAA;AACzD;AAUO,SAAS,eAAA,CAAgB;AAAA,EAC9B,UAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAyB;AACvB,EAAA,MAAM,SAAA,GAAY,WAAW,YAAY,CAAA;AACzC,EAAA,MAAM,QAAA,GAAW,WAAW,SAAS,CAAA;AACrC,EAAA,MAAM,aAAa,SAAA,KAAc,UAAA;AAEjC,EAAA,uBACE,GAAA;AAAA,IAACC,SAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,yEAAA;AAAA,QACA,UAAA,GACI,EAAA;AAAA,UACE,gGAAA;AAAA,UACA,WAAW,8BAAA,GAAiC;AAAA,SAC9C,GACA,EAAA;AAAA,UACE,8EAAA;AAAA,UACA,WAAW,8BAAA,GAAiC;AAAA,SAC9C;AAAA,QACJ,0EAAA;AAAA,QACA,kEAAA;AAAA,QACA,mEAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEH,QAAA,EAAA,UAAA,oBACC,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAW,EAAA;AAAA,YACT,qEAAA;AAAA,YACA,0EAAA;AAAA,YACA,2BAAA;AAAA,YACA,UAAA,GACK,QAAA,GAAW,SAAA,GAAY,SAAA,GACvB,WAAW,SAAA,GAAY;AAAA,WAC9B;AAAA,UAEA,QAAA,kBAAA,GAAA;AAAA,YAAC,IAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,eAAA;AAAA,cACL,IAAA,EAAM,WAAW,EAAA,GAAK,EAAA;AAAA,cACtB,SAAA,EAAW,EAAA;AAAA,gBACT,6BAAA;AAAA,gBACA,UAAA,IAAc;AAAA;AAChB;AAAA;AACF;AAAA;AACF;AAAA,GAEJ;AAEJ;AC/GA,IAAM,cAAA,GAAiB,EAAA;AA+DhB,SAAS,SAAA,CAAU;AAAA,EACxB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA,GAAa,SAAA;AAAA,EACb,UAAA,GAAa,KAAA;AAAA,EACb,aAAA;AAAA,EACA,UAAA,GAAa,OAAA;AAAA,EACb,YAAA,GAAe,OAAA;AAAA,EACf,UAAA,GAAa,OAAA;AAAA,EACb,SAAA,GAAY,MAAA;AAAA,EACZ,SAAA,GAAY,EAAA;AAAA,EACZ,YAAA,GAAe,EAAA;AAAA,EACf,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,GAAA;AAAA,EAChB,kBAAA,GAAqB,IAAA;AAAA,EACrB,cAAA,GAAiB,GAAA;AAAA,EACjB,QAAA,GAAW,KAAA;AAAA,EACX,KAAA,GAAQ,MAAA;AAAA,EACR,YAAA,EAAc,SAAA;AAAA,EACd;AACF,CAAA,EAAmB;AACjB,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAS,aAAA,EAAe,cAAc,CAAA;AACjE,EAAA,MAAM,WAAA,GAAc,gBAAgB,OAAO,CAAA;AAG3C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA,CAA4C,EAAE,CAAA;AAC1E,EAAA,MAAM,SAAA,GAAY,OAAO,IAAA,IAAQ,SAAA;AACjC,EAAA,MAAM,UAAA,GAAa,OAAO,KAAA,IAAS,UAAA;AAEnC,EAAA,MAAM,SAAA,GAAA,CAAa,GAAA,GAAM,SAAA,GAAY,UAAA,IAAc,GAAA;AACnD,EAAA,MAAM,eAAA,GAAkB,KAAA,GAAQ,WAAA,GAAc,SAAA,GAAY,CAAA;AAE1D,EAAA,MAAM,YAAA,GAAe,SAAS,eAAA,EAAiB,cAAA,EAAgB,iBAAiB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAS,CAAC,CAAA;AACtG,EAAA,MAAM,cAAA,GAAiB,QAAA,CAAS,WAAA,EAAa,kBAAA,EAAoB,cAAc,CAAA,IAAK,YAAA;AACpF,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1D,EAAA,MAAM,SAAS,YAAA,KAAiB,QAAA;AAGhC,EAAA,MAAM,SAAA,GAAY,CAAC,CAAC,KAAA,KAAU,CAAC,MAAA,IAAU,UAAA,CAAA;AACzC,EAAA,IAAI,MAAA,IAAU,CAAC,UAAA,IAAc,cAAA,oBAAkC,KAAK,CAAA;AAEpE,EAAA,IAAI,OAAA,IAAW,CAAC,UAAA,IAAc,cAAA,oBAAkC,KAAK,CAAA;AACrE,EAAA,MAAM,YAAY,cAAA,IAAkB,cAAA;AACpC,EAAA,MAAM,MAAA,GAAS,YAAY,EAAE,IAAA,EAAM,UAAmB,YAAA,EAAc,SAAA,KAAc,EAAC;AAInF,EAAA,MAAM,SAAS,KAAA,KAAU,MAAA;AAGzB,EAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,EAAA,MAAM,YAAY,MAAA,IAAU,KAAA,EAAO,YAAY,EAAE,MAAA,EAAQ,+BAA8B,GAAI,MAAA;AAI3F,EAAA,MAAM,WAAA,GAAc,SAAS,2BAAA,GAA8B,MAAA;AAC3D,EAAA,MAAM,OAAA,GAAU,EAAA;AAAA,IACd,uBAAA;AAAA,IACA,CAAC,MAAA,IAAU,2BAAA;AAAA,IACX,MAAA,IAAU,uBAAA;AAAA,IACV,QAAA,IAAY,CAAC,MAAA,IAAU,uHAAA;AAAA,IACvB;AAAA,GACF;AAQA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,SAAS,CAAA;AAChD,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,KAAA,GAAQ,OAA+B,IAAI,CAAA;AACjD,EAAA,MAAM,UAAA,GAAa,OAA2B,IAAI,CAAA;AAClD,EAAA,MAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC5B,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,OAAA,EAAS,aAAA,CAA2B,oBAAoB,CAAA,IAAK,IAAA;AACtF,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,MAAA,MAAM,QAAA,GAAW,QAAA,EAAU,aAAA,CAA2B,+CAA+C,CAAA;AACrG,MAAA,MAAM,OAAO,UAAA,CAAW,OAAA;AACxB,MAAA,MAAM,OAAQ,QAAA,KAAa,SAAA,CAAU,QAAQ,CAAA,GAAI,WAAW,QAAA,CAAS,QAAQ,CAAA,CAAE,CAAC,OAC1E,IAAA,EAAM,WAAA,IAAe,UAAU,QAAA,CAAS,IAAI,IAAI,IAAA,GAAO,IAAA,CAAA;AAC7D,MAAA,IAAI,IAAA,IAAQ,IAAA,KAAS,KAAA,CAAM,OAAA,EAAS,EAAA,EAAI,KAAA,CAAM,OAAA,GAAU,aAAA,CAAc,IAAA,EAAM,EAAE,WAAA,EAAa,IAAA,EAAM,CAAA;AACjG,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACtB,IAAA,OAAA,CAAQ,OAAA,GAAU,KAAA;AAClB,IAAA,MAAM,IAAI,KAAA,CAAM,OAAA;AAChB,IAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,IAAA,MAAM,SAAS,QAAA,CAAS,aAAA;AACxB,IAAA,IAAI,MAAA,IAAU,MAAA,KAAW,QAAA,CAAS,IAAA,IAAQ,OAAO,WAAA,EAAa;AAC9D,IAAA,MAAM,QAAQ,QAAA,GAAW,QAAA,CAAS,QAAQ,CAAA,CAAE,CAAC,CAAA,GAAI,MAAA;AACjD,IAAA,IAAI,CAAC,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA,SAAU,KAAA,EAAM;AAAA,EACvC,CAAC,CAAA;AACD,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAqC;AAC1D,IAAA,IAAI,EAAE,GAAA,KAAQ,QAAA,IAAY,CAAA,CAAE,gBAAA,IAAoB,CAAC,aAAA,EAAe;AAChE,IAAA,MAAM,IAAI,CAAA,CAAE,MAAA;AAEZ,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS,QAAA,CAAS,CAAC,CAAA,IAAK,CAAA,CAAE,OAAA,CAAQ,mDAAmD,CAAA,EAAG;AACrG,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAEA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAMC,WAAAA,GAAa,eAAe,OAAA,IAAW,UAAA;AAG7C,IAAA,MAAM,UAAA,GAAaA,WAAAA,IAAc,CAAC,CAAC,KAAA;AACnC,IAAA,4BACG,KAAA,EAAA,EAAI,GAAA,EAAK,SAAS,iBAAA,EAAiBA,WAAAA,GAAa,WAAW,MAAA,EAAQ,YAAA,EAAY,KAAA,EAAQ,GAAG,QAAQ,SAAA,EAAW,EAAA,CAAG,SAAS,eAAe,CAAA,EAAG,OAAO,SAAA,EAChJ,QAAA,EAAA;AAAA,MAAAA,8BACC,IAAA,CAAC,KAAA,EAAA,EAAI,WAAW,EAAA,CAAG,8BAAA,EAAgC,WAAW,CAAA,EAC5D,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,+IAAA,EACb,QAAA,EAAA;AAAA,0BAAAC,IAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,OAAA,EAAQ,IAAA,EAAK,MAAK,QAAA,kBAAUA,GAAAA,CAAC,IAAA,EAAA,EAAK,MAAK,YAAA,EAAa,IAAA,EAAM,IAAI,CAAA,EAAI,OAAA,EAAS,eAAgB,QAAA,EAAA,SAAA,EAAU,CAAA;AAAA,UACpH,UAAA,oBAAcA,GAAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,SAAQ,IAAA,EAAK,IAAA,EAAK,QAAA,kBAAUA,GAAAA,CAAC,IAAA,EAAA,EAAK,MAAK,MAAA,EAAO,IAAA,EAAM,EAAA,EAAI,CAAA,EAAI,OAAA,EAAS,MAAM,kBAAkB,IAAI,CAAA,EAAG,eAAA,EAAe,cAAA,EAAiB,QAAA,EAAA,UAAA,EAAW;AAAA,SAAA,EAChL,CAAA;AAAA,wBACAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gCAAA,EAAiC,QAAA,kBAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,KAAA,EAAO,IAAA,EAAO,QAAA,EAAA,MAAA,EAAO,CAAA,EAAe;AAAA,OAAA,EACpG,CAAA,mBAEAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gCAAA,EAAiC,QAAA,kBAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,KAAA,EAAO,IAAA,EAAO,QAAA,EAAA,IAAA,EAAK,CAAA,EAAe,CAAA;AAAA,MAEjG,UAAA,oBACCA,GAAAA,CAAC,MAAA,EAAA,EAAO,MAAM,cAAA,EAAgB,OAAA,EAAS,MAAM,iBAAA,CAAkB,KAAK,CAAA,EAAG,KAAA,EAAO,UAAA,EAAY,QAAA,EAAS,SAChG,QAAA,EAAA,KAAA,EACH;AAAA,KAAA,EAEJ,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,WAAA,GAAc,SAAA,IAAa,cAAA,mBAC7BA,GAAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,OAAA,EAAQ,IAAA,EAAK,IAAA,EAAK,QAAA,kBAAUA,GAAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAK,MAAA,EAAO,IAAA,EAAM,EAAA,EAAI,CAAA,EAAI,OAAA,EAAS,MAAM,iBAAA,CAAkB,IAAI,CAAA,EAAG,eAAA,EAAe,SAAA,EAAY,QAAA,EAAA,UAAA,EAAW,CAAA,GAC1J,IAAA;AACJ,EAAA,MAAM,WAAA,GAAc,UAAU,aAAA,mBAC1BA,IAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,OAAA,EAAQ,IAAA,EAAK,SAAA,EAAU,YAAA,EAAY,YAAY,OAAA,EAAS,aAAA,EAAe,0BAAAA,GAAAA,CAAC,IAAA,EAAA,EAAK,MAAK,OAAA,EAAQ,IAAA,EAAM,EAAA,EAAI,CAAA,EAAE,CAAA,GACtH,IAAA;AACJ,EAAA,MAAM,UAAU,WAAA,IAAe,WAAA,mBAC7B,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2IAAA,EACZ,QAAA,EAAA;AAAA,IAAA,WAAA;AAAA,IACA;AAAA,GAAA,EACH,CAAA,GACE,IAAA;AACJ,EAAA,MAAM,UAAA,GAAa,CAAC,MAAA,IAAU,UAAA;AAE9B,EAAA,4BACG,KAAA,EAAA,EAAI,GAAA,EAAK,OAAA,EAAS,iBAAA,EAAiB,UAAU,CAAC,UAAA,GAAa,MAAA,GAAS,OAAA,EAAS,cAAY,KAAA,EAAQ,GAAG,QAAQ,SAAA,EAAW,OAAA,EAAS,OAAO,SAAA,EACtI,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,mBAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,YAAA;AAAA,QACV,aAAA,EAAe,KAAA;AAAA,QACf,SAAA,EAAU,QAAA;AAAA,QAEV,eAAA,EAAiB,CAAC,MAAA,KAAW;AAC3B,UAAA,SAAA,CAAU,CAAC,IAAA,KAAU,IAAA,CAAK,IAAA,MAAU,MAAA,CAAO,IAAA,IAAQ,IAAA,CAAK,IAAA,CAAA,IAAS,IAAA,CAAK,KAAA,MAAW,MAAA,CAAO,KAAA,IAAS,IAAA,CAAK,SAAS,IAAA,GAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAM,KAAA,EAAO,MAAA,CAAO,KAAA,IAAS,IAAA,CAAK,KAAA,EAAQ,CAAA;AAC5L,UAAA,IAAI,MAAA,IAAU,OAAO,MAAA,IAAU,IAAA,IAAQ,OAAO,IAAA,IAAQ,IAAA,EAAM,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA;AAAA,QACpF,CAAA;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAAA,GAAAA,CAAC,cAAA,EAAA,EAAe,EAAA,EAAG,MAAA,EAAO,WAAA,EAAa,CAAA,EAAG,MAAA,GAAU,UAAA,GAAa,OAAA,GAAU,GAAA,GAAO,SAAS,CAAA,CAAA,CAAA,EAAK,OAAA,EAAS,GAAG,YAAY,CAAA,CAAA,CAAA,EACtH,QAAA,kBAAAA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAA,EAAU,MAAA,EAAO,SAAA,EAAU,gCAAA,EAAiC,OAAA,EAAS,MAAA,GAAS,CAAC,CAAA,KAAM;AAAE,YAAA,UAAA,CAAW,UAAU,CAAA,CAAE,MAAA;AAAA,UAAQ,CAAA,GAAI,QAC7H,QAAA,kBAAAA,GAAAA,CAAC,gBAAa,KAAA,EAAO,IAAA,EAAO,QAAA,EAAA,IAAA,EAAK,CAAA,EACnC,CAAA,EACF,CAAA;AAAA,UACC,8BACC,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAA,GAAAA,CAAC,eAAA,EAAA,EAAgB,UAAA,EAAU,IAAA,EAAC,CAAA;AAAA,4BAC5BA,IAAC,cAAA,EAAA,EAAe,EAAA,EAAG,UAAS,OAAA,EAAQ,KAAA,EAClC,+BAAC,KAAA,EAAA,EAAI,GAAA,EAAK,SAAS,WAAA,EAAU,QAAA,EAAS,WAAW,EAAA,CAAG,8BAAA,EAAgC,WAAW,CAAA,EAAG,SAAA,EAAW,MAAA,GAAS,aAAA,GAAgB,MAAA,EACnI,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,8BACDA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gCAAA,EAAiC,QAAA,kBAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,KAAA,EAAO,IAAA,EAAO,QAAA,EAAA,MAAA,EAAO,CAAA,EAAe;AAAA,aAAA,EACpG,CAAA,EACF;AAAA,WAAA,EACF,CAAA;AAAA,UAED,SAAA,IAAa,CAAC,cAAA,oBACb,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAA,GAAAA,CAAC,eAAA,EAAA,EAAgB,UAAA,EAAU,IAAA,EAAC,CAAA;AAAA,4BAC5BA,GAAAA,CAAC,cAAA,EAAA,EAAe,EAAA,EAAG,OAAA,EAAQ,WAAA,EAAa,CAAA,EAAG,UAAU,CAAA,CAAA,CAAA,EAAK,OAAA,EAAQ,KAAA,EAChE,QAAA,kBAAAA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAA,EAAU,OAAA,EAAQ,SAAA,EAAW,EAAA,CAAG,uCAAA,EAAyC,WAAW,CAAA,EAAG,QAAA,kBAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,KAAA,EAAO,IAAA,EAAO,QAAA,EAAA,KAAA,EAAM,CAAA,EAAe,CAAA,EAC/I;AAAA,WAAA,EACF;AAAA;AAAA;AAAA,KAEJ;AAAA,IACC,aAAa,cAAA,oBACZA,GAAAA,CAAC,MAAA,EAAA,EAAO,MAAM,SAAA,EAAW,OAAA,EAAS,MAAM,iBAAA,CAAkB,KAAK,CAAA,EAAG,KAAA,EAAO,UAAA,EAAY,QAAA,EAAS,SAC3F,QAAA,EAAA,KAAA,EACH;AAAA,GAAA,EAEJ,CAAA;AAEJ","file":"resizable.js","sourcesContent":["import {\n Panel,\n Group as PanelGroup,\n Separator as PanelResizeHandle,\n} from \"react-resizable-panels\";\nimport { createContext, useContext, type ComponentProps } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { Icon } from \"../lib/icons\";\n\ntype PanelGroupProps = ComponentProps<typeof PanelGroup>;\n\ntype Direction = \"horizontal\" | \"vertical\";\nconst DirectionCtx = createContext<Direction>(\"horizontal\");\nconst MobileCtx = createContext(false);\n\nexport interface ResizablePanelGroupProps\n extends Omit<PanelGroupProps, \"className\" | \"orientation\"> {\n direction?: Direction;\n stackOnMobile?: boolean;\n className?: string;\n /** Map of panel id → percentage (0–100). Must be an object, never an array. Panels need matching id props. */\n defaultLayout?: { [panelId: string]: number };\n /** Called after resize ends. Receives { [panelId]: number }, not number[]. */\n onLayoutChanged?: (layout: { [panelId: string]: number }) => void;\n}\n\nexport function ResizablePanelGroup({\n className,\n direction = \"horizontal\",\n stackOnMobile = true,\n ...props\n}: ResizablePanelGroupProps) {\n const isMobile = useBreakpoint(\"sm\");\n const shouldStack = stackOnMobile && isMobile && direction === \"horizontal\";\n const effectiveDirection = shouldStack ? \"vertical\" : direction;\n\n return (\n <DirectionCtx.Provider value={effectiveDirection}>\n <MobileCtx.Provider value={isMobile}>\n <PanelGroup\n className={cn(\n \"flex h-full w-full\",\n effectiveDirection === \"vertical\" && \"flex-col\",\n className,\n )}\n orientation={effectiveDirection}\n {...props}\n />\n </MobileCtx.Provider>\n </DirectionCtx.Provider>\n );\n}\n\ntype PanelProps = ComponentProps<typeof Panel>;\n\nexport interface ResizablePanelProps extends Omit<PanelProps, \"className\"> {\n className?: string;\n id?: string;\n /** number = px. \"30%\" or \"30\" = percent. \"28rem\" / \"400px\" also valid. */\n defaultSize?: number | string;\n minSize?: number | string;\n maxSize?: number | string;\n collapsible?: boolean;\n}\n\nexport function ResizablePanel({ className, ...props }: ResizablePanelProps) {\n return <Panel className={cn(\"\", className)} {...props} />;\n}\n\ntype PanelResizeHandleProps = ComponentProps<typeof PanelResizeHandle>;\n\nexport interface ResizableHandleProps\n extends Omit<PanelResizeHandleProps, \"className\"> {\n withHandle?: boolean;\n className?: string;\n}\n\nexport function ResizableHandle({\n withHandle,\n className,\n ...props\n}: ResizableHandleProps) {\n const direction = useContext(DirectionCtx);\n const isMobile = useContext(MobileCtx);\n const isVertical = direction === \"vertical\";\n\n return (\n <PanelResizeHandle\n className={cn(\n \"relative flex items-center justify-center bg-[var(--ds-border-default)]\",\n isVertical\n ? cn(\n \"h-[var(--ds-border-width)] w-full after:absolute after:left-0 after:right-0 after:content-['']\",\n isMobile ? \"after:-top-3 after:-bottom-3\" : \"after:-top-1 after:-bottom-1\"\n )\n : cn(\n \"w-[var(--ds-border-width)] after:absolute after:inset-y-0 after:content-['']\",\n isMobile ? \"after:-left-3 after:-right-3\" : \"after:-left-1 after:-right-1\"\n ),\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"[&[data-resize-handle-state=drag]]:bg-[var(--ds-accent-primary)]\",\n \"[&[data-resize-handle-state=hover]]:bg-[var(--ds-accent-primary)]\",\n className,\n )}\n {...props}\n >\n {withHandle && (\n <div\n className={cn(\n \"z-10 flex items-center justify-center rounded-[var(--ds-radius-sm)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"bg-[var(--ds-bg-surface)]\",\n isVertical\n ? (isMobile ? \"h-4 w-8\" : \"h-3 w-4\")\n : (isMobile ? \"h-8 w-4\" : \"h-4 w-3\"),\n )}\n >\n <Icon\n name=\"grip-vertical\"\n size={isMobile ? 12 : 10}\n className={cn(\n \"text-[var(--ds-text-muted)]\",\n isVertical && \"rotate-90\",\n )}\n />\n </div>\n )}\n </PanelResizeHandle>\n );\n}\n","import { useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useBelow, useCompact, useElementWidth } from \"../lib/use-compact\";\nimport { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"./ResizablePanels\";\nimport { Button } from \"./Button\";\nimport { Drawer } from \"./Drawer\";\nimport { Icon } from \"../lib/icons\";\nimport { useAppShell } from \"./AppShell\";\nimport { SplitPaneCtx } from \"../lib/surface-context\";\nimport { isTabStop, rememberFocus, tabStops, type RememberedFocus } from \"../lib/focus\";\n\nexport type SplitViewFrame = \"fill\" | \"page\";\n\n/**\n * How far past a threshold the layout must go before a collapsed part comes back, in px: a classic scrollbar's width\n * (15 px on a Mac with a mouse, 17 on Windows), whose coming and going with the aside moved the layout back and forth\n * across the threshold (round 27 review). No wider: inside the band the answer depends on the way the width came, so a\n * wider band could keep the aside collapsed at a width where it fits.\n */\nconst THRESHOLD_BAND = 17;\n\nexport interface SplitViewProps {\n /** The list (an inbox, a queue): it scrolls on its own. */\n list: ReactNode;\n /** The selected item (a thread, a record): it fills the height and scrolls on its own. */\n detail: ReactNode;\n /** Context beside the detail (a contact, properties). The pane pads it (16 px, like the Drawer it collapses into), so pass content with no padding classes (round 8). Collapses to a toggle that opens it in a Drawer when the layout is narrower than `asideCollapseBelow`. */\n aside?: ReactNode;\n /** Names the aside's toggle and Drawer. */\n asideTitle?: string;\n /**\n * Below `collapseBelow` the list shows alone; with `detailOpen` the detail replaces it, with a Back button that calls\n * `onDetailClose` (detailMode \"stack\"). The page owns the selection, so it owns this flag.\n */\n detailOpen?: boolean;\n onDetailClose?: () => void;\n /** \"stack\" (default): the detail stacks over the list below the collapse width. \"route\": the app navigates to the detail's own route instead, and this renders the list alone there. */\n detailMode?: \"stack\" | \"route\";\n /**\n * \"shown\" (default): above the collapse width the detail pane is always there — an inbox's reading pane may show a\n * placeholder. \"hidden\" (Sprint 25.7, C3): `detailOpen` decides at every width. Closed, the list takes the whole\n * width; open, the pane appears beside it at the width the user last gave it, with a close control that calls\n * `onDetailClose`. For boards, timelines and lists of records. The list stays mounted, so a board or a timeline keeps\n * its scroll and zoom.\n */\n closedDetail?: \"shown\" | \"hidden\";\n /** The open pane's close control, named for a screen reader (closedDetail=\"hidden\"). Default \"Close\". */\n closeLabel?: string;\n backLabel?: string;\n /** Percent of the width the list takes; the divider is draggable. */\n listWidth?: number;\n listMinWidth?: number;\n asideWidth?: number;\n /** Below this width of the layout itself (px) the list and detail stop sitting side by side. */\n collapseBelow?: number;\n /** Below this width of the layout itself (px) the aside becomes a toggle. */\n asideCollapseBelow?: number;\n /**\n * The narrowest the open item's pane may be beside an aside (px). When the list and the aside would leave it less,\n * the aside becomes its toggle and Drawer, so the item keeps the room its content needs: the aside is the secondary\n * content (round 27, R27-13, the owner: \"which information is more important\"). An item that holds a wide table or a\n * card grid may need more: pass it.\n */\n detailMinWidth?: number;\n /** A bordered, rounded surface (inside a page with padding) instead of edge to edge (filling <main>). */\n bordered?: boolean;\n /** \"fill\": the page root, flush to the shell, h-full against main, each pane owns its inset. \"page\" (default): inside a PageContainer under a PageHeader. */\n frame?: SplitViewFrame;\n \"aria-label\"?: string;\n className?: string;\n}\n\n/**\n * The reading-pane composition (Sprint 25.2, R3): one surface with dividers —\n * list, detail and an optional aside — that fills the height it is given (an\n * AppShell's <main> already gives it) and never three cards with gaps. The\n * panels compose ResizablePanelGroup, so the dividers drag. Below the collapse\n * width, measured on the layout itself, the list shows alone and the detail\n * stacks over it (or the app routes to it); the aside collapses to a toggle.\n * The page owns the selection and assembles the contents; keyboard movement\n * through the list is the list's job (StackedList selectedId).\n */\nexport function SplitView({\n list,\n detail,\n aside,\n asideTitle = \"Details\",\n detailOpen = false,\n onDetailClose,\n detailMode = \"stack\",\n closedDetail = \"shown\",\n closeLabel = \"Close\",\n backLabel = \"Back\",\n listWidth = 35,\n listMinWidth = 25,\n asideWidth = 25,\n collapseBelow = 768,\n asideCollapseBelow = 1024,\n detailMinWidth = 480,\n bordered = false,\n frame = \"page\",\n \"aria-label\": ariaLabel,\n className,\n}: SplitViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompact(rootRef, collapseBelow, THRESHOLD_BAND);\n const layoutWidth = useElementWidth(rootRef);\n // The panels' shares as the user left them (the review: the defaults were read, so a list dragged wider left the item\n // under its minimum beside the aside); the aside's last share stands in while it is collapsed.\n const [shares, setShares] = useState<{ list?: number; aside?: number }>({});\n const listShare = shares.list ?? listWidth;\n const asideShare = shares.aside ?? asideWidth;\n // the aside yields when the layout is narrow, or when beside it the item would have less than its minimum\n const itemShare = (100 - listShare - asideShare) / 100;\n const itemBesideAside = aside ? layoutWidth * itemShare : 0;\n // the item moves by its share of the scrollbar's width, so its band is that share of the layout's\n const itemSqueezed = useBelow(itemBesideAside, detailMinWidth, THRESHOLD_BAND * Math.max(0, itemShare));\n const asideCollapsed = useBelow(layoutWidth, asideCollapseBelow, THRESHOLD_BAND) || itemSqueezed;\n const [asideWantsOpen, setAsideWantsOpen] = useState(false);\n const hiding = closedDetail === \"hidden\";\n // Hidden: the aside belongs to the open item, so it (its pane, its toggle, its Drawer) goes with the pane, and a\n // Drawer left open does not come back with the next item (render-time state adjustment).\n const showAside = !!aside && (!hiding || detailOpen);\n if (hiding && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);\n // on a phone the aside's Drawer belongs to the item on screen: Back closes it with the item\n if (compact && !detailOpen && asideWantsOpen) setAsideWantsOpen(false);\n const asideOpen = asideCollapsed && asideWantsOpen;\n const region = ariaLabel ? { role: \"region\" as const, \"aria-label\": ariaLabel } : {};\n // fill: the page root, flush to the shell on every side (no border, no radius, no padding of its own); h-full\n // resolves against <main> because the shell is a flex column down to it. page: inside a PageContainer under a\n // PageHeader, where `bordered` draws the surface. Each pane owns its own inset either way.\n const isFill = frame === \"fill\";\n // In a shell with an app footer the children wrapper has no definite height, so h-full cannot resolve; the shell\n // publishes main's height and fill takes it (the footer then sits below the fold, as it must under a full pane).\n const shell = useAppShell();\n const fillStyle = isFill && shell?.hasFooter ? { height: \"var(--ds-main-height, 100%)\" } : undefined;\n // Edge to edge the list sits on the page's canvas, like every other page, and the open item (its detail and its aside)\n // on the surface (round 27, R27-12, the owner: the whole split on the surface read as a different page). A page frame,\n // bordered or not, stays one surface: a card on the canvas.\n const itemSurface = isFill ? \"bg-[var(--ds-bg-surface)]\" : undefined;\n const surface = cn(\n \"h-full min-h-0 w-full\",\n !isFill && \"bg-[var(--ds-bg-surface)]\",\n isFill && \"rounded-none border-0\",\n bordered && !isFill && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] rounded-[var(--ds-radius-md)] overflow-hidden\",\n className,\n );\n\n // closedDetail=\"hidden\" (C3): the pane opens beside the list at the width the user last gave it; closing it returns\n // focus to the item the pane shows, or its neighbour if that item is gone (D14(e)): the list's selected item, else\n // the last list item that took focus, checked on each render while the pane is open, so switching items is followed.\n // A selection kept in the URL changes the path, so the item is remembered whatever the route does. With no item\n // (opened on load, or from outside the list) focus goes to the list's first Tab stop. No overlay layer: an\n // assistant opened earlier keeps Escape.\n const [listPct, setListPct] = useState(listWidth);\n const paneRef = useRef<HTMLDivElement>(null);\n const shown = useRef<RememberedFocus | null>(null);\n const lastInList = useRef<HTMLElement | null>(null);\n const wasOpen = useRef(false);\n useLayoutEffect(() => {\n if (!hiding) return;\n const listPane = rootRef.current?.querySelector<HTMLElement>('[data-pane=\"list\"]') ?? null;\n if (detailOpen) {\n wasOpen.current = true;\n const selected = listPane?.querySelector<HTMLElement>('[aria-current=\"true\"], [aria-selected=\"true\"]');\n const last = lastInList.current;\n const item = (selected && (isTabStop(selected) ? selected : tabStops(selected)[0]))\n || (last?.isConnected && listPane?.contains(last) ? last : null);\n if (item && item !== shown.current?.el) shown.current = rememberFocus(item, { ignoreRoute: true });\n return;\n }\n if (!wasOpen.current) return;\n wasOpen.current = false;\n const r = shown.current;\n shown.current = null;\n const active = document.activeElement;\n if (active && active !== document.body && active.isConnected) return;\n const first = listPane ? tabStops(listPane)[0] : undefined;\n if (!r?.restore(first)) first?.focus();\n });\n const onPaneKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !onDetailClose) return;\n const t = e.target as HTMLElement;\n // the pane's own DOM only — a portalled popup inside it bubbles here in React's tree — and never from a field\n if (!paneRef.current?.contains(t) || t.closest(\"input, textarea, select, [contenteditable='true']\")) return;\n e.preventDefault();\n onDetailClose();\n };\n\n if (compact) {\n const showDetail = detailMode === \"stack\" && detailOpen;\n // on a phone the item's aside is a toggle beside Back that opens it in a Drawer, as it is on a narrow desktop (the\n // round 27 review: below the collapse width it could not be reached at all)\n const phoneAside = showDetail && !!aside;\n return (\n <div ref={rootRef} data-split-view={showDetail ? \"detail\" : \"list\"} data-frame={frame} {...region} className={cn(surface, \"flex flex-col\")} style={fillStyle}>\n {showDetail ? (\n <div className={cn(\"flex-1 min-h-0 flex flex-col\", itemSurface)}>\n <div className=\"shrink-0 flex items-center justify-between gap-ds-2 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"arrow-left\" size={14} />} onClick={onDetailClose}>{backLabel}</Button>\n {phoneAside && <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"info\" size={14} />} onClick={() => setAsideWantsOpen(true)} aria-expanded={asideWantsOpen}>{asideTitle}</Button>}\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </div>\n ) : (\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{list}</SplitPaneCtx></div>\n )}\n {phoneAside && (\n <Drawer open={asideWantsOpen} onClose={() => setAsideWantsOpen(false)} title={asideTitle} position=\"right\">\n {aside}\n </Drawer>\n )}\n </div>\n );\n }\n\n const asideButton = showAside && asideCollapsed\n ? <Button variant=\"ghost\" size=\"sm\" iconLeft={<Icon name=\"info\" size={14} />} onClick={() => setAsideWantsOpen(true)} aria-expanded={asideOpen}>{asideTitle}</Button>\n : null;\n const closeButton = hiding && onDetailClose\n ? <Button variant=\"ghost\" size=\"icon-sm\" aria-label={closeLabel} onClick={onDetailClose}><Icon name=\"close\" size={14} /></Button>\n : null;\n const paneBar = asideButton || closeButton ? (\n <div className=\"shrink-0 flex items-center justify-end gap-ds-1 px-ds-3 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\">\n {asideButton}\n {closeButton}\n </div>\n ) : null;\n const showDetail = !hiding || detailOpen;\n\n return (\n <div ref={rootRef} data-split-view={hiding && !detailOpen ? \"list\" : \"split\"} data-frame={frame} {...region} className={surface} style={fillStyle}>\n <ResizablePanelGroup\n direction=\"horizontal\"\n stackOnMobile={false}\n className=\"h-full\"\n // the width the user gives the list is kept, so a closed pane reopens at it; the shares decide when the aside yields\n onLayoutChanged={(layout) => {\n setShares((prev) => (prev.list === (layout.list ?? prev.list) && prev.aside === (layout.aside ?? prev.aside) ? prev : { list: layout.list ?? prev.list, aside: layout.aside ?? prev.aside }));\n if (hiding && layout.detail != null && layout.list != null) setListPct(layout.list);\n }}\n >\n <ResizablePanel id=\"list\" defaultSize={`${hiding ? (detailOpen ? listPct : 100) : listWidth}%`} minSize={`${listMinWidth}%`}>\n <div data-pane=\"list\" className=\"h-full min-h-0 overflow-y-auto\" onFocus={hiding ? (e) => { lastInList.current = e.target; } : undefined}>\n <SplitPaneCtx value={true}>{list}</SplitPaneCtx>\n </div>\n </ResizablePanel>\n {showDetail && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"detail\" minSize=\"30%\">\n <div ref={paneRef} data-pane=\"detail\" className={cn(\"h-full min-h-0 flex flex-col\", itemSurface)} onKeyDown={hiding ? onPaneKeyDown : undefined}>\n {paneBar}\n <div className=\"flex-1 min-h-0 overflow-y-auto\"><SplitPaneCtx value={true}>{detail}</SplitPaneCtx></div>\n </div>\n </ResizablePanel>\n </>\n )}\n {showAside && !asideCollapsed && (\n <>\n <ResizableHandle withHandle />\n <ResizablePanel id=\"aside\" defaultSize={`${asideWidth}%`} minSize=\"15%\">\n <div data-pane=\"aside\" className={cn(\"h-full min-h-0 overflow-y-auto p-ds-4\", itemSurface)}><SplitPaneCtx value={true}>{aside}</SplitPaneCtx></div>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n {showAside && asideCollapsed && (\n <Drawer open={asideOpen} onClose={() => setAsideWantsOpen(false)} title={asideTitle} position=\"right\">\n {aside}\n </Drawer>\n )}\n </div>\n );\n}\n"]}
package/dist/roots.css CHANGED
@@ -1,3 +1,7 @@
1
+ /* The cascade layers in Tailwind's order, declared before anything else here: an app that imports this file ahead of
2
+ tailwindcss still gets preflight (base) under the components layer and the utilities over it. */
3
+ @layer theme, base, components, utilities;
4
+
1
5
  /* ==========================================================================
2
6
  Root Control Engine
3
7
  Derives all design tokens from user-configurable root values.
@@ -192,7 +196,12 @@
192
196
  --ds-font-size-xl: calc(var(--ds-text-base) * 1.25);
193
197
  --ds-font-size-2xl: calc(var(--ds-text-base) * 1.5);
194
198
  --ds-font-size-3xl: calc(var(--ds-text-base) * 1.875);
195
- --ds-line-height-body: 1.5;
199
+ /* Line heights as ratios, so they scale with the theme's base size: at a 16px base (most presets), body text is
200
+ 16/22 and the large size 18/24 (the owner, 28 September 2026; Tailwind's defaults gave 24 and 28). The body ratio
201
+ is also the page's default, so body text has one line height with or without a size class. The other sizes keep
202
+ Tailwind's. */
203
+ --ds-line-height-body: calc(22 / 16);
204
+ --ds-line-height-lg: calc(24 / 18);
196
205
  --ds-line-height-tight: 1.25;
197
206
 
198
207
  /* ── L2: Derived Opacity Scale ───────────────────────────────────── */
@@ -886,4 +895,157 @@ body {
886
895
  /* Buttons, inputs, badges — small surfaces */
887
896
  [data-sketch] [class*="rounded-[var(--ds-radius-sm)"] {
888
897
  border-radius: var(--ds-sketch-wobble-sm);
889
- }
898
+ }
899
+
900
+ /* RichTextEditor's content styles sit in Tailwind's components layer (the round 27 review): above the base layer's
901
+ preflight, which they must beat, and below the utilities, so an app can restyle the editor's content with classes. */
902
+ @layer components {
903
+ /* RichTextEditor's content (headings, lists, quotes, code, links, tables, images), shipped with the tokens so every app
904
+ that renders the editor styles its content (round 27, R27-9: these lived only in the showcase's own stylesheet). */
905
+ /* ── Rich Text Editor (Tiptap) ───────────────────────────────────── */
906
+
907
+ .rte-content .tiptap {
908
+ outline: none;
909
+ min-height: 120px;
910
+ }
911
+
912
+ .rte-content .tiptap p.is-editor-empty:first-child::before {
913
+ content: attr(data-placeholder);
914
+ float: left;
915
+ color: var(--ds-text-muted);
916
+ pointer-events: none;
917
+ height: 0;
918
+ }
919
+
920
+ .rte-content .tiptap h1 { font-size: 1.5em; font-weight: var(--ds-weight-heavy); margin: 1em 0 0.4em; line-height: 1.2; }
921
+ .rte-content .tiptap h2 { font-size: 1.25em; font-weight: var(--ds-weight-strong); margin: 0.8em 0 0.3em; line-height: 1.3; }
922
+ .rte-content .tiptap h3 { font-size: 1.1em; font-weight: var(--ds-weight-strong); margin: 0.6em 0 0.2em; line-height: 1.4; }
923
+
924
+ .rte-content .tiptap p { margin: 0.5em 0; }
925
+
926
+ .rte-content .tiptap ul,
927
+ .rte-content .tiptap ol { padding-left: 1.5em; margin: 0.5em 0; }
928
+ .rte-content .tiptap ul { list-style: disc; }
929
+ .rte-content .tiptap ol { list-style: decimal; }
930
+ .rte-content .tiptap li { margin: 0.15em 0; }
931
+
932
+ .rte-content .tiptap blockquote {
933
+ border-left: 3px solid var(--ds-accent-primary);
934
+ padding-left: 1em;
935
+ margin: 0.5em 0;
936
+ color: var(--ds-text-secondary);
937
+ font-style: italic;
938
+ }
939
+
940
+ .rte-content .tiptap code {
941
+ font-family: var(--ds-font-mono);
942
+ font-size: 0.875em;
943
+ padding: 0.15em 0.35em;
944
+ border-radius: calc(var(--ds-radius-sm) * 0.5);
945
+ background: var(--ds-bg-muted);
946
+ color: var(--ds-accent-primary);
947
+ }
948
+
949
+ .rte-content .tiptap pre {
950
+ font-family: var(--ds-font-mono);
951
+ font-size: 0.85em;
952
+ padding: 0.75em 1em;
953
+ border-radius: var(--ds-radius-sm);
954
+ background: var(--ds-bg-muted);
955
+ overflow-x: auto;
956
+ margin: 0.5em 0;
957
+ }
958
+ .rte-content .tiptap pre code {
959
+ background: none;
960
+ padding: 0;
961
+ color: var(--ds-text-primary);
962
+ }
963
+
964
+ .rte-content .tiptap a,
965
+ .rte-content .tiptap .rte-link {
966
+ color: var(--ds-accent-primary);
967
+ text-decoration: underline;
968
+ cursor: pointer;
969
+ }
970
+
971
+ .rte-content .tiptap hr {
972
+ border: none;
973
+ border-top: var(--ds-border-width) solid var(--ds-border-subtle);
974
+ margin: 1em 0;
975
+ }
976
+
977
+ .rte-content .tiptap img {
978
+ max-width: 100%;
979
+ border-radius: var(--ds-radius-sm);
980
+ margin: 0.5em 0;
981
+ }
982
+
983
+ .rte-content .tiptap table {
984
+ border-collapse: collapse;
985
+ width: 100%;
986
+ margin: 0.5em 0;
987
+ }
988
+ .rte-content .tiptap th,
989
+ .rte-content .tiptap td {
990
+ border: var(--ds-border-width) solid var(--ds-border-default);
991
+ padding: 0.4em 0.6em;
992
+ text-align: left;
993
+ }
994
+ .rte-content .tiptap th {
995
+ background: var(--ds-bg-muted);
996
+ font-weight: var(--ds-weight-strong);
997
+ }
998
+
999
+ .rte-content .tiptap .selectedCell {
1000
+ background: var(--ds-accent-subtle);
1001
+ }
1002
+
1003
+ /* Task list */
1004
+ .rte-content .tiptap ul[data-type="taskList"] {
1005
+ list-style: none !important;
1006
+ padding-left: 0 !important;
1007
+ margin: 0.5em 0;
1008
+ }
1009
+ .rte-content .tiptap ul[data-type="taskList"] li {
1010
+ display: flex !important;
1011
+ flex-direction: row !important;
1012
+ align-items: flex-start !important;
1013
+ gap: 0.5em;
1014
+ margin: 0.25em 0;
1015
+ }
1016
+ .rte-content .tiptap ul[data-type="taskList"] li > label {
1017
+ display: flex;
1018
+ align-items: center;
1019
+ flex-shrink: 0;
1020
+ height: 1.5em;
1021
+ user-select: none;
1022
+ }
1023
+ .rte-content .tiptap ul[data-type="taskList"] li > label input[type="checkbox"] {
1024
+ width: 16px;
1025
+ height: 16px;
1026
+ margin: 0;
1027
+ accent-color: var(--ds-accent-primary);
1028
+ cursor: pointer;
1029
+ }
1030
+ .rte-content .tiptap ul[data-type="taskList"] li > div {
1031
+ flex: 1 1 0% !important;
1032
+ min-width: 0;
1033
+ }
1034
+ .rte-content .tiptap ul[data-type="taskList"] li > div > p {
1035
+ margin: 0;
1036
+ }
1037
+ .rte-content .tiptap ul[data-type="taskList"] li[data-checked="true"] > div {
1038
+ text-decoration: line-through;
1039
+ color: var(--ds-text-muted);
1040
+ }
1041
+
1042
+ /* Highlight */
1043
+ .rte-content .tiptap mark {
1044
+ border-radius: 0.15em;
1045
+ padding: 0.05em 0.15em;
1046
+ }
1047
+
1048
+ /* Superscript / subscript */
1049
+ .rte-content .tiptap sup { font-size: 0.75em; vertical-align: super; }
1050
+ .rte-content .tiptap sub { font-size: 0.75em; vertical-align: sub; }
1051
+ }
package/dist/tailwind.css CHANGED
@@ -37,7 +37,9 @@
37
37
  --text-xs: var(--ds-font-size-xs);
38
38
  --text-sm: var(--ds-font-size-sm);
39
39
  --text-base: var(--ds-font-size-body);
40
+ --text-base--line-height: var(--ds-line-height-body);
40
41
  --text-lg: var(--ds-font-size-lg);
42
+ --text-lg--line-height: var(--ds-line-height-lg);
41
43
  --text-xl: var(--ds-font-size-xl);
42
44
  --text-2xl: var(--ds-font-size-2xl);
43
45
  --text-3xl: var(--ds-font-size-3xl);
@@ -46,3 +48,15 @@
46
48
  @utility text-2xs {
47
49
  font-size: var(--ds-font-size-2xs);
48
50
  }
51
+
52
+ /* The package's own names carry its prefix, so they meet nothing of a customer's (the round 27 review). */
53
+ @utility gs-scrollbar-none {
54
+ -ms-overflow-style: none;
55
+ scrollbar-width: none;
56
+ &::-webkit-scrollbar { display: none; }
57
+ }
58
+
59
+ @keyframes gs-indeterminate {
60
+ 0% { transform: translateX(-100%); }
61
+ 100% { transform: translateX(400%); }
62
+ }
package/dist/utils.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ACTIVE_RING, FOCUS_RING, FOCUS_WITHIN_RING, GLASS_BACKDROP, GLASS_DROPDOWN, GLASS_OVERLAY, GLASS_SURFACE, HAS_FOCUS_RING, cn } from './chunk-4KVFH5KC.js';
1
+ export { ACTIVE_RING, FOCUS_RING, FOCUS_WITHIN_RING, GLASS_BACKDROP, GLASS_DROPDOWN, GLASS_OVERLAY, GLASS_SURFACE, HAS_FOCUS_RING, cn } from './chunk-M2QRNBDQ.js';
2
2
  //# sourceMappingURL=utils.js.map
3
3
  //# sourceMappingURL=utils.js.map
@@ -1,4 +1,4 @@
1
- import { cn } from './chunk-4KVFH5KC.js';
1
+ import { cn } from './chunk-M2QRNBDQ.js';
2
2
  import { useRef } from 'react';
3
3
  import { useVirtualizer } from '@tanstack/react-virtual';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -37,7 +37,9 @@
37
37
  --text-xs: var(--ds-font-size-xs);
38
38
  --text-sm: var(--ds-font-size-sm);
39
39
  --text-base: var(--ds-font-size-body);
40
+ --text-base--line-height: var(--ds-line-height-body);
40
41
  --text-lg: var(--ds-font-size-lg);
42
+ --text-lg--line-height: var(--ds-line-height-lg);
41
43
  --text-xl: var(--ds-font-size-xl);
42
44
  --text-2xl: var(--ds-font-size-2xl);
43
45
  --text-3xl: var(--ds-font-size-3xl);
@@ -46,3 +48,15 @@
46
48
  @utility text-2xs {
47
49
  font-size: var(--ds-font-size-2xs);
48
50
  }
51
+
52
+ /* The package's own names carry its prefix, so they meet nothing of a customer's (the round 27 review). */
53
+ @utility gs-scrollbar-none {
54
+ -ms-overflow-style: none;
55
+ scrollbar-width: none;
56
+ &::-webkit-scrollbar { display: none; }
57
+ }
58
+
59
+ @keyframes gs-indeterminate {
60
+ 0% { transform: translateX(-100%); }
61
+ 100% { transform: translateX(400%); }
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridsmith-ui",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Gridsmith component library — 116 React components (13 of them charts) with design tokens and a CSS custom property theming engine",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/utils.ts"],"names":[],"mappings":";;;;AAGO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AAEO,IAAM,cAAA,GAAiB;AAEvB,IAAM,aAAA,GAAgB,GAAG,cAAc,CAAA,qDAAA;AAEvC,IAAM,aAAA,GAAgB,GAAG,cAAc,CAAA,sDAAA;AAEvC,IAAM,cAAA,GAAiB,GAAG,cAAc,CAAA,yDAAA;AAKxC,IAAM,WAAA,GAAc;AAEpB,IAAM,UAAA,GAAa;AAEnB,IAAM,iBAAA,GAAoB;AAE1B,IAAM,cAAA,GAAiB;AAIvB,IAAM,iBAAA,GAAoB;AAE1B,IAAM,gBAAA,GAAmB;AAEzB,IAAM,uBAAA,GAA0B;AAEhC,IAAM,oBAAA,GAAuB;AAMpC,IAAI,eAAA,GAAkB,CAAA;AACtB,IAAI,0BAAA,GAA6B,EAAA;AAE1B,SAAS,cAAA,GAAiB;AAC/B,EAAA,IAAI,oBAAoB,CAAA,EAAG;AACzB,IAAA,0BAAA,GAA6B,QAAA,CAAS,KAAK,KAAA,CAAM,QAAA;AACjD,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,EACjC;AACA,EAAA,eAAA,EAAA;AACF;AAEO,SAAS,gBAAA,GAAmB;AACjC,EAAA,IAAI,oBAAoB,CAAA,EAAG;AAC3B,EAAA,eAAA,EAAA;AACA,EAAA,IAAI,oBAAoB,CAAA,EAAG;AACzB,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,0BAAA;AAC/B,IAAA,0BAAA,GAA6B,EAAA;AAAA,EAC/B;AACF","file":"chunk-4KVFH5KC.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport const GLASS_BACKDROP = \"[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]\";\n\nexport const GLASS_SURFACE = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-card),var(--ds-glass-inset)]`;\n\nexport const GLASS_OVERLAY = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-modal),var(--ds-glass-inset)]`;\n\nexport const GLASS_DROPDOWN = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]`;\n\n// One grammar for every stroke-based active state (focused, selected, open):\n// a solid accent stroke plus a soft halo. Borderless controls keep the solid\n// --ds-shadow-focus ring so keyboard focus stays high-contrast.\nexport const ACTIVE_RING = \"border-[var(--ds-border-focus)] ring-2 ring-[var(--ds-ring-active)]\";\n\nexport const FOCUS_RING = \"focus-visible:outline-none focus-visible:border-[var(--ds-border-focus)] focus-visible:ring-2 focus-visible:ring-[var(--ds-ring-active)]\";\n\nexport const FOCUS_WITHIN_RING = \"focus-within:border-[var(--ds-border-focus)] focus-within:ring-2 focus-within:ring-[var(--ds-ring-active)]\";\n\nexport const HAS_FOCUS_RING = \"has-[:focus-visible]:border-[var(--ds-border-focus)] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[var(--ds-ring-active)]\";\n\n// Same grammar in the error hue: the invalid stroke stays red on focus and\n// the halo is a 30% error mix, mirroring --ds-accent-soft.\nexport const ERROR_ACTIVE_RING = \"border-[var(--ds-status-error)] ring-2 ring-[var(--ds-ring-error)]\";\n\nexport const ERROR_FOCUS_RING = \"focus-visible:outline-none focus-visible:border-[var(--ds-status-error)] focus-visible:ring-2 focus-visible:ring-[var(--ds-ring-error)]\";\n\nexport const ERROR_FOCUS_WITHIN_RING = \"focus-within:border-[var(--ds-status-error)] focus-within:ring-2 focus-within:ring-[var(--ds-ring-error)]\";\n\nexport const ERROR_HAS_FOCUS_RING = \"has-[:focus-visible]:border-[var(--ds-status-error)] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[var(--ds-ring-error)]\";\n\n// Body scroll lock is reference-counted so stacked overlays (a Dialog opened\n// from a Drawer, for example) only restore scrolling when the LAST one\n// closes. The original overflow value is captured on the first lock and put\n// back on the last unlock; an unbalanced unlock is clamped at zero.\nlet scrollLockCount = 0;\nlet scrollLockPreviousOverflow = \"\";\n\nexport function lockBodyScroll() {\n if (scrollLockCount === 0) {\n scrollLockPreviousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n scrollLockCount++;\n}\n\nexport function unlockBodyScroll() {\n if (scrollLockCount === 0) return;\n scrollLockCount--;\n if (scrollLockCount === 0) {\n document.body.style.overflow = scrollLockPreviousOverflow;\n scrollLockPreviousOverflow = \"\";\n }\n}\n\n/** Test-only: current number of active locks. */\nexport function getBodyScrollLockCount() {\n return scrollLockCount;\n}\n"]}