Intro to LitElement
What is LitElement?
LitElement is a base class for creating web components.
Think of it as a lightweight library that helps you build ShadowDOM web components.
What the heck is ShadowDOM?
For more info about ShadowDOM check
Shadow DOM refers to the ability of the browser to include a subtree of DOM elements into the rendering of a document, but not into the main document DOM tree.
Getting started with LitElement
Let's play a bit with lit-element
Just follow the instructions from the next slides
Create a new folder and run npm init
# create a new folder
mkdir lit-element-demo
# go to that folder
cd lit-element-demo
#run npm init
npm init -ySetup
Install lit-element
# install lit-element as a dependency
npm install lit-element --saveSetup
Install webpack
# install webpack, webpack-cli, html-webpack-plugin, and webpack-dev-server as dev dependencies
npm install webpack webpack-cli html-webpack-plugin webpack-dev-server --save-devSetup
Install typescript
# install typescript and typescript-loader as dev dependencies
npm install typescript ts-loader --save-devSetup
Install support for styling lit-element components
# install node-sass, style-loader, sass-loader, and lit-css-loader as dev dependencies
npm install node-sass style-loader sass-loader lit-css-loader --save-devSetup
Setup webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.ts',
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.lit\.scss$/,
use: ['lit-css-loader', 'sass-loader']
}
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin()
],
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}
};Setup
Setup tsconfig.json
// These options are used to set up and enable some features for typescript
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"experimentalDecorators": true,
"module": "es6",
"target": "es6",
"moduleResolution": "node"
}
}Setup
Setup declarations.d.ts
// This enables proper reading of the styles used for elements
// The styles are named as [name].list.scss
declare module '*.lit.scss' {
import { CSSResult } from "lit-element";
const css: CSSResult;
export default css;
}Setup
Create index.ts file
// create a new directory called src and add an index.ts file
console.log('It works!');Setup
Add some scripts in package.json
{
"name": "lit-element-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// add these scripts
"start": "webpack-dev-server",
"build": "webpack"
},
"author": "",
"license": "ISC",
"dependencies": {
"lit-element": "^2.2.1"
},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"lit-css-loader": "0.0.3",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.1",
"ts-loader": "^6.2.1",
"typescript": "^3.7.2",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0"
}
}
Setup
Let's test the initial setup
# run this command
npm start
# then open localhost:9000Setup
Create a folder in /src called components, then create a button.ts file
// src/components/button.ts
import { LitElement, customElement, html } from 'lit-element';
@customElement('button-component')
export class Button extends LitElement {
render() {
return html`<button>Click me</button>`;
}
}
Create a basic component
Import the Button class in index.ts
// src/index.ts
import './components/button';
// Create a div with id=root, then append the web component
const div = document.createElement('div');
div.id = 'root';
document.body.appendChild(div);
document.querySelector('#root').innerHTML = '<button-component></button-component>';
// test on localhost:9000Create a basic component
Create a stylesheet for the button-component component
// src/components/button.lit.scss
button {
background: green;
border: none;
color: white;
padding: 10px;
}Create a basic component
Add the stylesheet to the button-component component
Create a basic component
// src/components/button.ts
import { LitElement, customElement, html } from 'lit-element';
import styles from './button.lit.scss';
@customElement('button-component')
export class Button extends LitElement {
static styles = styles;
render() {
return html`<button>Click me</button>`;
}
}
// test on localhost:9000
Let's build a todo list using lit-element
Delete button.ts and button.lit.scss, keep the rest and continue following the instructions
GitHub project:
Create the main placeholder
// src/components/todo-list-placeholder.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-placeholder')
export class TodoListPlaceholder extends LitElement {
render() {
return html`
<div class="todo-list-placeholder">
<div class="todo-list-header-placeholder">
</div>
<div class="todo-list-content-placeholder">
</div>
</div>
`;
}
}Create the todo list
Create the main component
// src/components/todo-list.ts
import { customElement, LitElement, html } from 'lit-element';
import './todo-list-placeholder';
@customElement('todo-list')
export class TodoList extends LitElement {
render() {
return html`
<div class="todo-list">
<todo-list-placeholder>
</todo-list-placeholder>
</div>
`;
}
}Create the todo list
Import the main component
// src/index.ts
import './components/todo-list';
const div = document.createElement('div');
div.id = 'root';
document.body.appendChild(div);
document.querySelector('#root').innerHTML = '<todo-list></todo-list>';
Create the todo list
Create the header
// src/components/todo-list-header.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-header')
export class TodoListHeader extends LitElement {
render() {
return html`
<div class="todo-list-header">
<input type="text" placeholder="Add item..." />
<button>Add</button>
</div>
`;
}
}Create the todo list
Make space in the placeholder for the header
// src/components/todo-list-placeholder.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-placeholder')
export class TodoListPlaceholder extends LitElement {
render() {
return html`
<div class="todo-list-placeholder">
<div class="todo-list-header-placeholder">
<slot></slot>
</div>
<div class="todo-list-content-placeholder">
</div>
</div>
`;
}
}Create the todo list
Add the header
// src/components/todo-list.ts
import { customElement, LitElement, html } from 'lit-element';
import './todo-list-placeholder';
import './todo-list-header';
@customElement('todo-list')
export class TodoList extends LitElement {
render() {
return html`
<div class="todo-list">
<todo-list-placeholder>
<todo-list-header></todo-list-header>
</todo-list-placeholder>
</div>
`;
}
}Create the todo list
Create the content
// src/components/todo-list-content.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-content')
export class TodoListContent extends LitElement {
render() {
return html`
<div class="todo-list-content">
<div class="todo-list-item">
<input type="checkbox" />
Item 1
<button>Delete</button>
</div>
<div class="todo-list-item">
<input type="checkbox" />
Item 2
<button>Delete</button>
</div>
<div class="todo-list-item">
<input type="checkbox" />
Item 3
<button>Delete</button>
</div>
</div>
`;
}
}Create the todo list
Make space in the placeholder for the content
// src/components/todo-list-placeholder.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-placeholder')
export class TodoListPlaceholder extends LitElement {
render() {
return html`
<div class="todo-list-placeholder">
<div class="todo-list-header-placeholder">
<slot name="header"></slot>
</div>
<div class="todo-list-content-placeholder">
<slot name="content"></slot>
</div>
</div>
`;
}
}
Create the todo list
Add the content
// src/components/todo-list.ts
import { customElement, LitElement, html } from 'lit-element';
import './todo-list-placeholder';
import './todo-list-header';
import './todo-list-content';
@customElement('todo-list')
export class TodoList extends LitElement {
render() {
return html`
<div class="todo-list">
<todo-list-placeholder>
<todo-list-header slot="header"></todo-list-header>
<todo-list-content slot="content"></todo-list-content>
</todo-list-placeholder>
</div>
`;
}
}
Create the todo list
Move the list items to a separate component
// src/components/todo-list-item.ts
import { customElement, LitElement, html } from 'lit-element';
@customElement('todo-list-item')
export class TodoListItem extends LitElement {
render() {
return html`
<div class="todo-list-item">
<input type="checkbox" />
<span><slot></slot></span>
<button>Delete</button>
</div>
`;
}
}Create the todo list
Move the list items to a separate component
// src/components/todo-list-content.ts
import { customElement, LitElement, html } from 'lit-element';
import './todo-list-item';
@customElement('todo-list-content')
export class TodoListContent extends LitElement {
render() {
return html`
<div class="todo-list-content">
<todo-list-item>Item 1</todo-list-item>
<todo-list-item>Item 2</todo-list-item>
<todo-list-item>Item 3</todo-list-item>
</div>
`;
}
}Create the todo list
A bit of styling
// src/components/todo-list-placeholder.lit.scss
.todo-list-placeholder {
border: 1px solid #ddd;
padding: 10px;
}Create the todo list
A bit of styling
// src/components/todo-list-placeholder.ts
import { customElement, LitElement, html } from 'lit-element';
import styles from './todo-list-placeholder.lit.scss';
@customElement('todo-list-placeholder')
export class TodoListPlaceholder extends LitElement {
static styles = styles;
render() {
return html`
<div class="todo-list-placeholder">
<div class="todo-list-header-placeholder">
<slot name="header"></slot>
</div>
<div class="todo-list-content-placeholder">
<slot name="content"></slot>
</div>
</div>
`;
}
}Create the todo list
A bit of styling
// src/components/todo-list-header.lit.scss
.todo-list-header {
display: flex;
margin-bottom: 10px;
input {
flex-grow: 1;
margin-right: 10px;
padding: 10px;
}
button {
background: green;
border: none;
color: white;
flex-basis: 100px;
}
}Create the todo list
A bit of styling
// src/components/todo-list-header.ts
import { customElement, LitElement, html } from 'lit-element';
import styles from './todo-list-header.lit.scss';
@customElement('todo-list-header')
export class TodoListHeader extends LitElement {
static styles = styles;
render() {
return html`
<div class="todo-list-header">
<input type="text" placeholder="Add item..." />
<button>Add</button>
</div>
`;
}
}Create the todo list
A bit of styling
// src/components/todo-list-item.lit.scss
:host {
display: block;
}
.todo-list-item {
border: 1px solid #ddd;
padding: 10px;
display: flex;
font-family: sans-serif;
align-items: center;
span {
flex-grow: 1;
margin-left: 10px;
}
&.checked span {
text-decoration: line-through;
}
button {
background: red;
padding: 5px;
color: white;
border: none;
}
}Create the todo list
A bit of styling
// src/components/todo-list-item.ts
import { customElement, LitElement, html } from 'lit-element';
import styles from './todo-list-item.lit.scss';
@customElement('todo-list-item')
export class TodoListItem extends LitElement {
static styles = styles;
render() {
return html`
<div class="todo-list-item">
<input type="checkbox" />
<span><slot></slot></span>
<button>Delete</button>
</div>
`;
}
}Create the todo list
A bit of styling
// src/components/todo-list-content.lit.scss
.todo-list-content {
todo-list-item {
margin-bottom: 10px;
&:last-of-type {
margin-bottom: 0;
}
}
}Create the todo list
A bit of styling
// src/components/todo-list-content.ts
import { customElement, LitElement, html } from 'lit-element';
import './todo-list-item';
import styles from './todo-list-content.lit.scss';
@customElement('todo-list-content')
export class TodoListContent extends LitElement {
static styles = styles;
render() {
return html`
<div class="todo-list-content">
<todo-list-item>Item 1</todo-list-item>
<todo-list-item>Item 2</todo-list-item>
<todo-list-item>Item 3</todo-list-item>
</div>
`;
}
}Create the todo list
Submit an event when adding a new item
// src/components/todo-list-header.ts
import { customElement, html, LitElement, query } from "lit-element";
import styles from "./todo-list-header.lit.scss";
@customElement("todo-list-header")
export class TodoListHeader extends LitElement {
static styles = styles;
@query("input")
textfield: HTMLInputElement;
private addItem = () => {
const value = this.textfield.value;
if (!value) return;
this.dispatchEvent(new CustomEvent("addItem", {
detail: {
value: value,
checked: false,
id: new Date().getTime() + Math.random()
}
}));
this.textfield.value = "";
}
render() {
return html`
<div class="todo-list-header">
<input type="text" placeholder="Add item..." />
<button @click="${this.addItem}">Add</button>
</div>
`;
}
}Create the todo list
Make the todo-list component listen to the event
// src/components/todo-list.ts
import { customElement, html, internalProperty, LitElement } from "lit-element";
import "./todo-list-placeholder";
import "./todo-list-header";
import "./todo-list-content";
@customElement("todo-list")
export class TodoList extends LitElement {
@internalProperty()
private items = [];
private addItem = (e: CustomEvent) => {
this.items = [...this.items, e.detail];
};
render() {
return html`
<div class="todo-list">
<todo-list-placeholder>
<todo-list-header slot="header" @addItem="${this.addItem}"></todo-list-header>
<todo-list-content slot="content" .items="${this.items}"></todo-list-content>
</todo-list-placeholder>
</div>
`;
}
}Create the todo list
Show the items in the todo-list-content component
// src/components/todo-list-content.ts
import { customElement, html, LitElement, property } from "lit-element";
import "./todo-list-item";
import styles from "./todo-list-content.lit.scss";
@customElement("todo-list-content")
export class TodoListContent extends LitElement {
static styles = styles;
@property({type: Object})
items = [];
render() {
return html`
<div class="todo-list-content">
${this.items.map(item => (
html`<todo-list-item>${item.value}</todo-list-item>`
))}
</div>
`;
}
}Create the todo list
Show items as checked
// src/components/todo-list-content.ts
import { customElement, html, LitElement, property } from "lit-element";
import "./todo-list-item";
import styles from "./todo-list-content.lit.scss";
@customElement("todo-list-content")
export class TodoListContent extends LitElement {
static styles = styles;
@property({type: Object})
items = [];
render() {
return html`
<div class="todo-list-content">
${this.items.map(item => (
html`<todo-list-item ?checked="${item.checked}">${item.value}</todo-list-item>`
))}
</div>
`;
}
}Create the todo list
Show items as checked
// src/components/todo-list-item.ts
import { customElement, LitElement, html, property } from 'lit-element';
import styles from './todo-list-item.lit.scss';
@customElement('todo-list-item')
export class TodoListItem extends LitElement {
static styles = styles;
@property({type: Boolean})
checked = false;
render() {
return html`
<div class="todo-list-item">
<input type="checkbox" ?checked="${this.checked}" />
<span><slot></slot></span>
<button>Delete</button>
</div>
`;
}
}Create the todo list
Check and uncheck items
// src/components/todo-list-item.ts
import { customElement, LitElement, html, property } from 'lit-element';
import { classMap } from 'lit-html/directives/class-map';
import styles from './todo-list-item.lit.scss';
@customElement('todo-list-item')
export class TodoListItem extends LitElement {
static styles = styles;
@property({type: Boolean})
checked = false;
private checkItem = () => {
this.checked = !this.checked;
};
render() {
return html`
<div @click="${this.checkItem}" class="${classMap({"todo-list-item": true, "checked": this.checked})}">
<input type="checkbox" ?checked="${this.checked}" />
<span><slot></slot></span>
<button>Delete</button>
</div>
`;
}
}Create the todo list
Delete items
// src/components/todo-list-content.ts
import { customElement, html, LitElement, property } from "lit-element";
import "./todo-list-item";
import styles from "./todo-list-content.lit.scss";
@customElement("todo-list-content")
export class TodoListContent extends LitElement {
static styles = styles;
@property({type: Object})
items = [];
render() {
return html`
<div class="todo-list-content">
${this.items.map(item => (
html`<todo-list-item ?checked="${item.checked}" itemId="${item.id}">${item.value}</todo-list-item>`
))}
</div>
`;
}
}Create the todo list
Delete items
// src/components/todo-list-item.ts
import { customElement, html, LitElement, property } from "lit-element";
import { classMap } from 'lit-html/directives/class-map';
import styles from "./todo-list-item.lit.scss";
@customElement("todo-list-item")
class TodoListItem extends LitElement {
static styles = styles;
@property({type: Boolean})
checked = false;
@property({type: Number})
itemId = 0;
private checkItem = () => {
this.checked = !this.checked;
};
private deleteItem = (e: Event) => {
e.stopImmediatePropagation();
this.dispatchEvent(new CustomEvent("deleteItem", {
detail: {
id: this.itemId
},
bubbles: true,
composed: true
}));
};
render() {
return html`
<div class="${classMap({"todo-list-item": true, "checked": this.checked})}" @click="${this.checkItem}">
<input type="checkbox" ?checked="${this.checked}" />
<span><slot></slot></span>
<button @click="${this.deleteItem}">Delete</button>
</div>
`;
}
}Create the todo list
Delete items
// src/components/todo-list.ts
import { customElement, html, internalProperty, LitElement } from "lit-element";
import "./todo-list-placeholder";
import "./todo-list-header";
import "./todo-list-content";
@customElement("todo-list")
export class TodoList extends LitElement {
@internalProperty()
private items = [];
private addItem = (e: CustomEvent) => {
this.items = [...this.items, e.detail];
};
private deleteItem = (e: CustomEvent) => {
this.items = this.items.filter(item => item.id !== e.detail.id);
};
render() {
return html`
<div class="todo-list">
<todo-list-placeholder>
<todo-list-header slot="header" @addItem="${this.addItem}"></todo-list-header>
<todo-list-content slot="content" .items="${this.items}" @deleteItem="${this.deleteItem}"></todo-list-content>
</todo-list-placeholder>
</div>
`;
}
}Create the todo list
Done!
Find out more at https://lit-element.polymer-project.org/
Intro to Lit Element
By alininayeh
Intro to Lit Element
- 534