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/lightview-x.js CHANGED
@@ -705,16 +705,92 @@
705
705
  executeScripts(target);
706
706
  };
707
707
  const isPath = (s) => typeof s === "string" && !isDangerousProtocol(s) && /^(https?:|\.|\/|[\w])|(\.(html|json|[vo]dom|cdomc?))$/i.test(s);
708
- const fetchContent = async (src) => {
708
+ const getRequestInfo = (el) => {
709
+ const domEl = el.domEl || el;
710
+ const method = (domEl.getAttribute("data-method") || "GET").toUpperCase();
711
+ const bodyAttr = domEl.getAttribute("data-body");
712
+ let body = null;
713
+ let headers = {};
714
+ if (bodyAttr) {
715
+ if (bodyAttr.startsWith("javascript:")) {
716
+ const expr = bodyAttr.slice(11);
717
+ const LV = globalThis.Lightview;
718
+ try {
719
+ body = new Function("state", "signal", `return ${expr}`)(LV.state || {}, LV.signal || {});
720
+ } catch (e) {
721
+ console.warn(`[LightviewX] Failed to evaluate data-body expression: ${expr}`, e);
722
+ }
723
+ } else if (bodyAttr.startsWith("json:")) {
724
+ try {
725
+ body = JSON.parse(bodyAttr.slice(5));
726
+ headers["Content-Type"] = "application/json";
727
+ } catch (e) {
728
+ console.warn(`[LightviewX] Failed to parse data-body JSON: ${bodyAttr.slice(5)}`, e);
729
+ }
730
+ } else if (bodyAttr.startsWith("text:")) {
731
+ body = bodyAttr.slice(5);
732
+ headers["Content-Type"] = "text/plain";
733
+ } else {
734
+ try {
735
+ const target = document.querySelector(bodyAttr);
736
+ if (target) {
737
+ if (target.tagName === "FORM") {
738
+ body = new FormData(target);
739
+ } else if (["INPUT", "SELECT", "TEXTAREA"].includes(target.tagName)) {
740
+ const name = target.getAttribute("name") || "body";
741
+ body = { [name]: target.value };
742
+ } else {
743
+ body = target.innerText;
744
+ }
745
+ }
746
+ } catch (e) {
747
+ body = bodyAttr;
748
+ }
749
+ }
750
+ }
751
+ return { method, body, headers };
752
+ };
753
+ const fetchContent = async (src, requestOptions = {}) => {
709
754
  var _a2;
755
+ const { method = "GET", body = null, headers = {} } = requestOptions;
710
756
  try {
711
757
  const LV = globalThis.Lightview;
712
758
  if (((_a2 = LV == null ? void 0 : LV.hooks) == null ? void 0 : _a2.validateUrl) && !LV.hooks.validateUrl(src)) {
713
759
  console.warn(`[LightviewX] Fetch blocked by validateUrl hook: ${src}`);
714
760
  return null;
715
761
  }
716
- const url = new URL(src, document.baseURI);
717
- const res = await fetch(url);
762
+ let url = new URL(src, document.baseURI);
763
+ const fetchOptions = { method, headers: { ...headers } };
764
+ if (body) {
765
+ if (method === "GET") {
766
+ const params = new URLSearchParams(url.search);
767
+ if (body instanceof FormData) {
768
+ for (const [key, value] of body.entries()) params.append(key, value);
769
+ } else if (typeof body === "object" && body !== null) {
770
+ for (const [key, value] of Object.entries(body)) params.append(key, String(value));
771
+ } else {
772
+ params.append("body", String(body));
773
+ }
774
+ const queryString = params.toString();
775
+ if (queryString) {
776
+ url = new URL(`${url.origin}${url.pathname}?${queryString}${url.hash}`, url.origin);
777
+ }
778
+ } else {
779
+ if (body instanceof FormData) {
780
+ fetchOptions.body = body;
781
+ } else if (typeof body === "object" && body !== null) {
782
+ if (headers["Content-Type"] === "application/json" || !headers["Content-Type"]) {
783
+ fetchOptions.body = JSON.stringify(body);
784
+ fetchOptions.headers["Content-Type"] = "application/json";
785
+ } else {
786
+ fetchOptions.body = String(body);
787
+ }
788
+ } else {
789
+ fetchOptions.body = String(body);
790
+ }
791
+ }
792
+ }
793
+ const res = await fetch(url, fetchOptions);
718
794
  if (!res.ok) return null;
719
795
  const ext = url.pathname.split(".").pop().toLowerCase();
720
796
  const isJson = ext === "vdom" || ext === "odom" || ext === "cdom";
@@ -772,8 +848,12 @@
772
848
  }
773
849
  };
774
850
  const updateTargetContent = (el, elements, raw, loc, contentHash, options, targetHash = null) => {
775
- var _a2;
776
- const { element, setupChildren, saveScrolls, restoreScrolls } = { ...options, ...(_a2 = globalThis.Lightview) == null ? void 0 : _a2.internals };
851
+ var _a2, _b2;
852
+ const { element, setupChildren, saveScrolls, restoreScrolls } = {
853
+ element: (_a2 = globalThis.Lightview) == null ? void 0 : _a2.element,
854
+ ...(_b2 = globalThis.Lightview) == null ? void 0 : _b2.internals,
855
+ ...options
856
+ };
777
857
  const markerId = `${loc}-${contentHash.slice(0, 8)}`;
778
858
  let track = getOrSet(insertedContentMap, el.domEl, () => ({}));
779
859
  if (track[loc]) removeInsertedContent(el.domEl, `${loc}-${track[loc].slice(0, 8)}`);
@@ -819,7 +899,8 @@
819
899
  if (src.includes("#")) {
820
900
  [src, targetHash] = src.split("#");
821
901
  }
822
- const result = await fetchContent(src);
902
+ const options = getRequestInfo(el);
903
+ const result = await fetchContent(src, options);
823
904
  if (result) {
824
905
  elements = parseElements(result.content, result.isJson, result.isHtml, el, element, result.isCdom, result.ext);
825
906
  raw = result.raw;
@@ -870,7 +951,7 @@
870
951
  }
871
952
  return { selector: targetStr, location: null };
872
953
  };
873
- const handleNonStandardHref = (e, { domToElement, wrapDomElement }) => {
954
+ const handleNonStandardHref = async (e, { domToElement, wrapDomElement }) => {
874
955
  var _a2;
875
956
  const clickedEl = e.target.closest("[href]");
876
957
  if (!clickedEl) return;
@@ -884,6 +965,7 @@
884
965
  return;
885
966
  }
886
967
  const targetAttr = clickedEl.getAttribute("target");
968
+ const options = getRequestInfo(clickedEl);
887
969
  if (!targetAttr) {
888
970
  let el = domToElement.get(clickedEl);
889
971
  if (!el) {
@@ -896,6 +978,9 @@
896
978
  return;
897
979
  }
898
980
  if (targetAttr.startsWith("_")) {
981
+ if (options.method !== "GET") {
982
+ console.warn("[LightviewX] Cannot use non-GET method for browser navigation (_blank, _top, etc.)");
983
+ }
899
984
  switch (targetAttr) {
900
985
  case "_self":
901
986
  globalThis.location.href = href;
@@ -916,6 +1001,11 @@
916
1001
  const { selector, location } = parseTargetWithLocation(targetAttr);
917
1002
  try {
918
1003
  const targetElements = document.querySelectorAll(selector);
1004
+ if (targetElements.length === 0) return;
1005
+ const result = await fetchContent(href, options);
1006
+ if (!result) return;
1007
+ const { setupChildren } = LV.internals;
1008
+ const element = LV.element;
919
1009
  targetElements.forEach((targetEl) => {
920
1010
  let el = domToElement.get(targetEl);
921
1011
  if (!el) {
@@ -923,14 +1013,14 @@
923
1013
  for (let attr of targetEl.attributes) attrs[attr.name] = attr.value;
924
1014
  el = wrapDomElement(targetEl, targetEl.tagName.toLowerCase(), attrs);
925
1015
  }
926
- const newAttrs = { ...el.attributes, src: href };
927
- if (location) {
928
- newAttrs.location = location;
929
- }
930
- el.attributes = newAttrs;
1016
+ const elements = parseElements(result.content, result.isJson, result.isHtml, el, element, result.isCdom, result.ext);
1017
+ const loc = (location || targetEl.getAttribute("location") || "innerhtml").toLowerCase();
1018
+ const contentHash = hashContent(result.raw);
1019
+ updateTargetContent(el, elements, result.raw, loc, contentHash, { element, setupChildren });
1020
+ targetEl.setAttribute("src", href);
931
1021
  });
932
1022
  } catch (err) {
933
- console.warn("Invalid target selector:", selector, err);
1023
+ console.warn("Invalid target selector or fetch error:", selector, err);
934
1024
  }
935
1025
  };
936
1026
  const gateStates = /* @__PURE__ */ new WeakMap();
@@ -1202,6 +1292,8 @@
1202
1292
  };
1203
1293
  if (typeof window !== "undefined" && globalThis.Lightview) {
1204
1294
  const LV = globalThis.Lightview;
1295
+ LV.state = state;
1296
+ LV.getState = getState;
1205
1297
  if (document.readyState === "loading") {
1206
1298
  document.addEventListener("DOMContentLoaded", () => setupSrcObserver(LV));
1207
1299
  } else {
@@ -1557,8 +1649,24 @@
1557
1649
  }
1558
1650
  if (globalThis.Lightview) globalThis.Lightview.validate = validateJSONSchema;
1559
1651
  }
1652
+ const request = async (el) => {
1653
+ const domEl = el.domEl || el;
1654
+ const href = domEl.getAttribute("href");
1655
+ if (!href) return;
1656
+ return handleNonStandardHref({
1657
+ target: domEl,
1658
+ preventDefault: () => {
1659
+ },
1660
+ stopPropagation: () => {
1661
+ }
1662
+ }, {
1663
+ domToElement: globalThis.Lightview.internals.domToElement,
1664
+ wrapDomElement: globalThis.Lightview.internals.wrapDomElement
1665
+ });
1666
+ };
1560
1667
  const LightviewX = {
1561
1668
  state,
1669
+ getState,
1562
1670
  themeSignal,
1563
1671
  setTheme,
1564
1672
  registerStyleSheet,
@@ -1566,6 +1674,28 @@
1566
1674
  // Gate modifiers
1567
1675
  throttle: gateThrottle,
1568
1676
  debounce: gateDebounce,
1677
+ // Hypermedia Actions
1678
+ request,
1679
+ get: (el, url) => {
1680
+ if (url) el.setAttribute("href", url);
1681
+ el.setAttribute("data-method", "GET");
1682
+ return request(el);
1683
+ },
1684
+ post: (el, url) => {
1685
+ if (url) el.setAttribute("href", url);
1686
+ el.setAttribute("data-method", "POST");
1687
+ return request(el);
1688
+ },
1689
+ put: (el, url) => {
1690
+ if (url) el.setAttribute("href", url);
1691
+ el.setAttribute("data-method", "PUT");
1692
+ return request(el);
1693
+ },
1694
+ delete: (el, url) => {
1695
+ if (url) el.setAttribute("href", url);
1696
+ el.setAttribute("data-method", "DELETE");
1697
+ return request(el);
1698
+ },
1569
1699
  // Component initialization
1570
1700
  initComponents,
1571
1701
  componentConfig,
@@ -1580,6 +1710,7 @@
1580
1710
  parseElements
1581
1711
  }
1582
1712
  };
1713
+ if (globalThis.Lightview) globalThis.Lightview.request = request;
1583
1714
  if (typeof module !== "undefined" && module.exports) {
1584
1715
  module.exports = LightviewX;
1585
1716
  }