@bsuite/page-builder 0.6.1 → 0.6.3
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.
|
@@ -112,5 +112,5 @@ export function PageEditorLauncher({ pageId, tenantId, widgets, onAddWidget, onR
|
|
|
112
112
|
// reference it here to avoid an unused-variable lint at the call site.
|
|
113
113
|
void tenantId;
|
|
114
114
|
const effectiveDefaultLayouts = defaultLayouts ?? { lg: [] };
|
|
115
|
-
return (_jsxs("div", { className: "relative isolate", children: [_jsx("div", { className: "sticky top-0 z-40 isolate flex flex-col gap-3 rounded-xl shadow-lg border-2 mb-4 p-4 bg-card/95 supports-[backdrop-filter]:bg-card/80 backdrop-blur border-primary", role: "region", "aria-label": "Page editor toolbar", 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: "rounded-full flex items-center justify-center bg-primary/10 h-10 w-10 animate-pulse", children: _jsx(LayoutGrid, { className: "h-5 w-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-foreground text-lg", children: "Page Editor" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Drag cards to rearrange. 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 px-3 py-2 text-sm font-medium transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", onClick: () => setPaletteOpen(true), children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Widget"] }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", onClick: handleCancel, children: [_jsx(X, { className: "h-4 w-4" }), "Cancel"] }), _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", onClick: handleSave, children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save"] })] })] }) }), _jsx(PageGridLayout, { pageKey: pageId, defaultLayouts: effectiveDefaultLayouts, canEditPage: true, preferenceAdapter: capturedAdapter, widgets: gridWidgets, widgetMeta: gridWidgetMeta }), paletteOpen && (_jsxs("div", { className: "fixed inset-0 z-50 flex", children: [_jsx("div", { className: "absolute inset-0 bg-
|
|
115
|
+
return (_jsxs("div", { className: "relative isolate", children: [_jsx("div", { className: "sticky top-0 z-40 isolate flex flex-col gap-3 rounded-xl shadow-lg border-2 mb-4 p-4 bg-card/95 supports-[backdrop-filter]:bg-card/80 backdrop-blur border-primary", role: "region", "aria-label": "Page editor toolbar", 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: "rounded-full flex items-center justify-center bg-primary/10 h-10 w-10 animate-pulse", children: _jsx(LayoutGrid, { className: "h-5 w-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-foreground text-lg", children: "Page Editor" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Drag cards to rearrange. 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 px-3 py-2 text-sm font-medium transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", onClick: () => setPaletteOpen(true), children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Widget"] }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", onClick: handleCancel, children: [_jsx(X, { className: "h-4 w-4" }), "Cancel"] }), _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", onClick: handleSave, children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save"] })] })] }) }), _jsx(PageGridLayout, { pageKey: pageId, defaultLayouts: effectiveDefaultLayouts, canEditPage: true, preferenceAdapter: capturedAdapter, widgets: gridWidgets, widgetMeta: gridWidgetMeta }), paletteOpen && (_jsxs("div", { className: "fixed inset-0 z-50 flex", children: [_jsx("div", { className: "absolute inset-0 bg-overlay/50", onClick: () => setPaletteOpen(false), "aria-hidden": "true" }), _jsxs("div", { className: "relative ml-auto h-full w-80 max-w-[85vw] flex flex-col bg-card border-l border-border shadow-xl", children: [_jsxs("div", { className: "flex items-center justify-between px-4 pt-4 pb-3 border-b", children: [_jsx("h2", { className: "text-base font-semibold", children: "Add Widget" }), _jsx("button", { type: "button", className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", onClick: () => setPaletteOpen(false), "aria-label": "Close widget palette", children: _jsx(X, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-4", children: [widgets.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground text-center py-8", children: "No widgets available. Add widgets via your app's widget catalogue." })) : (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground px-1", children: "Available Widgets" }), widgets.map((w) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border bg-card p-3 hover:bg-accent/50 transition-colors", children: [_jsx("span", { className: "text-sm font-medium truncate", children: w.meta?.label ?? w.key }), _jsx("button", { type: "button", className: "ml-2 shrink-0 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium bg-secondary text-secondary-foreground transition-colors hover:bg-secondary/80", onClick: () => handlePaletteAdd(w.key), children: "Add" })] }, w.key)))] })), _jsxs("div", { className: "border-t border-border pt-4 space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground px-1", children: "Remove Widget" }), widgets.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground px-1", children: "No widgets on the canvas to remove." })) : (widgets.map((w) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border bg-card p-3", children: [_jsx("span", { className: "text-sm font-medium truncate", children: w.meta?.label ?? w.key }), _jsx("button", { type: "button", className: "ml-2 shrink-0 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium bg-destructive/10 text-destructive transition-colors hover:bg-destructive/20", onClick: () => onRemoveWidget(w.key), children: "Remove" })] }, `remove-${w.key}`))))] })] })] })] }))] }));
|
|
116
116
|
}
|
package/dist/PageGridLayout.js
CHANGED
|
@@ -142,7 +142,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
|
|
|
142
142
|
// (buttons, inputs, textarea, select, links, [data-no-drag]) are still
|
|
143
143
|
// protected via the `cancel` selector below in the <Responsive> render.
|
|
144
144
|
const outerClass = cn('relative group overflow-visible', isEditing && 'drag-handle cursor-move', injectedClassName);
|
|
145
|
-
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-
|
|
145
|
+
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" })), isEditing && (_jsx("div", { className: "absolute top-2 left-2 z-30 flex items-center gap-1 pointer-events-none", children: _jsx("span", { className: "text-[10px] px-1.5 py-0.5 rounded-md opacity-80 font-medium bg-muted text-muted-foreground", children: label }) })), isEditing && (_jsx("button", { className: "absolute top-2 right-2 z-30 h-6 w-6 rounded-full flex items-center justify-center bg-destructive/80 hover:bg-destructive text-destructive-foreground shadow transition-colors", "data-no-drag": true, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
|
|
146
146
|
event.stopPropagation();
|
|
147
147
|
onHide(id);
|
|
148
148
|
}, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })), _jsx("div", { className: "h-full w-full rounded-3xl transition-all flex flex-col bg-card border border-border shadow-sm dark:shadow-[var(--glow-card,none)]", style: { contain: 'layout style' }, children: _jsx("div", { className: cn('flex-1 min-h-0', autoHeight ? 'overflow-hidden' : 'overflow-auto'), children: autoHeight ? _jsx("div", { ref: measureRef, children: content }) : content }) })] }), injectedChildren] }));
|
|
@@ -476,10 +476,10 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
476
476
|
const meta = widgetMeta?.[key];
|
|
477
477
|
const Icon = meta?.icon;
|
|
478
478
|
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', '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));
|
|
479
|
-
}) })] })), layerItems.length > 0 && (_jsxs("div", { className: "basis-full rounded-md border border-border bg-background/80 p-2", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [_jsx(Layers, { className: "h-3.5 w-3.5" }), "Layers"] }), _jsx("div", { className: "flex flex-col gap-1", children: layerItems.map((layer) => (_jsxs("div", { className: "flex items-center gap-1 rounded border border-border bg-card px-2 py-1", children: [_jsx("input", { type: "text", value: layer.label, onChange: (event) => handleLayerRename(layer.id, event.target.value), className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none", "aria-label": `Rename ${layer.label}` }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'up'), disabled: !layer.canMoveUp, "aria-label": `Move ${layer.label} up`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'down'), disabled: !layer.canMoveDown, "aria-label": `Move ${layer.label} down`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => setWidgetLocked(layer.id, !layer.locked), "aria-label": `${layer.locked ? 'Unlock' : 'Lock'} ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: layer.locked ? _jsx(Lock, { className: "h-3.5 w-3.5" }) : _jsx(Unlock, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => hideLayer(layer.id), "aria-label": `Hide ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(EyeOff, { className: "h-3.5 w-3.5" }) })] }, layer.id))) })] })), _jsxs("button", { type: "button", onClick: handleCompact, className: cn('ml-auto inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Layers, { className: "h-4 w-4 mr-1" }), "Compact Layout"] }), _jsxs("button", { type: "button", onClick: () => setResetConfirmOpen(true), className: cn('inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset to Default"] })] })] })), resetConfirmOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-
|
|
479
|
+
}) })] })), layerItems.length > 0 && (_jsxs("div", { className: "basis-full rounded-md border border-border bg-background/80 p-2", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [_jsx(Layers, { className: "h-3.5 w-3.5" }), "Layers"] }), _jsx("div", { className: "flex flex-col gap-1", children: layerItems.map((layer) => (_jsxs("div", { className: "flex items-center gap-1 rounded border border-border bg-card px-2 py-1", children: [_jsx("input", { type: "text", value: layer.label, onChange: (event) => handleLayerRename(layer.id, event.target.value), className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none", "aria-label": `Rename ${layer.label}` }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'up'), disabled: !layer.canMoveUp, "aria-label": `Move ${layer.label} up`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => moveWidget(layer.id, 'down'), disabled: !layer.canMoveDown, "aria-label": `Move ${layer.label} down`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40", children: _jsx(ArrowDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => setWidgetLocked(layer.id, !layer.locked), "aria-label": `${layer.locked ? 'Unlock' : 'Lock'} ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: layer.locked ? _jsx(Lock, { className: "h-3.5 w-3.5" }) : _jsx(Unlock, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => hideLayer(layer.id), "aria-label": `Hide ${layer.label}`, className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: _jsx(EyeOff, { className: "h-3.5 w-3.5" }) })] }, layer.id))) })] })), _jsxs("button", { type: "button", onClick: handleCompact, className: cn('ml-auto inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(Layers, { className: "h-4 w-4 mr-1" }), "Compact Layout"] }), _jsxs("button", { type: "button", onClick: () => setResetConfirmOpen(true), className: cn('inline-flex items-center rounded-md border border-border px-2 py-1 text-sm transition-colors', 'text-muted-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset to Default"] })] })] })), resetConfirmOpen && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-overlay/50 p-4", role: "dialog", "aria-modal": "true", "aria-labelledby": "page-grid-reset-title", "aria-describedby": "page-grid-reset-description", children: _jsxs("div", { className: "max-w-md rounded-lg bg-card p-5 shadow-lg border border-border", children: [_jsx("h2", { id: "page-grid-reset-title", className: "text-lg font-semibold", children: "Reset page layout?" }), _jsx("p", { id: "page-grid-reset-description", className: "mt-2 text-sm text-muted-foreground", children: "This will discard your current layout and restore all widgets to their default positions." }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx("button", { type: "button", ref: resetCancelButtonRef, onClick: () => setResetConfirmOpen(false), className: cn('inline-flex items-center rounded-md border border-border px-3 py-2 text-sm transition-colors', 'bg-card text-foreground hover:bg-muted hover:text-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1'), children: "Cancel" }), _jsx("button", { type: "button", ref: resetConfirmButtonRef, onClick: () => {
|
|
480
480
|
handleReset();
|
|
481
481
|
setResetConfirmOpen(false);
|
|
482
|
-
}, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-
|
|
482
|
+
}, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-destructive-foreground shadow hover:bg-destructive/90', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/40 focus-visible:ring-offset-1'), children: "Reset to Default" })] })] }) })), _jsx("div", { ref: containerRef, className: cn('relative z-0 overflow-visible', isEditing && 'min-h-[200px] bg-muted/30'), "data-page-grid-editing": isEditing || undefined, children: _jsx("div", { className: "relative z-0", style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout page-grid-canvas", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: DEFAULT_ROW_HEIGHT, onLayoutChange: onLayoutChange, dragConfig: {
|
|
483
483
|
enabled: isEditing,
|
|
484
484
|
handle: '.drag-handle',
|
|
485
485
|
bounded: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAGV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE,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,GAC7C,EAAE,wBAAwB,GAAG,uBAAuB,
|
|
1
|
+
{"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAGV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE,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,GAC7C,EAAE,wBAAwB,GAAG,uBAAuB,CA+fpD"}
|
|
@@ -190,23 +190,50 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
190
190
|
const pendingLayoutRef = useRef(null);
|
|
191
191
|
const layoutCommitFrameRef = useRef(null);
|
|
192
192
|
/**
|
|
193
|
-
* Strips DERIVED measured
|
|
194
|
-
* preference adapter
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
193
|
+
* Strips a DERIVED measured height out of a layout before it reaches the
|
|
194
|
+
* preference adapter — but only when the incoming `h` is nothing more than
|
|
195
|
+
* an ECHO of the render-layer merge, not a real user gesture.
|
|
196
|
+
*
|
|
197
|
+
* react-grid-layout is rendered with `autoHeightRows` merged over the saved
|
|
198
|
+
* layout (see `applyAutoHeightRows` / `activeLayouts` in
|
|
199
|
+
* `PageGridLayout.tsx`: `h = max(saved, measured)`), so the layouts it
|
|
200
|
+
* echoes back through `onLayoutChange` carry that MERGED `h` for every
|
|
201
|
+
* autoHeight item, on every commit — including commits that never touched
|
|
202
|
+
* this particular item (e.g. a re-measurement of a sibling card, or a
|
|
203
|
+
* column-count change, while mid-edit). Persisting that verbatim would
|
|
204
|
+
* smuggle a measured height into user_preferences and drop the
|
|
199
205
|
* `autoHeight` flag (react-grid-layout does not round-trip custom item
|
|
200
|
-
* props).
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
206
|
+
* props).
|
|
207
|
+
*
|
|
208
|
+
* crm7#744 (2026-07-31): the previous version of this function restored
|
|
209
|
+
* `h`/`minH` from the un-merged base layout UNCONDITIONALLY for every
|
|
210
|
+
* autoHeight item, on every call — which discarded a genuine SE-handle
|
|
211
|
+
* resize just as thoroughly as it discarded a measurement echo, because
|
|
212
|
+
* both arrive as "an `h` that differs from the base." Confirmed in a real
|
|
213
|
+
* signed-in browser: width (never touched by this function) persisted
|
|
214
|
+
* correctly across a resize; height always snapped back to its pre-drag
|
|
215
|
+
* value, deterministically, regardless of drag distance.
|
|
216
|
+
*
|
|
217
|
+
* Fix: only treat the incoming `h` as an echo — and revert it — when it
|
|
218
|
+
* exactly equals what react-grid-layout was actually RENDERED with
|
|
219
|
+
* (`max(baseItem.h, measuredRows)`). Anything else is a deliberate user
|
|
220
|
+
* resize (larger OR smaller) and must persist, still floored by the
|
|
221
|
+
* measured content height so nothing can clip. `x`/`y`/`w` (and `h` for
|
|
222
|
+
* non-autoHeight items) pass through untouched either way.
|
|
223
|
+
*
|
|
224
|
+
* Reads both the base layout and the measured rows via refs so the
|
|
225
|
+
* trailing-rAF commit below never closes over a stale snapshot, and so
|
|
226
|
+
* this callback's identity stays stable across every ResizeObserver tick
|
|
227
|
+
* (avoids recreating `onLayoutChange` — and therefore the `<Responsive>`
|
|
228
|
+
* prop identity react-grid-layout sees — on every measurement).
|
|
204
229
|
*/
|
|
205
230
|
const currentLayoutsForStripRef = useRef(null);
|
|
231
|
+
const autoHeightRowsForStripRef = useRef({});
|
|
206
232
|
const stripAutoHeightRows = useCallback((layouts) => {
|
|
207
233
|
const base = currentLayoutsForStripRef.current;
|
|
208
234
|
if (!base)
|
|
209
235
|
return layouts;
|
|
236
|
+
const measured = autoHeightRowsForStripRef.current;
|
|
210
237
|
const result = { lg: [] };
|
|
211
238
|
for (const bp of Object.keys(layouts)) {
|
|
212
239
|
const baseItems = base[bp] ?? base.lg ?? [];
|
|
@@ -215,7 +242,22 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
215
242
|
const baseItem = baseByKey.get(item.i);
|
|
216
243
|
if (!baseItem?.autoHeight)
|
|
217
244
|
return item;
|
|
218
|
-
|
|
245
|
+
const measuredRows = measured[item.i];
|
|
246
|
+
const renderedFloorH = measuredRows === undefined ? baseItem.h : Math.max(baseItem.h ?? 0, measuredRows);
|
|
247
|
+
if (item.h === renderedFloorH) {
|
|
248
|
+
// Echo of the render-layer merge (or genuinely unchanged) — not a
|
|
249
|
+
// user gesture on THIS item. Restore the un-merged base so a
|
|
250
|
+
// measured bump never persists.
|
|
251
|
+
return { ...item, autoHeight: true, h: baseItem.h, minH: baseItem.minH };
|
|
252
|
+
}
|
|
253
|
+
// A deliberate resize (bigger or smaller than the base) — keep it,
|
|
254
|
+
// still floored by the measured content height.
|
|
255
|
+
return {
|
|
256
|
+
...item,
|
|
257
|
+
autoHeight: true,
|
|
258
|
+
h: measuredRows === undefined ? item.h : Math.max(item.h, measuredRows),
|
|
259
|
+
minH: measuredRows ?? baseItem.minH,
|
|
260
|
+
};
|
|
219
261
|
});
|
|
220
262
|
}
|
|
221
263
|
return result;
|
|
@@ -357,16 +399,24 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
357
399
|
* producing the layouts handed to react-grid-layout (see
|
|
358
400
|
* `PageGridLayout`'s `activeLayouts` memo), so read-only users get
|
|
359
401
|
* full-height cards too.
|
|
360
|
-
* - NEVER persisted
|
|
361
|
-
*
|
|
362
|
-
* AND would turn mere viewing (e.g. Radix tab switches inside a
|
|
363
|
-
* which unmount/remount panel content and fire the ResizeObserver)
|
|
364
|
-
* storage upserts for any authenticated viewer
|
|
402
|
+
* - NEVER persisted from passive viewing. Measurements re-derive on every
|
|
403
|
+
* mount; writing them to the preference adapter on their own would be
|
|
404
|
+
* redundant AND would turn mere viewing (e.g. Radix tab switches inside a
|
|
405
|
+
* card, which unmount/remount panel content and fire the ResizeObserver)
|
|
406
|
+
* into storage upserts for any authenticated viewer — see the CRITICAL #2
|
|
407
|
+
* integration test. The one exception (crm7#744) is `minH` on a card the
|
|
408
|
+
* user is ACTIVELY, deliberately resizing right now: `stripAutoHeightRows`
|
|
409
|
+
* floors that card's persisted `minH` to the live measured value so the
|
|
410
|
+
* floor invariant survives the same commit as the resize, which is a
|
|
411
|
+
* user-driven write, not a passive one.
|
|
365
412
|
* - Updated FUNCTIONALLY in one combined state update per flush, so two
|
|
366
413
|
* cards settling in the same animation frame can never last-writer-wins
|
|
367
414
|
* each other through a stale closure.
|
|
368
415
|
*/
|
|
369
416
|
const [autoHeightRows, setAutoHeightRows] = useState({});
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
autoHeightRowsForStripRef.current = autoHeightRows;
|
|
419
|
+
}, [autoHeightRows]);
|
|
370
420
|
const applyAutoHeightRows = useCallback((rowsByWidget) => {
|
|
371
421
|
setAutoHeightRows((previous) => {
|
|
372
422
|
let changed = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsuite/page-builder",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepublishOnly": "pnpm build"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@bsuite/theme": ">=0.
|
|
35
|
+
"@bsuite/theme": ">=0.7.1",
|
|
36
36
|
"@dnd-kit/core": "^6",
|
|
37
37
|
"@dnd-kit/sortable": "^10",
|
|
38
38
|
"@dnd-kit/utilities": "^3",
|
|
@@ -72,8 +72,14 @@
|
|
|
72
72
|
bottom: 3px;
|
|
73
73
|
width: 6px;
|
|
74
74
|
height: 6px;
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
/* --text-secondary is NOT a token this design system defines — the name is
|
|
76
|
+
--role-text-secondary. So the fallback was not a fallback, it was the
|
|
77
|
+
value: every resize handle rendered at pure black 45%, which on a dark
|
|
78
|
+
surface is very nearly invisible. Named correctly, and the fallback
|
|
79
|
+
dropped rather than corrected — a fallback here can only ever mask the
|
|
80
|
+
same mistake again. */
|
|
81
|
+
border-right: 2px solid var(--role-text-secondary);
|
|
82
|
+
border-bottom: 2px solid var(--role-text-secondary);
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/* Highlight the marker when hovering the handle itself. */
|