├── .gitignore ├── README.md ├── client └── elements │ ├── buildShadowRoot.js │ ├── element-actions.js │ ├── element-display.js │ ├── element-item.js │ ├── element-list.js │ ├── element-properties.js │ ├── element-story.js │ ├── index.css │ ├── prop-item.js │ └── property-display.js ├── element-storybook.gif ├── index.js ├── objects ├── story.js └── storybook.js ├── package.json ├── templates ├── base.js └── item.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prova 2 | element-storybook is being renamed to prova, and being re-written. I want to do things with prova that can only be done with web components and will meet the needs of web components better than other testers. 3 | 4 | # element-storybook 5 | This is a development and testing environment for native components. 6 | _This is in beta and may have extensiv changes, all efforts will be provided to create non breaking changes, but there is no guarentee, and if a feature is needed that will benefit the product but cause a breaking change, it will be implamented._ 7 | 8 | ![Storybook screen](https://github.com/LuceStudio/element-storybook/blob/master/element-storybook.gif?raw=true) 9 | 10 | In a future release JS modules will be the default type and the UI will be moved over to that. I just want to give you a heads up. 11 | 12 | # How to use: 13 | Element-storybook is build on express and allows you to create an instance and configure it's deployment. It is a development tool, as well as a display tool as in using React-Storybook I have found it has been needed to showcase elements. 14 | 15 | To create an instance in your node server: 16 | ``` 17 | const storybook = new Storybook({ 18 | stories: `client/elements/**/*.story.js`, 19 | storybookRoot: '/element-storybook/', 20 | pathToElements: '/elements/', 21 | pathToPolyfills: `/polyfills/`, 22 | moduleType: 'html' 23 | app, 24 | dir: path.join(__dirname,`../`), 25 | stylesheet: '/path/to/stylesheet', 26 | inject: '' 27 | }); 28 | ``` 29 | You can now use JS import style instead of HTML imports. The property `moduleType` can be set to JS to allow this. 30 | 31 | ## Parts 32 | 33 | - stories: Path, using glob, to your stories. 34 | - storybookRoot: URL Path you want storybook served. For example `/element-storybook/` would serve at `http://yourServer/element-storybook`. 35 | - pathToElements: URL Path to the element directory in your local project. 36 | - pathToPolyfills: Element-storybook doesn't load the polyfills itself. We are assuming, since you are developing custom elements, you have your own support taken care of. So you can specify the path to your polyfills. If you need more information about the polyfills goto the [Web Components Project on GitHub](https://github.com/webcomponents/webcomponentsjs) 37 | - app: the express app. 38 | - dir: The root of your project, this may not be the same as `__dirname` as that is the root of the node process and some projects are organized to have a server directory and a client directory. This would be the root of the project. 39 | - stylesheet: This is a stylesheet that will be injected into the iframe to see how your project styles might effect the element. 40 | - inject: this is a block that allows you to inject anything right before the closing body tag. 41 | 42 | 43 | # Creating a story 44 | At this time storybook is assuming a little about your elements. 45 | 46 | - That they are in one directory 47 | - That that directory is organized with: 48 | - a folder that matches the name of the element 49 | - in that folder is an HTML file with the name of your element. 50 | - a story in that same folder. 51 | 52 | ## directory structure 53 | For example assume I have an elements folder for my elements. 54 | ``` 55 | - elements 56 | - my-element 57 | - my-element.html 58 | - my-element.story.js 59 | - another-element 60 | - another-element.html 61 | - another-element.story.js 62 | ``` 63 | 64 | If you are using JS module types instead of an `.html` file you would have a `.js` file. 65 | 66 | ## To make a story: 67 | ``` 68 | const {Story, Props} = require('element-storybook'); 69 | 70 | let story = new Story('my-element'); 71 | 72 | story.addProp('prop1') 73 | .addProp('prop2','bool') 74 | .addProp('--cssProp') 75 | .addProp('prop3',['option 1','options 2','options 3','option 4']); 76 | 77 | story.addSlotElement('another-element'); 78 | 79 | story.add(`default`, ` 80 | 81 | `) 82 | .add(`with child`, ` 83 | 84 | 85 | 86 | `); 87 | 88 | module.exports = story; 89 | ``` 90 | 91 | Story provides a few functions to create the story: 92 | 93 | - constructor: 94 | - `element`: the name of the element this is a story for. 95 | - add: This adds a story and accepts to params: 96 | - `story name`: defines the name of the story 97 | - `markup`: defines the markup for the story, including the element. 98 | - addProp: this adds a property to your element that can be modified via the interface. 99 | - `prop name`: Name of the prop. This can include custom properties from CSS by adding `--` to the front, or if you want to change the slot use `slot` as the prop name and it will allow you to freeform change the contents of the element. 100 | - `options` - optional: this has a few options - if the value is: 101 | - not present: it is assumed any value can be accepted and will be interpritade as a free form field. 102 | - `bool`: it will be rendered as a true false property and will be based on the presence of the property and rendered as a checkbox. 103 | - An array: it will be taken as the list of viable options and will be renedered as a select box. 104 | - addSlotElement: allows you to add an element to the slot that is also a custom element. It takes a string or an array and is the name of the element you are using. 105 | 106 | All functions return the story so can be chained. 107 | -------------------------------------------------------------------------------- /client/elements/buildShadowRoot.js: -------------------------------------------------------------------------------- 1 | export default (html, elem) => { 2 | const template = document.createElement('template'); 3 | template.innerHTML = html; 4 | typeof ShadyCSS !== 'undefined' && ShadyCSS.prepareTemplate(template, elem.localName); 5 | 6 | const shadowRoot = elem.attachShadow({mode: `open`}); 7 | shadowRoot.appendChild(template.content.cloneNode(true), true); 8 | 9 | typeof ShadyCSS !== 'undefined' && ShadyCSS.styleElement(elem); 10 | return shadowRoot; 11 | } 12 | -------------------------------------------------------------------------------- /client/elements/element-actions.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js' 2 | 3 | class ElementActions extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 7 | 31 |
32 | 33 |
`; 34 | 35 | buildShadowRoot(html, this); 36 | } 37 | 38 | } 39 | customElements.define('element-actions', ElementActions); 40 | export default ElementActions; 41 | -------------------------------------------------------------------------------- /client/elements/element-display.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js' 2 | 3 | class ElementDisplay extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ``; 17 | 18 | buildShadowRoot(html, this); 19 | 20 | this.elems = { 21 | iframe: document.createElement('iframe') 22 | }; 23 | this.elems.iframe.addEventListener('load', this.buildDevArea.bind(this)); 24 | this.shadowRoot.appendChild(this.elems.iframe); 25 | 26 | document.addEventListener('stiva-element', this.handleNewElement.bind(this)); 27 | document.addEventListener('stiva-properties', this.handleUpdatedProp.bind(this)); 28 | } 29 | buildDevArea(){ 30 | this.elems.iframeDoc = this.elems.iframe.contentDocument; 31 | this.addPolyfills(); 32 | this.cleanIframe(); 33 | this.addImports(); 34 | } 35 | handleUpdatedProp(e){ 36 | e.detail.forEach( (prop) => { 37 | const {name, value = undefined} = prop; 38 | const {targetElement} = this.elems; 39 | if(value){ 40 | if(name.startsWith('--')){ 41 | targetElement.style.setProperty(name, value); 42 | } 43 | else if(name === 'slot'){ 44 | targetElement.innerHTML = value; 45 | } 46 | else{ 47 | targetElement.setAttribute(name, value); 48 | } 49 | } 50 | }); 51 | } 52 | reload(){ 53 | this.elems.iframeDoc.location.reload(); 54 | setTimeout( () => stiva.dispatchAll(), 300); 55 | } 56 | addPolyfills(){ 57 | const s = document.createElement('script'); 58 | s.src = `${this.polyfills}/webcomponents-loader.js`; 59 | this.elems.iframeDoc.head.appendChild(s); 60 | } 61 | handleNewElement(e){ 62 | let {name, slotElements, stories, currentStory} = e.detail; 63 | this.elems.iframeDoc.body.innerHTML = `${stories[currentStory].markup}`; 64 | this.elems.targetElement = this.elems.iframeDoc.querySelector(name); 65 | this.elements = `${name},${slotElements}`; 66 | this.addImports(); 67 | 68 | // change out hte innerTHML and update the URL 69 | document.title = `${name} - Element Storybook`; 70 | window.history.pushState({"pageTitle":document.title},document.title, `${this.storybookroot}/${name}/${stories[currentStory].name}`); 71 | } 72 | addImports(){ 73 | 74 | let generateElement = (tag, attr, value, ext, href) => { 75 | this.elems.iframeDoc.head.querySelectorAll(`${tag}[${attr}="${value}"]`).forEach((elem) => elem.remove()); 76 | this.elements.split(',').forEach( (elem) => { 77 | if(elem){ 78 | let item = document.createElement(tag); 79 | item[attr] = value; 80 | item[href] = `${this.rootpath}/${elem}/${elem}.${ext}`; 81 | this.elems.iframeDoc.head.appendChild(item); 82 | } 83 | }); 84 | } 85 | if(this.moduleType === 'js' || this.moduleType === 'undefined'){ 86 | generateElement('script', 'type', 'module', 'js', 'src'); 87 | } 88 | else if(this.moduleType === "html"){ 89 | generateElement('link', 'rel', 'import', 'html', 'href'); 90 | } 91 | } 92 | cleanIframe(){ 93 | let style = document.createElement('style'); 94 | style.innerHTML = ` 95 | html, body { 96 | margin: 0; 97 | padding: 0; 98 | } 99 | `; 100 | this.elems.iframeDoc.head.appendChild(style); 101 | if(this.stylesheet){ 102 | let link = document.createElement('link'); 103 | link.href = this.stylesheet; 104 | link.rel = "stylesheet"; 105 | this.elems.iframeDoc.head.appendChild(link); 106 | } 107 | 108 | let script = document.createElement('script'); 109 | script.async = true; 110 | script.src = `${this.storybookroot}/stiva.js`; 111 | script.onload = script.onreadystatechange = (e) => { 112 | let rs = e.target.readyState; 113 | if (rs && rs !== 'complete' && rs !== 'loaded') { return; } 114 | this.elems.iframe.contentWindow.stiva = new Stiva({},this.elems.iframeDoc); 115 | }; 116 | this.elems.iframeDoc.head.appendChild(script); 117 | 118 | this.elems.iframeDoc.stiva = new Stiva(); 119 | this.elems.iframeDoc.body.classList.add('WebComponentsReady'); 120 | } 121 | get elements(){ 122 | return this.getAttribute('elements'); 123 | } 124 | set elements(val){ 125 | if(val){ 126 | this.setAttribute('elements', val); 127 | } 128 | else { 129 | this.removeAttribute('elements'); 130 | } 131 | } 132 | get rootpath(){ 133 | return this.getAttribute('rootpath'); 134 | } 135 | set rootpath(val){ 136 | if(val){ 137 | this.setAttribute('rootpath', val); 138 | } 139 | else { 140 | this.removeAttribute('rootpath'); 141 | } 142 | } 143 | get storybookroot(){ 144 | return this.getAttribute('storybookroot'); 145 | } 146 | set storybookroot(val){ 147 | if(val){ 148 | this.setAttribute('storybookroot', val); 149 | } 150 | else { 151 | this.removeAttribute('storybookroot'); 152 | } 153 | } 154 | get polyfills(){ 155 | return this.getAttribute('polyfills'); 156 | } 157 | set polyfills(val){ 158 | if(val){ 159 | this.setAttribute('polyfills', val); 160 | } 161 | else { 162 | this.removeAttribute('polyfills'); 163 | } 164 | } 165 | get stylesheet(){ 166 | return this.getAttribute('stylesheet'); 167 | } 168 | set stylesheet(val){ 169 | if(val){ 170 | this.setAttribute('stylesheet', val); 171 | } 172 | else { 173 | this.removeAttribute('stylesheet'); 174 | } 175 | } 176 | 177 | get moduleType(){ 178 | return this.getAttribute('moduletype'); 179 | } 180 | set moduleType(val){ 181 | if(val){ 182 | this.setAttribute('moduletype', val); 183 | } 184 | else { 185 | this.removeAttribute('moduletype'); 186 | } 187 | } 188 | 189 | 190 | } 191 | customElements.define('element-display', ElementDisplay); 192 | export default ElementDisplay; 193 | -------------------------------------------------------------------------------- /client/elements/element-item.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js' 2 | 3 | class ElementItem extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 7 | 47 | 48 | ` 49 | buildShadowRoot(html, this); 50 | this.elems = { 51 | block: this.shadowRoot.querySelector('.block'), 52 | props: [].slice.apply(this.querySelectorAll('element-properties prop-item')) 53 | } 54 | this.elems.block.dataset.element = this.name; 55 | this.addEventListener('click', this.handleBlockClick.bind(this)); 56 | } 57 | handleBlockClick(e) { 58 | let props = this.elems.props.map( item => { 59 | if(item.values){ 60 | return { 61 | name: item.innerText, 62 | values: item.values.indexOf(',') > -1 ? item.values.split(',') : item.values 63 | } 64 | } 65 | return { name: item.innerText, values: undefined} 66 | 67 | }); 68 | 69 | 70 | stiva.update('element', oldStore => ({ 71 | currentStory: this.findStoryIndex(), 72 | name: this.name, 73 | props, 74 | slotElements: this.slotElements ? this.slotElements.split(',') : [], 75 | stories: [].slice.apply(this.children).slice(1).map( (story) => ({name: story.name, markup: story.innerHTML})) 76 | })); 77 | } 78 | findStoryIndex(){ 79 | if(this.story){ 80 | return [].slice.apply(this.querySelectorAll('element-story')).findIndex( story => story.name === this.story); 81 | } 82 | else { 83 | return 0; 84 | } 85 | } 86 | get name() { 87 | return this.getAttribute('name'); 88 | } 89 | set name(val) { 90 | if (val) { 91 | this.setAttribute('name', val); 92 | } else { 93 | this.removeAttribute('name'); 94 | } 95 | } 96 | get slotElements() { 97 | return this.getAttribute('slot-elements'); 98 | } 99 | set slotElements(val) { 100 | if (val) { 101 | this.setAttribute('slot-elements', val); 102 | } else { 103 | this.removeAttribute('slot-elements'); 104 | } 105 | } 106 | 107 | get story() { 108 | return this.getAttribute('story'); 109 | } 110 | set story(val) { 111 | if (val) { 112 | this.setAttribute('story', val); 113 | } else { 114 | this.removeAttribute('story'); 115 | } 116 | } 117 | 118 | get hidden() { 119 | return this.hasAttribute('hidden'); 120 | } 121 | set hidden(val) { 122 | if (val) { 123 | this.setAttribute('hidden', ''); 124 | } else { 125 | this.removeAttribute('hidden'); 126 | } 127 | } 128 | } 129 | customElements.define('element-item', ElementItem); 130 | export default ElementItem; 131 | -------------------------------------------------------------------------------- /client/elements/element-list.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js'; 2 | 3 | class ElementList extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 7 | 96 |
97 | Element Storybook 98 |
99 | 102 |
103 |
104 | 105 |
106 |
107 |
108 | list 109 | 110 |
111 |
112 |
113 |
114 | `; 115 | buildShadowRoot(html, this); 116 | this.elems = { 117 | list: this.shadowRoot.querySelector('header'), 118 | overlay: this.shadowRoot.querySelector('.overlay'), 119 | close: this.shadowRoot.querySelector('.close'), 120 | title: this.shadowRoot.querySelector('.title'), 121 | stories: this.shadowRoot.querySelector('.stories'), 122 | search: this.shadowRoot.querySelector('.search input') 123 | } 124 | document.addEventListener('stiva-element', this.handleElementChange.bind(this)); 125 | this.elems.stories.addEventListener('click', this.handleStoriesClick.bind(this)); 126 | this.elems.close.addEventListener('click', this.handleClose.bind(this)); 127 | this.elems.search.addEventListener('keyup', this.handleSearch.bind(this)); 128 | this.elems.search.addEventListener('change', this.handleSearch.bind(this)); 129 | this.currentStoryIndex = 0; 130 | this.stories = []; 131 | } 132 | handleSearch(e){ 133 | this.elems.overlay.classList.remove('active'); 134 | if(e.target.value){ 135 | [].slice.apply(this.querySelectorAll(`element-item`)).forEach( (child) => { 136 | child.hidden = true; 137 | }); 138 | 139 | if(e.target.value){ 140 | [].slice.apply(this.querySelectorAll(`element-item[name*=${e.target.value}]`)).forEach( (child) => { 141 | child.hidden = false; 142 | }); 143 | } 144 | } else { 145 | [].slice.apply(this.querySelectorAll(`element-item`)).forEach( (child) => { 146 | child.hidden = false; 147 | }); 148 | } 149 | } 150 | handleClose(e){ 151 | this.elems.overlay.classList.remove('active'); 152 | } 153 | handleElementChange(e){ 154 | const {stories, name, currentStory} = e.detail; 155 | this.elems.overlay.classList.add('active'); 156 | this.elems.title.innerText = name; 157 | this.elems.stories.innerHTML = [].slice.apply(stories).map( story => `${story.markup}` ).join(''); 158 | let active = this.querySelector('[active]') 159 | active.removeAttribute('active'); 160 | active.removeAttribute('story'); 161 | 162 | this.querySelector(`element-item[name="${name}"]`).setAttribute(`active`,``); 163 | this.currentStoryIndex = currentStory; 164 | this.stories = stories; 165 | this.selectActiveStory(this.elems.stories.querySelector(`[name="${this.stories[this.currentStoryIndex].name}"]`)); 166 | // put this on the bench until the element event finishes propigating. 167 | window.setTimeout(() => this.updateProps(), 100); 168 | } 169 | selectActiveStory(story){ 170 | let activeStory = this.elems.stories.querySelector('[active]'); 171 | if(activeStory){ activeStory.removeAttribute('active'); } 172 | story.setAttribute('active',''); 173 | } 174 | handleStoriesClick(e){ 175 | if(e.target.localName === 'element-story'){ 176 | this.currentStoryIndex = [].slice.apply(this.elems.stories.children).indexOf(e.target); 177 | 178 | stiva.update('element', oldStore => ({ 179 | currentStory: this.currentStoryIndex, 180 | name: oldStore.name, 181 | props: oldStore.props, 182 | slotElements: oldStore.slotElements, 183 | stories: oldStore.stories 184 | })); 185 | this.updateProps(); 186 | } 187 | } 188 | updateProps(){ 189 | const story = this.stories[this.currentStoryIndex]; 190 | const markup = story.markup; 191 | let temp = document.createElement('div'); 192 | temp.innerHTML = markup; 193 | const elem = temp.querySelector(this.elems.title.innerText); 194 | const props = stiva.stores.element.props.reduce( (a,n) => { 195 | const {name} = n; 196 | let value; 197 | if(name === "slot"){ 198 | value = elem.innerHTML; 199 | } 200 | else { 201 | if(name.startsWith('--')){ 202 | let style = elem.getAttribute('style'); 203 | if(style){ 204 | let reg = new RegExp(`${name}:(.*?)(?:;|$)`); 205 | value = reg.exec(style)[1].trim(); 206 | } 207 | } else { 208 | let val = elem.getAttribute(name); 209 | if(elem.hasAttribute(name)){ 210 | value = val || true; 211 | } 212 | } 213 | } 214 | a.push({name, value}); 215 | return a; 216 | },[]); 217 | stiva.update('properties', oldStore => props); 218 | } 219 | 220 | static get observedAttributes() { 221 | return [`title`]; 222 | } 223 | 224 | attributeChangedCallback(attrName, oldVal, newVal) { 225 | switch(attrName){ 226 | case `title`: 227 | this.elems.list.innerText = newVal; 228 | break; 229 | default: 230 | break; 231 | } 232 | } 233 | 234 | get title(){ 235 | return this.getAttribute('title'); 236 | } 237 | set title(val){ 238 | if(val){ 239 | this.setAttribute('title', val); 240 | } 241 | else { 242 | this.removeAttribute('title'); 243 | } 244 | } 245 | } 246 | customElements.define('element-list', ElementList); 247 | export default ElementList; 248 | -------------------------------------------------------------------------------- /client/elements/element-properties.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js'; 2 | 3 | class ElementProperties extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ``; 7 | buildShadowRoot(html, this); 8 | } 9 | } 10 | customElements.define('element-properties', ElementProperties); 11 | export default ElementProperties; 12 | -------------------------------------------------------------------------------- /client/elements/element-story.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from "./buildShadowRoot.js"; 2 | 3 | class ElementStory extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 20 | `; 21 | 22 | buildShadowRoot(html, this); 23 | this.elems = { 24 | span: this.shadowRoot.querySelector("span") 25 | }; 26 | } 27 | static get observedAttributes() { 28 | return [`name`]; 29 | } 30 | 31 | attributeChangedCallback(attrName, oldVal, newVal) { 32 | switch (attrName) { 33 | case `name`: 34 | this.elems.span.innerText = newVal; 35 | break; 36 | default: 37 | break; 38 | } 39 | } 40 | get name() { 41 | return this.getAttribute("name"); 42 | } 43 | set name(val) { 44 | if (val) { 45 | this.setAttribute("name", val); 46 | } else { 47 | this.removeAttribute("name"); 48 | } 49 | } 50 | get active() { 51 | return this.getAttribute("active"); 52 | } 53 | set active(val) { 54 | if (val) { 55 | this.setAttribute("active", val); 56 | } else { 57 | this.removeAttribute("active"); 58 | } 59 | } 60 | } 61 | 62 | customElements.define('element-story', ElementStory); 63 | export default ElementStory; 64 | -------------------------------------------------------------------------------- /client/elements/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --es-color-100: #fff; 3 | --es-color-200: #fafafa; 4 | --es-color-300: #eee; 5 | --es-color-400: #ddd; 6 | --es-color-500: #aaa; 7 | --es-color-600: #555; 8 | --es-color-700: RGB(252,96,32); 9 | } 10 | 11 | html, body { 12 | margin: 0; 13 | padding: 0; 14 | height: 100%; 15 | width: 100%; 16 | box-sizing: border-box; 17 | color: var(--es-color-600); 18 | } 19 | 20 | body { 21 | font-family: helvetica, sans-serif; 22 | overflow: hidden; 23 | } 24 | 25 | main { 26 | --column-width: 15em; 27 | display: grid; 28 | height: 100%; 29 | width: 100%; 30 | grid-template-columns: var(--column-width) 1fr; 31 | box-sizing: border-box; 32 | } 33 | -------------------------------------------------------------------------------- /client/elements/prop-item.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from "./buildShadowRoot.js"; 2 | 3 | class PropItem extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 11 | `; 12 | buildShadowRoot(html, this); 13 | } 14 | get values() { 15 | return this.getAttribute("values"); 16 | } 17 | set values(val) { 18 | if (val) { 19 | this.setAttribute("values", val); 20 | } else { 21 | this.removeAttribute("values"); 22 | } 23 | } 24 | } 25 | 26 | customElements.define('prop-item', PropItem); 27 | export default PropItem; 28 | -------------------------------------------------------------------------------- /client/elements/property-display.js: -------------------------------------------------------------------------------- 1 | import buildShadowRoot from './buildShadowRoot.js'; 2 | 3 | class PropertyDisplay extends HTMLElement { 4 | constructor() { 5 | super(); 6 | const html = ` 57 |
`; 58 | buildShadowRoot(html, this); 59 | this.props = []; 60 | this.elems = { 61 | container: this.shadowRoot.querySelector('div') 62 | }; 63 | this.elems.container.addEventListener('change', this.handleChange.bind(this)); 64 | this.elems.container.addEventListener('keyup', this.handleChange.bind(this)); 65 | document.addEventListener('stiva-element', this.handleNewElement.bind(this)); 66 | document.addEventListener('stiva-properties', this.handleProperties.bind(this)); 67 | this.populateForm(); 68 | } 69 | handleNewElement(e){ 70 | this.innerHTML = ''; 71 | this.props = e.detail.props; 72 | this.populateForm(); 73 | } 74 | populateForm(){ 75 | this.elems.container.innerHTML = ` 76 | 83 | `; 84 | } 85 | handleProperties(e){ 86 | e.detail.forEach( (prop) => { 87 | const {name, value = ''} = prop; 88 | let elem = this.elems.container.querySelector(`[name=${name}]`) 89 | if(elem){ 90 | if(elem.type === 'checkbox'){ 91 | elem.checked = value 92 | } else if (elem.contenteditable){ 93 | elem.innerHTML = JSON.stringify(JSON.parse(value)); 94 | } else { 95 | elem.value = value; 96 | } 97 | } 98 | }); 99 | } 100 | renderInput({name, values}){ 101 | if(values === "bool"){ 102 | return ` 103 | 104 | 105 | `; 106 | } 107 | 108 | if(name.indexOf('stiva-') === 0){ 109 | let store = name.replace('stiva-',''); 110 | this.updateStiva(store, JSON.parse(values)); 111 | return ` 112 | 113 |
${JSON.stringify(JSON.parse(values))}
114 | ` 115 | } 116 | 117 | if(Array.isArray(values) && values.length > 0) { 118 | return ` 119 | 120 | 123 | ` 124 | } 125 | 126 | if(name === 'slot'){ 127 | return ` 128 | 129 | 130 | ` 131 | } 132 | 133 | return ` 134 | 135 | 136 | ` 137 | 138 | } 139 | updateStiva(store, value){ 140 | document.querySelector('element-display').elems.iframe.contentWindow.stiva.update(store, oldStore => value); 141 | } 142 | handleChange(e){ 143 | if(e.target.localName === 'div'){ 144 | let store = e.target.getAttribute('name').replace('stiva-',''); 145 | try { 146 | let json = JSON.parse(e.target.innerHTML); 147 | this.updateStiva(store, json); 148 | } 149 | catch(err){} 150 | } 151 | 152 | stiva.update('properties', oldStore => { 153 | const val = e.target.type === 'checkbox' ? e.target.checked : e.target.value; 154 | oldStore.splice(oldStore.findIndex( (item) => item.name === e.target.name), 1); 155 | oldStore.push({name: e.target.name, value: val}); 156 | return oldStore; 157 | }); 158 | 159 | } 160 | 161 | } 162 | customElements.define('property-display', PropertyDisplay); 163 | export default PropertyDisplay; 164 | -------------------------------------------------------------------------------- /element-storybook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/we-are-triing/prova/5c0e260a3f9f8cd5ed4ba1dcfcbc27af2dd7d999/element-storybook.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Story = require('./objects/story.js'); 2 | const Storybook = require('./objects/storybook.js'); 3 | 4 | module.exports = { 5 | Storybook, 6 | Story 7 | }; 8 | -------------------------------------------------------------------------------- /objects/story.js: -------------------------------------------------------------------------------- 1 | class Story { 2 | constructor(element, props = []){ 3 | this.name = element; 4 | this.props = props; 5 | this.slotElements = []; 6 | this.stories = []; 7 | } 8 | add(name, markup) { 9 | this.stories.push({name, markup}); 10 | return this; 11 | } 12 | addProp(name, values = []){ 13 | if(name.indexOf('stiva-') === 0){ 14 | this.props.push({name, values: JSON.stringify(values)}); 15 | } 16 | else if(Array.isArray(name)){ 17 | name.forEach( n => this.props.push({name: n}) ) 18 | } 19 | else { 20 | this.props.push({name,values}); 21 | } 22 | return this; 23 | } 24 | addSlotElement(name){ 25 | if(Array.isArray(name)){ 26 | name.forEach( n => this.slotElements.push(n) ) 27 | } 28 | else { 29 | this.slotElements.push(name); 30 | } 31 | return this; 32 | } 33 | } 34 | module.exports = Story; 35 | -------------------------------------------------------------------------------- /objects/storybook.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const ItemTemplate = require('../templates/item.js'); 3 | const path = require('path'); 4 | const express = require('express'); 5 | class Storybook { 6 | constructor({stories, storybookRoot = '', app, dir = __dirname, pathToElements='/elements', pathToPolyfills='/polyfills', stylesheet, inject, moduleType = 'js'}){ 7 | this.getStories(stories).then((storyObjects) => { 8 | this.stories = storyObjects; 9 | }); 10 | this.storybookRoot = this.trimSlash(storybookRoot); 11 | this.dir = this.trimSlash(dir); 12 | this.pathToElements = this.trimSlash(pathToElements); 13 | this.pathToPolyfills = this.trimSlash(pathToPolyfills); 14 | this.app = app; 15 | this.stylesheet = stylesheet; 16 | this.inject = inject; 17 | this.moduleType = moduleType; 18 | 19 | this.assignRoutes(); 20 | } 21 | trimSlash(str){ 22 | return str.endsWith('/') ? str.substring(0,str.length-1) : str; 23 | } 24 | getStories(stories){ 25 | if(typeof stories === "string"){ 26 | stories = [stories]; 27 | } 28 | if(typeof stories[0] === "object"){ 29 | return new Promise( (res, rej) => res(stories) ); 30 | } 31 | 32 | 33 | let globs = stories.reduce( 34 | (a,n) => ( 35 | [...a, 36 | new Promise( (res, rej) => { 37 | glob(n, (err, files) => { 38 | if(!err){ 39 | res(files); 40 | } 41 | else{ 42 | rej("there was an error in the glob"); 43 | } 44 | }); 45 | })] 46 | ),[]); 47 | 48 | return Promise.all(globs) 49 | .then(values => values.reduce( (a,n) => [...a,...n] ).map( storyPath => require(`${this.dir}/${storyPath}`))) 50 | .catch( (reason) => console.log(reason)); 51 | } 52 | navigateToStory({elementName,currentStory,res}){ 53 | let itemTemplate = new ItemTemplate({elementList: this.stories, elementName, currentStory, elementsRoot: this.pathToElements, storybookRoot: this.storybookRoot, polyfills: this.pathToPolyfills, stylesheet: this.stylesheet, inject: this.inject, moduleType: this.moduleType}); 54 | res.send(itemTemplate.render()); 55 | } 56 | assignRoutes(){ 57 | let stivaPath = require.resolve('stiva'); 58 | this.app.use(`${this.storybookRoot}/elements`, express.static( path.join(__dirname, '../client/elements') )); 59 | this.app.use(`${this.storybookRoot}/stiva.js`, express.static( require.resolve('stiva') )); 60 | this.app.get(`${this.storybookRoot}/:element`, (req, res) => { 61 | const {element} = req.params; 62 | this.navigateToStory({elementName: element, currentStory: 0, res}); 63 | }); 64 | this.app.get(`${this.storybookRoot}/:element/:story`, (req, res) => { 65 | const {element,story} = req.params; 66 | this.navigateToStory({elementName: element,currentStory: story,res}); 67 | }); 68 | this.app.get(`${this.storybookRoot}`, (req, res) => { 69 | if(this.stories.length === 0){ 70 | res.send('you need some stories!'); 71 | } 72 | else { 73 | res.redirect(`${this.storybookRoot}/${this.stories[0].name}`); 74 | } 75 | }) 76 | } 77 | } 78 | module.exports = Storybook; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element-storybook", 3 | "version": "1.0.0-alpha.8", 4 | "description": "This is a development and testing environment for native components.", 5 | "main": "index.js", 6 | "repository": "https://github.com/LuceStudio/element-storybook.git", 7 | "author": "Colt Pini ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "express": "^4.15.2", 11 | "glob": "^7.1.1", 12 | "parse5": "^3.0.2", 13 | "stiva": "^1.0.0-alpha.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/base.js: -------------------------------------------------------------------------------- 1 | class BaseTemplate { 2 | constructor(){ 3 | this.head = {}; 4 | this.stiva = {}; 5 | } 6 | render(){ 7 | const body = ` 8 |
9 | ${this.page} 10 |
11 | `; 12 | 13 | const elements = [ 14 | 'element-actions', 15 | 'element-display', 16 | 'element-item', 17 | 'element-list', 18 | 'element-properties', 19 | 'element-story', 20 | 'prop-item', 21 | 'property-display' 22 | ] 23 | 24 | 25 | return ` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ${this.head.title} 35 | ${this.head.content} 36 | 37 | 38 | 39 | 46 | 47 | 48 | ${ 49 | elements.map( (name) => ( 50 | `` 51 | )).join('') 52 | } 53 | 54 | 55 | ${body} 56 | ${this.inject} 57 | 58 | 59 | `; 60 | } 61 | parseElements(str){ 62 | const reg = /(?:<|is=")\w+?-[\w-]+(?:\s*?|>)/gi; 63 | return [...new Set(str.match(reg).map( (s) => s.replace(/^(<|is=")/gi,'') ))] 64 | } 65 | } 66 | module.exports = BaseTemplate; 67 | -------------------------------------------------------------------------------- /templates/item.js: -------------------------------------------------------------------------------- 1 | const BaseTemplate = require('./base.js'); 2 | const parse5 = require('parse5'); 3 | class ItemTemplate extends BaseTemplate { 4 | constructor({elementList, elementName, currentStory, elementsRoot, storybookRoot, polyfills, stylesheet, inject, moduleType}){ 5 | super(); 6 | this.stylesheet = stylesheet; 7 | this.inject = inject; 8 | this.moduleType = moduleType; 9 | this.elementList = elementList; 10 | this.elementName = elementName; 11 | this.element = this.findByName(this.elementList, elementName); 12 | if(typeof currentStory === "number"){ 13 | this.story = this.element.stories[currentStory]; 14 | this.element.currentStory = currentStory; 15 | } else{ 16 | this.story = this.findByName(this.element.stories, currentStory); 17 | this.element.currentStory = this.element.stories.findIndex( item => item.name === currentStory); 18 | } 19 | this.elementsRoot = elementsRoot; 20 | this.polyfills = polyfills; 21 | this.storybookRoot = storybookRoot; 22 | this.createParts(); 23 | } 24 | findByName(list, name){ 25 | return list.find( item => item.name === name); 26 | } 27 | createParts(){ 28 | this.head.title = `${this.elementName} - Element Storybook`; 29 | this.head.content = ``; 30 | this.stiva = this.populatestiva(); 31 | this.page = this.populatePage(); 32 | } 33 | populatestiva(){ 34 | const markup = parse5.parseFragment(this.story.markup); 35 | const elem = markup.childNodes.find( node => node.nodeName === this.element.name); 36 | 37 | const props = this.element.props.reduce( (a,n) => { 38 | const {name} = n; 39 | let value; 40 | if(name === "slot"){ 41 | value = elem.innerHTML; 42 | } 43 | else { 44 | if(name.startsWith('--')){ 45 | 46 | let style = elem.attrs.find( (attr) => attr.name === 'style' ); 47 | if(style){ 48 | style = style.value; 49 | let reg = new RegExp(`${name}:(.*?)(?:;|$)`); 50 | value = reg.exec(style)[1].trim(); 51 | } 52 | } else { 53 | let prop = elem.attrs.find( (attr) => attr.name === name ); 54 | if(prop){ 55 | value = prop.value || true; 56 | } 57 | } 58 | } 59 | return [...a,{name, value}]; 60 | },[]); 61 | return { 62 | element: this.element, 63 | properties: props 64 | }; 65 | } 66 | populatePage(){ 67 | return ` 68 | 69 | 70 | ${ 71 | this.elementList.map( (item) => ( 72 | ` 0 ? `slot-elements="${item.slotElements.join(",")}"`:``} 75 | ${item.name === this.element.name ? ` active${this.story ? ` story="${this.story.name}"` : ``}` : ``} 76 | 77 | > 78 | 79 | ${this.addProps(item.props)} 80 | 81 | ${ 82 | item.stories.map( (story) => ( 83 | `${story.markup}` 84 | )).join('') 85 | } 86 | ` 87 | )).join('') 88 | } 89 | 90 | 91 | 92 | ${this.addProps(this.element.props)} 93 | 94 | 95 | ${this.story.markup} 96 | `; 97 | } 98 | addProps(props){ 99 | return props.map( (prop) => { 100 | return ` 0 ? ` values='${prop.values}'` : ''}>${prop.name}` 101 | }).join('') 102 | } 103 | } 104 | module.exports = ItemTemplate; 105 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^6.0.46": 6 | version "6.0.79" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.79.tgz#5efe7d4a6d8c453c7e9eaf55d931f4a22fac5169" 8 | 9 | accepts@~1.3.3: 10 | version "1.3.3" 11 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 12 | dependencies: 13 | mime-types "~2.1.11" 14 | negotiator "0.6.1" 15 | 16 | array-flatten@1.1.1: 17 | version "1.1.1" 18 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 19 | 20 | balanced-match@^1.0.0: 21 | version "1.0.0" 22 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 23 | 24 | brace-expansion@^1.1.7: 25 | version "1.1.8" 26 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" 27 | dependencies: 28 | balanced-match "^1.0.0" 29 | concat-map "0.0.1" 30 | 31 | concat-map@0.0.1: 32 | version "0.0.1" 33 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 34 | 35 | content-disposition@0.5.2: 36 | version "0.5.2" 37 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 38 | 39 | content-type@~1.0.2: 40 | version "1.0.2" 41 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 42 | 43 | cookie-signature@1.0.6: 44 | version "1.0.6" 45 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 46 | 47 | cookie@0.3.1: 48 | version "0.3.1" 49 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 50 | 51 | debug@2.6.7: 52 | version "2.6.7" 53 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" 54 | dependencies: 55 | ms "2.0.0" 56 | 57 | depd@1.1.0, depd@~1.1.0: 58 | version "1.1.0" 59 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" 60 | 61 | destroy@~1.0.4: 62 | version "1.0.4" 63 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 64 | 65 | ee-first@1.1.1: 66 | version "1.1.1" 67 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 68 | 69 | encodeurl@~1.0.1: 70 | version "1.0.1" 71 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 72 | 73 | escape-html@~1.0.3: 74 | version "1.0.3" 75 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 76 | 77 | etag@~1.8.0: 78 | version "1.8.0" 79 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" 80 | 81 | express@^4.15.2: 82 | version "4.15.3" 83 | resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" 84 | dependencies: 85 | accepts "~1.3.3" 86 | array-flatten "1.1.1" 87 | content-disposition "0.5.2" 88 | content-type "~1.0.2" 89 | cookie "0.3.1" 90 | cookie-signature "1.0.6" 91 | debug "2.6.7" 92 | depd "~1.1.0" 93 | encodeurl "~1.0.1" 94 | escape-html "~1.0.3" 95 | etag "~1.8.0" 96 | finalhandler "~1.0.3" 97 | fresh "0.5.0" 98 | merge-descriptors "1.0.1" 99 | methods "~1.1.2" 100 | on-finished "~2.3.0" 101 | parseurl "~1.3.1" 102 | path-to-regexp "0.1.7" 103 | proxy-addr "~1.1.4" 104 | qs "6.4.0" 105 | range-parser "~1.2.0" 106 | send "0.15.3" 107 | serve-static "1.12.3" 108 | setprototypeof "1.0.3" 109 | statuses "~1.3.1" 110 | type-is "~1.6.15" 111 | utils-merge "1.0.0" 112 | vary "~1.1.1" 113 | 114 | finalhandler@~1.0.3: 115 | version "1.0.3" 116 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" 117 | dependencies: 118 | debug "2.6.7" 119 | encodeurl "~1.0.1" 120 | escape-html "~1.0.3" 121 | on-finished "~2.3.0" 122 | parseurl "~1.3.1" 123 | statuses "~1.3.1" 124 | unpipe "~1.0.0" 125 | 126 | forwarded@~0.1.0: 127 | version "0.1.0" 128 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 129 | 130 | fresh@0.5.0: 131 | version "0.5.0" 132 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" 133 | 134 | fs.realpath@^1.0.0: 135 | version "1.0.0" 136 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 137 | 138 | glob@^7.1.1: 139 | version "7.1.2" 140 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 141 | dependencies: 142 | fs.realpath "^1.0.0" 143 | inflight "^1.0.4" 144 | inherits "2" 145 | minimatch "^3.0.4" 146 | once "^1.3.0" 147 | path-is-absolute "^1.0.0" 148 | 149 | http-errors@~1.6.1: 150 | version "1.6.1" 151 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" 152 | dependencies: 153 | depd "1.1.0" 154 | inherits "2.0.3" 155 | setprototypeof "1.0.3" 156 | statuses ">= 1.3.1 < 2" 157 | 158 | inflight@^1.0.4: 159 | version "1.0.6" 160 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 161 | dependencies: 162 | once "^1.3.0" 163 | wrappy "1" 164 | 165 | inherits@2, inherits@2.0.3: 166 | version "2.0.3" 167 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 168 | 169 | ipaddr.js@1.3.0: 170 | version "1.3.0" 171 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" 172 | 173 | media-typer@0.3.0: 174 | version "0.3.0" 175 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 176 | 177 | merge-descriptors@1.0.1: 178 | version "1.0.1" 179 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 180 | 181 | methods@~1.1.2: 182 | version "1.1.2" 183 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 184 | 185 | mime-db@~1.27.0: 186 | version "1.27.0" 187 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 188 | 189 | mime-types@~2.1.11, mime-types@~2.1.15: 190 | version "2.1.15" 191 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 192 | dependencies: 193 | mime-db "~1.27.0" 194 | 195 | mime@1.3.4: 196 | version "1.3.4" 197 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 198 | 199 | minimatch@^3.0.4: 200 | version "3.0.4" 201 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 202 | dependencies: 203 | brace-expansion "^1.1.7" 204 | 205 | ms@2.0.0: 206 | version "2.0.0" 207 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 208 | 209 | negotiator@0.6.1: 210 | version "0.6.1" 211 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 212 | 213 | on-finished@~2.3.0: 214 | version "2.3.0" 215 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 216 | dependencies: 217 | ee-first "1.1.1" 218 | 219 | once@^1.3.0: 220 | version "1.4.0" 221 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 222 | dependencies: 223 | wrappy "1" 224 | 225 | parse5@^3.0.2: 226 | version "3.0.2" 227 | resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510" 228 | dependencies: 229 | "@types/node" "^6.0.46" 230 | 231 | parseurl@~1.3.1: 232 | version "1.3.1" 233 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 234 | 235 | path-is-absolute@^1.0.0: 236 | version "1.0.1" 237 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 238 | 239 | path-to-regexp@0.1.7: 240 | version "0.1.7" 241 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 242 | 243 | proxy-addr@~1.1.4: 244 | version "1.1.4" 245 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" 246 | dependencies: 247 | forwarded "~0.1.0" 248 | ipaddr.js "1.3.0" 249 | 250 | qs@6.4.0: 251 | version "6.4.0" 252 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 253 | 254 | range-parser@~1.2.0: 255 | version "1.2.0" 256 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 257 | 258 | send@0.15.3: 259 | version "0.15.3" 260 | resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309" 261 | dependencies: 262 | debug "2.6.7" 263 | depd "~1.1.0" 264 | destroy "~1.0.4" 265 | encodeurl "~1.0.1" 266 | escape-html "~1.0.3" 267 | etag "~1.8.0" 268 | fresh "0.5.0" 269 | http-errors "~1.6.1" 270 | mime "1.3.4" 271 | ms "2.0.0" 272 | on-finished "~2.3.0" 273 | range-parser "~1.2.0" 274 | statuses "~1.3.1" 275 | 276 | serve-static@1.12.3: 277 | version "1.12.3" 278 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2" 279 | dependencies: 280 | encodeurl "~1.0.1" 281 | escape-html "~1.0.3" 282 | parseurl "~1.3.1" 283 | send "0.15.3" 284 | 285 | setprototypeof@1.0.3: 286 | version "1.0.3" 287 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 288 | 289 | "statuses@>= 1.3.1 < 2", statuses@~1.3.1: 290 | version "1.3.1" 291 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 292 | 293 | stiva@^1.0.0-alpha.2: 294 | version "1.0.0-alpha.2" 295 | resolved "https://registry.yarnpkg.com/stiva/-/stiva-1.0.0-alpha.2.tgz#2480a44e1343eb3d76c803ae30a11fb9700e0016" 296 | 297 | type-is@~1.6.15: 298 | version "1.6.15" 299 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 300 | dependencies: 301 | media-typer "0.3.0" 302 | mime-types "~2.1.15" 303 | 304 | unpipe@~1.0.0: 305 | version "1.0.0" 306 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 307 | 308 | utils-merge@1.0.0: 309 | version "1.0.0" 310 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 311 | 312 | vary@~1.1.1: 313 | version "1.1.1" 314 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" 315 | 316 | wrappy@1: 317 | version "1.0.2" 318 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 319 | --------------------------------------------------------------------------------