@bsuite/page-builder 0.3.0 → 0.4.1

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.
@@ -1 +1 @@
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"}
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;AA+KnE,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,2CA+frB"}
@@ -2,24 +2,31 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
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
- import { gridBounds, maxSize, minMaxSize, minSize } from 'react-grid-layout/core';
5
+ import { gridBounds, 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
8
  import { defaultPreferenceAdapter } from './preferences.js';
9
9
  import { usePageGridLayout } from './usePageGridLayout.js';
10
10
  import { cn } from './utils.js';
11
11
  /**
12
- * Resize bounds for the bottom-right handle.
12
+ * Default resize bounds applied as a *global* constraint to ALL grid items.
13
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.
14
+ * History
15
+ * -------
16
+ * - 0.2.x: maxH=16 (≈512px) — clipped data tables.
17
+ * - 0.3.0: bumped to maxH=48 (≈1536px) — still a hard ceiling. Operators
18
+ * reported "the handle hits a wall before all my data fits" on tall
19
+ * data-table cards even after the bump.
20
+ * - 0.4.0 (this version): **removed the global maxW and maxH ceilings
21
+ * entirely.** Width is bounded by `gridBounds` (the active column count)
22
+ * so no separate global `maxSize(...)` is needed; per-item
23
+ * `LayoutItem.maxW` / `maxH` is honoured via `minMaxSize`. Combined
24
+ * with the new internal scroll wrapper around `{content}` (see GridItem
25
+ * render below), cards now grow to their bounds and content scrolls
26
+ * inside. Per-item `maxH` on `LayoutItem` remains the right knob for
27
+ * "this widget shouldn't grow past N rows".
21
28
  */
22
- const DEFAULT_RESIZE_BOUNDS = { minW: 2, minH: 1, maxW: 12, maxH: 48 };
29
+ const DEFAULT_RESIZE_BOUNDS = { minW: 2, minH: 1 };
23
30
  const DEFAULT_RESIZE_HANDLES = ['se'];
24
31
  const DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES = ['bsu-add-entity-widget', 'crm7-add-entity-widget'];
25
32
  /**
@@ -62,11 +69,11 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
62
69
  // the GridItem has it as an ancestor → drag fires. Interactive elements
63
70
  // (buttons, inputs, textarea, select, links, [data-no-drag]) are still
64
71
  // protected via the `cancel` selector below in the <Responsive> render.
65
- const outerClass = cn('relative group', isEditing && 'drag-handle cursor-move', injectedClassName);
72
+ const outerClass = cn('relative group overflow-visible', isEditing && 'drag-handle cursor-move', injectedClassName);
66
73
  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) => {
67
74
  event.stopPropagation();
68
75
  onHide(id);
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] }));
76
+ }, 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: 'layout style' }, children: _jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: content }) })] }), injectedChildren] }));
70
77
  }));
71
78
  export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className,
72
79
  // Resize default flipped 2026-05-12: rgl v2 dropped auto-handle rendering
@@ -86,10 +93,13 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
86
93
  });
87
94
  const resizeEnabled = isEditing && isResizable;
88
95
  const resizeConstraints = useMemo(() => [
96
+ // `gridBounds` caps width at the active column count, so we don't need
97
+ // a global maxSize(...). Width and height are unbounded by default;
98
+ // consumers cap individual widgets via LayoutItem.maxW / maxH which
99
+ // `minMaxSize` reads per-item.
89
100
  gridBounds,
90
101
  minMaxSize,
91
102
  minSize(DEFAULT_RESIZE_BOUNDS.minW, DEFAULT_RESIZE_BOUNDS.minH),
92
- maxSize(DEFAULT_RESIZE_BOUNDS.maxW, DEFAULT_RESIZE_BOUNDS.maxH),
93
103
  ], []);
94
104
  const [extraWidgetConfigs, setExtraWidgetConfigs] = useState({});
95
105
  const { value: layerNames, setValue: setLayerNames } = (preferenceAdapter ?? defaultPreferenceAdapter)(`page:${pageKey}_grid_layer_names`, {});
@@ -244,12 +254,12 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
244
254
  previousActiveElement?.focus?.();
245
255
  };
246
256
  }, [resetConfirmOpen]);
247
- return (_jsxs("div", { className: className, children: [isEditing && (_jsxs("div", { className: cn(
257
+ return (_jsxs("div", { className: cn('relative', isEditing && 'isolate', className), children: [isEditing && (_jsxs("div", { "data-page-grid-editor-controls": true, className: cn(
248
258
  // Sticky overlay banner — sits at the top of the scroll container
249
259
  // without pushing the form down. `top-0` anchors to the nearest
250
- // scrolling ancestor; `z-30` keeps it above grid items but below
260
+ // scrolling ancestor; `z-40` keeps it above grid items but below
251
261
  // 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',
262
+ 'sticky top-0 z-40 isolate flex flex-col gap-3 p-4 rounded-xl shadow-lg border-2 mb-4',
253
263
  // Subtle translucent background so the form behind it stays
254
264
  // partially visible — mitigates Issue 2 (banner consuming
255
265
  // vertical space). `bg-card/95` + `backdrop-blur` keeps text
@@ -261,7 +271,7 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
261
271
  }) })] })), 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: () => {
262
272
  handleReset();
263
273
  setResetConfirmOpen(false);
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: {
274
+ }, 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: 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: 32, onLayoutChange: onLayoutChange, dragConfig: {
265
275
  enabled: isEditing,
266
276
  handle: '.drag-handle',
267
277
  bounded: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -14,11 +14,24 @@
14
14
 
15
15
  .react-grid-layout {
16
16
  position: relative;
17
+ isolation: isolate;
17
18
  transition: height 200ms ease;
18
19
  }
19
20
 
20
21
  .react-grid-item {
22
+ overflow: visible;
21
23
  transition: transform 200ms ease, width 200ms ease, height 200ms ease;
24
+ z-index: 1;
25
+ }
26
+
27
+ .react-grid-layout .react-grid-item.react-draggable-dragging,
28
+ .react-grid-layout .react-grid-item.resizing {
29
+ z-index: 20;
30
+ }
31
+
32
+ .react-grid-layout .react-grid-item.react-grid-placeholder {
33
+ z-index: 0;
34
+ pointer-events: none;
22
35
  }
23
36
 
24
37
  .react-grid-item.react-grid-placeholder {