@bsuite/page-builder 0.7.0 → 0.9.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.
@@ -0,0 +1,52 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface CanvasCardProps {
3
+ /** Stable widget key used by PageGridLayout for layout persistence. Required. */
4
+ cardKey: string;
5
+ /** Default grid width in columns (12-col grid). Defaults to 12. */
6
+ w?: 2 | 3 | 4 | 6 | 8 | 12;
7
+ /** Minimum width in columns when the user resizes. Defaults to 4. */
8
+ minW?: 2 | 3 | 4 | 6 | 8 | 12;
9
+ /** Default grid height in 32px row units (seed before first measurement). Defaults to 6. */
10
+ h?: number;
11
+ /** Minimum height in row units when the user resizes. Defaults to 2. */
12
+ minH?: number;
13
+ /**
14
+ * When true, this card's height tracks its measured content height instead
15
+ * of a fixed manual size. `h` is only the seed row count used before the
16
+ * first ResizeObserver measurement lands. Threaded through to the underlying
17
+ * {@link GridLayoutItem.autoHeight} by `DraggableCardPage`.
18
+ *
19
+ * Defaults to TRUE. Cards grow to fit their content so forms and sections
20
+ * never render clipped.
21
+ *
22
+ * `autoHeight={false}` is RESERVED FOR VIRTUALIZED / WINDOWED LISTS ONLY
23
+ * (operator card-surfaces doctrine, 2026-07-23): a card whose body renders a
24
+ * windowed list that owns its own internal scroll and must NOT balloon to
25
+ * the full row count. An ordinary data table must NOT opt out — the doctrine
26
+ * is expand-by-default so every row is visible without an internal
27
+ * scrollbar. A blanket opt-out across ~120 crm7 pages was the 192px clip bug
28
+ * and was swept. Do not reintroduce it on a plain card or table.
29
+ */
30
+ autoHeight?: boolean;
31
+ /** Card body — typically a single card or page-header element. */
32
+ children: ReactNode;
33
+ }
34
+ /**
35
+ * CanvasCard — marker component consumed by `DraggableCardPage`.
36
+ *
37
+ * It renders `null` by design: `DraggableCardPage` reads its props off the JSX
38
+ * tree rather than rendering it, so accidental flat usage outside a
39
+ * `DraggableCardPage` is harmless rather than broken.
40
+ *
41
+ * Kept in its own module so a consumer can import the marker without pulling
42
+ * in `DraggableCardPage`'s transitive dependency on `react-grid-layout`. The
43
+ * `displayName` check inside `DraggableCardPage` compares against the literal
44
+ * string `'CanvasCard'`, so it keeps working across module boundaries, bundler
45
+ * chunk splits and duplicated package instances — an `instanceof`/identity
46
+ * check would not.
47
+ */
48
+ export declare function CanvasCard(_props: CanvasCardProps): null;
49
+ export declare namespace CanvasCard {
50
+ var displayName: string;
51
+ }
52
+ //# sourceMappingURL=CanvasCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasCard.d.ts","sourceRoot":"","sources":["../src/CanvasCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC3B,qEAAqE;IACrE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC9B,4FAA4F;IAC5F,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAExD;yBAFe,UAAU"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * CanvasCard — marker component consumed by `DraggableCardPage`.
3
+ *
4
+ * It renders `null` by design: `DraggableCardPage` reads its props off the JSX
5
+ * tree rather than rendering it, so accidental flat usage outside a
6
+ * `DraggableCardPage` is harmless rather than broken.
7
+ *
8
+ * Kept in its own module so a consumer can import the marker without pulling
9
+ * in `DraggableCardPage`'s transitive dependency on `react-grid-layout`. The
10
+ * `displayName` check inside `DraggableCardPage` compares against the literal
11
+ * string `'CanvasCard'`, so it keeps working across module boundaries, bundler
12
+ * chunk splits and duplicated package instances — an `instanceof`/identity
13
+ * check would not.
14
+ */
15
+ export function CanvasCard(_props) {
16
+ return null;
17
+ }
18
+ CanvasCard.displayName = 'CanvasCard';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * DraggableCardPage — declarative wrapper around `PageGridLayout` that gives
3
+ * each child card its own draggable, independently-resizable widget slot.
4
+ *
5
+ * Why this exists
6
+ * ---------------
7
+ * The default way to reach `PageGridLayout` is
8
+ * `widgets={{ content: <Card/><Card/>… }}`, which packs an entire page into
9
+ * ONE widget. The canvas editor then drags the whole page as a single block
10
+ * instead of letting the user rearrange individual cards, and the page's
11
+ * height is capped at that one grid item's pixel height, so long forms render
12
+ * truncated. That is the operator's most-repeated complaint about this estate
13
+ * ("cards move as one block"), and it is a property of how the page calls the
14
+ * grid, not of the grid itself.
15
+ *
16
+ * Why it lives HERE rather than in an app
17
+ * ---------------------------------------
18
+ * It previously existed as three hand-copied forms (crm7, braden, throughput)
19
+ * and was ABSENT from two apps (BSU, conduit). The absence had a cost beyond
20
+ * duplication: BSU's exclusion ledger recorded that its dynamic card lists
21
+ * "cannot be split into static widget keys without a dynamic-widget
22
+ * registration mechanism", which is FALSE — `buildCanvasCardLayout` derives
23
+ * the widget dict from the children at render time, so a `.map()` over a
24
+ * runtime-variable list already produces N independent grid items. crm7 ships
25
+ * exactly that against a Postgres-backed 0..N array. The objection was an
26
+ * artifact of the primitive not being present, not a real constraint.
27
+ * Tracked as crm7#412 / bsuite#1995.
28
+ *
29
+ * Usage
30
+ * -----
31
+ * <DraggableCardPage pageKey="/people/:id" layoutVersion={3}>
32
+ * <CanvasCard cardKey="header" h={4}>
33
+ * <PageHeader heading="Person" />
34
+ * </CanvasCard>
35
+ * <CanvasCard cardKey="personalInfo" h={12}>
36
+ * <Card>…</Card>
37
+ * </CanvasCard>
38
+ * {isTraining && (
39
+ * <CanvasCard cardKey="trainingContract" h={16}>
40
+ * <Card>…</Card>
41
+ * </CanvasCard>
42
+ * )}
43
+ * </DraggableCardPage>
44
+ *
45
+ * Heights are 32px row units. Falsy children are skipped, so conditional cards
46
+ * collapse cleanly without leaving empty slots. Fragments are flattened.
47
+ *
48
+ * What this component deliberately does NOT do
49
+ * --------------------------------------------
50
+ * - **Permissions.** Gating is an app concern with an app-specific permission
51
+ * vocabulary. Wrap this component in the app's own gate.
52
+ * - **`compactType`.** It is NOT accepted, on purpose. `usePageGridLayout`
53
+ * selects `verticalCompactor` unconditionally for BOTH view and edit mode,
54
+ * and that is load-bearing: the previous edit-mode config
55
+ * (`preventCollision: true` with `compactType: null`) made react-grid-layout
56
+ * full-revert any gesture landing on an occupied cell, and `onDragStop` only
57
+ * emits when the layout changed — so a reverted gesture emitted nothing and
58
+ * NOTHING COULD EVER SAVE (bsuite#1588). Accepting a `compactType` prop here
59
+ * would re-open that hole. crm7's local wrapper passed `compactType ?? null`
60
+ * through a cast to a component that does not read it; the prop was dead,
61
+ * and dead is the correct state for it.
62
+ */
63
+ import { type ComponentType, type ReactNode } from 'react';
64
+ import { CanvasCard, type CanvasCardProps } from './CanvasCard.js';
65
+ import type { PageGridLayoutProps } from './types.js';
66
+ export { CanvasCard };
67
+ export type { CanvasCardProps };
68
+ export type DraggableCardPageProps = Omit<PageGridLayoutProps, 'widgets' | 'defaultLayouts'> & {
69
+ /**
70
+ * App-wide layout epoch, added to this page's `layoutVersion` before it
71
+ * reaches `PageGridLayout`. Defaults to 0.
72
+ *
73
+ * This is the lever for "a DraggableCardPage-level default changed for EVERY
74
+ * page in this app" — bump it once instead of hand-bumping `layoutVersion`
75
+ * at hundreds of call sites, which is guaranteed to miss some. Per-page
76
+ * `layoutVersion` bumps keep working on top of it.
77
+ *
78
+ * It is deliberately a PROP rather than a package constant, because each app
79
+ * has its own history of saved layouts: crm7 is at 101 (E5a autoHeight flip,
80
+ * then the 2026-07-23 card-surfaces sweep), braden and throughput at 100
81
+ * (the D-74 sweep), and a fresh adopter starts at 0. Baking a single number
82
+ * into the package would silently reset or fail to reset saved layouts
83
+ * depending on which app imported it.
84
+ *
85
+ * Distinct from the package-level `PACKAGE_LAYOUT_EPOCH`, which is a
86
+ * one-time reset applied by `usePageGridLayout` to every consumer at once
87
+ * when a package behaviour change poisons stored layouts suite-wide.
88
+ */
89
+ layoutEpoch?: number;
90
+ /**
91
+ * The grid component to render. Defaults to this package's `PageGridLayout`.
92
+ *
93
+ * Apps that wrap `PageGridLayout` in a local adapter (to inject permissions,
94
+ * a preference adapter, tenant scoping or entity-widget factories) pass that
95
+ * adapter here, so they get the shared card algorithm without giving up
96
+ * their adapter. An app with no adapter passes nothing.
97
+ */
98
+ gridComponent?: ComponentType<PageGridLayoutProps>;
99
+ /**
100
+ * Called with the display names of children that occupied no grid slot and
101
+ * were discarded. Defaults to a development-only `console.error`.
102
+ *
103
+ * A dropped child is a defect, not a style note: if it was a dialog, its
104
+ * trigger appears to do nothing — the button renders, the handler runs, and
105
+ * the element it toggles was never mounted.
106
+ */
107
+ onDroppedChildren?: (dropped: string[], pageKey: string) => void;
108
+ /**
109
+ * CanvasCard children. Falsy entries are ignored. Fragments are flattened so
110
+ * a conditional multi-card branch still registers each CanvasCard.
111
+ */
112
+ children: ReactNode;
113
+ };
114
+ export declare function DraggableCardPage({ layoutEpoch, gridComponent, onDroppedChildren, children, ...pageProps }: DraggableCardPageProps): import("react").JSX.Element;
115
+ export declare namespace DraggableCardPage {
116
+ var displayName: string;
117
+ }
118
+ //# sourceMappingURL=DraggableCardPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DraggableCardPage.d.ts","sourceRoot":"","sources":["../src/DraggableCardPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,EAAW,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGpE,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,mBAAmB,EACnB,SAAS,GAAG,gBAAgB,CAC7B,GAAG;IACF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACnD;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAgCF,wBAAgB,iBAAiB,CAAC,EAChC,WAAe,EACf,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,GAAG,SAAS,EACb,EAAE,sBAAsB,+BA2BxB;yBAjCe,iBAAiB"}
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * DraggableCardPage — declarative wrapper around `PageGridLayout` that gives
4
+ * each child card its own draggable, independently-resizable widget slot.
5
+ *
6
+ * Why this exists
7
+ * ---------------
8
+ * The default way to reach `PageGridLayout` is
9
+ * `widgets={{ content: <Card/><Card/>… }}`, which packs an entire page into
10
+ * ONE widget. The canvas editor then drags the whole page as a single block
11
+ * instead of letting the user rearrange individual cards, and the page's
12
+ * height is capped at that one grid item's pixel height, so long forms render
13
+ * truncated. That is the operator's most-repeated complaint about this estate
14
+ * ("cards move as one block"), and it is a property of how the page calls the
15
+ * grid, not of the grid itself.
16
+ *
17
+ * Why it lives HERE rather than in an app
18
+ * ---------------------------------------
19
+ * It previously existed as three hand-copied forms (crm7, braden, throughput)
20
+ * and was ABSENT from two apps (BSU, conduit). The absence had a cost beyond
21
+ * duplication: BSU's exclusion ledger recorded that its dynamic card lists
22
+ * "cannot be split into static widget keys without a dynamic-widget
23
+ * registration mechanism", which is FALSE — `buildCanvasCardLayout` derives
24
+ * the widget dict from the children at render time, so a `.map()` over a
25
+ * runtime-variable list already produces N independent grid items. crm7 ships
26
+ * exactly that against a Postgres-backed 0..N array. The objection was an
27
+ * artifact of the primitive not being present, not a real constraint.
28
+ * Tracked as crm7#412 / bsuite#1995.
29
+ *
30
+ * Usage
31
+ * -----
32
+ * <DraggableCardPage pageKey="/people/:id" layoutVersion={3}>
33
+ * <CanvasCard cardKey="header" h={4}>
34
+ * <PageHeader heading="Person" />
35
+ * </CanvasCard>
36
+ * <CanvasCard cardKey="personalInfo" h={12}>
37
+ * <Card>…</Card>
38
+ * </CanvasCard>
39
+ * {isTraining && (
40
+ * <CanvasCard cardKey="trainingContract" h={16}>
41
+ * <Card>…</Card>
42
+ * </CanvasCard>
43
+ * )}
44
+ * </DraggableCardPage>
45
+ *
46
+ * Heights are 32px row units. Falsy children are skipped, so conditional cards
47
+ * collapse cleanly without leaving empty slots. Fragments are flattened.
48
+ *
49
+ * What this component deliberately does NOT do
50
+ * --------------------------------------------
51
+ * - **Permissions.** Gating is an app concern with an app-specific permission
52
+ * vocabulary. Wrap this component in the app's own gate.
53
+ * - **`compactType`.** It is NOT accepted, on purpose. `usePageGridLayout`
54
+ * selects `verticalCompactor` unconditionally for BOTH view and edit mode,
55
+ * and that is load-bearing: the previous edit-mode config
56
+ * (`preventCollision: true` with `compactType: null`) made react-grid-layout
57
+ * full-revert any gesture landing on an occupied cell, and `onDragStop` only
58
+ * emits when the layout changed — so a reverted gesture emitted nothing and
59
+ * NOTHING COULD EVER SAVE (bsuite#1588). Accepting a `compactType` prop here
60
+ * would re-open that hole. crm7's local wrapper passed `compactType ?? null`
61
+ * through a cast to a component that does not read it; the prop was dead,
62
+ * and dead is the correct state for it.
63
+ */
64
+ import { useMemo } from 'react';
65
+ import { PageGridLayout as DefaultPageGridLayout } from './PageGridLayout.js';
66
+ import { buildCanvasCardLayout } from './canvasCardLayout.js';
67
+ import { CanvasCard } from './CanvasCard.js';
68
+ // Re-exported so a consumer can import both from one module, matching the
69
+ // long-standing crm7 import shape used by ~284 call sites.
70
+ export { CanvasCard };
71
+ /**
72
+ * `process.env.NODE_ENV` rather than `import.meta.env`: every bundler in the
73
+ * estate (Vite, Next, Rollup) statically replaces it, and the `typeof` guard
74
+ * keeps the expression safe in a raw browser ESM context where `process` does
75
+ * not exist. This package must not assume a Vite consumer.
76
+ */
77
+ function isDevelopment() {
78
+ return (typeof process !== 'undefined' &&
79
+ process.env?.NODE_ENV !== 'production');
80
+ }
81
+ function defaultOnDroppedChildren(dropped, pageKey) {
82
+ if (!isDevelopment())
83
+ return;
84
+ // console.error, not warn: React uses error for "you have written something
85
+ // that will not work", and this is that.
86
+ //
87
+ // Deliberately NOT a thrown error. Hundreds of pages use this component and
88
+ // a throw would turn a missing dialog into a blank page for a shape that has
89
+ // been shipping for months — trading a partial page for no page at all is
90
+ // not an improvement for the user in front of it.
91
+ console.error(`[DraggableCardPage] ${dropped.length} child(ren) of "${pageKey}" were DISCARDED and will not render: ${dropped.join(', ')}. ` +
92
+ `Only <CanvasCard> children (optionally inside a Fragment) occupy a grid slot — everything else is dropped. ` +
93
+ `If this is a dialog or a modal, its trigger will appear to do nothing: the button renders, the handler runs, and the element it toggles was never mounted. ` +
94
+ `Wrap it in <CanvasCard cardKey="…"> or move it outside <DraggableCardPage>.`);
95
+ }
96
+ export function DraggableCardPage({ layoutEpoch = 0, gridComponent, onDroppedChildren, children, ...pageProps }) {
97
+ const { widgets, layouts, dropped } = useMemo(() => buildCanvasCardLayout(children), [children]);
98
+ if (dropped.length > 0) {
99
+ (onDroppedChildren ?? defaultOnDroppedChildren)(dropped, pageProps.pageKey);
100
+ }
101
+ // Ensure a sensible default 12-column grid when the consumer does not
102
+ // override. Without this, PageGridLayout's fallback collapses to cols=1 at
103
+ // every breakpoint and the page renders as a single vertical column with no
104
+ // horizontal canvas area — the user-reported "drag cards into these white
105
+ // spaces" surface had no white spaces because the grid was 1 column wide.
106
+ const defaultCols = pageProps.defaultCols ?? 12;
107
+ const Grid = gridComponent ?? DefaultPageGridLayout;
108
+ return (_jsx(Grid, { ...pageProps, layoutVersion: (pageProps.layoutVersion ?? 1) + layoutEpoch, defaultCols: defaultCols, widgets: widgets, defaultLayouts: layouts }));
109
+ }
110
+ DraggableCardPage.displayName = 'DraggableCardPage';
@@ -2,5 +2,5 @@ import React from 'react';
2
2
  import 'react-grid-layout/css/styles.css';
3
3
  import 'react-resizable/css/styles.css';
4
4
  import type { PageGridLayoutProps } from './types.js';
5
- export declare function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className, isResizable, resizeHandles, tenantId, addEntityWidgetEventNames, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }: PageGridLayoutProps): React.JSX.Element;
5
+ export declare function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className, isResizable, resizeHandles, tenantId, defaultAutoHeight, addEntityWidgetEventNames, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }: PageGridLayoutProps): React.JSX.Element;
6
6
  //# sourceMappingURL=PageGridLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6E,MAAM,OAAO,CAAC;AAGlG,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AAmR7F,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EAMT,WAAkB,EAClB,aAAsC,EACtC,QAAQ,EACR,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBAowBrB"}
1
+ {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AA8U7F,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EAMT,WAAkB,EAClB,aAAsC,EACtC,QAAQ,EACR,iBAAiB,EACjB,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBA6xBrB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowDown, ArrowUp, ChevronDown, ChevronUp, Eye, EyeOff, Layers, LayoutGrid, Lock, Plus, RotateCcw, Save, Settings2, Unlock } from 'lucide-react';
3
- import React, { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import React, { startTransition, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
4
4
  import { Responsive } from 'react-grid-layout';
5
5
  import { gridBounds, minMaxSize, minSize } from 'react-grid-layout/core';
6
6
  import 'react-grid-layout/css/styles.css';
@@ -97,6 +97,71 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
97
97
  const measureRef = useRef(null);
98
98
  const lastReportedRowsRef = useRef(null);
99
99
  const measureRafRef = useRef(null);
100
+ /**
101
+ * Latest measured content height, updated on EVERY ResizeObserver entry.
102
+ *
103
+ * D-76 root cause ("cards render half cut off on page open"). The previous
104
+ * implementation captured `contentPx` in the rAF closure and then bailed
105
+ * out of any further entry that arrived before that frame ran:
106
+ *
107
+ * if (measureRafRef.current !== null) return; // <- measurement lost
108
+ *
109
+ * That is first-writer-wins inside a frame, and the first writer is
110
+ * routinely the WRONG one: a card mounts empty or skeletonised, the
111
+ * observer fires at (say) 90px and schedules the frame, the query resolves
112
+ * and the observer fires again at 760px — and that second entry returned
113
+ * early, discarding the real height. The frame then converged the card to
114
+ * ~3 rows. Because `computeAutoHeightRows` is a pure function of the height
115
+ * it is given, nothing re-triggered: the card stayed at the empty-state
116
+ * height with its content clipped by the `overflow-hidden` autoHeight
117
+ * wrapper until something else happened to resize it.
118
+ *
119
+ * Storing the height in a ref and READING IT INSIDE the frame makes it
120
+ * last-writer-wins, which is the only correct policy for "how tall is this
121
+ * content right now".
122
+ */
123
+ const latestContentPxRef = useRef(null);
124
+ const reportRows = useCallback((contentPx) => {
125
+ if (!onAutoHeightChange)
126
+ return;
127
+ const rows = computeAutoHeightRows({
128
+ contentPx,
129
+ cardChromePx: DEFAULT_CARD_CHROME_PX,
130
+ rowHeightPx: DEFAULT_ROW_HEIGHT,
131
+ marginYPx: DEFAULT_MARGIN[1],
132
+ });
133
+ if (rows === lastReportedRowsRef.current)
134
+ return;
135
+ lastReportedRowsRef.current = rows;
136
+ onAutoHeightChange(id, rows);
137
+ }, [id, onAutoHeightChange]);
138
+ /**
139
+ * First measurement BEFORE paint (second half of D-76).
140
+ *
141
+ * A ResizeObserver's initial observation is delivered asynchronously, and
142
+ * the rAF hop below adds another frame. Until it lands the card renders at
143
+ * its seed `h` — 6 rows / 192px for a `DraggableCardPage` card — with
144
+ * `overflow-hidden`, so the first painted frame of a taller card is
145
+ * literally cut in half. That is what the operator sees "on page open",
146
+ * every open, on every surface with content taller than its seed.
147
+ *
148
+ * Measuring in a layout effect and reporting synchronously puts the correct
149
+ * row count into state within the same commit, so the browser never paints
150
+ * the clipped frame. React batches these updates across all GridItems
151
+ * mounting together, and `applyAutoHeightRows` is a functional update, so N
152
+ * cards settling at once cost one re-render and cannot clobber each other.
153
+ */
154
+ useLayoutEffect(() => {
155
+ if (!autoHeight || !onAutoHeightChange)
156
+ return;
157
+ const el = measureRef.current;
158
+ if (!el)
159
+ return;
160
+ const contentPx = el.getBoundingClientRect().height;
161
+ latestContentPxRef.current = contentPx;
162
+ if (contentPx > 0)
163
+ reportRows(contentPx);
164
+ }, [autoHeight, onAutoHeightChange, reportRows]);
100
165
  useEffect(() => {
101
166
  if (!autoHeight || !onAutoHeightChange)
102
167
  return;
@@ -107,7 +172,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
107
172
  const entry = entries[0];
108
173
  if (!entry)
109
174
  return;
110
- const contentPx = entry.contentRect.height;
175
+ latestContentPxRef.current = entry.contentRect.height;
111
176
  if (measureRafRef.current !== null)
112
177
  return;
113
178
  const schedule = typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function'
@@ -115,16 +180,10 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
115
180
  : (cb) => setTimeout(cb, 0);
116
181
  measureRafRef.current = schedule(() => {
117
182
  measureRafRef.current = null;
118
- const rows = computeAutoHeightRows({
119
- contentPx,
120
- cardChromePx: DEFAULT_CARD_CHROME_PX,
121
- rowHeightPx: DEFAULT_ROW_HEIGHT,
122
- marginYPx: DEFAULT_MARGIN[1],
123
- });
124
- if (rows !== lastReportedRowsRef.current) {
125
- lastReportedRowsRef.current = rows;
126
- onAutoHeightChange(id, rows);
127
- }
183
+ const contentPx = latestContentPxRef.current;
184
+ if (contentPx === null)
185
+ return;
186
+ reportRows(contentPx);
128
187
  });
129
188
  });
130
189
  ro.observe(el);
@@ -137,7 +196,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
137
196
  }
138
197
  measureRafRef.current = null;
139
198
  };
140
- }, [autoHeight, id, onAutoHeightChange]);
199
+ }, [autoHeight, onAutoHeightChange, reportRows]);
141
200
  // DnD root-cause fix (2026-05-07): react-draggable@4 (used internally by
142
201
  // react-grid-layout@2) checks the drag handle via
143
202
  // `matchesSelectorAndParentsTo(target, handle, baseNode)` — it walks UP
@@ -163,8 +222,8 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
163
222
  // adapters then defaulted it back to true. With v2's handle gap fixed,
164
223
  // the safe default is true so any consumer who forgets to set it still
165
224
  // gets a usable canvas. Opt out per-page with isResizable={false}.
166
- isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames = DEFAULT_ADD_RELATIONSHIP_WIDGET_EVENT_NAMES, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }) {
167
- const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, applyAutoHeightRows, autoHeightRows, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
225
+ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, defaultAutoHeight, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames = DEFAULT_ADD_RELATIONSHIP_WIDGET_EVENT_NAMES, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }) {
226
+ const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleBreakpointChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, applyAutoHeightRows, autoHeightRows, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
168
227
  pageKey,
169
228
  defaultLayouts,
170
229
  defaultCols,
@@ -172,6 +231,7 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, addEntityW
172
231
  canEditPage,
173
232
  editorEventNames,
174
233
  preferenceAdapter,
234
+ defaultAutoHeight,
175
235
  });
176
236
  // Auto-height dispatcher (blueprint amendment A1, hardened per quality
177
237
  // review 2026-07-14). `GridItem`'s ResizeObserver calls this per-widget
@@ -309,9 +369,28 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, addEntityW
309
369
  // Until the first measurement lands, `autoHeightRows` has no entry and
310
370
  // the item's seed `h` renders as-is.
311
371
  .map((item) => {
312
- if (!item.autoHeight)
313
- return item;
314
- // HEAL a persisted `isResizable: false`.
372
+ // HEAL a persisted `isResizable: false` on ANY item, not just an
373
+ // autoHeight one (D-75: "individual cards can no longer be resized").
374
+ //
375
+ // The 0.6.0 heal was scoped to `item.autoHeight`, which left the
376
+ // whole non-autoHeight population — every card that opted out, and
377
+ // every layout persisted before `autoHeight` existed as a concept —
378
+ // permanently unresizable with no way back: the flag is re-persisted
379
+ // on each load, so clearing it by hand does not survive a refresh.
380
+ //
381
+ // The one legitimate source of a per-item `isResizable: false` is the
382
+ // Layers "lock" control, which writes it together with `static: true`
383
+ // and `isDraggable: false`. Requiring that full signature separates a
384
+ // deliberate lock (kept) from the 0.5.2 leak (dropped), so the heal
385
+ // can be widened without silently unlocking anything a user locked.
386
+ const lockedDeliberately = item.static === true || item.isDraggable === false;
387
+ const needsResizeHeal = item.isResizable === false && !lockedDeliberately;
388
+ if (!item.autoHeight) {
389
+ if (!needsResizeHeal)
390
+ return item;
391
+ const { isResizable: _dropped, ...unlocked } = item;
392
+ return unlocked;
393
+ }
315
394
  //
316
395
  // 0.5.2 set this at the render layer, and it leaked out through
317
396
  // `onLayoutChange` into the SAVED layout. Removing the override in
@@ -565,7 +644,11 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, addEntityW
565
644
  }) })] })), layerItems.length > 0 && (_jsxs("div", { className: "basis-full rounded-md border border-border bg-background/80 p-2", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [_jsx(Layers, { className: "h-3.5 w-3.5" }), "Layers"] }), _jsx("div", { className: "flex flex-col gap-1", children: layerItems.map((layer) => (_jsxs("div", { className: "flex items-center gap-1 rounded border border-border bg-card px-2 py-1", children: [_jsx("input", { type: "text", value: layer.label, onChange: (event) => handleLayerRename(layer.id, event.target.value), className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none", "aria-label": `Rename ${layer.label}` }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'up'), disabled: !layer.canMoveUp, "aria-label": `Move ${layer.label} up`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'down'), disabled: !layer.canMoveDown, "aria-label": `Move ${layer.label} down`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => setWidgetLocked(layer.id, !layer.locked), "aria-label": `${layer.locked ? 'Unlock' : 'Lock'} ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: layer.locked ? _jsx(Lock, { className: "h-3.5 w-3.5" }) : _jsx(Unlock, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => hideLayer(layer.id), "aria-label": `Hide ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(EyeOff, { className: "h-3.5 w-3.5" }) })] }, layer.id))) })] })), _jsxs("button", { type: "button", onClick: handleCompact, className: cn('ml-auto inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Layers, { className: "h-4 w-4 mr-1" }), "Compact Layout"] }), _jsxs("button", { type: "button", onClick: () => setResetConfirmOpen(true), className: cn('inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset to Default"] })] })] })), resetConfirmOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-overlay/50 p-4", role: "dialog", "aria-modal": "true", "aria-labelledby": "page-grid-reset-title", "aria-describedby": "page-grid-reset-description", children: _jsxs("div", { className: "max-w-md rounded-lg bg-card p-5 shadow-lg border border-border", children: [_jsx("h2", { id: "page-grid-reset-title", className: "text-lg font-semibold", children: "Reset page layout?" }), _jsx("p", { id: "page-grid-reset-description", className: "mt-2 text-sm text-muted-foreground", children: "This will discard your current layout and restore all widgets to their default positions." }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx("button", { type: "button", ref: resetCancelButtonRef, onClick: () => setResetConfirmOpen(false), className: cn('inline-flex items-center rounded-md border border-border px-3 py-2 text-sm transition-colors', 'bg-card text-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: "Cancel" }), _jsx("button", { type: "button", ref: resetConfirmButtonRef, onClick: () => {
566
645
  handleReset();
567
646
  setResetConfirmOpen(false);
568
- }, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-destructive-foreground shadow hover:bg-destructive/90', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/40 focus-visible:ring-offset-1'), children: "Reset to Default" })] })] }) })), _jsx("div", { ref: containerRef, className: cn('relative z-0 overflow-visible', isEditing && 'min-h-[200px] bg-muted/30'), "data-page-grid-editing": isEditing || undefined, children: _jsx("div", { className: "relative z-0", style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout page-grid-canvas", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: DEFAULT_ROW_HEIGHT, onLayoutChange: onLayoutChange, dragConfig: {
647
+ }, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-destructive-foreground shadow hover:bg-destructive/90', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/40 focus-visible:ring-offset-1'), children: "Reset to Default" })] })] }) })), _jsx("div", { ref: containerRef, className: cn('relative z-0 overflow-visible', isEditing && 'min-h-[200px] bg-muted/30'), "data-page-grid-editing": isEditing || undefined, children: _jsx("div", { className: "relative z-0", style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout page-grid-canvas", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: DEFAULT_ROW_HEIGHT, onLayoutChange: onLayoutChange,
648
+ // Tells the hook which breakpoint a gesture belongs to, so the edit
649
+ // is folded back onto `lg` instead of into a derived breakpoint
650
+ // that is regenerated (and therefore discarded) on the next render.
651
+ onBreakpointChange: handleBreakpointChange, dragConfig: {
569
652
  enabled: isEditing,
570
653
  handle: '.drag-handle',
571
654
  bounded: false,
@@ -1,15 +1,72 @@
1
1
  import type { GridLayouts } from './types.js';
2
2
  export declare const RESPONSIVE_BREAKPOINTS: readonly ["lg", "md", "sm", "xs", "xxs"];
3
3
  export type ResponsiveBreakpoint = (typeof RESPONSIVE_BREAKPOINTS)[number];
4
+ /**
5
+ * Breakpoints whose layout MIRRORS `lg` verbatim.
6
+ *
7
+ * D-75 root cause (operator directive 2026-08-13: "columns available to move
8
+ * cards into do not respect the columns slider").
9
+ * ---------------------------------------------------------------------------
10
+ * react-grid-layout picks its breakpoint from the **container** width, not the
11
+ * viewport — `<Responsive width={containerWidth}>` in `PageGridLayout`. Inside
12
+ * an app shell with a sidebar and page padding, a 1280px laptop presents a
13
+ * container around 950px and a 1440px desktop around 1100px. Both land BELOW
14
+ * the `md: 996` / `lg: 1200` thresholds, so the great majority of real desktop
15
+ * sessions render at `sm` or `md`, not `lg`.
16
+ *
17
+ * `sm` used to be derived by `stackToSingleColumn`, which sets `w: cols` on
18
+ * every item — one card per row at full width, whatever the column count. So
19
+ * on an ordinary laptop the columns slider changed `activeCols` while every
20
+ * card stayed stretched across all of them: the control provably did nothing,
21
+ * and the SE resize handle could not widen a card that was already at the grid
22
+ * bound nor narrow one whose `minW` had been clamped up to `cols`.
23
+ *
24
+ * `md` and `sm` therefore mirror `lg`: at a container width of 768px and above
25
+ * there is room for the arrangement the user chose, and honouring their choice
26
+ * is the whole point of the control. Genuine phone widths (`xs` < 768px
27
+ * container, `xxs` < 480px) still stack, which is what that code was for.
28
+ */
29
+ export declare const MIRRORED_BREAKPOINTS: readonly ["md", "sm"];
30
+ /**
31
+ * Breakpoints derived as a single full-width stack — narrow enough that a
32
+ * multi-column arrangement cannot be read.
33
+ */
34
+ export declare const STACKED_BREAKPOINTS: readonly ["xs", "xxs"];
35
+ /**
36
+ * Every breakpoint this module DERIVES from `lg`.
37
+ *
38
+ * A derived breakpoint is a render-time projection, never a stored fact. It
39
+ * must not reach the preference adapter: react-grid-layout echoes ALL
40
+ * breakpoints back through `onLayoutChange` on every gesture, so persisting
41
+ * them verbatim froze the derivation — a layout derived once at 12 columns was
42
+ * then "consumer-supplied" forever and stopped tracking `lg`. See
43
+ * `canonicaliseLayoutForPersist` in `usePageGridLayout.ts`.
44
+ */
45
+ export declare const DERIVED_BREAKPOINTS: readonly ResponsiveBreakpoint[];
46
+ export declare function isDerivedBreakpoint(breakpoint: string): boolean;
47
+ /**
48
+ * True when a gesture at `breakpoint` can be written straight back onto `lg`.
49
+ *
50
+ * `lg`, `md` and `sm` all render the same array against the same column count,
51
+ * so an edit made at any of them is expressible in `lg` with no loss. `xs` and
52
+ * `xxs` render a full-width stack, where an edit carries only a vertical order
53
+ * and would flatten a multi-column desktop arrangement if applied to `lg`.
54
+ */
55
+ export declare function isCanonicalisableBreakpoint(breakpoint: string): boolean;
4
56
  export interface BuildResponsiveLayoutsOptions {
5
57
  cols?: number;
6
58
  }
7
59
  /**
8
60
  * Ensure a GridLayouts object has entries for all responsive breakpoints
9
- * (lg, md, sm, xs, xxs). Missing smaller breakpoints (sm/xs/xxs) are derived
10
- * from `lg` as a single-column vertical stack so that consumer-supplied
11
- * lg-only layouts no longer squash on phone viewports. Missing `md` is
12
- * mirrored from `lg`. Consumer-supplied breakpoints are preserved verbatim.
61
+ * (lg, md, sm, xs, xxs).
62
+ *
63
+ * - `md` and `sm` mirror `lg` so the user's chosen column arrangement survives
64
+ * every desktop and tablet container width (see {@link MIRRORED_BREAKPOINTS}).
65
+ * - `xs` and `xxs` are derived as a single-column stack so a phone viewport
66
+ * never squashes cards into unreadable slivers.
67
+ * - Consumer-supplied breakpoints are preserved verbatim. Supplying one opts
68
+ * that breakpoint OUT of tracking `lg`, including out of the columns slider —
69
+ * prefer not to.
13
70
  */
14
71
  export declare function buildResponsiveLayouts(layouts: GridLayouts, { cols }?: BuildResponsiveLayoutsOptions): GridLayouts;
15
72
  //# sourceMappingURL=buildResponsiveLayouts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildResponsiveLayouts.d.ts","sourceRoot":"","sources":["../src/buildResponsiveLayouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9D,eAAO,MAAM,sBAAsB,0CAA2C,CAAC;AAC/E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAkBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,EACpB,EAAE,IAAS,EAAE,GAAE,6BAAkC,GAChD,WAAW,CAyBb"}
1
+ {"version":3,"file":"buildResponsiveLayouts.d.ts","sourceRoot":"","sources":["../src/buildResponsiveLayouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9D,eAAO,MAAM,sBAAsB,0CAA2C,CAAC;AAC/E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,uBAAwB,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wBAAyB,CAAC;AAE1D;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,oBAAoB,EAG9D,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,EACpB,EAAE,IAAS,EAAE,GAAE,6BAAkC,GAChD,WAAW,CAyBb"}