├── .gitignore ├── src ├── assets │ ├── images │ │ ├── ionic.jpg │ │ ├── ionic.png │ │ ├── max-1.png │ │ ├── max-2.png │ │ ├── banana.gif │ │ ├── dark-bg.png │ │ ├── div-soup.png │ │ ├── hayden.png │ │ ├── its-over.png │ │ ├── polymer.png │ │ ├── sb-email.gif │ │ ├── skatejs.png │ │ ├── stencil.png │ │ ├── trogdor.png │ │ ├── vaadin.png │ │ ├── live-hardcore.gif │ │ ├── unboxed-logo.png │ │ ├── unboxed-photo.jpg │ │ ├── browser-support.png │ │ ├── cross-framework.png │ │ ├── polymer-release.png │ │ ├── ship-less-code.png │ │ ├── better-performance.png │ │ ├── brace-yourselves.png │ │ ├── jquery-bootstrap.png │ │ └── strong-bad-email.png │ └── fonts │ │ ├── roboto-bold.ttf │ │ ├── roboto-bold.woff │ │ ├── roboto-bold.woff2 │ │ ├── roboto-light.ttf │ │ ├── roboto-light.woff │ │ ├── roboto-light.woff2 │ │ ├── roboto-medium.ttf │ │ ├── roboto-medium.woff │ │ ├── roboto-regular.ttf │ │ ├── roboto-medium.woff2 │ │ ├── roboto-regular.woff │ │ └── roboto-regular.woff2 ├── styles │ ├── kitchen-sink-demo.scss │ ├── overrides.scss │ ├── highlight-style.scss │ ├── title-page.scss │ ├── font.scss │ └── slides.scss ├── examples │ ├── index.js │ ├── twitter-link.component.ts │ ├── foo-bar.component.ts │ ├── cool-tab-group.lit-html.component.js │ ├── cool-tab-group.vanilla.component.js │ ├── kitchen-sink.component.js │ ├── cool-stop-watch.lit-element.component.ts │ ├── cool-tab.lit-html.component.js │ ├── cool-tab.vanilla.component.js │ ├── cool-stop-watch.lit-html.component.js │ ├── cool-stop-watch.vanilla.component.js │ └── cool-side-menu.component.js ├── main.scss ├── main.js └── index.html ├── postcss.config.js ├── README.md ├── tsconfig.json ├── demo-notes.md ├── LICENSE ├── package.json └── webpack └── webpack.dev.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | outline.md 5 | dist 6 | *.log 7 | foo.md -------------------------------------------------------------------------------- /src/assets/images/ionic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/ionic.jpg -------------------------------------------------------------------------------- /src/assets/images/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/ionic.png -------------------------------------------------------------------------------- /src/assets/images/max-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/max-1.png -------------------------------------------------------------------------------- /src/assets/images/max-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/max-2.png -------------------------------------------------------------------------------- /src/assets/images/banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/banana.gif -------------------------------------------------------------------------------- /src/assets/images/dark-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/dark-bg.png -------------------------------------------------------------------------------- /src/assets/images/div-soup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/div-soup.png -------------------------------------------------------------------------------- /src/assets/images/hayden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/hayden.png -------------------------------------------------------------------------------- /src/assets/images/its-over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/its-over.png -------------------------------------------------------------------------------- /src/assets/images/polymer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/polymer.png -------------------------------------------------------------------------------- /src/assets/images/sb-email.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/sb-email.gif -------------------------------------------------------------------------------- /src/assets/images/skatejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/skatejs.png -------------------------------------------------------------------------------- /src/assets/images/stencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/stencil.png -------------------------------------------------------------------------------- /src/assets/images/trogdor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/trogdor.png -------------------------------------------------------------------------------- /src/assets/images/vaadin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/vaadin.png -------------------------------------------------------------------------------- /src/styles/kitchen-sink-demo.scss: -------------------------------------------------------------------------------- 1 | p { 2 | color: pink !important; 3 | } 4 | 5 | // .cool { 6 | // color: coral; 7 | // } 8 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-bold.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-bold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-light.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-regular.ttf -------------------------------------------------------------------------------- /src/assets/images/live-hardcore.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/live-hardcore.gif -------------------------------------------------------------------------------- /src/assets/images/unboxed-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/unboxed-logo.png -------------------------------------------------------------------------------- /src/assets/images/unboxed-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/unboxed-photo.jpg -------------------------------------------------------------------------------- /src/assets/fonts/roboto-medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-medium.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/fonts/roboto-regular.woff2 -------------------------------------------------------------------------------- /src/assets/images/browser-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/browser-support.png -------------------------------------------------------------------------------- /src/assets/images/cross-framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/cross-framework.png -------------------------------------------------------------------------------- /src/assets/images/polymer-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/polymer-release.png -------------------------------------------------------------------------------- /src/assets/images/ship-less-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/ship-less-code.png -------------------------------------------------------------------------------- /src/assets/images/better-performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/better-performance.png -------------------------------------------------------------------------------- /src/assets/images/brace-yourselves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/brace-yourselves.png -------------------------------------------------------------------------------- /src/assets/images/jquery-bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/jquery-bootstrap.png -------------------------------------------------------------------------------- /src/assets/images/strong-bad-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haydenbr/intro-to-web-components/HEAD/src/assets/images/strong-bad-email.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | 3 | const config = { 4 | plugins: [ autoprefixer ] 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Components are Coming 2 | 3 | 4 | 5 | ```bash 6 | yarn 7 | yarn start 8 | ``` 9 | 10 | The slides use [Reveal.js](https://revealjs.com/#/). 11 | -------------------------------------------------------------------------------- /src/styles/overrides.scss: -------------------------------------------------------------------------------- 1 | $primary: #22B8EB; 2 | $bg-color: #36373c; 3 | $white: #fff; 4 | 5 | $backgroundColor: $bg-color; 6 | $mainFont: 'Roboto'; 7 | $headingFont: 'Roboto'; 8 | $linkColor: $primary; 9 | $linkColorHover: lighten($linkColor, 20%); 10 | $selectionBackgroundColor: $primary; 11 | -------------------------------------------------------------------------------- /src/examples/index.js: -------------------------------------------------------------------------------- 1 | import './kitchen-sink.component'; 2 | import './cool-tab-group.vanilla.component'; 3 | import './cool-tab.vanilla.component'; 4 | import './cool-tab-group.lit-html.component'; 5 | import './cool-tab.lit-html.component'; 6 | import './cool-side-menu.component'; 7 | import './cool-stop-watch.lit-html.component'; 8 | import './cool-stop-watch.lit-element.component.ts'; 9 | import './cool-stop-watch.vanilla.component'; 10 | import './twitter-link.component.ts' -------------------------------------------------------------------------------- /src/main.scss: -------------------------------------------------------------------------------- 1 | @import 'node_modules/reveal.js/css/reveal.scss'; 2 | @import 'node_modules/reveal.js/css/theme/template/mixins.scss'; 3 | @import 'node_modules/reveal.js/css/theme/template/settings.scss'; 4 | 5 | @import './styles/font.scss'; 6 | @import './styles/overrides.scss'; 7 | @import './styles/slides.scss'; 8 | @import './styles/title-page.scss'; 9 | @import './styles/highlight-style.scss'; 10 | @import './styles/kitchen-sink-demo.scss'; 11 | 12 | @import 'node_modules/reveal.js/css/theme/template/theme.scss'; 13 | -------------------------------------------------------------------------------- /src/examples/twitter-link.component.ts: -------------------------------------------------------------------------------- 1 | import { customElement } from 'lit-element'; 2 | import { html, render } from 'lit-html' 3 | 4 | @customElement('twitter-link') 5 | export class TwitterLink extends HTMLElement { 6 | connectedCallback() { 7 | render(html` 8 | 17 | @hayden_dev 18 | `, this); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // { 2 | // "compilerOptions": { 3 | // "target": "es6", 4 | // "module": "es2015", 5 | // "moduleResolution": "node", 6 | // "declaration": false, 7 | // "sourceMap": true, 8 | // "experimentalDecorators": true, 9 | // "outDir": "dist", 10 | // "lib": ["es2017", "dom"], 11 | // "noImplicitAny": true, 12 | // "suppressImplicitAnyIndexErrors": true, 13 | // "removeComments": true 14 | // }, 15 | // "exclude": ["node_modules", "dist"] 16 | // } 17 | { 18 | "compilerOptions": { 19 | "target": "es2017", 20 | "module": "es2015", 21 | "moduleResolution": "node", 22 | "lib": ["es2017", "dom"], 23 | "experimentalDecorators": true 24 | } 25 | } -------------------------------------------------------------------------------- /demo-notes.md: -------------------------------------------------------------------------------- 1 | # kitchen-sink 2 | 3 | ## custom elements 4 | 5 | - create / define custom element 6 | - es6 classes 7 | - content 8 | - innerHTML 9 | - template 10 | - lifecycle 11 | - connectedCallback 12 | - disconnectedCallback 13 | - observedAttributes / attributeChangedCallback 14 | - getters and setters 15 | - property-attribute reflection 16 | - property vs attribute 17 | 18 | ## shadow DOM 19 | 20 | - attach shadow 21 | - open vs closed mode 22 | - encapsulation 23 | - expose variables 24 | - variables default value 25 | - :host selector 26 | - :host presedence 27 | - slots 28 | - default slot 29 | - multiple items in slot 30 | - light DOM vs shadow DOM 31 | - ::slotted selector 32 | - ::slotted presedence 33 | - ::slotted targeting deep elements 34 | 35 | # cool-stop-watch 36 | 37 | - event handling 38 | - change detection 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Hayden Braxton 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/styles/highlight-style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Date: 24 Fev 2015 3 | Author: Pedro Oliveira 4 | */ 5 | 6 | .hljs { 7 | color: #a9b7c6; 8 | background: #282b2e; 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | } 13 | 14 | .hljs-number, 15 | .hljs-literal, 16 | .hljs-symbol, 17 | .hljs-bullet { 18 | color: #6897BB; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-deletion { 24 | color: #cc7832; 25 | } 26 | 27 | .hljs-variable, 28 | .hljs-template-variable, 29 | .hljs-link { 30 | color: #629755; 31 | } 32 | 33 | .hljs-comment, 34 | .hljs-quote { 35 | color: #808080; 36 | } 37 | 38 | .hljs-meta { 39 | color: #bbb529; 40 | } 41 | 42 | .hljs-string, 43 | .hljs-attribute, 44 | .hljs-addition { 45 | color: #6A8759; 46 | } 47 | 48 | .hljs-section, 49 | .hljs-title, 50 | .hljs-type { 51 | color: #ffc66d; 52 | } 53 | 54 | .hljs-name, 55 | .hljs-selector-id, 56 | .hljs-selector-class { 57 | color: #e8bf6a; 58 | } 59 | 60 | .hljs-emphasis { 61 | font-style: italic; 62 | } 63 | 64 | .hljs-strong { 65 | font-weight: bold; 66 | } 67 | -------------------------------------------------------------------------------- /src/examples/foo-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, html, customElement, property, css, query } from 'lit-element'; 2 | 3 | const styles = css` 4 | :host { 5 | block; 6 | } 7 | 8 | button { 9 | background-color: cyan; 10 | border: 1px solid black; 11 | border-radius: 5px; 12 | padding: 10px 20px; 13 | } 14 | `; 15 | 16 | @customElement('foo-bar') 17 | export class FooBar extends LitElement { 18 | static styles = styles; 19 | @property() name: string = 'Bob'; 20 | switch: boolean = false; 21 | 22 | @query('#my-input') input: HTMLInputElement; 23 | 24 | resetInput() { 25 | let event = new Event('bob'); 26 | this.dispatchEvent(event); 27 | } 28 | 29 | render() { 30 | return html` 31 | console.log(e.target.checked)} 35 | > 36 | ${this.name} 37 | 38 | console.log(e.target.value)} 42 | > 43 | 44 | `; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/styles/title-page.scss: -------------------------------------------------------------------------------- 1 | section { 2 | &.intro-slide { 3 | .avatar-container { 4 | background: white; 5 | border-radius: 50%; 6 | box-shadow: 2px 2px 10px 0 rgba(0,0,0,0.75); 7 | height: 135px; 8 | margin-right: 20px; 9 | width: 135px; 10 | 11 | .avatar { 12 | background-image: url('assets/images/hayden.png'); 13 | background-repeat: no-repeat; 14 | background-position: -25px; 15 | background-size: cover; 16 | border-radius: 50%; 17 | height: 125px; 18 | position: relative; 19 | transform: translate3d(-50%, -50%, 0); 20 | width: 125px; 21 | top: 50%; 22 | left: 50%; 23 | } 24 | } 25 | 26 | .cover { 27 | width: 400px; 28 | margin-bottom: 75px; 29 | } 30 | 31 | .row { 32 | display: flex; 33 | justify-content: center; 34 | } 35 | 36 | .title { 37 | font-size: 60px; 38 | font-weight: bold; 39 | margin-top: 50px; 40 | } 41 | 42 | .presenter { 43 | display: flex; 44 | align-items: center; 45 | padding-left: 30px; 46 | position: absolute; 47 | bottom: -180px; 48 | left: -40px; 49 | } 50 | 51 | .whale { 52 | box-shadow: none; 53 | transform: scale(.75); 54 | transform-origin: center; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import './main.scss'; 2 | import './index.html'; 3 | import './examples'; 4 | 5 | function init(event) { 6 | Reveal.initialize({ 7 | autoSlide: 0, 8 | backgroundTransition: 'slide', 9 | controls: false, 10 | history: true, 11 | dependencies: [ 12 | // Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/ 13 | { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } }, 14 | 15 | // Interpret Markdown in
elements 16 | { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, 17 | { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, 18 | 19 | // Syntax highlight for elements 20 | { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, 21 | 22 | // Zoom in and out with Alt+click 23 | { src: 'plugin/zoom-js/zoom.js', async: true }, 24 | 25 | // Speaker notes 26 | { src: 'plugin/notes/notes.js', async: true }, 27 | 28 | // MathJax 29 | { src: 'plugin/math/math.js', async: true } 30 | ] 31 | }); 32 | 33 | event.target.removeEventListener(event.type, init); 34 | } 35 | 36 | document.addEventListener('DOMContentLoaded', init); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-components-are-coming", 3 | "version": "0.0.0", 4 | "main": "src/index.ts", 5 | "scripts": { 6 | "build": "webpack --config ./webpack/webpack.dev.js", 7 | "clean": "rimraf dist/*", 8 | "serve": "webpack-dev-server --config ./webpack/webpack.dev.js --port 3000 --host 0.0.0.0", 9 | "start": "yarn serve" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/haydenbr/web-components-are-coming" 14 | }, 15 | "author": "haydenbr", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "ajv": "^5.0.0", 19 | "autoprefixer": "^7.2.6", 20 | "chalk": "^2.3.1", 21 | "copy-webpack-plugin": "^4.4.1", 22 | "css-loader": "^0.28.9", 23 | "extract-loader": "3.1.0", 24 | "file-loader": "4.1.0", 25 | "html-loader": "^0.5.5", 26 | "node-sass": "^4.7.2", 27 | "pngquant-bin": "3.1.1", 28 | "postcss": "^6.0.17", 29 | "postcss-loader": "3.0.0", 30 | "rimraf": "^2.6.2", 31 | "sass-loader": "^6.0.6", 32 | "source-map": "^0.7.0", 33 | "ts-loader": "^6.0.4", 34 | "typescript": "^3.5.3", 35 | "webpack": "4.39.1", 36 | "webpack-dev-server": "3.7.2", 37 | "write-file-webpack-plugin": "^4.2.0" 38 | }, 39 | "dependencies": { 40 | "lit-element": "^2.2.1", 41 | "lit-html": "1.0.0", 42 | "reveal.js": "3.6.0", 43 | "webpack-cli": "^3.3.6" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/examples/cool-tab-group.lit-html.component.js: -------------------------------------------------------------------------------- 1 | import { html, render } from 'lit-html'; 2 | 3 | const style = ` 4 | :host { 5 | align-items: center; 6 | display: flex; 7 | justify-content: center; 8 | } 9 | `; 10 | 11 | class CoolTabGroup extends HTMLElement { 12 | constructor() { 13 | super(); 14 | 15 | this._value = undefined; 16 | } 17 | 18 | connectedCallback() { 19 | this.attachShadow({ mode: 'open' }); 20 | 21 | this.addEventListener('tabselect', (event) => this.value = event.target.value); 22 | this.value = this.defaultValue; 23 | // setTimeout(() => this.render()); 24 | this.render() 25 | } 26 | 27 | disconnectedCallback() { 28 | this.removeEventListener('tabselect'); 29 | } 30 | 31 | get defaultValue() { 32 | return this.getAttribute('value') || ''; 33 | } 34 | 35 | get value() { 36 | return this._value; 37 | } 38 | 39 | set value(newValue) { 40 | if (newValue === this.value) { 41 | return; 42 | } 43 | 44 | let oldValue = this._value; 45 | this._value = newValue; 46 | this.render(); 47 | this.dispatchTabChange({ oldValue, newValue }); 48 | } 49 | 50 | get tabButtons() { 51 | return Array.from(this.querySelectorAll('cool-tab')); 52 | } 53 | 54 | dispatchTabChange(payload) { 55 | this.dispatchEvent(new CustomEvent('tabchange', { bubbles: true, detail: payload })); 56 | } 57 | 58 | render() { 59 | this.tabButtons.forEach((tab) => tab.selected = (tab.value === this.value)); 60 | 61 | render(html` 62 | 63 | 64 | `, this.shadowRoot); 65 | } 66 | } 67 | 68 | // window.customElements.define('cool-tab-group', CoolTabGroup); 69 | -------------------------------------------------------------------------------- /src/examples/cool-tab-group.vanilla.component.js: -------------------------------------------------------------------------------- 1 | const template = document.createElement('template'); 2 | const style = ` 3 | :host { 4 | align-items: center; 5 | display: flex; 6 | justify-content: center; 7 | } 8 | `; 9 | 10 | template.innerHTML = ` 11 | 12 | 13 | `; 14 | 15 | class CoolTabGroup extends HTMLElement { 16 | constructor() { 17 | super(); 18 | 19 | this._value = undefined; 20 | } 21 | 22 | connectedCallback() { 23 | const shadowRoot = this.attachShadow({ mode: 'open' }); 24 | shadowRoot.appendChild(template.content.cloneNode(true)); 25 | 26 | this.addEventListener('tabselect', (event) => this.value = event.target.value); 27 | this.value = this.defaultValue; 28 | setTimeout(() => this.render()); 29 | } 30 | 31 | disconnectedCallback() { 32 | this.removeEventListener('tabselect'); 33 | } 34 | 35 | get defaultValue() { 36 | return this.getAttribute('value') || ''; 37 | } 38 | 39 | get value() { 40 | return this._value; 41 | } 42 | 43 | set value(newValue) { 44 | if (newValue === this.value) { 45 | return; 46 | } 47 | 48 | let oldValue = this._value; 49 | this._value = newValue; 50 | this.render(); 51 | this.dispatchTabChange({ oldValue, newValue }); 52 | } 53 | 54 | get tabButtons() { 55 | return Array.from(this.querySelectorAll('cool-tab')); 56 | } 57 | 58 | dispatchTabChange(payload) { 59 | this.dispatchEvent(new CustomEvent('tabchange', { bubbles: true, detail: payload })); 60 | } 61 | 62 | render() { 63 | this.tabButtons.forEach((tab) => tab.selected = (tab.value === this.value)); 64 | } 65 | } 66 | 67 | window.customElements.define('cool-tab-group', CoolTabGroup); 68 | -------------------------------------------------------------------------------- /src/styles/font.scss: -------------------------------------------------------------------------------- 1 | $roboto-font-path: 'assets/fonts'; 2 | 3 | @font-face { 4 | font-family: 'Roboto'; 5 | font-style: normal; 6 | font-weight: 300; 7 | src: local('Roboto'), local('Roboto-Light'), 8 | url('#{$roboto-font-path}/roboto-light.woff2') format('woff2'), 9 | url('#{$roboto-font-path}/roboto-light.woff') format('woff'), 10 | url('#{$roboto-font-path}/roboto-light.ttf') format('truetype'); 11 | } 12 | 13 | @font-face { 14 | font-family: 'Roboto'; 15 | font-style: normal; 16 | font-weight: 400; 17 | src: local('Roboto'), local('Roboto-Regular'), 18 | url('#{$roboto-font-path}/roboto-regular.woff2') format('woff2'), 19 | url('#{$roboto-font-path}/roboto-regular.woff') format('woff'), 20 | url('#{$roboto-font-path}/roboto-regular.ttf') format('truetype'); 21 | } 22 | 23 | @font-face { 24 | font-family: 'Roboto'; 25 | font-style: normal; 26 | font-weight: 500; 27 | src: local('Roboto Medium'), local('Roboto-Medium'), 28 | url('#{$roboto-font-path}/roboto-medium.woff2') format('woff2'), 29 | url('#{$roboto-font-path}/roboto-medium.woff') format('woff'), 30 | url('#{$roboto-font-path}/roboto-medium.ttf') format('truetype'); 31 | } 32 | 33 | @font-face { 34 | font-family: 'Roboto'; 35 | font-style: normal; 36 | font-weight: 700; 37 | src: local('Roboto Bold'), local('Roboto-Bold'), 38 | url('#{$roboto-font-path}/roboto-bold.woff2') format('woff2'), 39 | url('#{$roboto-font-path}/roboto-bold.woff') format('woff'), 40 | url('#{$roboto-font-path}/roboto-bold.ttf') format('truetype'); 41 | } 42 | 43 | .noselect { 44 | -webkit-touch-callout: none; 45 | -webkit-user-select: none; 46 | -khtml-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/slides.scss: -------------------------------------------------------------------------------- 1 | section { 2 | img { 3 | border: 0 !important; 4 | background-color: transparent !important; 5 | box-shadow: none !important; 6 | } 7 | 8 | pre { 9 | box-shadow: none !important; 10 | width: 100% !important; 11 | 12 | .hljs { 13 | box-shadow: 0px 0px 6px rgba(0,0,0,0.3); 14 | } 15 | } 16 | 17 | .row { 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | &.content-slide { 23 | text-align: left; 24 | top: 0 !important; 25 | } 26 | 27 | &.image-slide { 28 | padding: 0 !important; 29 | 30 | img { 31 | margin: 0 !important; 32 | } 33 | } 34 | 35 | &.title-slide { 36 | text-align: left; 37 | top: 0 !important; 38 | 39 | img { 40 | background: none !important; 41 | box-shadow: none !important; 42 | } 43 | } 44 | 45 | .section-title { 46 | color: $white; 47 | font-size: 60px; 48 | font-weight: bold; 49 | text-transform: uppercase; 50 | } 51 | 52 | .section-subtitle { 53 | color: $primary; 54 | font-size: 60px; 55 | font-weight: bold; 56 | text-transform: uppercase; 57 | } 58 | 59 | .page-title { 60 | color: $white; 61 | font-size: 60px; 62 | margin-bottom: 20px; 63 | // text-transform: capitalize; 64 | } 65 | } 66 | 67 | .slide-background { 68 | &.present { 69 | background-size: 100% 100%; 70 | } 71 | } 72 | 73 | .reveal { 74 | .slides { 75 | img { 76 | background: none; 77 | } 78 | } 79 | } 80 | 81 | #closing-image { 82 | cursor: pointer; 83 | display: block; 84 | margin-bottom: 40px; 85 | 86 | img { 87 | border-radius: 15px; 88 | margin: 0; 89 | width: 500px; 90 | } 91 | } 92 | 93 | #banana { 94 | // bottom: -100px; 95 | top: -550px; 96 | left: 100%; 97 | position: relative; 98 | width: 8px; 99 | } 100 | 101 | #unboxed-logo { 102 | width: 200px; 103 | position: absolute; 104 | } 105 | -------------------------------------------------------------------------------- /src/examples/kitchen-sink.component.js: -------------------------------------------------------------------------------- 1 | const template = document.createElement('template'); 2 | const styles = ` 3 | :host { 4 | --color: #f8981c 5 | } 6 | 7 | p { 8 | color: var(--color, #6f7dbc); 9 | } 10 | 11 | slot::slotted(small) { 12 | color: yellow; 13 | } 14 | 15 | slot[name="secondary-slot"]::slotted(small) { 16 | color: red; 17 | } 18 | 19 | slot[name="secondary-slot"]::slotted(.cool) { 20 | color: #6f7dbc; 21 | } 22 | `; 23 | const templateString = ` 24 | 25 |

is cool!!!

26 | 27 |
28 | 29 | `; 30 | template.innerHTML = templateString; 31 | 32 | class KitchenSink extends HTMLElement { 33 | constructor() { 34 | super(); 35 | 36 | this._$name = undefined; 37 | this._name = ''; 38 | } 39 | 40 | handleNameChange(oldValue, newValue) { 41 | if (oldValue === newValue) { 42 | return; 43 | } 44 | 45 | this.name = newValue; 46 | } 47 | 48 | connectedCallback() { 49 | // this.appendChild(template.content.cloneNode(true)) 50 | // this._$name = this.querySelector('#name'); 51 | this.attachShadow({ mode: 'open' }); 52 | this.shadowRoot.appendChild(template.content.cloneNode(true)); 53 | this._$name = this.shadowRoot.querySelector('#name'); 54 | 55 | this.name = this.getAttribute('name') || 'Bob'; 56 | } 57 | 58 | get name() { 59 | return this._name; 60 | } 61 | 62 | set name(newName) { 63 | if (newName === this.name) { 64 | return; 65 | } 66 | 67 | this.setAttribute('name', newName); 68 | this._name = newName; 69 | this.render(); 70 | } 71 | 72 | static get observedAttributes() { 73 | return ['name']; 74 | } 75 | 76 | attributeChangedCallback(attrName, oldValue, newValue) { 77 | if (oldValue !== newValue) { 78 | this[attrName] = newValue; 79 | } 80 | } 81 | 82 | disconnectedCallback() { 83 | // or do something useful like clean up event listeners 84 | alert(`I'll get you Eh Steve, if it's that last thing I DOOOOOOOOOOO!`); 85 | } 86 | 87 | render() { 88 | this._$name.textContent = this.name; 89 | } 90 | } 91 | 92 | window.customElements.define('kitchen-sink', KitchenSink); 93 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const WriteFilePlugin = require('write-file-webpack-plugin'); 5 | 6 | const config = { 7 | devtool: 'inline-source-map', 8 | entry: { 9 | main: ['./src/main.js'] 10 | }, 11 | output: { 12 | filename: '[name].js', 13 | path: path.resolve(__dirname, '..', 'dist') 14 | }, 15 | resolve: { 16 | extensions: ['.ts', '.js', '.json'], 17 | modules: [path.resolve('node_modules')] 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(ts)$/, 23 | use: 'ts-loader', 24 | exclude: /node_modules/ 25 | }, 26 | { 27 | test: /\.(scss)$/, 28 | use: [ 29 | { loader: 'file-loader', options: { name: '[name].css' } }, 30 | { loader: 'extract-loader' }, 31 | { loader: 'css-loader' }, 32 | { loader: 'postcss-loader' }, 33 | { loader: 'sass-loader' } 34 | ] 35 | }, 36 | { 37 | test: /\.html$/, 38 | use: [ 39 | { loader: 'file-loader', options: { name: '[name].html' } }, 40 | { loader: 'extract-loader' }, 41 | { loader: 'html-loader', options: { attrs: [ 'section:data-background-image', 'img:src' ] } } 42 | ] 43 | }, 44 | { 45 | test: /\.(jpg|gif|png)$/, 46 | use: [ 47 | { loader: 'file-loader', options: { name: 'assets/images/[name].[ext]' } } 48 | ] 49 | }, 50 | { 51 | test: /\.(ttf|woff|woff2)$/, 52 | use: [ 53 | { loader: 'file-loader', options: { name: 'assets/fonts/[name].[ext]' } } 54 | ] 55 | } 56 | ] 57 | }, 58 | plugins: [ 59 | new WriteFilePlugin(), 60 | new CopyWebpackPlugin([ 61 | { 62 | from: path.resolve(__dirname, '..', 'node_modules', 'reveal.js', 'plugin'), 63 | to: path.resolve(__dirname, '..', 'dist', 'plugin') 64 | }, 65 | { 66 | from: path.resolve(__dirname, '..', 'node_modules', 'reveal.js', 'lib', 'js', 'head.min.js'), 67 | to: path.resolve(__dirname, '..', 'dist', 'lib', 'js', 'head.min.js') 68 | }, 69 | { 70 | from: path.resolve(__dirname, '..', 'node_modules', 'reveal.js', 'js', 'reveal.js'), 71 | to: path.resolve(__dirname, '..', 'dist', 'js', 'reveal.js') 72 | } 73 | ]) 74 | ], 75 | devServer: { 76 | contentBase: 'dist', 77 | overlay: true 78 | } 79 | }; 80 | 81 | module.exports = config; 82 | -------------------------------------------------------------------------------- /src/examples/cool-stop-watch.lit-element.component.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, html, customElement, css, property, } from 'lit-element'; 2 | 3 | @customElement('cool-stop-watch-lit-element') 4 | export class CoolStopWatch extends LitElement { 5 | static get styles() { 6 | return css` 7 | :host { 8 | border: 1px solid black; 9 | background-color: white; 10 | color: black; 11 | display: inline-block; 12 | padding: 10px; 13 | } 14 | 15 | button { 16 | border-radius: 5px; 17 | font-size: 16px; 18 | padding: 5px 10px; 19 | } 20 | 21 | #stop-watch-controls { 22 | text-align: center; 23 | } 24 | ` 25 | } 26 | 27 | @property({ type: Number, reflect: false, attribute: 'current-time' }) private currentTime = 0; 28 | @property() private stopWatchInterval: any; 29 | private millisecondsInterval = 5; 30 | 31 | disconnectedCallback() { 32 | clearInterval(this.stopWatchInterval); 33 | } 34 | 35 | start = () => { 36 | if (this.stopWatchIsRunning) { 37 | return; 38 | } 39 | 40 | this.stopWatchInterval = setInterval(() => { 41 | this.currentTime = this.currentTime + this.millisecondsInterval; 42 | }, this.millisecondsInterval); 43 | } 44 | 45 | stop = () => { 46 | clearInterval(this.stopWatchInterval); 47 | this.stopWatchInterval = undefined; 48 | } 49 | 50 | reset = () => this.currentTime = 0; 51 | 52 | get formattedTime() { 53 | let minutesRaw = this.currentTime / 60000; 54 | let secondsRaw = (minutesRaw % 1) * 60; 55 | let millisecondsRaw = (secondsRaw % 1) * 1000; 56 | 57 | let minutes = Math.floor(minutesRaw); 58 | let seconds = Math.floor(secondsRaw).toString().padStart(2, '0'); 59 | let milliseconds = Math.floor(millisecondsRaw).toString().padStart(3, '0'); 60 | 61 | return `${minutes}:${seconds}.${milliseconds}`; 62 | } 63 | 64 | get stopWatchIsRunning() { 65 | return !!this.stopWatchInterval; 66 | } 67 | 68 | render = () => html` 69 |
${this.formattedTime}
70 |
71 | ${!this.stopWatchIsRunning 72 | ? html`` 73 | : null 74 | } 75 | ${this.stopWatchIsRunning 76 | ? html`` 77 | : null 78 | } 79 | 80 |
81 | `; 82 | } 83 | -------------------------------------------------------------------------------- /src/examples/cool-tab.lit-html.component.js: -------------------------------------------------------------------------------- 1 | import { render, html } from 'lit-html'; 2 | 3 | const componentStyles = ` 4 | :host { 5 | --tab-button-border-radius: 5px; 6 | --tab-button-border-width: 3px; 7 | --tab-button-color: #6f7dbc; 8 | 9 | border: var(--tab-button-border-width) solid var(--tab-button-color); 10 | display: block; 11 | flex-grow: 1; 12 | overflow: none; 13 | 14 | background-color: #fff; 15 | color: var(--tab-button-color); 16 | cursor: pointer; 17 | font-size: 18px; 18 | padding: 10px; 19 | text-align: center; 20 | user-select: none 21 | transition: all 200ms ease-in-out; 22 | } 23 | 24 | :host([selected]) { 25 | background-color: var(--tab-button-color); 26 | color: #fff; 27 | transition: all 200ms ease-in-out; 28 | } 29 | 30 | :host(:not(:first-of-type)) { 31 | border-left-width: 0; 32 | } 33 | 34 | :host(:first-of-type) { 35 | border-top-left-radius: var(--tab-button-border-radius); 36 | border-bottom-left-radius: var(--tab-button-border-radius); 37 | } 38 | 39 | :host(:last-of-type) { 40 | border-top-right-radius: var(--tab-button-border-radius); 41 | border-bottom-right-radius: var(--tab-button-border-radius); 42 | } 43 | `; 44 | 45 | class CoolTab extends HTMLElement { 46 | constructor() { 47 | super(); 48 | 49 | this._selected = false; 50 | this._value = ''; 51 | } 52 | 53 | connectedCallback() { 54 | this.attachShadow({ mode: 'open' }); 55 | this._value = this.getAttribute('value'); 56 | this.clickListener = this.addEventListener('click', () => this.selected = true); 57 | render(); 58 | } 59 | 60 | get value() { 61 | return this._value; 62 | } 63 | 64 | get selected() { 65 | return this._selected; 66 | } 67 | 68 | set selected(selected) { 69 | if (selected === this.selected) { 70 | return; 71 | } 72 | 73 | this._selected = selected; 74 | this.render(); 75 | 76 | if (this.selected) { 77 | this.dispatchTabSelect(); 78 | } 79 | } 80 | 81 | dispatchTabSelect() { 82 | this.dispatchEvent(new CustomEvent('tabselect', { bubbles: true })); 83 | } 84 | 85 | render() { 86 | if (this.selected) { 87 | this.setAttribute('selected', ''); 88 | this.set 89 | } else { 90 | this.removeAttribute('selected'); 91 | } 92 | 93 | render(html` 94 | 95 | 96 | `, this.shadowRoot); 97 | } 98 | } 99 | 100 | // window.customElements.define('cool-tab', CoolTab); 101 | -------------------------------------------------------------------------------- /src/examples/cool-tab.vanilla.component.js: -------------------------------------------------------------------------------- 1 | const template = document.createElement('template'); 2 | const style = ` 3 | :host { 4 | --tab-button-border-radius: 5px; 5 | --tab-button-border-width: 3px; 6 | --tab-button-color: #6f7dbc; 7 | 8 | border: var(--tab-button-border-width) solid var(--tab-button-color); 9 | display: block; 10 | flex-grow: 1; 11 | overflow: none; 12 | 13 | background-color: #fff; 14 | color: var(--tab-button-color); 15 | cursor: pointer; 16 | font-size: 18px; 17 | padding: 10px; 18 | text-align: center; 19 | user-select: none 20 | transition: all 200ms ease-in-out; 21 | } 22 | 23 | :host([selected]) { 24 | background-color: var(--tab-button-color); 25 | color: #fff; 26 | transition: all 200ms ease-in-out; 27 | } 28 | 29 | :host(:not(:first-of-type)) { 30 | border-left-width: 0; 31 | } 32 | 33 | :host(:first-of-type) { 34 | border-top-left-radius: var(--tab-button-border-radius); 35 | border-bottom-left-radius: var(--tab-button-border-radius); 36 | } 37 | 38 | :host(:last-of-type) { 39 | border-top-right-radius: var(--tab-button-border-radius); 40 | border-bottom-right-radius: var(--tab-button-border-radius); 41 | } 42 | `; 43 | 44 | template.innerHTML = ` 45 | 46 | 47 | `; 48 | 49 | class CoolTab extends HTMLElement { 50 | constructor() { 51 | super(); 52 | 53 | this._selected = false; 54 | this._value = ''; 55 | } 56 | 57 | connectedCallback() { 58 | const shadowRoot = this.attachShadow({ mode: 'open' }); 59 | shadowRoot.appendChild(template.content.cloneNode(true)); 60 | 61 | this._value = this.getAttribute('value'); 62 | this.clickListener = this.addEventListener('click', () => this.selected = true); 63 | } 64 | 65 | get value() { 66 | return this._value; 67 | } 68 | 69 | get selected() { 70 | return this._selected; 71 | } 72 | 73 | set selected(selected) { 74 | if (selected === this.selected) { 75 | return; 76 | } 77 | 78 | this._selected = selected; 79 | this.render(); 80 | 81 | if (this.selected) { 82 | this.dispatchTabSelect(); 83 | } 84 | } 85 | 86 | dispatchTabSelect() { 87 | this.dispatchEvent(new CustomEvent('tabselect', { bubbles: true })); 88 | } 89 | 90 | render() { 91 | if (this.selected) { 92 | this.setAttribute('selected', ''); 93 | this.set 94 | } else { 95 | this.removeAttribute('selected'); 96 | } 97 | } 98 | } 99 | 100 | window.customElements.define('cool-tab', CoolTab); 101 | -------------------------------------------------------------------------------- /src/examples/cool-stop-watch.lit-html.component.js: -------------------------------------------------------------------------------- 1 | import { html, render } from 'lit-html'; 2 | 3 | const componentStyles = ` 4 | :host { 5 | border: 1px solid black; 6 | background-color: white; 7 | color: black; 8 | display: inline-block; 9 | padding: 10px; 10 | } 11 | 12 | button { 13 | border-radius: 5px; 14 | font-size: 16px; 15 | padding: 5px 10px; 16 | } 17 | 18 | #stop-watch-controls { 19 | text-align: center; 20 | } 21 | `; 22 | 23 | class CoolStopWatch extends HTMLElement { 24 | connectedCallback() { 25 | this._currentTime = 0; 26 | this._stopWatchInterval; 27 | this._millisecondsInterval = 5; 28 | 29 | let initialTime = Number(this.getAttribute('current-time')); 30 | if (initialTime && !isNaN(initialTime)) { 31 | this._currentTime = initialTime; 32 | } 33 | 34 | this.attachShadow({ mode: 'open' }); 35 | this.render(); 36 | } 37 | 38 | disconnectedCallback() { 39 | clearInterval(this._stopWatchInterval); 40 | } 41 | 42 | start() { 43 | if (this.stopWatchIsRunning) { 44 | return; 45 | } 46 | 47 | this._stopWatchInterval = setInterval(() => { 48 | this.currentTime = this.currentTime + this._millisecondsInterval; 49 | }, this._millisecondsInterval); 50 | } 51 | 52 | stop() { 53 | clearInterval(this._stopWatchInterval); 54 | this._stopWatchInterval = undefined; 55 | this.render(); 56 | } 57 | 58 | reset() { 59 | this.currentTime = 0; 60 | } 61 | 62 | get currentTime() { 63 | return this._currentTime; 64 | } 65 | 66 | set currentTime(value) { 67 | this._currentTime = value; 68 | this.render(); 69 | } 70 | 71 | get formattedTime() { 72 | let minutes = this.currentTime / 60000; 73 | let seconds = (minutes % 1) * 60; 74 | let milliseconds = (seconds % 1) * 1000; 75 | 76 | minutes = Math.floor(minutes); 77 | seconds = Math.floor(seconds).toString().padStart(2, '0'); 78 | milliseconds = Math.floor(milliseconds).toString().padStart(3, '0'); 79 | 80 | return `${minutes}:${seconds}.${milliseconds}`; 81 | } 82 | 83 | get stopWatchIsRunning() { 84 | return !!this._stopWatchInterval; 85 | } 86 | 87 | render() { 88 | render(html` 89 | 90 | 91 |
${this.formattedTime}
92 |
93 | ${!this.stopWatchIsRunning 94 | ? html`` 95 | : null 96 | } 97 | ${this.stopWatchIsRunning 98 | ? html`` 99 | : null 100 | } 101 | 102 |
103 | `, this.shadowRoot); 104 | } 105 | } 106 | 107 | window.customElements.define('cool-stop-watch-lit-html', CoolStopWatch); 108 | -------------------------------------------------------------------------------- /src/examples/cool-stop-watch.vanilla.component.js: -------------------------------------------------------------------------------- 1 | const template = document.createElement('template'); 2 | const componentStyles = ` 3 | :host { 4 | border: 1px solid black; 5 | background-color: white; 6 | color: black; 7 | display: inline-block; 8 | padding: 10px; 9 | } 10 | 11 | button { 12 | border-radius: 5px; 13 | font-size: 16px; 14 | padding: 5px 10px; 15 | } 16 | 17 | #stop-watch-controls { 18 | text-align: center; 19 | } 20 | `; 21 | 22 | const templateString = ` 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | `; 32 | template.innerHTML = templateString; 33 | 34 | class CoolStopWatch extends HTMLElement { 35 | connectedCallback() { 36 | this._currentTime = 0; 37 | this._stopWatchInterval; 38 | this._millisecondsInterval = 5; 39 | 40 | let initialTime = Number(this.getAttribute('current-time')); 41 | if (initialTime && !isNaN(initialTime)) { 42 | this._currentTime = initialTime; 43 | } 44 | 45 | this.attachShadow({ mode: 'open' }); 46 | this.shadowRoot.appendChild(template.content.cloneNode(true)); 47 | this.$timeDisplay = this.shadowRoot.querySelector('#time'); 48 | this.$startButton = this.shadowRoot.querySelector('#start'); 49 | this.$stopButton = this.shadowRoot.querySelector('#stop'); 50 | this.$resetButton = this.shadowRoot.querySelector('#reset'); 51 | 52 | this.start = this.start.bind(this); 53 | this.stop = this.stop.bind(this); 54 | this.reset = this.reset.bind(this); 55 | 56 | this.$startButton.addEventListener('click', this.start); 57 | this.$stopButton.addEventListener('click', this.stop); 58 | this.$resetButton.addEventListener('click', this.reset); 59 | 60 | this.render(); 61 | } 62 | 63 | disconnectedCallback() { 64 | clearInterval(this._stopWatchInterval); 65 | this.$startButton.removeEventListener('click', this.start); 66 | this.$stopButton.removeEventListener('click', this.stop); 67 | this.$resetButton.removeEventListener('click', this.reset); 68 | } 69 | 70 | start() { 71 | if (this.isStopWatchRunning) { 72 | return; 73 | } 74 | 75 | this._stopWatchInterval = setInterval(() => { 76 | this.currentTime = this.currentTime + this._millisecondsInterval; 77 | }, this._millisecondsInterval); 78 | } 79 | 80 | stop() { 81 | clearInterval(this._stopWatchInterval); 82 | this._stopWatchInterval = undefined; 83 | this.render(); 84 | } 85 | 86 | reset() { 87 | this.currentTime = 0; 88 | } 89 | 90 | get currentTime() { 91 | return this._currentTime; 92 | } 93 | 94 | set currentTime(value) { 95 | this._currentTime = value; 96 | this.render(); 97 | } 98 | 99 | get formattedTime() { 100 | let minutes = this.currentTime / 60000; 101 | let seconds = (minutes % 1) * 60; 102 | let milliseconds = (seconds % 1) * 1000; 103 | 104 | minutes = Math.floor(minutes); 105 | seconds = Math.floor(seconds).toString().padStart(2, '0'); 106 | milliseconds = Math.floor(milliseconds).toString().padStart(3, '0'); 107 | 108 | return `${minutes}:${seconds}.${milliseconds}`; 109 | } 110 | 111 | get isStopWatchRunning() { 112 | return !!this._stopWatchInterval; 113 | } 114 | 115 | render() { 116 | this.$timeDisplay.textContent = this.formattedTime; 117 | 118 | if (this.isStopWatchRunning) { 119 | this.$startButton.style.display = 'none'; 120 | this.$stopButton.style.display = 'inline'; 121 | } else { 122 | this.$startButton.style.display = 'inline'; 123 | this.$stopButton.style.display = 'none'; 124 | } 125 | } 126 | } 127 | 128 | window.customElements.define('cool-stop-watch', CoolStopWatch); 129 | -------------------------------------------------------------------------------- /src/examples/cool-side-menu.component.js: -------------------------------------------------------------------------------- 1 | const template = document.createElement('template'); 2 | const styles = ` 3 | :host { 4 | --menu-primary-color: #f8981c; 5 | --menu-primary-contrast-color: #fff; 6 | --menu-width: 400px; 7 | --title-font-size: 1.5em; 8 | --menu-item-font-size: 1.2em; 9 | } 10 | .frame { 11 | position: fixed; 12 | top: 0; 13 | bottom: 0; 14 | width: 100%; 15 | overflow: hidden; 16 | pointer-events: none; 17 | z-index: 1000; 18 | transition: background-color 300ms ease-in; 19 | } 20 | .frame[open] { 21 | pointer-events: auto; 22 | background-color: rgba(0,0,0,0.25); 23 | } 24 | .frame[open] .container { 25 | transform: translate3d(0, 0, 0); 26 | } 27 | .container { 28 | width: var(--menu-width); 29 | background: #FFF; 30 | height: 100%; 31 | transform: translate3D(-100%, 0, 0); 32 | will-change: transform; 33 | transition: transform 300ms ease-in; 34 | box-shadow: 1px 0 3px rgba(51,51,51,0.25); 35 | } 36 | .title { 37 | display: flex; 38 | flex-direction: row; 39 | min-height: 3.2em; 40 | font-size: var(--title-font-size); 41 | background-color: var(--menu-primary-color); 42 | color: var(--menu-primary-contrast-color); 43 | } 44 | .title .title-content { 45 | flex-grow: 1; 46 | display: flex; 47 | align-items: center; 48 | padding-left: 1em; 49 | } 50 | .close { 51 | align-items: center; 52 | flex-basis: 100px; 53 | flex-grow: 0; 54 | flex-shrink: 0; 55 | cursor: pointer; 56 | display: flex; 57 | justify-content: center; 58 | user-select: none; 59 | } 60 | .menu-items::slotted(a) { 61 | display: block; 62 | font-size: var(--menu-item-font-size); 63 | text-decoration: none; 64 | line-height: 2.5em; 65 | padding: 0.5em; 66 | border-bottom: solid 1px #F1F1F1; 67 | color: #665; 68 | } 69 | .menu-items::slotted(a:hover) { 70 | color: var(--menu-primary-color); 71 | } 72 | :host([backdrop="false"]) .frame[open] { 73 | pointer-events: none; 74 | background-color: inherit; 75 | } 76 | :host([backdrop="false"]) .frame[open] .container { 77 | pointer-events: auto; 78 | } 79 | `; 80 | // #6f7dbc 81 | template.innerHTML = ` 82 | 83 |
84 | 95 |
96 | `; 97 | 98 | class CoolSideMenuComponent extends HTMLElement { 99 | constructor() { 100 | super(); 101 | 102 | this._$frame = null; 103 | this._isOpen = false; 104 | } 105 | 106 | connectedCallback() { 107 | const shadowRoot = this.attachShadow({ mode: 'open' }); 108 | shadowRoot.appendChild(template.content.cloneNode(true)); 109 | 110 | this._$frame = shadowRoot.querySelector('.frame'); 111 | this._$frame.addEventListener('click', (event) => this.onCloseClick(event)); 112 | } 113 | 114 | set isOpen(value) { 115 | const result = !!value; 116 | if (this._isOpen === result) { 117 | return; 118 | } 119 | this._isOpen = result; 120 | this.render(); 121 | } 122 | 123 | get isOpen() { 124 | return this._isOpen; 125 | } 126 | 127 | open() { 128 | this.isOpen = true; 129 | } 130 | 131 | close() { 132 | this.isOpen = false; 133 | } 134 | 135 | onCloseClick(event) { 136 | if (event.target.dataset.close === 'true') { 137 | this.close(); 138 | } 139 | } 140 | 141 | render() { 142 | if (this._$frame) { 143 | if (this.isOpen) { 144 | this._$frame.setAttribute('open', ''); 145 | this.dispatchEvent(new CustomEvent('menuopen')); 146 | } else { 147 | this._$frame.removeAttribute('open'); 148 | this.dispatchEvent(new CustomEvent('menuclose')); 149 | } 150 | } 151 | } 152 | } 153 | 154 | window.customElements.define('cool-side-menu', CoolSideMenuComponent); 155 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Intro to Web Components 📦 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
27 |
28 |
Intro to Web Components
29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
Hayden Braxton
39 | @hayden_dev 40 |
41 |
42 |
43 | 44 |
48 | 49 | 50 | 51 | 52 |
53 | 54 |
58 | Slides: github.com/haydenbr/intro-to-web-components 59 |
60 | 61 |
62 |
What are Web Components?
63 |
64 | 65 |
69 |
The Web Standards
70 | 71 | 72 | 73 | 74 |
HTML imports
75 |
76 | 77 |
78 |
Why should I care?
79 |
80 | 81 |
85 |
Cross-framework compatability
86 | 87 | 88 |
89 | 90 |
94 |
Ship less code
95 | 96 | 97 |
98 | 99 |
103 |
Potentially better performance
104 | 105 | 106 |
107 | 108 |
112 |
More semantic markup
113 | 114 | 115 |
116 | 117 |
121 |
Bundle markup, styles, and scripts together
122 | 123 |
124 | 125 |
126 |
What about browser support?
127 |
128 | 129 |
133 | 134 |
135 | 136 |
137 |
Demos!
138 | 139 |
140 | 141 |
145 |
kitchen-sink
146 | 147 | 148 | message in the default slot 149 | Masala cheese fries! 150 |
151 | nested content 152 |
153 |
154 |
155 | 156 |
159 | (opinion warning) 160 |
161 | 162 |
166 |

WEB COMPONENTS ARE THE FUTURE! NO MORE FRAMEWORKS!

167 | 168 | 169 | 177 |
178 | 179 |
183 |
Tools
184 | 185 |
186 | Polymer 187 |
188 |
189 | Stencil 190 |
191 |
192 | Ionic Framework 193 |
194 |
195 | 196 |
200 |
cool-stop-watch
201 | 202 | 203 | 204 | 205 |
206 | 207 |
211 |
Resources
212 | 213 | WebComponents.org: The Specs 214 |
215 | Google Web Fundamentals: Web Components 216 |
217 | Use case: Ola 218 |
219 | Polyfills 220 |
221 | Custom Elements Everywhere 222 |
223 | GitHub removes jquery 224 |
225 | Original Google I/O 2012 Web Components Talk 226 |
227 | awesome-lit-html 228 |
229 | 230 |
234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 |
243 |
244 |
245 | 246 | 247 | --------------------------------------------------------------------------------