Lit
Lit adapter — provideLayerClient, Reactive Controllers, StackOutlet, custom elements.
@stainless-code/lit-layers binds stacks via Reactive Controllers + requestUpdate. The core is re-exported.
Install
npm install @stainless-code/lit-layerspnpm add @stainless-code/lit-layersyarn add @stainless-code/lit-layersbun add @stainless-code/lit-layersPeers: lit (>=3.2.0), @lit/context (>=1.1.0)
Client
Call provideLayerClient(host, client?) on a host (creates a LayerClient when omitted), or mount <stack-provider .client=${client}> high in the tree. Descendants omit client, or read useLayerClient(host).current:
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { defineStackElements, provideLayerClient } from "@stainless-code/lit-layers";
defineStackElements();
@customElement("my-shell")
class MyShell extends LitElement {
constructor() {
super();
provideLayerClient(this);
}
render() {
return html`<slot></slot><stack-outlet stack="confirm"></stack-outlet>`;
}
}
Render a stack
<stack-provider> is a Lit-idiomatic shadow root + <slot> (context still reaches light children). <stack-outlet> and <stack-subscribe> render in light DOM so overlays stack inline where mounted.
<stack-outlet> (the StackOutlet CE from defineStackElements()) maps registered component entries — a LitElement constructor or (props) => TemplateResult (no tag strings in v1). Id-keyed repeat keeps instances stable. Headless: useStackHandles(this, stack, rootProps).
<stack-subscribe .selector .renderer> takes a (value) => TemplateResult callback.
Confirm dialog
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import {
layerOptions,
type LayerCallContext,
} from "@stainless-code/lit-layers";
type ConfirmPayload = { title: string };
type ConfirmResponse = boolean;
@customElement("confirm-dialog")
class ConfirmDialog extends LitElement {
// Light DOM (Lit: return `this` from createRenderRoot).
createRenderRoot() {
return this;
}
// Declared reactive — StackOutlet assigns these via LayerElementDirective.
@property({ attribute: false })
declare call: LayerCallContext<ConfirmPayload, ConfirmResponse>;
@property({ attribute: false })
declare payload: ConfirmPayload;
render() {
return html`<div role="dialog">
<h2>${this.payload.title}</h2>
<button type="button" @click=${() => void this.call.end(true)}>
Yes
</button>
<button type="button" @click=${() => void this.call.end(false)}>
No
</button>
</div>`;
}
}
export const confirm = layerOptions<ConfirmPayload, ConfirmResponse>({
stack: "confirm",
key: ["confirm", "remove"],
component: ConfirmDialog,
});
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import {
defineStackElements,
provideLayerClient,
useLayer,
} from "@stainless-code/lit-layers";
import { confirm } from "./confirm-dialog";
defineStackElements();
@customElement("remove-button")
class RemoveButton extends LitElement {
#confirm = useLayer(this, confirm);
render() {
return html`<button
type="button"
@click=${() => void this.#confirm.open({ title: "Remove item?" })}
>
Remove
</button>`;
}
}
@customElement("app-shell")
class AppShell extends LitElement {
constructor() {
super();
provideLayerClient(this);
}
render() {
return html`<remove-button></remove-button>
<stack-outlet stack="confirm"></stack-outlet>`;
}
}
Primitives
Options bag plus optional trailing LayerClient. Drive with useLayer(this, options, client?); observe with useLayerState(this, { key, … }).
| Export | Role |
|---|---|
useStack(host, { stack?, select?, compare? }, client?) |
StackController<T> — .current of the selected stack slice |
useQueuedStack(host, { ... }, client?) |
StackController<T> over the queued snapshot |
useLayer(host, options, client?) |
Drive — wired handle + reactive state / queued / top |
useLayerState(host, { key, stack?, select?, compare? }, client?) |
Observe — mounted same-key layers |
useLayerQueuedState(host, { key, ... }, client?) |
Observe — queued same-key layers |
Ergonomic APIs
| Export | Role |
|---|---|
useStackHandles(host, stack?, rootProps?, client?) |
{ states, getCall } for headless rendering |
StackSubscribe |
<stack-subscribe .selector .renderer> (renderer(value) → TemplateResult) |
useMutationFlow(host, call) |
pending: boolean; run(fn).orEnd(response) |
useLayerGroup(host, call, options?, client?) |
Child stack with outlet(): TemplateResult + stackId |
createStackHook(config?) |
StackProvider, useAppStack(host), AppHost CE, AppLayer controller |
defineStackElements() idempotently registers stack-provider, stack-outlet, stack-subscribe, and app-host; not auto-invoked on import.