Click either inside or outside this component with a box border
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/my-element.ts
new file mode 100644
index 000000000..5f575d42e
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/my-element.ts
@@ -0,0 +1,48 @@
+import { html, LitElement, isServer, css } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() clickedOutside = false;
+
+ render() {
+ return html`
+ ${!this.clickedOutside ? html`
Something was clicked INSIDE this component
` : ''}
+ ${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);
+ }
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+
+ if (!isServer) {
+ // clean up to prevent memory leaks
+ document.removeEventListener('click', this.onDocumentClick);
+ }
+ }
+
+ // Should be an arrow function and not a class method to ensure `this` is
+ // bound correctly.
+ private onDocumentClick = (e: MouseEvent) => {
+ const path = e.composedPath();
+ this.clickedOutside = !path.includes(this);
+ };
+
+ static styles = css`/* playground-fold */
+
+ :host {
+ display: inline-flex;
+ border: 1px solid black;
+ }
+ /* playground-fold-end */`;
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/docs/components/events/host/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/project.json
similarity index 71%
rename from packages/lit-dev-content/samples/docs/components/events/host/project.json
rename to packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/project.json
index b4ce9dfb5..875a71aaf 100644
--- a/packages/lit-dev-content/samples/docs/components/events/host/project.json
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/global-listeners/project.json
@@ -1,5 +1,5 @@
{
- "extends": "/samples/base.json",
+ "extends": "/samples/v3-base.json",
"files": {
"my-element.ts": {},
"index.html": {}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-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-listeners/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/my-element.ts
new file mode 100644
index 000000000..e961fc484
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/my-element.ts
@@ -0,0 +1,33 @@
+import { html, LitElement, isServer } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() focusedWithin = false;
+
+ render() {
+ return html`
+
+ ${this.focusedWithin ? html`
Something in this component was focused
` : ''}
+ `;
+ }
+
+ constructor() {
+ super();
+
+ // Only want to do this in the browser since the server doesn't have the
+ // concept of events or document.
+ if (!isServer) {
+ this.addEventListener('focusin', this.#onFocusin);
+ this.addEventListener('focusout', this.#onFocusout);
+ }
+ }
+
+ #onFocusin() {
+ this.focusedWithin = true;
+ }
+
+ #onFocusout() {
+ this.focusedWithin = false;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-listeners/project.json
new file mode 100644
index 000000000..875a71aaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-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-attributes/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/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-attributes/my-element.js b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/my-element.js
new file mode 100644
index 000000000..952c50945
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/my-element.js
@@ -0,0 +1,16 @@
+import { html, LitElement } from 'lit';
+import styles from './styles.css' with { type: 'css' };
+
+export class MyElement extends LitElement {
+ static styles = styles;
+
+ render() {
+ return html`
+
+ This should be red!
+
+ `;
+ }
+}
+
+customElements.define('my-element', MyElement);
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json
new file mode 100644
index 000000000..f9ed532bd
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.js": {},
+ "styles.css": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css
new file mode 100644
index 000000000..538fa56f4
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css
@@ -0,0 +1,3 @@
+div {
+ color: red;
+}
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..8e2671b95
--- /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/reactive-properties/id-card.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/id-card.ts
new file mode 100644
index 000000000..0feccc9d3
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/id-card.ts
@@ -0,0 +1,30 @@
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+@customElement('id-card')
+export class IdCard extends LitElement {
+ // Default attribute converter is string
+ @property() name = '';
+ // Number attribute converter converts attribtues to numbers
+ @property({ type: Number }) age = 0;
+ // Boolean attribute converter converts attribtues to boolean using
+ // .hasAttribute(). NOTE: boolean-attribute="false" will result in `true`
+ @property({ type: Boolean }) programmer = false;
+ // You can also specify the attribute name
+ @property({ type: Boolean, attribute: 'is-cool' }) isCool = 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/reactive-properties/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/index.html
new file mode 100644
index 000000000..d5f0f24d5
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts
new file mode 100644
index 000000000..b88d3de0e
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts
@@ -0,0 +1,19 @@
+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/reactive-properties/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/project.json
new file mode 100644
index 000000000..b768f2dcd
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "id-card.ts": {},
+ "my-wallet.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "400px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts
new file mode 100644
index 000000000..d73ac0ef2
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts
@@ -0,0 +1,66 @@
+import { html, LitElement } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ // Duration affects render, so it should be reactive. Though we don't want it
+ // to be exposed to consumers of my-element because we only want to expose
+ // `start()`, `pause()`, `reset()`, so we use a private state.
+ @state() private _duration = 0;
+ // isPlaying affects render, so it should be reactive. Though we don't want it
+ // to be exposed to consumers of my-element, so we use a private state.
+ @state() private _isPlaying = false;
+ private lastTick = 0;
+
+ render() {
+ const min = Math.floor(this._duration / 60000);
+ const sec = pad(min, Math.floor(this._duration / 1000 % 60));
+ const hun = pad(true, Math.floor(this._duration % 1000 / 10));
+
+ return html`
+