Giter Club home page Giter Club logo

portal-vue's Introduction

PortalVue

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

PortalVue Logo

Buy Me a Coffee at ko-fi.com

For more detailed documentation and additional Information, please visit the docs.

Looking for the version for Vue 2.*? Docs for PortalVue 2.*, compatible with Vue 2, are here

Installation

npm i portal-vue

# or

yarn add portal-vue
import PortalVue from 'portal-vue'
Vue.use(PortalVue)

Usage

<portal to="destination">
  <p>This slot content will be rendered wherever the <portal-target> with name 'destination'
    is  located.</p>
</portal>

<portal-target name="destination">
  <!--
  This component can be located anywhere in your App.
  The slot content of the above portal component will be rendered here.
  -->
</portal-target>

Nuxt module

v3 does not yet have a nuxt module integration. PRs welcome.

portal-vue's People

Contributors

atinux avatar boardend avatar btoo avatar cdvrooman avatar codebender828 avatar danielpe05 avatar dependabot[bot] avatar dsanders11 avatar eschalks avatar imomaliev avatar jaulz avatar jonathanmach avatar ky-is avatar larvanitis avatar linusborg avatar lucassifoni avatar luciorubeens avatar matt-oconnell avatar mbrgm avatar mitar avatar mohdshakir avatar nekuz0r avatar posva avatar raphaelkieling avatar rodrigogama avatar simplesmiler avatar soulaviator avatar synycboom avatar visualfanatic avatar zicklag avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

portal-vue's Issues

Support multiple portals sending content to one target at the same time

I'd like to be able to append multiple portals to single portal-target (like Jade-blocks does).

Here are some examples:

<portal-target name="under-nav">
  0
</portal-target>
<!-- if there are no "from-portals" content of portal-target is "0" -->

<portal to="under-nav">
    1
</portal>
<!-- the default content is overwritten so portal-target is "1" now -->

<portal to="under-nav" append="true">
    2
</portal>
<!-- appends to existing content => "12" -->

<portal to="under-nav" prepend="true">
    3
</portal>
<!-- prepends to existing content => "312" -->

My use-case.

I have a page like:
http://joxi.ru/E2pbGWvF98nNdr

And I want to create under-nav panels dynamically:
http://joxi.ru/DrlpJ7aFvJWw3r

Improve SSR support by skipping during SSR & hydration

I thought about a suggestion to skip rendering portal-targets on the server and during re-hydration. Then hydration should work as the DOM should match the vDOM, and we just render the PortalTarget's content a bit later.

Skipping on the client is easy with this.$isServer, and I think the best way to check if we are re-hydrating is to check if this.$root.$el.dataset['server-rendered']

If that’s present, we defer rendering the slot content with a setTimeout(this.$forceUpdate, 0), which will re-render the component with the slot content after the rehydration phase is done. We should also set a flag in the target's local data so the next re-render is not deferred again.

Finish documentation

TODOS

  • Getting Started
  • Examples
  • Documentation - Portal
  • Documentation - PortalTarget

Make portal update process sync

Inspired by #23, I think we can come up with a way to make the portal process sync:

  1. create a unique id for each portal
  2. send the id to the wormhole with each update
  3. when a portal sends an update with clear(), the wormhole should only clear the contents if the current content is from the same portal

That way, we don't have to use an update queue and can get rid of the $nextTick() in the wormhole.

Multiple portals with the same `target-el` don't work properly

I am looking for a similar method that you have used here to take elements out of their parent context and move them up in the dom so I can use fullscreen position: absolute elements without having to worry about my element hitting a relative container somewhere.

What I am missing though is having multiple elements point to the same target. I have a couple of components that I might want to display fullscreen. Their display is toggled through a view property in Vuex which decides which particular fullscreen component to display.

With your solution I would have to specify a to and target for everyone of my components though. I do not care about creating a specific placeholder for each components. I simply want to shove them all up in the dom.

Is this still possible with this component somehow or is this is a totally different approach?

Add e2e tests

  • Setup Testcafé

Test Cases will be all examples in /examples

Tests:

  • Events in portal'ed content correctly execute in source components
  • Hiding source removes content
  • Hiding and re-creating target makes content reappear
  • Switching name in Target gets content from new source
  • Switching to in source removes content from old Target and renders in new target
  • Switcheroo between 2 portals using the same target does not override each other (See #3).
  • Mouting outside of the DOM works
  • removing source that used mountTarget removes target

Transition Support

This naive use case doesn't work for me:

      <transition name="fade">
        <portal-target name="title" slim @change="titleChange">
          Title
        </portal-target>
      </transition>

After reading some of the code, I guess I'm not really sure why - I don't understand the transition component enough to speculate. Am I missing something?

Extract babel config into .babelrc file and add it to .npmignore

If you do not have the es2015 and stage-2 preset packages installed in your application, compilation fails when building a WebPack bundle of an application that depends on portal-vue with the following error:

Error: Couldn't find preset "es2015" relative to directory ...

Compilation works when the es2015/stage-2 presets are both installed. It seems this is due to the "babel" options in this package's package.json file overriding/merging into my own babel configuration and importing these plugins/presets.

Interestingly, the "devDependencies" of portal-vue includes the stage-3 preset, but the babel options only import in stage-2. I'm wondering if this will cause an issue for anyone who pulls down this repo to do development on portal-vue.

Seeing as it appears that the dist/*.js files have already been processed by babel, is there any way to have these options only be applied during the development and packaging of portal-vue itself, and not in the development and packaging of applications that use it?

The es2015 preset isn't always necessary; and generally having a dependency on a stage-x isn't suitable for production (as features will move between stage-x presets as they mature). So I'd prefer to not be required to pull them down.

Otherwise, awesome package!

Fix prod build process

rollup-plugin-vue doesn't like missing <template> - what to do?

Need a way to define components in .js

Idea:

  • use second babel-loader config with inject-loader chained before, and include /src/, just for tests.

Replace the event bus with a global storage and a class.

Problem with the Event Bus:

We can see race conditions if one portal is removed (and calls clear()) and another portal is created (and calls sendUpdate()). If the latter component is updating before the former, clear() will remove the content of sendUpdate().

The solution is to queue these events, which the Event bus does not allow.

Rename to vue-portal?

As this project is marked deprecated, you could ask the author if you can use its name in favour of portal-vue so that my OCD doesn't trigger when i look at my package.json :D

PortalTarget should not create rootNode if slotContent has only one vNode

This could be made optional with a slim prop.

<portal to="destination">
  <p>Content</p>
</portal>

<!-- Renders a div with the content inside: -->
<portal-target name="destination" /> 

<!-- Renders no div , only the `<p>` from the content: 
       Would still create a root div if content had more than one root node.
    -->
<portal-target name="destination" slim /> 

switch Unit test tooling

I found AVA to be not working well with .vue files and webpack in general.

Will switch to mocha & mocha-loader

Use case: Modals under CSS containment

Another pretty convincing use for portal-vue is showing modals from within components under CSS Containment (see this a friendlier introduction). Here portal-vue allows showing modals, and at the same time reap the performance benefits of CSS containment.

I would suggest adding this to the documentation!

Vue.use(PortalVue) causing event emissions to fail

Essentially, as soon as I import PortalVue and add it to Vue, any instance of this.$emit('event') stops firing. However, it doesn't affect the eventBus I've set up. I'm using Webpack 2 through the Webpacker gem for rails. Here's some code:

// vue-config.js

import Vue from 'vue/dist/vue.esm'

import VeeValidate from 'vee-validate'
Vue.use(VeeValidate)

import PortalVue from 'portal-vue'
Vue.use(PortalVue)

require('vue-apparate')
VueApparate.init(Vue)

window.eventBus = new Vue()

// app.js

import Vue from 'vue/dist/vue.esm'
import components from './pages/pages'

import '@/assets/css/frontend.scss'
require('@/scripts/require-all') // requires all files in /scripts, which includes vue-config.js

new Vue({
  el: '#app',
  components
})

Actual usage of PortalVue is irrelevant. If I comment out Vue.use(PortalVue) the problem immediately dissipates. Here's an example usage:

<template>
<div class="modal-container">
  <div class="modal" @click="close">
    <slot></slot>
  </div>
</div>
</template>

<script>
export default {
  name: "VueModal",
  methods: {
    close() {
      this.$emit('close')
    }
  }
}
</script>

Through some console.log debugging, I know that the method close is firing, but VueDevtools confirms that no events are being emitted. Additionally, in the console, without PortalVue I get the typical dev environment warning:

screen shot 2017-05-23 at 10 34 20 am

But once I add PortalVue I get two of these warnings:

screen shot 2017-05-23 at 10 35 00 am

Any thoughts what may be causing this?

Error if portal content is not defined

The render function crash if there is no content passed to the portal, at this line: portal.vue#L101

Example:

render (h) {
  return h('portal', {
    attrs: {
      to: 'modals',
    },
  }, store.getters[ACTIVE] ? [ h(modals[store.getters[OPERATION]]) ] : null)
},

Convert components to *.js (solve inject-loader issues)

We don'T need .vue files because we don't have any templates or styling.

The only reason for using -vue files is that for some reason, inject-loader (v2 and v3, no difference) won't work with normal .js files, and I can't figure out what's wrong.

When I have time I should switch file format and solve this inject-loader problem somehow.

Add "disabled" Prop

Right now, the portal component always renders to the target.

With an disabled prop, the developer can make the portal render the slot content in place

disabled="false (default)

<portal-target name="destination" />

<portal to="destination" enabled>
  <p>Content to be transferred</p>
</portal>

<!-- results in: -->
<div>
  <p>Content to be transferred</p>
</div>

<!-- -->

disabled

<portal-target name="destination"/>

<portal to="destination" disabled>
  <p>This will be shown in the Portal</p>
</portal>

<!-- results in: -->
<!-- -->

<p>Content to be transferred</p>

Add portal directive or be abstracted

@LinusBorg, thank you for this awesome plugin!

I would like to suggest making the portal component abstract or be a directive. Is kind of unexpected that this.$parent in ported components is not the component they were ported from. In my opinion, the portal should be just doing what it says and not getting in the way.

Idea: Support for some form of "portal-slots"

<portal to="destination>
  <div slot="header">
    this should go to the header slot
  </div>
</portal>

<portal to="destination>
  <div slot="footer">
    This should go tto the footer slot
  </div>
</portal>

<portal-target :slots=['header', 'default', 'footer']/>

This is similar to #26 but I think both APIs would serve different purposes. While #26 would enable an arbitrary number of portals to send to one portal-target with some position prop (to ensure order), The suggestion above would enable declaratively "sorting" a finite number of inputs by leveraging the normal slot functionality.

This features could also be combined I think - in the wormhole, we save all incoming seperated by slot and then position.

Remove the wrapper element of PortalTarget?

The portal leaves a div with a class of vue-portal-target in the DOM when there is nothing to render and renders any ported content inside this div. The expectation is that this div doesn't exist and the portal contect is directly placed inside the target destination without any additional bullshit wrappers. Why is this and how can I get rid of it?

Release Chores

Todos

  • Prepare Package.json for publishing (github links, description, version, main:, cleanup dependencies ...)
  • Update Vue-loader to 11.0.0
  • Improve Examples code & design

Portal.targetEl type HTMLElement breaks SSR

The portal.targetEl type of HTMLElement causes a ReferenceError: HTMLElement is not defined error to be thrown during SSR. As far as I can tell, this plugin supports SSR, but this type breaks that.

A quick fix is to add something like the following to my server:

global.HTMLElement = () => {}

However, this seems hacky and I would prefer if this was solved at the plugin level if that is at all possible!

refs are not available on $nextTick when using portal

Hi.

This is not very standard issue, but it's definitely caused by portal-vue. Let's see the code:

<portal to="whatever">
  <div v-if="open" ref="div">
  ...
  </div>
</portal>
methods: {
  handler(e) {
    this.open = true

    this.$nextTick(() => {
      const rect = this.$refs.div.getBoundingClientRect()
  }
}

If I click a button somewhere and trigger my handler, I get an error, because this.$refs.div is undefined. The element inside the portal isn't rendered yet at this point. But when I do the same without the portal, it works as intended. So far I couldn't come up with an adequate solution. Any ideas?

Changes are not reactive when compiled with vueify

<portal-target name="destination"></portal-target>

<portal to="destination">
  <p>test {{dynamicValue}}</p>
</portal>

When dynamicValue changes, the content is not updated in the <portal-target>
Is there currently any way to achieve this, or any plans to support this is future?

Enhancement: combine "to" and "targetEl" or make "to" optional

Would it be possible to combine to to and targetEl props? As it is currently to is required even if one is using the targetEl prop.

props: {
    disabled: { type: Boolean, default: false },
    slim: { type: Boolean, default: false },
    tag: { type: [String], default: 'DIV' },
    to: { type: [String, HTMLElement], required: true }
}

and then refacto detection of the type

if (typeof this.to === 'String') {
    if (this.to.charAt(0) == '#') {
        // Element ID
    } else {
        // <portal-target> name
    }
} else {
    // assume HTMLElement
}

Although this would break the ability to use a non ID CSS selector.

I supposed you could make both to and targetEl required: false and then to a sanity check to see if one of the other is present.

Using without target (appending to document.body by default)

Hi!
I got many different modals in different Vue-components and it is really boring to create a coincident <portal-target> for each of them. It is a quite common case, as I know.
Allowing to use <portal> without to prop and appending its content directly before </body> by default then would be a great solution.

Idea: Implement Events for PortalTarget

Would this be useful?

<portal-target name="destination" @update="handleUpdate">
methods: {
  handleUpdate (updateObj) {
    /**
    Properties of updateObj: 
    {
      source: Name of source component
      slot: slot the content was sent to
      index: index the content should be rendered at.
      vNodes: vNodes
    }
    **/
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.