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/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
|
|
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
|
-
|
|
717
|
-
const
|
|
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 } = {
|
|
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
|
|
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
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
|
|
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
|
}
|