@unhingged/vizu-core 0.1.1 → 0.1.3
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/dist/auto.cjs +663 -1758
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +290 -33
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +662 -1757
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -31
- package/dist/index.d.ts +0 -31
- package/dist/index.js +290 -33
- package/dist/index.js.map +1 -1
- package/dist/vizu.min.js +76 -257
- package/dist/vizu.min.js.map +1 -1
- package/package.json +24 -27
- package/dist/chunk-OMIFOSQ2.js +0 -295
- package/dist/chunk-OMIFOSQ2.js.map +0 -1
- package/dist/live-collab-IRUNFAPE.js +0 -1033
- package/dist/live-collab-IRUNFAPE.js.map +0 -1
package/dist/auto.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findByFingerprint,
|
|
3
|
-
findElementByFingerprint,
|
|
4
|
-
fingerprint,
|
|
5
|
-
fingerprintKey,
|
|
6
|
-
fingerprintLabel
|
|
7
|
-
} from "./chunk-OMIFOSQ2.js";
|
|
8
|
-
|
|
9
1
|
// src/types.ts
|
|
10
2
|
var SCHEMA_VERSION = 2;
|
|
11
3
|
|
|
@@ -537,7 +529,7 @@ var CloudStorageAdapter = class {
|
|
|
537
529
|
}
|
|
538
530
|
}
|
|
539
531
|
sessionKey() {
|
|
540
|
-
return `vizu:cloud-token:${this.workspace}`;
|
|
532
|
+
return `vizu:cloud-token:v2:${this.workspace}`;
|
|
541
533
|
}
|
|
542
534
|
/**
|
|
543
535
|
* Resolve a valid token, opening the popup if needed.
|
|
@@ -680,10 +672,297 @@ function apiErr(status, body) {
|
|
|
680
672
|
return err;
|
|
681
673
|
}
|
|
682
674
|
|
|
675
|
+
// src/fingerprint.ts
|
|
676
|
+
var TEXT_SNIPPET_MAX = 80;
|
|
677
|
+
var ANCESTOR_DEPTH = 4;
|
|
678
|
+
var ANCESTOR_MAX_SHIFT = 3;
|
|
679
|
+
var TEXT_RATIO_THRESHOLD = 0.75;
|
|
680
|
+
var CLASS_JACCARD_THRESHOLD = 0.6;
|
|
681
|
+
function buildSelector(el) {
|
|
682
|
+
const parts = [];
|
|
683
|
+
let current = el;
|
|
684
|
+
let depth = 0;
|
|
685
|
+
while (current && current !== document.documentElement && depth < 8) {
|
|
686
|
+
let part = current.tagName.toLowerCase();
|
|
687
|
+
if (current.id) {
|
|
688
|
+
parts.unshift("#" + CSS.escape(current.id));
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
const cls = Array.from(current.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")).slice(0, 2);
|
|
692
|
+
if (cls.length) part += "." + cls.map((c) => CSS.escape(c)).join(".");
|
|
693
|
+
const parent = current.parentElement;
|
|
694
|
+
if (parent) {
|
|
695
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
696
|
+
if (sameTag.length > 1) {
|
|
697
|
+
const idx = sameTag.indexOf(current);
|
|
698
|
+
part += ":nth-of-type(" + (idx + 1) + ")";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
parts.unshift(part);
|
|
702
|
+
current = parent;
|
|
703
|
+
depth++;
|
|
704
|
+
}
|
|
705
|
+
return parts.join(" > ");
|
|
706
|
+
}
|
|
707
|
+
function fingerprint(el) {
|
|
708
|
+
const text = el.innerText || el.textContent || "";
|
|
709
|
+
const parent = el.parentElement;
|
|
710
|
+
let siblingIndex = 0;
|
|
711
|
+
if (parent) {
|
|
712
|
+
siblingIndex = Array.from(parent.children).indexOf(el);
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
selector: buildSelector(el),
|
|
716
|
+
parentSelector: parent ? buildSelector(parent) : "",
|
|
717
|
+
tagName: el.tagName,
|
|
718
|
+
textSnippet: text.trim().slice(0, TEXT_SNIPPET_MAX),
|
|
719
|
+
siblingIndex,
|
|
720
|
+
attributes: {
|
|
721
|
+
id: el.id || void 0,
|
|
722
|
+
classList: Array.from(el.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")),
|
|
723
|
+
role: el.getAttribute("role") || void 0,
|
|
724
|
+
ariaLabel: el.getAttribute("aria-label") || void 0,
|
|
725
|
+
dataKey: el.getAttribute("data-vizu-key") || void 0
|
|
726
|
+
},
|
|
727
|
+
ancestorChain: captureAncestorChain(el, ANCESTOR_DEPTH),
|
|
728
|
+
algorithmVersion: 2
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function captureAncestorChain(el, depth) {
|
|
732
|
+
const steps = [];
|
|
733
|
+
let current = el.parentElement;
|
|
734
|
+
while (current && steps.length < depth) {
|
|
735
|
+
const parent = current.parentElement;
|
|
736
|
+
let nthOfType = 1;
|
|
737
|
+
if (parent) {
|
|
738
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
739
|
+
nthOfType = sameTag.indexOf(current) + 1;
|
|
740
|
+
}
|
|
741
|
+
steps.push({ tag: current.tagName, nthOfType });
|
|
742
|
+
current = parent;
|
|
743
|
+
}
|
|
744
|
+
return steps.length > 0 ? { steps } : void 0;
|
|
745
|
+
}
|
|
746
|
+
function findByFingerprint(fp, root = document) {
|
|
747
|
+
if (fp.attributes.dataKey) {
|
|
748
|
+
try {
|
|
749
|
+
const byKey = root.querySelector(`[data-vizu-key="${CSS.escape(fp.attributes.dataKey)}"]`);
|
|
750
|
+
if (byKey) return { element: byKey, confidence: "exact" };
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
return { element: null, confidence: "orphaned" };
|
|
754
|
+
}
|
|
755
|
+
if (fp.attributes.id) {
|
|
756
|
+
try {
|
|
757
|
+
const byId = root.querySelector("#" + CSS.escape(fp.attributes.id));
|
|
758
|
+
if (byId) return { element: byId, confidence: "exact" };
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const candidates = [];
|
|
763
|
+
const classes = fp.attributes.classList;
|
|
764
|
+
if (classes && classes.length > 0) {
|
|
765
|
+
const m = findByClassSignature(root, fp.tagName, classes);
|
|
766
|
+
if (m?.element) candidates.push({ el: m.element, rung: "class" });
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const bySelector = root.querySelector(fp.selector);
|
|
770
|
+
if (bySelector) candidates.push({ el: bySelector, rung: "selector" });
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
if (fp.ancestorChain && fp.ancestorChain.steps.length > 0) {
|
|
774
|
+
const m = findByAncestorChain(root, fp.tagName, fp.ancestorChain.steps);
|
|
775
|
+
if (m?.element) candidates.push({ el: m.element, rung: "chain" });
|
|
776
|
+
} else {
|
|
777
|
+
try {
|
|
778
|
+
const parent = fp.parentSelector ? root.querySelector(fp.parentSelector) : root;
|
|
779
|
+
if (parent) {
|
|
780
|
+
const candidate = parent.children[fp.siblingIndex];
|
|
781
|
+
if (candidate && candidate.tagName === fp.tagName) {
|
|
782
|
+
candidates.push({ el: candidate, rung: "chain" });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (fp.textSnippet) {
|
|
789
|
+
const m = findByTextSnippet(root, fp.tagName, fp.textSnippet);
|
|
790
|
+
if (m?.element) candidates.push({ el: m.element, rung: "text" });
|
|
791
|
+
}
|
|
792
|
+
if (candidates.length === 0) {
|
|
793
|
+
return { element: null, confidence: "orphaned" };
|
|
794
|
+
}
|
|
795
|
+
const votes = /* @__PURE__ */ new Map();
|
|
796
|
+
for (const c of candidates) {
|
|
797
|
+
let s = votes.get(c.el);
|
|
798
|
+
if (!s) {
|
|
799
|
+
s = /* @__PURE__ */ new Set();
|
|
800
|
+
votes.set(c.el, s);
|
|
801
|
+
}
|
|
802
|
+
s.add(c.rung);
|
|
803
|
+
}
|
|
804
|
+
let best = null;
|
|
805
|
+
for (const [el, rungs] of votes) {
|
|
806
|
+
if (!best || rungs.size > best.rungs.size) best = { el, rungs };
|
|
807
|
+
}
|
|
808
|
+
if (!best) return { element: null, confidence: "orphaned" };
|
|
809
|
+
if (best.rungs.size >= 3) return { element: best.el, confidence: "likely" };
|
|
810
|
+
if (best.rungs.size >= 2) return { element: best.el, confidence: "drifted" };
|
|
811
|
+
if (best.rungs.has("class") || best.rungs.has("chain")) {
|
|
812
|
+
return { element: best.el, confidence: "drifted" };
|
|
813
|
+
}
|
|
814
|
+
return { element: null, confidence: "orphaned" };
|
|
815
|
+
}
|
|
816
|
+
function findByClassSignature(root, tagName, needle) {
|
|
817
|
+
const needleSet = new Set(needle.filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")));
|
|
818
|
+
if (needleSet.size === 0) return null;
|
|
819
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
820
|
+
let best = null;
|
|
821
|
+
let secondScore = 0;
|
|
822
|
+
for (const el of all) {
|
|
823
|
+
const haystack = new Set(
|
|
824
|
+
Array.from(el.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-"))
|
|
825
|
+
);
|
|
826
|
+
if (haystack.size === 0) continue;
|
|
827
|
+
const score = jaccardSimilarity(needleSet, haystack);
|
|
828
|
+
if (score < CLASS_JACCARD_THRESHOLD) continue;
|
|
829
|
+
if (!best || score > best.score) {
|
|
830
|
+
secondScore = best?.score ?? 0;
|
|
831
|
+
best = { el, score };
|
|
832
|
+
} else if (score > secondScore) {
|
|
833
|
+
secondScore = score;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (!best) return null;
|
|
837
|
+
if (best.score - secondScore < 0.1) return null;
|
|
838
|
+
return { element: best.el, confidence: "likely" };
|
|
839
|
+
}
|
|
840
|
+
function findByAncestorChain(root, tagName, steps) {
|
|
841
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
842
|
+
let best = null;
|
|
843
|
+
for (const el of all) {
|
|
844
|
+
const ancestors = collectAncestorSteps(el, steps.length + ANCESTOR_MAX_SHIFT);
|
|
845
|
+
const score = scoreChainAgainstAncestors(ancestors, steps, ANCESTOR_MAX_SHIFT);
|
|
846
|
+
if (score < 2) continue;
|
|
847
|
+
if (!best || score > best.score) best = { el, score };
|
|
848
|
+
}
|
|
849
|
+
if (!best) return null;
|
|
850
|
+
return {
|
|
851
|
+
element: best.el,
|
|
852
|
+
confidence: best.score === steps.length ? "likely" : "drifted"
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function collectAncestorSteps(el, limit) {
|
|
856
|
+
const steps = [];
|
|
857
|
+
let current = el.parentElement;
|
|
858
|
+
while (current && steps.length < limit) {
|
|
859
|
+
const parent = current.parentElement;
|
|
860
|
+
let nth = 1;
|
|
861
|
+
if (parent) {
|
|
862
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
863
|
+
nth = sameTag.indexOf(current) + 1;
|
|
864
|
+
}
|
|
865
|
+
steps.push({ tag: current.tagName, nthOfType: nth });
|
|
866
|
+
current = parent;
|
|
867
|
+
}
|
|
868
|
+
return steps;
|
|
869
|
+
}
|
|
870
|
+
function findByTextSnippet(root, tagName, snippet) {
|
|
871
|
+
const needle = normalizeText(snippet);
|
|
872
|
+
if (!needle) return null;
|
|
873
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
874
|
+
let best = null;
|
|
875
|
+
for (const el of all) {
|
|
876
|
+
const raw = el.innerText || el.textContent || "";
|
|
877
|
+
const candidate = normalizeText(raw.slice(0, TEXT_SNIPPET_MAX * 2));
|
|
878
|
+
if (!candidate) continue;
|
|
879
|
+
const score = levenshteinRatio(needle, candidate);
|
|
880
|
+
if (score < TEXT_RATIO_THRESHOLD) continue;
|
|
881
|
+
if (!best || score > best.score) {
|
|
882
|
+
best = { el, score };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return best ? { element: best.el, confidence: "drifted" } : null;
|
|
886
|
+
}
|
|
887
|
+
function normalizeText(input) {
|
|
888
|
+
return input.toLowerCase().replace(/\s+/g, " ").trim();
|
|
889
|
+
}
|
|
890
|
+
function levenshteinRatio(a, b) {
|
|
891
|
+
if (a === b) return 1;
|
|
892
|
+
if (a.length === 0 || b.length === 0) return a.length === b.length ? 1 : 0;
|
|
893
|
+
const maxLen = Math.max(a.length, b.length);
|
|
894
|
+
const distance = levenshteinDistance(a, b);
|
|
895
|
+
return 1 - distance / maxLen;
|
|
896
|
+
}
|
|
897
|
+
function levenshteinDistance(a, b) {
|
|
898
|
+
if (a.length > b.length) {
|
|
899
|
+
const tmp = a;
|
|
900
|
+
a = b;
|
|
901
|
+
b = tmp;
|
|
902
|
+
}
|
|
903
|
+
const prev = new Array(a.length + 1);
|
|
904
|
+
const curr = new Array(a.length + 1);
|
|
905
|
+
for (let i = 0; i <= a.length; i++) prev[i] = i;
|
|
906
|
+
for (let j = 1; j <= b.length; j++) {
|
|
907
|
+
curr[0] = j;
|
|
908
|
+
for (let i = 1; i <= a.length; i++) {
|
|
909
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
910
|
+
curr[i] = Math.min(
|
|
911
|
+
curr[i - 1] + 1,
|
|
912
|
+
// insert
|
|
913
|
+
prev[i] + 1,
|
|
914
|
+
// delete
|
|
915
|
+
prev[i - 1] + cost
|
|
916
|
+
// substitute
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
for (let i = 0; i <= a.length; i++) prev[i] = curr[i];
|
|
920
|
+
}
|
|
921
|
+
return prev[a.length];
|
|
922
|
+
}
|
|
923
|
+
function scoreChainAgainstAncestors(actual, captured, maxShift) {
|
|
924
|
+
let best = 0;
|
|
925
|
+
for (let offset = 0; offset <= maxShift; offset++) {
|
|
926
|
+
let score = 0;
|
|
927
|
+
for (let i = 0; i < captured.length; i++) {
|
|
928
|
+
const a = actual[i + offset];
|
|
929
|
+
if (!a) break;
|
|
930
|
+
if (a.tag === captured[i].tag && a.nthOfType === captured[i].nthOfType) {
|
|
931
|
+
score++;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (score > best) best = score;
|
|
935
|
+
}
|
|
936
|
+
return best;
|
|
937
|
+
}
|
|
938
|
+
function jaccardSimilarity(a, b) {
|
|
939
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
940
|
+
let intersection = 0;
|
|
941
|
+
for (const item of a) if (b.has(item)) intersection++;
|
|
942
|
+
const union = a.size + b.size - intersection;
|
|
943
|
+
return union === 0 ? 0 : intersection / union;
|
|
944
|
+
}
|
|
945
|
+
function findElementByFingerprint(fp, root) {
|
|
946
|
+
return findByFingerprint(fp, root).element;
|
|
947
|
+
}
|
|
948
|
+
function fingerprintKey(fp) {
|
|
949
|
+
return fp.selector + "|" + fp.textSnippet;
|
|
950
|
+
}
|
|
951
|
+
function fingerprintLabel(fp) {
|
|
952
|
+
const tag = fp.tagName.toLowerCase();
|
|
953
|
+
if (fp.textSnippet) {
|
|
954
|
+
const snippet = fp.textSnippet.length > 24 ? fp.textSnippet.slice(0, 24) + "\u2026" : fp.textSnippet;
|
|
955
|
+
return tag + ": " + snippet;
|
|
956
|
+
}
|
|
957
|
+
if (fp.attributes.id) return tag + "#" + fp.attributes.id;
|
|
958
|
+
if (fp.attributes.classList && fp.attributes.classList.length) return tag + "." + fp.attributes.classList[0];
|
|
959
|
+
return tag;
|
|
960
|
+
}
|
|
961
|
+
|
|
683
962
|
// src/highlighter.ts
|
|
684
963
|
var IGNORE_SELECTOR = "[data-vizu-root], [data-vizu-ignore], script, style, link, meta, head";
|
|
685
964
|
var Highlighter = class {
|
|
686
|
-
constructor(root, extraIgnore, onClick
|
|
965
|
+
constructor(root, extraIgnore, onClick) {
|
|
687
966
|
this.current = null;
|
|
688
967
|
this.active = false;
|
|
689
968
|
this.paused = false;
|
|
@@ -718,7 +997,6 @@ var Highlighter = class {
|
|
|
718
997
|
this.root = root;
|
|
719
998
|
this.extraIgnore = extraIgnore;
|
|
720
999
|
this.onClick = onClick;
|
|
721
|
-
this.onCurrentChange = onCurrentChange ?? null;
|
|
722
1000
|
this.overlay = document.createElement("div");
|
|
723
1001
|
this.overlay.className = "vz-highlight";
|
|
724
1002
|
this.overlay.style.display = "none";
|
|
@@ -727,7 +1005,6 @@ var Highlighter = class {
|
|
|
727
1005
|
setCurrent(el) {
|
|
728
1006
|
if (this.current === el) return;
|
|
729
1007
|
this.current = el;
|
|
730
|
-
this.onCurrentChange?.(el);
|
|
731
1008
|
}
|
|
732
1009
|
start() {
|
|
733
1010
|
if (this.active) return;
|
|
@@ -2772,8 +3049,6 @@ var Vizu = class {
|
|
|
2772
3049
|
this.selfHealedThisSession = /* @__PURE__ */ new Set();
|
|
2773
3050
|
this.actions = [];
|
|
2774
3051
|
this.user = null;
|
|
2775
|
-
/** Live-collab module — non-null only when `opts.liveblocks` and `opts.cloud` are both set AND the plan carries `live_collab`. Stays null otherwise. */
|
|
2776
|
-
this.liveCollab = null;
|
|
2777
3052
|
this.enabled = false;
|
|
2778
3053
|
this.rafScheduled = false;
|
|
2779
3054
|
this.bus = new EventBus();
|
|
@@ -2845,18 +3120,6 @@ var Vizu = class {
|
|
|
2845
3120
|
if (options.actions) this.actions = [...options.actions];
|
|
2846
3121
|
if (options.onCommentAdded) this.bus.on("comment:added", (p) => options.onCommentAdded(p.comment));
|
|
2847
3122
|
if (options.onCommentRemoved) this.bus.on("comment:removed", (p) => options.onCommentRemoved(p.id));
|
|
2848
|
-
if (options.liveblocks && options.cloud) {
|
|
2849
|
-
const live = options.liveblocks;
|
|
2850
|
-
const cloud = options.cloud;
|
|
2851
|
-
void import("./live-collab-IRUNFAPE.js").then(({ LiveCollab }) => {
|
|
2852
|
-
this.liveCollab = new LiveCollab({
|
|
2853
|
-
publicKey: live.publicKey,
|
|
2854
|
-
authEndpoint: live.authEndpoint,
|
|
2855
|
-
workspaceSlug: cloud.workspace
|
|
2856
|
-
});
|
|
2857
|
-
void this.liveCollab.start();
|
|
2858
|
-
});
|
|
2859
|
-
}
|
|
2860
3123
|
if (typeof window !== "undefined") {
|
|
2861
3124
|
window.addEventListener("keydown", this.onKeyDown);
|
|
2862
3125
|
void this.loadComments().then(() => this.subscribeStorage());
|
|
@@ -2914,8 +3177,6 @@ var Vizu = class {
|
|
|
2914
3177
|
this.unsubscribeStorage?.();
|
|
2915
3178
|
this.unsubscribeStorage = null;
|
|
2916
3179
|
this.selfHealedThisSession.clear();
|
|
2917
|
-
this.liveCollab?.destroy();
|
|
2918
|
-
this.liveCollab = null;
|
|
2919
3180
|
this.bus.clear();
|
|
2920
3181
|
}
|
|
2921
3182
|
/* ===== Action API ===== */
|
|
@@ -3237,11 +3498,7 @@ var Vizu = class {
|
|
|
3237
3498
|
this.highlighter = new Highlighter(
|
|
3238
3499
|
this.root,
|
|
3239
3500
|
this.opts.ignoreSelectors ?? [],
|
|
3240
|
-
(el, e) => this.onElementClick(el, e)
|
|
3241
|
-
(el) => {
|
|
3242
|
-
if (!this.liveCollab) return;
|
|
3243
|
-
this.liveCollab.setLocalSelection(el ? fingerprint(el) : null);
|
|
3244
|
-
}
|
|
3501
|
+
(el, e) => this.onElementClick(el, e)
|
|
3245
3502
|
);
|
|
3246
3503
|
this.highlighter.start();
|
|
3247
3504
|
this.renderAllMarkers();
|