lightview 2.3.8 → 2.4.4
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 +259 -0
- package/README.md +35 -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/benchmarks/tagged-fragment.js +36 -0
- 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 +644 -0
- package/docs/getting-started/index.html +2 -2
- package/docs/hypermedia/index.html +391 -0
- package/docs/hypermedia/nav.html +17 -0
- package/docs/index.html +128 -18
- 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
package/index.html
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
<div class="nav-links">
|
|
33
33
|
<a href="/docs/" class="nav-link">Home</a>
|
|
34
34
|
<a href="/docs/getting-started/" class="nav-link">Get Started</a>
|
|
35
|
+
<a href="/docs/hypermedia/" class="nav-link">Hypermedia</a>
|
|
35
36
|
<a href="/docs/api/" class="nav-link">API</a>
|
|
36
37
|
<a href="/docs/styles/" class="nav-link">Styles</a>
|
|
37
38
|
<a href="/docs/components/" class="nav-link">Components</a>
|
|
@@ -39,7 +40,6 @@
|
|
|
39
40
|
<a href="/docs/cdom.html" class="nav-link">cDOM</a>
|
|
40
41
|
<!--a href="/docs/playground/" class="nav-link">Playground</a>-->
|
|
41
42
|
|
|
42
|
-
<a href="/docs/about/" class="nav-link">About</a>
|
|
43
43
|
<a href="https://github.com/anywhichway/lightview" class="nav-link nav-link-external" target="_blank"
|
|
44
44
|
rel="noopener">
|
|
45
45
|
GitHub
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
</svg>
|
|
50
50
|
</a>
|
|
51
51
|
</div>
|
|
52
|
+
<!-- Moved to components gallery per user request -->
|
|
52
53
|
<div class="nav-actions">
|
|
53
|
-
<div id="theme-toggle-container"></div>
|
|
54
54
|
</div>
|
|
55
55
|
</nav>
|
|
56
56
|
|
|
@@ -74,8 +74,12 @@
|
|
|
74
74
|
<div class="footer-column">
|
|
75
75
|
<h4>Docs</h4>
|
|
76
76
|
<a href="/docs/getting-started/">Getting Started</a>
|
|
77
|
+
<a href="/docs/hypermedia/">Hypermedia</a>
|
|
77
78
|
<a href="/docs/api/">API Reference</a>
|
|
79
|
+
<a href="/docs/styles/">Styles</a>
|
|
78
80
|
<a href="/docs/components">Components</a>
|
|
81
|
+
<a href="/docs/router/">Router</a>
|
|
82
|
+
<a href="/docs/cdom.html">cDOM</a>
|
|
79
83
|
</div>
|
|
80
84
|
<div class="footer-column">
|
|
81
85
|
<h4>Resources</h4>
|
|
@@ -174,6 +178,18 @@
|
|
|
174
178
|
// Initialize
|
|
175
179
|
mobileNav.init();
|
|
176
180
|
|
|
181
|
+
// SCROLL RESTORATION: Capture clicked link IDs
|
|
182
|
+
document.addEventListener('click', (e) => {
|
|
183
|
+
const link = e.target.closest('a');
|
|
184
|
+
if (link && link.id) {
|
|
185
|
+
const currentState = history.state || {};
|
|
186
|
+
const updatedState = { ...currentState, lastClickedId: link.id };
|
|
187
|
+
history.replaceState(updatedState, '', location.href);
|
|
188
|
+
}
|
|
189
|
+
}, {
|
|
190
|
+
capture: true
|
|
191
|
+
});
|
|
192
|
+
|
|
177
193
|
(async function () {
|
|
178
194
|
// Initialize components with shadow DOM enabled by default
|
|
179
195
|
//await LightviewX.initComponents({ shadowDefault: true });
|
|
@@ -182,14 +198,14 @@
|
|
|
182
198
|
const contentEl = document.getElementById('content');
|
|
183
199
|
const navLinks = document.querySelectorAll('.nav-link');
|
|
184
200
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
201
|
+
// Theme Selector Initializer
|
|
202
|
+
globalThis.initThemeSelector = (container) => {
|
|
203
|
+
if (!container) return;
|
|
204
|
+
if (container.querySelector('select')) return; // Already initialized
|
|
205
|
+
|
|
188
206
|
const themes = ['light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset'];
|
|
189
|
-
// Get current from document attribute (set by lightview-x auto-loader) or storage
|
|
190
207
|
const currentTheme = document.documentElement.getAttribute('data-theme') || localStorage.getItem('lightview-theme') || 'light';
|
|
191
208
|
|
|
192
|
-
// Simple select styling
|
|
193
209
|
const select = document.createElement('select');
|
|
194
210
|
Object.assign(select.style, {
|
|
195
211
|
padding: '0.25rem',
|
|
@@ -213,8 +229,11 @@
|
|
|
213
229
|
LightviewX.setTheme(e.target.value);
|
|
214
230
|
});
|
|
215
231
|
|
|
216
|
-
|
|
217
|
-
}
|
|
232
|
+
container.appendChild(select);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Setup Theme Selector if container exists (e.g. if we add it back to header later)
|
|
236
|
+
initThemeSelector(document.getElementById('theme-toggle-container'));
|
|
218
237
|
|
|
219
238
|
// Helper: Update active nav link
|
|
220
239
|
function updateActiveNav(path) {
|
|
@@ -269,7 +288,35 @@
|
|
|
269
288
|
// On Response - called AFTER auto-render for post-render logic
|
|
270
289
|
onResponse: (response, path) => {
|
|
271
290
|
if (globalThis.closeMobileNav) globalThis.closeMobileNav(); // Ensure menu closes on navigation
|
|
272
|
-
|
|
291
|
+
|
|
292
|
+
// Inject Theme Selector into Components Gallery if present
|
|
293
|
+
const galleryHeader = contentEl.querySelector('.gallery-header');
|
|
294
|
+
if (galleryHeader && !galleryHeader.querySelector('#theme-toggle-container')) {
|
|
295
|
+
const container = document.createElement('div');
|
|
296
|
+
container.id = 'theme-toggle-container';
|
|
297
|
+
container.style.marginLeft = 'auto';
|
|
298
|
+
galleryHeader.appendChild(container);
|
|
299
|
+
if (globalThis.initThemeSelector) {
|
|
300
|
+
globalThis.initThemeSelector(container);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Smart Scroll Restoration
|
|
305
|
+
const restoreId = history.state?.lastClickedId;
|
|
306
|
+
const targetEl = restoreId ? document.getElementById(restoreId) : null;
|
|
307
|
+
// Check the destination PATH for a hash, not window.location (which might be stale)
|
|
308
|
+
const hasHash = path.includes('#');
|
|
309
|
+
|
|
310
|
+
if (targetEl) {
|
|
311
|
+
requestAnimationFrame(() => {
|
|
312
|
+
targetEl.scrollIntoView({
|
|
313
|
+
block: 'center',
|
|
314
|
+
behavior: 'smooth'
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
} else if (!hasHash) {
|
|
318
|
+
globalThis.scrollTo(0, 0);
|
|
319
|
+
}
|
|
273
320
|
}
|
|
274
321
|
});
|
|
275
322
|
|
|
@@ -284,6 +331,8 @@
|
|
|
284
331
|
appRouter.use('/docs/components/', '/docs/components/index.html');
|
|
285
332
|
appRouter.use('/docs/examples/', '/docs/examples/index.html');
|
|
286
333
|
appRouter.use('/docs/router/', '/docs/router.html');
|
|
334
|
+
appRouter.use('/docs/hypermedia/', '/docs/hypermedia/index.html');
|
|
335
|
+
appRouter.use('/docs/hypermedia/index.html');
|
|
287
336
|
appRouter.use('/docs/cdom.html');
|
|
288
337
|
|
|
289
338
|
// Wildcard Routes - path replacement with automatic fetch
|
package/lightview-all.js
CHANGED
|
@@ -459,6 +459,7 @@
|
|
|
459
459
|
tag,
|
|
460
460
|
attributes,
|
|
461
461
|
children,
|
|
462
|
+
isProxy: true,
|
|
462
463
|
get domEl() {
|
|
463
464
|
return domNode;
|
|
464
465
|
}
|
|
@@ -467,6 +468,10 @@
|
|
|
467
468
|
domToElement.set(domNode, proxy);
|
|
468
469
|
return proxy;
|
|
469
470
|
};
|
|
471
|
+
const someRecursive = (item, predicate) => {
|
|
472
|
+
if (Array.isArray(item)) return item.some((i) => someRecursive(i, predicate));
|
|
473
|
+
return predicate(item);
|
|
474
|
+
};
|
|
470
475
|
const element = (tag, attributes = {}, children = []) => {
|
|
471
476
|
if (customTags[tag]) tag = customTags[tag];
|
|
472
477
|
if (typeof tag === "function") {
|
|
@@ -487,38 +492,56 @@
|
|
|
487
492
|
}
|
|
488
493
|
};
|
|
489
494
|
const update = () => {
|
|
490
|
-
const
|
|
491
|
-
const
|
|
495
|
+
const bits = [];
|
|
496
|
+
const walk = (c) => {
|
|
497
|
+
if (Array.isArray(c)) {
|
|
498
|
+
for (let i = 0; i < c.length; i++) walk(c[i]);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
492
501
|
const val = typeof c === "function" ? c() : c;
|
|
493
|
-
if (val && typeof val === "object" && val.domEl)
|
|
494
|
-
|
|
495
|
-
}
|
|
502
|
+
if (val && typeof val === "object" && val.domEl) bits.push(val.domEl.textContent);
|
|
503
|
+
else bits.push(val === null || val === void 0 ? "" : String(val));
|
|
504
|
+
};
|
|
505
|
+
walk(el.children);
|
|
496
506
|
domNode2.textContent = bits.join(" ");
|
|
497
507
|
};
|
|
498
|
-
const
|
|
508
|
+
const proxy = new Proxy(el, {
|
|
499
509
|
set(target, prop, value) {
|
|
500
510
|
target[prop] = value;
|
|
501
511
|
if (prop === "children") update();
|
|
502
512
|
return true;
|
|
503
513
|
}
|
|
504
514
|
});
|
|
505
|
-
const hasReactive = children
|
|
515
|
+
const hasReactive = someRecursive(children, (c) => typeof c === "function");
|
|
506
516
|
if (hasReactive) {
|
|
507
517
|
const runner = effect(update);
|
|
508
518
|
trackEffect(domNode2, runner);
|
|
509
519
|
}
|
|
510
520
|
update();
|
|
511
|
-
return
|
|
521
|
+
return proxy;
|
|
512
522
|
}
|
|
513
523
|
const isSVG = tag.toLowerCase() === "svg";
|
|
514
524
|
const wasInSVG = inSVG;
|
|
515
525
|
if (isSVG) inSVG = true;
|
|
516
526
|
const domNode = inSVG ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
527
|
+
const hasReactiveAttr = Object.values(attributes).some((v) => typeof v === "function");
|
|
528
|
+
const hasReactiveChild = someRecursive(children, (c) => typeof c === "function" || c && c.isProxy);
|
|
529
|
+
if (hasReactiveAttr || hasReactiveChild) {
|
|
530
|
+
const proxy = wrapDomElement(domNode, tag, attributes, children);
|
|
531
|
+
proxy.attributes = attributes;
|
|
532
|
+
proxy.children = children;
|
|
533
|
+
if (isSVG) inSVG = wasInSVG;
|
|
534
|
+
return proxy;
|
|
535
|
+
}
|
|
536
|
+
makeReactiveAttributes(attributes, domNode);
|
|
537
|
+
setupChildren(children, domNode);
|
|
520
538
|
if (isSVG) inSVG = wasInSVG;
|
|
521
|
-
return
|
|
539
|
+
return {
|
|
540
|
+
tag,
|
|
541
|
+
attributes,
|
|
542
|
+
children,
|
|
543
|
+
domEl: domNode
|
|
544
|
+
};
|
|
522
545
|
};
|
|
523
546
|
const processComponentResult = (result) => {
|
|
524
547
|
if (!result) return null;
|
|
@@ -527,7 +550,7 @@
|
|
|
527
550
|
}
|
|
528
551
|
if (result.domEl) return result;
|
|
529
552
|
const type = typeof result;
|
|
530
|
-
if (type === "object" && result
|
|
553
|
+
if (type === "object" && result && result.nodeType === 1) {
|
|
531
554
|
return wrapDomElement(result, result.tagName.toLowerCase(), {}, []);
|
|
532
555
|
}
|
|
533
556
|
if (type === "object" && result instanceof String) {
|
|
@@ -539,7 +562,7 @@
|
|
|
539
562
|
const template = document.createElement("template");
|
|
540
563
|
template.innerHTML = result.trim();
|
|
541
564
|
const content = template.content;
|
|
542
|
-
if (content.childNodes.length === 1 && content.firstChild
|
|
565
|
+
if (content.childNodes.length === 1 && content.firstChild && content.firstChild.nodeType === 1) {
|
|
543
566
|
const el = content.firstChild;
|
|
544
567
|
return wrapDomElement(el, el.tagName.toLowerCase(), {}, []);
|
|
545
568
|
} else {
|
|
@@ -584,10 +607,12 @@
|
|
|
584
607
|
domNode.setAttribute(key, value);
|
|
585
608
|
}
|
|
586
609
|
};
|
|
587
|
-
const makeReactiveAttributes = (attributes, domNode) => {
|
|
610
|
+
const makeReactiveAttributes = (attributes = {}, domNode) => {
|
|
588
611
|
const reactiveAttrs = {};
|
|
589
|
-
for (
|
|
590
|
-
|
|
612
|
+
for (const key in attributes) {
|
|
613
|
+
const value = attributes[key];
|
|
614
|
+
const type = typeof value;
|
|
615
|
+
if (value && type === "object" && value.__xpath__ && value.__static__) {
|
|
591
616
|
domNode.setAttribute(`data-xpath-${key}`, value.__xpath__);
|
|
592
617
|
reactiveAttrs[key] = value;
|
|
593
618
|
continue;
|
|
@@ -599,10 +624,9 @@
|
|
|
599
624
|
value(domNode);
|
|
600
625
|
}
|
|
601
626
|
} else if (key.startsWith("on")) {
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
} else if (typeof value === "string") {
|
|
627
|
+
if (type === "function") {
|
|
628
|
+
domNode.addEventListener(key.slice(2).toLowerCase(), value);
|
|
629
|
+
} else if (type === "string") {
|
|
606
630
|
domNode.setAttribute(key, value);
|
|
607
631
|
}
|
|
608
632
|
reactiveAttrs[key] = value;
|
|
@@ -611,7 +635,8 @@
|
|
|
611
635
|
if (processed !== void 0) {
|
|
612
636
|
reactiveAttrs[key] = processed;
|
|
613
637
|
} else if (key === "style") {
|
|
614
|
-
|
|
638
|
+
for (const styleKey in entries) {
|
|
639
|
+
const styleValue = entries[styleKey];
|
|
615
640
|
if (typeof styleValue === "function") {
|
|
616
641
|
const runner = effect(() => {
|
|
617
642
|
domNode.style[styleKey] = styleValue();
|
|
@@ -620,13 +645,13 @@
|
|
|
620
645
|
} else {
|
|
621
646
|
domNode.style[styleKey] = styleValue;
|
|
622
647
|
}
|
|
623
|
-
}
|
|
648
|
+
}
|
|
624
649
|
reactiveAttrs[key] = value;
|
|
625
650
|
} else {
|
|
626
651
|
setAttributeValue(domNode, key, value);
|
|
627
652
|
reactiveAttrs[key] = value;
|
|
628
653
|
}
|
|
629
|
-
} else if (
|
|
654
|
+
} else if (type === "function") {
|
|
630
655
|
const runner = effect(() => {
|
|
631
656
|
const result = value();
|
|
632
657
|
if (key === "style" && typeof result === "object") {
|
|
@@ -650,21 +675,24 @@
|
|
|
650
675
|
}
|
|
651
676
|
const childElements = [];
|
|
652
677
|
const isSpecialElement = targetNode.tagName && (targetNode.tagName.toLowerCase() === "script" || targetNode.tagName.toLowerCase() === "style");
|
|
653
|
-
const
|
|
654
|
-
|
|
678
|
+
const walk = (child) => {
|
|
679
|
+
if (Array.isArray(child)) {
|
|
680
|
+
for (let i = 0; i < child.length; i++) walk(child[i]);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (child === null || child === void 0) return;
|
|
655
684
|
if (Lightview.hooks.processChild && !isSpecialElement) {
|
|
656
685
|
child = Lightview.hooks.processChild(child) ?? child;
|
|
657
686
|
}
|
|
658
|
-
if (isShadowDOMMarker(child)) {
|
|
659
|
-
if (targetNode instanceof ShadowRoot) {
|
|
660
|
-
console.warn("Lightview: Cannot nest shadowDOM inside another shadowDOM");
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
processShadowDOM(child, targetNode);
|
|
664
|
-
continue;
|
|
665
|
-
}
|
|
666
687
|
const type = typeof child;
|
|
667
|
-
if (type === "
|
|
688
|
+
if (child && type === "object" && child.tag) {
|
|
689
|
+
const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
|
|
690
|
+
targetNode.appendChild(childEl.domEl);
|
|
691
|
+
childElements.push(childEl);
|
|
692
|
+
} else if (["string", "number", "boolean", "symbol"].includes(type) || child && type === "object" && child instanceof String) {
|
|
693
|
+
targetNode.appendChild(document.createTextNode(child));
|
|
694
|
+
childElements.push(child);
|
|
695
|
+
} else if (type === "function") {
|
|
668
696
|
const startMarker = document.createComment("lv:s");
|
|
669
697
|
const endMarker = document.createComment("lv:e");
|
|
670
698
|
targetNode.appendChild(startMarker);
|
|
@@ -693,17 +721,9 @@
|
|
|
693
721
|
runner = effect(update);
|
|
694
722
|
trackEffect(startMarker, runner);
|
|
695
723
|
childElements.push(child);
|
|
696
|
-
} else if (child && typeof child === "object" && child.__xpath__ && child.__static__) {
|
|
697
|
-
const textNode = document.createTextNode("");
|
|
698
|
-
textNode.__xpathExpr = child.__xpath__;
|
|
699
|
-
targetNode.appendChild(textNode);
|
|
700
|
-
childElements.push(child);
|
|
701
|
-
} else if (["string", "number", "boolean", "symbol"].includes(type) || child && type === "object" && child instanceof String) {
|
|
702
|
-
targetNode.appendChild(document.createTextNode(child));
|
|
703
|
-
childElements.push(child);
|
|
704
724
|
} else if (child instanceof Node) {
|
|
705
725
|
const node = child.domEl || child;
|
|
706
|
-
if (node
|
|
726
|
+
if (node.nodeType === 1) {
|
|
707
727
|
const wrapped = wrapDomElement(node, node.tagName.toLowerCase());
|
|
708
728
|
targetNode.appendChild(node);
|
|
709
729
|
childElements.push(wrapped);
|
|
@@ -711,12 +731,20 @@
|
|
|
711
731
|
targetNode.appendChild(node);
|
|
712
732
|
childElements.push(child);
|
|
713
733
|
}
|
|
714
|
-
} else if (child
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
734
|
+
} else if (isShadowDOMMarker(child)) {
|
|
735
|
+
if (targetNode instanceof ShadowRoot) {
|
|
736
|
+
console.warn("Lightview: Cannot nest shadowDOM inside another shadowDOM");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
processShadowDOM(child, targetNode);
|
|
740
|
+
} else if (child && typeof child === "object" && child.__xpath__ && child.__static__) {
|
|
741
|
+
const textNode = document.createTextNode("");
|
|
742
|
+
textNode.__xpathExpr = child.__xpath__;
|
|
743
|
+
targetNode.appendChild(textNode);
|
|
744
|
+
childElements.push(child);
|
|
718
745
|
}
|
|
719
|
-
}
|
|
746
|
+
};
|
|
747
|
+
walk(children);
|
|
720
748
|
return childElements;
|
|
721
749
|
};
|
|
722
750
|
const setupChildrenInTarget = (children, targetNode) => {
|
|
@@ -728,7 +756,7 @@
|
|
|
728
756
|
const enhance = (selectorOrNode, options = {}) => {
|
|
729
757
|
const domNode = typeof selectorOrNode === "string" ? document.querySelector(selectorOrNode) : selectorOrNode;
|
|
730
758
|
const node = domNode.domEl || domNode;
|
|
731
|
-
if (!
|
|
759
|
+
if (!node || node.nodeType !== 1) return null;
|
|
732
760
|
const tagName = node.tagName.toLowerCase();
|
|
733
761
|
let el = domToElement.get(node);
|
|
734
762
|
if (!el) {
|
|
@@ -820,8 +848,6 @@
|
|
|
820
848
|
}
|
|
821
849
|
});
|
|
822
850
|
const Lightview = {
|
|
823
|
-
state: state$1,
|
|
824
|
-
getState,
|
|
825
851
|
registerSchema: (name, definition) => internals.schemas.set(name, definition),
|
|
826
852
|
signal: signal$1,
|
|
827
853
|
get: signal$1.get,
|
|
@@ -1278,16 +1304,92 @@
|
|
|
1278
1304
|
executeScripts(target);
|
|
1279
1305
|
};
|
|
1280
1306
|
const isPath = (s) => typeof s === "string" && !isDangerousProtocol(s) && /^(https?:|\.|\/|[\w])|(\.(html|json|[vo]dom|cdomc?))$/i.test(s);
|
|
1281
|
-
const
|
|
1307
|
+
const getRequestInfo = (el) => {
|
|
1308
|
+
const domEl = el.domEl || el;
|
|
1309
|
+
const method = (domEl.getAttribute("data-method") || "GET").toUpperCase();
|
|
1310
|
+
const bodyAttr = domEl.getAttribute("data-body");
|
|
1311
|
+
let body = null;
|
|
1312
|
+
let headers = {};
|
|
1313
|
+
if (bodyAttr) {
|
|
1314
|
+
if (bodyAttr.startsWith("javascript:")) {
|
|
1315
|
+
const expr = bodyAttr.slice(11);
|
|
1316
|
+
const LV = globalThis.Lightview;
|
|
1317
|
+
try {
|
|
1318
|
+
body = new Function("state", "signal", `return ${expr}`)(LV.state || {}, LV.signal || {});
|
|
1319
|
+
} catch (e) {
|
|
1320
|
+
console.warn(`[LightviewX] Failed to evaluate data-body expression: ${expr}`, e);
|
|
1321
|
+
}
|
|
1322
|
+
} else if (bodyAttr.startsWith("json:")) {
|
|
1323
|
+
try {
|
|
1324
|
+
body = JSON.parse(bodyAttr.slice(5));
|
|
1325
|
+
headers["Content-Type"] = "application/json";
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
console.warn(`[LightviewX] Failed to parse data-body JSON: ${bodyAttr.slice(5)}`, e);
|
|
1328
|
+
}
|
|
1329
|
+
} else if (bodyAttr.startsWith("text:")) {
|
|
1330
|
+
body = bodyAttr.slice(5);
|
|
1331
|
+
headers["Content-Type"] = "text/plain";
|
|
1332
|
+
} else {
|
|
1333
|
+
try {
|
|
1334
|
+
const target = document.querySelector(bodyAttr);
|
|
1335
|
+
if (target) {
|
|
1336
|
+
if (target.tagName === "FORM") {
|
|
1337
|
+
body = new FormData(target);
|
|
1338
|
+
} else if (["INPUT", "SELECT", "TEXTAREA"].includes(target.tagName)) {
|
|
1339
|
+
const name = target.getAttribute("name") || "body";
|
|
1340
|
+
body = { [name]: target.value };
|
|
1341
|
+
} else {
|
|
1342
|
+
body = target.innerText;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
} catch (e) {
|
|
1346
|
+
body = bodyAttr;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return { method, body, headers };
|
|
1351
|
+
};
|
|
1352
|
+
const fetchContent = async (src, requestOptions = {}) => {
|
|
1282
1353
|
var _a2;
|
|
1354
|
+
const { method = "GET", body = null, headers = {} } = requestOptions;
|
|
1283
1355
|
try {
|
|
1284
1356
|
const LV = globalThis.Lightview;
|
|
1285
1357
|
if (((_a2 = LV == null ? void 0 : LV.hooks) == null ? void 0 : _a2.validateUrl) && !LV.hooks.validateUrl(src)) {
|
|
1286
1358
|
console.warn(`[LightviewX] Fetch blocked by validateUrl hook: ${src}`);
|
|
1287
1359
|
return null;
|
|
1288
1360
|
}
|
|
1289
|
-
|
|
1290
|
-
const
|
|
1361
|
+
let url = new URL(src, document.baseURI);
|
|
1362
|
+
const fetchOptions = { method, headers: { ...headers } };
|
|
1363
|
+
if (body) {
|
|
1364
|
+
if (method === "GET") {
|
|
1365
|
+
const params = new URLSearchParams(url.search);
|
|
1366
|
+
if (body instanceof FormData) {
|
|
1367
|
+
for (const [key, value] of body.entries()) params.append(key, value);
|
|
1368
|
+
} else if (typeof body === "object" && body !== null) {
|
|
1369
|
+
for (const [key, value] of Object.entries(body)) params.append(key, String(value));
|
|
1370
|
+
} else {
|
|
1371
|
+
params.append("body", String(body));
|
|
1372
|
+
}
|
|
1373
|
+
const queryString = params.toString();
|
|
1374
|
+
if (queryString) {
|
|
1375
|
+
url = new URL(`${url.origin}${url.pathname}?${queryString}${url.hash}`, url.origin);
|
|
1376
|
+
}
|
|
1377
|
+
} else {
|
|
1378
|
+
if (body instanceof FormData) {
|
|
1379
|
+
fetchOptions.body = body;
|
|
1380
|
+
} else if (typeof body === "object" && body !== null) {
|
|
1381
|
+
if (headers["Content-Type"] === "application/json" || !headers["Content-Type"]) {
|
|
1382
|
+
fetchOptions.body = JSON.stringify(body);
|
|
1383
|
+
fetchOptions.headers["Content-Type"] = "application/json";
|
|
1384
|
+
} else {
|
|
1385
|
+
fetchOptions.body = String(body);
|
|
1386
|
+
}
|
|
1387
|
+
} else {
|
|
1388
|
+
fetchOptions.body = String(body);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
const res = await fetch(url, fetchOptions);
|
|
1291
1393
|
if (!res.ok) return null;
|
|
1292
1394
|
const ext = url.pathname.split(".").pop().toLowerCase();
|
|
1293
1395
|
const isJson = ext === "vdom" || ext === "odom" || ext === "cdom";
|
|
@@ -1345,8 +1447,12 @@
|
|
|
1345
1447
|
}
|
|
1346
1448
|
};
|
|
1347
1449
|
const updateTargetContent = (el, elements, raw, loc, contentHash, options, targetHash = null) => {
|
|
1348
|
-
var _a2;
|
|
1349
|
-
const { element: element2, setupChildren: setupChildren2, saveScrolls: saveScrolls2, restoreScrolls: restoreScrolls2 } = {
|
|
1450
|
+
var _a2, _b2;
|
|
1451
|
+
const { element: element2, setupChildren: setupChildren2, saveScrolls: saveScrolls2, restoreScrolls: restoreScrolls2 } = {
|
|
1452
|
+
element: (_a2 = globalThis.Lightview) == null ? void 0 : _a2.element,
|
|
1453
|
+
...(_b2 = globalThis.Lightview) == null ? void 0 : _b2.internals,
|
|
1454
|
+
...options
|
|
1455
|
+
};
|
|
1350
1456
|
const markerId = `${loc}-${contentHash.slice(0, 8)}`;
|
|
1351
1457
|
let track = getOrSet(insertedContentMap, el.domEl, () => ({}));
|
|
1352
1458
|
if (track[loc]) removeInsertedContent(el.domEl, `${loc}-${track[loc].slice(0, 8)}`);
|
|
@@ -1392,7 +1498,8 @@
|
|
|
1392
1498
|
if (src.includes("#")) {
|
|
1393
1499
|
[src, targetHash] = src.split("#");
|
|
1394
1500
|
}
|
|
1395
|
-
const
|
|
1501
|
+
const options = getRequestInfo(el);
|
|
1502
|
+
const result = await fetchContent(src, options);
|
|
1396
1503
|
if (result) {
|
|
1397
1504
|
elements = parseElements(result.content, result.isJson, result.isHtml, el, element2, result.isCdom, result.ext);
|
|
1398
1505
|
raw = result.raw;
|
|
@@ -1443,7 +1550,7 @@
|
|
|
1443
1550
|
}
|
|
1444
1551
|
return { selector: targetStr, location: null };
|
|
1445
1552
|
};
|
|
1446
|
-
const handleNonStandardHref = (e, { domToElement: domToElement2, wrapDomElement: wrapDomElement2 }) => {
|
|
1553
|
+
const handleNonStandardHref = async (e, { domToElement: domToElement2, wrapDomElement: wrapDomElement2 }) => {
|
|
1447
1554
|
var _a2;
|
|
1448
1555
|
const clickedEl = e.target.closest("[href]");
|
|
1449
1556
|
if (!clickedEl) return;
|
|
@@ -1457,6 +1564,7 @@
|
|
|
1457
1564
|
return;
|
|
1458
1565
|
}
|
|
1459
1566
|
const targetAttr = clickedEl.getAttribute("target");
|
|
1567
|
+
const options = getRequestInfo(clickedEl);
|
|
1460
1568
|
if (!targetAttr) {
|
|
1461
1569
|
let el = domToElement2.get(clickedEl);
|
|
1462
1570
|
if (!el) {
|
|
@@ -1469,6 +1577,9 @@
|
|
|
1469
1577
|
return;
|
|
1470
1578
|
}
|
|
1471
1579
|
if (targetAttr.startsWith("_")) {
|
|
1580
|
+
if (options.method !== "GET") {
|
|
1581
|
+
console.warn("[LightviewX] Cannot use non-GET method for browser navigation (_blank, _top, etc.)");
|
|
1582
|
+
}
|
|
1472
1583
|
switch (targetAttr) {
|
|
1473
1584
|
case "_self":
|
|
1474
1585
|
globalThis.location.href = href;
|
|
@@ -1489,6 +1600,11 @@
|
|
|
1489
1600
|
const { selector, location } = parseTargetWithLocation(targetAttr);
|
|
1490
1601
|
try {
|
|
1491
1602
|
const targetElements = document.querySelectorAll(selector);
|
|
1603
|
+
if (targetElements.length === 0) return;
|
|
1604
|
+
const result = await fetchContent(href, options);
|
|
1605
|
+
if (!result) return;
|
|
1606
|
+
const { setupChildren: setupChildren2 } = LV.internals;
|
|
1607
|
+
const element2 = LV.element;
|
|
1492
1608
|
targetElements.forEach((targetEl) => {
|
|
1493
1609
|
let el = domToElement2.get(targetEl);
|
|
1494
1610
|
if (!el) {
|
|
@@ -1496,14 +1612,14 @@
|
|
|
1496
1612
|
for (let attr of targetEl.attributes) attrs[attr.name] = attr.value;
|
|
1497
1613
|
el = wrapDomElement2(targetEl, targetEl.tagName.toLowerCase(), attrs);
|
|
1498
1614
|
}
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1615
|
+
const elements = parseElements(result.content, result.isJson, result.isHtml, el, element2, result.isCdom, result.ext);
|
|
1616
|
+
const loc = (location || targetEl.getAttribute("location") || "innerhtml").toLowerCase();
|
|
1617
|
+
const contentHash = hashContent(result.raw);
|
|
1618
|
+
updateTargetContent(el, elements, result.raw, loc, contentHash, { element: element2, setupChildren: setupChildren2 });
|
|
1619
|
+
targetEl.setAttribute("src", href);
|
|
1504
1620
|
});
|
|
1505
1621
|
} catch (err) {
|
|
1506
|
-
console.warn("Invalid target selector:", selector, err);
|
|
1622
|
+
console.warn("Invalid target selector or fetch error:", selector, err);
|
|
1507
1623
|
}
|
|
1508
1624
|
};
|
|
1509
1625
|
const gateStates = /* @__PURE__ */ new WeakMap();
|
|
@@ -1775,6 +1891,8 @@
|
|
|
1775
1891
|
};
|
|
1776
1892
|
if (typeof window !== "undefined" && globalThis.Lightview) {
|
|
1777
1893
|
const LV = globalThis.Lightview;
|
|
1894
|
+
LV.state = state$1;
|
|
1895
|
+
LV.getState = getState;
|
|
1778
1896
|
if (document.readyState === "loading") {
|
|
1779
1897
|
document.addEventListener("DOMContentLoaded", () => setupSrcObserver(LV));
|
|
1780
1898
|
} else {
|
|
@@ -2130,8 +2248,24 @@
|
|
|
2130
2248
|
}
|
|
2131
2249
|
if (globalThis.Lightview) globalThis.Lightview.validate = validateJSONSchema;
|
|
2132
2250
|
}
|
|
2251
|
+
const request = async (el) => {
|
|
2252
|
+
const domEl = el.domEl || el;
|
|
2253
|
+
const href = domEl.getAttribute("href");
|
|
2254
|
+
if (!href) return;
|
|
2255
|
+
return handleNonStandardHref({
|
|
2256
|
+
target: domEl,
|
|
2257
|
+
preventDefault: () => {
|
|
2258
|
+
},
|
|
2259
|
+
stopPropagation: () => {
|
|
2260
|
+
}
|
|
2261
|
+
}, {
|
|
2262
|
+
domToElement: globalThis.Lightview.internals.domToElement,
|
|
2263
|
+
wrapDomElement: globalThis.Lightview.internals.wrapDomElement
|
|
2264
|
+
});
|
|
2265
|
+
};
|
|
2133
2266
|
const LightviewX = {
|
|
2134
2267
|
state: state$1,
|
|
2268
|
+
getState,
|
|
2135
2269
|
themeSignal,
|
|
2136
2270
|
setTheme,
|
|
2137
2271
|
registerStyleSheet,
|
|
@@ -2139,6 +2273,28 @@
|
|
|
2139
2273
|
// Gate modifiers
|
|
2140
2274
|
throttle: gateThrottle,
|
|
2141
2275
|
debounce: gateDebounce,
|
|
2276
|
+
// Hypermedia Actions
|
|
2277
|
+
request,
|
|
2278
|
+
get: (el, url) => {
|
|
2279
|
+
if (url) el.setAttribute("href", url);
|
|
2280
|
+
el.setAttribute("data-method", "GET");
|
|
2281
|
+
return request(el);
|
|
2282
|
+
},
|
|
2283
|
+
post: (el, url) => {
|
|
2284
|
+
if (url) el.setAttribute("href", url);
|
|
2285
|
+
el.setAttribute("data-method", "POST");
|
|
2286
|
+
return request(el);
|
|
2287
|
+
},
|
|
2288
|
+
put: (el, url) => {
|
|
2289
|
+
if (url) el.setAttribute("href", url);
|
|
2290
|
+
el.setAttribute("data-method", "PUT");
|
|
2291
|
+
return request(el);
|
|
2292
|
+
},
|
|
2293
|
+
delete: (el, url) => {
|
|
2294
|
+
if (url) el.setAttribute("href", url);
|
|
2295
|
+
el.setAttribute("data-method", "DELETE");
|
|
2296
|
+
return request(el);
|
|
2297
|
+
},
|
|
2142
2298
|
// Component initialization
|
|
2143
2299
|
initComponents,
|
|
2144
2300
|
componentConfig,
|
|
@@ -2153,6 +2309,7 @@
|
|
|
2153
2309
|
parseElements
|
|
2154
2310
|
}
|
|
2155
2311
|
};
|
|
2312
|
+
if (globalThis.Lightview) globalThis.Lightview.request = request;
|
|
2156
2313
|
if (typeof module !== "undefined" && module.exports) {
|
|
2157
2314
|
module.exports = LightviewX;
|
|
2158
2315
|
}
|
|
@@ -6011,10 +6168,9 @@
|
|
|
6011
6168
|
try {
|
|
6012
6169
|
validateXPath(xpath);
|
|
6013
6170
|
const doc = globalThis.document || node.ownerDocument;
|
|
6014
|
-
const contextNode = node.parentNode || node;
|
|
6015
6171
|
const result = doc.evaluate(
|
|
6016
6172
|
xpath,
|
|
6017
|
-
|
|
6173
|
+
node,
|
|
6018
6174
|
null,
|
|
6019
6175
|
XPathResult.STRING_TYPE,
|
|
6020
6176
|
null
|
package/lightview-cdom.js
CHANGED
|
@@ -3857,10 +3857,9 @@ var LightviewCDOM = function(exports) {
|
|
|
3857
3857
|
try {
|
|
3858
3858
|
validateXPath(xpath);
|
|
3859
3859
|
const doc = globalThis.document || node.ownerDocument;
|
|
3860
|
-
const contextNode = node.parentNode || node;
|
|
3861
3860
|
const result = doc.evaluate(
|
|
3862
3861
|
xpath,
|
|
3863
|
-
|
|
3862
|
+
node,
|
|
3864
3863
|
null,
|
|
3865
3864
|
XPathResult.STRING_TYPE,
|
|
3866
3865
|
null
|