@angular/cdk-experimental 14.1.0-next.2 → 14.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.
- package/column-resize/index.d.ts +1 -1
- package/esm2020/column-resize/resize-strategy.mjs +2 -2
- package/esm2020/listbox/listbox.mjs +257 -111
- package/esm2020/popover-edit/edit-event-dispatcher.mjs +2 -2
- package/esm2020/popover-edit/table-directives.mjs +2 -2
- package/esm2020/scrolling/auto-size-virtual-scroll.mjs +2 -2
- package/esm2020/version.mjs +1 -1
- package/fesm2015/cdk-experimental.mjs +1 -1
- package/fesm2015/cdk-experimental.mjs.map +1 -1
- package/fesm2015/column-resize.mjs +1 -1
- package/fesm2015/column-resize.mjs.map +1 -1
- package/fesm2015/listbox.mjs +262 -111
- package/fesm2015/listbox.mjs.map +1 -1
- package/fesm2015/popover-edit.mjs +2 -2
- package/fesm2015/popover-edit.mjs.map +1 -1
- package/fesm2015/scrolling.mjs +1 -1
- package/fesm2015/scrolling.mjs.map +1 -1
- package/fesm2020/cdk-experimental.mjs +1 -1
- package/fesm2020/cdk-experimental.mjs.map +1 -1
- package/fesm2020/column-resize.mjs +1 -1
- package/fesm2020/column-resize.mjs.map +1 -1
- package/fesm2020/listbox.mjs +256 -110
- package/fesm2020/listbox.mjs.map +1 -1
- package/fesm2020/popover-edit.mjs +2 -2
- package/fesm2020/popover-edit.mjs.map +1 -1
- package/fesm2020/scrolling.mjs +1 -1
- package/fesm2020/scrolling.mjs.map +1 -1
- package/listbox/index.d.ts +63 -21
- package/package.json +2 -2
- package/popover-edit/index.d.ts +1 -1
package/fesm2015/listbox.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, ElementRef, Directive, Input, ChangeDetectorRef, InjectFlags, forwardRef, Output, ContentChildren, NgModule } from '@angular/core';
|
|
3
3
|
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
|
|
4
|
-
import { SPACE, ENTER, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes';
|
|
4
|
+
import { A, hasModifierKey, SPACE, ENTER, HOME, END, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes';
|
|
5
5
|
import { coerceBooleanProperty, coerceArray } from '@angular/cdk/coercion';
|
|
6
6
|
import { SelectionModel } from '@angular/cdk/collections';
|
|
7
|
-
import { Subject,
|
|
8
|
-
import { startWith, switchMap,
|
|
7
|
+
import { Subject, defer, merge } from 'rxjs';
|
|
8
|
+
import { startWith, switchMap, map, takeUntil, filter } from 'rxjs/operators';
|
|
9
9
|
import { Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
10
10
|
import { Directionality } from '@angular/cdk/bidi';
|
|
11
|
-
import { CdkCombobox } from '@angular/cdk-experimental/combobox';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* @license
|
|
@@ -19,9 +18,33 @@ import { CdkCombobox } from '@angular/cdk-experimental/combobox';
|
|
|
19
18
|
*/
|
|
20
19
|
/** The next id to use for creating unique DOM IDs. */
|
|
21
20
|
let nextId = 0;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
/**
|
|
22
|
+
* An implementation of SelectionModel that internally always represents the selection as a
|
|
23
|
+
* multi-selection. This is necessary so that we can recover the full selection if the user
|
|
24
|
+
* switches the listbox from single-selection to multi-selection after initialization.
|
|
25
|
+
*
|
|
26
|
+
* This selection model may report multiple selected values, even if it is in single-selection
|
|
27
|
+
* mode. It is up to the user (CdkListbox) to check for invalid selections.
|
|
28
|
+
*/
|
|
29
|
+
class ListboxSelectionModel extends SelectionModel {
|
|
30
|
+
constructor(multiple = false, initiallySelectedValues, emitChanges = true, compareWith) {
|
|
31
|
+
super(true, initiallySelectedValues, emitChanges, compareWith);
|
|
32
|
+
this.multiple = multiple;
|
|
33
|
+
}
|
|
34
|
+
isMultipleSelection() {
|
|
35
|
+
return this.multiple;
|
|
36
|
+
}
|
|
37
|
+
select(...values) {
|
|
38
|
+
// The super class is always in multi-selection mode, so we need to override the behavior if
|
|
39
|
+
// this selection model actually belongs to a single-selection listbox.
|
|
40
|
+
if (this.multiple) {
|
|
41
|
+
return super.select(...values);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return super.setSelection(...values);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
25
48
|
/** A selectable option in a listbox. */
|
|
26
49
|
class CdkOption {
|
|
27
50
|
constructor() {
|
|
@@ -67,7 +90,7 @@ class CdkOption {
|
|
|
67
90
|
}
|
|
68
91
|
/** Whether this option is selected. */
|
|
69
92
|
isSelected() {
|
|
70
|
-
return this.listbox.isSelected(this
|
|
93
|
+
return this.listbox.isSelected(this);
|
|
71
94
|
}
|
|
72
95
|
/** Whether this option is active. */
|
|
73
96
|
isActive() {
|
|
@@ -127,7 +150,7 @@ class CdkOption {
|
|
|
127
150
|
}
|
|
128
151
|
}
|
|
129
152
|
CdkOption.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CdkOption, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
130
|
-
CdkOption.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: CdkOption, selector: "[cdkOption]", inputs: { id: "id", value: ["cdkOption", "value"], typeaheadLabel: ["cdkOptionTypeaheadLabel", "typeaheadLabel"], disabled: ["cdkOptionDisabled", "disabled"], enabledTabIndex: ["tabindex", "enabledTabIndex"] }, host: { attributes: { "role": "option" }, listeners: { "click": "_clicked.next()", "focus": "_handleFocus()" }, properties: { "id": "id", "attr.aria-selected": "isSelected() || null", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "class.cdk-option-disabled": "disabled", "class.cdk-option-active": "isActive()", "class.cdk-option-selected": "isSelected()" }, classAttribute: "cdk-option" }, exportAs: ["cdkOption"], ngImport: i0 });
|
|
153
|
+
CdkOption.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: CdkOption, selector: "[cdkOption]", inputs: { id: "id", value: ["cdkOption", "value"], typeaheadLabel: ["cdkOptionTypeaheadLabel", "typeaheadLabel"], disabled: ["cdkOptionDisabled", "disabled"], enabledTabIndex: ["tabindex", "enabledTabIndex"] }, host: { attributes: { "role": "option" }, listeners: { "click": "_clicked.next($event)", "focus": "_handleFocus()" }, properties: { "id": "id", "attr.aria-selected": "isSelected() || null", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "class.cdk-option-disabled": "disabled", "class.cdk-option-active": "isActive()", "class.cdk-option-selected": "isSelected()" }, classAttribute: "cdk-option" }, exportAs: ["cdkOption"], ngImport: i0 });
|
|
131
154
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CdkOption, decorators: [{
|
|
132
155
|
type: Directive,
|
|
133
156
|
args: [{
|
|
@@ -143,7 +166,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImpor
|
|
|
143
166
|
'[class.cdk-option-disabled]': 'disabled',
|
|
144
167
|
'[class.cdk-option-active]': 'isActive()',
|
|
145
168
|
'[class.cdk-option-selected]': 'isSelected()',
|
|
146
|
-
'(click)': '_clicked.next()',
|
|
169
|
+
'(click)': '_clicked.next($event)',
|
|
147
170
|
'(focus)': '_handleFocus()',
|
|
148
171
|
},
|
|
149
172
|
}]
|
|
@@ -165,22 +188,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImpor
|
|
|
165
188
|
class CdkListbox {
|
|
166
189
|
constructor() {
|
|
167
190
|
this._generatedId = `cdk-listbox-${nextId++}`;
|
|
168
|
-
this._multiple = false;
|
|
169
191
|
this._disabled = false;
|
|
170
192
|
this._useActiveDescendant = false;
|
|
171
|
-
|
|
172
|
-
this.
|
|
193
|
+
this._orientation = 'vertical';
|
|
194
|
+
this._keyboardNavigationWraps = true;
|
|
195
|
+
this._keyboardNavigationSkipsDisabled = true;
|
|
173
196
|
/** Emits when the selected value(s) in the listbox change. */
|
|
174
197
|
this.valueChange = new Subject();
|
|
175
|
-
// TODO(mmalerba): Refactor SelectionModel so that its not necessary to create new instances
|
|
176
198
|
/** The selection model used by the listbox. */
|
|
177
|
-
this.
|
|
199
|
+
this.selectionModel = new ListboxSelectionModel();
|
|
178
200
|
/** Emits when the listbox is destroyed. */
|
|
179
201
|
this.destroyed = new Subject();
|
|
180
202
|
/** The host element of the listbox. */
|
|
181
203
|
this.element = inject(ElementRef).nativeElement;
|
|
182
204
|
/** The change detector for this listbox. */
|
|
183
205
|
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
206
|
+
/** Whether the currently selected value in the selection model is invalid. */
|
|
207
|
+
this._invalid = false;
|
|
208
|
+
/** The last user-triggered option. */
|
|
209
|
+
this._lastTriggered = null;
|
|
184
210
|
/** Callback called when the listbox has been touched */
|
|
185
211
|
this._onTouched = () => { };
|
|
186
212
|
/** Callback called when the listbox value changes */
|
|
@@ -188,21 +214,23 @@ class CdkListbox {
|
|
|
188
214
|
/** Callback called when the form validator changes. */
|
|
189
215
|
this._onValidatorChange = () => { };
|
|
190
216
|
/** Emits when an option has been clicked. */
|
|
191
|
-
this._optionClicked = defer(() => this.options.changes.pipe(startWith(this.options), switchMap(options => merge(...options.map(option => option._clicked.pipe(
|
|
217
|
+
this._optionClicked = defer(() => this.options.changes.pipe(startWith(this.options), switchMap(options => merge(...options.map(option => option._clicked.pipe(map(event => ({ option, event }))))))));
|
|
192
218
|
/** The directionality of the page. */
|
|
193
219
|
this._dir = inject(Directionality, InjectFlags.Optional);
|
|
194
|
-
|
|
195
|
-
this.
|
|
220
|
+
/** A predicate that skips disabled options. */
|
|
221
|
+
this._skipDisabledPredicate = (option) => option.disabled;
|
|
222
|
+
/** A predicate that does not skip any options. */
|
|
223
|
+
this._skipNonePredicate = () => false;
|
|
196
224
|
/**
|
|
197
225
|
* Validator that produces an error if multiple values are selected in a single selection
|
|
198
226
|
* listbox.
|
|
199
227
|
* @param control The control to validate
|
|
200
228
|
* @return A validation error or null
|
|
201
229
|
*/
|
|
202
|
-
this.
|
|
230
|
+
this._validateUnexpectedMultipleValues = (control) => {
|
|
203
231
|
const controlValue = this._coerceValue(control.value);
|
|
204
232
|
if (!this.multiple && controlValue.length > 1) {
|
|
205
|
-
return { '
|
|
233
|
+
return { 'cdkListboxUnexpectedMultipleValues': true };
|
|
206
234
|
}
|
|
207
235
|
return null;
|
|
208
236
|
};
|
|
@@ -211,22 +239,21 @@ class CdkListbox {
|
|
|
211
239
|
* @param control The control to validate
|
|
212
240
|
* @return A validation error or null
|
|
213
241
|
*/
|
|
214
|
-
this.
|
|
242
|
+
this._validateUnexpectedOptionValues = (control) => {
|
|
215
243
|
const controlValue = this._coerceValue(control.value);
|
|
216
|
-
const invalidValues = this.
|
|
244
|
+
const invalidValues = this._getInvalidOptionValues(controlValue);
|
|
217
245
|
if (invalidValues.length) {
|
|
218
|
-
return { '
|
|
246
|
+
return { 'cdkListboxUnexpectedOptionValues': { 'values': invalidValues } };
|
|
219
247
|
}
|
|
220
248
|
return null;
|
|
221
249
|
};
|
|
222
250
|
/** The combined set of validators for this listbox. */
|
|
223
251
|
this._validators = Validators.compose([
|
|
224
|
-
this.
|
|
225
|
-
this.
|
|
252
|
+
this._validateUnexpectedMultipleValues,
|
|
253
|
+
this._validateUnexpectedOptionValues,
|
|
226
254
|
]);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.subscribe(() => {
|
|
255
|
+
// Update the internal value whenever the selection model's selection changes.
|
|
256
|
+
this.selectionModel.changed.pipe(startWith(null), takeUntil(this.destroyed)).subscribe(() => {
|
|
230
257
|
this._updateInternalValue();
|
|
231
258
|
});
|
|
232
259
|
}
|
|
@@ -246,7 +273,7 @@ class CdkListbox {
|
|
|
246
273
|
}
|
|
247
274
|
/** The value selected in the listbox, represented as an array of option values. */
|
|
248
275
|
get value() {
|
|
249
|
-
return this.selectionModel
|
|
276
|
+
return this._invalid ? [] : this.selectionModel.selected;
|
|
250
277
|
}
|
|
251
278
|
set value(value) {
|
|
252
279
|
this._setSelection(value);
|
|
@@ -256,12 +283,11 @@ class CdkListbox {
|
|
|
256
283
|
* to `false`, and more than one option is selected, all options are deselected.
|
|
257
284
|
*/
|
|
258
285
|
get multiple() {
|
|
259
|
-
return this.
|
|
286
|
+
return this.selectionModel.multiple;
|
|
260
287
|
}
|
|
261
288
|
set multiple(value) {
|
|
262
|
-
this.
|
|
263
|
-
this.
|
|
264
|
-
this._onValidatorChange();
|
|
289
|
+
this.selectionModel.multiple = coerceBooleanProperty(value);
|
|
290
|
+
this._updateInternalValue();
|
|
265
291
|
}
|
|
266
292
|
/** Whether the listbox is disabled. */
|
|
267
293
|
get disabled() {
|
|
@@ -277,28 +303,61 @@ class CdkListbox {
|
|
|
277
303
|
set useActiveDescendant(shouldUseActiveDescendant) {
|
|
278
304
|
this._useActiveDescendant = coerceBooleanProperty(shouldUseActiveDescendant);
|
|
279
305
|
}
|
|
306
|
+
/** The orientation of the listbox. Only affects keyboard interaction, not visual layout. */
|
|
307
|
+
get orientation() {
|
|
308
|
+
return this._orientation;
|
|
309
|
+
}
|
|
310
|
+
set orientation(value) {
|
|
311
|
+
var _a, _b, _c;
|
|
312
|
+
this._orientation = value === 'horizontal' ? 'horizontal' : 'vertical';
|
|
313
|
+
if (value === 'horizontal') {
|
|
314
|
+
(_a = this.listKeyManager) === null || _a === void 0 ? void 0 : _a.withHorizontalOrientation(((_b = this._dir) === null || _b === void 0 ? void 0 : _b.value) || 'ltr');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
(_c = this.listKeyManager) === null || _c === void 0 ? void 0 : _c.withVerticalOrientation();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
280
320
|
/** The function used to compare option values. */
|
|
281
321
|
get compareWith() {
|
|
282
|
-
return this.
|
|
322
|
+
return this.selectionModel.compareWith;
|
|
283
323
|
}
|
|
284
324
|
set compareWith(fn) {
|
|
285
|
-
this.
|
|
286
|
-
this._updateSelectionModel();
|
|
325
|
+
this.selectionModel.compareWith = fn;
|
|
287
326
|
}
|
|
288
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Whether the keyboard navigation should wrap when the user presses arrow down on the last item
|
|
329
|
+
* or arrow up on the first item.
|
|
330
|
+
*/
|
|
331
|
+
get keyboardNavigationWraps() {
|
|
332
|
+
return this._keyboardNavigationWraps;
|
|
333
|
+
}
|
|
334
|
+
set keyboardNavigationWraps(wrap) {
|
|
289
335
|
var _a;
|
|
336
|
+
this._keyboardNavigationWraps = coerceBooleanProperty(wrap);
|
|
337
|
+
(_a = this.listKeyManager) === null || _a === void 0 ? void 0 : _a.withWrap(this._keyboardNavigationWraps);
|
|
338
|
+
}
|
|
339
|
+
/** Whether keyboard navigation should skip over disabled items. */
|
|
340
|
+
get keyboardNavigationSkipsDisabled() {
|
|
341
|
+
return this._keyboardNavigationSkipsDisabled;
|
|
342
|
+
}
|
|
343
|
+
set keyboardNavigationSkipsDisabled(skip) {
|
|
344
|
+
var _a;
|
|
345
|
+
this._keyboardNavigationSkipsDisabled = coerceBooleanProperty(skip);
|
|
346
|
+
(_a = this.listKeyManager) === null || _a === void 0 ? void 0 : _a.skipPredicate(this._keyboardNavigationSkipsDisabled ? this._skipDisabledPredicate : this._skipNonePredicate);
|
|
347
|
+
}
|
|
348
|
+
ngAfterContentInit() {
|
|
290
349
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
291
350
|
this._verifyNoOptionValueCollisions();
|
|
292
351
|
}
|
|
293
352
|
this._initKeyManager();
|
|
294
|
-
|
|
295
|
-
|
|
353
|
+
// Update the internal value whenever the options change, as this may change the validity of
|
|
354
|
+
// the current selection
|
|
355
|
+
this.options.changes.pipe(startWith(this.options), takeUntil(this.destroyed)).subscribe(() => {
|
|
296
356
|
this._updateInternalValue();
|
|
297
|
-
this._onValidatorChange();
|
|
298
357
|
});
|
|
299
358
|
this._optionClicked
|
|
300
|
-
.pipe(filter(option => !option.disabled), takeUntil(this.destroyed))
|
|
301
|
-
.subscribe(option => this._handleOptionClicked(option));
|
|
359
|
+
.pipe(filter(({ option }) => !option.disabled), takeUntil(this.destroyed))
|
|
360
|
+
.subscribe(({ option, event }) => this._handleOptionClicked(option, event));
|
|
302
361
|
}
|
|
303
362
|
ngOnDestroy() {
|
|
304
363
|
this.listKeyManager.change.complete();
|
|
@@ -317,7 +376,10 @@ class CdkListbox {
|
|
|
317
376
|
* @param value The value to toggle
|
|
318
377
|
*/
|
|
319
378
|
toggleValue(value) {
|
|
320
|
-
this.
|
|
379
|
+
if (this._invalid) {
|
|
380
|
+
this.selectionModel.clear(false);
|
|
381
|
+
}
|
|
382
|
+
this.selectionModel.toggle(value);
|
|
321
383
|
}
|
|
322
384
|
/**
|
|
323
385
|
* Select the given option.
|
|
@@ -331,7 +393,10 @@ class CdkListbox {
|
|
|
331
393
|
* @param value The value to select
|
|
332
394
|
*/
|
|
333
395
|
selectValue(value) {
|
|
334
|
-
this.
|
|
396
|
+
if (this._invalid) {
|
|
397
|
+
this.selectionModel.clear(false);
|
|
398
|
+
}
|
|
399
|
+
this.selectionModel.select(value);
|
|
335
400
|
}
|
|
336
401
|
/**
|
|
337
402
|
* Deselect the given option.
|
|
@@ -345,7 +410,10 @@ class CdkListbox {
|
|
|
345
410
|
* @param value The value to deselect
|
|
346
411
|
*/
|
|
347
412
|
deselectValue(value) {
|
|
348
|
-
this.
|
|
413
|
+
if (this._invalid) {
|
|
414
|
+
this.selectionModel.clear(false);
|
|
415
|
+
}
|
|
416
|
+
this.selectionModel.deselect(value);
|
|
349
417
|
}
|
|
350
418
|
/**
|
|
351
419
|
* Set the selected state of all options.
|
|
@@ -353,10 +421,13 @@ class CdkListbox {
|
|
|
353
421
|
*/
|
|
354
422
|
setAllSelected(isSelected) {
|
|
355
423
|
if (!isSelected) {
|
|
356
|
-
this.selectionModel
|
|
424
|
+
this.selectionModel.clear();
|
|
357
425
|
}
|
|
358
426
|
else {
|
|
359
|
-
|
|
427
|
+
if (this._invalid) {
|
|
428
|
+
this.selectionModel.clear(false);
|
|
429
|
+
}
|
|
430
|
+
this.selectionModel.select(...this.options.map(option => option.value));
|
|
360
431
|
}
|
|
361
432
|
}
|
|
362
433
|
/**
|
|
@@ -364,7 +435,17 @@ class CdkListbox {
|
|
|
364
435
|
* @param option The option to get the selected state of
|
|
365
436
|
*/
|
|
366
437
|
isSelected(option) {
|
|
367
|
-
return this.
|
|
438
|
+
return this.isValueSelected(option.value);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get whether the given value is selected.
|
|
442
|
+
* @param value The value to get the selected state of
|
|
443
|
+
*/
|
|
444
|
+
isValueSelected(value) {
|
|
445
|
+
if (this._invalid) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return this.selectionModel.isSelected(value);
|
|
368
449
|
}
|
|
369
450
|
/**
|
|
370
451
|
* Registers a callback to be invoked when the listbox's value changes from user input.
|
|
@@ -417,10 +498,6 @@ class CdkListbox {
|
|
|
417
498
|
focus() {
|
|
418
499
|
this.element.focus();
|
|
419
500
|
}
|
|
420
|
-
/** The selection model used to track the listbox's value. */
|
|
421
|
-
selectionModel() {
|
|
422
|
-
return this.selectionModelSubject.value;
|
|
423
|
-
}
|
|
424
501
|
/**
|
|
425
502
|
* Triggers the given option in response to user interaction.
|
|
426
503
|
* - In single selection mode: selects the option and deselects any other selected option.
|
|
@@ -429,16 +506,10 @@ class CdkListbox {
|
|
|
429
506
|
*/
|
|
430
507
|
triggerOption(option) {
|
|
431
508
|
if (option && !option.disabled) {
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
.
|
|
435
|
-
.
|
|
436
|
-
if (this.multiple) {
|
|
437
|
-
this.toggle(option);
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
this.select(option);
|
|
441
|
-
}
|
|
509
|
+
this._lastTriggered = option;
|
|
510
|
+
const changed = this.multiple
|
|
511
|
+
? this.selectionModel.toggle(option.value)
|
|
512
|
+
: this.selectionModel.select(option.value);
|
|
442
513
|
if (changed) {
|
|
443
514
|
this._onChange(this.value);
|
|
444
515
|
this.valueChange.next({
|
|
@@ -449,6 +520,45 @@ class CdkListbox {
|
|
|
449
520
|
}
|
|
450
521
|
}
|
|
451
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Trigger the given range of options in response to user interaction.
|
|
525
|
+
* Should only be called in multi-selection mode.
|
|
526
|
+
* @param trigger The option that was triggered
|
|
527
|
+
* @param from The start index of the options to toggle
|
|
528
|
+
* @param to The end index of the options to toggle
|
|
529
|
+
* @param on Whether to toggle the option range on
|
|
530
|
+
*/
|
|
531
|
+
triggerRange(trigger, from, to, on) {
|
|
532
|
+
var _a;
|
|
533
|
+
if (this.disabled || (trigger && trigger.disabled)) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
this._lastTriggered = trigger;
|
|
537
|
+
const isEqual = (_a = this.compareWith) !== null && _a !== void 0 ? _a : Object.is;
|
|
538
|
+
const updateValues = [...this.options]
|
|
539
|
+
.slice(Math.max(0, Math.min(from, to)), Math.min(this.options.length, Math.max(from, to) + 1))
|
|
540
|
+
.filter(option => !option.disabled)
|
|
541
|
+
.map(option => option.value);
|
|
542
|
+
const selected = [...this.value];
|
|
543
|
+
for (const updateValue of updateValues) {
|
|
544
|
+
const selectedIndex = selected.findIndex(selectedValue => isEqual(selectedValue, updateValue));
|
|
545
|
+
if (on && selectedIndex === -1) {
|
|
546
|
+
selected.push(updateValue);
|
|
547
|
+
}
|
|
548
|
+
else if (!on && selectedIndex !== -1) {
|
|
549
|
+
selected.splice(selectedIndex, 1);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
let changed = this.selectionModel.setSelection(...selected);
|
|
553
|
+
if (changed) {
|
|
554
|
+
this._onChange(this.value);
|
|
555
|
+
this.valueChange.next({
|
|
556
|
+
value: this.value,
|
|
557
|
+
listbox: this,
|
|
558
|
+
option: trigger,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
452
562
|
/**
|
|
453
563
|
* Sets the given option as active.
|
|
454
564
|
* @param option The option to make active
|
|
@@ -465,24 +575,68 @@ class CdkListbox {
|
|
|
465
575
|
}
|
|
466
576
|
/** Called when the user presses keydown on the listbox. */
|
|
467
577
|
_handleKeydown(event) {
|
|
578
|
+
var _a;
|
|
468
579
|
if (this._disabled) {
|
|
469
580
|
return;
|
|
470
581
|
}
|
|
471
582
|
const { keyCode } = event;
|
|
472
583
|
const previousActiveIndex = this.listKeyManager.activeItemIndex;
|
|
584
|
+
const ctrlKeys = ['ctrlKey', 'metaKey'];
|
|
585
|
+
if (this.multiple && keyCode === A && hasModifierKey(event, ...ctrlKeys)) {
|
|
586
|
+
// Toggle all options off if they're all selected, otherwise toggle them all on.
|
|
587
|
+
this.triggerRange(null, 0, this.options.length - 1, this.options.length !== this.value.length);
|
|
588
|
+
event.preventDefault();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (this.multiple &&
|
|
592
|
+
(keyCode === SPACE || keyCode === ENTER) &&
|
|
593
|
+
hasModifierKey(event, 'shiftKey')) {
|
|
594
|
+
if (this.listKeyManager.activeItem && this.listKeyManager.activeItemIndex != null) {
|
|
595
|
+
this.triggerRange(this.listKeyManager.activeItem, (_a = this._getLastTriggeredIndex()) !== null && _a !== void 0 ? _a : this.listKeyManager.activeItemIndex, this.listKeyManager.activeItemIndex, !this.listKeyManager.activeItem.isSelected());
|
|
596
|
+
}
|
|
597
|
+
event.preventDefault();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (this.multiple &&
|
|
601
|
+
keyCode === HOME &&
|
|
602
|
+
hasModifierKey(event, ...ctrlKeys) &&
|
|
603
|
+
hasModifierKey(event, 'shiftKey')) {
|
|
604
|
+
const trigger = this.listKeyManager.activeItem;
|
|
605
|
+
if (trigger) {
|
|
606
|
+
const from = this.listKeyManager.activeItemIndex;
|
|
607
|
+
this.listKeyManager.setFirstItemActive();
|
|
608
|
+
this.triggerRange(trigger, from, this.listKeyManager.activeItemIndex, !trigger.isSelected());
|
|
609
|
+
}
|
|
610
|
+
event.preventDefault();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (this.multiple &&
|
|
614
|
+
keyCode === END &&
|
|
615
|
+
hasModifierKey(event, ...ctrlKeys) &&
|
|
616
|
+
hasModifierKey(event, 'shiftKey')) {
|
|
617
|
+
const trigger = this.listKeyManager.activeItem;
|
|
618
|
+
if (trigger) {
|
|
619
|
+
const from = this.listKeyManager.activeItemIndex;
|
|
620
|
+
this.listKeyManager.setLastItemActive();
|
|
621
|
+
this.triggerRange(trigger, from, this.listKeyManager.activeItemIndex, !trigger.isSelected());
|
|
622
|
+
}
|
|
623
|
+
event.preventDefault();
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
473
626
|
if (keyCode === SPACE || keyCode === ENTER) {
|
|
474
627
|
this.triggerOption(this.listKeyManager.activeItem);
|
|
475
628
|
event.preventDefault();
|
|
629
|
+
return;
|
|
476
630
|
}
|
|
477
|
-
|
|
478
|
-
this.listKeyManager.onKeydown(event);
|
|
479
|
-
}
|
|
480
|
-
/** Will select an option if shift was pressed while navigating to the option */
|
|
481
|
-
const isArrow = keyCode === UP_ARROW ||
|
|
631
|
+
const isNavKey = keyCode === UP_ARROW ||
|
|
482
632
|
keyCode === DOWN_ARROW ||
|
|
483
633
|
keyCode === LEFT_ARROW ||
|
|
484
|
-
keyCode === RIGHT_ARROW
|
|
485
|
-
|
|
634
|
+
keyCode === RIGHT_ARROW ||
|
|
635
|
+
keyCode === HOME ||
|
|
636
|
+
keyCode === END;
|
|
637
|
+
this.listKeyManager.onKeydown(event);
|
|
638
|
+
// Will select an option if shift was pressed while navigating to the option
|
|
639
|
+
if (isNavKey && event.shiftKey && previousActiveIndex !== this.listKeyManager.activeItemIndex) {
|
|
486
640
|
this.triggerOption(this.listKeyManager.activeItem);
|
|
487
641
|
}
|
|
488
642
|
}
|
|
@@ -512,10 +666,13 @@ class CdkListbox {
|
|
|
512
666
|
_initKeyManager() {
|
|
513
667
|
var _a;
|
|
514
668
|
this.listKeyManager = new ActiveDescendantKeyManager(this.options)
|
|
515
|
-
.withWrap()
|
|
669
|
+
.withWrap(this._keyboardNavigationWraps)
|
|
516
670
|
.withTypeAhead()
|
|
517
671
|
.withHomeAndEnd()
|
|
518
|
-
.withAllowedModifierKeys(['shiftKey'])
|
|
672
|
+
.withAllowedModifierKeys(['shiftKey'])
|
|
673
|
+
.skipPredicate(this._keyboardNavigationSkipsDisabled
|
|
674
|
+
? this._skipDisabledPredicate
|
|
675
|
+
: this._skipNonePredicate);
|
|
519
676
|
if (this.orientation === 'vertical') {
|
|
520
677
|
this.listKeyManager.withVerticalOrientation();
|
|
521
678
|
}
|
|
@@ -526,21 +683,6 @@ class CdkListbox {
|
|
|
526
683
|
.pipe(takeUntil(this.destroyed))
|
|
527
684
|
.subscribe(() => this._focusActiveOption());
|
|
528
685
|
}
|
|
529
|
-
// TODO(mmalerba): Should not depend on combobox.
|
|
530
|
-
_updatePanelForSelection(option) {
|
|
531
|
-
if (this._combobox) {
|
|
532
|
-
if (!this.multiple) {
|
|
533
|
-
this._combobox.updateAndClose(option.isSelected() ? option.value : []);
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
this._combobox.updateAndClose(this.value);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
/** Update the selection mode when the 'multiple' property changes. */
|
|
541
|
-
_updateSelectionModel() {
|
|
542
|
-
this.selectionModelSubject.next(new SelectionModel(this.multiple, !this.multiple && this.value.length > 1 ? [] : this.value.slice(), true, this._compareWith));
|
|
543
|
-
}
|
|
544
686
|
/** Focus the active option. */
|
|
545
687
|
_focusActiveOption() {
|
|
546
688
|
var _a;
|
|
@@ -554,26 +696,23 @@ class CdkListbox {
|
|
|
554
696
|
* @param value The list of new selected values.
|
|
555
697
|
*/
|
|
556
698
|
_setSelection(value) {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
699
|
+
if (this._invalid) {
|
|
700
|
+
this.selectionModel.clear(false);
|
|
701
|
+
}
|
|
702
|
+
this.selectionModel.setSelection(...this._coerceValue(value));
|
|
561
703
|
}
|
|
562
704
|
/** Update the internal value of the listbox based on the selection model. */
|
|
563
705
|
_updateInternalValue() {
|
|
564
706
|
const indexCache = new Map();
|
|
565
|
-
|
|
566
|
-
// (e.g. if the option was removed from the DOM.)
|
|
567
|
-
const selected = this.selectionModel().selected;
|
|
568
|
-
const validSelected = this._getValuesWithValidity(selected, true);
|
|
569
|
-
if (validSelected.length != selected.length) {
|
|
570
|
-
this.selectionModel().setSelection(...validSelected);
|
|
571
|
-
}
|
|
572
|
-
this.selectionModel().sort((a, b) => {
|
|
707
|
+
this.selectionModel.sort((a, b) => {
|
|
573
708
|
const aIndex = this._getIndexForValue(indexCache, a);
|
|
574
709
|
const bIndex = this._getIndexForValue(indexCache, b);
|
|
575
710
|
return aIndex - bIndex;
|
|
576
711
|
});
|
|
712
|
+
const selected = this.selectionModel.selected;
|
|
713
|
+
this._invalid =
|
|
714
|
+
(!this.multiple && selected.length > 1) || !!this._getInvalidOptionValues(selected).length;
|
|
715
|
+
this._onValidatorChange();
|
|
577
716
|
this.changeDetectorRef.markForCheck();
|
|
578
717
|
}
|
|
579
718
|
/**
|
|
@@ -600,17 +739,19 @@ class CdkListbox {
|
|
|
600
739
|
* Handle the user clicking an option.
|
|
601
740
|
* @param option The option that was clicked.
|
|
602
741
|
*/
|
|
603
|
-
_handleOptionClicked(option) {
|
|
742
|
+
_handleOptionClicked(option, event) {
|
|
743
|
+
var _a;
|
|
604
744
|
this.listKeyManager.setActiveItem(option);
|
|
605
|
-
this.
|
|
606
|
-
|
|
745
|
+
if (event.shiftKey && this.multiple) {
|
|
746
|
+
this.triggerRange(option, (_a = this._getLastTriggeredIndex()) !== null && _a !== void 0 ? _a : this.listKeyManager.activeItemIndex, this.listKeyManager.activeItemIndex, !option.isSelected());
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
this.triggerOption(option);
|
|
750
|
+
}
|
|
607
751
|
}
|
|
608
752
|
/** Verifies that no two options represent the same value under the compareWith function. */
|
|
609
753
|
_verifyNoOptionValueCollisions() {
|
|
610
|
-
|
|
611
|
-
this.selectionModelSubject,
|
|
612
|
-
this.options.changes.pipe(startWith(this.options)),
|
|
613
|
-
]).subscribe(() => {
|
|
754
|
+
this.options.changes.pipe(startWith(this.options), takeUntil(this.destroyed)).subscribe(() => {
|
|
614
755
|
var _a;
|
|
615
756
|
const isEqual = (_a = this.compareWith) !== null && _a !== void 0 ? _a : Object.is;
|
|
616
757
|
for (let i = 0; i < this.options.length; i++) {
|
|
@@ -652,19 +793,23 @@ class CdkListbox {
|
|
|
652
793
|
return value == null ? [] : coerceArray(value);
|
|
653
794
|
}
|
|
654
795
|
/**
|
|
655
|
-
* Get the sublist of values
|
|
796
|
+
* Get the sublist of values that do not represent valid option values in this listbox.
|
|
656
797
|
* @param values The list of values
|
|
657
|
-
* @
|
|
658
|
-
* @return The sublist of values with the requested validity
|
|
798
|
+
* @return The sublist of values that are not valid option values
|
|
659
799
|
*/
|
|
660
|
-
|
|
800
|
+
_getInvalidOptionValues(values) {
|
|
661
801
|
const isEqual = this.compareWith || Object.is;
|
|
662
802
|
const validValues = (this.options || []).map(option => option.value);
|
|
663
|
-
return values.filter(value =>
|
|
803
|
+
return values.filter(value => !validValues.some(validValue => isEqual(value, validValue)));
|
|
804
|
+
}
|
|
805
|
+
/** Get the index of the last triggered option. */
|
|
806
|
+
_getLastTriggeredIndex() {
|
|
807
|
+
const index = this.options.toArray().indexOf(this._lastTriggered);
|
|
808
|
+
return index === -1 ? null : index;
|
|
664
809
|
}
|
|
665
810
|
}
|
|
666
811
|
CdkListbox.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: CdkListbox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
667
|
-
CdkListbox.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: CdkListbox, selector: "[cdkListbox]", inputs: { id: "id", enabledTabIndex: ["tabindex", "enabledTabIndex"], value: ["cdkListboxValue", "value"], multiple: ["cdkListboxMultiple", "multiple"], disabled: ["cdkListboxDisabled", "disabled"], useActiveDescendant: ["cdkListboxUseActiveDescendant", "useActiveDescendant"], orientation: ["cdkListboxOrientation", "orientation"], compareWith: ["cdkListboxCompareWith", "compareWith"] }, outputs: { valueChange: "cdkListboxValueChange" }, host: { attributes: { "role": "listbox" }, listeners: { "focus": "_handleFocus()", "keydown": "_handleKeydown($event)", "focusout": "_handleFocusOut($event)" }, properties: { "id": "id", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "attr.aria-multiselectable": "multiple", "attr.aria-activedescendant": "_getAriaActiveDescendant()", "attr.aria-orientation": "orientation" }, classAttribute: "cdk-listbox" }, providers: [
|
|
812
|
+
CdkListbox.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: CdkListbox, selector: "[cdkListbox]", inputs: { id: "id", enabledTabIndex: ["tabindex", "enabledTabIndex"], value: ["cdkListboxValue", "value"], multiple: ["cdkListboxMultiple", "multiple"], disabled: ["cdkListboxDisabled", "disabled"], useActiveDescendant: ["cdkListboxUseActiveDescendant", "useActiveDescendant"], orientation: ["cdkListboxOrientation", "orientation"], compareWith: ["cdkListboxCompareWith", "compareWith"], keyboardNavigationWraps: ["cdkListboxKeyboardNavigationWraps", "keyboardNavigationWraps"], keyboardNavigationSkipsDisabled: ["cdkListboxKeyboardNavigationSkipsDisabled", "keyboardNavigationSkipsDisabled"] }, outputs: { valueChange: "cdkListboxValueChange" }, host: { attributes: { "role": "listbox" }, listeners: { "focus": "_handleFocus()", "keydown": "_handleKeydown($event)", "focusout": "_handleFocusOut($event)" }, properties: { "id": "id", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "attr.aria-multiselectable": "multiple", "attr.aria-activedescendant": "_getAriaActiveDescendant()", "attr.aria-orientation": "orientation" }, classAttribute: "cdk-listbox" }, providers: [
|
|
668
813
|
{
|
|
669
814
|
provide: NG_VALUE_ACCESSOR,
|
|
670
815
|
useExisting: forwardRef(() => CdkListbox),
|
|
@@ -730,6 +875,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImpor
|
|
|
730
875
|
}], compareWith: [{
|
|
731
876
|
type: Input,
|
|
732
877
|
args: ['cdkListboxCompareWith']
|
|
878
|
+
}], keyboardNavigationWraps: [{
|
|
879
|
+
type: Input,
|
|
880
|
+
args: ['cdkListboxKeyboardNavigationWraps']
|
|
881
|
+
}], keyboardNavigationSkipsDisabled: [{
|
|
882
|
+
type: Input,
|
|
883
|
+
args: ['cdkListboxKeyboardNavigationSkipsDisabled']
|
|
733
884
|
}], valueChange: [{
|
|
734
885
|
type: Output,
|
|
735
886
|
args: ['cdkListboxValueChange']
|