@bsuite/page-builder 0.6.0 → 0.6.2

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,KAA6E,MAAM,OAAO,CAAC;AAGlG,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAKxC,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0QnE,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,qBAwpBrB"}
1
+ {"version":3,"file":"PageGridLayout.d.ts","sourceRoot":"","sources":["../src/PageGridLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6E,MAAM,OAAO,CAAC;AAGlG,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAKxC,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0QnE,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,qBAirBrB"}
@@ -276,12 +276,35 @@ isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, addEntityWidgetEvent
276
276
  .map((item) => {
277
277
  if (!item.autoHeight)
278
278
  return item;
279
+ // HEAL a persisted `isResizable: false`.
280
+ //
281
+ // 0.5.2 set this at the render layer, and it leaked out through
282
+ // `onLayoutChange` into the SAVED layout. Removing the override in
283
+ // 0.6.0 was therefore not enough: react-grid-layout resolves
284
+ //
285
+ // typeof l.isResizable === 'boolean' ? l.isResizable
286
+ // : !l.static && gridResizable
287
+ //
288
+ // so an explicit persisted `false` beats the grid default forever.
289
+ // Every user who ever loaded an affected page has it stored.
290
+ //
291
+ // Confirmed in a real signed-in browser 2026-07-31: the saved
292
+ // preference for /financial/invoicing/:id held
293
+ // `"isResizable":false` on all 6 cards; clearing it by hand did not
294
+ // help, because it was re-persisted on the next load.
295
+ //
296
+ // A per-item `false` on an autoHeight item cannot be a user choice —
297
+ // nothing in the UI sets it, and page-level opt-out uses the
298
+ // `isResizable` PROP, not per-item layout. So it can only have come
299
+ // from the 0.5.2 bug, and dropping it is safe.
300
+ const { isResizable: persisted, ...rest } = item;
301
+ const healed = persisted === false ? rest : item;
279
302
  const measuredRows = autoHeightRows[item.i];
280
303
  if (measuredRows === undefined)
281
- return item;
304
+ return healed;
282
305
  return {
283
- ...item,
284
- h: Math.max(item.h ?? 0, measuredRows),
306
+ ...healed,
307
+ h: Math.max(healed.h ?? 0, measuredRows),
285
308
  minH: measuredRows,
286
309
  };
287
310
  });
@@ -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,CA2cpD"}
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 heights out of a layout before it reaches the
194
- * preference adapter. react-grid-layout is rendered with `autoHeightRows`
195
- * merged over the saved layout (see `applyAutoHeightRows`), so the layouts
196
- * it echoes back through `onLayoutChange` carry the MEASURED `h` for
197
- * autoHeight items — persisting that verbatim would smuggle measured
198
- * heights into user_preferences through the drag/resize path and drop the
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). Restore `h`/`minH`/`autoHeight` from the un-merged base layout;
201
- * user-driven `x`/`y`/`w` (and `h` for non-autoHeight items) pass through
202
- * untouched. Reads the base via a ref so the trailing-rAF commit below
203
- * never closes over a stale snapshot.
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
- return { ...item, autoHeight: true, h: baseItem.h, minH: baseItem.minH };
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 — not even while editing. Measurements re-derive on
361
- * every mount; writing them to the preference adapter would be redundant
362
- * AND would turn mere viewing (e.g. Radix tab switches inside a card,
363
- * which unmount/remount panel content and fire the ResizeObserver) into
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.0",
3
+ "version": "0.6.2",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",