@bsuite/page-builder 2.7.1-next.5 → 2.7.1-next.7

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/README.md CHANGED
@@ -71,6 +71,15 @@ stays flat. Consumers must read all three variables; chrome-owning grid items
71
71
  already do. Default painted grid cards now use neutral elevation 2, with
72
72
  interaction feedback, matching the D2C card recovery.
73
73
 
74
+ From `2.7.1-next.6`, app Cards import `useCardPadding` and
75
+ `CardPaddingBoundary` from `@bsuite/page-builder/card-padding`. Read the
76
+ inset before rendering the Card, apply a defined value to its painted surface,
77
+ and wrap its children with a boundary without a padding prop. Inner Cards then
78
+ keep their spacing; a nested grid establishes its own independent inset.
79
+ The grid supplies no child inset when it paints chrome itself. This replaces
80
+ consumer CSS `@scope` rules, which do not cover the supported Safari/Firefox
81
+ baseline. The subpath keeps a standalone Card import separate from grid code.
82
+
74
83
  While the canvas editor is open, each grid item renders an in-flow strip
75
84
  (`[data-slot="grid-item-editor-chrome"]`) above the card content: the widget
76
85
  name and a Hide control. The strip is **not** absolutely positioned over the
@@ -1 +1 @@
1
- {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAEA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAsBxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AAE7F,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAC5D,MAAM,WAAW,uBAAuB;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAiqB5D,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,gBAAgB,EAKhB,UAAkB,EAClB,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBAkhCrB"}
1
+ {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAGA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAsBxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AAE7F,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAC5D,MAAM,WAAW,uBAAuB;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAmrB5D,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,gBAAgB,EAKhB,UAAkB,EAClB,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBAoqCrB"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ElementScopeProvider } from './elementScope.js';
3
- import { ArrowDown, ArrowUp, ChevronDown, ChevronUp, Eye, EyeOff, Layers, LayoutGrid, Lock, Plus, RotateCcw, Save, Settings2, Unlock } from 'lucide-react';
3
+ import { CardPaddingBoundary } from './cardPadding.js';
4
+ import { ArrowDown, ArrowUp, ChevronDown, ChevronUp, Eye, EyeOff, Layers, LayoutGrid, Lock, Plus, RefreshCw, RotateCcw, Save, Settings2, Unlock, X } from 'lucide-react';
4
5
  import React, { startTransition, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
5
6
  import { Responsive } from 'react-grid-layout';
6
7
  import { gridBounds, minMaxSize, minSize } from 'react-grid-layout/core';
@@ -10,7 +11,7 @@ import { computeAutoHeightRows } from './autoHeight.js';
10
11
  import { BORDER_STYLES, BORDER_TONES, BORDER_WIDTH_RANGE, DEFAULT_CARD_STYLE, PADDING_RANGE, RADIUS_RANGE, describeCardStyle, isDefaultCardStyle, normaliseCardStyle, toCssVars, } from './cardStyle.js';
11
12
  import { defaultPreferenceAdapter } from './preferences.js';
12
13
  import { isRelationshipWritable } from './relationshipCatalog.js';
13
- import { usePageGridLayout } from './usePageGridLayout.js';
14
+ import { combinePreferenceStatuses, preferenceStatusOf, usePageGridLayout } from './usePageGridLayout.js';
14
15
  import { cn } from './utils.js';
15
16
  /** Ask one mounted canvas to acknowledge its preferences before closing. */
16
17
  export const PAGE_GRID_SAVE_EVENT = 'bsuite-page-grid-save';
@@ -130,8 +131,8 @@ const RGLResizeHandle = React.forwardRef(function RGLResizeHandle({ axis = 'se',
130
131
  * scroll away. Drag still starts from the strip (outer `.drag-handle`); hide
131
132
  * is a `button` + `data-no-drag` so the existing cancel selector ignores it.
132
133
  */
133
- function GridItemEditorChrome({ id, label, onHide, }) {
134
- return (_jsxs("div", { "data-slot": "grid-item-editor-chrome", className: "flex min-w-0 flex-none items-center gap-1 px-2 py-1 bg-muted text-muted-foreground", children: [_jsx("span", { className: "min-w-0 truncate text-[10px] px-1.5 py-0.5 rounded-md font-medium", title: label, children: label }), _jsx("button", { type: "button", className: "ml-auto h-6 w-6 shrink-0 rounded-full flex items-center justify-center bg-destructive hover:bg-destructive text-destructive-foreground shadow transition-colors", "data-no-drag": true, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
134
+ function GridItemEditorChrome({ id, label, onHide, hideDisabled = false, }) {
135
+ return (_jsxs("div", { "data-slot": "grid-item-editor-chrome", className: "flex min-w-0 flex-none items-center gap-1 px-2 py-1 bg-muted text-muted-foreground", children: [_jsx("span", { className: "min-w-0 truncate text-[10px] px-1.5 py-0.5 rounded-md font-medium", title: label, children: label }), _jsx("button", { type: "button", className: "ml-auto h-6 w-6 shrink-0 rounded-full flex items-center justify-center bg-destructive hover:bg-destructive text-destructive-foreground shadow transition-colors disabled:opacity-50 disabled:pointer-events-none", disabled: hideDisabled, "data-no-drag": true, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
135
136
  event.stopPropagation();
136
137
  onHide(id);
137
138
  }, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })] }));
@@ -168,7 +169,7 @@ function warnOnceAboutCollapsedContent(el) {
168
169
  + '(an aspect ratio, a px/rem height, or min-height), or pass autoHeight={false} on '
169
170
  + 'this card so it renders into the constrained scrollable wrapper instead.');
170
171
  }
171
- const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label, pageKey, onHide, autoHeight, onAutoHeightChange, chrome = false, cardStyleVars, children: injectedChildren, className: injectedClassName, style: injectedStyle, ...rest }, ref) {
172
+ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label, pageKey, onHide, editingPaused = false, autoHeight, onAutoHeightChange, chrome = false, cardStyleVars, children: injectedChildren, className: injectedClassName, style: injectedStyle, ...rest }, ref) {
172
173
  // Auto-height measurement (blueprint amendment A1). `measureRef` wraps
173
174
  // `content` with NO height constraint of its own — its parent has
174
175
  // `overflow-hidden` + `flex-1 min-h-0` (clips *painting* to the current
@@ -187,7 +188,10 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
187
188
  * of card overflow across 70 sites. An extra element here would have been a
188
189
  * real risk; a provider is not one.
189
190
  */
190
- const scopedContent = (_jsx(ElementScopeProvider, { scope: { pageKey, cardKey: id, cardLabel: label, isEditing }, children: content }));
191
+ const cardPadding = !chrome && cardStyleVars && '--card-padding' in cardStyleVars
192
+ && typeof cardStyleVars['--card-padding'] === 'string'
193
+ ? cardStyleVars['--card-padding'] : undefined;
194
+ const scopedContent = (_jsx(ElementScopeProvider, { scope: { pageKey, cardKey: id, cardLabel: label, isEditing }, children: _jsx(CardPaddingBoundary, { padding: cardPadding, children: content }) }));
191
195
  const measureRef = useRef(null);
192
196
  const surfaceRef = useRef(null);
193
197
  const lastReportedRowsRef = useRef(null);
@@ -317,7 +321,9 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
317
321
  // the GridItem has it as an ancestor → drag fires. Interactive elements
318
322
  // (buttons, inputs, textarea, select, links, [data-no-drag]) are still
319
323
  // protected via the `cancel` selector below in the <Responsive> render.
320
- const outerClass = cn('relative group overflow-visible', isEditing && 'drag-handle cursor-move', injectedClassName);
324
+ const outerClass = cn('relative group overflow-visible', isEditing && 'drag-handle',
325
+ // Paused editing (bsuite#3277) cannot move a card, so it must not promise to.
326
+ isEditing && !editingPaused && 'cursor-move', injectedClassName);
321
327
  return (_jsxs("div", { ref: ref, className: outerClass, style: injectedStyle, ...rest, children: [_jsxs("div", { className: "h-full w-full relative", children: [isEditing && (_jsx("div", { className: "absolute inset-0 z-10 pointer-events-none rounded-3xl border-2 border-transparent group-hover:border-primary/50 transition-colors bg-primary/5" })), _jsx("div", { "data-slot": "grid-item-surface", "data-chrome": chrome ? 'on' : 'off', "data-grid-slot-key": id, ref: surfaceRef, "data-card-padding": !chrome && cardStyleVars && '--card-padding' in cardStyleVars ? '' : undefined, className: chrome
322
328
  ? // ONE radius token, read by the grid item AND available to any
323
329
  // app card that opts into the same surface. Two surfaces
@@ -376,7 +382,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
376
382
  ...(chrome && cardStyleVars && '--card-padding' in cardStyleVars
377
383
  ? { padding: cardStyleVars['--card-padding'] }
378
384
  : null),
379
- }, children: autoHeight ? (_jsx("div", { "data-slot": "grid-item-body", className: "min-h-0 flex-none overflow-visible", children: _jsxs("div", { ref: measureRef, className: "flow-root", children: [isEditing ? (_jsx(GridItemEditorChrome, { id: id, label: label, onHide: onHide })) : null, scopedContent] }) })) : (_jsxs(_Fragment, { children: [isEditing ? (_jsx(GridItemEditorChrome, { id: id, label: label, onHide: onHide })) : null, _jsx("div", { "data-slot": "grid-item-body", className: "min-h-0 flex-1 overflow-auto", children: scopedContent })] })) })] }), injectedChildren] }));
385
+ }, children: autoHeight ? (_jsx("div", { "data-slot": "grid-item-body", className: "min-h-0 flex-none overflow-visible", children: _jsxs("div", { ref: measureRef, className: "flow-root", children: [isEditing ? (_jsx(GridItemEditorChrome, { id: id, label: label, onHide: onHide, hideDisabled: editingPaused })) : null, scopedContent] }) })) : (_jsxs(_Fragment, { children: [isEditing ? (_jsx(GridItemEditorChrome, { id: id, label: label, onHide: onHide, hideDisabled: editingPaused })) : null, _jsx("div", { "data-slot": "grid-item-body", className: "min-h-0 flex-1 overflow-auto", children: scopedContent })] })) })] }), injectedChildren] }));
380
386
  }));
381
387
  export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className,
382
388
  // Resize default flipped 2026-05-12: rgl v2 dropped auto-handle rendering
@@ -390,7 +396,7 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, defaultAut
390
396
  // `itemChrome` to get the pre-2.0.0 behaviour back for every slot, and
391
397
  // migrates page by page with the per-item `chrome` flag.
392
398
  itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames = DEFAULT_ADD_RELATIONSHIP_WIDGET_EVENT_NAMES, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }) {
393
- const { currentLayouts, layoutCols, isEditing, setIsEditing, flushPreferences, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleBreakpointChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, applyAutoHeightRows, autoHeightRows, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
399
+ const { currentLayouts, layoutCols, isEditing, setIsEditing, flushPreferences, preferencesStatus, canRetryPreferences, retryPreferences, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleBreakpointChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, applyAutoHeightRows, autoHeightRows, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
394
400
  pageKey,
395
401
  defaultLayouts,
396
402
  defaultCols,
@@ -465,7 +471,6 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
465
471
  autoHeightFrameRef.current = scheduleFrame(flushAutoHeightUpdates);
466
472
  }
467
473
  }, [flushAutoHeightUpdates, scheduleFrame]);
468
- const resizeEnabled = isEditing && isResizable;
469
474
  const resizeConstraints = useMemo(() => [
470
475
  // `gridBounds` caps width at the active column count, so we don't need
471
476
  // a global maxSize(...). Width and height are unbounded by default;
@@ -477,8 +482,10 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
477
482
  ], []);
478
483
  const [extraWidgetConfigs, setExtraWidgetConfigs] = useState({});
479
484
  const [extraRelationshipWidgetConfigs, setExtraRelationshipWidgetConfigs] = useState({});
480
- const { value: layerNames, setValue: setLayerNames, flush: flushLayerNames } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_layer_names`, {});
481
- const { value: hiddenLayerIds, setValue: setHiddenLayerIds, flush: flushHiddenLayers } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_hidden_layers`, {});
485
+ const layerNamesPreference = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_layer_names`, {});
486
+ const { value: layerNames, setValue: setLayerNames, flush: flushLayerNames } = layerNamesPreference;
487
+ const hiddenLayersPreference = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_hidden_layers`, {});
488
+ const { value: hiddenLayerIds, setValue: setHiddenLayerIds, flush: flushHiddenLayers } = hiddenLayersPreference;
482
489
  /*
483
490
  * Card appearance, per page, persisted beside the layout.
484
491
  *
@@ -486,13 +493,92 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
486
493
  * survives a reload, follows the operator across devices wherever the app
487
494
  * backs the adapter with the database, and needs no new storage concept.
488
495
  */
489
- const { value: storedCardStyle, setValue: setStoredCardStyle, flush: flushCardStyle } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_card_style`, DEFAULT_CARD_STYLE);
496
+ const cardStylePreference = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_card_style`, DEFAULT_CARD_STYLE);
497
+ const { value: storedCardStyle, setValue: setStoredCardStyle, flush: flushCardStyle } = cardStylePreference;
498
+ /*
499
+ * THE EDIT GATE (bsuite#3277).
500
+ *
501
+ * The hook refuses every write while any of ITS keys is loading or failed.
502
+ * This canvas reads and writes three more keys the hook never sees — layer
503
+ * names, hidden layers and card appearance — so the same rule is applied to
504
+ * them here, and the two are combined into the one state the editor shows.
505
+ *
506
+ * While it is not `'loaded'` the editor stays open but PAUSED: the banner says
507
+ * why, a failed read offers Retry, every control in the banner body is
508
+ * disabled by a single `<fieldset disabled>` (so a control added later cannot
509
+ * forget the gate), drag/resize/hide are off, and every write path in this
510
+ * component is refused underneath the disabled controls as well.
511
+ *
512
+ * Retry is in place. It calls `retry` on each FAILED key's adapter — the
513
+ * hook's four through `retryPreferences`, this component's three directly —
514
+ * and nothing else: no remount, no key change, no reset. The editor, its
515
+ * expanded/collapsed state and everything already loaded stay exactly as they
516
+ * are; the pause lifts on the render in which the last failed key loads.
517
+ */
518
+ const layerNamesStatus = preferenceStatusOf(layerNamesPreference);
519
+ const hiddenLayersStatus = preferenceStatusOf(hiddenLayersPreference);
520
+ const cardStyleStatus = preferenceStatusOf(cardStylePreference);
521
+ const editingPreferencesStatus = combinePreferenceStatuses([
522
+ preferencesStatus,
523
+ layerNamesStatus,
524
+ hiddenLayersStatus,
525
+ cardStyleStatus,
526
+ ]);
527
+ const editingPaused = editingPreferencesStatus !== 'loaded';
528
+ const resizeEnabled = isEditing && isResizable && !editingPaused;
529
+ const { retry: retryLayerNames } = layerNamesPreference;
530
+ const { retry: retryHiddenLayers } = hiddenLayersPreference;
531
+ const { retry: retryCardStyle } = cardStylePreference;
532
+ const canRetryEditingPreferences = canRetryPreferences ||
533
+ (layerNamesStatus === 'failed' && typeof retryLayerNames === 'function') ||
534
+ (hiddenLayersStatus === 'failed' && typeof retryHiddenLayers === 'function') ||
535
+ (cardStyleStatus === 'failed' && typeof retryCardStyle === 'function');
536
+ // Keyed by page so a retry on one page never says "still" about another.
537
+ const [retriedPage, setRetriedPage] = useState(null);
538
+ const retriedAndStillFailed = retriedPage === pageKey && editingPreferencesStatus === 'failed';
539
+ const retryEditingPreferences = useCallback(() => {
540
+ setRetriedPage(pageKey);
541
+ retryPreferences();
542
+ if (layerNamesStatus === 'failed')
543
+ retryLayerNames?.();
544
+ if (hiddenLayersStatus === 'failed')
545
+ retryHiddenLayers?.();
546
+ if (cardStyleStatus === 'failed')
547
+ retryCardStyle?.();
548
+ }, [
549
+ cardStyleStatus,
550
+ hiddenLayersStatus,
551
+ layerNamesStatus,
552
+ pageKey,
553
+ retryCardStyle,
554
+ retryHiddenLayers,
555
+ retryLayerNames,
556
+ retryPreferences,
557
+ ]);
490
558
  const cardStyle = useMemo(() => normaliseCardStyle(storedCardStyle), [storedCardStyle]);
491
559
  const cardStyleVars = useMemo(() => toCssVars(cardStyle), [cardStyle]);
492
- const updateCardStyle = useCallback((patch) => setStoredCardStyle((previous) => ({ ...normaliseCardStyle(previous), ...patch })), [setStoredCardStyle]);
560
+ const updateCardStyle = useCallback((patch) => {
561
+ if (editingPaused)
562
+ return;
563
+ setStoredCardStyle((previous) => ({ ...normaliseCardStyle(previous), ...patch }));
564
+ }, [editingPaused, setStoredCardStyle]);
565
+ const resetCardStyle = useCallback(() => {
566
+ if (editingPaused)
567
+ return;
568
+ setStoredCardStyle({ ...DEFAULT_CARD_STYLE });
569
+ }, [editingPaused, setStoredCardStyle]);
493
570
  const saveAndExit = useCallback(async () => {
494
571
  if (canEditPage === false || !isEditing || savingAttempt.current !== null)
495
572
  return;
573
+ if (editingPaused) {
574
+ // Every write is refused while paused, so the paused keys hold nothing to
575
+ // save. Flushing them would only let an adapter that refuses to flush a
576
+ // failed key hold the user in an editor they cannot use. (A write made
577
+ // before the pause — the page switched mid-session — is already with its
578
+ // adapter and keeps saving there; exiting does not cancel it.)
579
+ startTransition(() => setIsEditing(false));
580
+ return;
581
+ }
496
582
  const attempt = ++saveAttempt.current;
497
583
  savingAttempt.current = attempt;
498
584
  setSaveState({ pageKey, status: 'saving' });
@@ -511,7 +597,7 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
511
597
  if (savingAttempt.current === attempt)
512
598
  savingAttempt.current = null;
513
599
  }
514
- }, [canEditPage, isEditing, pageKey, flushPreferences, flushLayerNames, flushHiddenLayers, flushCardStyle, setIsEditing]);
600
+ }, [canEditPage, isEditing, editingPaused, pageKey, flushPreferences, flushLayerNames, flushHiddenLayers, flushCardStyle, setIsEditing]);
515
601
  useEffect(() => {
516
602
  const onSaveRequest = (event) => {
517
603
  const detail = event.detail;
@@ -666,10 +752,17 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
666
752
  }
667
753
  return filtered;
668
754
  }, [autoHeightRows, currentLayouts, hiddenLayerIds, renderableWidgetKeys]);
755
+ // Every write below is refused while editing is paused (bsuite#3277) — the
756
+ // controls that reach them are disabled too, but a disabled control is a
757
+ // presentation; the refusal is the guarantee.
669
758
  const hideLayer = (layerId) => {
759
+ if (editingPaused)
760
+ return;
670
761
  setHiddenLayerIds((previous) => ({ ...previous, [layerId]: true }));
671
762
  };
672
763
  const showLayer = (layerId, defaultSize) => {
764
+ if (editingPaused)
765
+ return;
673
766
  setHiddenLayerIds((previous) => {
674
767
  if (!previous[layerId])
675
768
  return previous;
@@ -688,6 +781,12 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
688
781
  const detail = event.detail;
689
782
  if (!detail?.entityType)
690
783
  return;
784
+ if (editingPaused) {
785
+ // Nothing is registered or added while paused; the user lands in the
786
+ // editor, where the reason and Retry are shown.
787
+ startTransition(() => setIsEditing(true));
788
+ return;
789
+ }
691
790
  const widgetId = `entity:${detail.entityType}`;
692
791
  const alreadyInLayout = activeLayouts.lg?.some((item) => item.i === widgetId);
693
792
  if (alreadyInLayout) {
@@ -715,6 +814,7 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
715
814
  addEntityWidgetEventNames,
716
815
  addWidget,
717
816
  createEntityWidget,
817
+ editingPaused,
718
818
  onRegisterEntityWidget,
719
819
  setIsEditing,
720
820
  ]);
@@ -733,6 +833,10 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
733
833
  const detail = event.detail;
734
834
  if (!detail?.hostEntityType || !detail.fkColumn || !detail.targetEntityType)
735
835
  return;
836
+ if (editingPaused) {
837
+ startTransition(() => setIsEditing(true));
838
+ return;
839
+ }
736
840
  if (!isRelationshipWritable(relationshipCatalog, detail)) {
737
841
  onRelationshipWidgetRejected?.(detail);
738
842
  return;
@@ -764,6 +868,7 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
764
868
  addRelationshipWidgetEventNames,
765
869
  addWidget,
766
870
  createRelationshipWidget,
871
+ editingPaused,
767
872
  onRegisterRelationshipWidget,
768
873
  onRelationshipWidgetRejected,
769
874
  relationshipCatalog,
@@ -782,6 +887,8 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
782
887
  };
783
888
  }), [activeLayouts.lg, layerNames, widgetMeta]);
784
889
  const handleLayerRename = (layerId, value) => {
890
+ if (editingPaused)
891
+ return;
785
892
  const trimmed = value.trim();
786
893
  setLayerNames((previous) => {
787
894
  if (trimmed.length === 0) {
@@ -878,13 +985,18 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
878
985
  // partially visible — mitigates Issue 2 (banner consuming
879
986
  // vertical space). `bg-card/95` + `backdrop-blur` keeps text
880
987
  // legible while showing form context behind the banner.
881
- 'bg-card/95 supports-[backdrop-filter]:bg-card/80 backdrop-blur border-primary'), role: "region", "aria-label": "Canvas editor controls", "aria-busy": isSaving, children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('rounded-full flex items-center justify-center bg-primary/10', controlsCollapsed ? 'h-8 w-8' : 'h-10 w-10 animate-pulse'), children: _jsx(Settings2, { className: cn('text-primary', controlsCollapsed ? 'h-4 w-4' : 'h-5 w-5') }) }), _jsxs("div", { children: [_jsx("h3", { className: cn('font-semibold text-foreground', controlsCollapsed ? 'text-sm' : 'text-lg'), children: "Canvas Editor Active" }), !controlsCollapsed && (_jsx("span", { className: "text-sm text-muted-foreground", children: "Drag anywhere on a card to move it. Resize with the bottom-right handle." }))] })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [_jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md border border-border-interactive px-2 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", "aria-expanded": !controlsCollapsed, "aria-controls": "page-grid-editor-controls-body", "aria-label": controlsCollapsed ? 'Expand controls: columns, cards and layers' : 'Collapse controls', onClick: () => setControlsCollapsed((previous) => !previous), children: [controlsCollapsed ? _jsx(ChevronDown, { className: "h-4 w-4" }) : _jsx(ChevronUp, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only sm:not-sr-only", children: controlsCollapsed ? 'Columns, cards & layers' : 'Collapse' })] }), _jsxs("button", { type: "button", className: "inline-flex items-center rounded-md px-3 py-2 text-sm font-medium bg-primary text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", disabled: isSaving, onClick: () => { void saveAndExit(); }, children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), isSaving ? 'Saving…' : 'Save & Exit'] })] })] }), saveFailed && (_jsx("p", { role: "alert", className: "mt-2 text-sm text-destructive", children: "Changes could not be saved. Your changes are still here. Try Save & Exit again." })), _jsxs("div", { id: "page-grid-editor-controls-body", hidden: controlsCollapsed, className: "flex flex-wrap items-center gap-x-4 gap-y-2 pt-2 border-t border-border max-h-[60vh] overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LayoutGrid, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Columns:" }), _jsx("input", { type: "range", min: 1, max: 24, value: layoutCols, onChange: (event) => handleColumnChange(Number(event.target.value)), className: "w-24 cursor-pointer accent-primary", "aria-label": "Column count" }), _jsx("span", { className: "text-sm font-mono w-5 text-center tabular-nums", children: layoutCols })] }), _jsx("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Column presets", children: [1, 2, 3, 4, 6, 12].map((columnCount) => (_jsx("button", { type: "button", onClick: () => handleColumnChange(columnCount), "data-active": layoutCols === columnCount || undefined, "aria-pressed": layoutCols === columnCount, className: cn('px-2 py-0.5 rounded text-xs font-medium transition-colors border', 'bg-muted text-muted-foreground border-border-interactive', 'hover:bg-muted/80 hover:text-foreground', 'data-[active]:bg-primary data-[active]:text-primary-foreground data-[active]:border-primary', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: columnCount }, columnCount))) }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 w-full pt-2 border-t border-border", role: "group", "aria-label": "Card appearance", children: [_jsx("span", { className: "text-sm shrink-0 font-medium text-foreground", children: "Card:" }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Corners" }), _jsx("input", { type: "range", min: RADIUS_RANGE.min, max: RADIUS_RANGE.max, value: cardStyle.radius, onChange: (event) => updateCardStyle({ radius: Number(event.target.value) }), className: "w-20 cursor-pointer accent-primary", "aria-label": "Card corner radius in pixels" }), _jsx("span", { className: "text-sm font-mono w-8 text-right tabular-nums", children: cardStyle.radius })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Border" }), _jsx("input", { type: "range", min: BORDER_WIDTH_RANGE.min, max: BORDER_WIDTH_RANGE.max, value: cardStyle.borderWidth, onChange: (event) => updateCardStyle({ borderWidth: Number(event.target.value) }), className: "w-16 cursor-pointer accent-primary", "aria-label": "Card border width in pixels" }), _jsx("span", { className: "text-sm font-mono w-6 text-right tabular-nums", children: cardStyle.borderWidth })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Border colour" }), _jsx("select", { value: cardStyle.borderTone, onChange: (event) => updateCardStyle({ borderTone: event.target.value }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card border colour", children: BORDER_TONES.map((tone) => (_jsx("option", { value: tone, children: tone === 'border' ? 'default colour' : tone }, tone))) })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Border style" }), _jsx("select", { value: cardStyle.borderStyle, onChange: (event) => updateCardStyle({ borderStyle: event.target.value }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card border style", children: BORDER_STYLES.map((style) => (_jsx("option", { value: style, children: style }, style))) })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Card shadow" }), _jsxs("select", { value: cardStyle.elevation === null ? 'default' : String(cardStyle.elevation), onChange: (event) => updateCardStyle({
882
- elevation: event.target.value === 'default' ? null : Number(event.target.value),
883
- }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card shadow depth", children: [_jsx("option", { value: "default", children: "default shadow" }), [0, 1, 2, 3, 4].map((level) => (_jsx("option", { value: level, children: level === 0 ? 'no shadow' : `shadow ${level}` }, level)))] })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Padding" }), _jsx("input", { type: "range", min: PADDING_RANGE.min, max: PADDING_RANGE.max, value: cardStyle.padding ?? 0, onChange: (event) => updateCardStyle({ padding: Number(event.target.value) }), className: "w-16 cursor-pointer accent-primary", "aria-label": "Card inner padding in pixels" }), _jsx("span", { className: "text-sm font-mono w-8 text-right tabular-nums", children: cardStyle.padding === null ? '\u2014' : cardStyle.padding })] }), _jsxs("button", { type: "button", onClick: () => setStoredCardStyle({ ...DEFAULT_CARD_STYLE }), disabled: isDefaultCardStyle(cardStyle), className: "inline-flex items-center gap-1 rounded-md border border-border-interactive px-2 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50 disabled:pointer-events-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(RotateCcw, { className: "h-3.5 w-3.5" }), "Reset cards"] }), _jsx("output", { className: "text-sm text-muted-foreground", "aria-live": "polite", children: describeCardStyle(cardStyle) })] }), hiddenWidgetKeys.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Add widget:" }), _jsx("div", { className: "flex flex-wrap gap-1", children: hiddenWidgetKeys.map((key) => {
884
- const meta = widgetMeta?.[key];
885
- const Icon = meta?.icon;
886
- return (_jsxs("button", { type: "button", onClick: () => showLayer(key, meta?.defaultSize), className: cn('flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium border transition-colors', 'bg-muted text-muted-foreground border-border-interactive', 'hover:bg-muted/80 hover:text-foreground hover:border-primary/60', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Plus, { className: "h-3 w-3" }), Icon && _jsx(Icon, { className: "h-3 w-3" }), layerNames[key] || meta?.label || key] }, key));
887
- }) })] })), 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-interactive 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-interactive 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-interactive 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: () => {
988
+ 'bg-card/95 supports-[backdrop-filter]:bg-card/80 backdrop-blur border-primary'), role: "region", "aria-label": "Canvas editor controls", "aria-busy": isSaving, children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('rounded-full flex items-center justify-center bg-primary/10', controlsCollapsed ? 'h-8 w-8' : 'h-10 w-10 animate-pulse'), children: _jsx(Settings2, { className: cn('text-primary', controlsCollapsed ? 'h-4 w-4' : 'h-5 w-5') }) }), _jsxs("div", { children: [_jsx("h3", { className: cn('font-semibold text-foreground', controlsCollapsed ? 'text-sm' : 'text-lg'), children: "Canvas Editor Active" }), !controlsCollapsed && !editingPaused && (_jsx("span", { className: "text-sm text-muted-foreground", children: "Drag anywhere on a card to move it. Resize with the bottom-right handle." }))] })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [_jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md border border-border-interactive px-2 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", "aria-expanded": !controlsCollapsed, "aria-controls": "page-grid-editor-controls-body", "aria-label": controlsCollapsed ? 'Expand controls: columns, cards and layers' : 'Collapse controls', onClick: () => setControlsCollapsed((previous) => !previous), children: [controlsCollapsed ? _jsx(ChevronDown, { className: "h-4 w-4" }) : _jsx(ChevronUp, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only sm:not-sr-only", children: controlsCollapsed ? 'Columns, cards & layers' : 'Collapse' })] }), _jsxs("button", { type: "button", className: "inline-flex items-center rounded-md px-3 py-2 text-sm font-medium bg-primary text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", disabled: isSaving, onClick: () => { void saveAndExit(); }, children: [editingPaused ? _jsx(X, { className: "h-4 w-4 mr-2" }) : _jsx(Save, { className: "h-4 w-4 mr-2" }), isSaving ? 'Saving…' : editingPaused ? 'Exit' : 'Save & Exit'] })] })] }), editingPaused && (_jsxs("div", { role: editingPreferencesStatus === 'failed' ? 'alert' : 'status', "data-page-grid-editing-paused": editingPreferencesStatus, className: cn('flex flex-wrap items-center gap-2 text-sm', editingPreferencesStatus === 'failed' ? 'text-destructive' : 'text-muted-foreground'), children: [_jsx("p", { children: editingPreferencesStatus === 'failed'
989
+ ? `Editing is paused: your saved settings for this page ${retriedAndStillFailed ? 'still couldn\'t' : 'couldn\'t'} be loaded. Nothing can be changed until they load, so nothing you saved is overwritten.${canRetryEditingPreferences ? '' : ' Reload the page to try again.'}`
990
+ : 'Loading your saved settings for this page… Editing starts as soon as they arrive.' }), editingPreferencesStatus === 'failed' && canRetryEditingPreferences && (_jsxs("button", { type: "button", onClick: retryEditingPreferences, className: "inline-flex items-center gap-1 rounded-md border border-border-interactive px-2 py-1 text-sm text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", children: [_jsx(RefreshCw, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), "Retry"] }))] })), saveFailed && (_jsx("p", { role: "alert", className: "mt-2 text-sm text-destructive", children: "Changes could not be saved. Your changes are still here. Try Save & Exit again." })), _jsx("div", { id: "page-grid-editor-controls-body", hidden: controlsCollapsed,
991
+ // Dimmed while paused so the disabled state reads at a glance; inline
992
+ // rather than a utility so no consumer's CSS build can drop it.
993
+ style: editingPaused ? { opacity: 0.6 } : undefined, className: "flex flex-wrap items-center gap-x-4 gap-y-2 pt-2 border-t border-border max-h-[60vh] overflow-y-auto", children: _jsxs("fieldset", { disabled: editingPaused, style: { display: 'contents' }, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LayoutGrid, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Columns:" }), _jsx("input", { type: "range", min: 1, max: 24, value: layoutCols, onChange: (event) => handleColumnChange(Number(event.target.value)), className: "w-24 cursor-pointer accent-primary", "aria-label": "Column count" }), _jsx("span", { className: "text-sm font-mono w-5 text-center tabular-nums", children: layoutCols })] }), _jsx("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Column presets", children: [1, 2, 3, 4, 6, 12].map((columnCount) => (_jsx("button", { type: "button", onClick: () => handleColumnChange(columnCount), "data-active": layoutCols === columnCount || undefined, "aria-pressed": layoutCols === columnCount, className: cn('px-2 py-0.5 rounded text-xs font-medium transition-colors border', 'bg-muted text-muted-foreground border-border-interactive', 'hover:bg-muted/80 hover:text-foreground', 'data-[active]:bg-primary data-[active]:text-primary-foreground data-[active]:border-primary', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: columnCount }, columnCount))) }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 w-full pt-2 border-t border-border", role: "group", "aria-label": "Card appearance", children: [_jsx("span", { className: "text-sm shrink-0 font-medium text-foreground", children: "Card:" }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Corners" }), _jsx("input", { type: "range", min: RADIUS_RANGE.min, max: RADIUS_RANGE.max, value: cardStyle.radius, onChange: (event) => updateCardStyle({ radius: Number(event.target.value) }), className: "w-20 cursor-pointer accent-primary", "aria-label": "Card corner radius in pixels" }), _jsx("span", { className: "text-sm font-mono w-8 text-right tabular-nums", children: cardStyle.radius })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Border" }), _jsx("input", { type: "range", min: BORDER_WIDTH_RANGE.min, max: BORDER_WIDTH_RANGE.max, value: cardStyle.borderWidth, onChange: (event) => updateCardStyle({ borderWidth: Number(event.target.value) }), className: "w-16 cursor-pointer accent-primary", "aria-label": "Card border width in pixels" }), _jsx("span", { className: "text-sm font-mono w-6 text-right tabular-nums", children: cardStyle.borderWidth })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Border colour" }), _jsx("select", { value: cardStyle.borderTone, onChange: (event) => updateCardStyle({ borderTone: event.target.value }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card border colour", children: BORDER_TONES.map((tone) => (_jsx("option", { value: tone, children: tone === 'border' ? 'default colour' : tone }, tone))) })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Border style" }), _jsx("select", { value: cardStyle.borderStyle, onChange: (event) => updateCardStyle({ borderStyle: event.target.value }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card border style", children: BORDER_STYLES.map((style) => (_jsx("option", { value: style, children: style }, style))) })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "sr-only", children: "Card shadow" }), _jsxs("select", { value: cardStyle.elevation === null ? 'default' : String(cardStyle.elevation), onChange: (event) => updateCardStyle({
994
+ elevation: event.target.value === 'default' ? null : Number(event.target.value),
995
+ }), className: "rounded-md border border-border-interactive bg-background px-2 py-1 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": "Card shadow depth", children: [_jsx("option", { value: "default", children: "default shadow" }), [0, 1, 2, 3, 4].map((level) => (_jsx("option", { value: level, children: level === 0 ? 'no shadow' : `shadow ${level}` }, level)))] })] }), _jsxs("label", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Padding" }), _jsx("input", { type: "range", min: PADDING_RANGE.min, max: PADDING_RANGE.max, value: cardStyle.padding ?? 0, onChange: (event) => updateCardStyle({ padding: Number(event.target.value) }), className: "w-16 cursor-pointer accent-primary", "aria-label": "Card inner padding in pixels" }), _jsx("span", { className: "text-sm font-mono w-8 text-right tabular-nums", children: cardStyle.padding === null ? '\u2014' : cardStyle.padding })] }), _jsxs("button", { type: "button", onClick: resetCardStyle, disabled: isDefaultCardStyle(cardStyle), className: "inline-flex items-center gap-1 rounded-md border border-border-interactive px-2 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50 disabled:pointer-events-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(RotateCcw, { className: "h-3.5 w-3.5" }), "Reset cards"] }), _jsx("output", { className: "text-sm text-muted-foreground", "aria-live": "polite", children: describeCardStyle(cardStyle) })] }), hiddenWidgetKeys.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "text-sm shrink-0 text-muted-foreground", children: "Add widget:" }), _jsx("div", { className: "flex flex-wrap gap-1", children: hiddenWidgetKeys.map((key) => {
996
+ const meta = widgetMeta?.[key];
997
+ const Icon = meta?.icon;
998
+ return (_jsxs("button", { type: "button", onClick: () => showLayer(key, meta?.defaultSize), className: cn('flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium border transition-colors', 'bg-muted text-muted-foreground border-border-interactive', 'hover:bg-muted/80 hover:text-foreground hover:border-primary/60', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Plus, { className: "h-3 w-3" }), Icon && _jsx(Icon, { className: "h-3 w-3" }), layerNames[key] || meta?.label || key] }, key));
999
+ }) })] })), 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-interactive 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-interactive 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-interactive 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: () => {
888
1000
  handleReset();
889
1001
  setResetConfirmOpen(false);
890
1002
  }, 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: (layout, layouts) => {
@@ -896,7 +1008,7 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
896
1008
  // is folded back onto `lg` instead of into a derived breakpoint
897
1009
  // that is regenerated (and therefore discarded) on the next render.
898
1010
  onBreakpointChange: handleBreakpointChange, dragConfig: {
899
- enabled: isEditing,
1011
+ enabled: isEditing && !editingPaused,
900
1012
  handle: '.drag-handle',
901
1013
  bounded: false,
902
1014
  cancel: '.react-resizable-handle, button, input, textarea, select, [contenteditable="true"], [data-no-drag], a[href], [role="button"], [role="combobox"], [role="menuitem"], [role="tab"], [role="checkbox"], [role="switch"], [role="slider"], [role="textbox"]',
@@ -914,6 +1026,6 @@ itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_
914
1026
  const content = allWidgets[layoutItem.i];
915
1027
  if (content === undefined || content === null)
916
1028
  return null;
917
- return (_jsx(GridItem, { id: layoutItem.i, content: content, isEditing: isEditing, label: layerNames[layoutItem.i] || widgetMeta?.[layoutItem.i]?.label || layoutItem.i, pageKey: pageKey, onHide: hideLayer, autoHeight: layoutItem.autoHeight, onAutoHeightChange: handleAutoHeightChange, chrome: layoutItem.chrome ?? itemChrome, cardStyleVars: cardStyleVars }, layoutItem.i));
1029
+ return (_jsx(GridItem, { id: layoutItem.i, content: content, isEditing: isEditing, label: layerNames[layoutItem.i] || widgetMeta?.[layoutItem.i]?.label || layoutItem.i, pageKey: pageKey, onHide: hideLayer, editingPaused: editingPaused, autoHeight: layoutItem.autoHeight, onAutoHeightChange: handleAutoHeightChange, chrome: layoutItem.chrome ?? itemChrome, cardStyleVars: cardStyleVars }, layoutItem.i));
918
1030
  }) }) }) })] }));
919
1031
  }
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * A grid offers its inset to the first app Card. That Card places a boundary
4
+ * around its children so inner panels keep their own spacing. A nested grid
5
+ * supplies a new value, including undefined when its inset is not overridden.
6
+ * No DOM wrapper or browser-specific CSS scoping is needed.
7
+ */
8
+ export declare function CardPaddingBoundary({ padding, children }: {
9
+ padding?: string;
10
+ children: ReactNode;
11
+ }): import("react").JSX.Element;
12
+ export declare function useCardPadding(): string | undefined;
13
+ //# sourceMappingURL=cardPadding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardPadding.d.ts","sourceRoot":"","sources":["../src/cardPadding.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEnG;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD"}
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from 'react';
4
+ const CardPaddingContext = createContext(undefined);
5
+ /**
6
+ * A grid offers its inset to the first app Card. That Card places a boundary
7
+ * around its children so inner panels keep their own spacing. A nested grid
8
+ * supplies a new value, including undefined when its inset is not overridden.
9
+ * No DOM wrapper or browser-specific CSS scoping is needed.
10
+ */
11
+ export function CardPaddingBoundary({ padding, children }) {
12
+ return _jsx(CardPaddingContext.Provider, { value: padding, children: children });
13
+ }
14
+ export function useCardPadding() {
15
+ return useContext(CardPaddingContext);
16
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { PageGridLayout, PAGE_GRID_SAVE_EVENT } from './PageGridLayout.js';
2
+ export { CardPaddingBoundary, useCardPadding } from './cardPadding.js';
2
3
  export type { PageGridSaveEventDetail } from './PageGridLayout.js';
3
4
  export { PageEditorLauncher } from './PageEditorLauncher.js';
4
5
  export { CanvasCard } from './CanvasCard.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,gCAAgC,EAChC,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,IAAI,kBAAkB,EAC/B,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQpF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,UAAU,EACV,eAAe,EACf,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { PageGridLayout, PAGE_GRID_SAVE_EVENT } from './PageGridLayout.js';
2
+ export { CardPaddingBoundary, useCardPadding } from './cardPadding.js';
2
3
  export { PageEditorLauncher } from './PageEditorLauncher.js';
3
4
  export { CanvasCard } from './CanvasCard.js';
4
5
  export { DraggableCardPage } from './DraggableCardPage.js';
package/dist/types.d.ts CHANGED
@@ -64,10 +64,52 @@ export interface GridLayouts {
64
64
  * reference widgets that no longer exist.
65
65
  */
66
66
  export type LayoutMigration = (saved: GridLayouts, defaults: GridLayouts) => GridLayouts;
67
+ /**
68
+ * Where the READ of one preference key stands (bsuite#3277).
69
+ *
70
+ * - `'loading'` — the stored value has not arrived yet. `value` is a local
71
+ * mirror or the fallback.
72
+ * - `'loaded'` — `value` is what is stored for this key, or nothing is stored
73
+ * and `value` is the fallback. Only this state authorises a write.
74
+ * - `'failed'` — the read errored. `value` is a local mirror or the fallback
75
+ * and says NOTHING about what is stored.
76
+ *
77
+ * A failed read is not an empty read. On a device with no local copy, crm7 and
78
+ * business-suite-unified reported a failed `_grid_version` read as "loaded,
79
+ * version 0"; the version gate then took its discard branch and wrote the
80
+ * defaults over the user's saved layout — 200 of 200 pages in the measurement.
81
+ */
82
+ export type PageGridPreferenceStatus = 'loading' | 'loaded' | 'failed';
67
83
  export interface PageGridPreferenceAdapter<T> {
68
84
  value: T;
69
85
  setValue: (value: T | ((previous: T) => T)) => void;
86
+ /**
87
+ * The two-state read signal every adapter has always reported. An adapter
88
+ * that implements `status` must keep this equal to `status === 'loaded'` —
89
+ * never true after a failed read.
90
+ *
91
+ * An adapter that does NOT implement `status` keeps exactly the meaning this
92
+ * had before: `true` is `'loaded'`, `false` is `'loading'`.
93
+ */
70
94
  loaded: boolean;
95
+ /**
96
+ * The three-state read signal. When present it is AUTHORITATIVE over
97
+ * `loaded`: `usePageGridLayout` treats a key as readable only when `status`
98
+ * is `'loaded'` AND `loaded` is true, so an adapter that adds `status` beside
99
+ * an older "the read settled" `loaded` still cannot authorise a write after a
100
+ * failure.
101
+ *
102
+ * Once a key has loaded, a later background re-read that fails must NOT move
103
+ * it back to `'failed'` — the value it holds is still the stored one.
104
+ */
105
+ status?: PageGridPreferenceStatus;
106
+ /**
107
+ * Re-read THIS key after a failed read, in place: no remount, no reset of any
108
+ * other key, and no write. The adapter moves the key to `'loading'` and then
109
+ * to `'loaded'` or back to `'failed'`. Omit it only if the adapter genuinely
110
+ * cannot read again without a reload.
111
+ */
112
+ retry?: () => void;
71
113
  /** Wait for pending writes and reject if the latest change was not saved.
72
114
  * A subsequent explicit call may retry a failed write without losing the draft.
73
115
  * Synchronous adapters may omit this; remote adapters should implement it. */
@@ -258,6 +300,22 @@ export interface UsePageGridLayoutResult {
258
300
  isEditing: boolean;
259
301
  setIsEditing: React.Dispatch<React.SetStateAction<boolean>>;
260
302
  flushPreferences: () => Promise<void>;
303
+ /**
304
+ * The read state of every preference key this hook reads OR writes
305
+ * (`_grid_version`, `_grid_layouts`, `_grid_cols`, `_grid_base_cols`),
306
+ * combined: `'failed'` if any failed, else `'loading'` if any is still
307
+ * loading, else `'loaded'`. Until it is `'loaded'` the hook runs no version
308
+ * gate, no migration, no discard and no write of any kind (bsuite#3277).
309
+ */
310
+ preferencesStatus: PageGridPreferenceStatus;
311
+ /** True when at least one failed key's adapter offers `retry`. */
312
+ canRetryPreferences: boolean;
313
+ /**
314
+ * Re-read ONLY the keys whose read failed, via each adapter's own `retry`.
315
+ * Nothing remounts: editing state, the draft and every loaded key are kept,
316
+ * and the gate simply runs once every key has loaded.
317
+ */
318
+ retryPreferences: () => void;
261
319
  activeCols: Record<string, number>;
262
320
  activeCompactor: Compactor;
263
321
  onLayoutChange: (_layout: unknown, layouts: unknown, wasGesture?: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEjF,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,cAAc,EAAE,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,WAAW,KAClB,WAAW,CAAC;AAEjB;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC;IACpD;;;;;;;OAOG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB;;kFAE8E;IAC9E,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,EACxC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,KACR,yBAAyB,CAAC,CAAC,CAAC,CAAC;AAElC,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAYD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,wBAAwB,EAAE,CAAC;AAEtE,MAAM,WAAW,mBAAoB,SAAQ,wBAAwB;IACnE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACtF;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,+BAA+B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpD,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,gCAAgC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1F,4BAA4B,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAClG;;;;;OAKG;IACH,4BAA4B,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,WAAW,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC;;;;;;OAMG;IACH,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,kEAAkE;IAClE,mBAAmB,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,eAAe,EAAE,SAAS,CAAC;IAC3B,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnF,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C;;;;;;OAMG;IACH,sBAAsB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,CACT,SAAS,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,KACrE,IAAI,CAAC;IACV,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IAClE,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C;;;;;;;;;;OAUG;IACH,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACpE;0CACsC;IACtC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAClD,cAAc,EAAE,MAAM,CAAC;CACxB"}
@@ -1,4 +1,23 @@
1
- import type { GridLayouts, LayoutMigration, UsePageGridLayoutOptions, UsePageGridLayoutResult } from './types.js';
1
+ import type { GridLayouts, LayoutMigration, PageGridPreferenceAdapter, PageGridPreferenceStatus, UsePageGridLayoutOptions, UsePageGridLayoutResult } from './types.js';
2
+ /**
3
+ * One key's read state, resolved from what its adapter reports (bsuite#3277).
4
+ *
5
+ * `status` is authoritative when present, and a key counts as readable only if
6
+ * `status` is `'loaded'` AND `loaded` is true. The conjunction is the point: the
7
+ * first adapter a consumer ships will add `status` beside its old "the read
8
+ * settled" `loaded`, and that `loaded` is true after an error. Anything that is
9
+ * not positively loaded — including a `status` value this version does not know —
10
+ * resolves to not-loaded, so an unrecognised state fails closed.
11
+ *
12
+ * An adapter without `status` keeps the meaning `loaded` always had.
13
+ */
14
+ export declare function preferenceStatusOf(adapter: Pick<PageGridPreferenceAdapter<unknown>, 'loaded' | 'status'>): PageGridPreferenceStatus;
15
+ /**
16
+ * Several keys' read states as one: a failure outranks a key still loading,
17
+ * because the failure is what the user can act on (Retry) and a still-loading
18
+ * key must not hide it.
19
+ */
20
+ export declare function combinePreferenceStatuses(statuses: readonly PageGridPreferenceStatus[]): PageGridPreferenceStatus;
2
21
  export declare const DEFAULT_EDITOR_EVENT_NAMES: readonly ["bsuite-open-page-editor", "bsu-open-page-editor", "crm7-open-page-editor", "conduit-open-page-editor", "r80-open-page-editor"];
3
22
  /**
4
23
  * Broadcast after a PageGridLayout commits an editing-state change. Header
@@ -1 +1 @@
1
- {"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,EAAE,EACF,UAAU,GACX,EAAE;IACD,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC;CACzD,GAAG,WAAW,GAAG,IAAI,CA+BrB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,cAAc,EACd,WAAgB,EAChB,aAAiB,EACjB,WAAkB,EAClB,gBAA6C,EAC7C,iBAA4C,EAC5C,iBAAiB,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,uBAAuB,CA4yBpD"}
1
+ {"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,WAAW,EACX,eAAe,EACf,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC,GACrE,wBAAwB,CAI1B;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAC5C,wBAAwB,CAI1B;AAED,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,EAAE,EACF,UAAU,GACX,EAAE;IACD,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC;CACzD,GAAG,WAAW,GAAG,IAAI,CA+BrB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,cAAc,EACd,WAAgB,EAChB,aAAiB,EACjB,WAAkB,EAClB,gBAA6C,EAC7C,iBAA4C,EAC5C,iBAAiB,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,uBAAuB,CAg3BpD"}
@@ -3,6 +3,37 @@ import { verticalCompactor } from 'react-grid-layout';
3
3
  import { buildResponsiveLayouts, isCanonicalisableBreakpoint, isDerivedBreakpoint, } from './buildResponsiveLayouts.js';
4
4
  import { defaultPreferenceAdapter } from './preferences.js';
5
5
  import { rescaleLayout } from './rescaleLayout.js';
6
+ /**
7
+ * One key's read state, resolved from what its adapter reports (bsuite#3277).
8
+ *
9
+ * `status` is authoritative when present, and a key counts as readable only if
10
+ * `status` is `'loaded'` AND `loaded` is true. The conjunction is the point: the
11
+ * first adapter a consumer ships will add `status` beside its old "the read
12
+ * settled" `loaded`, and that `loaded` is true after an error. Anything that is
13
+ * not positively loaded — including a `status` value this version does not know —
14
+ * resolves to not-loaded, so an unrecognised state fails closed.
15
+ *
16
+ * An adapter without `status` keeps the meaning `loaded` always had.
17
+ */
18
+ export function preferenceStatusOf(adapter) {
19
+ if (adapter.status === 'failed')
20
+ return 'failed';
21
+ if (adapter.status === undefined)
22
+ return adapter.loaded ? 'loaded' : 'loading';
23
+ return adapter.status === 'loaded' && adapter.loaded ? 'loaded' : 'loading';
24
+ }
25
+ /**
26
+ * Several keys' read states as one: a failure outranks a key still loading,
27
+ * because the failure is what the user can act on (Retry) and a still-loading
28
+ * key must not hide it.
29
+ */
30
+ export function combinePreferenceStatuses(statuses) {
31
+ if (statuses.includes('failed'))
32
+ return 'failed';
33
+ if (statuses.includes('loading'))
34
+ return 'loading';
35
+ return 'loaded';
36
+ }
6
37
  export const DEFAULT_EDITOR_EVENT_NAMES = [
7
38
  'bsuite-open-page-editor',
8
39
  'bsu-open-page-editor',
@@ -152,14 +183,70 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
152
183
  window.addEventListener('resize', onResize);
153
184
  return () => window.removeEventListener('resize', onResize);
154
185
  }, []);
155
- const { value: savedLayoutVersion, setValue: setSavedLayoutVersion, loaded: versionLoaded, flush: flushVersion } = preferenceAdapter(`page:${pageKey}_grid_version`, 0);
156
- const { value: savedLayout, setValue: setSavedLayout, loaded: layoutLoaded, flush: flushLayout } = preferenceAdapter(`page:${pageKey}_grid_layouts`, defaultLayouts);
157
- const { value: savedLayoutCols, setValue: setLayoutCols, flush: flushCols } = preferenceAdapter(`page:${pageKey}_grid_cols`, defaultCols);
158
- const { value: savedBaseCols, setValue: setBaseCols, flush: flushBaseCols } = preferenceAdapter(`page:${pageKey}_grid_base_cols`, defaultCols);
186
+ const versionPreference = preferenceAdapter(`page:${pageKey}_grid_version`, 0);
187
+ const layoutPreference = preferenceAdapter(`page:${pageKey}_grid_layouts`, defaultLayouts);
188
+ const colsPreference = preferenceAdapter(`page:${pageKey}_grid_cols`, defaultCols);
189
+ const baseColsPreference = preferenceAdapter(`page:${pageKey}_grid_base_cols`, defaultCols);
190
+ const { value: savedLayoutVersion, setValue: setSavedLayoutVersion, flush: flushVersion, retry: retryVersion } = versionPreference;
191
+ const { value: savedLayout, setValue: setSavedLayout, flush: flushLayout, retry: retryLayout } = layoutPreference;
192
+ const { value: savedLayoutCols, setValue: setLayoutCols, flush: flushCols, retry: retryCols } = colsPreference;
193
+ const { value: savedBaseCols, setValue: setBaseCols, flush: flushBaseCols, retry: retryBaseCols } = baseColsPreference;
159
194
  const flushPreferences = useCallback(async () => {
160
195
  await Promise.all([flushVersion?.(), flushLayout?.(), flushCols?.(), flushBaseCols?.()]);
161
196
  }, [flushVersion, flushLayout, flushCols, flushBaseCols]);
162
- const prefsLoaded = layoutLoaded && versionLoaded;
197
+ /*
198
+ * NOTHING IS WRITTEN UNTIL EVERY KEY THIS HOOK TOUCHES HAS BEEN READ (bsuite#3277).
199
+ *
200
+ * The gate below used to wait for `_grid_layouts` and `_grid_version` only, and
201
+ * trusted `loaded` to mean "the stored value is here". crm7 and
202
+ * business-suite-unified set `loaded` on a read ERROR too, so on a device with
203
+ * no local copy a failed `_grid_version` read looked like "loaded, version 0" —
204
+ * the gate took its discard branch and wrote the page defaults over the user's
205
+ * saved layout. Measured with the real 2.7.0 hook: 200 of 200 pages.
206
+ *
207
+ * The rule now covers every key the hook READS or WRITES — `_grid_cols` and
208
+ * `_grid_base_cols` included, because a discard writes them — and every write
209
+ * the hook can make, not only the gate's: a gesture, compact, reset, add, move,
210
+ * lock, remove and a column change are all refused while any key is loading or
211
+ * failed. `PageGridLayout` shows the reason and a Retry while that holds.
212
+ */
213
+ const versionStatus = preferenceStatusOf(versionPreference);
214
+ const layoutStatus = preferenceStatusOf(layoutPreference);
215
+ const colsStatus = preferenceStatusOf(colsPreference);
216
+ const baseColsStatus = preferenceStatusOf(baseColsPreference);
217
+ const preferencesStatus = combinePreferenceStatuses([versionStatus, layoutStatus, colsStatus, baseColsStatus]);
218
+ const preferencesWritable = preferencesStatus === 'loaded';
219
+ /*
220
+ * Retry is PER KEY and IN PLACE. Each adapter's own `retry` re-runs that one
221
+ * key's read — crm7 re-runs its fetch effect, business-suite-unified refetches
222
+ * that key's query, conduit re-reads that key's local copy. This hook, its
223
+ * editing state and every key that already loaded stay mounted; the gate above
224
+ * simply runs once the last failed key lands. A key that loaded is never
225
+ * re-read, so a retry cannot disturb a value that is already right.
226
+ */
227
+ const canRetryPreferences = (versionStatus === 'failed' && typeof retryVersion === 'function') ||
228
+ (layoutStatus === 'failed' && typeof retryLayout === 'function') ||
229
+ (colsStatus === 'failed' && typeof retryCols === 'function') ||
230
+ (baseColsStatus === 'failed' && typeof retryBaseCols === 'function');
231
+ const retryPreferences = useCallback(() => {
232
+ if (versionStatus === 'failed')
233
+ retryVersion?.();
234
+ if (layoutStatus === 'failed')
235
+ retryLayout?.();
236
+ if (colsStatus === 'failed')
237
+ retryCols?.();
238
+ if (baseColsStatus === 'failed')
239
+ retryBaseCols?.();
240
+ }, [
241
+ baseColsStatus,
242
+ colsStatus,
243
+ layoutStatus,
244
+ retryBaseCols,
245
+ retryCols,
246
+ retryLayout,
247
+ retryVersion,
248
+ versionStatus,
249
+ ]);
163
250
  /*
164
251
  * Migration keys arrive in the CONSUMER's version space — `layoutVersion={2}`
165
252
  * pairs with `{ 2: … }` — and are shifted onto the stored (effective) space
@@ -199,7 +286,8 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
199
286
  savedLayoutForVersionGateRef.current = savedLayout;
200
287
  }, [savedLayout]);
201
288
  useEffect(() => {
202
- if (!prefsLoaded)
289
+ // Every key read AND written must be loaded without failure — see above.
290
+ if (!preferencesWritable)
203
291
  return;
204
292
  const storedVersion = savedLayoutVersion ?? 0;
205
293
  if (storedVersion >= effectiveLayoutVersion)
@@ -245,7 +333,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
245
333
  defaultLayouts,
246
334
  effectiveLayoutVersion,
247
335
  effectiveMigrations,
248
- prefsLoaded,
336
+ preferencesWritable,
249
337
  savedLayoutVersion,
250
338
  setBaseCols,
251
339
  setLayoutCols,
@@ -590,12 +678,15 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
590
678
  * to agree, so it is the one moment `baseCols` may move.
591
679
  */
592
680
  const commitLayout = useCallback((layouts) => {
681
+ // bsuite#3277: no write while any key is loading or failed.
682
+ if (!preferencesWritable)
683
+ return;
593
684
  const canonical = canonicaliseLayoutForPersist(layouts);
594
685
  if (!canonical)
595
686
  return;
596
687
  setSavedLayout(stripAutoHeightRows(canonical));
597
688
  setBaseCols(layoutCols);
598
- }, [canonicaliseLayoutForPersist, layoutCols, setBaseCols, setSavedLayout, stripAutoHeightRows]);
689
+ }, [canonicaliseLayoutForPersist, layoutCols, preferencesWritable, setBaseCols, setSavedLayout, stripAutoHeightRows]);
599
690
  const onLayoutChange = useCallback((_layout, layouts, wasGesture = true) => {
600
691
  if (!isEditing)
601
692
  return;
@@ -681,8 +772,11 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
681
772
  * undo. Storage moves only when the user actually edits — see `commitLayout`.
682
773
  */
683
774
  const handleColumnChange = useCallback((newCols) => {
775
+ // bsuite#3277: no write while any key is loading or failed.
776
+ if (!preferencesWritable)
777
+ return;
684
778
  setLayoutCols(newCols);
685
- }, [setLayoutCols]);
779
+ }, [preferencesWritable, setLayoutCols]);
686
780
  /**
687
781
  * Build the object handed to the preference adapter from a new `lg` array.
688
782
  *
@@ -702,9 +796,12 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
702
796
  }, [currentLayouts]);
703
797
  /** Persist a bare `lg` item list from a mutator, re-basing the same way. */
704
798
  const commitItems = useCallback((items) => {
799
+ // bsuite#3277: no write while any key is loading or failed.
800
+ if (!preferencesWritable)
801
+ return;
705
802
  setSavedLayout(layoutsWithLg(items));
706
803
  setBaseCols(layoutCols);
707
- }, [layoutCols, layoutsWithLg, setBaseCols, setSavedLayout]);
804
+ }, [layoutCols, layoutsWithLg, preferencesWritable, setBaseCols, setSavedLayout]);
708
805
  const handleCompact = useCallback(() => {
709
806
  if (!currentLayouts.lg)
710
807
  return;
@@ -724,12 +821,15 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
724
821
  startTransition(() => commitItems(result));
725
822
  }, [currentLayouts, commitItems]);
726
823
  const handleReset = useCallback(() => {
824
+ // bsuite#3277: no write while any key is loading or failed.
825
+ if (!preferencesWritable)
826
+ return;
727
827
  startTransition(() => {
728
828
  setSavedLayout(defaultLayouts);
729
829
  setLayoutCols(defaultCols);
730
830
  setBaseCols(defaultCols);
731
831
  });
732
- }, [defaultCols, defaultLayouts, setBaseCols, setLayoutCols, setSavedLayout]);
832
+ }, [defaultCols, defaultLayouts, preferencesWritable, setBaseCols, setLayoutCols, setSavedLayout]);
733
833
  const addWidget = useCallback((widgetKey, initialSize) => {
734
834
  const maxY = (currentLayouts.lg ?? []).reduce((max, item) => Math.max(max, item.y + item.h), 0);
735
835
  const newItem = {
@@ -742,7 +842,12 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
742
842
  minH: initialSize?.minH,
743
843
  };
744
844
  startTransition(() => commitItems([...(currentLayouts.lg ?? []), newItem]));
745
- }, [currentLayouts, layoutCols, layoutsWithLg, setSavedLayout]);
845
+ },
846
+ // `commitItems` is the write funnel and carries the read-state refusal
847
+ // (bsuite#3277). Omit it and the mutator keeps the funnel from the render it
848
+ // was created in: paused editing never un-pauses for this control after a
849
+ // Retry, because retrying a key that changes no layout re-creates nothing.
850
+ [commitItems, currentLayouts, layoutCols]);
746
851
  const moveWidget = useCallback((widgetKey, direction) => {
747
852
  const items = [...(currentLayouts.lg ?? [])];
748
853
  const index = items.findIndex((item) => item.i === widgetKey);
@@ -759,7 +864,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
759
864
  ...withoutMoved.slice(targetIndex),
760
865
  ];
761
866
  startTransition(() => commitItems(nextItems));
762
- }, [currentLayouts, layoutsWithLg, setSavedLayout]);
867
+ }, [commitItems, currentLayouts]);
763
868
  const setWidgetLocked = useCallback((widgetKey, locked) => {
764
869
  const nextItems = (currentLayouts.lg ?? []).map((item) => {
765
870
  if (item.i !== widgetKey)
@@ -772,11 +877,11 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
772
877
  };
773
878
  });
774
879
  startTransition(() => commitItems(nextItems));
775
- }, [currentLayouts, layoutsWithLg, setSavedLayout]);
880
+ }, [commitItems, currentLayouts]);
776
881
  const removeWidget = useCallback((widgetKey) => {
777
882
  const nextItems = (currentLayouts.lg ?? []).filter((item) => item.i !== widgetKey);
778
883
  startTransition(() => commitItems(nextItems));
779
- }, [currentLayouts, layoutsWithLg, setSavedLayout]);
884
+ }, [commitItems, currentLayouts]);
780
885
  /**
781
886
  * Measured auto-height overrides (widgetKey -> rows). DERIVED, in-memory-
782
887
  * only state (quality-review design ruling, 2026-07-14):
@@ -850,6 +955,9 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
850
955
  isEditing,
851
956
  setIsEditing,
852
957
  flushPreferences,
958
+ preferencesStatus,
959
+ canRetryPreferences,
960
+ retryPreferences,
853
961
  activeCols,
854
962
  activeCompactor,
855
963
  onLayoutChange,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "2.7.1-next.5",
3
+ "version": "2.7.1-next.7",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -24,6 +24,10 @@
24
24
  "types": "./dist/index.d.ts"
25
25
  },
26
26
  "./styles.css": "./src/styles/react-grid-layout-overrides.css",
27
+ "./card-padding": {
28
+ "import": "./dist/cardPadding.js",
29
+ "types": "./dist/cardPadding.d.ts"
30
+ },
27
31
  "./scanner": {
28
32
  "import": "./dist/scanner/index.js",
29
33
  "types": "./dist/scanner/index.d.ts"