UNPKG

lightview

Version:

A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation

1,263 lines 68.1 kB
(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 = (name, scope) => { let current = scope; while (current && typeof current === "object") { const registry = _LV.localRegistries.get(current); if (registry && registry.has(name)) return registry.get(name); current = current.parentElement || _LV.parents.get(current); } return _LV.registry.get(name); }; const signal = (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 registry = scope && typeof scope === "object" ? _LV.localRegistries.get(scope) || _LV.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : _LV.registry; if (registry && registry.has(name) && registry.get(name) !== f) { throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`); } if (registry) registry.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(name, scope); if (existing) return existing; if (defaultValue !== void 0) return signal(defaultValue, { name, scope }); const future = signal(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.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 getRegistry = () => _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 = (map, key, factory) => { let v = map.get(key); if (!v) { v = factory(); map.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(Reflect.get(target, prop, receiver))); } const signal$1 = signals.get(prop); const val = signal$1.value; if (typeof val === "object" && val !== null) { const childProxy = state(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(Reflect.get(target, prop, receiver))); } const success = Reflect.set(target, prop, validatedValue, receiver); const signal$1 = signals.get(prop); if (success && signal$1) signal$1.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(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(element, index, array) { const wrappedElement = typeof element === "object" && element !== null ? state(element) : element; if (wrappedElement && typeof wrappedElement === "object") { parents.set(wrappedElement, receiver); } return originalCallback.call(this, wrappedElement, index, 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 = (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 registry = scope && typeof scope === "object" ? internals.localRegistries.get(scope) || internals.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : getRegistry(); if (registry && registry.has(name) && registry.get(name) !== proxy) { throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`); } if (registry) registry.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(name, scope); if (existing) return existing; if (defaultValue !== void 0) return state(defaultValue, { name, scope }); const future = signal(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.get = getState; 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( 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, element, 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 element(tagName, attributes, domToElements2(Array.from(node.childNodes), element, tagName)); }; const domToElements = (domNodes, element, 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, element, 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, setupChildren }) => { 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.Lightview) == null ? void 0 : (_b2 = _a2.hooks).processChild) == null ? void 0 : _c.call(_b2, c)) || c; if (v.tag) { const n = element(v.tag, v.attributes || {}, v.children || []); if (n == null ? void 0 : n.domEl) frag.appendChild(n.domEl); } } }); frag.appendChild(createMarker(markerId, true)); if (isOuter) target.replaceChild(frag, parent); else if (location === "beforebegin") target.insertBefore(frag, parent); else if (location === "afterend") target.insertBefore(frag, parent.nextSibling); else if (location === "afterbegin") parent.insertBefore(frag, parent.firstChild); else if (location === "beforeend") parent.appendChild(frag); executeScripts(target); }; const isPath = (s) => typeof s === "string" && !isDangerousProtocol(s) && /^(https?:|\.|\/|[\w])|(\.(html|json|[vo]dom|cdomc?))$/i.test(s); const getRequestInfo = (el) => { const domEl = el.domEl || el; const method = (domEl.getAttribute("data-method") || "GET").toUpperCase(); const bodyAttr = domEl.getAttribute("data-body"); let body = null; let headers = {}; if (bodyAttr) { if (bodyAttr.startsWith("javascript:")) { const expr = bodyAttr.slice(11); const LV = globalThis.Lightview; try { body = new Function("state", "signal", `return ${expr}`)(LV.state || {}, LV.signal || {}); } catch (e) { console.warn(`[LightviewX] Failed to evaluate data-body expression: ${expr}`, e); } } else if (bodyAttr.startsWith("json:")) { try { body = JSON.parse(bodyAttr.slice(5)); headers["Content-Type"] = "application/json"; } catch (e) { console.warn(`[LightviewX] Failed to parse data-body JSON: ${bodyAttr.slice(5)}`, e); } } else if (bodyAttr.startsWith("text:")) { body = bodyAttr.slice(5); headers["Content-Type"] = "text/plain"; } else { try { const target = document.querySelector(bodyAttr); if (target) { if (target.tagName === "FORM") { body = new FormData(target); } else if (["INPUT", "SELECT", "TEXTAREA"].includes(target.tagName)) { const name = target.getAttribute("name") || "body"; body = { [name]: target.value }; } else { body = target.innerText; } } } catch (e) { body = bodyAttr; } } } return { method, body, headers }; }; const fetchContent = async (src, requestOptions = {}) => { var _a2; const { method = "GET", body = null, headers = {} } = requestOptions; try { const LV = globalThis.Lightview; if (((_a2 = LV == null ? void 0 : LV.hooks) == null ? void 0 : _a2.validateUrl) && !LV.hooks.validateUrl(src)) { console.warn(`[LightviewX] Fetch blocked by validateUrl hook: ${src}`); return null; } let url = new URL(src, document.baseURI); const fetchOptions = { method, headers: { ...headers } }; if (body) { if (method === "GET") { const params = new URLSearchParams(url.search); if (body instanceof FormData) { for (const [key, value] of body.entries()) params.append(key, value); } else if (typeof body === "object" && body !== null) { for (const [key, value] of Object.entries(body)) params.append(key, String(value)); } else { params.append("body", String(body)); } const queryString = params.toString(); if (queryString) { url = new URL(`${url.origin}${url.pathname}?${queryString}${url.hash}`, url.origin); } } else { if (body instanceof FormData) { fetchOptions.body = body; } else if (typeof body === "object" && body !== null) { if (headers["Content-Type"] === "application/json" || !headers["Content-Type"]) { fetchOptions.body = JSON.stringify(body); fetchOptions.headers["Content-Type"] = "application/json"; } else { fetchOptions.body = String(body); } } else { fetchOptions.body = String(body); } } } const res = await fetch(url, fetchOptions); if (!res.ok) return null; const ext = url.pathname.split(".").pop().toLowerCase(); const isJson = ext === "vdom" || ext === "odom" || ext === "cdom"; const isHtml = ext === "html"; const isCdom = ext === "cdom" || ext === "cdomc"; const content = isJson ? await res.json() : await res.text(); return { content, isJson, isHtml, isCdom, ext, raw: isJson ? JSON.stringify(content) : content }; } catch (e) { return null; } }; const parseElements = (content, isJson, isHtml, el, element, isCdom = false, ext = "") => { if (isJson) return Array.isArray(content) ? content : [content]; if (isCdom && ext === "cdomc") { const CDOM = globalThis.LightviewCDOM; const parser = CDOM == null ? void 0 : CDOM.parseCDOMC; if (parser) { try { const obj = parser(content); const hydrated = CDOM.hydrate ? CDOM.hydrate(obj) : obj; return Array.isArray(hydrated) ? hydrated : [hydrated]; } catch (e) { console.warn("LightviewX: Failed to parse .cdomc:", e); return []; } } else { console.warn("LightviewX: CDOMC parser not found. Ensure lightview-cdom.js is loaded."); return []; } } if (isHtml) { if (el.domEl.getAttribute("escape") === "true") return [content]; const doc = new DOMParser().parseFromString(content.replace(/<head[^>]*>[\s\S]*?<\/head>/i, ""), "text/html"); return domToElements([...Array.from(doc.head.childNodes), ...Array.from(doc.body.childNodes)], element); } return [content]; }; const elementsFromSelector = (selector, element) => { try { const sel = document.querySelectorAll(selector); if (!sel.length) return null; return { elements: domToElements(Array.from(sel), element), raw: Array.from(sel).map((n) => n.outerHTML || n.textContent).join("") }; } catch (e) { return null; } }; const updateTargetContent = (el, elements, raw, loc, contentHash, options, targetHash = null) => { var _a2, _b2; const { element, setupChildren, saveScrolls, restoreScrolls } = { element: (_a2 = globalThis.Lightview) == null ? void 0 : _a2.element, ...(_b2 = globalThis.Lightview) == null ? void 0 : _b2.internals, ...options }; const markerId = `${loc}-${contentHash.slice(0, 8)}`; let track = getOrSet(insertedContentMap, el.domEl, () => ({})); if (track[loc]) removeInsertedContent(el.domEl, `${loc}-${track[loc].slice(0, 8)}`); track[loc] = contentHash; const scrollMap = saveScrolls ? saveScrolls() : null; const performScroll = (root) => { if (!targetHash) return; requestAnimationFrame(() => { requestAnimationFrame(() => { const id = targetHash.startsWith("#") ? targetHash.slice(1) : targetHash; const target = root.getElementById ? root.getElementById(id) : root.querySelector(`#${id}`); if (target) { target.style.scrollMarginTop = "calc(var(--site-nav-height, 0px) + 2rem)"; target.scrollIntoView({ behavior: "smooth", block: "start", inline: "start" }); } }); }); }; const runRestore = (root) => { if (restoreScrolls && scrollMap) restoreScrolls(scrollMap, root); }; if (loc === "shadow") { if (!el.domEl.shadowRoot) el.domEl.attachShadow({ mode: "open" }); setupChildren(elements, el.domEl.shadowRoot); executeScripts(el.domEl.shadowRoot); performScroll(el.domEl.shadowRoot); runRestore(el.domEl.shadowRoot); } else if (loc === "innerhtml") { el.children = elements; executeScripts(el.domEl); performScroll(document); runRestore(el.domEl); } else { insert(elements, el.domEl, loc, markerId, { element, setupChildren }); performScroll(document); runRestore(el.domEl); } }; const handleSrcAttribute = async (el, src, tagName, { element, setupChildren }) => { if (STANDARD_SRC_TAGS.includes(tagName)) return; let elements = [], raw = "", targetHash = null; if (isPath(src)) { if (src.includes("#")) { [src, targetHash] = src.split("#"); } const options = getRequestInfo(el); const result = await fetchContent(src, options); if (result) { elements = parseElements(result.content, result.isJson, result.isHtml, el, element, result.isCdom, result.ext); raw = result.raw; } } if (!elements.length) { const result = elementsFromSelector(src, element); if (result) { elements = result.elements; raw = result.raw; } } if (!elements.length) return; const loc = (el.domEl.getAttribute("location") || "innerhtml").toLowerCase(); const contentHash = hashContent(raw); const track = getOrSet(insertedContentMap, el.domEl, () => ({})); if (track[loc] === contentHash) { if (targetHash) { const root = loc === "shadow" ? el.domEl.shadowRoot : document; if (root) { requestAnimationFrame(() => { requestAnimationFrame(() => { var _a2; const id = targetHash.startsWith("#") ? targetHash.slice(1) : targetHash; const target = root.getElementById ? root.getElementById(id) : (_a2 = root.querySelector) == null ? void 0 : _a2.call(root, `#${id}`); if (target) { target.style.scrollMarginTop = "calc(var(--site-nav-height, 0px) + 2rem)"; target.scrollIntoView({ behavior: "smooth", block: "start", inline: "start" }); } }); }); } } return; } updateTargetContent(el, elements, raw, loc, contentHash, { element, setupChildren }, targetHash); }; const VALID_LOCATIONS = ["beforebegin", "afterbegin", "beforeend", "afterend", "innerhtml", "outerhtml", "shadow"]; const parseTargetWithLocation = (targetStr) => { for (const loc of VALID_LOCATIONS) { const suffix = ":" + loc; if (targetStr.toLowerCase().endsWith(suffix)) { return { selector: targetStr.slice(0, -suffix.length), location: loc }; } } return { selector: targetStr, location: null }; }; const handleNonStandardHref = async (e, { domToElement, wrapDomElement }) => { var _a2; const clickedEl = e.target.closest("[href]"); if (!clickedEl) return; const tagName = clickedEl.tagName.toLowerCase(); if (STANDARD_HREF_TAGS.includes(tagName)) return; e.preventDefault(); const href = clickedEl.getAttribute("href"); const LV = globalThis.Lightview; if (href && (isDangerousProtocol(href) || ((_a2 = LV == null ? void 0 : LV.hooks) == null ? void 0 : _a2.validateUrl) && !LV.hooks.validateUrl(href))) { console.warn(`[LightviewX] Navigation or fetch blocked by security policy: ${href}`); return; } const targetAttr = clickedEl.getAttribute("target"); const options = getRequestInfo(clickedEl); if (!targetAttr) { let el = domToElement.get(clickedEl); if (!el) { const attrs = {}; for (let attr of clickedEl.attributes) attrs[attr.name] = attr.value; el = wrapDomElement(clickedEl, tagName, attrs); } const newAttrs = { ...el.attributes, src: href }; el.attributes = newAttrs; return; } if (targetAttr.startsWith("_")) { if (options.method !== "GET") { console.warn("[LightviewX] Cannot use non-GET method for browser navigation (_blank, _top, etc.)"); } switch (targetAttr) { case "_self": globalThis.location.href = href; break; case "_parent": globalThis.parent.location.href = href; break; case "_top": globalThis.top.location.href = href; break; case "_blank": default: globalThis.open(href, targetAttr); break; } return; } const { selector, location } = parseTargetWithLocation(targetAttr); try { const targetElements = document.querySelectorAll(selector); if (targetElements.length === 0) return; const result = await fetchContent(href, options); if (!result) return; const { setupChildren } = LV.internals; const element = LV.element; targetElements.forEach((targetEl) => { let el = domToElement.get(targetEl); if (!el) { const attrs = {}; for (let attr of targetEl.attributes) attrs[attr.name] = attr.value; el = wrapDomElement(targetEl, targetEl.tagName.toLowerCase(), attrs); } const elements = parseElements(result.content, result.isJson, result.isHtml, el, element, result.isCdom, result.ext); const loc = (location || targetEl.getAttribute("location") || "innerhtml").toLowerCase(); const contentHash = hashContent(result.raw); updateTargetContent(el, elements, result.raw, loc, contentHash, { element, setupChildren }); targetEl.setAttribute("src", href); }); } catch (err) { console.warn("Invalid target selector or fetch error:", selector, err); } }; const gateStates = /* @__PURE__ */ new WeakMap(); const BYPASS_FLAG = "__lv_passed"; const RESUME_FLAG = "__lv_resume"; const SENSIBLE_EVENTS = [ "click", "dblclick", "mousedown", "mouseup", "contextmenu", "submit", "reset", "change", "input", "invalid", "keydown", "keyup", "keypress", "touchstart", "touchend" ]; const CAPTURE_EVENTS = ["focus", "blur"]; const getGateState = (el, key) => { let elState = gateStates.get(el); if (!elState) { elState = /* @__PURE__ */ new Map(); gateStates.set(el, elState); } let state2 = elState.get(key); if (!state2) { state2 = {}; elState.set(key, state2); } return state2; }; const gateThrottle = function(ms) { const event = arguments[arguments.length - 1]; if (event == null ? void 0 : event[RESUME_FLAG]) return true; const key = `throttle-${(event == null ? void 0 : event.type) || "all"}-${ms}`; const state2 = getGateState(this, key); const now = Date.now(); if (now - (state2.last || 0) >= ms) { state2.last = now; return true; } return false; }; const gateDebounce = function(ms) { const event = arguments[arguments.length - 1]; const key = `debounce-${(event == null ? void 0 : event.type) || "all"}-${ms}`; const state2 = getGateState(this, key); if (state2.timer) clearTimeout(state2.timer); if ((event == null ? void 0 : event[RESUME_FLAG]) && state2.passed) { state2.passed = false; return true; } state2.timer = setTimeout(() => { state2.passed = true; const newEvent = new event.constructor(event.type, event); newEvent[RESUME_FLAG] = true; this.dispatchEvent(newEvent); }, ms); return false; }; const parseBeforeAttribute = (attrValue) => { const tokens = []; let current = "", depth = 0, inQuote = null; for (let i2 = 0; i2 < attrValue.length; i2++) { const char = attrValue[i2]; if (inQuote) { current += char; if (char === inQuote && attrValue[i2 - 1] !== "\\") inQuote = null; } else if (char === "'" || char === '"') { inQuote = char; current += char; } else if (char === "(") { depth++; current += char; } else if (char === ")") { depth--; current += char; } else if (/\s/.test(char) && depth === 0) { if (current) tokens.push(current); current = ""; } else { current += char; } } if (current) tokens.push(current); const events = []; const exclusions = []; const calls = []; let i = 0; while (i < tokens.length) { const token = tokens[i]; if (!token || token.includes("(")) break; if (token.startsWith("!")) exclusions.push(token.slice(1)); else events.push(token); i++; } while (i < tokens.length) { if (tokens[i]) calls.push(tokens[i]); i++; } return { events, exclusions, calls }; }; const globalBeforeInterceptor = async (e) => { var _a2, _b2; if (e[BYPASS_FLAG]) return; const target = (_b2 = (_a2 = e.target).closest) == null ? void 0 : _b2.call(_a2, "[lv-before]"); if (!target) return; const { events, exclusions, calls } = parseBeforeAttribute(target.getAttribute("lv-before")); const isExcluded = exclusions.includes(e.type); const isIncluded = events.includes("*") || events.includes(e.type); if (isExcluded || !isIncluded) return; e.stopImmediatePropagation(); e.preventDefault(); for (const callStr of calls) { try { const match = callStr.match(/^([\w\.]+)\((.*)\)$/); if (!match) continue; const funcName = match[1]; const argsStr = match[2]; const LV = globalThis.Lightview; const LVX = globalThis.LightviewX; let fn = funcName.split(".").reduce((obj, key) => obj == null ? void 0 : obj[key], globalThis); if (!fn && funcName === "throttle") fn = gateThrottle; if (!fn && funcName === "debounce") fn = gateDebounce; if (!fn && LVX && LVX[funcName]) fn = LVX[funcName]; if (typeof fn !== "function") { console.warn(`LightviewX: lv-before function '${funcName}' not found`); continue; } const evalArgs = new Function("event", "state", "signal", `return [${argsStr}]`); const args = evalArgs.call(target, e, (LV == null ? void 0 : LV.state) || {}, (LV == null ? void 0 : LV.signal) || {}); args.push(e); let result = fn.apply(target, args); if (result instanceof Promise) result = await result; if (result === false || result === null || result === void 0) return; } catch (err) { console.error(`LightviewX: Error executing lv-before gate '${callStr}':`, err); return; } } const finalEvent = new e.constructor(e.type, e); finalEvent[BYPASS_FLAG] = true; target.dispatchEvent(finalEvent); }; const processSrcOnNode = (node, LV) => { if (node.nodeType !== Node.ELEMENT_NODE) return; const tagName = node.tagName.toLowerCase(); if (isStandardSrcTag(tagName)) return; const src = node.getAttribute("src"); if (!src) return; let el = LV.internals.domToElement.get(node); if (!el) { const attrs = {}; for (let attr of node.attributes) attrs[attr.name] = attr.value; el = LV.internals.wrapDomElement(node, tagName, attrs, []); } handleSrcAttribute(el, src, tagName, { element: LV.element, setupChildren: LV.internals.setupChildren }); }; const processedNodes = /* @__PURE__ */ new WeakSet(); const activateReactiveSyntax = (root, LV) => { if (!root || !LV) return; const bindEffect = (node, codeStr, isAttr = false, attrName = null) => { if (processedNodes.has(node) && !isAttr) return; if (!isAttr) processedNodes.add(node); const fn = compileTemplate(codeStr); LV.effect(() => { try { const val = fn(LV.state, LV.signal); if (isAttr) { if (attrName.startsWith("cdom-")) { node[attrName] = val; } else { val === null || val === void 0 || val === false ? node.removeAttribute(attrName) : node.setAttribute(attrName, val); } } else node.textContent = val !== void 0 ? val : ""; } catch (e) { } }); }; const textXPath = ".//text()[contains(., '${')]"; const textResult = document.evaluate( textXPath, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ); for (let i = 0; i < textResult.snapshotLength; i++) { const node = textResult.snapshotItem(i); if (node.parentElement && node.parentElement.closest("SCRIPT, STYLE, CODE, PRE, TEMPLATE, NOSCRIPT")) continue; bindEffect(node, node.textContent); } const attrXPath = ".//*[@*[contains(., '${')]]"; const attrResult = document.evaluate( attrXPath, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ); for (let i = 0; i < attrResult.snapshotLength; i++) { const element = attrResult.snapshotItem(i); if (["SCRIPT", "STYLE", "CODE", "PRE", "TEMPLATE", "NOSCRIPT"].includes(element.tagName)) continue; Array.from(element.attributes).forEach((attr) => { if (attr.value.includes("${")) { bindEffect(element, attr.value, true, attr.name); } }); } if (root.nodeType === Node.ELEMENT_NODE && !["SCRIPT", "STYLE", "CODE", "PRE", "TEMPLATE", "NOSCRIPT"].includes(root.tagName)) { Array.from(root.attributes).forEach((attr) => { if (attr.value.includes("${")) { bindEffect(root, attr.value, true, attr.name); } }); } }; const processAddedNode = (node, nodesToProcess, nodesToActivate) => { if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) { nodesToActivate.push(node); } if (node.nodeType !== Node.ELEMENT_NODE) return; nodesToProcess.push(node); const selector = "[src]:not(" + STANDARD_SRC_TAGS.join("):not(") + ")"; const descendants = node.querySelectorAll(selector); for (const desc of descendants) { if (!desc.tagName.toLowerCase().startsWith("lv-")) { nodesToProcess.push(desc); } } }; const collect