|
| 1 | +# vue-simple-portal |
| 2 | + |
| 3 | +NOT PUBLISHED YET |
| 4 | + |
| 5 | +<!-- markdownlint-disable MD025 MD033 --> |
| 6 | + |
| 7 | +## What this is |
| 8 | + |
| 9 | +`vue-simple-portal` allows you to mount its slot content to the very end of the body element (or potentially any other DOM element on he page) as an immediate child. |
| 10 | + |
| 11 | +Its main usecase are components/elements that need to be positioned absolutely relative to the document/viewport, or fixed in some way or another, like: |
| 12 | + |
| 13 | +* modals |
| 14 | +* drodowns |
| 15 | +* Alerts/notifications |
| 16 | +* Toasts |
| 17 | + |
| 18 | +## Installation |
| 19 | + |
| 20 | +> Since this package hasn't been published yet, this won't work. |
| 21 | +> It will be released soon. |
| 22 | +
|
| 23 | +```bash |
| 24 | +npm install -D @linusborg/vue-simple-portal |
| 25 | +# or |
| 26 | +yarn add -D @linusborg/vue-simple-portal |
| 27 | +``` |
| 28 | + |
| 29 | +## Usage |
| 30 | + |
| 31 | +Minimal example: |
| 32 | + |
| 33 | +```html |
| 34 | +<body> |
| 35 | + <script src="https://unkpg.com/vue/dist/vue.js"></script> |
| 36 | + <script src="https://unkpg.com/vue-simple-portal"></script> |
| 37 | + <div id="app"> |
| 38 | + <-- your Vue app mounts to this element --> |
| 39 | + </div> |
| 40 | + |
| 41 | + <div id="portal-target"> |
| 42 | + <!-- our `<portal>` should move stuff here --> |
| 43 | + </div> |
| 44 | + |
| 45 | + <script> |
| 46 | + new Vue({ |
| 47 | + template: ` |
| 48 | + <div> |
| 49 | + <portal selector="#portal-target"> |
| 50 | + <p>This will be mounted as a child element of <div id="portal-parget"> instead of somehwere inside the child tree of <div id="app"> |
| 51 | + </portal> |
| 52 | + <div> |
| 53 | + ` |
| 54 | + }) |
| 55 | + </script> |
| 56 | +</body> |
| 57 | +``` |
| 58 | + |
| 59 | +Usage with a Bundler: |
| 60 | + |
| 61 | +```javascript |
| 62 | +import Vue from 'vue' |
| 63 | +import Portal from '@linusborg/vue-simple-portal' |
| 64 | +import App from './App.vue' |
| 65 | + |
| 66 | +Vue.use(Portal) |
| 67 | + |
| 68 | +new Vue({ |
| 69 | + el: '#app', |
| 70 | + render: h => h(App) |
| 71 | +}) |
| 72 | +``` |
| 73 | + |
| 74 | +TODO: Insert jsfiddle |
| 75 | + |
| 76 | +## How this lib relates to `portal-vue` |
| 77 | + |
| 78 | +I'm the author of [portal-vue](https://github.com/LinusBorg/portal-vue), a pretty popular portal library for Vue.js, so the obvious question is: |
| 79 | + |
| 80 | +_Why publish another Portal component?_ |
| 81 | + |
| 82 | +Well, `portal-vue` was my first sucessful library, and I wanted it to be _awesome_, so I packed it full of features to portal _anything_ to _anywhere_, _anytime_ you want. |
| 83 | + |
| 84 | +That' turned out pretty well, but there were two issues that I found over time, so I wrote a smaller lib that adresses these issues while sliming down on features and (= bundle size) in order to concentrate on the main use case. |
| 85 | + |
| 86 | +<details> |
| 87 | + <summary> |
| 88 | + Click here if you want to know more |
| 89 | + </summary> |
| 90 | + |
| 91 | +### Drawbacks of `portal-vue` |
| 92 | + |
| 93 | +1. Useless Features: As far as I could tell, people didn't really use most of the features. For most people, this lib solved one problem: Moving stuff to the very end of the `<body>` element so they could properly style and position their modals and similar components. For them, portal-vue comes with a lot of extra pounds that they don't need. |
| 94 | +2. The approach that I chose to make the portal-ing work in all the different supported scenarios came with some caveats - the most severe being that it broke the `$parent <-> $children` chain between the host component and the children that were moved. That also means a couple of things that rely on this chain internally don't work as expected, for example: |
| 95 | + * `provide/inject` |
| 96 | + * `<route-view>` |
| 97 | + |
| 98 | +### A solution to these drawbacks |
| 99 | + |
| 100 | +So I experimented a little and came up with this library here, which solves these two things for the majority of users: |
| 101 | + |
| 102 | +1. It only does one thing, and does it well: It moves stuff to the end of the document. And it's much lighter for this reason. |
| 103 | +2. It keeps the `$parent <-> $children`chain intact, so most of the existing caveats of `portal-vue` are gone. |
| 104 | + |
| 105 | +### When to use which |
| 106 | + |
| 107 | +* Use `vue-simple-portal` when you want to move stuff to the end of the document only. |
| 108 | +* Use `portal-vue` when you want to do more edge-case things, i.e. move stuff around to anywhere *within* our existing app, like the header component area, dynamically move the same content to different places by changing the destination prop etc. |
| 109 | + |
| 110 | +</details> |
| 111 | + |
| 112 | +## Installation |
| 113 | + |
| 114 | +### NPM / Yarn |
| 115 | + |
| 116 | +```bash |
| 117 | +npm i -g vue-simple-portal |
| 118 | +# or |
| 119 | +yarn add vue-simple-portal |
| 120 | +``` |
| 121 | + |
| 122 | +#### Install a a global plugin (Optional) |
| 123 | + |
| 124 | +This will make the `<portal>` component available globally, but also make the portal not lazy-loadable. |
| 125 | + |
| 126 | +```javascript |
| 127 | +// main.js |
| 128 | +import Vue from 'vue' |
| 129 | +import VuePortal from 'vue-simple-portal' |
| 130 | + |
| 131 | +Vue.use(VuePortal, { |
| 132 | + name: 'portal' // optional, use to rename component |
| 133 | +}) |
| 134 | +``` |
| 135 | + |
| 136 | +#### Or use and register it locally |
| 137 | + |
| 138 | +```javascript |
| 139 | +// in a component definition |
| 140 | +import { Portal } from 'vue-simple-portal' |
| 141 | +export default { |
| 142 | + name 'MyComponent', |
| 143 | + components: { |
| 144 | + Portal |
| 145 | + } |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +### Browser |
| 150 | + |
| 151 | +Just include it with a script tag *after* Vue itself |
| 152 | + |
| 153 | +```html |
| 154 | +<head> |
| 155 | + <script src="https://unkpg.com/vue/dist/vue.js"></script> |
| 156 | + <script src="https://unkpg.com/vue-simple-portal"></script> |
| 157 | +</head> |
| 158 | +``` |
| 159 | + |
| 160 | +The plugin will automatically register the `<portal>` component globally. |
| 161 | + |
| 162 | +## Features |
| 163 | + |
| 164 | +This library aims to do one thing only, and do it well: move stuff to the end of the document for things like modals. But it still gives the developer a few props to influence how exactly that happens: |
| 165 | + |
| 166 | +## Caveats |
| 167 | + |
| 168 | +Some caveats still exist, such as: |
| 169 | + |
| 170 | +### Losing local state when toggling `disabled` prop |
| 171 | + |
| 172 | +When you toggle the `disabled` prop from `true` to `false` or vice versa, any components inside of the portal will be destroyed in their current location and re-created in their new location. |
| 173 | + |
| 174 | +This means all their *local* state is lost. |
| 175 | + |
| 176 | +If you need to keep state between these switches, keep it in a [global state manager](https://vuejs.org/v2/guide/state-management.html) |
| 177 | + |
| 178 | +### Transitions |
| 179 | + |
| 180 | +When you use a `<transition>` as the root element of the portal and then remove the portal (i.e. with `v-if`) or set `disabled` to `true`, no leave transition will happen. |
| 181 | + |
| 182 | +This is to expected, as the same thing would happen if you removed a div that contains a `<transition>`, but often trips people up nonetheless. |
| 183 | + |
| 184 | +If you need to remove or disable the portal *after* a transition has finished, you woulld have to work around this like this: |
| 185 | + |
| 186 | +<details> |
| 187 | + <summary>Show Example</summary> |
| 188 | + |
| 189 | +```html |
| 190 | +<template> |
| 191 | + <portal :disabled="disablePortal"> |
| 192 | + <transition name="fade" appear @afterLeave="disablePortal = true"> |
| 193 | + <div v-if="showTransitionContent"> |
| 194 | + this will fade in/out |
| 195 | + </div> |
| 196 | + </transition> |
| 197 | + </portal> |
| 198 | +</template> |
| 199 | +<script> |
| 200 | + export default { |
| 201 | + data: () => ({ |
| 202 | + showTransitionContent: true, |
| 203 | + disablePortal: false, |
| 204 | + }), |
| 205 | + methods: { |
| 206 | + getOut() { |
| 207 | + // calling this method this will trigger the transition, |
| 208 | + // which when finished, will disable the Portal |
| 209 | + // through the `afterLeave` hook callback |
| 210 | + this.showTransitionContent = false |
| 211 | +
|
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | +</script> |
| 216 | +``` |
| 217 | + |
| 218 | +</details> |
| 219 | + |
| 220 | +## Development |
| 221 | + |
| 222 | +<details> |
| 223 | + <summary> |
| 224 | + The following commands are useful for anyone forking this project and/or wanting to contribute |
| 225 | + </summary> |
| 226 | + |
| 227 | +### Project setup |
| 228 | + |
| 229 | +```bash |
| 230 | +yarn install |
| 231 | +``` |
| 232 | + |
| 233 | +### Compiles and hot-reloads for development |
| 234 | + |
| 235 | +```bash |
| 236 | +yarn run serve |
| 237 | +``` |
| 238 | + |
| 239 | +### Compiles and minifies for production |
| 240 | + |
| 241 | +```bash |
| 242 | +yarn run build |
| 243 | +``` |
| 244 | + |
| 245 | +### Lints and fixes files |
| 246 | + |
| 247 | +```bash |
| 248 | +yarn run lint |
| 249 | +``` |
| 250 | + |
| 251 | +### Run the tests |
| 252 | + |
| 253 | +```bash |
| 254 | +yarn run test |
| 255 | +``` |
| 256 | + |
| 257 | +### Run your end-to-end tests |
| 258 | + |
| 259 | +```bash |
| 260 | +yarn run test:e2e |
| 261 | +``` |
| 262 | + |
| 263 | +### Run your unit tests |
| 264 | + |
| 265 | +```bash |
| 266 | +yarn run test:unit |
| 267 | +``` |
| 268 | + |
| 269 | +### Customize configuration |
| 270 | + |
| 271 | +This project is based on Vue CLI, so see [Configuration Reference](https://cli.vuejs.org/config/) of Vue CLI for further details. |
| 272 | +</details> |
0 commit comments