Packages
lustre
5.3.2
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
priv/static/lustre-server-component.mjs
// build/dev/javascript/gleam_stdlib/dict.mjs
var SHIFT = 5;
var BUCKET_SIZE = Math.pow(2, SHIFT);
var MASK = BUCKET_SIZE - 1;
var MAX_INDEX_NODE = BUCKET_SIZE / 2;
var MIN_ARRAY_NODE = BUCKET_SIZE / 4;
// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs
var unicode_whitespaces = [
" ",
// Space
" ",
// Horizontal tab
"\n",
// Line feed
"\v",
// Vertical tab
"\f",
// Form feed
"\r",
// Carriage return
"\x85",
// Next line
"\u2028",
// Line separator
"\u2029"
// Paragraph separator
].join("");
var trim_start_regex = /* @__PURE__ */ new RegExp(
`^[${unicode_whitespaces}]*`
);
var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`);
// build/dev/javascript/lustre/lustre/internals/constants.ffi.mjs
var document = () => globalThis?.document;
var NAMESPACE_HTML = "http://www.w3.org/1999/xhtml";
var SUPPORTS_MOVE_BEFORE = !!globalThis.HTMLElement?.prototype?.moveBefore;
// build/dev/javascript/lustre/lustre/vdom/vattr.mjs
var attribute_kind = 0;
var property_kind = 1;
var event_kind = 2;
var never_kind = 0;
var always_kind = 2;
// build/dev/javascript/lustre/lustre/vdom/vnode.mjs
var fragment_kind = 0;
var element_kind = 1;
var text_kind = 2;
var unsafe_inner_html_kind = 3;
// build/dev/javascript/lustre/lustre/vdom/path.mjs
var separator_element = " ";
// build/dev/javascript/lustre/lustre/vdom/patch.mjs
var replace_text_kind = 0;
var replace_inner_html_kind = 1;
var update_kind = 2;
var move_kind = 3;
var remove_kind = 4;
var replace_kind = 5;
var insert_kind = 6;
// build/dev/javascript/lustre/lustre/vdom/reconciler.ffi.mjs
var setTimeout = globalThis.setTimeout;
var clearTimeout = globalThis.clearTimeout;
var createElementNS = (ns, name) => document().createElementNS(ns, name);
var createTextNode = (data) => document().createTextNode(data);
var createDocumentFragment = () => document().createDocumentFragment();
var insertBefore = (parent, node, reference) => parent.insertBefore(node, reference);
var moveBefore = SUPPORTS_MOVE_BEFORE ? (parent, node, reference) => parent.moveBefore(node, reference) : insertBefore;
var removeChild = (parent, child) => parent.removeChild(child);
var getAttribute = (node, name) => node.getAttribute(name);
var setAttribute = (node, name, value) => node.setAttribute(name, value);
var removeAttribute = (node, name) => node.removeAttribute(name);
var addEventListener = (node, name, handler, options) => node.addEventListener(name, handler, options);
var removeEventListener = (node, name, handler) => node.removeEventListener(name, handler);
var setInnerHtml = (node, innerHtml) => node.innerHTML = innerHtml;
var setData = (node, data) => node.data = data;
var meta = Symbol("lustre");
var MetadataNode = class {
constructor(kind, parent, node, key) {
this.kind = kind;
this.key = key;
this.parent = parent;
this.children = [];
this.node = node;
this.handlers = /* @__PURE__ */ new Map();
this.throttles = /* @__PURE__ */ new Map();
this.debouncers = /* @__PURE__ */ new Map();
}
get parentNode() {
return this.kind === fragment_kind ? this.node.parentNode : this.node;
}
};
var insertMetadataChild = (kind, parent, node, index2, key) => {
const child = new MetadataNode(kind, parent, node, key);
node[meta] = child;
parent?.children.splice(index2, 0, child);
return child;
};
var getPath = (node) => {
let path = "";
for (let current = node[meta]; current.parent; current = current.parent) {
if (current.key) {
path = `${separator_element}${current.key}${path}`;
} else {
const index2 = current.parent.children.indexOf(current);
path = `${separator_element}${index2}${path}`;
}
}
return path.slice(1);
};
var Reconciler = class {
#root = null;
#dispatch = () => {
};
#useServerEvents = false;
#exposeKeys = false;
constructor(root2, dispatch, { useServerEvents = false, exposeKeys = false } = {}) {
this.#root = root2;
this.#dispatch = dispatch;
this.#useServerEvents = useServerEvents;
this.#exposeKeys = exposeKeys;
}
mount(vdom) {
console.log("mount", vdom);
insertMetadataChild(element_kind, null, this.#root, 0, null);
this.#insertChild(this.#root, null, this.#root[meta], 0, vdom);
}
push(patch) {
this.#stack.push({ node: this.#root[meta], patch });
this.#reconcile();
}
// PATCHING ------------------------------------------------------------------
#stack = [];
#reconcile() {
const stack = this.#stack;
while (stack.length) {
const { node, patch } = stack.pop();
const { children: childNodes } = node;
const { changes, removed, children: childPatches } = patch;
iterate(changes, (change) => this.#patch(node, change));
if (removed) {
this.#removeChildren(node, childNodes.length - removed, removed);
}
iterate(childPatches, (childPatch) => {
const child = childNodes[childPatch.index | 0];
this.#stack.push({ node: child, patch: childPatch });
});
}
}
#patch(node, change) {
switch (change.kind) {
case replace_text_kind:
this.#replaceText(node, change);
break;
case replace_inner_html_kind:
this.#replaceInnerHtml(node, change);
break;
case update_kind:
this.#update(node, change);
break;
case move_kind:
this.#move(node, change);
break;
case remove_kind:
this.#remove(node, change);
break;
case replace_kind:
this.#replace(node, change);
break;
case insert_kind:
this.#insert(node, change);
break;
}
}
// CHANGES -------------------------------------------------------------------
#insert(parent, { children, before }) {
const fragment3 = createDocumentFragment();
const beforeEl = this.#getReference(parent, before);
this.#insertChildren(fragment3, null, parent, before | 0, children);
insertBefore(parent.parentNode, fragment3, beforeEl);
}
#replace(parent, { index: index2, with: child }) {
this.#removeChildren(parent, index2 | 0, 1);
const beforeEl = this.#getReference(parent, index2);
this.#insertChild(parent.parentNode, beforeEl, parent, index2 | 0, child);
}
#getReference(node, index2) {
index2 = index2 | 0;
const { children } = node;
const childCount = children.length;
if (index2 < childCount) {
return children[index2].node;
}
let lastChild = children[childCount - 1];
if (!lastChild && node.kind !== fragment_kind)
return null;
if (!lastChild)
lastChild = node;
while (lastChild.kind === fragment_kind && lastChild.children.length) {
lastChild = lastChild.children[lastChild.children.length - 1];
}
return lastChild.node.nextSibling;
}
#move(parent, { key, before }) {
before = before | 0;
const { children, parentNode } = parent;
const beforeEl = children[before].node;
let prev = children[before];
for (let i = before + 1; i < children.length; ++i) {
const next = children[i];
children[i] = prev;
prev = next;
if (next.key === key) {
children[before] = next;
break;
}
}
const { kind, node, children: prevChildren } = prev;
moveBefore(parentNode, node, beforeEl);
if (kind === fragment_kind) {
this.#moveChildren(parentNode, prevChildren, beforeEl);
}
}
#moveChildren(domParent, children, beforeEl) {
for (let i = 0; i < children.length; ++i) {
const { kind, node, children: nestedChildren } = children[i];
moveBefore(domParent, node, beforeEl);
if (kind === fragment_kind) {
this.#moveChildren(domParent, nestedChildren, beforeEl);
}
}
}
#remove(parent, { index: index2 }) {
this.#removeChildren(parent, index2, 1);
}
#removeChildren(parent, index2, count) {
const { children, parentNode } = parent;
const deleted = children.splice(index2, count);
for (let i = 0; i < deleted.length; ++i) {
const { kind, node, children: nestedChildren } = deleted[i];
removeChild(parentNode, node);
this.#removeDebouncers(deleted[i]);
if (kind === fragment_kind) {
deleted.push(...nestedChildren);
}
}
}
#removeDebouncers(node) {
const { debouncers, children } = node;
for (const { timeout } of debouncers.values()) {
if (timeout) {
clearTimeout(timeout);
}
}
debouncers.clear();
iterate(children, (child) => this.#removeDebouncers(child));
}
#update({ node, handlers, throttles, debouncers }, { added, removed }) {
iterate(removed, ({ name }) => {
if (handlers.delete(name)) {
removeEventListener(node, name, handleEvent);
this.#updateDebounceThrottle(throttles, name, 0);
this.#updateDebounceThrottle(debouncers, name, 0);
} else {
removeAttribute(node, name);
SYNCED_ATTRIBUTES[name]?.removed?.(node, name);
}
});
iterate(added, (attribute3) => this.#createAttribute(node, attribute3));
}
#replaceText({ node }, { content }) {
setData(node, content ?? "");
}
#replaceInnerHtml({ node }, { inner_html }) {
setInnerHtml(node, inner_html ?? "");
}
// INSERT --------------------------------------------------------------------
#insertChildren(domParent, beforeEl, metaParent, index2, children) {
iterate(
children,
(child) => this.#insertChild(domParent, beforeEl, metaParent, index2++, child)
);
}
#insertChild(domParent, beforeEl, metaParent, index2, vnode) {
switch (vnode.kind) {
case element_kind: {
const node = this.#createElement(metaParent, index2, vnode);
this.#insertChildren(node, null, node[meta], 0, vnode.children);
insertBefore(domParent, node, beforeEl);
break;
}
case text_kind: {
const node = this.#createTextNode(metaParent, index2, vnode);
insertBefore(domParent, node, beforeEl);
break;
}
case fragment_kind: {
const head = this.#createTextNode(metaParent, index2, vnode);
insertBefore(domParent, head, beforeEl);
this.#insertChildren(
domParent,
beforeEl,
head[meta],
0,
vnode.children
);
break;
}
case unsafe_inner_html_kind: {
const node = this.#createElement(metaParent, index2, vnode);
this.#replaceInnerHtml({ node }, vnode);
insertBefore(domParent, node, beforeEl);
break;
}
}
}
#createElement(parent, index2, { kind, key, tag, namespace, attributes }) {
const node = createElementNS(namespace || NAMESPACE_HTML, tag);
insertMetadataChild(kind, parent, node, index2, key);
if (this.#exposeKeys && key) {
setAttribute(node, "data-lustre-key", key);
}
iterate(attributes, (attribute3) => this.#createAttribute(node, attribute3));
return node;
}
#createTextNode(parent, index2, { kind, key, content }) {
const node = createTextNode(content ?? "");
insertMetadataChild(kind, parent, node, index2, key);
return node;
}
#createAttribute(node, attribute3) {
const { debouncers, handlers, throttles } = node[meta];
const {
kind,
name,
value,
prevent_default: prevent,
debounce: debounceDelay,
throttle: throttleDelay
} = attribute3;
switch (kind) {
case attribute_kind: {
const valueOrDefault = value ?? "";
if (name === "virtual:defaultValue") {
node.defaultValue = valueOrDefault;
return;
}
if (valueOrDefault !== getAttribute(node, name)) {
setAttribute(node, name, valueOrDefault);
}
SYNCED_ATTRIBUTES[name]?.added?.(node, valueOrDefault);
break;
}
case property_kind:
node[name] = value;
break;
case event_kind: {
if (handlers.has(name)) {
removeEventListener(node, name, handleEvent);
}
const passive = prevent.kind === never_kind;
addEventListener(node, name, handleEvent, { passive });
this.#updateDebounceThrottle(throttles, name, throttleDelay);
this.#updateDebounceThrottle(debouncers, name, debounceDelay);
handlers.set(name, (event2) => this.#handleEvent(attribute3, event2));
break;
}
}
}
#updateDebounceThrottle(map3, name, delay) {
const debounceOrThrottle = map3.get(name);
if (delay > 0) {
if (debounceOrThrottle) {
debounceOrThrottle.delay = delay;
} else {
map3.set(name, { delay });
}
} else if (debounceOrThrottle) {
const { timeout } = debounceOrThrottle;
if (timeout) {
clearTimeout(timeout);
}
map3.delete(name);
}
}
#handleEvent(attribute3, event2) {
const { currentTarget, type } = event2;
const { debouncers, throttles } = currentTarget[meta];
const path = getPath(currentTarget);
const {
prevent_default: prevent,
stop_propagation: stop,
include,
immediate
} = attribute3;
if (prevent.kind === always_kind)
event2.preventDefault();
if (stop.kind === always_kind)
event2.stopPropagation();
if (type === "submit") {
event2.detail ??= {};
event2.detail.formData = [...new FormData(event2.target).entries()];
}
const data = this.#useServerEvents ? createServerEvent(event2, include ?? []) : event2;
const throttle = throttles.get(type);
if (throttle) {
const now = Date.now();
const last = throttle.last || 0;
if (now > last + throttle.delay) {
throttle.last = now;
throttle.lastEvent = event2;
this.#dispatch(data, path, type, immediate);
}
}
const debounce = debouncers.get(type);
if (debounce) {
clearTimeout(debounce.timeout);
debounce.timeout = setTimeout(() => {
if (event2 === throttles.get(type)?.lastEvent)
return;
this.#dispatch(data, path, type, immediate);
}, debounce.delay);
}
if (!throttle && !debounce) {
this.#dispatch(data, path, type, immediate);
}
}
};
var iterate = (list4, callback) => {
if (Array.isArray(list4)) {
for (let i = 0; i < list4.length; i++) {
callback(list4[i]);
}
} else if (list4) {
for (list4; list4.head; list4 = list4.tail) {
callback(list4.head);
}
}
};
var handleEvent = (event2) => {
const { currentTarget, type } = event2;
const handler = currentTarget[meta].handlers.get(type);
handler(event2);
};
var createServerEvent = (event2, include = []) => {
const data = {};
if (event2.type === "input" || event2.type === "change") {
include.push("target.value");
}
if (event2.type === "submit") {
include.push("detail.formData");
}
for (const property2 of include) {
const path = property2.split(".");
for (let i = 0, input = event2, output = data; i < path.length; i++) {
if (i === path.length - 1) {
output[path[i]] = input[path[i]];
break;
}
output = output[path[i]] ??= {};
input = input[path[i]];
}
}
return data;
};
var syncedBooleanAttribute = /* @__NO_SIDE_EFFECTS__ */ (name) => {
return {
added(node) {
node[name] = true;
},
removed(node) {
node[name] = false;
}
};
};
var syncedAttribute = /* @__NO_SIDE_EFFECTS__ */ (name) => {
return {
added(node, value) {
node[name] = value;
}
};
};
var SYNCED_ATTRIBUTES = {
checked: /* @__PURE__ */ syncedBooleanAttribute("checked"),
selected: /* @__PURE__ */ syncedBooleanAttribute("selected"),
value: /* @__PURE__ */ syncedAttribute("value"),
autofocus: {
added(node) {
queueMicrotask(() => {
node.focus?.();
});
}
},
autoplay: {
added(node) {
try {
node.play?.();
} catch (e) {
console.error(e);
}
}
}
};
// build/dev/javascript/lustre/lustre/runtime/client/runtime.ffi.mjs
var copiedStyleSheets = /* @__PURE__ */ new WeakMap();
async function adoptStylesheets(shadowRoot) {
const pendingParentStylesheets = [];
for (const node of document().querySelectorAll(
"link[rel=stylesheet], style"
)) {
if (node.sheet)
continue;
pendingParentStylesheets.push(
new Promise((resolve, reject) => {
node.addEventListener("load", resolve);
node.addEventListener("error", reject);
})
);
}
await Promise.allSettled(pendingParentStylesheets);
if (!shadowRoot.host.isConnected) {
return [];
}
shadowRoot.adoptedStyleSheets = shadowRoot.host.getRootNode().adoptedStyleSheets;
const pending = [];
for (const sheet of document().styleSheets) {
try {
shadowRoot.adoptedStyleSheets.push(sheet);
} catch {
try {
let copiedSheet = copiedStyleSheets.get(sheet);
if (!copiedSheet) {
copiedSheet = new CSSStyleSheet();
for (const rule of sheet.cssRules) {
copiedSheet.insertRule(rule.cssText, copiedSheet.cssRules.length);
}
copiedStyleSheets.set(sheet, copiedSheet);
}
shadowRoot.adoptedStyleSheets.push(copiedSheet);
} catch {
const node = sheet.ownerNode.cloneNode();
shadowRoot.prepend(node);
pending.push(node);
}
}
}
return pending;
}
var ContextRequestEvent = class extends Event {
constructor(context, callback, subscribe) {
super("context-request", { bubbles: true, composed: true });
this.context = context;
this.callback = callback;
this.subscribe = subscribe;
}
};
// build/dev/javascript/lustre/lustre/runtime/transport.mjs
var mount_kind = 0;
var reconcile_kind = 1;
var emit_kind = 2;
var provide_kind = 3;
var attribute_changed_kind = 0;
var event_fired_kind = 1;
var property_changed_kind = 2;
var batch_kind = 3;
var context_provided_kind = 4;
// src/lustre/runtime/client/server_component.ffi.mjs
var ServerComponent = class extends HTMLElement {
static get observedAttributes() {
return ["route", "method"];
}
#shadowRoot;
#method = "ws";
#route = null;
#transport = null;
#adoptedStyleNodes = [];
#reconciler;
#remoteObservedAttributes = /* @__PURE__ */ new Set();
#remoteObservedProperties = /* @__PURE__ */ new Set();
#connected = false;
#changedAttributesQueue = [];
#contexts = /* @__PURE__ */ new Map();
#contextSubscriptions = /* @__PURE__ */ new Set();
#observer = new MutationObserver((mutations) => {
const attributes = [];
for (const mutation of mutations) {
if (mutation.type !== "attributes")
continue;
const name = mutation.attributeName;
if (!this.#connected || this.#remoteObservedAttributes.has(name)) {
attributes.push([name, this.getAttribute(name)]);
}
}
if (attributes.length === 1) {
const [name, value] = attributes[0];
this.#transport?.send({ kind: attribute_changed_kind, name, value });
} else if (attributes.length) {
this.#transport?.send({
kind: batch_kind,
messages: attributes.map(([name, value]) => ({
kind: attribute_changed_kind,
name,
value
}))
});
} else {
this.#changedAttributesQueue.push(...attributes);
}
});
constructor() {
super();
this.internals = this.attachInternals();
this.#observer.observe(this, {
attributes: true
});
}
connectedCallback() {
for (const attribute3 of this.attributes) {
this.#changedAttributesQueue.push([attribute3.name, attribute3.value]);
}
}
attributeChangedCallback(name, prev, next) {
switch (name) {
case (prev !== next && "route"): {
this.#route = new URL(next, location.href);
this.#connect();
return;
}
case "method": {
const normalised = next.toLowerCase();
if (normalised == this.#method)
return;
if (["ws", "sse", "polling"].includes(normalised)) {
this.#method = normalised;
if (this.#method == "ws") {
if (this.#route.protocol == "https:")
this.#route.protocol = "wss:";
if (this.#route.protocol == "http:")
this.#route.protocol = "ws:";
}
this.#connect();
}
return;
}
}
}
async messageReceivedCallback(data) {
switch (data.kind) {
case mount_kind: {
this.#shadowRoot ??= this.attachShadow({
mode: data.open_shadow_root ? "open" : "closed"
});
while (this.#shadowRoot.firstChild) {
this.#shadowRoot.firstChild.remove();
}
this.#reconciler = new Reconciler(
this.#shadowRoot,
(event2, path, name) => {
this.#transport?.send({
kind: event_fired_kind,
path,
name,
event: event2
});
},
{
useServerEvents: true
}
);
this.#remoteObservedAttributes = new Set(data.observed_attributes);
const filteredQueuedAttributes = this.#changedAttributesQueue.filter(
([name]) => this.#remoteObservedAttributes.has(name)
);
const messages = filteredQueuedAttributes.map(([name, value]) => ({
kind: attribute_changed_kind,
name,
value
}));
this.#changedAttributesQueue = [];
this.#remoteObservedProperties = new Set(data.observed_properties);
for (const name of this.#remoteObservedProperties) {
Object.defineProperty(this, name, {
get() {
return this[`_${name}`];
},
set(value) {
this[`_${name}`] = value;
this.#transport?.send({
kind: property_changed_kind,
name,
value
});
}
});
}
for (const [key, value] of Object.entries(data.provided_contexts)) {
this.provide(key, value);
}
for (const key of [...new Set(data.requested_contexts)]) {
this.dispatchEvent(
new ContextRequestEvent(key, (value, unsubscribe) => {
this.#transport?.send({
kind: context_provided_kind,
key,
value
});
this.#contextSubscriptions.add(unsubscribe);
})
);
}
if (messages.length) {
this.#transport.send({
kind: batch_kind,
messages
});
}
if (data.will_adopt_styles) {
await this.#adoptStyleSheets();
}
this.#shadowRoot.addEventListener("context-request", (event2) => {
if (!event2.context || !event2.callback)
return;
if (!this.#contexts.has(event2.context))
return;
event2.stopImmediatePropagation();
const context = this.#contexts.get(event2.context);
if (event2.subscribe) {
const callbackRef = new WeakRef(event2.callback);
const unsubscribe = () => {
context.subscribers = context.subscribers.filter(
(subscriber) => subscriber !== callbackRef
);
};
context.subscribers.push([callbackRef, unsubscribe]);
event2.callback(context.value, unsubscribe);
} else {
event2.callback(context.value);
}
});
this.#reconciler.mount(data.vdom);
this.dispatchEvent(new CustomEvent("lustre:mount"));
break;
}
case reconcile_kind: {
this.#reconciler.push(data.patch);
break;
}
case emit_kind: {
this.dispatchEvent(new CustomEvent(data.name, { detail: data.data }));
break;
}
case provide_kind: {
this.provide(data.key, data.value);
break;
}
}
}
//
disconnectedCallback() {
for (const unsubscribe of this.#contextSubscriptions) {
unsubscribe();
}
this.#contextSubscriptions.clear();
}
// Context provider method
provide(key, value) {
if (!this.#contexts.has(key)) {
this.#contexts.set(key, { value, subscribers: [] });
} else {
const context = this.#contexts.get(key);
context.value = value;
for (let i = context.subscribers.length - 1; i >= 0; i--) {
const [subscriberRef, unsubscribe] = context.subscribers[i];
const subscriber = subscriberRef.deref();
if (!subscriber) {
context.subscribers.splice(i, 1);
continue;
}
subscriber(value, unsubscribe);
}
}
}
#connect() {
if (!this.#route || !this.#method)
return;
if (this.#transport)
this.#transport.close();
const onConnect = () => {
this.#connected = true;
this.dispatchEvent(new CustomEvent("lustre:connect"), {
detail: {
route: this.#route,
method: this.#method
}
});
};
const onMessage = (data) => {
this.messageReceivedCallback(data);
};
const onClose = () => {
this.#connected = false;
this.dispatchEvent(
new CustomEvent("lustre:close", {
detail: {
route: this.#route,
method: this.#method
}
})
);
};
const options = { onConnect, onMessage, onClose };
switch (this.#method) {
case "ws":
this.#transport = new WebsocketTransport(this.#route, options);
break;
case "sse":
this.#transport = new SseTransport(this.#route, options);
break;
case "polling":
this.#transport = new PollingTransport(this.#route, options);
break;
}
}
//
async #adoptStyleSheets() {
while (this.#adoptedStyleNodes.length) {
this.#adoptedStyleNodes.pop().remove();
this.#shadowRoot.firstChild.remove();
}
this.#adoptedStyleNodes = await adoptStylesheets(this.#shadowRoot);
}
};
var WebsocketTransport = class {
#url;
#socket;
#waitingForResponse = false;
#queue = [];
#onConnect;
#onMessage;
#onClose;
constructor(url, { onConnect, onMessage, onClose }) {
this.#url = url;
this.#socket = new WebSocket(this.#url);
this.#onConnect = onConnect;
this.#onMessage = onMessage;
this.#onClose = onClose;
this.#socket.onopen = () => {
this.#onConnect();
};
this.#socket.onmessage = ({ data }) => {
try {
this.#onMessage(JSON.parse(data));
} finally {
if (this.#queue.length) {
this.#socket.send(
JSON.stringify({
kind: batch_kind,
messages: this.#queue
})
);
} else {
this.#waitingForResponse = false;
}
this.#queue = [];
}
};
this.#socket.onclose = () => {
this.#onClose();
};
}
send(data) {
if (this.#waitingForResponse || this.#socket.readyState !== WebSocket.OPEN) {
this.#queue.push(data);
return;
} else {
this.#socket.send(JSON.stringify(data));
this.#waitingForResponse = true;
}
}
close() {
this.#socket.close();
}
};
var SseTransport = class {
#url;
#eventSource;
#onConnect;
#onMessage;
#onClose;
constructor(url, { onConnect, onMessage, onClose }) {
this.#url = url;
this.#eventSource = new EventSource(this.#url);
this.#onConnect = onConnect;
this.#onMessage = onMessage;
this.#onClose = onClose;
this.#eventSource.onopen = () => {
this.#onConnect();
};
this.#eventSource.onmessage = ({ data }) => {
try {
this.#onMessage(JSON.parse(data));
} catch {
}
};
}
send(data) {
}
close() {
this.#eventSource.close();
this.#onClose();
}
};
var PollingTransport = class {
#url;
#interval;
#timer;
#onConnect;
#onMessage;
#onClose;
constructor(url, { onConnect, onMessage, onClose, ...opts }) {
this.#url = url;
this.#onConnect = onConnect;
this.#onMessage = onMessage;
this.#onClose = onClose;
this.#interval = opts.interval ?? 5e3;
this.#fetch().finally(() => {
this.#onConnect();
this.#timer = setInterval(() => this.#fetch(), this.#interval);
});
}
async send(data) {
}
close() {
clearInterval(this.#timer);
this.#onClose();
}
#fetch() {
return fetch(this.#url).then((response) => response.json()).then(this.#onMessage).catch(console.error);
}
};
customElements.define("lustre-server-component", ServerComponent);
export {
ServerComponent
};