fix: Keep the notification popup open while its text is selected (#2706)

This commit is contained in:
Dermot Duffy
2026-08-22 21:29:04 -07:00
committed by GitHub
parent b2f3243f3e
commit 965f2b4c9d
10 changed files with 360 additions and 16 deletions
@@ -4,11 +4,13 @@ import { hasPopOutAnimationEnded } from '../../utils/animation.js';
import { dispatchDismissNotificationEvent } from '../../utils/notification.js';
// Manages the popup notification's modal interaction: dismiss on outside
// interaction or Escape, and emit the dismiss event once the pop-out animation
// finishes.
// interaction or Escape, hold focus while it is shown, and emit the dismiss
// event once the pop-out animation finishes.
export class NotificationPopupController implements ReactiveController {
private _host: ReactiveControllerHost & HTMLElement;
private _getNotificationElement: () => HTMLElement | null;
private _elementFocusedBeforePopup: Element | null = null;
private _hasTakenFocus = false;
constructor(
host: ReactiveControllerHost & HTMLElement,
@@ -20,6 +22,8 @@ export class NotificationPopupController implements ReactiveController {
}
public hostConnected(): void {
this._elementFocusedBeforePopup = document.activeElement;
window.addEventListener('click', this._handleOutsideInteraction);
window.addEventListener('focusin', this._handleOutsideInteraction);
@@ -29,10 +33,31 @@ export class NotificationPopupController implements ReactiveController {
window.addEventListener('keydown', this._handleKeyDown, { capture: true });
}
public hostUpdated(): void {
const notification = this._getNotificationElement();
if (notification && !this._hasTakenFocus) {
this._hasTakenFocus = true;
notification.focus();
}
}
public hostDisconnected(): void {
window.removeEventListener('click', this._handleOutsideInteraction);
window.removeEventListener('focusin', this._handleOutsideInteraction);
window.removeEventListener('keydown', this._handleKeyDown, { capture: true });
this._hasTakenFocus = false;
// Focus returns to whatever held it before the popup appeared, unless
// something else has taken focus since.
if (
this._elementFocusedBeforePopup instanceof HTMLElement &&
document.activeElement === document.body
) {
this._elementFocusedBeforePopup.focus();
}
this._elementFocusedBeforePopup = null;
}
public dismiss = (): void => {
@@ -50,6 +50,8 @@ export function renderControl(
return html`
<div
class="${classMap(classes)}"
role="button"
tabindex="0"
title=${control.tooltip ?? ''}
.actionHandler=${actionHandler({
hasHold: hasAction(control.actions?.hold_action),
+8 -2
View File
@@ -53,6 +53,7 @@ export class AdvancedCameraCardNotification extends LitElement {
<div class="backdrop" @click=${this._popupController.dismiss}></div>
<div
class="notification"
tabindex="-1"
${ref(this._refNotification)}
@animationend=${this._popupController.handleAnimationEnd}
>
@@ -70,11 +71,16 @@ export class AdvancedCameraCardNotification extends LitElement {
)}
</div>`
: ''}
<div class="close" @click=${this._popupController.dismiss}>
<button
class="close"
title=${localize('common.close')}
aria-label=${localize('common.close')}
@click=${this._popupController.dismiss}
>
<advanced-camera-card-icon
.icon=${{ icon: 'mdi:close' }}
></advanced-camera-card-icon>
</div>
</button>
<div class="details">
${heading ? renderDetail(heading, 'heading') : ''}
${renderNotificationBody(this.notification, context)}
+1
View File
@@ -6,6 +6,7 @@
"common": {
"advanced_camera_card": "Advanced Camera Card",
"advanced_camera_card_description": "An Advanced Camera Card",
"close": "Close",
"event": "Event",
"folder": "Folder",
"in_progress": "In progress...",
+2
View File
@@ -113,6 +113,8 @@
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: var(--advanced-camera-card-button-border-radius);
background: color-mix(