Skip to content

Instantly share code, notes, and snippets.

@oscarmarina
Last active April 4, 2024 15:15
Show Gist options
  • Save oscarmarina/2d62708527350755089d47fce8d10551 to your computer and use it in GitHub Desktop.
Save oscarmarina/2d62708527350755089d47fce8d10551 to your computer and use it in GitHub Desktop.
Lit playground context-consume-provide
import { ReactiveController, ReactiveControllerHost } from 'lit';
import {
createContext,
ContextProvider,
ContextConsumer,
Context,
ContextType,
} from '@lit/context';
export class ContextMeta<
TMeta extends Context<unknown, unknown>,
HostElement extends ReactiveControllerHost & HTMLElement
> implements ReactiveController
{
private host: HostElement;
private context: TMeta;
private initialValue?: ContextType<TMeta>;
private callback?: (v: ContextType<TMeta>, dispose?: () => void) => void;
private _contextMetaProvider: ContextProvider<TMeta, HostElement>;
private _contextMetaConsumer!: ContextConsumer<TMeta, HostElement>;
constructor(
host: HostElement,
{
context,
initialValue,
callback,
}: {
context: string;
initialValue?: ContextType<TMeta>;
callback?: (v: ContextType<TMeta>, dispose?: () => void) => void;
}
) {
this.context = createContext<TMeta>(Symbol.for(context)) as TMeta;
this.initialValue = initialValue;
this.callback = callback;
this.host = host;
this._contextMetaProvider = new ContextProvider(this.host, {
context: this.context,
initialValue: this.initialValue,
});
this.host.addController?.(this);
}
get value() {
return this._contextMetaConsumer?.value;
}
setValue(v: ContextType<TMeta>, force = false) {
this._contextMetaProvider?.setValue?.(v, force);
}
async hostConnected() {
await this.host.updateComplete;
this._contextMetaConsumer = new ContextConsumer(this.host, {
context: this.context,
subscribe: true,
callback: this.callback,
});
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>context-consume-provide</title>
<style>
:root {
font-family: sans-serif;
}
</style>
</head>
<body>
<p>
Example inspired by:
<a href="https://react.dev/learn/passing-data-deeply-with-context" target="_blank"
>https://react.dev/learn/passing-data-deeply-with-context</a
>
</p>
<p>
Orginal example from lit.dev:
<a href="https://lit.dev/playground/#sample=examples/context-consume-provide" target="_blank"
>https://lit.dev/playground/#sample=examples/context-consume-provide</a
>
</p>
<my-app></my-app>
<script type="module" src="./my-app.js"></script>
</body>
</html>
import { html, LitElement } from 'lit';
import './my-section.js';
import './my-heading.js';
export type Level = { level: number; color: string };
export class MyApp extends LitElement {
render() {
// <my-section> serves as both context provider and consumer. It provides a
// level value that is 1 greater than what's provided to it. This allows
// nested <my-section> to provide a different value based on its depth.
// <my-heading> adjusts what heading tag to use and the color based on the
// level context.
return html`
<my-section id="root-1">
<my-heading>Heading level 1</my-heading>
<my-section id="1">
<my-heading>Heading level 2</my-heading>
</my-section>
<my-section id="2">
<my-heading>Heading level 2</my-heading>
<my-section id="2.1">
<my-heading>Heading level 3</my-heading>
</my-section>
<my-section id="2.2">
<my-heading>Heading level 3</my-heading>
<my-section id="2.3">
<my-heading>Heading level 4</my-heading>
</my-section>
</my-section>
</my-section>
<my-section id="3">
<my-heading>Heading level 2</my-heading>
<my-section id="3.1">
<my-heading>Heading level 3</my-heading>
<my-section id="3.2">
<my-heading>Heading level 4</my-heading>
</my-section>
<my-section id="3.3">
<my-heading>Heading level 4</my-heading>
</my-section>
</my-section>
</my-section>
</my-section>
<hr>
<my-section id="root-2">
<my-heading>Heading level 1</my-heading>
<my-section id="1">
<my-heading>Heading level 2</my-heading>
<my-section id="1.1">
<my-heading>Heading level 3</my-heading>
<my-section id="1.2">
<my-heading>Heading level 4</my-heading>
<my-section id="1.3">
<my-heading>Heading level 5</my-heading>
</my-section>
</my-section>
</my-section>
</my-section>
<my-section id="2">
<my-heading>Heading level 2</my-heading>
<my-section id="2.1">
<my-heading>Heading level 3</my-heading>
<my-section id="2.3">
<my-heading>Heading level 4</my-heading>
</my-section>
</my-section>
</my-section>
</my-section>
`;
}
};
customElements.define('my-app', MyApp);
import { LitElement } from 'lit';
import { html, literal, unsafeStatic } from 'lit/static-html.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ContextMeta } from './context-meta.js';
import { type Level } from './my-app.js';
export class MyHeading extends LitElement {
_level? = new ContextMeta(this, { context: 'level' });
get _tag() {
const level = (this._level?.value as Level)?.level;
if (typeof level === 'number' && level >= 0 && level <= 5) {
return unsafeStatic(`h${level}`);
} else {
return literal`p`;
}
}
render() {
return html`
<${this._tag} style=${styleMap({
color: (this._level?.value as Level)?.color,
})}>
<slot></slot>
</${this._tag}>`;
}
}
customElements.define('my-heading', MyHeading);
import { html, css, LitElement } from 'lit';
import { ContextMeta } from './context-meta.js';
import { type Level } from './my-app.js';
const COLORS = ['indianred', 'blue', 'orange', 'green', 'purple'];
export class MySection extends LitElement {
private _consumer = new ContextMeta(this, {
context: 'level',
initialValue: { level: 1, color: COLORS[0] },
callback: (v) => {
const { level } = v as Level;
this._consumer.setValue({
level: level + 1,
color: COLORS[(level + 1) % COLORS.length],
});
},
});
static styles = css`
:host {
display: block;
text-align: center;
}
:host([hidden]) {
display: none;
}
`;
render() {
return html`<section><slot></slot></section>`;
}
}
customElements.define('my-section', MySection);
{
"scripts": {
"start": "npx @web/dev-server --node-resolve --port 6174 --open"
},
"dependencies": {
"lit": "^3.0.0",
"@lit/context": "^1.1.0",
"@lit/reactive-element": "^2.0.0",
"lit-element": "^4.0.0",
"lit-html": "^3.0.0"
}
}
{
"files": {
"index.html": {
"position": 0
},
"my-app.ts": {
"position": 1
},
"my-heading.ts": {
"position": 2
},
"my-section.ts": {
"position": 3
},
"context-meta.ts": {
"position": 4
},
"package.json": {
"position": 5,
"hidden": true
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment