@8ux-co/eelzap 0.0.0-stage → 0.10.0
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/CHANGELOG.md +145 -0
- package/LICENSE +21 -0
- package/README.md +1061 -2
- package/dist/chunk-2IUU53QA.js +283 -0
- package/dist/chunk-2IUU53QA.js.map +1 -0
- package/dist/chunk-QN53G5P7.js +64 -0
- package/dist/chunk-QN53G5P7.js.map +1 -0
- package/dist/chunk-VW4EH7NY.js +1162 -0
- package/dist/chunk-VW4EH7NY.js.map +1 -0
- package/dist/fields.cjs +132 -0
- package/dist/fields.cjs.map +1 -0
- package/dist/fields.d.cts +116 -0
- package/dist/fields.d.ts +116 -0
- package/dist/fields.js +105 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.cjs +2055 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2409 -0
- package/dist/index.d.ts +2409 -0
- package/dist/index.js +1998 -0
- package/dist/index.js.map +1 -0
- package/dist/next.d.ts +281 -0
- package/dist/next.js +214 -0
- package/dist/next.js.map +1 -0
- package/dist/preview.d.ts +932 -0
- package/dist/preview.js +885 -0
- package/dist/preview.js.map +1 -0
- package/dist/react.d.ts +181 -0
- package/dist/react.js +225 -0
- package/dist/react.js.map +1 -0
- package/dist/signin-2QOSWZFK.js +305 -0
- package/dist/signin-2QOSWZFK.js.map +1 -0
- package/dist/suggest-5RHURHED.js +1256 -0
- package/dist/suggest-5RHURHED.js.map +1 -0
- package/package.json +109 -4
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-QN53G5P7.js";
|
|
4
|
+
|
|
5
|
+
// src/refs.ts
|
|
6
|
+
var FIELD_KEY_RE = /^[a-z][a-z0-9_]{0,127}$/;
|
|
7
|
+
var ENTRY_REF_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
8
|
+
var DOC_REF_RE = /^doc:([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
9
|
+
var MAX_REF_LENGTH = 300;
|
|
10
|
+
function parseRecordRef(value) {
|
|
11
|
+
if (typeof value !== "string" || value.length > MAX_REF_LENGTH) return null;
|
|
12
|
+
const doc = DOC_REF_RE.exec(value);
|
|
13
|
+
if (doc) return { kind: "document", key: doc[1] };
|
|
14
|
+
const entry = ENTRY_REF_RE.exec(value);
|
|
15
|
+
if (entry) return { kind: "entry", collection: entry[1], slug: entry[2] };
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function isRecordRef(value) {
|
|
19
|
+
return parseRecordRef(value) !== null;
|
|
20
|
+
}
|
|
21
|
+
var PREVIEW_TOKEN_RE = /^zpt_[A-Za-z0-9_-]{43}$/;
|
|
22
|
+
function isPreviewTokenShape(value) {
|
|
23
|
+
return typeof value === "string" && PREVIEW_TOKEN_RE.test(value);
|
|
24
|
+
}
|
|
25
|
+
function isFieldKey(value) {
|
|
26
|
+
return typeof value === "string" && FIELD_KEY_RE.test(value);
|
|
27
|
+
}
|
|
28
|
+
function recordRefsOf(entry) {
|
|
29
|
+
if (!entry) return [];
|
|
30
|
+
if (typeof entry === "string") return isRecordRef(entry) ? [entry] : [];
|
|
31
|
+
const refs = [];
|
|
32
|
+
const add = (ref) => {
|
|
33
|
+
if (isRecordRef(ref) && !refs.includes(ref)) refs.push(ref);
|
|
34
|
+
};
|
|
35
|
+
const collection = entry.collection?.key;
|
|
36
|
+
if (collection) {
|
|
37
|
+
if (entry.slug) add(`${collection}/${entry.slug}`);
|
|
38
|
+
for (const slug of Object.values(entry.localizedSlugs ?? {})) {
|
|
39
|
+
if (slug) add(`${collection}/${slug}`);
|
|
40
|
+
}
|
|
41
|
+
} else if (entry.key) {
|
|
42
|
+
add(`doc:${entry.key}`);
|
|
43
|
+
}
|
|
44
|
+
return refs;
|
|
45
|
+
}
|
|
46
|
+
function zapAttrs(entry, fieldKey) {
|
|
47
|
+
const [ref] = recordRefsOf(entry);
|
|
48
|
+
if (!ref || !isFieldKey(fieldKey)) return {};
|
|
49
|
+
return { "data-zap": `${ref}#${fieldKey}` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/preview/protocol.ts
|
|
53
|
+
var PROTOCOL_SOURCE = "eel-zap";
|
|
54
|
+
var PAGE_UNLOAD_ERROR = "PAGE_UNLOAD";
|
|
55
|
+
var PROTOCOL_VERSION = 1;
|
|
56
|
+
var MAX_MESSAGE_BYTES = 256 * 1024;
|
|
57
|
+
var MAX_ANCHORS = 20;
|
|
58
|
+
var MAX_QUOTE_EXACT = 500;
|
|
59
|
+
var MAX_QUOTE_CONTEXT = 32;
|
|
60
|
+
var MAX_SELECTOR_BYTES = 1024;
|
|
61
|
+
var MAX_URL_LENGTH = 2048;
|
|
62
|
+
var MAX_TAG_REPORT = 2e3;
|
|
63
|
+
var MAX_LABELS = 500;
|
|
64
|
+
var MAX_PINS = 50;
|
|
65
|
+
var OVERLAY_MODES = ["inspect", "select", "spot", "off"];
|
|
66
|
+
var CAPABILITIES = ["overlay", "values", "stega", "pins", "links"];
|
|
67
|
+
var MAX_EMAIL_LENGTH = 320;
|
|
68
|
+
function emptyDropStats() {
|
|
69
|
+
return {
|
|
70
|
+
"not-envelope": 0,
|
|
71
|
+
oversize: 0,
|
|
72
|
+
"unknown-type": 0,
|
|
73
|
+
"invalid-payload": 0,
|
|
74
|
+
"wrong-source": 0,
|
|
75
|
+
"wrong-origin": 0,
|
|
76
|
+
"wrong-session": 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var LOCALE_RE = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,3}$/;
|
|
80
|
+
var SESSION_RE = /^[0-9a-f]{32}$/;
|
|
81
|
+
var COLOR_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
82
|
+
var ERROR_CODE_RE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
83
|
+
var PIN_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
84
|
+
function isLocale(value) {
|
|
85
|
+
return typeof value === "string" && LOCALE_RE.test(value);
|
|
86
|
+
}
|
|
87
|
+
function isSession(value) {
|
|
88
|
+
return typeof value === "string" && SESSION_RE.test(value);
|
|
89
|
+
}
|
|
90
|
+
function isObject(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
|
93
|
+
function isString(value, max) {
|
|
94
|
+
return typeof value === "string" && value.length <= max;
|
|
95
|
+
}
|
|
96
|
+
function isFiniteNumber(value, min, max) {
|
|
97
|
+
return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
|
|
98
|
+
}
|
|
99
|
+
function isOptional(value, check) {
|
|
100
|
+
return value === void 0 || check(value);
|
|
101
|
+
}
|
|
102
|
+
function isNullableString(value, max) {
|
|
103
|
+
return value === null || value === void 0 || isString(value, max);
|
|
104
|
+
}
|
|
105
|
+
function isHttpUrl(value) {
|
|
106
|
+
if (!isString(value, MAX_URL_LENGTH)) return false;
|
|
107
|
+
try {
|
|
108
|
+
const url = new URL(value);
|
|
109
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
var MAX_DRAFT_ROUTE_LENGTH = 512;
|
|
115
|
+
var DRAFT_ROUTE_RE = /^\/[A-Za-z0-9\-._~!$&'()*+,;=:@%/]*$/;
|
|
116
|
+
function isDraftRoutePath(value) {
|
|
117
|
+
if (typeof value !== "string" || value.length > MAX_DRAFT_ROUTE_LENGTH) return false;
|
|
118
|
+
if (!DRAFT_ROUTE_RE.test(value) || value.includes("//")) return false;
|
|
119
|
+
return !value.split("/").some((segment2) => /^(?:\.|%2e){1,2}$/i.test(segment2));
|
|
120
|
+
}
|
|
121
|
+
function isMode(value) {
|
|
122
|
+
return typeof value === "string" && OVERLAY_MODES.includes(value);
|
|
123
|
+
}
|
|
124
|
+
function isTheme(value) {
|
|
125
|
+
if (!isObject(value)) return false;
|
|
126
|
+
for (const key of ["outline", "chip", "chipText"]) {
|
|
127
|
+
const v = value[key];
|
|
128
|
+
if (v !== void 0 && !(typeof v === "string" && COLOR_RE.test(v))) return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
function isZoom(value) {
|
|
133
|
+
return isFiniteNumber(value, 0.05, 4);
|
|
134
|
+
}
|
|
135
|
+
function isPinId(value) {
|
|
136
|
+
return typeof value === "string" && PIN_ID_RE.test(value);
|
|
137
|
+
}
|
|
138
|
+
function isSpotPoint(value) {
|
|
139
|
+
return isObject(value) && isFiniteNumber(value.x, 0, 1) && isFiniteNumber(value.y, 0, 1);
|
|
140
|
+
}
|
|
141
|
+
function isPin(value) {
|
|
142
|
+
return isObject(value) && isPinId(value.id) && Number.isInteger(value.n) && isFiniteNumber(value.n, 1, 999) && value.spot === void 0 !== (value.dom === void 0) && (value.spot === void 0 ? isDomAnchor(value.dom) : isSpotPoint(value.spot));
|
|
143
|
+
}
|
|
144
|
+
function isFieldRef(value) {
|
|
145
|
+
return isObject(value) && isRecordRef(value.recordRef) && isFieldKey(value.fieldKey);
|
|
146
|
+
}
|
|
147
|
+
function isPreviewValue(value) {
|
|
148
|
+
if (value === null || typeof value === "boolean") return true;
|
|
149
|
+
if (typeof value === "string") return true;
|
|
150
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
151
|
+
if (!isObject(value)) return false;
|
|
152
|
+
const keys = Object.keys(value);
|
|
153
|
+
if (keys.length !== 1) return false;
|
|
154
|
+
if ("text" in value) return typeof value.text === "string";
|
|
155
|
+
if ("html" in value) return typeof value.html === "string";
|
|
156
|
+
if ("url" in value) return isString(value.url, MAX_URL_LENGTH);
|
|
157
|
+
if ("email" in value) return isString(value.email, MAX_EMAIL_LENGTH);
|
|
158
|
+
if ("image" in value) {
|
|
159
|
+
const image = value.image;
|
|
160
|
+
return isObject(image) && isNullableString(image.src, MAX_URL_LENGTH) && isNullableString(image.srcset, MAX_URL_LENGTH * 4) && isNullableString(image.alt, 2e3);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
function isTextQuote(value) {
|
|
165
|
+
return isObject(value) && isString(value.exact, MAX_QUOTE_EXACT) && isString(value.prefix, MAX_QUOTE_CONTEXT) && isString(value.suffix, MAX_QUOTE_CONTEXT);
|
|
166
|
+
}
|
|
167
|
+
function isRect(value) {
|
|
168
|
+
return isObject(value) && isFiniteNumber(value.x, -1, 2) && isFiniteNumber(value.y, -1, 2) && isFiniteNumber(value.w, 0, 2) && isFiniteNumber(value.h, 0, 2);
|
|
169
|
+
}
|
|
170
|
+
function utf8Length(value) {
|
|
171
|
+
return new TextEncoder().encode(value).length;
|
|
172
|
+
}
|
|
173
|
+
function isDomAnchor(value) {
|
|
174
|
+
if (!isObject(value)) return false;
|
|
175
|
+
const tagOk = value.tag === null || isString(value.tag, 512) && parseTag(value.tag) !== null;
|
|
176
|
+
const selectorOk = value.selector === null || typeof value.selector === "string" && value.selector.length > 0 && utf8Length(value.selector) <= MAX_SELECTOR_BYTES;
|
|
177
|
+
const viewport = value.viewport;
|
|
178
|
+
return tagOk && selectorOk && (value.textQuote === null || isTextQuote(value.textQuote)) && isRect(value.rect) && isObject(viewport) && isFiniteNumber(viewport.w, 0, 1e5) && isFiniteNumber(viewport.h, 0, 1e7);
|
|
179
|
+
}
|
|
180
|
+
function isPageAnchor(value) {
|
|
181
|
+
if (!isObject(value)) return false;
|
|
182
|
+
const field = value.field;
|
|
183
|
+
const fieldOk = field === null || isObject(field) && isRecordRef(field.recordRef) && isFieldKey(field.key) && (field.locale === null || isLocale(field.locale));
|
|
184
|
+
return fieldOk && isDomAnchor(value.dom) && isHttpUrl(value.pageUrl);
|
|
185
|
+
}
|
|
186
|
+
function isAnchorList(value, check) {
|
|
187
|
+
return Array.isArray(value) && value.length <= MAX_ANCHORS && value.every(check);
|
|
188
|
+
}
|
|
189
|
+
function parseTag(value) {
|
|
190
|
+
const trimmed = value.trim();
|
|
191
|
+
const hash = trimmed.lastIndexOf("#");
|
|
192
|
+
if (hash <= 0) return null;
|
|
193
|
+
const recordRef = trimmed.slice(0, hash);
|
|
194
|
+
const fieldKey = trimmed.slice(hash + 1);
|
|
195
|
+
if (!isRecordRef(recordRef) || !isFieldKey(fieldKey)) return null;
|
|
196
|
+
return { recordRef, fieldKey };
|
|
197
|
+
}
|
|
198
|
+
var PAGE_VALIDATORS = {
|
|
199
|
+
"zap:ready": (p) => isObject(p) && isString(p.version, 32) && Array.isArray(p.capabilities) && p.capabilities.length <= CAPABILITIES.length && p.capabilities.every((c) => CAPABILITIES.includes(c)) && isHttpUrl(p.pageUrl) && (p.draftRoute === void 0 || p.draftRoute === null || isDraftRoutePath(p.draftRoute)),
|
|
200
|
+
"zap:tags": (p) => Array.isArray(p) && p.length <= MAX_TAG_REPORT && p.every(
|
|
201
|
+
(t) => isObject(t) && typeof t.tag === "string" && isRecordRef(t.recordRef) && isFieldKey(t.fieldKey) && t.tag === `${t.recordRef}#${t.fieldKey}` && Number.isInteger(t.count) && t.count > 0 && (t.source === void 0 || t.source === "stega" || t.source === "attr")
|
|
202
|
+
),
|
|
203
|
+
"zap:click": isFieldRef,
|
|
204
|
+
"zap:select": (p) => isObject(p) && isAnchorList(p.anchors, isPageAnchor),
|
|
205
|
+
"zap:navigate": (p) => isObject(p) && isHttpUrl(p.url),
|
|
206
|
+
"zap:error": (p) => isObject(p) && typeof p.code === "string" && ERROR_CODE_RE.test(p.code) && isOptional(p.detail, (d) => isString(d, 500)),
|
|
207
|
+
"zap:spot": (p) => isObject(p) && isSpotPoint(p.spot) && isObject(p.viewport) && isFiniteNumber(p.viewport.w, 1, 1e5) && isFiniteNumber(p.viewport.h, 1, 1e7) && isHttpUrl(p.pageUrl),
|
|
208
|
+
"zap:pin": (p) => isObject(p) && isPinId(p.id)
|
|
209
|
+
};
|
|
210
|
+
var EDITOR_VALIDATORS = {
|
|
211
|
+
"zap:hello": (p) => {
|
|
212
|
+
if (!isObject(p) || !isSession(p.session) || !isString(p.siteKey, 128)) return false;
|
|
213
|
+
if (!isLocale(p.locale) || !isRecordRef(p.recordRef) || !isMode(p.mode)) return false;
|
|
214
|
+
if (p.theme !== void 0 && !isTheme(p.theme)) return false;
|
|
215
|
+
if (p.zoom !== void 0 && !isZoom(p.zoom)) return false;
|
|
216
|
+
if (p.previewToken !== void 0 && !isPreviewTokenShape(p.previewToken)) return false;
|
|
217
|
+
const labels = p.labels;
|
|
218
|
+
if (!isObject(labels)) return false;
|
|
219
|
+
const entries = Object.entries(labels);
|
|
220
|
+
return entries.length <= MAX_LABELS && entries.every(([key, label]) => isFieldKey(key) && isString(label, 200));
|
|
221
|
+
},
|
|
222
|
+
"zap:values": (p) => {
|
|
223
|
+
if (!isObject(p) || !isRecordRef(p.recordRef) || !isLocale(p.locale)) return false;
|
|
224
|
+
const patch = p.patch;
|
|
225
|
+
if (!isObject(patch)) return false;
|
|
226
|
+
return Object.entries(patch).every(([key, value]) => isFieldKey(key) && isPreviewValue(value));
|
|
227
|
+
},
|
|
228
|
+
"zap:focus-field": isFieldRef,
|
|
229
|
+
"zap:mode": isMode,
|
|
230
|
+
"zap:zoom": (p) => isObject(p) && isZoom(p.zoom),
|
|
231
|
+
"zap:highlight": (p) => isObject(p) && isAnchorList(p.anchors, isDomAnchor),
|
|
232
|
+
"zap:pins": (p) => isObject(p) && Array.isArray(p.pins) && p.pins.length <= MAX_PINS && p.pins.every(isPin)
|
|
233
|
+
};
|
|
234
|
+
var PAGE_MESSAGE_TYPES = /* @__PURE__ */ Object.keys(PAGE_VALIDATORS);
|
|
235
|
+
var EDITOR_MESSAGE_TYPES = /* @__PURE__ */ Object.keys(
|
|
236
|
+
EDITOR_VALIDATORS
|
|
237
|
+
);
|
|
238
|
+
function messageBytes(data) {
|
|
239
|
+
try {
|
|
240
|
+
const json = JSON.stringify(data);
|
|
241
|
+
return json === void 0 ? Infinity : utf8Length(json);
|
|
242
|
+
} catch {
|
|
243
|
+
return Infinity;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function parseWith(data, validators) {
|
|
247
|
+
if (!isObject(data) || data.source !== PROTOCOL_SOURCE || data.v !== PROTOCOL_VERSION || typeof data.session !== "string" || data.session.length > 64 || typeof data.type !== "string") {
|
|
248
|
+
return { ok: false, reason: "not-envelope" };
|
|
249
|
+
}
|
|
250
|
+
if (messageBytes(data) > MAX_MESSAGE_BYTES) return { ok: false, reason: "oversize" };
|
|
251
|
+
const validate = Object.prototype.hasOwnProperty.call(validators, data.type) ? validators[data.type] : void 0;
|
|
252
|
+
if (!validate) return { ok: false, reason: "unknown-type" };
|
|
253
|
+
if (!validate(data.payload)) return { ok: false, reason: "invalid-payload" };
|
|
254
|
+
return {
|
|
255
|
+
ok: true,
|
|
256
|
+
message: {
|
|
257
|
+
source: PROTOCOL_SOURCE,
|
|
258
|
+
v: PROTOCOL_VERSION,
|
|
259
|
+
session: data.session,
|
|
260
|
+
type: data.type,
|
|
261
|
+
payload: data.payload
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function parseEditorMessage(data) {
|
|
266
|
+
return parseWith(data, EDITOR_VALIDATORS);
|
|
267
|
+
}
|
|
268
|
+
function parsePageMessage(data) {
|
|
269
|
+
return parseWith(data, PAGE_VALIDATORS);
|
|
270
|
+
}
|
|
271
|
+
function envelope(type, session, payload) {
|
|
272
|
+
return { source: PROTOCOL_SOURCE, v: PROTOCOL_VERSION, session, type, payload };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/preview/anchor.ts
|
|
276
|
+
function cssEscape(value) {
|
|
277
|
+
const native = globalThis.CSS?.escape;
|
|
278
|
+
if (native) return native(value);
|
|
279
|
+
let out = "";
|
|
280
|
+
for (let i = 0; i < value.length; i++) {
|
|
281
|
+
const ch = value.charAt(i);
|
|
282
|
+
const code = value.charCodeAt(i);
|
|
283
|
+
const digit = code >= 48 && code <= 57;
|
|
284
|
+
if (code === 0) out += "\uFFFD";
|
|
285
|
+
else if (code >= 1 && code <= 31 || code === 127 || i === 0 && digit || i === 1 && digit && value.charCodeAt(0) === 45) {
|
|
286
|
+
out += `\\${code.toString(16)} `;
|
|
287
|
+
} else if (i === 0 && value.length === 1 && code === 45) out += `\\${ch}`;
|
|
288
|
+
else if (code >= 128 || code === 45 || code === 95 || digit || /[A-Za-z]/.test(ch)) out += ch;
|
|
289
|
+
else out += `\\${ch}`;
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
function safeQueryAll(root, selector) {
|
|
294
|
+
try {
|
|
295
|
+
return Array.from(root.querySelectorAll(selector));
|
|
296
|
+
} catch {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function usableId(element) {
|
|
301
|
+
const id = element.getAttribute("id");
|
|
302
|
+
if (!id || id.length > 64 || /^[:\d]|:|\d{3,}/.test(id)) return null;
|
|
303
|
+
return id;
|
|
304
|
+
}
|
|
305
|
+
function segment(element) {
|
|
306
|
+
const tag = element.localName;
|
|
307
|
+
const parent = element.parentElement;
|
|
308
|
+
if (!parent) return tag;
|
|
309
|
+
let index = 0;
|
|
310
|
+
let count = 0;
|
|
311
|
+
for (const sibling of Array.from(parent.children)) {
|
|
312
|
+
if (sibling.localName !== tag) continue;
|
|
313
|
+
count++;
|
|
314
|
+
if (sibling === element) index = count;
|
|
315
|
+
}
|
|
316
|
+
return count > 1 ? `${tag}:nth-of-type(${index})` : tag;
|
|
317
|
+
}
|
|
318
|
+
function isUnique(doc, selector, element) {
|
|
319
|
+
const found = safeQueryAll(doc, selector);
|
|
320
|
+
return found.length === 1 && found[0] === element;
|
|
321
|
+
}
|
|
322
|
+
function buildSelector(element, doc) {
|
|
323
|
+
const ownId = usableId(element);
|
|
324
|
+
if (ownId) {
|
|
325
|
+
const selector = `#${cssEscape(ownId)}`;
|
|
326
|
+
if (isUnique(doc, selector, element)) return selector;
|
|
327
|
+
}
|
|
328
|
+
const parts = [];
|
|
329
|
+
for (let node = element; node; node = node.parentElement) {
|
|
330
|
+
if (node.localName === "html") break;
|
|
331
|
+
if (node !== element) {
|
|
332
|
+
const id = usableId(node);
|
|
333
|
+
if (id) {
|
|
334
|
+
const candidate2 = [`#${cssEscape(id)}`, ...parts].join(" > ");
|
|
335
|
+
if (isUnique(doc, candidate2, element)) return fit(candidate2);
|
|
336
|
+
}
|
|
337
|
+
const zap = node.getAttribute("data-zap") ?? node.getAttribute("data-zap-entry");
|
|
338
|
+
const zapAttr = node.hasAttribute("data-zap") ? "data-zap" : "data-zap-entry";
|
|
339
|
+
if (zap) {
|
|
340
|
+
const candidate2 = [`[${zapAttr}="${cssEscape(zap)}"]`, ...parts].join(" > ");
|
|
341
|
+
if (isUnique(doc, candidate2, element)) return fit(candidate2);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
parts.unshift(segment(node));
|
|
345
|
+
const candidate = parts.join(" > ");
|
|
346
|
+
if (isUnique(doc, candidate, element)) return fit(candidate);
|
|
347
|
+
}
|
|
348
|
+
const full = parts.join(" > ");
|
|
349
|
+
return full && isUnique(doc, full, element) ? fit(full) : null;
|
|
350
|
+
}
|
|
351
|
+
function fit(selector) {
|
|
352
|
+
return utf8Length(selector) <= MAX_SELECTOR_BYTES ? selector : null;
|
|
353
|
+
}
|
|
354
|
+
function normalizeText(value) {
|
|
355
|
+
return value.replace(/\s+/g, " ").trim();
|
|
356
|
+
}
|
|
357
|
+
function textQuoteFor(element, doc) {
|
|
358
|
+
const exact = normalizeText(element.textContent ?? "").slice(0, MAX_QUOTE_EXACT);
|
|
359
|
+
if (!exact) return null;
|
|
360
|
+
let prefix = "";
|
|
361
|
+
let suffix = "";
|
|
362
|
+
const body = doc.body;
|
|
363
|
+
if (body && body.contains(element) && typeof doc.createRange === "function") {
|
|
364
|
+
try {
|
|
365
|
+
const before = doc.createRange();
|
|
366
|
+
before.setStart(body, 0);
|
|
367
|
+
before.setEndBefore(element);
|
|
368
|
+
prefix = normalizeText(before.toString()).slice(-MAX_QUOTE_CONTEXT);
|
|
369
|
+
const after = doc.createRange();
|
|
370
|
+
after.setStartAfter(element);
|
|
371
|
+
after.setEnd(body, body.childNodes.length);
|
|
372
|
+
suffix = normalizeText(after.toString()).slice(0, MAX_QUOTE_CONTEXT);
|
|
373
|
+
} catch {
|
|
374
|
+
prefix = "";
|
|
375
|
+
suffix = "";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return { exact, prefix, suffix };
|
|
379
|
+
}
|
|
380
|
+
function round(value) {
|
|
381
|
+
return Math.round(value * 1e4) / 1e4;
|
|
382
|
+
}
|
|
383
|
+
function pageSize(doc) {
|
|
384
|
+
const root = doc.documentElement;
|
|
385
|
+
return {
|
|
386
|
+
w: Math.max(root.scrollWidth, root.clientWidth, 1),
|
|
387
|
+
h: Math.max(root.scrollHeight, root.clientHeight, 1)
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function normalizedRect(element, doc, win) {
|
|
391
|
+
const box = element.getBoundingClientRect();
|
|
392
|
+
const size = pageSize(doc);
|
|
393
|
+
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
394
|
+
return {
|
|
395
|
+
rect: {
|
|
396
|
+
x: round(clamp((box.left + win.scrollX) / size.w, -1, 2)),
|
|
397
|
+
y: round(clamp((box.top + win.scrollY) / size.h, -1, 2)),
|
|
398
|
+
w: round(clamp(box.width / size.w, 0, 2)),
|
|
399
|
+
h: round(clamp(box.height / size.h, 0, 2))
|
|
400
|
+
},
|
|
401
|
+
viewport: size
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function buildDomAnchor(element, tagged, doc, win) {
|
|
405
|
+
return {
|
|
406
|
+
tag: tagged?.tag ?? null,
|
|
407
|
+
selector: buildSelector(element, doc),
|
|
408
|
+
textQuote: textQuoteFor(element, doc),
|
|
409
|
+
...normalizedRect(element, doc, win)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function buildAnchor(element, index, pageUrl, doc, win) {
|
|
413
|
+
const tagged = index.get(element);
|
|
414
|
+
return {
|
|
415
|
+
field: tagged ? { recordRef: tagged.recordRef, key: tagged.fieldKey, locale: tagged.locale } : null,
|
|
416
|
+
dom: buildDomAnchor(element, tagged, doc, win),
|
|
417
|
+
pageUrl
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function findAnchorElement(anchor, index, doc, win) {
|
|
421
|
+
if (anchor.tag) {
|
|
422
|
+
const hash = anchor.tag.lastIndexOf("#");
|
|
423
|
+
const tagged = index.byField(anchor.tag.slice(0, hash), anchor.tag.slice(hash + 1));
|
|
424
|
+
if (tagged.length === 1) return tagged[0].element;
|
|
425
|
+
if (tagged.length > 1 && anchor.textQuote) {
|
|
426
|
+
const exact = anchor.textQuote.exact;
|
|
427
|
+
const match = tagged.find((t) => normalizeText(t.element.textContent ?? "") === exact);
|
|
428
|
+
if (match) return match.element;
|
|
429
|
+
}
|
|
430
|
+
if (tagged.length > 0) return tagged[0].element;
|
|
431
|
+
}
|
|
432
|
+
if (anchor.selector) {
|
|
433
|
+
const found = safeQueryAll(doc, anchor.selector);
|
|
434
|
+
if (found.length === 1) return found[0];
|
|
435
|
+
}
|
|
436
|
+
if (anchor.textQuote && anchor.textQuote.exact.length >= 3 && doc.body) {
|
|
437
|
+
const exact = anchor.textQuote.exact;
|
|
438
|
+
let best = null;
|
|
439
|
+
let bestLength = Infinity;
|
|
440
|
+
for (const element of Array.from(doc.body.getElementsByTagName("*"))) {
|
|
441
|
+
const text = normalizeText(element.textContent ?? "");
|
|
442
|
+
if (text.length < bestLength && text.includes(exact)) {
|
|
443
|
+
best = element;
|
|
444
|
+
bestLength = text.length;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (best) return best;
|
|
448
|
+
}
|
|
449
|
+
const fromPoint = doc.elementFromPoint;
|
|
450
|
+
if (typeof fromPoint === "function") {
|
|
451
|
+
const size = pageSize(doc);
|
|
452
|
+
const x = (anchor.rect.x + anchor.rect.w / 2) * size.w - win.scrollX;
|
|
453
|
+
const y = (anchor.rect.y + anchor.rect.h / 2) * size.h - win.scrollY;
|
|
454
|
+
if (x >= 0 && y >= 0 && x <= win.innerWidth && y <= win.innerHeight) {
|
|
455
|
+
try {
|
|
456
|
+
return fromPoint.call(doc, x, y);
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/preview/overlay.ts
|
|
466
|
+
var OVERLAY_HOST_TAG = "eel-zap-overlay";
|
|
467
|
+
var DEFAULT_THEME = {
|
|
468
|
+
outline: "#CF8700",
|
|
469
|
+
chip: "#9A6200",
|
|
470
|
+
chipText: "#FFFFFF"
|
|
471
|
+
};
|
|
472
|
+
var SELECTED_TINT = "rgba(207,135,0,0.10)";
|
|
473
|
+
var CHECK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
474
|
+
var Overlay = class {
|
|
475
|
+
constructor(options) {
|
|
476
|
+
__publicField(this, "options", options);
|
|
477
|
+
__publicField(this, "host", null);
|
|
478
|
+
__publicField(this, "layer", null);
|
|
479
|
+
__publicField(this, "mode", "off");
|
|
480
|
+
__publicField(this, "zoom", 1);
|
|
481
|
+
__publicField(this, "theme", { ...DEFAULT_THEME });
|
|
482
|
+
__publicField(this, "labels", {});
|
|
483
|
+
__publicField(this, "hover", null);
|
|
484
|
+
__publicField(this, "focused", []);
|
|
485
|
+
__publicField(this, "highlighted", []);
|
|
486
|
+
__publicField(this, "selected", []);
|
|
487
|
+
__publicField(this, "pins", []);
|
|
488
|
+
__publicField(this, "pending", null);
|
|
489
|
+
__publicField(this, "capture", null);
|
|
490
|
+
__publicField(this, "frame", null);
|
|
491
|
+
__publicField(this, "listeners", []);
|
|
492
|
+
}
|
|
493
|
+
mount() {
|
|
494
|
+
if (this.host) return;
|
|
495
|
+
const { doc } = this.options;
|
|
496
|
+
const host = doc.createElement(OVERLAY_HOST_TAG);
|
|
497
|
+
const hostStyle = {
|
|
498
|
+
all: "initial",
|
|
499
|
+
position: "fixed",
|
|
500
|
+
inset: "0",
|
|
501
|
+
"pointer-events": "none",
|
|
502
|
+
"z-index": "2147483647",
|
|
503
|
+
display: "block"
|
|
504
|
+
};
|
|
505
|
+
for (const [name, value] of Object.entries(hostStyle)) {
|
|
506
|
+
host.style.setProperty(name, value, "important");
|
|
507
|
+
}
|
|
508
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
509
|
+
const style = doc.createElement("style");
|
|
510
|
+
style.textContent = STYLES;
|
|
511
|
+
const layer = doc.createElement("div");
|
|
512
|
+
layer.className = "layer";
|
|
513
|
+
const capture = doc.createElement("div");
|
|
514
|
+
capture.className = "capture";
|
|
515
|
+
capture.onclick = (event) => {
|
|
516
|
+
this.consume(event);
|
|
517
|
+
this.spotAt(event);
|
|
518
|
+
};
|
|
519
|
+
shadow.append(style, capture, layer);
|
|
520
|
+
this.capture = capture;
|
|
521
|
+
doc.documentElement.appendChild(host);
|
|
522
|
+
this.host = host;
|
|
523
|
+
this.layer = layer;
|
|
524
|
+
this.install();
|
|
525
|
+
this.render();
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Register the event listeners. Called by `mount()`, and EARLIER by the
|
|
529
|
+
* runtime — at script start, before any site script has run — so our
|
|
530
|
+
* capture listeners on `window` are first in line, ahead of anything the
|
|
531
|
+
* site registers.
|
|
532
|
+
*/
|
|
533
|
+
install() {
|
|
534
|
+
if (this.listeners.length > 0) return;
|
|
535
|
+
const { win } = this.options;
|
|
536
|
+
this.listen(win, "mouseover", (event) => this.onPointer(event));
|
|
537
|
+
this.listen(win, "mouseout", (event) => {
|
|
538
|
+
if (!event.relatedTarget) this.setHover(null);
|
|
539
|
+
});
|
|
540
|
+
this.listen(win, "click", (event) => this.onClick(event));
|
|
541
|
+
this.listen(win, "keydown", (event) => this.onKey(event));
|
|
542
|
+
this.listen(win, "scroll", () => this.schedule());
|
|
543
|
+
this.listen(win, "resize", () => this.schedule());
|
|
544
|
+
}
|
|
545
|
+
destroy() {
|
|
546
|
+
for (const [target, type, listener, capture] of this.listeners) {
|
|
547
|
+
target.removeEventListener(type, listener, capture);
|
|
548
|
+
}
|
|
549
|
+
this.listeners.length = 0;
|
|
550
|
+
this.host?.remove();
|
|
551
|
+
this.host = null;
|
|
552
|
+
this.layer = null;
|
|
553
|
+
this.capture = null;
|
|
554
|
+
}
|
|
555
|
+
/** The host element, so the tag observer can ignore our own mutations. */
|
|
556
|
+
get hostElement() {
|
|
557
|
+
return this.host;
|
|
558
|
+
}
|
|
559
|
+
getMode() {
|
|
560
|
+
return this.mode;
|
|
561
|
+
}
|
|
562
|
+
setMode(mode) {
|
|
563
|
+
if (mode === this.mode) return;
|
|
564
|
+
const hadSelection = this.selected.length > 0;
|
|
565
|
+
this.mode = mode;
|
|
566
|
+
this.hover = null;
|
|
567
|
+
this.pending = null;
|
|
568
|
+
if (mode !== "select" && hadSelection) {
|
|
569
|
+
this.selected = [];
|
|
570
|
+
this.options.onSelect?.([]);
|
|
571
|
+
}
|
|
572
|
+
if (mode === "off") {
|
|
573
|
+
this.focused = [];
|
|
574
|
+
this.highlighted = [];
|
|
575
|
+
}
|
|
576
|
+
this.schedule();
|
|
577
|
+
}
|
|
578
|
+
setZoom(zoom) {
|
|
579
|
+
if (!(zoom > 0)) return;
|
|
580
|
+
this.zoom = zoom;
|
|
581
|
+
this.schedule();
|
|
582
|
+
}
|
|
583
|
+
setTheme(theme) {
|
|
584
|
+
this.theme = { ...DEFAULT_THEME, ...theme ?? {} };
|
|
585
|
+
this.schedule();
|
|
586
|
+
}
|
|
587
|
+
setLabels(labels) {
|
|
588
|
+
this.labels = { ...labels };
|
|
589
|
+
this.schedule();
|
|
590
|
+
}
|
|
591
|
+
/** Outline every element of a field and scroll the first into view. */
|
|
592
|
+
focusField(elements) {
|
|
593
|
+
this.focused = elements.slice();
|
|
594
|
+
const first = elements[0];
|
|
595
|
+
if (first && typeof first.scrollIntoView === "function") {
|
|
596
|
+
first.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
597
|
+
}
|
|
598
|
+
this.schedule();
|
|
599
|
+
return elements.length;
|
|
600
|
+
}
|
|
601
|
+
/** Outline change-request anchors and scroll to the first. */
|
|
602
|
+
highlight(elements) {
|
|
603
|
+
this.highlighted = elements.slice(0, MAX_ANCHORS);
|
|
604
|
+
const first = this.highlighted[0];
|
|
605
|
+
if (first && typeof first.scrollIntoView === "function") {
|
|
606
|
+
first.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
607
|
+
}
|
|
608
|
+
this.schedule();
|
|
609
|
+
}
|
|
610
|
+
/** Draw these pins (≤ 50); drops the pending spot pin. */
|
|
611
|
+
setPins(pins) {
|
|
612
|
+
this.pins = pins.slice(0, MAX_PINS);
|
|
613
|
+
this.clearPending();
|
|
614
|
+
}
|
|
615
|
+
/** Drop the pending spot pin. */
|
|
616
|
+
clearPending() {
|
|
617
|
+
this.pending = null;
|
|
618
|
+
this.schedule();
|
|
619
|
+
}
|
|
620
|
+
clearSelection() {
|
|
621
|
+
if (this.selected.length === 0) return;
|
|
622
|
+
this.selected = [];
|
|
623
|
+
this.options.onSelect?.([]);
|
|
624
|
+
this.schedule();
|
|
625
|
+
}
|
|
626
|
+
get selection() {
|
|
627
|
+
return this.selected;
|
|
628
|
+
}
|
|
629
|
+
/** For specs: what the closed shadow root currently draws. */
|
|
630
|
+
inspect() {
|
|
631
|
+
const nodes = Array.from(this.layer?.children ?? []);
|
|
632
|
+
const boxes = nodes.filter((node) => node.className === "box").map((node) => ({
|
|
633
|
+
kind: node.dataset.kind,
|
|
634
|
+
label: node.querySelector(".chip")?.textContent ?? null
|
|
635
|
+
}));
|
|
636
|
+
const capture = this.capture?.style.display === "block" ? this.capture : null;
|
|
637
|
+
return { boxes, pins: nodes.filter((n) => n.className === "pin"), capture, zoom: this.zoom };
|
|
638
|
+
}
|
|
639
|
+
/** For specs: the style of the n-th box. */
|
|
640
|
+
boxStyle(n) {
|
|
641
|
+
const node = this.layer?.children[n];
|
|
642
|
+
return node?.style ?? null;
|
|
643
|
+
}
|
|
644
|
+
// ── Events ────────────────────────────────────────────────────────────────
|
|
645
|
+
listen(target, type, listener) {
|
|
646
|
+
target.addEventListener(type, listener, true);
|
|
647
|
+
this.listeners.push([target, type, listener, true]);
|
|
648
|
+
}
|
|
649
|
+
isOwn(node) {
|
|
650
|
+
return !!this.host && node === this.host || !!node && !!this.options.ignore?.(node);
|
|
651
|
+
}
|
|
652
|
+
targetFor(event) {
|
|
653
|
+
const target = event.target;
|
|
654
|
+
if (!target || this.isOwn(target) || typeof target.closest !== "function") return null;
|
|
655
|
+
if (this.mode === "inspect") return this.options.index.closest(target)?.element ?? null;
|
|
656
|
+
if (this.mode === "select") {
|
|
657
|
+
const tagged = this.options.index.closest(target);
|
|
658
|
+
const element = tagged?.element ?? target;
|
|
659
|
+
const name = element.localName;
|
|
660
|
+
return name === "html" || name === "body" ? null : element;
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
onPointer(event) {
|
|
665
|
+
if (this.mode === "off") return;
|
|
666
|
+
this.setHover(this.targetFor(event));
|
|
667
|
+
}
|
|
668
|
+
setHover(element) {
|
|
669
|
+
if (element === this.hover) return;
|
|
670
|
+
this.hover = element;
|
|
671
|
+
this.schedule();
|
|
672
|
+
}
|
|
673
|
+
onClick(event) {
|
|
674
|
+
if (this.mode === "off" || this.isOwn(event.target)) return;
|
|
675
|
+
if (this.mode === "spot") {
|
|
676
|
+
this.consume(event);
|
|
677
|
+
return this.spotAt(event);
|
|
678
|
+
}
|
|
679
|
+
const element = this.targetFor(event);
|
|
680
|
+
if (this.mode === "inspect") {
|
|
681
|
+
if (!element) return;
|
|
682
|
+
const tagged = this.options.index.get(element);
|
|
683
|
+
if (!tagged) return;
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
event.stopImmediatePropagation();
|
|
686
|
+
this.options.onInspect?.(tagged);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.consume(event);
|
|
690
|
+
const additive = event.shiftKey || event.metaKey || event.ctrlKey || !!this.options.additive?.();
|
|
691
|
+
if (this.options.spotOnLarge && !additive && (!element || this.isLarge(element))) {
|
|
692
|
+
this.clearSelection();
|
|
693
|
+
return this.spotAt(event);
|
|
694
|
+
}
|
|
695
|
+
if (!element) return;
|
|
696
|
+
if (this.pending) this.clearPending();
|
|
697
|
+
let next;
|
|
698
|
+
if (additive) {
|
|
699
|
+
next = this.selected.includes(element) ? this.selected.filter((e) => e !== element) : this.selected.length < MAX_ANCHORS ? [...this.selected, element] : this.selected;
|
|
700
|
+
} else {
|
|
701
|
+
next = [element];
|
|
702
|
+
}
|
|
703
|
+
if (next === this.selected) return;
|
|
704
|
+
this.selected = next;
|
|
705
|
+
this.options.onSelect?.(next.slice());
|
|
706
|
+
this.schedule();
|
|
707
|
+
}
|
|
708
|
+
/** Most of the viewport: a wrapper, not the thing the person pointed at. */
|
|
709
|
+
isLarge(element) {
|
|
710
|
+
const { win } = this.options;
|
|
711
|
+
const rect = element.getBoundingClientRect();
|
|
712
|
+
const area = Math.max(1, win.innerWidth * win.innerHeight);
|
|
713
|
+
return rect.width * rect.height / area >= LARGE_SHARE;
|
|
714
|
+
}
|
|
715
|
+
consume(event) {
|
|
716
|
+
event.preventDefault();
|
|
717
|
+
event.stopImmediatePropagation();
|
|
718
|
+
}
|
|
719
|
+
/** The click as a point of the whole document (§3.3 spot anchor). */
|
|
720
|
+
spotAt(event) {
|
|
721
|
+
const { doc, win } = this.options;
|
|
722
|
+
const root = doc.documentElement;
|
|
723
|
+
const w = Math.max(root.scrollWidth, 1);
|
|
724
|
+
const h = Math.max(root.scrollHeight, 1);
|
|
725
|
+
const at = (v, size) => Math.min(1, Math.max(0, v / size));
|
|
726
|
+
const spot = { x: at(event.clientX + win.scrollX, w), y: at(event.clientY + win.scrollY, h) };
|
|
727
|
+
this.pending = spot;
|
|
728
|
+
this.schedule();
|
|
729
|
+
this.options.onSpot?.({ spot, viewport: { w, h } });
|
|
730
|
+
}
|
|
731
|
+
onKey(event) {
|
|
732
|
+
if (event.key !== "Escape") return;
|
|
733
|
+
if ((this.mode === "spot" || this.mode === "select") && this.pending) {
|
|
734
|
+
this.consume(event);
|
|
735
|
+
return this.clearPending();
|
|
736
|
+
}
|
|
737
|
+
if (this.mode !== "select" || this.selected.length === 0) return;
|
|
738
|
+
this.consume(event);
|
|
739
|
+
this.clearSelection();
|
|
740
|
+
}
|
|
741
|
+
// ── Drawing ───────────────────────────────────────────────────────────────
|
|
742
|
+
schedule() {
|
|
743
|
+
if (!this.layer || this.frame !== null) return;
|
|
744
|
+
const win = this.options.win;
|
|
745
|
+
const raf = typeof win.requestAnimationFrame === "function" ? (fn) => win.requestAnimationFrame(fn) : (fn) => win.setTimeout(fn, 16);
|
|
746
|
+
this.frame = raf(() => {
|
|
747
|
+
this.frame = null;
|
|
748
|
+
this.render();
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/** Draw now (also used by specs, which have no animation frames). */
|
|
752
|
+
render() {
|
|
753
|
+
const layer = this.layer;
|
|
754
|
+
if (!layer) return;
|
|
755
|
+
const boxes = [];
|
|
756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
757
|
+
const add = (element, kind) => {
|
|
758
|
+
if (seen.has(element) || !element.isConnected) return;
|
|
759
|
+
seen.add(element);
|
|
760
|
+
const tagged = this.options.index.get(element);
|
|
761
|
+
boxes.push({ element, kind, label: tagged ? this.labelFor(tagged) : null });
|
|
762
|
+
};
|
|
763
|
+
if (this.mode !== "off") {
|
|
764
|
+
for (const element of this.selected) add(element, "selected");
|
|
765
|
+
for (const element of this.focused) add(element, "focus");
|
|
766
|
+
for (const element of this.highlighted) add(element, "highlight");
|
|
767
|
+
if (this.hover) add(this.hover, "hover");
|
|
768
|
+
}
|
|
769
|
+
const nodes = boxes.map((box) => this.drawBox(box));
|
|
770
|
+
if (this.capture) this.capture.style.display = this.mode === "spot" ? "block" : "none";
|
|
771
|
+
if (this.mode !== "off" || this.options.pinsWhenOff) {
|
|
772
|
+
const { doc, win } = this.options;
|
|
773
|
+
const root = doc.documentElement;
|
|
774
|
+
const toView = (p) => [
|
|
775
|
+
p.x * root.scrollWidth - win.scrollX,
|
|
776
|
+
p.y * root.scrollHeight - win.scrollY
|
|
777
|
+
];
|
|
778
|
+
for (const pin of this.pins) {
|
|
779
|
+
const element = pin.element;
|
|
780
|
+
if (element) {
|
|
781
|
+
if (!element.isConnected) continue;
|
|
782
|
+
const rect = element.getBoundingClientRect();
|
|
783
|
+
nodes.push(this.drawPin([rect.left, rect.top], pin));
|
|
784
|
+
} else if (pin.spot) nodes.push(this.drawPin(toView(pin.spot), pin));
|
|
785
|
+
}
|
|
786
|
+
if (this.pending && (this.mode === "spot" || this.mode === "select")) {
|
|
787
|
+
nodes.push(this.drawPin(toView(this.pending)));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
layer.replaceChildren(...nodes);
|
|
791
|
+
}
|
|
792
|
+
/** A gold teardrop whose point (bottom-left) sits on `[x, y]`; without a pin, the pending one. */
|
|
793
|
+
drawPin([x, y], pin) {
|
|
794
|
+
const node = this.options.doc.createElement("span");
|
|
795
|
+
node.className = "pin";
|
|
796
|
+
const z = this.zoom;
|
|
797
|
+
const size = PIN_PX / z;
|
|
798
|
+
Object.assign(node.style, {
|
|
799
|
+
left: `${x}px`,
|
|
800
|
+
top: `${y - size}px`,
|
|
801
|
+
width: `${size}px`,
|
|
802
|
+
height: `${size}px`,
|
|
803
|
+
fontSize: `${11 / z}px`,
|
|
804
|
+
lineHeight: `${size}px`,
|
|
805
|
+
boxShadow: `0 0 0 ${2 / z}px #fff,0 ${2 / z}px ${6 / z}px rgba(0,0,0,.25)`
|
|
806
|
+
});
|
|
807
|
+
if (pin) {
|
|
808
|
+
node.textContent = String(pin.n);
|
|
809
|
+
node.onclick = (event) => {
|
|
810
|
+
this.consume(event);
|
|
811
|
+
this.options.onPin?.({ id: pin.id });
|
|
812
|
+
};
|
|
813
|
+
} else node.dataset.pending = "";
|
|
814
|
+
return node;
|
|
815
|
+
}
|
|
816
|
+
labelFor(tagged) {
|
|
817
|
+
const label = this.labels[tagged.fieldKey];
|
|
818
|
+
return typeof label === "string" && label.trim() ? label : tagged.fieldKey;
|
|
819
|
+
}
|
|
820
|
+
drawBox(box) {
|
|
821
|
+
const doc = this.options.doc;
|
|
822
|
+
const z = this.zoom;
|
|
823
|
+
const px = (n) => `${Math.round(n / z * 100) / 100}px`;
|
|
824
|
+
const rect = box.element.getBoundingClientRect();
|
|
825
|
+
const node = doc.createElement("div");
|
|
826
|
+
node.className = "box";
|
|
827
|
+
node.dataset.kind = box.kind;
|
|
828
|
+
const offset = 3 / z;
|
|
829
|
+
Object.assign(node.style, {
|
|
830
|
+
left: `${rect.left - offset}px`,
|
|
831
|
+
top: `${rect.top - offset}px`,
|
|
832
|
+
width: `${rect.width + offset * 2}px`,
|
|
833
|
+
height: `${rect.height + offset * 2}px`,
|
|
834
|
+
outline: `${px(2)} solid ${this.theme.outline}`,
|
|
835
|
+
borderRadius: px(3),
|
|
836
|
+
background: box.kind === "selected" ? SELECTED_TINT : "transparent"
|
|
837
|
+
});
|
|
838
|
+
const tooSmall = box.kind === "selected" && box.element !== this.hover && Math.min(rect.width, rect.height) * z < CHIP_HEIGHT_PX;
|
|
839
|
+
if (box.label && box.kind !== "highlight" && !tooSmall) {
|
|
840
|
+
const chip = doc.createElement("span");
|
|
841
|
+
chip.className = "chip";
|
|
842
|
+
Object.assign(chip.style, {
|
|
843
|
+
right: px(-5),
|
|
844
|
+
top: px(-27),
|
|
845
|
+
height: px(CHIP_HEIGHT_PX),
|
|
846
|
+
padding: `0 ${px(7)}`,
|
|
847
|
+
gap: px(4),
|
|
848
|
+
borderRadius: px(6),
|
|
849
|
+
fontSize: px(12),
|
|
850
|
+
background: this.theme.chip,
|
|
851
|
+
color: this.theme.chipText
|
|
852
|
+
});
|
|
853
|
+
if (box.kind === "selected") {
|
|
854
|
+
const icon = doc.createElement("span");
|
|
855
|
+
icon.className = "icon";
|
|
856
|
+
icon.style.width = px(12);
|
|
857
|
+
icon.style.height = px(12);
|
|
858
|
+
icon.innerHTML = CHECK_SVG;
|
|
859
|
+
chip.appendChild(icon);
|
|
860
|
+
}
|
|
861
|
+
chip.appendChild(doc.createTextNode(box.label));
|
|
862
|
+
node.appendChild(chip);
|
|
863
|
+
}
|
|
864
|
+
return node;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
var LARGE_SHARE = 0.5;
|
|
868
|
+
var CHIP_HEIGHT_PX = 20;
|
|
869
|
+
var PIN_PX = 22;
|
|
870
|
+
var STYLES = `
|
|
871
|
+
.layer { position: fixed; inset: 0; pointer-events: none; }
|
|
872
|
+
.box { position: fixed; box-sizing: border-box; pointer-events: none; }
|
|
873
|
+
.chip {
|
|
874
|
+
position: absolute; display: inline-flex; align-items: center; white-space: nowrap;
|
|
875
|
+
font-family: Poppins, ui-sans-serif, system-ui, sans-serif; font-weight: 600; line-height: 1;
|
|
876
|
+
letter-spacing: 0; box-sizing: border-box;
|
|
877
|
+
}
|
|
878
|
+
.capture { position: fixed; inset: 0; pointer-events: auto; cursor: crosshair; display: none; }
|
|
879
|
+
.pin {
|
|
880
|
+
position: fixed; box-sizing: border-box; pointer-events: auto; cursor: pointer; text-align: center;
|
|
881
|
+
border-radius: 999px 999px 999px 0; background: #CF8700; color: #fff;
|
|
882
|
+
font-family: Poppins, ui-sans-serif, system-ui, sans-serif; font-weight: 700;
|
|
883
|
+
}
|
|
884
|
+
.icon { display: inline-flex; flex-shrink: 0; }
|
|
885
|
+
.icon svg { width: 100%; height: 100%; display: block; }
|
|
886
|
+
`;
|
|
887
|
+
|
|
888
|
+
// src/stega.ts
|
|
889
|
+
var DIGITS = ["\u200B", "\u200C", "\u200D", "\u2060"];
|
|
890
|
+
var PREFIX = "\u2060\u200D\u200C\u200C\u200D\u200D\u200C\u2060";
|
|
891
|
+
var PAYLOAD_RE = /^(doc:[a-z0-9]+(?:-[a-z0-9]+)*|[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*)#([a-z][a-z0-9_]{0,127})(?:@([A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,3}))?$/;
|
|
892
|
+
var MARKER_RE = /(?:[][]{3})*/g;
|
|
893
|
+
function hasStega(value) {
|
|
894
|
+
return typeof value === "string" && value.includes(PREFIX);
|
|
895
|
+
}
|
|
896
|
+
function stegaDecode(text) {
|
|
897
|
+
if (!text.includes(PREFIX)) return [];
|
|
898
|
+
const found = [];
|
|
899
|
+
for (const match of text.match(MARKER_RE) ?? []) {
|
|
900
|
+
let payload = "";
|
|
901
|
+
for (let i = PREFIX.length; i + 4 <= match.length; i += 4) {
|
|
902
|
+
let byte = 0;
|
|
903
|
+
for (let j = 0; j < 4; j++) byte = byte * 4 + DIGITS.indexOf(match[i + j]);
|
|
904
|
+
payload += String.fromCharCode(byte);
|
|
905
|
+
}
|
|
906
|
+
const parsed = PAYLOAD_RE.exec(payload);
|
|
907
|
+
if (!parsed) continue;
|
|
908
|
+
found.push({
|
|
909
|
+
recordRef: parsed[1],
|
|
910
|
+
fieldKey: parsed[2],
|
|
911
|
+
locale: parsed[3] ?? null,
|
|
912
|
+
tag: `${parsed[1]}#${parsed[2]}`
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return found;
|
|
916
|
+
}
|
|
917
|
+
function splitStega(text) {
|
|
918
|
+
if (!text.includes(PREFIX)) return { text, markers: "" };
|
|
919
|
+
return { text: text.replace(MARKER_RE, ""), markers: (text.match(MARKER_RE) ?? []).join("") };
|
|
920
|
+
}
|
|
921
|
+
function cleanString(text) {
|
|
922
|
+
return text.includes(PREFIX) ? text.replace(MARKER_RE, "") : text;
|
|
923
|
+
}
|
|
924
|
+
function cleanStega(value) {
|
|
925
|
+
if (typeof value === "string") return cleanString(value);
|
|
926
|
+
if (Array.isArray(value)) return value.map((item) => cleanStega(item));
|
|
927
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
928
|
+
const out = {};
|
|
929
|
+
for (const [key, item] of Object.entries(value)) out[key] = cleanStega(item);
|
|
930
|
+
return out;
|
|
931
|
+
}
|
|
932
|
+
return value;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/preview/tags.ts
|
|
936
|
+
var TAG_SELECTOR = "[data-zap],[data-zap-field]";
|
|
937
|
+
function readElementTag(element) {
|
|
938
|
+
const locale = readLocale(element);
|
|
939
|
+
const html = element.hasAttribute("data-zap-html");
|
|
940
|
+
const direct = element.getAttribute("data-zap");
|
|
941
|
+
if (direct !== null) {
|
|
942
|
+
const parsed = parseTag(direct);
|
|
943
|
+
if (!parsed) return { ok: false, problem: { attribute: "data-zap", value: clip(direct) } };
|
|
944
|
+
return { ok: true, tagged: build(element, parsed.recordRef, parsed.fieldKey, locale, html) };
|
|
945
|
+
}
|
|
946
|
+
const field = element.getAttribute("data-zap-field");
|
|
947
|
+
if (field === null) return null;
|
|
948
|
+
const fieldKey = field.trim();
|
|
949
|
+
if (!parseTag(`doc:x#${fieldKey}`)) {
|
|
950
|
+
return { ok: false, problem: { attribute: "data-zap-field", value: clip(field) } };
|
|
951
|
+
}
|
|
952
|
+
const container = element.closest("[data-zap-entry]");
|
|
953
|
+
const entry = container?.getAttribute("data-zap-entry")?.trim();
|
|
954
|
+
if (!entry || !parseRecordRef(entry)) {
|
|
955
|
+
return { ok: false, problem: { attribute: "data-zap-entry", value: clip(entry ?? "") } };
|
|
956
|
+
}
|
|
957
|
+
return { ok: true, tagged: build(element, entry, fieldKey, locale, html) };
|
|
958
|
+
}
|
|
959
|
+
function build(element, recordRef, fieldKey, locale, html) {
|
|
960
|
+
return {
|
|
961
|
+
element,
|
|
962
|
+
recordRef,
|
|
963
|
+
fieldKey,
|
|
964
|
+
tag: `${recordRef}#${fieldKey}`,
|
|
965
|
+
locale,
|
|
966
|
+
html,
|
|
967
|
+
source: "attr"
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
var NON_RENDERED = /^(?:head|title|script|style|template|noscript|textarea)$/;
|
|
971
|
+
function readLocale(element) {
|
|
972
|
+
const value = element.closest("[data-zap-locale]")?.getAttribute("data-zap-locale")?.trim();
|
|
973
|
+
return value && isLocale(value) ? value : null;
|
|
974
|
+
}
|
|
975
|
+
function clip(value) {
|
|
976
|
+
return value.length > 200 ? value.slice(0, 200) : value;
|
|
977
|
+
}
|
|
978
|
+
var TagIndex = class {
|
|
979
|
+
constructor(root) {
|
|
980
|
+
__publicField(this, "root", root);
|
|
981
|
+
__publicField(this, "list", []);
|
|
982
|
+
__publicField(this, "byElement", /* @__PURE__ */ new WeakMap());
|
|
983
|
+
__publicField(this, "byTag", /* @__PURE__ */ new Map());
|
|
984
|
+
__publicField(this, "problems", []);
|
|
985
|
+
}
|
|
986
|
+
scan() {
|
|
987
|
+
const list = [];
|
|
988
|
+
const problems = [];
|
|
989
|
+
const byElement = /* @__PURE__ */ new WeakMap();
|
|
990
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
991
|
+
const add = (element, tagged) => {
|
|
992
|
+
list.push(tagged);
|
|
993
|
+
if (!byElement.has(element)) byElement.set(element, tagged);
|
|
994
|
+
const bucket = byTag.get(tagged.tag);
|
|
995
|
+
if (bucket) bucket.push(tagged);
|
|
996
|
+
else byTag.set(tagged.tag, [tagged]);
|
|
997
|
+
};
|
|
998
|
+
for (const element of Array.from(this.root.querySelectorAll(TAG_SELECTOR))) {
|
|
999
|
+
const result = readElementTag(element);
|
|
1000
|
+
if (!result) continue;
|
|
1001
|
+
if (!result.ok) {
|
|
1002
|
+
problems.push(result.problem);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
add(element, result.tagged);
|
|
1006
|
+
}
|
|
1007
|
+
const doc = this.root.ownerDocument ?? this.root;
|
|
1008
|
+
const walker = doc.createTreeWalker(
|
|
1009
|
+
this.root,
|
|
1010
|
+
4
|
|
1011
|
+
/* NodeFilter.SHOW_TEXT */
|
|
1012
|
+
);
|
|
1013
|
+
for (let node; node = walker.nextNode(); ) {
|
|
1014
|
+
const text = node;
|
|
1015
|
+
const element = text.parentElement;
|
|
1016
|
+
if (!element || !hasStega(text.data)) continue;
|
|
1017
|
+
if (NON_RENDERED.test(element.localName) || element.closest("head")) continue;
|
|
1018
|
+
if (byElement.get(element)?.source === "attr") continue;
|
|
1019
|
+
for (const found of stegaDecode(text.data)) {
|
|
1020
|
+
add(element, {
|
|
1021
|
+
element,
|
|
1022
|
+
recordRef: found.recordRef,
|
|
1023
|
+
fieldKey: found.fieldKey,
|
|
1024
|
+
tag: found.tag,
|
|
1025
|
+
locale: found.locale ?? readLocale(element),
|
|
1026
|
+
html: false,
|
|
1027
|
+
source: "stega",
|
|
1028
|
+
textNode: text
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
list.sort(
|
|
1033
|
+
(a, b) => a.element.compareDocumentPosition(b.element) & 4 ? -1 : a.element === b.element ? 0 : 1
|
|
1034
|
+
);
|
|
1035
|
+
this.list = list;
|
|
1036
|
+
this.byElement = byElement;
|
|
1037
|
+
this.byTag = byTag;
|
|
1038
|
+
this.problems = problems;
|
|
1039
|
+
}
|
|
1040
|
+
get elements() {
|
|
1041
|
+
return this.list;
|
|
1042
|
+
}
|
|
1043
|
+
byField(recordRef, fieldKey) {
|
|
1044
|
+
return this.byTag.get(`${recordRef}#${fieldKey}`) ?? [];
|
|
1045
|
+
}
|
|
1046
|
+
get(element) {
|
|
1047
|
+
return this.byElement.get(element) ?? null;
|
|
1048
|
+
}
|
|
1049
|
+
/** The nearest tagged element at or above `target`. */
|
|
1050
|
+
closest(target) {
|
|
1051
|
+
for (let node = target; node; node = node.parentElement) {
|
|
1052
|
+
const tagged = this.byElement.get(node);
|
|
1053
|
+
if (tagged) return tagged;
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
/** The `zap:tags` report: one row per tag with its element count, sorted. */
|
|
1058
|
+
summary() {
|
|
1059
|
+
return Array.from(this.byTag.entries()).map(([tag, items]) => ({
|
|
1060
|
+
tag,
|
|
1061
|
+
recordRef: items[0].recordRef,
|
|
1062
|
+
fieldKey: items[0].fieldKey,
|
|
1063
|
+
count: items.length,
|
|
1064
|
+
source: items.some((item) => item.source === "attr") ? "attr" : "stega"
|
|
1065
|
+
})).sort((a, b) => a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0);
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
function observeTags(target, onChange, options = {}) {
|
|
1069
|
+
const throttleMs = options.throttleMs ?? 250;
|
|
1070
|
+
const set = options.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1071
|
+
const clear = options.clearTimeout ?? ((h) => clearTimeout(h));
|
|
1072
|
+
const view = (target.ownerDocument ?? target).defaultView;
|
|
1073
|
+
const Observer = view?.MutationObserver ?? globalThis.MutationObserver;
|
|
1074
|
+
if (!Observer) return () => {
|
|
1075
|
+
};
|
|
1076
|
+
let timer = null;
|
|
1077
|
+
let last = -Infinity;
|
|
1078
|
+
const fire = () => {
|
|
1079
|
+
timer = null;
|
|
1080
|
+
last = Date.now();
|
|
1081
|
+
onChange();
|
|
1082
|
+
};
|
|
1083
|
+
const observer = new Observer((records) => {
|
|
1084
|
+
if (options.ignore && records.every((record) => options.ignore(record.target))) return;
|
|
1085
|
+
if (timer !== null) return;
|
|
1086
|
+
const wait = Math.max(0, last + throttleMs - Date.now());
|
|
1087
|
+
timer = set(fire, Math.max(wait, 16));
|
|
1088
|
+
});
|
|
1089
|
+
observer.observe(target, {
|
|
1090
|
+
subtree: true,
|
|
1091
|
+
childList: true,
|
|
1092
|
+
attributes: true,
|
|
1093
|
+
attributeFilter: ["data-zap", "data-zap-field", "data-zap-entry", "data-zap-locale"],
|
|
1094
|
+
characterData: true
|
|
1095
|
+
});
|
|
1096
|
+
return () => {
|
|
1097
|
+
observer.disconnect();
|
|
1098
|
+
if (timer !== null) clear(timer);
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
export {
|
|
1103
|
+
FIELD_KEY_RE,
|
|
1104
|
+
parseRecordRef,
|
|
1105
|
+
isRecordRef,
|
|
1106
|
+
PREVIEW_TOKEN_RE,
|
|
1107
|
+
isPreviewTokenShape,
|
|
1108
|
+
isFieldKey,
|
|
1109
|
+
recordRefsOf,
|
|
1110
|
+
zapAttrs,
|
|
1111
|
+
PROTOCOL_SOURCE,
|
|
1112
|
+
PAGE_UNLOAD_ERROR,
|
|
1113
|
+
PROTOCOL_VERSION,
|
|
1114
|
+
MAX_MESSAGE_BYTES,
|
|
1115
|
+
MAX_ANCHORS,
|
|
1116
|
+
MAX_QUOTE_EXACT,
|
|
1117
|
+
MAX_QUOTE_CONTEXT,
|
|
1118
|
+
MAX_SELECTOR_BYTES,
|
|
1119
|
+
MAX_URL_LENGTH,
|
|
1120
|
+
MAX_TAG_REPORT,
|
|
1121
|
+
MAX_LABELS,
|
|
1122
|
+
MAX_PINS,
|
|
1123
|
+
OVERLAY_MODES,
|
|
1124
|
+
CAPABILITIES,
|
|
1125
|
+
MAX_EMAIL_LENGTH,
|
|
1126
|
+
emptyDropStats,
|
|
1127
|
+
isLocale,
|
|
1128
|
+
isSession,
|
|
1129
|
+
MAX_DRAFT_ROUTE_LENGTH,
|
|
1130
|
+
isDraftRoutePath,
|
|
1131
|
+
isPinId,
|
|
1132
|
+
isPin,
|
|
1133
|
+
isPreviewValue,
|
|
1134
|
+
utf8Length,
|
|
1135
|
+
isDomAnchor,
|
|
1136
|
+
isPageAnchor,
|
|
1137
|
+
parseTag,
|
|
1138
|
+
PAGE_MESSAGE_TYPES,
|
|
1139
|
+
EDITOR_MESSAGE_TYPES,
|
|
1140
|
+
messageBytes,
|
|
1141
|
+
parseEditorMessage,
|
|
1142
|
+
parsePageMessage,
|
|
1143
|
+
envelope,
|
|
1144
|
+
cssEscape,
|
|
1145
|
+
safeQueryAll,
|
|
1146
|
+
buildSelector,
|
|
1147
|
+
normalizeText,
|
|
1148
|
+
textQuoteFor,
|
|
1149
|
+
normalizedRect,
|
|
1150
|
+
buildDomAnchor,
|
|
1151
|
+
buildAnchor,
|
|
1152
|
+
findAnchorElement,
|
|
1153
|
+
OVERLAY_HOST_TAG,
|
|
1154
|
+
Overlay,
|
|
1155
|
+
splitStega,
|
|
1156
|
+
cleanStega,
|
|
1157
|
+
TAG_SELECTOR,
|
|
1158
|
+
readElementTag,
|
|
1159
|
+
TagIndex,
|
|
1160
|
+
observeTags
|
|
1161
|
+
};
|
|
1162
|
+
//# sourceMappingURL=chunk-VW4EH7NY.js.map
|