@material/chips
Version:
The Material Components for the Web chips component
403 lines • 18.1 kB
JavaScript
/**
* @license
* Copyright 2016 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import { __assign, __extends } from "tslib";
import { MDCFoundation } from '@material/base/foundation';
import { cssClasses, Direction, EventSource, jumpChipKeys, navigationKeys, strings } from './constants';
var emptyClientRect = {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
};
var FocusBehavior;
(function (FocusBehavior) {
FocusBehavior[FocusBehavior["SHOULD_FOCUS"] = 0] = "SHOULD_FOCUS";
FocusBehavior[FocusBehavior["SHOULD_NOT_FOCUS"] = 1] = "SHOULD_NOT_FOCUS";
})(FocusBehavior || (FocusBehavior = {}));
var MDCChipFoundation = /** @class */ (function (_super) {
__extends(MDCChipFoundation, _super);
function MDCChipFoundation(adapter) {
var _this = _super.call(this, __assign(__assign({}, MDCChipFoundation.defaultAdapter), adapter)) || this;
/** Whether a trailing icon click should immediately trigger exit/removal of the chip. */
_this.shouldRemoveOnTrailingIconClick = true;
/**
* Whether the primary action should receive focus on click. Should only be
* set to true for clients who programmatically give focus to a different
* element on the page when a chip is clicked (like a menu).
*/
_this.shouldFocusPrimaryActionOnClick = true;
return _this;
}
Object.defineProperty(MDCChipFoundation, "strings", {
get: function () {
return strings;
},
enumerable: false,
configurable: true
});
Object.defineProperty(MDCChipFoundation, "cssClasses", {
get: function () {
return cssClasses;
},
enumerable: false,
configurable: true
});
Object.defineProperty(MDCChipFoundation, "defaultAdapter", {
get: function () {
return {
addClass: function () { return undefined; },
addClassToLeadingIcon: function () { return undefined; },
eventTargetHasClass: function () { return false; },
focusPrimaryAction: function () { return undefined; },
focusTrailingAction: function () { return undefined; },
getAttribute: function () { return null; },
getCheckmarkBoundingClientRect: function () { return emptyClientRect; },
getComputedStyleValue: function () { return ''; },
getRootBoundingClientRect: function () { return emptyClientRect; },
hasClass: function () { return false; },
hasLeadingIcon: function () { return false; },
isRTL: function () { return false; },
isTrailingActionNavigable: function () { return false; },
notifyEditFinish: function () { return undefined; },
notifyEditStart: function () { return undefined; },
notifyInteraction: function () { return undefined; },
notifyNavigation: function () { return undefined; },
notifyRemoval: function () { return undefined; },
notifySelection: function () { return undefined; },
notifyTrailingIconInteraction: function () { return undefined; },
removeClass: function () { return undefined; },
removeClassFromLeadingIcon: function () { return undefined; },
removeTrailingActionFocus: function () { return undefined; },
setPrimaryActionAttr: function () { return undefined; },
setStyleProperty: function () { return undefined; },
};
},
enumerable: false,
configurable: true
});
MDCChipFoundation.prototype.isSelected = function () {
return this.adapter.hasClass(cssClasses.SELECTED);
};
MDCChipFoundation.prototype.isEditable = function () {
return this.adapter.hasClass(cssClasses.EDITABLE);
};
MDCChipFoundation.prototype.isEditing = function () {
return this.adapter.hasClass(cssClasses.EDITING);
};
MDCChipFoundation.prototype.setSelected = function (selected) {
this.setSelectedImpl(selected);
this.notifySelection(selected);
};
MDCChipFoundation.prototype.setSelectedFromChipSet = function (selected, shouldNotifyClients) {
this.setSelectedImpl(selected);
if (shouldNotifyClients) {
this.notifyIgnoredSelection(selected);
}
};
MDCChipFoundation.prototype.getShouldRemoveOnTrailingIconClick = function () {
return this.shouldRemoveOnTrailingIconClick;
};
MDCChipFoundation.prototype.setShouldRemoveOnTrailingIconClick = function (shouldRemove) {
this.shouldRemoveOnTrailingIconClick = shouldRemove;
};
MDCChipFoundation.prototype.setShouldFocusPrimaryActionOnClick = function (shouldFocus) {
this.shouldFocusPrimaryActionOnClick = shouldFocus;
};
MDCChipFoundation.prototype.getDimensions = function () {
var _this = this;
var getRootRect = function () { return _this.adapter.getRootBoundingClientRect(); };
var getCheckmarkRect = function () {
return _this.adapter.getCheckmarkBoundingClientRect();
};
// When a chip has a checkmark and not a leading icon, the bounding rect changes in size depending on the current
// size of the checkmark.
if (!this.adapter.hasLeadingIcon()) {
var checkmarkRect = getCheckmarkRect();
if (checkmarkRect) {
var rootRect = getRootRect();
// Checkmark is a square, meaning the client rect's width and height are identical once the animation completes.
// However, the checkbox is initially hidden by setting the width to 0.
// To account for an initial width of 0, we use the checkbox's height instead (which equals the end-state width)
// when adding it to the root client rect's width.
return {
bottom: rootRect.bottom,
height: rootRect.height,
left: rootRect.left,
right: rootRect.right,
top: rootRect.top,
width: rootRect.width + checkmarkRect.height,
};
}
}
return getRootRect();
};
/**
* Begins the exit animation which leads to removal of the chip.
*/
MDCChipFoundation.prototype.beginExit = function () {
this.adapter.addClass(cssClasses.CHIP_EXIT);
};
MDCChipFoundation.prototype.handleClick = function () {
this.adapter.notifyInteraction();
this.setPrimaryActionFocusable(this.getFocusBehavior());
};
MDCChipFoundation.prototype.handleDoubleClick = function () {
if (this.isEditable()) {
this.startEditing();
}
};
/**
* Handles a transition end event on the root element.
*/
MDCChipFoundation.prototype.handleTransitionEnd = function (evt) {
var _this = this;
// Handle transition end event on the chip when it is about to be removed.
var shouldHandle = this.adapter.eventTargetHasClass(evt.target, cssClasses.CHIP_EXIT);
var widthIsAnimating = evt.propertyName === 'width';
var opacityIsAnimating = evt.propertyName === 'opacity';
if (shouldHandle && opacityIsAnimating) {
// See: https://css-tricks.com/using-css-transitions-auto-dimensions/#article-header-id-5
var chipWidth_1 = this.adapter.getComputedStyleValue('width');
// On the next frame (once we get the computed width), explicitly set the chip's width
// to its current pixel width, so we aren't transitioning out of 'auto'.
requestAnimationFrame(function () {
_this.adapter.setStyleProperty('width', chipWidth_1);
// To mitigate jitter, start transitioning padding and margin before width.
_this.adapter.setStyleProperty('padding', '0');
_this.adapter.setStyleProperty('margin', '0');
// On the next frame (once width is explicitly set), transition width to 0.
requestAnimationFrame(function () {
_this.adapter.setStyleProperty('width', '0');
});
});
return;
}
if (shouldHandle && widthIsAnimating) {
this.removeFocus();
var removedAnnouncement = this.adapter.getAttribute(strings.REMOVED_ANNOUNCEMENT_ATTRIBUTE);
this.adapter.notifyRemoval(removedAnnouncement);
}
// Handle a transition end event on the leading icon or checkmark, since the transition end event bubbles.
if (!opacityIsAnimating) {
return;
}
var shouldHideLeadingIcon = this.adapter.eventTargetHasClass(evt.target, cssClasses.LEADING_ICON) &&
this.adapter.hasClass(cssClasses.SELECTED);
var shouldShowLeadingIcon = this.adapter.eventTargetHasClass(evt.target, cssClasses.CHECKMARK) &&
!this.adapter.hasClass(cssClasses.SELECTED);
if (shouldHideLeadingIcon) {
this.adapter.addClassToLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
return;
}
if (shouldShowLeadingIcon) {
this.adapter.removeClassFromLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
return;
}
};
MDCChipFoundation.prototype.handleFocusIn = function (evt) {
// Early exit if the event doesn't come from the primary action
if (!this.eventFromPrimaryAction(evt)) {
return;
}
this.adapter.addClass(cssClasses.PRIMARY_ACTION_FOCUSED);
};
MDCChipFoundation.prototype.handleFocusOut = function (evt) {
// Early exit if the event doesn't come from the primary action
if (!this.eventFromPrimaryAction(evt)) {
return;
}
if (this.isEditing()) {
this.finishEditing();
}
this.adapter.removeClass(cssClasses.PRIMARY_ACTION_FOCUSED);
};
/**
* Handles an interaction event on the trailing icon element. This is used to
* prevent the ripple from activating on interaction with the trailing icon.
*/
MDCChipFoundation.prototype.handleTrailingActionInteraction = function () {
this.adapter.notifyTrailingIconInteraction();
this.removeChip();
};
/**
* Handles a keydown event from the root element.
*/
MDCChipFoundation.prototype.handleKeydown = function (evt) {
if (this.isEditing()) {
if (this.shouldFinishEditing(evt)) {
evt.preventDefault();
this.finishEditing();
}
// When editing, the foundation should only handle key events that finish
// the editing process.
return;
}
if (this.isEditable()) {
if (this.shouldStartEditing(evt)) {
evt.preventDefault();
this.startEditing();
}
}
if (this.shouldNotifyInteraction(evt)) {
this.adapter.notifyInteraction();
this.setPrimaryActionFocusable(this.getFocusBehavior());
return;
}
if (this.isDeleteAction(evt)) {
evt.preventDefault();
this.removeChip();
return;
}
// Early exit if the key is not usable
if (!navigationKeys.has(evt.key)) {
return;
}
// Prevent default behavior for movement keys which could include scrolling
evt.preventDefault();
this.focusNextAction(evt.key, EventSource.PRIMARY);
};
MDCChipFoundation.prototype.handleTrailingActionNavigation = function (evt) {
this.focusNextAction(evt.detail.key, EventSource.TRAILING);
};
/**
* Called by the chip set to remove focus from the chip actions.
*/
MDCChipFoundation.prototype.removeFocus = function () {
this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '-1');
this.adapter.removeTrailingActionFocus();
};
/**
* Called by the chip set to focus the primary action.
*
*/
MDCChipFoundation.prototype.focusPrimaryAction = function () {
this.setPrimaryActionFocusable(FocusBehavior.SHOULD_FOCUS);
};
/**
* Called by the chip set to focus the trailing action (if present), otherwise
* gives focus to the trailing action.
*/
MDCChipFoundation.prototype.focusTrailingAction = function () {
var trailingActionIsNavigable = this.adapter.isTrailingActionNavigable();
if (trailingActionIsNavigable) {
this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '-1');
this.adapter.focusTrailingAction();
return;
}
this.focusPrimaryAction();
};
MDCChipFoundation.prototype.setPrimaryActionFocusable = function (focusBehavior) {
this.adapter.setPrimaryActionAttr(strings.TAB_INDEX, '0');
if (focusBehavior === FocusBehavior.SHOULD_FOCUS) {
this.adapter.focusPrimaryAction();
}
this.adapter.removeTrailingActionFocus();
};
MDCChipFoundation.prototype.getFocusBehavior = function () {
if (this.shouldFocusPrimaryActionOnClick) {
return FocusBehavior.SHOULD_FOCUS;
}
return FocusBehavior.SHOULD_NOT_FOCUS;
};
MDCChipFoundation.prototype.focusNextAction = function (key, source) {
var isTrailingActionNavigable = this.adapter.isTrailingActionNavigable();
var dir = this.getDirection(key);
// Early exit if the key should jump chips
if (jumpChipKeys.has(key) || !isTrailingActionNavigable) {
this.adapter.notifyNavigation(key, source);
return;
}
if (source === EventSource.PRIMARY && dir === Direction.RIGHT) {
this.focusTrailingAction();
return;
}
if (source === EventSource.TRAILING && dir === Direction.LEFT) {
this.focusPrimaryAction();
return;
}
this.adapter.notifyNavigation(key, EventSource.NONE);
};
MDCChipFoundation.prototype.getDirection = function (key) {
var isRTL = this.adapter.isRTL();
var isLeftKey = key === strings.ARROW_LEFT_KEY || key === strings.IE_ARROW_LEFT_KEY;
var isRightKey = key === strings.ARROW_RIGHT_KEY || key === strings.IE_ARROW_RIGHT_KEY;
if (!isRTL && isLeftKey || isRTL && isRightKey) {
return Direction.LEFT;
}
return Direction.RIGHT;
};
MDCChipFoundation.prototype.removeChip = function () {
if (this.shouldRemoveOnTrailingIconClick) {
this.beginExit();
}
};
MDCChipFoundation.prototype.shouldStartEditing = function (evt) {
return this.eventFromPrimaryAction(evt) && evt.key === strings.ENTER_KEY;
};
MDCChipFoundation.prototype.shouldFinishEditing = function (evt) {
return evt.key === strings.ENTER_KEY;
};
MDCChipFoundation.prototype.shouldNotifyInteraction = function (evt) {
return evt.key === strings.ENTER_KEY || evt.key === strings.SPACEBAR_KEY;
};
MDCChipFoundation.prototype.isDeleteAction = function (evt) {
var isDeletable = this.adapter.hasClass(cssClasses.DELETABLE);
return isDeletable &&
(evt.key === strings.BACKSPACE_KEY || evt.key === strings.DELETE_KEY ||
evt.key === strings.IE_DELETE_KEY);
};
MDCChipFoundation.prototype.setSelectedImpl = function (selected) {
if (selected) {
this.adapter.addClass(cssClasses.SELECTED);
this.adapter.setPrimaryActionAttr(strings.ARIA_CHECKED, 'true');
}
else {
this.adapter.removeClass(cssClasses.SELECTED);
this.adapter.setPrimaryActionAttr(strings.ARIA_CHECKED, 'false');
}
};
MDCChipFoundation.prototype.notifySelection = function (selected) {
this.adapter.notifySelection(selected, false);
};
MDCChipFoundation.prototype.notifyIgnoredSelection = function (selected) {
this.adapter.notifySelection(selected, true);
};
MDCChipFoundation.prototype.eventFromPrimaryAction = function (evt) {
return this.adapter.eventTargetHasClass(evt.target, cssClasses.PRIMARY_ACTION);
};
MDCChipFoundation.prototype.startEditing = function () {
this.adapter.addClass(cssClasses.EDITING);
this.adapter.notifyEditStart();
};
MDCChipFoundation.prototype.finishEditing = function () {
this.adapter.removeClass(cssClasses.EDITING);
this.adapter.notifyEditFinish();
};
return MDCChipFoundation;
}(MDCFoundation));
export { MDCChipFoundation };
// tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
export default MDCChipFoundation;
//# sourceMappingURL=foundation.js.map