lightview 2.3.8 → 2.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gemini/CODE_ANALYSIS_AND_IMPROVEMENT_PLAN.md +56 -0
- package/AI-GUIDANCE.md +274 -0
- package/README.md +35 -0
- package/build_tmp/lightview-cdom.js +3934 -0
- package/build_tmp/lightview-router.js +185 -0
- package/build_tmp/lightview-x.js +1739 -0
- package/build_tmp/lightview.js +740 -0
- package/components/data-display/diff.js +36 -4
- package/docs/api/hypermedia.html +75 -5
- package/docs/api/index.html +3 -3
- package/docs/api/nav.html +0 -16
- package/docs/articles/html-vs-json-partials.md +102 -0
- package/docs/articles/lightview-vs-htmx.md +610 -0
- package/docs/assets/styles/site.css +16 -7
- package/docs/benchmarks/bau-tagged-fragment.js +41 -0
- package/docs/benchmarks/tagged-fragment.js +36 -0
- package/docs/cdom.html +127 -88
- package/docs/components/chart.html +157 -210
- package/docs/components/component-nav.html +1 -1
- package/docs/components/diff.html +33 -21
- package/docs/components/gallery.html +107 -4
- package/docs/components/index.css +18 -3
- package/docs/components/index.html +20 -9
- package/docs/dom-benchmark.html +771 -0
- package/docs/getting-started/index.html +42 -2
- package/docs/hypermedia/index.html +391 -0
- package/docs/hypermedia/nav.html +17 -0
- package/docs/index.html +136 -17
- package/index.html +59 -10
- package/lightview-all.js +223 -67
- package/lightview-cdom.js +1 -2
- package/lightview-x.js +144 -13
- package/lightview.js +85 -277
- package/package.json +2 -2
- package/src/lightview-cdom.js +1 -5
- package/src/lightview-x.js +158 -27
- package/src/lightview.js +94 -60
- package/docs/articles/calculator-no-javascript-hackernoon.md +0 -283
- package/docs/articles/calculator-no-javascript.md +0 -290
- package/docs/articles/part1-reference.md +0 -236
- package/lightview.js.bak +0 -1
- package/test-xpath.html +0 -63
- package/test_error.txt +0 -0
- package/test_output.txt +0 -0
- package/test_output_full.txt +0 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
const _LV = globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {
|
|
4
|
+
currentEffect: null,
|
|
5
|
+
registry: /* @__PURE__ */ new Map(),
|
|
6
|
+
// Global name -> Signal/Proxy
|
|
7
|
+
localRegistries: /* @__PURE__ */ new WeakMap(),
|
|
8
|
+
// Object/Element -> Map(name -> Signal/Proxy)
|
|
9
|
+
futureSignals: /* @__PURE__ */ new Map(),
|
|
10
|
+
// name -> Set of (signal) => void
|
|
11
|
+
schemas: /* @__PURE__ */ new Map(),
|
|
12
|
+
// name -> Schema (Draft 7+ or Shorthand)
|
|
13
|
+
parents: /* @__PURE__ */ new WeakMap(),
|
|
14
|
+
// Proxy -> Parent (Proxy/Element)
|
|
15
|
+
helpers: /* @__PURE__ */ new Map(),
|
|
16
|
+
// name -> function (used for transforms and expressions)
|
|
17
|
+
hooks: {
|
|
18
|
+
validate: (value, schema) => true
|
|
19
|
+
// Hook for extensions (like JPRX) to provide full validation
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const lookup = (name, scope) => {
|
|
23
|
+
let current = scope;
|
|
24
|
+
while (current && typeof current === "object") {
|
|
25
|
+
const registry2 = _LV.localRegistries.get(current);
|
|
26
|
+
if (registry2 && registry2.has(name)) return registry2.get(name);
|
|
27
|
+
current = current.parentElement || _LV.parents.get(current);
|
|
28
|
+
}
|
|
29
|
+
return _LV.registry.get(name);
|
|
30
|
+
};
|
|
31
|
+
const signal = (initialValue, optionsOrName) => {
|
|
32
|
+
const name = typeof optionsOrName === "string" ? optionsOrName : optionsOrName == null ? void 0 : optionsOrName.name;
|
|
33
|
+
const storage = optionsOrName == null ? void 0 : optionsOrName.storage;
|
|
34
|
+
const scope = optionsOrName == null ? void 0 : optionsOrName.scope;
|
|
35
|
+
if (name && storage) {
|
|
36
|
+
try {
|
|
37
|
+
const stored = storage.getItem(name);
|
|
38
|
+
if (stored !== null) initialValue = JSON.parse(stored);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let value = initialValue;
|
|
43
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
44
|
+
const f = (...args) => args.length === 0 ? f.value : f.value = args[0];
|
|
45
|
+
Object.defineProperty(f, "value", {
|
|
46
|
+
get() {
|
|
47
|
+
if (_LV.currentEffect) {
|
|
48
|
+
subscribers.add(_LV.currentEffect);
|
|
49
|
+
_LV.currentEffect.dependencies.add(subscribers);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
},
|
|
53
|
+
set(newValue) {
|
|
54
|
+
if (value !== newValue) {
|
|
55
|
+
value = newValue;
|
|
56
|
+
if (name && storage) {
|
|
57
|
+
try {
|
|
58
|
+
storage.setItem(name, JSON.stringify(value));
|
|
59
|
+
} catch (e) {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
[...subscribers].forEach((effect2) => effect2());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (name) {
|
|
67
|
+
const registry2 = scope && typeof scope === "object" ? _LV.localRegistries.get(scope) || _LV.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : _LV.registry;
|
|
68
|
+
if (registry2 && registry2.has(name) && registry2.get(name) !== f) {
|
|
69
|
+
throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`);
|
|
70
|
+
}
|
|
71
|
+
if (registry2) registry2.set(name, f);
|
|
72
|
+
const futures = _LV.futureSignals.get(name);
|
|
73
|
+
if (futures) {
|
|
74
|
+
futures.forEach((resolve) => resolve(f));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return f;
|
|
78
|
+
};
|
|
79
|
+
const getSignal = (name, defaultValueOrOptions) => {
|
|
80
|
+
const options = typeof defaultValueOrOptions === "object" && defaultValueOrOptions !== null ? defaultValueOrOptions : { defaultValue: defaultValueOrOptions };
|
|
81
|
+
const { scope, defaultValue } = options;
|
|
82
|
+
const existing = lookup(name, scope);
|
|
83
|
+
if (existing) return existing;
|
|
84
|
+
if (defaultValue !== void 0) return signal(defaultValue, { name, scope });
|
|
85
|
+
const future = signal(void 0);
|
|
86
|
+
const handler = (realSignal) => {
|
|
87
|
+
const hasValue = realSignal && (typeof realSignal === "object" || typeof realSignal === "function") && "value" in realSignal;
|
|
88
|
+
if (hasValue) {
|
|
89
|
+
future.value = realSignal.value;
|
|
90
|
+
effect(() => {
|
|
91
|
+
future.value = realSignal.value;
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
future.value = realSignal;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
if (!_LV.futureSignals.has(name)) _LV.futureSignals.set(name, /* @__PURE__ */ new Set());
|
|
98
|
+
_LV.futureSignals.get(name).add(handler);
|
|
99
|
+
return future;
|
|
100
|
+
};
|
|
101
|
+
signal.get = getSignal;
|
|
102
|
+
const effect = (fn) => {
|
|
103
|
+
const execute = () => {
|
|
104
|
+
if (!execute.active || execute.running) return;
|
|
105
|
+
execute.dependencies.forEach((dep) => dep.delete(execute));
|
|
106
|
+
execute.dependencies.clear();
|
|
107
|
+
execute.running = true;
|
|
108
|
+
_LV.currentEffect = execute;
|
|
109
|
+
try {
|
|
110
|
+
fn();
|
|
111
|
+
} finally {
|
|
112
|
+
_LV.currentEffect = null;
|
|
113
|
+
execute.running = false;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
execute.active = true;
|
|
117
|
+
execute.running = false;
|
|
118
|
+
execute.dependencies = /* @__PURE__ */ new Set();
|
|
119
|
+
execute.stop = () => {
|
|
120
|
+
execute.dependencies.forEach((dep) => dep.delete(execute));
|
|
121
|
+
execute.dependencies.clear();
|
|
122
|
+
execute.active = false;
|
|
123
|
+
};
|
|
124
|
+
execute();
|
|
125
|
+
return execute;
|
|
126
|
+
};
|
|
127
|
+
const computed = (fn) => {
|
|
128
|
+
const sig = signal(void 0);
|
|
129
|
+
effect(() => {
|
|
130
|
+
sig.value = fn();
|
|
131
|
+
});
|
|
132
|
+
return sig;
|
|
133
|
+
};
|
|
134
|
+
const getRegistry = () => _LV.registry;
|
|
135
|
+
const internals = _LV;
|
|
136
|
+
const { parents, schemas, hooks } = internals;
|
|
137
|
+
const protoMethods = (proto, test) => Object.getOwnPropertyNames(proto).filter((k) => typeof proto[k] === "function" && test(k));
|
|
138
|
+
protoMethods(Date.prototype, (k) => /^(to|get|valueOf)/.test(k));
|
|
139
|
+
protoMethods(Date.prototype, (k) => /^set/.test(k));
|
|
140
|
+
const getOrSet = (map, key, factory) => {
|
|
141
|
+
let v = map.get(key);
|
|
142
|
+
if (!v) {
|
|
143
|
+
v = factory();
|
|
144
|
+
map.set(key, v);
|
|
145
|
+
}
|
|
146
|
+
return v;
|
|
147
|
+
};
|
|
148
|
+
const core = {
|
|
149
|
+
get currentEffect() {
|
|
150
|
+
return (globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {})).currentEffect;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const nodeState = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
const nodeStateFactory = () => ({ effects: [], onmount: null, onunmount: null });
|
|
155
|
+
const registry = getRegistry();
|
|
156
|
+
const scrollMemory = /* @__PURE__ */ new Map();
|
|
157
|
+
const initScrollMemory = () => {
|
|
158
|
+
if (typeof document === "undefined") return;
|
|
159
|
+
document.addEventListener("scroll", (e) => {
|
|
160
|
+
const el = e.target;
|
|
161
|
+
if (el === document || el === document.documentElement) return;
|
|
162
|
+
const key = el.id || el.getAttribute && el.getAttribute("data-preserve-scroll");
|
|
163
|
+
if (key) {
|
|
164
|
+
scrollMemory.set(key, { top: el.scrollTop, left: el.scrollLeft });
|
|
165
|
+
}
|
|
166
|
+
}, true);
|
|
167
|
+
};
|
|
168
|
+
if (typeof document !== "undefined") {
|
|
169
|
+
if (document.readyState === "loading") {
|
|
170
|
+
document.addEventListener("DOMContentLoaded", initScrollMemory);
|
|
171
|
+
} else {
|
|
172
|
+
initScrollMemory();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const saveScrolls = () => new Map(scrollMemory);
|
|
176
|
+
const restoreScrolls = (map, root = document) => {
|
|
177
|
+
if (!map || map.size === 0) return;
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
map.forEach((pos, key) => {
|
|
180
|
+
const node = document.getElementById(key) || document.querySelector(`[data-preserve-scroll="${key}"]`);
|
|
181
|
+
if (node) {
|
|
182
|
+
node.scrollTop = pos.top;
|
|
183
|
+
node.scrollLeft = pos.left;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const trackEffect = (node, effectFn) => {
|
|
189
|
+
const state = getOrSet(nodeState, node, nodeStateFactory);
|
|
190
|
+
if (!state.effects) state.effects = [];
|
|
191
|
+
state.effects.push(effectFn);
|
|
192
|
+
};
|
|
193
|
+
const SHADOW_DOM_MARKER = Symbol("lightview.shadowDOM");
|
|
194
|
+
const createShadowDOMMarker = (attributes, children) => ({
|
|
195
|
+
[SHADOW_DOM_MARKER]: true,
|
|
196
|
+
mode: attributes.mode || "open",
|
|
197
|
+
styles: attributes.styles || [],
|
|
198
|
+
adoptedStyleSheets: attributes.adoptedStyleSheets || [],
|
|
199
|
+
children
|
|
200
|
+
});
|
|
201
|
+
const isShadowDOMMarker = (obj) => obj && typeof obj === "object" && obj[SHADOW_DOM_MARKER] === true;
|
|
202
|
+
const processShadowDOM = (marker, parentNode) => {
|
|
203
|
+
if (parentNode.shadowRoot) {
|
|
204
|
+
console.warn("Lightview: Element already has a shadowRoot, skipping shadowDOM directive");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const shadowRoot = parentNode.attachShadow({ mode: marker.mode });
|
|
208
|
+
const sheets = [];
|
|
209
|
+
const linkUrls = [...marker.styles || []];
|
|
210
|
+
if (marker.adoptedStyleSheets && marker.adoptedStyleSheets.length > 0) {
|
|
211
|
+
marker.adoptedStyleSheets.forEach((item) => {
|
|
212
|
+
if (item instanceof CSSStyleSheet) {
|
|
213
|
+
sheets.push(item);
|
|
214
|
+
} else if (typeof item === "string") {
|
|
215
|
+
linkUrls.push(item);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (sheets.length > 0) {
|
|
220
|
+
try {
|
|
221
|
+
shadowRoot.adoptedStyleSheets = sheets;
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.warn("Lightview: adoptedStyleSheets not supported");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const styleUrl of linkUrls) {
|
|
227
|
+
const link = document.createElement("link");
|
|
228
|
+
link.rel = "stylesheet";
|
|
229
|
+
link.href = styleUrl;
|
|
230
|
+
shadowRoot.appendChild(link);
|
|
231
|
+
}
|
|
232
|
+
if (marker.children && marker.children.length > 0) {
|
|
233
|
+
setupChildrenInTarget(marker.children, shadowRoot);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
let inSVG = false;
|
|
237
|
+
const domToElement = /* @__PURE__ */ new WeakMap();
|
|
238
|
+
const wrapDomElement = (domNode, tag, attributes = {}, children = []) => {
|
|
239
|
+
const el = {
|
|
240
|
+
tag,
|
|
241
|
+
attributes,
|
|
242
|
+
children,
|
|
243
|
+
isProxy: true,
|
|
244
|
+
get domEl() {
|
|
245
|
+
return domNode;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const proxy = makeReactive(el);
|
|
249
|
+
domToElement.set(domNode, proxy);
|
|
250
|
+
return proxy;
|
|
251
|
+
};
|
|
252
|
+
const someRecursive = (item, predicate) => {
|
|
253
|
+
if (Array.isArray(item)) return item.some((i) => someRecursive(i, predicate));
|
|
254
|
+
return predicate(item);
|
|
255
|
+
};
|
|
256
|
+
const element = (tag, attributes = {}, children = []) => {
|
|
257
|
+
if (customTags[tag]) tag = customTags[tag];
|
|
258
|
+
if (typeof tag === "function") {
|
|
259
|
+
const result = tag({ ...attributes }, children);
|
|
260
|
+
return processComponentResult(result);
|
|
261
|
+
}
|
|
262
|
+
if (tag === "shadowDOM") {
|
|
263
|
+
return createShadowDOMMarker(attributes, children);
|
|
264
|
+
}
|
|
265
|
+
if (tag === "text" && !inSVG) {
|
|
266
|
+
const domNode2 = document.createTextNode("");
|
|
267
|
+
const el = {
|
|
268
|
+
tag,
|
|
269
|
+
attributes,
|
|
270
|
+
children,
|
|
271
|
+
get domEl() {
|
|
272
|
+
return domNode2;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const update = () => {
|
|
276
|
+
const bits = [];
|
|
277
|
+
const walk = (c) => {
|
|
278
|
+
if (Array.isArray(c)) {
|
|
279
|
+
for (let i = 0; i < c.length; i++) walk(c[i]);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const val = typeof c === "function" ? c() : c;
|
|
283
|
+
if (val && typeof val === "object" && val.domEl) bits.push(val.domEl.textContent);
|
|
284
|
+
else bits.push(val === null || val === void 0 ? "" : String(val));
|
|
285
|
+
};
|
|
286
|
+
walk(el.children);
|
|
287
|
+
domNode2.textContent = bits.join(" ");
|
|
288
|
+
};
|
|
289
|
+
const proxy = new Proxy(el, {
|
|
290
|
+
set(target, prop, value) {
|
|
291
|
+
target[prop] = value;
|
|
292
|
+
if (prop === "children") update();
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
const hasReactive = someRecursive(children, (c) => typeof c === "function");
|
|
297
|
+
if (hasReactive) {
|
|
298
|
+
const runner = effect(update);
|
|
299
|
+
trackEffect(domNode2, runner);
|
|
300
|
+
}
|
|
301
|
+
update();
|
|
302
|
+
return proxy;
|
|
303
|
+
}
|
|
304
|
+
const isSVG = tag.toLowerCase() === "svg";
|
|
305
|
+
const wasInSVG = inSVG;
|
|
306
|
+
if (isSVG) inSVG = true;
|
|
307
|
+
const domNode = inSVG ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
|
|
308
|
+
const hasReactiveAttr = Object.values(attributes).some((v) => typeof v === "function");
|
|
309
|
+
const hasReactiveChild = someRecursive(children, (c) => typeof c === "function" || c && c.isProxy);
|
|
310
|
+
if (hasReactiveAttr || hasReactiveChild) {
|
|
311
|
+
const proxy = wrapDomElement(domNode, tag, attributes, children);
|
|
312
|
+
proxy.attributes = attributes;
|
|
313
|
+
proxy.children = children;
|
|
314
|
+
if (isSVG) inSVG = wasInSVG;
|
|
315
|
+
return proxy;
|
|
316
|
+
}
|
|
317
|
+
makeReactiveAttributes(attributes, domNode);
|
|
318
|
+
setupChildren(children, domNode);
|
|
319
|
+
if (isSVG) inSVG = wasInSVG;
|
|
320
|
+
return {
|
|
321
|
+
tag,
|
|
322
|
+
attributes,
|
|
323
|
+
children,
|
|
324
|
+
domEl: domNode
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
const processComponentResult = (result) => {
|
|
328
|
+
if (!result) return null;
|
|
329
|
+
if (Lightview.hooks.processChild) {
|
|
330
|
+
result = Lightview.hooks.processChild(result) ?? result;
|
|
331
|
+
}
|
|
332
|
+
if (result.domEl) return result;
|
|
333
|
+
const type = typeof result;
|
|
334
|
+
if (type === "object" && result && result.nodeType === 1) {
|
|
335
|
+
return wrapDomElement(result, result.tagName.toLowerCase(), {}, []);
|
|
336
|
+
}
|
|
337
|
+
if (type === "object" && result instanceof String) {
|
|
338
|
+
const span = document.createElement("span");
|
|
339
|
+
span.textContent = result.toString();
|
|
340
|
+
return wrapDomElement(span, "span", {}, []);
|
|
341
|
+
}
|
|
342
|
+
if (type === "string") {
|
|
343
|
+
const template = document.createElement("template");
|
|
344
|
+
template.innerHTML = result.trim();
|
|
345
|
+
const content = template.content;
|
|
346
|
+
if (content.childNodes.length === 1 && content.firstChild && content.firstChild.nodeType === 1) {
|
|
347
|
+
const el = content.firstChild;
|
|
348
|
+
return wrapDomElement(el, el.tagName.toLowerCase(), {}, []);
|
|
349
|
+
} else {
|
|
350
|
+
const wrapper = document.createElement("span");
|
|
351
|
+
wrapper.style.display = "contents";
|
|
352
|
+
wrapper.appendChild(content);
|
|
353
|
+
return wrapDomElement(wrapper, "span", {}, []);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (typeof result === "object" && result.tag) {
|
|
357
|
+
return element(result.tag, result.attributes || {}, result.children || []);
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
};
|
|
361
|
+
const makeReactive = (el) => {
|
|
362
|
+
const domNode = el.domEl;
|
|
363
|
+
return new Proxy(el, {
|
|
364
|
+
set(target, prop, value) {
|
|
365
|
+
if (prop === "attributes") {
|
|
366
|
+
target[prop] = makeReactiveAttributes(value, domNode);
|
|
367
|
+
} else if (prop === "children") {
|
|
368
|
+
target[prop] = setupChildren(value, domNode);
|
|
369
|
+
} else {
|
|
370
|
+
target[prop] = value;
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
const NODE_PROPERTIES = /* @__PURE__ */ new Set(["value", "checked", "selected", "selectedIndex", "className", "innerHTML", "innerText"]);
|
|
377
|
+
const setAttributeValue = (domNode, key, value) => {
|
|
378
|
+
const isBool = typeof domNode[key] === "boolean";
|
|
379
|
+
if ((key === "href" || key === "src") && typeof value === "string" && /^(javascript|vbscript|data:text\/html|data:application\/javascript)/i.test(value)) {
|
|
380
|
+
console.warn(`[Lightview] Blocked dangerous protocol in ${key}: ${value}`);
|
|
381
|
+
value = "javascript:void(0)";
|
|
382
|
+
}
|
|
383
|
+
if (NODE_PROPERTIES.has(key) || isBool || key.startsWith("cdom-")) {
|
|
384
|
+
domNode[key] = isBool ? value !== null && value !== void 0 && value !== false && value !== "false" : value;
|
|
385
|
+
} else if (value === null || value === void 0) {
|
|
386
|
+
domNode.removeAttribute(key);
|
|
387
|
+
} else {
|
|
388
|
+
domNode.setAttribute(key, value);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
const makeReactiveAttributes = (attributes = {}, domNode) => {
|
|
392
|
+
const reactiveAttrs = {};
|
|
393
|
+
for (const key in attributes) {
|
|
394
|
+
const value = attributes[key];
|
|
395
|
+
const type = typeof value;
|
|
396
|
+
if (value && type === "object" && value.__xpath__ && value.__static__) {
|
|
397
|
+
domNode.setAttribute(`data-xpath-${key}`, value.__xpath__);
|
|
398
|
+
reactiveAttrs[key] = value;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (key === "onmount" || key === "onunmount") {
|
|
402
|
+
const state = getOrSet(nodeState, domNode, nodeStateFactory);
|
|
403
|
+
state[key] = value;
|
|
404
|
+
if (key === "onmount" && domNode.isConnected) {
|
|
405
|
+
value(domNode);
|
|
406
|
+
}
|
|
407
|
+
} else if (key.startsWith("on")) {
|
|
408
|
+
if (type === "function") {
|
|
409
|
+
domNode.addEventListener(key.slice(2).toLowerCase(), value);
|
|
410
|
+
} else if (type === "string") {
|
|
411
|
+
domNode.setAttribute(key, value);
|
|
412
|
+
}
|
|
413
|
+
reactiveAttrs[key] = value;
|
|
414
|
+
} else if (typeof value === "object" && value !== null && Lightview.hooks.processAttribute) {
|
|
415
|
+
const processed = Lightview.hooks.processAttribute(domNode, key, value);
|
|
416
|
+
if (processed !== void 0) {
|
|
417
|
+
reactiveAttrs[key] = processed;
|
|
418
|
+
} else if (key === "style") {
|
|
419
|
+
for (const styleKey in entries) {
|
|
420
|
+
const styleValue = entries[styleKey];
|
|
421
|
+
if (typeof styleValue === "function") {
|
|
422
|
+
const runner = effect(() => {
|
|
423
|
+
domNode.style[styleKey] = styleValue();
|
|
424
|
+
});
|
|
425
|
+
trackEffect(domNode, runner);
|
|
426
|
+
} else {
|
|
427
|
+
domNode.style[styleKey] = styleValue;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
reactiveAttrs[key] = value;
|
|
431
|
+
} else {
|
|
432
|
+
setAttributeValue(domNode, key, value);
|
|
433
|
+
reactiveAttrs[key] = value;
|
|
434
|
+
}
|
|
435
|
+
} else if (type === "function") {
|
|
436
|
+
const runner = effect(() => {
|
|
437
|
+
const result = value();
|
|
438
|
+
if (key === "style" && typeof result === "object") {
|
|
439
|
+
Object.assign(domNode.style, result);
|
|
440
|
+
} else {
|
|
441
|
+
setAttributeValue(domNode, key, result);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
trackEffect(domNode, runner);
|
|
445
|
+
reactiveAttrs[key] = value;
|
|
446
|
+
} else {
|
|
447
|
+
setAttributeValue(domNode, key, value);
|
|
448
|
+
reactiveAttrs[key] = value;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return reactiveAttrs;
|
|
452
|
+
};
|
|
453
|
+
const processChildren = (children, targetNode, clearExisting = true) => {
|
|
454
|
+
if (clearExisting && targetNode.innerHTML !== void 0) {
|
|
455
|
+
targetNode.innerHTML = "";
|
|
456
|
+
}
|
|
457
|
+
const childElements = [];
|
|
458
|
+
const isSpecialElement = targetNode.tagName && (targetNode.tagName.toLowerCase() === "script" || targetNode.tagName.toLowerCase() === "style");
|
|
459
|
+
const walk = (child) => {
|
|
460
|
+
if (Array.isArray(child)) {
|
|
461
|
+
for (let i = 0; i < child.length; i++) walk(child[i]);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (child === null || child === void 0) return;
|
|
465
|
+
if (Lightview.hooks.processChild && !isSpecialElement) {
|
|
466
|
+
child = Lightview.hooks.processChild(child) ?? child;
|
|
467
|
+
}
|
|
468
|
+
const type = typeof child;
|
|
469
|
+
if (child && type === "object" && child.tag) {
|
|
470
|
+
const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
|
|
471
|
+
targetNode.appendChild(childEl.domEl);
|
|
472
|
+
childElements.push(childEl);
|
|
473
|
+
} else if (["string", "number", "boolean", "symbol"].includes(type) || child && type === "object" && child instanceof String) {
|
|
474
|
+
targetNode.appendChild(document.createTextNode(child));
|
|
475
|
+
childElements.push(child);
|
|
476
|
+
} else if (type === "function") {
|
|
477
|
+
const startMarker = document.createComment("lv:s");
|
|
478
|
+
const endMarker = document.createComment("lv:e");
|
|
479
|
+
targetNode.appendChild(startMarker);
|
|
480
|
+
targetNode.appendChild(endMarker);
|
|
481
|
+
let runner;
|
|
482
|
+
const update = () => {
|
|
483
|
+
while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) {
|
|
484
|
+
startMarker.nextSibling.remove();
|
|
485
|
+
}
|
|
486
|
+
const val = child();
|
|
487
|
+
if (val === void 0 || val === null) return;
|
|
488
|
+
if (runner && !startMarker.isConnected) {
|
|
489
|
+
runner.stop();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (typeof val === "object" && val instanceof String) {
|
|
493
|
+
const textNode = document.createTextNode(val);
|
|
494
|
+
endMarker.parentNode.insertBefore(textNode, endMarker);
|
|
495
|
+
} else {
|
|
496
|
+
const fragment = document.createDocumentFragment();
|
|
497
|
+
const childrenToProcess = Array.isArray(val) ? val : [val];
|
|
498
|
+
processChildren(childrenToProcess, fragment, false);
|
|
499
|
+
endMarker.parentNode.insertBefore(fragment, endMarker);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
runner = effect(update);
|
|
503
|
+
trackEffect(startMarker, runner);
|
|
504
|
+
childElements.push(child);
|
|
505
|
+
} else if (child instanceof Node) {
|
|
506
|
+
const node = child.domEl || child;
|
|
507
|
+
if (node.nodeType === 1) {
|
|
508
|
+
const wrapped = wrapDomElement(node, node.tagName.toLowerCase());
|
|
509
|
+
targetNode.appendChild(node);
|
|
510
|
+
childElements.push(wrapped);
|
|
511
|
+
} else {
|
|
512
|
+
targetNode.appendChild(node);
|
|
513
|
+
childElements.push(child);
|
|
514
|
+
}
|
|
515
|
+
} else if (isShadowDOMMarker(child)) {
|
|
516
|
+
if (targetNode instanceof ShadowRoot) {
|
|
517
|
+
console.warn("Lightview: Cannot nest shadowDOM inside another shadowDOM");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
processShadowDOM(child, targetNode);
|
|
521
|
+
} else if (child && typeof child === "object" && child.__xpath__ && child.__static__) {
|
|
522
|
+
const textNode = document.createTextNode("");
|
|
523
|
+
textNode.__xpathExpr = child.__xpath__;
|
|
524
|
+
targetNode.appendChild(textNode);
|
|
525
|
+
childElements.push(child);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
walk(children);
|
|
529
|
+
return childElements;
|
|
530
|
+
};
|
|
531
|
+
const setupChildrenInTarget = (children, targetNode) => {
|
|
532
|
+
return processChildren(children, targetNode, false);
|
|
533
|
+
};
|
|
534
|
+
const setupChildren = (children, domNode) => {
|
|
535
|
+
return processChildren(children, domNode, true);
|
|
536
|
+
};
|
|
537
|
+
const enhance = (selectorOrNode, options = {}) => {
|
|
538
|
+
const domNode = typeof selectorOrNode === "string" ? document.querySelector(selectorOrNode) : selectorOrNode;
|
|
539
|
+
const node = domNode.domEl || domNode;
|
|
540
|
+
if (!node || node.nodeType !== 1) return null;
|
|
541
|
+
const tagName = node.tagName.toLowerCase();
|
|
542
|
+
let el = domToElement.get(node);
|
|
543
|
+
if (!el) {
|
|
544
|
+
el = wrapDomElement(node, tagName);
|
|
545
|
+
}
|
|
546
|
+
const { innerText, innerHTML, ...attrs } = options;
|
|
547
|
+
if (innerText !== void 0) {
|
|
548
|
+
if (typeof innerText === "function") {
|
|
549
|
+
effect(() => {
|
|
550
|
+
node.innerText = innerText();
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
node.innerText = innerText;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (innerHTML !== void 0) {
|
|
557
|
+
if (typeof innerHTML === "function") {
|
|
558
|
+
effect(() => {
|
|
559
|
+
node.innerHTML = innerHTML();
|
|
560
|
+
});
|
|
561
|
+
} else {
|
|
562
|
+
node.innerHTML = innerHTML;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (Object.keys(attrs).length > 0) {
|
|
566
|
+
el.attributes = attrs;
|
|
567
|
+
}
|
|
568
|
+
return el;
|
|
569
|
+
};
|
|
570
|
+
const $ = (cssSelectorOrElement, startingDomEl = document.body) => {
|
|
571
|
+
const el = typeof cssSelectorOrElement === "string" ? startingDomEl.querySelector(cssSelectorOrElement) : cssSelectorOrElement;
|
|
572
|
+
if (!el) return null;
|
|
573
|
+
Object.defineProperty(el, "content", {
|
|
574
|
+
value(child, location = "inner") {
|
|
575
|
+
location = location.toLowerCase();
|
|
576
|
+
Lightview.tags;
|
|
577
|
+
const isSpecialElement = el.tagName && (el.tagName.toLowerCase() === "script" || el.tagName.toLowerCase() === "style");
|
|
578
|
+
const array = (Array.isArray(child) ? child : [child]).map((item) => {
|
|
579
|
+
if (Lightview.hooks.processChild && !isSpecialElement) {
|
|
580
|
+
item = Lightview.hooks.processChild(item) ?? item;
|
|
581
|
+
}
|
|
582
|
+
if (item.tag && !item.domEl) {
|
|
583
|
+
return element(item.tag, item.attributes || {}, item.children || []).domEl;
|
|
584
|
+
} else {
|
|
585
|
+
return item.domEl || item;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
const target = location === "shadow" ? el.shadowRoot || el.attachShadow({ mode: "open" }) : el;
|
|
589
|
+
if (location === "inner" || location === "shadow") {
|
|
590
|
+
target.replaceChildren(...array);
|
|
591
|
+
} else if (location === "outer") {
|
|
592
|
+
target.replaceWith(...array);
|
|
593
|
+
} else if (location === "afterbegin") {
|
|
594
|
+
target.prepend(...array);
|
|
595
|
+
} else if (location === "beforeend") {
|
|
596
|
+
target.append(...array);
|
|
597
|
+
} else {
|
|
598
|
+
array.forEach((item) => el.insertAdjacentElement(location, item));
|
|
599
|
+
}
|
|
600
|
+
return el;
|
|
601
|
+
},
|
|
602
|
+
configurable: true,
|
|
603
|
+
writable: true
|
|
604
|
+
});
|
|
605
|
+
return el;
|
|
606
|
+
};
|
|
607
|
+
const customTags = {};
|
|
608
|
+
const tags = new Proxy({}, {
|
|
609
|
+
get(_, tag) {
|
|
610
|
+
if (tag === "_customTags") return { ...customTags };
|
|
611
|
+
const wrapper = (...args) => {
|
|
612
|
+
let attributes = {};
|
|
613
|
+
let children = args;
|
|
614
|
+
const arg0 = args[0];
|
|
615
|
+
if (args.length > 0 && arg0 && typeof arg0 === "object" && !arg0.tag && !arg0.domEl && !Array.isArray(arg0)) {
|
|
616
|
+
attributes = arg0;
|
|
617
|
+
children = args.slice(1);
|
|
618
|
+
}
|
|
619
|
+
return element(customTags[tag] || tag, attributes, children);
|
|
620
|
+
};
|
|
621
|
+
if (customTags[tag]) {
|
|
622
|
+
Object.assign(wrapper, customTags[tag]);
|
|
623
|
+
}
|
|
624
|
+
return wrapper;
|
|
625
|
+
},
|
|
626
|
+
set(_, tag, value) {
|
|
627
|
+
customTags[tag] = value;
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
const Lightview = {
|
|
632
|
+
registerSchema: (name, definition) => internals.schemas.set(name, definition),
|
|
633
|
+
signal,
|
|
634
|
+
get: signal.get,
|
|
635
|
+
computed,
|
|
636
|
+
effect,
|
|
637
|
+
registry,
|
|
638
|
+
element,
|
|
639
|
+
// do not document this
|
|
640
|
+
enhance,
|
|
641
|
+
tags,
|
|
642
|
+
$,
|
|
643
|
+
// Extension hooks
|
|
644
|
+
hooks: {
|
|
645
|
+
onNonStandardHref: null,
|
|
646
|
+
processChild: null,
|
|
647
|
+
processAttribute: null,
|
|
648
|
+
validateUrl: null,
|
|
649
|
+
validate: (value, schema) => internals.hooks.validate(value, schema)
|
|
650
|
+
},
|
|
651
|
+
// Internals exposed for extensions
|
|
652
|
+
internals: {
|
|
653
|
+
core,
|
|
654
|
+
domToElement,
|
|
655
|
+
wrapDomElement,
|
|
656
|
+
setupChildren,
|
|
657
|
+
trackEffect,
|
|
658
|
+
saveScrolls,
|
|
659
|
+
restoreScrolls,
|
|
660
|
+
localRegistries: internals.localRegistries,
|
|
661
|
+
futureSignals: internals.futureSignals,
|
|
662
|
+
schemas: internals.schemas,
|
|
663
|
+
parents: internals.parents,
|
|
664
|
+
hooks: internals.hooks
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
668
|
+
module.exports = Lightview;
|
|
669
|
+
}
|
|
670
|
+
if (typeof window !== "undefined") {
|
|
671
|
+
globalThis.Lightview = Lightview;
|
|
672
|
+
globalThis.addEventListener("click", (e) => {
|
|
673
|
+
const path = e.composedPath();
|
|
674
|
+
const link = path.find((el) => {
|
|
675
|
+
var _a, _b;
|
|
676
|
+
return el.tagName === "A" && ((_b = (_a = el.getAttribute) == null ? void 0 : _a.call(el, "href")) == null ? void 0 : _b.startsWith("#"));
|
|
677
|
+
});
|
|
678
|
+
if (link && !e.defaultPrevented) {
|
|
679
|
+
const href = link.getAttribute("href");
|
|
680
|
+
if (href.length > 1) {
|
|
681
|
+
const id = href.slice(1);
|
|
682
|
+
const root = link.getRootNode();
|
|
683
|
+
const target = (root.getElementById ? root.getElementById(id) : null) || (root.querySelector ? root.querySelector(`#${id}`) : null);
|
|
684
|
+
if (target) {
|
|
685
|
+
e.preventDefault();
|
|
686
|
+
requestAnimationFrame(() => {
|
|
687
|
+
requestAnimationFrame(() => {
|
|
688
|
+
target.style.scrollMarginTop = "calc(var(--site-nav-height, 0px) + 2rem)";
|
|
689
|
+
target.scrollIntoView({ behavior: "smooth", block: "start", inline: "start" });
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (Lightview.hooks.onNonStandardHref) {
|
|
696
|
+
Lightview.hooks.onNonStandardHref(e);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
if (typeof MutationObserver !== "undefined") {
|
|
700
|
+
const walkNodes = (node, fn) => {
|
|
701
|
+
var _a;
|
|
702
|
+
fn(node);
|
|
703
|
+
(_a = node.childNodes) == null ? void 0 : _a.forEach((n) => walkNodes(n, fn));
|
|
704
|
+
if (node.shadowRoot) walkNodes(node.shadowRoot, fn);
|
|
705
|
+
};
|
|
706
|
+
const cleanupNode = (node) => walkNodes(node, (n) => {
|
|
707
|
+
var _a, _b;
|
|
708
|
+
const s = nodeState.get(n);
|
|
709
|
+
if (s) {
|
|
710
|
+
(_a = s.effects) == null ? void 0 : _a.forEach((e) => e.stop());
|
|
711
|
+
(_b = s.onunmount) == null ? void 0 : _b.call(s, n);
|
|
712
|
+
nodeState.delete(n);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
const mountNode = (node) => walkNodes(node, (n) => {
|
|
716
|
+
var _a, _b;
|
|
717
|
+
(_b = (_a = nodeState.get(n)) == null ? void 0 : _a.onmount) == null ? void 0 : _b.call(_a, n);
|
|
718
|
+
});
|
|
719
|
+
const observer = new MutationObserver((mutations) => {
|
|
720
|
+
mutations.forEach((mutation) => {
|
|
721
|
+
mutation.removedNodes.forEach(cleanupNode);
|
|
722
|
+
mutation.addedNodes.forEach(mountNode);
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
const startObserving = () => {
|
|
726
|
+
if (document.body) {
|
|
727
|
+
observer.observe(document.body, {
|
|
728
|
+
childList: true,
|
|
729
|
+
subtree: true
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
if (document.readyState === "loading") {
|
|
734
|
+
document.addEventListener("DOMContentLoaded", startObserving);
|
|
735
|
+
} else {
|
|
736
|
+
startObserving();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
})();
|