UNPKG

@empathyco/x-components

Version:
149 lines (146 loc) 6.16 kB
import { defineComponent, ref, getCurrentInstance, onUnmounted, onBeforeUnmount, watch, watchEffect, onMounted } from 'vue'; var _sfc_main = defineComponent({ name: 'BaseTeleport', props: { /** The element or css selector to which the component will be teleported. */ target: { type: [String, Object], required: true, }, /** * The position relative to the target * - `beforebegin`: Before the target element. * - `afterbegin`: Inside the target element, before its first child. * - `beforeend`: Inside the target element, after its last child. * - `afterend`: After the target element. * - `onlychild`: Adds it as child and hides all other children of the target element. */ position: { type: String, default: 'onlychild', }, /** If disabled, the slot content will not be teleported */ disabled: { type: Boolean, default: false, }, /** Styles for the teleport (content container) */ hostStyle: Object, }, setup(props) { if (typeof document === 'undefined') { return { teleportHost: ref() }; } const instance = getCurrentInstance(); /** Hook where the slot content will be teleported to. */ const teleportHost = ref(); /** The page element where the teleport host will be inserted. */ const targetElement = ref(); let isIsolated = false; // Before doing app.mount it is unknown if it will be mounted in a shadow so we need to wait. if (instance?.appContext.app._container) { createHost(); } else { afterAppMount(createHost); } const targetAddedObserver = new MutationObserver(targetAdded); const targetRemovedObserver = new MutationObserver(targetRemoved); onUnmounted(() => { if (isIsolated && teleportHost.value) { window.xCSSInjector.removeHost(teleportHost.value.shadowRoot); } }); onBeforeUnmount(() => { targetAddedObserver.disconnect(); targetRemovedObserver.disconnect(); teleportHost.value?.remove(); }); // Handles target prop changes and init the observers accordingly. watch(() => props.target, newTarget => { targetAddedObserver.disconnect(); targetRemovedObserver.disconnect(); const target = typeof newTarget === 'string' ? document.querySelector(newTarget) : newTarget; if (target?.isConnected) { targetAdded(); } else { targetRemoved(); } }, { immediate: true }); // Updates the teleport host when props change. watchEffect(() => { if (!teleportHost.value) { return; } if (props.disabled) { teleportHost.value.remove(); return; } teleportHost.value.className = `x-base-teleport x-base-teleport--${props.position}`; if (!targetElement.value) { console.warn(`BaseTeleport: Target element "${props.target}" not found.`); return; } const position = props.position === 'onlychild' ? 'beforeend' : props.position; targetElement.value.insertAdjacentElement(position, teleportHost.value); }); /* Update the host inline styles when it changes */ watchEffect(() => { if (teleportHost.value && props.hostStyle) { if (typeof props.hostStyle === 'string') { teleportHost.value.style.cssText = props.hostStyle; } else { Object.assign(teleportHost.value.style, props.hostStyle); } } }); /** Checks if the target element exists in the DOM and updates the observers */ function targetAdded() { let element = props.target; if (typeof element === 'string') { element = document.querySelector(element); } if (element instanceof HTMLElement && element.isConnected) { targetAddedObserver.disconnect(); targetElement.value = element; while (element.parentElement) { element = element.parentElement; targetRemovedObserver.observe(element, { childList: true }); } } } /** Checks if the target was disconnected from the DOM and updates the observers */ function targetRemoved() { if (targetElement.value && !targetElement.value.isConnected) { targetRemovedObserver.disconnect(); targetAddedObserver.observe(document.body, { childList: true, subtree: true }); targetElement.value = undefined; } } /** Creates and sets the teleport host element */ function createHost() { teleportHost.value = document.createElement('div'); isIsolated = instance?.appContext.app._container instanceof ShadowRoot; if (isIsolated) { teleportHost.value.attachShadow({ mode: 'open' }); window.xCSSInjector.addHost(teleportHost.value.shadowRoot); } } function afterAppMount(fn) { onMounted(() => setTimeout(fn, 0)); } return { teleportHost }; }, }); /** Teleport host styles should be injected outside our shadowRoots */ if (typeof document !== 'undefined') { const css = document.createElement('style'); css.textContent = ':has(> .x-base-teleport--onlychild) > *:not(.x-base-teleport) { display: none; }'; document.head?.appendChild(css) || document.addEventListener('DOMContentLoaded', () => document.head.appendChild(css)); } export { _sfc_main as default }; //# sourceMappingURL=base-teleport.vue2.js.map