diff --git a/packages/notification/src/vaadin-lit-notification.js b/packages/notification/src/vaadin-lit-notification.js new file mode 100644 index 0000000000..c706dbd5dc --- /dev/null +++ b/packages/notification/src/vaadin-lit-notification.js @@ -0,0 +1,129 @@ +/** + * @license + * Copyright (c) 2017 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { css, html, LitElement } from 'lit'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { NotificationContainerMixin, NotificationMixin } from './vaadin-notification-mixin.js'; +import { notificationCardStyles, notificationContainerStyles } from './vaadin-notification-styles.js'; + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes NotificationContainerMixin + * @mixes ElementMixin + * @mixes ThemableMixin + * @private + */ +class NotificationContainer extends NotificationContainerMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) { + static get styles() { + return notificationContainerStyles; + } + + render() { + return html` +
+
+
+
+
+
+
+
+
+
+
+
+
+ `; + } + + static get is() { + return 'vaadin-notification-container'; + } +} + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes ThemableMixin + * @mixes ElementMixin + * @private + */ +class NotificationCard extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) { + static get styles() { + return notificationCardStyles; + } + + render() { + return html` +
+
+ +
+
+ `; + } + + static get is() { + return 'vaadin-notification-card'; + } + + /** @protected */ + ready() { + super.ready(); + this.setAttribute('role', 'alert'); + } +} + +/** + * LitElement based version of `` web component. + * + * ## Disclaimer + * + * This component is an experiment and not yet a part of Vaadin platform. + * There is no ETA regarding specific Vaadin version where it'll land. + * Feel free to try this code in your apps as per Apache 2.0 license. + */ +class Notification extends NotificationMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) { + static get styles() { + return css` + :host { + display: none !important; + } + `; + } + + render() { + return html` + + `; + } + + static get is() { + return 'vaadin-notification'; + } + + /** + * Fired when the notification is closed. + * + * @event closed + */ +} + +defineCustomElement(NotificationContainer); +defineCustomElement(NotificationCard); +defineCustomElement(Notification); + +export { Notification }; diff --git a/packages/notification/src/vaadin-notification-mixin.js b/packages/notification/src/vaadin-notification-mixin.js index c309bcdfda..1780b5c355 100644 --- a/packages/notification/src/vaadin-notification-mixin.js +++ b/packages/notification/src/vaadin-notification-mixin.js @@ -26,6 +26,7 @@ export const NotificationContainerMixin = (superClass) => opened: { type: Boolean, value: false, + sync: true, observer: '_openedChanged', }, }; @@ -102,6 +103,7 @@ export const NotificationMixin = (superClass) => assertive: { type: Boolean, value: false, + sync: true, }, /** @@ -112,6 +114,7 @@ export const NotificationMixin = (superClass) => duration: { type: Number, value: 5000, + sync: true, }, /** @@ -122,6 +125,7 @@ export const NotificationMixin = (superClass) => type: Boolean, value: false, notify: true, + sync: true, observer: '_openedChanged', }, @@ -134,6 +138,7 @@ export const NotificationMixin = (superClass) => type: String, value: 'bottom-start', observer: '_positionChanged', + sync: true, }, /** @@ -145,7 +150,10 @@ export const NotificationMixin = (superClass) => * - `notification` The reference to the `` element. * @type {!NotificationRenderer | undefined} */ - renderer: Function, + renderer: { + type: Function, + sync: true, + }, }; } @@ -345,6 +353,10 @@ export const NotificationMixin = (superClass) => return; } + if (this._container.performUpdate) { + this._container.performUpdate(); + } + if (!this._container.shadowRoot.querySelector(`slot[name="${this.position}"]`)) { console.warn(`Invalid alignment parameter provided: position=${this.position}`); return; diff --git a/packages/notification/test/animation-lit.test.js b/packages/notification/test/animation-lit.test.js new file mode 100644 index 0000000000..56e4316109 --- /dev/null +++ b/packages/notification/test/animation-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './animation.common.js'; diff --git a/packages/notification/test/animation-polymer.test.js b/packages/notification/test/animation-polymer.test.js new file mode 100644 index 0000000000..fcf668b759 --- /dev/null +++ b/packages/notification/test/animation-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './animation.common.js'; diff --git a/packages/notification/test/animation.test.js b/packages/notification/test/animation.common.js similarity index 97% rename from packages/notification/test/animation.test.js rename to packages/notification/test/animation.common.js index e76fdfe158..4fe5a4b377 100644 --- a/packages/notification/test/animation.test.js +++ b/packages/notification/test/animation.common.js @@ -1,6 +1,5 @@ import { expect } from '@vaadin/chai-plugins'; -import { aTimeout, fixtureSync, oneEvent } from '@vaadin/testing-helpers'; -import '../vaadin-notification.js'; +import { aTimeout, fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; describe('animated notifications', () => { let wrapper, notifications, container; @@ -14,6 +13,7 @@ describe('animated notifications', () => { `); + await nextFrame(); notifications = Array.from(wrapper.children); container = notifications[0]._container; diff --git a/packages/notification/test/lit-lit.test.js b/packages/notification/test/lit-lit.test.js new file mode 100644 index 0000000000..79f979a414 --- /dev/null +++ b/packages/notification/test/lit-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './lit.common.js'; diff --git a/packages/notification/test/lit-polymer.test.js b/packages/notification/test/lit-polymer.test.js new file mode 100644 index 0000000000..1332611293 --- /dev/null +++ b/packages/notification/test/lit-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './lit.common.js'; diff --git a/packages/notification/test/lit-renderer-directives-lit.test.js b/packages/notification/test/lit-renderer-directives-lit.test.js new file mode 100644 index 0000000000..bba95dad02 --- /dev/null +++ b/packages/notification/test/lit-renderer-directives-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './lit-renderer-directives.common.js'; diff --git a/packages/notification/test/lit-renderer-directives-polymer.test.js b/packages/notification/test/lit-renderer-directives-polymer.test.js new file mode 100644 index 0000000000..40f4825ab7 --- /dev/null +++ b/packages/notification/test/lit-renderer-directives-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './lit-renderer-directives.common.js'; diff --git a/packages/notification/test/lit-renderer-directives.test.js b/packages/notification/test/lit-renderer-directives.common.js similarity index 98% rename from packages/notification/test/lit-renderer-directives.test.js rename to packages/notification/test/lit-renderer-directives.common.js index e675e123c8..c9a7e39aea 100644 --- a/packages/notification/test/lit-renderer-directives.test.js +++ b/packages/notification/test/lit-renderer-directives.common.js @@ -1,7 +1,6 @@ import { expect } from '@vaadin/chai-plugins'; import { fixtureSync, nextFrame } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import '../vaadin-notification.js'; import { html, render } from 'lit'; import { notificationRenderer } from '../lit.js'; diff --git a/packages/notification/test/lit.test.js b/packages/notification/test/lit.common.js similarity index 86% rename from packages/notification/test/lit.test.js rename to packages/notification/test/lit.common.js index f2422485b0..14dc3e96de 100644 --- a/packages/notification/test/lit.test.js +++ b/packages/notification/test/lit.common.js @@ -1,14 +1,14 @@ import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync } from '@vaadin/testing-helpers'; -import '../vaadin-notification.js'; +import { fixtureSync, nextFrame } from '@vaadin/testing-helpers'; import { html, render } from 'lit'; describe('lit', () => { describe('renderer', () => { let notification; - beforeEach(() => { + beforeEach(async () => { notification = fixtureSync(``); + await nextFrame(); notification.open(); notification.renderer = (root) => { render(html`Initial Content`, root); diff --git a/packages/notification/test/multiple-lit.test.js b/packages/notification/test/multiple-lit.test.js new file mode 100644 index 0000000000..3079f7b178 --- /dev/null +++ b/packages/notification/test/multiple-lit.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../vaadin-lit-notification.js'; +import './multiple.common.js'; diff --git a/packages/notification/test/multiple-polymer.test.js b/packages/notification/test/multiple-polymer.test.js new file mode 100644 index 0000000000..374e7c8759 --- /dev/null +++ b/packages/notification/test/multiple-polymer.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../vaadin-notification.js'; +import './multiple.common.js'; diff --git a/packages/notification/test/multiple.test.js b/packages/notification/test/multiple.common.js similarity index 95% rename from packages/notification/test/multiple.test.js rename to packages/notification/test/multiple.common.js index 4a00381055..b669a7180a 100644 --- a/packages/notification/test/multiple.test.js +++ b/packages/notification/test/multiple.common.js @@ -1,20 +1,6 @@ import { expect } from '@vaadin/chai-plugins'; import { aTimeout, fixtureSync } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import '../vaadin-notification.js'; -import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; - -registerStyles( - 'vaadin-notification-card', - css` - :host { - width: 200px; - background: lightgrey; - animation: none !important; - } - `, - { moduleId: 'vaadin-notification-card-multiple-theme' }, -); describe('multiple notification', () => { let wrapper, notifications, container, regions; diff --git a/packages/notification/test/notification-lit.test.js b/packages/notification/test/notification-lit.test.js new file mode 100644 index 0000000000..b47895f60f --- /dev/null +++ b/packages/notification/test/notification-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './notification.common.js'; diff --git a/packages/notification/test/notification-polymer.test.js b/packages/notification/test/notification-polymer.test.js new file mode 100644 index 0000000000..001da9e82d --- /dev/null +++ b/packages/notification/test/notification-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './notification.common.js'; diff --git a/packages/notification/test/notification.test.js b/packages/notification/test/notification.common.js similarity index 96% rename from packages/notification/test/notification.test.js rename to packages/notification/test/notification.common.js index 5208f7ada2..6f21c49837 100644 --- a/packages/notification/test/notification.test.js +++ b/packages/notification/test/notification.common.js @@ -1,15 +1,15 @@ import { expect } from '@vaadin/chai-plugins'; -import { aTimeout, fixtureSync, isIOS, listenOnce } from '@vaadin/testing-helpers'; +import { aTimeout, fixtureSync, isIOS, listenOnce, nextFrame } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import '../vaadin-notification.js'; describe('vaadin-notification', () => { let notification; - beforeEach(() => { + beforeEach(async () => { notification = fixtureSync(` `); + await nextFrame(); notification.renderer = (root) => { root.innerHTML = `Your work has been saved`; @@ -85,7 +85,7 @@ describe('vaadin-notification', () => { expect(document.body.querySelectorAll('vaadin-notification-container').length).to.be.equal(0); }); - it('should not be in the body when notification reopens', () => { + it('should be in the body when notification reopens', () => { notification.close(); notification._removeNotificationCard(); notification.open(); @@ -223,8 +223,9 @@ describe('vaadin-notification', () => { }); describe('theme', () => { - it('should propagate theme attribute to card', () => { + it('should propagate theme attribute to card', async () => { notification.setAttribute('theme', 'foo'); + await nextFrame(); expect(notification._card.getAttribute('theme')).to.equal('foo'); }); diff --git a/packages/notification/test/renderer-lit.test.js b/packages/notification/test/renderer-lit.test.js new file mode 100644 index 0000000000..1df72ae944 --- /dev/null +++ b/packages/notification/test/renderer-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './renderer.common.js'; diff --git a/packages/notification/test/renderer-polymer.test.js b/packages/notification/test/renderer-polymer.test.js new file mode 100644 index 0000000000..6fbdced6db --- /dev/null +++ b/packages/notification/test/renderer-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './renderer.common.js'; diff --git a/packages/notification/test/renderer.test.js b/packages/notification/test/renderer.common.js similarity index 97% rename from packages/notification/test/renderer.test.js rename to packages/notification/test/renderer.common.js index 87b608e2a4..c8eea538e0 100644 --- a/packages/notification/test/renderer.test.js +++ b/packages/notification/test/renderer.common.js @@ -1,18 +1,18 @@ import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync } from '@vaadin/testing-helpers'; +import { fixtureSync, nextFrame } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import '../vaadin-notification.js'; describe('renderer', () => { describe('basic', () => { let notification; let rendererContent; - beforeEach(() => { + beforeEach(async () => { rendererContent = document.createElement('p'); rendererContent.textContent = 'renderer-content'; notification = fixtureSync(''); + await nextFrame(); // Force sync card attaching and removal instead of waiting for the animation sinon diff --git a/packages/notification/test/statichelper-lit.test.js b/packages/notification/test/statichelper-lit.test.js new file mode 100644 index 0000000000..8feb895819 --- /dev/null +++ b/packages/notification/test/statichelper-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-notification.js'; +import './statichelper.common.js'; diff --git a/packages/notification/test/statichelper-polymer.test.js b/packages/notification/test/statichelper-polymer.test.js new file mode 100644 index 0000000000..1932b68030 --- /dev/null +++ b/packages/notification/test/statichelper-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-notification.js'; +import './statichelper.common.js'; diff --git a/packages/notification/test/statichelper.test.js b/packages/notification/test/statichelper.common.js similarity index 73% rename from packages/notification/test/statichelper.test.js rename to packages/notification/test/statichelper.common.js index 8da8e4938b..6ae593449a 100644 --- a/packages/notification/test/statichelper.test.js +++ b/packages/notification/test/statichelper.common.js @@ -1,20 +1,22 @@ import { expect } from '@vaadin/chai-plugins'; -import { aTimeout } from '@vaadin/testing-helpers'; -import '../vaadin-notification.js'; +import { aTimeout, nextFrame } from '@vaadin/testing-helpers'; import { html } from 'lit'; -import { Notification } from '../src/vaadin-notification.js'; + +const Notification = customElements.get('vaadin-notification'); describe('static helpers', () => { - it('show should show a text notification', () => { + it('show should show a text notification', async () => { const notification = Notification.show('Hello world'); + await nextFrame(); const notificationDom = document.body.querySelector('vaadin-notification'); expect(notification).to.equal(notificationDom); expect(notification._card.innerText.trim()).to.equal('Hello world'); }); - it('show should show a Lit template notification', () => { + it('show should show a Lit template notification', async () => { const notification = Notification.show(html`Hello world`); + await nextFrame(); // FIXME: This causes 'TypeError: Converting circular structure to JSON' // const notificationDom = document.body.querySelector('vaadin-notification'); @@ -23,52 +25,59 @@ describe('static helpers', () => { expect(notification._card.innerText.trim()).to.equal('Hello world'); }); - it('show should use a default duration of 5s and bottom-start', () => { + it('show should use a default duration of 5s and bottom-start', async () => { const notification = Notification.show('Hello world'); + await nextFrame(); expect(notification.duration).to.equal(5000); expect(notification.position).to.equal('bottom-start'); }); - it('show should use the given duration and position', () => { + it('show should use the given duration and position', async () => { const notification = Notification.show('Hello world', { duration: 123, position: 'top-center' }); + await nextFrame(); expect(notification.duration).to.equal(123); expect(notification.position).to.equal('top-center'); }); - it('show should use assertive property when set to true', () => { + it('show should use assertive property when set to true', async () => { const notification = Notification.show('Hello world', { assertive: true }); + await nextFrame(); expect(notification.assertive).to.be.true; }); - it('show should set the given theme attribute', () => { + it('show should set the given theme attribute', async () => { const notification = Notification.show('Hello world', { theme: 'error' }); + await nextFrame(); expect(notification.getAttribute('theme')).to.equal('error'); }); - it('show should work with a duration of zero', () => { + it('show should work with a duration of zero', async () => { const notification = Notification.show('Hello world', { duration: 0 }); + await nextFrame(); expect(notification.duration).to.equal(0); }); it('show remove the element from the document after closing', async () => { const notification = Notification.show('Hello world', { duration: 1 }); + await aTimeout(0); expect(notification.parentElement).to.equal(document.body); await aTimeout(10); expect(notification.parentElement).to.be.null; }); - it('show should support Lit event handlers', () => { + it('show should support Lit event handlers', async () => { let clicked = 0; const doClose = () => { clicked += 1; }; const notification = Notification.show(html`Click to count`); + await nextFrame(); notification._card.querySelector('button').click(); expect(clicked).to.equal(1); }); - it('show should support closing through an event handler', () => { + it('show should support closing through an event handler', async () => { const notification = Notification.show( html`Click