From 237ce5597922382b0ea988383595a257a86fdf60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 10 Feb 2023 18:45:51 +0200 Subject: [PATCH 1/5] feat(dialog): move DOM event handling to DialogDomRenderer, remove DialogDom --- .../src/plugins/dialog/dialog-controller.ts | 35 +++---- .../src/plugins/dialog/dialog-default-impl.ts | 94 ++++++++++++++----- .../src/plugins/dialog/dialog-interfaces.ts | 13 +-- .../src/plugins/dialog/dialog-service.ts | 37 -------- packages/dialog/src/utilities-di.ts | 3 + 5 files changed, 88 insertions(+), 94 deletions(-) diff --git a/packages/dialog/src/plugins/dialog/dialog-controller.ts b/packages/dialog/src/plugins/dialog/dialog-controller.ts index 61e98d6ffa..37fc5767df 100644 --- a/packages/dialog/src/plugins/dialog/dialog-controller.ts +++ b/packages/dialog/src/plugins/dialog/dialog-controller.ts @@ -4,7 +4,6 @@ import { DialogDeactivationStatuses, IDialogController, IDialogDomRenderer, - IDialogDom, DialogOpenResult, DialogCloseResult, DialogCancelError, @@ -47,9 +46,9 @@ export class DialogController implements IDialogController { public readonly closed: Promise; /** - * The dom structure created to support the dialog associated with this controller + * The renderer used to create dialog DOM */ - private dom!: IDialogDom; + private renderer!: IDialogDomRenderer; /** * The component controller associated with this dialog controller @@ -76,14 +75,14 @@ export class DialogController implements IDialogController { public activate(settings: IDialogLoadedSettings): Promise { const container = this.ctn.createChild(); const { model, template, rejectOnCancel } = settings; - const hostRenderer: IDialogDomRenderer = container.get(IDialogDomRenderer); const dialogTargetHost = settings.host ?? this.p.document.body; - const dom = this.dom = hostRenderer.render(dialogTargetHost, settings); + const renderer = container.get(IDialogDomRenderer); + const contentHost = renderer.render(dialogTargetHost, settings, this); const rootEventTarget = container.has(IEventTarget, true) ? container.get(IEventTarget) as Element : null; - const contentHost = dom.contentHost; + this.renderer = renderer; this.settings = settings; // application root host may be a different element with the dialog root host // example: @@ -97,7 +96,7 @@ export class DialogController implements IDialogController { container.register( instanceRegistration(INode, contentHost), - instanceRegistration(IDialogDom, dom), + instanceRegistration(IDialogDomRenderer, renderer), ); return new Promise(r => { @@ -106,7 +105,7 @@ export class DialogController implements IDialogController { }) .then(canActivate => { if (canActivate !== true) { - dom.dispose(); + renderer.dispose(); if (rejectOnCancel) { throw createDialogCancelError(null, 'Dialog activation rejected'); } @@ -126,12 +125,11 @@ export class DialogController implements IDialogController { ) ) as ICustomElementController; return onResolve(ctrlr.activate(ctrlr, null, LifecycleFlags.fromBind), () => { - dom.overlay.addEventListener(settings.mouseEvent ?? 'click', this); return DialogOpenResult.create(false, this); }); }); }, e => { - dom.dispose(); + renderer.dispose(); throw e; }); } @@ -143,7 +141,7 @@ export class DialogController implements IDialogController { } let deactivating = true; - const { controller, dom, cmp, settings: { mouseEvent, rejectOnCancel }} = this; + const { controller, renderer, cmp, settings: { rejectOnCancel }} = this; const dialogResult = DialogCloseResult.create(status, value); const promise: Promise> = new Promise>(r => { @@ -162,8 +160,7 @@ export class DialogController implements IDialogController { return onResolve(cmp.deactivate?.(dialogResult), () => onResolve(controller.deactivate(controller, null, LifecycleFlags.fromUnbind), () => { - dom.dispose(); - dom.overlay.removeEventListener(mouseEvent ?? 'click', this); + renderer.dispose(); if (!rejectOnCancel && status !== DialogDeactivationStatuses.Error) { this._resolve(dialogResult); } else { @@ -217,23 +214,13 @@ export class DialogController implements IDialogController { () => onResolve( this.controller.deactivate(this.controller, null, LifecycleFlags.fromUnbind), () => { - this.dom.dispose(); + this.renderer.dispose(); this._reject(closeError); } ) ))); } - /** @internal */ - public handleEvent(event: MouseEvent): void { - if (/* user allows dismiss on overlay click */this.settings.overlayDismiss - && /* did not click inside the host element */!this.dom.contentHost.contains(event.target as Element) - ) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.cancel(); - } - } - private getOrCreateVm(container: IContainer, settings: IDialogLoadedSettings, host: HTMLElement): IDialogComponent { const Component = settings.component; if (Component == null) { diff --git a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts index 397d724f8d..f4cad71f5e 100644 --- a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts +++ b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts @@ -1,12 +1,11 @@ import { IPlatform } from '@aurelia/runtime-html'; import { IDialogDomRenderer, - IDialogDom, - IDialogGlobalSettings, + IDialogGlobalSettings, DialogActionKey, IDialogLoadedSettings, IDialogController, } from './dialog-interfaces'; import { IContainer } from '@aurelia/kernel'; -import { singletonRegistration } from '../../utilities-di'; +import { singletonRegistration, transientRegistration } from '../../utilities-di'; export class DefaultDialogGlobalSettings implements IDialogGlobalSettings { @@ -20,45 +19,96 @@ export class DefaultDialogGlobalSettings implements IDialogGlobalSettings { } const baseWrapperCss = 'position:absolute;width:100%;height:100%;top:0;left:0;'; +const wrapperCss = `${baseWrapperCss}display:flex;`; +const hostCss = 'position:relative;margin:auto;'; -export class DefaultDialogDomRenderer implements IDialogDomRenderer { +export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListenerObject { /** @internal */ protected static inject = [IPlatform]; + public wrapper!: HTMLElement; + + public overlay!: HTMLElement; + + public contentHost!: HTMLElement; + + /** @internal */ + protected settings!: IDialogLoadedSettings; + + /** @internal */ + protected controller!: IDialogController; + public constructor(private readonly p: IPlatform) {} public static register(container: IContainer) { - singletonRegistration(IDialogDomRenderer, this).register(container); + transientRegistration(IDialogDomRenderer, this).register(container); } - private readonly wrapperCss: string = `${baseWrapperCss} display:flex;`; - private readonly overlayCss: string = baseWrapperCss; - private readonly hostCss: string = 'position:relative;margin:auto;'; - - public render(dialogHost: HTMLElement): IDialogDom { + public render(dialogHost: HTMLElement, settings: IDialogLoadedSettings, controller: IDialogController): HTMLElement { const doc = this.p.document; const h = (name: string, css: string) => { const el = doc.createElement(name); el.style.cssText = css; return el; }; - const wrapper = dialogHost.appendChild(h('au-dialog-container', this.wrapperCss)); - const overlay = wrapper.appendChild(h('au-dialog-overlay', this.overlayCss)); - const host = wrapper.appendChild(h('div', this.hostCss)); - return new DefaultDialogDom(wrapper, overlay, host); - } -} + const wrapper = dialogHost.appendChild(h('au-dialog-container', wrapperCss)); + const overlay = wrapper.appendChild(h('au-dialog-overlay', baseWrapperCss)); + const contentHost = wrapper.appendChild(h('div', hostCss)); + + overlay.addEventListener(settings.mouseEvent ?? 'click', this); + wrapper.addEventListener('keydown', this); -export class DefaultDialogDom implements IDialogDom { - public constructor( - public readonly wrapper: HTMLElement, - public readonly overlay: HTMLElement, - public readonly contentHost: HTMLElement, - ) { + this.wrapper = wrapper; + this.overlay = overlay; + this.contentHost = contentHost; + this.settings = settings; + this.controller = controller; + + return contentHost; } public dispose(): void { + this.wrapper.removeEventListener('keydown', this); + this.overlay.removeEventListener(this.settings.mouseEvent ?? 'click', this); this.wrapper.remove(); } + + /** @internal */ + public handleEvent(event: KeyboardEvent | MouseEvent): void { + const { controller } = this; + + // handle wrapper keydown + if (event.type === 'keydown') { + const key = getActionKey(event as KeyboardEvent); + if (key == null) { + return; + } + + const keyboard = this.settings.keyboard; + if (key === 'Escape' && keyboard.includes(key)) { + void controller.cancel(); + } else if (key === 'Enter' && keyboard.includes(key)) { + void controller.ok(); + } + return; + } + + // handle overlay click + if (/* user allows to dismiss on overlay click */this.settings.overlayDismiss + && /* did not click inside the host element */!this.contentHost.contains(event.target as Element) + ) { + void controller.cancel(); + } + } +} + +function getActionKey(e: KeyboardEvent): DialogActionKey | undefined { + if ((e.code || e.key) === 'Escape' || e.keyCode === 27) { + return 'Escape'; + } + if ((e.code || e.key) === 'Enter' || e.keyCode === 13) { + return 'Enter'; + } + return undefined; } diff --git a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts index c7070ceb23..5a20ed9f90 100644 --- a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts +++ b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts @@ -45,17 +45,8 @@ export interface IDialogController { * An interface describing the object responsible for creating the dom structure of a dialog */ export const IDialogDomRenderer = createInterface('IDialogDomRenderer'); -export interface IDialogDomRenderer { - render(dialogHost: Element, settings: IDialogLoadedSettings): IDialogDom; -} - -/** - * An interface describing the DOM structure of a dialog - */ -export const IDialogDom = createInterface('IDialogDom'); -export interface IDialogDom extends IDisposable { - readonly overlay: HTMLElement; - readonly contentHost: HTMLElement; +export interface IDialogDomRenderer extends IDisposable { + render(dialogHost: Element, settings: IDialogLoadedSettings, controller: IDialogController): HTMLElement; } // export type IDialogCancellableOpenResult = IDialogOpenResult | IDialogCancelResult; diff --git a/packages/dialog/src/plugins/dialog/dialog-service.ts b/packages/dialog/src/plugins/dialog/dialog-service.ts index 064af744bd..fd7bc56b3a 100644 --- a/packages/dialog/src/plugins/dialog/dialog-service.ts +++ b/packages/dialog/src/plugins/dialog/dialog-service.ts @@ -2,7 +2,6 @@ import { IContainer, onResolve, resolveAll } from '@aurelia/kernel'; import { AppTask, IPlatform } from '@aurelia/runtime-html'; import { - DialogActionKey, DialogCloseResult, DialogDeactivationStatuses, DialogOpenResult, @@ -104,10 +103,6 @@ export class DialogService implements IDialogService { dialogController.activate(loadedSettings), openResult => { if (!openResult.wasCancelled) { - if (this.dlgs.push(dialogController) === 1) { - this.p.window.addEventListener('keydown', this); - } - const $removeController = () => this.remove(dialogController); dialogController.closed.then($removeController, $removeController); } @@ -152,28 +147,6 @@ export class DialogService implements IDialogService { if (idx > -1) { this.dlgs.splice(idx, 1); } - if (dlgs.length === 0) { - this.p.window.removeEventListener('keydown', this); - } - } - - /** @internal */ - public handleEvent(e: Event): void { - const keyEvent = e as KeyboardEvent; - const key = getActionKey(keyEvent); - if (key == null) { - return; - } - const top = this.top; - if (top === null || top.settings.keyboard.length === 0) { - return; - } - const keyboard = top.settings.keyboard; - if (key === 'Escape' && keyboard.includes(key)) { - void top.cancel(); - } else if (key === 'Enter' && keyboard.includes(key)) { - void top.ok(); - } } } @@ -238,13 +211,3 @@ function asDialogOpenPromise(promise: Promise): DialogOpenPromise { (promise as DialogOpenPromise).whenClosed = whenClosed; return promise as DialogOpenPromise; } - -function getActionKey(e: KeyboardEvent): DialogActionKey | undefined { - if ((e.code || e.key) === 'Escape' || e.keyCode === 27) { - return 'Escape'; - } - if ((e.code || e.key) === 'Enter' || e.keyCode === 13) { - return 'Enter'; - } - return undefined; -} diff --git a/packages/dialog/src/utilities-di.ts b/packages/dialog/src/utilities-di.ts index 2e3b53dfaa..343d5f6ac5 100644 --- a/packages/dialog/src/utilities-di.ts +++ b/packages/dialog/src/utilities-di.ts @@ -11,3 +11,6 @@ export const instanceRegistration = Registration.instance; /** @internal */ export const callbackRegistration = Registration.callback; + +/** @internal */ +export const transientRegistration = Registration.transient; From 6aadfc4188d314f2b639162d65b16b7658e66123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 10 Feb 2023 18:50:56 +0200 Subject: [PATCH 2/5] fix(dialog): add missing tabindex --- packages/dialog/src/plugins/dialog/dialog-default-impl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts index f4cad71f5e..fefa92ca7d 100644 --- a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts +++ b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts @@ -47,12 +47,13 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen public render(dialogHost: HTMLElement, settings: IDialogLoadedSettings, controller: IDialogController): HTMLElement { const doc = this.p.document; - const h = (name: string, css: string) => { + const h = (name: string, css: string): HTMLElement => { const el = doc.createElement(name); el.style.cssText = css; return el; }; const wrapper = dialogHost.appendChild(h('au-dialog-container', wrapperCss)); + wrapper.setAttribute('tabindex', '-1'); const overlay = wrapper.appendChild(h('au-dialog-overlay', baseWrapperCss)); const contentHost = wrapper.appendChild(h('div', hostCss)); From d34ee33b38446eec8a4c08efa78705b9953f6a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 10 Feb 2023 23:44:21 +0200 Subject: [PATCH 3/5] fix(dialog): use setting from controller, fix build --- packages/dialog/src/index.ts | 2 -- .../src/plugins/dialog/dialog-controller.ts | 5 +++-- .../src/plugins/dialog/dialog-default-impl.ts | 19 ++++++------------- .../src/plugins/dialog/dialog-interfaces.ts | 2 +- .../src/plugins/dialog/dialog-service.ts | 1 + 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/dialog/src/index.ts b/packages/dialog/src/index.ts index 1b46e74355..f860b60100 100644 --- a/packages/dialog/src/index.ts +++ b/packages/dialog/src/index.ts @@ -8,7 +8,6 @@ export { IDialogService, IDialogController, IDialogDomRenderer, - IDialogDom, // dialog results DialogCloseResult, @@ -46,7 +45,6 @@ export { } from './plugins/dialog/dialog-configuration'; export { - DefaultDialogDom, DefaultDialogDomRenderer, DefaultDialogGlobalSettings, } from './plugins/dialog/dialog-default-impl'; diff --git a/packages/dialog/src/plugins/dialog/dialog-controller.ts b/packages/dialog/src/plugins/dialog/dialog-controller.ts index 37fc5767df..f84b2b25c6 100644 --- a/packages/dialog/src/plugins/dialog/dialog-controller.ts +++ b/packages/dialog/src/plugins/dialog/dialog-controller.ts @@ -73,17 +73,18 @@ export class DialogController implements IDialogController { /** @internal */ public activate(settings: IDialogLoadedSettings): Promise { + this.settings = settings; + const container = this.ctn.createChild(); const { model, template, rejectOnCancel } = settings; const dialogTargetHost = settings.host ?? this.p.document.body; const renderer = container.get(IDialogDomRenderer); - const contentHost = renderer.render(dialogTargetHost, settings, this); + const contentHost = renderer.render(dialogTargetHost, this); const rootEventTarget = container.has(IEventTarget, true) ? container.get(IEventTarget) as Element : null; this.renderer = renderer; - this.settings = settings; // application root host may be a different element with the dialog root host // example: // diff --git a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts index fefa92ca7d..10f8bd2da2 100644 --- a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts +++ b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts @@ -1,8 +1,5 @@ import { IPlatform } from '@aurelia/runtime-html'; -import { - IDialogDomRenderer, - IDialogGlobalSettings, DialogActionKey, IDialogLoadedSettings, IDialogController, -} from './dialog-interfaces'; +import { IDialogDomRenderer, IDialogGlobalSettings, DialogActionKey, IDialogController } from './dialog-interfaces'; import { IContainer } from '@aurelia/kernel'; import { singletonRegistration, transientRegistration } from '../../utilities-di'; @@ -33,9 +30,6 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen public contentHost!: HTMLElement; - /** @internal */ - protected settings!: IDialogLoadedSettings; - /** @internal */ protected controller!: IDialogController; @@ -45,7 +39,7 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen transientRegistration(IDialogDomRenderer, this).register(container); } - public render(dialogHost: HTMLElement, settings: IDialogLoadedSettings, controller: IDialogController): HTMLElement { + public render(dialogHost: HTMLElement, controller: IDialogController): HTMLElement { const doc = this.p.document; const h = (name: string, css: string): HTMLElement => { const el = doc.createElement(name); @@ -57,13 +51,12 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen const overlay = wrapper.appendChild(h('au-dialog-overlay', baseWrapperCss)); const contentHost = wrapper.appendChild(h('div', hostCss)); - overlay.addEventListener(settings.mouseEvent ?? 'click', this); + overlay.addEventListener(controller.settings.mouseEvent ?? 'click', this); wrapper.addEventListener('keydown', this); this.wrapper = wrapper; this.overlay = overlay; this.contentHost = contentHost; - this.settings = settings; this.controller = controller; return contentHost; @@ -71,7 +64,7 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen public dispose(): void { this.wrapper.removeEventListener('keydown', this); - this.overlay.removeEventListener(this.settings.mouseEvent ?? 'click', this); + this.overlay.removeEventListener(this.controller.settings.mouseEvent ?? 'click', this); this.wrapper.remove(); } @@ -86,7 +79,7 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen return; } - const keyboard = this.settings.keyboard; + const keyboard = controller.settings.keyboard; if (key === 'Escape' && keyboard.includes(key)) { void controller.cancel(); } else if (key === 'Enter' && keyboard.includes(key)) { @@ -96,7 +89,7 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen } // handle overlay click - if (/* user allows to dismiss on overlay click */this.settings.overlayDismiss + if (/* user allows to dismiss on overlay click */controller.settings.overlayDismiss && /* did not click inside the host element */!this.contentHost.contains(event.target as Element) ) { void controller.cancel(); diff --git a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts index 5a20ed9f90..d1b4626579 100644 --- a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts +++ b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts @@ -46,7 +46,7 @@ export interface IDialogController { */ export const IDialogDomRenderer = createInterface('IDialogDomRenderer'); export interface IDialogDomRenderer extends IDisposable { - render(dialogHost: Element, settings: IDialogLoadedSettings, controller: IDialogController): HTMLElement; + render(dialogHost: Element, controller: IDialogController): HTMLElement; } // export type IDialogCancellableOpenResult = IDialogOpenResult | IDialogCancelResult; diff --git a/packages/dialog/src/plugins/dialog/dialog-service.ts b/packages/dialog/src/plugins/dialog/dialog-service.ts index fd7bc56b3a..52506a2f87 100644 --- a/packages/dialog/src/plugins/dialog/dialog-service.ts +++ b/packages/dialog/src/plugins/dialog/dialog-service.ts @@ -103,6 +103,7 @@ export class DialogService implements IDialogService { dialogController.activate(loadedSettings), openResult => { if (!openResult.wasCancelled) { + this.dlgs.push(dialogController); const $removeController = () => this.remove(dialogController); dialogController.closed.then($removeController, $removeController); } From 4a24c7a85edcb5d931c2c9ad9b47d57f349a3ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 18 Feb 2023 14:49:11 +0200 Subject: [PATCH 4/5] feat(dialog): make renderer async and allow communication with component controller --- .../src/plugins/dialog/dialog-controller.ts | 46 ++++++++++--------- .../src/plugins/dialog/dialog-default-impl.ts | 41 ++++++++--------- .../src/plugins/dialog/dialog-interfaces.ts | 4 +- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/packages/dialog/src/plugins/dialog/dialog-controller.ts b/packages/dialog/src/plugins/dialog/dialog-controller.ts index f84b2b25c6..b3a70bafa4 100644 --- a/packages/dialog/src/plugins/dialog/dialog-controller.ts +++ b/packages/dialog/src/plugins/dialog/dialog-controller.ts @@ -73,32 +73,36 @@ export class DialogController implements IDialogController { /** @internal */ public activate(settings: IDialogLoadedSettings): Promise { - this.settings = settings; + const { model, template, rejectOnCancel } = settings; + + // TODO: use CEs name if provided? + const contentHost = document.createElement('div'); const container = this.ctn.createChild(); - const { model, template, rejectOnCancel } = settings; - const dialogTargetHost = settings.host ?? this.p.document.body; + container.register(instanceRegistration(IDialogController, this)); + const renderer = container.get(IDialogDomRenderer); - const contentHost = renderer.render(dialogTargetHost, this); - const rootEventTarget = container.has(IEventTarget, true) - ? container.get(IEventTarget) as Element - : null; + container.register(instanceRegistration(IDialogDomRenderer, renderer)); - this.renderer = renderer; + // moved to renderer + // const dialogTargetHost = settings.host ?? this.p.document.body; + + // delegate binding has been removed, so don't need this any more? // application root host may be a different element with the dialog root host // example: // // // // when it's different, needs to ensure delegate bindings work - if (rootEventTarget == null || !rootEventTarget.contains(dialogTargetHost)) { - container.register(instanceRegistration(IEventTarget, dialogTargetHost)); - } + // const rootEventTarget = container.has(IEventTarget, true) + // ? container.get(IEventTarget) as Element + // : null; + // if (rootEventTarget == null || !rootEventTarget.contains(dialogTargetHost)) { + // container.register(instanceRegistration(IEventTarget, dialogTargetHost)); + // } - container.register( - instanceRegistration(INode, contentHost), - instanceRegistration(IDialogDomRenderer, renderer), - ); + this.settings = settings; + this.renderer = renderer; return new Promise(r => { const cmp = Object.assign(this.cmp = this.getOrCreateVm(container, settings, contentHost), { $dialog: this }); @@ -106,7 +110,6 @@ export class DialogController implements IDialogController { }) .then(canActivate => { if (canActivate !== true) { - renderer.dispose(); if (rejectOnCancel) { throw createDialogCancelError(null, 'Dialog activation rejected'); } @@ -116,7 +119,7 @@ export class DialogController implements IDialogController { const cmp = this.cmp; return onResolve(cmp.activate?.(model), () => { - const ctrlr = this.controller = Controller.$el( + const controller = this.controller = Controller.$el( container, cmp, contentHost, @@ -125,13 +128,12 @@ export class DialogController implements IDialogController { this.getDefinition(cmp) ?? { name: CustomElement.generateName(), template } ) ) as ICustomElementController; - return onResolve(ctrlr.activate(ctrlr, null, LifecycleFlags.fromBind), () => { - return DialogOpenResult.create(false, this); + return onResolve(renderer.render(controller), () => { + return onResolve(controller.activate(controller, null, LifecycleFlags.fromBind), () => { + return DialogOpenResult.create(false, this); + }); }); }); - }, e => { - renderer.dispose(); - throw e; }); } diff --git a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts index 10f8bd2da2..97e8bd30c7 100644 --- a/packages/dialog/src/plugins/dialog/dialog-default-impl.ts +++ b/packages/dialog/src/plugins/dialog/dialog-default-impl.ts @@ -1,4 +1,4 @@ -import { IPlatform } from '@aurelia/runtime-html'; +import { ICustomElementController, IPlatform } from '@aurelia/runtime-html'; import { IDialogDomRenderer, IDialogGlobalSettings, DialogActionKey, IDialogController } from './dialog-interfaces'; import { IContainer } from '@aurelia/kernel'; @@ -22,7 +22,7 @@ const hostCss = 'position:relative;margin:auto;'; export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListenerObject { /** @internal */ - protected static inject = [IPlatform]; + protected static inject = [IPlatform, IDialogController]; public wrapper!: HTMLElement; @@ -30,47 +30,46 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen public contentHost!: HTMLElement; - /** @internal */ - protected controller!: IDialogController; - - public constructor(private readonly p: IPlatform) {} + public constructor(private readonly platform: IPlatform, private readonly dialogController: IDialogController) {} public static register(container: IContainer) { transientRegistration(IDialogDomRenderer, this).register(container); } - public render(dialogHost: HTMLElement, controller: IDialogController): HTMLElement { - const doc = this.p.document; + public render(componentController: ICustomElementController) { + const { document } = this.platform; + const { settings } = this.dialogController; + const dialogHost = settings.host ?? document.body; + const h = (name: string, css: string): HTMLElement => { - const el = doc.createElement(name); + const el = document.createElement(name); el.style.cssText = css; return el; }; + const wrapper = dialogHost.appendChild(h('au-dialog-container', wrapperCss)); wrapper.setAttribute('tabindex', '-1'); const overlay = wrapper.appendChild(h('au-dialog-overlay', baseWrapperCss)); - const contentHost = wrapper.appendChild(h('div', hostCss)); + const contentHost = wrapper.appendChild(componentController.host); + contentHost.style.cssText = hostCss; - overlay.addEventListener(controller.settings.mouseEvent ?? 'click', this); + overlay.addEventListener(settings.mouseEvent ?? 'click', this); wrapper.addEventListener('keydown', this); this.wrapper = wrapper; this.overlay = overlay; this.contentHost = contentHost; - this.controller = controller; - - return contentHost; } public dispose(): void { this.wrapper.removeEventListener('keydown', this); - this.overlay.removeEventListener(this.controller.settings.mouseEvent ?? 'click', this); + this.overlay.removeEventListener(this.dialogController.settings.mouseEvent ?? 'click', this); this.wrapper.remove(); } /** @internal */ public handleEvent(event: KeyboardEvent | MouseEvent): void { - const { controller } = this; + const { dialogController } = this; // handle wrapper keydown if (event.type === 'keydown') { @@ -79,20 +78,20 @@ export class DefaultDialogDomRenderer implements IDialogDomRenderer, EventListen return; } - const keyboard = controller.settings.keyboard; + const keyboard = dialogController.settings.keyboard; if (key === 'Escape' && keyboard.includes(key)) { - void controller.cancel(); + void dialogController.cancel(); } else if (key === 'Enter' && keyboard.includes(key)) { - void controller.ok(); + void dialogController.ok(); } return; } // handle overlay click - if (/* user allows to dismiss on overlay click */controller.settings.overlayDismiss + if (/* user allows to dismiss on overlay click */dialogController.settings.overlayDismiss && /* did not click inside the host element */!this.contentHost.contains(event.target as Element) ) { - void controller.cancel(); + void dialogController.cancel(); } } } diff --git a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts index d1b4626579..e780223297 100644 --- a/packages/dialog/src/plugins/dialog/dialog-interfaces.ts +++ b/packages/dialog/src/plugins/dialog/dialog-interfaces.ts @@ -1,7 +1,7 @@ import { createInterface } from '../../utilities-di'; import type { Constructable, IContainer, IDisposable } from '@aurelia/kernel'; -import type { ICustomElementViewModel } from '@aurelia/runtime-html'; +import type { ICustomElementController, ICustomElementViewModel } from '@aurelia/runtime-html'; /** * The dialog service for composing view & view model into a dialog @@ -46,7 +46,7 @@ export interface IDialogController { */ export const IDialogDomRenderer = createInterface('IDialogDomRenderer'); export interface IDialogDomRenderer extends IDisposable { - render(dialogHost: Element, controller: IDialogController): HTMLElement; + render(componentController: ICustomElementController): void | Promise; } // export type IDialogCancellableOpenResult = IDialogOpenResult | IDialogCancelResult; From 8784751b050c3809cb419dfc773c0f6722b65eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 18 Feb 2023 15:28:36 +0200 Subject: [PATCH 5/5] feat(dialog): adjust renderer container --- packages/dialog/src/plugins/dialog/dialog-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dialog/src/plugins/dialog/dialog-controller.ts b/packages/dialog/src/plugins/dialog/dialog-controller.ts index b3a70bafa4..422600fe0d 100644 --- a/packages/dialog/src/plugins/dialog/dialog-controller.ts +++ b/packages/dialog/src/plugins/dialog/dialog-controller.ts @@ -81,7 +81,7 @@ export class DialogController implements IDialogController { const container = this.ctn.createChild(); container.register(instanceRegistration(IDialogController, this)); - const renderer = container.get(IDialogDomRenderer); + const renderer = this.ctn.get(IDialogDomRenderer); container.register(instanceRegistration(IDialogDomRenderer, renderer)); // moved to renderer