[d565449] | 1 | /**
|
---|
| 2 | * --------------------------------------------------------------------------
|
---|
| 3 | * Bootstrap dom/selector-engine.js
|
---|
| 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
---|
| 5 | * --------------------------------------------------------------------------
|
---|
| 6 | */
|
---|
| 7 |
|
---|
| 8 | import { isDisabled, isVisible, parseSelector } from '../util/index.js'
|
---|
| 9 |
|
---|
| 10 | const getSelector = element => {
|
---|
| 11 | let selector = element.getAttribute('data-bs-target')
|
---|
| 12 |
|
---|
| 13 | if (!selector || selector === '#') {
|
---|
| 14 | let hrefAttribute = element.getAttribute('href')
|
---|
| 15 |
|
---|
| 16 | // The only valid content that could double as a selector are IDs or classes,
|
---|
| 17 | // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
---|
| 18 | // `document.querySelector` will rightfully complain it is invalid.
|
---|
| 19 | // See https://github.com/twbs/bootstrap/issues/32273
|
---|
| 20 | if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
|
---|
| 21 | return null
|
---|
| 22 | }
|
---|
| 23 |
|
---|
| 24 | // Just in case some CMS puts out a full URL with the anchor appended
|
---|
| 25 | if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
---|
| 26 | hrefAttribute = `#${hrefAttribute.split('#')[1]}`
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 29 | selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
---|
| 30 | }
|
---|
| 31 |
|
---|
| 32 | return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null
|
---|
| 33 | }
|
---|
| 34 |
|
---|
| 35 | const SelectorEngine = {
|
---|
| 36 | find(selector, element = document.documentElement) {
|
---|
| 37 | return [].concat(...Element.prototype.querySelectorAll.call(element, selector))
|
---|
| 38 | },
|
---|
| 39 |
|
---|
| 40 | findOne(selector, element = document.documentElement) {
|
---|
| 41 | return Element.prototype.querySelector.call(element, selector)
|
---|
| 42 | },
|
---|
| 43 |
|
---|
| 44 | children(element, selector) {
|
---|
| 45 | return [].concat(...element.children).filter(child => child.matches(selector))
|
---|
| 46 | },
|
---|
| 47 |
|
---|
| 48 | parents(element, selector) {
|
---|
| 49 | const parents = []
|
---|
| 50 | let ancestor = element.parentNode.closest(selector)
|
---|
| 51 |
|
---|
| 52 | while (ancestor) {
|
---|
| 53 | parents.push(ancestor)
|
---|
| 54 | ancestor = ancestor.parentNode.closest(selector)
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | return parents
|
---|
| 58 | },
|
---|
| 59 |
|
---|
| 60 | prev(element, selector) {
|
---|
| 61 | let previous = element.previousElementSibling
|
---|
| 62 |
|
---|
| 63 | while (previous) {
|
---|
| 64 | if (previous.matches(selector)) {
|
---|
| 65 | return [previous]
|
---|
| 66 | }
|
---|
| 67 |
|
---|
| 68 | previous = previous.previousElementSibling
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | return []
|
---|
| 72 | },
|
---|
| 73 | // TODO: this is now unused; remove later along with prev()
|
---|
| 74 | next(element, selector) {
|
---|
| 75 | let next = element.nextElementSibling
|
---|
| 76 |
|
---|
| 77 | while (next) {
|
---|
| 78 | if (next.matches(selector)) {
|
---|
| 79 | return [next]
|
---|
| 80 | }
|
---|
| 81 |
|
---|
| 82 | next = next.nextElementSibling
|
---|
| 83 | }
|
---|
| 84 |
|
---|
| 85 | return []
|
---|
| 86 | },
|
---|
| 87 |
|
---|
| 88 | focusableChildren(element) {
|
---|
| 89 | const focusables = [
|
---|
| 90 | 'a',
|
---|
| 91 | 'button',
|
---|
| 92 | 'input',
|
---|
| 93 | 'textarea',
|
---|
| 94 | 'select',
|
---|
| 95 | 'details',
|
---|
| 96 | '[tabindex]',
|
---|
| 97 | '[contenteditable="true"]'
|
---|
| 98 | ].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
|
---|
| 99 |
|
---|
| 100 | return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
|
---|
| 101 | },
|
---|
| 102 |
|
---|
| 103 | getSelectorFromElement(element) {
|
---|
| 104 | const selector = getSelector(element)
|
---|
| 105 |
|
---|
| 106 | if (selector) {
|
---|
| 107 | return SelectorEngine.findOne(selector) ? selector : null
|
---|
| 108 | }
|
---|
| 109 |
|
---|
| 110 | return null
|
---|
| 111 | },
|
---|
| 112 |
|
---|
| 113 | getElementFromSelector(element) {
|
---|
| 114 | const selector = getSelector(element)
|
---|
| 115 |
|
---|
| 116 | return selector ? SelectorEngine.findOne(selector) : null
|
---|
| 117 | },
|
---|
| 118 |
|
---|
| 119 | getMultipleElementsFromSelector(element) {
|
---|
| 120 | const selector = getSelector(element)
|
---|
| 121 |
|
---|
| 122 | return selector ? SelectorEngine.find(selector) : []
|
---|
| 123 | }
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | export default SelectorEngine
|
---|