UNPKG

29.5 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2018 Google Inc.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 */
23import { __assign, __extends } from "tslib";
24import { MDCFoundation } from '@material/base/foundation';
25import { normalizeKey } from '@material/dom/keyboard';
26import { cssClasses, numbers, strings } from './constants';
27import { preventDefaultEvent } from './events';
28import * as typeahead from './typeahead';
29function isNumberArray(selectedIndex) {
30 return selectedIndex instanceof Array;
31}
32var MDCListFoundation = /** @class */ (function (_super) {
33 __extends(MDCListFoundation, _super);
34 function MDCListFoundation(adapter) {
35 var _this = _super.call(this, __assign(__assign({}, MDCListFoundation.defaultAdapter), adapter)) || this;
36 _this.wrapFocus = false;
37 _this.isVertical = true;
38 _this.isSingleSelectionList = false;
39 _this.selectedIndex = numbers.UNSET_INDEX;
40 _this.focusedItemIndex = numbers.UNSET_INDEX;
41 _this.useActivatedClass = false;
42 _this.useSelectedAttr = false;
43 _this.ariaCurrentAttrValue = null;
44 _this.isCheckboxList = false;
45 _this.isRadioList = false;
46 _this.hasTypeahead = false;
47 // Transiently holds current typeahead prefix from user.
48 _this.typeaheadState = typeahead.initState();
49 _this.sortedIndexByFirstChar = new Map();
50 return _this;
51 }
52 Object.defineProperty(MDCListFoundation, "strings", {
53 get: function () {
54 return strings;
55 },
56 enumerable: false,
57 configurable: true
58 });
59 Object.defineProperty(MDCListFoundation, "cssClasses", {
60 get: function () {
61 return cssClasses;
62 },
63 enumerable: false,
64 configurable: true
65 });
66 Object.defineProperty(MDCListFoundation, "numbers", {
67 get: function () {
68 return numbers;
69 },
70 enumerable: false,
71 configurable: true
72 });
73 Object.defineProperty(MDCListFoundation, "defaultAdapter", {
74 get: function () {
75 return {
76 addClassForElementIndex: function () { return undefined; },
77 focusItemAtIndex: function () { return undefined; },
78 getAttributeForElementIndex: function () { return null; },
79 getFocusedElementIndex: function () { return 0; },
80 getListItemCount: function () { return 0; },
81 hasCheckboxAtIndex: function () { return false; },
82 hasRadioAtIndex: function () { return false; },
83 isCheckboxCheckedAtIndex: function () { return false; },
84 isFocusInsideList: function () { return false; },
85 isRootFocused: function () { return false; },
86 listItemAtIndexHasClass: function () { return false; },
87 notifyAction: function () { return undefined; },
88 removeClassForElementIndex: function () { return undefined; },
89 setAttributeForElementIndex: function () { return undefined; },
90 setCheckedCheckboxOrRadioAtIndex: function () { return undefined; },
91 setTabIndexForListItemChildren: function () { return undefined; },
92 getPrimaryTextAtIndex: function () { return ''; },
93 };
94 },
95 enumerable: false,
96 configurable: true
97 });
98 MDCListFoundation.prototype.layout = function () {
99 if (this.adapter.getListItemCount() === 0) {
100 return;
101 }
102 // TODO(b/172274142): consider all items when determining the list's type.
103 if (this.adapter.hasCheckboxAtIndex(0)) {
104 this.isCheckboxList = true;
105 }
106 else if (this.adapter.hasRadioAtIndex(0)) {
107 this.isRadioList = true;
108 }
109 else {
110 this.maybeInitializeSingleSelection();
111 }
112 if (this.hasTypeahead) {
113 this.sortedIndexByFirstChar = this.typeaheadInitSortedIndex();
114 }
115 };
116 /** Returns the index of the item that was last focused. */
117 MDCListFoundation.prototype.getFocusedItemIndex = function () {
118 return this.focusedItemIndex;
119 };
120 /** Toggles focus wrapping with keyboard navigation. */
121 MDCListFoundation.prototype.setWrapFocus = function (value) {
122 this.wrapFocus = value;
123 };
124 /**
125 * Toggles orientation direction for keyboard navigation (true for vertical,
126 * false for horizontal).
127 */
128 MDCListFoundation.prototype.setVerticalOrientation = function (value) {
129 this.isVertical = value;
130 };
131 /** Toggles single-selection behavior. */
132 MDCListFoundation.prototype.setSingleSelection = function (value) {
133 this.isSingleSelectionList = value;
134 if (value) {
135 this.maybeInitializeSingleSelection();
136 this.selectedIndex = this.getSelectedIndexFromDOM();
137 }
138 };
139 /**
140 * Automatically determines whether the list is single selection list. If so,
141 * initializes the internal state to match the selected item.
142 */
143 MDCListFoundation.prototype.maybeInitializeSingleSelection = function () {
144 var selectedItemIndex = this.getSelectedIndexFromDOM();
145 if (selectedItemIndex === numbers.UNSET_INDEX)
146 return;
147 var hasActivatedClass = this.adapter.listItemAtIndexHasClass(selectedItemIndex, cssClasses.LIST_ITEM_ACTIVATED_CLASS);
148 if (hasActivatedClass) {
149 this.setUseActivatedClass(true);
150 }
151 this.isSingleSelectionList = true;
152 this.selectedIndex = selectedItemIndex;
153 };
154 /** @return Index of the first selected item based on the DOM state. */
155 MDCListFoundation.prototype.getSelectedIndexFromDOM = function () {
156 var selectedIndex = numbers.UNSET_INDEX;
157 var listItemsCount = this.adapter.getListItemCount();
158 for (var i = 0; i < listItemsCount; i++) {
159 var hasSelectedClass = this.adapter.listItemAtIndexHasClass(i, cssClasses.LIST_ITEM_SELECTED_CLASS);
160 var hasActivatedClass = this.adapter.listItemAtIndexHasClass(i, cssClasses.LIST_ITEM_ACTIVATED_CLASS);
161 if (!(hasSelectedClass || hasActivatedClass)) {
162 continue;
163 }
164 selectedIndex = i;
165 break;
166 }
167 return selectedIndex;
168 };
169 /**
170 * Sets whether typeahead is enabled on the list.
171 * @param hasTypeahead Whether typeahead is enabled.
172 */
173 MDCListFoundation.prototype.setHasTypeahead = function (hasTypeahead) {
174 this.hasTypeahead = hasTypeahead;
175 if (hasTypeahead) {
176 this.sortedIndexByFirstChar = this.typeaheadInitSortedIndex();
177 }
178 };
179 /**
180 * @return Whether typeahead is currently matching a user-specified prefix.
181 */
182 MDCListFoundation.prototype.isTypeaheadInProgress = function () {
183 return this.hasTypeahead &&
184 typeahead.isTypingInProgress(this.typeaheadState);
185 };
186 /** Toggle use of the "activated" CSS class. */
187 MDCListFoundation.prototype.setUseActivatedClass = function (useActivated) {
188 this.useActivatedClass = useActivated;
189 };
190 /**
191 * Toggles use of the selected attribute (true for aria-selected, false for
192 * aria-checked).
193 */
194 MDCListFoundation.prototype.setUseSelectedAttribute = function (useSelected) {
195 this.useSelectedAttr = useSelected;
196 };
197 MDCListFoundation.prototype.getSelectedIndex = function () {
198 return this.selectedIndex;
199 };
200 MDCListFoundation.prototype.setSelectedIndex = function (index, _a) {
201 var _b = _a === void 0 ? {} : _a, forceUpdate = _b.forceUpdate;
202 if (!this.isIndexValid(index)) {
203 return;
204 }
205 if (this.isCheckboxList) {
206 this.setCheckboxAtIndex(index);
207 }
208 else if (this.isRadioList) {
209 this.setRadioAtIndex(index);
210 }
211 else {
212 this.setSingleSelectionAtIndex(index, { forceUpdate: forceUpdate });
213 }
214 };
215 /**
216 * Focus in handler for the list items.
217 */
218 MDCListFoundation.prototype.handleFocusIn = function (listItemIndex) {
219 if (listItemIndex >= 0) {
220 this.focusedItemIndex = listItemIndex;
221 this.adapter.setAttributeForElementIndex(listItemIndex, 'tabindex', '0');
222 this.adapter.setTabIndexForListItemChildren(listItemIndex, '0');
223 }
224 };
225 /**
226 * Focus out handler for the list items.
227 */
228 MDCListFoundation.prototype.handleFocusOut = function (listItemIndex) {
229 var _this = this;
230 if (listItemIndex >= 0) {
231 this.adapter.setAttributeForElementIndex(listItemIndex, 'tabindex', '-1');
232 this.adapter.setTabIndexForListItemChildren(listItemIndex, '-1');
233 }
234 /**
235 * Between Focusout & Focusin some browsers do not have focus on any
236 * element. Setting a delay to wait till the focus is moved to next element.
237 */
238 setTimeout(function () {
239 if (!_this.adapter.isFocusInsideList()) {
240 _this.setTabindexToFirstSelectedOrFocusedItem();
241 }
242 }, 0);
243 };
244 /**
245 * Key handler for the list.
246 */
247 MDCListFoundation.prototype.handleKeydown = function (event, isRootListItem, listItemIndex) {
248 var _this = this;
249 var isArrowLeft = normalizeKey(event) === 'ArrowLeft';
250 var isArrowUp = normalizeKey(event) === 'ArrowUp';
251 var isArrowRight = normalizeKey(event) === 'ArrowRight';
252 var isArrowDown = normalizeKey(event) === 'ArrowDown';
253 var isHome = normalizeKey(event) === 'Home';
254 var isEnd = normalizeKey(event) === 'End';
255 var isEnter = normalizeKey(event) === 'Enter';
256 var isSpace = normalizeKey(event) === 'Spacebar';
257 // Have to check both upper and lower case, because having caps lock on
258 // affects the value.
259 var isLetterA = event.key === 'A' || event.key === 'a';
260 if (this.adapter.isRootFocused()) {
261 if (isArrowUp || isEnd) {
262 event.preventDefault();
263 this.focusLastElement();
264 }
265 else if (isArrowDown || isHome) {
266 event.preventDefault();
267 this.focusFirstElement();
268 }
269 if (this.hasTypeahead) {
270 var handleKeydownOpts = {
271 event: event,
272 focusItemAtIndex: function (index) {
273 _this.focusItemAtIndex(index);
274 },
275 focusedItemIndex: -1,
276 isTargetListItem: isRootListItem,
277 sortedIndexByFirstChar: this.sortedIndexByFirstChar,
278 isItemAtIndexDisabled: function (index) {
279 return _this.adapter.listItemAtIndexHasClass(index, cssClasses.LIST_ITEM_DISABLED_CLASS);
280 },
281 };
282 typeahead.handleKeydown(handleKeydownOpts, this.typeaheadState);
283 }
284 return;
285 }
286 var currentIndex = this.adapter.getFocusedElementIndex();
287 if (currentIndex === -1) {
288 currentIndex = listItemIndex;
289 if (currentIndex < 0) {
290 // If this event doesn't have a mdc-list-item ancestor from the
291 // current list (not from a sublist), return early.
292 return;
293 }
294 }
295 if ((this.isVertical && isArrowDown) ||
296 (!this.isVertical && isArrowRight)) {
297 preventDefaultEvent(event);
298 this.focusNextElement(currentIndex);
299 }
300 else if ((this.isVertical && isArrowUp) || (!this.isVertical && isArrowLeft)) {
301 preventDefaultEvent(event);
302 this.focusPrevElement(currentIndex);
303 }
304 else if (isHome) {
305 preventDefaultEvent(event);
306 this.focusFirstElement();
307 }
308 else if (isEnd) {
309 preventDefaultEvent(event);
310 this.focusLastElement();
311 }
312 else if (isLetterA && event.ctrlKey && this.isCheckboxList) {
313 event.preventDefault();
314 this.toggleAll(this.selectedIndex === numbers.UNSET_INDEX ?
315 [] :
316 this.selectedIndex);
317 }
318 else if (isEnter || isSpace) {
319 if (isRootListItem) {
320 // Return early if enter key is pressed on anchor element which triggers
321 // synthetic MouseEvent event.
322 var target = event.target;
323 if (target && target.tagName === 'A' && isEnter) {
324 return;
325 }
326 preventDefaultEvent(event);
327 if (this.adapter.listItemAtIndexHasClass(currentIndex, cssClasses.LIST_ITEM_DISABLED_CLASS)) {
328 return;
329 }
330 if (!this.isTypeaheadInProgress()) {
331 if (this.isSelectableList()) {
332 this.setSelectedIndexOnAction(currentIndex);
333 }
334 this.adapter.notifyAction(currentIndex);
335 }
336 }
337 }
338 if (this.hasTypeahead) {
339 var handleKeydownOpts = {
340 event: event,
341 focusItemAtIndex: function (index) {
342 _this.focusItemAtIndex(index);
343 },
344 focusedItemIndex: this.focusedItemIndex,
345 isTargetListItem: isRootListItem,
346 sortedIndexByFirstChar: this.sortedIndexByFirstChar,
347 isItemAtIndexDisabled: function (index) { return _this.adapter.listItemAtIndexHasClass(index, cssClasses.LIST_ITEM_DISABLED_CLASS); },
348 };
349 typeahead.handleKeydown(handleKeydownOpts, this.typeaheadState);
350 }
351 };
352 /**
353 * Click handler for the list.
354 */
355 MDCListFoundation.prototype.handleClick = function (index, toggleCheckbox) {
356 if (index === numbers.UNSET_INDEX) {
357 return;
358 }
359 if (this.adapter.listItemAtIndexHasClass(index, cssClasses.LIST_ITEM_DISABLED_CLASS)) {
360 return;
361 }
362 if (this.isSelectableList()) {
363 this.setSelectedIndexOnAction(index, toggleCheckbox);
364 }
365 this.adapter.notifyAction(index);
366 };
367 /**
368 * Focuses the next element on the list.
369 */
370 MDCListFoundation.prototype.focusNextElement = function (index) {
371 var count = this.adapter.getListItemCount();
372 var nextIndex = index + 1;
373 if (nextIndex >= count) {
374 if (this.wrapFocus) {
375 nextIndex = 0;
376 }
377 else {
378 // Return early because last item is already focused.
379 return index;
380 }
381 }
382 this.focusItemAtIndex(nextIndex);
383 return nextIndex;
384 };
385 /**
386 * Focuses the previous element on the list.
387 */
388 MDCListFoundation.prototype.focusPrevElement = function (index) {
389 var prevIndex = index - 1;
390 if (prevIndex < 0) {
391 if (this.wrapFocus) {
392 prevIndex = this.adapter.getListItemCount() - 1;
393 }
394 else {
395 // Return early because first item is already focused.
396 return index;
397 }
398 }
399 this.focusItemAtIndex(prevIndex);
400 return prevIndex;
401 };
402 MDCListFoundation.prototype.focusFirstElement = function () {
403 this.focusItemAtIndex(0);
404 return 0;
405 };
406 MDCListFoundation.prototype.focusLastElement = function () {
407 var lastIndex = this.adapter.getListItemCount() - 1;
408 this.focusItemAtIndex(lastIndex);
409 return lastIndex;
410 };
411 MDCListFoundation.prototype.focusInitialElement = function () {
412 var initialIndex = this.getFirstSelectedOrFocusedItemIndex();
413 this.focusItemAtIndex(initialIndex);
414 return initialIndex;
415 };
416 /**
417 * @param itemIndex Index of the list item
418 * @param isEnabled Sets the list item to enabled or disabled.
419 */
420 MDCListFoundation.prototype.setEnabled = function (itemIndex, isEnabled) {
421 if (!this.isIndexValid(itemIndex)) {
422 return;
423 }
424 if (isEnabled) {
425 this.adapter.removeClassForElementIndex(itemIndex, cssClasses.LIST_ITEM_DISABLED_CLASS);
426 this.adapter.setAttributeForElementIndex(itemIndex, strings.ARIA_DISABLED, 'false');
427 }
428 else {
429 this.adapter.addClassForElementIndex(itemIndex, cssClasses.LIST_ITEM_DISABLED_CLASS);
430 this.adapter.setAttributeForElementIndex(itemIndex, strings.ARIA_DISABLED, 'true');
431 }
432 };
433 MDCListFoundation.prototype.setSingleSelectionAtIndex = function (index, _a) {
434 var _b = _a === void 0 ? {} : _a, forceUpdate = _b.forceUpdate;
435 if (this.selectedIndex === index && !forceUpdate) {
436 return;
437 }
438 var selectedClassName = cssClasses.LIST_ITEM_SELECTED_CLASS;
439 if (this.useActivatedClass) {
440 selectedClassName = cssClasses.LIST_ITEM_ACTIVATED_CLASS;
441 }
442 if (this.selectedIndex !== numbers.UNSET_INDEX) {
443 this.adapter.removeClassForElementIndex(this.selectedIndex, selectedClassName);
444 }
445 this.setAriaForSingleSelectionAtIndex(index);
446 this.setTabindexAtIndex(index);
447 if (index !== numbers.UNSET_INDEX) {
448 this.adapter.addClassForElementIndex(index, selectedClassName);
449 }
450 this.selectedIndex = index;
451 };
452 /**
453 * Sets aria attribute for single selection at given index.
454 */
455 MDCListFoundation.prototype.setAriaForSingleSelectionAtIndex = function (index) {
456 // Detect the presence of aria-current and get the value only during list
457 // initialization when it is in unset state.
458 if (this.selectedIndex === numbers.UNSET_INDEX) {
459 this.ariaCurrentAttrValue =
460 this.adapter.getAttributeForElementIndex(index, strings.ARIA_CURRENT);
461 }
462 var isAriaCurrent = this.ariaCurrentAttrValue !== null;
463 var ariaAttribute = isAriaCurrent ? strings.ARIA_CURRENT : strings.ARIA_SELECTED;
464 if (this.selectedIndex !== numbers.UNSET_INDEX) {
465 this.adapter.setAttributeForElementIndex(this.selectedIndex, ariaAttribute, 'false');
466 }
467 if (index !== numbers.UNSET_INDEX) {
468 var ariaAttributeValue = isAriaCurrent ? this.ariaCurrentAttrValue : 'true';
469 this.adapter.setAttributeForElementIndex(index, ariaAttribute, ariaAttributeValue);
470 }
471 };
472 /**
473 * Returns the attribute to use for indicating selection status.
474 */
475 MDCListFoundation.prototype.getSelectionAttribute = function () {
476 return this.useSelectedAttr ? strings.ARIA_SELECTED : strings.ARIA_CHECKED;
477 };
478 /**
479 * Toggles radio at give index. Radio doesn't change the checked state if it
480 * is already checked.
481 */
482 MDCListFoundation.prototype.setRadioAtIndex = function (index) {
483 var selectionAttribute = this.getSelectionAttribute();
484 this.adapter.setCheckedCheckboxOrRadioAtIndex(index, true);
485 if (this.selectedIndex !== numbers.UNSET_INDEX) {
486 this.adapter.setAttributeForElementIndex(this.selectedIndex, selectionAttribute, 'false');
487 }
488 this.adapter.setAttributeForElementIndex(index, selectionAttribute, 'true');
489 this.selectedIndex = index;
490 };
491 MDCListFoundation.prototype.setCheckboxAtIndex = function (index) {
492 var selectionAttribute = this.getSelectionAttribute();
493 for (var i = 0; i < this.adapter.getListItemCount(); i++) {
494 var isChecked = false;
495 if (index.indexOf(i) >= 0) {
496 isChecked = true;
497 }
498 this.adapter.setCheckedCheckboxOrRadioAtIndex(i, isChecked);
499 this.adapter.setAttributeForElementIndex(i, selectionAttribute, isChecked ? 'true' : 'false');
500 }
501 this.selectedIndex = index;
502 };
503 MDCListFoundation.prototype.setTabindexAtIndex = function (index) {
504 if (this.focusedItemIndex === numbers.UNSET_INDEX && index !== 0) {
505 // If some list item was selected set first list item's tabindex to -1.
506 // Generally, tabindex is set to 0 on first list item of list that has no
507 // preselected items.
508 this.adapter.setAttributeForElementIndex(0, 'tabindex', '-1');
509 }
510 else if (this.focusedItemIndex >= 0 && this.focusedItemIndex !== index) {
511 this.adapter.setAttributeForElementIndex(this.focusedItemIndex, 'tabindex', '-1');
512 }
513 // Set the previous selection's tabindex to -1. We need this because
514 // in selection menus that are not visible, programmatically setting an
515 // option will not change focus but will change where tabindex should be 0.
516 if (!(this.selectedIndex instanceof Array) &&
517 this.selectedIndex !== index) {
518 this.adapter.setAttributeForElementIndex(this.selectedIndex, 'tabindex', '-1');
519 }
520 if (index !== numbers.UNSET_INDEX) {
521 this.adapter.setAttributeForElementIndex(index, 'tabindex', '0');
522 }
523 };
524 /**
525 * @return Return true if it is single selectin list, checkbox list or radio
526 * list.
527 */
528 MDCListFoundation.prototype.isSelectableList = function () {
529 return this.isSingleSelectionList || this.isCheckboxList ||
530 this.isRadioList;
531 };
532 MDCListFoundation.prototype.setTabindexToFirstSelectedOrFocusedItem = function () {
533 var targetIndex = this.getFirstSelectedOrFocusedItemIndex();
534 this.setTabindexAtIndex(targetIndex);
535 };
536 MDCListFoundation.prototype.getFirstSelectedOrFocusedItemIndex = function () {
537 // Action lists retain focus on the most recently focused item.
538 if (!this.isSelectableList()) {
539 return Math.max(this.focusedItemIndex, 0);
540 }
541 // Single-selection lists focus the selected item.
542 if (typeof this.selectedIndex === 'number' &&
543 this.selectedIndex !== numbers.UNSET_INDEX) {
544 return this.selectedIndex;
545 }
546 // Multiple-selection lists focus the first selected item.
547 if (isNumberArray(this.selectedIndex) && this.selectedIndex.length > 0) {
548 return this.selectedIndex.reduce(function (minIndex, currentIndex) { return Math.min(minIndex, currentIndex); });
549 }
550 // Selection lists without a selection focus the first item.
551 return 0;
552 };
553 MDCListFoundation.prototype.isIndexValid = function (index) {
554 var _this = this;
555 if (index instanceof Array) {
556 if (!this.isCheckboxList) {
557 throw new Error('MDCListFoundation: Array of index is only supported for checkbox based list');
558 }
559 if (index.length === 0) {
560 return true;
561 }
562 else {
563 return index.some(function (i) { return _this.isIndexInRange(i); });
564 }
565 }
566 else if (typeof index === 'number') {
567 if (this.isCheckboxList) {
568 throw new Error("MDCListFoundation: Expected array of index for checkbox based list but got number: " + index);
569 }
570 return this.isIndexInRange(index) ||
571 this.isSingleSelectionList && index === numbers.UNSET_INDEX;
572 }
573 else {
574 return false;
575 }
576 };
577 MDCListFoundation.prototype.isIndexInRange = function (index) {
578 var listSize = this.adapter.getListItemCount();
579 return index >= 0 && index < listSize;
580 };
581 /**
582 * Sets selected index on user action, toggles checkbox / radio based on
583 * toggleCheckbox value. User interaction should not toggle list item(s) when
584 * disabled.
585 */
586 MDCListFoundation.prototype.setSelectedIndexOnAction = function (index, toggleCheckbox) {
587 if (toggleCheckbox === void 0) { toggleCheckbox = true; }
588 if (this.isCheckboxList) {
589 this.toggleCheckboxAtIndex(index, toggleCheckbox);
590 }
591 else {
592 this.setSelectedIndex(index);
593 }
594 };
595 MDCListFoundation.prototype.toggleCheckboxAtIndex = function (index, toggleCheckbox) {
596 var selectionAttribute = this.getSelectionAttribute();
597 var isChecked = this.adapter.isCheckboxCheckedAtIndex(index);
598 if (toggleCheckbox) {
599 isChecked = !isChecked;
600 this.adapter.setCheckedCheckboxOrRadioAtIndex(index, isChecked);
601 }
602 this.adapter.setAttributeForElementIndex(index, selectionAttribute, isChecked ? 'true' : 'false');
603 // If none of the checkbox items are selected and selectedIndex is not
604 // initialized then provide a default value.
605 var selectedIndexes = this.selectedIndex === numbers.UNSET_INDEX ?
606 [] :
607 this.selectedIndex.slice();
608 if (isChecked) {
609 selectedIndexes.push(index);
610 }
611 else {
612 selectedIndexes = selectedIndexes.filter(function (i) { return i !== index; });
613 }
614 this.selectedIndex = selectedIndexes;
615 };
616 MDCListFoundation.prototype.focusItemAtIndex = function (index) {
617 this.adapter.focusItemAtIndex(index);
618 this.focusedItemIndex = index;
619 };
620 MDCListFoundation.prototype.toggleAll = function (currentlySelectedIndexes) {
621 var count = this.adapter.getListItemCount();
622 // If all items are selected, deselect everything.
623 if (currentlySelectedIndexes.length === count) {
624 this.setCheckboxAtIndex([]);
625 }
626 else {
627 // Otherwise select all enabled options.
628 var allIndexes = [];
629 for (var i = 0; i < count; i++) {
630 if (!this.adapter.listItemAtIndexHasClass(i, cssClasses.LIST_ITEM_DISABLED_CLASS) ||
631 currentlySelectedIndexes.indexOf(i) > -1) {
632 allIndexes.push(i);
633 }
634 }
635 this.setCheckboxAtIndex(allIndexes);
636 }
637 };
638 /**
639 * Given the next desired character from the user, adds it to the typeahead
640 * buffer. Then, attempts to find the next option matching the buffer. Wraps
641 * around if at the end of options.
642 *
643 * @param nextChar The next character to add to the prefix buffer.
644 * @param startingIndex The index from which to start matching. Only relevant
645 * when starting a new match sequence. To start a new match sequence,
646 * clear the buffer using `clearTypeaheadBuffer`, or wait for the buffer
647 * to clear after a set interval defined in list foundation. Defaults to
648 * the currently focused index.
649 * @return The index of the matched item, or -1 if no match.
650 */
651 MDCListFoundation.prototype.typeaheadMatchItem = function (nextChar, startingIndex, skipFocus) {
652 var _this = this;
653 if (skipFocus === void 0) { skipFocus = false; }
654 var opts = {
655 focusItemAtIndex: function (index) {
656 _this.focusItemAtIndex(index);
657 },
658 focusedItemIndex: startingIndex ? startingIndex : this.focusedItemIndex,
659 nextChar: nextChar,
660 sortedIndexByFirstChar: this.sortedIndexByFirstChar,
661 skipFocus: skipFocus,
662 isItemAtIndexDisabled: function (index) { return _this.adapter.listItemAtIndexHasClass(index, cssClasses.LIST_ITEM_DISABLED_CLASS); }
663 };
664 return typeahead.matchItem(opts, this.typeaheadState);
665 };
666 /**
667 * Initializes the MDCListTextAndIndex data structure by indexing the current
668 * list items by primary text.
669 *
670 * @return The primary texts of all the list items sorted by first character.
671 */
672 MDCListFoundation.prototype.typeaheadInitSortedIndex = function () {
673 return typeahead.initSortedIndex(this.adapter.getListItemCount(), this.adapter.getPrimaryTextAtIndex);
674 };
675 /**
676 * Clears the typeahead buffer.
677 */
678 MDCListFoundation.prototype.clearTypeaheadBuffer = function () {
679 typeahead.clearBuffer(this.typeaheadState);
680 };
681 return MDCListFoundation;
682}(MDCFoundation));
683export { MDCListFoundation };
684// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
685export default MDCListFoundation;
686//# sourceMappingURL=foundation.js.map
\No newline at end of file