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.
Files changed (38) hide show
  1. package/.gemini/CODE_ANALYSIS_AND_IMPROVEMENT_PLAN.md +56 -0
  2. package/AI-GUIDANCE.md +259 -0
  3. package/README.md +35 -0
  4. package/components/data-display/diff.js +36 -4
  5. package/docs/api/hypermedia.html +75 -5
  6. package/docs/api/index.html +3 -3
  7. package/docs/api/nav.html +0 -16
  8. package/docs/articles/html-vs-json-partials.md +102 -0
  9. package/docs/articles/lightview-vs-htmx.md +610 -0
  10. package/docs/benchmarks/tagged-fragment.js +36 -0
  11. package/docs/components/chart.html +157 -210
  12. package/docs/components/component-nav.html +1 -1
  13. package/docs/components/diff.html +33 -21
  14. package/docs/components/gallery.html +107 -4
  15. package/docs/components/index.css +18 -3
  16. package/docs/components/index.html +20 -9
  17. package/docs/dom-benchmark.html +644 -0
  18. package/docs/getting-started/index.html +2 -2
  19. package/docs/hypermedia/index.html +391 -0
  20. package/docs/hypermedia/nav.html +17 -0
  21. package/docs/index.html +128 -18
  22. package/index.html +59 -10
  23. package/lightview-all.js +223 -67
  24. package/lightview-cdom.js +1 -2
  25. package/lightview-x.js +144 -13
  26. package/lightview.js +85 -277
  27. package/package.json +2 -2
  28. package/src/lightview-cdom.js +1 -5
  29. package/src/lightview-x.js +158 -27
  30. package/src/lightview.js +94 -60
  31. package/docs/articles/calculator-no-javascript-hackernoon.md +0 -283
  32. package/docs/articles/calculator-no-javascript.md +0 -290
  33. package/docs/articles/part1-reference.md +0 -236
  34. package/lightview.js.bak +0 -1
  35. package/test-xpath.html +0 -63
  36. package/test_error.txt +0 -0
  37. package/test_output.txt +0 -0
  38. 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
- // Setup Theme Selector
186
- const themeContainer = document.getElementById('theme-toggle-container');
187
- if (themeContainer) {
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
- themeContainer.appendChild(select);
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
- globalThis.scrollTo(0, 0);
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 flat = (Array.isArray(el.children) ? el.children : [el.children]).flat(Infinity);
491
- const bits = flat.map((c) => {
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) return val.domEl.textContent;
494
- return val === null || val === void 0 ? "" : String(val);
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 proxy2 = new Proxy(el, {
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.flat(Infinity).some((c) => typeof c === "function");
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 proxy2;
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 proxy = wrapDomElement(domNode, tag, attributes, children);
518
- proxy.attributes = attributes;
519
- proxy.children = children;
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 proxy;
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 instanceof HTMLElement) {
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 instanceof HTMLElement) {
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 (let [key, value] of Object.entries(attributes)) {
590
- if (value && typeof value === "object" && value.__xpath__ && value.__static__) {
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 (typeof value === "function") {
603
- const eventName = key.slice(2).toLowerCase();
604
- domNode.addEventListener(eventName, value);
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
- Object.entries(value).forEach(([styleKey, styleValue]) => {
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 (typeof value === "function") {
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 flatChildren = children.flat(Infinity);
654
- for (let child of flatChildren) {
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 === "function") {
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 instanceof HTMLElement || node instanceof SVGElement) {
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 && type === "object" && child.tag) {
715
- const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
716
- targetNode.appendChild(childEl.domEl);
717
- childElements.push(childEl);
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 (!(node instanceof HTMLElement)) return null;
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 fetchContent = async (src) => {
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
- const url = new URL(src, document.baseURI);
1290
- const res = await fetch(url);
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 } = { ...options, ...(_a2 = globalThis.Lightview) == null ? void 0 : _a2.internals };
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 result = await fetchContent(src);
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 newAttrs = { ...el.attributes, src: href };
1500
- if (location) {
1501
- newAttrs.location = location;
1502
- }
1503
- el.attributes = newAttrs;
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
- contextNode,
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
- contextNode,
3862
+ node,
3864
3863
  null,
3865
3864
  XPathResult.STRING_TYPE,
3866
3865
  null