@nutin/cli 1.3.0 → 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 (35) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +8 -9
  3. package/lib/index.mjs +18 -18
  4. package/lib/src/context-builder.mjs +21 -16
  5. package/lib/src/print.mjs +1 -0
  6. package/lib/src/project-generator.mjs +5 -5
  7. package/lib/src/prompts.mjs +13 -85
  8. package/lib/src/version.mjs +1 -1
  9. package/package.json +1 -1
  10. package/templates/base/builder.config.js.hbs +1 -0
  11. package/templates/base/src/app/globals.d.ts.hbs +12 -8
  12. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +25 -8
  13. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +34 -20
  14. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +34 -0
  15. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +11 -12
  16. package/templates/base/src/core/base-classes/component/component.ts.hbs +13 -17
  17. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
  18. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +2 -1
  19. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +0 -33
  20. package/templates/base/src/core/services/router/router.ts.hbs +0 -1
  21. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +35 -7
  22. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +31 -8
  23. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
  24. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
  25. package/templates/base/src/libs/popover/popover.ts.hbs +84 -91
  26. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +6 -3
  27. package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
  28. package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
  29. package/templates/base/src/styles/core/_index.scss.hbs +6 -0
  30. package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
  31. package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
  32. package/templates/base/src/styles/main.scss.hbs +1 -1
  33. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +41 -8
  34. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +14 -0
  35. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
@@ -3,9 +3,9 @@ import { TokenHelper } from './token.helper.js';
3
3
 
4
4
  export class EventHelper {
5
5
  public static bindEvents(
6
- component: BaseComponent,
7
- element: HTMLElement,
8
- eventListeners: Array<[string, EventListener]>
6
+ component: BaseComponent,
7
+ element: HTMLElement,
8
+ eventListeners: Array<[EventTarget, string, EventListener]>
9
9
  ): void {
10
10
  element.querySelectorAll('[data-event]').forEach(el => {
11
11
  const parts = el.getAttribute('data-event')!.split(':');
@@ -24,11 +24,10 @@ export class EventHelper {
24
24
  }
25
25
 
26
26
  public static destroyEvents(
27
- element: HTMLElement,
28
- eventListeners: Array<[string, EventListener]>
27
+ eventListeners: Array<[EventTarget, string, EventListener]>
29
28
  ): void {
30
- eventListeners.forEach(([event, listener]) => {
31
- element.removeEventListener(event, listener);
29
+ eventListeners.forEach(([target, event, listener]) => {
30
+ target.removeEventListener(event, listener);
32
31
  });
33
32
  }
34
33
 
@@ -46,12 +45,12 @@ export class EventHelper {
46
45
  }
47
46
 
48
47
  private static addEvent(
49
- target: EventTarget,
50
- event: string,
51
- listener: EventListener,
52
- eventListeners: Array<[string, EventListener]>
48
+ target: EventTarget,
49
+ event: string,
50
+ listener: EventListener,
51
+ eventListeners: Array<[EventTarget, string, EventListener]>
53
52
  ): void {
54
53
  target.addEventListener(event, listener);
55
- eventListeners.push([event, listener]);
54
+ eventListeners.push([target, event, listener]);
56
55
  }
57
56
  }
@@ -56,24 +56,14 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
56
56
  normalizeKeys = [],
57
57
  defaults = {},
58
58
  }: ComponentOptions) {
59
- let normalizedConfig = ConfigHelper.normalize(config, defaults);
60
- normalizedConfig = ConfigHelper.normalizeStrings(normalizedConfig, normalizeKeys);
61
-
62
- const template = templateFn(normalizedConfig);
63
-
59
+ const template = ConfigHelper.createNormalizedTemplate({
60
+ config, defaults, normalizeKeys, templateFn,
61
+ });
64
62
  super({template, mountTarget, tagName});
65
63
 
66
- this.config = normalizeKeys && config
67
- ? ConfigHelper.normalizeStrings(config, normalizeKeys)
68
- : config ?? ({} as T);
69
-
64
+ this.config = ConfigHelper.setConfigValue(config, normalizeKeys);
70
65
  this.props = props;
71
-
72
- this.buttonManager = new ButtonManager(
73
- this,
74
- this.props.buttons,
75
- { containerClassName: 'component-buttons' }
76
- );
66
+ this.buttonManager = this.createButtonManager();
77
67
  }
78
68
 
79
69
  public getValues(): Record<string, string> {
@@ -88,15 +78,21 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
88
78
  return super.render();
89
79
  }
90
80
 
81
+ private createButtonManager(): ButtonManager {
82
+ return new ButtonManager(
83
+ this,
84
+ this.props.buttons,
85
+ { containerClassName: 'component-buttons' }
86
+ );
87
+ }
88
+
91
89
  private applyProps(): void {
92
90
  if (this.props.className) {
93
91
  this.element.classList.add(this.props.className);
94
92
  }
95
-
96
93
  if (this.props.style) {
97
94
  this.element.style = this.props.style;
98
95
  }
99
-
100
96
  this.applyDataBindings();
101
97
  }
102
98
 
@@ -23,4 +23,21 @@ export class ConfigHelper {
23
23
 
24
24
  return normalized;
25
25
  }
26
+
27
+ public static createNormalizedTemplate<T extends Record<string, any>>({
28
+ config,
29
+ defaults = {},
30
+ normalizeKeys = [],
31
+ templateFn = () => ''
32
+ }: T) {
33
+ let normalizedConfig = ConfigHelper.normalize(config, defaults);
34
+ normalizedConfig = ConfigHelper.normalizeStrings(normalizedConfig, normalizeKeys);
35
+ return templateFn(normalizedConfig);
36
+ }
37
+
38
+ public static setConfigValue<T extends Record<string, any>>(config: T, normalizeKeys: (keyof T)[]): T {
39
+ if (normalizeKeys) return ConfigHelper.normalizeStrings(config, normalizeKeys) as T;
40
+ else if (config) return config as T
41
+ else return ({} as T);
42
+ }
26
43
  }
@@ -5,7 +5,8 @@ import { {{#if i18n}}I18nService, {{/if}}View } from "../../../index.js";
5
5
  */
6
6
  export class NavigationManager {
7
7
  public static normalizePath(path: string): string {
8
- const url = new URL(path, window.location.origin);
8
+ const collapsed = path.replace(/\/\/+/g, '/');
9
+ const url = new URL(collapsed, window.location.origin);
9
10
  return (url.pathname || '/').replace(/\/+$/, '') || '/';
10
11
  }
11
12
 
@@ -49,43 +49,10 @@ export class ViewRenderManager {
49
49
  view.render();
50
50
 
51
51
  {{/if}}
52
-
53
- this.cleanupOptionalContent();
54
-
55
52
  this.emitEvent('view-mount', view.viewName);
56
53
  return view;
57
54
  }
58
55
 
59
- public static cleanupOptionalContent(): void {
60
- const isEmpty = (el: HTMLElement): boolean => {
61
- const attrName = el.dataset.optional?.trim();
62
-
63
- if (attrName && attrName !== "") {
64
- const attrValue = el.getAttribute(attrName);
65
- return !attrValue || attrValue.trim() === "" || attrValue === "undefined";
66
- }
67
-
68
- if (el instanceof HTMLImageElement) {
69
- return !el.src || el.src.trim() === "";
70
- }
71
-
72
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
73
- return !el.value?.trim();
74
- }
75
-
76
- if (el instanceof HTMLMediaElement || el instanceof HTMLSourceElement) {
77
- return !el.getAttribute("src");
78
- }
79
-
80
- const content = el.textContent?.trim();
81
- return !content || content === "undefined";
82
- };
83
-
84
- document.querySelectorAll<HTMLElement>("[data-optional]").forEach(el => {
85
- if (isEmpty(el)) el.remove();
86
- });
87
- }
88
-
89
56
  private static emitEvent(event: EventKey, viewName: string): void {
90
57
  AppEventBus.emit(event, viewName);
91
58
  }
@@ -5,7 +5,6 @@ import { NavigationManager } from './helpers/navigation-manager.helper.js';
5
5
 
6
6
  // centralized export
7
7
  export { Routes, RouteGuard } from './helpers/route-guard-manager.helper.js';
8
- export { ViewRenderManager } from './helpers/view-render-manager.helper.js';
9
8
 
10
9
  /**
11
10
  * IRouter is a type alias for the instance of Router, not a true interface
@@ -45,21 +45,15 @@ export class AnchorManager {
45
45
  private createAnchorContainer(): HTMLSpanElement {
46
46
  const container = document.createElement('span');
47
47
  const anchor = this.createAnchor();
48
-
49
48
  container.appendChild(anchor);
50
-
51
49
  return container;
52
50
  }
53
51
 
54
52
  private createAnchor(): HTMLElement {
55
53
  const anchor = document.createElement('a');
56
-
57
54
  this.setAnchorRef(anchor);
58
-
59
55
  AttributesHelper.setAttributes(anchor, this.config);
60
-
61
56
  this.handleClick(anchor);
62
-
63
57
  return anchor;
64
58
  }
65
59
 
@@ -79,7 +73,41 @@ export class AnchorManager {
79
73
  anchor?.addEventListener('click', (e) => {
80
74
  e.preventDefault();
81
75
  const target = document.getElementById(id);
82
- target?.scrollIntoView({ behavior: 'smooth' });
76
+ this.scrollToTarget(target);
77
+ this.accessibilityFeatures(target, id);
83
78
  });
84
79
  }
80
+
81
+ private scrollToTarget(target: HTMLElement | null): void {
82
+ const targetPosition = target?.offsetTop;
83
+ window.scrollTo({
84
+ top: targetPosition,
85
+ behavior: 'smooth'
86
+ })
87
+ }
88
+
89
+ private accessibilityFeatures(target: HTMLElement | null, id: string) {
90
+ this.setFocus(target);
91
+ this.setAriaLive(target, id);
92
+ }
93
+
94
+ private setFocus(target: HTMLElement | null): void {
95
+ setTimeout(() => {
96
+ target?.setAttribute('tabindex', '-1');
97
+ target?.focus({ preventScroll: true });
98
+ // Remove tabindex after focus
99
+ target?.addEventListener('blur', () => {
100
+ target?.removeAttribute('tabindex');
101
+ }, { once: true });
102
+ }, 100);
103
+ }
104
+
105
+ private setAriaLive(target: HTMLElement | null, id: string): void {
106
+ const announcement = document.createElement('div');
107
+ announcement.setAttribute('aria-live', 'polite');
108
+ announcement.className = 'sr-only';
109
+ announcement.textContent = `Navigated to ${ target?.textContent || id }`;
110
+ document.body.appendChild(announcement);
111
+ setTimeout(() => document.body.removeChild(announcement), 1000);
112
+ }
85
113
  }
@@ -2,6 +2,7 @@ import { AttributesHelper, IAttributesConfig } from "../helpers/attributes.helpe
2
2
 
3
3
  export interface BaseButton extends IAttributesConfig {
4
4
  callback: () => void;
5
+ label?: string;
5
6
  }
6
7
 
7
8
  export interface ButtonContainerOptions {
@@ -22,10 +23,6 @@ export interface ButtonContainerOptions {
22
23
  pipes?: string;
23
24
  }
24
25
 
25
- interface BaseButton extends IAttributesConfig {
26
- callback: () => void;
27
- }
28
-
29
26
  interface ButtonContainerOptions {
30
27
  containerClassName?: string;
31
28
  containerStyles?: string;
@@ -61,9 +58,13 @@ export class ButtonManager {
61
58
  container.style = this.containerOptions.containerStyles || '';
62
59
 
63
60
  this.buttons.forEach((config, index) => {
64
- const button = this.createButton(config, index);
65
- container.appendChild(button);
66
- this.bindButtonCallback(config, index);
61
+ if (config.label) {
62
+ return this.createButtonWithCheckbox(config, index, container);
63
+ } else {
64
+ const button = this.createButton(config, index);
65
+ container.appendChild(button);
66
+ this.bindButtonCallback(config, index);
67
+ }
67
68
  });
68
69
 
69
70
  return container;
@@ -76,6 +77,25 @@ export class ButtonManager {
76
77
  }
77
78
  }
78
79
 
80
+ private createButtonWithCheckbox(config: BaseButton, index: number, container: HTMLElement): HTMLElement {
81
+ if (!config.label) return container;
82
+ const checkbox = document.createElement('input');
83
+ checkbox.setAttribute('type', 'checkbox');
84
+ checkbox.setAttribute('id', config.label);
85
+ checkbox.hidden = true;
86
+ container.appendChild(checkbox);
87
+
88
+ const label = document.createElement('label');
89
+ label.setAttribute('for', config.label);
90
+
91
+ const button = this.createButton(config, index);
92
+ label.appendChild(button);
93
+
94
+ container.appendChild(label);
95
+ this.bindButtonCallback(config, index, checkbox);
96
+ return container;
97
+ }
98
+
79
99
  private createButton(config: BaseButton, index: number): HTMLElement {
80
100
  const button = document.createElement('button');
81
101
 
@@ -90,12 +110,15 @@ export class ButtonManager {
90
110
  button.setAttribute('data-event', `click:onButtonClick_${index}`);
91
111
  }
92
112
 
93
- private bindButtonCallback(config: BaseButton, index: number): void {
113
+ private bindButtonCallback(config: BaseButton, index: number, checkbox?: HTMLInputElement): void {
94
114
  const methodName = `onButtonClick_${index}`;
95
115
 
96
116
  this.component[methodName] = () => {
97
117
  if (typeof config.callback === 'function') {
98
118
  config.callback();
119
+ if (checkbox) {
120
+ checkbox.checked = !checkbox.checked;
121
+ }
99
122
  }
100
123
  };
101
124
  }
@@ -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
+ }