@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.
- package/dist/CanvasCard.d.ts +52 -0
- package/dist/CanvasCard.d.ts.map +1 -0
- package/dist/CanvasCard.js +18 -0
- package/dist/DraggableCardPage.d.ts +118 -0
- package/dist/DraggableCardPage.d.ts.map +1 -0
- package/dist/DraggableCardPage.js +110 -0
- package/dist/PageGridLayout.d.ts +1 -1
- package/dist/PageGridLayout.d.ts.map +1 -1
- package/dist/PageGridLayout.js +102 -19
- package/dist/buildResponsiveLayouts.d.ts +61 -4
- package/dist/buildResponsiveLayouts.d.ts.map +1 -1
- package/dist/buildResponsiveLayouts.js +77 -5
- package/dist/canvasCardLayout.d.ts +62 -0
- package/dist/canvasCardLayout.d.ts.map +1 -0
- package/dist/canvasCardLayout.js +120 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/scanner/cardSurfaceScanner.d.ts +120 -0
- package/dist/scanner/cardSurfaceScanner.d.ts.map +1 -0
- package/dist/scanner/cardSurfaceScanner.js +289 -0
- package/dist/scanner/index.d.ts +3 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usePageGridLayout.d.ts +22 -1
- package/dist/usePageGridLayout.d.ts.map +1 -1
- package/dist/usePageGridLayout.js +197 -66
- package/package.json +8 -3
|
@@ -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';
|
package/dist/PageGridLayout.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/PageGridLayout.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
//
|
|
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,
|
|
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).
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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;
|
|
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"}
|