@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +25 -18
  3. package/lib/index.mjs +23 -20
  4. package/lib/src/context-builder.mjs +40 -0
  5. package/lib/src/file-generator.mjs +26 -126
  6. package/lib/src/git-manager.mjs +1 -1
  7. package/lib/src/json-generator.mjs +31 -0
  8. package/lib/src/package-manager.mjs +21 -18
  9. package/lib/src/print.mjs +1 -0
  10. package/lib/src/project-generator.mjs +53 -0
  11. package/lib/src/prompts.mjs +16 -75
  12. package/lib/src/template-compiler.mjs +1 -1
  13. package/lib/src/utils.mjs +9 -9
  14. package/lib/src/version.mjs +1 -0
  15. package/package.json +1 -1
  16. package/templates/base/.gitignore.hbs +1 -0
  17. package/templates/base/builder.config.js.hbs +8 -0
  18. package/templates/base/src/app/globals.d.ts.hbs +12 -8
  19. package/templates/base/src/app/main.ts.hbs +2 -1
  20. package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
  21. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  22. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
  23. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +46 -26
  24. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +36 -22
  25. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
  26. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +60 -13
  27. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +16 -17
  28. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
  29. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
  30. package/templates/base/src/core/base-classes/component/component.ts.hbs +20 -24
  31. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +19 -2
  32. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
  33. package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
  34. package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
  35. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
  36. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
  37. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
  38. package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
  39. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
  40. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +15 -14
  41. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
  42. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +4 -36
  43. package/templates/base/src/core/services/router/router.ts.hbs +45 -46
  44. package/templates/base/src/core/services/store/store.ts.hbs +6 -6
  45. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +41 -15
  46. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +40 -19
  47. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
  48. package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
  49. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
  50. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
  51. package/templates/base/src/libs/popover/popover.ts.hbs +89 -96
  52. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +7 -4
  53. package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
  54. package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
  55. package/templates/base/src/styles/core/_index.scss.hbs +6 -0
  56. package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
  57. package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
  58. package/templates/base/src/styles/main.scss.hbs +1 -1
  59. package/templates/base/tools/builder/builder.js.hbs +9 -3
  60. package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
  61. package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
  62. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
  63. package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
  64. package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
  65. package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
  66. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
  67. package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
  68. package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
  69. package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
  70. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +76 -52
  71. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +31 -0
  72. package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
  73. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
  74. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
  75. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
  76. package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
  77. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  78. package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
  79. package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
  80. /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 = '&times;';
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, ViewRenderManager } from '../../core/index.js';
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 onClose?: () => void;
16
- private overlay: HTMLElement | null = null;
17
- private buttonManager: ButtonManager;
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 = 'popover',
33
+ viewName,
34
+ components = [],
35
+ catalogs = [],
36
+ focusTrapOptions = {}
24
37
  }: PopoverOptions) {
25
- super({template, tagName: 'div', mountTarget: 'body', viewName});
26
- this.onClose = onClose;
27
- this.buttonManager = new ButtonManager(
28
- this,
29
- this.wrapButtonCallbacks(buttons),
30
- { containerClassName: 'popover-buttons' }
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
- // Wraps button callbacks to include destroy() call
35
- private wrapButtonCallbacks(buttons: PopoverButton[]): PopoverButton[] {
36
- return buttons.map(btn => ({
37
- ...btn,
38
- callback: () => {
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
- private createOverlay(): HTMLElement {
48
- const overlay = document.createElement('div');
49
- overlay.className = 'popover-overlay';
50
- return overlay;
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
- private createWrapper(): HTMLElement {
54
- const wrapper = document.createElement('div');
55
- wrapper.className = 'popover-wrapper';
56
- return wrapper;
63
+ public override shouldUpdateMetaContent(): boolean {
64
+ return false;
57
65
  }
58
66
 
59
- private createContent(): HTMLElement {
60
- const content = super.render();
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
- private appendCloseButton(content: HTMLElement): void {
67
- const closeButton = document.createElement('button');
68
- closeButton.className = 'popover-close-button';
69
- closeButton.innerHTML = '&times;';
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
- // Handles the close button (via data-event bound in appendCloseButton)
77
- private onCloseClick(): void {
78
- this.destroy();
79
- }
72
+ this._focusTrap = this.createFocusTrap(wrapper, overlay);
73
+ this._focusTrap.activate();
80
74
 
81
- private appendFooterButtons(content: HTMLElement): void {
82
- this.buttonManager.appendTo(content);
75
+ AppEventBus.emit('popover-opened');
76
+ this._overlay = overlay;
77
+ return overlay;
83
78
  }
84
79
 
85
- private bindOverlayClose(): void {
86
- this.overlay?.addEventListener('click', (e: MouseEvent) => {
87
- if (e.target === this.overlay) this.destroy();
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 animateIn(wrapper: HTMLElement): void {
92
- requestAnimationFrame(() => {
93
- this.overlay?.classList.add('show');
94
- wrapper.classList.add('show');
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
- public override shouldUpdateMetaContent(): boolean {
99
- return false;
100
+ private createButtonManager(buttons: PopoverButton[]): ButtonManager {
101
+ return new ButtonManager(
102
+ this,
103
+ buttons,
104
+ { containerClassName: 'popover-buttons' }
105
+ );
100
106
  }
101
107
 
102
- public override render(): HTMLElement {
103
- document.body.classList.add('no-scroll');
104
- this.overlay = this.createOverlay();
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
- this.bindOverlayClose();
121
-
122
- return this.overlay;
116
+ return { overlay, wrapper };
123
117
  }
124
118
 
125
- public override destroy(): void {
126
- if (this.overlay) {
127
- const wrapper = this.overlay.querySelector('.popover-wrapper');
128
- this.overlay.classList.remove('show');
129
- wrapper?.classList.remove('show');
130
-
131
- setTimeout(() => {
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
- document.body.classList.remove('no-scroll');
139
- super.destroy();
127
+ // Handles the close button (via data-event bound in appendCloseButton)
128
+ private onCloseClick(): void {
129
+ this.destroy();
130
+ }
140
131
 
141
- if (typeof this.onClose === 'function') {
142
- this.onClose();
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
- const { message, options } = queue.shift()!;
48
- const { type = 'info', position = 'bottom', duration = 3000, actionText, onAction } = options;
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 = `
@@ -8,6 +8,7 @@ $background-color: rgb(255 255 255); // WHITE
8
8
  $text-color: rgb(0 0 0); // BLACK
9
9
  $primary-color: rgb(0 0 0); // BLACK
10
10
  $secondary-color: rgb(0 0 0); // BLACK
11
+ $accent-color: rgb(0 0 0); // BLACK
11
12
 
12
13
  // snackbar colours
13
14
  $snack-success: rgb(76 175 80);
@@ -0,0 +1,10 @@
1
+ .sr-only {
2
+ position: absolute;
3
+ width: 1px;
4
+ height: 1px;
5
+ overflow: hidden;
6
+ clip: rect(1px, 1px, 1px, 1px);
7
+ white-space: nowrap;
8
+ text-decoration: none;
9
+ outline: none;
10
+ }
@@ -0,0 +1,6 @@
1
+ @forward "anchors";
2
+ @forward "popover";
3
+ @forward "snackbar";
4
+ {{#if stylinNutin}}
5
+ @forward "utilities";
6
+ {{/if}}
@@ -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
+ }