1 | /**
|
---|
2 | * --------------------------------------------------------------------------
|
---|
3 | * Bootstrap (v5.1.3): util/index.js
|
---|
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
---|
5 | * --------------------------------------------------------------------------
|
---|
6 | */
|
---|
7 |
|
---|
8 | const MAX_UID = 1000000
|
---|
9 | const MILLISECONDS_MULTIPLIER = 1000
|
---|
10 | const TRANSITION_END = 'transitionend'
|
---|
11 |
|
---|
12 | // Shoutout AngusCroll (https://goo.gl/pxwQGp)
|
---|
13 | const toType = obj => {
|
---|
14 | if (obj === null || obj === undefined) {
|
---|
15 | return `${obj}`
|
---|
16 | }
|
---|
17 |
|
---|
18 | return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase()
|
---|
19 | }
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * --------------------------------------------------------------------------
|
---|
23 | * Public Util Api
|
---|
24 | * --------------------------------------------------------------------------
|
---|
25 | */
|
---|
26 |
|
---|
27 | const getUID = prefix => {
|
---|
28 | do {
|
---|
29 | prefix += Math.floor(Math.random() * MAX_UID)
|
---|
30 | } while (document.getElementById(prefix))
|
---|
31 |
|
---|
32 | return prefix
|
---|
33 | }
|
---|
34 |
|
---|
35 | const getSelector = element => {
|
---|
36 | let selector = element.getAttribute('data-bs-target')
|
---|
37 |
|
---|
38 | if (!selector || selector === '#') {
|
---|
39 | let hrefAttr = element.getAttribute('href')
|
---|
40 |
|
---|
41 | // The only valid content that could double as a selector are IDs or classes,
|
---|
42 | // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
---|
43 | // `document.querySelector` will rightfully complain it is invalid.
|
---|
44 | // See https://github.com/twbs/bootstrap/issues/32273
|
---|
45 | if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) {
|
---|
46 | return null
|
---|
47 | }
|
---|
48 |
|
---|
49 | // Just in case some CMS puts out a full URL with the anchor appended
|
---|
50 | if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
|
---|
51 | hrefAttr = `#${hrefAttr.split('#')[1]}`
|
---|
52 | }
|
---|
53 |
|
---|
54 | selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null
|
---|
55 | }
|
---|
56 |
|
---|
57 | return selector
|
---|
58 | }
|
---|
59 |
|
---|
60 | const getSelectorFromElement = element => {
|
---|
61 | const selector = getSelector(element)
|
---|
62 |
|
---|
63 | if (selector) {
|
---|
64 | return document.querySelector(selector) ? selector : null
|
---|
65 | }
|
---|
66 |
|
---|
67 | return null
|
---|
68 | }
|
---|
69 |
|
---|
70 | const getElementFromSelector = element => {
|
---|
71 | const selector = getSelector(element)
|
---|
72 |
|
---|
73 | return selector ? document.querySelector(selector) : null
|
---|
74 | }
|
---|
75 |
|
---|
76 | const getTransitionDurationFromElement = element => {
|
---|
77 | if (!element) {
|
---|
78 | return 0
|
---|
79 | }
|
---|
80 |
|
---|
81 | // Get transition-duration of the element
|
---|
82 | let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
|
---|
83 |
|
---|
84 | const floatTransitionDuration = Number.parseFloat(transitionDuration)
|
---|
85 | const floatTransitionDelay = Number.parseFloat(transitionDelay)
|
---|
86 |
|
---|
87 | // Return 0 if element or transition duration is not found
|
---|
88 | if (!floatTransitionDuration && !floatTransitionDelay) {
|
---|
89 | return 0
|
---|
90 | }
|
---|
91 |
|
---|
92 | // If multiple durations are defined, take the first
|
---|
93 | transitionDuration = transitionDuration.split(',')[0]
|
---|
94 | transitionDelay = transitionDelay.split(',')[0]
|
---|
95 |
|
---|
96 | return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
|
---|
97 | }
|
---|
98 |
|
---|
99 | const triggerTransitionEnd = element => {
|
---|
100 | element.dispatchEvent(new Event(TRANSITION_END))
|
---|
101 | }
|
---|
102 |
|
---|
103 | const isElement = obj => {
|
---|
104 | if (!obj || typeof obj !== 'object') {
|
---|
105 | return false
|
---|
106 | }
|
---|
107 |
|
---|
108 | if (typeof obj.jquery !== 'undefined') {
|
---|
109 | obj = obj[0]
|
---|
110 | }
|
---|
111 |
|
---|
112 | return typeof obj.nodeType !== 'undefined'
|
---|
113 | }
|
---|
114 |
|
---|
115 | const getElement = obj => {
|
---|
116 | if (isElement(obj)) { // it's a jQuery object or a node element
|
---|
117 | return obj.jquery ? obj[0] : obj
|
---|
118 | }
|
---|
119 |
|
---|
120 | if (typeof obj === 'string' && obj.length > 0) {
|
---|
121 | return document.querySelector(obj)
|
---|
122 | }
|
---|
123 |
|
---|
124 | return null
|
---|
125 | }
|
---|
126 |
|
---|
127 | const typeCheckConfig = (componentName, config, configTypes) => {
|
---|
128 | Object.keys(configTypes).forEach(property => {
|
---|
129 | const expectedTypes = configTypes[property]
|
---|
130 | const value = config[property]
|
---|
131 | const valueType = value && isElement(value) ? 'element' : toType(value)
|
---|
132 |
|
---|
133 | if (!new RegExp(expectedTypes).test(valueType)) {
|
---|
134 | throw new TypeError(
|
---|
135 | `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`
|
---|
136 | )
|
---|
137 | }
|
---|
138 | })
|
---|
139 | }
|
---|
140 |
|
---|
141 | const isVisible = element => {
|
---|
142 | if (!isElement(element) || element.getClientRects().length === 0) {
|
---|
143 | return false
|
---|
144 | }
|
---|
145 |
|
---|
146 | return getComputedStyle(element).getPropertyValue('visibility') === 'visible'
|
---|
147 | }
|
---|
148 |
|
---|
149 | const isDisabled = element => {
|
---|
150 | if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
---|
151 | return true
|
---|
152 | }
|
---|
153 |
|
---|
154 | if (element.classList.contains('disabled')) {
|
---|
155 | return true
|
---|
156 | }
|
---|
157 |
|
---|
158 | if (typeof element.disabled !== 'undefined') {
|
---|
159 | return element.disabled
|
---|
160 | }
|
---|
161 |
|
---|
162 | return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
|
---|
163 | }
|
---|
164 |
|
---|
165 | const findShadowRoot = element => {
|
---|
166 | if (!document.documentElement.attachShadow) {
|
---|
167 | return null
|
---|
168 | }
|
---|
169 |
|
---|
170 | // Can find the shadow root otherwise it'll return the document
|
---|
171 | if (typeof element.getRootNode === 'function') {
|
---|
172 | const root = element.getRootNode()
|
---|
173 | return root instanceof ShadowRoot ? root : null
|
---|
174 | }
|
---|
175 |
|
---|
176 | if (element instanceof ShadowRoot) {
|
---|
177 | return element
|
---|
178 | }
|
---|
179 |
|
---|
180 | // when we don't find a shadow root
|
---|
181 | if (!element.parentNode) {
|
---|
182 | return null
|
---|
183 | }
|
---|
184 |
|
---|
185 | return findShadowRoot(element.parentNode)
|
---|
186 | }
|
---|
187 |
|
---|
188 | const noop = () => {}
|
---|
189 |
|
---|
190 | /**
|
---|
191 | * Trick to restart an element's animation
|
---|
192 | *
|
---|
193 | * @param {HTMLElement} element
|
---|
194 | * @return void
|
---|
195 | *
|
---|
196 | * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
|
---|
197 | */
|
---|
198 | const reflow = element => {
|
---|
199 | // eslint-disable-next-line no-unused-expressions
|
---|
200 | element.offsetHeight
|
---|
201 | }
|
---|
202 |
|
---|
203 | const getjQuery = () => {
|
---|
204 | const { jQuery } = window
|
---|
205 |
|
---|
206 | if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
|
---|
207 | return jQuery
|
---|
208 | }
|
---|
209 |
|
---|
210 | return null
|
---|
211 | }
|
---|
212 |
|
---|
213 | const DOMContentLoadedCallbacks = []
|
---|
214 |
|
---|
215 | const onDOMContentLoaded = callback => {
|
---|
216 | if (document.readyState === 'loading') {
|
---|
217 | // add listener on the first call when the document is in loading state
|
---|
218 | if (!DOMContentLoadedCallbacks.length) {
|
---|
219 | document.addEventListener('DOMContentLoaded', () => {
|
---|
220 | DOMContentLoadedCallbacks.forEach(callback => callback())
|
---|
221 | })
|
---|
222 | }
|
---|
223 |
|
---|
224 | DOMContentLoadedCallbacks.push(callback)
|
---|
225 | } else {
|
---|
226 | callback()
|
---|
227 | }
|
---|
228 | }
|
---|
229 |
|
---|
230 | const isRTL = () => document.documentElement.dir === 'rtl'
|
---|
231 |
|
---|
232 | const defineJQueryPlugin = plugin => {
|
---|
233 | onDOMContentLoaded(() => {
|
---|
234 | const $ = getjQuery()
|
---|
235 | /* istanbul ignore if */
|
---|
236 | if ($) {
|
---|
237 | const name = plugin.NAME
|
---|
238 | const JQUERY_NO_CONFLICT = $.fn[name]
|
---|
239 | $.fn[name] = plugin.jQueryInterface
|
---|
240 | $.fn[name].Constructor = plugin
|
---|
241 | $.fn[name].noConflict = () => {
|
---|
242 | $.fn[name] = JQUERY_NO_CONFLICT
|
---|
243 | return plugin.jQueryInterface
|
---|
244 | }
|
---|
245 | }
|
---|
246 | })
|
---|
247 | }
|
---|
248 |
|
---|
249 | const execute = callback => {
|
---|
250 | if (typeof callback === 'function') {
|
---|
251 | callback()
|
---|
252 | }
|
---|
253 | }
|
---|
254 |
|
---|
255 | const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
|
---|
256 | if (!waitForTransition) {
|
---|
257 | execute(callback)
|
---|
258 | return
|
---|
259 | }
|
---|
260 |
|
---|
261 | const durationPadding = 5
|
---|
262 | const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
|
---|
263 |
|
---|
264 | let called = false
|
---|
265 |
|
---|
266 | const handler = ({ target }) => {
|
---|
267 | if (target !== transitionElement) {
|
---|
268 | return
|
---|
269 | }
|
---|
270 |
|
---|
271 | called = true
|
---|
272 | transitionElement.removeEventListener(TRANSITION_END, handler)
|
---|
273 | execute(callback)
|
---|
274 | }
|
---|
275 |
|
---|
276 | transitionElement.addEventListener(TRANSITION_END, handler)
|
---|
277 | setTimeout(() => {
|
---|
278 | if (!called) {
|
---|
279 | triggerTransitionEnd(transitionElement)
|
---|
280 | }
|
---|
281 | }, emulatedDuration)
|
---|
282 | }
|
---|
283 |
|
---|
284 | /**
|
---|
285 | * Return the previous/next element of a list.
|
---|
286 | *
|
---|
287 | * @param {array} list The list of elements
|
---|
288 | * @param activeElement The active element
|
---|
289 | * @param shouldGetNext Choose to get next or previous element
|
---|
290 | * @param isCycleAllowed
|
---|
291 | * @return {Element|elem} The proper element
|
---|
292 | */
|
---|
293 | const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
|
---|
294 | let index = list.indexOf(activeElement)
|
---|
295 |
|
---|
296 | // if the element does not exist in the list return an element depending on the direction and if cycle is allowed
|
---|
297 | if (index === -1) {
|
---|
298 | return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]
|
---|
299 | }
|
---|
300 |
|
---|
301 | const listLength = list.length
|
---|
302 |
|
---|
303 | index += shouldGetNext ? 1 : -1
|
---|
304 |
|
---|
305 | if (isCycleAllowed) {
|
---|
306 | index = (index + listLength) % listLength
|
---|
307 | }
|
---|
308 |
|
---|
309 | return list[Math.max(0, Math.min(index, listLength - 1))]
|
---|
310 | }
|
---|
311 |
|
---|
312 | export {
|
---|
313 | getElement,
|
---|
314 | getUID,
|
---|
315 | getSelectorFromElement,
|
---|
316 | getElementFromSelector,
|
---|
317 | getTransitionDurationFromElement,
|
---|
318 | triggerTransitionEnd,
|
---|
319 | isElement,
|
---|
320 | typeCheckConfig,
|
---|
321 | isVisible,
|
---|
322 | isDisabled,
|
---|
323 | findShadowRoot,
|
---|
324 | noop,
|
---|
325 | getNextActiveElement,
|
---|
326 | reflow,
|
---|
327 | getjQuery,
|
---|
328 | onDOMContentLoaded,
|
---|
329 | isRTL,
|
---|
330 | defineJQueryPlugin,
|
---|
331 | execute,
|
---|
332 | executeAfterTransition
|
---|
333 | }
|
---|