/*! * portal-vue © Thorsten Lünborg, 2019 * * Version: 2.1.7 * * LICENCE: MIT * * https://github.com/linusborg/portal-vue * */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) : typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) : (factory((global.PortalVue = {}),global.Vue)); }(this, (function (exports,Vue) { 'use strict'; Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } var inBrowser = typeof window !== 'undefined'; function freeze(item) { if (Array.isArray(item) || _typeof(item) === 'object') { return Object.freeze(item); } return item; } function combinePassengers(transports) { var slotProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return transports.reduce(function (passengers, transport) { var temp = transport.passengers[0]; var newPassengers = typeof temp === 'function' ? temp(slotProps) : transport.passengers; return passengers.concat(newPassengers); }, []); } function stableSort(array, compareFn) { return array.map(function (v, idx) { return [idx, v]; }).sort(function (a, b) { return compareFn(a[1], b[1]) || a[0] - b[0]; }).map(function (c) { return c[1]; }); } function pick(obj, keys) { return keys.reduce(function (acc, key) { if (obj.hasOwnProperty(key)) { acc[key] = obj[key]; } return acc; }, {}); } var transports = {}; var targets = {}; var sources = {}; var Wormhole = Vue.extend({ data: function data() { return { transports: transports, targets: targets, sources: sources, trackInstances: inBrowser }; }, methods: { open: function open(transport) { if (!inBrowser) return; var to = transport.to, from = transport.from, passengers = transport.passengers, _transport$order = transport.order, order = _transport$order === void 0 ? Infinity : _transport$order; if (!to || !from || !passengers) return; var newTransport = { to: to, from: from, passengers: freeze(passengers), order: order }; var keys = Object.keys(this.transports); if (keys.indexOf(to) === -1) { Vue.set(this.transports, to, []); } var currentIndex = this.$_getTransportIndex(newTransport); // Copying the array here so that the PortalTarget change event will actually contain two distinct arrays var newTransports = this.transports[to].slice(0); if (currentIndex === -1) { newTransports.push(newTransport); } else { newTransports[currentIndex] = newTransport; } this.transports[to] = stableSort(newTransports, function (a, b) { return a.order - b.order; }); }, close: function close(transport) { var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var to = transport.to, from = transport.from; if (!to || !from && force === false) return; if (!this.transports[to]) { return; } if (force) { this.transports[to] = []; } else { var index = this.$_getTransportIndex(transport); if (index >= 0) { // Copying the array here so that the PortalTarget change event will actually contain two distinct arrays var newTransports = this.transports[to].slice(0); newTransports.splice(index, 1); this.transports[to] = newTransports; } } }, registerTarget: function registerTarget(target, vm, force) { if (!inBrowser) return; if (this.trackInstances && !force && this.targets[target]) { console.warn("[portal-vue]: Target ".concat(target, " already exists")); } this.$set(this.targets, target, Object.freeze([vm])); }, unregisterTarget: function unregisterTarget(target) { this.$delete(this.targets, target); }, registerSource: function registerSource(source, vm, force) { if (!inBrowser) return; if (this.trackInstances && !force && this.sources[source]) { console.warn("[portal-vue]: source ".concat(source, " already exists")); } this.$set(this.sources, source, Object.freeze([vm])); }, unregisterSource: function unregisterSource(source) { this.$delete(this.sources, source); }, hasTarget: function hasTarget(to) { return !!(this.targets[to] && this.targets[to][0]); }, hasSource: function hasSource(to) { return !!(this.sources[to] && this.sources[to][0]); }, hasContentFor: function hasContentFor(to) { return !!this.transports[to] && !!this.transports[to].length; }, // Internal $_getTransportIndex: function $_getTransportIndex(_ref) { var to = _ref.to, from = _ref.from; for (var i in this.transports[to]) { if (this.transports[to][i].from === from) { return +i; } } return -1; } } }); var wormhole = new Wormhole(transports); var _id = 1; var Portal = Vue.extend({ name: 'portal', props: { disabled: { type: Boolean }, name: { type: String, default: function _default() { return String(_id++); } }, order: { type: Number, default: 0 }, slim: { type: Boolean }, slotProps: { type: Object, default: function _default() { return {}; } }, tag: { type: String, default: 'DIV' }, to: { type: String, default: function _default() { return String(Math.round(Math.random() * 10000000)); } } }, created: function created() { var _this = this; this.$nextTick(function () { wormhole.registerSource(_this.name, _this); }); }, mounted: function mounted() { if (!this.disabled) { this.sendUpdate(); } }, updated: function updated() { if (this.disabled) { this.clear(); } else { this.sendUpdate(); } }, beforeDestroy: function beforeDestroy() { wormhole.unregisterSource(this.name); this.clear(); }, watch: { to: function to(newValue, oldValue) { oldValue && oldValue !== newValue && this.clear(oldValue); this.sendUpdate(); } }, methods: { clear: function clear(target) { var closer = { from: this.name, to: target || this.to }; wormhole.close(closer); }, normalizeSlots: function normalizeSlots() { return this.$scopedSlots.default ? [this.$scopedSlots.default] : this.$slots.default; }, normalizeOwnChildren: function normalizeOwnChildren(children) { return typeof children === 'function' ? children(this.slotProps) : children; }, sendUpdate: function sendUpdate() { var slotContent = this.normalizeSlots(); if (slotContent) { var transport = { from: this.name, to: this.to, passengers: _toConsumableArray(slotContent), order: this.order }; wormhole.open(transport); } else { this.clear(); } } }, render: function render(h) { var children = this.$slots.default || this.$scopedSlots.default || []; var Tag = this.tag; if (children && this.disabled) { return children.length <= 1 && this.slim ? this.normalizeOwnChildren(children)[0] : h(Tag, [this.normalizeOwnChildren(children)]); } else { return this.slim ? h() : h(Tag, { class: { 'v-portal': true }, style: { display: 'none' }, key: 'v-portal-placeholder' }); } } }); var PortalTarget = Vue.extend({ name: 'portalTarget', props: { multiple: { type: Boolean, default: false }, name: { type: String, required: true }, slim: { type: Boolean, default: false }, slotProps: { type: Object, default: function _default() { return {}; } }, tag: { type: String, default: 'div' }, transition: { type: [String, Object, Function] } }, data: function data() { return { transports: wormhole.transports, firstRender: true }; }, created: function created() { var _this = this; this.$nextTick(function () { wormhole.registerTarget(_this.name, _this); }); }, watch: { ownTransports: function ownTransports() { this.$emit('change', this.children().length > 0); }, name: function name(newVal, oldVal) { /** * TODO * This should warn as well ... */ wormhole.unregisterTarget(oldVal); wormhole.registerTarget(newVal, this); } }, mounted: function mounted() { var _this2 = this; if (this.transition) { this.$nextTick(function () { // only when we have a transition, because it causes a re-render _this2.firstRender = false; }); } }, beforeDestroy: function beforeDestroy() { wormhole.unregisterTarget(this.name); }, computed: { ownTransports: function ownTransports() { var transports = this.transports[this.name] || []; if (this.multiple) { return transports; } return transports.length === 0 ? [] : [transports[transports.length - 1]]; }, passengers: function passengers() { return combinePassengers(this.ownTransports, this.slotProps); } }, methods: { // can't be a computed prop because it has to "react" to $slot changes. children: function children() { return this.passengers.length !== 0 ? this.passengers : this.$scopedSlots.default ? this.$scopedSlots.default(this.slotProps) : this.$slots.default || []; }, // can't be a computed prop because it has to "react" to this.children(). noWrapper: function noWrapper() { var noWrapper = this.slim && !this.transition; if (noWrapper && this.children().length > 1) { console.warn('[portal-vue]: PortalTarget with `slim` option received more than one child element.'); } return noWrapper; } }, render: function render(h) { var noWrapper = this.noWrapper(); var children = this.children(); var Tag = this.transition || this.tag; return noWrapper ? children[0] : this.slim && !Tag ? h() : h(Tag, { props: { // if we have a transition component, pass the tag if it exists tag: this.transition && this.tag ? this.tag : undefined }, class: { 'vue-portal-target': true } }, children); } }); var _id$1 = 0; var portalProps = ['disabled', 'name', 'order', 'slim', 'slotProps', 'tag', 'to']; var targetProps = ['multiple', 'transition']; var MountingPortal = Vue.extend({ name: 'MountingPortal', inheritAttrs: false, props: { append: { type: [Boolean, String] }, bail: { type: Boolean }, mountTo: { type: String, required: true }, // Portal disabled: { type: Boolean }, // name for the portal name: { type: String, default: function _default() { return 'mounted_' + String(_id$1++); } }, order: { type: Number, default: 0 }, slim: { type: Boolean }, slotProps: { type: Object, default: function _default() { return {}; } }, tag: { type: String, default: 'DIV' }, // name for the target to: { type: String, default: function _default() { return String(Math.round(Math.random() * 10000000)); } }, // Target multiple: { type: Boolean, default: false }, targetSlim: { type: Boolean }, targetSlotProps: { type: Object, default: function _default() { return {}; } }, targetTag: { type: String, default: 'div' }, transition: { type: [String, Object, Function] } }, created: function created() { if (typeof document === 'undefined') return; var el = document.querySelector(this.mountTo); if (!el) { console.error("[portal-vue]: Mount Point '".concat(this.mountTo, "' not found in document")); return; } var props = this.$props; // Target already exists if (wormhole.targets[props.name]) { if (props.bail) { console.warn("[portal-vue]: Target ".concat(props.name, " is already mounted.\n Aborting because 'bail: true' is set")); } else { this.portalTarget = wormhole.targets[props.name]; } return; } var append = props.append; if (append) { var type = typeof append === 'string' ? append : 'DIV'; var mountEl = document.createElement(type); el.appendChild(mountEl); el = mountEl; } // get props for target from $props // we have to rename a few of them var _props = pick(this.$props, targetProps); _props.slim = this.targetSlim; _props.tag = this.targetTag; _props.slotProps = this.targetSlotProps; _props.name = this.to; this.portalTarget = new PortalTarget({ el: el, parent: this.$parent || this, propsData: _props }); }, beforeDestroy: function beforeDestroy() { var target = this.portalTarget; if (this.append) { var el = target.$el; el.parentNode.removeChild(el); } target.$destroy(); }, render: function render(h) { if (!this.portalTarget) { console.warn("[portal-vue] Target wasn't mounted"); return h(); } // if there's no "manual" scoped slot, so we create a ourselves if (!this.$scopedSlots.manual) { var props = pick(this.$props, portalProps); return h(Portal, { props: props, attrs: this.$attrs, on: this.$listeners, scopedSlots: this.$scopedSlots }, this.$slots.default); } // else, we render the scoped slot var content = this.$scopedSlots.manual({ to: this.to }); // if user used