@edgedev/create-edge-app 1.2.32 → 1.2.34

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.
Files changed (33) hide show
  1. package/deploy.sh +77 -0
  2. package/edge/components/cms/block.vue +228 -18
  3. package/edge/components/cms/blockApi.vue +3 -3
  4. package/edge/components/cms/blockEditor.vue +374 -85
  5. package/edge/components/cms/blockPicker.vue +29 -3
  6. package/edge/components/cms/blockRender.vue +3 -3
  7. package/edge/components/cms/blocksManager.vue +755 -82
  8. package/edge/components/cms/codeEditor.vue +15 -6
  9. package/edge/components/cms/fontUpload.vue +318 -2
  10. package/edge/components/cms/htmlContent.vue +230 -89
  11. package/edge/components/cms/menu.vue +5 -4
  12. package/edge/components/cms/page.vue +750 -21
  13. package/edge/components/cms/site.vue +624 -84
  14. package/edge/components/cms/sitesManager.vue +5 -4
  15. package/edge/components/cms/themeEditor.vue +196 -162
  16. package/edge/components/editor.vue +5 -1
  17. package/edge/composables/global.ts +37 -5
  18. package/edge/composables/useCmsNewDocs.js +100 -0
  19. package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
  20. package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
  21. package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
  22. package/edge/routes/cms/dashboard/media/index.vue +5 -0
  23. package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
  24. package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
  25. package/edge/routes/cms/dashboard/sites/index.vue +4 -0
  26. package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
  27. package/edge/routes/cms/dashboard/templates/index.vue +4 -0
  28. package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
  29. package/edge/routes/cms/dashboard/themes/index.vue +330 -1
  30. package/firebase.json +4 -0
  31. package/nuxt.config.ts +1 -1
  32. package/package.json +2 -2
  33. package/pages/app.vue +12 -12
@@ -35,6 +35,44 @@ const emit = defineEmits(['loaded'])
35
35
 
36
36
  const scopeId = `hc-${Math.random().toString(36).slice(2)}`
37
37
 
38
+ const themeExtraCSS = computed(() => {
39
+ const value = props.theme?.extraCSS
40
+ return typeof value === 'string' ? value : ''
41
+ })
42
+
43
+ const toCssVarToken = (key) => {
44
+ return String(key || '')
45
+ .trim()
46
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
47
+ .replace(/[\s_]+/g, '-')
48
+ .replace(/[^a-zA-Z0-9-]/g, '-')
49
+ .replace(/-+/g, '-')
50
+ .replace(/^-|-$/g, '')
51
+ .toLowerCase()
52
+ }
53
+
54
+ const isSafeLegacyVarKey = key => /^[A-Za-z0-9_-]+$/.test(String(key || ''))
55
+
56
+ const pushVarDecl = (decls, prefix, key, value) => {
57
+ const token = toCssVarToken(key)
58
+ if (!token)
59
+ return
60
+
61
+ const normalizedName = `--${prefix}-${token}`
62
+ decls.push(`${normalizedName}: ${value};`)
63
+
64
+ const legacyKey = String(key || '')
65
+ if (!isSafeLegacyVarKey(legacyKey))
66
+ return
67
+
68
+ const legacyName = `--${prefix}-${legacyKey}`
69
+ if (legacyName !== normalizedName)
70
+ decls.push(`${legacyName}: ${value};`)
71
+ }
72
+
73
+ const cssVarRef = (prefix, key) => `var(--${prefix}-${toCssVarToken(key)})`
74
+ const escapeClassToken = value => String(value || '').replace(/([^a-zA-Z0-9_-])/g, '\\$1')
75
+
38
76
  // --- UnoCSS Runtime singleton (global, one init for the whole app) ---
39
77
  async function ensureUnoRuntime() {
40
78
  if (typeof window === 'undefined')
@@ -89,24 +127,24 @@ function buildGlobalThemeCSS(theme) {
89
127
  const t = normalizeTheme(theme || {})
90
128
  const { colors, fontFamily, fontSize, borderRadius, boxShadow } = t
91
129
  const decls = []
92
- Object.entries(colors).forEach(([k, v]) => decls.push(`--color-${k}: ${Array.isArray(v) ? v[0] : v};`))
130
+ Object.entries(colors).forEach(([k, v]) => pushVarDecl(decls, 'color', k, Array.isArray(v) ? v[0] : v))
93
131
  Object.entries(fontFamily).forEach(([k, v]) => {
94
132
  const val = Array.isArray(v) ? v.map(x => (x.includes(' ') ? `'${x}'` : x)).join(', ') : v
95
- decls.push(`--font-${k}: ${val};`)
133
+ pushVarDecl(decls, 'font', k, val)
96
134
  })
97
135
  Object.entries(fontSize).forEach(([k, v]) => {
98
136
  if (Array.isArray(v)) {
99
137
  const [size, opts] = v
100
- decls.push(`--font-size-${k}: ${size};`)
138
+ pushVarDecl(decls, 'font-size', k, size)
101
139
  if (opts && opts.lineHeight)
102
- decls.push(`--line-height-${k}: ${opts.lineHeight};`)
140
+ pushVarDecl(decls, 'line-height', k, opts.lineHeight)
103
141
  }
104
142
  else {
105
- decls.push(`--font-size-${k}: ${v};`)
143
+ pushVarDecl(decls, 'font-size', k, v)
106
144
  }
107
145
  })
108
- Object.entries(borderRadius).forEach(([k, v]) => decls.push(`--radius-${k}: ${v};`))
109
- Object.entries(boxShadow).forEach(([k, v]) => decls.push(`--shadow-${k}: ${v};`))
146
+ Object.entries(borderRadius).forEach(([k, v]) => pushVarDecl(decls, 'radius', k, v))
147
+ Object.entries(boxShadow).forEach(([k, v]) => pushVarDecl(decls, 'shadow', k, v))
110
148
  return `:root{${decls.join('')}}`
111
149
  }
112
150
 
@@ -114,25 +152,34 @@ function buildScopedThemeCSS(theme, scopeId) {
114
152
  const t = normalizeTheme(theme || {})
115
153
  const { colors, fontFamily, fontSize, borderRadius, boxShadow } = t
116
154
  const decls = []
117
- Object.entries(colors).forEach(([k, v]) => decls.push(`--color-${k}: ${Array.isArray(v) ? v[0] : v};`))
155
+ Object.entries(colors).forEach(([k, v]) => pushVarDecl(decls, 'color', k, Array.isArray(v) ? v[0] : v))
118
156
  Object.entries(fontFamily).forEach(([k, v]) => {
119
157
  const val = Array.isArray(v) ? v.map(x => (x.includes(' ') ? `'${x}'` : x)).join(', ') : v
120
- decls.push(`--font-${k}: ${val};`)
158
+ pushVarDecl(decls, 'font', k, val)
121
159
  })
122
160
  Object.entries(fontSize).forEach(([k, v]) => {
123
161
  if (Array.isArray(v)) {
124
162
  const [size, opts] = v
125
- decls.push(`--font-size-${k}: ${size};`)
163
+ pushVarDecl(decls, 'font-size', k, size)
126
164
  if (opts?.lineHeight)
127
- decls.push(`--line-height-${k}: ${opts.lineHeight};`)
165
+ pushVarDecl(decls, 'line-height', k, opts.lineHeight)
128
166
  }
129
167
  else {
130
- decls.push(`--font-size-${k}: ${v};`)
168
+ pushVarDecl(decls, 'font-size', k, v)
131
169
  }
132
170
  })
133
- Object.entries(borderRadius).forEach(([k, v]) => decls.push(`--radius-${k}: ${v};`))
134
- Object.entries(boxShadow).forEach(([k, v]) => decls.push(`--shadow-${k}: ${v};`))
135
- return `[data-theme-scope="${scopeId}"]{${decls.join('')}}`
171
+ Object.entries(borderRadius).forEach(([k, v]) => pushVarDecl(decls, 'radius', k, v))
172
+ Object.entries(boxShadow).forEach(([k, v]) => pushVarDecl(decls, 'shadow', k, v))
173
+
174
+ const rules = [`[data-theme-scope="${scopeId}"]{${decls.join('')}}`]
175
+ Object.keys(colors || {}).forEach((key) => {
176
+ const escapedKey = escapeClassToken(key)
177
+ const colorRef = cssVarRef('color', key)
178
+ rules.push(`[data-theme-scope="${scopeId}"] .bg-${escapedKey}{background-color:${colorRef} !important;}`)
179
+ rules.push(`[data-theme-scope="${scopeId}"] .text-${escapedKey}{color:${colorRef} !important;}`)
180
+ rules.push(`[data-theme-scope="${scopeId}"] .border-${escapedKey}{border-color:${colorRef} !important;}`)
181
+ })
182
+ return rules.join('')
136
183
  }
137
184
 
138
185
  function setGlobalThemeVars(theme) {
@@ -167,11 +214,18 @@ const safeHtml = computed(() => {
167
214
  })
168
215
 
169
216
  // Inject theme CSS variables into <head> for SSR + client
170
- useHead(() => ({
171
- style: [
172
- { id: 'htmlcontent-theme-global', children: buildScopedThemeCSS(props.theme, scopeId) },
173
- ],
174
- }))
217
+ useHead(() => {
218
+ const style = [
219
+ { id: `htmlcontent-theme-vars-${scopeId}`, children: buildScopedThemeCSS(props.theme, scopeId) },
220
+ ]
221
+ if (themeExtraCSS.value.trim()) {
222
+ style.push({
223
+ id: `htmlcontent-theme-extra-${scopeId}`,
224
+ children: themeExtraCSS.value,
225
+ })
226
+ }
227
+ return { style }
228
+ })
175
229
 
176
230
  // --- Embla initializer (runs client-side only) ---
177
231
  async function initEmblaCarousels(scope) {
@@ -339,10 +393,106 @@ async function initEmblaCarousels(scope) {
339
393
  })
340
394
  }
341
395
 
396
+ function initCmsNavHelpers(scope) {
397
+ if (!scope || !import.meta.client)
398
+ return
399
+
400
+ const roots = scope.querySelectorAll('.cms-nav-root, [data-cms-nav-root]')
401
+ roots.forEach((root) => {
402
+ if (root.dataset.cmsNavInit === 'true')
403
+ return
404
+
405
+ root.dataset.cmsNavInit = 'true'
406
+ const openClass = root.getAttribute('data-cms-nav-open-class') || 'is-open'
407
+ const panel = root.querySelector('.cms-nav-panel, [data-cms-nav-panel]')
408
+ const overlay = root.querySelector('.cms-nav-overlay, [data-cms-nav-overlay]')
409
+ const toggles = Array.from(root.querySelectorAll('.cms-nav-toggle, [data-cms-nav-toggle]'))
410
+ const closeButtons = Array.from(root.querySelectorAll('.cms-nav-close, [data-cms-nav-close]'))
411
+ const links = Array.from(root.querySelectorAll('.cms-nav-link, [data-cms-nav-link]'))
412
+ const closeOnLink = root.getAttribute('data-cms-nav-close-on-link') !== 'false'
413
+
414
+ const markInteractive = (el) => {
415
+ if (!el)
416
+ return
417
+ el.setAttribute('data-cms-interactive', 'true')
418
+ }
419
+
420
+ toggles.forEach(markInteractive)
421
+ closeButtons.forEach(markInteractive)
422
+ links.forEach(markInteractive)
423
+ markInteractive(panel)
424
+ markInteractive(overlay)
425
+
426
+ const isOpen = () => root.classList.contains(openClass)
427
+
428
+ const setOpen = (open) => {
429
+ root.classList.toggle(openClass, open)
430
+ root.setAttribute('data-cms-nav-open', open ? 'true' : 'false')
431
+
432
+ toggles.forEach((btn) => {
433
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false')
434
+ })
435
+
436
+ if (panel) {
437
+ panel.classList.toggle('translate-x-0', open)
438
+ panel.classList.toggle('opacity-100', open)
439
+ panel.classList.toggle('pointer-events-auto', open)
440
+ panel.classList.toggle('translate-x-full', !open)
441
+ panel.classList.toggle('opacity-0', !open)
442
+ panel.classList.toggle('pointer-events-none', !open)
443
+ panel.setAttribute('aria-hidden', open ? 'false' : 'true')
444
+ }
445
+
446
+ if (overlay) {
447
+ overlay.classList.toggle('opacity-100', open)
448
+ overlay.classList.toggle('pointer-events-auto', open)
449
+ overlay.classList.toggle('opacity-0', !open)
450
+ overlay.classList.toggle('pointer-events-none', !open)
451
+ overlay.setAttribute('aria-hidden', open ? 'false' : 'true')
452
+ }
453
+ }
454
+
455
+ setOpen(root.classList.contains(openClass) || root.getAttribute('data-cms-nav-open') === 'true')
456
+
457
+ toggles.forEach((btn) => {
458
+ btn.addEventListener('click', (event) => {
459
+ event.preventDefault()
460
+ event.stopPropagation()
461
+ setOpen(!isOpen())
462
+ })
463
+ })
464
+
465
+ closeButtons.forEach((btn) => {
466
+ btn.addEventListener('click', (event) => {
467
+ event.preventDefault()
468
+ event.stopPropagation()
469
+ setOpen(false)
470
+ })
471
+ })
472
+
473
+ if (overlay) {
474
+ overlay.addEventListener('click', (event) => {
475
+ event.preventDefault()
476
+ event.stopPropagation()
477
+ setOpen(false)
478
+ })
479
+ }
480
+
481
+ if (closeOnLink) {
482
+ links.forEach((link) => {
483
+ link.addEventListener('click', () => {
484
+ setOpen(false)
485
+ })
486
+ })
487
+ }
488
+ })
489
+ }
490
+
342
491
  function renderSafeHtml(content) {
343
492
  if (hostEl.value) {
344
493
  // The HTML is already in the DOM via v-html; just (re)wire behaviors
345
494
  initEmblaCarousels(hostEl.value)
495
+ initCmsNavHelpers(hostEl.value)
346
496
  }
347
497
  }
348
498
 
@@ -378,42 +528,7 @@ function setScopedThemeVars(scopeEl, theme) {
378
528
  document.head.appendChild(styleEl)
379
529
  }
380
530
 
381
- // Build CSS custom properties from theme tokens
382
- const { colors, fontFamily, fontSize, borderRadius, boxShadow } = theme
383
-
384
- const decls = []
385
- // colors
386
- Object.entries(colors).forEach(([k, v]) => {
387
- decls.push(`--color-${k}: ${Array.isArray(v) ? v[0] : v};`)
388
- })
389
- // fonts
390
- Object.entries(fontFamily).forEach(([k, v]) => {
391
- const val = Array.isArray(v) ? v.map(x => (x.includes(' ') ? `'${x}'` : x)).join(', ') : v
392
- decls.push(`--font-${k}: ${val};`)
393
- })
394
- // font sizes
395
- Object.entries(fontSize).forEach(([k, v]) => {
396
- if (Array.isArray(v)) {
397
- const [size, opts] = v
398
- decls.push(`--font-size-${k}: ${size};`)
399
- if (opts && opts.lineHeight)
400
- decls.push(`--line-height-${k}: ${opts.lineHeight};`)
401
- }
402
- else {
403
- decls.push(`--font-size-${k}: ${v};`)
404
- }
405
- })
406
- // radii
407
- Object.entries(borderRadius).forEach(([k, v]) => {
408
- decls.push(`--radius-${k}: ${v};`)
409
- })
410
- // shadows
411
- Object.entries(boxShadow).forEach(([k, v]) => {
412
- decls.push(`--shadow-${k}: ${v};`)
413
- })
414
-
415
- styleEl.textContent = `
416
- [data-theme-scope="${scopeId}"]{${decls.join('')}}`
531
+ styleEl.textContent = buildScopedThemeCSS(theme, scopeId)
417
532
  }
418
533
 
419
534
  // Convert utility tokens like text-brand/bg-surface/rounded-xl/shadow-card
@@ -446,7 +561,7 @@ function toVarBackedUtilities(classList, theme) {
446
561
  }
447
562
 
448
563
  if (colorKeys.has(key)) {
449
- const varRef = `var(--color-${key})`
564
+ const varRef = cssVarRef('color', key)
450
565
 
451
566
  // no /opacity → plain var()
452
567
  if (!opacity) {
@@ -477,7 +592,7 @@ function toVarBackedUtilities(classList, theme) {
477
592
  if (radiusMatch) {
478
593
  const key = radiusMatch[1]
479
594
  if (radiusKeys.has(key))
480
- return `rounded-[var(--radius-${key})]`
595
+ return `rounded-[${cssVarRef('radius', key)}]`
481
596
  return cls
482
597
  }
483
598
 
@@ -486,23 +601,23 @@ function toVarBackedUtilities(classList, theme) {
486
601
  if (shadowMatch) {
487
602
  const key = shadowMatch[1]
488
603
  if (shadowKeys.has(key))
489
- return `shadow-[var(--shadow-${key})]`
604
+ return `shadow-[${cssVarRef('shadow', key)}]`
490
605
  return cls
491
606
  }
492
607
 
493
608
  // font families via root apply, including custom keys like "brand"
494
609
  if (cls === 'font-sans')
495
- return 'font-[var(--font-sans)]'
610
+ return `font-[${cssVarRef('font', 'sans')}]`
496
611
  if (cls === 'font-serif')
497
- return 'font-[var(--font-serif)]'
612
+ return `font-[${cssVarRef('font', 'serif')}]`
498
613
  if (cls === 'font-mono')
499
- return 'font-[var(--font-mono)]'
614
+ return `font-[${cssVarRef('font', 'mono')}]`
500
615
 
501
616
  const ffMatch = /^font-([\w-]+)$/.exec(cls)
502
617
  if (ffMatch) {
503
618
  const key = ffMatch[1]
504
619
  if (Object.prototype.hasOwnProperty.call(tokens.fontFamily, key))
505
- return `font-[var(--font-${key})]`
620
+ return `font-[${cssVarRef('font', key)}]`
506
621
  }
507
622
 
508
623
  return cls
@@ -510,6 +625,35 @@ function toVarBackedUtilities(classList, theme) {
510
625
  .join(' ')
511
626
  }
512
627
 
628
+ function readElementClass(el) {
629
+ if (!el)
630
+ return ''
631
+ if (typeof el.className === 'string')
632
+ return el.className
633
+ if (el.className && typeof el.className.baseVal === 'string')
634
+ return el.className.baseVal
635
+ return el.getAttribute('class') || ''
636
+ }
637
+
638
+ function writeElementClass(el, nextClass = '') {
639
+ if (!el)
640
+ return
641
+ const normalized = typeof nextClass === 'string' ? nextClass : String(nextClass || '')
642
+ if (typeof el.className === 'string') {
643
+ el.className = normalized
644
+ return
645
+ }
646
+ el.setAttribute('class', normalized)
647
+ }
648
+
649
+ function appendElementClasses(el, classList) {
650
+ const additions = typeof classList === 'string' ? classList.trim() : ''
651
+ if (!additions)
652
+ return
653
+ const base = readElementClass(el)
654
+ writeElementClass(el, `${base} ${additions}`.trim())
655
+ }
656
+
513
657
  function applyThemeClasses(scopeEl, theme, variant = 'light', isolated = true) {
514
658
  if (!scopeEl)
515
659
  return
@@ -529,7 +673,7 @@ function applyThemeClasses(scopeEl, theme, variant = 'light', isolated = true) {
529
673
  if (apply.root) {
530
674
  const mapped = toVarBackedUtilities(apply.root, t)
531
675
  if (isolated) {
532
- scopeEl.className = `block-content ${mapped}`.trim()
676
+ writeElementClass(scopeEl, `block-content ${mapped}`.trim())
533
677
  }
534
678
  else {
535
679
  const applied = (scopeEl.dataset.themeRootClasses || '').split(/\s+/).filter(Boolean)
@@ -547,22 +691,22 @@ function applyThemeClasses(scopeEl, theme, variant = 'light', isolated = true) {
547
691
  // Optional convenience: map a few generic applies
548
692
  if (apply.link) {
549
693
  scopeEl.querySelectorAll('a').forEach((el) => {
550
- el.className = `${el.className} ${toVarBackedUtilities(apply.link, t)}`.trim()
694
+ appendElementClasses(el, toVarBackedUtilities(apply.link, t))
551
695
  })
552
696
  }
553
697
  if (apply.heading) {
554
698
  scopeEl.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach((el) => {
555
- el.className = `${el.className} ${toVarBackedUtilities(apply.heading, t)}`.trim()
699
+ appendElementClasses(el, toVarBackedUtilities(apply.heading, t))
556
700
  })
557
701
  }
558
702
  if (apply.button) {
559
703
  scopeEl.querySelectorAll('button,[data-theme="button"]').forEach((el) => {
560
- el.className = `${el.className} ${toVarBackedUtilities(apply.button, t)}`.trim()
704
+ appendElementClasses(el, toVarBackedUtilities(apply.button, t))
561
705
  })
562
706
  }
563
707
  if (apply.badge) {
564
708
  scopeEl.querySelectorAll('[data-theme="badge"]').forEach((el) => {
565
- el.className = `${el.className} ${toVarBackedUtilities(apply.badge, t)}`.trim()
709
+ appendElementClasses(el, toVarBackedUtilities(apply.badge, t))
566
710
  })
567
711
  }
568
712
 
@@ -573,7 +717,7 @@ function applyThemeClasses(scopeEl, theme, variant = 'light', isolated = true) {
573
717
  Object.entries(obj).forEach(([part, classes]) => {
574
718
  const sel = `[data-slot="${slotBase}.${part}"]`
575
719
  scopeEl.querySelectorAll(sel).forEach((el) => {
576
- el.className = `${el.className} ${toVarBackedUtilities(classes, t)}`.trim()
720
+ appendElementClasses(el, toVarBackedUtilities(classes, t))
577
721
  })
578
722
  })
579
723
  }
@@ -628,6 +772,7 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
628
772
  const forcedWidth = viewportModeToWidth(viewportMode)
629
773
 
630
774
  const TEXT_SIZE_RE = /^text-(xs|sm|base|lg|xl|\d+xl)$/
775
+ const FONT_UTILITY_RE = /^font-([\w-]+|\[[^\]]+\])$/
631
776
 
632
777
  const mapToken = (token) => {
633
778
  const parts = token.split(':')
@@ -654,7 +799,8 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
654
799
 
655
800
  const mappedCore = toVarBackedUtilities(core, theme)
656
801
  const isTextSize = TEXT_SIZE_RE.test(nakedCore)
657
- const shouldImportant = hadBreakpoint || isTextSize
802
+ const isFontUtility = FONT_UTILITY_RE.test(nakedCore)
803
+ const shouldImportant = hadBreakpoint || isTextSize || isFontUtility
658
804
  const finalCore = shouldImportant ? importantify(mappedCore) : mappedCore
659
805
 
660
806
  return [...nextParts, finalCore].filter(Boolean).join(':')
@@ -691,7 +837,10 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
691
837
  return ''
692
838
 
693
839
  const mappedCore = toVarBackedUtilities(core, theme)
694
- const finalCore = hadBreakpoint ? importantify(mappedCore) : mappedCore
840
+ const isTextSize = TEXT_SIZE_RE.test(nakedCore)
841
+ const isFontUtility = FONT_UTILITY_RE.test(nakedCore)
842
+ const shouldImportant = hadBreakpoint || isTextSize || isFontUtility
843
+ const finalCore = shouldImportant ? importantify(mappedCore) : mappedCore
695
844
 
696
845
  return [...nextParts, finalCore].filter(Boolean).join(':')
697
846
  }
@@ -699,15 +848,7 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
699
848
  scopeEl.querySelectorAll('[class]').forEach((el) => {
700
849
  let base = el.dataset.viewportBaseClass
701
850
  if (typeof base !== 'string') {
702
- if (typeof el.className === 'string') {
703
- base = el.className
704
- }
705
- else if (el.className && typeof el.className.baseVal === 'string') {
706
- base = el.className.baseVal
707
- }
708
- else {
709
- base = el.getAttribute('class') || ''
710
- }
851
+ base = readElementClass(el)
711
852
  el.dataset.viewportBaseClass = base
712
853
  }
713
854
  const orig = typeof base === 'string' ? base : String(base || '')
@@ -719,7 +860,7 @@ function rewriteAllClasses(scopeEl, theme, isolated = true, viewportMode = 'auto
719
860
  .filter(Boolean)
720
861
  if (isolated) {
721
862
  const mapped = mappedTokens.join(' ')
722
- el.className = mapped
863
+ writeElementClass(el, mapped)
723
864
  return
724
865
  }
725
866
 
@@ -742,10 +883,11 @@ onMounted(async () => {
742
883
 
743
884
  // Initialize carousels/behaviors for SSR-inserted HTML
744
885
  initEmblaCarousels(hostEl.value)
886
+ initCmsNavHelpers(hostEl.value)
745
887
 
746
888
  // Apply global theme once (keeps one style tag for vars; blocks can still override locally if needed)
747
889
  // setGlobalThemeVars(props.theme)
748
- setScopedThemeVars(hostEl.value, normalizeTheme(props.theme))
890
+ setScopedThemeVars(hostEl.value, props.theme)
749
891
  // If you later need per-block overrides, keep the next line; otherwise, it can be omitted.
750
892
  // setScopedThemeVars(hostEl.value, normalizeTheme(props.theme))
751
893
  applyThemeClasses(hostEl.value, props.theme, (props.theme && props.theme.variant) || 'light')
@@ -761,8 +903,9 @@ watch(
761
903
  // Wait for DOM to reflect new v-html, then (re)wire behaviors and class mappings
762
904
  await nextTick()
763
905
  initEmblaCarousels(hostEl.value)
906
+ initCmsNavHelpers(hostEl.value)
764
907
  // setGlobalThemeVars(props.theme)
765
- setScopedThemeVars(hostEl.value, normalizeTheme(props.theme))
908
+ setScopedThemeVars(hostEl.value, props.theme)
766
909
 
767
910
  applyThemeClasses(hostEl.value, props.theme, (props.theme && props.theme.variant) || 'light')
768
911
  rewriteAllClasses(hostEl.value, props.theme, props.isolated, props.viewportMode)
@@ -774,13 +917,11 @@ watch(
774
917
  watch(
775
918
  () => props.theme,
776
919
  async (val) => {
777
- const t = normalizeTheme(val)
778
- // 1) Write CSS variables globally
779
- // setGlobalThemeVars(t)
780
- setScopedThemeVars(hostEl.value, t)
920
+ // 1) Write scoped CSS variables from the raw theme object
921
+ setScopedThemeVars(hostEl.value, val)
781
922
  // 2) Apply classes based on `apply`, `slots`, and optional variants
782
- applyThemeClasses(hostEl.value, t, (val && val.variant) || 'light')
783
- rewriteAllClasses(hostEl.value, t, props.isolated, props.viewportMode)
923
+ applyThemeClasses(hostEl.value, val, (val && val.variant) || 'light')
924
+ rewriteAllClasses(hostEl.value, val, props.isolated, props.viewportMode)
784
925
  await nextTick()
785
926
  notifyLoaded()
786
927
  },
@@ -960,13 +960,13 @@ const theme = computed(() => {
960
960
  <SidebarMenuItem v-for="({ menu, name: menuName }) in orderedMenus" :key="menuName">
961
961
  <SidebarMenuButton class="group !px-0 hover:!bg-transparent">
962
962
  <FolderOpen
963
- class="mr-2 group-hover:text-black"
963
+ class="mr-2 group-hover:text-foreground"
964
964
  />
965
- <span v-if="!props.isTemplateSite" class="hover:text-black !text-black">{{ menuName === 'Site Root' ? 'Site Menu' : menuName }}</span>
965
+ <span v-if="!props.isTemplateSite" class="!text-foreground">{{ menuName === 'Site Root' ? 'Site Menu' : menuName }}</span>
966
966
  <SidebarGroupAction class="absolute right-2 top-0 hover:!bg-transparent">
967
967
  <DropdownMenu>
968
968
  <DropdownMenuTrigger as-child>
969
- <SidebarMenuAction>
969
+ <SidebarMenuAction class="hover:bg-primary text-foreground hover:text-primary-foreground">
970
970
  <PlusIcon />
971
971
  </SidebarMenuAction>
972
972
  </DropdownMenuTrigger>
@@ -1029,6 +1029,7 @@ const theme = computed(() => {
1029
1029
  :class="{ 'text-gray-400': element.item === '' }"
1030
1030
  as-child
1031
1031
  :is-active="!isExternalLinkEntry(element) && element.item === props.page"
1032
+ class="text-foreground hover:bg-primary hover:text-primary-foreground"
1032
1033
  >
1033
1034
  <NuxtLink
1034
1035
  v-if="!isExternalLinkEntry(element)"
@@ -1056,7 +1057,7 @@ const theme = computed(() => {
1056
1057
  <div class="absolute right-0 -top-0.5">
1057
1058
  <DropdownMenu>
1058
1059
  <DropdownMenuTrigger as-child>
1059
- <SidebarMenuAction>
1060
+ <SidebarMenuAction class="hover:bg-primary text-foreground hover:text-primary-foreground">
1060
1061
  <MoreHorizontal />
1061
1062
  </SidebarMenuAction>
1062
1063
  </DropdownMenuTrigger>