@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
1 lines • 10.6 kB
Source Map (JSON)
{"version":3,"file":"keyboard-list-navigation.cjs","sources":["../../../common/mixins/keyboard_list_navigation.js"],"sourcesContent":["import Dom from './dom';\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 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 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"],"names":["ERROR_INVALID_LIST_ELEMENT","KeyboardNavigation","listItemRole","indexKey","idKey","listElementKey","activeItemKey","openMethod","afterHighlightMethod","beginningOfListMethod","endOfListMethod","scrollToOnHighlight","focusOnKeyboardNavigation","Dom","_a","listItems","listElement","key","matchingItems","item","highlightedMatchingItemIndex","nextHighlightedItemIndex","num","id","index","activeItemEl"],"mappings":"yIAEMA,EACJ,6FA0BFC,EAAe,CAAC,CAGd,aAAAC,EAAe,SAEf,SAAAC,EAAW,iBACX,MAAAC,EAAQ,cAER,eAAAC,EAAiB,UAEjB,cAAAC,EAAgB,GAGhB,WAAAC,EAAa,KAEb,qBAAAC,EAAuB,KAEvB,sBAAAC,EAAwB,KAExB,gBAAAC,EAAkB,KAElB,oBAAAC,EAAsB,GAEtB,0BAAAC,EAA4B,EAC9B,EAAI,MAAQ,CACV,OAAQ,CAACC,EAAAA,OAAG,EAEZ,MAAQ,CACN,MAAO,CACL,CAACV,CAAQ,EAAG,GACZ,CAACC,CAAK,EAAG,GACT,oBAAAO,EACA,0BAAAC,CACN,CACE,EAEA,SAAW,CACT,MAAO,CACL,YAAa,IAAM,KAAKR,CAAK,CACnC,CACE,EAEA,QAAS,CAIP,iBAAmB,OACjB,QAAOU,EAAA,KAAKT,CAAc,EAAC,IAApB,YAAAS,EAAwB,MAAO,KAAKT,CAAc,EAAC,CAC5D,EAIA,cAAgB,CACd,MAAMU,EAAY,KAAK,kBAAiB,EAExC,OAAIA,IAAc,KACT,EAGFA,EAAU,MACnB,EAGA,mBAAqB,CACnB,MAAMC,EAAc,KAAK,gBAAe,EAExC,OAAKA,EAKE,MAAM,KAAKA,EAAY,iBAAiB,UAAUd,CAAY,2BAA2B,CAAC,GAJ/F,QAAQ,MAAMF,EAA4BgB,CAAW,EAC9C,KAIX,EAEA,SAAW,CACLT,GACF,KAAKA,CAAU,EAAE,EAAI,EAEnB,KAAKJ,CAAQ,EAAI,EACnB,KAAK,kBAAkB,KAAKA,CAAQ,EAAI,CAAC,EAChCM,GACT,KAAKA,CAAqB,EAAC,EAE7B,KAAK,iCAAgC,EACrC,KAAK,wBAAuB,CAC9B,EAEA,WAAa,CACPF,GACF,KAAKA,CAAU,EAAE,EAAI,EAEnB,KAAKJ,CAAQ,EAAI,KAAK,aAAY,EAAK,EACzC,KAAK,kBAAkB,KAAKA,CAAQ,EAAI,CAAC,EAChCO,GACT,KAAKA,CAAe,EAAC,EAEvB,KAAK,iCAAgC,EACrC,KAAK,wBAAuB,CAC9B,EAEA,WAAa,CACX,KAAK,gBAAe,EACpB,KAAK,iCAAgC,EACrC,KAAK,wBAAuB,CAC9B,EAEA,UAAY,CACV,KAAK,UAAS,EACd,KAAK,iCAAgC,EACrC,KAAK,wBAAuB,CAC9B,EAEA,gBAAiBO,EAAK,CACpB,MAAMF,EAAY,KAAK,kBAAiB,EAElCG,EAAgBH,EAAU,OAAOI,GACrBA,EAAK,YAAY,KAAI,EAAG,YAAW,EACpC,WAAWF,EAAI,YAAW,CAAE,CAC5C,EAED,GAAIC,EAAc,QAAU,EAC1B,OAGF,MAAME,EAA+BF,EAAc,UAAUC,GACpD,KAAKhB,CAAQ,IAAMY,EAAU,QAAQI,CAAI,CACjD,EAEKE,EAA2BN,EAAU,QACzCK,EAA+BF,EAAc,OAAS,EAClDA,EAAcE,EAA+B,CAAC,EAC9CF,EAAc,CAAC,CAC3B,EAEM,KAAK,kBAAkBG,CAAwB,EAC/C,KAAK,iCAAgC,EACrC,KAAK,wBAAuB,CAC9B,EAEA,cAAeJ,EAAK,CAClB,OAAIA,EAAI,OAAS,EACR,GAGDA,GAAO,KAAOA,GAAO,KAASA,GAAO,KAAOA,GAAO,GAC7D,EAEA,iBAAmB,CACjB,KAAK,kBAAkB,CAAC,CAC1B,EAEA,WAAa,CACX,KAAK,kBAAkB,KAAK,aAAY,EAAK,CAAC,CAChD,EAEA,kBAAmBK,EAAK,CACtB,KAAKnB,CAAQ,EAAImB,EACjB,KAAKlB,CAAK,EAAI,KAAK,WAAWkB,CAAG,EAE7B,KAAK,aAAY,GAAMd,GACzB,KAAKA,CAAoB,EAAEc,CAAG,CAElC,EAEA,eAAgBC,EAAI,CAClB,KAAKnB,CAAK,EAAImB,EACd,KAAKpB,CAAQ,EAAI,KAAK,cAAcoB,CAAE,EAElC,KAAK,aAAY,GAAMf,GACzB,KAAKA,CAAoB,EAAE,KAAK,cAAce,CAAE,CAAC,CAErD,EAEA,cAAeA,EAAI,CACjB,MAAMP,EAAc,KAAK,gBAAe,EACxC,OAAKA,EAIa,MAAM,KAAKA,EAAY,iBAAiB,UAAUd,CAAY,2BAA2B,CAAC,EAC3F,QAAQc,EAAY,cAAc,IAAIO,CAAE,EAAE,CAAC,EAJ1D,MAKJ,EAEA,WAAYC,EAAO,OACjB,MAAMR,EAAc,KAAK,gBAAe,EACxC,GAAKA,EAIL,OAAOF,EAAAE,EAAY,iBAAiB,UAAUd,CAAY,2BAA2B,EAAEsB,CAAK,IAArF,YAAAV,EAAwF,EACjG,EAEA,kCAAoC,CAClC,GAAI,CAAC,KAAK,oBACR,OAEF,MAAMW,EAAe,KAAKnB,CAAa,EACvC,GAAImB,EAAc,CAGhB,MAAMT,EAAc,KAAK,gBAAe,EACxC,KAAK,8BAA8BS,EAAc,KAAM,KAAMT,CAAW,CAC1E,CACF,EAEA,yBAA2B,CACzB,GAAI,CAAC,KAAK,0BACR,OAEF,MAAMS,EAAe,KAAKnB,CAAa,EACnCmB,GACFA,EAAa,MAAK,CAEtB,CACJ,CACA"}