diff --git a/.changeset/shell-measures-its-container.md b/.changeset/shell-measures-its-container.md new file mode 100644 index 0000000000..bf4554e8bc --- /dev/null +++ b/.changeset/shell-measures-its-container.md @@ -0,0 +1,30 @@ +--- +"@nextlyhq/adapter-drizzle": patch +"@nextlyhq/adapter-mysql": patch +"@nextlyhq/adapter-postgres": patch +"@nextlyhq/adapter-sqlite": patch +"@nextlyhq/admin": patch +"@nextlyhq/admin-css": patch +"@nextlyhq/blocks-engine": patch +"@nextlyhq/blocks-react": patch +"@nextlyhq/builder": patch +"@nextlyhq/eslint-config": patch +"@nextlyhq/module-specifiers": patch +"@nextlyhq/plugin-form-builder": patch +"@nextlyhq/plugin-page-builder": patch +"@nextlyhq/plugin-sdk": patch +"@nextlyhq/plugin-seo": patch +"@nextlyhq/prettier-config": patch +"@nextlyhq/storage-s3": patch +"@nextlyhq/storage-uploadthing": patch +"@nextlyhq/storage-vercel-blob": patch +"@nextlyhq/telemetry": patch +"@nextlyhq/tsconfig": patch +"@nextlyhq/ui": patch +"create-nextly-app": patch +"nextly": patch +--- + +The page editor now decides whether it fits by measuring the space it was actually given, rather than the size of the browser window. Embedded as a field inside a form on a wide screen, it used to conclude it had room and squeeze the rail, panel and canvas below the widths they need; it now shows the "needs a wider screen" message in that case, and goes back to the full layout as soon as the space grows again. + +The media picker also no longer floats over that message. It opens in a layer outside the editor, so hiding the editor left an open picker visible and clickable on top of the notice saying the editor was unavailable. diff --git a/apps/playground/src/app/builder-shell/harness.tsx b/apps/playground/src/app/builder-shell/harness.tsx index 8a404767dd..48940ca4c9 100644 --- a/apps/playground/src/app/builder-shell/harness.tsx +++ b/apps/playground/src/app/builder-shell/harness.tsx @@ -26,9 +26,27 @@ import "@nextlyhq/builder/styles.css"; * Slot contents are deliberately inert. Anything interactive here would be * testing the harness rather than the shell. */ -export function BuilderShellHarness() { +export function BuilderShellHarness({ + containerWidth, +}: { + /** + * Constrain the shell's container without touching the window. + * + * Omitted, the shell fills the viewport as before. Supplied, it gets a box + * narrower than the window — the arrangement that separates measuring the + * container from measuring the viewport, which no viewport-sized harness + * can distinguish. + */ + containerWidth?: number; +}) { return ( -
narrow canvas
+wide canvas
+the caller's canvas
@@ -594,7 +725,7 @@ describe("a viewport too narrow for the shell", () => { // The positive control for the test above. Keeping the slots mounted is // only correct while they are also unreachable — a tab order that runs // through an editor nobody can see is worse than the unmount was. - stubViewport(false); + stubContainerFits(false); render(the caller's canvas
@@ -609,13 +740,152 @@ describe("a viewport too narrow for the shell", () => { expect(screen.queryByRole("region", { name: "Canvas" })).toBeNull(); }); + it("decides at the boundary the exported predicate defines", () => { + // ONE implementation of "does this fit". The hook used to repeat + // `width >= MIN_SHELL_WIDTH` inline, which is a second answer to a question + // `shell-state` already exports and tests — and the two would first diverge + // at exactly the boundary those tests pin. + // + // Asserted AT the boundary rather than well inside it: the shell must agree + // with `fitsFullShell(MIN_SHELL_WIDTH) === true`, so a hook that had drifted + // to a strict `>` shows the notice here while the helper's own tests stay + // green. + observedWidth = MIN_SHELL_WIDTH; + expect(fitsFullShell(observedWidth)).toBe(true); + render( +canvas
+canvas
+canvas
+canvas
+canvas
+the caller's canvas
+the caller's canvas
@@ -679,7 +949,7 @@ describe("a viewport too narrow for the shell", () => { // The className is how the host places the shell in its own layout — a grid // area, a height, a border. Dropping it on this path let the notice escape // the box the shell had been given. - stubViewport(false); + stubContainerFits(false); const { container } = render(- The page editor needs a wider screen -
- {/* - * The escape sentence and the button below it are ONE UNIT with the - * handler: all three present, or all three absent. - * - * Keeping the copy while dropping the control would instruct the - * author to go somewhere and then offer nothing to get there — and - * keeping a button with no handler is worse, because it looks - * operable. A host that supplies no `onExit` has no destination to - * offer, and an embedded one needs none: the author is already - * inside the surrounding form and can scroll to the rest of it. - */} - {props.onExit ? ( - <> + {/* + * THE measured element, and the only one. + * + * Always rendered, whichever branch is showing, so it always has a box + * to measure — which is what makes the decision reversible. Observing a + * branch instead made the answer depend on that branch's own padding + * and, in the editor's case, on a wrapper that is `display: contents` + * when visible and `hidden` when narrow and therefore reports 0. + * + * It carries the caller's `className` and nothing else of its own, so + * its CONTENT box is the space inside whatever padding, border or grid + * area the host gave the shell — which is precisely the space its + * children have to lay out in. Both branches are sized from it rather + * than from the host directly, so neither can disagree with the + * measurement. + */} ++ The page editor needs a wider screen +
+ {/* + * The escape sentence and the button below it are ONE UNIT with the + * handler: all three present, or all three absent. + * + * Keeping the copy while dropping the control would instruct the + * author to go somewhere and then offer nothing to get there — and + * keeping a button with no handler is worse, because it looks + * operable. A host that supplies no `onExit` has no destination to + * offer, and an embedded one needs none: the author is already + * inside the surrounding form and can scroll to the rest of it. + */} + {props.onExit ? ( + <> ++ Editing a layout needs at least {MIN_SHELL_WIDTH}px. On a + smaller screen you can still edit this page's content + from the admin. +
+ + > + ) : (- Editing a layout needs at least {MIN_SHELL_WIDTH}px. On a - smaller screen you can still edit this page's content - from the admin. + Editing a layout needs at least {MIN_SHELL_WIDTH}px.
- - > - ) : ( -- Editing a layout needs at least {MIN_SHELL_WIDTH}px. -
- )} -