fix(board): restore container grid geometry (#6808)
This commit is contained in:
1 parent
3aac106156
commit
61d3fd5f08
7 files changed
+37
-54
No files matched your search
@@ -36,6 +36,11 @@ current Base layout.
|
||||
A **Container** groups apps, widgets, and nested Containers. It can have a label, collapse control, app-opening action,
|
||||
border color, and CSS classes.
|
||||
|
||||
Items can occupy the outermost rows and columns of a Container. The drop preview shows the position where the item
|
||||
will remain when released.
|
||||
Dragging an item onto another item of the same size swaps their positions. Displaced items snap directly to their
|
||||
previewed positions without a movement animation and stay there when you release the pointer.
|
||||
|
||||
A **rail** reserves one to three columns at the left or right of a non-Mobile layout. Rails stay fixed while the page
|
||||
scrolls and store their contents separately for each layout.
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
.board-grid-editor {
|
||||
position: absolute;
|
||||
inset: 0 auto auto 0;
|
||||
inset: 0 0 auto;
|
||||
margin: 0 auto;
|
||||
overflow: visible;
|
||||
border-radius: var(--mantine-radius-md);
|
||||
pointer-events: none;
|
||||
zoom: var(--board-grid-content-scale, 1);
|
||||
}
|
||||
|
||||
.board-grid-entry {
|
||||
@@ -20,14 +22,7 @@
|
||||
width: var(--board-grid-preview-width) !important;
|
||||
height: var(--board-grid-preview-height) !important;
|
||||
translate: var(--board-grid-preview-x) var(--board-grid-preview-y);
|
||||
will-change: translate;
|
||||
}
|
||||
|
||||
.board-grid-entry[data-grid-preview="true"]:not([data-dnd-active="true"]) {
|
||||
transition:
|
||||
translate 100ms ease,
|
||||
width 100ms ease,
|
||||
height 100ms ease;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.board-grid-entry[data-dnd-active="true"],
|
||||
@@ -109,8 +104,8 @@
|
||||
zoom: var(--board-grid-overlay-content-zoom, 1);
|
||||
}
|
||||
|
||||
body[data-board-grid-interacting] .board-grid-entry[data-dnd-active="true"],
|
||||
body[data-board-grid-interacting] .board-grid-entry[data-dnd-drag-source="true"],
|
||||
/* Displaced entries must snap with the preview too; animating their offsets can trail a new swap target. */
|
||||
body[data-board-grid-interacting] .board-grid-entry,
|
||||
body[data-board-grid-interacting] .board-grid-placeholder {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -415,7 +415,11 @@ export const BoardGridEditorProvider = ({ children }: PropsWithChildren) => {
|
||||
sectionId: grid.id,
|
||||
placements: grid.placements,
|
||||
}));
|
||||
commitSectionGrids(snapshots);
|
||||
const hasChanges = commitSectionGrids(snapshots);
|
||||
if (!hasChanges) {
|
||||
clearInteraction();
|
||||
return;
|
||||
}
|
||||
|
||||
const placement = state.grids
|
||||
.flatMap((grid) => grid.placements)
|
||||
@@ -429,7 +433,9 @@ export const BoardGridEditorProvider = ({ children }: PropsWithChildren) => {
|
||||
)} ${placement.h}`,
|
||||
);
|
||||
}
|
||||
clearInteraction();
|
||||
// Cache subscribers receive the committed board asynchronously. Keep the preview until
|
||||
// the board-change layout effect clears it, so displaced items never flash at their old positions.
|
||||
activeRef.current = null;
|
||||
},
|
||||
[announce, clearInteraction, commitSectionGrids, currentLayoutId, t],
|
||||
);
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
|
||||
.editorGrid {
|
||||
position: absolute;
|
||||
inset: 0 auto auto 0;
|
||||
}
|
||||
|
||||
.editorPortalHost {
|
||||
@@ -63,6 +62,11 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Containers provide their own inner item gap, so their card must keep the full grid aspect ratio. */
|
||||
.staticItem[data-type="section"] > .contentMount {
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.staticItem[data-type="item"] > .contentMount {
|
||||
inset: calc(10px * var(--board-canvas-inverse-scale, 1));
|
||||
}
|
||||
|
||||
@@ -143,40 +143,15 @@ export const SectionGrid = ({
|
||||
const isScrollableContainer = section.kind === "container" && section.options.scrollable;
|
||||
const viewportRowCount =
|
||||
viewportRowCountOverride ?? (isScrollableContainer ? Math.max(requestedRowCount, 1) : rowCount);
|
||||
// A container's own visible card is inset from its allocated board cell by the board's
|
||||
// standard per-item gap (see the base, non-container `.staticItem[data-type="item"] >
|
||||
// .contentMount` rule in section-grid.module.css - a container is placed as an "item" like
|
||||
// any widget, so it gets that same gap). A widget's content just fills whatever size its
|
||||
// card ends up being, but this SectionGrid instead computes its own fixed pixel size from
|
||||
// the same column/row counts used to allocate the *outer*, uninset cell - so without
|
||||
// subtracting that gap back out here, a container's inner grid renders larger than its own
|
||||
// card and visually spills past its right/bottom edges. 10 must match that CSS rule's inset.
|
||||
const effectiveCanvasScale = Number.isFinite(canvasScale) && canvasScale > 0 ? canvasScale : 1;
|
||||
const outerCardInset = section.kind === "container" ? (2 * 10) / effectiveCanvasScale : 0;
|
||||
// A collapsible container's toggle bar (see the `containerToggle` Button in
|
||||
// container-section.tsx) is an absolutely positioned overlay sitting on top of this grid.
|
||||
// Its height comes from a Mantine size prop, which - like spacing/font-size - is compensated
|
||||
// by --mantine-scale to render at a constant physical size regardless of board zoom (see
|
||||
// scaled-board-canvas.module.css). That makes it behave like the outerCardInset gap above,
|
||||
// not like the grid's own un-compensated logical-pixel math, so it needs the same
|
||||
// effectiveCanvasScale conversion - a plain, unconverted subtraction would under-reserve on
|
||||
// a zoomed-out board and leave the header overlapping the content. Without reserving it at
|
||||
// all, the header bar covers the first row of the container's content instead of sitting
|
||||
// above it. Only the vertical axis is affected - the toggle spans the full width already.
|
||||
// The collapsible label keeps a fixed physical height while the grid uses logical canvas pixels.
|
||||
// Convert that height so the label reserves the same space at every board scale.
|
||||
const collapsibleHeaderInset =
|
||||
section.kind === "container" && section.options.collapsible ? CONTAINER_HEADER_HEIGHT / effectiveCanvasScale : 0;
|
||||
const logicalWidth = getLogicalGridSize(columnCount) - outerCardInset;
|
||||
const viewportHeight = Math.max(1, getLogicalGridSize(viewportRowCount) - outerCardInset - collapsibleHeaderInset);
|
||||
// Items are positioned in a coordinate space sized to the *un-inset* column/row count
|
||||
// (fullGridWidth/Height below - see getLogicalItemStyle), but logicalWidth/Height above are
|
||||
// deliberately smaller by outerCardInset to match the container's actual visible card size.
|
||||
// Left alone, that mismatch means the items - not just the grid's own box - spill past the
|
||||
// card's right/bottom edge by exactly that inset. Scale the grid's content down by the same
|
||||
// ratio the board's own canvas uses for its whole-board zoom (see ScaledBoardCanvas), just one
|
||||
// level deeper, so it fits the actual card instead of clipping. --board-canvas-ui-scale is
|
||||
// corrected to compensate so icon/text sizing inside the container isn't affected.
|
||||
const fullGridWidth = getLogicalGridSize(columnCount);
|
||||
const fullGridHeight = getLogicalGridSize(rowCount);
|
||||
const logicalWidth = fullGridWidth;
|
||||
const viewportHeight = Math.max(1, getLogicalGridSize(viewportRowCount) - collapsibleHeaderInset);
|
||||
// For a scrollable container, rowCount covers *all* content rows, not just the visible card -
|
||||
// fullGridHeight grows right along with it, so a ratio built from it drifts toward 1 as content
|
||||
// grows regardless of the (fixed) inset, under-scaling relative to what the actually-visible
|
||||
@@ -184,16 +159,9 @@ export const SectionGrid = ({
|
||||
// describe the real visible card size in both cases (they equal the non-scrollable values when
|
||||
// rowCount and viewportRowCount are the same), so use those instead.
|
||||
const fullViewportHeight = getLogicalGridSize(viewportRowCount);
|
||||
// A uniform zoom is required (not a non-uniform transform scale(x, y)) - --board-canvas-ui-scale
|
||||
// is a single scalar that every icon/text/custom-CSS size compensation in the app multiplies
|
||||
// by, so a non-uniform stretch can never be captured by it correctly. That means one axis can
|
||||
// be left with a small amount of unfilled slack when width/height need different ratios to
|
||||
// exactly fit - centered below so it's even on both sides rather than left-aligned.
|
||||
// logicalWidth/viewportHeight can go non-positive for a narrow (e.g. single-column) container
|
||||
// on a heavily zoomed-out board, where outerCardInset (which grows as canvasScale shrinks) can
|
||||
// exceed the container's own un-inset size. Floor the scale well above 0 so it can never reach
|
||||
// zero or negative - combinedUiScale divides by this value, and this value is also used as
|
||||
// zoom directly, both of which break (Infinity/NaN, or invalid/collapsed content) otherwise.
|
||||
// A collapsible container reserves a fixed-height label above the square-cell grid. Keep the
|
||||
// nested content uniformly scaled within the remaining height so labels and icons retain their
|
||||
// proportions; ordinary containers use the original unscaled, edge-aligned grid geometry.
|
||||
const containerContentScale =
|
||||
section.kind === "container" && fullGridWidth > 0 && fullViewportHeight > 0
|
||||
? Math.max(0.01, Math.min(logicalWidth / fullGridWidth, viewportHeight / fullViewportHeight, 1))
|
||||
@@ -302,6 +270,7 @@ export const SectionGrid = ({
|
||||
height: `var(--board-grid-drag-height, ${viewportHeight}px)`,
|
||||
marginTop: collapsibleHeaderInset || undefined,
|
||||
"--board-item-radius": `var(--mantine-radius-${board.itemRadius})`,
|
||||
"--board-grid-content-scale": containerContentScale,
|
||||
} as CSSProperties
|
||||
}
|
||||
data-section-id={section.id}
|
||||
|
||||
@@ -33,8 +33,8 @@ export const useGridLayoutActions = () => {
|
||||
placements.map((placement) => [`${layoutId}:${placement.id}`, { layoutId, sectionId, placement }] as const),
|
||||
),
|
||||
);
|
||||
let hasChanges = false;
|
||||
updateBoard((previous) => {
|
||||
let hasChanges = false;
|
||||
const items = previous.items.map((item) => {
|
||||
const layouts = item.layouts.map((layout) => {
|
||||
const resolved = byId.get(`${layout.layoutId}:${item.id}`);
|
||||
@@ -97,6 +97,7 @@ export const useGridLayoutActions = () => {
|
||||
|
||||
return hasChanges ? { ...previous, items, sections } : previous;
|
||||
});
|
||||
return hasChanges;
|
||||
},
|
||||
[updateBoard],
|
||||
);
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
--border-color: rgb(
|
||||
from var(--container-border-color, var(--container-default-border-color)) r g b / var(--opacity, 1)
|
||||
);
|
||||
/* Keep the border visual without letting it shrink the square-cell grid's content box. */
|
||||
border-width: 0;
|
||||
box-shadow: inset 0 0 0 calc(1px * var(--board-canvas-inverse-scale, 1)) var(--border-color);
|
||||
}
|
||||
|
||||
.itemCard:global(.iframe-wrapper) {
|
||||
|
||||
Reference in new issue
Block a user