fix(overlays): support reconnecting overlay nodes inside other overlays
This commit is contained in:
parent
82f019dc82
commit
afd5ac96cc
3 changed files with 89 additions and 8 deletions
|
|
@ -126,17 +126,25 @@ export const OverlayMixin = dedupeMixin(
|
|||
}
|
||||
}
|
||||
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// we setup in firstUpdated so we can use nodes from light and shadowDom
|
||||
this._setupOverlayCtrl();
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._overlaySetupComplete = new Promise(resolve => {
|
||||
this.__overlaySetupCompleteResolve = resolve;
|
||||
});
|
||||
// Wait for DOM to be ready before setting up the overlay
|
||||
this.updateComplete.then(() => this._setupOverlayCtrl());
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
if (this._overlayCtrl) {
|
||||
this.__tornDown = true;
|
||||
this.__overlayContentNodeWrapperBeforeTeardown = this._overlayContentNodeWrapper;
|
||||
this._teardownOverlayCtrl();
|
||||
}
|
||||
}
|
||||
|
|
@ -178,6 +186,13 @@ export const OverlayMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
_setupOverlayCtrl() {
|
||||
// When we reconnect, this is for recovering from disconnectedCallback --> teardown which removes the
|
||||
// the content node wrapper contents (which is necessary for global overlays to remove them from bottom of body)
|
||||
if (this.__tornDown) {
|
||||
this.__reappendContentNodeWrapperNodes();
|
||||
this.__tornDown = false;
|
||||
}
|
||||
|
||||
this._overlayCtrl = this._defineOverlay({
|
||||
contentNode: this._overlayContentNode,
|
||||
invokerNode: this._overlayInvokerNode,
|
||||
|
|
@ -186,6 +201,7 @@ export const OverlayMixin = dedupeMixin(
|
|||
this.__syncToOverlayController();
|
||||
this.__setupSyncFromOverlayController();
|
||||
this._setupOpenCloseListeners();
|
||||
this.__overlaySetupCompleteResolve();
|
||||
}
|
||||
|
||||
_teardownOverlayCtrl() {
|
||||
|
|
@ -239,5 +255,13 @@ export const OverlayMixin = dedupeMixin(
|
|||
this._overlayCtrl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Simplify this logic of tearing down / reappending overlay content node wrapper
|
||||
// after we have moved this wrapper to ShadowDOM.
|
||||
__reappendContentNodeWrapperNodes() {
|
||||
Array.from(this.__overlayContentNodeWrapperBeforeTeardown.children).forEach(child => {
|
||||
this.appendChild(child);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
withDropdownConfig,
|
||||
withModalDialogConfig,
|
||||
} from '../index.js';
|
||||
|
||||
import './demo-overlay-system.js';
|
||||
import './applyDemoOverlayStyles.js';
|
||||
import { ref as r } from './directives/ref.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { expect, fixture, html, aTimeout } from '@open-wc/testing';
|
||||
import { expect, fixture, html, nextFrame } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { overlays } from '../src/overlays.js';
|
||||
|
||||
export function runOverlayMixinSuite({ /* tagString, */ tag, suffix = '' }) {
|
||||
describe(`OverlayMixin${suffix}`, () => {
|
||||
|
|
@ -22,12 +23,12 @@ export function runOverlayMixinSuite({ /* tagString, */ tag, suffix = '' }) {
|
|||
it('syncs opened to overlayController', async () => {
|
||||
el.opened = true;
|
||||
expect(el.opened).to.be.true;
|
||||
await aTimeout(); // overlayCtrl show/hide is async
|
||||
await nextFrame(); // overlayCtrl show/hide is async
|
||||
expect(el._overlayCtrl.isShown).to.be.true;
|
||||
|
||||
el.opened = false;
|
||||
expect(el.opened).to.be.false;
|
||||
await aTimeout(0); // overlayCtrl show/hide is async
|
||||
await nextFrame(); // overlayCtrl show/hide is async
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
|
|
@ -80,6 +81,8 @@ export function runOverlayMixinSuite({ /* tagString, */ tag, suffix = '' }) {
|
|||
<button slot="invoker">invoker button</button>
|
||||
</${tag}>
|
||||
`);
|
||||
// Wait until it's done opening (handling features is async)
|
||||
await nextFrame();
|
||||
expect(beforeSpy).not.to.have.been.called;
|
||||
await el._overlayCtrl.hide();
|
||||
expect(beforeSpy).to.have.been.called;
|
||||
|
|
@ -137,4 +140,59 @@ export function runOverlayMixinSuite({ /* tagString, */ tag, suffix = '' }) {
|
|||
expect(el.opened).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`OverlayMixin${suffix} nested`, () => {
|
||||
it('reconstructs the overlay when disconnected and reconnected to DOM (support for nested overlay nodes)', async () => {
|
||||
const nestedEl = await fixture(html`
|
||||
<${tag}>
|
||||
<div slot="content">content of the nested overlay</div>
|
||||
<button slot="invoker">invoker nested</button>
|
||||
</${tag}>
|
||||
`);
|
||||
|
||||
const mainEl = await fixture(html`
|
||||
<${tag}>
|
||||
<div slot="content">
|
||||
open nested overlay:
|
||||
${nestedEl}
|
||||
</div>
|
||||
<button slot="invoker">invoker button</button>
|
||||
</${tag}>
|
||||
`);
|
||||
|
||||
if (mainEl._overlayCtrl.placementMode === 'global') {
|
||||
// Specifically checking the output in global root node, because the _contentOverlayNode still references
|
||||
// the node that was removed in the teardown but hasn't been garbage collected due to reference to it still existing..
|
||||
|
||||
// Find the outlets that are not backdrop outlets
|
||||
const outletsInGlobalRootNode = Array.from(overlays.globalRootNode.children).filter(
|
||||
child =>
|
||||
child.slot === '_overlay-shadow-outlet' &&
|
||||
!child.classList.contains('global-overlays__backdrop'),
|
||||
);
|
||||
|
||||
// Check the last one, which is the most nested one
|
||||
const lastContentNodeInContainer =
|
||||
outletsInGlobalRootNode[outletsInGlobalRootNode.length - 1];
|
||||
expect(outletsInGlobalRootNode.length).to.equal(2);
|
||||
|
||||
// Check that it indeed has the intended content
|
||||
expect(lastContentNodeInContainer.firstElementChild.innerText).to.equal(
|
||||
'content of the nested overlay',
|
||||
);
|
||||
expect(lastContentNodeInContainer.firstElementChild.slot).to.equal('content');
|
||||
} else {
|
||||
const actualNestedOverlay = mainEl._overlayContentNode.firstElementChild;
|
||||
const outletNode = Array.from(actualNestedOverlay.children).find(
|
||||
child => child.slot === '_overlay-shadow-outlet',
|
||||
);
|
||||
const contentNode = Array.from(outletNode.children).find(child => child.slot === 'content');
|
||||
|
||||
expect(contentNode).to.not.be.undefined;
|
||||
expect(contentNode.innerText).to.equal('content of the nested overlay');
|
||||
}
|
||||
|
||||
expect(true).to.be.true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue