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
|
---|