Skip to content

fix(popover): DLT-3601 scope modal z-index bump to the anchor's own modal - #1410

Draft
Brad Paugh (braddialpad) wants to merge 1 commit into
nextfrom
fix/popover-modal-zindex-scope
Draft

fix(popover): DLT-3601 scope modal z-index bump to the anchor's own modal#1410
Brad Paugh (braddialpad) wants to merge 1 commit into
nextfrom
fix/popover-modal-zindex-scope

Conversation

@braddialpad

@braddialpad Brad Paugh (braddialpad) commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🛠️ Type Of Change

  • Fix

📖 Jira Ticket

https://dialpad.atlassian.net/browse/DLT-3601

📖 Description

calculateAnchorZindex() promoted a popover from the popover layer (--zi-popover, 300) to the modal-element layer (--zi-modal-element, 650) whenever a document-wide querySelector found any open modal:

returnFirstEl(this.$el).getRootNode()
  .querySelector('.d-modal[aria-hidden="false"], …, .d-modal[open], …')

It now tests the popover's own anchor with closest() instead, so only popovers that actually live inside an open modal are promoted. The existing drawer special case is preserved.

Also extracts the repeated 300 / 650 literals and the open-modal selector into named constants (POPOVER_Z_INDEX, POPOVER_MODAL_ELEMENT_Z_INDEX, OPEN_MODAL_SELECTOR) in PopoverConstants.js, and uses them at the two modal ? 650 : … call sites.

💡 Context

A document-wide search cannot distinguish a popover genuinely nested inside the open modal — which should outrank it — from unrelated global chrome that merely happens to be open at the same time. The result was that a global menu rendered bright and undimmed on top of the dimming overlay of a modal it had nothing to do with.

Worth noting for reviewers: this is a plain z-index bug, not a native <dialog> top-layer bug. The modal in the reported case is opened with show() (DtModal's modal: false default), so it never enters the browser top layer and nothing is inert — confirmed by the popover remaining clickable in that state.

I verified the underlying platform behavior separately with a Playwright spike across Chromium 148, Firefox 150 and WebKit 26.4. Two results are relevant to how we approach this area going forward, though neither is addressed here:

  • showModal() inerts everything outside the dialog's subtree, including top-layer popovers. A popover shown after showModal() paints bright above the backdrop but is not focusable or clickable — so re-promotion cannot rescue an out-of-subtree overlay.
  • With the modal: false default there is no keyboard focus containment. Modal.vue has only focusFirstTabbable for initial placement and fix(modal): NO-JIRA stop inerting the background when the dialog is not modal #1397 removed the background-inert machinery, so Tab escapes into dimmed, mouse-unreachable background content (reproduced in Chromium).

Follow-ups, deliberately out of scope

  • Product decision: an unrelated popover now renders dimmed behind the overlay. Closing it outright when a modal opens may be the better UX.
  • The focus-containment gap above deserves its own ticket.

📝 Checklist

For all PRs:

  • I have ensured no private Dialpad links or info are in the code or pull request description (Dialtone is a public repo!).
  • I have reviewed my changes.
  • I have considered the performance impact of my change.

For all Vue changes:

  • I have added / updated unit tests.

New Z-index Tests block covers four cases: no modal open → 300; anchor inside an open modal → 650; unrelated modal open elsewhere → 300 (the regression guard); anchor inside a drawer → 650.

DtDropdown inherits the fix through DtPopover; no dropdown-level tests were added. No API change, so no Combinator/docs artifacts were regenerated.

Full suite: 3271 passed, 2 skipped. Lint: 0 errors.

…odal

calculateAnchorZindex promoted a popover from the popover layer (300) to
the modal-element layer (650) whenever a document-wide querySelector found
any open modal. That cannot distinguish a popover nested inside the open
modal, which should outrank it, from unrelated global chrome such as the
profile menu that merely happens to be open at the same time. The latter
ended up painting above the dimming overlay of a modal it has nothing to
do with.

Test the popover's own anchor with closest() instead, so only popovers
that actually live inside an open modal are promoted. The drawer special
case is preserved.

Also extract the repeated 300/650 literals and the open-modal selector
into named constants in PopoverConstants.js.
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Please add either the visual-test-ready or no-visual-test label to this PR depending on whether you want to run visual tests or not.
It is recommended to run visual tests if your PR changes any UI. ‼️

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✔️ Deploy previews ready!
😎 Dialtone documentation preview: https://dialtone.dialpad.com/deploy-previews/pr-1410/
😎 Dialtone-vue preview: https://dialtone.dialpad.com/vue/deploy-previews/pr-1410/

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.

1 participant