From e59a6233f156213168da0f5172572253420d7a46 Mon Sep 17 00:00:00 2001 From: Elliott Marquez <5981958+e111077@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:34:30 -0700 Subject: [PATCH] [WIP] lit cheat sheet --- .../lit-cheat-sheet/add-styles/index.html | 3 + .../lit-cheat-sheet/add-styles/my-element.ts | 18 + .../lit-cheat-sheet/add-styles/project.json | 8 + .../lit-cheat-sheet/classes/index.html | 3 + .../lit-cheat-sheet/classes/my-element.ts | 29 ++ .../lit-cheat-sheet/classes/project.json | 8 + .../lit-cheat-sheet/conditionals/index.html | 3 + .../conditionals/my-element.ts | 26 + .../lit-cheat-sheet/conditionals/project.json | 8 + .../css-shadow-parts/index.html | 24 + .../css-shadow-parts/my-element.ts | 18 + .../css-shadow-parts/project.json | 8 + .../lit-cheat-sheet/data-up/game-player.ts | 18 + .../lit-cheat-sheet/data-up/index.html | 3 + .../lit-cheat-sheet/data-up/project.json | 9 + .../lit-cheat-sheet/data-up/score-board.ts | 20 + .../lit-cheat-sheet/define/hello-world.ts | 9 + .../lit-cheat-sheet/define/index.html | 3 + .../lit-cheat-sheet/define/project.json | 8 + .../event-listeners/index.html | 3 + .../event-listeners/my-element.ts | 47 ++ .../event-listeners/project.json | 8 + .../export-part/another-element.ts | 12 + .../lit-cheat-sheet/export-part/index.html | 12 + .../lit-cheat-sheet/export-part/my-element.ts | 10 + .../lit-cheat-sheet/export-part/project.json | 9 + .../lit-cheat-sheet/expressions/index.html | 3 + .../lit-cheat-sheet/expressions/my-element.ts | 62 +++ .../lit-cheat-sheet/expressions/project.json | 8 + .../host-global-listeners/index.html | 3 + .../host-global-listeners/my-element.ts | 55 ++ .../host-global-listeners/project.json | 8 + .../import/another-component.ts | 9 + .../lit-cheat-sheet/import/hello-world.ts | 10 + .../lit-cheat-sheet/import/index.html | 3 + .../lit-cheat-sheet/import/project.json | 9 + .../inheriting-custom-props/index.html | 21 + .../inheriting-custom-props/my-element.ts | 17 + .../inheriting-custom-props/project.json | 8 + .../lit-cheat-sheet/pass-data-down/id-card.ts | 20 + .../lit-cheat-sheet/pass-data-down/index.html | 3 + .../pass-data-down/my-wallet.ts | 12 + .../pass-data-down/project.json | 9 + .../lit-cheat-sheet/render-lists/index.html | 3 + .../render-lists/my-element.ts | 25 + .../lit-cheat-sheet/render-lists/project.json | 8 + .../lit-cheat-sheet/repeat/index.html | 3 + .../lit-cheat-sheet/repeat/my-element.ts | 38 ++ .../lit-cheat-sheet/repeat/project.json | 8 + .../lit-cheat-sheet/scope-styles/index.html | 4 + .../scope-styles/my-element.ts | 15 + .../lit-cheat-sheet/scope-styles/project.json | 8 + .../shadow-root-options/index.html | 3 + .../shadow-root-options/my-element.ts | 28 + .../shadow-root-options/project.json | 8 + .../share-styles-import/another-element.ts | 14 + .../share-styles-import/index.html | 5 + .../share-styles-import/my-element.ts | 19 + .../share-styles-import/project.json | 11 + .../share-styles-import/shared-styles.ts | 17 + .../turn-off-shadow-dom/index.html | 7 + .../turn-off-shadow-dom/my-element.ts | 16 + .../turn-off-shadow-dom/project.json | 8 + .../update-complete/index.html | 3 + .../update-complete/my-element.ts | 34 ++ .../update-complete/project.json | 8 + .../lit-cheat-sheet/virtualizer/index.html | 3 + .../lit-cheat-sheet/virtualizer/my-element.ts | 23 + .../lit-cheat-sheet/virtualizer/project.json | 8 + .../site/_includes/articles.html | 1 + .../site/articles/article/lit-cheat-sheet.md | 488 ++++++++++++++++++ packages/lit-dev-content/site/css/docs.css | 4 +- .../site/images/articles/lit_cheat_sheet.jpg | Bin 0 -> 12249 bytes .../images/articles/lit_cheat_sheet_2x.jpg | Bin 0 -> 21484 bytes .../src/components/litdev-example.ts | 69 ++- packages/lit-dev-server/src/server.ts | 10 + .../src/playground-plugin/plugin.ts | 36 +- 77 files changed, 1494 insertions(+), 28 deletions(-) create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/another-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/another-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/shared-styles.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts create mode 100644 packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json create mode 100644 packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md create mode 100644 packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg create mode 100644 packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts new file mode 100644 index 000000000..9fc3fecd0 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts @@ -0,0 +1,18 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + render() { + return html`

I'm blue

I'm red
`; + } + + static styles = css` + p { + color: blue; + } + div { + color: red; + } + `; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json new file mode 100644 index 000000000..816404c3d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/my-element.ts new file mode 100644 index 000000000..e31973473 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/my-element.ts @@ -0,0 +1,29 @@ +import { html, LitElement, css } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() counter = 0 + + firstUpdated() { + setInterval(() => this.counter += 1 , 1000); + } + + render() { + const classes = { + red: this.counter % 2 === 0, + blue: this.counter % 2 === 1 + }; + return html`

Hello!

`; + } + + static styles = css` + .red { + color: red; + } + .blue { + color: blue; + } + `; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json new file mode 100644 index 000000000..816404c3d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts new file mode 100644 index 000000000..860091f41 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts @@ -0,0 +1,26 @@ +import { html, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() private someBoolean = false; + + render() { + let someText = html`

Some text

`; + + if (this.someBoolean) { + someText = html`

Some other text

`; + } + + return html` + +
This is an inline ternary conditional
+ ${this.someBoolean ? html`

Some other text

` : html`

Some text

`} +
This is a variable conditional
+ ${someText} + `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json new file mode 100644 index 000000000..9c791c2a3 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "200px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html new file mode 100644 index 000000000..63ef6c4b2 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html @@ -0,0 +1,24 @@ + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts new file mode 100644 index 000000000..6fac25d2d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts @@ -0,0 +1,18 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + static styles = css` + p { + color: blue; + border: 1px solid black; + padding: 4px; + margin-block: 4px; + } + `; + + render() { + return html`

This is in a shadow root!

`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json new file mode 100644 index 000000000..875a71aaf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "120px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts new file mode 100644 index 000000000..fdfdab6a8 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts @@ -0,0 +1,18 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +export type ScoreEvent = CustomEvent; + +@customElement('game-player') +export class GamePlayer extends LitElement { + render() { + return html` + + + `; + } + + handleScore(points: number) { + this.dispatchEvent(new CustomEvent('score', { detail: points, bubbles: true })); + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html new file mode 100644 index 000000000..0e004d712 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json new file mode 100644 index 000000000..9ccb50497 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json @@ -0,0 +1,9 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "score-board.ts": {}, + "game-player.ts": {}, + "index.html": {} + }, + "previewHeight": "250px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts new file mode 100644 index 000000000..6a8d3dd36 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts @@ -0,0 +1,20 @@ +import { html, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import './game-player.js'; +import type {ScoreEvent} from './game-player.js'; + +@customElement('score-board') +export class ScoreBoard extends LitElement { + @state() playerOneScore = 0; + @state() playerTwoScore = 0; + + render() { + return html` +

${this.playerOneScore} - ${this.playerTwoScore}

+

Player 1

+ this.playerOneScore += e.detail}> +

Player 2

+ this.playerTwoScore += e.detail}> + `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts new file mode 100644 index 000000000..b4f5ba4d5 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts @@ -0,0 +1,9 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('hello-world') +export class HelloWorld extends LitElement { + render() { + return html`

Hello, world!

`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html new file mode 100644 index 000000000..c53974764 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json new file mode 100644 index 000000000..6430bb8e9 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "hello-world.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts new file mode 100644 index 000000000..4d0219b83 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts @@ -0,0 +1,47 @@ +import { html, LitElement } from 'lit'; +import { customElement, query, state } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() inputValue = ''; + @state() hasError = false; + @query('input') inputEl!: HTMLInputElement; + + render() { + return html` + +
+ + + + ${this.hasError ? html`Only positive numbers are allowed` : ''} +
${this.inputValue}
+
+ `; + } + + onInput(e: InputEvent) { + this.inputValue = (e.target as HTMLInputElement).value; + if (!this.inputValue.match(/^[0-9]+$/)) { + this.inputEl.dispatchEvent(new Event('number-error', {bubbles: true})); + return; + } + + this.inputEl.dispatchEvent(new Event('number-success', {bubbles: true})); + } + + onClick() { + this.inputValue = ''; + this.inputEl.focus(); + } + + onNumberError() { + this.hasError = true; + } + + onNumberSuccess() { + this.hasError = false; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/project.json new file mode 100644 index 000000000..9936a8af2 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "170px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/another-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/another-element.ts new file mode 100644 index 000000000..05456dd5d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/another-element.ts @@ -0,0 +1,12 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('another-element') +export class AnotherElement extends LitElement { + render() { + return html` +
Part 1
+
Part 2
+ `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/index.html new file mode 100644 index 000000000..09a583b4e --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/index.html @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/my-element.ts new file mode 100644 index 000000000..f13ab63c8 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/my-element.ts @@ -0,0 +1,10 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import './another-element.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + render() { + return html``; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/project.json new file mode 100644 index 000000000..911c75617 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/export-part/project.json @@ -0,0 +1,9 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "another-element.ts": {}, + "index.html": {} + }, + "previewHeight": "120px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/my-element.ts new file mode 100644 index 000000000..b2d40febd --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/my-element.ts @@ -0,0 +1,62 @@ +import { css, html, LitElement, PropertyValues } from 'lit'; +import { customElement, query, state } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() htmlText = ''; + @state() disabled = false; + @state() label = 'label'; + @state() value = 'value'; + @query('input') inputEl!: HTMLElement; + + render() { + return html` + +
+ Renders to: +
${this.htmlText}
+
+ +
+ + + +
+ + `; + } + + updated(changed: PropertyValues) { + /* playground-fold */ + super.updated(changed); + this.htmlText = this.inputEl.outerHTML; + /* playground-fold-end */ + } + + static styles = css` + /* playground-fold */ + label { + display: block; + } + /* playground-fold-end */ + `; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/project.json new file mode 100644 index 000000000..9936a8af2 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/expressions/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "170px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/my-element.ts new file mode 100644 index 000000000..47bc9b474 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/my-element.ts @@ -0,0 +1,55 @@ +import { html, LitElement, isServer } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() focusedWithin = false; + @state() clickedOutside = false; + + render() { + return html` + + ${this.focusedWithin ? html`

Something in this component was focused

` : ''} + ${this.clickedOutside ? html`

Something was clicked OUTSIDE this component

` : ''} + `; + } + + connectedCallback(): void { + super.connectedCallback(); + + // Only want to do this in the browser since the server doesn't have the + // concept of events or document. + if (!isServer) { + document.addEventListener('click', this.onDocumentClick); + this.addEventListener('focusin', this.onFocusin); + this.addEventListener('focusout', this.onFocusout); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + + if (!isServer) { + // clean up to prevent memory leaks + document.removeEventListener('click', this.onDocumentClick); + // Garbage should also take care of removing these, but it's good practice + this.removeEventListener('focusin', this.onFocusin); + this.removeEventListener('focusout', this.onFocusout); + } + } + + // Should be an arrow function and not a class method to ensure `this` is + // bound correctly. + private onFocusin = () => { + this.focusedWithin = true; + }; + + private onFocusout = () => { + this.focusedWithin = false; + }; + + private onDocumentClick = (e: MouseEvent) => { + const path = e.composedPath(); + this.clickedOutside = !path.includes(this); + }; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json new file mode 100644 index 000000000..875a71aaf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "120px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts new file mode 100644 index 000000000..375269012 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts @@ -0,0 +1,9 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('another-component') +export class AnotherComponent extends LitElement { + render() { + return html`(I'm another component.)`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts new file mode 100644 index 000000000..de5f2dccf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts @@ -0,0 +1,10 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import './another-component.js' + +@customElement('hello-world') +export class HelloWorld extends LitElement { + render() { + return html`Hello, world! `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html new file mode 100644 index 000000000..c53974764 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json new file mode 100644 index 000000000..58dfe19ca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json @@ -0,0 +1,9 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "hello-world.ts": {}, + "another-component.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html new file mode 100644 index 000000000..5b273eb5d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html @@ -0,0 +1,21 @@ + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts new file mode 100644 index 000000000..f6ed8d2d6 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts @@ -0,0 +1,17 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + static styles = css` + p { + color: var(--sys-color-on-background, blue); + border: 1px solid var(--sys-color-outline, black); + padding: var(--comp-my-element-padding, 4px); + margin-block: var(--comp-my-element-margin, 4px); + } + `; + render() { + return html`

This is in a shadow root!

`; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json new file mode 100644 index 000000000..875a71aaf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "120px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts new file mode 100644 index 000000000..b7a427e95 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts @@ -0,0 +1,20 @@ +import { html, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('id-card') +export class IdCard extends LitElement { + @property() name = ''; + @property({ type: Number }) age = 0; + @property({ type: Boolean }) programmer = false; + + render() { + return html` +

${this.name}

+

Age: ${this.age}

+ + `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html new file mode 100644 index 000000000..d5f0f24d5 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts new file mode 100644 index 000000000..0159d6911 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts @@ -0,0 +1,12 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import './id-card.js'; + +@customElement('my-wallet') +export class MyWallet extends LitElement { + render() { + return html` + + `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json new file mode 100644 index 000000000..c8faab7cf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json @@ -0,0 +1,9 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-wallet.ts": {}, + "id-card.ts": {}, + "index.html": {} + }, + "previewHeight": "170px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/my-element.ts new file mode 100644 index 000000000..3e87d606a --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/my-element.ts @@ -0,0 +1,25 @@ +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import { map } from 'lit/directives/map.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + names = ['Chandler', 'Phoebe', 'Joey', 'Monica', 'Rachel', 'Ross']; + items = new Set(['Apple', 'Banana', 'Grape', 'Orange', 'Lime']) + + render() { + return html` +

A list of names that include the letter "e" (rendering an Array)

+ + +

My unique fruits (rendering a Set)

+ + `; + } +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/project.json new file mode 100644 index 000000000..00842cd85 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/render-lists/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "300px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/my-element.ts new file mode 100644 index 000000000..449c3537e --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/my-element.ts @@ -0,0 +1,38 @@ +import {LitElement, html} from 'lit'; +import {customElement, state} from 'lit/decorators.js'; +import {repeat} from 'lit/directives/repeat.js'; + +@customElement('my-element') +class MyElement extends LitElement { + @state() + tasks = [ + { id: 'a', label: 'Learn Lit'}, + { id: 'b', label: 'Feed the cat'}, + { id: 'c', label: 'Go for a walk'}, + { id: 'd', label: 'Take a nap'}, + ]; + + render() { + return html` +

Things to do today:

+ + + + `; + } + + private _sort(dir: number) { + this.tasks.sort((a, b) => a.label.localeCompare(b.label) * dir); + this.requestUpdate(); + } +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json new file mode 100644 index 000000000..89b03c57f --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "180px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html new file mode 100644 index 000000000..f22bdd0aa --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html @@ -0,0 +1,4 @@ + + + +

I'm also a p, but I'm not blue.

\ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts new file mode 100644 index 000000000..65dae1c97 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts @@ -0,0 +1,15 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + render() { + return html`

I'm blue

`; + } + + static styles = css` + p { + color: blue; + } + `; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json new file mode 100644 index 000000000..816404c3d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts new file mode 100644 index 000000000..fd5901705 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts @@ -0,0 +1,28 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + render() { + return html` +

+ Calling focus on this element will focus the first focusable element in + its shadow root thanks to delegatesFocus: true. Just try + clicking on this text and see how the input is focused instead. +

+ + `; + } + + static styles = css` + code { + background-color: #f4f4f4; + padding: 0.2em 0.4em; + } + `; +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/project.json new file mode 100644 index 000000000..816404c3d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/another-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/another-element.ts new file mode 100644 index 000000000..8da15f22c --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/another-element.ts @@ -0,0 +1,14 @@ +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import {sharedStyles} from './shared-styles.js'; + +@customElement('another-element') +export class AnotherElement extends LitElement { + static styles = sharedStyles + + render() { + return html` + This <code> element shares styles with the <my-element> element. + `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/index.html new file mode 100644 index 000000000..239e0f7ef --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/index.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/my-element.ts new file mode 100644 index 000000000..a16e97136 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/my-element.ts @@ -0,0 +1,19 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import {sharedStyles} from './shared-styles.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + static styles = [sharedStyles, css` + p code { + background-color: #faff00; + } + `] + + render() { + return html` + This <code> element shares styles with the <another-element> element. +

I am overriding the <code> element in this <p> tag.

+ `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/project.json new file mode 100644 index 000000000..1b3c94174 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/project.json @@ -0,0 +1,11 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "another-element.ts": {}, + "shared-styles.ts": {}, + "index.html": {} + }, + "previewHeight": "150px" +} + diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/shared-styles.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/shared-styles.ts new file mode 100644 index 000000000..1bf46d01b --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/share-styles-import/shared-styles.ts @@ -0,0 +1,17 @@ +import { css } from 'lit'; + +export const sharedStyles = css` + :host { + display: block; + border: 1px solid black; + padding: 8px; + margin-block: 4px; + } + + code { + font-family: monospace; + background-color: #f4f4f4; + padding: 2px; + border-radius: 3px; + } +`; \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/index.html new file mode 100644 index 000000000..fb0358064 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/index.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/my-element.ts new file mode 100644 index 000000000..60aee491b --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/my-element.ts @@ -0,0 +1,16 @@ +import { html, LitElement, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + override createRenderRoot() { + return this; + } + + render() { + return html` +

This is not rendered in a shadow root

+

Styles from index.html will leak in and static styles do not apply

+ `; + } +} \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/project.json new file mode 100644 index 000000000..816404c3d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/turn-off-shadow-dom/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "100px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/my-element.ts new file mode 100644 index 000000000..9a1cddb7d --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/my-element.ts @@ -0,0 +1,34 @@ +import { css, html, LitElement } from 'lit'; +import { customElement, query, state } from 'lit/decorators.js'; +import {styleMap} from 'lit/directives/style-map.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + @state() width = 1000; + @state() sizeInfo = ''; + @query('#text') textEl!: HTMLElement; + + private async updateSize() { + this.width = Math.random() * 900; + + // Wait for the updateComplete promise to resolve before measuring the text. + await this.updateComplete; + const rect = this.textEl.getBoundingClientRect(); + this.sizeInfo = `The width and height of the text is ${rect.width} x ${rect.height} pixels.`; + } + + render() { + return html` + +

${this.sizeInfo}

+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ `; + } + + static styles = css` + #text { + max-width: 100%; + border: 1px solid black; + } + `; +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json new file mode 100644 index 000000000..00842cd85 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "300px" +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html new file mode 100644 index 000000000..57a0c9aca --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts new file mode 100644 index 000000000..8161cfaaf --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts @@ -0,0 +1,23 @@ +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import '@lit-labs/virtualizer'; + +@customElement('my-element') +export class MyItems extends LitElement { + data = new Array(10000).fill('').map((i, n) => ({text: `Item ${n}`})); + + render() { + return html` + + `; + } +} diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json new file mode 100644 index 000000000..00842cd85 --- /dev/null +++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json @@ -0,0 +1,8 @@ +{ + "extends": "/samples/v3-base.json", + "files": { + "my-element.ts": {}, + "index.html": {} + }, + "previewHeight": "300px" +} diff --git a/packages/lit-dev-content/site/_includes/articles.html b/packages/lit-dev-content/site/_includes/articles.html index 3beccf0fb..66c4b431b 100644 --- a/packages/lit-dev-content/site/_includes/articles.html +++ b/packages/lit-dev-content/site/_includes/articles.html @@ -15,6 +15,7 @@ + {% endblock %} {% block content %} diff --git a/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md b/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md new file mode 100644 index 000000000..750ee3090 --- /dev/null +++ b/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md @@ -0,0 +1,488 @@ +--- +title: The Lit Cheat Sheet +publishDate: 2024-05-10 +lastUpdated: 2024-05-10 +summary: A quick reference for the basics of Lit +thumbnail: /images/articles/lit_cheat_sheet +tags: + - web-components + - web-components +eleventyNavigation: + parent: Articles + key: The Lit Cheat Sheet + order: 1 +author: + - elliott-marquez +preloadTsWorker: true +--- + +Do you need a quick reference for the basics of Lit? Look no further! This cheat +sheet will help you get started with, or just remember, the features of Lit. + +If you are coming from another framework, you might also want to supplement this +article with [Component Party](https://component-party.dev/) which compares +basic concepts across different frameworks. Just make sure that Lit is selected +at the top of the page! + +{% aside "positive" "no-header" %} + +Use the Table of Contents to jump to a specific section! + +{% endaside %} + +## Component Definition + +### Defining a Component + +`LitElement` is the base class for all Lit components. + +@customElementcustomElements.define is where you associate the name of your component with the class definition / logic of that component. + +`render()` is the method where you define your component's template using a tagged template literal with `html`. + +Write your HTML in the `html` tagged template literal. + +{% playground-ide "articles/lit-cheat-sheet/define", true %} + + +{% aside "info" %} + +Important rules: + +Components are global HTML elements, you currently can't have more than one with the same name on a page. + +Components must have dashes in their name (defined using @customElementcustomElements.define). + +{% endaside %} + + +**Related Documentation & Topics:** + +- [Defining a Component](/docs/components/defining/) +- [LitElement](/docs/api/LitElement/) +- [`@customElement`](/docs/api/decorators/#customElement) +- [`customElements.define`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) +- [Rendering](/docs/components/rendering/) +- [How to build your first Lit component](https://www.youtube.com/watch?v=QBa1_QQnRcs) (Video) +- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) + +### Import a component + +To use a component, import the file with it's definition. + +{% playground-ide "articles/lit-cheat-sheet/import", true %} + +**Related Documentation & Topics:** + +- [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) (external) +- [`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) (external) +- [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) (external) +- [`@customElement`](/docs/api/decorators/#customElement) +- [`customElements.define`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) (external) +- [Local dev servers (bare module specifiers)](/docs/tools/development/#devserver) + +## Templating + +### Render HTML + +Use the `html` tagged template literal to define your component's template. + +{% playground-ide "articles/lit-cheat-sheet/define", true %} + +**Related Documentation & Topics:** + +- [Templates Overview](/docs/templates/overview/) +- [Rendering](/docs/components/rendering/) + +### Conditionals + +Use standard JavaScript conditional expressions in your template to conditionally render content. + +{% playground-ide "articles/lit-cheat-sheet/conditionals", true %} + +**Related Documentation & Topics:** + +- [Conditionals](/docs/templates/conditionals/) +- [Expressions](/docs/templates/expressions/) + +### Attribute and Property Expressions (Binding syntax) + +Lit-html has three types of built-in expressions to set attributes or properties on elements: + +- Property expressions `.prop=${value}` +- Attribute expressions `attr=${value}` +- Boolean attribute expressions `?attr=${value}` + +{% playground-ide "articles/lit-cheat-sheet/expressions", true %} + +**Related Documentation & Topics:** + +- [Expressions](/docs/templates/expressions/) +- [Expressions – Attribute Expressions](/docs/templates/expressions/#attribute-expressions) +- [Expressions – Property Expressions](/docs/templates/expressions/#property-expressions) + +### Event Listener Expressions + +Lit-html has a built-in event listener expression to add event listeners to elements. + +{% playground-ide "articles/lit-cheat-sheet/event-listeners", true %} + +**Related Documentation & Topics:** + +- [Expressions](/docs/templates/expressions/) +- [Expressions – Event listener expressions](/docs/templates/expressions/#event-listener-expressions) +- [Events](/docs/components/events/) +- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video) +- [Customizing event listener options](/docs/components/events/#event-options-decorator) +- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external) + +### Rendering lists + +Lit-html can render JavaScript arrays and iterables. For most simple use cases, +you can use the `Array.map()` method to render arrays of items or the `map()` +directive to render any other iterables. This pattern is best used for short, +simple lists. + +{% playground-ide "articles/lit-cheat-sheet/render-lists", true %} + +**Related Documentation & Topics:** + +- [Lists](/docs/templates/lists/) +- [Working With Lists Tutorial](/tutorials/working-with-lists/) +- [Built-in directives – `map()`](/docs/templates/directives/#map) +- [Built-in directives – `range()`](/docs/templates/directives/#range) +- [Built-in directives – `join()`](/docs/templates/directives/#join) + +### Re-rendering lists efficiently + +For long lists that may change frequently, use the `repeat()` directive to efficiently re-render only the items that have changed. + +{% playground-ide "articles/lit-cheat-sheet/repeat", true %} + +- [Lists](/docs/templates/lists/) +- [Working With Lists Tutorial – The `repeat()` directive](/tutorials/working-with-lists/#6) +- [Built-in directives – `repeat()`](/docs/templates/directives/#repeat) + +### Virtualizing long lists + +For lists that are so long that it would be impractical to render all items at once, use the Lit Virtualizer to render only the items that are currently in view. + +{% playground-ide "articles/lit-cheat-sheet/virtualizer", true %} + +{% aside "labs" %} + +Lit Virtualizer is in labs + +meaning that its implementation might change until it has graduated and become stable. Additionally there are many more features to virtualizer, so it is recommended to look at the documentation. + +{% endaside %} + +**Related Documentation & Topics:** + +- [Lit Virtualizer Documentation](https://github.com/lit/lit/tree/main/packages/labs/virtualizer#readme) (external) +- [Lit Labs: Virtualizer](https://www.youtube.com/watch?v=ay8ImAgO9ik) (video) + +## Styles + +### Add Styles + +Add styles by defining the `static styles` property. Write CSS in the `css` tagged template literal. + +{% playground-ide "articles/lit-cheat-sheet/add-styles", true %} + +**Related Documentation & Topics:** + +- [Styles](/docs/components/styles/) + +### Styles are Scoped + +Styles *only* apply to the current element. That means you can feel free to use super generic selectors that you'd normally have to make up class names for. + +{% playground-ide "articles/lit-cheat-sheet/scope-styles", true %} + +**Related Documentation & Topics:** + +- [Styles](/docs/components/styles/) +- [Shadow DOM](/docs/components/shadow-dom/) + +### Conditionally Add Classes + +To conditionally apply styles it's generally best to use `classMap`. + +{% playground-ide "articles/lit-cheat-sheet/classes", true %} + +**Related Documentation & Topics:** + +- [Defining Scoped Styles in the template](/docs/components/styles/#styles-in-the-template) +- [`classMap`](/docs/templates/directives/#classmap) +- [`classMap` tsdoc](/docs/api/directives/#classMap) +- [Playground example](/playground/#sample=examples/directive-class-map) + + +### Sharing Lit styles with imports + +You can share Lit stylesheets with other components by exporting them from a module and importing them into another. + +{% playground-ide "articles/lit-cheat-sheet/share-styles-import", true %} + +- [Styling](/docs/components/styles/) +- [Sharing Styles](/docs/components/styles/#sharing-styles) + +### Inheriting Styles Through Shadow DOM With CSS Custom Properties + + +CSS Custom Properties can pierce multiple shadow roots allowing you to share values for specific properties. + +{% playground-ide "articles/lit-cheat-sheet/inheriting-custom-props", true %} + +**Related Documentation & Topics:** + +- [CSS Custom Properties](/docs/components/styles/#customprops) +- [Theming](/docs/components/styles/#theming) +- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (Video) + +### Setting Arbitrary Styles With CSS Shadow Parts + +CSS Shadow Parts are exposed by components with the `part=""` attribute. + +Shadow Parts can pierce individual shadow roots allowing you to set arbitrary styles on a given node using the `::part()` pseudo-element. + +{% playground-ide "articles/lit-cheat-sheet/css-shadow-parts", true %} + +**Related Documentation & Topics:** + +- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video) +- [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts) (external) +- [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) (external) + +### Exporting a CSS Shadow Part + +CSS Shadow part names can only apply to the targeted element. You need to use `exportparts` to expose a shadow part in nested shadow roots. + +You can export multiple parts by separating them with a comma (`,`). + +You can also rename parts with a colon (`:`). + +{% playground-ide "articles/lit-cheat-sheet/export-part", true %} + +**Related Documentation & Topics:** + +- [`exportparts`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/exportparts) (external) +- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video) +- [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts) (external) +- [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) (external) + +## Shadow DOM + +### What does Shadow DOM do? + +- Scopes styles to the shadow root +- Scopes the DOM to the shadow root + - Can't be targeted by querySelector calls from outside the shadow root +- Enables slotting content with the `` element +- Exposes an API for CSS with CSS Custom Properties and CSS Shadow Parts + +**Related Documentation & Topics:** + +- [DOM Encapsulation](/docs/components/rendering/#dom-encapsulation) +- [Working with Shadow DOM](/docs/components/shadow-dom/) +- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video) + +### Turn off Shadow DOM + +You can turn off the Shadow DOM by overriding the `createRenderRoot()` method and setting the render root to the element itself. + +{% playground-ide "articles/lit-cheat-sheet/turn-off-shadow-dom", true %} + +{% aside "warn" %} + +By turning off shadow DOM you lose the benefits of encapsulation, DOM scoping, and `` elements. + +Since the Shadow root no longer exists, Lit will no longer handle the `static styles` property for you. You must must decide how to handle your styles. + +{% endaside %} + +**Related Documentation & Topics:** + +- [Implementing `createRenderRoot()`](/docs/components/shadow-dom/#implementing-createrenderroot) + +### Turning on `delegatesFocus` and other shadow root options + +You can set shadow root options passed to `Element.attachShadow()` by overriding the static `shadowRootOptions` member. + +{% playground-ide "articles/lit-cheat-sheet/shadow-root-options", true %} + +**Related Documentation & Topics:** + +- [Setting `shadowRootOptions`](/docs/components/shadow-dom/#setting-shadowrootoptions) +- [`Element.prototype.attachShadow():options`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options) (external) +- [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus) (external) + +## Properties and State + +## Lifecycle + +### Lifecycle order + +There are two lifecycles in Lit, the native Web Component lifecycle and the +lifecycle that Lit adds on top of it to help handle property and state changes. + +There are more lifecycle events which can be +[found in the documentation](/docs/components/lifecycle/#reactive-update-cycle), +but the ones you would normally use are the following and this is their general +order: + +1. `constructor` – (Native custom element lifecycle) +2. `connectedCallback` – (Native) +3. `willUpdate` – (Lit lifecycle) +4. `update` – (Lit) +5. `render` – (Lit) +6. `firstUpdated` – (Lit) +7. `updated` – (Lit) +8. `disconnectedCallback` – (Native) + +{% aside "warn" %} + +The Lit lifecycle and the native custom element lifecycle are distinct and managed separately. + +While they generally follow a specific order, they may intermix because the +browser controls the native lifecycle, while Lit and JavaScript manage the Lit +lifecycle. + +For example, a component may be attached to the DOM and then removed before the +Lit lifecycle may run at all, or a component may be created with +`document.createElement` which would call the `constructor`, but if it's never +added to the DOM, the `connectedCallback` would never run and thus the Lit +lifecycle would never run. + +{% endaside %} + +**Related Documentation & Topics:** + +- [Lifecycle](/docs/components/lifecycle/) +- [Lifecycle diagram](/docs/components/lifecycle/#reactive-update-cycle) + +#### `constructor` + +- Runs when the element is created via: + - `document.createElement('my-element')` + - `element.innerHTML = ''` + - `new MyElement()` + - etc. +- A good place to set initial properties +- Do **NOT** require parameters in the constructor or modify DOM + +#### `connectedCallback` + +- *May* run on the server. This has not been finalized. +- Runs when the element is added to the DOM via: + - `element.appendChild(element)` + - `element.innerHTML = ''` + - etc. +- Can run multiple times, but a good spot to set up event listeners + +#### `willUpdate` + +- Runs on the server – do **NOT** access the DOM or browser APIs +- A good place to set up properties that depend on other properties + +#### `update` + +- A good place to update properties that depend on other properties that depend on DOM + +#### `render` + +- Runs on the server - do NOT access the DOM or browser APIs + +#### `firstUpdated` + +- Runs after the first render +- A good place to do some initializations that require the copmonent's rendered + DOM + +#### `updated` + +- A good place to do some updates that require the component's rendered DOM or + to update properties that depend on the rendered DOM +- Try to avoid setting reactive properties in this lifecycle as it may cause + unnecessary renders. Try to do them in `willUpdate` or `update` if possible. + +#### `disconnectedCallback` + +- Is called *AFTER* the element is removed from the DOM +- A good place to clean up event listeners + +### Waiting for an update + +All Lit elements have asynchronous lifecycles. You need to wait for the `updateComplete` promise to resolve before you can be sure that the element has finished updating its DOM. + +{% playground-ide "articles/lit-cheat-sheet/update-complete", true %} + +**Related Documentation & Topics:** + +- [Lifecycle](/docs/components/lifecycle/) +- [updateComplete()](/docs/components/lifecycle/#updatecomplete) +- [requestUpdate()](/docs/components/lifecycle/#requestUpdate) + +### Adding listeners to host element or globally + +A common pattern is to add event listeners to the host element or globally in the `connectedCallback` and remove them in the `disconnectedCallback`. + +{% playground-ide "articles/lit-cheat-sheet/host-global-listeners", true %} + +**Related Documentation & Topics:** + +- [Lifecycle](/docs/components/lifecycle/) +- [Events](/docs/components/events/) +- [Authoring components for Lit SSR](/docs/ssr/authoring/) +- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external) + +## Data Flow and State Management + +### Pass data down + +The simplest way to pass data down is to use properties and attributes. + +For example, you can pass data down to child components using property bindings like this: + +`.name=${'Steven'}` + +For boolean properties, use a question mark instead of a period, like this: + +`?programmer=${true}`. + +You generally want to expose you component's external attribute and property API with @property() instead of @state()static properties = {propName: {state: false}}. + +{% playground-ide "articles/lit-cheat-sheet/pass-data-down", true %} + +**Related Documentation & Topics:** + +- [Expressions](/docs/templates/expressions/) +- [Expressions – Attribute Expressions](/docs/templates/expressions/#attribute-expressions) +- [Expressions – Property Expressions](/docs/templates/expressions/#property-expressions) +- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video) + +### Dispatch Events Up + +To send data up the tree to ancestors, you can dispatch custom events. Yo emit an event, use `Element.dispatchEvent()`. + +`dispatchEvent()` takes an event object as the first argument. Construct a custom event object like this: + +`new CustomEvent('event-name', {detail: data, bubbles: true, composed: true})` + +Provide data you wat to pass to ancestors in the `detail` property of the event, and ancestors can react to the event by adding an event listener to the component like this: + +`@event-name=${this.eventHandler}` + +If you want an event to bubble through shadow Roots, set `composed: true`. + +{% playground-ide "articles/lit-cheat-sheet/data-up", true %} + +**Related Documentation & Topics:** + +- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video) +- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external) +- [Expressions](/docs/templates/expressions/) +- [Expressions – Event listener expressions](/docs/templates/expressions/#event-listener-expressions) +- [Events](/docs/components/events/) +- [Customizing event listener options](/docs/components/events/#event-options-decorator) diff --git a/packages/lit-dev-content/site/css/docs.css b/packages/lit-dev-content/site/css/docs.css index e72209812..dc94c35bc 100644 --- a/packages/lit-dev-content/site/css/docs.css +++ b/packages/lit-dev-content/site/css/docs.css @@ -137,11 +137,11 @@ litdev-example[filename] { } body[code-language-preference="ts"] litdev-example { - --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-ts) * 22px + 22px); + --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-ts) * 22.4px + 22px); } body[code-language-preference="js"] litdev-example { - --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-js) * 22px + 22px); + --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-js) * 22.4px + 22px); } litdev-example { diff --git a/packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..432687ec13cd1f33401027cba900a4e1f89a9497 GIT binary patch literal 12249 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 literal 0 HcmV?d00001 diff --git a/packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e8d88d89d8cb1a532a77b69f745744de23d4951a GIT binary patch literal 21484 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 Z`#@o{LzZ3L2v82CeMVp7sk+7W{{d(&h;IM@ literal 0 HcmV?d00001 diff --git a/packages/lit-dev-content/src/components/litdev-example.ts b/packages/lit-dev-content/src/components/litdev-example.ts index 705d58556..6dbc2d222 100644 --- a/packages/lit-dev-content/src/components/litdev-example.ts +++ b/packages/lit-dev-content/src/components/litdev-example.ts @@ -5,7 +5,7 @@ */ import {LitElement, html, css, nothing} from 'lit'; -import {property} from 'lit/decorators.js'; +import {property, state} from 'lit/decorators.js'; import {styleMap} from 'lit/directives/style-map.js'; import {ifDefined} from 'lit/directives/if-defined.js'; import { @@ -110,6 +110,23 @@ export class LitDevExample extends LitElement { @property() project?: string; + /** + * Whether to load the project on intersection with the viewport. + */ + @property({type: Boolean}) + lazy = false; + + /** + * Whether or not to load the project. Setting Lazy to true will re-initialize + * this value to `false`. We would want to do this so that we do not load the + * large TS worker file until the user has scrolled to the example. This is + * something we would want to do if we want to have the server cache the + * worker. Otherwise every example on the page would load the worker at the + * same time and none of them would be cached. + */ + @state() + private loadProject = true; + /** * Name of file in project to display. * If no file is provided, we show the tab-bar with all project files. @@ -143,6 +160,12 @@ export class LitDevExample extends LitElement { this.requestUpdate(); }; + willUpdate() { + if (!this.hasUpdated) { + this.loadProject = !this.lazy; + } + } + render() { if (!this.project) { return nothing; @@ -162,17 +185,23 @@ export class LitDevExample extends LitElement { mode === 'ts' ? this.filename : this.filename?.replace(/.ts$/, '.js'); return html` - - + ${this.loadProject + ? // We need to conditionally render this because playground-project + // will load its serviceworker on firstUpdated + html` + ` + : nothing}
${showTabBar ? html`` : nothing} @@ -184,15 +213,35 @@ export class LitDevExample extends LitElement { - + `; } + + firstUpdated() { + if (this.lazy) { + const io = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + this.loadProject = true; + io.disconnect(); + } + }, + { + rootMargin: '40px', + } + ); + + io.observe(this); + } + } } customElements.define('litdev-example', LitDevExample); diff --git a/packages/lit-dev-server/src/server.ts b/packages/lit-dev-server/src/server.ts index c3b71cf67..23da80b89 100644 --- a/packages/lit-dev-server/src/server.ts +++ b/packages/lit-dev-server/src/server.ts @@ -92,6 +92,16 @@ app.use( if (path.includes('/fonts/')) { res.setHeader('Cache-Control', 'max-age=31536000'); } + if (path.includes('/playground-typescript-worker.js')) { + // This is a huge file, so we want to cache the request for 2 minutes + // which should basically handle a page with multiple playgrounds. + // Then after those two minutes, it will use the same cached file for a + // day while it revalidates the cache in the background. + res.setHeader( + 'Cache-Control', + 'max-age=120, stale-while-revalidate=86400' + ); + } }, }) ); diff --git a/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts b/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts index e9fa8c5c9..5fd291e3d 100644 --- a/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts +++ b/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts @@ -163,31 +163,35 @@ export const playgroundPlugin = ( (code: string, lang: 'js' | 'ts' | 'html' | 'css') => render(code, lang) ); - eleventyConfig.addShortcode('playground-ide', async (project: string) => { - if (!project) { - throw new Error( - `Invalid playground-ide invocation.` + - `Usage {% playground-ide "path/to/project" %}` + eleventyConfig.addShortcode( + 'playground-ide', + async (project: string, lazy = false) => { + if (!project) { + throw new Error( + `Invalid playground-ide invocation.` + + `Usage {% playground-ide "path/to/project" %}` + ); + } + project = trimTrailingSlash(project); + const config = await readProjectConfig(project); + const firstFilename = Object.keys(config.files ?? {})[0]; + const numVisibleLines = await getNumVisibleLinesForProjectFile( + project, + firstFilename ); - } - project = trimTrailingSlash(project); - const config = await readProjectConfig(project); - const firstFilename = Object.keys(config.files ?? {})[0]; - const numVisibleLines = await getNumVisibleLinesForProjectFile( - project, - firstFilename - ); - const previewHeight = config.previewHeight ?? '120px'; - return ` + const previewHeight = config.previewHeight ?? '120px'; + return ` `.trim(); - }); + } + ); type LitProjectConfig = ProjectManifest & { previewHeight?: string;