From 0182c7ac4b4a5bcf514dfc1ec9dfb0f4e23e87c9 Mon Sep 17 00:00:00 2001 From: Raphael Kieling Date: Fri, 19 Jul 2019 03:48:42 -0300 Subject: [PATCH 1/6] Update getting-started.md (#242) --- docs/guide/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 8426134..5f4a48b 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -156,7 +156,7 @@ In older browsers, `position: fixed` works unreliably when the element with that But we normally need it to render components like modals, dialogs, notifications, snackbars and similar UI elements in a fixed position. -Also, z-indices can be a problem when trying to render things on top of each other somehwere in the DOM. +Also, z-indices can be a problem when trying to render things on top of each other somewhere in the DOM. With PortalVue, you can render your modal/overlay/dropdown component to a `` that you can position as the very last in the page's `body`, making styling and positioning much easier and less error-prone. From e7b35045816c50f4924e2d405646c609ce574c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BAcio=20Rubens?= <4539235+luciorubeens@users.noreply.github.com> Date: Fri, 19 Jul 2019 03:50:00 -0300 Subject: [PATCH 2/6] docs(api): fix order type in the portal component (#240) --- docs/api/portal.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/portal.md b/docs/api/portal.md index 9350682..953fd9c 100644 --- a/docs/api/portal.md +++ b/docs/api/portal.md @@ -69,9 +69,9 @@ But it might be a good idea to name your `` components so you can debug ### `order` -| Type | Required | Default | -| ----------------- | -------- | --------------- | -| `[String,Number]` | no\* | a random String | +| Type | Required | Default | +| -------- | -------- | ------- | +| `Number` | no\* | 0 | This prop defines the order position in the output of the ``. From 69244f9f07d9433db882614c2c18995254d10a61 Mon Sep 17 00:00:00 2001 From: Arif Hussain Date: Fri, 26 Jul 2019 11:55:28 +0500 Subject: [PATCH 3/6] Props Message text correction (#246) --- docs/api/portal-target.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/portal-target.md b/docs/api/portal-target.md index 9714c92..dc4813a 100644 --- a/docs/api/portal-target.md +++ b/docs/api/portal-target.md @@ -264,7 +264,7 @@ Example: ```html {1-3}

- {{props.mesage}} This is rendered when no other content is available. + {{props.message}} This is rendered when no other content is available.

``` From cb7e8ccdffd168b43a88084baec4286c7bac68de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20L=C3=BCnborg?= Date: Sun, 28 Jul 2019 15:06:02 +0200 Subject: [PATCH 4/6] fix(docs): `passengers` is a required argument (#248) --- docs/api/wormhole.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/wormhole.md b/docs/api/wormhole.md index 7c3ece2..232b6b2 100644 --- a/docs/api/wormhole.md +++ b/docs/api/wormhole.md @@ -28,7 +28,7 @@ The `open` method accepts one argument, an object with the following properties: | ---------- | -------- | ------- | ------------------------------------------------------------------- | | to | yes | | The name of the `` to send to | | from | yes | | The name of the `` this content comes from. | -| passengers | no | | An array of vNodes - the content to be sent to the `` | +| passengers | yes | | An array of vNodes - the content to be sent to the `` | Even if you use this method programmatically and there is not source ``, you still have to provide `from` - every content sent through the wormhole needs a source. From f810fe9b6368647603d85f14c9637ca5a74f3aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20L=C3=BCnborg?= Date: Sun, 28 Jul 2019 16:18:25 +0200 Subject: [PATCH 5/6] fix(docs): add missing argument for Wormhole.open --- docs/api/wormhole.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/api/wormhole.md b/docs/api/wormhole.md index 232b6b2..f758fb1 100644 --- a/docs/api/wormhole.md +++ b/docs/api/wormhole.md @@ -24,11 +24,12 @@ The `open` method accepts one argument, an object with the following properties: `Wormhole.open({to, from, passengers})` -| Property | Required | Default | Explanation | -| ---------- | -------- | ------- | ------------------------------------------------------------------- | -| to | yes | | The name of the `` to send to | -| from | yes | | The name of the `` this content comes from. | -| passengers | yes | | An array of vNodes - the content to be sent to the `` | +| Property | Required | Default | Explanation | +| ---------- | -------- | -------- | ------------------------------------------------------------------- | +| to | yes | | The name of the `` to send to | +| from | yes | | The name of the `` this content comes from. | +| passengers | yes | | An array of vNodes - the content to be sent to the `` | +| order | no | Infinity | a number indicating the order when multipe sources for a target are used | Even if you use this method programmatically and there is not source ``, you still have to provide `from` - every content sent through the wormhole needs a source. From 60c0503cf6f1bb10392a1ddf3821655448599ca2 Mon Sep 17 00:00:00 2001 From: Thorsten Date: Thu, 1 Aug 2019 21:45:08 +0200 Subject: [PATCH 6/6] build 2.1.6 --- dist/portal-vue.common.js | 4 ++-- dist/portal-vue.common.js.map | 2 +- dist/portal-vue.esm.js | 4 ++-- dist/portal-vue.esm.js.map | 2 +- dist/portal-vue.umd.js | 4 ++-- dist/portal-vue.umd.min.js | 4 ++-- package.json | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dist/portal-vue.common.js b/dist/portal-vue.common.js index 6e091c3..5f85799 100644 --- a/dist/portal-vue.common.js +++ b/dist/portal-vue.common.js @@ -2,7 +2,7 @@ /*! * portal-vue © Thorsten Lünborg, 2019 * - * Version: 2.1.5 + * Version: 2.1.6 * * LICENCE: MIT * @@ -138,7 +138,7 @@ var Wormhole = Vue.extend({ var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var to = transport.to, from = transport.from; - if (!to || !from) return; + if (!to || !from && force === false) return; if (!this.transports[to]) { return; diff --git a/dist/portal-vue.common.js.map b/dist/portal-vue.common.js.map index 766a5ee..82f62b6 100644 --- a/dist/portal-vue.common.js.map +++ b/dist/portal-vue.common.js.map @@ -1 +1 @@ -{"version":3,"file":"portal-vue.common.js","sources":["../src/utils/index.ts","../src/components/wormhole.ts","../src/components/portal.tsx","../src/components/portal-target.tsx","../src/components/mounting-portal.tsx","../src/index.ts"],"sourcesContent":["import { VNode } from 'vue'\nimport { Transport } from '../types'\n\nexport const inBrowser = typeof window !== 'undefined'\n\nexport function freeze(item: R[]): ReadonlyArray {\n if (Array.isArray(item) || typeof item === 'object') {\n return Object.freeze(item)\n }\n return item\n}\n\nexport function combinePassengers(\n transports: Transport[],\n slotProps = {}\n): Array {\n return transports.reduce(\n (passengers, transport) => {\n const temp = transport.passengers[0]\n const newPassengers =\n typeof temp === 'function'\n ? (temp(slotProps) as VNode[])\n : (transport.passengers as VNode[])\n return passengers.concat(newPassengers)\n },\n [] as Array\n )\n}\n\nexport function stableSort(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\nexport function pick(\n obj: T,\n keys: K[]\n): Pick {\n return keys.reduce(\n (acc, key) => {\n if (obj.hasOwnProperty(key)) {\n acc[key] = obj[key]\n }\n return acc\n },\n {} as Pick\n )\n}\n","import Vue from 'vue'\nimport { freeze, inBrowser, stableSort } from '../utils'\nimport {\n Transports,\n Transport,\n TransportInput,\n TransportVector,\n VMRegister,\n} from '../types'\n\nconst transports: Transports = {}\nconst targets: VMRegister = {}\nconst sources: VMRegister = {}\n\nexport const Wormhole = Vue.extend({\n data: () => ({\n transports,\n targets,\n sources,\n trackInstances: inBrowser,\n }),\n methods: {\n open(transport: TransportInput) {\n if (!inBrowser) return\n const { to, from, passengers, order = Infinity } = transport\n if (!to || !from || !passengers) return\n\n const newTransport = {\n to,\n from,\n passengers: freeze(passengers),\n order,\n } as Transport\n const keys = Object.keys(this.transports)\n\n if (keys.indexOf(to) === -1) {\n Vue.set(this.transports, to, [])\n }\n\n const currentIndex = this.$_getTransportIndex(newTransport)\n // Copying the array here so that the PortalTarget change event will actually contain two distinct arrays\n const newTransports = this.transports[to].slice(0)\n if (currentIndex === -1) {\n newTransports.push(newTransport)\n } else {\n newTransports[currentIndex] = newTransport\n }\n\n this.transports[to] = stableSort(\n newTransports,\n (a: Transport, b: Transport) => a.order - b.order\n )\n },\n\n close(transport: TransportVector, force = false) {\n const { to, from } = transport\n if (!to || !from) return\n if (!this.transports[to]) {\n return\n }\n\n if (force) {\n this.transports[to] = []\n } else {\n const index = this.$_getTransportIndex(transport)\n if (index >= 0) {\n // Copying the array here so that the PortalTarget change event will actually contain two distinct arrays\n const newTransports = this.transports[to].slice(0)\n newTransports.splice(index, 1)\n this.transports[to] = newTransports\n }\n }\n },\n registerTarget(target: string, vm: Vue, force?: boolean): void {\n if (!inBrowser) return\n if (this.trackInstances && !force && this.targets[target]) {\n console.warn(`[portal-vue]: Target ${target} already exists`)\n }\n this.$set(this.targets, target, Object.freeze([vm]))\n },\n unregisterTarget(target: string) {\n this.$delete(this.targets, target)\n },\n registerSource(source: string, vm: Vue, force?: boolean): void {\n if (!inBrowser) return\n if (this.trackInstances && !force && this.sources[source]) {\n console.warn(`[portal-vue]: source ${source} already exists`)\n }\n this.$set(this.sources, source, Object.freeze([vm]))\n },\n unregisterSource(source: string) {\n this.$delete(this.sources, source)\n },\n hasTarget(to: string) {\n return !!(this.targets[to] && this.targets[to][0])\n },\n hasSource(to: string) {\n return !!(this.sources[to] && this.sources[to][0])\n },\n hasContentFor(to: string) {\n return !!this.transports[to] && !!this.transports[to].length\n },\n // Internal\n $_getTransportIndex({ to, from }: TransportVector): number {\n for (const i in this.transports[to]) {\n if (this.transports[to][i].from === from) {\n return +i\n }\n }\n return -1\n },\n },\n})\n\nconst wormhole = new Wormhole(transports)\nexport { wormhole }\n","import Vue from 'vue'\nimport { VNode } from 'vue'\nimport { TransportInput, TransportVector } from '../types'\nimport { wormhole } from './wormhole'\n\nlet _id = 1\n\nexport default Vue.extend({\n name: 'portal',\n props: {\n disabled: { type: Boolean },\n name: { type: String, default: () => String(_id++) },\n order: { type: Number, default: 0 },\n slim: { type: Boolean },\n slotProps: { type: Object, default: () => ({}) },\n tag: { type: String, default: 'DIV' },\n to: {\n type: String,\n default: () => String(Math.round(Math.random() * 10000000)),\n },\n },\n created() {\n this.$nextTick(() => {\n wormhole.registerSource(this.name, this)\n })\n },\n mounted() {\n if (!this.disabled) {\n this.sendUpdate()\n }\n },\n\n updated() {\n if (this.disabled) {\n this.clear()\n } else {\n this.sendUpdate()\n }\n },\n\n beforeDestroy() {\n wormhole.unregisterSource(this.name)\n this.clear()\n },\n watch: {\n to(newValue: string, oldValue: string): void {\n oldValue && oldValue !== newValue && this.clear(oldValue)\n this.sendUpdate()\n },\n },\n\n methods: {\n clear(target?: string) {\n const closer: TransportVector = {\n from: this.name,\n to: target || this.to,\n }\n wormhole.close(closer)\n },\n normalizeSlots(): Function[] | VNode[] | undefined {\n return this.$scopedSlots.default\n ? [this.$scopedSlots.default]\n : this.$slots.default\n },\n normalizeOwnChildren(children: VNode[] | Function): VNode[] {\n return typeof children === 'function'\n ? children(this.slotProps)\n : children\n },\n sendUpdate() {\n const slotContent = this.normalizeSlots()\n if (slotContent) {\n const transport: TransportInput = {\n from: this.name,\n to: this.to,\n passengers: [...slotContent],\n order: this.order,\n }\n wormhole.open(transport)\n } else {\n this.clear()\n }\n },\n },\n\n render(h): VNode {\n const children: VNode[] | Function =\n this.$slots.default || this.$scopedSlots.default || []\n const Tag = this.tag\n if (children && this.disabled) {\n return children.length <= 1 && this.slim ? (\n this.normalizeOwnChildren(children)[0]\n ) : (\n {this.normalizeOwnChildren(children)}\n )\n } else {\n return this.slim\n ? h()\n : h(Tag, {\n class: { 'v-portal': true },\n style: { display: 'none' },\n key: 'v-portal-placeholder',\n })\n }\n },\n})\n","import Vue from 'vue'\nimport { VNode, PropOptions } from 'vue'\nimport { combinePassengers } from '@/utils'\nimport { Transport, PropWithComponent } from '../types'\n\nimport { wormhole } from '@/components/wormhole'\n\nexport default Vue.extend({\n name: 'portalTarget',\n props: {\n multiple: { type: Boolean, default: false },\n name: { type: String, required: true },\n slim: { type: Boolean, default: false },\n slotProps: { type: Object, default: () => ({}) },\n tag: { type: String, default: 'div' },\n transition: { type: [String, Object, Function] } as PropOptions<\n PropWithComponent\n >,\n },\n data() {\n return {\n transports: wormhole.transports,\n firstRender: true,\n }\n },\n created() {\n this.$nextTick(() => {\n wormhole.registerTarget(this.name, this)\n })\n },\n watch: {\n ownTransports() {\n this.$emit('change', this.children().length > 0)\n },\n name(newVal, oldVal) {\n /**\n * TODO\n * This should warn as well ...\n */\n wormhole.unregisterTarget(oldVal)\n wormhole.registerTarget(newVal, this)\n },\n },\n mounted() {\n if (this.transition) {\n this.$nextTick(() => {\n // only when we have a transition, because it causes a re-render\n this.firstRender = false\n })\n }\n },\n beforeDestroy() {\n wormhole.unregisterTarget(this.name)\n },\n\n computed: {\n ownTransports(): Transport[] {\n const transports: Transport[] = this.transports[this.name] || []\n if (this.multiple) {\n return transports\n }\n return transports.length === 0 ? [] : [transports[transports.length - 1]]\n },\n passengers(): VNode[] {\n return combinePassengers(this.ownTransports, this.slotProps)\n },\n },\n\n methods: {\n // can't be a computed prop because it has to \"react\" to $slot changes.\n children(): VNode[] {\n return this.passengers.length !== 0\n ? this.passengers\n : this.$scopedSlots.default\n ? (this.$scopedSlots.default(this.slotProps) as VNode[])\n : this.$slots.default || []\n },\n // can't be a computed prop because it has to \"react\" to this.children().\n noWrapper() {\n const noWrapper = this.slim && !this.transition\n if (noWrapper && this.children().length > 1) {\n console.warn(\n '[portal-vue]: PortalTarget with `slim` option received more than one child element.'\n )\n }\n return noWrapper\n },\n },\n render(h): VNode {\n const noWrapper = this.noWrapper()\n const children = this.children()\n const Tag = this.transition || this.tag\n\n return noWrapper\n ? children[0]\n : this.slim && !Tag\n ? h()\n : h(\n Tag,\n {\n props: {\n // if we have a transition component, pass the tag if it exists\n tag: this.transition && this.tag ? this.tag : undefined,\n },\n class: { 'vue-portal-target': true },\n },\n\n children\n )\n },\n})\n","import Vue from 'vue'\nimport { VNode, VueConstructor, PropOptions } from 'vue'\nimport Portal from './portal'\nimport PortalTarget from './portal-target'\nimport { wormhole } from './wormhole'\nimport { pick } from '@/utils'\n\nimport { PropWithComponent } from '../types'\n\nlet _id = 0\n\nexport type withPortalTarget = VueConstructor<\n Vue & {\n portalTarget: any\n }\n>\n\nconst portalProps = [\n 'disabled',\n 'name',\n 'order',\n 'slim',\n 'slotProps',\n 'tag',\n 'to',\n]\n\nconst targetProps = ['multiple', 'transition']\n\nexport default (Vue as withPortalTarget).extend({\n name: 'MountingPortal',\n inheritAttrs: false,\n props: {\n append: { type: [Boolean, String] },\n bail: {\n type: Boolean,\n },\n mountTo: { type: String, required: true },\n\n // Portal\n disabled: { type: Boolean },\n // name for the portal\n name: {\n type: String,\n default: () => 'mounted_' + String(_id++),\n },\n order: { type: Number, default: 0 },\n slim: { type: Boolean },\n slotProps: { type: Object, default: () => ({}) },\n tag: { type: String, default: 'DIV' },\n // name for the target\n to: {\n type: String,\n default: () => String(Math.round(Math.random() * 10000000)),\n },\n\n // Target\n multiple: { type: Boolean, default: false },\n targetSlim: { type: Boolean },\n targetSlotProps: { type: Object, default: () => ({}) },\n targetTag: { type: String, default: 'div' },\n transition: { type: [String, Object, Function] } as PropOptions<\n PropWithComponent\n >,\n },\n created() {\n if (typeof document === 'undefined') return\n let el: HTMLElement | null = document.querySelector(this.mountTo)\n\n if (!el) {\n console.error(\n `[portal-vue]: Mount Point '${this.mountTo}' not found in document`\n )\n return\n }\n\n const props = this.$props\n\n // Target already exists\n if (wormhole.targets[props.name]) {\n if (props.bail) {\n console.warn(`[portal-vue]: Target ${props.name} is already mounted.\n Aborting because 'bail: true' is set`)\n } else {\n this.portalTarget = wormhole.targets[props.name]\n }\n return\n }\n\n const { append } = props\n if (append) {\n const type = typeof append === 'string' ? append : 'DIV'\n const mountEl = document.createElement(type)\n el.appendChild(mountEl)\n el = mountEl\n }\n\n // get props for target from $props\n // we have to rename a few of them\n const _props = pick(this.$props, targetProps)\n _props.slim = this.targetSlim\n _props.tag = this.targetTag\n _props.slotProps = this.targetSlotProps\n _props.name = this.to\n\n this.portalTarget = new PortalTarget({\n el,\n parent: this.$parent || this,\n propsData: _props,\n })\n },\n\n beforeDestroy() {\n const target = this.portalTarget\n if (this.append) {\n const el = target.$el\n el.parentNode.removeChild(el)\n }\n target.$destroy()\n },\n\n render(h): VNode {\n if (!this.portalTarget) {\n console.warn(\"[portal-vue] Target wasn't mounted\")\n return h()\n }\n\n // if there's no \"manual\" scoped slot, so we create a ourselves\n if (!this.$scopedSlots.manual) {\n const props = pick(this.$props, portalProps)\n return h(\n Portal,\n {\n props: props,\n attrs: this.$attrs,\n on: this.$listeners,\n scopedSlots: this.$scopedSlots,\n },\n this.$slots.default\n )\n }\n\n // else, we render the scoped slot\n let content: VNode = (this.$scopedSlots.manual({\n to: this.to,\n }) as unknown) as VNode\n\n // if user used