UNPKG

portal-vue

Version:

> A Portal Component for Vue 3, to render DOM outside of a component, anywhere in the document.

1 lines 15.2 kB
{"version":3,"file":"portal-vue.es.dev.mjs","sources":["../src/composables/wormhole.ts","../src/utils/index.ts","../src/components/portal.ts","../src/components/portal-target.ts","../src/wormhole.ts","../src/utils/mountPortalTarget.ts","../src/index.ts"],"sourcesContent":["import { type InjectionKey, inject, provide } from 'vue'\nimport type { Wormhole } from '../types'\n\nexport const wormholeSymbol = Symbol('wormhole') as InjectionKey<Wormhole>\n\nexport function useWormhole() {\n const wh = inject(wormholeSymbol)\n\n if (!wh) {\n throw new Error(`\n [portal-vue]: Necessary Injection not found. Make sur you installed the plugin properly.`)\n }\n\n return wh\n}\n\nexport function provideWormhole(wormhole: Wormhole) {\n provide(wormholeSymbol, wormhole)\n}\n","import { watch } from 'vue'\n\nexport const inBrowser = typeof window !== 'undefined'\n\nexport const __DEV__ = __NODE_ENV__ === 'development'\n\nexport function warn(msg: string) {\n console.log('[portal-vue]: ' + msg)\n}\n\nexport function assertStaticProps(\n component: string,\n props: Record<string, any>,\n propNames: string[]\n) {\n propNames.forEach(\n (name) => {\n watch(\n () => props[name],\n () => {\n warn(\n `Prop '${name}' of component ${component} is static, but was dynamically changed by the parent.\n This change will not have any effect.`\n )\n }\n )\n },\n { flush: 'post' }\n )\n}\n\nexport function stableSort<T>(array: T[], compareFn: Function) {\n return array\n .map((v: T, idx: number) => {\n return [idx, v] as [number, T]\n })\n .sort(function (a, b) {\n return compareFn(a[1], b[1]) || a[0] - b[0]\n })\n .map((c) => c[1])\n}\n","import {\n type Slots,\n defineComponent,\n onBeforeUnmount,\n onMounted,\n onUpdated,\n watch,\n} from 'vue'\nimport { useWormhole } from '../composables/wormhole'\nimport type { Name, PortalProps } from '../types'\nimport { __DEV__, assertStaticProps, inBrowser } from '../utils'\n\nexport function usePortal(props: PortalProps, slots: Slots) {\n const wormhole = useWormhole()\n\n function sendUpdate() {\n if (!inBrowser) return\n const { to, name: from, order } = props\n if (slots.default) {\n wormhole.open({\n to,\n from: from!,\n order,\n content: slots.default,\n })\n } else {\n clear()\n }\n }\n\n function clear(target?: Name) {\n wormhole.close({\n to: target ?? props.to,\n from: props.name,\n })\n }\n onMounted(() => {\n if (!props.disabled) {\n sendUpdate()\n }\n })\n\n onUpdated(() => {\n if (props.disabled) {\n clear()\n } else {\n sendUpdate()\n }\n })\n\n onBeforeUnmount(() => {\n clear()\n })\n\n watch(\n () => props.to,\n (newTo, oldTo) => {\n if (props.disabled) return\n if (oldTo && oldTo !== newTo) {\n clear(oldTo)\n }\n sendUpdate()\n }\n )\n}\n\nexport default defineComponent({\n compatConfig: { MODE: 3 },\n name: 'portal',\n props: {\n disabled: { type: Boolean },\n name: { type: [String, Symbol], default: () => Symbol() },\n order: { type: Number },\n slotProps: { type: Object, default: () => ({}) },\n to: {\n type: String,\n default: () => String(Math.round(Math.random() * 10000000)),\n },\n },\n setup(props, { slots }) {\n __DEV__ && assertStaticProps('Portal', props, ['order', 'name'])\n usePortal(props, slots)\n\n return () => {\n if (props.disabled && slots.default) {\n return slots.default(props.slotProps)\n } else {\n return null\n }\n }\n },\n})\n","import {\n type FunctionalComponent,\n type VNode,\n computed,\n defineComponent,\n h,\n watch,\n} from 'vue'\nimport { useWormhole } from '../composables/wormhole'\n\nconst PortalTargetContent: FunctionalComponent = (_, { slots }) => {\n return slots.default?.()\n}\n\nexport default defineComponent({\n compatConfig: { MODE: 3 },\n name: 'portalTarget',\n props: {\n multiple: { type: Boolean, default: false },\n name: { type: String, required: true },\n slotProps: { type: Object, default: () => ({}) },\n },\n emits: ['change'],\n setup(props, { emit, slots }) {\n const wormhole = useWormhole()\n\n const slotVnodes = computed<{ vnodes: VNode[]; vnodesFn: () => VNode[] }>(\n () => {\n const transports = wormhole.getContentForTarget(\n props.name,\n props.multiple\n )\n const wrapperSlot = slots.wrapper\n const rawNodes = transports.map((t) => t.content(props.slotProps))\n const vnodes = wrapperSlot\n ? rawNodes.flatMap((nodes) =>\n nodes.length ? wrapperSlot(nodes) : []\n )\n : rawNodes.flat(1)\n return {\n vnodes,\n vnodesFn: () => vnodes, // just to make Vue happy. raw vnodes in a slot give a DEV warning\n }\n }\n )\n\n watch(\n slotVnodes,\n ({ vnodes }) => {\n const hasContent = vnodes.length > 0\n const content = wormhole.transports.get(props.name)\n const sources = content ? [...content.keys()] : []\n emit('change', { hasContent, sources })\n },\n { flush: 'post' }\n )\n return () => {\n const hasContent = !!slotVnodes.value.vnodes.length\n if (hasContent) {\n return [\n // this node is a necessary hack to force Vue to change the scoped-styles boundary\n // TODO: find less hacky solution\n h('div', {\n style: 'display: none',\n key: '__portal-vue-hacky-scoped-slot-repair__',\n }),\n // we wrap the slot content in a functional component\n // so that transitions in the slot can properly determine first render\n // for `appear` behavior to work properly\n h(PortalTargetContent, slotVnodes.value.vnodesFn),\n ]\n } else {\n return slots.default?.()\n }\n }\n },\n})\n","import { reactive, readonly } from 'vue'\nimport type {\n Name,\n Transport,\n TransportCloser,\n TransportInput,\n TransportsHub,\n Wormhole,\n} from './types'\nimport { inBrowser, stableSort } from './utils'\n\nexport function createWormhole(asReadonly = true): Wormhole {\n const transports: TransportsHub = reactive(new Map())\n\n function open(transport: TransportInput) {\n if (!inBrowser) return\n\n const { to, from, content, order = Infinity } = transport\n if (!to || !from || !content) return\n\n if (!transports.has(to)) {\n transports.set(to, new Map())\n }\n const transportsForTarget = transports.get(to)!\n\n const newTransport = {\n to,\n from,\n content,\n order,\n } as Transport\n\n transportsForTarget.set(from, newTransport)\n }\n\n function close(transport: TransportCloser) {\n const { to, from } = transport\n if (!to || !from) return\n const transportsForTarget = transports.get(to)\n if (!transportsForTarget) {\n return\n }\n transportsForTarget.delete(from)\n if (!transportsForTarget.size) {\n transports.delete(to)\n }\n }\n\n function getContentForTarget(target: Name, returnAll?: boolean) {\n const transportsForTarget = transports.get(target)\n if (!transportsForTarget) return []\n\n const content = Array.from(transportsForTarget?.values() || [])\n\n if (!returnAll) {\n // return Transport that was added last\n return [content.pop()] as Transport[]\n }\n // return all Transports, sorted by their order property\n return stableSort(\n content,\n (a: Transport, b: Transport) => a.order - b.order\n )\n }\n\n const wh: Wormhole = {\n open,\n close,\n transports,\n getContentForTarget,\n }\n return asReadonly ? (readonly(wh) as Wormhole) : wh\n}\n\nexport const wormhole = createWormhole()\n","import type { PortalTargetProps } from '../types'\nimport {\n type ComponentInternalInstance,\n createApp,\n getCurrentInstance,\n h,\n onBeforeUnmount,\n onMounted,\n} from 'vue'\nimport PortalTarget from '../components/portal-target'\n\nexport function mountPortalTarget(\n targetProps: PortalTargetProps,\n el: HTMLElement | string\n) {\n const app = createApp({\n // @ts-expect-error no idea why h() doesn't like this import\n render: () => h(PortalTarget, targetProps),\n })\n\n if (!targetProps.multiple) {\n // this is hacky as it relies on internals, but works.\n // TODO: can we get rid of this by somehow properly replacing the target's .parent?\n const provides =\n (\n getCurrentInstance() as ComponentInternalInstance & {\n provides: Record<string, any>\n }\n ).provides ?? {}\n app._context.provides = Object.create(provides)\n //Object.assign(app._context.provides, Object.create(provides))\n }\n onMounted(() => {\n app.mount(el)\n })\n onBeforeUnmount(() => {\n app.unmount()\n })\n}\n","import type { App } from 'vue'\nimport Portal from './components/portal'\nimport PortalTarget from './components/portal-target'\nimport {\n provideWormhole,\n useWormhole,\n wormholeSymbol,\n} from './composables/wormhole'\nimport type { Wormhole as TWormhole } from './types'\nimport { createWormhole, wormhole as defaultWormhole } from './wormhole'\nexport { mountPortalTarget } from './utils/mountPortalTarget'\nexport interface PluginOptions {\n portalName?: string | false\n portalTargetName?: string | false\n MountingPortalName?: string\n wormhole?: TWormhole\n}\n\nexport default function install(app: App, options: PluginOptions = {}) {\n options.portalName !== false &&\n app.component(options.portalName || 'Portal', Portal)\n options.portalTargetName !== false &&\n app.component(options.portalTargetName || 'PortalTarget', PortalTarget)\n\n const wormhole = options.wormhole ?? defaultWormhole\n app.provide(wormholeSymbol, wormhole)\n}\n\nexport const Wormhole = defaultWormhole\n\nexport const version = __PORTAL_VUE_VERSION__\n\nexport {\n install,\n Portal,\n PortalTarget,\n useWormhole,\n provideWormhole,\n TWormhole,\n createWormhole,\n}\n"],"names":["wormhole","defaultWormhole"],"mappings":";AAGa,MAAA,iBAAiB,OAAO,UAAU;AAExC,SAAS,cAAc;AACtB,QAAA,KAAK,OAAO,cAAc;AAEhC,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM;AAAA,6FACyE;AAAA,EAC3F;AAEO,SAAA;AACT;AAEO,SAAS,gBAAgBA,WAAoB;AAClD,UAAQ,gBAAgBA,SAAQ;AAClC;AChBa,MAAA,YAAY,OAAO,WAAW;AAIpC,SAAS,KAAK,KAAa;AACxB,UAAA,IAAI,mBAAmB,GAAG;AACpC;AAEgB,SAAA,kBACd,WACA,OACA,WACA;AACU,YAAA;AAAA,IACR,CAAC,SAAS;AACR;AAAA,QACE,MAAM,MAAM;AAAA,QACZ,MAAM;AACJ;AAAA,YACE,SAAS,sBAAsB;AAAA;AAAA,UAAA;AAAA,QAGnC;AAAA,MAAA;AAAA,IAEJ;AAAA,IACA,EAAE,OAAO,OAAO;AAAA,EAAA;AAEpB;AAEgB,SAAA,WAAc,OAAY,WAAqB;AAC7D,SAAO,MACJ,IAAI,CAAC,GAAM,QAAgB;AACnB,WAAA,CAAC,KAAK,CAAC;AAAA,EACf,CAAA,EACA,KAAK,SAAU,GAAG,GAAG;AACb,WAAA,UAAU,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;AAAA,EAAA,CAC1C,EACA,IAAI,CAAC,MAAM,EAAE,EAAE;AACpB;AC5BgB,SAAA,UAAU,OAAoB,OAAc;AAC1D,QAAMA,YAAW;AAEjB,WAAS,aAAa;AACpB,QAAI,CAAC;AAAW;AAChB,UAAM,EAAE,IAAI,MAAM,MAAM,UAAU;AAClC,QAAI,MAAM,SAAS;AACjB,MAAAA,UAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,MAAM;AAAA,MAAA,CAChB;AAAA,IAAA,OACI;AACC;IACR;AAAA,EACF;AAEA,WAAS,MAAM,QAAe;AAC5B,IAAAA,UAAS,MAAM;AAAA,MACb,IAAI,UAAU,MAAM;AAAA,MACpB,MAAM,MAAM;AAAA,IAAA,CACb;AAAA,EACH;AACA,YAAU,MAAM;AACV,QAAA,CAAC,MAAM,UAAU;AACR;IACb;AAAA,EAAA,CACD;AAED,YAAU,MAAM;AACd,QAAI,MAAM,UAAU;AACZ;IAAA,OACD;AACM;IACb;AAAA,EAAA,CACD;AAED,kBAAgB,MAAM;AACd;EAAA,CACP;AAED;AAAA,IACE,MAAM,MAAM;AAAA,IACZ,CAAC,OAAO,UAAU;AAChB,UAAI,MAAM;AAAU;AAChB,UAAA,SAAS,UAAU,OAAO;AAC5B,cAAM,KAAK;AAAA,MACb;AACW;IACb;AAAA,EAAA;AAEJ;AAEA,MAAA,SAAe,gBAAgB;AAAA,EAC7B,cAAc,EAAE,MAAM,EAAE;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,IACL,UAAU,EAAE,MAAM,QAAQ;AAAA,IAC1B,MAAM,EAAE,MAAM,CAAC,QAAQ,MAAM,GAAG,SAAS,MAAM,SAAS;AAAA,IACxD,OAAO,EAAE,MAAM,OAAO;AAAA,IACtB,WAAW,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAI,GAAA;AAAA,IAC/C,IAAI;AAAA,MACF,MAAM;AAAA,MACN,SAAS,MAAM,OAAO,KAAK,MAAM,KAAK,OAAA,IAAW,GAAQ,CAAC;AAAA,IAC5D;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,SAAS;AACX,sBAAkB,UAAU,OAAO,CAAC,SAAS,MAAM,CAAC;AAC/D,cAAU,OAAO,KAAK;AAEtB,WAAO,MAAM;AACP,UAAA,MAAM,YAAY,MAAM,SAAS;AAC5B,eAAA,MAAM,QAAQ,MAAM,SAAS;AAAA,MAAA,OAC/B;AACE,eAAA;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AACF,CAAC;ACjFD,MAAM,sBAA2C,CAAC,GAAG,EAAE,YAAY;;AACjE,UAAO,WAAM,YAAN;AACT;AAEA,MAAA,eAAe,gBAAgB;AAAA,EAC7B,cAAc,EAAE,MAAM,EAAE;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,IACL,UAAU,EAAE,MAAM,SAAS,SAAS,MAAM;AAAA,IAC1C,MAAM,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,IACrC,WAAW,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAI,GAAA;AAAA,EACjD;AAAA,EACA,OAAO,CAAC,QAAQ;AAAA,EAChB,MAAM,OAAO,EAAE,MAAM,SAAS;AAC5B,UAAMA,YAAW;AAEjB,UAAM,aAAa;AAAA,MACjB,MAAM;AACJ,cAAM,aAAaA,UAAS;AAAA,UAC1B,MAAM;AAAA,UACN,MAAM;AAAA,QAAA;AAER,cAAM,cAAc,MAAM;AACpB,cAAA,WAAW,WAAW,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM,SAAS,CAAC;AAC3D,cAAA,SAAS,cACX,SAAS;AAAA,UAAQ,CAAC,UAChB,MAAM,SAAS,YAAY,KAAK,IAAI,CAAC;AAAA,QAAA,IAEvC,SAAS,KAAK,CAAC;AACZ,eAAA;AAAA,UACL;AAAA,UACA,UAAU,MAAM;AAAA,QAAA;AAAA,MAEpB;AAAA,IAAA;AAGF;AAAA,MACE;AAAA,MACA,CAAC,EAAE,OAAA,MAAa;AACR,cAAA,aAAa,OAAO,SAAS;AACnC,cAAM,UAAUA,UAAS,WAAW,IAAI,MAAM,IAAI;AAC5C,cAAA,UAAU,UAAU,CAAC,GAAG,QAAQ,KAAK,CAAC,IAAI;AAChD,aAAK,UAAU,EAAE,YAAY,QAAS,CAAA;AAAA,MACxC;AAAA,MACA,EAAE,OAAO,OAAO;AAAA,IAAA;AAElB,WAAO,MAAM;;AACX,YAAM,aAAa,CAAC,CAAC,WAAW,MAAM,OAAO;AAC7C,UAAI,YAAY;AACP,eAAA;AAAA,UAGL,EAAE,OAAO;AAAA,YACP,OAAO;AAAA,YACP,KAAK;AAAA,UAAA,CACN;AAAA,UAID,EAAE,qBAAqB,WAAW,MAAM,QAAQ;AAAA,QAAA;AAAA,MAClD,OACK;AACL,gBAAO,WAAM,YAAN;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AACF,CAAC;ACjEe,SAAA,eAAe,aAAa,MAAgB;AAC1D,QAAM,aAA4B,SAAa,oBAAA,IAAK,CAAA;AAEpD,WAAS,KAAK,WAA2B;AACvC,QAAI,CAAC;AAAW;AAEhB,UAAM,EAAE,IAAI,MAAM,SAAS,QAAQ,SAAa,IAAA;AAChD,QAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;AAAS;AAE9B,QAAI,CAAC,WAAW,IAAI,EAAE,GAAG;AACvB,iBAAW,IAAI,IAAQ,oBAAA,IAAK,CAAA;AAAA,IAC9B;AACM,UAAA,sBAAsB,WAAW,IAAI,EAAE;AAE7C,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGkB,wBAAA,IAAI,MAAM,YAAY;AAAA,EAC5C;AAEA,WAAS,MAAM,WAA4B;AACnC,UAAA,EAAE,IAAI,KAAS,IAAA;AACjB,QAAA,CAAC,MAAM,CAAC;AAAM;AACZ,UAAA,sBAAsB,WAAW,IAAI,EAAE;AAC7C,QAAI,CAAC,qBAAqB;AACxB;AAAA,IACF;AACA,wBAAoB,OAAO,IAAI;AAC3B,QAAA,CAAC,oBAAoB,MAAM;AAC7B,iBAAW,OAAO,EAAE;AAAA,IACtB;AAAA,EACF;AAES,WAAA,oBAAoB,QAAc,WAAqB;AACxD,UAAA,sBAAsB,WAAW,IAAI,MAAM;AACjD,QAAI,CAAC;AAAqB,aAAO;AAEjC,UAAM,UAAU,MAAM,MAAK,2DAAqB,aAAY,CAAA,CAAE;AAE9D,QAAI,CAAC,WAAW;AAEP,aAAA,CAAC,QAAQ,IAAA,CAAK;AAAA,IACvB;AAEO,WAAA;AAAA,MACL;AAAA,MACA,CAAC,GAAc,MAAiB,EAAE,QAAQ,EAAE;AAAA,IAAA;AAAA,EAEhD;AAEA,QAAM,KAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEK,SAAA,aAAc,SAAS,EAAE,IAAiB;AACnD;AAEO,MAAM,WAAW,eAAe;AC/DvB,SAAA,kBACd,aACA,IACA;AACA,QAAM,MAAM,UAAU;AAAA,IAEpB,QAAQ,MAAM,EAAE,cAAc,WAAW;AAAA,EAAA,CAC1C;AAEG,MAAA,CAAC,YAAY,UAAU;AAGzB,UAAM,WAEF,qBAGA,YAAY,CAAA;AAChB,QAAI,SAAS,WAAW,OAAO,OAAO,QAAQ;AAAA,EAEhD;AACA,YAAU,MAAM;AACd,QAAI,MAAM,EAAE;AAAA,EAAA,CACb;AACD,kBAAgB,MAAM;AACpB,QAAI,QAAQ;AAAA,EAAA,CACb;AACH;ACpBA,SAAwB,QAAQ,KAAU,UAAyB,IAAI;AACrE,UAAQ,eAAe,SACrB,IAAI,UAAU,QAAQ,cAAc,UAAU,MAAM;AACtD,UAAQ,qBAAqB,SAC3B,IAAI,UAAU,QAAQ,oBAAoB,gBAAgB,YAAY;AAElE,QAAAA,aAAW,QAAQ,YAAYC;AACjC,MAAA,QAAQ,gBAAgBD,UAAQ;AACtC;AAEO,MAAM,WAAWC;AAEjB,MAAM,UAAU;"}