@syncfusion/ej2-splitbuttons 20.2.45 → 20.3.52

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 (118) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/ej2-splitbuttons.min.js +10 -0
  3. package/dist/ej2-splitbuttons.umd.min.js +1 -1
  4. package/dist/global/ej2-splitbuttons.min.js +1 -1
  5. package/dist/global/index.d.ts +1 -1
  6. package/dist/ts/button-group/button-group.ts +84 -0
  7. package/dist/ts/common/common.ts +203 -0
  8. package/dist/ts/drop-down-button/drop-down-button.ts +831 -0
  9. package/dist/ts/progress-button/progress-button.ts +689 -0
  10. package/dist/ts/split-button/split-button.ts +501 -0
  11. package/package.json +10 -9
  12. package/src/drop-down-button/drop-down-button-model.d.ts +4 -4
  13. package/styles/bootstrap-dark.css +26 -5
  14. package/styles/bootstrap.css +21 -0
  15. package/styles/bootstrap4.css +22 -1
  16. package/styles/bootstrap5-dark.css +23 -0
  17. package/styles/bootstrap5.css +23 -0
  18. package/styles/button-group/_bootstrap-dark-definition.scss +2 -1
  19. package/styles/button-group/_fabric-dark-definition.scss +2 -1
  20. package/styles/button-group/_group-button-mixin.scss +3 -3
  21. package/styles/button-group/_highcontrast-light-definition.scss +3 -3
  22. package/styles/button-group/_layout.scss +1 -1
  23. package/styles/button-group/_material-dark-definition.scss +2 -1
  24. package/styles/button-group/_theme.scss +1 -1
  25. package/styles/button-group/bootstrap-dark.css +2 -0
  26. package/styles/button-group/bootstrap.css +2 -0
  27. package/styles/button-group/bootstrap4.css +2 -0
  28. package/styles/button-group/bootstrap5-dark.css +2 -0
  29. package/styles/button-group/bootstrap5.css +2 -0
  30. package/styles/button-group/fabric-dark.css +2 -0
  31. package/styles/button-group/fabric.css +2 -0
  32. package/styles/button-group/fluent-dark.css +2 -0
  33. package/styles/button-group/fluent.css +2 -0
  34. package/styles/button-group/highcontrast-light.css +2 -0
  35. package/styles/button-group/highcontrast.css +2 -0
  36. package/styles/button-group/material-dark.css +2 -0
  37. package/styles/button-group/material.css +2 -0
  38. package/styles/button-group/tailwind-dark.css +2 -0
  39. package/styles/button-group/tailwind.css +2 -0
  40. package/styles/drop-down-button/_all.scss +1 -1
  41. package/styles/drop-down-button/_bootstrap-dark-definition.scss +1 -0
  42. package/styles/drop-down-button/_bootstrap4-definition.scss +2 -2
  43. package/styles/drop-down-button/_bootstrap5-definition.scss +0 -1
  44. package/styles/drop-down-button/_fabric-dark-definition.scss +2 -1
  45. package/styles/drop-down-button/_fluent-definition.scss +1 -1
  46. package/styles/drop-down-button/_fusionnew-definition.scss +0 -1
  47. package/styles/drop-down-button/_highcontrast-definition.scss +0 -1
  48. package/styles/drop-down-button/_highcontrast-light-definition.scss +2 -1
  49. package/styles/drop-down-button/_layout.scss +3 -2
  50. package/styles/drop-down-button/_material-dark-definition.scss +2 -1
  51. package/styles/drop-down-button/_material3-definition.scss +0 -1
  52. package/styles/drop-down-button/_tailwind-definition.scss +0 -1
  53. package/styles/drop-down-button/_theme.scss +21 -0
  54. package/styles/drop-down-button/bootstrap-dark.css +18 -0
  55. package/styles/drop-down-button/bootstrap.css +18 -0
  56. package/styles/drop-down-button/bootstrap4.css +18 -0
  57. package/styles/drop-down-button/bootstrap5-dark.css +19 -0
  58. package/styles/drop-down-button/bootstrap5.css +19 -0
  59. package/styles/drop-down-button/fabric-dark.css +18 -0
  60. package/styles/drop-down-button/fabric.css +18 -0
  61. package/styles/drop-down-button/fluent-dark.css +19 -0
  62. package/styles/drop-down-button/fluent.css +19 -0
  63. package/styles/drop-down-button/highcontrast-light.css +18 -0
  64. package/styles/drop-down-button/highcontrast.css +18 -0
  65. package/styles/drop-down-button/material-dark.css +18 -0
  66. package/styles/drop-down-button/material.css +18 -0
  67. package/styles/drop-down-button/tailwind-dark.css +19 -0
  68. package/styles/drop-down-button/tailwind.css +19 -0
  69. package/styles/fabric-dark.css +21 -0
  70. package/styles/fabric.css +21 -0
  71. package/styles/fluent-dark.css +23 -0
  72. package/styles/fluent.css +23 -0
  73. package/styles/highcontrast-light.css +21 -0
  74. package/styles/highcontrast.css +23 -2
  75. package/styles/material-dark.css +21 -0
  76. package/styles/material.css +21 -0
  77. package/styles/progress-button/_bootstrap-dark-definition.scss +6 -5
  78. package/styles/progress-button/_bootstrap4-definition.scss +1 -1
  79. package/styles/progress-button/_fabric-dark-definition.scss +1 -0
  80. package/styles/progress-button/_highcontrast-definition.scss +1 -1
  81. package/styles/progress-button/_layout.scss +43 -42
  82. package/styles/progress-button/_material-dark-definition.scss +1 -0
  83. package/styles/progress-button/_theme.scss +23 -22
  84. package/styles/progress-button/bootstrap-dark.css +6 -5
  85. package/styles/progress-button/bootstrap.css +1 -0
  86. package/styles/progress-button/bootstrap4.css +2 -1
  87. package/styles/progress-button/bootstrap5-dark.css +1 -0
  88. package/styles/progress-button/bootstrap5.css +1 -0
  89. package/styles/progress-button/fabric-dark.css +1 -0
  90. package/styles/progress-button/fabric.css +1 -0
  91. package/styles/progress-button/fluent-dark.css +1 -0
  92. package/styles/progress-button/fluent.css +1 -0
  93. package/styles/progress-button/highcontrast-light.css +1 -0
  94. package/styles/progress-button/highcontrast.css +3 -2
  95. package/styles/progress-button/material-dark.css +1 -0
  96. package/styles/progress-button/material.css +1 -0
  97. package/styles/progress-button/tailwind-dark.css +1 -0
  98. package/styles/progress-button/tailwind.css +1 -0
  99. package/styles/split-button/_bootstrap-dark-definition.scss +2 -1
  100. package/styles/split-button/_bootstrap4-definition.scss +2 -1
  101. package/styles/split-button/_bootstrap5-definition.scss +0 -1
  102. package/styles/split-button/_fabric-dark-definition.scss +1 -0
  103. package/styles/split-button/_fluent-definition.scss +0 -1
  104. package/styles/split-button/_fusionnew-definition.scss +0 -1
  105. package/styles/split-button/_highcontrast-light-definition.scss +2 -2
  106. package/styles/split-button/_layout.scss +2 -1
  107. package/styles/split-button/_material-dark-definition.scss +2 -1
  108. package/styles/split-button/_material3-definition.scss +0 -1
  109. package/styles/split-button/_tailwind-definition.scss +0 -1
  110. package/styles/split-button/_theme.scss +1 -1
  111. package/styles/split-button/bootstrap5-dark.css +1 -0
  112. package/styles/split-button/bootstrap5.css +1 -0
  113. package/styles/split-button/fluent-dark.css +1 -0
  114. package/styles/split-button/fluent.css +1 -0
  115. package/styles/split-button/tailwind-dark.css +1 -0
  116. package/styles/split-button/tailwind.css +1 -0
  117. package/styles/tailwind-dark.css +23 -0
  118. package/styles/tailwind.css +23 -0
@@ -0,0 +1,831 @@
1
+ import { Collection, Event, NotifyPropertyChanges, detach, Property, EventHandler, EmitType } from '@syncfusion/ej2-base';
2
+ import { addClass, INotifyPropertyChanged, getUniqueID, rippleEffect, getComponent } from '@syncfusion/ej2-base';
3
+ import { attributes, Component, closest, select, KeyboardEventArgs, SanitizeHtmlHelper } from '@syncfusion/ej2-base';
4
+ import { classList, removeClass } from '@syncfusion/ej2-base';
5
+ import { Button } from '@syncfusion/ej2-buttons';
6
+ import { Popup } from '@syncfusion/ej2-popups';
7
+ import { SplitButton } from '../split-button/split-button';
8
+ import { MenuEventArgs, BeforeOpenCloseMenuEventArgs, OpenCloseMenuEventArgs, upDownKeyHandler } from './../common/common';
9
+ import { getModel, SplitButtonIconPosition, Item, setBlankIconStyle } from './../common/common';
10
+ import { ItemModel } from './../common/common-model';
11
+ import { DropDownButtonModel } from './drop-down-button-model';
12
+
13
+ const classNames: ClassNames = {
14
+ DISABLED: 'e-disabled',
15
+ FOCUS: 'e-focused',
16
+ ICON: 'e-menu-icon',
17
+ ITEM: 'e-item',
18
+ POPUP: 'e-dropdown-popup',
19
+ RTL: 'e-rtl',
20
+ SEPARATOR: 'e-separator',
21
+ VERTICAL: 'e-vertical'
22
+ };
23
+
24
+ /**
25
+ * DropDownButton component is used to toggle contextual overlays for displaying list of action items.
26
+ * It can contain both text and images.
27
+ * ```html
28
+ * <button id="element">DropDownButton</button>
29
+ * ```
30
+ * ```typescript
31
+ * <script>
32
+ * var dropDownButtonObj = new DropDownButton({items: [{ text: 'Action1' }, { text: 'Action2' },{ text: 'Action3' }]);
33
+ * dropDownButtonObj.appendTo("#element");
34
+ * </script>
35
+ * ```
36
+ */
37
+ @NotifyPropertyChanges
38
+ export class DropDownButton extends Component<HTMLButtonElement> implements INotifyPropertyChanged {
39
+ /** @hidden */
40
+ public dropDown: Popup;
41
+ protected button: Button;
42
+ /** @hidden */
43
+ public activeElem: HTMLElement[];
44
+ private rippleFn: Function;
45
+ private delegateMousedownHandler: Function;
46
+ private isPopupCreated: boolean = true;
47
+
48
+ /**
49
+ * Defines the content of the DropDownButton element that can either be a text or HTML elements.
50
+ *
51
+ * @default ""
52
+ */
53
+ @Property('')
54
+ public content: string;
55
+
56
+ /**
57
+ * Defines class/multiple classes separated by a space in the DropDownButton element. The
58
+ * DropDownButton size and styles can be customized by using this.
59
+ *
60
+ * @default ""
61
+ */
62
+ @Property('')
63
+ public cssClass: string;
64
+
65
+ /**
66
+ * Specifies a value that indicates whether the DropDownButton is `disabled` or not.
67
+ *
68
+ * @default false.
69
+ */
70
+ @Property(false)
71
+ public disabled: boolean;
72
+
73
+ /**
74
+ * Defines class/multiple classes separated by a space for the DropDownButton that is used to
75
+ * include an icon. DropDownButton can also include font icon and sprite image.
76
+ *
77
+ * @default ""
78
+ */
79
+ @Property('')
80
+ public iconCss: string;
81
+
82
+ /**
83
+ * Positions the icon before/top of the text content in the DropDownButton. The possible values are:
84
+ * * Left: The icon will be positioned to the left of the text content.
85
+ * * Top: The icon will be positioned to the top of the text content.
86
+ *
87
+ * @default "Left"
88
+ */
89
+ @Property('Left')
90
+ public iconPosition: SplitButtonIconPosition;
91
+
92
+ /**
93
+ * Defines whether to allow the cross-scripting site or not.
94
+ *
95
+ * @default false
96
+ */
97
+ @Property(false)
98
+ public enableHtmlSanitizer: boolean;
99
+
100
+ /**
101
+ * Specifies action items with its properties which will be rendered as DropDownButton popup.
102
+ *
103
+ * @default []
104
+ */
105
+ @Collection<ItemModel>([], Item)
106
+ public items: ItemModel[];
107
+
108
+ /**
109
+ * Specifies the popup element creation on open.
110
+ *
111
+ * @default false
112
+ */
113
+ @Property(false)
114
+ public createPopupOnClick: boolean;
115
+
116
+ /**
117
+ * Allows to specify the DropDownButton popup item element.
118
+ *
119
+ * @default ""
120
+ */
121
+ @Property('')
122
+ public target: string | Element;
123
+
124
+ /**
125
+ * Specifies the event to close the DropDownButton popup.
126
+ *
127
+ * @default ""
128
+ */
129
+ @Property('')
130
+ public closeActionEvents: string;
131
+
132
+ /**
133
+ * Triggers while rendering each Popup item of DropDownButton.
134
+ *
135
+ * @event beforeItemRender
136
+ */
137
+ @Event()
138
+ public beforeItemRender: EmitType<MenuEventArgs>;
139
+
140
+ /**
141
+ * Triggers before opening the DropDownButton popup.
142
+ *
143
+ * @event beforeOpen
144
+ */
145
+ @Event()
146
+ public beforeOpen: EmitType<BeforeOpenCloseMenuEventArgs>;
147
+
148
+ /**
149
+ * Triggers before closing the DropDownButton popup.
150
+ *
151
+ * @event beforeClose
152
+ */
153
+ @Event()
154
+ public beforeClose: EmitType<BeforeOpenCloseMenuEventArgs>;
155
+
156
+ /**
157
+ * Triggers while closing the DropDownButton popup.
158
+ *
159
+ * @event close
160
+ */
161
+ @Event()
162
+ public close: EmitType<OpenCloseMenuEventArgs>;
163
+
164
+ /**
165
+ * Triggers while opening the DropDownButton popup.
166
+ *
167
+ * @event open
168
+ */
169
+ @Event()
170
+ public open: EmitType<OpenCloseMenuEventArgs>;
171
+
172
+ /**
173
+ * Triggers while selecting action item in DropDownButton popup.
174
+ *
175
+ * @event select
176
+ */
177
+ @Event()
178
+ public select: EmitType<MenuEventArgs>;
179
+
180
+ /**
181
+ * Triggers once the component rendering is completed.
182
+ *
183
+ * @event created
184
+ */
185
+ @Event()
186
+ public created: EmitType<Event>;
187
+
188
+ /**
189
+ * Constructor for creating the widget
190
+ *
191
+ * @param {DropDownButtonModel} options - Specifies dropdown button model
192
+ * @param {string|HTMLButtonElement} element - Specifies element
193
+ * @hidden
194
+ */
195
+ public constructor(options?: DropDownButtonModel, element?: string | HTMLButtonElement) {
196
+ super(options, <string | HTMLButtonElement>element);
197
+ }
198
+
199
+ protected preRender(): void {
200
+ /** */
201
+ }
202
+
203
+ /**
204
+ * Get the properties to be maintained in the persisted state.
205
+ *
206
+ * @returns {string} - Persist data
207
+ */
208
+ public getPersistData(): string {
209
+ return this.addOnPersist([]);
210
+ }
211
+
212
+ /**
213
+ * To open/close DropDownButton popup based on current state of the DropDownButton.
214
+ *
215
+ * @returns {void}
216
+ */
217
+ public toggle(): void {
218
+ if (this.canOpen()) {
219
+ this.openPopUp();
220
+ } else {
221
+ this.closePopup();
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Initialize the Component rendering
227
+ *
228
+ * @returns {void}
229
+ * @private
230
+ */
231
+ public render(): void {
232
+ this.initialize();
233
+ if (!this.disabled) {
234
+ this.wireEvents();
235
+ }
236
+ this.renderComplete();
237
+ }
238
+ /**
239
+ * Adds a new item to the menu. By default, new item appends to the list as the last item,
240
+ * but you can insert based on the text parameter.
241
+ *
242
+ * @param { ItemModel[] } items - Specifies an array of JSON data.
243
+ * @param { string } text - Specifies the text to insert the newly added item in the menu.
244
+ * @returns {void}.
245
+ */
246
+ public addItems(items: ItemModel[], text?: string): void {
247
+ let newItem: ItemModel;
248
+ let idx: number = this.items.length;
249
+ for (let j: number = 0, len: number = this.items.length; j < len; j++) {
250
+ if (text === this.items[j].text) {
251
+ idx = j;
252
+ break;
253
+ }
254
+ }
255
+ for (let i: number = items.length - 1 ; i >= 0; i--) {
256
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
257
+ newItem = new Item(this as any, 'items', items[i], true);
258
+ this.items.splice(idx, 0, newItem);
259
+ }
260
+ if (!this.canOpen()) { this.createItems(); }
261
+ }
262
+ /**
263
+ * Removes the items from the menu.
264
+ *
265
+ * @param { string[] } items - Specifies an array of string to remove the items.
266
+ * @param { string } isUniqueId - Set `true` if specified items is a collection of unique id.
267
+ * @returns {void}.
268
+ */
269
+ public removeItems(items: string[], isUniqueId?: boolean): void {
270
+ let refresh: boolean = false;
271
+ for (let i: number = 0, len: number = items.length; i < len; i++) {
272
+ for (let j: number = 0, len: number = this.items.length; j < len; j++) {
273
+ if (items[i] === (isUniqueId ? this.items[j].id : this.items[j].text)) {
274
+ this.items.splice(j, 1); refresh = true;
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ if (refresh && this.getULElement()) { this.createItems(); }
280
+ }
281
+
282
+ private createPopup(): void {
283
+ const div: HTMLElement = this.createElement('div', {
284
+ className: classNames.POPUP,
285
+ id: this.element.id + '-popup'
286
+ });
287
+ document.body.appendChild(div);
288
+ this.dropDown = new Popup(div, {
289
+ relateTo: this.element,
290
+ collision: { X: 'fit', Y: 'flip' },
291
+ position: { X: 'left', Y: 'bottom' },
292
+ targetType: 'relative',
293
+ content: this.target ? this.getTargetElement() as HTMLElement : '',
294
+ enableRtl: this.enableRtl
295
+ });
296
+ if (this.dropDown.element.style.position === 'fixed') {
297
+ this.dropDown.refreshPosition(this.element);
298
+ }
299
+ this.dropDown.hide();
300
+ attributes(this.element, {
301
+ ['aria-haspopup']: this.items.length || this.target ? 'true' : 'false', ['aria-expanded']: 'false',
302
+ ['aria-owns']: this.getPopUpElement().id, ['type']: 'button',
303
+ ['aria-label']: this.element.textContent ? this.element.textContent : 'dropdownbutton'
304
+ });
305
+ if (this.cssClass) { addClass([div], this.cssClass.split(' ')); }
306
+ this.isPopupCreated = true;
307
+ }
308
+
309
+ private getTargetElement(): Element {
310
+ return typeof (this.target) === 'string' ? select(this.target as string) : this.target;
311
+ }
312
+
313
+ private createItems(appendItems?: boolean): void {
314
+ const items: ItemModel[] = this.items;
315
+ const showIcon: boolean = this.hasIcon(this.items, 'iconCss');
316
+ let span: Element; let item: ItemModel; let li: Element; let eventArgs: MenuEventArgs;
317
+ let ul: HTMLElement = this.getULElement();
318
+ if (ul) {
319
+ ul.innerHTML = '';
320
+ } else {
321
+ ul = this.createElement('ul', {
322
+ attrs: { 'role': 'menu', 'tabindex': '0' }
323
+ });
324
+ }
325
+ for (let i: number = 0; i < items.length; i++) {
326
+ item = items[i];
327
+ const tempItem: string = item.text;
328
+ li = this.createElement('li', {
329
+ innerHTML: item.url ? '' : tempItem,
330
+ className: item.separator ? classNames.ITEM + ' ' + classNames.SEPARATOR : classNames.ITEM,
331
+ attrs: { 'role': 'menuItem', 'tabindex': '-1', 'aria-label': tempItem },
332
+ id: item.id ? item.id : getUniqueID('e-' + this.getModuleName() + '-item')
333
+ });
334
+ if (this.enableHtmlSanitizer) {
335
+ li.textContent = item.url ? '' : tempItem;
336
+ }
337
+ else {
338
+ li.innerHTML = item.url ? '' : tempItem;
339
+ }
340
+ if (item.url) {
341
+ li.appendChild(this.createAnchor(item));
342
+ li.classList.add('e-url');
343
+ }
344
+ if (item.iconCss) {
345
+ span = this.createElement('span', { className: classNames.ICON + ' ' + item.iconCss });
346
+ if (item.url) {
347
+ li.childNodes[0].appendChild(span);
348
+ } else {
349
+ li.insertBefore(span, li.childNodes[0]);
350
+ }
351
+ } else {
352
+ if (showIcon && !item.separator) {
353
+ li.classList.add('e-blank-icon');
354
+ }
355
+ }
356
+ if (item.disabled) { li.classList.add('e-disabled'); }
357
+ eventArgs = { item: item, element: li as HTMLElement };
358
+ this.trigger('beforeItemRender', eventArgs);
359
+ ul.appendChild(li);
360
+ }
361
+ if (appendItems) {
362
+ this.getPopUpElement().appendChild(ul);
363
+ }
364
+ if (showIcon) { setBlankIconStyle(this.getPopUpElement()); }
365
+ }
366
+
367
+ private hasIcon(items: ItemModel[], field: string): boolean {
368
+ for (let i: number = 0, len: number = items.length; i < len; i++) {
369
+ if ((<{ [key: string]: object }>items[i])[field]) {
370
+ return true;
371
+ }
372
+ }
373
+ return false;
374
+ }
375
+
376
+ private createAnchor(item: ItemModel): HTMLElement {
377
+ const tempItem: string = (this.enableHtmlSanitizer) ? SanitizeHtmlHelper.sanitize(item.text) : item.text;
378
+ return this.createElement('a', { className: 'e-menu-text e-menu-url', innerHTML: tempItem, attrs: { 'href': item.url } });
379
+ }
380
+
381
+ private initialize(): void {
382
+ this.button = new Button({
383
+ iconCss: this.iconCss, iconPosition: this.iconPosition, cssClass: this.cssClass, content: this.content,
384
+ disabled: this.disabled, enableRtl: this.enableRtl, enablePersistence: this.enablePersistence
385
+ });
386
+ this.button.createElement = this.createElement;
387
+ this.button.appendTo(this.element);
388
+ if (!this.element.id) {
389
+ this.element.id = getUniqueID('e-' + this.getModuleName());
390
+ }
391
+ this.appendArrowSpan();
392
+ this.setActiveElem([this.element]);
393
+ if ((this.target && !this.isColorPicker()) || !this.createPopupOnClick) {
394
+ this.createPopup();
395
+ } else {
396
+ this.isPopupCreated = false;
397
+ }
398
+ }
399
+
400
+ private isColorPicker(): boolean {
401
+ if (!this.element) {
402
+ return false;
403
+ }
404
+ const prevElem: HTMLElement = this.element.previousSibling as HTMLElement;
405
+ if (prevElem && prevElem.classList && prevElem.classList.contains('e-split-colorpicker')) {
406
+ return true;
407
+ }
408
+ return false;
409
+ }
410
+
411
+ private appendArrowSpan(): void {
412
+ this.element.appendChild(this.createElement('span', {
413
+ className: 'e-btn-icon e-icons ' + 'e-icon-' + (this.cssClass.indexOf(classNames.VERTICAL) > -1
414
+ ? 'bottom' : 'right') + ' e-caret'
415
+ }));
416
+ }
417
+
418
+ protected setActiveElem(elem: HTMLElement[]): void {
419
+ this.activeElem = elem;
420
+ }
421
+
422
+ /**
423
+ * Get component name.
424
+ *
425
+ * @returns {string} - Module Name
426
+ * @private
427
+ */
428
+ public getModuleName(): string {
429
+ return 'dropdown-btn';
430
+ }
431
+
432
+ private canOpen(): boolean {
433
+ let val: boolean = false;
434
+ if (this.isPopupCreated) {
435
+ val = this.getPopUpElement().classList.contains('e-popup-close');
436
+ }
437
+ return val;
438
+ }
439
+
440
+ /**
441
+ * Destroys the widget.
442
+ *
443
+ * @returns {void}
444
+ */
445
+ public destroy(): void {
446
+ super.destroy();
447
+ if (this.getModuleName() === 'dropdown-btn') {
448
+ let classList: string[];
449
+ if (this.element.querySelector('span.e-caret')) {
450
+ detach(this.element.querySelector('span.e-caret'));
451
+ }
452
+ if (this.cssClass) {
453
+ classList = this.cssClass.split(' ');
454
+ }
455
+ this.button.destroy();
456
+ if (classList) {
457
+ removeClass([this.element], classList);
458
+ }
459
+ removeClass(this.activeElem, ['e-active']);
460
+ const attrList: string[] = this.element.getAttribute('class') ? ['aria-haspopup', 'aria-expanded', 'aria-owns', 'type']
461
+ : ['aria-haspopup', 'aria-expanded', 'aria-owns', 'type', 'class'];
462
+ attrList.forEach((key: string) => {
463
+ this.element.removeAttribute(key);
464
+ });
465
+ this.popupUnWireEvents();
466
+ this.destroyPopup();
467
+ this.isPopupCreated = false;
468
+ if (!this.disabled) {
469
+ this.unWireEvents();
470
+ }
471
+ }
472
+ }
473
+
474
+ protected destroyPopup(): void {
475
+ if (this.isPopupCreated) {
476
+ this.dropDown.destroy();
477
+ if (this.getPopUpElement()) {
478
+ const popupEle: HTMLElement = document.getElementById(this.getPopUpElement().id);
479
+ if (popupEle) {
480
+ removeClass([popupEle], ['e-popup-open', 'e-popup-close']);
481
+ detach(popupEle);
482
+ }
483
+ }
484
+ EventHandler.remove(this.getPopUpElement(), 'click', this.clickHandler);
485
+ EventHandler.remove(this.getPopUpElement(), 'keydown', this.keyBoardHandler);
486
+ if (this.isPopupCreated) {
487
+ this.dropDown = undefined;
488
+ }
489
+ }
490
+ this.isPopupCreated = false;
491
+ }
492
+
493
+ protected getPopUpElement(): HTMLElement {
494
+ let val: HTMLElement = null;
495
+ if (this.dropDown) {
496
+ val = this.dropDown.element;
497
+ }
498
+ return val;
499
+ }
500
+
501
+ protected getULElement(): HTMLElement {
502
+ let val: HTMLElement = null;
503
+ if (this.getPopUpElement()) {
504
+ val = this.getPopUpElement().children[0] as HTMLElement;
505
+ }
506
+ return val;
507
+ }
508
+
509
+ protected wireEvents(): void {
510
+ this.delegateMousedownHandler = this.mousedownHandler.bind(this);
511
+ if (!this.createPopupOnClick) {
512
+ EventHandler.add(document, 'mousedown touchstart', this.delegateMousedownHandler, this);
513
+ }
514
+ EventHandler.add(this.element, 'click', this.clickHandler, this);
515
+ EventHandler.add(this.element, 'keydown', this.keyBoardHandler, this);
516
+ }
517
+
518
+ protected popupWireEvents(): void {
519
+ const popupElement: HTMLElement = this.getPopUpElement();
520
+ if (this.createPopupOnClick) {
521
+ EventHandler.add(document, 'mousedown touchstart', this.delegateMousedownHandler, this);
522
+ }
523
+ if (popupElement) {
524
+ EventHandler.add(popupElement, 'click', this.clickHandler, this);
525
+ EventHandler.add(popupElement, 'keydown', this.keyBoardHandler, this);
526
+ if (this.closeActionEvents) {
527
+ EventHandler.add(popupElement, this.closeActionEvents, this.focusoutHandler, this);
528
+ }
529
+ }
530
+ this.rippleFn = rippleEffect(popupElement, { selector: '.' + classNames.ITEM });
531
+ }
532
+
533
+ protected popupUnWireEvents(): void {
534
+ const popupElement: HTMLElement = this.getPopUpElement();
535
+ if (this.createPopupOnClick) {
536
+ EventHandler.remove(document, 'mousedown touchstart', this.delegateMousedownHandler);
537
+ }
538
+ if (popupElement && popupElement.parentElement) {
539
+ EventHandler.remove(popupElement, 'click', this.clickHandler);
540
+ EventHandler.remove(popupElement, 'keydown', this.keyBoardHandler);
541
+ if (this.closeActionEvents) {
542
+ EventHandler.remove(popupElement, this.closeActionEvents, this.focusoutHandler);
543
+ }
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Handles the keyboard interactions.
549
+ *
550
+ * @param {KeyboardEventArgs} e - Specifies keyboard event args.
551
+ * @returns {void}
552
+ * @hidden
553
+ */
554
+ public keyBoardHandler(e: KeyboardEventArgs): void {
555
+ if (e.target === this.element && (e.keyCode === 9 || (!e.altKey && e.keyCode === 40) || e.keyCode === 38)) {
556
+ return;
557
+ }
558
+ switch (e.keyCode) {
559
+ case 38:
560
+ case 40:
561
+ if (e.altKey && (e.keyCode === 38 || e.keyCode === 40)) {
562
+ this.keyEventHandler(e);
563
+ } else {
564
+ this.upDownKeyHandler(e);
565
+ }
566
+ break;
567
+ case 9:
568
+ case 13:
569
+ case 27:
570
+ case 32:
571
+ this.keyEventHandler(e);
572
+ break;
573
+ }
574
+ }
575
+
576
+ protected upDownKeyHandler(e: KeyboardEventArgs): void {
577
+ if (this.target && (e.keyCode === 38 || e.keyCode === 40)) {
578
+ return;
579
+ }
580
+ e.preventDefault();
581
+ upDownKeyHandler(this.getULElement(), e.keyCode);
582
+ }
583
+
584
+ private keyEventHandler(e: KeyboardEventArgs): void {
585
+ if (this.target && (e.keyCode === 13 || e.keyCode === 9 )) {
586
+ return;
587
+ }
588
+ if (e.target && (e.target as Element).className.indexOf('e-edit-template') > -1 && e.keyCode === 32) {
589
+ return;
590
+ }
591
+ if (e.keyCode !== 9) {
592
+ e.preventDefault();
593
+ }
594
+ if (e.keyCode === 27 || e.keyCode === 38 || e.keyCode === 9) {
595
+ if (!this.canOpen()) {
596
+ this.closePopup(e, this.element);
597
+ }
598
+ } else {
599
+ this.clickHandler(e);
600
+ }
601
+ }
602
+
603
+ private getLI(elem: Element): Element {
604
+ return elem.tagName === 'LI' ? elem : closest(elem, 'li');
605
+ }
606
+
607
+ private mousedownHandler(e: MouseEvent): void {
608
+ const trgt: HTMLElement = e.target as HTMLElement;
609
+ if (this.dropDown && !this.canOpen() && !(closest(trgt, '[id="' + this.getPopUpElement().id + '"]')
610
+ || closest(trgt, '[id="' + this.element.id + '"]'))) {
611
+ this.closePopup(e);
612
+ }
613
+ }
614
+
615
+ private focusoutHandler(e: MouseEvent): void {
616
+ if (this.isPopupCreated && !this.canOpen()) {
617
+ this.closePopup(e);
618
+ }
619
+ }
620
+
621
+ protected clickHandler(e: MouseEvent | KeyboardEventArgs): void {
622
+ const trgt: HTMLElement = e.target as HTMLElement;
623
+ if (closest(trgt, '[id="' + this.element.id + '"]')) {
624
+ if (!this.createPopupOnClick || (this.target && !this.isColorPicker())) {
625
+ if (this.getPopUpElement().classList.contains('e-popup-close')) {
626
+ this.openPopUp(e);
627
+ } else {
628
+ this.closePopup(e);
629
+ }
630
+ } else if (this.isPopupCreated) {
631
+ this.closePopup(e, this.activeElem[0]);
632
+ } else {
633
+ this.createPopup();
634
+ this.openPopUp(e);
635
+ }
636
+ } else {
637
+ if (closest(trgt, '[id="' + this.getPopUpElement().id + '"]')) {
638
+ let eventArgs: MenuEventArgs;
639
+ let liIdx: number;
640
+ let item: ItemModel;
641
+ const li: Element = this.getLI(trgt);
642
+ if (li) {
643
+ liIdx = Array.prototype.indexOf.call(this.getULElement().children, li);
644
+ item = this.items[liIdx];
645
+ if (item) {
646
+ eventArgs = { element: li as HTMLElement, item: item };
647
+ this.trigger('select', eventArgs);
648
+ }
649
+ this.closePopup(e, this.activeElem[0]);
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ private openPopUp(e: MouseEvent | KeyboardEventArgs = null): void {
656
+ let isReact: boolean = false; const popupElem: HTMLElement = this.getPopUpElement();
657
+ if (!this.target) {
658
+ this.createItems(true);
659
+ } else {
660
+ if (this.activeElem.length > 1) {
661
+ let splitButton: SplitButton = getComponent(this.activeElem[0], 'split-btn');
662
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
663
+ if ((splitButton as any).isReact && popupElem.childNodes.length < 1) {
664
+ isReact = true;
665
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
666
+ (splitButton as any).appendReactElement(this.getTargetElement(), this.getPopUpElement());
667
+ this.renderReactTemplates();
668
+ }
669
+ } else {
670
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
671
+ if ((this as any).isReact && popupElem.childNodes.length < 1) {
672
+ isReact = true;
673
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
674
+ (this as any).appendReactElement(this.getTargetElement(), this.getPopUpElement());
675
+ this.renderReactTemplates();
676
+ }
677
+ }
678
+ }
679
+ const ul: HTMLElement = this.getULElement();
680
+ this.popupWireEvents();
681
+ const beforeOpenArgs: BeforeOpenCloseMenuEventArgs = { element: ul, items: this.items, event: e, cancel: false };
682
+ this.trigger('beforeOpen', beforeOpenArgs, (observedArgs: BeforeOpenCloseMenuEventArgs) => {
683
+ if (!observedArgs.cancel) {
684
+ const ul: HTMLElement = this.getULElement();
685
+ this.dropDown.show(null, this.element);
686
+ addClass([this.element], 'e-active');
687
+ this.element.setAttribute('aria-expanded', 'true');
688
+ if (ul) {
689
+ ul.focus();
690
+ }
691
+ const openArgs: OpenCloseMenuEventArgs = { element: ul, items: this.items };
692
+ this.trigger('open', openArgs);
693
+ }
694
+ });
695
+ }
696
+
697
+ private closePopup(e: MouseEvent | KeyboardEventArgs = null, focusEle?: HTMLElement): void {
698
+ const ul: HTMLElement = this.getULElement();
699
+ const beforeCloseArgs: BeforeOpenCloseMenuEventArgs = { element: ul, items: this.items, event: e, cancel: false };
700
+ let popupElement: HTMLElement = this.getPopUpElement();
701
+ if (popupElement) {
702
+ EventHandler.remove(popupElement, 'keydown', this.keyBoardHandler);
703
+ }
704
+ this.trigger('beforeClose', beforeCloseArgs, (observedArgs: BeforeOpenCloseMenuEventArgs) => {
705
+ if (!observedArgs.cancel) {
706
+ this.popupUnWireEvents();
707
+ const ul: HTMLElement = this.getULElement();
708
+ let selectedLi: Element;
709
+ if (ul) {
710
+ selectedLi = ul.querySelector('.e-selected');
711
+ }
712
+ if (selectedLi) { selectedLi.classList.remove('e-selected'); }
713
+ this.dropDown.hide();
714
+ removeClass(this.activeElem, 'e-active');
715
+ this.element.setAttribute('aria-expanded', 'false');
716
+ if (focusEle) {
717
+ focusEle.focus();
718
+ }
719
+ const closeArgs: OpenCloseMenuEventArgs = { element: ul, items: this.items };
720
+ this.trigger('close', closeArgs);
721
+ if (!this.target && ul) { detach(ul); }
722
+ if (!this.target || this.isColorPicker()) {
723
+ if (this.createPopupOnClick) { this.destroyPopup(); }
724
+ }
725
+ if (this.target) {
726
+ this.isPopupCreated = this.createPopupOnClick ? false : true;
727
+ }
728
+ }
729
+ });
730
+ }
731
+
732
+ protected unWireEvents(): void {
733
+ if (!this.createPopupOnClick) {
734
+ EventHandler.remove(document, 'mousedown touchstart', this.delegateMousedownHandler);
735
+ }
736
+ EventHandler.remove(this.element, 'click', this.clickHandler);
737
+ EventHandler.remove(this.element, 'keydown', this.keyBoardHandler);
738
+ if (this.isPopupCreated) {
739
+ EventHandler.remove(this.getPopUpElement(), 'click', this.clickHandler);
740
+ EventHandler.remove(this.getPopUpElement(), 'keydown', this.keyBoardHandler);
741
+ }
742
+ }
743
+
744
+ /**
745
+ * Called internally if any of the property value changed.
746
+ *
747
+ * @param {DropDownButtonModel} newProp - Specifies new properties
748
+ * @param {DropDownButtonModel} oldProp - Specifies old properties
749
+ * @returns {void}
750
+ * @private
751
+ */
752
+ public onPropertyChanged(newProp: DropDownButtonModel, oldProp: DropDownButtonModel): void {
753
+ const btnModel: string[] = ['content', 'cssClass', 'iconCss', 'iconPosition', 'disabled', 'enableRtl'];
754
+ this.button.setProperties(getModel(newProp, btnModel));
755
+ let popupElement: Element;
756
+ if (this.isPopupCreated) {
757
+ popupElement = this.getPopUpElement();
758
+ this.dropDown.setProperties(getModel(newProp, ['enableRtl']));
759
+ }
760
+ for (const prop of Object.keys(newProp)) {
761
+ switch (prop) {
762
+ case 'content':
763
+ if (!this.element.querySelector('span.e-caret')) {
764
+ this.appendArrowSpan();
765
+ }
766
+ break;
767
+ case 'disabled':
768
+ if (newProp.disabled) {
769
+ this.unWireEvents();
770
+ if (this.isPopupCreated && !this.canOpen()) {
771
+ this.closePopup();
772
+ }
773
+ } else {
774
+ this.wireEvents();
775
+ }
776
+ break;
777
+ case 'cssClass':
778
+ if (newProp.cssClass.indexOf(classNames.VERTICAL) > -1) {
779
+ const arrowSpan: Element = this.element.querySelector('span.e-caret');
780
+ classList(arrowSpan, ['e-icon-bottom'], ['e-icon-right']);
781
+ }
782
+ if (this.isPopupCreated) {
783
+ if (oldProp.cssClass) {
784
+ removeClass([popupElement], oldProp.cssClass.split(' '));
785
+ }
786
+ if (newProp.cssClass) {
787
+ addClass([popupElement], newProp.cssClass.split(' '));
788
+ }
789
+ }
790
+ break;
791
+ case 'target':
792
+ this.dropDown.content = this.getTargetElement() as HTMLElement;
793
+ this.dropDown.dataBind();
794
+ break;
795
+ case 'items':
796
+ if (this.isPopupCreated && this.getULElement()) { this.createItems(); }
797
+ break;
798
+ case 'createPopupOnClick':
799
+ if (newProp.createPopupOnClick) {
800
+ this.destroyPopup();
801
+ } else {
802
+ this.createPopup();
803
+ }
804
+ break;
805
+ }
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Sets the focus to DropDownButton
811
+ * its native method
812
+ *
813
+ * @public
814
+ * @returns {void}
815
+ */
816
+ public focusIn(): void {
817
+ this.element.focus();
818
+ }
819
+
820
+ }
821
+
822
+ interface ClassNames {
823
+ DISABLED: string;
824
+ FOCUS: string;
825
+ ICON: string;
826
+ ITEM: string;
827
+ POPUP: string;
828
+ RTL: string;
829
+ SEPARATOR: string;
830
+ VERTICAL: string;
831
+ }