@bsuite/page-builder 2.2.0 → 2.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AA4W7F,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EAMT,WAAkB,EAClB,aAAsC,EACtC,QAAQ,EACR,iBAAiB,EAKjB,UAAkB,EAClB,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBAyzBrB"}
1
+ {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,OAAO,KAAK,EAAe,mBAAmB,EAA4B,MAAM,YAAY,CAAC;AA8f7F,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACV,SAAS,EAMT,WAAkB,EAClB,aAAsC,EACtC,QAAQ,EACR,iBAAiB,EAKjB,UAAkB,EAClB,yBAAiE,EACjE,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,+BAA6E,EAC7E,wBAAwB,EACxB,4BAA4B,EAC5B,4BAA4B,GAC7B,EAAE,mBAAmB,qBAyzBrB"}
@@ -84,6 +84,61 @@ const DEFAULT_ADD_RELATIONSHIP_WIDGET_EVENT_NAMES = [
84
84
  const RGLResizeHandle = React.forwardRef(function RGLResizeHandle({ axis = 'se', className: injectedClassName, ...rest }, ref) {
85
85
  return (_jsx("span", { ref: ref, "data-no-drag": true, className: `react-resizable-handle react-resizable-handle-${axis}${injectedClassName ? ' ' + injectedClassName : ''}`, ...rest }));
86
86
  });
87
+ /* A ZERO MEASUREMENT IS DISCARDED SILENTLY, AND THAT IS HOW A CARD GOES INVISIBLE.
88
+ *
89
+ * `reportRows` is guarded by `contentPx > 0` — correctly, because a zero reading
90
+ * during mount would converge the card to nothing. But a card whose content
91
+ * genuinely measures zero then keeps whatever row count it had, inside a wrapper
92
+ * that is `overflow-hidden`. The card renders. The content does not. Nothing
93
+ * fails, and nothing says so.
94
+ *
95
+ * The reachable cause is a percentage height. The measuring div is deliberately
96
+ * UNCONSTRAINED so the observer reads intrinsic height rather than the clipped
97
+ * flex box — that is the whole reason it exists. An unconstrained parent has
98
+ * `height: auto`, and a child asking for `height: 100%` against `auto` resolves
99
+ * to auto, i.e. its own content height. A chart, a canvas or a map that sizes
100
+ * itself from its parent therefore collapses to nothing, and the taller the
101
+ * intended element the more invisible it is. The same CSS rule produced a
102
+ * 1184x0 schema-builder canvas in production with 45 laid-out nodes inside it.
103
+ *
104
+ * This does NOT change layout. Constraining the wrapper would break the
105
+ * measurement it exists to take, and changing the autoHeight default is how
106
+ * page-builder 2.0.0 discarded every saved layout across 1,729 cards. It makes
107
+ * the silent case audible in development and leaves production untouched, which
108
+ * is the same trade @bsuite/theme 1.3.0 made for its missing-provider warning.
109
+ */
110
+ let warnedAboutCollapsedContent = false;
111
+ function warnOnceAboutCollapsedContent(el) {
112
+ if (warnedAboutCollapsedContent)
113
+ return;
114
+ /* THE SIGNATURE, not merely "height is zero".
115
+ *
116
+ * Requiring childElementCount > 0 is not enough, and jsdom is why: it has no
117
+ * layout engine, so getBoundingClientRect().height is 0 for EVERY element in
118
+ * every test. A warning keyed on that fires on every mounted card in the suite
119
+ * and means nothing — the recorded lesson is that jsdom cannot model the bug
120
+ * this exists to catch, so the check must not pretend it can.
121
+ *
122
+ * The real defect has a distinctive shape, measured in production: laid-out
123
+ * content inside a zero-height box — scrollHeight 1938 against clientHeight 0.
124
+ * Requiring scrollHeight > 0 selects exactly that, and jsdom reports 0 for
125
+ * both, so it stays silent there rather than crying wolf. */
126
+ if (el.scrollHeight <= 0)
127
+ return;
128
+ const isDev = typeof import.meta !== 'undefined' &&
129
+ import.meta.env?.DEV === true;
130
+ if (!isDev)
131
+ return;
132
+ warnedAboutCollapsedContent = true;
133
+ console.error('[@bsuite/page-builder] An autoHeight card measured 0px with content inside it, '
134
+ + 'so its height was left unchanged and the content is clipped by overflow-hidden — '
135
+ + 'the card looks empty rather than broken. The usual cause is a child sized with '
136
+ + 'height:100% (a chart, canvas or map): the measuring wrapper is intentionally '
137
+ + 'unconstrained so the observer can read intrinsic height, and a percentage height '
138
+ + 'against an auto-height parent resolves to auto. Give the child an intrinsic height '
139
+ + '(an aspect ratio, a px/rem height, or min-height), or pass autoHeight={false} on '
140
+ + 'this card so it renders into the constrained scrollable wrapper instead.');
141
+ }
87
142
  const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, isEditing, label, onHide, autoHeight, onAutoHeightChange, chrome = false, children: injectedChildren, className: injectedClassName, style: injectedStyle, ...rest }, ref) {
88
143
  // Auto-height measurement (blueprint amendment A1). `measureRef` wraps
89
144
  // `content` with NO height constraint of its own — its parent has
@@ -161,6 +216,8 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
161
216
  latestContentPxRef.current = contentPx;
162
217
  if (contentPx > 0)
163
218
  reportRows(contentPx);
219
+ else
220
+ warnOnceAboutCollapsedContent(el);
164
221
  }, [autoHeight, onAutoHeightChange, reportRows]);
165
222
  useEffect(() => {
166
223
  if (!autoHeight || !onAutoHeightChange)
@@ -214,7 +271,7 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
214
271
  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) => {
215
272
  event.stopPropagation();
216
273
  onHide(id);
217
- }, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })), _jsx("div", { "data-slot": "grid-item-surface", "data-chrome": chrome ? 'on' : 'off', className: chrome
274
+ }, title: `Hide ${label}`, "aria-label": `Hide ${label}`, children: _jsx(EyeOff, { className: "h-3 w-3", "aria-hidden": "true" }) })), _jsx("div", { "data-slot": "grid-item-surface", "data-chrome": chrome ? 'on' : 'off', "data-grid-slot-key": id, className: chrome
218
275
  ? // ONE radius token, read by the grid item AND available to any
219
276
  // app card that opts into the same surface. Two surfaces
220
277
  // reading the SAME var is what makes their corners align, and
@@ -223,9 +280,32 @@ const GridItem = React.memo(React.forwardRef(function GridItem({ id, content, is
223
280
  // `--radius-card` defaults to today's value (1.5rem =
224
281
  // rounded-3xl), so this is byte-identical until a tenant sets
225
282
  // one.
226
- 'h-full w-full rounded-[var(--radius-card,1.5rem)] transition-all flex flex-col bg-card border border-border shadow-sm dark:shadow-[var(--glow-card,none)]'
283
+ cn('w-full rounded-[var(--radius-card,1.5rem)] transition-all flex flex-col bg-card border border-border shadow-sm dark:shadow-[var(--glow-card,none)]',
284
+ // THE DOUBLED BOTTOM BORDER, AND WHY THE ARITHMETIC COULD NEVER FIX IT.
285
+ //
286
+ // computeAutoHeightRows uses Math.ceil to round content height up to
287
+ // whole rows, because react-grid-layout only accepts integer row units.
288
+ // So an autoHeight slot is up to (rowHeight + marginY - 1)px TALLER than
289
+ // its content — measured on /placements: 6.6px at 1440 and 768, 28.6px
290
+ // at 1024. That remainder is unavoidable; it is what "whole rows" means.
291
+ //
292
+ // With `h-full` the painted surface FILLS the remainder, so its bottom
293
+ // border is drawn below the content's own bottom edge. When the content
294
+ // also paints a Card — which ~90% of slots do — the user sees TWO
295
+ // bottom borders with a few px of card background between them.
296
+ //
297
+ // Raised as D-46, D-98 and D-136 ("Bottom border still double for cards
298
+ // which we have raised 100s of times now"), and 2.3.1 did not fix it
299
+ // because 2.3.1 changed the row ARITHMETIC. No arithmetic removes a ceil
300
+ // remainder. The fix has to be in the PAINT.
301
+ //
302
+ // So for autoHeight the surface HUGS its content and the remainder
303
+ // becomes transparent space below the card, which nobody can see. For a
304
+ // manually-resized slot h-full is still right: the user chose that
305
+ // height and the chrome should fill it.
306
+ autoHeight ? 'h-fit' : 'h-full')
227
307
  : // Chrome OFF: layout only. Identical box, no paint.
228
- 'h-full w-full transition-all flex flex-col', 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] }));
308
+ cn('w-full transition-all flex flex-col', autoHeight ? 'h-fit' : 'h-full'), style: { contain: 'layout style' }, children: _jsx("div", { className: cn('min-h-0', autoHeight ? 'flex-none overflow-hidden' : 'flex-1 overflow-auto'), children: autoHeight ? _jsx("div", { ref: measureRef, children: content }) : content }) })] }), injectedChildren] }));
229
309
  }));
230
310
  export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVersion, canEditPage, editorEventNames, preferenceAdapter, widgets, widgetMeta, className,
231
311
  // Resize default flipped 2026-05-12: rgl v2 dropped auto-handle rendering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",