@bsuite/page-builder 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PageGridLayout.d.ts.map +1 -1
- package/dist/PageGridLayout.js +75 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usePageGridLayout.d.ts +14 -0
- package/dist/usePageGridLayout.d.ts.map +1 -1
- package/dist/usePageGridLayout.js +111 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAIxC,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqJnE,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,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,GACvB,EAAE,mBAAmB,2CA4frB"}
|
package/dist/PageGridLayout.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Eye, EyeOff, Layers, LayoutGrid, Plus, RotateCcw, Save, Settings2 } from 'lucide-react';
|
|
2
|
+
import { ArrowDown, ArrowUp, Eye, EyeOff, Layers, LayoutGrid, Lock, Plus, RotateCcw, Save, Settings2, Unlock } from 'lucide-react';
|
|
3
3
|
import React, { startTransition, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Responsive } from 'react-grid-layout';
|
|
5
5
|
import { gridBounds, maxSize, minMaxSize, minSize } from 'react-grid-layout/core';
|
|
6
6
|
import 'react-grid-layout/css/styles.css';
|
|
7
7
|
import 'react-resizable/css/styles.css';
|
|
8
|
+
import { defaultPreferenceAdapter } from './preferences.js';
|
|
8
9
|
import { usePageGridLayout } from './usePageGridLayout.js';
|
|
9
10
|
import { cn } from './utils.js';
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Resize bounds for the bottom-right handle.
|
|
13
|
+
*
|
|
14
|
+
* `maxH` was 16 (≈512px at rowHeight=32) which clipped data tables holding
|
|
15
|
+
* more than ~7 rows — operators reported "the resize handle hits a wall
|
|
16
|
+
* before all my data fits". Bumped to 48 (≈1536px) which comfortably fits
|
|
17
|
+
* ~20–30 list rows of any density and matches the maximum viewport height
|
|
18
|
+
* on common displays. Cards that still need to show more data should rely
|
|
19
|
+
* on their own internal scroll container (we apply `min-h-0 overflow-auto`
|
|
20
|
+
* to the card body wrapper below) rather than growing without bound.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_RESIZE_BOUNDS = { minW: 2, minH: 1, maxW: 12, maxH: 48 };
|
|
11
23
|
const DEFAULT_RESIZE_HANDLES = ['se'];
|
|
12
24
|
const DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES = ['bsu-add-entity-widget', 'crm7-add-entity-widget'];
|
|
13
25
|
/**
|
|
@@ -36,7 +48,7 @@ const DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES = ['bsu-add-entity-widget', 'crm7-ad
|
|
|
36
48
|
const RGLResizeHandle = React.forwardRef(function RGLResizeHandle({ axis = 'se', className: injectedClassName, ...rest }, ref) {
|
|
37
49
|
return (_jsx("span", { ref: ref, "data-no-drag": true, className: `react-resizable-handle react-resizable-handle-${axis}${injectedClassName ? ' ' + injectedClassName : ''}`, ...rest }));
|
|
38
50
|
});
|
|
39
|
-
const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label,
|
|
51
|
+
const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label, onHide, children: injectedChildren, className: injectedClassName, style: injectedStyle, ...rest }, ref) {
|
|
40
52
|
// DnD root-cause fix (2026-05-07): react-draggable@4 (used internally by
|
|
41
53
|
// react-grid-layout@2) checks the drag handle via
|
|
42
54
|
// `matchesSelectorAndParentsTo(target, handle, baseNode)` — it walks UP
|
|
@@ -53,7 +65,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
|
|
|
53
65
|
const outerClass = cn('relative group', isEditing && 'drag-handle cursor-move', injectedClassName);
|
|
54
66
|
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-black/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-white shadow transition-colors", "data-no-drag": true, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
|
|
55
67
|
event.stopPropagation();
|
|
56
|
-
|
|
68
|
+
onHide(id);
|
|
57
69
|
}, 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 shadow-sm", style: { contain: 'paint' }, children: content })] }), injectedChildren] }));
|
|
58
70
|
}));
|
|
59
71
|
export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className,
|
|
@@ -63,7 +75,7 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
|
|
|
63
75
|
// the safe default is true so any consumer who forgets to set it still
|
|
64
76
|
// gets a usable canvas. Opt out per-page with isResizable={false}.
|
|
65
77
|
isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, }) {
|
|
66
|
-
const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleCompact, handleReset, addWidget,
|
|
78
|
+
const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
|
|
67
79
|
pageKey,
|
|
68
80
|
defaultLayouts,
|
|
69
81
|
defaultCols,
|
|
@@ -80,6 +92,8 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
80
92
|
maxSize(DEFAULT_RESIZE_BOUNDS.maxW, DEFAULT_RESIZE_BOUNDS.maxH),
|
|
81
93
|
], []);
|
|
82
94
|
const [extraWidgetConfigs, setExtraWidgetConfigs] = useState({});
|
|
95
|
+
const { value: layerNames, setValue: setLayerNames } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_layer_names`, {});
|
|
96
|
+
const { value: hiddenLayerIds, setValue: setHiddenLayerIds } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_hidden_layers`, {});
|
|
83
97
|
const extraWidgets = useMemo(() => {
|
|
84
98
|
const rendered = {};
|
|
85
99
|
if (!createEntityWidget)
|
|
@@ -99,10 +113,25 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
99
113
|
const activeLayouts = useMemo(() => {
|
|
100
114
|
const filtered = { lg: [] };
|
|
101
115
|
for (const bp in currentLayouts) {
|
|
102
|
-
filtered[bp] = (currentLayouts[bp] ?? []).filter((item) => renderableWidgetKeys.has(item.i));
|
|
116
|
+
filtered[bp] = (currentLayouts[bp] ?? []).filter((item) => renderableWidgetKeys.has(item.i) && !hiddenLayerIds[item.i]);
|
|
103
117
|
}
|
|
104
118
|
return filtered;
|
|
105
|
-
}, [currentLayouts, renderableWidgetKeys]);
|
|
119
|
+
}, [currentLayouts, hiddenLayerIds, renderableWidgetKeys]);
|
|
120
|
+
const hideLayer = (layerId) => {
|
|
121
|
+
setHiddenLayerIds((previous) => ({ ...previous, [layerId]: true }));
|
|
122
|
+
};
|
|
123
|
+
const showLayer = (layerId, defaultSize) => {
|
|
124
|
+
setHiddenLayerIds((previous) => {
|
|
125
|
+
if (!previous[layerId])
|
|
126
|
+
return previous;
|
|
127
|
+
const { [layerId]: _removed, ...rest } = previous;
|
|
128
|
+
return rest;
|
|
129
|
+
});
|
|
130
|
+
const existsInLayouts = Object.values(currentLayouts).some((items) => (items ?? []).some((item) => item.i === layerId));
|
|
131
|
+
if (!existsInLayouts) {
|
|
132
|
+
addWidget(layerId, defaultSize);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
106
135
|
useEffect(() => {
|
|
107
136
|
if (!createEntityWidget || typeof window === 'undefined')
|
|
108
137
|
return;
|
|
@@ -142,6 +171,30 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
142
171
|
]);
|
|
143
172
|
const visibleKeys = useMemo(() => new Set((activeLayouts.lg ?? []).map((item) => item.i)), [activeLayouts.lg]);
|
|
144
173
|
const hiddenWidgetKeys = useMemo(() => [...renderableWidgetKeys].filter((key) => !visibleKeys.has(key)), [renderableWidgetKeys, visibleKeys]);
|
|
174
|
+
const layerItems = useMemo(() => (activeLayouts.lg ?? []).map((item, index, layers) => {
|
|
175
|
+
const fallbackLabel = widgetMeta?.[item.i]?.label ?? item.i;
|
|
176
|
+
return {
|
|
177
|
+
id: item.i,
|
|
178
|
+
label: layerNames[item.i] || fallbackLabel,
|
|
179
|
+
locked: Boolean(item.static || item.isDraggable === false || item.isResizable === false),
|
|
180
|
+
canMoveUp: index > 0,
|
|
181
|
+
canMoveDown: index < layers.length - 1,
|
|
182
|
+
};
|
|
183
|
+
}), [activeLayouts.lg, layerNames, widgetMeta]);
|
|
184
|
+
const handleLayerRename = (layerId, value) => {
|
|
185
|
+
const trimmed = value.trim();
|
|
186
|
+
setLayerNames((previous) => {
|
|
187
|
+
if (trimmed.length === 0) {
|
|
188
|
+
if (!(layerId in previous))
|
|
189
|
+
return previous;
|
|
190
|
+
const { [layerId]: _removed, ...rest } = previous;
|
|
191
|
+
return rest;
|
|
192
|
+
}
|
|
193
|
+
if (previous[layerId] === trimmed)
|
|
194
|
+
return previous;
|
|
195
|
+
return { ...previous, [layerId]: trimmed };
|
|
196
|
+
});
|
|
197
|
+
};
|
|
145
198
|
// Reset-confirmation dialog: full ARIA APG dialog-modal pattern.
|
|
146
199
|
// - Escape key closes the dialog.
|
|
147
200
|
// - Tab / Shift+Tab are trapped within the two dialog buttons (cancel ↔ confirm).
|
|
@@ -191,14 +244,24 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
191
244
|
previousActiveElement?.focus?.();
|
|
192
245
|
};
|
|
193
246
|
}, [resetConfirmOpen]);
|
|
194
|
-
return (_jsxs("div", { className: className, children: [isEditing && (_jsxs("div", { className:
|
|
247
|
+
return (_jsxs("div", { className: className, children: [isEditing && (_jsxs("div", { className: cn(
|
|
248
|
+
// Sticky overlay banner — sits at the top of the scroll container
|
|
249
|
+
// without pushing the form down. `top-0` anchors to the nearest
|
|
250
|
+
// scrolling ancestor; `z-30` keeps it above grid items but below
|
|
251
|
+
// app-level overlays (toaster, dialogs are z-50+).
|
|
252
|
+
'sticky top-0 z-30 flex flex-col gap-3 p-4 rounded-xl shadow-lg border-2 mb-4',
|
|
253
|
+
// Subtle translucent background so the form behind it stays
|
|
254
|
+
// partially visible — mitigates Issue 2 (banner consuming
|
|
255
|
+
// vertical space). `bg-card/95` + `backdrop-blur` keeps text
|
|
256
|
+
// legible while showing form context behind the banner.
|
|
257
|
+
'bg-card/95 supports-[backdrop-filter]:bg-card/80 backdrop-blur border-primary'), role: "region", "aria-label": "Canvas editor controls", 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: "h-10 w-10 rounded-full flex items-center justify-center animate-pulse bg-primary/10", children: _jsx(Settings2, { className: "h-5 w-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-lg text-foreground", children: "Canvas Editor Active" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Drag anywhere on a card to move it. Resize with the bottom-right handle." })] })] }), _jsxs("button", { type: "button", className: "shrink-0 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: () => startTransition(() => setIsEditing(false)), children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save & Exit"] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 pt-2 border-t border-border", 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', '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))) }), 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) => {
|
|
195
258
|
const meta = widgetMeta?.[key];
|
|
196
259
|
const Icon = meta?.icon;
|
|
197
|
-
return (_jsxs("button", { type: "button", onClick: () =>
|
|
198
|
-
}) })] })), _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-black/40 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: () => {
|
|
260
|
+
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));
|
|
261
|
+
}) })] })), 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-black/40 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: () => {
|
|
199
262
|
handleReset();
|
|
200
263
|
setResetConfirmOpen(false);
|
|
201
|
-
}, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-white 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: isEditing ? 'min-h-[200px]' : '', style: { backgroundColor: isEditing ? 'rgb(0 0 0 / 0.03)' : 'transparent' }, children: _jsx("div", { style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: 32, onLayoutChange: onLayoutChange, dragConfig: {
|
|
264
|
+
}, className: cn('inline-flex items-center rounded-md px-3 py-2 text-sm font-medium transition-colors', 'bg-destructive text-white 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: isEditing ? 'min-h-[200px]' : '', style: { backgroundColor: isEditing ? 'rgb(0 0 0 / 0.03)' : 'transparent' }, "data-page-grid-editing": isEditing || undefined, children: _jsx("div", { style: { opacity: containerWidth > 0 ? 1 : 0 }, "aria-busy": containerWidth <= 0, children: _jsx(Responsive, { width: Math.max(containerWidth, 1), className: "layout", layouts: activeLayouts, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, rowHeight: 32, onLayoutChange: onLayoutChange, dragConfig: {
|
|
202
265
|
enabled: isEditing,
|
|
203
266
|
handle: '.drag-handle',
|
|
204
267
|
bounded: false,
|
|
@@ -212,6 +275,6 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
|
|
|
212
275
|
const content = allWidgets[layoutItem.i];
|
|
213
276
|
if (content === undefined || content === null)
|
|
214
277
|
return null;
|
|
215
|
-
return (_jsx(GridItem, { id: layoutItem.i, content: content, isEditing: isEditing, label: widgetMeta?.[layoutItem.i]?.label
|
|
278
|
+
return (_jsx(GridItem, { id: layoutItem.i, content: content, isEditing: isEditing, label: layerNames[layoutItem.i] || widgetMeta?.[layoutItem.i]?.label || layoutItem.i, onHide: hideLayer }, layoutItem.i));
|
|
216
279
|
}) }) }) })] }));
|
|
217
280
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
|
-
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES } from './usePageGridLayout.js';
|
|
2
|
+
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
3
|
+
export type { PageGridEditingEventDetail } from './usePageGridLayout.js';
|
|
3
4
|
export { rescaleLayout } from './rescaleLayout.js';
|
|
4
5
|
export { WYSIWYG_REQUIRED_PRIMITIVES, WYSIWYG_SURFACE_CONTRACTS, assertWysiwygPrimitiveCoverage, getWysiwygSurfaceContract, } from './wysiwygContract.js';
|
|
5
6
|
export type { WysiwygPrimitive, WysiwygSurfaceContract, WysiwygSurfaceId, } from './wysiwygContract.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,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,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
|
-
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES } from './usePageGridLayout.js';
|
|
2
|
+
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
3
3
|
export { rescaleLayout } from './rescaleLayout.js';
|
|
4
4
|
export { WYSIWYG_REQUIRED_PRIMITIVES, WYSIWYG_SURFACE_CONTRACTS, assertWysiwygPrimitiveCoverage, getWysiwygSurfaceContract, } from './wysiwygContract.js';
|
|
5
5
|
export { buildResponsiveLayouts, RESPONSIVE_BREAKPOINTS, } from './buildResponsiveLayouts.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface UsePageGridLayoutResult {
|
|
|
64
64
|
handleCompact: () => void;
|
|
65
65
|
handleReset: () => void;
|
|
66
66
|
addWidget: (widgetKey: string, initialSize?: Partial<Pick<GridLayoutItem, 'w' | 'h' | 'minW' | 'minH'>>) => void;
|
|
67
|
+
moveWidget: (widgetKey: string, direction: 'up' | 'down') => void;
|
|
68
|
+
setWidgetLocked: (widgetKey: string, locked: boolean) => void;
|
|
67
69
|
removeWidget: (widgetKey: string) => void;
|
|
68
70
|
resetConfirmOpen: boolean;
|
|
69
71
|
setResetConfirmOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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":"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"}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { UsePageGridLayoutOptions, UsePageGridLayoutResult } from './types.js';
|
|
2
2
|
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
|
+
/**
|
|
4
|
+
* Event name broadcast on `window` whenever any PageGridLayout transitions
|
|
5
|
+
* its `isEditing` state. Launcher widgets (e.g. floating "Edit Page" FABs)
|
|
6
|
+
* subscribe to this so they can hide themselves while the canvas editor is
|
|
7
|
+
* already active — prevents the redundant-affordance UX issue where a
|
|
8
|
+
* "Edit Page" button sits in the corner while the editor banner is visible
|
|
9
|
+
* at the top. `detail.editing` is the new state; `detail.pageKey` lets
|
|
10
|
+
* launchers scope by page if they handle multiple grids on one screen.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PAGE_GRID_EDITING_EVENT = "bsuite-page-grid-editing";
|
|
13
|
+
export interface PageGridEditingEventDetail {
|
|
14
|
+
pageKey: string;
|
|
15
|
+
editing: boolean;
|
|
16
|
+
}
|
|
3
17
|
export declare function usePageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, }: UsePageGridLayoutOptions): UsePageGridLayoutResult;
|
|
4
18
|
//# sourceMappingURL=usePageGridLayout.d.ts.map
|
|
@@ -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,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,CA8UpD"}
|
|
@@ -10,10 +10,32 @@ export const DEFAULT_EDITOR_EVENT_NAMES = [
|
|
|
10
10
|
'conduit-open-page-editor',
|
|
11
11
|
'r80-open-page-editor',
|
|
12
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* Event name broadcast on `window` whenever any PageGridLayout transitions
|
|
15
|
+
* its `isEditing` state. Launcher widgets (e.g. floating "Edit Page" FABs)
|
|
16
|
+
* subscribe to this so they can hide themselves while the canvas editor is
|
|
17
|
+
* already active — prevents the redundant-affordance UX issue where a
|
|
18
|
+
* "Edit Page" button sits in the corner while the editor banner is visible
|
|
19
|
+
* at the top. `detail.editing` is the new state; `detail.pageKey` lets
|
|
20
|
+
* launchers scope by page if they handle multiple grids on one screen.
|
|
21
|
+
*/
|
|
22
|
+
export const PAGE_GRID_EDITING_EVENT = 'bsuite-page-grid-editing';
|
|
13
23
|
export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, layoutVersion = 1, canEditPage = true, editorEventNames = DEFAULT_EDITOR_EVENT_NAMES, preferenceAdapter = defaultPreferenceAdapter, }) {
|
|
14
24
|
const containerRef = useRef(null);
|
|
15
25
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
16
|
-
const [isEditing,
|
|
26
|
+
const [isEditing, setIsEditingState] = useState(false);
|
|
27
|
+
const setIsEditing = useCallback((next) => {
|
|
28
|
+
setIsEditingState((previous) => {
|
|
29
|
+
const resolved = typeof next === 'function' ? next(previous) : next;
|
|
30
|
+
if (resolved !== previous && typeof window !== 'undefined') {
|
|
31
|
+
// Broadcast to FAB launchers so they can hide while the editor is open.
|
|
32
|
+
window.dispatchEvent(new CustomEvent(PAGE_GRID_EDITING_EVENT, {
|
|
33
|
+
detail: { pageKey, editing: resolved },
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
});
|
|
38
|
+
}, [pageKey]);
|
|
17
39
|
const [resetConfirmOpen, setResetConfirmOpen] = useState(false);
|
|
18
40
|
useLayoutEffect(() => {
|
|
19
41
|
const el = containerRef.current;
|
|
@@ -100,10 +122,54 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
100
122
|
}, [baseCols, layoutCols, rawLayouts, setBaseCols, setSavedLayout]);
|
|
101
123
|
const activeCols = useMemo(() => ({ lg: layoutCols, md: layoutCols, sm: layoutCols, xs: layoutCols, xxs: layoutCols }), [layoutCols]);
|
|
102
124
|
const activeCompactor = useMemo(() => (isEditing ? { ...noCompactor, preventCollision: true } : verticalCompactor), [isEditing]);
|
|
125
|
+
/**
|
|
126
|
+
* Throttled layout-change handler.
|
|
127
|
+
*
|
|
128
|
+
* `react-grid-layout` fires `onLayoutChange` on **every** drag/resize tick
|
|
129
|
+
* (≈60Hz). The previous implementation called `setSavedLayout` directly,
|
|
130
|
+
* which delegates to a preference adapter — in apps like crm7 that adapter
|
|
131
|
+
* writes to localStorage and/or queues a Supabase upsert per call. Operators
|
|
132
|
+
* reported 6–7s INP blocks on resize gestures (long-task warnings on
|
|
133
|
+
* `.react-resizable-handle-se` and `.react-grid-layout`).
|
|
134
|
+
*
|
|
135
|
+
* Fix: capture the latest layout in a ref each tick (cheap, no React work)
|
|
136
|
+
* and only commit to the preference adapter on a trailing rAF tick. This
|
|
137
|
+
* cuts adapter calls from ~60/sec to ~1/sec during a typical drag while
|
|
138
|
+
* still persisting the final position when the gesture ends. The component
|
|
139
|
+
* still receives `activeLayouts` from `savedLayout` synchronously, so the
|
|
140
|
+
* grid keeps following the cursor visually.
|
|
141
|
+
*/
|
|
142
|
+
const pendingLayoutRef = useRef(null);
|
|
143
|
+
const layoutCommitFrameRef = useRef(null);
|
|
103
144
|
const onLayoutChange = useCallback((_layout, layouts) => {
|
|
104
|
-
if (isEditing)
|
|
145
|
+
if (!isEditing)
|
|
146
|
+
return;
|
|
147
|
+
pendingLayoutRef.current = layouts;
|
|
148
|
+
if (layoutCommitFrameRef.current !== null)
|
|
149
|
+
return;
|
|
150
|
+
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
|
|
105
151
|
setSavedLayout(layouts);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Trailing rAF — lets a burst of resize ticks coalesce into one commit.
|
|
155
|
+
layoutCommitFrameRef.current = window.requestAnimationFrame(() => {
|
|
156
|
+
layoutCommitFrameRef.current = null;
|
|
157
|
+
const pending = pendingLayoutRef.current;
|
|
158
|
+
if (pending) {
|
|
159
|
+
pendingLayoutRef.current = null;
|
|
160
|
+
setSavedLayout(pending);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
106
163
|
}, [isEditing, setSavedLayout]);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
return () => {
|
|
166
|
+
if (layoutCommitFrameRef.current !== null &&
|
|
167
|
+
typeof window !== 'undefined' &&
|
|
168
|
+
typeof window.cancelAnimationFrame === 'function') {
|
|
169
|
+
window.cancelAnimationFrame(layoutCommitFrameRef.current);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, []);
|
|
107
173
|
const handleColumnChange = useCallback((newCols) => {
|
|
108
174
|
const rescaled = rescaleLayout(currentLayouts, layoutCols, newCols);
|
|
109
175
|
setSavedLayout(rescaled);
|
|
@@ -153,6 +219,47 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
153
219
|
}
|
|
154
220
|
startTransition(() => setSavedLayout(updated));
|
|
155
221
|
}, [currentLayouts, layoutCols, setSavedLayout]);
|
|
222
|
+
const moveWidget = useCallback((widgetKey, direction) => {
|
|
223
|
+
const updated = { lg: [] };
|
|
224
|
+
for (const bp of Object.keys(currentLayouts)) {
|
|
225
|
+
const items = [...(currentLayouts[bp] ?? [])];
|
|
226
|
+
const index = items.findIndex((item) => item.i === widgetKey);
|
|
227
|
+
if (index === -1) {
|
|
228
|
+
updated[bp] = items;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
|
232
|
+
if (targetIndex < 0 || targetIndex >= items.length) {
|
|
233
|
+
updated[bp] = items;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const moved = items[index];
|
|
237
|
+
const withoutMoved = [...items.slice(0, index), ...items.slice(index + 1)];
|
|
238
|
+
const nextItems = [
|
|
239
|
+
...withoutMoved.slice(0, targetIndex),
|
|
240
|
+
moved,
|
|
241
|
+
...withoutMoved.slice(targetIndex),
|
|
242
|
+
];
|
|
243
|
+
updated[bp] = nextItems;
|
|
244
|
+
}
|
|
245
|
+
startTransition(() => setSavedLayout(updated));
|
|
246
|
+
}, [currentLayouts, setSavedLayout]);
|
|
247
|
+
const setWidgetLocked = useCallback((widgetKey, locked) => {
|
|
248
|
+
const updated = { lg: [] };
|
|
249
|
+
for (const bp of Object.keys(currentLayouts)) {
|
|
250
|
+
updated[bp] = (currentLayouts[bp] ?? []).map((item) => {
|
|
251
|
+
if (item.i !== widgetKey)
|
|
252
|
+
return item;
|
|
253
|
+
return {
|
|
254
|
+
...item,
|
|
255
|
+
static: locked,
|
|
256
|
+
isDraggable: !locked,
|
|
257
|
+
isResizable: !locked,
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
startTransition(() => setSavedLayout(updated));
|
|
262
|
+
}, [currentLayouts, setSavedLayout]);
|
|
156
263
|
const removeWidget = useCallback((widgetKey) => {
|
|
157
264
|
const updated = { lg: [] };
|
|
158
265
|
for (const bp of Object.keys(currentLayouts)) {
|
|
@@ -191,6 +298,8 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
191
298
|
handleCompact,
|
|
192
299
|
handleReset,
|
|
193
300
|
addWidget,
|
|
301
|
+
moveWidget,
|
|
302
|
+
setWidgetLocked,
|
|
194
303
|
removeWidget,
|
|
195
304
|
resetConfirmOpen,
|
|
196
305
|
setResetConfirmOpen,
|