Skip to content

Commit 9da52fe

Browse files
timfishbigopon
authored andcommitted
feat(dialog-renderer): add native dialog renderer close #338
1 parent 2f57949 commit 9da52fe

File tree

4 files changed

+600
-1
lines changed

4 files changed

+600
-1
lines changed

src/aurelia-dialog.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './dialog-cancel-error';
2222
export * from './dialog-result';
2323
export * from './dialog-service';
2424
export * from './dialog-controller';
25+
export { NativeDialogRenderer } from './native-dialog-renderer';

src/dialog-settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export interface DialogSettings {
9191
* Usde to provide custom positioning logic.
9292
* When invoked the function is passed the dialog container and the dialog overlay elements.
9393
*/
94-
position?: (dialogContainer: Element, dialogOverlay: Element) => void;
94+
position?: (dialogContainer: Element, dialogOverlay?: Element) => void;
9595
}
9696

9797
/**

src/native-dialog-renderer.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { DOM } from 'aurelia-pal';
2+
import { transient } from 'aurelia-dependency-injection';
3+
import { Renderer } from './renderer';
4+
import { DialogController } from './dialog-controller';
5+
import { transitionEvent, hasTransition } from './dialog-renderer';
6+
7+
const containerTagName = 'dialog';
8+
let body: HTMLBodyElement;
9+
10+
@transient()
11+
export class NativeDialogRenderer implements Renderer {
12+
public static dialogControllers: DialogController[] = [];
13+
14+
public static keyboardEventHandler(e: KeyboardEvent) {
15+
const key = (e.code || e.key) === 'Enter' || e.keyCode === 13
16+
? 'Enter'
17+
: undefined;
18+
19+
if (!key) { return; }
20+
const top = NativeDialogRenderer.dialogControllers[NativeDialogRenderer.dialogControllers.length - 1];
21+
if (!top || !top.settings.keyboard) { return; }
22+
const keyboard = top.settings.keyboard;
23+
if (key === 'Enter' && (keyboard === key || (Array.isArray(keyboard) && keyboard.indexOf(key) > -1))) {
24+
top.ok();
25+
}
26+
}
27+
28+
public static trackController(dialogController: DialogController): void {
29+
if (!NativeDialogRenderer.dialogControllers.length) {
30+
DOM.addEventListener('keyup', NativeDialogRenderer.keyboardEventHandler, false);
31+
}
32+
NativeDialogRenderer.dialogControllers.push(dialogController);
33+
}
34+
35+
public static untrackController(dialogController: DialogController): void {
36+
const i = NativeDialogRenderer.dialogControllers.indexOf(dialogController);
37+
if (i !== -1) {
38+
NativeDialogRenderer.dialogControllers.splice(i, 1);
39+
}
40+
if (!NativeDialogRenderer.dialogControllers.length) {
41+
DOM.removeEventListener('keyup', NativeDialogRenderer.keyboardEventHandler, false);
42+
}
43+
}
44+
45+
private stopPropagation: (e: MouseEvent & { _aureliaDialogHostClicked: boolean }) => void;
46+
private closeDialogClick: (e: MouseEvent & { _aureliaDialogHostClicked: boolean }) => void;
47+
private dialogCancel: (e: Event) => void;
48+
49+
public dialogContainer: HTMLDialogElement;
50+
public host: Element;
51+
public anchor: Element;
52+
53+
private getOwnElements(parent: Element, selector: string): Element[] {
54+
const elements = parent.querySelectorAll(selector);
55+
const own: Element[] = [];
56+
for (let i = 0; i < elements.length; i++) {
57+
if (elements[i].parentElement === parent) {
58+
own.push(elements[i]);
59+
}
60+
}
61+
return own;
62+
}
63+
64+
private attach(dialogController: DialogController): void {
65+
const spacingWrapper = DOM.createElement('div'); // TODO: check if redundant
66+
spacingWrapper.appendChild(this.anchor);
67+
this.dialogContainer = DOM.createElement(containerTagName) as HTMLDialogElement;
68+
if ((window as any).dialogPolyfill) {
69+
(window as any).dialogPolyfill.registerDialog(this.dialogContainer);
70+
}
71+
72+
this.dialogContainer.appendChild(spacingWrapper);
73+
74+
const lastContainer = this.getOwnElements(this.host, containerTagName).pop();
75+
if (lastContainer && lastContainer.parentElement) {
76+
this.host.insertBefore(this.dialogContainer, lastContainer.nextSibling);
77+
} else {
78+
this.host.insertBefore(this.dialogContainer, this.host.firstChild);
79+
}
80+
dialogController.controller.attached();
81+
this.host.classList.add('ux-dialog-open');
82+
}
83+
84+
private detach(dialogController: DialogController): void {
85+
// This check only seems required for the polyfill
86+
if (this.dialogContainer.hasAttribute('open')) {
87+
this.dialogContainer.close();
88+
}
89+
90+
this.host.removeChild(this.dialogContainer);
91+
dialogController.controller.detached();
92+
if (!NativeDialogRenderer.dialogControllers.length) {
93+
this.host.classList.remove('ux-dialog-open');
94+
}
95+
}
96+
97+
private setAsActive(): void {
98+
this.dialogContainer.showModal();
99+
this.dialogContainer.classList.add('active');
100+
}
101+
102+
private setAsInactive(): void {
103+
this.dialogContainer.classList.remove('active');
104+
}
105+
106+
private setupEventHandling(dialogController: DialogController): void {
107+
this.stopPropagation = e => { e._aureliaDialogHostClicked = true; };
108+
this.closeDialogClick = e => {
109+
if (dialogController.settings.overlayDismiss && !e._aureliaDialogHostClicked) {
110+
dialogController.cancel();
111+
}
112+
};
113+
this.dialogCancel = e => {
114+
const keyboard = dialogController.settings.keyboard;
115+
const key = 'Escape';
116+
117+
if (keyboard === true || keyboard === key || (Array.isArray(keyboard) && keyboard.indexOf(key) > -1)) {
118+
dialogController.cancel();
119+
} else {
120+
e.preventDefault();
121+
}
122+
};
123+
this.dialogContainer.addEventListener('click', this.closeDialogClick);
124+
this.dialogContainer.addEventListener('cancel', this.dialogCancel);
125+
this.anchor.addEventListener('click', this.stopPropagation);
126+
}
127+
128+
private clearEventHandling(): void {
129+
this.dialogContainer.removeEventListener('click', this.closeDialogClick);
130+
this.dialogContainer.removeEventListener('cancel', this.dialogCancel);
131+
this.anchor.removeEventListener('click', this.stopPropagation);
132+
}
133+
134+
private awaitTransition(setActiveInactive: () => void, ignore: boolean): Promise<void> {
135+
return new Promise<void>(resolve => {
136+
// tslint:disable-next-line:no-this-assignment
137+
const renderer = this;
138+
const eventName = transitionEvent();
139+
function onTransitionEnd(e: TransitionEvent): void {
140+
if (e.target !== renderer.dialogContainer) {
141+
return;
142+
}
143+
renderer.dialogContainer.removeEventListener(eventName, onTransitionEnd);
144+
resolve();
145+
}
146+
147+
if (ignore || !hasTransition(this.dialogContainer)) {
148+
resolve();
149+
} else {
150+
this.dialogContainer.addEventListener(eventName, onTransitionEnd);
151+
}
152+
setActiveInactive();
153+
});
154+
}
155+
156+
public getDialogContainer(): Element {
157+
return this.anchor || (this.anchor = DOM.createElement('div'));
158+
}
159+
160+
public showDialog(dialogController: DialogController): Promise<void> {
161+
if (!body) {
162+
body = DOM.querySelectorAll('body')[0] as HTMLBodyElement;
163+
}
164+
if (dialogController.settings.host) {
165+
this.host = dialogController.settings.host;
166+
} else {
167+
this.host = body;
168+
}
169+
const settings = dialogController.settings;
170+
this.attach(dialogController);
171+
172+
if (typeof settings.position === 'function') {
173+
settings.position(this.dialogContainer);
174+
}
175+
176+
NativeDialogRenderer.trackController(dialogController);
177+
this.setupEventHandling(dialogController);
178+
return this.awaitTransition(() => this.setAsActive(), dialogController.settings.ignoreTransitions as boolean);
179+
}
180+
181+
public hideDialog(dialogController: DialogController): Promise<void> {
182+
this.clearEventHandling();
183+
NativeDialogRenderer.untrackController(dialogController);
184+
return this.awaitTransition(() => this.setAsInactive(), dialogController.settings.ignoreTransitions as boolean)
185+
.then(() => { this.detach(dialogController); });
186+
}
187+
}

0 commit comments

Comments
 (0)