UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

1 lines 9.87 kB
{"version":3,"file":"keyboard-list-navigation.cjs","names":["Dom","returnFirstEl"],"sources":["../../../common/mixins/keyboard_list_navigation.js"],"sourcesContent":["import Dom from './dom';\nimport { returnFirstEl } from '@/common/utils';\n\nconst ERROR_INVALID_LIST_ELEMENT = (\n 'listElementKey is required or the referenced ' +\n 'element doesn\\'t exist. Received listElement: '\n);\n\n/**\n * Usage: `mixins: [keyboardNavigationMixin(options)]`\n *\n * This mixin provides some common data and methods to navigate a list of items\n * (such as a dropdown or select menu) by keyboard.\n *\n * To be effective, you must bind the onUpKey and onDownKey events, usually to\n * the root element of the component.\n *\n * @param listItemRole\n * @param indexKey\n * @param idKey\n * @param listElementKey\n * @param activeItemKey\n * @param openMethod\n * @param afterHighlightMethod\n * @param beginningOfListMethod\n * @param endOfListMethod\n * @param scrollToOnHighlight\n * @param focusOnKeyboardNavigation\n * @displayName Keyboard Navigation Mixin\n */\nexport default ({\n // Role of the list items in the component. This is used to identify the list items\n // so you must update this if the role of your list items is anything other than 'option'\n listItemRole = 'option',\n // Key of the data prop that will be added to the component.\n indexKey = 'highlightIndex',\n idKey = 'highlightId',\n // Key of the method that references the list element.\n listElementKey = 'listRef',\n // Optional, Key of the computed prop that references the currently active item element.\n activeItemKey = '',\n // Optional, name of the method that toggles the list visibility. Used for\n // opening the list when up or down is pressed.\n openMethod = null,\n // Optional, method to call when the highlightIndex is changed.\n afterHighlightMethod = null,\n // Optional, method to call when the highlightIndex goes past the beginning of the list.\n beginningOfListMethod = null,\n // Optional, method to call when the highlightIndex goes past the end of the list.\n endOfListMethod = null,\n // Scroll the active element into view when highlighted by a keyboard event.\n scrollToOnHighlight = true,\n // Focus the active element on keyboard navigation.\n focusOnKeyboardNavigation = false,\n} = {}) => ({\n mixins: [Dom],\n\n data () {\n return {\n [indexKey]: -1,\n [idKey]: '',\n scrollToOnHighlight,\n focusOnKeyboardNavigation,\n };\n },\n\n provide () {\n return {\n highlightId: () => this[idKey],\n };\n },\n\n methods: {\n // Returns the list element\n // this[listElement]() can return a Vue component, in which case we need to target\n // the $el property, or it can simply be an html element.\n _getListElement () {\n return returnFirstEl(this[listElementKey]()?.$el) || this[listElementKey]();\n },\n\n // Gets the length of all the items in the list, uses the listItemRole param to determine\n // whether an element is a list item.\n _itemsLength () {\n const listItems = this._getListItemNodes();\n\n if (listItems === null) {\n return 0;\n }\n\n return listItems.length;\n },\n\n // Gets all the list item nodes within the list element\n _getListItemNodes () {\n const listElement = this._getListElement();\n\n if (!listElement) {\n console.error(ERROR_INVALID_LIST_ELEMENT, listElement);\n return null;\n }\n\n return Array.from(listElement.querySelectorAll(`[role=\"${listItemRole}\"], #sr-only-close-button`));\n },\n\n onUpKey () {\n if (openMethod) {\n this[openMethod](true);\n }\n if (this[indexKey] > 0) {\n this.setHighlightIndex(this[indexKey] - 1);\n } else if (beginningOfListMethod) {\n this[beginningOfListMethod]();\n }\n this.scrollActiveItemIntoViewIfNeeded();\n this.focusActiveItemIfNeeded();\n },\n\n onDownKey () {\n if (openMethod) {\n this[openMethod](true);\n }\n if (this[indexKey] < this._itemsLength() - 1) {\n this.setHighlightIndex(this[indexKey] + 1);\n } else if (endOfListMethod) {\n this[endOfListMethod]();\n }\n this.scrollActiveItemIntoViewIfNeeded();\n this.focusActiveItemIfNeeded();\n },\n\n onHomeKey () {\n this.jumpToBeginning();\n this.scrollActiveItemIntoViewIfNeeded();\n this.focusActiveItemIfNeeded();\n },\n\n onEndKey () {\n this.jumpToEnd();\n this.scrollActiveItemIntoViewIfNeeded();\n this.focusActiveItemIfNeeded();\n },\n\n onNavigationKey (key) {\n const listItems = this._getListItemNodes();\n\n const matchingItems = listItems.filter(item => {\n const content = item.textContent.trim().toLowerCase();\n return content.startsWith(key.toLowerCase());\n });\n\n if (matchingItems.length <= 0) {\n return;\n }\n\n const highlightedMatchingItemIndex = matchingItems.findIndex(item => {\n return this[indexKey] === listItems.indexOf(item);\n });\n\n const nextHighlightedItemIndex = listItems.indexOf(\n highlightedMatchingItemIndex < matchingItems.length - 1\n ? matchingItems[highlightedMatchingItemIndex + 1]\n : matchingItems[0],\n );\n\n this.setHighlightIndex(nextHighlightedItemIndex);\n this.scrollActiveItemIntoViewIfNeeded();\n this.focusActiveItemIfNeeded();\n },\n\n isValidLetter (key) {\n if (key.length > 1) {\n return false;\n }\n\n return (key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z');\n },\n\n jumpToBeginning () {\n this.setHighlightIndex(0);\n },\n\n jumpToEnd () {\n this.setHighlightIndex(this._itemsLength() - 1);\n },\n\n setHighlightIndex (num) {\n this[indexKey] = num;\n this[idKey] = this._getItemId(num);\n\n if (this._itemsLength() && afterHighlightMethod) {\n this[afterHighlightMethod](num);\n }\n },\n\n setHighlightId (id) {\n if (!id) return;\n this[idKey] = id;\n this[indexKey] = this._getItemIndex(id);\n\n if (this._itemsLength() && afterHighlightMethod) {\n this[afterHighlightMethod](this._getItemIndex(id));\n }\n },\n\n _getItemIndex (id) {\n const listElement = this._getListElement();\n if (!listElement) {\n return;\n }\n\n const listItems = Array.from(listElement.querySelectorAll(`[role=\"${listItemRole}\"], #sr-only-close-button`));\n return listItems.indexOf(listElement.querySelector(`#${id}`));\n },\n\n _getItemId (index) {\n const listElement = this._getListElement();\n if (!listElement) {\n return;\n }\n\n return listElement.querySelectorAll(`[role=\"${listItemRole}\"], #sr-only-close-button`)[index]?.id;\n },\n\n scrollActiveItemIntoViewIfNeeded () {\n if (!this.scrollToOnHighlight) {\n return;\n }\n const activeItemEl = this[activeItemKey];\n if (activeItemEl) {\n // When listElementKey is not passed,\n // scrollElementIntoViewIfNeeded will default to the immediate wrapper of the item.\n const listElement = this._getListElement();\n this.scrollElementIntoViewIfNeeded(activeItemEl, null, null, listElement);\n }\n },\n\n focusActiveItemIfNeeded () {\n if (!this.focusOnKeyboardNavigation) {\n return;\n }\n const activeItemEl = this[activeItemKey];\n if (activeItemEl) {\n activeItemEl.focus();\n }\n },\n },\n});\n"],"mappings":"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"}