diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..178135c
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+/dist/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f2e1255
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+node-debug.log
+TODO.md
+yarn-error.log
+yarn.lock
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad278e9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# PortalVue
+
+> A Portal Component for Vuejs, to render DOM outside of a component, anywhere in the document.
+
+
+
+
+
+
+For more detailed documentation and additional Information, please visit the docs
+
+## Installation
+
+```
+npm install -g portal-vue
+
+// in .js
+import PortalVue from 'portal-vue'
+Vue.use(PortalVue)
+```
+
+
+
+## Usage
+
+```html
+
+
This slot content will be rendered wherever the with name 'destination'
+ is located.
+
+
+
+
+
+```
diff --git a/build/rollup.conf.prod.js b/build/rollup.conf.prod.js
new file mode 100644
index 0000000..5808672
--- /dev/null
+++ b/build/rollup.conf.prod.js
@@ -0,0 +1,32 @@
+import babel from 'rollup-plugin-babel'
+import vue from 'rollup-plugin-vue'
+import commonjs from 'rollup-plugin-commonjs'
+import nodeResolve from 'rollup-plugin-node-resolve'
+
+const babelConfig = {
+ // runtimeHelpers: true,
+ exclude: 'node_modules/**',
+}
+
+const nodeResolveOptions = {
+ module: true, jsnext: true,
+ extensions: ['.js', '.vue'],
+}
+
+export default {
+ entry: './src/index.js',
+ external: ['vue'],
+ globals: {
+ vue: 'Vue',
+ },
+ format: 'umd',
+ moduleName: 'PortalVue',
+ dest: './dist/portal-vue.js', // equivalent to --output
+ sourceMap: true,
+ plugins: [
+ nodeResolve(nodeResolveOptions),
+ vue({ compileTemplate: false }),
+ commonjs(),
+ babel(babelConfig),
+ ],
+}
diff --git a/build/webpack.conf.js b/build/webpack.conf.js
new file mode 100644
index 0000000..a4f9e59
--- /dev/null
+++ b/build/webpack.conf.js
@@ -0,0 +1,80 @@
+var path = require('path')
+var webpack = require('webpack')
+var FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
+
+const config = {
+ entry: {
+ example: path.resolve(__dirname, '../example/index.js'),
+ vendor: ['vue'],
+ },
+ output: {
+ path: path.resolve(__dirname, '../example'),
+ publicPath: '/',
+ library: 'VuePortal',
+ libraryTarget: 'umd',
+ filename: '[name].build.js',
+ },
+ resolve: {
+ extensions: ['.js', '.json', '.vue'],
+ alias: {
+ 'vue$': 'vue/dist/vue.common',
+ },
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|vue)$/,
+ loader: 'eslint-loader',
+ enforce: 'pre',
+ exclude: /node_modules/,
+ options: {
+ formatter: require('eslint-friendly-formatter'),
+ },
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ exclude: path.resolve(__dirname, '../node_modules'),
+ },
+ {
+ test: /\.css$/,
+ use: [
+ 'style-loader',
+ {
+ loader: 'css-loader',
+ },
+ ],
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
+ exclude: /node_modules/,
+ },
+ ],
+ },
+ plugins: [
+ new FriendlyErrorsWebpackPlugin(),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: process.env.NODE_ENV ? JSON.stringify(process.env.NODE_ENV) : "'development'",
+ },
+ }),
+ ],
+ devtool: 'source-map',
+ performance: {
+ hints: false,
+ },
+ devServer: {
+ contentBase: 'example/',
+ inline: true,
+ hot: true,
+ quiet: true,
+ stats: {
+ colors: true,
+ chunks: false,
+ },
+ },
+}
+
+module.exports = config
diff --git a/build/webpack.test.conf.js b/build/webpack.test.conf.js
new file mode 100644
index 0000000..5d995b8
--- /dev/null
+++ b/build/webpack.test.conf.js
@@ -0,0 +1,9 @@
+var merge = require('webpack-merge')
+
+var devConfig = require('./webpack.conf.js')
+
+var config = merge(devConfig, {
+ devtool: '#inline-source-map',
+})
+
+module.exports = config
diff --git a/dist/portal-vue.js b/dist/portal-vue.js
new file mode 100644
index 0000000..d2524ce
--- /dev/null
+++ b/dist/portal-vue.js
@@ -0,0 +1,293 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) :
+ typeof define === 'function' && define.amd ? define(['vue'], factory) :
+ (global.PortalVue = factory(global.Vue));
+}(this, (function (Vue) { 'use strict';
+
+Vue = 'default' in Vue ? Vue['default'] : Vue;
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+function extractAttributes(el) {
+ var map = el.hasAttributes() ? el.attributes : [];
+ var attrs = {};
+ for (var i = 0; i < map.length; i++) {
+ var attr = map[i];
+ if (attr.value) {
+ attrs[attr.name] = attr.value === '' ? true : attr.value;
+ }
+ }
+ return attrs;
+}
+
+function freeze(item) {
+ if (Array.isArray(item) || (typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object') {
+ return Object.freeze(item);
+ }
+ return item;
+}
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var routes = {};
+
+var Wormhole = function () {
+ function Wormhole(routes) {
+ _classCallCheck(this, Wormhole);
+
+ this.routes = routes;
+ this.clearQueue = [];
+ this.updateQueue = [];
+ this.runScheduled = false;
+ }
+
+ _createClass(Wormhole, [{
+ key: 'send',
+ value: function send(name, passengers) {
+ var job = { name: name, passengers: passengers };
+ this.updateQueue.push(job);
+ this._scheduleRun();
+ }
+ }, {
+ key: 'close',
+ value: function close(name) {
+ var job = { name: name };
+ this.clearQueue.push(job);
+ this._scheduleRun();
+ }
+ }, {
+ key: '_scheduleRun',
+ value: function _scheduleRun() {
+ if (!this.runScheduled) {
+ this.runScheduled = true;
+
+ setTimeout(this._runQueue.bind(this), 0);
+ }
+ }
+ }, {
+ key: '_runQueue',
+ value: function _runQueue() {
+ var _this = this;
+
+ var keys = Object.keys(this.routes);
+
+ this.clearQueue.forEach(function (_ref) {
+ var name = _ref.name;
+
+ if (keys.includes(name)) {
+ _this.routes[name] = undefined;
+ }
+ });
+ this.clearQueue = [];
+
+ this.updateQueue.forEach(function (_ref2) {
+ var name = _ref2.name,
+ passengers = _ref2.passengers;
+
+ if (keys.includes(name)) {
+ _this.routes[name] = freeze(passengers);
+ } else {
+ Vue.set(_this.routes, name, freeze(passengers));
+ }
+ });
+ this.updateQueue = [];
+
+ this.runScheduled = false;
+ }
+ }]);
+
+ return Wormhole;
+}();
+var wormhole = new Wormhole(routes);
+
+var Target = {
+ name: 'portalTarget',
+ props: {
+ attributes: { type: Object },
+ name: { type: String, required: true },
+ slim: { type: Boolean, default: false },
+ tag: { type: String, default: 'div' }
+ },
+ data: function data() {
+ return {
+ routes: routes
+ };
+ },
+ mounted: function mounted() {
+ this.updateAttributes();
+ },
+ updated: function updated() {
+ this.updateAttributes();
+ },
+ beforeDestroy: function beforeDestroy() {
+ this.$el.innerHTML = '';
+ },
+
+
+ methods: {
+ updateAttributes: function updateAttributes() {
+ if (this.attributes) {
+ var attrs = this.attributes;
+ var el = this.$el;
+
+ if (attrs.class) {
+ attrs.class.trim().split(' ').forEach(function (klass) {
+ el.classList.add(klass);
+ });
+ delete attrs.class;
+ }
+
+ var keys = Object.keys(attrs);
+
+ for (var i = 0; i < keys.length; i++) {
+ el.setAttribute(keys[i], attrs[keys[i]]);
+ }
+ }
+ }
+ },
+ computed: {
+ passengers: function passengers() {
+ return this.routes[this.name] || null;
+ },
+ renderSlim: function renderSlim() {
+ var passengers = this.passengers || [];
+ return passengers.length === 1 && !this.attributes && this.slim;
+ }
+ },
+
+ render: function render(h) {
+ var children = this.passengers || [];
+
+ if (this.renderSlim) {
+ return children[0];
+ } else {
+ return h(this.tag, {
+ class: { 'vue-portal-target': true }
+ }, children);
+ }
+ }
+};
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+var Portal = {
+ name: 'portal',
+ props: {
+ disabled: { type: Boolean, default: false },
+ slim: { type: Boolean, default: false },
+ tag: { type: [String], default: 'DIV' },
+ targetEl: { type: [String, HTMLElement] },
+ to: { type: String, required: true }
+ },
+
+ mounted: function mounted() {
+ if (this.targetEl) {
+ this.mountToTarget();
+ }
+ if (!this.disabled) {
+ this.sendUpdate();
+ }
+ },
+ updated: function updated() {
+ if (this.disabled) {
+ this.clear();
+ } else {
+ this.sendUpdate();
+ }
+ },
+ beforeDestroy: function beforeDestroy() {
+ this.clear();
+ if (this.mountedComp) {
+ this.mountedComp.$destroy();
+ }
+ },
+
+
+ watch: {
+ to: function to(newValue, oldValue) {
+ oldValue && this.clear(oldValue);
+ this.sendUpdate();
+ },
+ targetEl: function targetEl(newValue, oldValue) {
+ this.mountToTarget();
+ }
+ },
+
+ methods: {
+ sendUpdate: function sendUpdate() {
+ if (this.to) {
+ wormhole.send(this.to, [].concat(_toConsumableArray(this.$slots.default)));
+ } else {
+ console.warn('[vue-portal]: You have to define a targte via the `to` prop.');
+ }
+ },
+ clear: function clear(target) {
+ wormhole.close(target || this.to);
+ },
+ mountToTarget: function mountToTarget() {
+ var el = void 0;
+ var target = this.targetEl;
+
+ if (target instanceof HTMLElement) {
+ el = target;
+ } else if (typeof target === 'string') {
+ el = document.querySelector(this.targetEl);
+ } else {
+ console.warn('[vue-portal]: value of targetEl must eb of type String or HTMLElement');
+ return;
+ }
+
+ var attributes = extractAttributes(el);
+
+ if (el) {
+ var _target = new Vue(_extends({}, Target, {
+ propsData: {
+ name: this.to || Math.round(Math.random() * 10000000),
+ tag: el.tagName,
+ attributes: attributes
+ }
+ }));
+ _target.$mount(el);
+ this.mountedComp = _target;
+ } else {
+ console.warn('[vue-portal]: The specified targetEl ' + this.targetEl + ' was not found');
+ }
+ }
+ },
+
+ render: function render(h) {
+ var children = this.$slots.default;
+
+ if (children.length && this.disabled) {
+ return children.length <= 1 && this.slim ? children[0] : h(this.tag, children);
+ } else {
+ return h(this.tag, { class: { 'v-portal': true }, style: { display: 'none' }, key: 'v-portal-placeholder' });
+ }
+ }
+};
+
+function install(Vue$$1) {
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ Vue$$1.component(opts.portalName || 'portal', Portal);
+ Vue$$1.component(opts.portalTargetName || 'portal-target', Target);
+}
+if (typeof window !== 'undefined' && window.Vue) {
+ console.log('auto install!');
+ window.Vue.use({ install: install });
+}
+
+var index = {
+ install: install,
+ Portal: Portal,
+ PortalTarget: Target
+};
+
+return index;
+
+})));
+//# sourceMappingURL=portal-vue.js.map
diff --git a/dist/portal-vue.js.map b/dist/portal-vue.js.map
new file mode 100644
index 0000000..78f4612
--- /dev/null
+++ b/dist/portal-vue.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"portal-vue.js","sources":["../src/utils.js","../src/components/wormhole.js","../src/components/portal-target.vue","../src/components/portal.vue","../src/index.js"],"sourcesContent":["export function extractAttributes (el) {\n const map = el.hasAttributes() ? el.attributes : []\n const attrs = {}\n for (let i = 0; i < map.length; i++) {\n const attr = map[i]\n if (attr.value) {\n attrs[attr.name] = attr.value === '' ? true : attr.value\n }\n }\n return attrs\n}\n\nexport function freeze (item) {\n if (Array.isArray(item) || typeof item === 'object') {\n return Object.freeze(item)\n }\n return item\n}\n","import Vue from 'vue'\nimport { freeze } from '../utils'\nconst routes = {}\n\nexport { routes }\n\nexport class Wormhole {\n constructor (routes) {\n this.routes = routes\n this.clearQueue = []\n this.updateQueue = []\n this.runScheduled = false\n }\n\n send (name, passengers) {\n const job = { name, passengers }\n this.updateQueue.push(job)\n this._scheduleRun()\n }\n\n close (name) {\n const job = { name }\n this.clearQueue.push(job)\n this._scheduleRun()\n }\n\n _scheduleRun () {\n if (!this.runScheduled) {\n this.runScheduled = true\n\n setTimeout(this._runQueue.bind(this), 0)\n }\n }\n\n _runQueue () {\n const keys = Object.keys(this.routes)\n\n this.clearQueue.forEach(({ name }) => {\n if (keys.includes(name)) {\n this.routes[name] = undefined\n }\n })\n this.clearQueue = []\n\n this.updateQueue.forEach(({ name, passengers }) => {\n if (keys.includes(name)) {\n this.routes[name] = freeze(passengers)\n } else {\n Vue.set(this.routes, name, freeze(passengers))\n }\n })\n this.updateQueue = []\n\n this.runScheduled = false\n }\n\n}\nconst wormhole = new Wormhole(routes)\nexport default wormhole\n","\n","\n","import Portal from './components/portal.vue'\nimport PortalTarget from './components/portal-target.vue'\n\nfunction install (Vue, opts = {}) {\n Vue.component(opts.portalName || 'portal', Portal)\n Vue.component(opts.portalTargetName || 'portal-target', PortalTarget)\n}\nif (typeof window !== 'undefined' && window.Vue) {\n console.log('auto install!')\n window.Vue.use({ install: install })\n}\n\nexport default {\n install,\n Portal,\n PortalTarget,\n}\n"],"names":["extractAttributes","el","map","hasAttributes","attributes","attrs","i","length","attr","value","name","freeze","item","Array","isArray","Object","routes","Wormhole","clearQueue","updateQueue","runScheduled","passengers","job","push","_scheduleRun","_runQueue","bind","keys","forEach","includes","undefined","set","wormhole","type","String","required","Boolean","default","updateAttributes","$el","innerHTML","class","trim","split","klass","classList","add","setAttribute","slim","h","children","renderSlim","tag","HTMLElement","targetEl","mountToTarget","disabled","sendUpdate","clear","mountedComp","$destroy","newValue","oldValue","to","send","$slots","warn","target","close","document","querySelector","Vue","Target","Math","round","random","tagName","$mount","style","display","key","install","opts","component","portalName","Portal","portalTargetName","PortalTarget","window","log","use"],"mappings":";;;;;;;;;;AAAA,AAAO,SAASA,iBAAT,CAA4BC,EAA5B,EAAgC;MAC/BC,MAAMD,GAAGE,aAAH,KAAqBF,GAAGG,UAAxB,GAAqC,EAAjD;MACMC,QAAQ,EAAd;OACK,IAAIC,IAAI,CAAb,EAAgBA,IAAIJ,IAAIK,MAAxB,EAAgCD,GAAhC,EAAqC;QAC7BE,OAAON,IAAII,CAAJ,CAAb;QACIE,KAAKC,KAAT,EAAgB;YACRD,KAAKE,IAAX,IAAmBF,KAAKC,KAAL,KAAe,EAAf,GAAoB,IAApB,GAA2BD,KAAKC,KAAnD;;;SAGGJ,KAAP;;;AAGF,AAAO,SAASM,MAAT,CAAiBC,IAAjB,EAAuB;MACxBC,MAAMC,OAAN,CAAcF,IAAd,KAAuB,QAAOA,IAAP,yCAAOA,IAAP,OAAgB,QAA3C,EAAqD;WAC5CG,OAAOJ,MAAP,CAAcC,IAAd,CAAP;;SAEKA,IAAP;;;;;;;AChBF,AACA,AACA,IAAMI,SAAS,EAAf;;AAEA,AAEA,IAAaC,QAAb;oBACeD,MAAb,EAAqB;;;SACdA,MAAL,GAAcA,MAAd;SACKE,UAAL,GAAkB,EAAlB;SACKC,WAAL,GAAmB,EAAnB;SACKC,YAAL,GAAoB,KAApB;;;;;yBAGIV,IARR,EAQcW,UARd,EAQ0B;UAChBC,MAAM,EAAEZ,UAAF,EAAQW,sBAAR,EAAZ;WACKF,WAAL,CAAiBI,IAAjB,CAAsBD,GAAtB;WACKE,YAAL;;;;0BAGKd,IAdT,EAce;UACLY,MAAM,EAAEZ,UAAF,EAAZ;WACKQ,UAAL,CAAgBK,IAAhB,CAAqBD,GAArB;WACKE,YAAL;;;;mCAGc;UACV,CAAC,KAAKJ,YAAV,EAAwB;aACjBA,YAAL,GAAoB,IAApB;;mBAEW,KAAKK,SAAL,CAAeC,IAAf,CAAoB,IAApB,CAAX,EAAsC,CAAtC;;;;;gCAIS;;;UACLC,OAAOZ,OAAOY,IAAP,CAAY,KAAKX,MAAjB,CAAb;;WAEKE,UAAL,CAAgBU,OAAhB,CAAwB,gBAAc;YAAXlB,IAAW,QAAXA,IAAW;;YAChCiB,KAAKE,QAAL,CAAcnB,IAAd,CAAJ,EAAyB;gBAClBM,MAAL,CAAYN,IAAZ,IAAoBoB,SAApB;;OAFJ;WAKKZ,UAAL,GAAkB,EAAlB;;WAEKC,WAAL,CAAiBS,OAAjB,CAAyB,iBAA0B;YAAvBlB,IAAuB,SAAvBA,IAAuB;YAAjBW,UAAiB,SAAjBA,UAAiB;;YAC7CM,KAAKE,QAAL,CAAcnB,IAAd,CAAJ,EAAyB;gBAClBM,MAAL,CAAYN,IAAZ,IAAoBC,OAAOU,UAAP,CAApB;SADF,MAEO;cACDU,GAAJ,CAAQ,MAAKf,MAAb,EAAqBN,IAArB,EAA2BC,OAAOU,UAAP,CAA3B;;OAJJ;WAOKF,WAAL,GAAmB,EAAnB;;WAEKC,YAAL,GAAoB,KAApB;;;;;;AAIJ,IAAMY,WAAW,IAAIf,QAAJ,CAAaD,MAAb,CAAjB,CACA;;ACvDA,aAAe;QACP,cADO;SAEN;gBACO,EAAEiB,MAAMlB,MAAR,EADP;UAEC,EAAEkB,MAAMC,MAAR,EAAgBC,UAAU,IAA1B,EAFD;UAGC,EAAEF,MAAMG,OAAR,EAAiBC,SAAS,KAA1B,EAHD;SAIA,EAAEJ,MAAMC,MAAR,EAAgBG,SAAS,KAAzB;GANM;MAAA,kBAQL;WACC;;KAAP;GATW;SAAA,qBAcF;SACJC,gBAAL;GAfW;SAAA,qBAiBF;SACJA,gBAAL;GAlBW;eAAA,2BAoBI;SACVC,GAAL,CAASC,SAAT,GAAqB,EAArB;GArBW;;;WAwBJ;oBAAA,8BACa;UACd,KAAKpC,UAAT,EAAqB;YACbC,QAAQ,KAAKD,UAAnB;YACMH,KAAK,KAAKsC,GAAhB;;YAGIlC,MAAMoC,KAAV,EAAiB;gBACTA,KAAN,CAAYC,IAAZ,GAAmBC,KAAnB,CAAyB,GAAzB,EAA8Bf,OAA9B,CAAsC,UAACgB,KAAD,EAAW;eAC5CC,SAAH,CAAaC,GAAb,CAAiBF,KAAjB;WADF;iBAGOvC,MAAMoC,KAAb;;;YAGId,OAAOZ,OAAOY,IAAP,CAAYtB,KAAZ,CAAb;;aAEK,IAAIC,IAAI,CAAb,EAAgBA,IAAIqB,KAAKpB,MAAzB,EAAiCD,GAAjC,EAAsC;aACjCyC,YAAH,CAAgBpB,KAAKrB,CAAL,CAAhB,EAAyBD,MAAMsB,KAAKrB,CAAL,CAAN,CAAzB;;;;GAzCK;YA8CH;cAAA,wBACM;aACL,KAAKU,MAAL,CAAY,KAAKN,IAAjB,KAA0B,IAAjC;KAFM;cAAA,wBAIM;UACNW,aAAa,KAAKA,UAAL,IAAmB,EAAtC;aACOA,WAAWd,MAAX,KAAsB,CAAtB,IAA2B,CAAC,KAAKH,UAAjC,IAA+C,KAAK4C,IAA3D;;GApDS;;QAAA,kBAwDLC,CAxDK,EAwDF;QACHC,WAAW,KAAK7B,UAAL,IAAmB,EAApC;;QAEI,KAAK8B,UAAT,EAAqB;aACZD,SAAS,CAAT,CAAP;KADF,MAEO;aACED,EAAE,KAAKG,GAAP,EAAY;eACV,EAAE,qBAAqB,IAAvB;OADF,EAEJF,QAFI,CAAP;;;CA9DN;;;;;;ACFA,AACA,AACA,AACA,AAEA,aAAe;QACP,QADO;SAEN;cAEK,EAAEjB,MAAMG,OAAR,EAAiBC,SAAS,KAA1B,EAFL;UAGC,EAAEJ,MAAMG,OAAR,EAAiBC,SAAS,KAA1B,EAHD;SAIA,EAAEJ,MAAM,CAACC,MAAD,CAAR,EAAkBG,SAAS,KAA3B,EAJA;cAKK,EAAEJ,MAAM,CAACC,MAAD,EAASmB,WAAT,CAAR,EALL;QAMD,EAAEpB,MAAMC,MAAR,EAAgBC,UAAU,IAA1B;GARO;;SAAA,qBAWF;QACL,KAAKmB,QAAT,EAAmB;WACZC,aAAL;;QAEE,CAAC,KAAKC,QAAV,EAAoB;WACbC,UAAL;;GAhBS;SAAA,qBAoBF;QACL,KAAKD,QAAT,EAAmB;WACZE,KAAL;KADF,MAEO;WACAD,UAAL;;GAxBS;eAAA,2BA4BI;SACVC,KAAL;QACI,KAAKC,WAAT,EAAsB;WACfA,WAAL,CAAiBC,QAAjB;;GA/BS;;;SAmCN;MAAA,cACDC,QADC,EACSC,QADT,EACmB;kBACV,KAAKJ,KAAL,CAAWI,QAAX,CAAZ;WACKL,UAAL;KAHG;YAAA,oBAKKI,QALL,EAKeC,QALf,EAKyB;WACvBP,aAAL;;GAzCS;;WA6CJ;cAAA,wBAEO;UACR,KAAKQ,EAAT,EAAa;iBACFC,IAAT,CAAc,KAAKD,EAAnB,+BAA2B,KAAKE,MAAL,CAAY5B,OAAvC;OADF,MAEO;gBACG6B,IAAR,CAAa,8DAAb;;KANG;SAAA,iBAUAC,MAVA,EAUQ;eACJC,KAAT,CAAeD,UAAU,KAAKJ,EAA9B;KAXK;iBAAA,2BAcU;UACX9D,WAAJ;UACMkE,SAAS,KAAKb,QAApB;;UAEIa,kBAAkBd,WAAtB,EAAmC;aAC5Bc,MAAL;OADF,MAEO,IAAI,OAAOA,MAAP,KAAkB,QAAtB,EAAgC;aAChCE,SAASC,aAAT,CAAuB,KAAKhB,QAA5B,CAAL;OADK,MAEA;gBACGY,IAAR,CAAa,uEAAb;;;;UAII9D,aAAaJ,kBAAkBC,EAAlB,CAAnB;;UAEIA,EAAJ,EAAQ;YACAkE,UAAS,IAAII,GAAJ,cACVC,MADU;qBAEF;kBACH,KAAKT,EAAL,IAAWU,KAAKC,KAAL,CAAWD,KAAKE,MAAL,KAAgB,QAA3B,CADR;iBAEJ1E,GAAG2E,OAFC;;;WAFb;gBAQOC,MAAP,CAAc5E,EAAd;aACK0D,WAAL,GAAmBQ,OAAnB;OAVF,MAWO;gBACGD,IAAR,CAAa,0CAA0C,KAAKZ,QAA/C,GAA0D,gBAAvE;;;GAtFO;;QAAA,kBA2FLL,CA3FK,EA2FF;QACHC,WAAW,KAAKe,MAAL,CAAY5B,OAA7B;;QAEIa,SAAS3C,MAAT,IAAmB,KAAKiD,QAA5B,EAAsC;aAC7BN,SAAS3C,MAAT,IAAmB,CAAnB,IAAwB,KAAKyC,IAA7B,GACHE,SAAS,CAAT,CADG,GAEHD,EAAE,KAAKG,GAAP,EAAYF,QAAZ,CAFJ;KADF,MAIO;aACED,EAAE,KAAKG,GAAP,EAAY,EAAEX,OAAO,EAAE,YAAY,IAAd,EAAT,EAA+BqC,OAAO,EAAEC,SAAS,MAAX,EAAtC,EAA2DC,KAAK,sBAAhE,EAAZ,CAAP;;;CAnGN;;ACHA,SAASC,OAAT,CAAkBV,MAAlB,EAAkC;MAAXW,IAAW,uEAAJ,EAAI;;SAC5BC,SAAJ,CAAcD,KAAKE,UAAL,IAAmB,QAAjC,EAA2CC,MAA3C;SACIF,SAAJ,CAAcD,KAAKI,gBAAL,IAAyB,eAAvC,EAAwDC,MAAxD;;AAEF,IAAI,OAAOC,MAAP,KAAkB,WAAlB,IAAiCA,OAAOjB,GAA5C,EAAiD;UACvCkB,GAAR,CAAY,eAAZ;SACOlB,GAAP,CAAWmB,GAAX,CAAe,EAAET,SAASA,OAAX,EAAf;;;AAGF,YAAe;kBAAA;gBAAA;;CAAf;;;;"}
\ No newline at end of file
diff --git a/dist/portal-vue.min.js b/dist/portal-vue.min.js
new file mode 100644
index 0000000..7e1907e
--- /dev/null
+++ b/dist/portal-vue.min.js
@@ -0,0 +1 @@
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("vue")):"function"==typeof define&&define.amd?define(["vue"],e):t.PortalVue=e(t.Vue)}(this,function(t){"use strict";function e(t){for(var e=t.hasAttributes()?t.attributes:[],n={},r=0;r1&&void 0!==arguments[1]?arguments[1]:{};t.component(e.portalName||"portal",h),t.component(e.portalTargetName||"portal-target",d)}t="default"in t?t.default:t;var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/portal-vue-logo.gif b/docs/assets/portal-vue-logo.gif
new file mode 100644
index 0000000..2d37147
Binary files /dev/null and b/docs/assets/portal-vue-logo.gif differ
diff --git a/docs/config.js b/docs/config.js
new file mode 100644
index 0000000..eb19fa4
--- /dev/null
+++ b/docs/config.js
@@ -0,0 +1,21 @@
+/* global self */
+self.$config = {
+ landing: true,
+ home: 'getting-started.md',
+ nav: [
+ { title: 'PortalVue', path: '/' },
+ { title: 'Getting Started', path: '/getting-started' },
+ { title: 'Documentation', type: 'dropdown', items: [
+ { title: 'Installation Instructions', type: 'label' },
+ { title: 'Installation', path: '/docs/installation' },
+ { type: 'sep' },
+ { title: 'Components', type: 'label' },
+ { title: 'Portal', path: '/docs/portal' },
+ { title: 'PortalTarget', path: '/docs/portal-target' },
+ ],
+ },
+ { title: 'Examples', path: '/examples' },
+ ],
+ repo: 'linusborg/portal-vue',
+ twitter: 'Linus_Borg',
+}
diff --git a/docs/docs/installation.md b/docs/docs/installation.md
new file mode 100644
index 0000000..167d47a
--- /dev/null
+++ b/docs/docs/installation.md
@@ -0,0 +1,72 @@
+# Installation
+
+## Possible ways to install
+
+### NPM
+
+This is the recommended way to install this Plugin.
+
+Install vom npm as a dependency:
+``` bash
+npm install --save portal-vue
+# or with yarn:
+yarn add portal-vue
+```
+Then include rthe package in your application and register it with Vue:
+
+``` javascript
+import PortalVue from 'portal-vue' // as ES6 module
+var PortalVue = require('portal-vue') // OR as a commonjs require
+
+Vue.use(PortalVue)
+```
+
+### CDN
+
+PortalVue is available through a couple of CDNs, I recommend
+unpkg.com
+
+Just include the script tag *after* the one of Vue.js
+
+```html
+
+
+```
+
+In this case, the plugin will auto-install itself, so there is no need to call Vue.use().
+
+The components will be named `` and ``, respectively.
+
+
+### Local copy
+
+Of course oyu can include PortalVue into your page as a local file on your server as well.
+
+The same rules and restrictions as for CDN apply.
+
+## Install Options
+
+When installing with `Vue.use()`, you can pass options to change the component names.
+```javascript
+Vue.use(PortalVue, {
+ portalNme: 'my-portal', // default: 'portal'
+ portalTargetname: 'my-target', // default:'portal-target'
+})
+```
+These options would make the components available globally as `` and `` respectively.
+
+## Using Components manually
+
+If you don't want to register the components globally, don't do `Vue.use('PortalVue')`
+
+Instead, import the plugin in those components you need them, and register them locally:
+```javascript
+import { Portal, PortalTarget } from 'portal-vue'
+
+export default {
+ components: {
+ Portal,
+ PortalTarget
+ }
+}
+```
diff --git a/docs/docs/portal-target.md b/docs/docs/portal-target.md
new file mode 100644
index 0000000..4258d42
--- /dev/null
+++ b/docs/docs/portal-target.md
@@ -0,0 +1,62 @@
+#
+
+
+This component is an outlet for any content that was sent by a `` component. It renders received content and doesn't do much else.
+
+## Example usage
+
+```html
+
+```
+
+## Props API
+
+### name
+
+|Type|Required|Default|
+|----|--------|-------|
+|`String`|yes|none|
+
+Defines the name of this portal-target. `` components can send content to this instance by this name.
+
+### slim
+
+|Type|Required|Default|
+|----|--------|-------|
+|`Boolean`|no|`false`|
+
+When set to true, the component will check if the sent content has only one root node. If that is the case, the component will *not* render a root node of its own but instead just output the conent as-is.
+
+**Source**
+```html
+
+
Only one content element
+
+
+
+
+```
+**Result**
+```html
+
Only one content element
+```
+
+### tag
+
+|Type|Required|Default|
+|----|--------|-------|
+|`String`|no|`'DIV'`|
+
+Defines the type of tag that should be rendered as a root component.
+
+**Source**
+```html
+
+```
+
+**Result**
+```html
+
+
+
+```
diff --git a/docs/docs/portal.md b/docs/docs/portal.md
new file mode 100644
index 0000000..eb428ec
--- /dev/null
+++ b/docs/docs/portal.md
@@ -0,0 +1,121 @@
+#
+
+Wrap any content that you want to render somehwere else in a `` component.
+
+## Example usage
+
+```html
+
+
This coonent will be sent through the portal
+
+```
+
+## Props API
+
+### disabled
+
+|Type|Required|Default|
+|----|--------|-------|
+|`Boolean`|no|`false`|
+
+When `true`, the slot content will *not* be send through the portal to the defined PortalTarget.
+
+Instead, they will be rendered in place:
+**Source**
+```html
+
+
+
some content
+
+
+```
+**Result**
+```html
+
+
+
some content
+
+
+```
+
+### slim
+
+|Type|Required|Default|
+|----|--------|-------|
+|`Boolean`|no|`false`|
+
+When set to true, the component will check if the sent content has only one root node. If that is the case, the component will *not* render a root node of its own but instead just output the conent as-is.
+
+
This prop only has an effect when the 'disabled' prop is set as well
+
+**Source**
+```html
+
+
Only one content element
+
+```
+**Result**
+```html
+
Only one content element
+```
+
+### `tag`
+
+|Type|Required|Default|
+|----|--------|-------|
+|`String`|no|`'DIV'`|
+
+Defines the type of tag that should be rendered as a root element.
+
+
This prop only works when the 'disabled' prop is true
+
+**Source**
+```html
+
+
some content
+
+```
+**Result**
+```html
+
+
some content
+
+```
+
+### `targetEl`
+
+### `To`
+|Type|Required|Default|
+|----|--------|-------|
+|`String`|yes|none|
+
+Defines the name of the `` component that the slot contents should be sent to. This mounts a new instance of the
+
PortalTarget
component.
+
+
+ This feature should only be used on elements outside of the scope of your Vue app,
+ because it replaces the target element while mounting the
PortalVue
instance, which can lead to unwanted
+ side effects in your Vue App.
+
+
+**Source**
+```html
+
+
some content
+
+
+
+
+
+
+```
+**Result**
+```html
+
+
+
+
+
some content
+
+
+```
diff --git a/docs/examples.md b/docs/examples.md
new file mode 100644
index 0000000..ec20e48
--- /dev/null
+++ b/docs/examples.md
@@ -0,0 +1,3 @@
+# Examples
+
+> Coming soon. Still working on the JSFiddles ;)
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..a5c3ba2
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,144 @@
+# Getting Started with PortalVue
+
+## What is PortalVue?
+
+PortalVue is a set of components that allow you to render a component's template
+(or a part of it) anywhere in the document - even outside of your the part that is controlled by your Vue App!
+
+## Setup
+
+Install Package:
+```bash
+npm install --save portal-vue
+# or with yarn
+yarn add portal-vue
+```
+Add it to your application:
+```javascript
+import PortalVue from 'portal-vue'
+
+Vue.use(PortalVue)
+```
+
+For more detailed Installation instructions, additional options and Installation via CDN,
+see the Installation Page in the Documentation.
+
+### Simple Example
+
+```html
+
+
This slot content will be rendered wherever the with name 'destination'
+ is located.
+
+
+
+
+
+```
+
+See it in action in a fiddle here.
+
+## Feature Examples
+
+### Enabling/Disabling the Portal
+```html
+
+
+ This slot content will be rendered right here as long as the `disabled` prop evaluates to `false`,
+ and will be rendered at the defined destination as when it is set to `true`
+
+
+```
+
+### v-if works, too
+
+```html
+
+ <
+
+ When `userPortal` efvaluates to `true`, the portal's slot content will be rendered
+ at the destination.
+
+
+ When it evaluates to `fales`, the content will be removed from the detination
+
+
+
+```
+
+### Switching targets and sources
+
+the `to` prop of `` and the `name` prop of `` can be changed dynamically with `v-bind`.
+```html
+
+ Content will be dynamically sent to the destination that `name` evaluates to
+
+
+
+ by changeing the `name`, you can define which portal's content should be shown.
+
+```
+### Rendering outside of the Vue-App
+
+```html
+
+
+
+
+ PortalVue will dynamically mount an instance of in place of the Element
+ with `id="widget"`, and this paragraph will be rendered inside of it.
+
+
+
+
+
+
+```
+
+## But why?
+
+### Working around `position: fixed` issues
+
+In older browsers, `position: fixed` works unreliably when the element with that poperty
+is nested in a node tree that has other `position` values.
+
+But we normally need it to render components like modals, dialogs, notifications, snackbars
+and similar UI elements in a fixed position.
+
+With PortalVue, you can instead render the component to a `` that you can position
+as as the first child of your #app element.
+
+Now you can position your components with `position: absolute` instead
+
+```html
+
+
+
+
+
+
+
+ This modal can be positioned absolutely, working around problems with 'fixed'
+
+
+
+
+
+```
+
+### Rendering dynamic widgets
+
+If you use Vue for small bits and pieces on your website, but want to render something in a location at the other end of the page, PortalVue got you covered.
+
+### Tell us about your usecase!
+
+We're sure you will find use cases beyond the ones we mentioned. If you do, please
+let us know by opening an issue on Github
+and we will include it here.
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..8d6bae0
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ Portal Vue - Render anywhere in the DOM
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/landing.html b/docs/landing.html
new file mode 100644
index 0000000..67cc8fb
--- /dev/null
+++ b/docs/landing.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
PortalVue
+
+
+
+
+ A Vue Plugin to render your component's templates anywhere in the DOM
+
This is the content source. The portal is defined below.
+
+ This Content was defined on the left,
+ and transferred to this target on the right.
+ Timestamp updates on each switch:
+ {{ getTimeStamp() }}
+
+
+
+ It was created in the component on the right, and transfered here with VuePortal.
+ Click the "x" to remove links - all vue directives continue to work in their original context.
+ Hint: When the source component is removed, then the target's content below disappears as well.
+
+ Normally, they would be rendered below the input, but with VuePortal
+ we can render them outside of this component, into the componenton the left in this case.
+
`,
+ }).$mount(el)
+ })
+
+ it('renders a div element with class `v-portal`', function () {
+ // expect(vm.$refs.portal.$el.nodeName).to.equal('#comment')
+ const el = vm.$el.querySelector('.v-portal')
+ expect(el).not.to.be.undefined
+ })
+
+ it('renders no extra root element with slim prop', () => {
+ const el = document.createElement('DIV')
+
+ vm = new Vue({
+ components: { Portal },
+ template: `
+