Composition
A reactive controller is an F# class that implements ReactiveController and adds itself to its host, as in Lit. Mixins don't carry over: F# can't build a class from a function, so shared behaviour goes in a base class or a controller.
Composing components, with child elements in a template, properties passed down and events sent up, works as in Lit: see Components, Events and the Component communication guide.
Reactive controllers
The F# controller implements all four callbacks, where TypeScript leaves out the ones it doesn't
need. as this lets the constructor add the controller to its host.
import { LitElement, html } from 'lit';
import type {
ReactiveController, ReactiveControllerHost,
} from 'lit';
import { customElement } from 'lit/decorators.js';
export class WidthController implements ReactiveController {
width = 0;
constructor(private host: ReactiveControllerHost) {
host.addController(this);
}
private onResize = () => {
this.width = window.innerWidth;
this.host.requestUpdate();
};
hostConnected() {
this.width = window.innerWidth;
this.host.requestUpdate();
window.addEventListener('resize', this.onResize);
}
hostDisconnected() {
window.removeEventListener('resize', this.onResize);
}
}
@customElement('width-label')
export class WidthLabel extends LitElement {
private size = new WidthController(this);
render() {
const wide = this.size.width >= 600;
const label = wide ? 'Wide' : 'Narrow';
return html`${label} window`;
}
}
open Fable.Core
open Browser
open Browser.Types
open Firelight
open type Firelight.Lit
type WidthController(host: ReactiveControllerHost) as this =
inherit ReactiveControllerBase()
let mutable width = 0.0
let mutable stop = ignore
do host.addController this
member _.Width = width
member private _.Listen() =
let onResize (_: Event) =
width <- window.innerWidth
host.requestUpdate ()
width <- window.innerWidth
host.requestUpdate ()
stop <- Ev.listen window "resize" onResize
override this.hostConnected() = this.Listen()
override _.hostDisconnected() = stop ()
[<AttachMembers>]
type WidthLabel() as this =
inherit LitElement()
let size = WidthController(this)
override _.render() =
let wide = size.Width >= 600.0
let label = if wide then "Wide" else "Narrow"
html $"{label} window"
defineElement<WidthLabel> "width-label"
ReactiveControllerBase stands for implements ReactiveController: override the callbacks you
need, and the others do nothing. The controller doesn't need [<AttachMembers>]: Lit calls only
the four callbacks, which the base class implements under their own names. The listener is added with Ev.listen when the
host connects, and its remover kept, as on Lifecycle.
Mixins
A mixin adds the same members to classes with different bases. F# has one base class per class, so a behaviour you'd write as one mixin becomes a base class. Several, combined freely, become controllers.
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
type Constructor<T = {}> = new (...args: any[]) => T;
const Highlightable = <T extends Constructor<LitElement>>(
Base: T,
) => {
class Highlighted extends Base {
static styles = css`
:host([highlighted]) { background: #fff3bf; }
`;
@property({ type: Boolean, reflect: true })
highlighted = false;
}
return Highlighted;
};
@customElement('tip-text')
export class TipText extends Highlightable(LitElement) {
render() {
return html`<slot></slot>`;
}
}
open Fable.Core
open Fable.Core.JsInterop
open Firelight
open type Firelight.Lit
[<AbstractClass; AttachMembers>]
type Highlightable() =
inherit LitElement()
static member styles =
css $$"""
:host([highlighted]) { background: #fff3bf; }
"""
static member properties =
PropertyDeclarations.create [
"highlighted",
PropertyDeclaration<bool>(
reflect = true)
]
member val highlighted = false with get, set
[<AttachMembers>]
type TipText() =
inherit Highlightable()
override _.render() = html $"<slot></slot>"
defineElement<TipText> "tip-text"
A subclass can declare static member properties of its own; Lit merges them with the base
class's, as it does in TypeScript.
No direct equivalent
- Mixins. No class expressions and no multiple inheritance. Use a base class for one behaviour, controllers for several.
- Lit's own mixins.
SignalWatcheris applied byLitSignals.defineElement; see Signals.
Firelight packages bind several of Lit's controllers: ContextProvider and ContextConsumer
(Context), Task as LitTask (Tasks), the resize,
intersection, mutation and performance controllers in
Firelight.Observers, and the animation controllers in
Firelight.Motion. The Controllers guide covers
writing your own.