@edgedev/create-edge-app 1.2.34 → 1.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/agents.md +95 -2
- package/deploy.sh +60 -1
- package/edge/components/cms/block.vue +763 -301
- package/edge/components/cms/blockEditor.vue +342 -29
- package/edge/components/cms/blockPicker.vue +3 -3
- package/edge/components/cms/blocksManager.vue +48 -13
- package/edge/components/cms/htmlContent.vue +601 -10
- package/edge/components/cms/init_blocks/contact_us.html +55 -47
- package/edge/components/cms/init_blocks/newsletter.html +56 -96
- package/edge/components/cms/menu.vue +92 -31
- package/edge/components/cms/page.vue +165 -50
- package/edge/components/cms/posts.vue +13 -4
- package/edge/components/cms/site.vue +15 -4
- package/edge/components/cms/siteSettingsForm.vue +19 -9
- package/edge/components/cms/themeDefaultMenu.vue +20 -2
- package/edge/composables/siteSettingsTemplate.js +2 -0
- package/edge-pull.sh +16 -2
- package/edge-push.sh +9 -1
- package/edge-remote.sh +20 -0
- package/edge-status.sh +9 -5
- package/edge-update-all.sh +127 -0
- package/package.json +1 -1
|
@@ -397,12 +397,124 @@ function initCmsNavHelpers(scope) {
|
|
|
397
397
|
if (!scope || !import.meta.client)
|
|
398
398
|
return
|
|
399
399
|
|
|
400
|
+
const existingCleanupFns = Array.isArray(scope.__cmsNavCleanupFns)
|
|
401
|
+
? scope.__cmsNavCleanupFns
|
|
402
|
+
: []
|
|
403
|
+
existingCleanupFns.forEach((cleanup) => {
|
|
404
|
+
try {
|
|
405
|
+
cleanup()
|
|
406
|
+
}
|
|
407
|
+
catch {}
|
|
408
|
+
})
|
|
409
|
+
scope.__cmsNavCleanupFns = []
|
|
410
|
+
|
|
400
411
|
const roots = scope.querySelectorAll('.cms-nav-root, [data-cms-nav-root]')
|
|
401
412
|
roots.forEach((root) => {
|
|
402
|
-
|
|
403
|
-
|
|
413
|
+
const parseBoolean = (rawValue, fallback = false) => {
|
|
414
|
+
if (rawValue == null || rawValue === '')
|
|
415
|
+
return fallback
|
|
416
|
+
const normalized = String(rawValue).trim().toLowerCase()
|
|
417
|
+
if (['false', '0', 'off', 'no'].includes(normalized))
|
|
418
|
+
return false
|
|
419
|
+
if (['true', '1', 'on', 'yes'].includes(normalized))
|
|
420
|
+
return true
|
|
421
|
+
return fallback
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const toClassTokens = (rawValue) => {
|
|
425
|
+
return String(rawValue || '')
|
|
426
|
+
.split(/\s+/)
|
|
427
|
+
.map(token => token.trim())
|
|
428
|
+
.filter(Boolean)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const normalizeRuntimeToken = (token) => {
|
|
432
|
+
if (!token)
|
|
433
|
+
return token
|
|
434
|
+
const parts = token.split(':')
|
|
435
|
+
const core = parts.pop()
|
|
436
|
+
const nakedCore = core.startsWith('!') ? core.slice(1) : core
|
|
437
|
+
const hasBreakpoint = parts.some((part) => {
|
|
438
|
+
const normalized = part.replace(/^!/, '')
|
|
439
|
+
return Object.prototype.hasOwnProperty.call(BREAKPOINT_MIN_WIDTHS, normalized)
|
|
440
|
+
})
|
|
441
|
+
const isTextSize = /^text-(xs|sm|base|lg|xl|\d+xl)$/.test(nakedCore)
|
|
442
|
+
const isFontUtility = /^font-([\w-]+|\[[^\]]+\])$/.test(nakedCore)
|
|
443
|
+
const nextCore = (hasBreakpoint || isTextSize || isFontUtility) && !core.startsWith('!')
|
|
444
|
+
? `!${core}`
|
|
445
|
+
: core
|
|
446
|
+
return [...parts, nextCore].join(':')
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const tokenVariants = (token) => {
|
|
450
|
+
if (!token)
|
|
451
|
+
return []
|
|
452
|
+
const variants = new Set([token])
|
|
453
|
+
const parts = token.split(':')
|
|
454
|
+
const core = parts.pop()
|
|
455
|
+
const nakedCore = core.startsWith('!') ? core.slice(1) : core
|
|
456
|
+
variants.add([...parts, nakedCore].join(':'))
|
|
457
|
+
variants.add([...parts, `!${nakedCore}`].join(':'))
|
|
458
|
+
return Array.from(variants).filter(Boolean)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const mapRuntimeTokens = (rawValue) => {
|
|
462
|
+
const mapped = toVarBackedUtilities(rawValue, props.theme)
|
|
463
|
+
return toClassTokens(mapped).map(normalizeRuntimeToken)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const replaceClassTokens = (el, currentTokens, nextTokens) => {
|
|
467
|
+
if (!el)
|
|
468
|
+
return nextTokens
|
|
469
|
+
currentTokens
|
|
470
|
+
.flatMap(tokenVariants)
|
|
471
|
+
.forEach(token => el.classList.remove(token))
|
|
472
|
+
const normalizedNextTokens = (nextTokens || []).map(normalizeRuntimeToken)
|
|
473
|
+
normalizedNextTokens.forEach(token => el.classList.add(token))
|
|
474
|
+
return normalizedNextTokens
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const getAncestorElements = (el) => {
|
|
478
|
+
const ancestors = []
|
|
479
|
+
let parent = el?.parentElement || null
|
|
480
|
+
while (parent && parent !== document.body && parent !== document.documentElement) {
|
|
481
|
+
ancestors.push(parent)
|
|
482
|
+
parent = parent.parentElement
|
|
483
|
+
}
|
|
484
|
+
return ancestors
|
|
485
|
+
}
|
|
404
486
|
|
|
405
|
-
|
|
487
|
+
const isScrollableElement = (el) => {
|
|
488
|
+
if (!el || typeof window?.getComputedStyle !== 'function')
|
|
489
|
+
return false
|
|
490
|
+
const styles = window.getComputedStyle(el)
|
|
491
|
+
const overflowY = styles?.overflowY || ''
|
|
492
|
+
const overflow = styles?.overflow || ''
|
|
493
|
+
return /(auto|scroll|overlay)/.test(overflowY) || /(auto|scroll|overlay)/.test(overflow)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const collectScrollTargets = (el) => {
|
|
497
|
+
const targets = [window, document, document.documentElement, document.body]
|
|
498
|
+
const ancestors = getAncestorElements(el)
|
|
499
|
+
ancestors.forEach((ancestor) => {
|
|
500
|
+
if (isScrollableElement(ancestor))
|
|
501
|
+
targets.push(ancestor)
|
|
502
|
+
})
|
|
503
|
+
return Array.from(new Set(targets.filter(Boolean)))
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const resolvePosition = () => {
|
|
507
|
+
const attrValue = String(root.getAttribute('data-cms-nav-position') || '').trim().toLowerCase()
|
|
508
|
+
if (attrValue === 'left' || attrValue === 'center' || attrValue === 'right')
|
|
509
|
+
return attrValue
|
|
510
|
+
if (root.classList.contains('cms-nav-pos-left'))
|
|
511
|
+
return 'left'
|
|
512
|
+
if (root.classList.contains('cms-nav-pos-center'))
|
|
513
|
+
return 'center'
|
|
514
|
+
return 'right'
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const position = resolvePosition()
|
|
406
518
|
const openClass = root.getAttribute('data-cms-nav-open-class') || 'is-open'
|
|
407
519
|
const panel = root.querySelector('.cms-nav-panel, [data-cms-nav-panel]')
|
|
408
520
|
const overlay = root.querySelector('.cms-nav-overlay, [data-cms-nav-overlay]')
|
|
@@ -410,6 +522,180 @@ function initCmsNavHelpers(scope) {
|
|
|
410
522
|
const closeButtons = Array.from(root.querySelectorAll('.cms-nav-close, [data-cms-nav-close]'))
|
|
411
523
|
const links = Array.from(root.querySelectorAll('.cms-nav-link, [data-cms-nav-link]'))
|
|
412
524
|
const closeOnLink = root.getAttribute('data-cms-nav-close-on-link') !== 'false'
|
|
525
|
+
const navMain = root.querySelector('.cms-nav-main, [data-cms-nav-main], nav')
|
|
526
|
+
const navRow = root.querySelector('.cms-nav-layout, [data-cms-nav-layout], nav > div > div')
|
|
527
|
+
const desktopWrap = root.querySelector('.cms-nav-desktop, [data-cms-nav-desktop]')
|
|
528
|
+
|| navRow?.children?.[1]
|
|
529
|
+
|| null
|
|
530
|
+
const logoLink = root.querySelector('.cms-nav-logo, [data-cms-nav-logo]')
|
|
531
|
+
|| navRow?.querySelector('a')
|
|
532
|
+
|| null
|
|
533
|
+
const panelHiddenClass = position === 'left' ? '-translate-x-full' : 'translate-x-full'
|
|
534
|
+
const previewSurface = root.closest('[data-cms-preview-surface]')
|
|
535
|
+
const shouldContainFixedInPreview = Boolean(previewSurface)
|
|
536
|
+
const stickyEnabled = root.classList.contains('cms-nav-sticky') || parseBoolean(root.getAttribute('data-cms-nav-sticky'))
|
|
537
|
+
const getPreviewMode = () => String(previewSurface?.getAttribute('data-cms-preview-mode') || 'preview').toLowerCase()
|
|
538
|
+
const shouldPinInsidePreview = () => shouldContainFixedInPreview && getPreviewMode() === 'preview' && stickyEnabled
|
|
539
|
+
const hideOnDown = root.classList.contains('cms-nav-hide-on-down') || parseBoolean(root.getAttribute('data-cms-nav-hide-on-down'))
|
|
540
|
+
const scrollThreshold = Number(root.getAttribute('data-cms-nav-scroll-threshold') || 10)
|
|
541
|
+
const hideThreshold = Number(root.getAttribute('data-cms-nav-hide-threshold') || 80)
|
|
542
|
+
const hideDelta = Number(root.getAttribute('data-cms-nav-hide-delta') || 6)
|
|
543
|
+
const topNavClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-top-class') || '')
|
|
544
|
+
const scrolledNavClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-scrolled-class') || '')
|
|
545
|
+
const topRowClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-top-row-class') || '')
|
|
546
|
+
const scrolledRowClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-scrolled-row-class') || '')
|
|
547
|
+
const hiddenClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-hidden-class') || '-translate-y-full opacity-0')
|
|
548
|
+
const visibleClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-visible-class') || 'translate-y-0 opacity-100')
|
|
549
|
+
const transitionClassTokens = mapRuntimeTokens(root.getAttribute('data-cms-nav-transition-class') || 'transition-all duration-300')
|
|
550
|
+
const resolvePreviewPinAnchor = () => {
|
|
551
|
+
if (!previewSurface)
|
|
552
|
+
return null
|
|
553
|
+
const candidates = getAncestorElements(previewSurface).filter(isScrollableElement)
|
|
554
|
+
const scrollingCandidate = candidates.find(el => (el.scrollHeight - el.clientHeight) > 1)
|
|
555
|
+
return scrollingCandidate || candidates[0] || previewSurface
|
|
556
|
+
}
|
|
557
|
+
const resolvePreviewScrollTarget = () => {
|
|
558
|
+
if (!previewSurface)
|
|
559
|
+
return null
|
|
560
|
+
if (isScrollableElement(previewSurface) && (previewSurface.scrollHeight - previewSurface.clientHeight) > 1)
|
|
561
|
+
return previewSurface
|
|
562
|
+
const candidates = getAncestorElements(previewSurface).filter(isScrollableElement)
|
|
563
|
+
const scrollingCandidate = candidates.find(el => (el.scrollHeight - el.clientHeight) > 1)
|
|
564
|
+
return scrollingCandidate || candidates[0] || previewSurface
|
|
565
|
+
}
|
|
566
|
+
const previewPinAnchor = resolvePreviewPinAnchor()
|
|
567
|
+
const previewScrollTarget = resolvePreviewScrollTarget()
|
|
568
|
+
const scrollTargets = shouldContainFixedInPreview
|
|
569
|
+
? Array.from(new Set([previewScrollTarget, previewPinAnchor, previewSurface].filter(Boolean)))
|
|
570
|
+
: collectScrollTargets(root)
|
|
571
|
+
const windowScrollY = () => window.scrollY || document.documentElement.scrollTop || 0
|
|
572
|
+
const readScrollY = () => {
|
|
573
|
+
if (shouldContainFixedInPreview) {
|
|
574
|
+
const scopedTarget = previewScrollTarget || previewSurface
|
|
575
|
+
if (scopedTarget && typeof scopedTarget.scrollTop === 'number')
|
|
576
|
+
return Number(scopedTarget.scrollTop || 0)
|
|
577
|
+
}
|
|
578
|
+
let maxScrollY = windowScrollY()
|
|
579
|
+
scrollTargets.forEach((target) => {
|
|
580
|
+
if (!target || target === window || target === document)
|
|
581
|
+
return
|
|
582
|
+
const current = Number(target.scrollTop || 0)
|
|
583
|
+
if (current > maxScrollY)
|
|
584
|
+
maxScrollY = current
|
|
585
|
+
})
|
|
586
|
+
return maxScrollY
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const readPinAnchorTop = () => {
|
|
590
|
+
if (!previewPinAnchor || typeof previewPinAnchor.getBoundingClientRect !== 'function')
|
|
591
|
+
return 0
|
|
592
|
+
return Math.round(previewPinAnchor.getBoundingClientRect().top)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
let appliedNavTokens = []
|
|
596
|
+
let appliedRowTokens = []
|
|
597
|
+
let appliedVisibilityTokens = []
|
|
598
|
+
let lastScrollY = readScrollY()
|
|
599
|
+
let pinnedViewportTop = null
|
|
600
|
+
let pinnedOffsetFromSurface = null
|
|
601
|
+
const resolvePinnedTop = () => {
|
|
602
|
+
const anchorTop = readPinAnchorTop()
|
|
603
|
+
const rootTop = Math.round(root.getBoundingClientRect().top)
|
|
604
|
+
const measuredOffset = Math.max(0, rootTop - anchorTop)
|
|
605
|
+
if (pinnedOffsetFromSurface == null) {
|
|
606
|
+
pinnedOffsetFromSurface = measuredOffset
|
|
607
|
+
}
|
|
608
|
+
pinnedViewportTop = Math.max(anchorTop + pinnedOffsetFromSurface, anchorTop, 0)
|
|
609
|
+
return pinnedViewportTop
|
|
610
|
+
}
|
|
611
|
+
if (shouldPinInsidePreview())
|
|
612
|
+
pinnedViewportTop = resolvePinnedTop()
|
|
613
|
+
|
|
614
|
+
if (navRow) {
|
|
615
|
+
navRow.classList.remove('flex-row', 'flex-row-reverse', 'justify-between', 'justify-center', 'flex-wrap', 'flex-wrap-reverse')
|
|
616
|
+
navRow.classList.add('flex', 'flex-nowrap', 'items-center', 'gap-6')
|
|
617
|
+
if (position === 'left') {
|
|
618
|
+
navRow.classList.add('flex-row-reverse', 'justify-between')
|
|
619
|
+
}
|
|
620
|
+
else if (position === 'center') {
|
|
621
|
+
navRow.classList.add('flex-row', 'justify-center')
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
navRow.classList.add('flex-row', 'justify-between')
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (logoLink) {
|
|
629
|
+
logoLink.classList.remove('mr-6')
|
|
630
|
+
if (position === 'center')
|
|
631
|
+
logoLink.classList.add('mr-6')
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (desktopWrap) {
|
|
635
|
+
desktopWrap.classList.remove('ml-auto', 'md:flex-1', 'md:pl-6', 'items-center', 'gap-6')
|
|
636
|
+
if (position === 'left') {
|
|
637
|
+
desktopWrap.classList.add('md:flex-1', 'md:pl-6')
|
|
638
|
+
}
|
|
639
|
+
else if (position === 'center') {
|
|
640
|
+
desktopWrap.classList.add('items-center', 'gap-6')
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
desktopWrap.classList.add('ml-auto')
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const applyNavPinMode = () => {
|
|
648
|
+
if (!navMain)
|
|
649
|
+
return
|
|
650
|
+
if (shouldPinInsidePreview()) {
|
|
651
|
+
// Pin to viewport with explicit left/width so it stays within preview surface.
|
|
652
|
+
root.classList.remove('cms-nav-preview-relative')
|
|
653
|
+
navMain.classList.remove('sticky', 'absolute', 'inset-x-0')
|
|
654
|
+
navMain.classList.add('fixed', 'top-0')
|
|
655
|
+
}
|
|
656
|
+
else if (shouldContainFixedInPreview) {
|
|
657
|
+
// In preview but non-sticky: overlay within preview surface and scroll away naturally.
|
|
658
|
+
root.classList.add('cms-nav-preview-relative')
|
|
659
|
+
navMain.classList.remove('fixed', 'sticky')
|
|
660
|
+
navMain.classList.add('absolute', 'inset-x-0', 'top-0')
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
root.classList.remove('cms-nav-preview-relative')
|
|
664
|
+
navMain.classList.remove('absolute')
|
|
665
|
+
if (stickyEnabled) {
|
|
666
|
+
navMain.classList.remove('sticky')
|
|
667
|
+
navMain.classList.add('fixed', 'inset-x-0', 'top-0')
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
applyNavPinMode()
|
|
672
|
+
|
|
673
|
+
const clearPinnedPreviewPosition = () => {
|
|
674
|
+
if (!navMain)
|
|
675
|
+
return
|
|
676
|
+
navMain.style.left = ''
|
|
677
|
+
navMain.style.width = ''
|
|
678
|
+
navMain.style.right = ''
|
|
679
|
+
navMain.style.top = ''
|
|
680
|
+
navMain.style.bottom = ''
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const updatePinnedPreviewPosition = () => {
|
|
684
|
+
if (!shouldPinInsidePreview() || !navMain || !previewSurface)
|
|
685
|
+
return
|
|
686
|
+
const surfaceRect = previewSurface.getBoundingClientRect()
|
|
687
|
+
if (pinnedViewportTop == null)
|
|
688
|
+
pinnedViewportTop = resolvePinnedTop()
|
|
689
|
+
navMain.style.left = `${Math.round(surfaceRect.left)}px`
|
|
690
|
+
navMain.style.width = `${Math.round(surfaceRect.width)}px`
|
|
691
|
+
navMain.style.right = 'auto'
|
|
692
|
+
navMain.style.bottom = 'auto'
|
|
693
|
+
navMain.style.top = `${pinnedViewportTop}px`
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (navMain && transitionClassTokens.length) {
|
|
697
|
+
transitionClassTokens.forEach(token => navMain.classList.add(token))
|
|
698
|
+
}
|
|
413
699
|
|
|
414
700
|
const markInteractive = (el) => {
|
|
415
701
|
if (!el)
|
|
@@ -423,8 +709,189 @@ function initCmsNavHelpers(scope) {
|
|
|
423
709
|
markInteractive(panel)
|
|
424
710
|
markInteractive(overlay)
|
|
425
711
|
|
|
712
|
+
const folderEntries = []
|
|
713
|
+
const helperFolders = Array.from(root.querySelectorAll('.cms-nav-folder, [data-cms-nav-folder]'))
|
|
714
|
+
helperFolders.forEach((folder) => {
|
|
715
|
+
folderEntries.push({
|
|
716
|
+
folder,
|
|
717
|
+
toggle: folder.querySelector('.cms-nav-folder-toggle, [data-cms-nav-folder-toggle]'),
|
|
718
|
+
menu: folder.querySelector('.cms-nav-folder-menu, [data-cms-nav-folder-menu]'),
|
|
719
|
+
})
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
const fallbackFolders = Array.from(root.querySelectorAll('.cms-nav-desktop li.group, [data-cms-nav-desktop] li.group'))
|
|
723
|
+
fallbackFolders.forEach((folder) => {
|
|
724
|
+
if (folderEntries.some(entry => entry.folder === folder))
|
|
725
|
+
return
|
|
726
|
+
const directChildren = Array.from(folder.children || [])
|
|
727
|
+
const toggle = directChildren.find(child => child?.matches?.('a, button, [role="button"]'))
|
|
728
|
+
|| folder.querySelector('a, button, [role="button"]')
|
|
729
|
+
const menu = directChildren.find((child) => {
|
|
730
|
+
if (!child?.matches?.('div.hidden, ul.hidden, [hidden], div.absolute, ul.absolute'))
|
|
731
|
+
return false
|
|
732
|
+
const hasItemLinks = child.querySelectorAll('a, button, [role="button"]').length > 0
|
|
733
|
+
return hasItemLinks
|
|
734
|
+
})
|
|
735
|
+
|| folder.querySelector('div.hidden, ul.hidden, [hidden]')
|
|
736
|
+
if (!toggle || !menu)
|
|
737
|
+
return
|
|
738
|
+
folderEntries.push({ folder, toggle, menu })
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
const folderCleanupFns = []
|
|
742
|
+
const setFolderOpenState = (folder, menu, open) => {
|
|
743
|
+
folder.classList.toggle('cms-nav-folder-open', open)
|
|
744
|
+
folder.setAttribute('data-cms-nav-folder-open', open ? 'true' : 'false')
|
|
745
|
+
menu.classList.toggle('hidden', !open)
|
|
746
|
+
menu.classList.toggle('block', open)
|
|
747
|
+
menu.classList.toggle('pointer-events-none', !open)
|
|
748
|
+
menu.classList.toggle('pointer-events-auto', open)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
folderEntries.forEach(({ folder, toggle, menu }) => {
|
|
752
|
+
if (!toggle || !menu)
|
|
753
|
+
return
|
|
754
|
+
|
|
755
|
+
Array.from(menu.classList).forEach((token) => {
|
|
756
|
+
if (token.startsWith('group-hover:') || token.startsWith('group-focus-within:'))
|
|
757
|
+
menu.classList.remove(token)
|
|
758
|
+
})
|
|
759
|
+
folder.classList.add('cms-nav-folder')
|
|
760
|
+
toggle.classList.add('cms-nav-folder-toggle')
|
|
761
|
+
menu.classList.add('cms-nav-folder-menu')
|
|
762
|
+
markInteractive(toggle)
|
|
763
|
+
markInteractive(menu)
|
|
764
|
+
Array.from(menu.querySelectorAll('a, button, [role="button"]')).forEach(markInteractive)
|
|
765
|
+
|
|
766
|
+
let closeTimer = 0
|
|
767
|
+
const clearCloseTimer = () => {
|
|
768
|
+
if (closeTimer) {
|
|
769
|
+
clearTimeout(closeTimer)
|
|
770
|
+
closeTimer = 0
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const openFolder = () => {
|
|
775
|
+
clearCloseTimer()
|
|
776
|
+
setFolderOpenState(folder, menu, true)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const closeFolder = () => {
|
|
780
|
+
clearCloseTimer()
|
|
781
|
+
setFolderOpenState(folder, menu, false)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const scheduleCloseFolder = () => {
|
|
785
|
+
clearCloseTimer()
|
|
786
|
+
closeTimer = window.setTimeout(() => {
|
|
787
|
+
closeTimer = 0
|
|
788
|
+
setFolderOpenState(folder, menu, false)
|
|
789
|
+
}, 120)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const onPointerEnter = () => {
|
|
793
|
+
openFolder()
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const onPointerLeave = (event) => {
|
|
797
|
+
const nextTarget = event?.relatedTarget
|
|
798
|
+
if (nextTarget && folder.contains(nextTarget))
|
|
799
|
+
return
|
|
800
|
+
scheduleCloseFolder()
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const onFocusIn = () => {
|
|
804
|
+
openFolder()
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const onFocusOut = (event) => {
|
|
808
|
+
const nextTarget = event?.relatedTarget
|
|
809
|
+
if (nextTarget && folder.contains(nextTarget))
|
|
810
|
+
return
|
|
811
|
+
closeFolder()
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const onToggleClick = (event) => {
|
|
815
|
+
if (event.defaultPrevented)
|
|
816
|
+
return
|
|
817
|
+
if (window.matchMedia('(hover: hover) and (pointer: fine)').matches)
|
|
818
|
+
return
|
|
819
|
+
const isOpenNow = folder.getAttribute('data-cms-nav-folder-open') === 'true'
|
|
820
|
+
if (!isOpenNow) {
|
|
821
|
+
event.preventDefault()
|
|
822
|
+
event.stopPropagation()
|
|
823
|
+
openFolder()
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
closeFolder()
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const onDocumentClickCapture = (event) => {
|
|
830
|
+
const clickTarget = event?.target
|
|
831
|
+
if (clickTarget && folder.contains(clickTarget))
|
|
832
|
+
return
|
|
833
|
+
closeFolder()
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
folder.addEventListener('pointerenter', onPointerEnter)
|
|
837
|
+
folder.addEventListener('pointerleave', onPointerLeave)
|
|
838
|
+
folder.addEventListener('focusin', onFocusIn)
|
|
839
|
+
folder.addEventListener('focusout', onFocusOut)
|
|
840
|
+
toggle.addEventListener('click', onToggleClick)
|
|
841
|
+
document.addEventListener('click', onDocumentClickCapture, true)
|
|
842
|
+
|
|
843
|
+
setFolderOpenState(folder, menu, false)
|
|
844
|
+
|
|
845
|
+
folderCleanupFns.push(() => {
|
|
846
|
+
clearCloseTimer()
|
|
847
|
+
setFolderOpenState(folder, menu, false)
|
|
848
|
+
folder.removeEventListener('pointerenter', onPointerEnter)
|
|
849
|
+
folder.removeEventListener('pointerleave', onPointerLeave)
|
|
850
|
+
folder.removeEventListener('focusin', onFocusIn)
|
|
851
|
+
folder.removeEventListener('focusout', onFocusOut)
|
|
852
|
+
toggle.removeEventListener('click', onToggleClick)
|
|
853
|
+
document.removeEventListener('click', onDocumentClickCapture, true)
|
|
854
|
+
})
|
|
855
|
+
})
|
|
856
|
+
|
|
426
857
|
const isOpen = () => root.classList.contains(openClass)
|
|
427
858
|
|
|
859
|
+
const setScrolledState = (isScrolled) => {
|
|
860
|
+
root.classList.toggle('cms-nav-scrolled', isScrolled)
|
|
861
|
+
root.setAttribute('data-cms-nav-scrolled', isScrolled ? 'true' : 'false')
|
|
862
|
+
appliedNavTokens = replaceClassTokens(navMain, appliedNavTokens, isScrolled ? scrolledNavClassTokens : topNavClassTokens)
|
|
863
|
+
appliedRowTokens = replaceClassTokens(navRow, appliedRowTokens, isScrolled ? scrolledRowClassTokens : topRowClassTokens)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const setVisibilityState = (isHidden) => {
|
|
867
|
+
root.classList.toggle('cms-nav-hidden', isHidden)
|
|
868
|
+
root.setAttribute('data-cms-nav-hidden', isHidden ? 'true' : 'false')
|
|
869
|
+
appliedVisibilityTokens = replaceClassTokens(navMain, appliedVisibilityTokens, isHidden ? hiddenClassTokens : visibleClassTokens)
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const handleScroll = () => {
|
|
873
|
+
const currentScrollY = readScrollY()
|
|
874
|
+
const isScrolled = currentScrollY > scrollThreshold
|
|
875
|
+
setScrolledState(isScrolled)
|
|
876
|
+
updatePinnedPreviewPosition()
|
|
877
|
+
|
|
878
|
+
if (hideOnDown && navMain) {
|
|
879
|
+
const delta = currentScrollY - lastScrollY
|
|
880
|
+
const absDelta = Math.abs(delta)
|
|
881
|
+
if (currentScrollY <= hideThreshold) {
|
|
882
|
+
setVisibilityState(false)
|
|
883
|
+
}
|
|
884
|
+
else if (absDelta >= hideDelta) {
|
|
885
|
+
setVisibilityState(delta > 0)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
setVisibilityState(false)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
lastScrollY = currentScrollY
|
|
893
|
+
}
|
|
894
|
+
|
|
428
895
|
const setOpen = (open) => {
|
|
429
896
|
root.classList.toggle(openClass, open)
|
|
430
897
|
root.setAttribute('data-cms-nav-open', open ? 'true' : 'false')
|
|
@@ -434,10 +901,18 @@ function initCmsNavHelpers(scope) {
|
|
|
434
901
|
})
|
|
435
902
|
|
|
436
903
|
if (panel) {
|
|
904
|
+
panel.classList.remove('left-0', 'right-0', 'right-auto', 'left-auto', 'translate-x-full', '-translate-x-full')
|
|
905
|
+
if (position === 'left') {
|
|
906
|
+
panel.classList.add('left-0', 'right-auto')
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
panel.classList.add('right-0', 'left-auto')
|
|
910
|
+
}
|
|
911
|
+
|
|
437
912
|
panel.classList.toggle('translate-x-0', open)
|
|
438
913
|
panel.classList.toggle('opacity-100', open)
|
|
439
914
|
panel.classList.toggle('pointer-events-auto', open)
|
|
440
|
-
panel.classList.toggle(
|
|
915
|
+
panel.classList.toggle(panelHiddenClass, !open)
|
|
441
916
|
panel.classList.toggle('opacity-0', !open)
|
|
442
917
|
panel.classList.toggle('pointer-events-none', !open)
|
|
443
918
|
panel.setAttribute('aria-hidden', open ? 'false' : 'true')
|
|
@@ -453,6 +928,11 @@ function initCmsNavHelpers(scope) {
|
|
|
453
928
|
}
|
|
454
929
|
|
|
455
930
|
setOpen(root.classList.contains(openClass) || root.getAttribute('data-cms-nav-open') === 'true')
|
|
931
|
+
handleScroll()
|
|
932
|
+
if (shouldPinInsidePreview())
|
|
933
|
+
updatePinnedPreviewPosition()
|
|
934
|
+
else
|
|
935
|
+
clearPinnedPreviewPosition()
|
|
456
936
|
|
|
457
937
|
toggles.forEach((btn) => {
|
|
458
938
|
btn.addEventListener('click', (event) => {
|
|
@@ -485,6 +965,101 @@ function initCmsNavHelpers(scope) {
|
|
|
485
965
|
})
|
|
486
966
|
})
|
|
487
967
|
}
|
|
968
|
+
|
|
969
|
+
const scrollListeners = scrollTargets
|
|
970
|
+
scrollListeners.forEach((target) => {
|
|
971
|
+
target.addEventListener('scroll', handleScroll, { passive: true })
|
|
972
|
+
})
|
|
973
|
+
let previewModeObserver = null
|
|
974
|
+
let previewSurfaceObserver = null
|
|
975
|
+
let previewSurfaceAttrObserver = null
|
|
976
|
+
let pinSyncRaf = 0
|
|
977
|
+
const schedulePinnedPositionSync = () => {
|
|
978
|
+
if (pinSyncRaf)
|
|
979
|
+
cancelAnimationFrame(pinSyncRaf)
|
|
980
|
+
pinSyncRaf = requestAnimationFrame(() => {
|
|
981
|
+
pinSyncRaf = 0
|
|
982
|
+
updatePinnedPreviewPosition()
|
|
983
|
+
})
|
|
984
|
+
}
|
|
985
|
+
const handlePreviewModeMutation = () => {
|
|
986
|
+
pinnedViewportTop = null
|
|
987
|
+
pinnedOffsetFromSurface = null
|
|
988
|
+
applyNavPinMode()
|
|
989
|
+
if (shouldPinInsidePreview()) {
|
|
990
|
+
updatePinnedPreviewPosition()
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
clearPinnedPreviewPosition()
|
|
994
|
+
}
|
|
995
|
+
handleScroll()
|
|
996
|
+
}
|
|
997
|
+
if (previewSurface) {
|
|
998
|
+
previewModeObserver = new MutationObserver((entries) => {
|
|
999
|
+
if (entries.some(entry => entry.type === 'attributes' && entry.attributeName === 'data-cms-preview-mode'))
|
|
1000
|
+
handlePreviewModeMutation()
|
|
1001
|
+
})
|
|
1002
|
+
previewModeObserver.observe(previewSurface, {
|
|
1003
|
+
attributes: true,
|
|
1004
|
+
attributeFilter: ['data-cms-preview-mode'],
|
|
1005
|
+
})
|
|
1006
|
+
// Viewport-mode switches change preview width/position without a window resize.
|
|
1007
|
+
// Keep fixed nav left/width aligned to the preview surface as it transitions.
|
|
1008
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
1009
|
+
previewSurfaceObserver = new ResizeObserver(() => {
|
|
1010
|
+
schedulePinnedPositionSync()
|
|
1011
|
+
})
|
|
1012
|
+
previewSurfaceObserver.observe(previewSurface)
|
|
1013
|
+
if (previewPinAnchor && previewPinAnchor !== previewSurface)
|
|
1014
|
+
previewSurfaceObserver.observe(previewPinAnchor)
|
|
1015
|
+
}
|
|
1016
|
+
previewSurfaceAttrObserver = new MutationObserver((entries) => {
|
|
1017
|
+
if (entries.some(entry => entry.type === 'attributes' && (entry.attributeName === 'class' || entry.attributeName === 'style')))
|
|
1018
|
+
schedulePinnedPositionSync()
|
|
1019
|
+
})
|
|
1020
|
+
previewSurfaceAttrObserver.observe(previewSurface, {
|
|
1021
|
+
attributes: true,
|
|
1022
|
+
attributeFilter: ['class', 'style'],
|
|
1023
|
+
})
|
|
1024
|
+
previewSurface.addEventListener('transitionrun', schedulePinnedPositionSync)
|
|
1025
|
+
previewSurface.addEventListener('transitionstart', schedulePinnedPositionSync)
|
|
1026
|
+
previewSurface.addEventListener('transitionend', schedulePinnedPositionSync)
|
|
1027
|
+
previewSurface.addEventListener('transitioncancel', schedulePinnedPositionSync)
|
|
1028
|
+
}
|
|
1029
|
+
const handleResize = () => {
|
|
1030
|
+
if (shouldPinInsidePreview() && pinnedViewportTop == null)
|
|
1031
|
+
pinnedViewportTop = resolvePinnedTop()
|
|
1032
|
+
updatePinnedPreviewPosition()
|
|
1033
|
+
}
|
|
1034
|
+
window.addEventListener('resize', handleResize, { passive: true })
|
|
1035
|
+
|
|
1036
|
+
scope.__cmsNavCleanupFns.push(() => {
|
|
1037
|
+
folderCleanupFns.forEach((cleanup) => {
|
|
1038
|
+
try {
|
|
1039
|
+
cleanup()
|
|
1040
|
+
}
|
|
1041
|
+
catch {}
|
|
1042
|
+
})
|
|
1043
|
+
scrollListeners.forEach((target) => {
|
|
1044
|
+
target.removeEventListener('scroll', handleScroll)
|
|
1045
|
+
})
|
|
1046
|
+
window.removeEventListener('resize', handleResize)
|
|
1047
|
+
if (previewModeObserver)
|
|
1048
|
+
previewModeObserver.disconnect()
|
|
1049
|
+
if (previewSurfaceObserver)
|
|
1050
|
+
previewSurfaceObserver.disconnect()
|
|
1051
|
+
if (previewSurfaceAttrObserver)
|
|
1052
|
+
previewSurfaceAttrObserver.disconnect()
|
|
1053
|
+
if (previewSurface) {
|
|
1054
|
+
previewSurface.removeEventListener('transitionrun', schedulePinnedPositionSync)
|
|
1055
|
+
previewSurface.removeEventListener('transitionstart', schedulePinnedPositionSync)
|
|
1056
|
+
previewSurface.removeEventListener('transitionend', schedulePinnedPositionSync)
|
|
1057
|
+
previewSurface.removeEventListener('transitioncancel', schedulePinnedPositionSync)
|
|
1058
|
+
}
|
|
1059
|
+
if (pinSyncRaf)
|
|
1060
|
+
cancelAnimationFrame(pinSyncRaf)
|
|
1061
|
+
clearPinnedPreviewPosition()
|
|
1062
|
+
})
|
|
488
1063
|
})
|
|
489
1064
|
}
|
|
490
1065
|
|
|
@@ -881,10 +1456,6 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
|
|
|
881
1456
|
onMounted(async () => {
|
|
882
1457
|
await ensureUnoRuntime()
|
|
883
1458
|
|
|
884
|
-
// Initialize carousels/behaviors for SSR-inserted HTML
|
|
885
|
-
initEmblaCarousels(hostEl.value)
|
|
886
|
-
initCmsNavHelpers(hostEl.value)
|
|
887
|
-
|
|
888
1459
|
// Apply global theme once (keeps one style tag for vars; blocks can still override locally if needed)
|
|
889
1460
|
// setGlobalThemeVars(props.theme)
|
|
890
1461
|
setScopedThemeVars(hostEl.value, props.theme)
|
|
@@ -892,6 +1463,9 @@ onMounted(async () => {
|
|
|
892
1463
|
// setScopedThemeVars(hostEl.value, normalizeTheme(props.theme))
|
|
893
1464
|
applyThemeClasses(hostEl.value, props.theme, (props.theme && props.theme.variant) || 'light')
|
|
894
1465
|
rewriteAllClasses(hostEl.value, props.theme, props.isolated, props.viewportMode)
|
|
1466
|
+
// Initialize behaviors after class rewriting so helpers use final class state.
|
|
1467
|
+
initEmblaCarousels(hostEl.value)
|
|
1468
|
+
initCmsNavHelpers(hostEl.value)
|
|
895
1469
|
await nextTick()
|
|
896
1470
|
hasMounted = true
|
|
897
1471
|
notifyLoaded()
|
|
@@ -902,13 +1476,13 @@ watch(
|
|
|
902
1476
|
async (val) => {
|
|
903
1477
|
// Wait for DOM to reflect new v-html, then (re)wire behaviors and class mappings
|
|
904
1478
|
await nextTick()
|
|
905
|
-
initEmblaCarousels(hostEl.value)
|
|
906
|
-
initCmsNavHelpers(hostEl.value)
|
|
907
1479
|
// setGlobalThemeVars(props.theme)
|
|
908
1480
|
setScopedThemeVars(hostEl.value, props.theme)
|
|
909
1481
|
|
|
910
1482
|
applyThemeClasses(hostEl.value, props.theme, (props.theme && props.theme.variant) || 'light')
|
|
911
1483
|
rewriteAllClasses(hostEl.value, props.theme, props.isolated, props.viewportMode)
|
|
1484
|
+
initEmblaCarousels(hostEl.value)
|
|
1485
|
+
initCmsNavHelpers(hostEl.value)
|
|
912
1486
|
await nextTick()
|
|
913
1487
|
notifyLoaded()
|
|
914
1488
|
},
|
|
@@ -922,6 +1496,7 @@ watch(
|
|
|
922
1496
|
// 2) Apply classes based on `apply`, `slots`, and optional variants
|
|
923
1497
|
applyThemeClasses(hostEl.value, val, (val && val.variant) || 'light')
|
|
924
1498
|
rewriteAllClasses(hostEl.value, val, props.isolated, props.viewportMode)
|
|
1499
|
+
initCmsNavHelpers(hostEl.value)
|
|
925
1500
|
await nextTick()
|
|
926
1501
|
notifyLoaded()
|
|
927
1502
|
},
|
|
@@ -933,10 +1508,22 @@ watch(
|
|
|
933
1508
|
async () => {
|
|
934
1509
|
await nextTick()
|
|
935
1510
|
rewriteAllClasses(hostEl.value, props.theme, props.isolated, props.viewportMode)
|
|
1511
|
+
// Viewport button changes can alter preview surface width without a window resize.
|
|
1512
|
+
// Reinitialize nav helpers so fixed nav left/width is recalculated immediately.
|
|
1513
|
+
initCmsNavHelpers(hostEl.value)
|
|
936
1514
|
},
|
|
937
1515
|
)
|
|
938
1516
|
|
|
939
1517
|
onBeforeUnmount(() => {
|
|
1518
|
+
if (hostEl.value && Array.isArray(hostEl.value.__cmsNavCleanupFns)) {
|
|
1519
|
+
hostEl.value.__cmsNavCleanupFns.forEach((cleanup) => {
|
|
1520
|
+
try {
|
|
1521
|
+
cleanup()
|
|
1522
|
+
}
|
|
1523
|
+
catch {}
|
|
1524
|
+
})
|
|
1525
|
+
hostEl.value.__cmsNavCleanupFns = []
|
|
1526
|
+
}
|
|
940
1527
|
// UnoCSS runtime attaches globally; no per-component teardown required.
|
|
941
1528
|
})
|
|
942
1529
|
</script>
|
|
@@ -953,4 +1540,8 @@ onBeforeUnmount(() => {
|
|
|
953
1540
|
p {
|
|
954
1541
|
margin-bottom: 1em;
|
|
955
1542
|
}
|
|
1543
|
+
|
|
1544
|
+
.cms-nav-preview-relative {
|
|
1545
|
+
position: relative;
|
|
1546
|
+
}
|
|
956
1547
|
</style>
|