From d73ac010aff86587fa640116a5bbaf0d682ff25e Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:51:21 +0100 Subject: [PATCH] feat(base): use negative margin to extend inline grid past parent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach (position: relative + explicit width + inset-inline-start) didn't physically grow the box in some flex contexts, so the grid stayed at the parent's width. Switch to negative margin-left / margin-right on the grid wrapper only — with width: auto, the rendered width becomes parent + |margin|, extending the box past the parent without any positioning hacks. The toolbar keeps its natural parent-constrained width (no extension) so it stays aligned with the page text above. Two CSS vars, --embed-extend-l / --embed-extend-r, are computed on mount + on ResizeObserver from the wrapper and the closest wider ancestor. --- .../features/base/components/base-table.tsx | 71 +++++++----------- .../components/base-embed/base-embed-view.tsx | 74 ++++++++++--------- 2 files changed, 68 insertions(+), 77 deletions(-) diff --git a/apps/client/src/features/base/components/base-table.tsx b/apps/client/src/features/base/components/base-table.tsx index 2bcdb529a..ab7f2508b 100644 --- a/apps/client/src/features/base/components/base-table.tsx +++ b/apps/client/src/features/base/components/base-table.tsx @@ -314,27 +314,16 @@ export function BaseTable({ pageId, embedded }: BaseTableProps) { if (!base) return null; - // When the table is embedded inline in a doc page, the parent - // measures the available area and exposes - // --embed-width / --embed-shift / --embed-pad. We extend the - // toolbar and grid sections out to the full available width via - // those vars, then re-pad the inner content so it visually aligns - // with page text. Sticky inset-inline-start keeps the toolbar - // pinned to the page-content edge during horizontal scroll. - const extendStyle = embedded + // When embedded inline in a doc page, the parent + // exposes --embed-extend-l / --embed-extend-r (positive px values). + // We pull the grid's left/right edges outward via negative margin — + // box-model: width: auto becomes parent_width + extend, so the box + // physically grows past its parent's bounds. The toolbar keeps the + // parent-constrained width so it stays in line with the page text. + const gridExtendStyle = embedded ? ({ - position: "relative", - width: "var(--embed-width, 100%)", - insetInlineStart: "var(--embed-shift, 0px)", - paddingInline: "var(--embed-pad, 0px)", - } as const) - : undefined; - - const stickyToolbarStyle = embedded - ? ({ - position: "sticky", - insetInlineStart: 0, - zIndex: 4, + marginLeft: "calc(-1 * var(--embed-extend-l, 0px))", + marginRight: "calc(-1 * var(--embed-extend-r, 0px))", } as const) : undefined; @@ -346,29 +335,25 @@ export function BaseTable({ pageId, embedded }: BaseTableProps) { height: embedded ? "auto" : "100%", }} > -
- -
- -
-
-
+ + +
baseWidth + 32) return cur; + const r = cur.getBoundingClientRect(); + if (r.width > rect.width + 32) { + targetLeft = r.left + LEFT_GUTTER; + break; + } cur = cur.parentElement; } - return null; -} + const extendLeft = Math.max(0, rect.left - targetLeft); -function applyExtension(wrapper: HTMLDivElement) { - const wrapperRect = wrapper.getBoundingClientRect(); - if (wrapperRect.width === 0) return; - const wider = findWiderAncestor(wrapper); - if (!wider) { - wrapper.style.setProperty("--embed-shift", "0px"); - wrapper.style.setProperty("--embed-width", "100%"); - wrapper.style.setProperty("--embed-pad", "0px"); - return; - } - const widerRect = wider.getBoundingClientRect(); - const targetLeft = widerRect.left + SIDE_GUTTER; - const targetWidth = widerRect.width - SIDE_GUTTER * 2; - const shift = targetLeft - wrapperRect.left; - wrapper.style.setProperty("--embed-shift", `${shift}px`); - wrapper.style.setProperty("--embed-width", `${targetWidth}px`); - // Re-pad inner content back to the original wrapper bounds so - // toolbar buttons / first column visually align with page text. - wrapper.style.setProperty("--embed-pad", `${-shift}px`); + wrapper.style.setProperty("--embed-extend-r", `${extendRight}px`); + wrapper.style.setProperty("--embed-extend-l", `${extendLeft}px`); } export function BaseEmbedView({ node }: NodeViewProps) { @@ -57,8 +54,17 @@ export function BaseEmbedView({ node }: NodeViewProps) { const ro = new ResizeObserver(update); ro.observe(wrapper); - const wider = findWiderAncestor(wrapper); - if (wider) ro.observe(wider); + // Also observe an ancestor so sidebar collapse / window changes + // propagate even when the wrapper itself doesn't resize. + let cur: HTMLElement | null = wrapper.parentElement; + while (cur && cur !== document.body) { + const r = cur.getBoundingClientRect(); + if (r.width > wrapper.getBoundingClientRect().width + 32) { + ro.observe(cur); + break; + } + cur = cur.parentElement; + } window.addEventListener("resize", update); return () => {