Diff: STRATO-apps/wordpress_03/app/wp-content/themes/blocksy/static/js/frontend/header/menu.js
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
import { isTouchDevice } from '../helpers/is-touch-device'
2
+
import { isIosDevice } from '../helpers/is-ios-device'
3
+
4
+
export const isMegaMenuCustomWidthCentered = (el) => {
5
+
return (
6
+
el.classList.contains('ct-mega-menu-custom-width') &&
7
+
el.classList.contains('ct-mega-menu-centered')
8
+
)
9
+
}
10
+
11
+
const isRtl = () => document.querySelector('html').dir === 'rtl'
12
+
13
+
const isEligibleForSubmenu = (el) => {
14
+
if (!el.className.includes('animated-submenu')) {
15
+
return false
16
+
}
17
+
18
+
if (el.className.includes('ct-mega-menu')) {
19
+
// If not custom width, it should not apply data-submenu attribute
20
+
// at all.
21
+
if (!el.classList.contains('ct-mega-menu-custom-width')) {
22
+
return false
23
+
}
24
+
25
+
// Don't treat centered custom width mega menus as regular sub-menus.
26
+
if (isMegaMenuCustomWidthCentered(el)) {
27
+
return false
28
+
}
29
+
30
+
return true
31
+
}
32
+
33
+
return true
34
+
}
35
+
36
+
const getAllParents = (a) => {
37
+
var els = []
38
+
39
+
while (a) {
40
+
els.unshift(a)
41
+
a = a.parentNode
42
+
}
43
+
44
+
return els
45
+
}
46
+
47
+
function furthest(el, s) {
48
+
var nodes = []
49
+
50
+
while (el.parentNode) {
51
+
if (
52
+
el.parentNode &&
53
+
el.parentNode.matches &&
54
+
el.parentNode.matches(s)
55
+
) {
56
+
nodes.push(el.parentNode)
57
+
}
58
+
59
+
el = el.parentNode
60
+
}
61
+
62
+
return nodes[nodes.length - 1]
63
+
}
64
+
65
+
const getAllParentsUntil = (el, parent) => {
66
+
const parents = []
67
+
68
+
while (el.parentNode !== parent) {
69
+
parents.push(el.parentNode)
70
+
el = el.parentNode
71
+
}
72
+
73
+
return parents
74
+
}
75
+
76
+
const reversePlacementIfNeeded = (placement) => {
77
+
if (!isRtl()) {
78
+
return placement
79
+
}
80
+
81
+
if (placement === 'left') {
82
+
return 'right'
83
+
}
84
+
85
+
if (placement === 'right') {
86
+
return 'left'
87
+
}
88
+
89
+
return placement
90
+
}
91
+
92
+
const getPreferedPlacementFor = (el) => {
93
+
let farmost = furthest(el, 'li.menu-item')
94
+
95
+
if (!farmost) {
96
+
return reversePlacementIfNeeded('right')
97
+
}
98
+
99
+
const submenusWithParents = [...farmost.querySelectorAll('.sub-menu')].map(
100
+
(el) => {
101
+
return { el, parents: getAllParentsUntil(el, farmost) }
102
+
}
103
+
)
104
+
105
+
if (submenusWithParents.length === 0) {
106
+
return reversePlacementIfNeeded('right')
107
+
}
108
+
109
+
const submenusWithParentsSorted = submenusWithParents
110
+
.sort((a, b) => a.parents.length - b.parents.length)
111
+
.reverse()
112
+
113
+
const submenuWithMostParents = submenusWithParentsSorted[0]
114
+
115
+
const allSubmenus = [
116
+
...submenuWithMostParents.parents.filter((el) =>
117
+
el.matches('.sub-menu')
118
+
),
119
+
120
+
...[submenuWithMostParents.el]
121
+
]
122
+
123
+
const allSubmenusAlignedWidth = allSubmenus.reduce((acc, el, index) => {
124
+
const style = getComputedStyle(el)
125
+
126
+
return (
127
+
acc +
128
+
el.getBoundingClientRect().width +
129
+
(index === 0
130
+
? 0
131
+
: parseFloat(
132
+
style.getPropertyValue(
133
+
'--dropdown-horizontal-offset'
134
+
) || '5px'
135
+
))
136
+
)
137
+
}, 0)
138
+
139
+
const farmostRect = farmost.getBoundingClientRect()
140
+
141
+
let willItFitToTheLeft = allSubmenusAlignedWidth < farmostRect.right
142
+
143
+
let willItFitToTheRight =
144
+
innerWidth - farmostRect.left > allSubmenusAlignedWidth
145
+
146
+
if (farmost.matches('.animated-submenu-inline')) {
147
+
willItFitToTheRight =
148
+
innerWidth - farmostRect.left - farmostRect.width >
149
+
allSubmenusAlignedWidth
150
+
}
151
+
152
+
if (isRtl()) {
153
+
if (!willItFitToTheLeft && !willItFitToTheRight) {
154
+
return 'left'
155
+
}
156
+
157
+
return willItFitToTheLeft ? 'left' : 'right'
158
+
}
159
+
160
+
if (!willItFitToTheLeft && !willItFitToTheRight) {
161
+
return 'right'
162
+
}
163
+
164
+
return willItFitToTheRight ? 'right' : 'left'
165
+
}
166
+
167
+
const computeItemSubmenuFor = (
168
+
reference,
169
+
{
170
+
// left -- 1st level menu items
171
+
// end -- submenus
172
+
startPosition = 'end'
173
+
}
174
+
) => {
175
+
const menu = reference.querySelector('.sub-menu')
176
+
const placement = getPreferedPlacementFor(menu)
177
+
178
+
const { left, width, right } = menu.getBoundingClientRect()
179
+
180
+
let futurePlacement = placement
181
+
let referenceRect = reference.getBoundingClientRect()
182
+
183
+
if (placement === 'left') {
184
+
let referencePoint =
185
+
startPosition === 'end' ? referenceRect.left : referenceRect.right
186
+
187
+
if (referencePoint - width < 0) {
188
+
futurePlacement = 'right'
189
+
}
190
+
}
191
+
192
+
if (placement === 'right') {
193
+
let referencePoint =
194
+
startPosition === 'end' ? referenceRect.right : referenceRect.left
195
+
196
+
if (referencePoint + width > innerWidth) {
197
+
futurePlacement = 'left'
198
+
}
199
+
}
200
+
201
+
reference.dataset.submenu = futurePlacement
202
+
203
+
reference.addEventListener('click', () => {})
204
+
}
205
+
206
+
const openSubmenu = (e) => {
207
+
const li = e.target.closest('li')
208
+
209
+
if (li.__closeSubmenuTimer__) {
210
+
clearTimeout(li.__closeSubmenuTimer__)
211
+
li.__closeSubmenuTimer__ = null
212
+
}
213
+
214
+
li.classList.add('ct-active')
215
+
216
+
let childIndicator = [...li.children].find((el) =>
217
+
el.matches('.ct-toggle-dropdown-desktop-ghost')
218
+
)
219
+
220
+
if (!childIndicator) {
221
+
childIndicator = li.firstElementChild
222
+
}
223
+
224
+
if (childIndicator) {
225
+
childIndicator.setAttribute('aria-expanded', 'true')
226
+
if (childIndicator.tagName.toLowerCase() === 'button') {
227
+
childIndicator.setAttribute(
228
+
'aria-label',
229
+
ct_localizations.collapse_submenu
230
+
)
231
+
}
232
+
}
233
+
234
+
if (!isEligibleForSubmenu(li)) {
235
+
return
236
+
}
237
+
238
+
const menu = li.querySelector('.sub-menu')
239
+
240
+
mountMenuLevel(menu)
241
+
242
+
if (menu.closest('[data-interaction="hover"]')) {
243
+
menu.parentNode.addEventListener(
244
+
'mouseleave',
245
+
() => {
246
+
;[...menu.children]
247
+
.filter((el) => isEligibleForSubmenu(el))
248
+
.map((el) => el.removeAttribute('data-submenu'))
249
+
},
250
+
{ once: true }
251
+
)
252
+
}
253
+
}
254
+
255
+
const closeSubmenu = (e) => {
256
+
if (!e.target) {
257
+
return
258
+
}
259
+
260
+
const li = e.target.closest('li')
261
+
li.classList.remove('ct-active')
262
+
263
+
let childIndicator = [...li.children].find((el) =>
264
+
el.matches('.ct-toggle-dropdown-desktop-ghost')
265
+
)
266
+
267
+
if (!childIndicator) {
268
+
childIndicator = li.firstElementChild
269
+
}
270
+
271
+
if (childIndicator) {
272
+
childIndicator.setAttribute('aria-expanded', 'false')
273
+
274
+
if (childIndicator.tagName.toLowerCase() === 'button') {
275
+
childIndicator.setAttribute(
276
+
'aria-label',
277
+
ct_localizations.expand_submenu
278
+
)
279
+
}
280
+
281
+
if (e.focusOnIndicator) {
282
+
childIndicator.focus({
283
+
focusVisible: true
284
+
})
285
+
}
286
+
}
287
+
288
+
li.__closeSubmenuTimer__ = setTimeout(() => {
289
+
li.__closeSubmenuTimer__ = null
290
+
;[...li.querySelectorAll('[data-submenu]')].map((el) => {
291
+
el.removeAttribute('data-submenu')
292
+
})
293
+
;[...li.querySelectorAll('.ct-active')].map((el) => {
294
+
el.classList.remove('ct-active')
295
+
})
296
+
}, 30)
297
+
}
298
+
299
+
const mountMenuForElement = (el, args = {}) => {
300
+
if (isMegaMenuCustomWidthCentered(el)) {
301
+
const menu = el.querySelector('.sub-menu')
302
+
303
+
const elRect = el.getBoundingClientRect()
304
+
const menuRect = menu.getBoundingClientRect()
305
+
306
+
let centerFits =
307
+
elRect.left + elRect.width / 2 > menuRect.width / 2 &&
308
+
innerWidth - (elRect.left + elRect.width / 2) > menuRect.width / 2
309
+
310
+
if (!centerFits) {
311
+
const placement = getPreferedPlacementFor(menu)
312
+
313
+
let offset = 0
314
+
315
+
let edgeOffset = 15
316
+
317
+
if (placement === 'right') {
318
+
offset = `${
319
+
Math.round(
320
+
elRect.left - (innerWidth - menuRect.width) + edgeOffset
321
+
) * -1
322
+
}px`
323
+
324
+
if (!(elRect.left + elRect.width / 2 > menuRect.width / 2)) {
325
+
offset = `${Math.round(elRect.left - edgeOffset) * -1}px`
326
+
}
327
+
}
328
+
329
+
if (placement === 'left') {
330
+
offset = `${
331
+
Math.round(innerWidth - elRect.right - edgeOffset) * -1
332
+
}px`
333
+
}
334
+
335
+
el.dataset.submenu = placement
336
+
337
+
menu.style.setProperty('--theme-submenu-inline-offset', offset)
338
+
}
339
+
}
340
+
341
+
if (isEligibleForSubmenu(el)) {
342
+
computeItemSubmenuFor(el, args)
343
+
}
344
+
345
+
if (el.hasSubmenuEventListeners) {
346
+
return
347
+
}
348
+
349
+
el.hasSubmenuEventListeners = true
350
+
351
+
let hasClickInteraction = el.matches('[data-interaction*="click"] *')
352
+
353
+
el.addEventListener('keydown', function (e) {
354
+
if (e.keyCode == 27) {
355
+
closeSubmenu({
356
+
target: el.firstElementChild,
357
+
focusOnIndicator: true
358
+
})
359
+
}
360
+
})
361
+
362
+
el.addEventListener('focusout', (evt) => {
363
+
if (el.contains(evt.relatedTarget)) {
364
+
return
365
+
}
366
+
367
+
closeSubmenu({
368
+
target: el.firstElementChild
369
+
})
370
+
})
371
+
372
+
if (!hasClickInteraction) {
373
+
const handleMouseEnter = () => {
374
+
// So that mouseenter event is catched before the open itself
375
+
if (isIosDevice()) {
376
+
openSubmenu({ target: el.firstElementChild })
377
+
} else {
378
+
requestAnimationFrame(() => {
379
+
openSubmenu({ target: el.firstElementChild })
380
+
})
381
+
}
382
+
383
+
// If first level
384
+
if (!el.parentNode.classList.contains('.sub-menu')) {
385
+
;[...el.parentNode.children]
386
+
.filter((firstLevelEl) => firstLevelEl !== el)
387
+
.map((firstLevelEl) => {
388
+
closeSubmenu({
389
+
target: firstLevelEl.firstElementChild
390
+
})
391
+
})
392
+
}
393
+
394
+
el.addEventListener(
395
+
'mouseleave',
396
+
() => {
397
+
closeSubmenu({ target: el.firstElementChild })
398
+
},
399
+
{ once: true }
400
+
)
401
+
}
402
+
403
+
el.addEventListener('mouseenter', handleMouseEnter)
404
+
405
+
if (el.matches(':hover')) {
406
+
handleMouseEnter()
407
+
}
408
+
409
+
// On Android devices, allow only 2nd click to open the link.
410
+
// First click will ensure the submenu is opened
411
+
//
412
+
// iOS has this behaviour out of the box.
413
+
//
414
+
// Important: only perform this for touch devices so that keyboard
415
+
// users are not affected.
416
+
if (isTouchDevice()) {
417
+
el.addEventListener('click', (e) => {
418
+
if (!el.classList.contains('ct-active')) {
419
+
e.preventDefault()
420
+
}
421
+
})
422
+
}
423
+
}
424
+
425
+
if (hasClickInteraction) {
426
+
let itemTarget = el.matches('[data-interaction*="item"] *')
427
+
? el.firstElementChild
428
+
: el.firstElementChild.querySelector('.ct-toggle-dropdown-desktop')
429
+
430
+
itemTarget.addEventListener('click', (e) => {
431
+
e.preventDefault()
432
+
433
+
if (e.target.closest('li').classList.contains('ct-active')) {
434
+
closeSubmenu(e)
435
+
} else {
436
+
openSubmenu(e)
437
+
438
+
if (isIosDevice()) {
439
+
e.target.closest('li').addEventListener(
440
+
'mouseleave',
441
+
() => {
442
+
closeSubmenu({
443
+
target: el.firstElementChild
444
+
})
445
+
},
446
+
{ once: true }
447
+
)
448
+
}
449
+
450
+
if (!e.target.hasDocumentListener) {
451
+
e.target.hasDocumentListener = true
452
+
// Add the event a bit later
453
+
setTimeout(() => {
454
+
document.addEventListener('click', (evt) => {
455
+
if (!e.target.closest('li').contains(evt.target)) {
456
+
closeSubmenu(e)
457
+
}
458
+
})
459
+
})
460
+
}
461
+
}
462
+
})
463
+
}
464
+
465
+
let childIndicator = [...el.children].find((el) =>
466
+
el.matches('.ct-toggle-dropdown-desktop-ghost')
467
+
)
468
+
469
+
if (childIndicator) {
470
+
childIndicator.addEventListener('click', (e) => {
471
+
if (e.target.closest('li').classList.contains('ct-active')) {
472
+
closeSubmenu(e)
473
+
} else {
474
+
openSubmenu(e)
475
+
}
476
+
})
477
+
}
478
+
}
479
+
480
+
export const mountMenuLevel = (menuLevel, args = {}) => {
481
+
;[...menuLevel.children]
482
+
.filter((el) =>
483
+
el.matches('.menu-item-has-children, .page_item_has_children')
484
+
)
485
+
.map((el) => {
486
+
mountMenuForElement(el, args)
487
+
})
488
+
}
489
+