UNPKG

@material/chips

Version:
403 lines • 18.1 kB
/** * @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