UNPKG

18.1 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2016 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 { cssClasses, Direction, EventSource, jumpChipKeys, navigationKeys, strings } from './constants';
26var emptyClientRect = {
27 bottom: 0,
28 height: 0,
29 left: 0,
30 right: 0,
31 top: 0,
32 width: 0,
33};
34var FocusBehavior;
35(function (FocusBehavior) {
36 FocusBehavior[FocusBehavior["SHOULD_FOCUS"] = 0] = "SHOULD_FOCUS";
37 FocusBehavior[FocusBehavior["SHOULD_NOT_FOCUS"] = 1] = "SHOULD_NOT_FOCUS";
38})(FocusBehavior || (FocusBehavior = {}));
39var MDCChipFoundation = /** @class */ (function (_super) {
40 __extends(MDCChipFoundation, _super);
41 function MDCChipFoundation(adapter) {
42 var _this = _super.call(this, __assign(__assign({}, MDCChipFoundation.defaultAdapter), adapter)) || this;
43 /** Whether a trailing icon click should immediately trigger exit/removal of the chip. */
44 _this.shouldRemoveOnTrailingIconClick = true;
45 /**
46 * Whether the primary action should receive focus on click. Should only be
47 * set to true for clients who programmatically give focus to a different
48 * element on the page when a chip is clicked (like a menu).
49 */
50 _this.shouldFocusPrimaryActionOnClick = true;
51 return _this;
52 }
53 Object.defineProperty(MDCChipFoundation, "strings", {
54 get: function () {
55 return strings;
56 },
57 enumerable: false,
58 configurable: true
59 });
60 Object.defineProperty(MDCChipFoundation, "cssClasses", {
61 get: function () {
62 return cssClasses;
63 },
64 enumerable: false,
65 configurable: true
66 });
67 Object.defineProperty(MDCChipFoundation, "defaultAdapter", {
68 get: function () {
69 return {
70 addClass: function () { return undefined; },
71 addClassToLeadingIcon: function () { return undefined; },
72 eventTargetHasClass: function () { return false; },
73 focusPrimaryAction: function () { return undefined; },
74 focusTrailingAction: function () { return undefined; },
75 getAttribute: function () { return null; },
76 getCheckmarkBoundingClientRect: function () { return emptyClientRect; },
77 getComputedStyleValue: function () { return ''; },
78 getRootBoundingClientRect: function () { return emptyClientRect; },
79 hasClass: function () { return false; },
80 hasLeadingIcon: function () { return false; },
81 isRTL: function () { return false; },
82 isTrailingActionNavigable: function () { return false; },
83 notifyEditFinish: function () { return undefined; },
84 notifyEditStart: function () { return undefined; },
85 notifyInteraction: function () { return undefined; },
86 notifyNavigation: function () { return undefined; },
87 notifyRemoval: function () { return undefined; },
88 notifySelection: function () { return undefined; },
89 notifyTrailingIconInteraction: function () { return undefined; },
90 removeClass: function () { return undefined; },
91 removeClassFromLeadingIcon: function () { return undefined; },
92 removeTrailingActionFocus: function () { return undefined; },
93 setPrimaryActionAttr: function () { return undefined; },
94 setStyleProperty: function () { return undefined; },
95 };
96 },
97 enumerable: false,
98 configurable: true
99 });
100 MDCChipFoundation.prototype.isSelected = function () {
101 return this.adapter.hasClass(cssClasses.SELECTED);
102 };
103 MDCChipFoundation.prototype.isEditable = function () {
104 return this.adapter.hasClass(cssClasses.EDITABLE);
105 };
106 MDCChipFoundation.prototype.isEditing = function () {
107 return this.adapter.hasClass(cssClasses.EDITING);
108 };
109 MDCChipFoundation.prototype.setSelected = function (selected) {
110 this.setSelectedImpl(selected);
111 this.notifySelection(selected);
112 };
113 MDCChipFoundation.prototype.setSelectedFromChipSet = function (selected, shouldNotifyClients) {
114 this.setSelectedImpl(selected);
115 if (shouldNotifyClients) {
116 this.notifyIgnoredSelection(selected);
117 }
118 };
119 MDCChipFoundation.prototype.getShouldRemoveOnTrailingIconClick = function () {
120 return this.shouldRemoveOnTrailingIconClick;
121 };
122 MDCChipFoundation.prototype.setShouldRemoveOnTrailingIconClick = function (shouldRemove) {
123 this.shouldRemoveOnTrailingIconClick = shouldRemove;
124 };
125 MDCChipFoundation.prototype.setShouldFocusPrimaryActionOnClick = function (shouldFocus) {
126 this.shouldFocusPrimaryActionOnClick = shouldFocus;
127 };
128 MDCChipFoundation.prototype.getDimensions = function () {
129 var _this = this;
130 var getRootRect = function () { return _this.adapter.getRootBoundingClientRect(); };
131 var getCheckmarkRect = function () {
132 return _this.adapter.getCheckmarkBoundingClientRect();
133 };
134 // When a chip has a checkmark and not a leading icon, the bounding rect changes in size depending on the current
135 // size of the checkmark.
136 if (!this.adapter.hasLeadingIcon()) {
137 var checkmarkRect = getCheckmarkRect();
138 if (checkmarkRect) {
139 var rootRect = getRootRect();
140 // Checkmark is a square, meaning the client rect's width and height are identical once the animation completes.
141 // However, the checkbox is initially hidden by setting the width to 0.
142 // To account for an initial width of 0, we use the checkbox's height instead (which equals the end-state width)
143 // when adding it to the root client rect's width.
144 return {
145 bottom: rootRect.bottom,
146 height: rootRect.height,
147 left: rootRect.left,
148 right: rootRect.right,
149 top: rootRect.top,
150 width: rootRect.width + checkmarkRect.height,
151 };
152 }
153 }
154 return getRootRect();
155 };
156 /**
157 * Begins the exit animation which leads to removal of the chip.
158 */
159 MDCChipFoundation.prototype.beginExit = function () {
160 this.adapter.addClass(cssClasses.CHIP_EXIT);
161 };
162 MDCChipFoundation.prototype.handleClick = function () {
163 this.adapter.notifyInteraction();
164 this.setPrimaryActionFocusable(this.getFocusBehavior());
165 };
166 MDCChipFoundation.prototype.handleDoubleClick = function () {
167 if (this.isEditable()) {
168 this.startEditing();
169 }
170 };
171 /**
172 * Handles a transition end event on the root element.
173 */
174 MDCChipFoundation.prototype.handleTransitionEnd = function (evt) {
175 var _this = this;
176 // Handle transition end event on the chip when it is about to be removed.
177 var shouldHandle = this.adapter.eventTargetHasClass(evt.target, cssClasses.CHIP_EXIT);
178 var widthIsAnimating = evt.propertyName === 'width';
179 var opacityIsAnimating = evt.propertyName === 'opacity';
180 if (shouldHandle && opacityIsAnimating) {
181 // See: https://css-tricks.com/using-css-transitions-auto-dimensions/#article-header-id-5
182 var chipWidth_1 = this.adapter.getComputedStyleValue('width');
183 // On the next frame (once we get the computed width), explicitly set the chip's width
184 // to its current pixel width, so we aren't transitioning out of 'auto'.
185 requestAnimationFrame(function () {
186 _this.adapter.setStyleProperty('width', chipWidth_1);
187 // To mitigate jitter, start transitioning padding and margin before width.
188 _this.adapter.setStyleProperty('padding', '0');
189 _this.adapter.setStyleProperty('margin', '0');
190 // On the next frame (once width is explicitly set), transition width to 0.
191 requestAnimationFrame(function () {
192 _this.adapter.setStyleProperty('width', '0');
193 });
194 });
195 return;
196 }
197 if (shouldHandle && widthIsAnimating) {
198 this.removeFocus();
199 var removedAnnouncement = this.adapter.getAttribute(strings.REMOVED_ANNOUNCEMENT_ATTRIBUTE);
200 this.adapter.notifyRemoval(removedAnnouncement);
201 }
202 // Handle a transition end event on the leading icon or checkmark, since the transition end event bubbles.
203 if (!opacityIsAnimating) {
204 return;
205 }
206 var shouldHideLeadingIcon = this.adapter.eventTargetHasClass(evt.target, cssClasses.LEADING_ICON) &&
207 this.adapter.hasClass(cssClasses.SELECTED);
208 var shouldShowLeadingIcon = this.adapter.eventTargetHasClass(evt.target, cssClasses.CHECKMARK) &&
209 !this.adapter.hasClass(cssClasses.SELECTED);
210 if (shouldHideLeadingIcon) {
211 this.adapter.addClassToLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
212 return;
213 }
214 if (shouldShowLeadingIcon) {
215 this.adapter.removeClassFromLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
216 return;
217 }
218 };
219 MDCChipFoundation.prototype.handleFocusIn = function (evt) {
220 // Early exit if the event doesn't come from the primary action
221 if (!this.eventFromPrimaryAction(evt)) {
222 return;
223 }
224 this.adapter.addClass(cssClasses.PRIMARY_ACTION_FOCUSED);
225 };
226 MDCChipFoundation.prototype.handleFocusOut = function (evt) {
227 // Early exit if the event doesn't come from the primary action
228 if (!this.eventFromPrimaryAction(evt)) {
229 return;
230 }
231 if (this.isEditing()) {
232 this.finishEditing();
233 }
234 this.adapter.removeClass(cssClasses.PRIMARY_ACTION_FOCUSED);
235 };
236 /**
237 * Handles an interaction event on the trailing icon element. This is used to
238 * prevent the ripple from activating on interaction with the trailing icon.
239 */
240 MDCChipFoundation.prototype.handleTrailingActionInteraction = function () {
241 this.adapter.notifyTrailingIconInteraction();
242 this.removeChip();
243 };
244 /**
245 * Handles a keydown event from the root element.
246 */
247 MDCChipFoundation.prototype.handleKeydown = function (evt) {
248 if (this.isEditing()) {
249 if (this.shouldFinishEditing(evt)) {
250 evt.preventDefault();
251 this.finishEditing();
252 }
253 // When editing, the foundation should only handle key events that finish
254 // the editing process.
255 return;
256 }
257 if (this.isEditable()) {
258 if (this.shouldStartEditing(evt)) {
259 evt.preventDefault();
260 this.startEditing();
261 }
262 }
263 if (this.shouldNotifyInteraction(evt)) {
264 this.adapter.notifyInteraction();
265 this.setPrimaryActionFocusable(this.getFocusBehavior());
266 return;
267 }
268 if (this.isDeleteAction(evt)) {
269 evt.preventDefault();
270 this.removeChip();
271 return;
272 }
273 // Early exit if the key is not usable
274 if (!navigationKeys.has(evt.key)) {
275 return;
276 }
277 // Prevent default behavior for movement keys which could include scrolling
278 evt.preventDefault();
279 this.focusNextAction(evt.key, EventSource.PRIMARY);
280 };
281 MDCChipFoundation.prototype.handleTrailingActionNavigation = function (evt) {
282 this.focusNextAction(evt.detail.key, EventSource.TRAILING);
283 };
284 /**
285 * Called by the chip set to remove focus from the chip actions.
286 */
287 MDCChipFoundation.prototype.removeFocus = function () {
288 this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '-1');
289 this.adapter.removeTrailingActionFocus();
290 };
291 /**
292 * Called by the chip set to focus the primary action.
293 *
294 */
295 MDCChipFoundation.prototype.focusPrimaryAction = function () {
296 this.setPrimaryActionFocusable(FocusBehavior.SHOULD_FOCUS);
297 };
298 /**
299 * Called by the chip set to focus the trailing action (if present), otherwise
300 * gives focus to the trailing action.
301 */
302 MDCChipFoundation.prototype.focusTrailingAction = function () {
303 var trailingActionIsNavigable = this.adapter.isTrailingActionNavigable();
304 if (trailingActionIsNavigable) {
305 this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '-1');
306 this.adapter.focusTrailingAction();
307 return;
308 }
309 this.focusPrimaryAction();
310 };
311 MDCChipFoundation.prototype.setPrimaryActionFocusable = function (focusBehavior) {
312 this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '0');
313 if (focusBehavior === FocusBehavior.SHOULD_FOCUS) {
314 this.adapter.focusPrimaryAction();
315 }
316 this.adapter.removeTrailingActionFocus();
317 };
318 MDCChipFoundation.prototype.getFocusBehavior = function () {
319 if (this.shouldFocusPrimaryActionOnClick) {
320 return FocusBehavior.SHOULD_FOCUS;
321 }
322 return FocusBehavior.SHOULD_NOT_FOCUS;
323 };
324 MDCChipFoundation.prototype.focusNextAction = function (key, source) {
325 var isTrailingActionNavigable = this.adapter.isTrailingActionNavigable();
326 var dir = this.getDirection(key);
327 // Early exit if the key should jump chips
328 if (jumpChipKeys.has(key) || !isTrailingActionNavigable) {
329 this.adapter.notifyNavigation(key, source);
330 return;
331 }
332 if (source === EventSource.PRIMARY && dir === Direction.RIGHT) {
333 this.focusTrailingAction();
334 return;
335 }
336 if (source === EventSource.TRAILING && dir === Direction.LEFT) {
337 this.focusPrimaryAction();
338 return;
339 }
340 this.adapter.notifyNavigation(key, EventSource.NONE);
341 };
342 MDCChipFoundation.prototype.getDirection = function (key) {
343 var isRTL = this.adapter.isRTL();
344 var isLeftKey = key === strings.ARROW_LEFT_KEY || key === strings.IE_ARROW_LEFT_KEY;
345 var isRightKey = key === strings.ARROW_RIGHT_KEY || key === strings.IE_ARROW_RIGHT_KEY;
346 if (!isRTL && isLeftKey || isRTL && isRightKey) {
347 return Direction.LEFT;
348 }
349 return Direction.RIGHT;
350 };
351 MDCChipFoundation.prototype.removeChip = function () {
352 if (this.shouldRemoveOnTrailingIconClick) {
353 this.beginExit();
354 }
355 };
356 MDCChipFoundation.prototype.shouldStartEditing = function (evt) {
357 return this.eventFromPrimaryAction(evt) && evt.key === strings.ENTER_KEY;
358 };
359 MDCChipFoundation.prototype.shouldFinishEditing = function (evt) {
360 return evt.key === strings.ENTER_KEY;
361 };
362 MDCChipFoundation.prototype.shouldNotifyInteraction = function (evt) {
363 return evt.key === strings.ENTER_KEY || evt.key === strings.SPACEBAR_KEY;
364 };
365 MDCChipFoundation.prototype.isDeleteAction = function (evt) {
366 var isDeletable = this.adapter.hasClass(cssClasses.DELETABLE);
367 return isDeletable &&
368 (evt.key === strings.BACKSPACE_KEY || evt.key === strings.DELETE_KEY ||
369 evt.key === strings.IE_DELETE_KEY);
370 };
371 MDCChipFoundation.prototype.setSelectedImpl = function (selected) {
372 if (selected) {
373 this.adapter.addClass(cssClasses.SELECTED);
374 this.adapter.setPrimaryActionAttr(strings.ARIA_CHECKED, 'true');
375 }
376 else {
377 this.adapter.removeClass(cssClasses.SELECTED);
378 this.adapter.setPrimaryActionAttr(strings.ARIA_CHECKED, 'false');
379 }
380 };
381 MDCChipFoundation.prototype.notifySelection = function (selected) {
382 this.adapter.notifySelection(selected, false);
383 };
384 MDCChipFoundation.prototype.notifyIgnoredSelection = function (selected) {
385 this.adapter.notifySelection(selected, true);
386 };
387 MDCChipFoundation.prototype.eventFromPrimaryAction = function (evt) {
388 return this.adapter.eventTargetHasClass(evt.target, cssClasses.PRIMARY_ACTION);
389 };
390 MDCChipFoundation.prototype.startEditing = function () {
391 this.adapter.addClass(cssClasses.EDITING);
392 this.adapter.notifyEditStart();
393 };
394 MDCChipFoundation.prototype.finishEditing = function () {
395 this.adapter.removeClass(cssClasses.EDITING);
396 this.adapter.notifyEditFinish();
397 };
398 return MDCChipFoundation;
399}(MDCFoundation));
400export { MDCChipFoundation };
401// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
402export default MDCChipFoundation;
403//# sourceMappingURL=foundation.js.map
\No newline at end of file