Skip to content

Instantly share code, notes, and snippets.

@theacodes
Created June 18, 2024 21:56
Show Gist options
  • Save theacodes/c156b0402605183e67557a875ee2b344 to your computer and use it in GitHub Desktop.
Save theacodes/c156b0402605183e67557a875ee2b344 to your computer and use it in GitHub Desktop.
Lit attachController directive
/*
* Copyright (c) 2024 Opulo, Inc
* Published under the Mozilla Public License
* Full text available at: https://www.mozilla.org/en-US/MPL/
*/
import { isFunction } from "@glimmer/common/types";
import {
AsyncDirective,
LitElement,
PartType,
directive,
noChange,
type ElementPart,
type PartInfo,
type ReactiveController,
} from "./aliases";
type ReactiveControllerFactory = (host: LitElement) => ReactiveController;
/**
* A directive that adds a ReactiveController to the given element.
*
* This is based on a pattern observed in some Lit directives, such as @lit-labs/animate, where the directive is
* also a controller. This is a generalization of that.
*
* @example
* ```ts
* html`<div ${attachController((host) => { new MyController(host) })}></div>
* ```
*
* Note that the argument can either be a factory function or a `ReactiveController` instance. If it's an instance, it
* will automatically be added to host. If not, it expects that the factory function or controller constructor will
* call `host.addController`.
*/
export const attachController = directive(
class ControllerDirective extends AsyncDirective {
part?: ElementPart;
host?: LitElement | null;
controller?: ReactiveController | null;
constructor(partInfo: PartInfo) {
super(partInfo);
if (partInfo.type != PartType.ELEMENT) {
throw new Error("The `attachController` directive must be used inside the element");
}
}
render(_controller: ReactiveController | ReactiveControllerFactory) {
return noChange;
}
override update(part: ElementPart, [controller]: Parameters<this["render"]>) {
if (this.part === undefined) {
this.part = part;
}
this._attach(controller);
this.render(controller);
}
_attach(controller: ReactiveController | ReactiveControllerFactory) {
if (this.part && !this.host) {
this.host = this.part.element as LitElement;
}
if (this.host && !this.controller) {
if (isFunction(controller)) {
this.controller = controller(this.host);
} else {
this.controller = controller;
this.host.addController(this.controller);
}
}
}
override disconnected() {
this.host = null;
this.controller = null;
}
override reconnected() {}
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment