Skip to content

Commit c173d67

Browse files
authored
refactor!: update notification container to use native popover (#9819)
1 parent 0f4816f commit c173d67

File tree

9 files changed

+91
-187
lines changed

9 files changed

+91
-187
lines changed

packages/notification/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"@open-wc/dedupe-mixin": "^1.3.0",
4040
"@vaadin/component-base": "25.0.0-alpha8",
4141
"@vaadin/lit-renderer": "25.0.0-alpha8",
42-
"@vaadin/overlay": "25.0.0-alpha8",
4342
"@vaadin/vaadin-lumo-styles": "25.0.0-alpha8",
4443
"@vaadin/vaadin-themable-mixin": "25.0.0-alpha8",
4544
"lit": "^3.0.0"

packages/notification/src/styles/vaadin-notification-container-core-styles.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ import { css } from 'lit';
88
export const notificationContainerStyles = css`
99
:host {
1010
position: fixed;
11-
z-index: 1000;
1211
inset: 0;
1312
box-sizing: border-box;
1413
display: flex;
1514
flex-direction: column;
1615
align-items: stretch;
1716
pointer-events: none;
17+
18+
/* Override native [popover] user agent styles */
19+
width: auto;
20+
height: auto;
21+
border: none;
22+
padding: 0;
23+
background-color: transparent;
24+
overflow: visible;
1825
}
1926
2027
[region-group] {

packages/notification/src/vaadin-notification-mixin.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
import type { Constructor } from '@open-wc/dedupe-mixin';
77
import type { OverlayClassMixinClass } from '@vaadin/component-base/src/overlay-class-mixin.js';
8-
import type { OverlayStackMixinClass } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js';
98
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
109
import type { Notification } from './vaadin-notification.js';
1110

@@ -27,7 +26,7 @@ export type NotificationRenderer = (root: HTMLElement, notification: Notificatio
2726
*/
2827
export declare function NotificationContainerMixin<T extends Constructor<HTMLElement>>(
2928
base: T,
30-
): Constructor<NotificationContainerMixinClass> & Constructor<OverlayStackMixinClass> & T;
29+
): Constructor<NotificationContainerMixinClass> & T;
3130

3231
export declare class NotificationContainerMixinClass {
3332
/**

packages/notification/src/vaadin-notification-mixin.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import { render } from 'lit';
77
import { isTemplateResult } from 'lit/directive-helpers.js';
88
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
99
import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
10-
import { OverlayStackMixin } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js';
1110
import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
1211

1312
/**
1413
* A mixin providing common notification container functionality.
1514
*
1615
* @polymerMixin
17-
* @mixes OverlayStackMixin
1816
*/
1917
export const NotificationContainerMixin = (superClass) =>
20-
class extends OverlayStackMixin(superClass) {
18+
class extends superClass {
2119
static get properties() {
2220
return {
2321
/**
@@ -42,19 +40,36 @@ export const NotificationContainerMixin = (superClass) =>
4240
}
4341
}
4442

43+
/** @protected */
44+
firstUpdated(props) {
45+
super.firstUpdated(props);
46+
47+
this.popover = 'manual';
48+
}
49+
50+
/**
51+
* Move the notification container to the top of the stack.
52+
*/
53+
bringToFront() {
54+
if (this.matches(':popover-open')) {
55+
this.hidePopover();
56+
this.showPopover();
57+
}
58+
}
59+
4560
/** @private */
4661
_openedChanged(opened) {
4762
if (opened) {
4863
document.body.appendChild(this);
49-
this._appendAttachedInstance();
64+
this.showPopover();
5065
document.addEventListener('vaadin-overlay-close', this._boundVaadinOverlayClose);
5166
if (this._boundIosResizeListener) {
5267
this._detectIosNavbar();
5368
window.addEventListener('resize', this._boundIosResizeListener);
5469
}
5570
} else {
5671
document.body.removeChild(this);
57-
this._removeAttachedInstance();
72+
this.hidePopover();
5873
document.removeEventListener('vaadin-overlay-close', this._boundVaadinOverlayClose);
5974
if (this._boundIosResizeListener) {
6075
window.removeEventListener('resize', this._boundIosResizeListener);
@@ -360,7 +375,11 @@ export const NotificationMixin = (superClass) =>
360375
return;
361376
}
362377

363-
this._container.bringToFront();
378+
// Only call bringToFront if the container already has a child / was already opened.
379+
// Otherwise, just setting `opened` on the container will make it the topmost overlay.
380+
if (this._container.firstElementChild) {
381+
this._container.bringToFront();
382+
}
364383

365384
this._card.slot = this.position;
366385
if (this._container.firstElementChild && /top/u.test(this.position)) {

packages/notification/test/multiple.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('multiple notification', () => {
128128
});
129129

130130
describe('styles', () => {
131-
it('content should ignore pointer events', () => {
131+
it('container should ignore pointer events', () => {
132132
expect(getComputedStyle(container)['pointer-events']).to.equal('none');
133133
});
134134

packages/notification/test/notification.test.js

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,30 @@ import sinon from 'sinon';
44
import '../src/vaadin-notification.js';
55

66
describe('vaadin-notification', () => {
7-
let notification;
7+
let notification, container, showPopoverSpy, hidePopoverSpy;
88

99
beforeEach(async () => {
1010
notification = fixtureSync(`
1111
<vaadin-notification duration="20"></vaadin-notification>
1212
`);
13+
container = notification._container;
14+
showPopoverSpy = sinon.spy(container, 'showPopover');
15+
hidePopoverSpy = sinon.spy(container, 'hidePopover');
16+
1317
await nextFrame();
1418

1519
notification.renderer = (root) => {
1620
root.innerHTML = `Your work has been <strong>saved</strong>`;
1721
};
1822

19-
// Force sync card attaching and removal instead of waiting for the animation
20-
sinon.stub(notification, '_animatedAppendNotificationCard').callsFake(() => notification._appendNotificationCard());
21-
sinon.stub(notification, '_animatedRemoveNotificationCard').callsFake(() => notification._removeNotificationCard());
22-
2323
notification.open();
2424
});
2525

2626
afterEach(() => {
27-
// Close to stop all pending timers.
28-
notification.close();
27+
// Close all notifications to stop all pending timers.
28+
document.querySelectorAll('vaadin-notification').forEach((n) => {
29+
n.close();
30+
});
2931
// Delete singleton reference, so as it's created in next test
3032
delete notification.constructor._container;
3133
});
@@ -98,6 +100,42 @@ describe('vaadin-notification', () => {
98100
expect(isVisible(document.body.querySelector('vaadin-notification-container'))).to.be.true;
99101
});
100102

103+
it('should open as native popover', () => {
104+
expect(container.popover).to.equal('manual');
105+
expect(container.opened).to.be.true;
106+
expect(showPopoverSpy.calledOnce).to.be.true;
107+
});
108+
109+
it('should close native popover when notifications close', () => {
110+
notification.close();
111+
112+
expect(container.opened).to.be.false;
113+
expect(hidePopoverSpy.calledOnce).to.be.true;
114+
});
115+
116+
it('should reopen native popover when notifications open', () => {
117+
notification.close();
118+
notification._removeNotificationCard();
119+
notification.open();
120+
121+
expect(container.opened).to.be.true;
122+
expect(showPopoverSpy.calledTwice).to.be.true;
123+
});
124+
125+
it('should bring native popover to top of overlay stack when another notification opens', () => {
126+
showPopoverSpy.resetHistory();
127+
128+
const anotherNotification = fixtureSync(`
129+
<vaadin-notification duration="20"></vaadin-notification>
130+
`);
131+
anotherNotification.open();
132+
133+
// Bringing to front involves hiding and showing the popover again
134+
expect(hidePopoverSpy.calledOnce).to.be.true;
135+
expect(showPopoverSpy.calledOnce).to.be.true;
136+
expect(hidePopoverSpy.calledBefore(showPopoverSpy)).to.be.true;
137+
});
138+
101139
it('should cancel vaadin-overlay-close events when the source event occurred within the container', (done) => {
102140
listenOnce(document, 'click', (clickEvent) => {
103141
const overlayCloseEvent = new CustomEvent('vaadin-overlay-close', {
@@ -163,13 +201,6 @@ describe('vaadin-notification', () => {
163201
expect(container._detectIosNavbar.called).to.be.true;
164202
container._detectIosNavbar.restore();
165203
});
166-
167-
it('should bring to front on notification open ', () => {
168-
notification.opened = false;
169-
const spy = sinon.spy(container, 'bringToFront');
170-
notification.opened = true;
171-
expect(spy).to.be.calledOnce;
172-
});
173204
});
174205
});
175206

packages/overlay/src/vaadin-overlay-stack-mixin.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ const getAttachedInstances = () =>
1616
(el) => el instanceof HTMLElement && el._hasOverlayStackMixin && !el.hasAttribute('closing'),
1717
);
1818

19-
/**
20-
* Returns all attached overlay instances excluding notification container,
21-
* which only needs to be in the stack for zIndex but not pointer-events.
22-
* @private
23-
*/
24-
const getOverlayInstances = () => getAttachedInstances().filter((el) => el.$.overlay);
25-
2619
/**
2720
* Returns true if all the instances on top of the overlay are nested overlays.
2821
* @private
@@ -49,7 +42,7 @@ const hasOnlyNestedOverlays = (overlay) => {
4942
* @protected
5043
*/
5144
export const isLastOverlay = (overlay, filter = (_overlay) => true) => {
52-
const filteredOverlays = getOverlayInstances().filter(filter);
45+
const filteredOverlays = getAttachedInstances().filter(filter);
5346
return overlay === filteredOverlays.pop();
5447
};
5548

@@ -128,7 +121,7 @@ export const OverlayStackMixin = (superClass) =>
128121
}
129122

130123
// Disable pointer events in other attached overlays
131-
getOverlayInstances().forEach((el) => {
124+
getAttachedInstances().forEach((el) => {
132125
if (el !== this) {
133126
el.$.overlay.style.pointerEvents = 'none';
134127
}
@@ -144,7 +137,7 @@ export const OverlayStackMixin = (superClass) =>
144137
}
145138

146139
// Restore pointer events in the previous overlay(s)
147-
const instances = getOverlayInstances();
140+
const instances = getAttachedInstances();
148141

149142
let el;
150143
// Use instances.pop() to ensure the reverse order

packages/vaadin-lumo-styles/src/components/notification-container.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
@media lumo_components_notification-container {
77
:host {
88
position: fixed;
9-
z-index: 1000;
109
inset: 0;
1110
box-sizing: border-box;
1211
display: flex;
1312
flex-direction: column;
1413
align-items: stretch;
1514
pointer-events: none;
15+
16+
/* Override native [popover] user agent styles */
17+
width: auto;
18+
height: auto;
19+
border: none;
20+
padding: 0;
21+
background-color: transparent;
22+
overflow: visible;
1623
}
1724

1825
[region-group] {

0 commit comments

Comments
 (0)