lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
1,291 lines • 228 kB
JavaScript
(function() {
"use strict";
var _a, _b;
const _LV = globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {
currentEffect: null,
registry: /* @__PURE__ */ new Map(),
// Global name -> Signal/Proxy
localRegistries: /* @__PURE__ */ new WeakMap(),
// Object/Element -> Map(name -> Signal/Proxy)
futureSignals: /* @__PURE__ */ new Map(),
// name -> Set of (signal) => void
schemas: /* @__PURE__ */ new Map(),
// name -> Schema (Draft 7+ or Shorthand)
parents: /* @__PURE__ */ new WeakMap(),
// Proxy -> Parent (Proxy/Element)
helpers: /* @__PURE__ */ new Map(),
// name -> function (used for transforms and expressions)
hooks: {
validate: (value, schema) => true
// Hook for extensions (like JPRX) to provide full validation
}
});
const lookup$1 = (name, scope) => {
let current = scope;
while (current && typeof current === "object") {
const registry2 = _LV.localRegistries.get(current);
if (registry2 && registry2.has(name)) return registry2.get(name);
current = current.parentElement || _LV.parents.get(current);
}
return _LV.registry.get(name);
};
const signal$1 = (initialValue, optionsOrName) => {
const name = typeof optionsOrName === "string" ? optionsOrName : optionsOrName == null ? void 0 : optionsOrName.name;
const storage = optionsOrName == null ? void 0 : optionsOrName.storage;
const scope = optionsOrName == null ? void 0 : optionsOrName.scope;
if (name && storage) {
try {
const stored = storage.getItem(name);
if (stored !== null) initialValue = JSON.parse(stored);
} catch (e) {
}
}
let value = initialValue;
const subscribers = /* @__PURE__ */ new Set();
const f = (...args) => args.length === 0 ? f.value : f.value = args[0];
Object.defineProperty(f, "value", {
get() {
if (_LV.currentEffect) {
subscribers.add(_LV.currentEffect);
_LV.currentEffect.dependencies.add(subscribers);
}
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
if (name && storage) {
try {
storage.setItem(name, JSON.stringify(value));
} catch (e) {
}
}
[...subscribers].forEach((effect2) => effect2());
}
}
});
if (name) {
const registry2 = scope && typeof scope === "object" ? _LV.localRegistries.get(scope) || _LV.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : _LV.registry;
if (registry2 && registry2.has(name) && registry2.get(name) !== f) {
throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`);
}
if (registry2) registry2.set(name, f);
const futures = _LV.futureSignals.get(name);
if (futures) {
futures.forEach((resolve) => resolve(f));
}
}
return f;
};
const getSignal = (name, defaultValueOrOptions) => {
const options = typeof defaultValueOrOptions === "object" && defaultValueOrOptions !== null ? defaultValueOrOptions : { defaultValue: defaultValueOrOptions };
const { scope, defaultValue } = options;
const existing = lookup$1(name, scope);
if (existing) return existing;
if (defaultValue !== void 0) return signal$1(defaultValue, { name, scope });
const future = signal$1(void 0);
const handler = (realSignal) => {
const hasValue = realSignal && (typeof realSignal === "object" || typeof realSignal === "function") && "value" in realSignal;
if (hasValue) {
future.value = realSignal.value;
effect(() => {
future.value = realSignal.value;
});
} else {
future.value = realSignal;
}
};
if (!_LV.futureSignals.has(name)) _LV.futureSignals.set(name, /* @__PURE__ */ new Set());
_LV.futureSignals.get(name).add(handler);
return future;
};
signal$1.get = getSignal;
const effect = (fn) => {
const execute = () => {
if (!execute.active || execute.running) return;
execute.dependencies.forEach((dep) => dep.delete(execute));
execute.dependencies.clear();
execute.running = true;
_LV.currentEffect = execute;
try {
fn();
} finally {
_LV.currentEffect = null;
execute.running = false;
}
};
execute.active = true;
execute.running = false;
execute.dependencies = /* @__PURE__ */ new Set();
execute.stop = () => {
execute.dependencies.forEach((dep) => dep.delete(execute));
execute.dependencies.clear();
execute.active = false;
};
execute();
return execute;
};
const computed = (fn) => {
const sig = signal$1(void 0);
effect(() => {
sig.value = fn();
});
return sig;
};
const getRegistry$1 = () => _LV.registry;
const internals = _LV;
const stateCache = /* @__PURE__ */ new WeakMap();
const stateSignals = /* @__PURE__ */ new WeakMap();
const stateSchemas = /* @__PURE__ */ new WeakMap();
const { parents, schemas, hooks } = internals;
const validate = (target, prop, value, schema) => {
var _a2, _b2;
const current = target[prop];
const type = typeof current;
const isNew = !(prop in target);
let behavior = schema;
if (typeof schema === "object" && schema !== null) behavior = schema.type;
if (behavior === "auto" && isNew) throw new Error(`Lightview: Cannot add new property "${prop}" to fixed 'auto' state.`);
if (behavior === "polymorphic" || typeof behavior === "object" && (behavior == null ? void 0 : behavior.coerce)) {
if (type === "number") return Number(value);
if (type === "boolean") return Boolean(value);
if (type === "string") return String(value);
} else if (behavior === "auto" || behavior === "dynamic") {
if (!isNew && typeof value !== type) {
throw new Error(`Lightview: Type mismatch for "${prop}". Expected ${type}, got ${typeof value}.`);
}
}
if (typeof schema === "object" && schema !== null && schema.transform) {
const trans = schema.transform;
const transformFn = typeof trans === "function" ? trans : internals.helpers.get(trans) || ((_b2 = (_a2 = globalThis.Lightview) == null ? void 0 : _a2.helpers) == null ? void 0 : _b2[trans]);
if (transformFn) value = transformFn(value);
}
if (hooks.validate(value, schema) === false) {
throw new Error(`Lightview: Validation failed for "${prop}".`);
}
return value;
};
const protoMethods = (proto, test) => Object.getOwnPropertyNames(proto).filter((k) => typeof proto[k] === "function" && test(k));
const DATE_TRACKING = protoMethods(Date.prototype, (k) => /^(to|get|valueOf)/.test(k));
const DATE_MUTATING = protoMethods(Date.prototype, (k) => /^set/.test(k));
const ARRAY_TRACKING = [
"map",
"forEach",
"filter",
"find",
"findIndex",
"some",
"every",
"reduce",
"reduceRight",
"includes",
"indexOf",
"lastIndexOf",
"join",
"slice",
"concat",
"flat",
"flatMap",
"at",
"entries",
"keys",
"values"
];
const ARRAY_MUTATING = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "copyWithin"];
const ARRAY_ITERATION = ["map", "forEach", "filter", "find", "findIndex", "some", "every", "flatMap"];
const getOrSet = (map2, key, factory) => {
let v = map2.get(key);
if (!v) {
v = factory();
map2.set(key, v);
}
return v;
};
const proxyGet = (target, prop, receiver, signals) => {
if (prop === "__parent__") return parents.get(receiver);
if (!signals.has(prop)) {
signals.set(prop, signal$1(Reflect.get(target, prop, receiver)));
}
const signal2 = signals.get(prop);
const val = signal2.value;
if (typeof val === "object" && val !== null) {
const childProxy = state$1(val);
parents.set(childProxy, receiver);
return childProxy;
}
return val;
};
const proxySet = (target, prop, value, receiver, signals) => {
const schema = stateSchemas.get(receiver);
const validatedValue = schema ? validate(target, prop, value, schema) : value;
if (!signals.has(prop)) {
signals.set(prop, signal$1(Reflect.get(target, prop, receiver)));
}
const success = Reflect.set(target, prop, validatedValue, receiver);
const signal2 = signals.get(prop);
if (success && signal2) signal2.value = validatedValue;
return success;
};
const createSpecialProxy = (obj, monitor, trackingProps = []) => {
const signals = getOrSet(stateSignals, obj, () => /* @__PURE__ */ new Map());
if (!signals.has(monitor)) {
const initialValue = typeof obj[monitor] === "function" ? obj[monitor].call(obj) : obj[monitor];
signals.set(monitor, signal$1(initialValue));
}
const isDate = obj instanceof Date;
const isArray = Array.isArray(obj);
const trackingMethods = isDate ? DATE_TRACKING : isArray ? ARRAY_TRACKING : trackingProps;
const mutatingMethods = isDate ? DATE_MUTATING : isArray ? ARRAY_MUTATING : [];
return new Proxy(obj, {
get(target, prop, receiver) {
if (prop === "__parent__") return parents.get(receiver);
const value = target[prop];
if (typeof value === "function") {
const isTracking = trackingMethods.includes(prop);
const isMutating = mutatingMethods.includes(prop);
return function(...args) {
if (isTracking) {
const sig = signals.get(monitor);
if (sig) void sig.value;
}
const startValue = typeof target[monitor] === "function" ? target[monitor].call(target) : target[monitor];
if (isArray && ARRAY_ITERATION.includes(prop) && typeof args[0] === "function") {
const originalCallback = args[0];
args[0] = function(element2, index2, array) {
const wrappedElement = typeof element2 === "object" && element2 !== null ? state$1(element2) : element2;
if (wrappedElement && typeof wrappedElement === "object") {
parents.set(wrappedElement, receiver);
}
return originalCallback.call(this, wrappedElement, index2, array);
};
}
const result = value.apply(target, args);
const endValue = typeof target[monitor] === "function" ? target[monitor].call(target) : target[monitor];
if (startValue !== endValue || isMutating) {
const sig = signals.get(monitor);
if (sig && sig.value !== endValue) {
sig.value = endValue;
}
}
return result;
};
}
if (prop === monitor) {
const sig = signals.get(monitor);
return sig ? sig.value : Reflect.get(target, prop, receiver);
}
if (isArray && !isNaN(parseInt(prop))) {
const monitorSig = signals.get(monitor);
if (monitorSig) void monitorSig.value;
}
return proxyGet(target, prop, receiver, signals);
},
set(target, prop, value, receiver) {
if (prop === monitor) {
const success = Reflect.set(target, prop, value, receiver);
if (success) {
const sig = signals.get(monitor);
if (sig) sig.value = value;
}
return success;
}
return proxySet(target, prop, value, receiver, signals);
}
});
};
const state$1 = (obj, optionsOrName) => {
if (typeof obj !== "object" || obj === null) return obj;
const name = typeof optionsOrName === "string" ? optionsOrName : optionsOrName == null ? void 0 : optionsOrName.name;
const storage = optionsOrName == null ? void 0 : optionsOrName.storage;
const scope = optionsOrName == null ? void 0 : optionsOrName.scope;
const schema = optionsOrName == null ? void 0 : optionsOrName.schema;
if (name && storage) {
try {
const item = storage.getItem(name);
if (item) {
const loaded = JSON.parse(item);
Array.isArray(obj) && Array.isArray(loaded) ? (obj.length = 0, obj.push(...loaded)) : Object.assign(obj, loaded);
}
} catch (e) {
}
}
let proxy = stateCache.get(obj);
if (!proxy) {
const isArray = Array.isArray(obj), isDate = obj instanceof Date;
const isSpecial = isArray || isDate;
const monitor = isArray ? "length" : isDate ? "getTime" : null;
if (isSpecial || !(obj instanceof RegExp || obj instanceof Map || obj instanceof Set || obj instanceof WeakMap || obj instanceof WeakSet)) {
proxy = isSpecial ? createSpecialProxy(obj, monitor) : new Proxy(obj, {
get(t, p, r) {
if (p === "__parent__") return parents.get(r);
return proxyGet(t, p, r, getOrSet(stateSignals, t, () => /* @__PURE__ */ new Map()));
},
set(t, p, v, r) {
return proxySet(t, p, v, r, getOrSet(stateSignals, t, () => /* @__PURE__ */ new Map()));
}
});
stateCache.set(obj, proxy);
} else return obj;
}
if (schema) stateSchemas.set(proxy, schema);
if (name && storage) {
effect(() => {
try {
storage.setItem(name, JSON.stringify(proxy));
} catch (e) {
}
});
}
if (name) {
const registry2 = scope && typeof scope === "object" ? internals.localRegistries.get(scope) || internals.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : getRegistry$1();
if (registry2 && registry2.has(name) && registry2.get(name) !== proxy) {
throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`);
}
if (registry2) registry2.set(name, proxy);
const futures = internals.futureSignals.get(name);
if (futures) {
futures.forEach((resolve) => resolve(proxy));
}
}
return proxy;
};
const getState = (name, defaultValueOrOptions) => {
const options = typeof defaultValueOrOptions === "object" && defaultValueOrOptions !== null ? defaultValueOrOptions : { defaultValue: defaultValueOrOptions };
const { scope, defaultValue } = options;
const existing = lookup$1(name, scope);
if (existing) return existing;
if (defaultValue !== void 0) return state$1(defaultValue, { name, scope });
const future = signal$1(void 0);
const handler = (realState) => {
future.value = realState;
};
if (!internals.futureSignals.has(name)) internals.futureSignals.set(name, /* @__PURE__ */ new Set());
internals.futureSignals.get(name).add(handler);
return future;
};
state$1.get = getState;
const core = {
get currentEffect() {
return (globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {})).currentEffect;
}
};
const nodeState = /* @__PURE__ */ new WeakMap();
const nodeStateFactory = () => ({ effects: [], onmount: null, onunmount: null });
const registry = getRegistry$1();
const scrollMemory = /* @__PURE__ */ new Map();
const initScrollMemory = () => {
if (typeof document === "undefined") return;
document.addEventListener("scroll", (e) => {
const el = e.target;
if (el === document || el === document.documentElement) return;
const key = el.id || el.getAttribute && el.getAttribute("data-preserve-scroll");
if (key) {
scrollMemory.set(key, { top: el.scrollTop, left: el.scrollLeft });
}
}, true);
};
if (typeof document !== "undefined") {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initScrollMemory);
} else {
initScrollMemory();
}
}
const saveScrolls = () => new Map(scrollMemory);
const restoreScrolls = (map2, root = document) => {
if (!map2 || map2.size === 0) return;
requestAnimationFrame(() => {
map2.forEach((pos, key) => {
const node = document.getElementById(key) || document.querySelector(`[data-preserve-scroll="${key}"]`);
if (node) {
node.scrollTop = pos.top;
node.scrollLeft = pos.left;
}
});
});
};
const trackEffect = (node, effectFn) => {
const state2 = getOrSet(nodeState, node, nodeStateFactory);
if (!state2.effects) state2.effects = [];
state2.effects.push(effectFn);
};
const SHADOW_DOM_MARKER = Symbol("lightview.shadowDOM");
const createShadowDOMMarker = (attributes, children) => ({
[SHADOW_DOM_MARKER]: true,
mode: attributes.mode || "open",
styles: attributes.styles || [],
adoptedStyleSheets: attributes.adoptedStyleSheets || [],
children
});
const isShadowDOMMarker = (obj) => obj && typeof obj === "object" && obj[SHADOW_DOM_MARKER] === true;
const processShadowDOM = (marker, parentNode) => {
if (parentNode.shadowRoot) {
console.warn("Lightview: Element already has a shadowRoot, skipping shadowDOM directive");
return;
}
const shadowRoot = parentNode.attachShadow({ mode: marker.mode });
const sheets = [];
const linkUrls = [...marker.styles || []];
if (marker.adoptedStyleSheets && marker.adoptedStyleSheets.length > 0) {
marker.adoptedStyleSheets.forEach((item) => {
if (item instanceof CSSStyleSheet) {
sheets.push(item);
} else if (typeof item === "string") {
linkUrls.push(item);
}
});
}
if (sheets.length > 0) {
try {
shadowRoot.adoptedStyleSheets = sheets;
} catch (e) {
console.warn("Lightview: adoptedStyleSheets not supported");
}
}
for (const styleUrl of linkUrls) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = styleUrl;
shadowRoot.appendChild(link);
}
if (marker.children && marker.children.length > 0) {
setupChildrenInTarget(marker.children, shadowRoot);
}
};
let inSVG = false;
const domToElement = /* @__PURE__ */ new WeakMap();
const wrapDomElement = (domNode, tag, attributes = {}, children = []) => {
const el = {
tag,
attributes,
children,
isProxy: true,
get domEl() {
return domNode;
}
};
const proxy = makeReactive(el);
domToElement.set(domNode, proxy);
return proxy;
};
const someRecursive = (item, predicate) => {
if (Array.isArray(item)) return item.some((i) => someRecursive(i, predicate));
return predicate(item);
};
const element = (tag, attributes = {}, children = []) => {
if (customTags[tag]) tag = customTags[tag];
if (typeof tag === "function") {
const result = tag({ ...attributes }, children);
return processComponentResult(result);
}
if (tag === "shadowDOM") {
return createShadowDOMMarker(attributes, children);
}
if (tag === "text" && !inSVG) {
const domNode2 = document.createTextNode("");
const el = {
tag,
attributes,
children,
get domEl() {
return domNode2;
}
};
const update = () => {
const bits = [];
const walk = (c) => {
if (Array.isArray(c)) {
for (let i = 0; i < c.length; i++) walk(c[i]);
return;
}
const val = typeof c === "function" ? c() : c;
if (val && typeof val === "object" && val.domEl) bits.push(val.domEl.textContent);
else bits.push(val === null || val === void 0 ? "" : String(val));
};
walk(el.children);
domNode2.textContent = bits.join(" ");
};
const proxy = new Proxy(el, {
set(target, prop, value) {
target[prop] = value;
if (prop === "children") update();
return true;
}
});
const hasReactive = someRecursive(children, (c) => typeof c === "function");
if (hasReactive) {
const runner = effect(update);
trackEffect(domNode2, runner);
}
update();
return proxy;
}
const isSVG = tag.toLowerCase() === "svg";
const wasInSVG = inSVG;
if (isSVG) inSVG = true;
const domNode = inSVG ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
const hasReactiveAttr = Object.values(attributes).some((v) => typeof v === "function");
const hasReactiveChild = someRecursive(children, (c) => typeof c === "function" || c && c.isProxy);
if (hasReactiveAttr || hasReactiveChild) {
const proxy = wrapDomElement(domNode, tag, attributes, children);
proxy.attributes = attributes;
proxy.children = children;
if (isSVG) inSVG = wasInSVG;
return proxy;
}
makeReactiveAttributes(attributes, domNode);
setupChildren(children, domNode);
if (isSVG) inSVG = wasInSVG;
return {
tag,
attributes,
children,
domEl: domNode
};
};
const processComponentResult = (result) => {
if (!result) return null;
if (Lightview.hooks.processChild) {
result = Lightview.hooks.processChild(result) ?? result;
}
if (result.domEl) return result;
const type = typeof result;
if (type === "object" && result && result.nodeType === 1) {
return wrapDomElement(result, result.tagName.toLowerCase(), {}, []);
}
if (type === "object" && result instanceof String) {
const span = document.createElement("span");
span.textContent = result.toString();
return wrapDomElement(span, "span", {}, []);
}
if (type === "string") {
const template = document.createElement("template");
template.innerHTML = result.trim();
const content = template.content;
if (content.childNodes.length === 1 && content.firstChild && content.firstChild.nodeType === 1) {
const el = content.firstChild;
return wrapDomElement(el, el.tagName.toLowerCase(), {}, []);
} else {
const wrapper = document.createElement("span");
wrapper.style.display = "contents";
wrapper.appendChild(content);
return wrapDomElement(wrapper, "span", {}, []);
}
}
if (typeof result === "object" && result.tag) {
return element(result.tag, result.attributes || {}, result.children || []);
}
return null;
};
const makeReactive = (el) => {
const domNode = el.domEl;
return new Proxy(el, {
set(target, prop, value) {
if (prop === "attributes") {
target[prop] = makeReactiveAttributes(value, domNode);
} else if (prop === "children") {
target[prop] = setupChildren(value, domNode);
} else {
target[prop] = value;
}
return true;
}
});
};
const NODE_PROPERTIES = /* @__PURE__ */ new Set(["value", "checked", "selected", "selectedIndex", "className", "innerHTML", "innerText"]);
const setAttributeValue = (domNode, key, value) => {
const isBool = typeof domNode[key] === "boolean";
if ((key === "href" || key === "src") && typeof value === "string" && /^(javascript|vbscript|data:text\/html|data:application\/javascript)/i.test(value)) {
console.warn(`[Lightview] Blocked dangerous protocol in ${key}: ${value}`);
value = "javascript:void(0)";
}
if (NODE_PROPERTIES.has(key) || isBool || key.startsWith("cdom-")) {
domNode[key] = isBool ? value !== null && value !== void 0 && value !== false && value !== "false" : value;
} else if (value === null || value === void 0) {
domNode.removeAttribute(key);
} else {
domNode.setAttribute(key, value);
}
};
const makeReactiveAttributes = (attributes = {}, domNode) => {
const reactiveAttrs = {};
for (const key in attributes) {
const value = attributes[key];
const type = typeof value;
if (value && type === "object" && value.__xpath__ && value.__static__) {
domNode.setAttribute(`data-xpath-${key}`, value.__xpath__);
reactiveAttrs[key] = value;
continue;
}
if (key === "onmount" || key === "onunmount") {
const state2 = getOrSet(nodeState, domNode, nodeStateFactory);
state2[key] = value;
if (key === "onmount" && domNode.isConnected) {
value(domNode);
}
} else if (key.startsWith("on")) {
if (type === "function") {
domNode.addEventListener(key.slice(2).toLowerCase(), value);
} else if (type === "string") {
domNode.setAttribute(key, value);
}
reactiveAttrs[key] = value;
} else if (typeof value === "object" && value !== null && Lightview.hooks.processAttribute) {
const processed = Lightview.hooks.processAttribute(domNode, key, value);
if (processed !== void 0) {
reactiveAttrs[key] = processed;
} else if (key === "style") {
for (const styleKey in entries) {
const styleValue = entries[styleKey];
if (typeof styleValue === "function") {
const runner = effect(() => {
domNode.style[styleKey] = styleValue();
});
trackEffect(domNode, runner);
} else {
domNode.style[styleKey] = styleValue;
}
}
reactiveAttrs[key] = value;
} else {
setAttributeValue(domNode, key, value);
reactiveAttrs[key] = value;
}
} else if (type === "function") {
const runner = effect(() => {
const result = value();
if (key === "style" && typeof result === "object") {
Object.assign(domNode.style, result);
} else {
setAttributeValue(domNode, key, result);
}
});
trackEffect(domNode, runner);
reactiveAttrs[key] = value;
} else {
setAttributeValue(domNode, key, value);
reactiveAttrs[key] = value;
}
}
return reactiveAttrs;
};
const processChildren = (children, targetNode, clearExisting = true) => {
if (clearExisting && targetNode.innerHTML !== void 0) {
targetNode.innerHTML = "";
}
const childElements = [];
const isSpecialElement = targetNode.tagName && (targetNode.tagName.toLowerCase() === "script" || targetNode.tagName.toLowerCase() === "style");
const walk = (child) => {
if (Array.isArray(child)) {
for (let i = 0; i < child.length; i++) walk(child[i]);
return;
}
if (child === null || child === void 0) return;
if (Lightview.hooks.processChild && !isSpecialElement) {
child = Lightview.hooks.processChild(child) ?? child;
}
const type = typeof child;
if (child && type === "object" && child.tag) {
const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
targetNode.appendChild(childEl.domEl);
childElements.push(childEl);
} else if (["string", "number", "boolean", "symbol"].includes(type) || child && type === "object" && child instanceof String) {
targetNode.appendChild(document.createTextNode(child));
childElements.push(child);
} else if (type === "function") {
const startMarker = document.createComment("lv:s");
const endMarker = document.createComment("lv:e");
targetNode.appendChild(startMarker);
targetNode.appendChild(endMarker);
let runner;
const update = () => {
while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) {
startMarker.nextSibling.remove();
}
const val = child();
if (val === void 0 || val === null) return;
if (runner && !startMarker.isConnected) {
runner.stop();
return;
}
if (typeof val === "object" && val instanceof String) {
const textNode = document.createTextNode(val);
endMarker.parentNode.insertBefore(textNode, endMarker);
} else {
const fragment = document.createDocumentFragment();
const childrenToProcess = Array.isArray(val) ? val : [val];
processChildren(childrenToProcess, fragment, false);
endMarker.parentNode.insertBefore(fragment, endMarker);
}
};
runner = effect(update);
trackEffect(startMarker, runner);
childElements.push(child);
} else if (child instanceof Node) {
const node = child.domEl || child;
if (node.nodeType === 1) {
const wrapped = wrapDomElement(node, node.tagName.toLowerCase());
targetNode.appendChild(node);
childElements.push(wrapped);
} else {
targetNode.appendChild(node);
childElements.push(child);
}
} else if (isShadowDOMMarker(child)) {
if (targetNode instanceof ShadowRoot) {
console.warn("Lightview: Cannot nest shadowDOM inside another shadowDOM");
return;
}
processShadowDOM(child, targetNode);
} else if (child && typeof child === "object" && child.__xpath__ && child.__static__) {
const textNode = document.createTextNode("");
textNode.__xpathExpr = child.__xpath__;
targetNode.appendChild(textNode);
childElements.push(child);
}
};
walk(children);
return childElements;
};
const setupChildrenInTarget = (children, targetNode) => {
return processChildren(children, targetNode, false);
};
const setupChildren = (children, domNode) => {
return processChildren(children, domNode, true);
};
const enhance = (selectorOrNode, options = {}) => {
const domNode = typeof selectorOrNode === "string" ? document.querySelector(selectorOrNode) : selectorOrNode;
const node = domNode.domEl || domNode;
if (!node || node.nodeType !== 1) return null;
const tagName = node.tagName.toLowerCase();
let el = domToElement.get(node);
if (!el) {
el = wrapDomElement(node, tagName);
}
const { innerText, innerHTML, ...attrs } = options;
if (innerText !== void 0) {
if (typeof innerText === "function") {
effect(() => {
node.innerText = innerText();
});
} else {
node.innerText = innerText;
}
}
if (innerHTML !== void 0) {
if (typeof innerHTML === "function") {
effect(() => {
node.innerHTML = innerHTML();
});
} else {
node.innerHTML = innerHTML;
}
}
if (Object.keys(attrs).length > 0) {
el.attributes = attrs;
}
return el;
};
const $ = (cssSelectorOrElement, startingDomEl = document.body) => {
const el = typeof cssSelectorOrElement === "string" ? startingDomEl.querySelector(cssSelectorOrElement) : cssSelectorOrElement;
if (!el) return null;
Object.defineProperty(el, "content", {
value(child, location = "inner") {
location = location.toLowerCase();
Lightview.tags;
const isSpecialElement = el.tagName && (el.tagName.toLowerCase() === "script" || el.tagName.toLowerCase() === "style");
const array = (Array.isArray(child) ? child : [child]).map((item) => {
if (Lightview.hooks.processChild && !isSpecialElement) {
item = Lightview.hooks.processChild(item) ?? item;
}
if (item.tag && !item.domEl) {
return element(item.tag, item.attributes || {}, item.children || []).domEl;
} else {
return item.domEl || item;
}
});
const target = location === "shadow" ? el.shadowRoot || el.attachShadow({ mode: "open" }) : el;
if (location === "inner" || location === "shadow") {
target.replaceChildren(...array);
} else if (location === "outer") {
target.replaceWith(...array);
} else if (location === "afterbegin") {
target.prepend(...array);
} else if (location === "beforeend") {
target.append(...array);
} else {
array.forEach((item) => el.insertAdjacentElement(location, item));
}
return el;
},
configurable: true,
writable: true
});
return el;
};
const customTags = {};
const tags = new Proxy({}, {
get(_, tag) {
if (tag === "_customTags") return { ...customTags };
const wrapper = (...args) => {
let attributes = {};
let children = args;
const arg0 = args[0];
if (args.length > 0 && arg0 && typeof arg0 === "object" && !arg0.tag && !arg0.domEl && !Array.isArray(arg0)) {
attributes = arg0;
children = args.slice(1);
}
return element(customTags[tag] || tag, attributes, children);
};
if (customTags[tag]) {
Object.assign(wrapper, customTags[tag]);
}
return wrapper;
},
set(_, tag, value) {
customTags[tag] = value;
return true;
}
});
const Lightview = {
registerSchema: (name, definition) => internals.schemas.set(name, definition),
signal: signal$1,
get: signal$1.get,
computed,
effect,
registry,
element,
// do not document this
enhance,
tags,
$,
// Extension hooks
hooks: {
onNonStandardHref: null,
processChild: null,
processAttribute: null,
validateUrl: null,
validate: (value, schema) => internals.hooks.validate(value, schema)
},
// Internals exposed for extensions
internals: {
core,
domToElement,
wrapDomElement,
setupChildren,
trackEffect,
saveScrolls,
restoreScrolls,
localRegistries: internals.localRegistries,
futureSignals: internals.futureSignals,
schemas: internals.schemas,
parents: internals.parents,
hooks: internals.hooks
}
};
if (typeof module !== "undefined" && module.exports) {
module.exports = Lightview;
}
if (typeof window !== "undefined") {
globalThis.Lightview = Lightview;
globalThis.addEventListener("click", (e) => {
const path = e.composedPath();
const link = path.find((el) => {
var _a2, _b2;
return el.tagName === "A" && ((_b2 = (_a2 = el.getAttribute) == null ? void 0 : _a2.call(el, "href")) == null ? void 0 : _b2.startsWith("#"));
});
if (link && !e.defaultPrevented) {
const href = link.getAttribute("href");
if (href.length > 1) {
const id = href.slice(1);
const root = link.getRootNode();
const target = (root.getElementById ? root.getElementById(id) : null) || (root.querySelector ? root.querySelector(`#${id}`) : null);
if (target) {
e.preventDefault();
requestAnimationFrame(() => {
requestAnimationFrame(() => {
target.style.scrollMarginTop = "calc(var(--site-nav-height, 0px) + 2rem)";
target.scrollIntoView({ behavior: "smooth", block: "start", inline: "start" });
});
});
}
}
}
if (Lightview.hooks.onNonStandardHref) {
Lightview.hooks.onNonStandardHref(e);
}
});
if (typeof MutationObserver !== "undefined") {
const walkNodes = (node, fn) => {
var _a2;
fn(node);
(_a2 = node.childNodes) == null ? void 0 : _a2.forEach((n) => walkNodes(n, fn));
if (node.shadowRoot) walkNodes(node.shadowRoot, fn);
};
const cleanupNode = (node) => walkNodes(node, (n) => {
var _a2, _b2;
const s = nodeState.get(n);
if (s) {
(_a2 = s.effects) == null ? void 0 : _a2.forEach((e) => e.stop());
(_b2 = s.onunmount) == null ? void 0 : _b2.call(s, n);
nodeState.delete(n);
}
});
const mountNode = (node) => walkNodes(node, (n) => {
var _a2, _b2;
(_b2 = (_a2 = nodeState.get(n)) == null ? void 0 : _a2.onmount) == null ? void 0 : _b2.call(_a2, n);
});
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach(cleanupNode);
mutation.addedNodes.forEach(mountNode);
});
});
const startObserving = () => {
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", startObserving);
} else {
startObserving();
}
}
}
const STANDARD_SRC_TAGS = ["img", "script", "iframe", "video", "audio", "source", "track", "embed", "input"];
const isStandardSrcTag = (tagName) => STANDARD_SRC_TAGS.includes(tagName) || tagName.startsWith("lv-");
const STANDARD_HREF_TAGS = ["a", "area", "base", "link"];
const isValidTagName = (name) => typeof name === "string" && name.length > 0 && name !== "children";
const isDangerousProtocol = (url) => {
if (!url || typeof url !== "string") return false;
const normalized = url.trim().toLowerCase();
return normalized.startsWith("javascript:") || normalized.startsWith("vbscript:") || normalized.startsWith("data:text/html") || normalized.startsWith("data:application/javascript");
};
const validateUrl = (url) => {
if (!url) return false;
if (!/^[a-z][a-z0-9+.-]*:/i.test(url)) return true;
try {
const base = typeof document !== "undefined" ? document.baseURI : globalThis.location.origin;
const target = new URL(url, base === "null" ? void 0 : base);
const current = globalThis.location;
if (target.origin === current.origin && target.origin !== "null") return true;
if (target.hostname && target.hostname === current.hostname) return true;
if (target.hostname && current.hostname && target.hostname.endsWith("." + current.hostname)) return true;
if (current.protocol === "file:" && target.protocol === "file:") return true;
return false;
} catch (e) {
return false;
}
};
const isObjectDOM = (obj) => {
if (typeof obj !== "object" || obj === null || Array.isArray(obj) || obj.tag || obj.domEl) return false;
const keys = Object.keys(obj);
return keys.length === 1 && isValidTagName(keys[0]) && typeof obj[keys[0]] === "object";
};
const convertObjectDOM = (obj) => {
var _a2, _b2;
if (typeof obj !== "object" || obj === null) return obj;
if (Array.isArray(obj)) return obj.map(convertObjectDOM);
if (obj.tag) return { ...obj, children: obj.children ? convertObjectDOM(obj.children) : [] };
if (obj.domEl || !isObjectDOM(obj)) return obj;
const tagKey = Object.keys(obj)[0];
const content = obj[tagKey];
const LV = typeof window !== "undefined" ? globalThis.Lightview : typeof globalThis !== "undefined" ? globalThis.Lightview : null;
const tag = ((_b2 = (_a2 = LV == null ? void 0 : LV.tags) == null ? void 0 : _a2._customTags) == null ? void 0 : _b2[tagKey]) || tagKey;
const { children, ...attributes } = content;
return { tag, attributes, children: children ? convertObjectDOM(children) : [] };
};
const DAISYUI_CDN = "https://cdn.jsdelivr.net/npm/daisyui@4.12.23/dist/full.min.css";
const componentConfig = {
initialized: false,
shadowDefault: true,
// Default: components use shadow DOM
daisyStyleSheet: null,
themeStyleSheet: null,
// Global theme stylesheet
componentStyleSheets: /* @__PURE__ */ new Map(),
customStyleSheets: /* @__PURE__ */ new Map(),
// Registry for named custom stylesheets
customStyleSheetPromises: /* @__PURE__ */ new Map()
// Cache for pending stylesheet fetches
};
const registerStyleSheet = async (nameOrIdOrUrl, cssText) => {
if (componentConfig.customStyleSheets.has(nameOrIdOrUrl)) return componentConfig.customStyleSheets.get(nameOrIdOrUrl);
if (componentConfig.customStyleSheetPromises.has(nameOrIdOrUrl)) return componentConfig.customStyleSheetPromises.get(nameOrIdOrUrl);
const promise = (async () => {
try {
let finalCss = cssText;
if (finalCss === void 0) {
if (nameOrIdOrUrl.startsWith("#")) {
const el = document.querySelector(nameOrIdOrUrl);
if (el) {
finalCss = el.textContent;
} else {
throw new Error(`Style block '${nameOrIdOrUrl}' not found`);
}
} else {
const response = await fetch(nameOrIdOrUrl);
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
finalCss = await response.text();
}
}
if (finalCss !== void 0) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(finalCss);
componentConfig.customStyleSheets.set(nameOrIdOrUrl, sheet);
return sheet;
}
} catch (e) {
console.error(`LightviewX: Failed to register stylesheet '${nameOrIdOrUrl}':`, e);
} finally {
componentConfig.customStyleSheetPromises.delete(nameOrIdOrUrl);
}
})();
componentConfig.customStyleSheetPromises.set(nameOrIdOrUrl, promise);
return promise;
};
const getSavedTheme = () => {
try {
if (typeof localStorage !== "undefined") {
return localStorage.getItem("lightview-theme");
}
} catch (e) {
return null;
}
};
const themeSignal = signal$1(
typeof document !== "undefined" && document.documentElement.getAttribute("data-theme") || getSavedTheme() || "light"
);
const setTheme = (themeName) => {
if (!themeName) return;
if (typeof document !== "undefined") {
document.documentElement.setAttribute("data-theme", themeName);
}
if (themeSignal && themeSignal.value !== themeName) {
themeSignal.value = themeName;
}
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem("lightview-theme", themeName);
}
} catch (e) {
}
};
const registerThemeSheet = async (url) => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to fetch theme CSS: ${response.status}`);
const cssText = await response.text();
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssText);
componentConfig.themeStyleSheet = sheet;
} catch (e) {
console.error(`LightviewX: Failed to register theme stylesheet '${url}':`, e);
}
};
const initComponents = async (options = {}) => {
const { shadowDefault = true } = options;
componentConfig.shadowDefault = shadowDefault;
if (shadowDefault) {
try {
const response = await fetch(DAISYUI_CDN);
if (!response.ok) {
throw new Error(`Failed to fetch DaisyUI CSS: ${response.status}`);
}
const cssText = await response.text();
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssText);
componentConfig.daisyStyleSheet = sheet;
} catch (e) {
console.error("LightviewX: Failed to preload DaisyUI stylesheet:", e);
}
}
componentConfig.initialized = true;
};
(async () => await initComponents())();
const getComponentStyleSheet = async (cssUrl) => {
if (componentConfig.componentStyleSheets.has(cssUrl)) {
return componentConfig.componentStyleSheets.get(cssUrl);
}
try {
const response = await fetch(cssUrl);
if (!response.ok) {
throw new Error(`Failed to fetch component CSS: ${response.status}`);
}
const cssText = await response.text();
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssText);
componentConfig.componentStyleSheets.set(cssUrl, sheet);
return sheet;
} catch (e) {
console.error(`LightviewX: Failed to create stylesheet for ${cssUrl}:`, e);
return null;
}
};
const shouldUseShadow = (useShadowProp) => {
if (useShadowProp !== void 0) {
return useShadowProp;
}
return componentConfig.shadowDefault;
};
const getAdoptedStyleSheets = (componentCssUrl, requestedSheets = []) => {
const result = [];
if (componentConfig.daisyStyleSheet) {
result.push(componentConfig.daisyStyleSheet);
} else {
result.push(DAISYUI_CDN);
}
if (componentConfig.themeStyleSheet) {
result.push(componentConfig.themeStyleSheet);
}
if (componentCssUrl) {
const componentSheet = componentConfig.componentStyleSheets.get(componentCssUrl);
if (componentSheet) {
result.push(componentSheet);
}
}
if (Array.isArray(requestedSheets)) {
requestedSheets.forEach((url) => {
const sheet = componentConfig.customStyleSheets.get(url);
if (sheet) {
result.push(sheet);
} else {
registerStyleSheet(url);
result.push(url);
}
});
}
return result;
};
const preloadComponentCSS = async (cssUrl) => {
if (!componentConfig.componentStyleSheets.has(cssUrl)) {
await getComponentStyleSheet(cssUrl);
}
};
const compileTemplate = (code) => {
try {
const isSingle = code.trim().startsWith("${") && code.trim().endsWith("}") && !code.trim().includes("${", 2);
const body = isSingle ? "return " + code.trim().slice(2, -1) : "return `" + code.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + "`";
return new Function("state", "signal", body);
} catch (e) {
return () => "";
}
};
const processTemplateChild = (child, LV) => {
if (typeof child === "string" && child.includes("${")) {
const fn = compileTemplate(child);
return () => fn(LV.state, LV.signal);
}
return child;
};
const transformTextNode = (node, isRaw, LV) => {
const text = node.textContent;
if (isRaw) return text;
if (!text.trim() && !text.includes("${")) return null;
if (text.includes("${")) {
const fn = compileTemplate(text);
return () => fn(LV.state, LV.signal);
}
return text;
};
const transformElementNode = (node, element2, domToElements2) => {
const tagName = node.tagName.toLowerCase();
const attributes = {};
const skip = tagName === "script" || tagName === "style";
const LV = typeof window !== "undefined" ? globalThis.Lightview : typeof globalThis !== "undefined" ? globalThis.Lightview : null;
for (let attr of node.attributes) {
const val = attr.value;
attributes[attr.name] = !skip && val.includes("${") ? (() => {
const fn = compileTemplate(val);
return () => fn(LV.state, LV.signal);
})() : val;
}
return element2(tagName, attributes, domToElements2(Array.from(node.childNodes), element2, tagName));
};
const domToElements = (domNodes, element2, parentTagName = null) => {
const isRaw = parentTagName === "script" || parentTagName === "style";
const LV = globalThis.Lightview;
return domNodes.map((node) => {
if (node.nodeType === Node.TEXT_NODE) return transformTextNode(node, isRaw, LV);
if (node.nodeType === Node.ELEMENT_NODE) return transformElementNode(node, element2, domToElements);
return null;
}).filter((n) => n !== null);
};
const insertedContentMap = /* @__PURE__ */ new WeakMap();
const hashContent = (str) => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return hash.toString(36);
};
const createMarker = (id, isEnd = false) => {
return document.createComment(`lv-src-${isEnd ? "end" : "start"}:${id}`);
};
const executeScripts = (container) => {
if (!container) return;
const scripts = container.querySelectorAll("script");
scripts.forEach((oldScript) => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach((attr) => {
newScript.setAttribute(attr.name, attr.value);
});
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
};
const removeInsertedContent = (parentEl, markerId) => {
const startMarker = `lv-src-start:${markerId}`;
const endMarker = `lv-src-end:${markerId}`;
let inRange = false;
const nodesToRemove = [];
const walker = document.createTreeWalker(
parentEl.parentElement || parentEl,
NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
null,
false
);
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.nodeType === Node.COMMENT_NODE) {
if (node.textContent === startMarker) {
inRange = true;
nodesToRemove.push(node);
continue;
}
if (node.textContent === endMarker) {
nodesToRemove.push(node);
break;
}
}
if (inRange) {
nodesToRemove.push(node);
}
}
nodesToRemove.forEach((node) => node.remove());
return nodesToRemove.length > 0;
};
const insert = (elements, parent, location, markerId, { element: element2, setupChildren: setupChildren2 }) => {
const isSibling = location === "beforebegin" || location === "afterend";
const isOuter = location === "outerhtml";
const target = isSibling || isOuter ? parent.parentElement : parent;
if (!target) return console.warn(`LightviewX: No parent for ${location}`);
const frag = document.createDocumentFragment();
frag.appendChild(createMarker(markerId, false));
elements.forEach((c) => {
var _a2, _b2, _c;
if (typeof c === "string") frag.appendChild(document.createTextNode(c));
else if (c.domEl) frag.appendChild(c.domEl);
else if (c instanceof Node) frag.appendChild(c);
else {
const v = ((_c = (_a2 = globalThis.Ligh