@bsuite/page-builder 0.6.3 → 0.8.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/PageGridLayout.d.ts +1 -1
- package/dist/PageGridLayout.d.ts.map +1 -1
- package/dist/PageGridLayout.js +190 -21
- package/dist/RelationshipField.d.ts +77 -0
- package/dist/RelationshipField.d.ts.map +1 -0
- package/dist/RelationshipField.js +158 -0
- package/dist/buildResponsiveLayouts.d.ts +61 -4
- package/dist/buildResponsiveLayouts.d.ts.map +1 -1
- package/dist/buildResponsiveLayouts.js +77 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/relationshipCatalog.d.ts +22 -0
- package/dist/relationshipCatalog.d.ts.map +1 -0
- package/dist/relationshipCatalog.js +26 -0
- package/dist/types.d.ts +102 -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 +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { startTransition, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
2
2
|
import { verticalCompactor } from 'react-grid-layout';
|
|
3
|
-
import { buildResponsiveLayouts } from './buildResponsiveLayouts.js';
|
|
3
|
+
import { buildResponsiveLayouts, isCanonicalisableBreakpoint, isDerivedBreakpoint, } from './buildResponsiveLayouts.js';
|
|
4
4
|
import { defaultPreferenceAdapter } from './preferences.js';
|
|
5
5
|
import { rescaleLayout } from './rescaleLayout.js';
|
|
6
6
|
export const DEFAULT_EDITOR_EVENT_NAMES = [
|
|
@@ -20,7 +20,43 @@ export const DEFAULT_EDITOR_EVENT_NAMES = [
|
|
|
20
20
|
* launchers scope by page if they handle multiple grids on one screen.
|
|
21
21
|
*/
|
|
22
22
|
export const PAGE_GRID_EDITING_EVENT = 'bsuite-page-grid-editing';
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Package-wide layout epoch, added to EVERY consumer's `layoutVersion`.
|
|
25
|
+
*
|
|
26
|
+
* When a default that governs all grids changes, a saved layout produced under
|
|
27
|
+
* the old default has to be invalidated — and there is no way to reach ~330
|
|
28
|
+
* call sites across six apps to hand-bump each one. crm7's `DraggableCardPage`
|
|
29
|
+
* already solved this locally with its own `LAYOUT_EPOCH`; this is the same
|
|
30
|
+
* lever one level down, so it also covers the raw `PageGridLayout` consumers
|
|
31
|
+
* (Dashboard, Billing, GTO, Analytics, the throughput and braden pages) that
|
|
32
|
+
* no app-level epoch can see.
|
|
33
|
+
*
|
|
34
|
+
* 0 -> 1000 (2026-08-13, D-75/D-76): two all-grids defaults changed in the same
|
|
35
|
+
* release — `autoHeight` now defaults to true for raw consumers, and md/sm no
|
|
36
|
+
* longer collapse to a full-width stack. Every stored layout predates both and
|
|
37
|
+
* was additionally polluted with frozen derived breakpoints, so all of them
|
|
38
|
+
* reset once to the corrected defaults.
|
|
39
|
+
*
|
|
40
|
+
* Per-page `layoutVersion` bumps keep working on top; this only moves the
|
|
41
|
+
* floor.
|
|
42
|
+
*/
|
|
43
|
+
export const PACKAGE_LAYOUT_EPOCH = 1000;
|
|
44
|
+
/**
|
|
45
|
+
* Default for `GridLayoutItem.autoHeight` when an item does not state one.
|
|
46
|
+
*
|
|
47
|
+
* D-76 ("cards render half cut off on page open"). `DraggableCardPage` has
|
|
48
|
+
* defaulted this to true since the E5a flip, but a RAW `PageGridLayout`
|
|
49
|
+
* consumer builds its layout array by hand and inherits nothing — the flag had
|
|
50
|
+
* to be written on every item. Sixteen of the eighteen raw consumers across the
|
|
51
|
+
* suite never did, so their cards were fixed at whatever seed `h` the author
|
|
52
|
+
* guessed and clipped everything past it behind `overflow-auto`. That is not a
|
|
53
|
+
* per-page authoring mistake repeated sixteen times; it is a default in the
|
|
54
|
+
* wrong place. An item that genuinely owns its own scroll (a virtualized list)
|
|
55
|
+
* opts out with an explicit `autoHeight: false`.
|
|
56
|
+
*/
|
|
57
|
+
const DEFAULT_ITEM_AUTO_HEIGHT = true;
|
|
58
|
+
export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, layoutVersion = 1, canEditPage = true, editorEventNames = DEFAULT_EDITOR_EVENT_NAMES, preferenceAdapter = defaultPreferenceAdapter, defaultAutoHeight, }) {
|
|
59
|
+
const effectiveLayoutVersion = layoutVersion + PACKAGE_LAYOUT_EPOCH;
|
|
24
60
|
const containerRef = useRef(null);
|
|
25
61
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
26
62
|
const [isEditing, setIsEditingState] = useState(false);
|
|
@@ -65,18 +101,18 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
65
101
|
useEffect(() => {
|
|
66
102
|
if (!prefsLoaded)
|
|
67
103
|
return;
|
|
68
|
-
if ((savedLayoutVersion ?? 0) <
|
|
104
|
+
if ((savedLayoutVersion ?? 0) < effectiveLayoutVersion) {
|
|
69
105
|
startTransition(() => {
|
|
70
106
|
setSavedLayout(defaultLayouts);
|
|
71
107
|
setLayoutCols(defaultCols);
|
|
72
108
|
setBaseCols(defaultCols);
|
|
73
|
-
setSavedLayoutVersion(
|
|
109
|
+
setSavedLayoutVersion(effectiveLayoutVersion);
|
|
74
110
|
});
|
|
75
111
|
}
|
|
76
112
|
}, [
|
|
77
113
|
defaultCols,
|
|
78
114
|
defaultLayouts,
|
|
79
|
-
|
|
115
|
+
effectiveLayoutVersion,
|
|
80
116
|
prefsLoaded,
|
|
81
117
|
savedLayoutVersion,
|
|
82
118
|
setBaseCols,
|
|
@@ -89,7 +125,26 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
89
125
|
const rawLayouts = useMemo(() => {
|
|
90
126
|
if (Object.keys(savedLayout || {}).length === 0)
|
|
91
127
|
return defaultLayouts;
|
|
92
|
-
|
|
128
|
+
// Drop DERIVED breakpoints out of a stored layout before anything reads it.
|
|
129
|
+
//
|
|
130
|
+
// Until 0.8.0 react-grid-layout's `onLayoutChange` echo persisted md/sm/xs/
|
|
131
|
+
// xxs alongside lg. `buildResponsiveLayouts` then treated them as
|
|
132
|
+
// consumer-supplied and preserved them verbatim, so a layout derived once —
|
|
133
|
+
// at whatever column count happened to be active that day — outlived every
|
|
134
|
+
// later change to `lg` and to the columns slider (D-75). Every user who has
|
|
135
|
+
// ever dragged a card on any page has that pollution stored, so the fix has
|
|
136
|
+
// to heal on read rather than wait for a layoutVersion bump; `lg` (the only
|
|
137
|
+
// breakpoint that was ever authoritative) is preserved untouched, so nobody
|
|
138
|
+
// loses the arrangement they made.
|
|
139
|
+
const stored = savedLayout;
|
|
140
|
+
const saved = {};
|
|
141
|
+
for (const bp of Object.keys(stored)) {
|
|
142
|
+
if (isDerivedBreakpoint(bp))
|
|
143
|
+
continue;
|
|
144
|
+
saved[bp] = stored[bp];
|
|
145
|
+
}
|
|
146
|
+
if (!saved.lg)
|
|
147
|
+
saved.lg = stored.lg ?? defaultLayouts.lg ?? [];
|
|
93
148
|
let changed = false;
|
|
94
149
|
const merged = {};
|
|
95
150
|
for (const bp of Object.keys(saved)) {
|
|
@@ -107,12 +162,28 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
107
162
|
merged[bp] = savedItems;
|
|
108
163
|
}
|
|
109
164
|
}
|
|
110
|
-
|
|
165
|
+
// `merged` is always returned (never the raw `savedLayout`) because the
|
|
166
|
+
// derived-breakpoint filter above may have removed keys even when no
|
|
167
|
+
// default item was missing — handing back `savedLayout` would smuggle the
|
|
168
|
+
// stale derived breakpoints straight past the heal.
|
|
169
|
+
void changed;
|
|
170
|
+
return merged;
|
|
111
171
|
}, [savedLayout, defaultLayouts]);
|
|
112
172
|
const currentLayouts = useMemo(() => {
|
|
113
173
|
const rescaled = baseCols !== layoutCols ? rescaleLayout(rawLayouts, baseCols, layoutCols) : rawLayouts;
|
|
114
|
-
|
|
115
|
-
|
|
174
|
+
const responsive = buildResponsiveLayouts(rescaled, { cols: layoutCols });
|
|
175
|
+
// Resolve the autoHeight default HERE rather than at the render layer, so
|
|
176
|
+
// `stripAutoHeightRows` (which reads this object through
|
|
177
|
+
// `currentLayoutsForStripRef`) and `PageGridLayout`'s `activeLayouts` agree
|
|
178
|
+
// on which items are auto-height. They disagreed once before and the
|
|
179
|
+
// measured height leaked into storage.
|
|
180
|
+
const resolvedAutoHeight = defaultAutoHeight ?? DEFAULT_ITEM_AUTO_HEIGHT;
|
|
181
|
+
const resolved = { lg: [] };
|
|
182
|
+
for (const bp of Object.keys(responsive)) {
|
|
183
|
+
resolved[bp] = (responsive[bp] ?? []).map((item) => item.autoHeight === undefined ? { ...item, autoHeight: resolvedAutoHeight } : item);
|
|
184
|
+
}
|
|
185
|
+
return resolved;
|
|
186
|
+
}, [rawLayouts, baseCols, layoutCols, defaultAutoHeight]);
|
|
116
187
|
useEffect(() => {
|
|
117
188
|
if (baseCols !== layoutCols) {
|
|
118
189
|
const rescaled = rescaleLayout(rawLayouts, baseCols, layoutCols);
|
|
@@ -265,6 +336,67 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
265
336
|
useEffect(() => {
|
|
266
337
|
currentLayoutsForStripRef.current = currentLayouts;
|
|
267
338
|
}, [currentLayouts]);
|
|
339
|
+
/**
|
|
340
|
+
* The breakpoint react-grid-layout is currently rendering, tracked so a
|
|
341
|
+
* gesture can be written back to the breakpoint that OWNS it.
|
|
342
|
+
*
|
|
343
|
+
* Seeded to `lg` and corrected by `<Responsive onBreakpointChange>`, which
|
|
344
|
+
* fires on mount once the container width is known.
|
|
345
|
+
*/
|
|
346
|
+
const activeBreakpointRef = useRef('lg');
|
|
347
|
+
const handleBreakpointChange = useCallback((breakpoint) => {
|
|
348
|
+
activeBreakpointRef.current = breakpoint;
|
|
349
|
+
}, []);
|
|
350
|
+
/**
|
|
351
|
+
* Reduce react-grid-layout's all-breakpoints echo down to the ONE breakpoint
|
|
352
|
+
* that is authoritative — `lg` — or refuse the write.
|
|
353
|
+
*
|
|
354
|
+
* Two defects this closes, both reported as D-75:
|
|
355
|
+
*
|
|
356
|
+
* 1. **The derived breakpoints froze.** RGL echoes every breakpoint on every
|
|
357
|
+
* gesture. Persisting them made them consumer-supplied, so
|
|
358
|
+
* `buildResponsiveLayouts` stopped deriving them and the columns slider
|
|
359
|
+
* (which rescales `lg`) could no longer reach the breakpoint actually on
|
|
360
|
+
* screen. Below a 1200px container — i.e. most real desktop sessions once
|
|
361
|
+
* the sidebar is subtracted — moving the slider changed nothing at all.
|
|
362
|
+
*
|
|
363
|
+
* 2. **An edit made at `md`/`sm` was invisible at `lg`.** The gesture landed
|
|
364
|
+
* in `layouts.md`; `layouts.lg` kept its old values; the same user on a
|
|
365
|
+
* wider monitor saw their arrangement revert. `md` and `sm` render the
|
|
366
|
+
* same array against the same column count as `lg`, so folding the
|
|
367
|
+
* gesture onto `lg` is lossless and makes one arrangement follow the user
|
|
368
|
+
* across every window size.
|
|
369
|
+
*
|
|
370
|
+
* `xs`/`xxs` return `null` (no write). Those breakpoints render a full-width
|
|
371
|
+
* stack, so a gesture there carries only a vertical order; applying it to
|
|
372
|
+
* `lg` would flatten a multi-column desktop arrangement that the user cannot
|
|
373
|
+
* even see on that device. Refusing the write is the conservative half of
|
|
374
|
+
* the trade: a phone visit can no longer destroy the desktop layout, at the
|
|
375
|
+
* cost of phone-only reordering not persisting. Stated in the PR, not hidden.
|
|
376
|
+
*/
|
|
377
|
+
const canonicaliseLayoutForPersist = useCallback((layouts) => {
|
|
378
|
+
const breakpoint = activeBreakpointRef.current;
|
|
379
|
+
if (!isCanonicalisableBreakpoint(breakpoint))
|
|
380
|
+
return null;
|
|
381
|
+
const items = layouts[breakpoint] ?? layouts.lg;
|
|
382
|
+
if (!items)
|
|
383
|
+
return null;
|
|
384
|
+
const next = { lg: items };
|
|
385
|
+
// Preserve any breakpoint a CONSUMER supplied deliberately (never a
|
|
386
|
+
// derived one) so an opt-out layout is not silently discarded.
|
|
387
|
+
for (const bp of Object.keys(layouts)) {
|
|
388
|
+
if (bp === 'lg' || isDerivedBreakpoint(bp))
|
|
389
|
+
continue;
|
|
390
|
+
next[bp] = layouts[bp] ?? [];
|
|
391
|
+
}
|
|
392
|
+
return next;
|
|
393
|
+
}, []);
|
|
394
|
+
const commitLayout = useCallback((layouts) => {
|
|
395
|
+
const canonical = canonicaliseLayoutForPersist(layouts);
|
|
396
|
+
if (!canonical)
|
|
397
|
+
return;
|
|
398
|
+
setSavedLayout(stripAutoHeightRows(canonical));
|
|
399
|
+
}, [canonicaliseLayoutForPersist, setSavedLayout, stripAutoHeightRows]);
|
|
268
400
|
const onLayoutChange = useCallback((_layout, layouts) => {
|
|
269
401
|
if (!isEditing)
|
|
270
402
|
return;
|
|
@@ -272,7 +404,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
272
404
|
if (layoutCommitFrameRef.current !== null)
|
|
273
405
|
return;
|
|
274
406
|
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
|
|
275
|
-
|
|
407
|
+
commitLayout(layouts);
|
|
276
408
|
return;
|
|
277
409
|
}
|
|
278
410
|
// Trailing rAF — lets a burst of resize ticks coalesce into one commit.
|
|
@@ -281,10 +413,10 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
281
413
|
const pending = pendingLayoutRef.current;
|
|
282
414
|
if (pending) {
|
|
283
415
|
pendingLayoutRef.current = null;
|
|
284
|
-
|
|
416
|
+
commitLayout(pending);
|
|
285
417
|
}
|
|
286
418
|
});
|
|
287
|
-
}, [
|
|
419
|
+
}, [commitLayout, isEditing]);
|
|
288
420
|
useEffect(() => {
|
|
289
421
|
return () => {
|
|
290
422
|
if (layoutCommitFrameRef.current !== null &&
|
|
@@ -300,6 +432,23 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
300
432
|
setLayoutCols(newCols);
|
|
301
433
|
setBaseCols(newCols);
|
|
302
434
|
}, [currentLayouts, layoutCols, setBaseCols, setLayoutCols, setSavedLayout]);
|
|
435
|
+
/**
|
|
436
|
+
* Build the object handed to the preference adapter from a new `lg` array.
|
|
437
|
+
*
|
|
438
|
+
* Only `lg` and consumer-supplied breakpoints are stored; md/sm/xs/xxs are
|
|
439
|
+
* re-derived on every render. Before 0.8.0 every mutator below spread
|
|
440
|
+
* `currentLayouts` (which contains the derived breakpoints) into its write,
|
|
441
|
+
* which re-froze them one gesture after `rawLayouts` had healed them.
|
|
442
|
+
*/
|
|
443
|
+
const layoutsWithLg = useCallback((items) => {
|
|
444
|
+
const next = { lg: items };
|
|
445
|
+
for (const bp of Object.keys(currentLayouts)) {
|
|
446
|
+
if (bp === 'lg' || isDerivedBreakpoint(bp))
|
|
447
|
+
continue;
|
|
448
|
+
next[bp] = currentLayouts[bp] ?? [];
|
|
449
|
+
}
|
|
450
|
+
return next;
|
|
451
|
+
}, [currentLayouts]);
|
|
303
452
|
const handleCompact = useCallback(() => {
|
|
304
453
|
if (!currentLayouts.lg)
|
|
305
454
|
return;
|
|
@@ -316,8 +465,8 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
316
465
|
placed.push(compacted);
|
|
317
466
|
return compacted;
|
|
318
467
|
});
|
|
319
|
-
startTransition(() => setSavedLayout(
|
|
320
|
-
}, [currentLayouts, setSavedLayout]);
|
|
468
|
+
startTransition(() => setSavedLayout(layoutsWithLg(result)));
|
|
469
|
+
}, [currentLayouts, layoutsWithLg, setSavedLayout]);
|
|
321
470
|
const handleReset = useCallback(() => {
|
|
322
471
|
startTransition(() => {
|
|
323
472
|
setSavedLayout(defaultLayouts);
|
|
@@ -326,7 +475,6 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
326
475
|
});
|
|
327
476
|
}, [defaultCols, defaultLayouts, setBaseCols, setLayoutCols, setSavedLayout]);
|
|
328
477
|
const addWidget = useCallback((widgetKey, initialSize) => {
|
|
329
|
-
const bps = Object.keys(currentLayouts);
|
|
330
478
|
const maxY = (currentLayouts.lg ?? []).reduce((max, item) => Math.max(max, item.y + item.h), 0);
|
|
331
479
|
const newItem = {
|
|
332
480
|
i: widgetKey,
|
|
@@ -337,60 +485,42 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
337
485
|
minW: initialSize?.minW,
|
|
338
486
|
minH: initialSize?.minH,
|
|
339
487
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
updated[bp] = [...(currentLayouts[bp] ?? []), newItem];
|
|
343
|
-
}
|
|
344
|
-
startTransition(() => setSavedLayout(updated));
|
|
345
|
-
}, [currentLayouts, layoutCols, setSavedLayout]);
|
|
488
|
+
startTransition(() => setSavedLayout(layoutsWithLg([...(currentLayouts.lg ?? []), newItem])));
|
|
489
|
+
}, [currentLayouts, layoutCols, layoutsWithLg, setSavedLayout]);
|
|
346
490
|
const moveWidget = useCallback((widgetKey, direction) => {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
...withoutMoved.slice(0, targetIndex),
|
|
364
|
-
moved,
|
|
365
|
-
...withoutMoved.slice(targetIndex),
|
|
366
|
-
];
|
|
367
|
-
updated[bp] = nextItems;
|
|
368
|
-
}
|
|
369
|
-
startTransition(() => setSavedLayout(updated));
|
|
370
|
-
}, [currentLayouts, setSavedLayout]);
|
|
491
|
+
const items = [...(currentLayouts.lg ?? [])];
|
|
492
|
+
const index = items.findIndex((item) => item.i === widgetKey);
|
|
493
|
+
if (index === -1)
|
|
494
|
+
return;
|
|
495
|
+
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
|
496
|
+
if (targetIndex < 0 || targetIndex >= items.length)
|
|
497
|
+
return;
|
|
498
|
+
const moved = items[index];
|
|
499
|
+
const withoutMoved = [...items.slice(0, index), ...items.slice(index + 1)];
|
|
500
|
+
const nextItems = [
|
|
501
|
+
...withoutMoved.slice(0, targetIndex),
|
|
502
|
+
moved,
|
|
503
|
+
...withoutMoved.slice(targetIndex),
|
|
504
|
+
];
|
|
505
|
+
startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
|
|
506
|
+
}, [currentLayouts, layoutsWithLg, setSavedLayout]);
|
|
371
507
|
const setWidgetLocked = useCallback((widgetKey, locked) => {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
startTransition(() => setSavedLayout(updated));
|
|
386
|
-
}, [currentLayouts, setSavedLayout]);
|
|
508
|
+
const nextItems = (currentLayouts.lg ?? []).map((item) => {
|
|
509
|
+
if (item.i !== widgetKey)
|
|
510
|
+
return item;
|
|
511
|
+
return {
|
|
512
|
+
...item,
|
|
513
|
+
static: locked,
|
|
514
|
+
isDraggable: !locked,
|
|
515
|
+
isResizable: !locked,
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
|
|
519
|
+
}, [currentLayouts, layoutsWithLg, setSavedLayout]);
|
|
387
520
|
const removeWidget = useCallback((widgetKey) => {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
startTransition(() => setSavedLayout(updated));
|
|
393
|
-
}, [currentLayouts, setSavedLayout]);
|
|
521
|
+
const nextItems = (currentLayouts.lg ?? []).filter((item) => item.i !== widgetKey);
|
|
522
|
+
startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
|
|
523
|
+
}, [currentLayouts, layoutsWithLg, setSavedLayout]);
|
|
394
524
|
/**
|
|
395
525
|
* Measured auto-height overrides (widgetKey -> rows). DERIVED, in-memory-
|
|
396
526
|
* only state (quality-review design ruling, 2026-07-14):
|
|
@@ -467,6 +597,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
467
597
|
activeCompactor,
|
|
468
598
|
onLayoutChange,
|
|
469
599
|
handleColumnChange,
|
|
600
|
+
handleBreakpointChange,
|
|
470
601
|
handleCompact,
|
|
471
602
|
handleReset,
|
|
472
603
|
addWidget,
|