diff --git a/.changeset/little-jeans-yell.md b/.changeset/little-jeans-yell.md new file mode 100644 index 000000000..619e61291 --- /dev/null +++ b/.changeset/little-jeans-yell.md @@ -0,0 +1,5 @@ +--- +'@lion/overlays': patch +--- + +Adds `async transitionShow` and `async transitionHide` to OverlayController to enable basic support for transitions/animations diff --git a/package.json b/package.json index 0509673f7..88943364c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:docs": "wca analyze \"packages/tabs/**/*.js\"", "build:types": "tsc -p tsconfig.build.types.json", "bundlesize": "rollup -c bundlesize/rollup.config.js && bundlesize", - "debug": "web-test-runner \"packages/input-datepicker/test/**/*.test.js\" --watch", + "debug": "web-test-runner \"packages/overlays/test/**/*.test.js\" --watch", "dev-server": "es-dev-server", "format": "npm run format:eslint && npm run format:prettier", "format:eslint": "eslint --ext .js,.html . --fix", diff --git a/packages/input-datepicker/test/lion-input-datepicker.test.js b/packages/input-datepicker/test/lion-input-datepicker.test.js index d4ef8c0c4..979d2c6d9 100644 --- a/packages/input-datepicker/test/lion-input-datepicker.test.js +++ b/packages/input-datepicker/test/lion-input-datepicker.test.js @@ -2,7 +2,7 @@ import { LionCalendar } from '@lion/calendar'; import { isSameDate } from '@lion/calendar/src/utils/isSameDate.js'; import { html, LitElement } from '@lion/core'; import { IsDateDisabled, MaxDate, MinDate, MinMaxDate } from '@lion/form-core'; -import { aTimeout, defineCE, expect, fixture } from '@open-wc/testing'; +import { aTimeout, defineCE, expect, fixture, nextFrame } from '@open-wc/testing'; import sinon from 'sinon'; import '../lion-input-datepicker.js'; import { LionInputDatepicker } from '../src/LionInputDatepicker.js'; @@ -73,6 +73,7 @@ describe('', () => { elObj.overlayController.contentNode.dispatchEvent( new KeyboardEvent('keyup', { key: 'Escape' }), ); + await nextFrame(); expect(elObj.overlayController.isShown).to.equal(false); }); @@ -83,6 +84,7 @@ describe('', () => { expect(elObj.overlayController.isShown).to.equal(true); elObj.overlayCloseButtonEl.click(); + await nextFrame(); expect(elObj.overlayController.isShown).to.equal(false); }); diff --git a/packages/overlays/src/OverlayController.js b/packages/overlays/src/OverlayController.js index 05861310b..5ebaed326 100644 --- a/packages/overlays/src/OverlayController.js +++ b/packages/overlays/src/OverlayController.js @@ -678,6 +678,7 @@ export class OverlayController extends EventTargetShim { await this._handlePosition({ phase: 'show' }); this.__elementToFocusAfterHide = elementToFocusAfterHide; this.dispatchEvent(new Event('show')); + await this.transitionShow({ backdropNode: this.backdropNode, contentNode: this.contentNode }); } /** @type {function} */ (this._showResolve)(); } @@ -782,7 +783,7 @@ export class OverlayController extends EventTargetShim { const event = new CustomEvent('before-hide', { cancelable: true }); this.dispatchEvent(event); if (!event.defaultPrevented) { - // await this.transitionHide({ backdropNode: this.backdropNode, contentNode: this.contentNode }); + await this.transitionHide({ backdropNode: this.backdropNode, contentNode: this.contentNode }); this.contentWrapperNode.style.display = 'none'; this._handleFeatures({ phase: 'hide' }); this._keepBodySize({ phase: 'hide' }); @@ -798,6 +799,12 @@ export class OverlayController extends EventTargetShim { // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars async transitionHide(config) {} + /** + * @param {{backdropNode:HTMLElement, contentNode:HTMLElement}} config + */ + // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars + async transitionShow(config) {} + _restoreFocus() { // We only are allowed to move focus if we (still) 'own' it. // Otherwise we assume the 'outside world' has, purposefully, taken over diff --git a/packages/overlays/test-suites/OverlayMixin.suite.js b/packages/overlays/test-suites/OverlayMixin.suite.js index c156f0af9..057860549 100644 --- a/packages/overlays/test-suites/OverlayMixin.suite.js +++ b/packages/overlays/test-suites/OverlayMixin.suite.js @@ -206,6 +206,7 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) { `)); closeBtn.click(); + await nextFrame(); // hide takes at least a frame expect(el.opened).to.be.false; }); }); diff --git a/packages/overlays/test/OverlayController.test.js b/packages/overlays/test/OverlayController.test.js index 47a4fdc8a..438bb3fa7 100644 --- a/packages/overlays/test/OverlayController.test.js +++ b/packages/overlays/test/OverlayController.test.js @@ -1100,6 +1100,60 @@ describe('OverlayController', () => { await ctrl0.show(); expect(getTopEl()).to.equal(ctrl0.contentNode); }); + + it('awaits a "transitionHide" hook before hiding for real', done => { + const ctrl = new OverlayController({ + ...withGlobalTestConfig(), + }); + ctrl.show(); + + /** @type {{ (): void; (value?: void | PromiseLike | undefined): void; }} */ + let hideTransitionFinished; + ctrl.transitionHide = () => + new Promise(resolve => { + hideTransitionFinished = resolve; + }); + + ctrl.hide(); + + expect(getComputedStyle(ctrl.contentWrapperNode).display).to.equal('block'); + setTimeout(() => { + hideTransitionFinished(); + setTimeout(() => { + expect(getComputedStyle(ctrl.contentWrapperNode).display).to.equal('none'); + done(); + }, 0); + }, 0); + }); + + it('awaits a "transitionShow" hook before finishing the show method', done => { + const ctrl = new OverlayController({ + ...withGlobalTestConfig(), + }); + + /** @type {{ (): void; (value?: void | PromiseLike | undefined): void; }} */ + let showTransitionFinished; + ctrl.transitionShow = () => + new Promise(resolve => { + showTransitionFinished = resolve; + }); + ctrl.show(); + + let showIsDone = false; + + /** @type {Promise} */ (ctrl._showComplete).then(() => { + showIsDone = true; + }); + + expect(showIsDone).to.be.false; + setTimeout(() => { + showTransitionFinished(); + setTimeout(() => { + expect(showIsDone).to.be.true; + done(); + }, 0); + }, 0); + }); }); describe('Update Configuration', () => {