@progress/kendo-angular-buttons 8.0.1-dev.202208111121 → 8.1.0

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.
@@ -2,30 +2,36 @@
2
2
  * Copyright © 2021 Progress Software Corporation. All rights reserved.
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
- import { Component } from '@angular/core';
5
+ import { EventEmitter, Component, Input, Output } from '@angular/core';
6
6
  /* eslint-disable import/no-deprecated */
7
7
  import { Subscription, fromEvent, merge } from 'rxjs';
8
- import { filter } from 'rxjs/operators';
8
+ import { filter, tap } from 'rxjs/operators';
9
9
  import { KeyEvents } from './../navigation/key-events';
10
10
  import { NavigationAction } from './../navigation/navigation-action';
11
- import { isDocumentAvailable, guid, Keys } from '@progress/kendo-angular-common';
12
- import { isPresent } from './../util';
11
+ import { isDocumentAvailable, guid, Keys, isChanged } from '@progress/kendo-angular-common';
13
12
  import { validatePackage } from '@progress/kendo-licensing';
14
13
  import { packageMetadata } from '../package-metadata';
14
+ import { PreventableEvent } from '../preventable-event';
15
+ import { isPresent } from '../util';
15
16
  import * as i0 from "@angular/core";
16
17
  import * as i1 from "./../focusable/focus.service";
17
18
  import * as i2 from "./../navigation/navigation.service";
18
- import * as i3 from "@progress/kendo-angular-l10n";
19
+ import * as i3 from "@progress/kendo-angular-popup";
20
+ import * as i4 from "@progress/kendo-angular-l10n";
21
+ import * as i5 from "./container.service";
19
22
  /**
20
23
  * @hidden
21
24
  */
22
25
  export class ListButton {
23
- constructor(focusService, navigationService, wrapperRef, _zone, localization, cdr) {
26
+ constructor(focusService, navigationService, wrapperRef, _zone, popupService, elRef, localization, cdr, containerService) {
24
27
  this.focusService = focusService;
25
28
  this.navigationService = navigationService;
26
29
  this.wrapperRef = wrapperRef;
27
30
  this._zone = _zone;
31
+ this.popupService = popupService;
32
+ this.elRef = elRef;
28
33
  this.cdr = cdr;
34
+ this.containerService = containerService;
29
35
  this._open = false;
30
36
  this._disabled = false;
31
37
  this._active = false;
@@ -33,6 +39,22 @@ export class ListButton {
33
39
  this.listId = guid();
34
40
  this._isFocused = false;
35
41
  this.subs = new Subscription();
42
+ this.popupSubs = new Subscription();
43
+ /**
44
+ * Specifies the [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the component.
45
+ */
46
+ this.tabIndex = 0;
47
+ /**
48
+ * Fires each time the popup is about to open.
49
+ * This event is preventable. If you cancel the event, the popup will remain closed.
50
+ */
51
+ this.open = new EventEmitter();
52
+ /**
53
+ * Fires each time the popup is about to close.
54
+ * This event is preventable. If you cancel the event, the popup will remain open.
55
+ */
56
+ this.close = new EventEmitter();
57
+ this.isClosePrevented = false;
36
58
  validatePackage(packageMetadata);
37
59
  this.focusService = focusService;
38
60
  this.navigationService = navigationService;
@@ -40,6 +62,75 @@ export class ListButton {
40
62
  this.subs.add(localization.changes.subscribe(({ rtl }) => (this.direction = rtl ? 'rtl' : 'ltr')));
41
63
  this.subscribeEvents();
42
64
  }
65
+ /**
66
+ * Sets the disabled state of the DropDownButton.
67
+ */
68
+ set disabled(value) {
69
+ if (value && this.openState) {
70
+ this.openState = false;
71
+ }
72
+ this._disabled = value;
73
+ }
74
+ get disabled() {
75
+ return this._disabled;
76
+ }
77
+ /**
78
+ * @hidden
79
+ */
80
+ get componentTabIndex() {
81
+ return this.disabled ? (-1) : this.tabIndex;
82
+ }
83
+ get appendTo() {
84
+ const { appendTo } = this.popupSettings;
85
+ if (!appendTo || appendTo === 'root') {
86
+ return undefined;
87
+ }
88
+ return appendTo === 'component' ? this.containerService.container : appendTo;
89
+ }
90
+ /**
91
+ * Configures the popup of the DropDownButton.
92
+ *
93
+ * The available options are:
94
+ * - `animate: Boolean`—Controls the popup animation. By default, the open and close animations are enabled.
95
+ * - `popupClass: String`—Specifies a list of CSS classes that are used to style the popup.
96
+ * - `appendTo: "root" | "component" | ViewContainerRef`—Specifies the component to which the popup will be appended.
97
+ * - `align: "left" | "center" | "right"`—Specifies the alignment of the popup.
98
+ */
99
+ set popupSettings(settings) {
100
+ this._popupSettings = Object.assign({ animate: true, popupClass: '' }, settings);
101
+ }
102
+ get popupSettings() {
103
+ return this._popupSettings;
104
+ }
105
+ /**
106
+ * @hidden
107
+ */
108
+ get anchorAlign() {
109
+ let align = { horizontal: this.popupSettings.align || 'left', vertical: 'bottom' };
110
+ if (this.direction === 'rtl' && !isPresent(this.popupSettings.align)) {
111
+ align.horizontal = 'right';
112
+ }
113
+ return align;
114
+ }
115
+ /**
116
+ * @hidden
117
+ */
118
+ get popupAlign() {
119
+ let align = { horizontal: this.popupSettings.align || 'left', vertical: 'top' };
120
+ if (this.direction === 'rtl' && !isPresent(this.popupSettings.align)) {
121
+ align.horizontal = 'right';
122
+ }
123
+ return align;
124
+ }
125
+ ngOnChanges(changes) {
126
+ if (isChanged("popupSettings", changes) && isPresent(this.popupRef)) {
127
+ const popup = this.popupRef.popup.instance;
128
+ const newSettings = changes.popupSettings.currentValue;
129
+ popup.popupClass = newSettings.popupClass;
130
+ popup.animate = newSettings.animate;
131
+ popup.popupAlign = this.popupAlign;
132
+ }
133
+ }
43
134
  get popupClasses() {
44
135
  const popupClasses = ['k-menu-popup'];
45
136
  if (this._popupSettings.popupClass) {
@@ -50,9 +141,21 @@ export class ListButton {
50
141
  get openState() {
51
142
  return this._open;
52
143
  }
144
+ /**
145
+ * @hidden
146
+ */
53
147
  set openState(open) {
148
+ if (this.disabled) {
149
+ return;
150
+ }
54
151
  this._open = open;
55
152
  }
153
+ /**
154
+ * Returns the current open state of the popup.
155
+ */
156
+ get isOpen() {
157
+ return this._open;
158
+ }
56
159
  /**
57
160
  * @hidden
58
161
  */
@@ -60,23 +163,29 @@ export class ListButton {
60
163
  if (this._disabled) {
61
164
  return;
62
165
  }
63
- this.openState = !this.openState;
64
- if (!this.openState) {
65
- this.focusService.focus(-1);
166
+ this._toggle(!this.openState);
167
+ if (!this.isClosePrevented) {
168
+ this.focusService.focus(this.openState ? 0 : -1);
66
169
  }
67
170
  }
68
171
  /**
69
172
  * @hidden
70
173
  */
71
174
  onItemClick(index) {
175
+ this.togglePopupVisibility();
176
+ if (this.isClosePrevented) {
177
+ this.emitItemClickHandler(index);
178
+ return;
179
+ }
180
+ if (isDocumentAvailable() && !this.isClosePrevented) {
181
+ this.focusButton();
182
+ }
72
183
  this.emitItemClickHandler(index);
73
- setTimeout(() => this.focusWrapper(), 1);
74
184
  }
75
185
  ngOnDestroy() {
76
186
  this.openState = false;
77
- clearTimeout(this.focusFirstTimeout);
78
- clearTimeout(this.blurTimeout);
79
187
  this.subs.unsubscribe();
188
+ this.destroyPopup();
80
189
  }
81
190
  subscribeEvents() {
82
191
  if (!isDocumentAvailable()) {
@@ -93,41 +202,36 @@ export class ListButton {
93
202
  }
94
203
  subscribeComponentBlurredEvent() {
95
204
  this._zone.runOutsideAngular(() => {
96
- this.subs.add(this.navigationService.tab.pipe(filter(() => this._isFocused)).subscribe(this.handleTab.bind(this)));
205
+ this.subs.add(this.navigationService.tab.pipe(filter(() => this._isFocused), tap(() => this.focusButton())).subscribe(this.handleTab.bind(this)));
97
206
  this.subs.add(fromEvent(document, 'click')
98
207
  .pipe(filter((event) => !this.wrapperContains(event.target)), filter(() => this._isFocused))
99
208
  .subscribe(() => this._zone.run(() => this.blurWrapper())));
100
209
  });
101
210
  }
102
211
  subscribeNavigationEvents() {
103
- this.subs.add(this.navigationService.navigate.subscribe(this.focusService.focus.bind(this.focusService)));
104
- this.subs.add(this.navigationService.enterup.subscribe(() => {
105
- this.enterHandler();
106
- this.focusWrapper();
107
- }));
108
- this.subs.add(this.navigationService.open.subscribe(() => {
109
- if (!this._open) {
110
- this.togglePopupVisibility();
111
- this.focusFirstItem();
112
- }
113
- else {
114
- this.focusWrapper();
115
- }
116
- }));
117
- this.subs.add(merge(this.navigationService.close, this.navigationService.esc).subscribe(() => this.focusWrapper()));
212
+ this.subs.add(this.navigationService.navigate
213
+ .subscribe(this.onArrowKeyNavigate.bind(this)));
214
+ this.subs.add(this.navigationService.enterup.subscribe(this.onNavigationEnterUp.bind(this)));
215
+ this.subs.add(this.navigationService.open.subscribe(this.onNavigationOpen.bind(this)));
216
+ this.subs.add(merge(this.navigationService.close, this.navigationService.esc).subscribe(this.onNavigationClose.bind(this)));
118
217
  }
119
- enterHandler() { } // eslint-disable-line
120
218
  /**
121
- * @hidden
219
+ * Toggles the visibility of the popup.
220
+ * If the `toggle` method is used to open or close the popup, the `open` and `close` events will not be fired.
221
+ *
222
+ * @param open - The state of the popup.
122
223
  */
123
- keyDownHandler(event) {
124
- this.keyHandler(event);
224
+ toggle(open) {
225
+ if (this.disabled) {
226
+ return;
227
+ }
228
+ this._toggle((open === undefined) ? !this.openState : open);
125
229
  }
126
230
  /**
127
231
  * @hidden
128
232
  */
129
- keyPressHandler(event) {
130
- this.keyHandler(event, KeyEvents.keypress);
233
+ keyDownHandler(event) {
234
+ this.keyHandler(event);
131
235
  }
132
236
  /**
133
237
  * @hidden
@@ -142,23 +246,22 @@ export class ListButton {
142
246
  if (this._disabled) {
143
247
  return;
144
248
  }
145
- let focused = this.focusService.focused || 0;
146
249
  const eventData = event;
250
+ eventData.stopImmediatePropagation();
251
+ let focused = this.focusService.focused || 0;
147
252
  const action = this.navigationService.process({
148
253
  altKey: eventData.altKey,
149
254
  current: focused,
150
255
  keyCode: eventData.keyCode,
151
256
  keyEvent: keyEvent,
152
257
  max: this._data ? this._data.length - 1 : 0,
153
- min: 0
258
+ min: 0,
259
+ target: event.target
154
260
  });
155
261
  if (action !== NavigationAction.Undefined &&
156
262
  action !== NavigationAction.Tab &&
157
- (action !== NavigationAction.Enter || (action === NavigationAction.Enter && this._open))) {
158
- if (event.keyCode === Keys.Space && action === NavigationAction.EnterUp) {
159
- this._open = false;
160
- }
161
- else {
263
+ (action !== NavigationAction.Enter || (action === NavigationAction.Enter && this.openState))) {
264
+ if (!(event.keyCode === Keys.Space && action === NavigationAction.EnterUp)) {
162
265
  eventData.preventDefault();
163
266
  }
164
267
  }
@@ -171,14 +274,10 @@ export class ListButton {
171
274
  if (dataItem && dataItem.click && !dataItem.disabled) {
172
275
  dataItem.click(dataItem);
173
276
  }
174
- }
175
- focusFirstItem() {
176
- if (this._data && isPresent(this._data[0])) {
177
- this.focusFirstTimeout = setTimeout(() => this.focusService.focus(0), 1);
178
- }
277
+ this.focusService.focus(index);
179
278
  }
180
279
  focusWrapper() {
181
- if (this._open) {
280
+ if (this.openState) {
182
281
  this.togglePopupVisibility();
183
282
  this.focusButton();
184
283
  }
@@ -187,7 +286,10 @@ export class ListButton {
187
286
  return this.wrapper === element || this.wrapper.contains(element);
188
287
  }
189
288
  blurWrapper(emit = true) {
190
- if (this._open) {
289
+ if (!this._isFocused) {
290
+ return;
291
+ }
292
+ if (this.openState) {
191
293
  this.togglePopupVisibility();
192
294
  }
193
295
  this._isFocused = false;
@@ -202,15 +304,99 @@ export class ListButton {
202
304
  }
203
305
  }
204
306
  handleTab() {
205
- this.focusButton();
206
- this.blurWrapper(false);
307
+ this.blurWrapper();
308
+ }
309
+ onNavigationEnterUp() {
310
+ if (!this._disabled && !this.openState) {
311
+ this._active = false;
312
+ }
313
+ if (this.openState) {
314
+ let focused = this.focusService.focused;
315
+ if (isPresent(focused) && focused !== -1) {
316
+ this.emitItemClickHandler(focused);
317
+ }
318
+ }
319
+ this.togglePopupVisibility();
320
+ if (!this.openState && isDocumentAvailable()) {
321
+ this.button.nativeElement.focus();
322
+ }
323
+ }
324
+ onNavigationOpen() {
325
+ if (!this._disabled && !this.openState) {
326
+ this.togglePopupVisibility();
327
+ }
328
+ }
329
+ onNavigationClose() {
330
+ if (this.openState && !this.isClosePrevented) {
331
+ this.togglePopupVisibility();
332
+ if (isDocumentAvailable()) {
333
+ this.button.nativeElement.focus();
334
+ }
335
+ }
336
+ }
337
+ onArrowKeyNavigate({ index }) {
338
+ this.focusService.focus(index);
339
+ }
340
+ _toggle(open) {
341
+ if (this.openState === open) {
342
+ return;
343
+ }
344
+ const eventArgs = new PreventableEvent();
345
+ if (open && !this.openState) {
346
+ this.open.emit(eventArgs);
347
+ }
348
+ else if (!open && this.openState) {
349
+ this.close.emit(eventArgs);
350
+ }
351
+ if (eventArgs.isDefaultPrevented()) {
352
+ this.isClosePrevented = true;
353
+ return;
354
+ }
355
+ this.openState = open;
356
+ this.destroyPopup();
357
+ if (this.openState) {
358
+ this.createPopup();
359
+ }
360
+ }
361
+ createPopup() {
362
+ this.popupRef = this.popupService.open({
363
+ anchor: this.elRef,
364
+ anchorAlign: this.anchorAlign,
365
+ animate: this.popupSettings.animate,
366
+ appendTo: this.appendTo,
367
+ content: this.containerService.template,
368
+ popupAlign: this.popupAlign,
369
+ popupClass: this.popupClasses
370
+ });
371
+ this.popupSubs.add(this.popupRef.popupAnchorViewportLeave.subscribe(() => this.openState = false));
372
+ }
373
+ destroyPopup() {
374
+ if (this.popupRef) {
375
+ this.popupRef.close();
376
+ this.popupRef = null;
377
+ this.popupSubs.unsubscribe();
378
+ this.isClosePrevented = false;
379
+ // this.focusService.resetFocus();
380
+ }
207
381
  }
208
382
  }
209
- ListButton.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: ListButton, deps: [{ token: i1.FocusService }, { token: i2.NavigationService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i3.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
210
- ListButton.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.16", type: ListButton, selector: "ng-component", ngImport: i0, template: '', isInline: true });
383
+ ListButton.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: ListButton, deps: [{ token: i1.FocusService }, { token: i2.NavigationService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i3.PopupService }, { token: i0.ElementRef }, { token: i4.LocalizationService }, { token: i0.ChangeDetectorRef }, { token: i5.PopupContainerService }], target: i0.ɵɵFactoryTarget.Component });
384
+ ListButton.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.16", type: ListButton, selector: "ng-component", inputs: { disabled: "disabled", tabIndex: "tabIndex", buttonClass: "buttonClass", popupSettings: "popupSettings" }, outputs: { open: "open", close: "close" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
211
385
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: ListButton, decorators: [{
212
386
  type: Component,
213
387
  args: [{
214
388
  template: ''
215
389
  }]
216
- }], ctorParameters: function () { return [{ type: i1.FocusService }, { type: i2.NavigationService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i3.LocalizationService }, { type: i0.ChangeDetectorRef }]; } });
390
+ }], ctorParameters: function () { return [{ type: i1.FocusService }, { type: i2.NavigationService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i3.PopupService }, { type: i0.ElementRef }, { type: i4.LocalizationService }, { type: i0.ChangeDetectorRef }, { type: i5.PopupContainerService }]; }, propDecorators: { disabled: [{
391
+ type: Input
392
+ }], tabIndex: [{
393
+ type: Input
394
+ }], buttonClass: [{
395
+ type: Input
396
+ }], open: [{
397
+ type: Output
398
+ }], close: [{
399
+ type: Output
400
+ }], popupSettings: [{
401
+ type: Input
402
+ }] } });
@@ -50,13 +50,14 @@ export class ListComponent {
50
50
  ListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.16", ngImport: i0, type: ListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
51
51
  ListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.16", type: ListComponent, selector: "kendo-button-list", inputs: { data: "data", textField: "textField", itemTemplate: "itemTemplate", size: "size" }, outputs: { onItemClick: "onItemClick", onItemBlur: "onItemBlur" }, ngImport: i0, template: `
52
52
  <ul class="k-group k-menu-group k-reset" [ngClass]="sizeClass" unselectable="on" role="menu">
53
- <li role="menuitem" unselectable="on"
53
+ <li role="menuitem"
54
+ unselectable="on"
54
55
  kendoButtonFocusable
55
56
  *ngFor="let dataItem of data; let index = index;"
56
57
  [index]="index"
57
58
  tabindex="-1"
58
59
  class="k-item k-menu-item"
59
- (click)="onClick(index)"
60
+ (click)="$event.stopImmediatePropagation(); onClick(index);"
60
61
  (blur)="onBlur()"
61
62
  [attr.aria-disabled]="dataItem.disabled ? true : false">
62
63
  <ng-template [ngIf]="itemTemplate?.templateRef">
@@ -90,13 +91,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.16", ngImpo
90
91
  selector: 'kendo-button-list',
91
92
  template: `
92
93
  <ul class="k-group k-menu-group k-reset" [ngClass]="sizeClass" unselectable="on" role="menu">
93
- <li role="menuitem" unselectable="on"
94
+ <li role="menuitem"
95
+ unselectable="on"
94
96
  kendoButtonFocusable
95
97
  *ngFor="let dataItem of data; let index = index;"
96
98
  [index]="index"
97
99
  tabindex="-1"
98
100
  class="k-item k-menu-item"
99
- (click)="onClick(index)"
101
+ (click)="$event.stopImmediatePropagation(); onClick(index);"
100
102
  (blur)="onBlur()"
101
103
  [attr.aria-disabled]="dataItem.disabled ? true : false">
102
104
  <ng-template [ngIf]="itemTemplate?.templateRef">
@@ -29,13 +29,8 @@ export class NavigationService {
29
29
  const keyEvent = args.keyEvent;
30
30
  let index;
31
31
  let action = NavigationAction.Undefined;
32
- if (keyEvent === KeyEvents.keypress) {
33
- if (this.isEnter(keyCode)) {
34
- action = NavigationAction.EnterPress;
35
- }
36
- }
37
- else if (keyEvent === KeyEvents.keyup) {
38
- if (this.isEnter(keyCode)) {
32
+ if (keyEvent === KeyEvents.keyup) {
33
+ if (this.isEnterOrSpace(keyCode)) {
39
34
  action = NavigationAction.EnterUp;
40
35
  }
41
36
  }
@@ -46,7 +41,7 @@ export class NavigationService {
46
41
  else if (args.altKey && keyCode === Keys.ArrowUp) {
47
42
  action = NavigationAction.Close;
48
43
  }
49
- else if (this.isEnter(keyCode)) {
44
+ else if (this.isEnterOrSpace(keyCode)) {
50
45
  action = NavigationAction.Enter;
51
46
  }
52
47
  else if (keyCode === Keys.Escape) {
@@ -89,11 +84,11 @@ export class NavigationService {
89
84
  }
90
85
  }
91
86
  if (action !== NavigationAction.Undefined) {
92
- this[NavigationAction[action].toLowerCase()].emit(index);
87
+ this[NavigationAction[action].toLowerCase()].emit({ index, target: args.target });
93
88
  }
94
89
  return action;
95
90
  }
96
- isEnter(keyCode) {
91
+ isEnterOrSpace(keyCode) {
97
92
  return keyCode === Keys.Enter || keyCode === Keys.Space;
98
93
  }
99
94
  next(args) {
@@ -9,7 +9,7 @@ export const packageMetadata = {
9
9
  name: '@progress/kendo-angular-buttons',
10
10
  productName: 'Kendo UI for Angular',
11
11
  productCodes: ['KENDOUIANGULAR', 'KENDOUICOMPLETE'],
12
- publishDate: 1660216864,
12
+ publishDate: 1662712402,
13
13
  version: '',
14
14
  licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning'
15
15
  };