UNPKG

balm-ui

Version:

A modular and customizable UI library based on Material Design and Vue 3

389 lines 17.5 kB
/** * @license * Copyright 2018 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 { __extends } from "tslib"; import { MDCComponent } from '../base/component'; import { closest, matches } from '../dom/ponyfill'; import { cssClasses, deprecatedClassNameMap, evolutionAttribute, evolutionClassNameMap, numbers, strings } from './constants'; import { MDCListFoundation } from './foundation'; var MDCList = /** @class */ (function (_super) { __extends(MDCList, _super); function MDCList() { return _super !== null && _super.apply(this, arguments) || this; } Object.defineProperty(MDCList.prototype, "vertical", { set: function (value) { this.foundation.setVerticalOrientation(value); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "listElements", { get: function () { return Array.from(this.root.querySelectorAll("." + this.classNameMap[cssClasses.LIST_ITEM_CLASS])); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "wrapFocus", { set: function (value) { this.foundation.setWrapFocus(value); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "typeaheadInProgress", { /** * @return Whether typeahead is currently matching a user-specified prefix. */ get: function () { return this.foundation.isTypeaheadInProgress(); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "hasTypeahead", { /** * Sets whether typeahead functionality is enabled on the list. * @param hasTypeahead Whether typeahead is enabled. */ set: function (hasTypeahead) { this.foundation.setHasTypeahead(hasTypeahead); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "singleSelection", { set: function (isSingleSelectionList) { this.foundation.setSingleSelection(isSingleSelectionList); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "disabledItemsFocusable", { set: function (areDisabledItemsFocusable) { this.foundation.setDisabledItemsFocusable(areDisabledItemsFocusable); }, enumerable: false, configurable: true }); Object.defineProperty(MDCList.prototype, "selectedIndex", { get: function () { return this.foundation.getSelectedIndex(); }, set: function (index) { this.foundation.setSelectedIndex(index); }, enumerable: false, configurable: true }); MDCList.attachTo = function (root) { return new MDCList(root); }; MDCList.prototype.initialSyncWithDOM = function () { this.isEvolutionEnabled = evolutionAttribute in this.root.dataset; if (this.isEvolutionEnabled) { this.classNameMap = evolutionClassNameMap; } else if (matches(this.root, strings.DEPRECATED_SELECTOR)) { this.classNameMap = deprecatedClassNameMap; } else { this.classNameMap = Object.values(cssClasses) .reduce(function (obj, className) { obj[className] = className; return obj; }, {}); } this.handleClick = this.handleClickEvent.bind(this); this.handleKeydown = this.handleKeydownEvent.bind(this); this.focusInEventListener = this.handleFocusInEvent.bind(this); this.focusOutEventListener = this.handleFocusOutEvent.bind(this); this.listen('keydown', this.handleKeydown); this.listen('click', this.handleClick); this.listen('focusin', this.focusInEventListener); this.listen('focusout', this.focusOutEventListener); this.layout(); this.initializeListType(); this.ensureFocusable(); }; MDCList.prototype.destroy = function () { this.unlisten('keydown', this.handleKeydown); this.unlisten('click', this.handleClick); this.unlisten('focusin', this.focusInEventListener); this.unlisten('focusout', this.focusOutEventListener); }; MDCList.prototype.layout = function () { var direction = this.root.getAttribute(strings.ARIA_ORIENTATION); this.vertical = direction !== strings.ARIA_ORIENTATION_HORIZONTAL; var itemSelector = "." + this.classNameMap[cssClasses.LIST_ITEM_CLASS] + ":not([tabindex])"; var childSelector = strings.FOCUSABLE_CHILD_ELEMENTS; // List items need to have at least tabindex=-1 to be focusable. var itemEls = this.root.querySelectorAll(itemSelector); if (itemEls.length) { Array.prototype.forEach.call(itemEls, function (el) { el.setAttribute('tabindex', '-1'); }); } // Child button/a elements are not tabbable until the list item is focused. var focusableChildEls = this.root.querySelectorAll(childSelector); if (focusableChildEls.length) { Array.prototype.forEach.call(focusableChildEls, function (el) { el.setAttribute('tabindex', '-1'); }); } if (this.isEvolutionEnabled) { this.foundation.setUseSelectedAttribute(true); } this.foundation.layout(); }; /** * Extracts the primary text from a list item. * @param item The list item element. * @return The primary text in the element. */ MDCList.prototype.getPrimaryText = function (item) { var _a; var primaryText = item.querySelector("." + this.classNameMap[cssClasses.LIST_ITEM_PRIMARY_TEXT_CLASS]); if (this.isEvolutionEnabled || primaryText) { return (_a = primaryText === null || primaryText === void 0 ? void 0 : primaryText.textContent) !== null && _a !== void 0 ? _a : ''; } var singleLineText = item.querySelector("." + this.classNameMap[cssClasses.LIST_ITEM_TEXT_CLASS]); return (singleLineText && singleLineText.textContent) || ''; }; /** * Initialize selectedIndex value based on pre-selected list items. */ MDCList.prototype.initializeListType = function () { var _this = this; this.isInteractive = matches(this.root, strings.ARIA_INTERACTIVE_ROLES_SELECTOR); if (this.isEvolutionEnabled && this.isInteractive) { var selection = Array.from(this.root.querySelectorAll(strings.SELECTED_ITEM_SELECTOR), function (listItem) { return _this.listElements.indexOf(listItem); }); if (matches(this.root, strings.ARIA_MULTI_SELECTABLE_SELECTOR)) { this.selectedIndex = selection; } else if (selection.length > 0) { this.selectedIndex = selection[0]; } return; } var checkboxListItems = this.root.querySelectorAll(strings.ARIA_ROLE_CHECKBOX_SELECTOR); var radioSelectedListItem = this.root.querySelector(strings.ARIA_CHECKED_RADIO_SELECTOR); if (checkboxListItems.length) { var preselectedItems = this.root.querySelectorAll(strings.ARIA_CHECKED_CHECKBOX_SELECTOR); this.selectedIndex = Array.from(preselectedItems, function (listItem) { return _this.listElements.indexOf(listItem); }); } else if (radioSelectedListItem) { this.selectedIndex = this.listElements.indexOf(radioSelectedListItem); } }; /** * Updates the list item at itemIndex to the desired isEnabled state. * @param itemIndex Index of the list item * @param isEnabled Sets the list item to enabled or disabled. */ MDCList.prototype.setEnabled = function (itemIndex, isEnabled) { this.foundation.setEnabled(itemIndex, isEnabled); }; /** * Given the next desired character from the user, adds it to the typeahead * buffer. Then, attempts to find the next option matching the buffer. Wraps * around if at the end of options. * * @param nextChar The next character to add to the prefix buffer. * @param startingIndex The index from which to start matching. Defaults to * the currently focused index. * @return The index of the matched item. */ MDCList.prototype.typeaheadMatchItem = function (nextChar, startingIndex) { return this.foundation.typeaheadMatchItem(nextChar, startingIndex, /** skipFocus */ true); }; MDCList.prototype.getDefaultFoundation = function () { var _this = this; // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial<MDCFooAdapter>. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. var adapter = { addClassForElementIndex: function (index, className) { var element = _this.listElements[index]; if (element) { element.classList.add(_this.classNameMap[className]); } }, focusItemAtIndex: function (index) { var element = _this.listElements[index]; if (element) { element.focus(); } }, getAttributeForElementIndex: function (index, attr) { return _this.listElements[index].getAttribute(attr); }, getFocusedElementIndex: function () { return _this.listElements.indexOf(document.activeElement); }, getListItemCount: function () { return _this.listElements.length; }, getPrimaryTextAtIndex: function (index) { return _this.getPrimaryText(_this.listElements[index]); }, hasCheckboxAtIndex: function (index) { var listItem = _this.listElements[index]; return !!listItem.querySelector(strings.CHECKBOX_SELECTOR); }, hasRadioAtIndex: function (index) { var listItem = _this.listElements[index]; return !!listItem.querySelector(strings.RADIO_SELECTOR); }, isCheckboxCheckedAtIndex: function (index) { var listItem = _this.listElements[index]; var toggleEl = listItem.querySelector(strings.CHECKBOX_SELECTOR); return toggleEl.checked; }, isFocusInsideList: function () { return _this.root !== document.activeElement && _this.root.contains(document.activeElement); }, isRootFocused: function () { return document.activeElement === _this.root; }, listItemAtIndexHasClass: function (index, className) { return _this.listElements[index].classList.contains(_this.classNameMap[className]); }, notifyAction: function (index) { _this.emit(strings.ACTION_EVENT, { index: index }, /** shouldBubble */ true); }, notifySelectionChange: function (changedIndices) { _this.emit(strings.SELECTION_CHANGE_EVENT, { changedIndices: changedIndices }, /** shouldBubble */ true); }, removeClassForElementIndex: function (index, className) { var element = _this.listElements[index]; if (element) { element.classList.remove(_this.classNameMap[className]); } }, setAttributeForElementIndex: function (index, attr, value) { var element = _this.listElements[index]; if (element) { element.setAttribute(attr, value); } }, setCheckedCheckboxOrRadioAtIndex: function (index, isChecked) { var listItem = _this.listElements[index]; var toggleEl = listItem.querySelector(strings.CHECKBOX_RADIO_SELECTOR); toggleEl.checked = isChecked; var event = document.createEvent('Event'); event.initEvent('change', true, true); toggleEl.dispatchEvent(event); }, setTabIndexForListItemChildren: function (listItemIndex, tabIndexValue) { var element = _this.listElements[listItemIndex]; var selector = strings.CHILD_ELEMENTS_TO_TOGGLE_TABINDEX; Array.prototype.forEach.call(element.querySelectorAll(selector), function (el) { el.setAttribute('tabindex', tabIndexValue); }); }, }; return new MDCListFoundation(adapter); }; /** * Ensures that at least one item is focusable if the list is interactive and * doesn't specify a suitable tabindex. */ MDCList.prototype.ensureFocusable = function () { if (this.isEvolutionEnabled && this.isInteractive) { if (!this.root.querySelector("." + this.classNameMap[cssClasses.LIST_ITEM_CLASS] + "[tabindex=\"0\"]")) { var index = this.initialFocusIndex(); if (index !== -1) { this.listElements[index].tabIndex = 0; } } } }; MDCList.prototype.initialFocusIndex = function () { if (this.selectedIndex instanceof Array && this.selectedIndex.length > 0) { return this.selectedIndex[0]; } if (typeof this.selectedIndex === 'number' && this.selectedIndex !== numbers.UNSET_INDEX) { return this.selectedIndex; } var el = this.root.querySelector("." + this.classNameMap[cssClasses.LIST_ITEM_CLASS] + ":not(." + this.classNameMap[cssClasses.LIST_ITEM_DISABLED_CLASS] + ")"); if (el === null) { return -1; } return this.getListItemIndex(el); }; /** * Used to figure out which list item this event is targetting. Or returns -1 * if there is no list item */ MDCList.prototype.getListItemIndex = function (el) { var nearestParent = closest(el, "." + this.classNameMap[cssClasses.LIST_ITEM_CLASS] + ", ." + this.classNameMap[cssClasses.ROOT]); // Get the index of the element if it is a list item. if (nearestParent && matches(nearestParent, "." + this.classNameMap[cssClasses.LIST_ITEM_CLASS])) { return this.listElements.indexOf(nearestParent); } return -1; }; /** * Used to figure out which element was clicked before sending the event to * the foundation. */ MDCList.prototype.handleFocusInEvent = function (evt) { var index = this.getListItemIndex(evt.target); this.foundation.handleFocusIn(index); }; /** * Used to figure out which element was clicked before sending the event to * the foundation. */ MDCList.prototype.handleFocusOutEvent = function (evt) { var index = this.getListItemIndex(evt.target); this.foundation.handleFocusOut(index); }; /** * Used to figure out which element was focused when keydown event occurred * before sending the event to the foundation. */ MDCList.prototype.handleKeydownEvent = function (evt) { var index = this.getListItemIndex(evt.target); var target = evt.target; this.foundation.handleKeydown(evt, target.classList.contains(this.classNameMap[cssClasses.LIST_ITEM_CLASS]), index); }; /** * Used to figure out which element was clicked before sending the event to * the foundation. */ MDCList.prototype.handleClickEvent = function (evt) { var index = this.getListItemIndex(evt.target); var target = evt.target; // Toggle the checkbox only if it's not the target of the event, or the // checkbox will have 2 change events. var toggleCheckbox = !matches(target, strings.CHECKBOX_RADIO_SELECTOR); this.foundation.handleClick(index, toggleCheckbox, evt); }; return MDCList; }(MDCComponent)); export { MDCList }; //# sourceMappingURL=component.js.map