diff --git a/packages/checkbox-group/stories/index.stories.mdx b/packages/checkbox-group/stories/index.stories.mdx index 3d8242deb..1736b9e37 100644 --- a/packages/checkbox-group/stories/index.stories.mdx +++ b/packages/checkbox-group/stories/index.stories.mdx @@ -169,7 +169,7 @@ The interaction states of the `` are evaluated in order to {() => { loadDefaultFeedbackMessages(); const validate = () => { - const checkboxGroup = document.querySelector('#scientistsGroup'); + const checkboxGroup = document.querySelector('#scientists'); checkboxGroup.submitted = !checkboxGroup.submitted; }; return html` @@ -201,7 +201,7 @@ The interaction states of the `` are evaluated in order to import { Required, loadDefaultFeedbackMessages } from '@lion/validate'; loadDefaultFeedbackMessages(); const validate = () => { - const checkboxGroup = document.querySelector('#scientistsGroup'); + const checkboxGroup = document.querySelector('#scientists'); checkboxGroup.submitted = !checkboxGroup.submitted; }; ``` diff --git a/packages/overlays/src/OverlayController.js b/packages/overlays/src/OverlayController.js index d695ce171..bdc9b74c8 100644 --- a/packages/overlays/src/OverlayController.js +++ b/packages/overlays/src/OverlayController.js @@ -24,6 +24,7 @@ export class OverlayController { placementMode: null, contentNode: config.contentNode, invokerNode: config.invokerNode, + backdropNode: config.backdropNode, referenceNode: null, elementToFocusAfterHide: config.invokerNode, inheritsReferenceWidth: '', @@ -423,10 +424,38 @@ export class OverlayController { */ _handleBackdrop({ animation = true, phase }) { if (this.placementMode === 'local') { - return; // coming soon... + switch (phase) { + case 'init': + if (!this.backdropNode) { + this.backdropNode = document.createElement('div'); + this.backdropNode.classList.add('local-overlays__backdrop'); + } + this.backdropNode.slot = '_overlay-shadow-outlet'; + this._contentNodeWrapper.parentElement.insertBefore( + this.backdropNode, + this._contentNodeWrapper, + ); + break; + case 'show': + this.__hasActiveBackdrop = true; + break; + case 'hide': + if (!this.backdropNode) { + return; + } + this.__hasActiveBackdrop = false; + break; + case 'teardown': + if (!this.backdropNode) { + return; + } + this.backdropNode.parentNode.removeChild(this.backdropNode); + break; + /* no default */ + } + return; } const { backdropNode } = this; - switch (phase) { case 'init': this.backdropNode = document.createElement('div'); diff --git a/packages/overlays/src/OverlayMixin.js b/packages/overlays/src/OverlayMixin.js index 7364694eb..0eddac764 100644 --- a/packages/overlays/src/OverlayMixin.js +++ b/packages/overlays/src/OverlayMixin.js @@ -50,10 +50,11 @@ export const OverlayMixin = dedupeMixin( * @returns {OverlayController} */ // eslint-disable-next-line - _defineOverlay({ contentNode, invokerNode }) { + _defineOverlay({ contentNode, invokerNode, backdropNode }) { return new OverlayController({ contentNode, invokerNode, + backdropNode, ...this._defineOverlayConfig(), // wc provided in the class as defaults ...this.config, // user provided (e.g. in template) popperConfig: { @@ -144,6 +145,10 @@ export const OverlayMixin = dedupeMixin( return Array.from(this.children).find(child => child.slot === 'invoker'); } + get _overlayBackdropNode() { + return Array.from(this.children).find(child => child.slot === 'backdrop'); + } + get _overlayContentNode() { if (this._cachedOverlayContentNode) { return this._cachedOverlayContentNode; @@ -176,6 +181,7 @@ export const OverlayMixin = dedupeMixin( this._overlayCtrl = this._defineOverlay({ contentNode: this._overlayContentNode, invokerNode: this._overlayInvokerNode, + backdropNode: this._overlayBackdropNode, }); this.__syncToOverlayController(); this.__setupSyncFromOverlayController(); diff --git a/packages/overlays/stories/20-index.stories.mdx b/packages/overlays/stories/20-index.stories.mdx index 8f86ceb74..a09bca9be 100644 --- a/packages/overlays/stories/20-index.stories.mdx +++ b/packages/overlays/stories/20-index.stories.mdx @@ -584,3 +584,162 @@ Below an example is shown with the `isBlocking` option, which makes use of the O ``` + + +## Local Backdrop +We provide a possibility to add a backdrop to a locally placed overlay. +You can pass your backdropNode as a configuration parameter and control its styling reacting upon OverlayController events. +Here is the example below + + + {() => { + let backdropNode = document.createElement('div'); + backdropNode.classList.add('local-backdrop-01'); + return html` + + backdropNode.style.display = 'block'} + @before-closed=${(e) => backdropNode.style.display = 'none'} + .config=${{ hasBackdrop: true, placementMode: 'local', backdropNode }} + > + +
+ Hello! You can close this notification here: + +
+
+ `}} +
+ +```js + let backdropNode = document.createElement('div'); + backdropNode.classList.add('local-backdrop-01'); + return html` + + backdropNode.style.display = 'block'} + @before-closed=${(e) => backdropNode.style.display = 'none'} + .config=${{ hasBackdrop: true, placementMode: 'local', backdropNode }} + > + +
+ Hello! You can close this notification here: + +
+
+``` + +## Declarative Local Backdrop +Another way to add custom backdrop is declaratively add an element with `slot="backdrop"`. + + + {() => { + const beforeOpened = () => { + document.querySelector('.local-backdrop-02').style.display = 'block'; + } + const beforeClosed = () => { + document.querySelector('.local-backdrop-02').style.display = 'none'; + } + return html` + + +
+ +
+ Hello! You can close this notification here: + +
+
+ `}} +
+ +```js + const beforeOpened = () => { + document.querySelector('.local-backdrop-02').style.display = 'block'; + } + const beforeClosed = () => { + document.querySelector('.local-backdrop-02').style.display = 'none'; + } + return html` + + +
+ +
+ Hello! You can close this notification here: + +
+
+``` diff --git a/packages/overlays/test/OverlayController.test.js b/packages/overlays/test/OverlayController.test.js index e799d0f07..6754976b4 100644 --- a/packages/overlays/test/OverlayController.test.js +++ b/packages/overlays/test/OverlayController.test.js @@ -671,6 +671,88 @@ describe('OverlayController', () => { }); }); + describe('locally placed overlay with hasBackdrop', () => { + it('has no backdrop by default', async () => { + const ctrl = new OverlayController({ + ...withLocalTestConfig(), + }); + await ctrl.show(); + expect(ctrl.backdropNode).to.be.undefined; + }); + + it('supports a backdrop option', async () => { + const ctrl = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: false, + }); + await ctrl.show(); + expect(ctrl.backdropNode).to.be.undefined; + await ctrl.hide(); + + const backdropNode = document.createElement('div'); + backdropNode.classList.add('custom-backdrop'); + + const controllerWithBackdrop = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: true, + backdropNode, + }); + await controllerWithBackdrop.show(); + expect(controllerWithBackdrop.backdropNode).to.have.class('custom-backdrop'); + }); + + it('reenables the backdrop when shown/hidden/shown', async () => { + const backdropNode = document.createElement('div'); + backdropNode.classList.add('custom-backdrop'); + + const ctrl = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: true, + backdropNode, + }); + await ctrl.show(); + expect(ctrl.backdropNode).to.have.class('custom-backdrop'); + await ctrl.hide(); + await ctrl.show(); + expect(ctrl.backdropNode).to.have.class('custom-backdrop'); + }); + + it('adds and stacks backdrops if .hasBackdrop is enabled', async () => { + const backdropNode = document.createElement('div'); + backdropNode.classList.add('custom-backdrop-zero'); + + const ctrl0 = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: true, + backdropNode, + }); + await ctrl0.show(); + expect(ctrl0.backdropNode).to.have.class('custom-backdrop-zero'); + + const ctrl1 = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: false, + }); + await ctrl1.show(); + expect(ctrl0.backdropNode).to.have.class('custom-backdrop-zero'); + expect(ctrl1.backdropNode).to.be.undefined; + + const anotherBackdropNode = document.createElement('div'); + anotherBackdropNode.classList.add('custom-backdrop-two'); + + const ctrl2 = new OverlayController({ + ...withLocalTestConfig(), + hasBackdrop: true, + backdropNode: anotherBackdropNode, + }); + await ctrl2.show(); + + expect(ctrl0.backdropNode).to.have.class('custom-backdrop-zero'); + expect(ctrl1.backdropNode).to.be.undefined; + expect(ctrl2.backdropNode).to.have.class('custom-backdrop-two'); + }); + }); + describe('isBlocking', () => { it('prevents showing of other overlays', async () => { const ctrl0 = new OverlayController({ diff --git a/yarn.lock b/yarn.lock index e6c712938..d56f990c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2266,6 +2266,11 @@ resolved "https://registry.yarnpkg.com/@open-wc/dedupe-mixin/-/dedupe-mixin-1.1.1.tgz#3ac8e498422ef316276bbe4aa687e35bd10c6871" integrity sha512-Y1+h5nQjJnDHP+8OceZB47I4D7iOiYnM0jXYLGEi96IusR93et30BIyEEQAJ4AvYfbuIrdbf0L5vQWfszU6/Jg== +"@open-wc/dedupe-mixin@^1.2.1": + version "1.2.10" + resolved "https://registry.yarnpkg.com/@open-wc/dedupe-mixin/-/dedupe-mixin-1.2.10.tgz#4992874e98b8c49ed71e7e17d2adc7538d62260b" + integrity sha512-I3/aKV8OJ5LkZLOvTiGRgKs+o7VVz3EUozbc7yeKJo7x8+j+NHWhVvtNHE8GXAXbN3s4KmMWQt1mXWCEZwNg7g== + "@open-wc/demoing-storybook@^1.10.4": version "1.10.5" resolved "https://registry.yarnpkg.com/@open-wc/demoing-storybook/-/demoing-storybook-1.10.5.tgz#3886e01fcc3b13485d5bdb4904f4ec627895609f"