@bsuite/page-builder 1.0.3 → 1.0.5

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":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC,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,EAC5C,iBAAiB,GAClB,EAAE,wBAAwB,GAAG,uBAAuB,CAwnBpD"}
1
+ {"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AA6BD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,cAAc,EACd,WAAgB,EAChB,aAAiB,EACjB,WAAkB,EAClB,gBAA6C,EAC7C,iBAA4C,EAC5C,iBAAiB,GAClB,EAAE,wBAAwB,GAAG,uBAAuB,CAirBpD"}
@@ -55,6 +55,33 @@ export const PACKAGE_LAYOUT_EPOCH = 1000;
55
55
  * opts out with an explicit `autoHeight: false`.
56
56
  */
57
57
  const DEFAULT_ITEM_AUTO_HEIGHT = true;
58
+ /**
59
+ * Do two layout SETS describe the same arrangement, at every breakpoint?
60
+ *
61
+ * Compares only the five fields that place an item. Anything else react-grid-layout
62
+ * attaches (`moved`, `static`, `isDraggable`, measured heights) is noise here, and
63
+ * comparing it would make every reflow look like a change.
64
+ *
65
+ * EVERY breakpoint, not just `lg`. A gesture made while the canvas is at `md`
66
+ * leaves `lg` untouched, so an `lg`-only comparison reads that real gesture as a
67
+ * reflow and silently drops it. That is not hypothetical — it broke the D-75
68
+ * "folds a gesture made at md back onto lg" test the moment the comparison was
69
+ * introduced.
70
+ */
71
+ function sameArrangement(a, b) {
72
+ if (!a || !b)
73
+ return false;
74
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
75
+ const fingerprint = (items) => (items ?? [])
76
+ .map((i) => `${i.i}:${i.x}:${i.y}:${i.w}:${i.h}`)
77
+ .sort()
78
+ .join('|');
79
+ for (const k of keys) {
80
+ if (fingerprint(a[k]) !== fingerprint(b[k]))
81
+ return false;
82
+ }
83
+ return true;
84
+ }
58
85
  export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, layoutVersion = 1, canEditPage = true, editorEventNames = DEFAULT_EDITOR_EVENT_NAMES, preferenceAdapter = defaultPreferenceAdapter, defaultAutoHeight, }) {
59
86
  const effectiveLayoutVersion = layoutVersion + PACKAGE_LAYOUT_EPOCH;
60
87
  const containerRef = useRef(null);
@@ -184,13 +211,24 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
184
211
  }
185
212
  return resolved;
186
213
  }, [rawLayouts, baseCols, layoutCols, defaultAutoHeight]);
187
- useEffect(() => {
188
- if (baseCols !== layoutCols) {
189
- const rescaled = rescaleLayout(rawLayouts, baseCols, layoutCols);
190
- setSavedLayout(rescaled);
191
- setBaseCols(layoutCols);
192
- }
193
- }, [baseCols, layoutCols, rawLayouts, setBaseCols, setSavedLayout]);
214
+ /*
215
+ * THERE IS DELIBERATELY NO EFFECT HERE.
216
+ *
217
+ * This used to rescale the stored layout to the new column count and write it
218
+ * back, which made a column change DESTRUCTIVE and unrecoverable. Measured on
219
+ * `rescaleLayout` directly: an 11 -> 2 -> 11 round trip takes
220
+ *
221
+ * a(w4 @x0) b(w3 @x4) c(w4 @x7) / d(w6 @x0) e(w5 @x6)
222
+ *
223
+ * to five items of w6 stacked one per row at x0. Everything narrows to the
224
+ * one-column minimum on the way down, the row-packer puts one item per row,
225
+ * and coming back up cannot recover an arrangement that no longer exists.
226
+ * That is what flattened the operator's dashboard.
227
+ *
228
+ * `currentLayouts` above already derives the DISPLAY at `layoutCols` from the
229
+ * authored layout at `baseCols`. Leaving storage alone makes the column
230
+ * control a view, and the round trip exact.
231
+ */
194
232
  const activeCols = useMemo(() => ({ lg: layoutCols, md: layoutCols, sm: layoutCols, xs: layoutCols, xxs: layoutCols }), [layoutCols]);
195
233
  /**
196
234
  * The compactor handed to react-grid-layout — the SAME one in edit mode and
@@ -413,15 +451,45 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
413
451
  }
414
452
  return next;
415
453
  }, []);
454
+ /**
455
+ * Persist a layout the USER authored, and re-base the column count to the one
456
+ * they authored it at.
457
+ *
458
+ * Re-basing belongs here and ONLY here. A column-count change is a view
459
+ * operation and must not touch storage — see `handleColumnChange`. An edit is
460
+ * the one moment the stored arrangement and the stored column count are known
461
+ * to agree, so it is the one moment `baseCols` may move.
462
+ */
416
463
  const commitLayout = useCallback((layouts) => {
417
464
  const canonical = canonicaliseLayoutForPersist(layouts);
418
465
  if (!canonical)
419
466
  return;
420
467
  setSavedLayout(stripAutoHeightRows(canonical));
421
- }, [canonicaliseLayoutForPersist, setSavedLayout, stripAutoHeightRows]);
468
+ setBaseCols(layoutCols);
469
+ }, [canonicaliseLayoutForPersist, layoutCols, setBaseCols, setSavedLayout, stripAutoHeightRows]);
422
470
  const onLayoutChange = useCallback((_layout, layouts) => {
423
471
  if (!isEditing)
424
472
  return;
473
+ /*
474
+ * A COLUMN-COUNT CHANGE MAKES REACT-GRID-LAYOUT REFLOW AND EMIT HERE, and
475
+ * that emission is not a user gesture. Committing it copies the rescaled
476
+ * DISPLAY back over the authored layout — which is the data loss this
477
+ * whole path exists to prevent, arriving through the back door.
478
+ *
479
+ * Removing the writes from `handleColumnChange` was not enough, and the
480
+ * unit tests did not catch it because they call the handler directly with
481
+ * no grid attached. Driving the deployed app is what found it: the
482
+ * persisted widths still marched 2,2,2 -> 4,4,4 -> 1,1,1 with the
483
+ * handler already inert.
484
+ *
485
+ * A reflow emits exactly `currentLayouts`, because that is what was just
486
+ * rendered. Any real gesture differs from it — and a gesture that does
487
+ * NOT differ has nothing to commit anyway, so this cannot swallow one.
488
+ * Identified by comparison rather than by a flag, so it needs no event
489
+ * ordering to be correct.
490
+ */
491
+ if (sameArrangement(layouts, currentLayouts))
492
+ return;
425
493
  pendingLayoutRef.current = layouts;
426
494
  if (layoutCommitFrameRef.current !== null)
427
495
  return;
@@ -438,7 +506,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
438
506
  commitLayout(pending);
439
507
  }
440
508
  });
441
- }, [commitLayout, isEditing]);
509
+ }, [commitLayout, currentLayouts, isEditing]);
442
510
  useEffect(() => {
443
511
  return () => {
444
512
  if (layoutCommitFrameRef.current !== null &&
@@ -448,12 +516,18 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
448
516
  }
449
517
  };
450
518
  }, []);
519
+ /**
520
+ * Change how many columns the canvas is DISPLAYED at.
521
+ *
522
+ * This writes nothing to storage. `currentLayouts` rescales the authored
523
+ * layout for display on every render, so switching 11 -> 2 -> 11 returns the
524
+ * user to exactly what they authored. The previous version persisted the
525
+ * rescale, which destroyed the arrangement at the narrow end and offered no
526
+ * undo. Storage moves only when the user actually edits — see `commitLayout`.
527
+ */
451
528
  const handleColumnChange = useCallback((newCols) => {
452
- const rescaled = rescaleLayout(currentLayouts, layoutCols, newCols);
453
- setSavedLayout(rescaled);
454
529
  setLayoutCols(newCols);
455
- setBaseCols(newCols);
456
- }, [currentLayouts, layoutCols, setBaseCols, setLayoutCols, setSavedLayout]);
530
+ }, [setLayoutCols]);
457
531
  /**
458
532
  * Build the object handed to the preference adapter from a new `lg` array.
459
533
  *
@@ -471,6 +545,11 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
471
545
  }
472
546
  return next;
473
547
  }, [currentLayouts]);
548
+ /** Persist a bare `lg` item list from a mutator, re-basing the same way. */
549
+ const commitItems = useCallback((items) => {
550
+ setSavedLayout(layoutsWithLg(items));
551
+ setBaseCols(layoutCols);
552
+ }, [layoutCols, layoutsWithLg, setBaseCols, setSavedLayout]);
474
553
  const handleCompact = useCallback(() => {
475
554
  if (!currentLayouts.lg)
476
555
  return;
@@ -487,8 +566,8 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
487
566
  placed.push(compacted);
488
567
  return compacted;
489
568
  });
490
- startTransition(() => setSavedLayout(layoutsWithLg(result)));
491
- }, [currentLayouts, layoutsWithLg, setSavedLayout]);
569
+ startTransition(() => commitItems(result));
570
+ }, [currentLayouts, commitItems]);
492
571
  const handleReset = useCallback(() => {
493
572
  startTransition(() => {
494
573
  setSavedLayout(defaultLayouts);
@@ -507,7 +586,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
507
586
  minW: initialSize?.minW,
508
587
  minH: initialSize?.minH,
509
588
  };
510
- startTransition(() => setSavedLayout(layoutsWithLg([...(currentLayouts.lg ?? []), newItem])));
589
+ startTransition(() => commitItems([...(currentLayouts.lg ?? []), newItem]));
511
590
  }, [currentLayouts, layoutCols, layoutsWithLg, setSavedLayout]);
512
591
  const moveWidget = useCallback((widgetKey, direction) => {
513
592
  const items = [...(currentLayouts.lg ?? [])];
@@ -524,7 +603,7 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
524
603
  moved,
525
604
  ...withoutMoved.slice(targetIndex),
526
605
  ];
527
- startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
606
+ startTransition(() => commitItems(nextItems));
528
607
  }, [currentLayouts, layoutsWithLg, setSavedLayout]);
529
608
  const setWidgetLocked = useCallback((widgetKey, locked) => {
530
609
  const nextItems = (currentLayouts.lg ?? []).map((item) => {
@@ -537,11 +616,11 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
537
616
  isResizable: !locked,
538
617
  };
539
618
  });
540
- startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
619
+ startTransition(() => commitItems(nextItems));
541
620
  }, [currentLayouts, layoutsWithLg, setSavedLayout]);
542
621
  const removeWidget = useCallback((widgetKey) => {
543
622
  const nextItems = (currentLayouts.lg ?? []).filter((item) => item.i !== widgetKey);
544
- startTransition(() => setSavedLayout(layoutsWithLg(nextItems)));
623
+ startTransition(() => commitItems(nextItems));
545
624
  }, [currentLayouts, layoutsWithLg, setSavedLayout]);
546
625
  /**
547
626
  * Measured auto-height overrides (widgetKey -> rows). DERIVED, in-memory-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/page-builder",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Shared BSuite responsive page-builder grid and layout persistence primitives",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",