Skip to content

Replace portals with Popover API for layering#722

Open
NullVoxPopuli-ai-agent wants to merge 27 commits intouniversal-ember:mainfrom
NullVoxPopuli-ai-agent:popover-api
Open

Replace portals with Popover API for layering#722
NullVoxPopuli-ai-agent wants to merge 27 commits intouniversal-ember:mainfrom
NullVoxPopuli-ai-agent:popover-api

Conversation

@NullVoxPopuli-ai-agent
Copy link
Copy Markdown

Summary

  • Replaces <Portal> + <PortalTargets> with the native Popover API (popover="manual") for promoting Content elements to the browser's top layer
  • Removes @inline arg — no longer needed since the top layer replaces both inline and portaled modes
  • Consumers no longer need <PortalTargets /> in their templates
  • FloatingUI is unchanged — it still handles all positioning (flip, shift, offset, arrow)

This is a focused change: only the layering mechanism changes (portals → popover API). Positioning stays the same.

Closes #265

Test plan

  • First demo: popover renders above anchor, follows on scroll, not clipped by overflow
  • Second demo: settings dropdown and nested "view profile" popover both render in top layer, not clipped
  • Build passes
  • Verified in headless Chrome via Puppeteer

🤖 Generated with Claude Code

Content elements now use popover="manual" + showPopover() to promote
to the browser's top layer, replacing the Portal/PortalTargets approach.
This solves z-index and overflow clipping issues natively without
requiring consumers to add <PortalTargets /> to their templates.

- Remove Portal and TARGETS imports from popover component
- Remove @inline arg (no longer needed — top layer replaces both modes)
- Add showPopover modifier for popover="manual" lifecycle
- Update demos to remove <PortalTargets />
- FloatingUI still handles all positioning (flip, shift, offset, arrow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Project Preview URL
Docs https://72ba101c.ember-primitives.pages.dev

Logs

NullVoxPopuli and others added 14 commits April 10, 2026 18:49
- Reset overflow: visible on popover elements to prevent arrow clipping
- Add border: none to demo 1 CSS to override [popover] UA border

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nested popovers inside another popover no longer get promoted to the
top layer separately. Instead, they render as regular positioned
elements within the parent popover's top layer entry. This ensures
the child popover renders above the parent's content rather than
behind it (which happened when both competed for top layer position).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows how to use Popover for accessible disabled-state tooltips that
appear on hover/focus via CSS opacity transitions. The tooltip is
always in the DOM for screen readers but visually hidden until needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A tooltip is not a dialog — plain div is correct here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Content now always renders as <dialog popover="manual" role="none">.
This replaces the @as arg and element helper with a single element.

- <dialog> allows nested popovers to fall back to <dialog open> when
  opting out of the top layer (avoiding parent/child stacking issues)
- popover="manual" + showPopover() promotes to the browser's top layer,
  escaping overflow clipping without portals
- role="none" suppresses the dialog a11y announcement by default;
  consumers can override via ...attributes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Setup job was missing `pnpm i -f` before `pnpm build`, causing
ember-tsc binary symlinks to not be created. All other jobs already
had this step.

Also updated the disabled button tooltip demo to mention that users
can select and copy tooltip text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The docs-support build requires ember-tsc from @glint/ember-tsc.
In CI, pnpm may not create the bin symlink even after pnpm i -f.
This was masked by turbo caching on main — the docs-support build
was replayed from cache and never actually ran.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Header, Settings button, and site container use light-dark() for
  adaptive backgrounds, text colors, and borders
- Nested popover uses dark slate in dark mode instead of #eee
- Removed hardcoded color: black from header text
- Changed header filter: drop-shadow to box-shadow (avoids creating
  a containing block for position: fixed children)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the docs site's existing dark mode colors:
- Backgrounds: #0f172a (header/button), #1e293b (nested popover, borders)
- Text: #f1f5f9 (primary), #94a3b8 (body)
- Borders: #e2e8f0 light / #1e293b dark

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
<div> is simpler — no UA dialog styles (border, padding), no role="none"
needed, and nested popovers just work when popover attr is removed
(a div without popover is already visible, unlike dialog which needs
the open attribute).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allows consumers to customize the wrapper element tag, e.g.
@as="dialog" for focus trapping. Defaults to div.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NullVoxPopuli and others added 2 commits April 11, 2026 20:50
- Remove extra ember-tsc check from CI (pnpm i -f handles it)
- Fix emdash in docs intro
- Add "Migrating from <= v0.55" section covering:
  - PortalTargets removal
  - @inline removal
  - CSS considerations for [popover] UA defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a popover element has tabindex (e.g. Menu content), focus it
after entering the top layer. This partially fixes Menu focus
management but tabster's deloser still doesn't move focus to the
first menuitem correctly in the top layer.

5 Menu tests still failing - needs tabster/top-layer investigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- When @as="dialog" is used, the component automatically adds the
  open attribute when opting out of the top layer (nested case),
  so users don't need to specify it
- Use full import paths in migration diff
- Clarify that border/padding defaults come from the browser's UA
  stylesheet, not from ember-primitives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NullVoxPopuli and others added 4 commits April 11, 2026 22:57
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When popover content enters the top layer, focus doesn't
automatically move into it (unlike portaled content). Use
schedule('afterRender') to focus the first focusable child,
which integrates with Ember's runloop so test helpers like
await click() wait for focus to settle.

All Menu tests now pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace @ember/runloop schedule with requestAnimationFrame.
Check el.isConnected before focusing to guard against the
element being removed before the frame fires.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Popover should only handle layering, not focus. The focus-first-child
logic now lives in Menu's installContent modifier where it belongs.

Uses a microtask (Promise.resolve) to defer focus until after
showPopover() promotes the element to the top layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NullVoxPopuli and others added 5 commits April 11, 2026 23:57
The browser fires a toggle event natively when showPopover() opens
a popover. Listen for it to focus the first menu item, replacing
the microtask hack.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove PortalTargets import and usage from Menu demo
- Update intro text to reference Popover API instead of portals
- Add migration section matching Popover's migration guide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same ember-tsc binary issue as Setup job. The existing pnpm i -f
ran after pnpm build, too late.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <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.

use the new popover attribute/api for popovers

2 participants