@nutin/cli 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -1
- package/README.md +25 -18
- package/lib/index.mjs +23 -20
- package/lib/src/context-builder.mjs +40 -0
- package/lib/src/file-generator.mjs +26 -126
- package/lib/src/git-manager.mjs +1 -1
- package/lib/src/json-generator.mjs +31 -0
- package/lib/src/package-manager.mjs +21 -18
- package/lib/src/print.mjs +1 -0
- package/lib/src/project-generator.mjs +53 -0
- package/lib/src/prompts.mjs +16 -75
- package/lib/src/template-compiler.mjs +1 -1
- package/lib/src/utils.mjs +9 -9
- package/lib/src/version.mjs +1 -0
- package/package.json +1 -1
- package/templates/base/.gitignore.hbs +1 -0
- package/templates/base/builder.config.js.hbs +8 -0
- package/templates/base/src/app/globals.d.ts.hbs +12 -8
- package/templates/base/src/app/main.ts.hbs +2 -1
- package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
- package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +46 -26
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +36 -22
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +60 -13
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +16 -17
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
- package/templates/base/src/core/base-classes/component/component.ts.hbs +20 -24
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +19 -2
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
- package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +15 -14
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +4 -36
- package/templates/base/src/core/services/router/router.ts.hbs +45 -46
- package/templates/base/src/core/services/store/store.ts.hbs +6 -6
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +41 -15
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +40 -19
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
- package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
- package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
- package/templates/base/src/libs/popover/popover.ts.hbs +89 -96
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +7 -4
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
- package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
- package/templates/base/src/styles/core/_index.scss.hbs +6 -0
- package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
- package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
- package/templates/base/src/styles/main.scss.hbs +1 -1
- package/templates/base/tools/builder/builder.js.hbs +9 -3
- package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
- package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
- package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
- package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
- package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
- package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
- package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
- package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
- package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
- package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +76 -52
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +31 -0
- package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
- package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
- package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
- package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
- /package/templates/{base → features/deployHelper}/.dockerignore.hbs +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { AppEventBus } from "../../../core/index.js";
|
|
2
|
+
|
|
3
|
+
type KeyboardEventHandler = (event: KeyboardEvent) => void;
|
|
4
|
+
|
|
5
|
+
export interface IFocusTrapOptions {
|
|
6
|
+
escapeDeactivates?: boolean;
|
|
7
|
+
clickOutsideDeactivates?: boolean;
|
|
8
|
+
returnFocusOnDeactivate?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IFocusTrapHelperParams {
|
|
12
|
+
container: HTMLElement;
|
|
13
|
+
overlay: HTMLElement;
|
|
14
|
+
options?: IFocusTrapOptions;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class FocusTrapHelper {
|
|
18
|
+
private _container: HTMLElement;
|
|
19
|
+
private _overlay: HTMLElement;
|
|
20
|
+
private _previousActiveElement: HTMLElement | null | undefined = null;
|
|
21
|
+
private _focusableElements: NodeListOf<HTMLElement> | null | undefined = null;
|
|
22
|
+
private _firstFocusableElement: HTMLElement | null | undefined = null;
|
|
23
|
+
private _lastFocusableElement: HTMLElement | null | undefined = null;
|
|
24
|
+
private _boundKeyDown: KeyboardEventHandler;
|
|
25
|
+
private _options: IFocusTrapOptions;
|
|
26
|
+
private _isActive: boolean = false;
|
|
27
|
+
|
|
28
|
+
constructor({container, overlay, options = {}}: IFocusTrapHelperParams) {
|
|
29
|
+
this._container = container;
|
|
30
|
+
this._overlay = overlay;
|
|
31
|
+
this._options = {
|
|
32
|
+
escapeDeactivates: true,
|
|
33
|
+
clickOutsideDeactivates: true,
|
|
34
|
+
returnFocusOnDeactivate: true,
|
|
35
|
+
...options
|
|
36
|
+
};
|
|
37
|
+
this._boundKeyDown = this._handleKeyDown.bind(this);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private _getFocusableElements(): NodeListOf<HTMLElement> {
|
|
41
|
+
return this._container.querySelectorAll(
|
|
42
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
43
|
+
) as NodeListOf<HTMLElement>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public activate(): void {
|
|
47
|
+
if (this._isActive) return;
|
|
48
|
+
|
|
49
|
+
this._previousActiveElement = document.activeElement as HTMLElement;
|
|
50
|
+
|
|
51
|
+
this._focusableElements = this._getFocusableElements();
|
|
52
|
+
|
|
53
|
+
if (this._focusableElements.length > 0) {
|
|
54
|
+
this._firstFocusableElement = this._focusableElements[0];
|
|
55
|
+
this._lastFocusableElement = this._focusableElements[this._focusableElements.length - 1];
|
|
56
|
+
|
|
57
|
+
this._firstFocusableElement?.focus();
|
|
58
|
+
} else {
|
|
59
|
+
this._container.setAttribute('tabindex', '-1');
|
|
60
|
+
this._container.focus();
|
|
61
|
+
this._firstFocusableElement = this._container;
|
|
62
|
+
this._lastFocusableElement = this._container;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this._container.addEventListener('keydown', this._boundKeyDown);
|
|
66
|
+
|
|
67
|
+
document.addEventListener('click', this._handleOutsideClick.bind(this));
|
|
68
|
+
|
|
69
|
+
this._disableOutsideTabIndex();
|
|
70
|
+
|
|
71
|
+
this._isActive = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public deactivate(): void {
|
|
75
|
+
if (!this._isActive) return;
|
|
76
|
+
|
|
77
|
+
this._container.removeEventListener('keydown', this._boundKeyDown);
|
|
78
|
+
document.removeEventListener('click', this._handleOutsideClick.bind(this));
|
|
79
|
+
|
|
80
|
+
this._enableOutsideTabIndex();
|
|
81
|
+
|
|
82
|
+
if (this._container.getAttribute('tabindex') === '-1') {
|
|
83
|
+
this._container.removeAttribute('tabindex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this._options.returnFocusOnDeactivate &&
|
|
87
|
+
this._previousActiveElement &&
|
|
88
|
+
this._previousActiveElement.focus
|
|
89
|
+
) {
|
|
90
|
+
this._previousActiveElement.focus();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this._focusableElements = null;
|
|
94
|
+
this._firstFocusableElement = null;
|
|
95
|
+
this._lastFocusableElement = null;
|
|
96
|
+
this._previousActiveElement = null;
|
|
97
|
+
|
|
98
|
+
this._isActive = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getIsActive(): boolean {
|
|
102
|
+
return this._isActive;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public updateFocusableElements(): void {
|
|
106
|
+
if (!this._isActive) return;
|
|
107
|
+
|
|
108
|
+
this._focusableElements = this._getFocusableElements();
|
|
109
|
+
|
|
110
|
+
if (this._focusableElements.length > 0) {
|
|
111
|
+
this._firstFocusableElement = this._focusableElements[0];
|
|
112
|
+
this._lastFocusableElement = this._focusableElements[this._focusableElements.length - 1];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private _disableOutsideTabIndex(): void {
|
|
117
|
+
const allElements = document.querySelectorAll('body *') as NodeListOf<HTMLElement>;
|
|
118
|
+
|
|
119
|
+
allElements.forEach((el: HTMLElement) => {
|
|
120
|
+
if (!this._container.contains(el)) {
|
|
121
|
+
const currentTabIndex = el.getAttribute('tabindex');
|
|
122
|
+
if (currentTabIndex !== '-1') {
|
|
123
|
+
const originalTabIndex = currentTabIndex ||
|
|
124
|
+
(el.tabIndex !== -1 ? el.tabIndex.toString() : null);
|
|
125
|
+
|
|
126
|
+
if (originalTabIndex !== null) {
|
|
127
|
+
el.setAttribute('data-original-tabindex', originalTabIndex);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
el.setAttribute('tabindex', '-1');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private _enableOutsideTabIndex(): void {
|
|
137
|
+
const allElements = document.querySelectorAll(
|
|
138
|
+
'[data-original-tabindex]'
|
|
139
|
+
) as NodeListOf<HTMLElement>;
|
|
140
|
+
|
|
141
|
+
allElements.forEach((el: HTMLElement) => {
|
|
142
|
+
const originalTabIndex = el.getAttribute('data-original-tabindex');
|
|
143
|
+
|
|
144
|
+
if (originalTabIndex === null) {
|
|
145
|
+
el.removeAttribute('tabindex');
|
|
146
|
+
} else if (originalTabIndex === '0') {
|
|
147
|
+
el.removeAttribute('tabindex');
|
|
148
|
+
} else {
|
|
149
|
+
el.setAttribute('tabindex', originalTabIndex);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
el.removeAttribute('data-original-tabindex');
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private _handleKeyDown(event: KeyboardEvent): void {
|
|
157
|
+
switch (event.key) {
|
|
158
|
+
case 'Tab':
|
|
159
|
+
this._handleTabKey(event);
|
|
160
|
+
break;
|
|
161
|
+
case 'Escape':
|
|
162
|
+
if (this._options.escapeDeactivates) {
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
AppEventBus.emit('popover-close');
|
|
165
|
+
this.deactivate();
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private _handleTabKey(event: KeyboardEvent): void {
|
|
172
|
+
if (!this._focusableElements || this._focusableElements.length === 0) return;
|
|
173
|
+
|
|
174
|
+
if (event.shiftKey) {
|
|
175
|
+
if (document.activeElement === this._firstFocusableElement) {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
this._lastFocusableElement?.focus();
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
if (document.activeElement === this._lastFocusableElement) {
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
this._firstFocusableElement?.focus();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private _handleOutsideClick(event: MouseEvent): void {
|
|
188
|
+
if (!this._options.clickOutsideDeactivates) return;
|
|
189
|
+
this._overlay.addEventListener('click', (e: MouseEvent) => {
|
|
190
|
+
if (e.target === this._overlay){
|
|
191
|
+
event.stopPropagation();
|
|
192
|
+
AppEventBus.emit('popover-close');
|
|
193
|
+
this.deactivate();;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ButtonManager } from "src/core/index.js";
|
|
2
|
+
|
|
3
|
+
export interface IPopoverDomElements {
|
|
4
|
+
wrapper: HTMLElement;
|
|
5
|
+
overlay: HTMLElement;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class PopoverDomHelper {
|
|
9
|
+
public static createDomElements(
|
|
10
|
+
content: HTMLElement,
|
|
11
|
+
buttonManager: ButtonManager
|
|
12
|
+
): IPopoverDomElements {
|
|
13
|
+
const overlay = this.createOverlay();
|
|
14
|
+
const wrapper = this.createWrapper();
|
|
15
|
+
content = this.createContent(content);
|
|
16
|
+
|
|
17
|
+
this.appendCloseButton(content);
|
|
18
|
+
this.appendFooterButtons(content, buttonManager);
|
|
19
|
+
|
|
20
|
+
wrapper.appendChild(content);
|
|
21
|
+
overlay.appendChild(wrapper);
|
|
22
|
+
document.body.appendChild(overlay);
|
|
23
|
+
|
|
24
|
+
return { wrapper, overlay };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static removeDomElements(overlay: HTMLElement | null, onRemoved?: () => void): null {
|
|
28
|
+
if (overlay) {
|
|
29
|
+
const wrapper = overlay.querySelector('.popover-wrapper');
|
|
30
|
+
overlay.classList.remove('show');
|
|
31
|
+
wrapper?.classList.remove('show');
|
|
32
|
+
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
wrapper?.remove();
|
|
35
|
+
overlay?.remove();
|
|
36
|
+
onRemoved?.();
|
|
37
|
+
}, 300); // match transition duration
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static createOverlay(): HTMLElement {
|
|
43
|
+
const overlay = document.createElement('div');
|
|
44
|
+
overlay.className = 'popover-overlay';
|
|
45
|
+
return overlay;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static createWrapper(): HTMLElement {
|
|
49
|
+
const wrapper = document.createElement('div');
|
|
50
|
+
wrapper.className = 'popover-wrapper';
|
|
51
|
+
return wrapper;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static createContent(content: HTMLElement): HTMLElement {
|
|
55
|
+
content.classList.add('popover-content');
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private static appendCloseButton(content: HTMLElement): void {
|
|
60
|
+
const closeButton = document.createElement('button');
|
|
61
|
+
closeButton.className = 'popover-close-button';
|
|
62
|
+
closeButton.innerHTML = '×';
|
|
63
|
+
closeButton.setAttribute('aria-label', 'Close');
|
|
64
|
+
// bind method onCloseClick
|
|
65
|
+
closeButton.setAttribute('data-event', 'click:onCloseClick');
|
|
66
|
+
content.prepend(closeButton);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private static appendFooterButtons(
|
|
70
|
+
content: HTMLElement,
|
|
71
|
+
buttonManager: ButtonManager
|
|
72
|
+
): void {
|
|
73
|
+
buttonManager.appendTo(content);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { View, ButtonManager, BaseButton,
|
|
1
|
+
import { View, ButtonManager, BaseButton, ComponentConfig, AppPipeRegistry, CatalogConfig, AppEventBus } from '../../core/index.js';
|
|
2
|
+
import { FocusTrapHelper, IFocusTrapOptions } from './helpers/focus-trap.helper.js';
|
|
3
|
+
import { IPopoverDomElements, PopoverDomHelper } from './helpers/popover-dom.helper.js';
|
|
2
4
|
|
|
3
5
|
export interface PopoverButton extends BaseButton {
|
|
4
6
|
// if needed for Popover-specific properties
|
|
@@ -6,140 +8,131 @@ export interface PopoverButton extends BaseButton {
|
|
|
6
8
|
|
|
7
9
|
export interface PopoverOptions {
|
|
8
10
|
template: string;
|
|
11
|
+
components?: ComponentConfig[];
|
|
12
|
+
catalogs?: CatalogConfig[];
|
|
9
13
|
viewName?: string;
|
|
10
14
|
onClose?: () => void;
|
|
11
15
|
buttons?: PopoverButton[];
|
|
16
|
+
focusTrapOptions?: IFocusTrapOptions;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
export class PopoverView extends View {
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private
|
|
20
|
+
private _onClose?: () => void;
|
|
21
|
+
private _overlay: HTMLElement | null = null;
|
|
22
|
+
private _buttonManager: ButtonManager;
|
|
23
|
+
private _prevTitle: string | undefined;
|
|
24
|
+
private _catalogs: CatalogConfig[];
|
|
25
|
+
private _focusTrap: FocusTrapHelper | null = null;
|
|
26
|
+
private _focusTrapOptions: IFocusTrapOptions;
|
|
27
|
+
private _components: ComponentConfig[];
|
|
18
28
|
|
|
19
29
|
constructor({
|
|
20
30
|
template,
|
|
21
31
|
buttons = [],
|
|
22
32
|
onClose,
|
|
23
|
-
viewName
|
|
33
|
+
viewName,
|
|
34
|
+
components = [],
|
|
35
|
+
catalogs = [],
|
|
36
|
+
focusTrapOptions = {}
|
|
24
37
|
}: PopoverOptions) {
|
|
25
|
-
super({template, tagName: 'div', mountTarget: 'body'
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
super({ template, tagName: 'div', mountTarget: 'body' });
|
|
39
|
+
this._onClose = onClose;
|
|
40
|
+
this._components = components;
|
|
41
|
+
this._catalogs = catalogs;
|
|
42
|
+
this._buttonManager = this.createButtonManager(buttons);
|
|
43
|
+
this.setOptionalViewName(viewName);
|
|
44
|
+
this._focusTrapOptions = focusTrapOptions;
|
|
45
|
+
AppEventBus.subscribe('popover-close', () => this.destroy());
|
|
32
46
|
}
|
|
33
47
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
...
|
|
38
|
-
|
|
39
|
-
if (btn.callback) {
|
|
40
|
-
btn.callback();
|
|
41
|
-
}
|
|
42
|
-
this.destroy();
|
|
43
|
-
}
|
|
44
|
-
}));
|
|
48
|
+
public childConfigs(): ComponentConfig[] {
|
|
49
|
+
return [
|
|
50
|
+
...this._components,
|
|
51
|
+
...this.catalogConfigs()
|
|
52
|
+
];
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
public catalogConfigs(): ComponentConfig[] {
|
|
56
|
+
const configs: ComponentConfig[] = []
|
|
57
|
+
this._catalogs.map((catalog) => {
|
|
58
|
+
configs.push(...this.catalogConfig(catalog));
|
|
59
|
+
})
|
|
60
|
+
return configs;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
wrapper.className = 'popover-wrapper';
|
|
56
|
-
return wrapper;
|
|
63
|
+
public override shouldUpdateMetaContent(): boolean {
|
|
64
|
+
return false;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
content.classList.add('popover-content');
|
|
62
|
-
ViewRenderManager.cleanupOptionalContent();
|
|
63
|
-
return content;
|
|
64
|
-
}
|
|
67
|
+
public override render(): HTMLElement {
|
|
68
|
+
document.documentElement.classList.add('no-scroll');
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
const closeButton = document.createElement('button');
|
|
68
|
-
closeButton.className = 'popover-close-button';
|
|
69
|
-
closeButton.innerHTML = '×';
|
|
70
|
-
closeButton.setAttribute('aria-label', 'Close');
|
|
71
|
-
// bind method onCloseClick
|
|
72
|
-
closeButton.setAttribute('data-event', 'click:onCloseClick');
|
|
73
|
-
content.prepend(closeButton);
|
|
74
|
-
}
|
|
70
|
+
const { wrapper, overlay } = this.createPopoverDomElements();
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.destroy();
|
|
79
|
-
}
|
|
72
|
+
this._focusTrap = this.createFocusTrap(wrapper, overlay);
|
|
73
|
+
this._focusTrap.activate();
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
this.
|
|
75
|
+
AppEventBus.emit('popover-opened');
|
|
76
|
+
this._overlay = overlay;
|
|
77
|
+
return overlay;
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
80
|
+
public override destroy(): void {
|
|
81
|
+
this._focusTrap?.deactivate();
|
|
82
|
+
if (this._prevTitle) document.title = this._prevTitle;
|
|
83
|
+
|
|
84
|
+
this._overlay = PopoverDomHelper.removeDomElements(this._overlay, () => {
|
|
85
|
+
document.documentElement.classList.remove('no-scroll');
|
|
86
|
+
super.destroy();
|
|
87
|
+
if (typeof this._onClose === 'function') {
|
|
88
|
+
this._onClose();
|
|
89
|
+
}
|
|
88
90
|
});
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
}
|
|
93
|
+
private setOptionalViewName(viewName: string | undefined): void {
|
|
94
|
+
if (viewName) {
|
|
95
|
+
this._prevTitle = document.title;
|
|
96
|
+
document.title = AppPipeRegistry.apply('capitalize', viewName);
|
|
97
|
+
}
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
+
private createButtonManager(buttons: PopoverButton[]): ButtonManager {
|
|
101
|
+
return new ButtonManager(
|
|
102
|
+
this,
|
|
103
|
+
buttons,
|
|
104
|
+
{ containerClassName: 'popover-buttons' }
|
|
105
|
+
);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const wrapper = this.createWrapper();
|
|
107
|
-
const content = this.createContent();
|
|
108
|
-
|
|
109
|
-
this.appendCloseButton(content);
|
|
110
|
-
this.appendFooterButtons(content);
|
|
111
|
-
|
|
112
|
-
wrapper.appendChild(content);
|
|
113
|
-
this.overlay.appendChild(wrapper);
|
|
114
|
-
document.body.appendChild(this.overlay);
|
|
108
|
+
private createPopoverDomElements(): IPopoverDomElements {
|
|
109
|
+
const content = super.render();
|
|
110
|
+
const { overlay, wrapper } = PopoverDomHelper.createDomElements(content, this._buttonManager);
|
|
115
111
|
|
|
116
112
|
this.autoBindEvents();
|
|
117
113
|
this.parseDataAttributes();
|
|
118
114
|
this.animateIn(wrapper);
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return this.overlay;
|
|
116
|
+
return { overlay, wrapper };
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
wrapper?.remove()
|
|
133
|
-
this.overlay?.remove();
|
|
134
|
-
this.overlay = null;
|
|
135
|
-
}, 250); // match transition duration
|
|
136
|
-
}
|
|
119
|
+
private createFocusTrap(wrapper: HTMLElement, overlay: HTMLElement): FocusTrapHelper {
|
|
120
|
+
return new FocusTrapHelper({
|
|
121
|
+
container: wrapper,
|
|
122
|
+
overlay: overlay,
|
|
123
|
+
options: this._focusTrapOptions
|
|
124
|
+
});
|
|
125
|
+
}
|
|
137
126
|
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
// Handles the close button (via data-event bound in appendCloseButton)
|
|
128
|
+
private onCloseClick(): void {
|
|
129
|
+
this.destroy();
|
|
130
|
+
}
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
private animateIn(wrapper: HTMLElement): void {
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
this._overlay?.classList.add('show');
|
|
135
|
+
wrapper.classList.add('show');
|
|
136
|
+
});
|
|
144
137
|
}
|
|
145
|
-
}
|
|
138
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {SecurityHelper} from "../../core/base-classes/base-component/helpers/security.helper.js";
|
|
2
|
+
|
|
1
3
|
export type NotifyOptions = {
|
|
2
4
|
type?: 'info' | 'success' | 'error';
|
|
3
5
|
position?: 'top' | 'bottom';
|
|
@@ -37,16 +39,17 @@ export function notify(message: string, options: NotifyOptions = {}): void {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
function displayNext() {
|
|
42
|
+
function displayNext(): void {
|
|
41
43
|
if (queue.length === 0) {
|
|
42
44
|
isDisplaying = false;
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
isDisplaying = true;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
let { message, options } = queue.shift()!;
|
|
50
|
+
let { type = 'info', position = 'bottom', duration = 3000, actionText, onAction } = options;
|
|
51
|
+
message = SecurityHelper.sanitizeTemplate(message);
|
|
52
|
+
actionText = SecurityHelper.sanitizeTemplate(actionText);
|
|
50
53
|
const snackbar = document.createElement('div');
|
|
51
54
|
snackbar.className = `app-snackbar app-snackbar--${type} app-snackbar--${position}`;
|
|
52
55
|
snackbar.innerHTML = `
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* Prevent scroll when popover is active */
|
|
2
|
+
html.no-scroll {
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.popover-overlay {
|
|
7
|
+
z-index: 99;
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
backdrop-filter: blur(5px);
|
|
11
|
+
background-color: rgba(0, 0, 0, .1);
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
opacity: 0;
|
|
16
|
+
animation: hide-pop 0.3s ease forwards;
|
|
17
|
+
&.show {
|
|
18
|
+
animation: show-pop 0.3s ease forwards;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.popover-wrapper {
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
position: relative;
|
|
25
|
+
overscroll-behavior: none;
|
|
26
|
+
border-radius: 1rem;
|
|
27
|
+
width: 90%;
|
|
28
|
+
height: 90%;
|
|
29
|
+
transform: scale(0.95);
|
|
30
|
+
opacity: 0;
|
|
31
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
32
|
+
-moz-transition: opacity 0.3s ease, -moz-transform 0.3s ease;
|
|
33
|
+
-webkit-transition: opacity 0.3s ease, -webkit-transform 0.3s ease;
|
|
34
|
+
-o-transition: opacity 0.3s ease, -o-transform 0.3s ease;
|
|
35
|
+
&.show {
|
|
36
|
+
transform: scale(1);
|
|
37
|
+
-moz-transform: scale(1);
|
|
38
|
+
-webkit-transform: scale(1);
|
|
39
|
+
-o-transform: scale(1);
|
|
40
|
+
opacity: 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.popover-content {
|
|
45
|
+
position: relative;
|
|
46
|
+
height: 100%;
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
-webkit-overflow-scrolling: touch;
|
|
49
|
+
overscroll-behavior: contain;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.popover-close-button {
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0.5rem;
|
|
55
|
+
right: 0.75rem;
|
|
56
|
+
background: none;
|
|
57
|
+
border: none;
|
|
58
|
+
font-size: 1.5rem;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
padding: 0 1rem;
|
|
61
|
+
&:focus:not(:focus-visible) {
|
|
62
|
+
}
|
|
63
|
+
&:focus-visible {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.popover-buttons {
|
|
68
|
+
display: flex;
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
gap: 1rem;
|
|
71
|
+
margin-top: 1.5rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.popover-buttons button {
|
|
75
|
+
background-color: inherit;
|
|
76
|
+
transition: ease-in-out 0.3s;
|
|
77
|
+
padding: 0.5rem 1rem;
|
|
78
|
+
font-size: 1rem;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
&:hover {
|
|
81
|
+
}
|
|
82
|
+
@media (hover: none) {
|
|
83
|
+
&:hover {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes show-pop {
|
|
89
|
+
from { opacity: 0 }
|
|
90
|
+
to { opacity: 1 }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@keyframes hide-pop {
|
|
94
|
+
from { opacity: 1 }
|
|
95
|
+
to { opacity: 0 }
|
|
96
|
+
}
|