UNPKG

18.1 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2020 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, __values } from "tslib";
24import { MDCFoundation } from '@material/base/foundation';
25import { KEY } from '@material/dom/keyboard';
26import { MDCChipActionFocusBehavior, MDCChipActionType } from '../action/constants';
27import { MDCChipAnimation } from '../chip/constants';
28import { MDCChipSetAttributes, MDCChipSetEvents } from './constants';
29var Operator;
30(function (Operator) {
31 Operator[Operator["INCREMENT"] = 0] = "INCREMENT";
32 Operator[Operator["DECREMENT"] = 1] = "DECREMENT";
33})(Operator || (Operator = {}));
34/**
35 * MDCChipSetFoundation provides a foundation for all chips.
36 */
37var MDCChipSetFoundation = /** @class */ (function (_super) {
38 __extends(MDCChipSetFoundation, _super);
39 function MDCChipSetFoundation(adapter) {
40 return _super.call(this, __assign(__assign({}, MDCChipSetFoundation.defaultAdapter), adapter)) || this;
41 }
42 Object.defineProperty(MDCChipSetFoundation, "defaultAdapter", {
43 get: function () {
44 return {
45 announceMessage: function () { return undefined; },
46 emitEvent: function () { return undefined; },
47 getAttribute: function () { return null; },
48 getChipActionsAtIndex: function () { return []; },
49 getChipCount: function () { return 0; },
50 getChipIdAtIndex: function () { return ''; },
51 getChipIndexById: function () { return 0; },
52 isChipFocusableAtIndex: function () { return false; },
53 isChipSelectableAtIndex: function () { return false; },
54 isChipSelectedAtIndex: function () { return false; },
55 removeChipAtIndex: function () { },
56 setChipFocusAtIndex: function () { return undefined; },
57 setChipSelectedAtIndex: function () { return undefined; },
58 startChipAnimationAtIndex: function () { return undefined; },
59 };
60 },
61 enumerable: false,
62 configurable: true
63 });
64 MDCChipSetFoundation.prototype.handleChipAnimation = function (_a) {
65 var detail = _a.detail;
66 var chipID = detail.chipID, animation = detail.animation, isComplete = detail.isComplete, addedAnnouncement = detail.addedAnnouncement, removedAnnouncement = detail.removedAnnouncement;
67 var index = this.adapter.getChipIndexById(chipID);
68 if (animation === MDCChipAnimation.EXIT && isComplete) {
69 if (removedAnnouncement) {
70 this.adapter.announceMessage(removedAnnouncement);
71 }
72 this.removeAfterAnimation(index, chipID);
73 return;
74 }
75 if (animation === MDCChipAnimation.ENTER && isComplete && addedAnnouncement) {
76 this.adapter.announceMessage(addedAnnouncement);
77 return;
78 }
79 };
80 MDCChipSetFoundation.prototype.handleChipInteraction = function (_a) {
81 var detail = _a.detail;
82 var source = detail.source, chipID = detail.chipID, isSelectable = detail.isSelectable, isSelected = detail.isSelected, shouldRemove = detail.shouldRemove;
83 var index = this.adapter.getChipIndexById(chipID);
84 if (shouldRemove) {
85 this.removeChip(index);
86 return;
87 }
88 this.focusChip(index, source, MDCChipActionFocusBehavior.FOCUSABLE);
89 this.adapter.emitEvent(MDCChipSetEvents.INTERACTION, {
90 chipIndex: index,
91 chipID: chipID,
92 });
93 if (isSelectable) {
94 this.setSelection(index, source, !isSelected);
95 }
96 };
97 MDCChipSetFoundation.prototype.handleChipNavigation = function (_a) {
98 var detail = _a.detail;
99 var chipID = detail.chipID, key = detail.key, isRTL = detail.isRTL, source = detail.source;
100 var index = this.adapter.getChipIndexById(chipID);
101 var toNextChip = (key === KEY.ARROW_RIGHT && !isRTL) ||
102 (key === KEY.ARROW_LEFT && isRTL);
103 if (toNextChip) {
104 // Start from the next chip so we increment the index
105 this.focusNextChipFrom(index + 1);
106 return;
107 }
108 var toPreviousChip = (key === KEY.ARROW_LEFT && !isRTL) ||
109 (key === KEY.ARROW_RIGHT && isRTL);
110 if (toPreviousChip) {
111 // Start from the previous chip so we decrement the index
112 this.focusPrevChipFrom(index - 1);
113 return;
114 }
115 if (key === KEY.ARROW_DOWN) {
116 // Start from the next chip so we increment the index
117 this.focusNextChipFrom(index + 1, source);
118 return;
119 }
120 if (key === KEY.ARROW_UP) {
121 // Start from the previous chip so we decrement the index
122 this.focusPrevChipFrom(index - 1, source);
123 return;
124 }
125 if (key === KEY.HOME) {
126 this.focusNextChipFrom(0, source);
127 return;
128 }
129 if (key === KEY.END) {
130 this.focusPrevChipFrom(this.adapter.getChipCount() - 1, source);
131 return;
132 }
133 };
134 /** Returns the unique selected indexes of the chips. */
135 MDCChipSetFoundation.prototype.getSelectedChipIndexes = function () {
136 var e_1, _a;
137 var selectedIndexes = new Set();
138 var chipCount = this.adapter.getChipCount();
139 for (var i = 0; i < chipCount; i++) {
140 var actions = this.adapter.getChipActionsAtIndex(i);
141 try {
142 for (var actions_1 = (e_1 = void 0, __values(actions)), actions_1_1 = actions_1.next(); !actions_1_1.done; actions_1_1 = actions_1.next()) {
143 var action = actions_1_1.value;
144 if (this.adapter.isChipSelectedAtIndex(i, action)) {
145 selectedIndexes.add(i);
146 }
147 }
148 }
149 catch (e_1_1) { e_1 = { error: e_1_1 }; }
150 finally {
151 try {
152 if (actions_1_1 && !actions_1_1.done && (_a = actions_1.return)) _a.call(actions_1);
153 }
154 finally { if (e_1) throw e_1.error; }
155 }
156 }
157 return selectedIndexes;
158 };
159 /** Sets the selected state of the chip at the given index and action. */
160 MDCChipSetFoundation.prototype.setChipSelected = function (index, action, isSelected) {
161 if (this.adapter.isChipSelectableAtIndex(index, action)) {
162 this.setSelection(index, action, isSelected);
163 }
164 };
165 /** Returns the selected state of the chip at the given index and action. */
166 MDCChipSetFoundation.prototype.isChipSelected = function (index, action) {
167 return this.adapter.isChipSelectedAtIndex(index, action);
168 };
169 /** Removes the chip at the given index. */
170 MDCChipSetFoundation.prototype.removeChip = function (index) {
171 // Early exit if the index is out of bounds
172 if (index >= this.adapter.getChipCount() || index < 0)
173 return;
174 this.adapter.startChipAnimationAtIndex(index, MDCChipAnimation.EXIT);
175 this.adapter.emitEvent(MDCChipSetEvents.REMOVAL, {
176 chipID: this.adapter.getChipIdAtIndex(index),
177 chipIndex: index,
178 isComplete: false,
179 });
180 };
181 MDCChipSetFoundation.prototype.addChip = function (index) {
182 // Early exit if the index is out of bounds
183 if (index >= this.adapter.getChipCount() || index < 0)
184 return;
185 this.adapter.startChipAnimationAtIndex(index, MDCChipAnimation.ENTER);
186 };
187 /**
188 * Increments to find the first focusable chip.
189 */
190 MDCChipSetFoundation.prototype.focusNextChipFrom = function (startIndex, targetAction) {
191 var chipCount = this.adapter.getChipCount();
192 for (var i = startIndex; i < chipCount; i++) {
193 var focusableAction = this.getFocusableAction(i, Operator.INCREMENT, targetAction);
194 if (focusableAction) {
195 this.focusChip(i, focusableAction, MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED);
196 return;
197 }
198 }
199 };
200 /**
201 * Decrements to find the first focusable chip. Takes an optional target
202 * action that can be used to focus the first matching focusable action.
203 */
204 MDCChipSetFoundation.prototype.focusPrevChipFrom = function (startIndex, targetAction) {
205 for (var i = startIndex; i > -1; i--) {
206 var focusableAction = this.getFocusableAction(i, Operator.DECREMENT, targetAction);
207 if (focusableAction) {
208 this.focusChip(i, focusableAction, MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED);
209 return;
210 }
211 }
212 };
213 /** Returns the appropriate focusable action, or null if none exist. */
214 MDCChipSetFoundation.prototype.getFocusableAction = function (index, op, targetAction) {
215 var actions = this.adapter.getChipActionsAtIndex(index);
216 // Reverse the actions if decrementing
217 if (op === Operator.DECREMENT)
218 actions.reverse();
219 if (targetAction) {
220 return this.getMatchingFocusableAction(index, actions, targetAction);
221 }
222 return this.getFirstFocusableAction(index, actions);
223 };
224 /**
225 * Returs the first focusable action, regardless of type, or null if no
226 * focusable actions exist.
227 */
228 MDCChipSetFoundation.prototype.getFirstFocusableAction = function (index, actions) {
229 var e_2, _a;
230 try {
231 for (var actions_2 = __values(actions), actions_2_1 = actions_2.next(); !actions_2_1.done; actions_2_1 = actions_2.next()) {
232 var action = actions_2_1.value;
233 if (this.adapter.isChipFocusableAtIndex(index, action)) {
234 return action;
235 }
236 }
237 }
238 catch (e_2_1) { e_2 = { error: e_2_1 }; }
239 finally {
240 try {
241 if (actions_2_1 && !actions_2_1.done && (_a = actions_2.return)) _a.call(actions_2);
242 }
243 finally { if (e_2) throw e_2.error; }
244 }
245 return null;
246 };
247 /**
248 * If the actions contain a focusable action that matches the target action,
249 * return that. Otherwise, return the first focusable action, or null if no
250 * focusable action exists.
251 */
252 MDCChipSetFoundation.prototype.getMatchingFocusableAction = function (index, actions, targetAction) {
253 var e_3, _a;
254 var focusableAction = null;
255 try {
256 for (var actions_3 = __values(actions), actions_3_1 = actions_3.next(); !actions_3_1.done; actions_3_1 = actions_3.next()) {
257 var action = actions_3_1.value;
258 if (this.adapter.isChipFocusableAtIndex(index, action)) {
259 focusableAction = action;
260 }
261 // Exit and return the focusable action if it matches the target
262 if (focusableAction === targetAction) {
263 return focusableAction;
264 }
265 }
266 }
267 catch (e_3_1) { e_3 = { error: e_3_1 }; }
268 finally {
269 try {
270 if (actions_3_1 && !actions_3_1.done && (_a = actions_3.return)) _a.call(actions_3);
271 }
272 finally { if (e_3) throw e_3.error; }
273 }
274 return focusableAction;
275 };
276 MDCChipSetFoundation.prototype.focusChip = function (index, action, focus) {
277 var e_4, _a;
278 this.adapter.setChipFocusAtIndex(index, action, focus);
279 var chipCount = this.adapter.getChipCount();
280 for (var i = 0; i < chipCount; i++) {
281 var actions = this.adapter.getChipActionsAtIndex(i);
282 try {
283 for (var actions_4 = (e_4 = void 0, __values(actions)), actions_4_1 = actions_4.next(); !actions_4_1.done; actions_4_1 = actions_4.next()) {
284 var chipAction = actions_4_1.value;
285 // Skip the action and index provided since we set it above
286 if (chipAction === action && i === index)
287 continue;
288 this.adapter.setChipFocusAtIndex(i, chipAction, MDCChipActionFocusBehavior.NOT_FOCUSABLE);
289 }
290 }
291 catch (e_4_1) { e_4 = { error: e_4_1 }; }
292 finally {
293 try {
294 if (actions_4_1 && !actions_4_1.done && (_a = actions_4.return)) _a.call(actions_4);
295 }
296 finally { if (e_4) throw e_4.error; }
297 }
298 }
299 };
300 MDCChipSetFoundation.prototype.supportsMultiSelect = function () {
301 return this.adapter.getAttribute(MDCChipSetAttributes.ARIA_MULTISELECTABLE) === 'true';
302 };
303 MDCChipSetFoundation.prototype.setSelection = function (index, action, isSelected) {
304 var e_5, _a;
305 this.adapter.setChipSelectedAtIndex(index, action, isSelected);
306 this.adapter.emitEvent(MDCChipSetEvents.SELECTION, {
307 chipID: this.adapter.getChipIdAtIndex(index),
308 chipIndex: index,
309 isSelected: isSelected,
310 });
311 // Early exit if we support multi-selection
312 if (this.supportsMultiSelect()) {
313 return;
314 }
315 // If we get here, we ony support single selection. This means we need to
316 // unselect all chips
317 var chipCount = this.adapter.getChipCount();
318 for (var i = 0; i < chipCount; i++) {
319 var actions = this.adapter.getChipActionsAtIndex(i);
320 try {
321 for (var actions_5 = (e_5 = void 0, __values(actions)), actions_5_1 = actions_5.next(); !actions_5_1.done; actions_5_1 = actions_5.next()) {
322 var chipAction = actions_5_1.value;
323 // Skip the action and index provided since we set it above
324 if (chipAction === action && i === index)
325 continue;
326 this.adapter.setChipSelectedAtIndex(i, chipAction, false);
327 }
328 }
329 catch (e_5_1) { e_5 = { error: e_5_1 }; }
330 finally {
331 try {
332 if (actions_5_1 && !actions_5_1.done && (_a = actions_5.return)) _a.call(actions_5);
333 }
334 finally { if (e_5) throw e_5.error; }
335 }
336 }
337 };
338 MDCChipSetFoundation.prototype.removeAfterAnimation = function (index, chipID) {
339 this.adapter.removeChipAtIndex(index);
340 this.adapter.emitEvent(MDCChipSetEvents.REMOVAL, {
341 chipIndex: index,
342 isComplete: true,
343 chipID: chipID,
344 });
345 var chipCount = this.adapter.getChipCount();
346 // Early exit if we have an empty chip set
347 if (chipCount <= 0)
348 return;
349 this.focusNearestFocusableAction(index);
350 };
351 /**
352 * Find the first focusable action by moving bidirectionally horizontally
353 * from the start index.
354 *
355 * Given chip set [A, B, C, D, E, F, G]...
356 * Let's say we remove chip "F". We don't know where the nearest focusable
357 * action is since any of them could be disabled. The nearest focusable
358 * action could be E, it could be G, it could even be A. To find it, we
359 * start from the source index (5 for "F" in this case) and move out
360 * horizontally, checking each chip at each index.
361 *
362 */
363 MDCChipSetFoundation.prototype.focusNearestFocusableAction = function (index) {
364 var chipCount = this.adapter.getChipCount();
365 var decrIndex = index;
366 var incrIndex = index;
367 while (decrIndex > -1 || incrIndex < chipCount) {
368 var focusAction = this.getNearestFocusableAction(decrIndex, incrIndex, MDCChipActionType.TRAILING);
369 if (focusAction) {
370 this.focusChip(focusAction.index, focusAction.action, MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED);
371 return;
372 }
373 decrIndex--;
374 incrIndex++;
375 }
376 };
377 MDCChipSetFoundation.prototype.getNearestFocusableAction = function (decrIndex, incrIndex, actionType) {
378 var decrAction = this.getFocusableAction(decrIndex, Operator.DECREMENT, actionType);
379 if (decrAction) {
380 return {
381 index: decrIndex,
382 action: decrAction,
383 };
384 }
385 // Early exit if the incremented and decremented indices are identical
386 if (incrIndex === decrIndex)
387 return null;
388 var incrAction = this.getFocusableAction(incrIndex, Operator.INCREMENT, actionType);
389 if (incrAction) {
390 return {
391 index: incrIndex,
392 action: incrAction,
393 };
394 }
395 return null;
396 };
397 return MDCChipSetFoundation;
398}(MDCFoundation));
399export { MDCChipSetFoundation };
400// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
401export default MDCChipSetFoundation;
402//# sourceMappingURL=foundation.js.map
\No newline at end of file