@syncfusion/ej2-splitbuttons 24.2.5 → 25.1.35-579988

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