Skip to content

fix(bulma-ui): make auto leave room for an in-place picker panel's themed offset - #915

Merged
allxsmith merged 3 commits into
mainfrom
fix/904-inplace-auto-offset
Oct 7, 2026
Merged

allxsmith merged 3 commits into
mainfrom
fix/904-inplace-auto-offset

Conversation

@allxsmith

Copy link
Copy Markdown
Owner

An in-place DateInput, TimeInput or DateTimeInput panel carried its gap to the input in its top or bottom inset. The positioning hook can't read that, so position="auto" budgeted its own default gap below the input, whatever --bulma-picker-popover-offset was set to. With a larger themed offset, auto could choose a bottom corner where the panel then ran past the bottom of the viewport. A portaled panel didn't have this problem, because its gap has been a margin since #886.

This moves the in-place corners' gap into a margin on the side facing the input, the way Popover and the portaled panel already do it. The hook then reads the gap off the panel on both paths, and PickerPopover stops adding an estimate of its own. Each corner's inset and its new margin add up to the inset it had, so with the default offset an in-place panel lands exactly where it did. TimeInput's bottom sheet on a narrow screen resets both margins: otherwise a top corner's gap would lift the sheet off the bottom, and auto would leave room for a gap the sheet doesn't have.

The styles test proves the placement is unchanged. It now ranks margins in the cascade along with insets, across every published stylesheet including the prefixed flavors, at a phone and a desktop width. It checks that each in-place corner's edge is the offset past the input, that a portaled panel keeps its margins, and that the TimeInput sheet sits where it did with no margin. A new test resolves the offset the way a browser would and shows auto choosing a top corner once a larger offset no longer fits below.

The other way to close this was to measure the gap from the panel's resolved inset, picking the inset by corner. That leaves the stylesheet alone but depends on the containing block's size and needs a special case for the fixed TimeInput sheet. The margin approach matches the existing pattern, and the cascade test can prove it in full. One consequence: a consumer stylesheet that overrode an in-place corner's top or bottom to set its own gap now gets the offset margin on top of it. The supported way to theme the gap is the variable, which behaves as before.

pnpm all passes locally.

Fixes #904

…emed offset

An in-place DateInput, TimeInput or DateTimeInput panel carried its gap in its `top` or
`bottom`, which the positioning hook cannot read, so `position="auto"` budgeted the hook's
default 4px below the input whatever `--bulma-picker-popover-offset` was set to. With a larger
offset it could pick a bottom corner the panel then ran past the viewport from.

The in-place corners now put the gap in a margin on the side facing the input, as Popover and
the portaled panel do, and the hook reads it off the panel on both paths. The inset and the
margin add up to the inset each corner had, so the panel lands where it did. TimeInput's sheet
on a phone drops the margin, so a top corner's gap does not lift it off the bottom and `auto`
reads no gap for it on any corner.

Fixes #904
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 50 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: allxsmith/bestax/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e7aa6b3b-541c-422c-93db-c73791ff35f6
📥 Commits

Reviewing files that changed from the base of the PR and between 2ce64e1 and 7a35a85.

📒 Files selected for processing (7)
  • bulma-ui/src/form/DateInput.stories.tsx
  • bulma-ui/src/form/__tests__/PickerPopover.styles.test.tsx
  • bulma-ui/src/form/__tests__/PickerPopover.test.tsx
  • bulma-ui/src/form/_pickerInternals/PickerPopover.tsx
  • bulma-ui/src/helpers/useAnchoredPosition.ts
  • bulma-ui/src/scss/form/_picker-popover.scss
  • bulma-ui/src/scss/form/_timeinput.scss
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://eb734426.bestax.pages.dev

Comment thread bulma-ui/src/scss/form/_picker-popover.scss
Comment thread bulma-ui/src/form/__tests__/PickerPopover.styles.test.tsx Outdated
Comment thread bulma-ui/src/form/_pickerInternals/PickerPopover.tsx

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 0 blocking · 3 advisory

# Severity Area Finding Location
1 🔵 Advisory Coverage The inset→margin move is asserted only symbolically in jsdom; the repo's one browser-level placement check covers the portaled path only bulma-ui/src/scss/form/_picker-popover.scss:48
2 🔵 Advisory Coverage The new margin guard covers only the block axis — margin-left/margin-right/margin-inline* are in neither PLACEMENT nor isIndirect, so edges()'s horizontal lookup is dead and an inline-axis margin would move the panel undetected bulma-ui/src/form/__tests__/PickerPopover.styles.test.tsx:307
3 🔵 Advisory API With both call sites now passing offset: 0, the hook's offset = 4 default is reachable only from its own unit test bulma-ui/src/form/_pickerInternals/PickerPopover.tsx:85

Overall: The change is sound and lands the fix the issue asked for. I verified the failure shape against #904, then verified the fix empirically: the two new tests both discriminate — pre-PR, offset: appendToBody ? 0 : undefined makes the in-place gap 4 + marginGap, so PickerPopover.test.tsx's 680 + 300 + 20 = 1000 case becomes 1004 and resolves top-left instead of bottom-left, and the styles test's 20px/19px pair fails on the pre-PR stylesheet too (gap read as 4, so both rows resolve bottom-left). Full suite: 158 files, 6065 tests, green; typecheck:tests and format:check clean; check:conformance clean apart from version-regression, which is the shallow-clone artefact of this checkout and not the PR. The riskiest part is the one thing no test here can observe — jsdom does no layout, so "lands exactly where it did" rests on the abs-positioned inset/margin equation rather than on a measurement; that risk is small because _popover.scss:76-105 has shipped the identical shape for a while, with the same offset: 0 and the same margin-bottom: 0 in its portal block, so this PR converges PickerPopover onto a pattern already in production. Look at finding 1 first.

Residual risk:

  • auto can still push an in-place panel past the top of the viewport. Open, but pre-existing and untouched: resolveAuto tests fitsBelow only and returns a top corner unconditionally, with no height check above. The geometry above the input is unchanged by this PR (bottom: calc(100% + gap) and bottom: 100% + margin-bottom: gap put the border-box bottom edge in the same place), so a large themed offset overflows the top exactly as much as before.
  • marginGap takes max(abs(marginTop), abs(marginBottom)), so a consumer className adding an unrelated vertical margin inflates the gap auto budgets, on the in-place path now as well as the portaled one. Refuted as a new defect: no published stylesheet sets both margins non-zero on the panel — the cascade test now ranks both and checks every flavor, at both widths, and it passes.
  • The TimeInput phone sheet reading no gap. Refuted: the reset is load-bearing and tested. The sheet rule is :not(.is-portal):has(.timeinput-panel) at three classes, so it beats the two-class corner rules; remove margin-top: 0/margin-bottom: 0 and the "a sheet on a phone" case reads var(--bulma-picker-popover-offset) and fails. The corner auto picks for the sheet is inert — top: auto plus bottom: 1rem and left/right: 1rem override every corner's insets in all published sheets.
  • DateTimeInput tripping the sheet rule. Refuted: timeinput-panel is rendered only by TimeInputBase; DateTimeInputBase composes TimeWheels directly, so :has() never matches and the phone-width in-place corner cases cover it.
  • A browser without :has() support drops the sheet rule whole, leaving an in-place TimeInput as a normal corner panel with its margins intact. Self-consistent — marginGap then reads the gap that is really there — and pre-existing.

🏄 Clean little swap, dude — the gap paddled off the inset and onto the margin, which is where Popover's been riding it all along, and the cascade test now watches both. Nothing gnarly in the lineup; good to go.

…margin side

The cascade test can only compare declarations, so a DateInput story now opens an in-place panel
from each corner and measures it against the input in a real browser, sharing the play check the
portaled story already ran. The test runner runs it on every PR.

The published-sheet check ranked only the vertical margins, so a later rule adding a horizontal
or inline margin to the panel would have moved it off its corner with nothing failing. It now
ranks every margin side and refuses the `margin-inline` forms too.
…offset

Popover and the pickers' popover both pass `offset: 0` and take their gap from a margin read
through `styledGap`, so the hook's default is reached only by its own test. Its TSDoc now says
so, so that a new panel built on the hook does the same rather than inherit a gap no stylesheet
sees. The default itself is unchanged.
@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: verify

Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 7a35a85.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://dfa14db1.bestax.pages.dev

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review (verify) — 3 resolved · 0 open

# Severity Area Thread Outcome
1 🔵 Advisory Coverage Inset→margin move never checked by a layout engine ✅ Verified fixed · resolved
2 🔵 Advisory Coverage Margin guard covered only the block axis; edges() implied otherwise ✅ Verified fixed · resolved
3 🔵 Advisory API useAnchoredPosition's offset default reachable only from its own test ✅ Verified fixed · resolved

What I checked

  • #1 — DateInput.stories.tsx now exports Positions, rendering all four corners in place (no appendToBody) through the shared playCorners, which asserts box.top === anchor.bottom + gap for the bottom corners and box.bottom === anchor.top - gap for the top ones, with gap read from the computed --bulma-picker-popover-offset. anchor is inputs[i].parentElement, i.e. the *-container that _picker-popover.scss:148 makes position: relative — the exact containing block the 100% insets resolve against, so the assertion really does measure the inset-plus-margin sum. .github/workflows/ci.yml:140 runs test-storybook:ci on every PR, and typecheck:tests passes on the new story.
  • #2 — INITIAL gained margin-right/margin-left, so (via PLACEMENT = Object.keys(INITIAL)) they are collected by placementRules, cascaded in placement(), and returned — edges()'s horizontal lookups are no longer dead. isIndirect now matches margin-inline*, so the hard assertion inside placement() refuses those forms. jest src/form/__tests__/PickerPopover.styles.test.tsx → 360 passed.
  • #3 — useAnchoredPosition.ts:34-41 documents that both shipped panels pass 0 and take the gap from a margin via styledGap; verified true against Popover.tsx:242-243 and PickerPopover.tsx:85-86. The default is unchanged, as the finding allowed.

Overall: This pass settled threads and reviewed no commits — all three open advisories were answered as fixed, verified against the current code, and resolved; none were blocking to begin with. The one thing worth a human's glance is that the browser-level Positions play test could not be executed here (playwright install chromium has no network in this sandbox — ECONNREFUSED cdn.playwright.dev) and CI's Build and Test was still in_progress on 7a35a858, so confirm that job goes green. The assertions themselves are the right ones.

Residual risk:

  • The browser run is unobserved — open. Reading the code says Positions fails if the top corners' margin-bottom is dropped (off(box.bottom, anchor.top - gap) becomes 4), but only CI's storybook smoke step proves the real layout engine agrees, and it had not finished on the head commit.
  • Inline-axis drift on .picker-popover — refuted. A margin-left/margin-right now reaches both the edges() comparison and the two literal '0' expectations; margin-inline* trips the isIndirect assertion in placement().
  • A future hook caller inheriting the 4px default — open, documented rather than enforced. The TSDoc is guidance, not a gate, which is the trade-off thread 3 accepted.

🏄 Three mellow advisories, three clean answers — the author paddled back out and caught every one of 'em. Only thing left is waiting on the CI set to roll in; after that this one glides all the way to the beach.

@allxsmith allxsmith added the needs-human-review Loop converged (or contested): awaiting owner review + manual merge label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📸 Story screenshots at handoff — 7a35a85

12 stories (light + dark) from this PR's changed files under bulma-ui/src/ (workflow run · artifact story-screenshots-pr-915, 30-day retention).

form-dateinput--default · Form/DateInput · Default
Light Dark
form-dateinput--default light form-dateinput--default dark
form-dateinput--controlled · Form/DateInput · Controlled
Light Dark
form-dateinput--controlled light form-dateinput--controlled dark
form-dateinput--sizes · Form/DateInput · Sizes
Light Dark
form-dateinput--sizes light form-dateinput--sizes dark
form-dateinput--colors · Form/DateInput · Colors
Light Dark
form-dateinput--colors light form-dateinput--colors dark
form-dateinput--inline-colors · Form/DateInput · Inline Colors
Light Dark
form-dateinput--inline-colors light form-dateinput--inline-colors dark
form-dateinput--states · Form/DateInput · States
Light Dark
form-dateinput--states light form-dateinput--states dark
form-timeinput--default · Form/TimeInput · Default
Light Dark
form-timeinput--default light form-timeinput--default dark
form-timeinput--controlled · Form/TimeInput · Controlled
Light Dark
form-timeinput--controlled light form-timeinput--controlled dark
form-timeinput--sizes · Form/TimeInput · Sizes
Light Dark
form-timeinput--sizes light form-timeinput--sizes dark
form-timeinput--format-12-h · Form/TimeInput · Format 12 H
Light Dark
form-timeinput--format-12-h light form-timeinput--format-12-h dark
form-timeinput--with-seconds · Form/TimeInput · With Seconds
Light Dark
form-timeinput--with-seconds light form-timeinput--with-seconds dark
form-timeinput--increment-steps · Form/TimeInput · Increment Steps
Light Dark
form-timeinput--increment-steps light form-timeinput--increment-steps dark
> ✂ 69 more matched stories skipped by the cap: `form-dateinput--inline`, `form-dateinput--min-max`, `form-dateinput--disabled-dates`, `form-dateinput--first-day-of-week`, `form-dateinput--locale`, `form-dateinput--custom-format`, `form-dateinput--formats`, `form-dateinput--custom-parse`, `form-dateinput--mobile-native`, `form-dateinput--horizontal-field`, … — full set in the artifact.

github-actions Bot added a commit that referenced this pull request Oct 6, 2026
@github-actions github-actions Bot added the review-converged Deep review converged: no blocking findings, all review threads resolved, checks green label Oct 6, 2026
@allxsmith
allxsmith merged commit 00d3e5c into main Oct 7, 2026
84 checks passed
@allxsmith
allxsmith deleted the fix/904-inplace-auto-offset branch October 7, 2026 05:42
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.26.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.2.13 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.14.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-review needs-human-review Loop converged (or contested): awaiting owner review + manual merge released review-converged Deep review converged: no blocking findings, all review threads resolved, checks green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An in-place picker panel's auto placement ignores the themed offset

1 participant