From df72a21fa89a4885bb666f4a3bc0a9e757b870c2 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 19 May 2021 01:23:52 +0300 Subject: [PATCH] Add `getNextActiveElement` helper function to utils, replacing custom implementation through components (#33608) --- js/src/carousel.js | 17 ++------------ js/src/dropdown.js | 22 +++++------------- js/src/util/index.js | 29 ++++++++++++++++++++++++ js/tests/unit/util/index.spec.js | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/js/src/carousel.js b/js/src/carousel.js index bb894e9c37..7d197ab1ee 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -10,6 +10,7 @@ import { getElementFromSelector, isRTL, isVisible, + getNextActiveElement, reflow, triggerTransitionEnd, typeCheckConfig @@ -337,21 +338,7 @@ class Carousel extends BaseComponent { _getItemByOrder(order, activeElement) { const isNext = order === ORDER_NEXT - const isPrev = order === ORDER_PREV - const activeIndex = this._getItemIndex(activeElement) - const lastItemIndex = this._items.length - 1 - const isGoingToWrap = (isPrev && activeIndex === 0) || (isNext && activeIndex === lastItemIndex) - - if (isGoingToWrap && !this._config.wrap) { - return activeElement - } - - const delta = isPrev ? -1 : 1 - const itemIndex = (activeIndex + delta) % this._items.length - - return itemIndex === -1 ? - this._items[this._items.length - 1] : - this._items[itemIndex] + return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) } _triggerSlideEvent(relatedTarget, eventDirectionName) { diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 565edb8929..cab2d018bb 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -16,6 +16,7 @@ import { isVisible, isRTL, noop, + getNextActiveElement, typeCheckConfig } from './util/index' import Data from './dom/data' @@ -354,28 +355,17 @@ class Dropdown extends BaseComponent { } _selectMenuItem(event) { + if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) { + return + } + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) if (!items.length) { return } - let index = items.indexOf(event.target) - - // Up - if (event.key === ARROW_UP_KEY && index > 0) { - index-- - } - - // Down - if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { - index++ - } - - // index is -1 if the first keydown is an ArrowUp - index = index === -1 ? 0 : index - - items[index].focus() + getNextActiveElement(items, event.target, event.key === ARROW_DOWN_KEY, false).focus() } // Static diff --git a/js/src/util/index.js b/js/src/util/index.js index 9441d0a44a..6b38a05e94 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -261,6 +261,34 @@ const execute = callback => { } } +/** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ +const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + let index = list.indexOf(activeElement) + + // if the element does not exist in the list initialize it as the first element + if (index === -1) { + return list[0] + } + + const listLength = list.length + + index += shouldGetNext ? 1 : -1 + + if (isCycleAllowed) { + index = (index + listLength) % listLength + } + + return list[Math.max(0, Math.min(index, listLength - 1))] +} + export { getElement, getUID, @@ -275,6 +303,7 @@ export { isDisabled, findShadowRoot, noop, + getNextActiveElement, reflow, getjQuery, onDOMContentLoaded, diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js index b4010e0e0e..ca6430bee5 100644 --- a/js/tests/unit/util/index.spec.js +++ b/js/tests/unit/util/index.spec.js @@ -611,4 +611,43 @@ describe('Util', () => { expect(spy).toHaveBeenCalled() }) }) + + describe('getNextActiveElement', () => { + it('should return first element if active not exists or not given', () => { + const array = ['a', 'b', 'c', 'd'] + + expect(Util.getNextActiveElement(array, '', true, true)).toEqual('a') + expect(Util.getNextActiveElement(array, 'g', true, true)).toEqual('a') + }) + + it('should return next element or same if is last', () => { + const array = ['a', 'b', 'c', 'd'] + + expect(Util.getNextActiveElement(array, 'a', true, true)).toEqual('b') + expect(Util.getNextActiveElement(array, 'b', true, true)).toEqual('c') + expect(Util.getNextActiveElement(array, 'd', true, false)).toEqual('d') + }) + + it('should return next element or first, if is last and "isCycleAllowed = true"', () => { + const array = ['a', 'b', 'c', 'd'] + + expect(Util.getNextActiveElement(array, 'c', true, true)).toEqual('d') + expect(Util.getNextActiveElement(array, 'd', true, true)).toEqual('a') + }) + + it('should return previous element or same if is first', () => { + const array = ['a', 'b', 'c', 'd'] + + expect(Util.getNextActiveElement(array, 'b', false, true)).toEqual('a') + expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c') + expect(Util.getNextActiveElement(array, 'a', false, false)).toEqual('a') + }) + + it('should return next element or first, if is last and "isCycleAllowed = true"', () => { + const array = ['a', 'b', 'c', 'd'] + + expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c') + expect(Util.getNextActiveElement(array, 'a', false, true)).toEqual('d') + }) + }) })