| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- /**
- * @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, __read, __spreadArray } from "tslib";
- import { MDCFoundation } from '@material/base/foundation';
- import { KEY, normalizeKey } from '@material/dom/keyboard';
- import { Corner } from '@material/menu-surface/constants';
- import { cssClasses, numbers, strings } from './constants';
- /** MDC Select Foundation */
- var MDCSelectFoundation = /** @class */ (function (_super) {
- __extends(MDCSelectFoundation, _super);
- /* istanbul ignore next: optional argument is not a branch statement */
- /**
- * @param adapter
- * @param foundationMap Map from subcomponent names to their subfoundations.
- */
- function MDCSelectFoundation(adapter, foundationMap) {
- if (foundationMap === void 0) { foundationMap = {}; }
- var _a, _b;
- var _this = _super.call(this, __assign(__assign({}, MDCSelectFoundation.defaultAdapter), adapter)) || this;
- // Disabled state
- _this.disabled = false;
- // isMenuOpen is used to track the state of the menu by listening to the
- // MDCMenuSurface:closed event For reference, menu.open will return false if
- // the menu is still closing, but isMenuOpen returns false only after the menu
- // has closed
- _this.isMenuOpen = false;
- // By default, select is invalid if it is required but no value is selected.
- _this.useDefaultValidation = true;
- _this.customValidity = true;
- _this.lastSelectedIndex = numbers.UNSET_INDEX;
- _this.clickDebounceTimeout = 0;
- _this.recentlyClicked = false;
- _this.leadingIcon = foundationMap.leadingIcon;
- _this.helperText = foundationMap.helperText;
- _this.ariaDescribedbyIds =
- ((_b = (_a = _this.adapter.getSelectAnchorAttr(strings.ARIA_DESCRIBEDBY)) === null || _a === void 0 ? void 0 : _a.split(' ')) === null || _b === void 0 ? void 0 : _b.filter(function (id) { var _a; return id !== ((_a = _this.helperText) === null || _a === void 0 ? void 0 : _a.getId()) && id !== ''; })) ||
- [];
- return _this;
- }
- Object.defineProperty(MDCSelectFoundation, "cssClasses", {
- get: function () {
- return cssClasses;
- },
- enumerable: false,
- configurable: true
- });
- Object.defineProperty(MDCSelectFoundation, "numbers", {
- get: function () {
- return numbers;
- },
- enumerable: false,
- configurable: true
- });
- Object.defineProperty(MDCSelectFoundation, "strings", {
- get: function () {
- return strings;
- },
- enumerable: false,
- configurable: true
- });
- Object.defineProperty(MDCSelectFoundation, "defaultAdapter", {
- /**
- * See {@link MDCSelectAdapter} for typing information on parameters and
- * return types.
- */
- get: function () {
- // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
- return {
- addClass: function () { return undefined; },
- removeClass: function () { return undefined; },
- hasClass: function () { return false; },
- activateBottomLine: function () { return undefined; },
- deactivateBottomLine: function () { return undefined; },
- getSelectedIndex: function () { return -1; },
- setSelectedIndex: function () { return undefined; },
- hasLabel: function () { return false; },
- floatLabel: function () { return undefined; },
- getLabelWidth: function () { return 0; },
- setLabelRequired: function () { return undefined; },
- hasOutline: function () { return false; },
- notchOutline: function () { return undefined; },
- closeOutline: function () { return undefined; },
- setRippleCenter: function () { return undefined; },
- notifyChange: function () { return undefined; },
- setSelectedText: function () { return undefined; },
- isSelectAnchorFocused: function () { return false; },
- getSelectAnchorAttr: function () { return ''; },
- setSelectAnchorAttr: function () { return undefined; },
- removeSelectAnchorAttr: function () { return undefined; },
- addMenuClass: function () { return undefined; },
- removeMenuClass: function () { return undefined; },
- openMenu: function () { return undefined; },
- closeMenu: function () { return undefined; },
- getAnchorElement: function () { return null; },
- setMenuAnchorElement: function () { return undefined; },
- setMenuAnchorCorner: function () { return undefined; },
- setMenuWrapFocus: function () { return undefined; },
- focusMenuItemAtIndex: function () { return undefined; },
- getMenuItemCount: function () { return 0; },
- getMenuItemValues: function () { return []; },
- getMenuItemTextAtIndex: function () { return ''; },
- isTypeaheadInProgress: function () { return false; },
- typeaheadMatchItem: function () { return -1; },
- };
- // tslint:enable:object-literal-sort-keys
- },
- enumerable: false,
- configurable: true
- });
- /** Returns the index of the currently selected menu item, or -1 if none. */
- MDCSelectFoundation.prototype.getSelectedIndex = function () {
- return this.adapter.getSelectedIndex();
- };
- MDCSelectFoundation.prototype.setSelectedIndex = function (index, closeMenu, skipNotify) {
- if (closeMenu === void 0) { closeMenu = false; }
- if (skipNotify === void 0) { skipNotify = false; }
- if (index >= this.adapter.getMenuItemCount()) {
- return;
- }
- this.adapter.setSelectedIndex(index);
- if (index === numbers.UNSET_INDEX) {
- this.adapter.setSelectedText('');
- }
- else {
- this.adapter.setSelectedText(this.adapter.getMenuItemTextAtIndex(index).trim());
- }
- if (closeMenu) {
- this.adapter.closeMenu();
- }
- if (!skipNotify && this.lastSelectedIndex !== index) {
- this.handleChange();
- }
- this.lastSelectedIndex = index;
- };
- MDCSelectFoundation.prototype.setValue = function (value, skipNotify) {
- if (skipNotify === void 0) { skipNotify = false; }
- var index = this.adapter.getMenuItemValues().indexOf(value);
- this.setSelectedIndex(index, /** closeMenu */ false, skipNotify);
- };
- MDCSelectFoundation.prototype.getValue = function () {
- var index = this.adapter.getSelectedIndex();
- var menuItemValues = this.adapter.getMenuItemValues();
- return index !== numbers.UNSET_INDEX ? menuItemValues[index] : '';
- };
- MDCSelectFoundation.prototype.getDisabled = function () {
- return this.disabled;
- };
- MDCSelectFoundation.prototype.setDisabled = function (isDisabled) {
- this.disabled = isDisabled;
- if (this.disabled) {
- this.adapter.addClass(cssClasses.DISABLED);
- this.adapter.closeMenu();
- }
- else {
- this.adapter.removeClass(cssClasses.DISABLED);
- }
- if (this.leadingIcon) {
- this.leadingIcon.setDisabled(this.disabled);
- }
- if (this.disabled) {
- // Prevent click events from focusing select. Simply pointer-events: none
- // is not enough since screenreader clicks may bypass this.
- this.adapter.removeSelectAnchorAttr('tabindex');
- }
- else {
- this.adapter.setSelectAnchorAttr('tabindex', '0');
- }
- this.adapter.setSelectAnchorAttr('aria-disabled', this.disabled.toString());
- };
- /** Opens the menu. */
- MDCSelectFoundation.prototype.openMenu = function () {
- this.adapter.addClass(cssClasses.ACTIVATED);
- this.adapter.openMenu();
- this.isMenuOpen = true;
- this.adapter.setSelectAnchorAttr('aria-expanded', 'true');
- };
- /**
- * @param content Sets the content of the helper text.
- */
- MDCSelectFoundation.prototype.setHelperTextContent = function (content) {
- if (this.helperText) {
- this.helperText.setContent(content);
- }
- };
- /**
- * Re-calculates if the notched outline should be notched and if the label
- * should float.
- */
- MDCSelectFoundation.prototype.layout = function () {
- if (this.adapter.hasLabel()) {
- var optionHasValue = this.getValue().length > 0;
- var isFocused = this.adapter.hasClass(cssClasses.FOCUSED);
- var shouldFloatAndNotch = optionHasValue || isFocused;
- var isRequired = this.adapter.hasClass(cssClasses.REQUIRED);
- this.notchOutline(shouldFloatAndNotch);
- this.adapter.floatLabel(shouldFloatAndNotch);
- this.adapter.setLabelRequired(isRequired);
- }
- };
- /**
- * Synchronizes the list of options with the state of the foundation. Call
- * this whenever menu options are dynamically updated.
- */
- MDCSelectFoundation.prototype.layoutOptions = function () {
- var menuItemValues = this.adapter.getMenuItemValues();
- var selectedIndex = menuItemValues.indexOf(this.getValue());
- this.setSelectedIndex(selectedIndex, /** closeMenu */ false, /** skipNotify */ true);
- };
- MDCSelectFoundation.prototype.handleMenuOpened = function () {
- if (this.adapter.getMenuItemValues().length === 0) {
- return;
- }
- // Menu should open to the last selected element, should open to first menu
- // item otherwise.
- var selectedIndex = this.getSelectedIndex();
- var focusItemIndex = selectedIndex >= 0 ? selectedIndex : 0;
- this.adapter.focusMenuItemAtIndex(focusItemIndex);
- };
- MDCSelectFoundation.prototype.handleMenuClosing = function () {
- this.adapter.setSelectAnchorAttr('aria-expanded', 'false');
- };
- MDCSelectFoundation.prototype.handleMenuClosed = function () {
- this.adapter.removeClass(cssClasses.ACTIVATED);
- this.isMenuOpen = false;
- // Unfocus the select if menu is closed without a selection
- if (!this.adapter.isSelectAnchorFocused()) {
- this.blur();
- }
- };
- /**
- * Handles value changes, via change event or programmatic updates.
- */
- MDCSelectFoundation.prototype.handleChange = function () {
- this.layout();
- this.adapter.notifyChange(this.getValue());
- var isRequired = this.adapter.hasClass(cssClasses.REQUIRED);
- if (isRequired && this.useDefaultValidation) {
- this.setValid(this.isValid());
- }
- };
- MDCSelectFoundation.prototype.handleMenuItemAction = function (index) {
- this.setSelectedIndex(index, /** closeMenu */ true);
- };
- /**
- * Handles focus events from select element.
- */
- MDCSelectFoundation.prototype.handleFocus = function () {
- this.adapter.addClass(cssClasses.FOCUSED);
- this.layout();
- this.adapter.activateBottomLine();
- };
- /**
- * Handles blur events from select element.
- */
- MDCSelectFoundation.prototype.handleBlur = function () {
- if (this.isMenuOpen) {
- return;
- }
- this.blur();
- };
- MDCSelectFoundation.prototype.handleClick = function (normalizedX) {
- if (this.disabled || this.recentlyClicked) {
- return;
- }
- this.setClickDebounceTimeout();
- if (this.isMenuOpen) {
- this.adapter.closeMenu();
- return;
- }
- this.adapter.setRippleCenter(normalizedX);
- this.openMenu();
- };
- /**
- * Handles keydown events on select element. Depending on the type of
- * character typed, does typeahead matching or opens menu.
- */
- MDCSelectFoundation.prototype.handleKeydown = function (event) {
- if (this.isMenuOpen || !this.adapter.hasClass(cssClasses.FOCUSED)) {
- return;
- }
- var isEnter = normalizeKey(event) === KEY.ENTER;
- var isSpace = normalizeKey(event) === KEY.SPACEBAR;
- var arrowUp = normalizeKey(event) === KEY.ARROW_UP;
- var arrowDown = normalizeKey(event) === KEY.ARROW_DOWN;
- var isModifier = event.ctrlKey || event.metaKey;
- // Typeahead
- if (!isModifier &&
- (!isSpace && event.key && event.key.length === 1 ||
- isSpace && this.adapter.isTypeaheadInProgress())) {
- var key = isSpace ? ' ' : event.key;
- var typeaheadNextIndex = this.adapter.typeaheadMatchItem(key, this.getSelectedIndex());
- if (typeaheadNextIndex >= 0) {
- this.setSelectedIndex(typeaheadNextIndex);
- }
- event.preventDefault();
- return;
- }
- if (!isEnter && !isSpace && !arrowUp && !arrowDown) {
- return;
- }
- this.openMenu();
- event.preventDefault();
- };
- /**
- * Opens/closes the notched outline.
- */
- MDCSelectFoundation.prototype.notchOutline = function (openNotch) {
- if (!this.adapter.hasOutline()) {
- return;
- }
- var isFocused = this.adapter.hasClass(cssClasses.FOCUSED);
- if (openNotch) {
- var labelScale = numbers.LABEL_SCALE;
- var labelWidth = this.adapter.getLabelWidth() * labelScale;
- this.adapter.notchOutline(labelWidth);
- }
- else if (!isFocused) {
- this.adapter.closeOutline();
- }
- };
- /**
- * Sets the aria label of the leading icon.
- */
- MDCSelectFoundation.prototype.setLeadingIconAriaLabel = function (label) {
- if (this.leadingIcon) {
- this.leadingIcon.setAriaLabel(label);
- }
- };
- /**
- * Sets the text content of the leading icon.
- */
- MDCSelectFoundation.prototype.setLeadingIconContent = function (content) {
- if (this.leadingIcon) {
- this.leadingIcon.setContent(content);
- }
- };
- MDCSelectFoundation.prototype.getUseDefaultValidation = function () {
- return this.useDefaultValidation;
- };
- MDCSelectFoundation.prototype.setUseDefaultValidation = function (useDefaultValidation) {
- this.useDefaultValidation = useDefaultValidation;
- };
- MDCSelectFoundation.prototype.setValid = function (isValid) {
- if (!this.useDefaultValidation) {
- this.customValidity = isValid;
- }
- this.adapter.setSelectAnchorAttr('aria-invalid', (!isValid).toString());
- if (isValid) {
- this.adapter.removeClass(cssClasses.INVALID);
- this.adapter.removeMenuClass(cssClasses.MENU_INVALID);
- }
- else {
- this.adapter.addClass(cssClasses.INVALID);
- this.adapter.addMenuClass(cssClasses.MENU_INVALID);
- }
- this.syncHelperTextValidity(isValid);
- };
- MDCSelectFoundation.prototype.isValid = function () {
- if (this.useDefaultValidation &&
- this.adapter.hasClass(cssClasses.REQUIRED) &&
- !this.adapter.hasClass(cssClasses.DISABLED)) {
- // See notes for required attribute under
- // https://www.w3.org/TR/html52/sec-forms.html#the-select-element TL;DR:
- // Invalid if no index is selected, or if the first index is selected and
- // has an empty value.
- return this.getSelectedIndex() !== numbers.UNSET_INDEX &&
- (this.getSelectedIndex() !== 0 || Boolean(this.getValue()));
- }
- return this.customValidity;
- };
- MDCSelectFoundation.prototype.setRequired = function (isRequired) {
- if (isRequired) {
- this.adapter.addClass(cssClasses.REQUIRED);
- }
- else {
- this.adapter.removeClass(cssClasses.REQUIRED);
- }
- this.adapter.setSelectAnchorAttr('aria-required', isRequired.toString());
- this.adapter.setLabelRequired(isRequired);
- };
- MDCSelectFoundation.prototype.getRequired = function () {
- return this.adapter.getSelectAnchorAttr('aria-required') === 'true';
- };
- MDCSelectFoundation.prototype.init = function () {
- var anchorEl = this.adapter.getAnchorElement();
- if (anchorEl) {
- this.adapter.setMenuAnchorElement(anchorEl);
- this.adapter.setMenuAnchorCorner(Corner.BOTTOM_START);
- }
- this.adapter.setMenuWrapFocus(false);
- this.setDisabled(this.adapter.hasClass(cssClasses.DISABLED));
- this.syncHelperTextValidity(!this.adapter.hasClass(cssClasses.INVALID));
- this.layout();
- this.layoutOptions();
- };
- /**
- * Unfocuses the select component.
- */
- MDCSelectFoundation.prototype.blur = function () {
- this.adapter.removeClass(cssClasses.FOCUSED);
- this.layout();
- this.adapter.deactivateBottomLine();
- var isRequired = this.adapter.hasClass(cssClasses.REQUIRED);
- if (isRequired && this.useDefaultValidation) {
- this.setValid(this.isValid());
- }
- };
- MDCSelectFoundation.prototype.syncHelperTextValidity = function (isValid) {
- if (!this.helperText) {
- return;
- }
- this.helperText.setValidity(isValid);
- var helperTextVisible = this.helperText.isVisible();
- var helperTextId = this.helperText.getId();
- if (helperTextVisible && helperTextId) {
- this.adapter.setSelectAnchorAttr(strings.ARIA_DESCRIBEDBY, __spreadArray(__spreadArray([], __read(this.ariaDescribedbyIds)), [helperTextId]).join(' '));
- }
- else {
- // Remove helptext from list of describedby ids. Needed because
- // screenreaders will read labels pointed to by `aria-describedby` even if
- // they are `aria-hidden`.
- if (this.ariaDescribedbyIds.length > 0) {
- this.adapter.setSelectAnchorAttr(strings.ARIA_DESCRIBEDBY, this.ariaDescribedbyIds.join(' '));
- }
- else { // helper text is the only describedby element
- this.adapter.removeSelectAnchorAttr(strings.ARIA_DESCRIBEDBY);
- }
- }
- };
- MDCSelectFoundation.prototype.setClickDebounceTimeout = function () {
- var _this = this;
- clearTimeout(this.clickDebounceTimeout);
- this.clickDebounceTimeout = setTimeout(function () {
- _this.recentlyClicked = false;
- }, numbers.CLICK_DEBOUNCE_TIMEOUT_MS);
- this.recentlyClicked = true;
- };
- return MDCSelectFoundation;
- }(MDCFoundation));
- export { MDCSelectFoundation };
- // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier.
- export default MDCSelectFoundation;
- //# sourceMappingURL=foundation.js.map
|