Skip to content

Instantly share code, notes, and snippets.

@WickyNilliams
Last active December 1, 2021 17:13
Show Gist options
  • Save WickyNilliams/79ee85ea370506ac6b16de1920f48e5e to your computer and use it in GitHub Desktop.
Save WickyNilliams/79ee85ea370506ac6b16de1920f48e5e to your computer and use it in GitHub Desktop.
reactive controllers in stencil
import { Component, h, Host, State, forceUpdate } from "@stencil/core";
import { Task } from "@lit-labs/task";
import { ReactiveController, ReactiveControllerHost } from "lit"; // using the interfaces directly from lit just for this example
class LightDismissController implements ReactiveController {
host: ReactiveControllerHost;
onClose: () => void;
constructor(host: ReactiveControllerHost, onClose: () => void) {
this.host = host;
host.addController(this)
this.onClose = onClose;
}
hostConnected() {
window.addEventListener("click", this.onClose);
window.addEventListener("keyup", this.handleKeyUp);
}
hostDisconnected() {
window.removeEventListener("click", this.onClose);
window.removeEventListener("keyup", this.handleKeyUp);
}
private handleKeyUp = (event: KeyboardEvent) => {
if (event.key === "Escape") {
this.onClose();
}
};
}
// taken directly from lit docs, no changes!
export class MouseController {
host: ReactiveControllerHost;
pos = { x: 0, y: 0 };
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
window.addEventListener("mousemove", this.onMouseMove);
}
hostDisconnected() {
window.removeEventListener("mousemove", this.onMouseMove);
}
private onMouseMove = ({ clientX, clientY }) => {
this.pos = { x: clientX, y: clientY };
this.host.requestUpdate();
};
}
class IntervalController implements ReactiveController {
host: ReactiveControllerHost;
timerId: ReturnType<typeof setTimeout>;
ms: number;
onTick: () => void;
constructor(host: ReactiveControllerHost, onTick: () => void, ms = 1000) {
this.host = host;
host.addController(this)
this.ms = ms;
this.onTick = onTick;
}
hostConnected() {
this.timerId = setInterval(this.onTick, this.ms);
}
hostDisconnected() {
clearInterval(this.timerId);
}
}
async function loadTodos([counter, fetchParams]) {
const params = new URLSearchParams(fetchParams);
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos?_limit=5&_page=${counter}?${params}`,
{ headers: { "Content-Type": "application/json" } }
);
return response.json();
}
@Component({
tag: 'controller-demo',
})
export class Demo implements ReactiveControllerHost {
// stencil would implement this as base functionality in components
controllers = new Set<ReactiveController>();
addController(controller: ReactiveController) {
this.controllers.add(controller);
}
removeController(controller: ReactiveController) {
this.controllers.delete(controller);
}
requestUpdate() {
forceUpdate(this);
}
connectedCallback() {
this.controllers.forEach((controller) => controller?.hostConnected());
}
disconnectedCallback() {
this.controllers.forEach((controller) => controller?.hostDisconnected());
}
componentWillUpdate() {
this.controllers.forEach((controller) => controller?.hostUpdate());
}
componentDidUpdate() {
this.controllers.forEach((controller) => controller?.hostUpdated());
}
updateComplete = Promise.resolve() // this is just a stub, it should be implemented properly eventually
// now the actual component code
mousemove = new MouseController(this);
interval = new IntervalController(this, () => this.increment());
dismiss = new LightDismissController(this, () => this.close());
fetch = new Task(this, loadTodos, () => [this.counter, this.fetchParams]);
@State() counter = 0;
@State() fetchParams = { foo: "bar" };
@State() open = false;
close = () => {
this.open = false;
}
increment = () => {
this.counter++;
}
render() {
return (
<Host class="p-6 bg-surface-high">
{this.fetch.render({
pending: () => <p>loading...</p>,
complete: (todos) => {
return (
<ul>
{todos.map((todo) => (
<li>
<input type="checkbox" checked={todo.completed} />
{todo.title}
</li>
))}
</ul>
);
},
})}
<div>Mouse Position: {JSON.stringify(this.mousemove.pos)}</div>
<button
onClick={() => (this.fetchParams = { foo: "bar" + this.counter })}
>
Fetch Todos (Page {this.counter})
</button>
<button onClick={this.close}>Close</button>
</Host>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment