diff --git a/js/src/carousel.js b/js/src/carousel.js index 49527200d..153dd82a9 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -15,7 +15,7 @@ import { } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import Swipe from './util/swipe' import BaseComponent from './base-component' @@ -430,7 +430,7 @@ class Carousel extends BaseComponent { */ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) { - const target = getElementFromSelector(this) + const target = SelectorEngine.getElementFromSelector(this) if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { return diff --git a/js/src/collapse.js b/js/src/collapse.js index 4999257f8..2e7760fcc 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -7,12 +7,7 @@ import { defineJQueryPlugin, getElement, reflow } from './util/index' import EventHandler from './dom/event-handler' -import { - getElementFromSelector, - getMultipleElementsFromSelector, - getSelectorFromElement, - SelectorEngine -} from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' /** @@ -67,7 +62,7 @@ class Collapse extends BaseComponent { const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (const elem of toggleList) { - const selector = getSelectorFromElement(elem) + const selector = SelectorEngine.getSelectorFromElement(elem) const filterElement = SelectorEngine.find(selector) .filter(foundElement => foundElement === this._element) @@ -184,7 +179,7 @@ class Collapse extends BaseComponent { this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) for (const trigger of this._triggerArray) { - const element = getElementFromSelector(trigger) + const element = SelectorEngine.getElementFromSelector(trigger) if (element && !this._isShown(element)) { this._addAriaAndCollapsedClass([trigger], false) @@ -228,7 +223,7 @@ class Collapse extends BaseComponent { const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE) for (const element of children) { - const selected = getElementFromSelector(element) + const selected = SelectorEngine.getElementFromSelector(element) if (selected) { this._addAriaAndCollapsedClass([element], this._isShown(selected)) @@ -284,7 +279,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( event.preventDefault() } - for (const element of getMultipleElementsFromSelector(this)) { + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { Collapse.getOrCreateInstance(element, { toggle: false }).toggle() } }) diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 69eb79aa2..bdc1e1593 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -77,59 +77,50 @@ const SelectorEngine = { ].map(selector => `${selector}:not([tabindex^="-"])`).join(',') return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) - } -} - -const getSelector = element => { - let selector = element.getAttribute('data-bs-target') - - if (!selector || selector === '#') { - let hrefAttribute = element.getAttribute('href') - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { - return null + }, + getSelector(element) { + let selector = element.getAttribute('data-bs-target') + + if (!selector || selector === '#') { + let hrefAttribute = element.getAttribute('href') + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { + return null + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}` + } + + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null } - - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { - hrefAttribute = `#${hrefAttribute.split('#')[1]}` + + return selector + }, + getSelectorFromElement(element) { + const selector = SelectorEngine.getSelector(element) + + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null } - - selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null - } - - return selector -} - -const getSelectorFromElement = element => { - const selector = getSelector(element) - - if (selector) { - return SelectorEngine.findOne(selector) ? selector : null + + return null + }, + getElementFromSelector(element) { + const selector = SelectorEngine.getSelector(element) + + return selector ? SelectorEngine.findOne(selector) : null + }, + getMultipleElementsFromSelector(element) { + const selector = SelectorEngine.getSelector(element) + + return selector ? SelectorEngine.find(selector) : [] } - - return null } -const getElementFromSelector = element => { - const selector = getSelector(element) - - return selector ? SelectorEngine.findOne(selector) : null -} - -const getMultipleElementsFromSelector = element => { - const selector = getSelector(element) - - return selector ? SelectorEngine.find(selector) : [] -} - -export { - SelectorEngine, - getElementFromSelector, - getMultipleElementsFromSelector, - getSelectorFromElement -} +export default SelectorEngine diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 1f4f2c2bd..d37886d89 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -19,7 +19,7 @@ import { } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import { SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' /** diff --git a/js/src/modal.js b/js/src/modal.js index 22ae926b2..2ef9f49d4 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -7,7 +7,7 @@ import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index' import EventHandler from './dom/event-handler' -import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' import Backdrop from './util/backdrop' @@ -336,7 +336,7 @@ class Modal extends BaseComponent { */ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { - const target = getElementFromSelector(this) + const target = SelectorEngine.getElementFromSelector(this) if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 436f441c9..a768cfad4 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -13,7 +13,7 @@ import { import ScrollBarHelper from './util/scrollbar' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' -import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap' import { enableDismissTrigger } from './util/component-functions' @@ -230,7 +230,7 @@ class Offcanvas extends BaseComponent { */ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { - const target = getElementFromSelector(this) + const target = SelectorEngine.getElementFromSelector(this) if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index ae70ae8bc..a73bba840 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -7,7 +7,7 @@ import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' import EventHandler from './dom/event-handler' -import { SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' /** diff --git a/js/src/tab.js b/js/src/tab.js index 6d53016f0..fd8d7f6da 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -7,7 +7,7 @@ import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index' import EventHandler from './dom/event-handler' -import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' +import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' /** @@ -106,7 +106,7 @@ class Tab extends BaseComponent { element.classList.add(CLASS_NAME_ACTIVE) - this._activate(getElementFromSelector(element)) // Search and activate/show the proper section + this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section const complete = () => { if (element.getAttribute('role') !== 'tab') { @@ -133,7 +133,7 @@ class Tab extends BaseComponent { element.classList.remove(CLASS_NAME_ACTIVE) element.blur() - this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too + this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too const complete = () => { if (element.getAttribute('role') !== 'tab') { @@ -203,7 +203,7 @@ class Tab extends BaseComponent { } _setInitialAttributesOnTargetPanel(child) { - const target = getElementFromSelector(child) + const target = SelectorEngine.getElementFromSelector(child) if (!target) { return diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index 796b1bfa6..ecfc707e2 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -7,7 +7,7 @@ import EventHandler from '../dom/event-handler' import { isDisabled } from './index' -import { getElementFromSelector } from '../dom/selector-engine' +import SelectorEngine from '../dom/selector-engine' const enableDismissTrigger = (component, method = 'hide') => { const clickEvent = `click.dismiss${component.EVENT_KEY}` @@ -22,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => { return } - const target = getElementFromSelector(this) || this.closest(`.${name}`) + const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`) const instance = component.getOrCreateInstance(target) // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index 5924639d2..01ac76683 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -6,7 +6,7 @@ */ import EventHandler from '../dom/event-handler' -import { SelectorEngine } from '../dom/selector-engine' +import SelectorEngine from '../dom/selector-engine' import Config from './config' /** diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index d531b769d..421426d41 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -5,7 +5,7 @@ * -------------------------------------------------------------------------- */ -import { SelectorEngine } from '../dom/selector-engine' +import SelectorEngine from '../dom/selector-engine' import Manipulator from '../dom/manipulator' import { isElement } from './index' diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index 1de03473c..16ec6c28d 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -7,7 +7,7 @@ import { DefaultAllowlist, sanitizeHtml } from './sanitizer' import { execute, getElement, isElement } from '../util/index' -import { SelectorEngine } from '../dom/selector-engine' +import SelectorEngine from '../dom/selector-engine' import Config from './config' /** diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js index eb485c135..905e25bae 100644 --- a/js/tests/unit/dom/selector-engine.spec.js +++ b/js/tests/unit/dom/selector-engine.spec.js @@ -1,9 +1,4 @@ -import { - getElementFromSelector, - getMultipleElementsFromSelector, - getSelectorFromElement, - SelectorEngine -} from '../../../src/dom/selector-engine' +import SelectorEngine from '../../../src/dom/selector-engine' import { clearFixture, getFixture } from '../../helpers/fixture' describe('SelectorEngine', () => { @@ -247,7 +242,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toEqual('.target') + expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target') }) it('should get selector from href if no data-bs-target set', () => { @@ -258,7 +253,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toEqual('.target') + expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target') }) it('should get selector from href if data-bs-target equal to #', () => { @@ -269,7 +264,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toEqual('.target') + expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target') }) it('should return null if a selector from a href is a url without an anchor', () => { @@ -280,7 +275,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toBeNull() + expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull() }) it('should return the anchor if a selector from a href is a url', () => { @@ -291,7 +286,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toEqual('#target') + expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('#target') }) it('should return null if selector not found', () => { @@ -299,7 +294,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getSelectorFromElement(testEl)).toBeNull() + expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull() }) it('should return null if no selector', () => { @@ -307,7 +302,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('div') - expect(getSelectorFromElement(testEl)).toBeNull() + expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull() }) }) @@ -320,7 +315,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) + expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) }) it('should get element from href if no data-bs-target set', () => { @@ -331,7 +326,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) + expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) }) it('should return null if element not found', () => { @@ -339,7 +334,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getElementFromSelector(testEl)).toBeNull() + expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull() }) it('should return null if no selector', () => { @@ -347,7 +342,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('div') - expect(getElementFromSelector(testEl)).toBeNull() + expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull() }) }) @@ -361,7 +356,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) }) it('should get elements in array, from href if no data-bs-target set', () => { @@ -373,7 +368,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) }) it('should return empty array if elements not found', () => { @@ -381,7 +376,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('#test') - expect(getMultipleElementsFromSelector(testEl)).toHaveSize(0) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0) }) it('should return empty array if no selector', () => { @@ -389,7 +384,7 @@ describe('SelectorEngine', () => { const testEl = fixtureEl.querySelector('div') - expect(getMultipleElementsFromSelector(testEl)).toHaveSize(0) + expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0) }) }) }) diff --git a/js/tests/unit/util/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js index 6178f7a5c..bedd124c9 100644 --- a/js/tests/unit/util/focustrap.spec.js +++ b/js/tests/unit/util/focustrap.spec.js @@ -1,6 +1,6 @@ import FocusTrap from '../../../src/util/focustrap' import EventHandler from '../../../src/dom/event-handler' -import { SelectorEngine } from '../../../src/dom/selector-engine' +import SelectorEngine from '../../../src/dom/selector-engine' import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' describe('FocusTrap', () => {