├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── doc ├── assets │ ├── arrow.svg │ ├── favicon.svg │ ├── github.svg │ ├── stackblitz.svg │ ├── state-diagram.png │ └── state-diagram.svg ├── demo.js ├── guide │ ├── 00-about.md │ ├── 01-getting-started.md │ ├── 02-basics.md │ ├── 04-css-transitions.md │ ├── 05-js-transitions.md │ ├── 07-examples.md │ └── 10-limitations.md ├── index.html ├── index.js ├── index.scss ├── landing.js ├── loaders │ ├── md-loader.js │ └── svg-loader.js ├── mount-count.js ├── router.js ├── transitions.js ├── utils.js └── version.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── readme.md ├── release.config.js ├── src ├── core │ ├── class-list.ts │ ├── transition-base.ts │ └── utils.ts ├── css │ ├── flow.ts │ ├── index.ts │ ├── interfaces.ts │ ├── transitions │ │ ├── fade.ts │ │ ├── index.ts │ │ ├── land.ts │ │ └── slide.ts │ └── utils.ts └── index.ts ├── test ├── bundle │ ├── .npmrc │ ├── package.json │ ├── src.js │ └── webpack.config.js ├── css │ ├── edge-cases.test.ts │ └── index.test.ts ├── index.js └── utils │ ├── comp.ts │ └── dom.ts ├── tsconfig.json └── webpack.config.js /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build 3 | 4 | on: 5 | push: 6 | branches: [ master, next ] 7 | pull_request: 8 | branches: [ master, next] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [12.x] 17 | 18 | steps: 19 | # https://help.github.com/en/actions/reference/development-tools-for-github-actions#set-an-environment-variable-set-env 20 | - name: Extract Branch Name 21 | run: echo "::set-env name=BRANCH::$(echo ${GITHUB_REF##*/})" 22 | - name: Set Release Mode (branch == master) 23 | if: env.BRANCH == 'master' 24 | run: echo "::set-env name=PUBLISH::true" 25 | - name: Set Release Mode (branch == next) 26 | if: env.BRANCH == 'next' 27 | run: | 28 | echo "::set-env name=PUBLISH::true" 29 | echo "::set-env name=NEXT::true" 30 | - uses: actions/checkout@v2 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v1 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | - run: npm ci 36 | - run: npm run build 37 | - run: npm test 38 | - run: npm run test-bundle 39 | - name: Coveralls GitHub Action 40 | uses: coverallsapp/github-action@v1.0.1 41 | with: 42 | github-token: ${{ secrets.GITHUB_TOKEN }} 43 | env: 44 | CI: true 45 | 46 | - run: npx semantic-release@17.0.7 47 | env: # Or as an environment variable 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | if: env.PUBLISH == 'true' 51 | # will do verythingbut publish! (we need the bumped version number) 52 | - run: npm run build-doc 53 | if: env.PUBLISH == 'true' 54 | # fallback 55 | - run: cp dist-doc/index.html dist-doc/404.html 56 | if: ${{ env.PUBLISH == 'true' && !(env.NEXT == 'true') }} 57 | # only on next 58 | #- run: npm run build-doc&&cp dist-doc/next/index.html dist-doc/404.html 59 | # if: env.NEXT == 'true' 60 | - name: Deploy Doc 61 | if: env.PUBLISH == 'true' 62 | uses: peaceiris/actions-gh-pages@v3 63 | with: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | target_branch: gh-pages 66 | publish_dir: dist-doc 67 | keep_files: true 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /extension 3 | /dist 4 | /dist-doc 5 | /coverage 6 | .DS_Store 7 | lit-transition-*.tgz 8 | test/bundle/dist/* 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !/dist/** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-present Jan Kretschmer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /doc/assets/arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /doc/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/assets/stackblitz.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/assets/state-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sijakret/lit-transition/20d016aebb7aa34111f643640c816b8e6fbedcaf/doc/assets/state-diagram.png -------------------------------------------------------------------------------- /doc/assets/state-diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | .enter-active.enter-active.enter-from.enter-from.enter-to.enter-toframesframesstartstartendendViewer does not support full SVG 1.1 -------------------------------------------------------------------------------- /doc/demo.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import 'markdown-it-highlight/dist/index.css'; 3 | import 'highlight.js/styles/hybrid.css'; 4 | import hljs from 'highlight.js'; 5 | import {load} from './loaders/md-loader?folder=./guide!'; 6 | import StackBlitzSDK from '@stackblitz/sdk'; 7 | import sbIcon from './assets/stackblitz.svg'; 8 | 9 | // Import the LitElement base class and html tag function 10 | import { LitElement, html } from 'lit-element'; 11 | import {unsafeHTML} from 'lit-html/directives/unsafe-html'; 12 | import {devDependencies, peerDependencies, name} from '../package.json'; 13 | import {version} from './version'; 14 | 15 | const deps = { 16 | ...devDependencies, 17 | ...peerDependencies, 18 | [name]: version 19 | }; 20 | 21 | let defs = new WeakMap(); 22 | let id = 0; 23 | 24 | class Component extends LitElement { 25 | 26 | createRenderRoot() { 27 | return this; 28 | } 29 | 30 | static get properties() { 31 | return { 32 | chunk: String, 33 | code: String, 34 | name: String 35 | } 36 | } 37 | 38 | async firstUpdated() { 39 | if(!this.code) { 40 | if(!this.chunk) { 41 | throw new Error('doc-demo needs either chunk or code') 42 | } 43 | let {Comp,render,template,code,run} = await load(this.chunk); 44 | this.code = code; 45 | if(render) { 46 | Comp = class Auto extends LitElement { 47 | render() { 48 | return render(); 49 | } 50 | } 51 | } else if(template) { 52 | Comp = class Auto extends LitElement { 53 | render() { 54 | return template; 55 | } 56 | } 57 | } else if(run) { 58 | Comp = class Auto extends LitElement { 59 | render() { 60 | return undefined 61 | } 62 | updated() { 63 | run(this.shadowRoot); 64 | } 65 | } 66 | } 67 | if(!defs.has(Comp)) { 68 | this.name = 'lit-transition-demo-'+(id++); 69 | customElements.define(this.name, Comp); 70 | defs.set(Comp, this.name); 71 | } else { 72 | this.name = defs.get(Comp); 73 | } 74 | } 75 | } 76 | 77 | render() { 78 | if(!this.code) return undefined; 79 | const h = hljs.highlight('javascript', this.code.trim(), true).value; 80 | const code = unsafeHTML(`${h}`); 81 | if(!this.name) { 82 | // just return highlighted ode 83 | return code; 84 | } else { 85 | return html` 86 | ${code} 87 | Result: 88 | stackBlitz(this.code)} stackblitz> 89 | ${sbIcon} run live on stackblitz! 90 | 91 | 92 | 93 | ${unsafeHTML(`<${this.name}>${this.name}>`)} 94 | 95 | `; 96 | } 97 | } 98 | } 99 | 100 | customElements.define('doc-demo', Component); 101 | 102 | // opens example on stackblitz 103 | function stackBlitz(code) { 104 | const tmpl = new RegExp('export const template ='); 105 | const hasTemplate = code.match(tmpl); 106 | code = hasTemplate ? code.replace(tmpl, 'const template =') : code; 107 | const name = 'lit-transition-demo'; 108 | const dependencies = { 109 | '@webcomponents/custom-elements': '*', 110 | ...(['lit-html','lit-element','lit-transition'].reduce((a,d) => ({ 111 | ...a, 112 | [d]: deps[d] 113 | }), {})) 114 | }; 115 | StackBlitzSDK.openProject({ 116 | files: { 117 | 'index.js': `// auto-generated by lit-transition doc 118 | ${code} 119 | ${!hasTemplate ? `customElements.define('${name}', Comp)` : ` 120 | import { render } from 'lit-html'; 121 | render(template, document.querySelector('#demo'));` 122 | }`, 123 | 'index.html': ` 124 | 125 | 126 | 127 | 128 | LitElement Example 129 | 130 | 131 | ${!hasTemplate ? `<${name}>${name}>` : ''} 132 | 133 | ` 134 | }, 135 | title: 'lit-transition example', 136 | description: 'try out lit-trnsition', 137 | template: 'javascript', 138 | dependencies, 139 | settings: { 140 | compile: { 141 | trigger: 'save', 142 | action: 'refresh' 143 | } 144 | } 145 | }) 146 | } -------------------------------------------------------------------------------- /doc/guide/00-about.md: -------------------------------------------------------------------------------- 1 | About 2 | 3 | lit-transition is a directive for [lit-html](https://lit-html.polymer-project.org/) 4 | that enables animated transitions between templates. 5 | It is in parts inspired by the [transition system of vue.js](https://vuejs.org/v2/guide/transitions.html). 6 | 7 | lit-transition is written in typescript, extremely tiny and tree-shakeable. 8 | It mainly orchestrates state transitions when a lit-html template is updated. 9 | By adding css classes or executing js-based animations and delaying the removal or insertion of new DOM, transitions can be played. 10 | 11 | Currently we only support [css transitions](https://developer.mozilla.org/de/docs/Web/CSS/transition) [css animations](https://developer.mozilla.org/de/docs/Web/CSS/animation). 12 | However, the plan is to eventually also transparently support [web animations](https://developer.mozilla.org/de/docs/Web/API/Web_Animations_API). 13 | 14 | Check out the following example: 15 | 16 | 35 | 36 | 37 | Head over to the [Getting Started](getting-started) section to get set up. -------------------------------------------------------------------------------- /doc/guide/01-getting-started.md: -------------------------------------------------------------------------------- 1 | Getting Started 2 | 3 | lit-transition is available as an npm package. 4 | 5 | Install it as a devDependency using npm: 6 | 7 | ```bash 8 | npm install lit-transition --save-dev 9 | ``` 10 | 11 | Now, anywhere you swap templates in your render code, 12 | you can animate the transition like this: 13 | 14 | ```javascript 15 | import {transition} from 'lit-transition'; 16 | // this template will change depending on 'cond' 17 | const dynamic = cond ? html`One` : html`Two`; 18 | // when dynamic changes, it will animate automatically 19 | render(html`${transition(dynamic /*, [options] */)}`, ...); 20 | ``` 21 | 22 | > Throughout this guide we will mostly be using LitElement 23 | > Components in examples since they allow for simple reactive rendering 24 | > and storing some state. 25 | > All techniques, however also apply 26 | > in a plain lit-html rendering setup! 27 | > If you have lit-element and lit-transition installed, 28 | > the examples should work as they are.. 29 | 30 | ## Example 31 | Here is an example of custom element that changes 32 | its template when clicked. 33 | With no options supplied, the `transition` directive will 34 | use in the default transition (fade). 35 | 36 | 59 | 60 | How could this be any easier, right? 61 | 62 | Continue with the [basics](basics) to learn 63 | how lit-transition works and what else you can do with it. 64 | -------------------------------------------------------------------------------- /doc/guide/02-basics.md: -------------------------------------------------------------------------------- 1 | Basics 2 | 3 | > Currently, we only support CSS-based transitions. We plan, however, on adding 4 | > Javascript/web animation transitions as well. 5 | > The concepts in this document are general and will also apply to Javascript transitions 6 | > once they are implemented 7 | 8 | # Transition types 9 | 10 | Currently, only transitioning between single elements/components are supported. 11 | As opposed to concepts like [list transitions](https://vuejs.org/v2/guide/transitions.html#List-Transitions), here, only one of the transitioned items is designated for 12 | presentation at any point in time. 13 | 14 | This means you can apply the transition directive on anything that returns a 15 | template with one single root node. 16 | 17 | ```javascript 18 | html` 19 | ${transition(html`cool stuff`)} 20 | `; 21 | ``` 22 | 23 | This makes it very easy to transition between items in a list for instance: 24 | 25 | 60 | 61 | > Note: if you transition text nodes as above, 62 | > lit-transition will automatically create a `` node 63 | > under the hood to apply styles to. 64 | 65 | # Transition modes 66 | 67 | We support three transition modes: 68 | 69 | * __`in-out`__: 70 | will schedule the enter transition for the new content first. 71 | Once the enter transition has completed, 72 | the leave transition for the old content is triggered 73 | * __`out-in`__. 74 | will schedule the leave transition for the current content first. 75 | Once the leave transition has completed, 76 | the enter transition for the new content triggered 77 | * __`both`__. 78 | both enter and leave transition are triggered simultaneously right away 79 | 80 | The transition mode can be supplied as part of the [options of a transition](/css-transitions#sec-1) 81 | 82 | ```javascript 83 | transition(template, { mode: 'in-out' /*, ..more options*/ }) 84 | ``` 85 | 86 | Take a look at the following example to try out the differences 87 | between the available transition modes: 88 | 120 | 121 | # Marking templates 122 | 123 | Transitions are triggered every time a template is re-rendered. 124 | This means, that whenever sections unrelated to your transition 125 | trigger a redraw, all transition directives are re-executed as well. 126 | 127 | In some scenarios, re-running these animations might be desired. 128 | To only trigger transitions on content that actually changed, 129 | we need to mark templates so lit-transition can recognized templates 130 | it has already seen and skip animations. 131 | 132 | ## The issue 133 | Consider the example below. 134 | If you click the 'unrelated' button, 135 | even though the affected part of the template 136 | is not a direct child of the transition directive, 137 | it still triggers a re-render of the whole template 138 | and thus a transition animation. 139 | 140 | 174 | 175 | ## The fix 176 | To fix this, we use the `mark` helper to make 177 | lit-transition recognize templates it has already seen. 178 | 179 | ```javascript 180 | import { transition, mark } from 'lit-transition'; 181 | // marked template will be reidentifyable by transition directive 182 | transition(mark(hmtl`MyTemplate`, 'UniqueName')); 183 | ``` 184 | 185 | This way, animations are only executed when the 186 | actual transition content changes. 187 | 188 | 222 | 223 | Once you familiarized yourself with these basic concepts, 224 | continue by learning how to use [css transitions](css-transitions). 225 | -------------------------------------------------------------------------------- /doc/guide/04-css-transitions.md: -------------------------------------------------------------------------------- 1 | CSS Transitions 2 | 3 | # Transition classes 4 | 5 | css transitions are simple. 6 | When a transition is executed, css classes are applied and removed 7 | to the entering or leaving DOM nodes in a particular sequence. 8 | The default names of these classes are as follows 9 | (respectively for enter and leave transitions): 10 | 11 | * __`[enter/leave]-active`__: 12 | Added at the very begining, stays active throughout the whole transition. 13 | Use this class to define transition and animation times, easing, etc.. 14 | Removed after transition has finished. 15 | * __`[enter/leave]-from`__: 16 | Added at the very begining, and removed right after the first frame. 17 | This class should describe the initial state of the css properties to animate 18 | at the start of your animation. 19 | * __`[enter/leave]-to`__: 20 | Added after first frame, right when `[enter/leave]-from` is removed. 21 | This class should describe the eventual target state of css properties. 22 | Removed after transition has finished. 23 | 24 | The diagram below illustrates at what time which css classes are applied in case of an enter transition 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | # Transition options 33 | 34 | You can change the default class names that are applied 35 | at the respective stages by supplying customized names in 36 | the transition options. 37 | 38 | On top of that, the following options 39 | can be passed as the second argument to the transition directive: 40 | 41 | ```javascript 42 | // all options are optional and have sane defaults! 43 | transition(html`..transition me..`, { 44 | // see transition Basics -> transition mode 45 | // * TransitionMode.InOut = 'in-out' 46 | // * TransitionMode.OutIn = 'out-in' 47 | // * TransitionMode.Both = 'both' 48 | // default: TransitionMode.Both 49 | mode: TransitionMode.Both , 50 | 51 | // css that will be injected in a style tag 52 | // alongside the animated templates 53 | // it preferable and more performant to just have classes 54 | // already present in your app so they will be simply assigned 55 | // if you set this, style tags will be inserted automatically 56 | // default: undefined 57 | css: `.enter-active { /* .. */ }`, 58 | 59 | // in case your css transitions have inconsistent 60 | // durations (some finish earlier than others) 61 | // we won't be able to automaticically know when it 62 | // finished so you need to specify the duration explicitly 63 | // applies to enter and leave transition 64 | // default: 500ms 65 | duration: 1000 //ms 66 | 67 | // if true, animations will be bypassed during while 68 | // pages are not shown, the intent is to save computations 69 | // js hooks will still be executed. 70 | // default: true 71 | skipHidden: true 72 | 73 | // if set to 'false' no enter transition is performed 74 | // default: as indicated below 75 | enter: { 76 | // override classes assigned during enter transitions 77 | active: 'enter-active', 78 | from: 'enter-from', 79 | to: 'enter-to', 80 | 81 | // only applies to enter transition 82 | // falls back to base duration 83 | // default: undefined 84 | duration: Number (ms), 85 | 86 | // if true, additional styles 87 | // will be applied that will freeze the current geometry 88 | // present before animation starts 89 | // check out the 'Layout reflows' section below 90 | // options: 91 | // GeometryLockMode.None = 0 | false 92 | // GeometryLockMode.Lock = 1 | true 93 | // GeometryLockMode.Auto = 'auto' 94 | lock: GeometryLockMode.None 95 | }, 96 | 97 | // if set to 'false' no enter transition is performed 98 | // default: as indicated below 99 | leave: { 100 | // override classes assigned during enter transitions 101 | active: 'leave-active', 102 | from: 'leave-from', 103 | to: 'leave-to', 104 | 105 | // only applies to enter transition 106 | // falls back to base duration 107 | // default: undefined 108 | duration: Number (ms), 109 | 110 | // if true, additional styles 111 | // will be applied that will freeze the current geometry 112 | // present before animation starts 113 | // check out the 'Layout reflows' section below 114 | // options: 115 | // GeometryLockMode.None = 0 | false 116 | // GeometryLockMode.Lock = 1 | true 117 | // GeometryLockMode.Auto = 'auto' 118 | lock: GeometryLockMode.None 119 | }, 120 | 121 | // hook called right before enter transition starts 122 | onEnter: () => {}, 123 | 124 | // hook called after enter transition completed 125 | onAfterEnter: () => {}, 126 | 127 | // hook called right before leave transition starts 128 | onLeave: () => {}, 129 | 130 | // hook called after leave transition completed 131 | onAfterLeave: () => {} 132 | }) 133 | ``` 134 | # Simple CSS transition 135 | 136 | Let's create our first custom transition. 137 | We will call it `spin3D` and, we want it to 138 | rotate out old content and spin in new content 139 | so everyone get's dizzy. 140 | 141 | Let's first create the css classes we need for our enter transition: 142 | ```css 143 | /* 144 | * let's be lazy and just transition all 145 | * css props. a more clean solution is to 146 | * only transition the needed props! 147 | */ 148 | .enter-active, .leave-active { 149 | transition: all 0.2s linear; 150 | } 151 | 152 | /* 153 | * when a new template enters, it starts 154 | * with these props, so it will be very transparent 155 | * a bit blurry and rotated 180degres away 156 | */ 157 | .enter-from { 158 | opacity: 0.1; 159 | filter: blur(1px); 160 | transform: rotate3d(1, 0, 0.5, 180deg) scale(2); 161 | } 162 | 163 | /* 164 | * after entering, and befor leaving 165 | * we just set all transitioned properties to iniial 166 | */ 167 | .enter-to, .leave-from { 168 | opacity: initial; 169 | filter: initial; 170 | transform: initial; 171 | } 172 | 173 | /* 174 | * pretty much the same as .enter-from 175 | * the main difference is the rotation angle 176 | * so the rotation looks continuous and does not bounce 177 | */ 178 | .leave-to { 179 | opacity: 0.1; 180 | filter: blur(2px); 181 | transform: rotate3d(1, 0, 0.5, -180deg) scale(2); 182 | } 183 | ``` 184 | 185 | That's pretty much it, if we also specify `in-out` mode we have 186 | a neat rotating transition ready to be used with lit-transition. 187 | 188 | Putting everything together: 189 | 190 | 235 | 236 | 237 | # Advanced CSS transition 238 | 239 | In the last example we inject the css directly. 240 | If we can make it available in the transition context 241 | this is not necessary. 242 | 243 | This what we did in the example below. 244 | We also renamed the classes of `spin3D` merging some classes 245 | for the different states of enter and leave. 246 | In addition, we added some easing 247 | and went a bit more out there with what the animation does :) 248 | 305 | 306 | It is typically preferable to move your transition 307 | css with your other app css so it does not clutter your 308 | code. 309 | Moreover, performanc is better since 310 | the classes are only created once and simply applied during 311 | transition phases. 312 | 313 | > It is important your css classes actually execute 314 | > a transition since lit-transition uses 'ontransitionend' 315 | > and 'onanimationend' events to determine if your animation 316 | > has finished so the next transition can be scheduled. 317 | > If your animations never start or finish, this mechanism breaks. 318 | > You can specify a fixed duration using the `duration` field in 319 | > the transition optins to nail it down in case of problems. 320 | 321 | > If you see elements piling up in your document 322 | > it likely means your transitions are not finishing! 323 | 324 | # CSS Animations 325 | 326 | css animations can be used pretty much the analogously. 327 | Since typically keyframes are used to define web animations, 328 | no `from` and `to` phases are are required. 329 | 330 | If you assign a string or an array to the `enter` and `leave` fields 331 | of the transition options, the respective class names 332 | are used during the the `active` phase. 333 | 334 | 377 | 378 | # Layout reflows 379 | 380 | At some point during transitions the leaving template is removed 381 | and the entering template is added. 382 | 383 | If any of these items is part of the flow of the document (i.e. has `position: relative` and not `absolute` or `fixed`) this operation will lead to a reflow of 384 | your app. 385 | Typically transitions have a certain point in time where the leacing template gets taken 386 | out of the flow and the entering one gets added. 387 | Similarly, the css `display` property has a huge effect on how a DOM node 388 | is nested in the flow. 389 | 390 | > If you use `display: block`, a node will fill the whole line. 391 | > If you remove it from the flow by setting `position: fixed|absolute` 392 | > the extends of the object may change drastically. 393 | > If this is not desired, try `display: inline-block` to make the 394 | > DOM Node keep more of its geometry! 395 | 396 | 397 | If the `active` phase of your leave transition sets the position to `absolute` (or `fixed`), 398 | that element get's taken out of the flow the second its leave transition is started. 399 | In case of transition mode `TransitionMode.Both (==='both')` this might be fine since the entering element will be added right at that same time anyways. 400 | In case of `'out-in'` mode, however, the template would be taken out of the flow, and the re-calculated layout would probably collapse a bit taking up the empty space. 401 | So here, you likely want to keep elements in the flow of the document as long as they live. 402 | 403 | ### GeometryLockMode 404 | 405 | If you want to take the leaving template out of the flow by setting its 406 | position to `absolute`. 407 | The item might snap to a different location depending other css properties 408 | like margins etc. 409 | To help with this, we have a `lock` helper described in the transition options. 410 | 411 | ```javascript 412 | transition(template, { 413 | leave: { 414 | /*..*/, 415 | lock: true 416 | } 417 | }) 418 | ``` 419 | and set the position to `absolute` in your `leave-active` state. 420 | The geometry of the node right before applying the leave transition will be recorded 421 | and locked so you can for instance easily apply `transform: translate(..)` css 422 | without having to worry about positioning much. 423 | 424 | > Note: you will likely want to set the position of you parent element to `relative` 425 | > when using transitions with absolute positioning! 426 | 427 | If you are not sure or are having problems, try 'auto' mode. 428 | ```javascript 429 | transition(template, { 430 | leave: { 431 | /*..*/, 432 | lock: GeomtryLockMode.Auto (=='auto') 433 | } 434 | }) 435 | ``` 436 | It will try to detect if you are applying absolute positioning during the `-active` 437 | phase of your animation and are located in a container with `relative`positioning. 438 | In that case geometry will be locked for you! 439 | 440 | > __Tip__: if you have problems with animations, try grabbing an existing 441 | > working one that is close to what you want and tweak it. 442 | 443 | 444 | # Built-in transitions 445 | 446 | We have a set of predefined css-based transitions ready for use. 447 | These transitions have a set of custom options and try 448 | to derive some transition css in a context-aware way. 449 | 450 | They also forward all standard options (like `mode`, `duration`) 451 | to the underlying system: 452 | 453 | 499 | 500 | > Note: some of the built-in transitions manipulate positions, and by default they 501 | > assume 502 | 503 | ### Options 504 | 505 | The following options are available for our built-in transitions 506 | 507 | ```javascript 508 | interface CSSTransitionOptions { 509 | // css string or template 510 | css?: TemplateResult|string|null = null 511 | // duration in ms 512 | duration?: number = 500 513 | // enter classes {active,from,to} 514 | enter?: CSSClassSequence|Boolean = undefined 515 | // leave classes {active,from,to} 516 | leave?: CSSClassSequence|Boolean = undefined 517 | // enter classes {'in-out','out-in', 'both'} 518 | mode?: TransitionMode = 'both' 519 | // dont animate while document.hidden === true 520 | skipHidden?: Boolean = true 521 | // callbacks 522 | onEnter?: Function = undefined 523 | onLeave?: Function = undefined 524 | onAfterEnter?: Function = undefined 525 | onAfterLeave?: Function = undefined 526 | } 527 | 528 | interface CSSFadeOptions extends CSSTransitionOptions { 529 | // css easing options (default: ease-out) 530 | ease?: string, 531 | // opactiy to fade from and to (default: 0) 532 | opacity?: number 533 | } 534 | 535 | interface CSSSlideOptions extends CSSTransitionOptions { 536 | // easing options (default: ease-out) 537 | ease?: string 538 | // opacity at start of animation (default: 0) 539 | opacity?: number 540 | // force positioning (default: undefined) 541 | leavePosition?: string 542 | // slide to left (default: false) 543 | left?:Boolean 544 | // slide to right (default: false) 545 | right?:Boolean 546 | // slide to up (default: false) 547 | up?:Boolean 548 | // slide to down (default: false) 549 | down?:Boolean 550 | // slide out target x (default: 100%) 551 | x?: string 552 | // slide out target y (default: 0%) 553 | y?: string 554 | // slide in start x (default: same as x) 555 | x1?: string 556 | // slide in start y (default: same as y) 557 | y1?: string 558 | 559 | // additional options will be added to 560 | // to the TransitionOptions passed to the directive 561 | } 562 | 563 | interface CSSLandOptions extends CSSTransitionOptions { 564 | // css easing options (default: ease-out) 565 | ease?: string, 566 | // opacity to fade from and to (default: 0) 567 | opacity?: number 568 | } 569 | 570 | 571 | ``` 572 | -------------------------------------------------------------------------------- /doc/guide/05-js-transitions.md: -------------------------------------------------------------------------------- 1 | Javascript Transitions 2 | 3 | soon.. -------------------------------------------------------------------------------- /doc/guide/07-examples.md: -------------------------------------------------------------------------------- 1 | More Examples 2 | 3 | Let's go to town and actually use lit-transition for some cool stuff. 4 | 5 | # Simple slideshow 6 | 7 | With lit-transition it is very easy to create a slide-show component with a few lines. 8 | have a look at the comments in the code (probably 80% of it is unrelated logic, and styling) 9 | 10 | 105 | 106 | # Use with animate.css 107 | 108 | [animate.css](https://daneden.github.io/animate.css/) is a neat collection 109 | of css animations. 110 | It is ridiculously easy to combine it with the lit-transition directive. 111 | In fact, We use animate.css on many of the tansitions in this documentation. 112 | 113 | For this, make sure animate.css is available where your transitions are used. 114 | 115 | ```html 116 | 117 | 119 | 120 | ``` 121 | 122 | After this, it is enough set the `enter` and `leave` options 123 | in our transition. 124 | You need to add 'animated' class and whatever transition class you 125 | want to use. 126 | In the example below, we add another class 'absolute' for the leave transition 127 | since we set the transition mode to `Transition.Both` so the entering conent 128 | will immediately take the space of the leaving content in the flow when transitioning. 129 | 130 | > Settinng only `enter: ..` or `leave: ..` is equivalent to setting `enter: { active: .. }` 131 | > or `leave: { active: .. }`. 132 | > Using an array, as we do below, will simply apply multiple classes. 133 | 134 | 194 | 195 | 196 | # Use with lit-element-router 197 | 198 | TODO 199 | 200 | Basically you just need to add the transition directive 201 | whereever your templates are swapped. -------------------------------------------------------------------------------- /doc/guide/10-limitations.md: -------------------------------------------------------------------------------- 1 | Limitations and Notes 2 | 3 | 4 | # General remarks 5 | 6 | This goes without saying, but be aware that.. 7 | 8 | * animating large sections of your web app will 9 | most certainly come at a __performance cost__. 10 | * transitions need to be employed with care 11 | so they don't interrupt the user experience. 12 | Where transitions block user interaction, 13 | __200ms__ should typically not be exceeded. 14 | 15 | 16 | # Single roots 17 | 18 | Templates passed to the `transition` directive are currently required to have only one 19 | root node. 20 | Templates with multipe root nodes will work but will not animate all children. 21 | 22 | ```javascript 23 | // will not work as expected 24 | export const render = () => 25 | transition(html`....`); 26 | 27 | // will work as expected 28 | export const render = () => 29 | transition(html`..`); 30 | ``` 31 | 32 | # Interplay with other directives 33 | 34 | Since lit-transition manages the rendered dom and holds on to 35 | references during transitions, mixing it with other directives that do 36 | the same may lead to undesired effects. 37 | 38 | ```javascript 39 | // will not animate 40 | export const render = () => 41 | html`${(transition(asyncReplace(/*temlplate*/))}`; 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc/index.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import 'highlight.js/styles/atelier-savanna-dark.css'; 3 | import './demo'; 4 | import './landing'; 5 | 6 | import { html } from 'lit-element'; 7 | import {cache} from 'lit-html/directives/cache'; 8 | import {index} from './loaders/md-loader?folder=./guide!'; 9 | import router from './router'; 10 | import {transition, mark } from 'lit-transition'; 11 | import {transLanding,transContent,transTitle,transSubNav} from './transitions'; 12 | import github from './assets/github.svg'; 13 | import {version} from './version'; 14 | 15 | 16 | // main app 17 | class Component extends router() { 18 | 19 | render() { 20 | return html` 21 | { 22 | if(e.target.tagName === 'A') { 23 | const href = e.target.href; 24 | if(href.startsWith(window.location.origin)) { 25 | e.preventDefault(); 26 | this.navigate(href); 27 | } 28 | } 29 | }}> 30 | ${cache( 31 | html` 32 | { 33 | this.route === 'Landing' && this.navigate('about') 34 | this.menu = !this.menu; 35 | }} mobile-menu-button>☰ 36 | 37 | lit-transition 38 | ${version} 39 | 40 | 41 | ${github} 42 | doc 43 | ` 44 | )} 45 | ${transition( 46 | this.route === 'Landing' ? 47 | mark(html` 48 | 49 | 50 | `, 'landing') : 51 | mark(this.page, 'page') 52 | , transLanding 53 | )} 54 | `; 55 | } 56 | 57 | get nav() { 58 | return index.map(i => { 59 | const active = this.route===i.title; 60 | return [ 61 | html`${i.title}`, 62 | // subsections 63 | transition(active ? mark(html` 64 | ${i.index.map((s,j) => html` 65 | ${s} 66 | ` )} 67 | `,i.route+'-sub') : undefined, transSubNav) 68 | ] 69 | }); 70 | } 71 | 72 | get page() { 73 | return html` 74 | 75 | ${this.nav} 76 | 77 | 78 | 79 | 80 | ${transition(this.routeTitle, transTitle)} 81 | ${transition(this.renderContent, { 82 | ...transContent 83 | })} 84 | 85 | 86 | `; 87 | } 88 | } 89 | 90 | customElements.define('doc-app', Component); -------------------------------------------------------------------------------- /doc/index.scss: -------------------------------------------------------------------------------- 1 | body, html { 2 | font-family: Roboto; 3 | font-size: 14px; 4 | display: flex; 5 | flex-direction: column; 6 | --theme-color: rgb(93, 186, 214); 7 | --theme-color-trans-opaque: #c7eaaa; 8 | --bg-color: #FFFFFF; 9 | --font-color: #000000; 10 | padding: 0px; 11 | overflow: hidden; 12 | margin: 0px; 13 | height: 100%; 14 | flex: 1 1; 15 | } 16 | 17 | code { 18 | color: var(--theme-color); 19 | } 20 | 21 | doc-app, doc-app > div { 22 | flex: 1 1; 23 | display: flex; 24 | flex-direction: column; 25 | overflow: hidden; 26 | } 27 | 28 | 29 | [layout] { 30 | position: relative; 31 | overflow: hidden; 32 | height: 100%; 33 | } 34 | 35 | svg { 36 | fill: var(--bg-color); 37 | } 38 | 39 | a { 40 | color: var(--bg-color); 41 | } 42 | 43 | header { 44 | height: 60px; 45 | position: fixed; 46 | z-index: 3; 47 | top: 0px; 48 | width: 100%; 49 | background-image: linear-gradient( 50 | to right, 51 | var(--theme-color), 52 | var(--theme-color-trans-opaque) 53 | ); 54 | font-size: 20px; 55 | line-height: 54px; 56 | font-weight: 600; 57 | color: #ffffff; 58 | 59 | svg { 60 | margin-bottom: -4px; 61 | } 62 | a { 63 | float: right; 64 | font-weight: 100; 65 | padding-left: 20px; 66 | padding-right: 20px; 67 | transition: border 0.5s; 68 | border-bottom: 3px solid rgba(255,255,255,0); 69 | border-top: 3px solid rgba(255,255,255,0); 70 | 71 | } 72 | a:hover { 73 | border-bottom: 3px solid var(--bg-color); 74 | } 75 | 76 | [mobile-menu-button] { 77 | display: none; 78 | float: left; 79 | padding-left: 20px; 80 | padding-right: 20px; 81 | } 82 | [mobile-menu] { 83 | display: none; 84 | } 85 | 86 | [title] { 87 | float: left; 88 | font-weight: inherit; 89 | margin-left: 40px; 90 | > span { 91 | font-size: 12px; 92 | font-weight: 100; 93 | vertical-align: top; 94 | } 95 | } 96 | [underline]:before { 97 | width: 22px; 98 | height: 1px; 99 | left: 18px; 100 | margin-top: 40px; 101 | border-bottom: 1px solid var(--bg-color); 102 | } 103 | } 104 | 105 | 106 | main { 107 | flex: 1 1; 108 | position: fixed; 109 | left: var(--nav-width); 110 | top: 0px; 111 | height: 100%; 112 | width: calc(100% - var(--nav-width) - 64px); 113 | overflow-y: scroll; 114 | overflow-x: hidden; 115 | flex-direction: row; 116 | padding-left: 32px; 117 | padding-right: 32px; 118 | } 119 | 120 | nav { 121 | flex: 1 1; 122 | width: var(--nav-width); 123 | position: fixed; 124 | display: flex; 125 | flex-direction: column; 126 | left: var(--nav-left); 127 | top: 120px; 128 | 129 | a { 130 | color: inherit; 131 | display: block; 132 | cursor: pointer; 133 | padding: 4px; 134 | transition: color 0.2s; 135 | } 136 | a[active], a:hover { 137 | color: var(--theme-color); 138 | } 139 | > ul { 140 | padding: 0px; 141 | margin: 0px; 142 | margin-left: 20px; 143 | > li { 144 | margin-left: 0px; 145 | list-style-type: none; 146 | font-size: 12px; 147 | > a { 148 | font-size: 12px; 149 | padding: 5px; 150 | } 151 | } 152 | } 153 | } 154 | 155 | h1[title] { 156 | font-size: 40px; 157 | color: var(--theme-color); 158 | } 159 | 160 | h1[title]:before, [underline]:before { 161 | content: ''; 162 | float: left; 163 | width: 40px; 164 | height: 2px; 165 | margin-right: -40px; 166 | margin-left: -4px; 167 | margin-top: 47px; 168 | border-bottom: 2px solid; 169 | } 170 | 171 | content { 172 | display: inline-block; 173 | overflow: hidden; 174 | max-width: 500px; 175 | width: calc(100%); 176 | margin-top: 90px; 177 | margin-bottom: 30px; 178 | h1[title]:before { 179 | border-bottom: 2px solid rgba(0,0,0,0.7); 180 | } 181 | 182 | a { 183 | color: var(--theme-color); 184 | } 185 | a:visited { 186 | color: var(--theme-color); 187 | } 188 | blockquote { 189 | margin: 2em 0; 190 | margin-left: 10px; 191 | padding: 4px; 192 | padding-left: 20px; 193 | border-left: 4px solid var(--theme-color); 194 | background: #f8f8f8; 195 | position: relative; 196 | > p:after { 197 | content: "!"; 198 | background-color: var(--theme-color); 199 | position: absolute; 200 | top: calc(50% - 10px); 201 | left: -12px; 202 | color: #fff; 203 | width: 20px; 204 | height: 20px; 205 | border-radius: 100%; 206 | text-align: center; 207 | line-height: 20px; 208 | font-weight: bold; 209 | font-family: "Dosis", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 210 | font-size: 14px; 211 | 212 | } 213 | } 214 | } 215 | 216 | [app] { 217 | --nav-width: 300px; 218 | --nav-left: 60px; 219 | } 220 | 221 | a { 222 | text-decoration: none; 223 | } 224 | a:visited { 225 | color: inherit; 226 | } 227 | 228 | doc-landing { 229 | display: flex; 230 | flex: 1 1; 231 | margin-top: 60px; 232 | height: 100%; 233 | flex-direction: column; 234 | flex-wrap: nowrap; 235 | 236 | background-image: linear-gradient( 237 | to right, 238 | var(--theme-color), 239 | var(--theme-color-trans-opaque) 240 | ); 241 | 242 | h2 { 243 | margin-bottom: 0px; 244 | text-align: left; 245 | } 246 | 247 | > div { 248 | flex: 1 1; 249 | display: flex; 250 | height: 100%; 251 | flex-direction: column; 252 | 253 | > div { 254 | width: 100%; 255 | margin: auto; 256 | display: flex; 257 | flex-direction: row; 258 | flex-wrap:nowrap; 259 | 260 | [underline] { 261 | color: var(--bg-color); 262 | } 263 | [underline]:before { 264 | margin-top: 65px; 265 | width: 55px; 266 | margin-right: -60px; 267 | border-color: var(--bg-color); 268 | } 269 | 270 | > * { 271 | flex: 1 1; 272 | margin: auto; 273 | } 274 | 275 | h1 { 276 | font-size: 60px; 277 | margin-bottom: 20px; 278 | } 279 | 280 | 281 | 282 | [short] { 283 | p { 284 | margin-top: 0px; 285 | margin-bottom: 30px; 286 | } 287 | svg { 288 | margin-bottom: -5px; 289 | fill: var(--font-color); 290 | } 291 | } 292 | } 293 | } 294 | } 295 | 296 | doc-demo { 297 | .result { 298 | margin: 5px; 299 | padding: 5px; 300 | margin-bottom: 20px; 301 | /*border-top: 1px solid rgba(0,0,0,0.2);*/ 302 | } 303 | } 304 | 305 | [short], [teaser] { 306 | flex: 1 1; 307 | position: relative; 308 | font-size: 18px; 309 | height: 100%; 310 | width: 50%; 311 | display: flex; 312 | flex-direction: column; 313 | > div { 314 | flex: 1 1; 315 | margin: auto; 316 | } 317 | } 318 | [short] { 319 | > div { 320 | margin-top: 0px; 321 | margin-bottom: 80px; 322 | margin-left: 60px; 323 | margin-right: 0px; 324 | } 325 | } 326 | [teaser] { 327 | > div { 328 | max-width: 400px; 329 | margin-left: 0px; 330 | margin-right: 80px; 331 | } 332 | } 333 | 334 | .fixed { 335 | position: fixed; 336 | } 337 | .absolute { 338 | position: absolute; 339 | } 340 | .top { 341 | z-index: 2; 342 | } 343 | .transformOriginTop { 344 | transform-origin: 50% 0px; 345 | } 346 | 347 | 348 | .hljs { 349 | font-size: 12px; 350 | border-radius: 4px; 351 | padding: 10px !important; 352 | margin: 2px; 353 | margin-top: 12px; 354 | box-shadow: 2px 2px 2px rgba(0,0,0,0.2); 355 | 356 | code { 357 | color: inherit; 358 | } 359 | } 360 | 361 | [stackblitz] { 362 | float: right; 363 | font-size: 12px; 364 | --sb-color1: #1389FD; 365 | --sb-color2: #ffffff; 366 | cursor: pointer; 367 | transition: all 0.5s; 368 | svg { 369 | fill: var(--sb-color1); 370 | margin-bottom: -7px; 371 | } 372 | color: var(--sb-color1); 373 | background: var(--sb-color2); 374 | border: 2px solid var(--sb-color1); 375 | border-radius: 6px; 376 | padding: 4px; 377 | padding-right: 12px; 378 | line-height: 20px; 379 | margin-top: -6px; 380 | vertical-align: center; 381 | } 382 | 383 | [stackblitz]:hover { 384 | background: var(--sb-color1); 385 | color: var(--sb-color2); 386 | border: 2px solid var(--sb-color2); 387 | svg { 388 | fill: var(--sb-color2); 389 | } 390 | } 391 | 392 | 393 | @media screen and (max-width: 980px) { 394 | [app] { 395 | --nav-left: 20px; 396 | --nav-width: 200px; 397 | } 398 | [teaser] { 399 | > div { 400 | max-width: 400px; 401 | margin-left: 40px; 402 | margin-right: 40px; 403 | } 404 | } 405 | } 406 | 407 | 408 | // Medium devices (tablets, 768px and up) 409 | @media screen and (max-width: 768px) { 410 | 411 | [app] { 412 | --nav-left: 20px; 413 | --nav-width: 180px; 414 | } 415 | [teaser] { 416 | display: none; 417 | } 418 | } 419 | 420 | 421 | // Medium devices (tablets, 768px and up) 422 | @media screen and (max-width: 600px) { 423 | header { 424 | [mobile-menu-button] { 425 | display: initial; 426 | } 427 | [title] { 428 | margin-left: 0px; 429 | } 430 | } 431 | [app] { 432 | --nav-left: 0px; 433 | --nav-width: 0px; 434 | } 435 | 436 | nav { 437 | transition: all 0.2s; 438 | opacity: 1; 439 | width: initial; 440 | height: 100%; 441 | left: -100%; 442 | top: 60px; 443 | position: fixed; 444 | > a { 445 | font-size: 16px; 446 | } 447 | 448 | padding: 20px; 449 | } 450 | 451 | nav[menu] { 452 | opacity: 1; 453 | display: initial; 454 | left: 0%; 455 | height: 100%; 456 | z-index: 10; 457 | background: var(--bg-color); 458 | border-right: 1px solid var(--theme-color); 459 | } 460 | [short] { 461 | > div { 462 | margin-left: 20px; 463 | margin-right: 20px; 464 | } 465 | } 466 | } 467 | 468 | // Phones 469 | @media screen and (max-width: 400px) { 470 | header { 471 | a { 472 | padding-left: 00px; 473 | padding-right: 20px; 474 | } 475 | [title] { 476 | position: relative; 477 | > span { 478 | position: absolute; 479 | font-size: 12px; 480 | right: 20px; 481 | bottom: -18px; 482 | } 483 | } 484 | } 485 | } 486 | 487 | 488 | -------------------------------------------------------------------------------- /doc/landing.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html} from 'lit-element'; 2 | import {asyncReplace} from 'lit-html/directives/async-replace.js'; 3 | import {transition} from 'lit-transition'; 4 | import {transTeaser as trans, transWaitBar} from './transitions'; 5 | import arrow from './assets/arrow.svg'; 6 | 7 | const interval = 8000; 8 | 9 | class Component extends LitElement { 10 | 11 | createRenderRoot() { 12 | return this; 13 | } 14 | 15 | render() { 16 | return html` 17 | 18 | 19 | 20 | lit-transition 21 | 22 | 23 | A tiny yet effective transition directive for lit-html 24 | 25 | 26 | 27 | ${arrow} 28 | GET STARTED 29 | 30 | 31 | 32 | 33 | ${asyncReplace(this.teaser())} 34 | 35 | `; 36 | } 37 | 38 | async *teaser() { 39 | while (true) { 40 | let one = teasers[0]; 41 | teasers.splice(0,1); 42 | teasers.push(one); 43 | // wrapping a template using transition directive will 44 | // automatically animate it on change 45 | yield transition(one,trans); 46 | await new Promise(r => setTimeout(r, interval)); 47 | } 48 | } 49 | } 50 | 51 | customElements.define('doc-landing', Component); 52 | 53 | const teasers = [ 54 | html ` 55 | Dead simple 56 | Bold\`; 60 | // will animate when 'dynamic' changes 61 | render( 62 | html\`\${transition(dynamic)}\`, 63 | document.body 64 | );` 65 | }>`, 66 | html ` 67 | Configurable 68 | { /* hook */ } 74 | css: \` 75 | .my-enter { 76 | rotate3d(...) 77 | }\` 78 | } 79 | )` 80 | }>` 81 | ].map(t => html` 82 | ${t} 83 | ${transition(html``, transWaitBar(interval))} 84 | `); -------------------------------------------------------------------------------- /doc/loaders/md-loader.js: -------------------------------------------------------------------------------- 1 | const { getOptions } = require('loader-utils'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const MarkdownIt = require('markdown-it'); 5 | const hljs = require('highlight.js'); 6 | 7 | const md = new MarkdownIt({ 8 | html: true, 9 | highlight: function (str, lang) { 10 | if (lang && hljs.getLanguage(lang)) { 11 | try { 12 | return '' + 13 | hljs.highlight(lang, str, true).value + 14 | ''; 15 | } catch (__) {} 16 | } 17 | return '' + md.utils.escapeHtml(str) + ''; 18 | } 19 | }); 20 | 21 | module.exports = function(source) { 22 | const options = getOptions(this); 23 | 24 | // script blocks 25 | if(options.block&&options.file) { 26 | const {title,markdown,blocks} = processMd(options.file); 27 | this.dependency(options.file); 28 | //return `export default () => 'sers'`; 29 | const {code,opts} = blocks[Number(options.block)]; 30 | return ` 31 | ${code} 32 | export const code = ${JSON.stringify(code)}; 33 | ` 34 | 35 | } 36 | 37 | // index + markdown 38 | if(options.folder) { 39 | const imports = []; 40 | // Apply some transformations to the source... 41 | const root = path.join(__dirname,'..',options.folder); 42 | const files = fs.readdirSync(path.join(root)); 43 | const data = files.map(file => { 44 | const mdFile = path.join(root,file); 45 | this.dependency(mdFile); 46 | const {title, markdown, blocks, index} = processMd(mdFile); 47 | 48 | blocks.forEach(({opts},i) => 49 | imports.push(`./doc/loaders/md-loader.js?file=${mdFile}&block=${i}&opts=${opts}!`) 50 | ); 51 | return { 52 | file, 53 | title, 54 | index, 55 | markdown: md.render(markdown) 56 | } 57 | }) 58 | 59 | // 60 | return ` 61 | export function load(id) { 62 | // generate import statements so chunks are generated 63 | // by webpack 64 | switch(id) { 65 | ${imports.map(i => `case '${i}': return import('${i}');`).join('\n')} 66 | } 67 | } 68 | export const index = ${ JSON.stringify(data) } 69 | `; 70 | } 71 | 72 | 73 | } 74 | 75 | function processMd(mdFile) { 76 | let markdown = fs.readFileSync(mdFile, 'utf8').split('\n'); 77 | const title = markdown[0]; 78 | markdown = markdown.slice(1).join('\n'); 79 | const blocks = []; 80 | markdown = markdown.replace(/
${h}
23 | A tiny yet effective transition directive for lit-html 24 |
' + 13 | hljs.highlight(lang, str, true).value + 14 | '
' + md.utils.escapeHtml(str) + '