Skip to content

Widgets: let a floating card grow to fit its content (opt-in) - #989

Open
juanlentino wants to merge 2 commits into
WordPress:trunkfrom
juanlentino:widgets-fit-content
Open

juanlentino wants to merge 2 commits into
WordPress:trunkfrom
juanlentino:widgets-fit-content

Conversation

@juanlentino

@juanlentino juanlentino commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #988

A floating widget keeps the height the user saved, and its body scrolls. When the content grows later (a status line that only appears when something is wrong, a button added below a list), the new rows sit below the fold of the card until the user drags it taller.

What changed

  • fit_content (PHP) / fitContent (JS) on the widget definition, off by default. Widgets that don't set it behave as before.
  • src/widgets/fit-content.ts (new): when the content needs more room than the user's height, the card grows to fit. It is capped at the smaller of maxHeight and the work-area bottom, keeping the same 20 px of air the drag clamp keeps. When the content shrinks, the card shrinks back, never below the user's height. Width and position never change.
  • The user's height and the grown height are kept apart. The grown height lives only in the card's inline style. Every geometry the frame hands to persistence carries the user's height: a drag, a liberate from the column, and a docked resize. Only a vertical pull (an n / s edge or a corner moved up or down) sets a new user height. Widening a grown card, or clicking a handle without moving, keeps the old one.
  • Refit doesn't run mid-drag or mid-resize, so it never fights the pointer. It is triggered by a ResizeObserver on the body's children (deferred one frame to avoid the "loop completed with undelivered notifications" error), a MutationObserver on the body's children and on the card's style and class (the layer re-placing or re-docking the card), and the work-area subscription.
  • src/widgets/frame.ts: wiring only. layer.ts and state.ts are untouched, so this doesn't overlap Widgets: reflow floating cards when the desktop resizes #914.
  • includes/registries/widgets.php, src/types.ts, src/widgets/types.ts, src/widgets/server-sync.ts: the new field through registration, the payload and the def.
  • Docs: docs/javascript-reference.md (optional fields, geometry), docs/examples/register-widget.md (size constraints table and notes), and the args list in includes/widgets/widget-starter.php.

Two things to know:

  • On a fit card, a resize shorter than the content grows back on release, because the content still needs the room. The docs say so.
  • A docked card with a saved height uses the same cap, measured from where it sits on screen. The column scrolls, so that cap is approximate there.

Moving the cards below a grown card out of its way is not part of this; it belongs with the reflow in #914.

Testing

  • npm run build, npm run lint, npm run typecheck, and npm run test:js all pass.
  • tests/vitest/widgets.test.ts: grows when the content overflows; capped at maxHeight; capped at the work-area bottom; shrinks back to the user's height and not below; a widget without fitContent is unchanged; a drag after growing saves the user's height, not the grown one; widening a grown card keeps the user's height; a resize sets a new user height that survives the content shrinking. Each was checked against a deliberately broken build (persisting the grown height, skipping the floor on resize, measuring without resetting to the floor, not observing the card's style, removing the cap) and fails there.
  • tests/vitest/widgets-lazy.test.ts: the server-sync mapping carries fitContent.
  • tests/phpunit/tests/registrationActions.php: fit_content defaults to false and casts to bool. Not run locally (no Docker for wp-env); CI will run it.
  • Manual, in Playground with this PR's build at 1440×1000: two floating test widgets, one with fit_content and one without, both saved at 160 px, each getting nine rows. The opted-in card grew to 362 px (160 plus the 202 px that had scrolled) with its last line in view; the other stayed at 160 and scrolled. Clearing the rows took the card back to 160. A drag of the grown card and a widen from its e edge both saved 160, not the drawn height. The server payload carried fitContent: true, and maxHeight: 0 mapped to no cap. In a 327×336 viewport the card did not grow, as expected: the work-area cap there equals the saved height.

Engineered with Claude as a pair programmer.

🤖 Generated with Claude Code

Open WordPress Playground Preview

juanlentino and others added 2 commits October 10, 2026 21:35
A floating card takes a fixed inline height from its saved geometry, and
the body scrolls whatever no longer fits, so a control a widget adds
after the size was saved (a Save row, a revealed field) is hidden below
the fold. A new opt-in def field, `fitContent` (PHP `fit_content`), lets
the card grow past the user's height when its content needs the room.

- The user's height is a floor: the saved geometry height, or the
  resized height of a docked card. The card grows from it up to
  `maxHeight` and the work-area bottom (the floating `s` handle's far
  edge), shrinks back as the content does, never below the floor, and
  never changes width or position.
- The grown height is never persisted. A drag saves the floor; a user
  resize sets a new floor and saves that.
- Content is watched through the body's children (ResizeObserver, one
  frame later) and a MutationObserver on the body and the card, which
  also catches the layer re-placing or re-docking the card. Measuring
  happens at the floor, since `scrollHeight` cannot report slack once
  the content fits.
- The logic lives in src/widgets/fit-content.ts; frame.ts only wires
  it, so layer.ts and state.ts are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widening a grown fit-content card with an e / w edge (or a corner
moved sideways) recorded the grown height as the user's floor and
persisted it. Only a vertical pull now sets the floor. Test added.
Also: the cap comment names the drag clamp's margin, the size-args
table and the starter args list name fit_content, and the fit tests
restore document.body.getBoundingClientRect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Widgets: let a floating card grow to fit content that arrives after its size was saved

1 participant