Packages
lustre
4.3.0
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/client-component.ffi.mjs
import { Ok, Error, isEqual } from "./gleam.mjs";
import { Dispatch, Shutdown } from "./lustre/internals/runtime.mjs";
import {
ComponentAlreadyRegistered,
BadComponentName,
NotABrowser,
} from "./lustre.mjs";
import { LustreClientApplication, is_browser } from "./client-runtime.ffi.mjs";
export function register({ init, update, view, on_attribute_change }, name) {
if (!is_browser()) return new Error(new NotABrowser());
if (!name.includes("-")) return new Error(new BadComponentName(name));
if (window.customElements.get(name)) {
return new Error(new ComponentAlreadyRegistered(name));
}
const Component = makeComponent(init, update, view, on_attribute_change);
window.customElements.define(name, Component);
for (const el of document.querySelectorAll(name)) {
const replaced = new Component();
for (const attr of el.attributes) {
replaced.setAttribute(attr.name, attr.value);
}
el.replaceWith(replaced);
}
return new Ok(undefined);
}
function makeComponent(init, update, view, on_attribute_change) {
return class LustreClientComponent extends HTMLElement {
#root = document.createElement("div");
#application = null;
#shadow = null;
slotContent = [];
static get observedAttributes() {
return on_attribute_change[0]?.entries().map(([name, _]) => name) ?? [];
}
constructor() {
super();
this.#shadow = this.attachShadow({ mode: "closed" });
on_attribute_change[0]?.forEach((decoder, name) => {
Object.defineProperty(this, name, {
get() {
return this[`_${name}`] || this.getAttribute(name);
},
set(value) {
const prev = this[name];
const decoded = decoder(value);
if (decoded instanceof Ok && !isEqual(prev, value)) {
this.#application
? this.#application.send(new Dispatch(decoded[0]))
: window.requestAnimationFrame(() =>
this.#application.send(new Dispatch(decoded[0])),
);
}
this[`_${name}`] = value;
},
});
});
}
connectedCallback() {
for (const link of document.querySelectorAll("link")) {
if (link.rel === "stylesheet") {
this.#shadow.appendChild(link.cloneNode(true));
}
}
for (const style of document.querySelectorAll("style")) {
this.#shadow.appendChild(style.cloneNode(true));
}
this.#application = new LustreClientApplication(
init(),
update,
view,
this.#root,
true,
);
this.#shadow.append(this.#root);
}
attributeChangedCallback(key, _, next) {
this[key] = next;
}
disconnectedCallback() {
this.#application.send(new Shutdown());
}
get adoptedStyleSheets() {
return this.#shadow.adoptedStyleSheets;
}
set adoptedStyleSheets(value) {
this.#shadow.adoptedStyleSheets = value;
}
};
}