@bsuite/page-builder 0.5.1 → 0.5.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.
- package/dist/PageEditorLauncher.d.ts +73 -0
- package/dist/PageEditorLauncher.d.ts.map +1 -0
- package/dist/PageEditorLauncher.js +116 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/usePageGridLayout.d.ts.map +1 -1
- package/dist/usePageGridLayout.js +50 -2
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { GridLayouts, WidgetMeta } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Widget configuration accepted by {@link PageEditorLauncher}.
|
|
5
|
+
*
|
|
6
|
+
* A widget is a single renderable card on the canvas — its `key` matches the
|
|
7
|
+
* `i` field of a {@link GridLayouts} item, and `content` is the React node
|
|
8
|
+
* rendered inside the card body.
|
|
9
|
+
*/
|
|
10
|
+
export interface WidgetConfig {
|
|
11
|
+
/** Unique key — must match the `i` field of the corresponding layout item. */
|
|
12
|
+
key: string;
|
|
13
|
+
/** React node rendered inside the card. */
|
|
14
|
+
content: ReactNode;
|
|
15
|
+
/** Optional metadata (label, icon, default size) for the editor banner. */
|
|
16
|
+
meta?: WidgetMeta;
|
|
17
|
+
}
|
|
18
|
+
export interface PageEditorLauncherProps {
|
|
19
|
+
/** The page identifier — used as the `pageKey` for the grid layout persistence. */
|
|
20
|
+
pageId: string;
|
|
21
|
+
/** The tenant identifier — passed through to the consumer's add-widget handler. */
|
|
22
|
+
tenantId: string;
|
|
23
|
+
/** The current set of widgets to render on the canvas. */
|
|
24
|
+
widgets: WidgetConfig[];
|
|
25
|
+
/** Called when the user adds a widget from the palette. */
|
|
26
|
+
onAddWidget: (widget: WidgetConfig) => void;
|
|
27
|
+
/** Called when the user removes a widget from the canvas. */
|
|
28
|
+
onRemoveWidget: (widgetKey: string) => void;
|
|
29
|
+
/** Called with the final layout when the user clicks Save. */
|
|
30
|
+
onSave: (layout: GridLayouts) => void;
|
|
31
|
+
/** Called when the user clicks Cancel or closes the editor. */
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
/** Default layouts for the grid. If omitted, a single empty `lg` layout is used. */
|
|
34
|
+
defaultLayouts?: GridLayouts;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* PageEditorLauncher — framework-agnostic page layout editor.
|
|
38
|
+
*
|
|
39
|
+
* Extracted from crm7's `PageEditorLauncher` (crm7/src/components/platform/
|
|
40
|
+
* PageEditorLauncher.tsx) into the shared `@bsuite/page-builder` package so
|
|
41
|
+
* any consumer app (conduit, BSU, R80) can mount a full drag-and-drop page
|
|
42
|
+
* editor without reimplementing the WidgetPalette drawer, canvas grid, or
|
|
43
|
+
* save/cancel toolbar.
|
|
44
|
+
*
|
|
45
|
+
* Unlike the crm7 original, this component is **headless** with respect to
|
|
46
|
+
* auth/permissions/routing — the consumer app gates mount via its own
|
|
47
|
+
* auth/permission checks and passes the authorised `tenantId`/`pageId`.
|
|
48
|
+
* All crm7-specific imports (supabase client, `useAuth`, `usePermissions`,
|
|
49
|
+
* `useLocation`/wouter, `getCustomPageBySlug`, crm7 stores) have been removed.
|
|
50
|
+
*
|
|
51
|
+
* The editor renders:
|
|
52
|
+
* 1. A **WidgetPalette drawer** (right-side slide-over) for adding widgets.
|
|
53
|
+
* 2. A **canvas grid** (`PageGridLayout` with `canEditPage=true`) for
|
|
54
|
+
* drag-and-drop arrangement.
|
|
55
|
+
* 3. A **save/cancel toolbar** at the top for committing or discarding changes.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* {editMode && (
|
|
60
|
+
* <PageEditorLauncher
|
|
61
|
+
* pageId={`custom-page:${page.id}`}
|
|
62
|
+
* tenantId={tenantId ?? ''}
|
|
63
|
+
* widgets={widgets}
|
|
64
|
+
* onAddWidget={handleAddWidget}
|
|
65
|
+
* onRemoveWidget={handleRemoveWidget}
|
|
66
|
+
* onSave={handleSaveLayout}
|
|
67
|
+
* onClose={() => setEditMode(false)}
|
|
68
|
+
* />
|
|
69
|
+
* )}
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function PageEditorLauncher({ pageId, tenantId, widgets, onAddWidget, onRemoveWidget, onSave, onClose, defaultLayouts, }: PageEditorLauncherProps): import("react").JSX.Element;
|
|
73
|
+
//# sourceMappingURL=PageEditorLauncher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PageEditorLauncher.d.ts","sourceRoot":"","sources":["../src/PageEditorLauncher.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI/E,OAAO,KAAK,EACV,WAAW,EAGX,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,OAAO,EAAE,SAAS,CAAC;IACnB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,2DAA2D;IAC3D,WAAW,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,6DAA6D;IAC7D,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,8DAA8D;IAC9D,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oFAAoF;IACpF,cAAc,CAAC,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,QAAQ,EACR,OAAO,EACP,WAAW,EACX,cAAc,EACd,MAAM,EACN,OAAO,EACP,cAAc,GACf,EAAE,uBAAuB,+BAmPzB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { LayoutGrid, Plus, Save, X } from 'lucide-react';
|
|
4
|
+
import { PageGridLayout } from './PageGridLayout.js';
|
|
5
|
+
import { useLocalPreference } from './preferences.js';
|
|
6
|
+
/**
|
|
7
|
+
* PageEditorLauncher — framework-agnostic page layout editor.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from crm7's `PageEditorLauncher` (crm7/src/components/platform/
|
|
10
|
+
* PageEditorLauncher.tsx) into the shared `@bsuite/page-builder` package so
|
|
11
|
+
* any consumer app (conduit, BSU, R80) can mount a full drag-and-drop page
|
|
12
|
+
* editor without reimplementing the WidgetPalette drawer, canvas grid, or
|
|
13
|
+
* save/cancel toolbar.
|
|
14
|
+
*
|
|
15
|
+
* Unlike the crm7 original, this component is **headless** with respect to
|
|
16
|
+
* auth/permissions/routing — the consumer app gates mount via its own
|
|
17
|
+
* auth/permission checks and passes the authorised `tenantId`/`pageId`.
|
|
18
|
+
* All crm7-specific imports (supabase client, `useAuth`, `usePermissions`,
|
|
19
|
+
* `useLocation`/wouter, `getCustomPageBySlug`, crm7 stores) have been removed.
|
|
20
|
+
*
|
|
21
|
+
* The editor renders:
|
|
22
|
+
* 1. A **WidgetPalette drawer** (right-side slide-over) for adding widgets.
|
|
23
|
+
* 2. A **canvas grid** (`PageGridLayout` with `canEditPage=true`) for
|
|
24
|
+
* drag-and-drop arrangement.
|
|
25
|
+
* 3. A **save/cancel toolbar** at the top for committing or discarding changes.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* {editMode && (
|
|
30
|
+
* <PageEditorLauncher
|
|
31
|
+
* pageId={`custom-page:${page.id}`}
|
|
32
|
+
* tenantId={tenantId ?? ''}
|
|
33
|
+
* widgets={widgets}
|
|
34
|
+
* onAddWidget={handleAddWidget}
|
|
35
|
+
* onRemoveWidget={handleRemoveWidget}
|
|
36
|
+
* onSave={handleSaveLayout}
|
|
37
|
+
* onClose={() => setEditMode(false)}
|
|
38
|
+
* />
|
|
39
|
+
* )}
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function PageEditorLauncher({ pageId, tenantId, widgets, onAddWidget, onRemoveWidget, onSave, onClose, defaultLayouts, }) {
|
|
43
|
+
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
44
|
+
// Track the latest layout in a ref so the Save handler can read it.
|
|
45
|
+
// The grid's preference adapter writes to this ref via the custom factory
|
|
46
|
+
// below, and Save reads from it — no localStorage coupling required.
|
|
47
|
+
const layoutRef = useRef(defaultLayouts ?? { lg: [] });
|
|
48
|
+
// Custom preference adapter factory: wraps `useLocalPreference` (so the
|
|
49
|
+
// grid still persists to localStorage for the session) and also captures
|
|
50
|
+
// the layout value in `layoutRef` whenever the `_grid_layouts` key changes.
|
|
51
|
+
// This is the seam that lets `onSave` emit the final layout without
|
|
52
|
+
// reaching into localStorage directly.
|
|
53
|
+
const capturedAdapter = useCallback((key, fallback) => {
|
|
54
|
+
const adapter = useLocalPreference(key, fallback);
|
|
55
|
+
// Track the layouts key so Save can read the ref.
|
|
56
|
+
if (key.endsWith('_grid_layouts')) {
|
|
57
|
+
const originalSetValue = adapter.setValue;
|
|
58
|
+
const wrappedSetValue = useCallback((next) => {
|
|
59
|
+
originalSetValue((previous) => {
|
|
60
|
+
const resolved = typeof next === 'function'
|
|
61
|
+
? next(previous)
|
|
62
|
+
: next;
|
|
63
|
+
layoutRef.current = resolved;
|
|
64
|
+
return resolved;
|
|
65
|
+
});
|
|
66
|
+
}, [originalSetValue]);
|
|
67
|
+
// Also capture the initial value.
|
|
68
|
+
layoutRef.current = adapter.value;
|
|
69
|
+
return { ...adapter, setValue: wrappedSetValue };
|
|
70
|
+
}
|
|
71
|
+
return adapter;
|
|
72
|
+
}, []);
|
|
73
|
+
// Build the `widgets` record expected by PageGridLayout from the WidgetConfig[].
|
|
74
|
+
const gridWidgets = useMemo(() => {
|
|
75
|
+
const record = {};
|
|
76
|
+
for (const w of widgets) {
|
|
77
|
+
record[w.key] = w.content;
|
|
78
|
+
}
|
|
79
|
+
return record;
|
|
80
|
+
}, [widgets]);
|
|
81
|
+
// Build the `widgetMeta` record for the editor banner labels.
|
|
82
|
+
const gridWidgetMeta = useMemo(() => {
|
|
83
|
+
const record = {};
|
|
84
|
+
for (const w of widgets) {
|
|
85
|
+
if (w.meta)
|
|
86
|
+
record[w.key] = w.meta;
|
|
87
|
+
}
|
|
88
|
+
return record;
|
|
89
|
+
}, [widgets]);
|
|
90
|
+
const handleSave = useCallback(() => {
|
|
91
|
+
onSave(layoutRef.current);
|
|
92
|
+
onClose();
|
|
93
|
+
}, [onSave, onClose]);
|
|
94
|
+
const handleCancel = useCallback(() => {
|
|
95
|
+
onClose();
|
|
96
|
+
}, [onClose]);
|
|
97
|
+
// The palette calls this when the user picks a widget to add.
|
|
98
|
+
const handlePaletteAdd = useCallback((widgetType) => {
|
|
99
|
+
// Delegate to the consumer's onAddWidget — the consumer knows how to
|
|
100
|
+
// resolve a widget type string into a WidgetConfig (entity table,
|
|
101
|
+
// built-in, custom, etc.). This is the framework-agnostic seam: crm7
|
|
102
|
+
// resolved this via its widget registry + supabase; conduit resolves it
|
|
103
|
+
// via its own widget catalogue.
|
|
104
|
+
onAddWidget({
|
|
105
|
+
key: widgetType,
|
|
106
|
+
content: null,
|
|
107
|
+
meta: { label: widgetType },
|
|
108
|
+
});
|
|
109
|
+
setPaletteOpen(false);
|
|
110
|
+
}, [onAddWidget]);
|
|
111
|
+
// tenantId is passed through to onAddWidget via the consumer's handler;
|
|
112
|
+
// reference it here to avoid an unused-variable lint at the call site.
|
|
113
|
+
void tenantId;
|
|
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-black/40", 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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
|
+
export { PageEditorLauncher } from './PageEditorLauncher.js';
|
|
3
|
+
export type { WidgetConfig, PageEditorLauncherProps } from './PageEditorLauncher.js';
|
|
2
4
|
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
3
5
|
export type { PageGridEditingEventDetail } from './usePageGridLayout.js';
|
|
4
6
|
export { rescaleLayout } from './rescaleLayout.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,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,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,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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACrF,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,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,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,4 +1,5 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
|
+
export { PageEditorLauncher } from './PageEditorLauncher.js';
|
|
2
3
|
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
3
4
|
export { rescaleLayout } from './rescaleLayout.js';
|
|
4
5
|
export { computeAutoHeightRows } from './autoHeight.js';
|
|
@@ -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,CA2cpD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { startTransition, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { verticalCompactor } from 'react-grid-layout';
|
|
3
3
|
import { buildResponsiveLayouts } from './buildResponsiveLayouts.js';
|
|
4
4
|
import { defaultPreferenceAdapter } from './preferences.js';
|
|
5
5
|
import { rescaleLayout } from './rescaleLayout.js';
|
|
@@ -121,7 +121,55 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
121
121
|
}
|
|
122
122
|
}, [baseCols, layoutCols, rawLayouts, setBaseCols, setSavedLayout]);
|
|
123
123
|
const activeCols = useMemo(() => ({ lg: layoutCols, md: layoutCols, sm: layoutCols, xs: layoutCols, xxs: layoutCols }), [layoutCols]);
|
|
124
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The compactor handed to react-grid-layout — the SAME one in edit mode and
|
|
126
|
+
* view mode (bsuite#1588).
|
|
127
|
+
*
|
|
128
|
+
* Why this is NOT `{ ...noCompactor, preventCollision: true }` while editing
|
|
129
|
+
* -------------------------------------------------------------------------
|
|
130
|
+
* That was the original edit-mode config, present since this package's first
|
|
131
|
+
* commit, and it silently made drag/resize gestures unable to persist for
|
|
132
|
+
* any layout with adjacent cards. react-grid-layout derives three knobs
|
|
133
|
+
* straight off the compactor object:
|
|
134
|
+
*
|
|
135
|
+
* preventCollision = compactor.preventCollision ?? false // was true
|
|
136
|
+
* allowOverlap = compactor.allowOverlap // false
|
|
137
|
+
* compactType = compactor.type // was null
|
|
138
|
+
*
|
|
139
|
+
* and feeds them to `moveElement`, whose colliding-move branch is:
|
|
140
|
+
*
|
|
141
|
+
* if (hasCollisions && preventCollision) {
|
|
142
|
+
* l.x = oldX; l.y = oldY; l.moved = false; return layout; // full revert
|
|
143
|
+
* }
|
|
144
|
+
*
|
|
145
|
+
* With `compactType: null` there is NO compaction to displace the colliding
|
|
146
|
+
* neighbour, so the only outcome for a gesture landing on an occupied cell
|
|
147
|
+
* is a total revert. `onDragStop` then guards its emit with
|
|
148
|
+
* `if (oldLayout && !deepEqual(oldLayout, finalLayout)) onLayoutChange(...)`
|
|
149
|
+
* — a reverted gesture emits NOTHING, so the persisted layout could never
|
|
150
|
+
* change. Resize is rejected by the same lever (`if (preventCollision &&
|
|
151
|
+
* !allowOverlap)` restores the old w/h/x/y). The card still tracked the
|
|
152
|
+
* cursor because react-draggable/react-resizable transform the DOM node
|
|
153
|
+
* directly, independent of whether RGL accepted the move — which is why the
|
|
154
|
+
* gesture looked live while nothing ever saved.
|
|
155
|
+
*
|
|
156
|
+
* In a full-width stack (every card `w: cols` — the dominant archetype on
|
|
157
|
+
* /dashboard and /people/:id) EVERY reorder target is occupied, so EVERY
|
|
158
|
+
* gesture reverted.
|
|
159
|
+
*
|
|
160
|
+
* Why `verticalCompactor` in BOTH modes rather than another combination
|
|
161
|
+
* --------------------------------------------------------------------
|
|
162
|
+
* View mode always compacted vertically. An edit-mode compactor that allows
|
|
163
|
+
* free/overlapping placement therefore produces a layout the viewer
|
|
164
|
+
* immediately re-compacts away — the editor/viewer mismatch meant "free
|
|
165
|
+
* placement" was never deliverable, and Save & Exit re-compacted regardless.
|
|
166
|
+
* One compactor for both modes makes what the user arranges exactly what
|
|
167
|
+
* they get, lets `moveElement` displace neighbours (so reordering a stack
|
|
168
|
+
* works at all), and removes a spurious `onLayoutChange` that RGL fired on
|
|
169
|
+
* every edit-mode toggle purely because `compactType` changed — that echo
|
|
170
|
+
* carried the pre-gesture layout and was the only write reaching storage.
|
|
171
|
+
*/
|
|
172
|
+
const activeCompactor = verticalCompactor;
|
|
125
173
|
/**
|
|
126
174
|
* Throttled layout-change handler.
|
|
127
175
|
*
|