qapture2 0.2.2 → 0.2.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/dist/bin/init.cjs +48 -12
- package/dist/{chunk-PBXQDL7C.cjs → chunk-DPJW626S.cjs} +391 -192
- package/dist/{chunk-L6VS36GE.js → chunk-RC7ZUQ5X.js} +391 -193
- package/dist/index.cjs +8 -4
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +26 -21
- package/dist/standalone.js +25 -20
- package/package.json +9 -2
|
@@ -9,7 +9,134 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
9
9
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
10
10
|
var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
|
|
11
11
|
|
|
12
|
-
// src/
|
|
12
|
+
// src/lib/idb.ts
|
|
13
|
+
var DB_VERSION = 2;
|
|
14
|
+
var NOTES_STORE = "notes";
|
|
15
|
+
var META_STORE = "meta";
|
|
16
|
+
var dbCache = /* @__PURE__ */ new Map();
|
|
17
|
+
function isIdbAvailable() {
|
|
18
|
+
return typeof indexedDB !== "undefined";
|
|
19
|
+
}
|
|
20
|
+
function openDB(dbName) {
|
|
21
|
+
const cached = dbCache.get(dbName);
|
|
22
|
+
if (cached) return cached;
|
|
23
|
+
const promise = new Promise((resolve, reject) => {
|
|
24
|
+
let req;
|
|
25
|
+
try {
|
|
26
|
+
req = indexedDB.open(dbName, DB_VERSION);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
dbCache.delete(dbName);
|
|
29
|
+
reject(err);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
req.onupgradeneeded = (e) => {
|
|
33
|
+
const db = e.target.result;
|
|
34
|
+
const oldVersion = e.oldVersion;
|
|
35
|
+
switch (true) {
|
|
36
|
+
case oldVersion < 1:
|
|
37
|
+
if (!db.objectStoreNames.contains(NOTES_STORE)) {
|
|
38
|
+
db.createObjectStore(NOTES_STORE, { keyPath: "id" });
|
|
39
|
+
}
|
|
40
|
+
// falls through
|
|
41
|
+
case oldVersion < 2:
|
|
42
|
+
if (!db.objectStoreNames.contains(META_STORE)) {
|
|
43
|
+
db.createObjectStore(META_STORE, { keyPath: "key" });
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
req.onsuccess = (e) => resolve(e.target.result);
|
|
49
|
+
req.onerror = (e) => {
|
|
50
|
+
dbCache.delete(dbName);
|
|
51
|
+
reject(e.target.error);
|
|
52
|
+
};
|
|
53
|
+
req.onblocked = () => {
|
|
54
|
+
dbCache.delete(dbName);
|
|
55
|
+
reject(new Error(`IndexedDB open blocked for "${dbName}" \u2014 another tab has an older connection open`));
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
dbCache.set(dbName, promise);
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
function run(dbName, store, mode, fn) {
|
|
62
|
+
return openDB(dbName).then(
|
|
63
|
+
(db) => new Promise((resolve, reject) => {
|
|
64
|
+
const tx = db.transaction(store, mode);
|
|
65
|
+
const s = tx.objectStore(store);
|
|
66
|
+
let result;
|
|
67
|
+
const req = fn(s);
|
|
68
|
+
if (req) {
|
|
69
|
+
req.onsuccess = () => {
|
|
70
|
+
result = req.result;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
tx.oncomplete = () => resolve(result);
|
|
74
|
+
tx.onerror = () => reject(tx.error);
|
|
75
|
+
tx.onabort = () => reject(tx.error);
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function createIdb(namespace) {
|
|
80
|
+
const dbName = `${namespace}-db`;
|
|
81
|
+
if (!isIdbAvailable()) {
|
|
82
|
+
return {
|
|
83
|
+
getAll: () => Promise.resolve([]),
|
|
84
|
+
put: () => Promise.resolve(),
|
|
85
|
+
delete: () => Promise.resolve(),
|
|
86
|
+
clear: () => Promise.resolve()
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
getAll: async () => {
|
|
91
|
+
try {
|
|
92
|
+
const rows = await run(dbName, NOTES_STORE, "readonly", (s) => s.getAll());
|
|
93
|
+
return rows ?? [];
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
put: async (record) => {
|
|
99
|
+
try {
|
|
100
|
+
await run(dbName, NOTES_STORE, "readwrite", (s) => s.put(record));
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
delete: async (id) => {
|
|
105
|
+
try {
|
|
106
|
+
await run(dbName, NOTES_STORE, "readwrite", (s) => s.delete(id));
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
clear: async () => {
|
|
111
|
+
try {
|
|
112
|
+
await run(dbName, NOTES_STORE, "readwrite", (s) => s.clear());
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function deleteQaDatabase(namespace) {
|
|
119
|
+
const dbName = `${namespace}-db`;
|
|
120
|
+
if (!isIdbAvailable()) return Promise.resolve();
|
|
121
|
+
const cached = dbCache.get(dbName);
|
|
122
|
+
dbCache.delete(dbName);
|
|
123
|
+
const closed = cached ? cached.then((db) => db.close()).catch(() => {
|
|
124
|
+
}) : Promise.resolve();
|
|
125
|
+
return closed.then(
|
|
126
|
+
() => new Promise((resolve, reject) => {
|
|
127
|
+
let req;
|
|
128
|
+
try {
|
|
129
|
+
req = indexedDB.deleteDatabase(dbName);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
reject(err);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
req.onsuccess = () => resolve();
|
|
135
|
+
req.onerror = (e) => reject(e.target.error);
|
|
136
|
+
req.onblocked = () => reject(new Error(`IndexedDB deleteDatabase blocked for "${dbName}" \u2014 another connection is still open`));
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
13
140
|
|
|
14
141
|
// src/config/schema.ts
|
|
15
142
|
var DEFAULT_THEME = {
|
|
@@ -44,6 +171,9 @@ var VALID_RISKS = /* @__PURE__ */ new Set(["red", "amber", "green"]);
|
|
|
44
171
|
function isNonEmptyString(v) {
|
|
45
172
|
return typeof v === "string" && v.trim().length > 0;
|
|
46
173
|
}
|
|
174
|
+
function stripNewlines(v) {
|
|
175
|
+
return v.replace(/\r\n|\r|\n/g, " ");
|
|
176
|
+
}
|
|
47
177
|
function isValidBilingual(v) {
|
|
48
178
|
if (typeof v === "string") return true;
|
|
49
179
|
if (v !== null && typeof v === "object") {
|
|
@@ -92,17 +222,17 @@ function coerceCredentials(raw, warnings) {
|
|
|
92
222
|
continue;
|
|
93
223
|
}
|
|
94
224
|
const cred = {
|
|
95
|
-
role: c["role"].trim(),
|
|
96
|
-
login: c["login"].trim(),
|
|
97
|
-
password: isNonEmptyString(c["password"]) ? c["password"].trim() : ""
|
|
225
|
+
role: stripNewlines(c["role"].trim()),
|
|
226
|
+
login: stripNewlines(c["login"].trim()),
|
|
227
|
+
password: isNonEmptyString(c["password"]) ? stripNewlines(c["password"].trim()) : ""
|
|
98
228
|
};
|
|
99
|
-
if (isNonEmptyString(c["roleAr"])) cred.roleAr = c["roleAr"].trim();
|
|
229
|
+
if (isNonEmptyString(c["roleAr"])) cred.roleAr = stripNewlines(c["roleAr"].trim());
|
|
100
230
|
if (typeof c["seeded"] === "boolean") cred.seeded = c["seeded"];
|
|
101
231
|
if (c["hint"] !== null && c["hint"] !== void 0 && typeof c["hint"] === "object") {
|
|
102
232
|
const h = c["hint"];
|
|
103
233
|
if (typeof h["en"] === "string") {
|
|
104
|
-
cred.hint = { en: h["en"] };
|
|
105
|
-
if (typeof h["ar"] === "string") cred.hint.ar = h["ar"];
|
|
234
|
+
cred.hint = { en: stripNewlines(h["en"]) };
|
|
235
|
+
if (typeof h["ar"] === "string") cred.hint.ar = stripNewlines(h["ar"]);
|
|
106
236
|
}
|
|
107
237
|
}
|
|
108
238
|
out.push(cred);
|
|
@@ -160,9 +290,11 @@ function coerceJourney(raw, warnings) {
|
|
|
160
290
|
if (isNonEmptyString(s["riskWhy"])) step.riskWhy = s["riskWhy"];
|
|
161
291
|
steps.push(step);
|
|
162
292
|
}
|
|
293
|
+
const rawRole = lane["role"];
|
|
294
|
+
const role = typeof rawRole === "string" ? stripNewlines(rawRole) : { en: stripNewlines(rawRole.en), ...typeof rawRole.ar === "string" ? { ar: stripNewlines(rawRole.ar) } : {} };
|
|
163
295
|
const resolved = {
|
|
164
296
|
id: lane["id"].trim(),
|
|
165
|
-
role
|
|
297
|
+
role,
|
|
166
298
|
steps
|
|
167
299
|
};
|
|
168
300
|
if (isNonEmptyString(lane["color"])) resolved.color = lane["color"].trim();
|
|
@@ -173,7 +305,10 @@ function coerceJourney(raw, warnings) {
|
|
|
173
305
|
function coercePreamble(raw) {
|
|
174
306
|
if (raw === null || raw === void 0) return null;
|
|
175
307
|
if (typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
176
|
-
|
|
308
|
+
const p = { ...raw };
|
|
309
|
+
if (typeof p.projectName === "string") p.projectName = stripNewlines(p.projectName);
|
|
310
|
+
if (typeof p.stack === "string") p.stack = stripNewlines(p.stack);
|
|
311
|
+
return p;
|
|
177
312
|
}
|
|
178
313
|
function validateConfig(input) {
|
|
179
314
|
const warnings = [];
|
|
@@ -665,11 +800,12 @@ function createStorage(namespace) {
|
|
|
665
800
|
const prefix = `${namespace}:`;
|
|
666
801
|
const fallback = /* @__PURE__ */ new Map();
|
|
667
802
|
const available = isStorageAvailable();
|
|
803
|
+
let degraded = false;
|
|
668
804
|
function fullKey(key) {
|
|
669
805
|
return `${prefix}${key}`;
|
|
670
806
|
}
|
|
671
807
|
function getItem(key) {
|
|
672
|
-
if (available) {
|
|
808
|
+
if (available && !degraded) {
|
|
673
809
|
try {
|
|
674
810
|
return window.localStorage.getItem(fullKey(key));
|
|
675
811
|
} catch {
|
|
@@ -678,11 +814,12 @@ function createStorage(namespace) {
|
|
|
678
814
|
return fallback.get(fullKey(key)) ?? null;
|
|
679
815
|
}
|
|
680
816
|
function setItem(key, value) {
|
|
681
|
-
if (available) {
|
|
817
|
+
if (available && !degraded) {
|
|
682
818
|
try {
|
|
683
819
|
window.localStorage.setItem(fullKey(key), value);
|
|
684
820
|
return;
|
|
685
821
|
} catch {
|
|
822
|
+
degraded = true;
|
|
686
823
|
}
|
|
687
824
|
}
|
|
688
825
|
fallback.set(fullKey(key), value);
|
|
@@ -705,109 +842,6 @@ function createStorage(namespace) {
|
|
|
705
842
|
return { getItem, setItem, getJSON, setJSON };
|
|
706
843
|
}
|
|
707
844
|
|
|
708
|
-
// src/lib/idb.ts
|
|
709
|
-
var DB_VERSION = 2;
|
|
710
|
-
var NOTES_STORE = "notes";
|
|
711
|
-
var META_STORE = "meta";
|
|
712
|
-
var dbCache = /* @__PURE__ */ new Map();
|
|
713
|
-
function isIdbAvailable() {
|
|
714
|
-
return typeof indexedDB !== "undefined";
|
|
715
|
-
}
|
|
716
|
-
function openDB(dbName) {
|
|
717
|
-
const cached = dbCache.get(dbName);
|
|
718
|
-
if (cached) return cached;
|
|
719
|
-
const promise = new Promise((resolve, reject) => {
|
|
720
|
-
let req;
|
|
721
|
-
try {
|
|
722
|
-
req = indexedDB.open(dbName, DB_VERSION);
|
|
723
|
-
} catch (err) {
|
|
724
|
-
dbCache.delete(dbName);
|
|
725
|
-
reject(err);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
req.onupgradeneeded = (e) => {
|
|
729
|
-
const db = e.target.result;
|
|
730
|
-
const oldVersion = e.oldVersion;
|
|
731
|
-
switch (true) {
|
|
732
|
-
case oldVersion < 1:
|
|
733
|
-
if (!db.objectStoreNames.contains(NOTES_STORE)) {
|
|
734
|
-
db.createObjectStore(NOTES_STORE, { keyPath: "id" });
|
|
735
|
-
}
|
|
736
|
-
// falls through
|
|
737
|
-
case oldVersion < 2:
|
|
738
|
-
if (!db.objectStoreNames.contains(META_STORE)) {
|
|
739
|
-
db.createObjectStore(META_STORE, { keyPath: "key" });
|
|
740
|
-
}
|
|
741
|
-
break;
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
req.onsuccess = (e) => resolve(e.target.result);
|
|
745
|
-
req.onerror = (e) => {
|
|
746
|
-
dbCache.delete(dbName);
|
|
747
|
-
reject(e.target.error);
|
|
748
|
-
};
|
|
749
|
-
});
|
|
750
|
-
dbCache.set(dbName, promise);
|
|
751
|
-
return promise;
|
|
752
|
-
}
|
|
753
|
-
function run(dbName, store, mode, fn) {
|
|
754
|
-
return openDB(dbName).then(
|
|
755
|
-
(db) => new Promise((resolve, reject) => {
|
|
756
|
-
const tx = db.transaction(store, mode);
|
|
757
|
-
const s = tx.objectStore(store);
|
|
758
|
-
let result;
|
|
759
|
-
const req = fn(s);
|
|
760
|
-
if (req) {
|
|
761
|
-
req.onsuccess = () => {
|
|
762
|
-
result = req.result;
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
tx.oncomplete = () => resolve(result);
|
|
766
|
-
tx.onerror = () => reject(tx.error);
|
|
767
|
-
tx.onabort = () => reject(tx.error);
|
|
768
|
-
})
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
function createIdb(namespace) {
|
|
772
|
-
const dbName = `${namespace}-db`;
|
|
773
|
-
if (!isIdbAvailable()) {
|
|
774
|
-
return {
|
|
775
|
-
getAll: () => Promise.resolve([]),
|
|
776
|
-
put: () => Promise.resolve(),
|
|
777
|
-
delete: () => Promise.resolve(),
|
|
778
|
-
clear: () => Promise.resolve()
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
return {
|
|
782
|
-
getAll: async () => {
|
|
783
|
-
try {
|
|
784
|
-
const rows = await run(dbName, NOTES_STORE, "readonly", (s) => s.getAll());
|
|
785
|
-
return rows ?? [];
|
|
786
|
-
} catch {
|
|
787
|
-
return [];
|
|
788
|
-
}
|
|
789
|
-
},
|
|
790
|
-
put: async (record) => {
|
|
791
|
-
try {
|
|
792
|
-
await run(dbName, NOTES_STORE, "readwrite", (s) => s.put(record));
|
|
793
|
-
} catch {
|
|
794
|
-
}
|
|
795
|
-
},
|
|
796
|
-
delete: async (id) => {
|
|
797
|
-
try {
|
|
798
|
-
await run(dbName, NOTES_STORE, "readwrite", (s) => s.delete(id));
|
|
799
|
-
} catch {
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
clear: async () => {
|
|
803
|
-
try {
|
|
804
|
-
await run(dbName, NOTES_STORE, "readwrite", (s) => s.clear());
|
|
805
|
-
} catch {
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
|
|
811
845
|
// src/lib/strings.ts
|
|
812
846
|
var STR = {
|
|
813
847
|
en: {
|
|
@@ -1036,7 +1070,7 @@ function mdTable(headers, rows) {
|
|
|
1036
1070
|
const lines = [
|
|
1037
1071
|
`| ${headers.join(" | ")} |`,
|
|
1038
1072
|
`| ${sep.join(" | ")} |`,
|
|
1039
|
-
...rows.map((r) => `| ${r.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`)
|
|
1073
|
+
...rows.map((r) => `| ${r.map((c) => c.replace(/\|/g, "\\|").replace(/\r\n|\r|\n/g, " ")).join(" | ")} |`)
|
|
1040
1074
|
];
|
|
1041
1075
|
return lines.join("\n");
|
|
1042
1076
|
}
|
|
@@ -1640,6 +1674,17 @@ function QaFab() {
|
|
|
1640
1674
|
const [pos, setPos] = React.useState(() => loadFabPos());
|
|
1641
1675
|
const dragRef = React.useRef(null);
|
|
1642
1676
|
const didDragRef = React.useRef(false);
|
|
1677
|
+
const [, setViewportTick] = React.useState(0);
|
|
1678
|
+
React.useEffect(() => {
|
|
1679
|
+
if (typeof window === "undefined") return;
|
|
1680
|
+
const onViewportChange = () => setViewportTick((n) => n + 1);
|
|
1681
|
+
window.addEventListener("resize", onViewportChange);
|
|
1682
|
+
window.addEventListener("orientationchange", onViewportChange);
|
|
1683
|
+
return () => {
|
|
1684
|
+
window.removeEventListener("resize", onViewportChange);
|
|
1685
|
+
window.removeEventListener("orientationchange", onViewportChange);
|
|
1686
|
+
};
|
|
1687
|
+
}, []);
|
|
1643
1688
|
if (captureActive) return null;
|
|
1644
1689
|
const onPointerDown = (e) => {
|
|
1645
1690
|
if (dragRef.current) return;
|
|
@@ -1769,9 +1814,13 @@ function NoteEditor() {
|
|
|
1769
1814
|
const [previewUrl, setPreviewUrl] = React.useState(null);
|
|
1770
1815
|
const [dragOver, setDragOver] = React.useState(false);
|
|
1771
1816
|
const fileRef = React.useRef(null);
|
|
1817
|
+
const previewUrlRef = React.useRef(null);
|
|
1818
|
+
React.useEffect(() => {
|
|
1819
|
+
previewUrlRef.current = previewUrl;
|
|
1820
|
+
}, [previewUrl]);
|
|
1772
1821
|
React.useEffect(() => {
|
|
1773
1822
|
return () => {
|
|
1774
|
-
if (
|
|
1823
|
+
if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
|
|
1775
1824
|
};
|
|
1776
1825
|
}, []);
|
|
1777
1826
|
const setImage = React.useCallback((blob) => {
|
|
@@ -1967,16 +2016,17 @@ function NoteEditor() {
|
|
|
1967
2016
|
}
|
|
1968
2017
|
|
|
1969
2018
|
// src/lib/highlight.ts
|
|
2019
|
+
var SETTLE_TIMEOUT_MS = 400;
|
|
1970
2020
|
function readCssVar(name, fallback) {
|
|
1971
2021
|
if (typeof document === "undefined") return fallback;
|
|
1972
2022
|
const val = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
1973
2023
|
return val || fallback;
|
|
1974
2024
|
}
|
|
1975
|
-
function paint(rect,
|
|
2025
|
+
function paint(rect, colors) {
|
|
1976
2026
|
if (typeof document === "undefined") return;
|
|
1977
2027
|
if (!rect || rect.width < 1 || rect.height < 1) return;
|
|
1978
|
-
const accent = readCssVar("--qa-accent", "#7c3aed");
|
|
1979
|
-
const primary = readCssVar("--qa-primary", "#4f46e5");
|
|
2028
|
+
const accent = colors?.accent ?? readCssVar("--qa-accent", "#7c3aed");
|
|
2029
|
+
const primary = colors?.primary ?? readCssVar("--qa-primary", "#4f46e5");
|
|
1980
2030
|
const box = document.createElement("div");
|
|
1981
2031
|
box.setAttribute("data-qa-overlay", "true");
|
|
1982
2032
|
Object.assign(box.style, {
|
|
@@ -2002,7 +2052,25 @@ function paint(rect, color) {
|
|
|
2002
2052
|
if (box.parentNode) box.remove();
|
|
2003
2053
|
}, 1500);
|
|
2004
2054
|
}
|
|
2005
|
-
function
|
|
2055
|
+
function settleThenPaint(el, colors) {
|
|
2056
|
+
const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
2057
|
+
const start = now();
|
|
2058
|
+
let last = null;
|
|
2059
|
+
let stableFrames = 0;
|
|
2060
|
+
const tick = () => {
|
|
2061
|
+
const r = el.getBoundingClientRect();
|
|
2062
|
+
const unchanged = !!last && r.top === last.top && r.left === last.left && r.width === last.width && r.height === last.height;
|
|
2063
|
+
stableFrames = unchanged ? stableFrames + 1 : 0;
|
|
2064
|
+
last = r;
|
|
2065
|
+
if (stableFrames >= 2 || now() - start >= SETTLE_TIMEOUT_MS) {
|
|
2066
|
+
paint({ top: r.top, left: r.left, width: r.width, height: r.height }, colors);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
requestAnimationFrame(tick);
|
|
2070
|
+
};
|
|
2071
|
+
requestAnimationFrame(tick);
|
|
2072
|
+
}
|
|
2073
|
+
function flashLocate(target, colors) {
|
|
2006
2074
|
if (typeof document === "undefined" || !target) return;
|
|
2007
2075
|
let el = null;
|
|
2008
2076
|
if (target.selector) {
|
|
@@ -2014,13 +2082,18 @@ function flashLocate(target, color) {
|
|
|
2014
2082
|
}
|
|
2015
2083
|
if (el) {
|
|
2016
2084
|
el.scrollIntoView({ block: "center", inline: "center" });
|
|
2017
|
-
|
|
2018
|
-
if (!el) return;
|
|
2019
|
-
const r = el.getBoundingClientRect();
|
|
2020
|
-
paint({ top: r.top, left: r.left, width: r.width, height: r.height });
|
|
2021
|
-
});
|
|
2085
|
+
settleThenPaint(el, colors);
|
|
2022
2086
|
} else if (target.rect) {
|
|
2023
|
-
|
|
2087
|
+
let rect = target.rect;
|
|
2088
|
+
const snap = target.scroll;
|
|
2089
|
+
if (snap) {
|
|
2090
|
+
const dx = window.scrollX - snap.x;
|
|
2091
|
+
const dy = window.scrollY - snap.y;
|
|
2092
|
+
if (dx || dy) {
|
|
2093
|
+
rect = { ...rect, left: rect.left - dx, top: rect.top - dy };
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
paint(rect, colors);
|
|
2024
2097
|
}
|
|
2025
2098
|
}
|
|
2026
2099
|
function LocationReveal({ target }) {
|
|
@@ -2104,7 +2177,7 @@ function LocationReveal({ target }) {
|
|
|
2104
2177
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2105
2178
|
"button",
|
|
2106
2179
|
{
|
|
2107
|
-
onClick: () => flashLocate(target),
|
|
2180
|
+
onClick: () => flashLocate(target, { primary: theme.primary, accent: theme.accent }),
|
|
2108
2181
|
className: "qa-mt-1 qa-inline-flex qa-items-center qa-gap-1 qa-rounded-md qa-px-2 qa-py-1 qa-font-medium qa-text-white qa-tap",
|
|
2109
2182
|
style: { background: theme.accent, border: "none", cursor: "pointer" },
|
|
2110
2183
|
children: [
|
|
@@ -2378,8 +2451,37 @@ function NoteList() {
|
|
|
2378
2451
|
}
|
|
2379
2452
|
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "qa-space-y-2", children: notes.map((n, i) => /* @__PURE__ */ jsxRuntime.jsx(NoteItem, { note: n, index: notes.length - i }, n.id)) });
|
|
2380
2453
|
}
|
|
2381
|
-
function
|
|
2454
|
+
function EyeIcon({ open, size = 12, className }) {
|
|
2455
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2456
|
+
"svg",
|
|
2457
|
+
{
|
|
2458
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2459
|
+
viewBox: "0 0 24 24",
|
|
2460
|
+
width: size,
|
|
2461
|
+
height: size,
|
|
2462
|
+
fill: "none",
|
|
2463
|
+
stroke: "currentColor",
|
|
2464
|
+
strokeWidth: 2,
|
|
2465
|
+
strokeLinecap: "round",
|
|
2466
|
+
strokeLinejoin: "round",
|
|
2467
|
+
className,
|
|
2468
|
+
"aria-hidden": "true",
|
|
2469
|
+
children: open ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2470
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }),
|
|
2471
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
2472
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2473
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }),
|
|
2474
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }),
|
|
2475
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }),
|
|
2476
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2", x2: "22", y1: "2", y2: "22" })
|
|
2477
|
+
] })
|
|
2478
|
+
}
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
2482
|
+
function CopyField({ value, ink, maskable = false }) {
|
|
2382
2483
|
const [done, setDone] = React.useState(false);
|
|
2484
|
+
const [revealed, setRevealed] = React.useState(true);
|
|
2383
2485
|
const copy = async () => {
|
|
2384
2486
|
if (value === "\u2014") return;
|
|
2385
2487
|
if (typeof navigator === "undefined" || !navigator.clipboard) return;
|
|
@@ -2390,20 +2492,36 @@ function CopyField({ value, ink }) {
|
|
|
2390
2492
|
} catch {
|
|
2391
2493
|
}
|
|
2392
2494
|
};
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2495
|
+
const hidden = maskable && !revealed && value !== "\u2014";
|
|
2496
|
+
const displayValue = hidden ? MASK : value;
|
|
2497
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1", children: [
|
|
2498
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2499
|
+
"button",
|
|
2500
|
+
{
|
|
2501
|
+
onClick: copy,
|
|
2502
|
+
disabled: value === "\u2014",
|
|
2503
|
+
dir: "ltr",
|
|
2504
|
+
className: "qa-group qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-md qa-px-1.5 qa-py-0.5 qa-font-mono qa-text-xs qa-hover-bg-black-5",
|
|
2505
|
+
style: { background: "transparent", border: "none", cursor: value === "\u2014" ? "default" : "pointer" },
|
|
2506
|
+
children: [
|
|
2507
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: ink }, children: displayValue }),
|
|
2508
|
+
value !== "\u2014" && (done ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 12, className: "qa-text-green-600" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Copy", size: 12, className: "qa-opacity-40 qa-group-hover-opacity-80" }))
|
|
2509
|
+
]
|
|
2510
|
+
}
|
|
2511
|
+
),
|
|
2512
|
+
maskable && value !== "\u2014" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2513
|
+
"button",
|
|
2514
|
+
{
|
|
2515
|
+
type: "button",
|
|
2516
|
+
onClick: () => setRevealed((r) => !r),
|
|
2517
|
+
"aria-label": revealed ? "Hide password" : "Show password",
|
|
2518
|
+
title: revealed ? "Hide password" : "Show password",
|
|
2519
|
+
className: "qa-inline-flex qa-items-center qa-rounded-md qa-p-0.5 qa-opacity-40 qa-hover-opacity-80",
|
|
2520
|
+
style: { background: "transparent", border: "none", cursor: "pointer" },
|
|
2521
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(EyeIcon, { open: revealed, size: 12 })
|
|
2522
|
+
}
|
|
2523
|
+
)
|
|
2524
|
+
] });
|
|
2407
2525
|
}
|
|
2408
2526
|
function CredentialsSection() {
|
|
2409
2527
|
const { loginsUsed, toggleLogin, t, lang, pick: pick2, loginField, credentials, theme } = useQa();
|
|
@@ -2421,7 +2539,7 @@ function CredentialsSection() {
|
|
|
2421
2539
|
}
|
|
2422
2540
|
)
|
|
2423
2541
|
] }),
|
|
2424
|
-
credentials.map((c) => {
|
|
2542
|
+
credentials.map((c, i) => {
|
|
2425
2543
|
const used = loginsUsed.has(c.role);
|
|
2426
2544
|
const label = lang === "ar" && c.roleAr ? c.roleAr : c.role;
|
|
2427
2545
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2459,11 +2577,11 @@ function CredentialsSection() {
|
|
|
2459
2577
|
c.seeded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mt-1.5 qa-flex qa-flex-wrap qa-items-center qa-gap-x-3 qa-gap-y-1 qa-ps-6", children: [
|
|
2460
2578
|
/* @__PURE__ */ jsxRuntime.jsx(CopyField, { value: c.login, ink: theme.ink }),
|
|
2461
2579
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-slate-300", children: "\xB7" }),
|
|
2462
|
-
/* @__PURE__ */ jsxRuntime.jsx(CopyField, { value: c.password, ink: theme.ink })
|
|
2580
|
+
/* @__PURE__ */ jsxRuntime.jsx(CopyField, { value: c.password, ink: theme.ink, maskable: true })
|
|
2463
2581
|
] })
|
|
2464
2582
|
]
|
|
2465
2583
|
},
|
|
2466
|
-
c.role
|
|
2584
|
+
`${c.role}-${i}`
|
|
2467
2585
|
);
|
|
2468
2586
|
})
|
|
2469
2587
|
] });
|
|
@@ -2534,7 +2652,7 @@ function Lane({
|
|
|
2534
2652
|
style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
|
|
2535
2653
|
}
|
|
2536
2654
|
),
|
|
2537
|
-
steps.map((s) => {
|
|
2655
|
+
steps.map((s, i) => {
|
|
2538
2656
|
const k = keyOf(id, s.path);
|
|
2539
2657
|
const on = checked.has(k);
|
|
2540
2658
|
const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
|
|
@@ -2598,7 +2716,7 @@ function Lane({
|
|
|
2598
2716
|
] })
|
|
2599
2717
|
]
|
|
2600
2718
|
}
|
|
2601
|
-
) },
|
|
2719
|
+
) }, `${k}-${i}`);
|
|
2602
2720
|
})
|
|
2603
2721
|
] })
|
|
2604
2722
|
]
|
|
@@ -2742,6 +2860,8 @@ function QaPanel() {
|
|
|
2742
2860
|
}
|
|
2743
2861
|
if (phase === "hidden") {
|
|
2744
2862
|
setShowIn(false);
|
|
2863
|
+
setNaming(false);
|
|
2864
|
+
setConfirmClear(false);
|
|
2745
2865
|
}
|
|
2746
2866
|
if (phase === "visible") {
|
|
2747
2867
|
setShowIn(true);
|
|
@@ -2938,7 +3058,8 @@ function QaPanel() {
|
|
|
2938
3058
|
activeTab,
|
|
2939
3059
|
setActiveTab,
|
|
2940
3060
|
t,
|
|
2941
|
-
theme
|
|
3061
|
+
theme,
|
|
3062
|
+
lang
|
|
2942
3063
|
}
|
|
2943
3064
|
),
|
|
2944
3065
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px", style: { background: `${theme.primary}14` } }),
|
|
@@ -3090,7 +3211,8 @@ function TabsBar({
|
|
|
3090
3211
|
activeTab,
|
|
3091
3212
|
setActiveTab,
|
|
3092
3213
|
t,
|
|
3093
|
-
theme
|
|
3214
|
+
theme,
|
|
3215
|
+
lang
|
|
3094
3216
|
}) {
|
|
3095
3217
|
const tabRefs = React.useRef([]);
|
|
3096
3218
|
const barRef = React.useRef(null);
|
|
@@ -3102,7 +3224,7 @@ function TabsBar({
|
|
|
3102
3224
|
if (!btn || !bar) return;
|
|
3103
3225
|
bar.style.left = `${btn.offsetLeft + 8}px`;
|
|
3104
3226
|
bar.style.width = `${Math.max(0, btn.offsetWidth - 16)}px`;
|
|
3105
|
-
}, [activeTab]);
|
|
3227
|
+
}, [activeTab, lang]);
|
|
3106
3228
|
React.useLayoutEffect(() => {
|
|
3107
3229
|
reposition();
|
|
3108
3230
|
}, [reposition]);
|
|
@@ -3151,6 +3273,13 @@ function TabsBar({
|
|
|
3151
3273
|
}
|
|
3152
3274
|
|
|
3153
3275
|
// src/lib/capture.ts
|
|
3276
|
+
var HTML2CANVAS_TIMEOUT_MS = 1e4;
|
|
3277
|
+
function withTimeout(promise, ms) {
|
|
3278
|
+
return Promise.race([
|
|
3279
|
+
promise,
|
|
3280
|
+
new Promise((resolve) => setTimeout(() => resolve(null), ms))
|
|
3281
|
+
]);
|
|
3282
|
+
}
|
|
3154
3283
|
function toBlob(canvas) {
|
|
3155
3284
|
return new Promise((resolve) => {
|
|
3156
3285
|
if (canvas.toBlob) {
|
|
@@ -3172,24 +3301,28 @@ async function captureRegion(rect, scroll) {
|
|
|
3172
3301
|
try {
|
|
3173
3302
|
const { default: html2canvas } = await import('html2canvas');
|
|
3174
3303
|
const scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
3175
|
-
const canvas = await
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3304
|
+
const canvas = await withTimeout(
|
|
3305
|
+
html2canvas(document.body, {
|
|
3306
|
+
x: sx + rect.left,
|
|
3307
|
+
y: sy + rect.top,
|
|
3308
|
+
width: rect.width,
|
|
3309
|
+
height: rect.height,
|
|
3310
|
+
scale,
|
|
3311
|
+
useCORS: true,
|
|
3312
|
+
allowTaint: true,
|
|
3313
|
+
backgroundColor: null,
|
|
3314
|
+
logging: false,
|
|
3315
|
+
scrollX: sx,
|
|
3316
|
+
scrollY: sy,
|
|
3317
|
+
// Viewport-only clone (not the full document) — see iOS canvas-cap
|
|
3318
|
+
// rationale above. Keeps the offscreen render surface ~viewport*scale.
|
|
3319
|
+
windowWidth: window.innerWidth,
|
|
3320
|
+
windowHeight: window.innerHeight,
|
|
3321
|
+
ignoreElements: (el) => el.nodeType === 1 && typeof el.hasAttribute === "function" && el.hasAttribute("data-qa-overlay")
|
|
3322
|
+
}),
|
|
3323
|
+
HTML2CANVAS_TIMEOUT_MS
|
|
3324
|
+
);
|
|
3325
|
+
if (!canvas) return null;
|
|
3193
3326
|
return await toBlob(canvas);
|
|
3194
3327
|
} catch (err) {
|
|
3195
3328
|
console.warn("[QA] region capture failed:", err);
|
|
@@ -3202,7 +3335,8 @@ function isCleanId(id) {
|
|
|
3202
3335
|
return !!id && /^[a-zA-Z][\w-]*$/.test(id) && id.length <= 40;
|
|
3203
3336
|
}
|
|
3204
3337
|
function esc(value) {
|
|
3205
|
-
|
|
3338
|
+
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(value);
|
|
3339
|
+
return value.replace(/[\\"]/g, "\\$&");
|
|
3206
3340
|
}
|
|
3207
3341
|
function nthOfTypePath(el, maxDepth = 6) {
|
|
3208
3342
|
if (typeof document === "undefined" || !document.body) return "";
|
|
@@ -3230,46 +3364,68 @@ function nthOfTypePath(el, maxDepth = 6) {
|
|
|
3230
3364
|
}
|
|
3231
3365
|
return parts.join(" > ");
|
|
3232
3366
|
}
|
|
3367
|
+
function isUniqueSelector(selector) {
|
|
3368
|
+
if (!selector) return false;
|
|
3369
|
+
try {
|
|
3370
|
+
return document.querySelectorAll(selector).length === 1;
|
|
3371
|
+
} catch {
|
|
3372
|
+
return false;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3233
3375
|
function getStableSelector(el) {
|
|
3234
3376
|
if (typeof document === "undefined") return "";
|
|
3235
3377
|
if (!el || el.nodeType !== 1) return "";
|
|
3236
3378
|
const tag = el.tagName.toLowerCase();
|
|
3237
3379
|
const htmlEl = el;
|
|
3238
|
-
if (isCleanId(htmlEl.id))
|
|
3380
|
+
if (isCleanId(htmlEl.id)) {
|
|
3381
|
+
const candidate = `#${esc(htmlEl.id)}`;
|
|
3382
|
+
if (isUniqueSelector(candidate)) return candidate;
|
|
3383
|
+
}
|
|
3239
3384
|
for (const attr of ["data-testid", "data-test", "data-cy", "data-id", "data-key"]) {
|
|
3240
3385
|
const val = el.getAttribute(attr);
|
|
3241
|
-
if (val)
|
|
3386
|
+
if (val) {
|
|
3387
|
+
const candidate = `[${attr}="${esc(val)}"]`;
|
|
3388
|
+
if (isUniqueSelector(candidate)) return candidate;
|
|
3389
|
+
}
|
|
3242
3390
|
}
|
|
3243
3391
|
if (["button", "a", "input", "select", "textarea"].includes(tag)) {
|
|
3244
3392
|
const label = el.getAttribute("aria-label");
|
|
3245
|
-
if (label)
|
|
3393
|
+
if (label) {
|
|
3394
|
+
const candidate = `${tag}[aria-label="${esc(label)}"]`;
|
|
3395
|
+
if (isUniqueSelector(candidate)) return candidate;
|
|
3396
|
+
}
|
|
3246
3397
|
}
|
|
3247
3398
|
const name = el.getAttribute("name");
|
|
3248
3399
|
if (name && ["input", "select", "textarea"].includes(tag)) {
|
|
3249
|
-
|
|
3400
|
+
const candidate = `${tag}[name="${esc(name)}"]`;
|
|
3401
|
+
if (isUniqueSelector(candidate)) return candidate;
|
|
3250
3402
|
}
|
|
3251
3403
|
return nthOfTypePath(el);
|
|
3252
3404
|
}
|
|
3253
3405
|
|
|
3254
3406
|
// src/lib/scrollLock.ts
|
|
3255
|
-
var
|
|
3407
|
+
var lockCount = 0;
|
|
3256
3408
|
var prevHtmlOverflow = "";
|
|
3257
3409
|
var prevBodyOverflow = "";
|
|
3258
3410
|
function lockPageScroll() {
|
|
3259
|
-
if (typeof document === "undefined"
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3411
|
+
if (typeof document === "undefined") return;
|
|
3412
|
+
if (lockCount === 0) {
|
|
3413
|
+
const html = document.documentElement;
|
|
3414
|
+
const body = document.body;
|
|
3415
|
+
prevHtmlOverflow = html.style.overflow;
|
|
3416
|
+
prevBodyOverflow = body ? body.style.overflow : "";
|
|
3417
|
+
html.style.overflow = "hidden";
|
|
3418
|
+
if (body) body.style.overflow = "hidden";
|
|
3419
|
+
}
|
|
3420
|
+
lockCount++;
|
|
3267
3421
|
}
|
|
3268
3422
|
function unlockPageScroll() {
|
|
3269
|
-
if (typeof document === "undefined" ||
|
|
3270
|
-
|
|
3271
|
-
if (
|
|
3272
|
-
|
|
3423
|
+
if (typeof document === "undefined" || lockCount === 0) return;
|
|
3424
|
+
lockCount--;
|
|
3425
|
+
if (lockCount === 0) {
|
|
3426
|
+
document.documentElement.style.overflow = prevHtmlOverflow;
|
|
3427
|
+
if (document.body) document.body.style.overflow = prevBodyOverflow;
|
|
3428
|
+
}
|
|
3273
3429
|
}
|
|
3274
3430
|
var DRAG_THRESHOLD2 = 6;
|
|
3275
3431
|
var TOUCH_DRAG_THRESHOLD = 12;
|
|
@@ -3288,6 +3444,7 @@ function CaptureMode() {
|
|
|
3288
3444
|
const { addNote, endCapture, t, dir, theme } = useQa();
|
|
3289
3445
|
const coarse = useCoarsePointer();
|
|
3290
3446
|
const layerRef = React.useRef(null);
|
|
3447
|
+
const overlayRootRef = React.useRef(null);
|
|
3291
3448
|
const [phase, setPhase] = React.useState("selecting");
|
|
3292
3449
|
const [hover, setHover] = React.useState(null);
|
|
3293
3450
|
const [drag, setDrag] = React.useState(null);
|
|
@@ -3304,6 +3461,10 @@ function CaptureMode() {
|
|
|
3304
3461
|
const pointerKind = React.useRef("mouse");
|
|
3305
3462
|
const scrollSnap = React.useRef({ x: 0, y: 0 });
|
|
3306
3463
|
const handleDragRef = React.useRef(null);
|
|
3464
|
+
const mountedRef = React.useRef(true);
|
|
3465
|
+
React.useEffect(() => () => {
|
|
3466
|
+
mountedRef.current = false;
|
|
3467
|
+
}, []);
|
|
3307
3468
|
const [cardIn, setCardIn] = React.useState(false);
|
|
3308
3469
|
const elementUnder = React.useCallback((x, y) => {
|
|
3309
3470
|
const layer = layerRef.current;
|
|
@@ -3326,6 +3487,10 @@ function CaptureMode() {
|
|
|
3326
3487
|
lockPageScroll();
|
|
3327
3488
|
try {
|
|
3328
3489
|
const blob = await captureRegion(sel.rect, scrollSnap.current);
|
|
3490
|
+
if (!mountedRef.current) {
|
|
3491
|
+
if (blob) URL.revokeObjectURL(URL.createObjectURL(blob));
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3329
3494
|
setShot(blob);
|
|
3330
3495
|
setShotUrl((old) => {
|
|
3331
3496
|
if (old) URL.revokeObjectURL(old);
|
|
@@ -3333,7 +3498,7 @@ function CaptureMode() {
|
|
|
3333
3498
|
});
|
|
3334
3499
|
} finally {
|
|
3335
3500
|
unlockPageScroll();
|
|
3336
|
-
setCapturing(false);
|
|
3501
|
+
if (mountedRef.current) setCapturing(false);
|
|
3337
3502
|
}
|
|
3338
3503
|
}, []);
|
|
3339
3504
|
React.useEffect(() => {
|
|
@@ -3502,6 +3667,38 @@ function CaptureMode() {
|
|
|
3502
3667
|
document.addEventListener("keydown", onKey, true);
|
|
3503
3668
|
return () => document.removeEventListener("keydown", onKey, true);
|
|
3504
3669
|
}, [endCapture]);
|
|
3670
|
+
React.useEffect(() => {
|
|
3671
|
+
const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
3672
|
+
const onKeyDown = (e) => {
|
|
3673
|
+
if (e.key !== "Tab") return;
|
|
3674
|
+
const root = overlayRootRef.current;
|
|
3675
|
+
if (!root) return;
|
|
3676
|
+
const focusable = Array.from(
|
|
3677
|
+
root.querySelectorAll(FOCUSABLE_SELECTOR)
|
|
3678
|
+
).filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
|
|
3679
|
+
if (focusable.length === 0) {
|
|
3680
|
+
e.preventDefault();
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
const first = focusable[0];
|
|
3684
|
+
const last = focusable[focusable.length - 1];
|
|
3685
|
+
const active = document.activeElement;
|
|
3686
|
+
const activeInside = !!active && root.contains(active);
|
|
3687
|
+
if (e.shiftKey) {
|
|
3688
|
+
if (!activeInside || active === first) {
|
|
3689
|
+
e.preventDefault();
|
|
3690
|
+
last.focus();
|
|
3691
|
+
}
|
|
3692
|
+
} else {
|
|
3693
|
+
if (!activeInside || active === last) {
|
|
3694
|
+
e.preventDefault();
|
|
3695
|
+
first.focus();
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
3700
|
+
return () => document.removeEventListener("keydown", onKeyDown, true);
|
|
3701
|
+
}, []);
|
|
3505
3702
|
React.useEffect(() => {
|
|
3506
3703
|
if (phase === "annotating" && taRef.current) taRef.current.focus();
|
|
3507
3704
|
}, [phase]);
|
|
@@ -3523,7 +3720,8 @@ function CaptureMode() {
|
|
|
3523
3720
|
left: Math.round(selection.rect.left),
|
|
3524
3721
|
width: Math.round(selection.rect.width),
|
|
3525
3722
|
height: Math.round(selection.rect.height)
|
|
3526
|
-
}
|
|
3723
|
+
},
|
|
3724
|
+
scroll: { ...scrollSnap.current }
|
|
3527
3725
|
};
|
|
3528
3726
|
await addNote({ description, screenshot: shot ?? void 0, target });
|
|
3529
3727
|
endCapture();
|
|
@@ -3547,7 +3745,7 @@ function CaptureMode() {
|
|
|
3547
3745
|
const activeRect = drag?.rect ?? candidate?.rect ?? selection?.rect ?? hover?.rect ?? null;
|
|
3548
3746
|
const isRegion = !!drag?.rect || candidate?.kind === "region" || selection?.kind === "region";
|
|
3549
3747
|
const confirmingRegion = phase === "confirming" && candidate?.kind === "region" && coarse;
|
|
3550
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-qa-overlay": "true", children: [
|
|
3748
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-qa-overlay": "true", ref: overlayRootRef, children: [
|
|
3551
3749
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3552
3750
|
"div",
|
|
3553
3751
|
{
|
|
@@ -3559,7 +3757,7 @@ function CaptureMode() {
|
|
|
3559
3757
|
className: "qa-fixed qa-inset-0 qa-z-10090",
|
|
3560
3758
|
style: {
|
|
3561
3759
|
cursor: phase === "selecting" && !coarse ? "crosshair" : "default",
|
|
3562
|
-
touchAction: coarse ?
|
|
3760
|
+
touchAction: coarse ? "none" : "auto",
|
|
3563
3761
|
background: "rgba(58,42,46,0.18)"
|
|
3564
3762
|
}
|
|
3565
3763
|
}
|
|
@@ -4017,6 +4215,7 @@ function Qapture({ config }) {
|
|
|
4017
4215
|
}
|
|
4018
4216
|
|
|
4019
4217
|
exports.Qapture = Qapture;
|
|
4218
|
+
exports.deleteQaDatabase = deleteQaDatabase;
|
|
4020
4219
|
exports.initQaStudio = initQaStudio;
|
|
4021
|
-
//# sourceMappingURL=chunk-
|
|
4022
|
-
//# sourceMappingURL=chunk-
|
|
4220
|
+
//# sourceMappingURL=chunk-DPJW626S.cjs.map
|
|
4221
|
+
//# sourceMappingURL=chunk-DPJW626S.cjs.map
|