@bsuite/page-builder
v2.8.3
Published
Shared BSuite responsive page-builder grid and layout persistence primitives
Readme
@bsuite/page-builder
Shared BSuite responsive page-builder grid primitives.
Exports:
PageGridLayoutusePageGridLayoutrescaleLayoutcomputeAutoHeightRowsGridLayouts,GridLayoutItem,WidgetMeta, and related types
Consumer apps must depend on the published npm package, not workspace:* or
file: links, because each app deploys independently.
Measured auto-height (autoHeight, 0.5.0+)
Opt a widget into content-driven height by setting autoHeight: true on its
GridLayoutItem:
const defaultLayouts: GridLayouts = {
lg: [
// Fixed card — user resizes it manually, height persists.
{ i: 'summary', x: 0, y: 0, w: 6, h: 8 },
// Measured card — height tracks rendered content; `h` is only the
// initial seed used until the first measurement lands.
{ i: 'details', x: 6, y: 0, w: 6, h: 20, autoHeight: true },
],
};Behaviour and guarantees:
- A
ResizeObserveron an unconstrained measure wrapper reads the content's intrinsic height;computeAutoHeightRowsconverts px → rows using the samerowHeight/marginthe grid renders with, so the loop converges to a fixed point instead of oscillating. - Auto-height items render with
isResizable: falseautomatically — do not also wire a manual resize path for them. - Measured rows are derived, in-memory-only state: merged over the saved
layout at render time for every viewer, and never persisted. A
read-only viewer switching tabs inside a card produces zero preference
writes. User-driven
x/y/w(andhfor non-autoHeight items) persistence is unchanged. - Updates are rAF-batched (all cards settling in a frame apply as one state update) and suppressed mid-drag/mid-resize, then flushed on gesture end.
When adding autoHeight to an existing page's layout, bump that page's
layoutVersion so stale persisted layouts (without the flag) are reset.
Edit-mode chrome (2.7.1-next.0+)
PAGE_GRID_EDITING_EVENT reports committed changes from 2.7.1-next.1.
Its { pageKey, editing } payload and event name are unchanged. The matching
canvas DOM is updated before listeners run, including when Save & Exit uses
a React transition. Initial mounting, unchanged state and abandoned transitions
do not emit notifications. Consumers must match the page key and preserve
focus on surviving inputs when synchronizing their header controls.
From 2.7.1-next.5, a header Save & exit dispatches PAGE_GRID_SAVE_EVENT
with { pageKey }. The canvas waits for all preference adapters, keeps its
controls and error visible on failure, and emits the existing committed editing
notification on success. Headers wait for that notification before clearing
edit mode. Existing open/close events remain available for route navigation.
Selected depths expose separate resting, light-interaction and dark-interaction shadow variables. Light interaction adds neutral elevation; dark interaction adds the role-bound glow while retaining the selected depth. Explicit zero stays flat. Consumers must read all three variables; chrome-owning grid items already do. Default painted grid cards now use neutral elevation 2, with interaction feedback, matching the D2C card recovery.
From 2.7.1-next.6, app Cards import useCardPadding and
CardPaddingBoundary from @bsuite/page-builder/card-padding. Read the
inset before rendering the Card, apply a defined value to its painted surface,
and wrap its children with a boundary without a padding prop. Inner Cards then
keep their spacing; a nested grid establishes its own independent inset.
The grid supplies no child inset when it paints chrome itself. This replaces
consumer CSS @scope rules, which do not cover the supported Safari/Firefox
baseline. The subpath keeps a standalone Card import separate from grid code.
While the canvas editor is open, each grid item renders an in-flow strip
([data-slot="grid-item-editor-chrome"]) above the card content: the widget
name and a Hide control. The strip is not absolutely positioned over the
card heading. AutoHeight measures it with the content so the grid grows;
leaving edit mode removes it and restores the content-only measurement.
Do not restyle this in a consumer — it is the shared contract.
Layers: Move up / Move down. A move places a card immediately before (up) or
after (down) its nearest movable neighbour in the order the grid DRAWS: top to
bottom, then left to right, after react-grid-layout compacts the layout. It
changes saved x/y in authored units and nothing else: sizes, flags, hidden
cards, locked cards and every card that does not have to move keep their saved
values, and _grid_base_cols stays put. The Layers panel, the Move buttons and
the DOM / tab order all follow the drawn order. Lock, Remove, Add and Compact
Layout work on the same authored layout. Compact uses the react-grid-layout
compactor and keeps locked cards where they are.
Limits, stated rather than hidden:
- A locked card (
staticorisDraggable: false) never moves and a move steps over it.isResizable: falsealone is not a lock. - A card that is hidden or has no content is neither a partner nor an obstacle.
- Other cards move only when the exchange would overlap them; they are pushed down, never resized. If no exchange fits without an overlap or a locked card, the move is refused, nothing is written, and the disabled button says why.
- The single-column phone stack (
xxs, container under 480px) is displayed in the same order: it is re-stacked from the drawn order after measured heights, for display only. A phone layout the consumer supplied indefaultLayoutsis kept as given while the authored default object is in force. The version gate may persist that default on first visit; reading it back after a reload also counts as a stored layout. Once a stored layout is read, itslgreplaces it, the hook dropsxxson read, and the phone stack is derived and re-stacked again. The publicbuildResponsiveLayoutsderivation is unchanged. - Reordering re-orders the existing grid items in the DOM (input state is kept);
an embedded
iframeinside a card that changes place reloads, as it did with the previous Move.
The label carries min-w-0 truncate and a title with the full name. That is
load-bearing on a narrow card, not decoration: without it the label's automatic
minimum size is its min-content, and a long unbroken widget name pushes the
ml-auto hide button outside the card box. Measured both ways in
scripts/editor-chrome-geometry.mjs, which reproduces the defect with the two
classes removed at run time.
