doxa-maps/library/composables/useShadowStyles.js
Doxa Maps b16004e072
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Update from source f1f93d78
2026-09-26 04:20:35 +00:00

67 lines
2.6 KiB
JavaScript

/**
* useShadowStyles.js — Shadow DOM Style Injection
*
* Vue's defineCustomElement only injects styles from the ROOT component
* into the Shadow DOM. Child components loaded via defineAsyncComponent
* (import.meta.glob) have their <style> blocks silently dropped.
*
* This composable injects a child component's critical styles into the
* shadow root so they actually take effect.
*
* Usage (in any child component):
* import { useShadowStyles } from '../composables/useShadowStyles.js'
*
* // Call at setup() time — injects styles into shadow root on mount
* useShadowStyles(`
* .my-component { position: absolute; inset: 0; }
* .map-canvas { position: absolute; inset: 0; }
* `)
*/
import { getCurrentInstance, onMounted } from 'vue'
/**
* Injects a CSS string into the shadow root that contains this component.
* Idempotent — uses a data-attribute key to avoid duplicate injection.
*
* @param {string} css - Raw CSS text to inject
* @param {string} [label] - Optional label for the <style> tag (for debugging)
*/
export function useShadowStyles(css, label) {
// Captured in setup (onMounted's callback cannot see it reliably).
const instance = getCurrentInstance()
// A stable key: the label; else, under the dev server, the component's HMR id (so an edited stylesheet
// REPLACES its old copy instead of piling up next to it); else a hash of the CSS (builds: unchanged).
const key = label || (instance && instance.type && instance.type.__hmrId ? 'hmr-' + instance.type.__hmrId : hashCode(css))
onMounted(() => {
if (!instance) return
// Walk up from the component's root element to find the shadow root
const el = instance.proxy?.$el
if (!el) return
const root = el.getRootNode()
if (!(root instanceof ShadowRoot)) return // light DOM — no injection needed
// Already injected: keep it — unless the CSS changed (a hot update), then replace its text. The early
// return here used to keep the OLD CSS after an edit, under a label.
const existing = root.querySelector(`style[data-shadow-styles="${key}"]`)
if (existing) { if (existing.textContent !== css) existing.textContent = css; return }
const style = document.createElement('style')
style.setAttribute('data-shadow-styles', key)
style.textContent = css
root.appendChild(style)
})
}
/** Simple string hash for dedup key */
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i)
hash |= 0
}
return 's' + Math.abs(hash).toString(36)
}