@siteping/widget 0.8.1 → 0.9.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/dist/index.js CHANGED
@@ -1,580 +1,28 @@
1
- // ../../node_modules/.bun/@medv+finder@3.2.0/node_modules/@medv/finder/finder.js
2
- var config;
3
- var rootDocument;
4
- var start;
5
- function finder(input, options) {
6
- start = /* @__PURE__ */ new Date();
7
- if (input.nodeType !== Node.ELEMENT_NODE) {
8
- throw new Error(`Can't generate CSS selector for non-element node type.`);
9
- }
10
- if ("html" === input.tagName.toLowerCase()) {
11
- return "html";
12
- }
13
- const defaults = {
14
- root: document.body,
15
- idName: (name) => true,
16
- className: (name) => true,
17
- tagName: (name) => true,
18
- attr: (name, value) => false,
19
- seedMinLength: 1,
20
- optimizedMinLength: 2,
21
- threshold: 1e3,
22
- maxNumberOfTries: 1e4,
23
- timeoutMs: void 0
24
- };
25
- config = { ...defaults, ...options };
26
- rootDocument = findRootDocument(config.root, defaults);
27
- let path = bottomUpSearch(input, "all", () => bottomUpSearch(input, "two", () => bottomUpSearch(input, "one", () => bottomUpSearch(input, "none"))));
28
- if (path) {
29
- const optimized = sort(optimize(path, input));
30
- if (optimized.length > 0) {
31
- path = optimized[0];
32
- }
33
- return selector(path);
34
- } else {
35
- throw new Error(`Selector was not found.`);
36
- }
37
- }
38
- function findRootDocument(rootNode, defaults) {
39
- if (rootNode.nodeType === Node.DOCUMENT_NODE) {
40
- return rootNode;
41
- }
42
- if (rootNode === defaults.root) {
43
- return rootNode.ownerDocument;
44
- }
45
- return rootNode;
46
- }
47
- function bottomUpSearch(input, limit, fallback) {
48
- let path = null;
49
- let stack = [];
50
- let current = input;
51
- let i = 0;
52
- while (current) {
53
- const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - start.getTime();
54
- if (config.timeoutMs !== void 0 && elapsedTime > config.timeoutMs) {
55
- throw new Error(`Timeout: Can't find a unique selector after ${elapsedTime}ms`);
56
- }
57
- let level = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()];
58
- const nth = index(current);
59
- if (limit == "all") {
60
- if (nth) {
61
- level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));
62
- }
63
- } else if (limit == "two") {
64
- level = level.slice(0, 1);
65
- if (nth) {
66
- level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));
67
- }
68
- } else if (limit == "one") {
69
- const [node] = level = level.slice(0, 1);
70
- if (nth && dispensableNth(node)) {
71
- level = [nthChild(node, nth)];
72
- }
73
- } else if (limit == "none") {
74
- level = [any()];
75
- if (nth) {
76
- level = [nthChild(level[0], nth)];
77
- }
78
- }
79
- for (let node of level) {
80
- node.level = i;
81
- }
82
- stack.push(level);
83
- if (stack.length >= config.seedMinLength) {
84
- path = findUniquePath(stack, fallback);
85
- if (path) {
86
- break;
87
- }
88
- }
89
- current = current.parentElement;
90
- i++;
91
- }
92
- if (!path) {
93
- path = findUniquePath(stack, fallback);
94
- }
95
- if (!path && fallback) {
96
- return fallback();
97
- }
98
- return path;
99
- }
100
- function findUniquePath(stack, fallback) {
101
- const paths = sort(combinations(stack));
102
- if (paths.length > config.threshold) {
103
- return fallback ? fallback() : null;
104
- }
105
- for (let candidate of paths) {
106
- if (unique(candidate)) {
107
- return candidate;
108
- }
109
- }
110
- return null;
111
- }
112
- function selector(path) {
113
- let node = path[0];
114
- let query = node.name;
115
- for (let i = 1; i < path.length; i++) {
116
- const level = path[i].level || 0;
117
- if (node.level === level - 1) {
118
- query = `${path[i].name} > ${query}`;
119
- } else {
120
- query = `${path[i].name} ${query}`;
121
- }
122
- node = path[i];
123
- }
124
- return query;
125
- }
126
- function penalty(path) {
127
- return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);
128
- }
129
- function unique(path) {
130
- const css = selector(path);
131
- switch (rootDocument.querySelectorAll(css).length) {
132
- case 0:
133
- throw new Error(`Can't select any node with this selector: ${css}`);
134
- case 1:
135
- return true;
136
- default:
137
- return false;
138
- }
139
- }
140
- function id(input) {
141
- const elementId = input.getAttribute("id");
142
- if (elementId && config.idName(elementId)) {
143
- return {
144
- name: "#" + CSS.escape(elementId),
145
- penalty: 0
146
- };
147
- }
148
- return null;
149
- }
150
- function attr(input) {
151
- const attrs = Array.from(input.attributes).filter((attr2) => config.attr(attr2.name, attr2.value));
152
- return attrs.map((attr2) => ({
153
- name: `[${CSS.escape(attr2.name)}="${CSS.escape(attr2.value)}"]`,
154
- penalty: 0.5
155
- }));
156
- }
157
- function classNames(input) {
158
- const names = Array.from(input.classList).filter(config.className);
159
- return names.map((name) => ({
160
- name: "." + CSS.escape(name),
161
- penalty: 1
162
- }));
163
- }
164
- function tagName(input) {
165
- const name = input.tagName.toLowerCase();
166
- if (config.tagName(name)) {
167
- return {
168
- name,
169
- penalty: 2
170
- };
171
- }
172
- return null;
173
- }
174
- function any() {
175
- return {
176
- name: "*",
177
- penalty: 3
178
- };
179
- }
180
- function index(input) {
181
- const parent = input.parentNode;
182
- if (!parent) {
183
- return null;
184
- }
185
- let child = parent.firstChild;
186
- if (!child) {
187
- return null;
188
- }
189
- let i = 0;
190
- while (child) {
191
- if (child.nodeType === Node.ELEMENT_NODE) {
192
- i++;
193
- }
194
- if (child === input) {
195
- break;
196
- }
197
- child = child.nextSibling;
198
- }
199
- return i;
200
- }
201
- function nthChild(node, i) {
202
- return {
203
- name: node.name + `:nth-child(${i})`,
204
- penalty: node.penalty + 1
205
- };
206
- }
207
- function dispensableNth(node) {
208
- return node.name !== "html" && !node.name.startsWith("#");
209
- }
210
- function maybe(...level) {
211
- const list = level.filter(notEmpty);
212
- if (list.length > 0) {
213
- return list;
214
- }
215
- return null;
216
- }
217
- function notEmpty(value) {
218
- return value !== null && value !== void 0;
219
- }
220
- function* combinations(stack, path = []) {
221
- if (stack.length > 0) {
222
- for (let node of stack[0]) {
223
- yield* combinations(stack.slice(1, stack.length), path.concat(node));
224
- }
225
- } else {
226
- yield path;
227
- }
228
- }
229
- function sort(paths) {
230
- return [...paths].sort((a, b) => penalty(a) - penalty(b));
231
- }
232
- function* optimize(path, input, scope = {
233
- counter: 0,
234
- visited: /* @__PURE__ */ new Map()
235
- }) {
236
- if (path.length > 2 && path.length > config.optimizedMinLength) {
237
- for (let i = 1; i < path.length - 1; i++) {
238
- if (scope.counter > config.maxNumberOfTries) {
239
- return;
240
- }
241
- scope.counter += 1;
242
- const newPath = [...path];
243
- newPath.splice(i, 1);
244
- const newPathKey = selector(newPath);
245
- if (scope.visited.has(newPathKey)) {
246
- return;
247
- }
248
- if (unique(newPath) && same(newPath, input)) {
249
- yield newPath;
250
- scope.visited.set(newPathKey, true);
251
- yield* optimize(newPath, input, scope);
252
- }
253
- }
254
- }
255
- }
256
- function same(path, input) {
257
- return rootDocument.querySelector(selector(path)) === input;
258
- }
259
-
260
- // src/dom/fingerprint.ts
261
- var STABLE_ATTRS = ["role", "aria-label", "type", "name", "href", "src", "data-testid", "data-id"];
262
- function djb2(str) {
263
- let hash = 5381;
264
- for (let i = 0; i < str.length; i++) {
265
- hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
266
- }
267
- return (hash >>> 0).toString(36);
268
- }
269
- function generateFingerprint(element) {
270
- const childCount = element.children.length;
271
- let siblingIdx = 0;
272
- const parent = element.parentElement;
273
- if (parent) {
274
- for (const child of parent.children) {
275
- if (child === element) break;
276
- if (child.tagName === element.tagName) siblingIdx++;
277
- }
278
- }
279
- const attrs = [];
280
- for (const attr2 of STABLE_ATTRS) {
281
- const val = element.getAttribute(attr2);
282
- if (val) attrs.push(`${attr2}=${val}`);
283
- }
284
- const attrHash = attrs.length > 0 ? djb2(attrs.join(",")) : "0";
285
- return `${childCount}:${siblingIdx}:${attrHash}`;
286
- }
287
- function scoreFingerprint(candidate, storedFingerprint) {
288
- const parts = storedFingerprint.split(":");
289
- if (parts.length !== 3) return 0;
290
- const [storedChildren, storedSibIdx, storedAttrHash] = parts;
291
- const storedChildCount = Number(storedChildren);
292
- const storedSibIndex = Number(storedSibIdx);
293
- if (Number.isNaN(storedChildCount) || Number.isNaN(storedSibIndex)) return 0;
294
- const candidateFp = generateFingerprint(candidate);
295
- const [candChildren, candSibIdx, candAttrHash] = candidateFp.split(":");
296
- let score = 0;
297
- const childDiff = Math.abs(Number(candChildren) - storedChildCount);
298
- if (childDiff === 0) score += 0.2;
299
- else if (childDiff <= 2) score += 0.1;
300
- else if (childDiff <= 5) score += 0.03;
301
- const sibDiff = Math.abs(Number(candSibIdx) - storedSibIndex);
302
- if (sibDiff === 0) score += 0.4;
303
- else if (sibDiff === 1) score += 0.2;
304
- else if (sibDiff <= 3) score += 0.08;
305
- if (candAttrHash === storedAttrHash) score += 0.4;
306
- return score;
307
- }
308
-
309
- // src/dom/text-context.ts
310
- function adjacentText(element, direction) {
311
- const prop = direction === "before" ? "previousElementSibling" : "nextElementSibling";
312
- let sibling = element[prop];
313
- let attempts = 3;
314
- while (sibling && attempts > 0) {
315
- const text = sibling.textContent?.trim();
316
- if (text) {
317
- return direction === "before" ? text.slice(-32) : text.slice(0, 32);
318
- }
319
- sibling = sibling[prop];
320
- attempts--;
321
- }
322
- return "";
323
- }
324
- function neighborText(element) {
325
- const prev = element.previousElementSibling?.textContent?.trim().slice(0, 40) ?? "";
326
- const next = element.nextElementSibling?.textContent?.trim().slice(0, 40) ?? "";
327
- return [prev, next].filter(Boolean).join(" | ");
328
- }
329
-
330
- // src/dom/xpath.ts
331
- function generateXPath(element) {
332
- if (element.id) {
333
- const safeId = element.id.includes("'") ? `concat('${element.id.replace(/'/g, `',"'",'`)}')` : `'${element.id}'`;
334
- return `//${element.localName}[@id=${safeId}]`;
335
- }
336
- const segments = [];
337
- let current = element;
338
- while (current && current !== document.body && segments.length < 6) {
339
- const tag = current.localName;
340
- const parent = current.parentElement;
341
- if (current.id) {
342
- const safeId = current.id.includes("'") ? `concat('${current.id.replace(/'/g, `',"'",'`)}')` : `'${current.id}'`;
343
- segments.unshift(`/${tag}[@id=${safeId}]`);
344
- return "/" + segments.join("");
345
- }
346
- let position = 1;
347
- if (parent) {
348
- for (const sibling of parent.children) {
349
- if (sibling === current) break;
350
- if (sibling.localName === tag) position++;
351
- }
352
- }
353
- segments.unshift(`/${tag}[${position}]`);
354
- current = parent;
355
- }
356
- return "/html/body" + segments.join("");
357
- }
358
-
359
- // src/dom/anchor.ts
360
- function generateAnchor(element) {
361
- const cssSelector = finder(element, {
362
- // Filter out CSS-in-JS hashed class names
363
- className: (name) => !/^(css|sc|emotion|styled)-/.test(name) && !/^[a-z]{1,3}[A-Za-z0-9]{4,8}$/.test(name),
364
- // Prefer stable attributes
365
- attr: (name) => ["data-testid", "data-id", "role", "aria-label"].includes(name),
366
- // Exclude framework-generated dynamic IDs
367
- idName: (name) => !name.startsWith("radix-") && !/^:r[0-9]+:$/.test(name),
368
- seedMinLength: 3,
369
- optimizedMinLength: 2
370
- });
371
- const xpath = generateXPath(element);
372
- const rawText = element.textContent?.trim() ?? "";
373
- const textSnippet = rawText.slice(0, 120);
374
- const textPrefix = adjacentText(element, "before");
375
- const textSuffix = adjacentText(element, "after");
376
- const fingerprint = generateFingerprint(element);
377
- const neighbor = neighborText(element);
378
- return {
379
- cssSelector,
380
- xpath,
381
- textSnippet,
382
- textPrefix,
383
- textSuffix,
384
- fingerprint,
385
- neighborText: neighbor,
386
- elementTag: element.tagName,
387
- elementId: element.id || void 0
388
- };
389
- }
390
- function findAnchorElement(rect, root = document.documentElement) {
391
- const centerX = rect.x + rect.width / 2;
392
- const centerY = rect.y + rect.height / 2;
393
- const elementAtCenter = document.elementFromPoint(centerX, centerY);
394
- if (!elementAtCenter || elementAtCenter === root) return document.body;
395
- let candidate = elementAtCenter;
396
- let current = elementAtCenter;
397
- while (current && current !== document.body) {
398
- const bounds = current.getBoundingClientRect();
399
- if (bounds.left <= rect.x && bounds.top <= rect.y && bounds.right >= rect.x + rect.width && bounds.bottom >= rect.y + rect.height) {
400
- candidate = current;
401
- break;
402
- }
403
- current = current.parentElement;
404
- }
405
- return candidate;
406
- }
407
- function rectToPercentages(rect, anchorBounds) {
408
- if (anchorBounds.width <= 0 || anchorBounds.height <= 0) {
409
- return { xPct: 0, yPct: 0, wPct: 1, hPct: 1 };
410
- }
411
- return {
412
- xPct: (rect.x - anchorBounds.x) / anchorBounds.width,
413
- yPct: (rect.y - anchorBounds.y) / anchorBounds.height,
414
- wPct: rect.width / anchorBounds.width,
415
- hPct: rect.height / anchorBounds.height
416
- };
417
- }
418
-
419
- // src/dom-utils.ts
420
- function parseSvg(svgString) {
421
- const range = document.createRange();
422
- const fragment = range.createContextualFragment(svgString);
423
- const svg = fragment.firstElementChild;
424
- if (!svg || svg.nodeName.toLowerCase() !== "svg") {
425
- throw new Error("[siteping] Invalid SVG string");
426
- }
427
- return svg;
428
- }
429
- function el(tag, attrs) {
430
- const element = document.createElement(tag);
431
- if (attrs) {
432
- for (const [key, value] of Object.entries(attrs)) {
433
- if (key === "class") {
434
- element.className = value;
435
- } else if (key === "style") {
436
- element.style.cssText = value;
437
- } else {
438
- element.setAttribute(key, value);
439
- }
440
- }
441
- }
442
- return element;
443
- }
444
- function setText(element, text) {
445
- element.textContent = text;
446
- }
447
- function formatRelativeDate(isoString) {
448
- const diff = Date.now() - new Date(isoString).getTime();
449
- const minutes = Math.floor(diff / 6e4);
450
- if (minutes < 1) return "maintenant";
451
- if (minutes < 60) return `il y a ${minutes}min`;
452
- const hours = Math.floor(minutes / 60);
453
- if (hours < 24) return `il y a ${hours}h`;
454
- const days = Math.floor(hours / 24);
455
- if (days < 7) return `il y a ${days}j`;
456
- return new Date(isoString).toLocaleDateString("fr-FR");
457
- }
458
-
459
- // src/icons.ts
460
- var ICON_SITEPING = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><circle cx="12" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="8" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="16" cy="10" r="1" fill="currentColor" stroke="none"/></svg>`;
461
- var ICON_CHAT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`;
462
- var ICON_ANNOTATE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 3v18"/></svg>`;
463
- var ICON_EYE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`;
464
- var ICON_EYE_OFF = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>`;
465
- var ICON_CLOSE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
466
- var ICON_SEARCH = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
467
- var ICON_CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
468
- var ICON_QUESTION = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`;
469
- var ICON_CHANGE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`;
470
- var ICON_BUG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="6" width="8" height="14" rx="4"/><path d="M19 9h2"/><path d="M3 9h2"/><path d="M19 13h2"/><path d="M3 13h2"/><path d="M19 17h2"/><path d="M3 17h2"/><path d="M10 2h4"/></svg>`;
471
- var ICON_OTHER = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>`;
472
- var ICON_UNDO = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>`;
473
- var ICON_TRASH = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>`;
474
-
475
- // src/styles/theme.ts
476
- var DEFAULT_ACCENT = "#0066ff";
477
- var HEX6_RE = /^#[0-9a-fA-F]{6}$/;
478
- var HEX3_RE = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/;
479
- var HEX8_RE = /^#[0-9a-fA-F]{8}$/;
480
- function normalizeHex(color) {
481
- if (HEX6_RE.test(color)) return color;
482
- const short = HEX3_RE.test(color) ? color.match(HEX3_RE) : null;
483
- if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
484
- if (HEX8_RE.test(color)) return color.slice(0, 7);
485
- return DEFAULT_ACCENT;
486
- }
487
- function darkenHex(hex, amount) {
488
- const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));
489
- const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));
490
- const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));
491
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
492
- }
493
- function buildThemeColors(accent = DEFAULT_ACCENT) {
494
- const hex = normalizeHex(accent);
495
- const dark = darkenHex(hex, 0.15);
496
- return {
497
- accent: hex,
498
- accentLight: hex + "14",
499
- // 8% opacity
500
- accentDark: dark,
501
- accentGlow: hex + "33",
502
- // 20% opacity
503
- accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,
504
- bg: "#ffffff",
505
- bgHover: "#f8f9fb",
506
- text: "#0f172a",
507
- textSecondary: "#475569",
508
- textTertiary: "#64748b",
509
- border: "#e2e8f0",
510
- shadow: "rgba(0, 0, 0, 0.06)",
511
- // Glass tokens
512
- glassBg: "rgba(255, 255, 255, 0.72)",
513
- glassBgHeavy: "rgba(255, 255, 255, 0.85)",
514
- glassBorder: "rgba(255, 255, 255, 0.35)",
515
- glassBorderSubtle: "rgba(255, 255, 255, 0.18)",
516
- // Vibrant type colors
517
- typeQuestion: "#3b82f6",
518
- typeChangement: "#f59e0b",
519
- typeBug: "#ef4444",
520
- typeAutre: "#64748b",
521
- // Pastel backgrounds
522
- typeQuestionBg: "#eff6ff",
523
- typeChangementBg: "#fffbeb",
524
- typeBugBg: "#fef2f2",
525
- typeAutreBg: "#f8fafc"
526
- };
527
- }
528
- function getTypeColor(type, colors) {
529
- switch (type) {
530
- case "question":
531
- return colors.typeQuestion;
532
- case "changement":
533
- return colors.typeChangement;
534
- case "bug":
535
- return colors.typeBug;
536
- default:
537
- return colors.typeAutre;
538
- }
539
- }
540
- function getTypeBgColor(type, colors) {
541
- switch (type) {
542
- case "question":
543
- return colors.typeQuestionBg;
544
- case "changement":
545
- return colors.typeChangementBg;
546
- case "bug":
547
- return colors.typeBugBg;
548
- default:
549
- return colors.typeAutreBg;
550
- }
551
- }
552
- function cssVariables(colors) {
553
- return `
554
- --sp-accent: ${colors.accent};
555
- --sp-accent-light: ${colors.accentLight};
556
- --sp-accent-dark: ${colors.accentDark};
557
- --sp-accent-glow: ${colors.accentGlow};
558
- --sp-accent-gradient: ${colors.accentGradient};
559
- --sp-bg: ${colors.bg};
560
- --sp-bg-hover: ${colors.bgHover};
561
- --sp-text: ${colors.text};
562
- --sp-text-secondary: ${colors.textSecondary};
563
- --sp-text-tertiary: ${colors.textTertiary};
564
- --sp-border: ${colors.border};
565
- --sp-shadow: ${colors.shadow};
566
- --sp-glass-bg: ${colors.glassBg};
567
- --sp-glass-bg-heavy: ${colors.glassBgHeavy};
568
- --sp-glass-border: ${colors.glassBorder};
569
- --sp-glass-border-subtle: ${colors.glassBorderSubtle};
570
- --sp-type-question: ${colors.typeQuestion};
571
- --sp-type-changement: ${colors.typeChangement};
572
- --sp-type-bug: ${colors.typeBug};
573
- --sp-type-autre: ${colors.typeAutre};
574
- --sp-type-question-bg: ${colors.typeQuestionBg};
575
- --sp-type-changement-bg: ${colors.typeChangementBg};
576
- --sp-type-bug-bg: ${colors.typeBugBg};
577
- --sp-type-autre-bg: ${colors.typeAutreBg};
1
+ var T,ne,ge;function ye(n,e){if(ge=new Date,n.nodeType!==Node.ELEMENT_NODE)throw new Error("Can't generate CSS selector for non-element node type.");if(n.tagName.toLowerCase()==="html")return"html";let t={root:document.body,idName:s=>!0,className:s=>!0,tagName:s=>!0,attr:(s,r)=>!1,seedMinLength:1,optimizedMinLength:2,threshold:1e3,maxNumberOfTries:1e4,timeoutMs:void 0};T={...t,...e},ne=nt(T.root,t);let i=P(n,"all",()=>P(n,"two",()=>P(n,"one",()=>P(n,"none"))));if(i){let s=we(ke(i,n));return s.length>0&&(i=s[0]),_(i)}else throw new Error("Selector was not found.")}function nt(n,e){return n.nodeType===Node.DOCUMENT_NODE?n:n===e.root?n.ownerDocument:n}function P(n,e,t){let i=null,s=[],r=n,o=0;for(;r;){let a=new Date().getTime()-ge.getTime();if(T.timeoutMs!==void 0&&a>T.timeoutMs)throw new Error(`Timeout: Can't find a unique selector after ${a}ms`);let l=j(it(r))||j(...st(r))||j(...rt(r))||j(ot(r))||[be()],p=at(r);if(e=="all")p&&(l=l.concat(l.filter(te).map(d=>z(d,p))));else if(e=="two")l=l.slice(0,1),p&&(l=l.concat(l.filter(te).map(d=>z(d,p))));else if(e=="one"){let[d]=l=l.slice(0,1);p&&te(d)&&(l=[z(d,p)])}else e=="none"&&(l=[be()],p&&(l=[z(l[0],p)]));for(let d of l)d.level=o;if(s.push(l),s.length>=T.seedMinLength&&(i=fe(s,t),i))break;r=r.parentElement,o++}return i||(i=fe(s,t)),!i&&t?t():i}function fe(n,e){let t=we(xe(n));if(t.length>T.threshold)return e?e():null;for(let i of t)if(ve(i))return i;return null}function _(n){let e=n[0],t=e.name;for(let i=1;i<n.length;i++){let s=n[i].level||0;e.level===s-1?t=`${n[i].name} > ${t}`:t=`${n[i].name} ${t}`,e=n[i]}return t}function me(n){return n.map(e=>e.penalty).reduce((e,t)=>e+t,0)}function ve(n){let e=_(n);switch(ne.querySelectorAll(e).length){case 0:throw new Error(`Can't select any node with this selector: ${e}`);case 1:return!0;default:return!1}}function it(n){let e=n.getAttribute("id");return e&&T.idName(e)?{name:"#"+CSS.escape(e),penalty:0}:null}function st(n){return Array.from(n.attributes).filter(t=>T.attr(t.name,t.value)).map(t=>({name:`[${CSS.escape(t.name)}="${CSS.escape(t.value)}"]`,penalty:.5}))}function rt(n){return Array.from(n.classList).filter(T.className).map(t=>({name:"."+CSS.escape(t),penalty:1}))}function ot(n){let e=n.tagName.toLowerCase();return T.tagName(e)?{name:e,penalty:2}:null}function be(){return{name:"*",penalty:3}}function at(n){let e=n.parentNode;if(!e)return null;let t=e.firstChild;if(!t)return null;let i=0;for(;t&&(t.nodeType===Node.ELEMENT_NODE&&i++,t!==n);)t=t.nextSibling;return i}function z(n,e){return{name:n.name+`:nth-child(${e})`,penalty:n.penalty+1}}function te(n){return n.name!=="html"&&!n.name.startsWith("#")}function j(...n){let e=n.filter(lt);return e.length>0?e:null}function lt(n){return n!=null}function*xe(n,e=[]){if(n.length>0)for(let t of n[0])yield*xe(n.slice(1,n.length),e.concat(t));else yield e}function we(n){return[...n].sort((e,t)=>me(e)-me(t))}function*ke(n,e,t={counter:0,visited:new Map}){if(n.length>2&&n.length>T.optimizedMinLength)for(let i=1;i<n.length-1;i++){if(t.counter>T.maxNumberOfTries)return;t.counter+=1;let s=[...n];s.splice(i,1);let r=_(s);if(t.visited.has(r))return;ve(s)&&pt(s,e)&&(yield s,t.visited.set(r,!0),yield*ke(s,e,t))}}function pt(n,e){return ne.querySelector(_(n))===e}var ct=["role","aria-label","type","name","href","src","data-testid","data-id"];function dt(n){let e=5381;for(let t=0;t<n.length;t++)e=(e<<5)+e+n.charCodeAt(t)|0;return(e>>>0).toString(36)}function ie(n){let e=n.children.length,t=0,i=n.parentElement;if(i)for(let o of i.children){if(o===n)break;o.tagName===n.tagName&&t++}let s=[];for(let o of ct){let a=n.getAttribute(o);a&&s.push(`${o}=${a}`)}let r=s.length>0?dt(s.join(",")):"0";return`${e}:${t}:${r}`}function Ee(n,e){let t=e.split(":");if(t.length!==3)return 0;let[i,s,r]=t,o=Number(i),a=Number(s);if(Number.isNaN(o)||Number.isNaN(a))return 0;let l=ie(n),[p,d,c]=l.split(":"),h=0,b=Math.abs(Number(p)-o);b===0?h+=.2:b<=2?h+=.1:b<=5&&(h+=.03);let g=Math.abs(Number(d)-a);return g===0?h+=.4:g===1?h+=.2:g<=3&&(h+=.08),c===r&&(h+=.4),h}function I(n,e){let t=e==="before"?"previousElementSibling":"nextElementSibling",i=n[t],s=3;for(;i&&s>0;){let r=i.textContent?.trim();if(r)return e==="before"?r.slice(-32):r.slice(0,32);i=i[t],s--}return""}function Y(n){let e=n.previousElementSibling?.textContent?.trim().slice(0,40)??"",t=n.nextElementSibling?.textContent?.trim().slice(0,40)??"";return[e,t].filter(Boolean).join(" | ")}function Ce(n){if(n.id){let i=n.id.includes("'")?`concat('${n.id.replace(/'/g,`',"'",'`)}')`:`'${n.id}'`;return`//${n.localName}[@id=${i}]`}let e=[],t=n;for(;t&&t!==document.body&&e.length<6;){let i=t.localName,s=t.parentElement;if(t.id){let o=t.id.includes("'")?`concat('${t.id.replace(/'/g,`',"'",'`)}')`:`'${t.id}'`;return e.unshift(`/${i}[@id=${o}]`),"/"+e.join("")}let r=1;if(s)for(let o of s.children){if(o===t)break;o.localName===i&&r++}e.unshift(`/${i}[${r}]`),t=s}return"/html/body"+e.join("")}function Te(n){let e=ye(n,{className:p=>!/^(css|sc|emotion|styled)-/.test(p)&&!/^[a-z]{1,3}[A-Za-z0-9]{4,8}$/.test(p),attr:p=>["data-testid","data-id","role","aria-label"].includes(p),idName:p=>!p.startsWith("radix-")&&!/^:r[0-9]+:$/.test(p),seedMinLength:3,optimizedMinLength:2}),t=Ce(n),s=(n.textContent?.trim()??"").slice(0,120),r=I(n,"before"),o=I(n,"after"),a=ie(n),l=Y(n);return{cssSelector:e,xpath:t,textSnippet:s,textPrefix:r,textSuffix:o,fingerprint:a,neighborText:l,elementTag:n.tagName,elementId:n.id||void 0}}function Se(n,e=document.documentElement){let t=n.x+n.width/2,i=n.y+n.height/2,s=document.elementFromPoint(t,i);if(!s||s===e)return document.body;let r=s,o=s;for(;o&&o!==document.body;){let a=o.getBoundingClientRect();if(a.left<=n.x&&a.top<=n.y&&a.right>=n.x+n.width&&a.bottom>=n.y+n.height){r=o;break}o=o.parentElement}return r}function Ae(n,e){return e.width<=0||e.height<=0?{xPct:0,yPct:0,wPct:1,hPct:1}:{xPct:(n.x-e.x)/e.width,yPct:(n.y-e.y)/e.height,wPct:n.width/e.width,hPct:n.height/e.height}}function C(n){let i=document.createRange().createContextualFragment(n).firstElementChild;if(!i||i.nodeName.toLowerCase()!=="svg")throw new Error("[siteping] Invalid SVG string");return i}function u(n,e){let t=document.createElement(n);if(e)for(let[i,s]of Object.entries(e))i==="class"?t.className=s:i==="style"?t.style.cssText=s:t.setAttribute(i,s);return t}function m(n,e){n.textContent=e}function q(n,e="fr"){let t=Date.now()-new Date(n).getTime(),i=Math.floor(t/1e3);if(i<60)return new Intl.RelativeTimeFormat(e,{numeric:"auto"}).format(0,"second");let s=new Intl.RelativeTimeFormat(e,{numeric:"always",style:"narrow"}),r=Math.floor(i/60);if(r<60)return s.format(-r,"minute");let o=Math.floor(r/60);if(o<24)return s.format(-o,"hour");let a=Math.floor(o/24);return a<7?s.format(-a,"day"):new Date(n).toLocaleDateString(e)}var se='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><circle cx="12" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="8" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="16" cy="10" r="1" fill="currentColor" stroke="none"/></svg>',Le='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',Me='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 3v18"/></svg>',re='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',oe='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>',K='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',Fe='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',Ie='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>',Re='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',Ne='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>',$e='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="6" width="8" height="14" rx="4"/><path d="M19 9h2"/><path d="M3 9h2"/><path d="M19 13h2"/><path d="M3 13h2"/><path d="M19 17h2"/><path d="M3 17h2"/><path d="M10 2h4"/></svg>',De='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>',Be='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>',ae='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';var He="#0066ff",ut=/^#[0-9a-fA-F]{6}$/,Oe=/^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/,ht=/^#[0-9a-fA-F]{8}$/;function ft(n){if(ut.test(n))return n;let e=Oe.test(n)?n.match(Oe):null;return e?`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`:ht.test(n)?n.slice(0,7):He}function mt(n,e){let t=Math.max(0,Math.round(parseInt(n.slice(1,3),16)*(1-e))),i=Math.max(0,Math.round(parseInt(n.slice(3,5),16)*(1-e))),s=Math.max(0,Math.round(parseInt(n.slice(5,7),16)*(1-e)));return`#${t.toString(16).padStart(2,"0")}${i.toString(16).padStart(2,"0")}${s.toString(16).padStart(2,"0")}`}function bt(){return typeof window>"u"?!1:window.matchMedia("(prefers-color-scheme: dark)").matches}function gt(n){return n==="dark"||n==="auto"&&bt()?"dark":"light"}function Pe(n=He,e){let t=ft(n),i=mt(t,.15);return gt(e)==="dark"?{accent:t,accentLight:t+"22",accentDark:i,accentGlow:t+"44",accentGradient:`linear-gradient(135deg, ${t}, ${i})`,bg:"#0f172a",bgHover:"#1e293b",text:"#f1f5f9",textSecondary:"#94a3b8",textTertiary:"#64748b",border:"#334155",shadow:"rgba(0, 0, 0, 0.3)",glassBg:"rgba(15, 23, 42, 0.78)",glassBgHeavy:"rgba(15, 23, 42, 0.88)",glassBorder:"rgba(51, 65, 85, 0.5)",glassBorderSubtle:"rgba(51, 65, 85, 0.3)",typeQuestion:"#60a5fa",typeChange:"#fbbf24",typeBug:"#f87171",typeOther:"#94a3b8",typeQuestionBg:"rgba(59, 130, 246, 0.15)",typeChangeBg:"rgba(245, 158, 11, 0.15)",typeBugBg:"rgba(239, 68, 68, 0.15)",typeOtherBg:"rgba(100, 116, 139, 0.15)"}:{accent:t,accentLight:t+"14",accentDark:i,accentGlow:t+"33",accentGradient:`linear-gradient(135deg, ${t}, ${i})`,bg:"#ffffff",bgHover:"#f8f9fb",text:"#0f172a",textSecondary:"#475569",textTertiary:"#64748b",border:"#e2e8f0",shadow:"rgba(0, 0, 0, 0.06)",glassBg:"rgba(255, 255, 255, 0.72)",glassBgHeavy:"rgba(255, 255, 255, 0.85)",glassBorder:"rgba(255, 255, 255, 0.35)",glassBorderSubtle:"rgba(255, 255, 255, 0.18)",typeQuestion:"#3b82f6",typeChange:"#b45309",typeBug:"#ef4444",typeOther:"#64748b",typeQuestionBg:"#eff6ff",typeChangeBg:"#fffbeb",typeBugBg:"#fef2f2",typeOtherBg:"#f8fafc"}}function A(n,e){switch(n){case"question":return e.typeQuestion;case"change":return e.typeChange;case"bug":return e.typeBug;default:return e.typeOther}}function F(n,e){switch(n){case"question":return e.typeQuestionBg;case"change":return e.typeChangeBg;case"bug":return e.typeBugBg;default:return e.typeOtherBg}}function ze(n){return`
2
+ --sp-accent: ${n.accent};
3
+ --sp-accent-light: ${n.accentLight};
4
+ --sp-accent-dark: ${n.accentDark};
5
+ --sp-accent-glow: ${n.accentGlow};
6
+ --sp-accent-gradient: ${n.accentGradient};
7
+ --sp-bg: ${n.bg};
8
+ --sp-bg-hover: ${n.bgHover};
9
+ --sp-text: ${n.text};
10
+ --sp-text-secondary: ${n.textSecondary};
11
+ --sp-text-tertiary: ${n.textTertiary};
12
+ --sp-border: ${n.border};
13
+ --sp-shadow: ${n.shadow};
14
+ --sp-glass-bg: ${n.glassBg};
15
+ --sp-glass-bg-heavy: ${n.glassBgHeavy};
16
+ --sp-glass-border: ${n.glassBorder};
17
+ --sp-glass-border-subtle: ${n.glassBorderSubtle};
18
+ --sp-type-question: ${n.typeQuestion};
19
+ --sp-type-change: ${n.typeChange};
20
+ --sp-type-bug: ${n.typeBug};
21
+ --sp-type-other: ${n.typeOther};
22
+ --sp-type-question-bg: ${n.typeQuestionBg};
23
+ --sp-type-change-bg: ${n.typeChangeBg};
24
+ --sp-type-bug-bg: ${n.typeBugBg};
25
+ --sp-type-other-bg: ${n.typeOtherBg};
578
26
  --sp-radius: 12px;
579
27
  --sp-radius-lg: 16px;
580
28
  --sp-radius-xl: 20px;
@@ -587,21 +35,7 @@ function cssVariables(colors) {
587
35
  --sp-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.04);
588
36
  --sp-shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.06);
589
37
  --sp-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
590
- `;
591
- }
592
-
593
- // src/popup.ts
594
- var TYPE_OPTIONS = [
595
- { type: "question", label: "Question", icon: ICON_QUESTION },
596
- { type: "changement", label: "Changement", icon: ICON_CHANGE },
597
- { type: "bug", label: "Bug", icon: ICON_BUG },
598
- { type: "autre", label: "Autre", icon: ICON_OTHER }
599
- ];
600
- var Popup = class {
601
- constructor(colors) {
602
- this.colors = colors;
603
- this.root = el("div", {
604
- style: `
38
+ `}var W=class{constructor(e,t){this.colors=e;this.t=t;this.root=u("div",{style:`
605
39
  position:fixed;
606
40
  z-index:2147483647;
607
41
  width:300px;
@@ -618,12 +52,7 @@ var Popup = class {
618
52
  transition:opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1),transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
619
53
  display:none;
620
54
  -webkit-font-smoothing:antialiased;
621
- `
622
- });
623
- const typeRow = el("div", { style: "display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px;" });
624
- for (const option of TYPE_OPTIONS) {
625
- const btn = document.createElement("button");
626
- btn.style.cssText = `
55
+ `}),this.root.setAttribute("role","dialog"),this.root.setAttribute("aria-modal","true"),this.root.setAttribute("aria-label","Formulaire de feedback");let i=[{type:"question",label:this.t("type.question"),icon:Re},{type:"change",label:this.t("type.change"),icon:Ne},{type:"bug",label:this.t("type.bug"),icon:$e},{type:"other",label:this.t("type.other"),icon:De}],s=u("div",{style:"display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px;"});for(let d of i){let c=document.createElement("button");c.style.cssText=`
627
56
  height:34px;
628
57
  border-radius:9999px;border:1px solid #e2e8f0;
629
58
  background:rgba(255,255,255,0.8);cursor:pointer;
@@ -632,34 +61,7 @@ var Popup = class {
632
61
  font-size:12px;font-weight:500;color:#64748b;
633
62
  transition:all 0.2s ease;
634
63
  padding:0 10px;
635
- `;
636
- const icon = parseSvg(option.icon);
637
- icon.setAttribute("style", "width:13px;height:13px;flex-shrink:0;");
638
- btn.appendChild(icon);
639
- const labelSpan = document.createElement("span");
640
- setText(labelSpan, option.label);
641
- btn.appendChild(labelSpan);
642
- btn.dataset.type = option.type;
643
- btn.addEventListener("click", () => {
644
- this.selectType(option.type, typeRow);
645
- });
646
- btn.addEventListener("mouseenter", () => {
647
- if (btn.dataset.type !== this.selectedType) {
648
- const bgColor = getTypeBgColor(btn.dataset.type ?? "", this.colors);
649
- btn.style.background = bgColor;
650
- btn.style.borderColor = getTypeColor(btn.dataset.type ?? "", this.colors) + "40";
651
- }
652
- });
653
- btn.addEventListener("mouseleave", () => {
654
- if (btn.dataset.type !== this.selectedType) {
655
- btn.style.background = "rgba(255,255,255,0.8)";
656
- btn.style.borderColor = "#e2e8f0";
657
- }
658
- });
659
- typeRow.appendChild(btn);
660
- }
661
- this.textarea = document.createElement("textarea");
662
- this.textarea.style.cssText = `
64
+ `;let h=C(d.icon);h.setAttribute("style","width:13px;height:13px;flex-shrink:0;"),c.appendChild(h);let b=document.createElement("span");m(b,d.label),c.appendChild(b),c.dataset.type=d.type,c.setAttribute("aria-pressed","false"),c.addEventListener("click",()=>{this.selectType(d.type,s)}),c.addEventListener("mouseenter",()=>{if(c.dataset.type!==this.selectedType){let g=F(c.dataset.type??"",this.colors);c.style.background=g,c.style.borderColor=A(c.dataset.type??"",this.colors)+"40"}}),c.addEventListener("mouseleave",()=>{c.dataset.type!==this.selectedType&&(c.style.background="rgba(255,255,255,0.8)",c.style.borderColor="#e2e8f0")}),s.appendChild(c)}this.textarea=document.createElement("textarea"),this.textarea.style.cssText=`
663
65
  width:100%;min-height:72px;max-height:152px;
664
66
  padding:10px 12px;border-radius:12px;
665
67
  border:1px solid #e2e8f0;
@@ -668,63 +70,19 @@ var Popup = class {
668
70
  font-size:13px;line-height:1.5;resize:vertical;
669
71
  outline:none;transition:all 0.2s ease;
670
72
  box-sizing:border-box;
671
- `;
672
- this.textarea.placeholder = "D\xE9crivez votre retour...";
673
- this.textarea.setAttribute("aria-label", "Message de feedback");
674
- const hint = el("div", {
675
- style: `
73
+ `,this.textarea.placeholder=this.t("popup.placeholder"),this.textarea.setAttribute("aria-label",this.t("popup.textareaAria"));let r=u("div",{style:`
676
74
  font-size:11px;color:#94a3b8;
677
75
  text-align:right;margin-top:4px;
678
76
  font-family:"Inter",system-ui,-apple-system,sans-serif;
679
77
  letter-spacing:0.01em;
680
- `
681
- });
682
- const isMac = navigator.platform.includes("Mac");
683
- setText(hint, isMac ? "\u2318+Entr\xE9e pour envoyer" : "Ctrl+Entr\xE9e pour envoyer");
684
- this.textarea.addEventListener("focus", () => {
685
- this.textarea.style.borderColor = this.colors.accent;
686
- this.textarea.style.boxShadow = `0 0 0 3px ${this.colors.accent}14`;
687
- this.textarea.style.background = "#fff";
688
- });
689
- this.textarea.addEventListener("blur", () => {
690
- this.textarea.style.borderColor = "#e2e8f0";
691
- this.textarea.style.boxShadow = "none";
692
- this.textarea.style.background = "rgba(255,255,255,0.85)";
693
- });
694
- this.textarea.addEventListener("input", () => {
695
- this.updateSubmitState();
696
- });
697
- this.textarea.addEventListener("keydown", (e) => {
698
- if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
699
- e.preventDefault();
700
- this.submit();
701
- }
702
- if (e.key === "Escape") {
703
- this.cancel();
704
- }
705
- });
706
- const btnRow = el("div", { style: "display:flex;justify-content:flex-end;gap:8px;margin-top:12px;" });
707
- const cancelBtn = document.createElement("button");
708
- cancelBtn.style.cssText = `
78
+ `}),o=navigator.userAgentData,a=o?o.platform==="macOS":navigator.platform?.includes("Mac")??!1;m(r,a?this.t("popup.submitHintMac"):this.t("popup.submitHintOther")),this.textarea.addEventListener("focus",()=>{this.textarea.style.borderColor=this.colors.accent,this.textarea.style.boxShadow=`0 0 0 3px ${this.colors.accent}14`,this.textarea.style.background="#fff"}),this.textarea.addEventListener("blur",()=>{this.textarea.style.borderColor="#e2e8f0",this.textarea.style.boxShadow="none",this.textarea.style.background="rgba(255,255,255,0.85)"}),this.textarea.addEventListener("input",()=>{this.updateSubmitState()}),this.textarea.addEventListener("keydown",d=>{d.key==="Enter"&&(d.ctrlKey||d.metaKey)&&(d.preventDefault(),this.submit()),d.key==="Escape"&&this.cancel()});let l=u("div",{style:"display:flex;justify-content:flex-end;gap:8px;margin-top:12px;"}),p=document.createElement("button");p.style.cssText=`
709
79
  height:34px;padding:0 16px;border-radius:9999px;
710
80
  border:1px solid #e2e8f0;
711
81
  background:rgba(255,255,255,0.8);
712
82
  color:#64748b;font-family:"Inter",system-ui,-apple-system,sans-serif;
713
83
  font-size:13px;font-weight:500;cursor:pointer;
714
84
  transition:all 0.2s ease;
715
- `;
716
- setText(cancelBtn, "Annuler");
717
- cancelBtn.addEventListener("click", () => this.cancel());
718
- cancelBtn.addEventListener("mouseenter", () => {
719
- cancelBtn.style.borderColor = this.colors.accent;
720
- cancelBtn.style.color = this.colors.accent;
721
- });
722
- cancelBtn.addEventListener("mouseleave", () => {
723
- cancelBtn.style.borderColor = "#e2e8f0";
724
- cancelBtn.style.color = "#64748b";
725
- });
726
- this.submitBtn = document.createElement("button");
727
- this.submitBtn.style.cssText = `
85
+ `,m(p,this.t("popup.cancel")),p.addEventListener("click",()=>this.cancel()),p.addEventListener("mouseenter",()=>{p.style.borderColor=this.colors.accent,p.style.color=this.colors.accent}),p.addEventListener("mouseleave",()=>{p.style.borderColor="#e2e8f0",p.style.color="#64748b"}),this.submitBtn=document.createElement("button"),this.submitBtn.style.cssText=`
728
86
  height:34px;padding:0 18px;border-radius:9999px;
729
87
  border:none;background:${this.colors.accentGradient};
730
88
  color:#fff;font-family:"Inter",system-ui,-apple-system,sans-serif;
@@ -732,142 +90,12 @@ var Popup = class {
732
90
  opacity:0.35;pointer-events:none;
733
91
  transition:all 0.2s ease;
734
92
  box-shadow:0 2px 8px ${this.colors.accentGlow};
735
- `;
736
- setText(this.submitBtn, "Envoyer");
737
- this.submitBtn.addEventListener("click", () => this.submit());
738
- btnRow.appendChild(cancelBtn);
739
- btnRow.appendChild(this.submitBtn);
740
- this.root.appendChild(typeRow);
741
- this.root.appendChild(this.textarea);
742
- this.root.appendChild(hint);
743
- this.root.appendChild(btnRow);
744
- document.body.appendChild(this.root);
745
- }
746
- colors;
747
- root;
748
- selectedType = null;
749
- textarea;
750
- submitBtn;
751
- resolve = null;
752
- /**
753
- * Show the popup near a drawn rectangle and return the user's input.
754
- * Returns null if cancelled.
755
- */
756
- show(rectBounds) {
757
- return new Promise((resolve) => {
758
- this.resolve = resolve;
759
- this.selectedType = null;
760
- this.textarea.value = "";
761
- this.updateSubmitState();
762
- this.resetTypeButtons();
763
- let top = rectBounds.bottom + 8;
764
- let left = rectBounds.left;
765
- if (top + 220 > window.innerHeight) {
766
- top = rectBounds.top - 220 - 8;
767
- }
768
- if (left + 300 > window.innerWidth) {
769
- left = rectBounds.right - 300;
770
- }
771
- left = Math.max(8, left);
772
- top = Math.max(8, top);
773
- this.root.style.top = `${top}px`;
774
- this.root.style.left = `${left}px`;
775
- this.root.style.display = "block";
776
- requestAnimationFrame(() => {
777
- this.root.style.opacity = "1";
778
- this.root.style.transform = "translateY(0) scale(1)";
779
- this.textarea.focus();
780
- });
781
- });
782
- }
783
- selectType(type, container) {
784
- this.selectedType = type;
785
- const buttons = container.querySelectorAll("button");
786
- for (const btn of buttons) {
787
- const isActive = btn.dataset.type === type;
788
- const color = getTypeColor(btn.dataset.type ?? "", this.colors);
789
- const bgColor = getTypeBgColor(btn.dataset.type ?? "", this.colors);
790
- btn.style.background = isActive ? bgColor : "rgba(255,255,255,0.8)";
791
- btn.style.borderColor = isActive ? color + "60" : "#e2e8f0";
792
- btn.style.color = isActive ? color : "#64748b";
793
- btn.style.fontWeight = isActive ? "600" : "500";
794
- }
795
- this.updateSubmitState();
796
- }
797
- resetTypeButtons() {
798
- const buttons = this.root.querySelectorAll("button[data-type]");
799
- for (const btn of buttons) {
800
- btn.style.background = "rgba(255,255,255,0.8)";
801
- btn.style.borderColor = "#e2e8f0";
802
- btn.style.color = "#64748b";
803
- btn.style.fontWeight = "500";
804
- }
805
- }
806
- updateSubmitState() {
807
- const enabled = this.selectedType !== null && this.textarea.value.trim().length > 0;
808
- this.submitBtn.style.opacity = enabled ? "1" : "0.35";
809
- this.submitBtn.style.pointerEvents = enabled ? "auto" : "none";
810
- }
811
- submit() {
812
- if (!this.selectedType || !this.textarea.value.trim()) return;
813
- this.resolve?.({ type: this.selectedType, message: this.textarea.value.trim() });
814
- this.resolve = null;
815
- this.hideElement();
816
- }
817
- cancel() {
818
- this.resolve?.(null);
819
- this.resolve = null;
820
- this.hideElement();
821
- }
822
- hideElement() {
823
- this.root.style.opacity = "0";
824
- this.root.style.transform = "translateY(8px) scale(0.98)";
825
- setTimeout(() => {
826
- this.root.style.display = "none";
827
- }, 250);
828
- }
829
- destroy() {
830
- this.root.remove();
831
- }
832
- };
833
-
834
- // src/annotator.ts
835
- var Annotator = class {
836
- constructor(config2, colors, bus) {
837
- this.config = config2;
838
- this.colors = colors;
839
- this.bus = bus;
840
- this.popup = new Popup(colors);
841
- this.bus.on("annotation:start", () => this.activate());
842
- }
843
- config;
844
- colors;
845
- bus;
846
- overlay = null;
847
- toolbar = null;
848
- drawingRect = null;
849
- startX = 0;
850
- startY = 0;
851
- isDrawing = false;
852
- isActive = false;
853
- popup;
854
- savedOverflow = "";
855
- activate() {
856
- if (this.isActive) return;
857
- this.isActive = true;
858
- this.config.onAnnotationStart?.();
859
- this.savedOverflow = document.body.style.overflow;
860
- document.body.style.overflow = "hidden";
861
- this.overlay = el("div", {
862
- style: `
93
+ `,m(this.submitBtn,this.t("popup.submit")),this.submitBtn.addEventListener("click",()=>this.submit()),l.appendChild(p),l.appendChild(this.submitBtn),this.root.appendChild(s),this.root.appendChild(this.textarea),this.root.appendChild(r),this.root.appendChild(l),document.body.appendChild(this.root)}colors;t;root;selectedType=null;textarea;submitBtn;resolve=null;previouslyFocused=null;onKeydownTrap=null;show(e){return new Promise(t=>{this.resolve=t,this.selectedType=null,this.textarea.value="",this.updateSubmitState(),this.resetTypeButtons(),this.previouslyFocused=document.activeElement;let i=e.bottom+8,s=e.left;i+220>window.innerHeight&&(i=e.top-220-8),s+300>window.innerWidth&&(s=e.right-300),s=Math.max(8,s),i=Math.max(8,i),this.root.style.top=`${i}px`,this.root.style.left=`${s}px`,this.root.style.display="block",this.onKeydownTrap=r=>{if(r.key==="Tab"){let o=Array.from(this.root.querySelectorAll('button:not([disabled]), textarea, input, [tabindex]:not([tabindex="-1"])'));if(o.length===0)return;let a=o[0],l=o[o.length-1];r.shiftKey?(document.activeElement===a||!this.root.contains(document.activeElement))&&(r.preventDefault(),l.focus()):(document.activeElement===l||!this.root.contains(document.activeElement))&&(r.preventDefault(),a.focus())}},this.root.addEventListener("keydown",this.onKeydownTrap),requestAnimationFrame(()=>{this.root.style.opacity="1",this.root.style.transform="translateY(0) scale(1)",this.textarea.focus()})})}selectType(e,t){this.selectedType=e;let i=t.querySelectorAll("button");for(let s of i){let r=s.dataset.type===e,o=A(s.dataset.type??"",this.colors),a=F(s.dataset.type??"",this.colors);s.style.background=r?a:"rgba(255,255,255,0.8)",s.style.borderColor=r?o+"60":"#e2e8f0",s.style.color=r?o:"#64748b",s.style.fontWeight=r?"600":"500",s.setAttribute("aria-pressed",String(r))}this.updateSubmitState()}resetTypeButtons(){let e=this.root.querySelectorAll("button[data-type]");for(let t of e)t.style.background="rgba(255,255,255,0.8)",t.style.borderColor="#e2e8f0",t.style.color="#64748b",t.style.fontWeight="500"}updateSubmitState(){let e=this.selectedType!==null&&this.textarea.value.trim().length>0;this.submitBtn.disabled=!e,this.submitBtn.style.opacity=e?"1":"0.35",this.submitBtn.style.pointerEvents=e?"auto":"none"}submit(){!this.selectedType||!this.textarea.value.trim()||(this.resolve?.({type:this.selectedType,message:this.textarea.value.trim()}),this.resolve=null,this.hideElement())}cancel(){this.resolve?.(null),this.resolve=null,this.hideElement()}hideElement(){this.onKeydownTrap&&(this.root.removeEventListener("keydown",this.onKeydownTrap),this.onKeydownTrap=null),this.root.style.opacity="0",this.root.style.transform="translateY(8px) scale(0.98)",this.previouslyFocused?.focus(),this.previouslyFocused=null,setTimeout(()=>{this.root.style.display="none"},250)}destroy(){this.root.remove()}};var G=class{constructor(e,t,i){this.colors=e;this.bus=t;this.t=i;this.popup=new W(e,i),this.bus.on("annotation:start",()=>this.activate())}colors;bus;t;overlay=null;toolbar=null;drawingRect=null;startX=0;startY=0;isDrawing=!1;isActive=!1;popup;savedOverflow="";activate(){if(this.isActive)return;this.isActive=!0,this.savedOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.overlay=u("div",{style:`
863
94
  position:fixed;inset:0;
864
95
  z-index:2147483646;
865
96
  background:rgba(15, 23, 42, 0.04);
866
97
  cursor:crosshair;
867
- `
868
- });
869
- this.toolbar = el("div", {
870
- style: `
98
+ `}),this.overlay.setAttribute("aria-hidden","true"),this.toolbar=u("div",{style:`
871
99
  position:fixed;top:0;left:0;right:0;
872
100
  z-index:2147483647;
873
101
  height:52px;
@@ -880,77 +108,19 @@ var Annotator = class {
880
108
  font-size:14px;color:#0f172a;
881
109
  box-shadow:0 4px 16px rgba(0,0,0,0.06);
882
110
  -webkit-font-smoothing:antialiased;
883
- `
884
- });
885
- const dot = el("span", {
886
- style: `
111
+ `});let e=u("span",{style:`
887
112
  width:8px;height:8px;border-radius:50%;
888
113
  background:${this.colors.accent};
889
114
  box-shadow:0 0 8px ${this.colors.accentGlow};
890
115
  animation:pulse 1.5s ease-in-out infinite;
891
- `
892
- });
893
- const style = document.createElement("style");
894
- style.textContent = `@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}`;
895
- this.toolbar.appendChild(style);
896
- const instruction = el("span", { style: "font-weight:500;letter-spacing:-0.01em;" });
897
- setText(instruction, "Tracez un rectangle sur la zone \xE0 commenter");
898
- const cancelBtn = document.createElement("button");
899
- cancelBtn.style.cssText = `
116
+ `}),t=document.createElement("style");t.textContent="@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}",this.toolbar.appendChild(t);let i=u("span",{style:"font-weight:500;letter-spacing:-0.01em;"});m(i,this.t("annotator.instruction"));let s=document.createElement("button");s.style.cssText=`
900
117
  height:34px;padding:0 18px;border-radius:9999px;
901
118
  border:1px solid #e2e8f0;
902
119
  background:rgba(255,255,255,0.8);
903
120
  color:#64748b;font-family:"Inter",system-ui,-apple-system,sans-serif;
904
121
  font-size:13px;font-weight:500;cursor:pointer;
905
122
  transition:all 0.2s ease;
906
- `;
907
- setText(cancelBtn, "Annuler");
908
- cancelBtn.addEventListener("click", () => this.deactivate());
909
- cancelBtn.addEventListener("mouseenter", () => {
910
- cancelBtn.style.borderColor = "#ef4444";
911
- cancelBtn.style.color = "#ef4444";
912
- cancelBtn.style.background = "rgba(239,68,68,0.06)";
913
- });
914
- cancelBtn.addEventListener("mouseleave", () => {
915
- cancelBtn.style.borderColor = "#e2e8f0";
916
- cancelBtn.style.color = "#64748b";
917
- cancelBtn.style.background = "rgba(255,255,255,0.8)";
918
- });
919
- this.toolbar.appendChild(dot);
920
- this.toolbar.appendChild(instruction);
921
- this.toolbar.appendChild(cancelBtn);
922
- this.overlay.addEventListener("mousedown", this.onMouseDown);
923
- this.overlay.addEventListener("mousemove", this.onMouseMove);
924
- this.overlay.addEventListener("mouseup", this.onMouseUp);
925
- document.addEventListener("keydown", this.onKeyDown);
926
- document.body.appendChild(this.overlay);
927
- document.body.appendChild(this.toolbar);
928
- }
929
- deactivate() {
930
- if (!this.isActive) return;
931
- this.isActive = false;
932
- this.isDrawing = false;
933
- document.body.style.overflow = this.savedOverflow;
934
- document.removeEventListener("keydown", this.onKeyDown);
935
- this.overlay?.remove();
936
- this.toolbar?.remove();
937
- this.drawingRect?.remove();
938
- this.overlay = null;
939
- this.toolbar = null;
940
- this.drawingRect = null;
941
- this.config.onAnnotationEnd?.();
942
- this.bus.emit("annotation:end");
943
- }
944
- onKeyDown = (e) => {
945
- if (e.key === "Escape") this.deactivate();
946
- };
947
- onMouseDown = (e) => {
948
- this.isDrawing = true;
949
- this.startX = e.clientX;
950
- this.startY = e.clientY;
951
- this.drawingRect?.remove();
952
- this.drawingRect = el("div", {
953
- style: `
123
+ `,m(s,this.t("annotator.cancel")),s.addEventListener("click",()=>this.deactivate()),s.addEventListener("mouseenter",()=>{s.style.borderColor="#ef4444",s.style.color="#ef4444",s.style.background="rgba(239,68,68,0.06)"}),s.addEventListener("mouseleave",()=>{s.style.borderColor="#e2e8f0",s.style.color="#64748b",s.style.background="rgba(255,255,255,0.8)"}),this.toolbar.appendChild(e),this.toolbar.appendChild(i),this.toolbar.appendChild(s),this.overlay.addEventListener("mousedown",this.onMouseDown),this.overlay.addEventListener("mousemove",this.onMouseMove),this.overlay.addEventListener("mouseup",this.onMouseUp),document.addEventListener("keydown",this.onKeyDown),document.body.appendChild(this.overlay),document.body.appendChild(this.toolbar)}deactivate(){this.isActive&&(this.isActive=!1,this.isDrawing=!1,document.body.style.overflow=this.savedOverflow,document.removeEventListener("keydown",this.onKeyDown),this.overlay?.remove(),this.toolbar?.remove(),this.drawingRect?.remove(),this.overlay=null,this.toolbar=null,this.drawingRect=null,this.bus.emit("annotation:end"))}onKeyDown=e=>{e.key==="Escape"&&this.deactivate()};onMouseDown=e=>{this.isDrawing=!0,this.startX=e.clientX,this.startY=e.clientY,this.drawingRect?.remove(),this.drawingRect=u("div",{style:`
954
124
  position:fixed;
955
125
  border:2px solid ${this.colors.accent};
956
126
  background:${this.colors.accent}12;
@@ -958,774 +128,7 @@ var Annotator = class {
958
128
  border-radius:8px;
959
129
  box-shadow:0 0 16px ${this.colors.accentGlow};
960
130
  transition:box-shadow 0.15s ease;
961
- `
962
- });
963
- this.overlay?.appendChild(this.drawingRect);
964
- };
965
- onMouseMove = (e) => {
966
- if (!this.isDrawing || !this.drawingRect) return;
967
- const x = Math.min(e.clientX, this.startX);
968
- const y = Math.min(e.clientY, this.startY);
969
- const w = Math.abs(e.clientX - this.startX);
970
- const h = Math.abs(e.clientY - this.startY);
971
- this.drawingRect.style.left = `${x}px`;
972
- this.drawingRect.style.top = `${y}px`;
973
- this.drawingRect.style.width = `${w}px`;
974
- this.drawingRect.style.height = `${h}px`;
975
- };
976
- onMouseUp = async (e) => {
977
- if (!this.isDrawing || !this.drawingRect) return;
978
- this.isDrawing = false;
979
- const x = Math.min(e.clientX, this.startX);
980
- const y = Math.min(e.clientY, this.startY);
981
- const w = Math.abs(e.clientX - this.startX);
982
- const h = Math.abs(e.clientY - this.startY);
983
- if (w < 10 || h < 10) {
984
- this.drawingRect.remove();
985
- this.drawingRect = null;
986
- return;
987
- }
988
- const rectBounds = new DOMRect(x, y, w, h);
989
- const result = await this.popup.show(rectBounds);
990
- if (!result) {
991
- this.drawingRect?.remove();
992
- this.drawingRect = null;
993
- return;
994
- }
995
- const annotation = this.buildAnnotation(rectBounds);
996
- this.drawingRect?.remove();
997
- this.drawingRect = null;
998
- this.deactivate();
999
- this.bus.emit("annotation:complete", {
1000
- annotation,
1001
- type: result.type,
1002
- message: result.message
1003
- });
1004
- };
1005
- /**
1006
- * Build an AnnotationPayload from a drawn rectangle.
1007
- * Temporarily hides the overlay to access the real DOM underneath.
1008
- */
1009
- buildAnnotation(rectBounds) {
1010
- if (this.overlay) this.overlay.style.pointerEvents = "none";
1011
- const anchorElement = findAnchorElement(rectBounds);
1012
- if (this.overlay) this.overlay.style.pointerEvents = "auto";
1013
- const anchor = generateAnchor(anchorElement);
1014
- const anchorBounds = anchorElement.getBoundingClientRect();
1015
- const rect = rectToPercentages(rectBounds, anchorBounds);
1016
- return {
1017
- anchor,
1018
- rect,
1019
- scrollX: window.scrollX,
1020
- scrollY: window.scrollY,
1021
- viewportW: window.innerWidth,
1022
- viewportH: window.innerHeight,
1023
- devicePixelRatio: window.devicePixelRatio
1024
- };
1025
- }
1026
- destroy() {
1027
- this.deactivate();
1028
- this.popup.destroy();
1029
- }
1030
- };
1031
-
1032
- // src/api-client.ts
1033
- var MAX_RETRIES = 3;
1034
- var TIMEOUT_MS = 1e4;
1035
- var RETRY_QUEUE_KEY = "siteping_retry_queue";
1036
- async function resilientFetch(url, init, retries = MAX_RETRIES) {
1037
- for (let attempt = 0; attempt <= retries; attempt++) {
1038
- const controller = new AbortController();
1039
- const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
1040
- try {
1041
- const response = await fetch(url, {
1042
- ...init,
1043
- signal: controller.signal
1044
- });
1045
- clearTimeout(timeout);
1046
- if (response.ok || response.status >= 400 && response.status < 500) {
1047
- return response;
1048
- }
1049
- if (attempt === retries) return response;
1050
- } catch (error) {
1051
- clearTimeout(timeout);
1052
- if (attempt === retries) throw error;
1053
- }
1054
- const baseDelay = 1e3 * 2 ** attempt;
1055
- const jitter = Math.random() * 1e3 - 500;
1056
- await new Promise((r) => setTimeout(r, baseDelay + jitter));
1057
- }
1058
- throw new Error("Max retries exceeded");
1059
- }
1060
- function queueForRetry(endpoint, payload) {
1061
- try {
1062
- const raw = localStorage.getItem(RETRY_QUEUE_KEY);
1063
- const queue = raw ? JSON.parse(raw) : [];
1064
- queue.push({ endpoint, payload });
1065
- localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(queue));
1066
- } catch {
1067
- }
1068
- }
1069
- async function flushRetryQueue(endpoint) {
1070
- try {
1071
- const raw = localStorage.getItem(RETRY_QUEUE_KEY);
1072
- if (!raw) return;
1073
- const queue = JSON.parse(raw);
1074
- const toRetry = queue.filter((e) => e.endpoint === endpoint);
1075
- if (toRetry.length === 0) return;
1076
- const failed = [];
1077
- for (const entry of toRetry) {
1078
- try {
1079
- const res = await fetch(endpoint, {
1080
- method: "POST",
1081
- headers: { "Content-Type": "application/json" },
1082
- body: JSON.stringify(entry.payload)
1083
- });
1084
- if (!res.ok) failed.push(entry);
1085
- } catch {
1086
- failed.push(entry);
1087
- }
1088
- }
1089
- const remaining = queue.filter((e) => e.endpoint !== endpoint).concat(failed);
1090
- if (remaining.length > 0) {
1091
- localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(remaining));
1092
- } else {
1093
- localStorage.removeItem(RETRY_QUEUE_KEY);
1094
- }
1095
- } catch {
1096
- }
1097
- }
1098
- var ApiClient = class {
1099
- constructor(endpoint) {
1100
- this.endpoint = endpoint;
1101
- }
1102
- endpoint;
1103
- async sendFeedback(payload) {
1104
- try {
1105
- const response = await resilientFetch(this.endpoint, {
1106
- method: "POST",
1107
- headers: { "Content-Type": "application/json" },
1108
- body: JSON.stringify(payload)
1109
- });
1110
- if (!response.ok) {
1111
- const text = await response.text().catch(() => "Unknown error");
1112
- throw new Error(`Failed to send feedback: ${response.status} ${text}`);
1113
- }
1114
- return await response.json();
1115
- } catch (error) {
1116
- queueForRetry(this.endpoint, payload);
1117
- throw error;
1118
- }
1119
- }
1120
- async getFeedbacks(projectName, options) {
1121
- const params = new URLSearchParams({ projectName });
1122
- if (options?.page) params.set("page", String(options.page));
1123
- if (options?.limit) params.set("limit", String(options.limit));
1124
- if (options?.type) params.set("type", options.type);
1125
- if (options?.status) params.set("status", options.status);
1126
- if (options?.search) params.set("search", options.search);
1127
- const response = await resilientFetch(`${this.endpoint}?${params.toString()}`, { method: "GET" });
1128
- if (!response.ok) {
1129
- throw new Error(`Failed to fetch feedbacks: ${response.status}`);
1130
- }
1131
- return await response.json();
1132
- }
1133
- async resolveFeedback(id2, resolved) {
1134
- const response = await resilientFetch(this.endpoint, {
1135
- method: "PATCH",
1136
- headers: { "Content-Type": "application/json" },
1137
- body: JSON.stringify({ id: id2, status: resolved ? "resolved" : "open" })
1138
- });
1139
- if (!response.ok) {
1140
- throw new Error(`Failed to update feedback: ${response.status}`);
1141
- }
1142
- return await response.json();
1143
- }
1144
- async deleteFeedback(id2) {
1145
- const response = await resilientFetch(this.endpoint, {
1146
- method: "DELETE",
1147
- headers: { "Content-Type": "application/json" },
1148
- body: JSON.stringify({ id: id2 })
1149
- });
1150
- if (!response.ok) {
1151
- throw new Error(`Failed to delete feedback: ${response.status}`);
1152
- }
1153
- }
1154
- async deleteAllFeedbacks(projectName) {
1155
- const response = await resilientFetch(this.endpoint, {
1156
- method: "DELETE",
1157
- headers: { "Content-Type": "application/json" },
1158
- body: JSON.stringify({ projectName, deleteAll: true })
1159
- });
1160
- if (!response.ok) {
1161
- throw new Error(`Failed to delete all feedbacks: ${response.status}`);
1162
- }
1163
- }
1164
- };
1165
-
1166
- // src/events.ts
1167
- var EventBus = class {
1168
- listeners = /* @__PURE__ */ new Map();
1169
- on(event, listener) {
1170
- if (!this.listeners.has(event)) {
1171
- this.listeners.set(event, /* @__PURE__ */ new Set());
1172
- }
1173
- const set = this.listeners.get(event);
1174
- set.add(listener);
1175
- return () => {
1176
- set.delete(listener);
1177
- };
1178
- }
1179
- emit(event, ...args) {
1180
- const set = this.listeners.get(event);
1181
- if (!set) return;
1182
- for (const fn of set) {
1183
- try {
1184
- fn(...args);
1185
- } catch (err) {
1186
- console.error(`[siteping] Error in event listener for "${String(event)}":`, err);
1187
- }
1188
- }
1189
- }
1190
- removeAll() {
1191
- this.listeners.clear();
1192
- }
1193
- };
1194
-
1195
- // src/fab.ts
1196
- var ITEM_GAP = 54;
1197
- var Fab = class {
1198
- constructor(shadowRoot, config2, bus) {
1199
- this.bus = bus;
1200
- const position = config2.position ?? "bottom-right";
1201
- const isRight = position === "bottom-right";
1202
- this.items = [
1203
- { id: "chat", icon: ICON_CHAT, label: "Messages" },
1204
- { id: "annotate", icon: ICON_ANNOTATE, label: "Annoter" },
1205
- { id: "toggle-annotations", icon: ICON_EYE, iconAlt: ICON_EYE_OFF, label: "Annotations" }
1206
- ];
1207
- this.fab = document.createElement("button");
1208
- this.fab.className = `sp-fab sp-fab--${position} sp-anim-fab-in`;
1209
- this.fab.style.position = "fixed";
1210
- this.fab.appendChild(parseSvg(ICON_SITEPING));
1211
- this.fab.setAttribute("aria-label", "Siteping \u2014 Menu feedback");
1212
- this.fab.setAttribute("aria-expanded", "false");
1213
- this.fab.addEventListener("click", () => this.toggle());
1214
- this.radialContainer = document.createElement("div");
1215
- this.radialContainer.className = `sp-radial sp-radial--${position}`;
1216
- this.radialContainer.setAttribute("role", "menu");
1217
- for (let i = 0; i < this.items.length; i++) {
1218
- const item = this.items[i];
1219
- const btn = document.createElement("button");
1220
- btn.className = "sp-radial-item";
1221
- btn.style.setProperty("--sp-i", String(i));
1222
- btn.appendChild(parseSvg(item.icon));
1223
- btn.setAttribute("role", "menuitem");
1224
- btn.setAttribute("aria-label", item.label);
1225
- btn.dataset.itemId = item.id;
1226
- btn.addEventListener("click", (e) => {
1227
- e.stopPropagation();
1228
- this.handleItemClick(item.id);
1229
- });
1230
- const label = document.createElement("span");
1231
- label.className = "sp-radial-label";
1232
- label.textContent = item.label;
1233
- label.style.cssText = isRight ? "position:absolute; right:54px; top:50%; transform:translateY(-50%); white-space:nowrap;" : "position:absolute; left:54px; top:50%; transform:translateY(-50%); white-space:nowrap;";
1234
- btn.appendChild(label);
1235
- this.radialContainer.appendChild(btn);
1236
- }
1237
- this.root = document.createElement("div");
1238
- this.root.appendChild(this.radialContainer);
1239
- this.root.appendChild(this.fab);
1240
- shadowRoot.appendChild(this.root);
1241
- const host = shadowRoot.host;
1242
- this.onDocumentClick = (e) => {
1243
- if (this.isOpen && !e.composedPath().includes(host)) {
1244
- this.close();
1245
- }
1246
- };
1247
- document.addEventListener("click", this.onDocumentClick);
1248
- this.fab.addEventListener("keydown", (e) => {
1249
- if (e.key === "Escape" && this.isOpen) this.close();
1250
- });
1251
- }
1252
- bus;
1253
- root;
1254
- fab;
1255
- radialContainer;
1256
- badgeEl = null;
1257
- isOpen = false;
1258
- annotationsVisible = true;
1259
- items;
1260
- onDocumentClick;
1261
- /** Update the badge count. Pass 0 to hide. */
1262
- updateBadge(count) {
1263
- if (count <= 0) {
1264
- this.badgeEl?.remove();
1265
- this.badgeEl = null;
1266
- return;
1267
- }
1268
- if (!this.badgeEl) {
1269
- this.badgeEl = document.createElement("span");
1270
- this.badgeEl.className = "sp-fab-badge";
1271
- this.fab.appendChild(this.badgeEl);
1272
- }
1273
- setText(this.badgeEl, count > 99 ? "99+" : String(count));
1274
- }
1275
- toggle() {
1276
- this.isOpen ? this.close() : this.open();
1277
- }
1278
- open() {
1279
- this.isOpen = true;
1280
- this.setFabIcon(ICON_CLOSE);
1281
- this.fab.setAttribute("aria-expanded", "true");
1282
- const buttons = this.radialContainer.querySelectorAll(".sp-radial-item");
1283
- buttons.forEach((btn, i) => {
1284
- const y = -(16 + ITEM_GAP * (i + 1));
1285
- btn.style.transform = `translate(0px, ${y}px) scale(1)`;
1286
- btn.classList.add("sp-radial-item--open");
1287
- });
1288
- }
1289
- close() {
1290
- this.isOpen = false;
1291
- this.setFabIcon(ICON_SITEPING);
1292
- this.fab.setAttribute("aria-expanded", "false");
1293
- const buttons = this.radialContainer.querySelectorAll(".sp-radial-item");
1294
- buttons.forEach((btn) => {
1295
- btn.style.transform = "translate(0, 0) scale(0.8)";
1296
- btn.classList.remove("sp-radial-item--open");
1297
- });
1298
- }
1299
- setFabIcon(svgStr) {
1300
- const badge = this.badgeEl;
1301
- this.fab.replaceChildren(parseSvg(svgStr));
1302
- if (badge) this.fab.appendChild(badge);
1303
- }
1304
- handleItemClick(id2) {
1305
- this.close();
1306
- switch (id2) {
1307
- case "chat":
1308
- this.bus.emit("panel:toggle", true);
1309
- break;
1310
- case "annotate":
1311
- this.bus.emit("annotation:start");
1312
- break;
1313
- case "toggle-annotations": {
1314
- this.annotationsVisible = !this.annotationsVisible;
1315
- this.bus.emit("annotations:toggle", this.annotationsVisible);
1316
- const btn = this.radialContainer.querySelector('[data-item-id="toggle-annotations"]');
1317
- if (btn) {
1318
- btn.replaceChildren(parseSvg(this.annotationsVisible ? ICON_EYE : ICON_EYE_OFF));
1319
- }
1320
- break;
1321
- }
1322
- }
1323
- }
1324
- destroy() {
1325
- document.removeEventListener("click", this.onDocumentClick);
1326
- this.root.remove();
1327
- }
1328
- };
1329
-
1330
- // src/identity.ts
1331
- var STORAGE_KEY = "siteping_identity";
1332
- function getIdentity() {
1333
- try {
1334
- const raw = localStorage.getItem(STORAGE_KEY);
1335
- if (!raw) return null;
1336
- const parsed = JSON.parse(raw);
1337
- if (parsed.name && parsed.email) return parsed;
1338
- return null;
1339
- } catch {
1340
- return null;
1341
- }
1342
- }
1343
- function saveIdentity(identity) {
1344
- try {
1345
- localStorage.setItem(STORAGE_KEY, JSON.stringify(identity));
1346
- } catch {
1347
- }
1348
- }
1349
-
1350
- // src/dom/fuzzy.ts
1351
- function editDistance(a, b) {
1352
- if (a === b) return 0;
1353
- if (a.length === 0) return b.length;
1354
- if (b.length === 0) return a.length;
1355
- if (a.length > b.length) {
1356
- const t = a;
1357
- a = b;
1358
- b = t;
1359
- }
1360
- const aLen = a.length;
1361
- const bLen = b.length;
1362
- let prev = new Array(aLen + 1);
1363
- for (let k = 0; k <= aLen; k++) prev[k] = k;
1364
- let curr = new Array(aLen + 1);
1365
- for (let j = 1; j <= bLen; j++) {
1366
- curr[0] = j;
1367
- for (let i = 1; i <= aLen; i++) {
1368
- curr[i] = a[i - 1] === b[j - 1] ? prev[i - 1] : 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
1369
- }
1370
- const tmp = prev;
1371
- prev = curr;
1372
- curr = tmp;
1373
- }
1374
- return prev[aLen];
1375
- }
1376
- function similarity(a, b) {
1377
- if (a === b) return 1;
1378
- const maxLen = Math.max(a.length, b.length);
1379
- if (maxLen === 0) return 1;
1380
- return 1 - editDistance(a, b) / maxLen;
1381
- }
1382
- function fuzzyIncludes(haystack, needle, minScore = 0.6) {
1383
- if (!needle || !haystack) return 0;
1384
- if (haystack.includes(needle)) return 1;
1385
- const nLen = needle.length;
1386
- if (nLen > haystack.length) {
1387
- const score = similarity(haystack, needle);
1388
- return score >= minScore ? score : 0;
1389
- }
1390
- let best = 0;
1391
- const capped = haystack.length > 500 ? haystack.slice(0, 500) : haystack;
1392
- const limit = capped.length - nLen;
1393
- for (let i = 0; i <= limit; i++) {
1394
- const window2 = capped.slice(i, i + nLen);
1395
- const score = similarity(window2, needle);
1396
- if (score > best) best = score;
1397
- if (best >= 0.95) break;
1398
- }
1399
- return best >= minScore ? best : 0;
1400
- }
1401
-
1402
- // src/dom/resolver.ts
1403
- var MAX_SCAN_CANDIDATES = 300;
1404
- var TEXT_MATCH_THRESHOLD = 0.3;
1405
- function textMatches(el2, anchor) {
1406
- if (!anchor.textSnippet) return true;
1407
- const text = (el2.textContent?.trim() ?? "").slice(0, 500);
1408
- return fuzzyIncludes(text, anchor.textSnippet, 0.5) > TEXT_MATCH_THRESHOLD;
1409
- }
1410
- function resolveAnchor(anchor) {
1411
- if (anchor.elementId) {
1412
- const el2 = document.getElementById(anchor.elementId);
1413
- if (el2 && el2.tagName === anchor.elementTag && textMatches(el2, anchor)) {
1414
- return { element: el2, confidence: 1, strategy: "id" };
1415
- }
1416
- }
1417
- try {
1418
- const el2 = document.querySelector(anchor.cssSelector);
1419
- if (el2 && el2.tagName === anchor.elementTag && textMatches(el2, anchor)) {
1420
- return { element: el2, confidence: 0.95, strategy: "css" };
1421
- }
1422
- } catch {
1423
- }
1424
- try {
1425
- const result = document.evaluate(anchor.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1426
- const el2 = result.singleNodeValue;
1427
- if (el2 instanceof Element && el2.tagName === anchor.elementTag && textMatches(el2, anchor)) {
1428
- return { element: el2, confidence: 0.9, strategy: "xpath" };
1429
- }
1430
- } catch {
1431
- }
1432
- return smartScan(anchor);
1433
- }
1434
- function smartScan(anchor) {
1435
- const tag = anchor.elementTag.toLowerCase();
1436
- const candidates = document.querySelectorAll(tag);
1437
- if (candidates.length === 0) return null;
1438
- let bestElement = null;
1439
- let bestScore = 0;
1440
- const limit = Math.min(candidates.length, MAX_SCAN_CANDIDATES);
1441
- for (let i = 0; i < limit; i++) {
1442
- const el2 = candidates[i];
1443
- const score = scoreCandidate(el2, anchor);
1444
- if (score > bestScore) {
1445
- bestScore = score;
1446
- bestElement = el2;
1447
- if (bestScore >= 0.85) break;
1448
- }
1449
- }
1450
- if (!bestElement || bestScore < 0.4) return null;
1451
- return {
1452
- element: bestElement,
1453
- confidence: Math.min(bestScore, 0.85),
1454
- strategy: "scan"
1455
- };
1456
- }
1457
- function scoreCandidate(candidate, anchor) {
1458
- let score = 0;
1459
- let totalWeight = 0;
1460
- const candidateText = (candidate.textContent?.trim() ?? "").slice(0, 500);
1461
- if (anchor.textSnippet) {
1462
- totalWeight += 40;
1463
- score += fuzzyIncludes(candidateText, anchor.textSnippet, 0.5) * 40;
1464
- }
1465
- if (anchor.fingerprint) {
1466
- totalWeight += 20;
1467
- score += scoreFingerprint(candidate, anchor.fingerprint) * 20;
1468
- }
1469
- if (anchor.textPrefix || anchor.textSuffix) {
1470
- totalWeight += 20;
1471
- let contextScore = 0;
1472
- let contextParts = 0;
1473
- if (anchor.textPrefix) {
1474
- const prevText = adjacentText(candidate, "before");
1475
- contextScore += prevText ? similarity(prevText, anchor.textPrefix) : 0;
1476
- contextParts++;
1477
- }
1478
- if (anchor.textSuffix) {
1479
- const nextText = adjacentText(candidate, "after");
1480
- contextScore += nextText ? similarity(nextText, anchor.textSuffix) : 0;
1481
- contextParts++;
1482
- }
1483
- if (contextParts > 0) {
1484
- score += contextScore / contextParts * 20;
1485
- }
1486
- }
1487
- if (anchor.neighborText) {
1488
- totalWeight += 20;
1489
- const candidateNeighbor = neighborText(candidate);
1490
- score += candidateNeighbor ? similarity(candidateNeighbor, anchor.neighborText) * 20 : 0;
1491
- }
1492
- return totalWeight > 0 ? score / totalWeight : 0;
1493
- }
1494
- function resolveAnnotation(anchor, rect) {
1495
- const resolution = resolveAnchor(anchor);
1496
- if (!resolution) return null;
1497
- const bounds = resolution.element.getBoundingClientRect();
1498
- const absoluteRect = new DOMRect(
1499
- bounds.x + rect.xPct * bounds.width,
1500
- bounds.y + rect.yPct * bounds.height,
1501
- rect.wPct * bounds.width,
1502
- rect.hPct * bounds.height
1503
- );
1504
- return {
1505
- element: resolution.element,
1506
- rect: absoluteRect,
1507
- confidence: resolution.confidence,
1508
- strategy: resolution.strategy
1509
- };
1510
- }
1511
-
1512
- // src/markers.ts
1513
- function toAnchorData(a) {
1514
- return {
1515
- cssSelector: a.cssSelector,
1516
- xpath: a.xpath,
1517
- textSnippet: a.textSnippet,
1518
- elementTag: a.elementTag,
1519
- elementId: a.elementId ?? void 0,
1520
- textPrefix: a.textPrefix,
1521
- textSuffix: a.textSuffix,
1522
- fingerprint: a.fingerprint,
1523
- neighborText: a.neighborText
1524
- };
1525
- }
1526
- function toRectData(a) {
1527
- return { xPct: a.xPct, yPct: a.yPct, wPct: a.wPct, hPct: a.hPct };
1528
- }
1529
- var MARKER_OFFSET = 13;
1530
- function markerPosition(rect) {
1531
- return {
1532
- top: rect.top + window.scrollY - MARKER_OFFSET,
1533
- left: rect.right + window.scrollX - MARKER_OFFSET
1534
- };
1535
- }
1536
- function clusterMarker(cluster, i) {
1537
- return cluster.entries[i].elements[cluster.elementIndices[i]];
1538
- }
1539
- var HIGHLIGHT_FADE = 300;
1540
- var REPOSITION_DEBOUNCE = 200;
1541
- var LOW_CONFIDENCE_THRESHOLD = 0.7;
1542
- var CLUSTER_DISTANCE = 28;
1543
- var FAN_SPACING = 32;
1544
- var MarkerManager = class {
1545
- constructor(colors, tooltip, bus) {
1546
- this.colors = colors;
1547
- this.tooltip = tooltip;
1548
- this.bus = bus;
1549
- this.container = el("div", {
1550
- style: "position:absolute;top:0;left:0;pointer-events:none;z-index:2147483646;"
1551
- });
1552
- this.container.id = "siteping-markers";
1553
- document.body.appendChild(this.container);
1554
- this.bus.on("annotations:toggle", (visible) => {
1555
- this.container.style.display = visible ? "block" : "none";
1556
- });
1557
- this.resizeHandler = () => this.scheduleReposition();
1558
- window.addEventListener("resize", this.resizeHandler, { passive: true });
1559
- this.mutationObserver = new MutationObserver((mutations) => {
1560
- const isWidgetMutation = mutations.every(
1561
- (m) => this.container.contains(m.target) || this.tooltip.contains(m.target)
1562
- );
1563
- if (!isWidgetMutation) this.scheduleReposition();
1564
- });
1565
- this.mutationObserver.observe(document.body, {
1566
- childList: true,
1567
- subtree: true,
1568
- attributes: false,
1569
- characterData: false
1570
- });
1571
- this.onDocumentClickForClusters = (e) => {
1572
- if (this.container.contains(e.target)) return;
1573
- this.collapseAllClusters();
1574
- };
1575
- document.addEventListener("click", this.onDocumentClickForClusters);
1576
- }
1577
- colors;
1578
- tooltip;
1579
- bus;
1580
- container;
1581
- entries = [];
1582
- highlightElements = [];
1583
- pinnedFeedback = null;
1584
- onDocumentClick = null;
1585
- repositionTimer = null;
1586
- mutationObserver = null;
1587
- resizeHandler = null;
1588
- clusters = [];
1589
- onDocumentClickForClusters = null;
1590
- get count() {
1591
- return this.entries.length;
1592
- }
1593
- scheduleReposition() {
1594
- if (this.repositionTimer) return;
1595
- this.repositionTimer = setTimeout(() => {
1596
- this.repositionTimer = null;
1597
- this.repositionAll();
1598
- }, REPOSITION_DEBOUNCE);
1599
- }
1600
- repositionAll() {
1601
- for (const entry of this.entries) {
1602
- for (let i = 0; i < entry.feedback.annotations.length; i++) {
1603
- const markerEl = entry.elements[i];
1604
- if (!markerEl) continue;
1605
- const annotation = entry.feedback.annotations[i];
1606
- const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));
1607
- if (!resolved) {
1608
- markerEl.style.display = "none";
1609
- continue;
1610
- }
1611
- const pos = markerPosition(resolved.rect);
1612
- entry.baseTop = pos.top;
1613
- entry.baseLeft = pos.left;
1614
- markerEl.style.display = "flex";
1615
- this.applyConfidenceStyle(markerEl, resolved.confidence, entry.feedback);
1616
- }
1617
- }
1618
- this.applyClusterPositions();
1619
- }
1620
- applyClusterPositions() {
1621
- for (const cluster of this.clusters) {
1622
- if (cluster.expanded) {
1623
- this.applyFanPositions(cluster);
1624
- } else {
1625
- this.applyStackPositions(cluster);
1626
- }
1627
- }
1628
- }
1629
- render(feedbacks) {
1630
- this.clear();
1631
- feedbacks.forEach((feedback, i) => {
1632
- const entry = this.buildEntry(feedback, i + 1);
1633
- this.entries.push(entry);
1634
- });
1635
- this.buildClusters();
1636
- }
1637
- addFeedback(feedback, index2) {
1638
- const entry = this.buildEntry(feedback, index2);
1639
- for (const m of entry.elements) {
1640
- m.style.animation = "sp-marker-in 0.35s cubic-bezier(0.34,1.56,0.64,1) both";
1641
- }
1642
- this.entries.push(entry);
1643
- this.buildClusters();
1644
- }
1645
- buildEntry(feedback, index2) {
1646
- const entry = { feedback, elements: [], baseTop: 0, baseLeft: 0 };
1647
- for (const annotation of feedback.annotations) {
1648
- const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));
1649
- if (!resolved) continue;
1650
- const pos = markerPosition(resolved.rect);
1651
- entry.baseTop = pos.top;
1652
- entry.baseLeft = pos.left;
1653
- const marker = this.createMarker(index2, feedback, pos);
1654
- this.applyConfidenceStyle(marker, resolved.confidence, feedback);
1655
- this.container.appendChild(marker);
1656
- entry.elements.push(marker);
1657
- }
1658
- return entry;
1659
- }
1660
- buildClusters() {
1661
- for (const badge of this.container.querySelectorAll(".sp-cluster-badge")) {
1662
- badge.remove();
1663
- }
1664
- const allItems = [];
1665
- for (const entry of this.entries) {
1666
- for (let i = 0; i < entry.elements.length; i++) {
1667
- allItems.push({ entry, elIdx: i });
1668
- }
1669
- }
1670
- const used = /* @__PURE__ */ new Set();
1671
- this.clusters = [];
1672
- for (let i = 0; i < allItems.length; i++) {
1673
- if (used.has(i)) continue;
1674
- const cluster = {
1675
- entries: [allItems[i].entry],
1676
- elementIndices: [allItems[i].elIdx],
1677
- expanded: false
1678
- };
1679
- used.add(i);
1680
- for (let j = i + 1; j < allItems.length; j++) {
1681
- if (used.has(j)) continue;
1682
- const a = allItems[i].entry;
1683
- const b = allItems[j].entry;
1684
- const dist = Math.sqrt((a.baseLeft - b.baseLeft) ** 2 + (a.baseTop - b.baseTop) ** 2);
1685
- if (dist < CLUSTER_DISTANCE) {
1686
- cluster.entries.push(b);
1687
- cluster.elementIndices.push(allItems[j].elIdx);
1688
- used.add(j);
1689
- }
1690
- }
1691
- this.clusters.push(cluster);
1692
- }
1693
- for (const cluster of this.clusters) {
1694
- if (cluster.entries.length <= 1) continue;
1695
- this.applyStackPositions(cluster);
1696
- this.addClusterBadge(cluster);
1697
- }
1698
- }
1699
- applyStackPositions(cluster) {
1700
- const { baseTop, baseLeft } = cluster.entries[0];
1701
- const isSolo = cluster.entries.length <= 1;
1702
- for (let i = 0; i < cluster.entries.length; i++) {
1703
- const m = clusterMarker(cluster, i);
1704
- if (!m) continue;
1705
- m.style.top = `${baseTop + (isSolo ? 0 : i * 3)}px`;
1706
- m.style.left = `${baseLeft + (isSolo ? 0 : i * 3)}px`;
1707
- m.style.zIndex = String(i + 1);
1708
- }
1709
- }
1710
- applyFanPositions(cluster) {
1711
- const { baseTop, baseLeft } = cluster.entries[0];
1712
- const count = cluster.entries.length;
1713
- const totalWidth = (count - 1) * FAN_SPACING;
1714
- const startLeft = baseLeft - totalWidth / 2;
1715
- for (let i = 0; i < count; i++) {
1716
- const m = clusterMarker(cluster, i);
1717
- if (!m) continue;
1718
- m.style.top = `${baseTop}px`;
1719
- m.style.left = `${startLeft + i * FAN_SPACING}px`;
1720
- m.style.zIndex = String(10 + i);
1721
- }
1722
- }
1723
- addClusterBadge(cluster) {
1724
- const topMarker = clusterMarker(cluster, cluster.entries.length - 1);
1725
- if (!topMarker) return;
1726
- const badge = el("div", {
1727
- class: "sp-cluster-badge",
1728
- style: `
131
+ `}),this.overlay?.appendChild(this.drawingRect)};onMouseMove=e=>{if(!this.isDrawing||!this.drawingRect)return;let t=Math.min(e.clientX,this.startX),i=Math.min(e.clientY,this.startY),s=Math.abs(e.clientX-this.startX),r=Math.abs(e.clientY-this.startY);this.drawingRect.style.left=`${t}px`,this.drawingRect.style.top=`${i}px`,this.drawingRect.style.width=`${s}px`,this.drawingRect.style.height=`${r}px`};onMouseUp=async e=>{if(!this.isDrawing||!this.drawingRect)return;this.isDrawing=!1;let t=Math.min(e.clientX,this.startX),i=Math.min(e.clientY,this.startY),s=Math.abs(e.clientX-this.startX),r=Math.abs(e.clientY-this.startY);if(s<10||r<10){this.drawingRect.remove(),this.drawingRect=null;return}let o=new DOMRect(t,i,s,r),a=await this.popup.show(o);if(!a){this.drawingRect?.remove(),this.drawingRect=null;return}let l=this.buildAnnotation(o);this.drawingRect?.remove(),this.drawingRect=null,this.deactivate(),this.bus.emit("annotation:complete",{annotation:l,type:a.type,message:a.message})};buildAnnotation(e){this.overlay&&(this.overlay.style.pointerEvents="none");let t=Se(e);this.overlay&&(this.overlay.style.pointerEvents="auto");let i=Te(t),s=t.getBoundingClientRect(),r=Ae(e,s);return{anchor:i,rect:r,scrollX:window.scrollX,scrollY:window.scrollY,viewportW:window.innerWidth,viewportH:window.innerHeight,devicePixelRatio:window.devicePixelRatio}}destroy(){this.deactivate(),this.popup.destroy()}};var $="siteping_retry_queue";async function N(n,e,t=3){for(let i=0;i<=t;i++){let s=new AbortController,r=setTimeout(()=>s.abort(),1e4);try{let l=await fetch(n,{...e,signal:s.signal});if(clearTimeout(r),l.ok||l.status>=400&&l.status<500||i===t)return l}catch(l){if(clearTimeout(r),i===t)throw l}let o=1e3*2**i,a=Math.random()*1e3-500;await new Promise(l=>setTimeout(l,o+a))}throw new Error("Max retries exceeded")}function yt(n,e){try{let t=localStorage.getItem($),i=t?JSON.parse(t):[];i.push({endpoint:n,payload:e}),localStorage.setItem($,JSON.stringify(i))}catch{}}async function je(n){try{let e=localStorage.getItem($);if(!e)return;let t=JSON.parse(e),i=t.filter(a=>a.endpoint===n);if(i.length===0)return;let r=(await Promise.allSettled(i.map(a=>fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a.payload)}).then(l=>({entry:a,ok:l.ok}))))).map((a,l)=>a.status==="rejected"||!a.value.ok?i[l]:null).filter(a=>a!==null),o=t.filter(a=>a.endpoint!==n).concat(r);o.length>0?localStorage.setItem($,JSON.stringify(o)):localStorage.removeItem($)}catch{}}var X=class{constructor(e){this.endpoint=e}endpoint;async sendFeedback(e){try{let t=await N(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let i=await t.text().catch(()=>"Unknown error");throw new Error(`Failed to send feedback: ${t.status} ${i}`)}return await t.json()}catch(t){throw yt(this.endpoint,e),t}}async getFeedbacks(e,t){let i=new URLSearchParams({projectName:e});t?.page&&i.set("page",String(t.page)),t?.limit&&i.set("limit",String(t.limit)),t?.type&&i.set("type",t.type),t?.status&&i.set("status",t.status),t?.search&&i.set("search",t.search);let s=await N(`${this.endpoint}?${i.toString()}`,{method:"GET"});if(!s.ok)throw new Error(`Failed to fetch feedbacks: ${s.status}`);return await s.json()}async resolveFeedback(e,t){let i=await N(this.endpoint,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e,status:t?"resolved":"open"})});if(!i.ok)throw new Error(`Failed to update feedback: ${i.status}`);return await i.json()}async deleteFeedback(e){let t=await N(this.endpoint,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e})});if(!t.ok)throw new Error(`Failed to delete feedback: ${t.status}`)}async deleteAllFeedbacks(e){let t=await N(this.endpoint,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectName:e,deleteAll:!0})});if(!t.ok)throw new Error(`Failed to delete all feedbacks: ${t.status}`)}};var D=class{listeners=new Map;on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set);let i=this.listeners.get(e);return i.add(t),()=>{i.delete(t)}}off(e,t){let i=this.listeners.get(e);i&&i.delete(t)}emit(e,...t){let i=this.listeners.get(e);if(i)for(let s of i)try{s(...t)}catch(r){console.error(`[siteping] Error in event listener for "${String(e)}":`,r)}}removeAll(){this.listeners.clear()}};var vt=54,V=class{constructor(e,t,i,s){this.bus=i;let r=t.position??"bottom-right",o=r==="bottom-right";this.items=[{id:"chat",icon:Le,label:s("fab.messages")},{id:"annotate",icon:Me,label:s("fab.annotate")},{id:"toggle-annotations",icon:re,iconAlt:oe,label:s("fab.annotations")}],this.fab=document.createElement("button"),this.fab.className=`sp-fab sp-fab--${r} sp-anim-fab-in`,this.fab.style.position="fixed",this.fab.appendChild(C(se)),this.fab.setAttribute("aria-label",s("fab.aria")),this.fab.setAttribute("aria-expanded","false"),this.fab.addEventListener("click",()=>this.toggle()),this.radialContainer=document.createElement("div"),this.radialContainer.className=`sp-radial sp-radial--${r}`,this.radialContainer.setAttribute("role","menu");for(let p=0;p<this.items.length;p++){let d=this.items[p],c=document.createElement("button");c.className="sp-radial-item",c.style.setProperty("--sp-i",String(p)),c.appendChild(C(d.icon)),c.setAttribute("role","menuitem"),c.setAttribute("aria-label",d.label),c.dataset.itemId=d.id,c.addEventListener("click",b=>{b.stopPropagation(),this.handleItemClick(d.id)});let h=document.createElement("span");h.className="sp-radial-label",h.textContent=d.label,h.style.cssText=o?"position:absolute; right:54px; top:50%; transform:translateY(-50%); white-space:nowrap;":"position:absolute; left:54px; top:50%; transform:translateY(-50%); white-space:nowrap;",c.appendChild(h),this.radialContainer.appendChild(c)}this.root=document.createElement("div"),this.root.appendChild(this.radialContainer),this.root.appendChild(this.fab),e.appendChild(this.root);let a=e.host;this.onDocumentClick=p=>{this.isOpen&&!p.composedPath().includes(a)&&this.close()},document.addEventListener("click",this.onDocumentClick);let l=p=>{p.key==="Escape"&&this.isOpen&&(p.stopPropagation(),this.close())};this.fab.addEventListener("keydown",l),this.radialContainer.addEventListener("keydown",l),this.radialContainer.addEventListener("keydown",p=>{let d=Array.from(this.radialContainer.querySelectorAll(".sp-radial-item"));if(d.length===0||!this.isOpen)return;let c=e.activeElement??document.activeElement,h=d.indexOf(c);switch(p.key){case"ArrowUp":{p.preventDefault();let b=h<=0?d.length-1:h-1;d[b].focus();break}case"ArrowDown":{p.preventDefault();let b=h>=d.length-1?0:h+1;d[b].focus();break}case"Home":{p.preventDefault(),d[0].focus();break}case"End":{p.preventDefault(),d[d.length-1].focus();break}}})}bus;root;fab;radialContainer;badgeEl=null;isOpen=!1;annotationsVisible=!0;items;onDocumentClick;updateBadge(e){if(e<=0){this.badgeEl?.remove(),this.badgeEl=null;return}this.badgeEl||(this.badgeEl=document.createElement("span"),this.badgeEl.className="sp-fab-badge",this.fab.appendChild(this.badgeEl)),m(this.badgeEl,e>99?"99+":String(e))}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen=!0,this.setFabIcon(K),this.fab.setAttribute("aria-expanded","true"),this.radialContainer.querySelectorAll(".sp-radial-item").forEach((t,i)=>{let s=-(16+vt*(i+1));t.style.transform=`translate(0px, ${s}px) scale(1)`,t.classList.add("sp-radial-item--open")}),requestAnimationFrame(()=>{this.radialContainer.querySelector(".sp-radial-item")?.focus()})}close(){this.isOpen=!1,this.setFabIcon(se),this.fab.setAttribute("aria-expanded","false"),this.radialContainer.querySelectorAll(".sp-radial-item").forEach(t=>{t.style.transform="translate(0, 0) scale(0.8)",t.classList.remove("sp-radial-item--open")}),this.fab.focus()}setFabIcon(e){let t=this.badgeEl;this.fab.replaceChildren(C(e)),t&&this.fab.appendChild(t)}handleItemClick(e){switch(this.close(),e){case"chat":this.bus.emit("panel:toggle",!0);break;case"annotate":this.bus.emit("annotation:start");break;case"toggle-annotations":{this.annotationsVisible=!this.annotationsVisible,this.bus.emit("annotations:toggle",this.annotationsVisible);let t=this.radialContainer.querySelector('[data-item-id="toggle-annotations"]');t&&t.replaceChildren(C(this.annotationsVisible?re:oe));break}}}destroy(){document.removeEventListener("click",this.onDocumentClick),this.root.remove()}};var _e={"panel.title":"Feedbacks","panel.close":"Close panel","panel.deleteAll":"Delete all","panel.deleteAllConfirmTitle":"Delete all","panel.deleteAllConfirmMessage":"Delete all feedbacks for this project? This action cannot be undone.","panel.search":"Search...","panel.searchAria":"Search feedbacks","panel.filterAll":"All","panel.loadError":"Failed to load","panel.retry":"Retry","panel.empty":"No feedback yet","panel.showMore":"Show more","panel.showLess":"Show less","panel.resolve":"Resolve","panel.reopen":"Reopen","panel.delete":"Delete","panel.cancel":"Cancel","panel.confirmDelete":"Delete","type.question":"Question","type.change":"Change","type.bug":"Bug","type.other":"Other","fab.aria":"Siteping \u2014 Feedback menu","fab.messages":"Messages","fab.annotate":"Annotate","fab.annotations":"Annotations","annotator.instruction":"Draw a rectangle on the area to comment","annotator.cancel":"Cancel","popup.placeholder":"Describe your feedback...","popup.textareaAria":"Feedback message","popup.submitHintMac":"\u2318+Enter to send","popup.submitHintOther":"Ctrl+Enter to send","popup.cancel":"Cancel","popup.submit":"Send","identity.title":"Identify yourself","identity.nameLabel":"Name","identity.namePlaceholder":"Your name","identity.emailLabel":"Email","identity.emailPlaceholder":"your@email.com","identity.cancel":"Cancel","identity.submit":"Continue","marker.approximate":"Approximate position (confidence: {confidence}%)"};var Ye={"panel.title":"Feedbacks","panel.close":"Fermer le panneau","panel.deleteAll":"Tout supprimer","panel.deleteAllConfirmTitle":"Tout supprimer","panel.deleteAllConfirmMessage":"Supprimer tous les feedbacks de ce projet ? Cette action est irr\xE9versible.","panel.search":"Rechercher...","panel.searchAria":"Rechercher dans les feedbacks","panel.filterAll":"Tous","panel.loadError":"Erreur de chargement","panel.retry":"R\xE9essayer","panel.empty":"Aucun feedback pour le moment","panel.showMore":"Voir plus","panel.showLess":"Voir moins","panel.resolve":"R\xE9soudre","panel.reopen":"Rouvrir","panel.delete":"Supprimer","panel.cancel":"Annuler","panel.confirmDelete":"Supprimer","type.question":"Question","type.change":"Changement","type.bug":"Bug","type.other":"Autre","fab.aria":"Siteping \u2014 Menu feedback","fab.messages":"Messages","fab.annotate":"Annoter","fab.annotations":"Annotations","annotator.instruction":"Tracez un rectangle sur la zone \xE0 commenter","annotator.cancel":"Annuler","popup.placeholder":"D\xE9crivez votre retour...","popup.textareaAria":"Message de feedback","popup.submitHintMac":"\u2318+Entr\xE9e pour envoyer","popup.submitHintOther":"Ctrl+Entr\xE9e pour envoyer","popup.cancel":"Annuler","popup.submit":"Envoyer","identity.title":"Identifiez-vous","identity.nameLabel":"Nom","identity.namePlaceholder":"Votre nom","identity.emailLabel":"Email","identity.emailPlaceholder":"votre@email.com","identity.cancel":"Annuler","identity.submit":"Continuer","marker.approximate":"Position approximative (confiance : {confidence}%)"};var qe={fr:Ye,en:_e};function U(n){let e=n.split("-")[0].toLowerCase(),t=qe[e]??qe.fr;return i=>t[i]??i}function B(n,e){switch(n){case"question":return e("type.question");case"change":return e("type.change");case"bug":return e("type.bug");case"other":return e("type.other");default:return n}}var Ke="siteping_identity";function We(){try{let n=localStorage.getItem(Ke);if(!n)return null;let e=JSON.parse(n);return e.name&&e.email?e:null}catch{return null}}function Ge(n){try{localStorage.setItem(Ke,JSON.stringify(n))}catch{}}function xt(n,e){if(n===e)return 0;if(n.length===0)return e.length;if(e.length===0)return n.length;if(n.length>e.length){let o=n;n=e,e=o}let t=n.length,i=e.length,s=new Array(t+1);for(let o=0;o<=t;o++)s[o]=o;let r=new Array(t+1);for(let o=1;o<=i;o++){r[0]=o;for(let l=1;l<=t;l++)r[l]=n[l-1]===e[o-1]?s[l-1]:1+Math.min(s[l-1],s[l],r[l-1]);let a=s;s=r,r=a}return s[t]}function R(n,e){if(n===e)return 1;let t=Math.max(n.length,e.length);return t===0?1:1-xt(n,e)/t}function le(n,e,t=.6){if(!e||!n)return 0;if(n.includes(e))return 1;let i=e.length;if(i>n.length){let a=R(n,e);return a>=t?a:0}let s=0,r=n.length>500?n.slice(0,500):n,o=r.length-i;for(let a=0;a<=o;a++){let l=r.slice(a,a+i),p=R(l,e);if(p>s&&(s=p),s>=.95)break}return s>=t?s:0}var wt=300,kt=.3;function pe(n,e){if(!e.textSnippet)return!0;let t=(n.textContent?.trim()??"").slice(0,500);return le(t,e.textSnippet,.5)>kt}function Et(n){if(n.elementId){let e=document.getElementById(n.elementId);if(e&&e.tagName===n.elementTag&&pe(e,n))return{element:e,confidence:1,strategy:"id"}}try{let e=document.querySelector(n.cssSelector);if(e&&e.tagName===n.elementTag&&pe(e,n))return{element:e,confidence:.95,strategy:"css"}}catch{}try{let t=document.evaluate(n.xpath,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;if(t instanceof Element&&t.tagName===n.elementTag&&pe(t,n))return{element:t,confidence:.9,strategy:"xpath"}}catch{}return Ct(n)}function Ct(n){let e=n.elementTag.toLowerCase(),t=document.querySelectorAll(e);if(t.length===0)return null;let i=null,s=0,r=Math.min(t.length,wt);for(let o=0;o<r;o++){let a=t[o],l=Tt(a,n);if(l>s&&(s=l,i=a,s>=.85))break}return!i||s<.4?null:{element:i,confidence:Math.min(s,.85),strategy:"scan"}}function Tt(n,e){let t=0,i=0,s=(n.textContent?.trim()??"").slice(0,500);if(e.textSnippet&&(i+=40,t+=le(s,e.textSnippet,.5)*40),e.fingerprint&&(i+=20,t+=Ee(n,e.fingerprint)*20),e.textPrefix||e.textSuffix){i+=20;let r=0,o=0;if(e.textPrefix){let a=I(n,"before");r+=a?R(a,e.textPrefix):0,o++}if(e.textSuffix){let a=I(n,"after");r+=a?R(a,e.textSuffix):0,o++}o>0&&(t+=r/o*20)}if(e.neighborText){i+=20;let r=Y(n);t+=r?R(r,e.neighborText)*20:0}return i>0?t/i:0}function Q(n,e){let t=Et(n);if(!t)return null;let i=t.element.getBoundingClientRect(),s=new DOMRect(i.x+e.xPct*i.width,i.y+e.yPct*i.height,e.wPct*i.width,e.hPct*i.height);return{element:t.element,rect:s,confidence:t.confidence,strategy:t.strategy}}function ce(n){return{cssSelector:n.cssSelector,xpath:n.xpath,textSnippet:n.textSnippet,elementTag:n.elementTag,elementId:n.elementId??void 0,textPrefix:n.textPrefix,textSuffix:n.textSuffix,fingerprint:n.fingerprint,neighborText:n.neighborText}}function de(n){return{xPct:n.xPct,yPct:n.yPct,wPct:n.wPct,hPct:n.hPct}}var Xe=13;function Ve(n){return{top:n.top+window.scrollY-Xe,left:n.right+window.scrollX-Xe}}function O(n,e){return n.entries[e].elements[n.elementIndices[e]]}var Ue=300,St=200,At=.7,Lt=28,Qe=32,J=class{constructor(e,t,i,s){this.colors=e;this.tooltip=t;this.bus=i;this.t=s;this.container=u("div",{style:"position:absolute;top:0;left:0;pointer-events:none;z-index:2147483646;"}),this.container.id="siteping-markers",document.body.appendChild(this.container),this.bus.on("annotations:toggle",r=>{this.container.style.display=r?"block":"none"}),this.resizeHandler=()=>this.scheduleReposition(),window.addEventListener("resize",this.resizeHandler,{passive:!0}),this.mutationObserver=new MutationObserver(r=>{r.every(a=>this.container.contains(a.target)||this.tooltip.contains(a.target))||this.scheduleReposition()}),this.mutationObserver.observe(document.body,{childList:!0,subtree:!0,attributes:!1,characterData:!1}),this.onDocumentClickForClusters=r=>{this.container.contains(r.target)||this.collapseAllClusters()},document.addEventListener("click",this.onDocumentClickForClusters)}colors;tooltip;bus;t;container;entries=[];highlightElements=[];pinnedFeedback=null;onDocumentClick=null;repositionTimer=null;mutationObserver=null;resizeHandler=null;clusters=[];onDocumentClickForClusters=null;get count(){return this.entries.length}scheduleReposition(){this.repositionTimer||(this.repositionTimer=setTimeout(()=>{this.repositionTimer=null,this.repositionAll()},St))}repositionAll(){for(let e of this.entries)for(let t=0;t<e.feedback.annotations.length;t++){let i=e.elements[t];if(!i)continue;let s=e.feedback.annotations[t],r=Q(ce(s),de(s));if(!r){i.style.display="none";continue}let o=Ve(r.rect);e.baseTop=o.top,e.baseLeft=o.left,i.style.display="flex",this.applyConfidenceStyle(i,r.confidence,e.feedback)}this.applyClusterPositions()}applyClusterPositions(){for(let e of this.clusters)e.expanded?this.applyFanPositions(e):this.applyStackPositions(e)}render(e){this.clear(),e.forEach((t,i)=>{let s=this.buildEntry(t,i+1);this.entries.push(s)}),this.buildClusters()}addFeedback(e,t){let i=this.buildEntry(e,t);for(let s of i.elements)s.style.animation="sp-marker-in 0.35s cubic-bezier(0.34,1.56,0.64,1) both";this.entries.push(i),this.buildClusters()}buildEntry(e,t){let i={feedback:e,elements:[],baseTop:0,baseLeft:0};for(let s of e.annotations){let r=Q(ce(s),de(s));if(!r)continue;let o=Ve(r.rect);i.baseTop=o.top,i.baseLeft=o.left;let a=this.createMarker(t,e,o);this.applyConfidenceStyle(a,r.confidence,e),this.container.appendChild(a),i.elements.push(a)}return i}buildClusters(){for(let i of this.container.querySelectorAll(".sp-cluster-badge"))i.remove();let e=[];for(let i of this.entries)for(let s=0;s<i.elements.length;s++)e.push({entry:i,elIdx:s});let t=new Set;this.clusters=[];for(let i=0;i<e.length;i++){if(t.has(i))continue;let s={entries:[e[i].entry],elementIndices:[e[i].elIdx],expanded:!1};t.add(i);for(let r=i+1;r<e.length;r++){if(t.has(r))continue;let o=e[i].entry,a=e[r].entry;Math.sqrt((o.baseLeft-a.baseLeft)**2+(o.baseTop-a.baseTop)**2)<Lt&&(s.entries.push(a),s.elementIndices.push(e[r].elIdx),t.add(r))}this.clusters.push(s)}for(let i of this.clusters)i.entries.length<=1||(this.applyStackPositions(i),this.addClusterBadge(i))}applyStackPositions(e){let{baseTop:t,baseLeft:i}=e.entries[0],s=e.entries.length<=1;for(let r=0;r<e.entries.length;r++){let o=O(e,r);o&&(o.style.top=`${t+(s?0:r*3)}px`,o.style.left=`${i+(s?0:r*3)}px`,o.style.zIndex=String(r+1))}}applyFanPositions(e){let{baseTop:t,baseLeft:i}=e.entries[0],s=e.entries.length,r=(s-1)*Qe,o=i-r/2;for(let a=0;a<s;a++){let l=O(e,a);l&&(l.style.top=`${t}px`,l.style.left=`${o+a*Qe}px`,l.style.zIndex=String(10+a))}}addClusterBadge(e){let t=O(e,e.entries.length-1);if(!t)return;let i=u("div",{class:"sp-cluster-badge",style:`
1729
132
  position:absolute;top:-6px;right:-6px;
1730
133
  min-width:16px;height:16px;padding:0 4px;
1731
134
  border-radius:9999px;
@@ -1736,618 +139,38 @@ var MarkerManager = class {
1736
139
  pointer-events:none;
1737
140
  font-family:"Inter",system-ui,-apple-system,sans-serif;
1738
141
  line-height:1;
1739
- `
1740
- });
1741
- setText(badge, String(cluster.entries.length));
1742
- topMarker.appendChild(badge);
1743
- }
1744
- setBadgesVisible(cluster, visible) {
1745
- for (let i = 0; i < cluster.entries.length; i++) {
1746
- const badge = clusterMarker(cluster, i)?.querySelector(".sp-cluster-badge");
1747
- if (badge) badge.style.display = visible ? "flex" : "none";
1748
- }
1749
- }
1750
- findCluster(marker) {
1751
- for (const cluster of this.clusters) {
1752
- if (cluster.entries.length <= 1) continue;
1753
- for (let i = 0; i < cluster.entries.length; i++) {
1754
- if (clusterMarker(cluster, i) === marker) return cluster;
1755
- }
1756
- }
1757
- return null;
1758
- }
1759
- handleClusterClick(marker, e) {
1760
- const cluster = this.findCluster(marker);
1761
- if (!cluster) return false;
1762
- if (!cluster.expanded) {
1763
- e.stopPropagation();
1764
- this.collapseAllClusters();
1765
- cluster.expanded = true;
1766
- this.applyFanPositions(cluster);
1767
- this.setBadgesVisible(cluster, false);
1768
- return true;
1769
- }
1770
- return false;
1771
- }
1772
- collapseCluster(cluster) {
1773
- if (!cluster.expanded) return;
1774
- cluster.expanded = false;
1775
- this.applyStackPositions(cluster);
1776
- this.setBadgesVisible(cluster, true);
1777
- }
1778
- collapseAllClusters() {
1779
- for (const cluster of this.clusters) {
1780
- this.collapseCluster(cluster);
1781
- }
1782
- }
1783
- applyConfidenceStyle(marker, confidence, feedback) {
1784
- const isResolved = feedback.status === "resolved";
1785
- if (confidence < LOW_CONFIDENCE_THRESHOLD && !isResolved) {
1786
- marker.style.borderStyle = "dashed";
1787
- marker.style.opacity = "0.7";
1788
- marker.title = `Position approximative (confiance : ${Math.round(confidence * 100)}%)`;
1789
- } else {
1790
- marker.style.borderStyle = "solid";
1791
- marker.style.opacity = "1";
1792
- marker.title = "";
1793
- }
1794
- }
1795
- createMarker(number, feedback, pos) {
1796
- const typeColor = getTypeColor(feedback.type, this.colors);
1797
- const isResolved = feedback.status === "resolved";
1798
- const marker = el("div", {
1799
- style: `
142
+ `});m(i,String(e.entries.length)),t.appendChild(i)}setBadgesVisible(e,t){for(let i=0;i<e.entries.length;i++){let s=O(e,i)?.querySelector(".sp-cluster-badge");s&&(s.style.display=t?"flex":"none")}}findCluster(e){for(let t of this.clusters)if(!(t.entries.length<=1)){for(let i=0;i<t.entries.length;i++)if(O(t,i)===e)return t}return null}handleClusterClick(e,t){let i=this.findCluster(e);return i?i.expanded?!1:(t.stopPropagation(),this.collapseAllClusters(),i.expanded=!0,this.applyFanPositions(i),this.setBadgesVisible(i,!1),!0):!1}collapseCluster(e){e.expanded&&(e.expanded=!1,this.applyStackPositions(e),this.setBadgesVisible(e,!0))}collapseAllClusters(){for(let e of this.clusters)this.collapseCluster(e)}applyConfidenceStyle(e,t,i){let s=i.status==="resolved";t<At&&!s?(e.style.borderStyle="dashed",e.style.opacity="0.7",e.title=this.t("marker.approximate").replace("{confidence}",String(Math.round(t*100)))):(e.style.borderStyle="solid",e.style.opacity="1",e.title="")}createMarker(e,t,i){let s=A(t.type,this.colors),r=t.status==="resolved",o=u("div",{style:`
1800
143
  position:absolute;
1801
- top:${pos.top}px;
1802
- left:${pos.left}px;
144
+ top:${i.top}px;
145
+ left:${i.left}px;
1803
146
  width:26px;height:26px;
1804
147
  border-radius:50%;
1805
- background:${isResolved ? "rgba(241,245,249,0.9)" : "rgba(255,255,255,0.92)"};
148
+ background:${r?"rgba(241,245,249,0.9)":"rgba(255,255,255,0.92)"};
1806
149
  backdrop-filter:blur(12px);
1807
150
  -webkit-backdrop-filter:blur(12px);
1808
- border:2px solid ${isResolved ? "#cbd5e1" : typeColor};
151
+ border:2px solid ${r?"#cbd5e1":s};
1809
152
  display:flex;align-items:center;justify-content:center;
1810
153
  font-family:"Inter",system-ui,-apple-system,sans-serif;
1811
154
  font-size:11px;font-weight:700;
1812
- color:${isResolved ? "#94a3b8" : typeColor};
155
+ color:${r?"#94a3b8":s};
1813
156
  cursor:pointer;pointer-events:auto;
1814
- box-shadow:${isResolved ? "0 2px 8px rgba(0,0,0,0.06)" : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`};
157
+ box-shadow:${r?"0 2px 8px rgba(0,0,0,0.06)":`0 2px 12px ${s}25, 0 2px 6px rgba(0,0,0,0.06)`};
1815
158
  transition:top 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.15s ease, box-shadow 0.15s ease;
1816
159
  user-select:none;
1817
160
  -webkit-font-smoothing:antialiased;
1818
- `
1819
- });
1820
- marker.dataset.feedbackId = feedback.id;
1821
- setText(marker, isResolved ? "\u2713" : String(number));
1822
- marker.addEventListener("mouseenter", () => {
1823
- marker.style.transform = "scale(1.2)";
1824
- marker.style.boxShadow = isResolved ? "0 4px 16px rgba(0,0,0,0.1)" : `0 4px 20px ${typeColor}35, 0 4px 12px rgba(0,0,0,0.08)`;
1825
- this.tooltip.show(feedback, marker.getBoundingClientRect());
1826
- if (!this.pinnedFeedback) this.showHighlight(feedback);
1827
- });
1828
- marker.addEventListener("mouseleave", () => {
1829
- marker.style.transform = "scale(1)";
1830
- marker.style.boxShadow = isResolved ? "0 2px 8px rgba(0,0,0,0.06)" : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`;
1831
- this.tooltip.scheduleHide();
1832
- if (!this.pinnedFeedback) this.clearHighlight();
1833
- });
1834
- marker.addEventListener("click", (e) => {
1835
- if (this.handleClusterClick(marker, e)) return;
1836
- this.pinHighlight(feedback);
1837
- this.bus.emit("panel:toggle", true);
1838
- marker.dispatchEvent(
1839
- new CustomEvent("sp-marker-click", {
1840
- detail: { feedbackId: feedback.id },
1841
- bubbles: true
1842
- })
1843
- );
1844
- });
1845
- return marker;
1846
- }
1847
- highlight(feedbackId) {
1848
- for (const entry of this.entries) {
1849
- if (entry.feedback.id === feedbackId) {
1850
- for (const markerEl of entry.elements) {
1851
- markerEl.style.animation = "sp-pulse-ring 0.7s ease-out";
1852
- markerEl.addEventListener(
1853
- "animationend",
1854
- () => {
1855
- markerEl.style.animation = "";
1856
- },
1857
- { once: true }
1858
- );
1859
- }
1860
- }
1861
- }
1862
- }
1863
- showHighlight(feedback) {
1864
- this.removeHighlightElements();
1865
- for (const annotation of feedback.annotations) {
1866
- const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));
1867
- if (!resolved) continue;
1868
- const typeColor = getTypeColor(feedback.type, this.colors);
1869
- const rect = resolved.rect;
1870
- const highlight = el("div", {
1871
- style: `
161
+ `});o.dataset.feedbackId=t.id,o.setAttribute("tabindex","0"),o.setAttribute("role","button"),o.setAttribute("aria-label",`Feedback #${e}: ${t.type} \u2014 ${t.message.slice(0,60)}${t.message.length>60?"...":""}`),o.setAttribute("aria-describedby",this.tooltip.tooltipId),m(o,r?"\u2713":String(e)),o.addEventListener("mouseenter",()=>{o.style.transform="scale(1.2)",o.style.boxShadow=r?"0 4px 16px rgba(0,0,0,0.1)":`0 4px 20px ${s}35, 0 4px 12px rgba(0,0,0,0.08)`,this.tooltip.show(t,o.getBoundingClientRect()),this.pinnedFeedback||this.showHighlight(t)}),o.addEventListener("mouseleave",()=>{o.style.transform="scale(1)",o.style.boxShadow=r?"0 2px 8px rgba(0,0,0,0.06)":`0 2px 12px ${s}25, 0 2px 6px rgba(0,0,0,0.06)`,this.tooltip.scheduleHide(),this.pinnedFeedback||this.clearHighlight()});let a=l=>{l instanceof MouseEvent&&this.handleClusterClick(o,l)||(this.pinHighlight(t),this.bus.emit("panel:toggle",!0),o.dispatchEvent(new CustomEvent("sp-marker-click",{detail:{feedbackId:t.id},bubbles:!0})))};return o.addEventListener("click",l=>a(l)),o.addEventListener("keydown",l=>{(l.key==="Enter"||l.key===" ")&&(l.preventDefault(),a(l))}),o}highlight(e){for(let t of this.entries)if(t.feedback.id===e)for(let i of t.elements)i.style.animation="sp-pulse-ring 0.7s ease-out",i.addEventListener("animationend",()=>{i.style.animation=""},{once:!0})}showHighlight(e){this.removeHighlightElements();for(let t of e.annotations){let i=Q(ce(t),de(t));if(!i)continue;let s=A(e.type,this.colors),r=i.rect,o=u("div",{style:`
1872
162
  position:absolute;
1873
- top:${rect.top + window.scrollY}px;
1874
- left:${rect.left + window.scrollX}px;
1875
- width:${rect.width}px;height:${rect.height}px;
1876
- border:2px solid ${typeColor};
1877
- background:${typeColor}0c;
163
+ top:${r.top+window.scrollY}px;
164
+ left:${r.left+window.scrollX}px;
165
+ width:${r.width}px;height:${r.height}px;
166
+ border:2px solid ${s};
167
+ background:${s}0c;
1878
168
  border-radius:8px;
1879
169
  pointer-events:none;z-index:-1;
1880
170
  opacity:0;
1881
- box-shadow:0 0 16px ${typeColor}20;
1882
- transition:opacity ${HIGHLIGHT_FADE}ms ease;
1883
- `
1884
- });
1885
- this.container.appendChild(highlight);
1886
- this.highlightElements.push(highlight);
1887
- highlight.offsetHeight;
1888
- highlight.style.opacity = "1";
1889
- }
1890
- }
1891
- pinHighlight(feedback) {
1892
- this.unpinHighlight();
1893
- this.showHighlight(feedback);
1894
- this.pinnedFeedback = feedback;
1895
- this.onDocumentClick = (e) => {
1896
- if (this.container.contains(e.target)) return;
1897
- this.unpinHighlight();
1898
- };
1899
- document.addEventListener("click", this.onDocumentClick, { capture: true });
1900
- }
1901
- unpinHighlight() {
1902
- if (this.onDocumentClick) {
1903
- document.removeEventListener("click", this.onDocumentClick, { capture: true });
1904
- this.onDocumentClick = null;
1905
- }
1906
- this.pinnedFeedback = null;
1907
- this.clearHighlight();
1908
- }
1909
- clearHighlight() {
1910
- for (const h of this.highlightElements) {
1911
- h.style.opacity = "0";
1912
- setTimeout(() => h.remove(), HIGHLIGHT_FADE);
1913
- }
1914
- this.highlightElements = [];
1915
- }
1916
- removeHighlightElements() {
1917
- for (const h of this.highlightElements) h.remove();
1918
- this.highlightElements = [];
1919
- }
1920
- clear() {
1921
- this.unpinHighlight();
1922
- this.container.replaceChildren();
1923
- this.entries = [];
1924
- this.clusters = [];
1925
- }
1926
- destroy() {
1927
- this.unpinHighlight();
1928
- if (this.repositionTimer) clearTimeout(this.repositionTimer);
1929
- if (this.resizeHandler) window.removeEventListener("resize", this.resizeHandler);
1930
- if (this.onDocumentClickForClusters) document.removeEventListener("click", this.onDocumentClickForClusters);
1931
- this.mutationObserver?.disconnect();
1932
- this.container.remove();
1933
- }
1934
- };
1935
-
1936
- // src/panel.ts
1937
- var TYPE_LABELS = {
1938
- question: "Question",
1939
- changement: "Changement",
1940
- bug: "Bug",
1941
- autre: "Autre"
1942
- };
1943
- var Panel = class {
1944
- constructor(shadowRoot, colors, bus, apiClient, projectName, markers) {
1945
- this.colors = colors;
1946
- this.bus = bus;
1947
- this.apiClient = apiClient;
1948
- this.projectName = projectName;
1949
- this.markers = markers;
1950
- this.root = el("div", { class: "sp-panel" });
1951
- const header = el("div", { class: "sp-panel-header" });
1952
- const title = el("span", { class: "sp-panel-title" });
1953
- setText(title, "Feedbacks");
1954
- const closeBtn = document.createElement("button");
1955
- closeBtn.className = "sp-panel-close";
1956
- closeBtn.setAttribute("aria-label", "Fermer le panneau");
1957
- closeBtn.appendChild(parseSvg(ICON_CLOSE));
1958
- closeBtn.addEventListener("click", () => this.close());
1959
- this.deleteAllBtn = document.createElement("button");
1960
- this.deleteAllBtn.className = "sp-btn-delete-all";
1961
- this.deleteAllBtn.setAttribute("aria-label", "Tout supprimer");
1962
- this.deleteAllBtn.appendChild(parseSvg(ICON_TRASH));
1963
- const deleteAllLabel = document.createElement("span");
1964
- setText(deleteAllLabel, " Tout supprimer");
1965
- this.deleteAllBtn.appendChild(deleteAllLabel);
1966
- this.deleteAllBtn.addEventListener("click", () => this.confirmDeleteAll());
1967
- const headerRight = el("div", { class: "sp-panel-header-right" });
1968
- headerRight.appendChild(this.deleteAllBtn);
1969
- headerRight.appendChild(closeBtn);
1970
- header.appendChild(title);
1971
- header.appendChild(headerRight);
1972
- const filters = el("div", { class: "sp-filters" });
1973
- const searchWrap = el("div", { class: "sp-search-wrap" });
1974
- const searchIcon = parseSvg(ICON_SEARCH);
1975
- searchIcon.setAttribute("class", "sp-search-icon");
1976
- this.searchInput = document.createElement("input");
1977
- this.searchInput.type = "text";
1978
- this.searchInput.className = "sp-search";
1979
- this.searchInput.placeholder = "Rechercher...";
1980
- this.searchInput.setAttribute("aria-label", "Rechercher dans les feedbacks");
1981
- this.searchInput.addEventListener("input", () => {
1982
- if (this.searchTimeout) clearTimeout(this.searchTimeout);
1983
- this.searchTimeout = setTimeout(() => this.loadFeedbacks(), 200);
1984
- });
1985
- searchWrap.appendChild(searchIcon);
1986
- searchWrap.appendChild(this.searchInput);
1987
- const chips = el("div", { class: "sp-chips" });
1988
- const chipOptions = [
1989
- { value: "all", label: "Tous" },
1990
- { value: "question", label: "Question" },
1991
- { value: "changement", label: "Changement" },
1992
- { value: "bug", label: "Bug" },
1993
- { value: "autre", label: "Autre" }
1994
- ];
1995
- for (const option of chipOptions) {
1996
- const chip = document.createElement("button");
1997
- chip.className = `sp-chip ${option.value === "all" ? "sp-chip--active" : ""}`;
1998
- if (option.value !== "all") {
1999
- chip.style.borderColor = getTypeColor(option.value, this.colors);
2000
- }
2001
- setText(chip, option.label);
2002
- chip.dataset.filter = option.value;
2003
- chip.addEventListener("click", () => this.toggleFilter(option.value, chips));
2004
- chips.appendChild(chip);
2005
- }
2006
- filters.appendChild(searchWrap);
2007
- filters.appendChild(chips);
2008
- this.listContainer = el("div", { class: "sp-list" });
2009
- this.root.appendChild(header);
2010
- this.root.appendChild(filters);
2011
- this.root.appendChild(this.listContainer);
2012
- shadowRoot.appendChild(this.root);
2013
- this.bus.on("panel:toggle", (open) => {
2014
- open ? this.open() : this.close();
2015
- });
2016
- shadowRoot.addEventListener("keydown", (e) => {
2017
- if (e.key === "Escape" && this.isOpen) this.close();
2018
- });
2019
- this.onMarkerClick = ((e) => {
2020
- this.scrollToFeedback(e.detail.feedbackId);
2021
- });
2022
- document.addEventListener("sp-marker-click", this.onMarkerClick);
2023
- }
2024
- colors;
2025
- bus;
2026
- apiClient;
2027
- projectName;
2028
- markers;
2029
- root;
2030
- listContainer;
2031
- searchInput;
2032
- deleteAllBtn;
2033
- activeFilters = /* @__PURE__ */ new Set(["all"]);
2034
- feedbacks = [];
2035
- isOpen = false;
2036
- searchTimeout = null;
2037
- onMarkerClick;
2038
- async open() {
2039
- if (this.isOpen) return;
2040
- this.isOpen = true;
2041
- this.root.classList.add("sp-panel--open");
2042
- this.bus.emit("open");
2043
- await this.loadFeedbacks();
2044
- }
2045
- close() {
2046
- if (!this.isOpen) return;
2047
- this.isOpen = false;
2048
- this.root.classList.remove("sp-panel--open");
2049
- this.bus.emit("close");
2050
- }
2051
- showLoading() {
2052
- this.listContainer.replaceChildren();
2053
- const loading = el("div", { class: "sp-loading" });
2054
- const spinner = el("div", { class: "sp-spinner" });
2055
- loading.appendChild(spinner);
2056
- this.listContainer.appendChild(loading);
2057
- }
2058
- showError() {
2059
- this.listContainer.replaceChildren();
2060
- const empty = el("div", { class: "sp-empty" });
2061
- const text = el("div", { class: "sp-empty-text" });
2062
- setText(text, "Erreur de chargement");
2063
- const retryBtn = document.createElement("button");
2064
- retryBtn.className = "sp-btn-ghost";
2065
- retryBtn.style.marginTop = "8px";
2066
- setText(retryBtn, "R\xE9essayer");
2067
- retryBtn.addEventListener("click", () => this.loadFeedbacks());
2068
- empty.appendChild(text);
2069
- empty.appendChild(retryBtn);
2070
- this.listContainer.appendChild(empty);
2071
- }
2072
- async loadFeedbacks() {
2073
- const search = this.searchInput.value.trim() || void 0;
2074
- const typeFilter = this.activeFilters.has("all") ? void 0 : Array.from(this.activeFilters)[0];
2075
- const options = { limit: 50 };
2076
- if (typeFilter) options.type = typeFilter;
2077
- if (search) options.search = search;
2078
- const hasContent = this.feedbacks.length > 0;
2079
- if (!hasContent) this.showLoading();
2080
- try {
2081
- const { feedbacks } = await this.apiClient.getFeedbacks(this.projectName, options);
2082
- this.feedbacks = feedbacks;
2083
- this.renderList();
2084
- this.markers.render(feedbacks);
2085
- } catch (error) {
2086
- if (!hasContent) this.showError();
2087
- this.bus.emit("feedback:error", error instanceof Error ? error : new Error(String(error)));
2088
- }
2089
- }
2090
- renderList() {
2091
- this.listContainer.replaceChildren();
2092
- if (this.feedbacks.length === 0) {
2093
- const empty = el("div", { class: "sp-empty" });
2094
- const emptyText = el("div", { class: "sp-empty-text" });
2095
- setText(emptyText, "Aucun feedback pour le moment");
2096
- empty.appendChild(emptyText);
2097
- this.listContainer.appendChild(empty);
2098
- return;
2099
- }
2100
- this.feedbacks.forEach((feedback, index2) => {
2101
- const card = this.createCard(feedback, index2 + 1);
2102
- card.style.setProperty("--sp-card-i", String(index2));
2103
- this.listContainer.appendChild(card);
2104
- });
2105
- }
2106
- createCard(feedback, number) {
2107
- const isResolved = feedback.status === "resolved";
2108
- const typeColor = getTypeColor(feedback.type, this.colors);
2109
- const card = el("div", {
2110
- class: `sp-card ${isResolved ? "sp-card--resolved" : ""}`
2111
- });
2112
- card.dataset.feedbackId = feedback.id;
2113
- const bar = el("div", { class: "sp-card-bar" });
2114
- bar.style.background = isResolved ? "#9ca3af" : typeColor;
2115
- const body = el("div", { class: "sp-card-body" });
2116
- const header = el("div", { class: "sp-card-header" });
2117
- const num = el("span", { class: "sp-card-number" });
2118
- setText(num, `#${number}`);
2119
- const badge = el("span", { class: "sp-badge" });
2120
- const typeBg = getTypeBgColor(feedback.type, this.colors);
2121
- badge.style.background = typeBg;
2122
- badge.style.color = typeColor;
2123
- setText(badge, TYPE_LABELS[feedback.type] ?? feedback.type);
2124
- const date = el("span", { class: "sp-card-date" });
2125
- setText(date, formatRelativeDate(feedback.createdAt));
2126
- header.appendChild(num);
2127
- header.appendChild(badge);
2128
- header.appendChild(date);
2129
- const message = el("div", { class: "sp-card-message" });
2130
- setText(message, feedback.message);
2131
- const expandBtn = document.createElement("button");
2132
- expandBtn.className = "sp-card-expand";
2133
- setText(expandBtn, "Voir plus");
2134
- expandBtn.style.display = "none";
2135
- expandBtn.setAttribute("aria-expanded", "false");
2136
- expandBtn.addEventListener("click", (e) => {
2137
- e.stopPropagation();
2138
- const isExpanded = message.classList.toggle("sp-card-message--expanded");
2139
- setText(expandBtn, isExpanded ? "Voir moins" : "Voir plus");
2140
- expandBtn.setAttribute("aria-expanded", String(isExpanded));
2141
- });
2142
- requestAnimationFrame(() => {
2143
- if (message.scrollHeight > message.clientHeight) {
2144
- expandBtn.style.display = "block";
2145
- }
2146
- });
2147
- const footer = el("div", { class: "sp-card-footer" });
2148
- const resolveBtn = document.createElement("button");
2149
- resolveBtn.className = "sp-btn-resolve";
2150
- if (isResolved) {
2151
- resolveBtn.appendChild(parseSvg(ICON_UNDO));
2152
- const span = document.createElement("span");
2153
- setText(span, " Rouvrir");
2154
- resolveBtn.appendChild(span);
2155
- } else {
2156
- resolveBtn.appendChild(parseSvg(ICON_CHECK));
2157
- const span = document.createElement("span");
2158
- setText(span, " R\xE9soudre");
2159
- resolveBtn.appendChild(span);
2160
- }
2161
- resolveBtn.addEventListener("click", async (e) => {
2162
- e.stopPropagation();
2163
- await this.toggleResolve(feedback, resolveBtn);
2164
- });
2165
- const deleteBtn = document.createElement("button");
2166
- deleteBtn.className = "sp-btn-delete";
2167
- deleteBtn.appendChild(parseSvg(ICON_TRASH));
2168
- const deleteLabel = document.createElement("span");
2169
- setText(deleteLabel, " Supprimer");
2170
- deleteBtn.appendChild(deleteLabel);
2171
- deleteBtn.addEventListener("click", async (e) => {
2172
- e.stopPropagation();
2173
- await this.deleteFeedback(feedback, deleteBtn);
2174
- });
2175
- footer.appendChild(resolveBtn);
2176
- footer.appendChild(deleteBtn);
2177
- body.appendChild(header);
2178
- body.appendChild(message);
2179
- body.appendChild(expandBtn);
2180
- body.appendChild(footer);
2181
- card.appendChild(bar);
2182
- card.appendChild(body);
2183
- card.addEventListener("mouseenter", () => {
2184
- this.markers.highlight(feedback.id);
2185
- });
2186
- card.addEventListener("click", () => {
2187
- if (feedback.annotations.length > 0) {
2188
- const ann = feedback.annotations[0];
2189
- window.scrollTo({ left: ann.scrollX, top: ann.scrollY, behavior: "smooth" });
2190
- this.markers.pinHighlight(feedback);
2191
- }
2192
- });
2193
- return card;
2194
- }
2195
- async deleteFeedback(feedback, btn) {
2196
- btn.disabled = true;
2197
- try {
2198
- await this.apiClient.deleteFeedback(feedback.id);
2199
- this.bus.emit("feedback:deleted", feedback.id);
2200
- await this.loadFeedbacks();
2201
- } catch (error) {
2202
- btn.disabled = false;
2203
- this.bus.emit("feedback:error", error instanceof Error ? error : new Error(String(error)));
2204
- }
2205
- }
2206
- async confirmDeleteAll() {
2207
- const confirmed = await this.showConfirmDialog(
2208
- "Tout supprimer",
2209
- "Supprimer tous les feedbacks de ce projet ? Cette action est irr\xE9versible."
2210
- );
2211
- if (!confirmed) return;
2212
- this.deleteAllBtn.disabled = true;
2213
- try {
2214
- await this.apiClient.deleteAllFeedbacks(this.projectName);
2215
- this.bus.emit("feedback:all-deleted");
2216
- await this.loadFeedbacks();
2217
- } catch (error) {
2218
- this.bus.emit("feedback:error", error instanceof Error ? error : new Error(String(error)));
2219
- } finally {
2220
- this.deleteAllBtn.disabled = false;
2221
- }
2222
- }
2223
- showConfirmDialog(title, message) {
2224
- return new Promise((resolve) => {
2225
- const backdrop = el("div", { class: "sp-confirm-backdrop" });
2226
- const titleId = `sp-confirm-title-${Date.now()}`;
2227
- const messageId = `sp-confirm-msg-${Date.now()}`;
2228
- const dialog = el("div", { class: "sp-confirm-dialog" });
2229
- dialog.setAttribute("role", "alertdialog");
2230
- dialog.setAttribute("aria-modal", "true");
2231
- dialog.setAttribute("aria-labelledby", titleId);
2232
- dialog.setAttribute("aria-describedby", messageId);
2233
- const titleEl = el("div", { class: "sp-confirm-title" });
2234
- titleEl.id = titleId;
2235
- setText(titleEl, title);
2236
- const messageEl = el("div", { class: "sp-confirm-message" });
2237
- messageEl.id = messageId;
2238
- setText(messageEl, message);
2239
- const btnRow = el("div", { class: "sp-confirm-actions" });
2240
- const cancelBtn = document.createElement("button");
2241
- cancelBtn.type = "button";
2242
- cancelBtn.className = "sp-btn-ghost";
2243
- setText(cancelBtn, "Annuler");
2244
- const confirmBtn = document.createElement("button");
2245
- confirmBtn.type = "button";
2246
- confirmBtn.className = "sp-btn-danger";
2247
- setText(confirmBtn, "Supprimer");
2248
- let closed = false;
2249
- const close = (result) => {
2250
- if (closed) return;
2251
- closed = true;
2252
- backdrop.removeEventListener("keydown", onKeydown);
2253
- backdrop.style.opacity = "0";
2254
- dialog.style.transform = "translateY(8px) scale(0.97)";
2255
- setTimeout(() => {
2256
- backdrop.remove();
2257
- resolve(result);
2258
- }, 200);
2259
- };
2260
- const onKeydown = (e) => {
2261
- const ke = e;
2262
- if (ke.key === "Escape") {
2263
- close(false);
2264
- return;
2265
- }
2266
- if (ke.key === "Tab") {
2267
- ke.preventDefault();
2268
- const active = backdrop.getRootNode().activeElement;
2269
- if (active === cancelBtn) {
2270
- confirmBtn.focus();
2271
- } else {
2272
- cancelBtn.focus();
2273
- }
2274
- }
2275
- };
2276
- backdrop.addEventListener("keydown", onKeydown);
2277
- cancelBtn.addEventListener("click", () => close(false));
2278
- confirmBtn.addEventListener("click", () => close(true));
2279
- backdrop.addEventListener("click", (e) => {
2280
- if (e.target === backdrop) close(false);
2281
- });
2282
- btnRow.appendChild(cancelBtn);
2283
- btnRow.appendChild(confirmBtn);
2284
- dialog.appendChild(titleEl);
2285
- dialog.appendChild(messageEl);
2286
- dialog.appendChild(btnRow);
2287
- backdrop.appendChild(dialog);
2288
- this.root.getRootNode() instanceof ShadowRoot ? this.root.getRootNode().appendChild(backdrop) : this.root.appendChild(backdrop);
2289
- requestAnimationFrame(() => {
2290
- backdrop.style.opacity = "1";
2291
- dialog.style.transform = "translateY(0) scale(1)";
2292
- cancelBtn.focus();
2293
- });
2294
- });
2295
- }
2296
- async toggleResolve(feedback, btn) {
2297
- btn.disabled = true;
2298
- try {
2299
- const newResolved = feedback.status !== "resolved";
2300
- await this.apiClient.resolveFeedback(feedback.id, newResolved);
2301
- await this.loadFeedbacks();
2302
- } catch (error) {
2303
- btn.disabled = false;
2304
- this.bus.emit("feedback:error", error instanceof Error ? error : new Error(String(error)));
2305
- }
2306
- }
2307
- toggleFilter(value, container) {
2308
- this.activeFilters.clear();
2309
- this.activeFilters.add(value);
2310
- const chips = container.querySelectorAll(".sp-chip");
2311
- for (const chip of chips) {
2312
- const isActive = this.activeFilters.has(chip.dataset.filter ?? "");
2313
- chip.classList.toggle("sp-chip--active", isActive);
2314
- }
2315
- this.loadFeedbacks();
2316
- }
2317
- scrollToFeedback(feedbackId) {
2318
- const escapedId = CSS.escape(feedbackId);
2319
- const card = this.listContainer.querySelector(`[data-feedback-id="${escapedId}"]`);
2320
- if (card) {
2321
- card.scrollIntoView({ behavior: "smooth", block: "center" });
2322
- card.classList.add("sp-anim-flash");
2323
- card.addEventListener(
2324
- "animationend",
2325
- () => {
2326
- card.classList.remove("sp-anim-flash");
2327
- },
2328
- { once: true }
2329
- );
2330
- }
2331
- }
2332
- /** Refresh the panel after a new feedback is submitted */
2333
- async refresh() {
2334
- if (this.isOpen) {
2335
- await this.loadFeedbacks();
2336
- }
2337
- }
2338
- destroy() {
2339
- if (this.searchTimeout) clearTimeout(this.searchTimeout);
2340
- document.removeEventListener("sp-marker-click", this.onMarkerClick);
2341
- this.root.remove();
2342
- }
2343
- };
2344
-
2345
- // src/styles/animations.ts
2346
- var SPRING_LINEAR = `linear(0, 0.006, 0.025, 0.06, 0.11, 0.17, 0.25, 0.34, 0.45, 0.56, 0.67, 0.78, 0.88, 0.95, 1.01, 1.04, 1.05, 1.04, 1.02, 1, 0.99, 1)`;
2347
- var EASE_OUT_EXPO = `cubic-bezier(0.16, 1, 0.3, 1)`;
2348
- var SPRING_OVERSHOOT = `cubic-bezier(0.34, 1.56, 0.64, 1)`;
2349
- var EASE_OUT_QUART = `cubic-bezier(0.25, 1, 0.5, 1)`;
2350
- var ANIMATION_CSS = `
171
+ box-shadow:0 0 16px ${s}20;
172
+ transition:opacity ${Ue}ms ease;
173
+ `});this.container.appendChild(o),this.highlightElements.push(o),o.offsetHeight,o.style.opacity="1"}}pinHighlight(e){this.unpinHighlight(),this.showHighlight(e),this.pinnedFeedback=e,this.onDocumentClick=t=>{this.container.contains(t.target)||this.unpinHighlight()},document.addEventListener("click",this.onDocumentClick,{capture:!0})}unpinHighlight(){this.onDocumentClick&&(document.removeEventListener("click",this.onDocumentClick,{capture:!0}),this.onDocumentClick=null),this.pinnedFeedback=null,this.clearHighlight()}clearHighlight(){for(let e of this.highlightElements)e.style.opacity="0",setTimeout(()=>e.remove(),Ue);this.highlightElements=[]}removeHighlightElements(){for(let e of this.highlightElements)e.remove();this.highlightElements=[]}clear(){this.unpinHighlight(),this.container.replaceChildren(),this.entries=[],this.clusters=[]}destroy(){this.unpinHighlight(),this.repositionTimer&&clearTimeout(this.repositionTimer),this.resizeHandler&&window.removeEventListener("resize",this.resizeHandler),this.onDocumentClickForClusters&&document.removeEventListener("click",this.onDocumentClickForClusters),this.mutationObserver?.disconnect(),this.container.remove()}};var Z=class{constructor(e,t,i,s,r,o,a,l){this.colors=t;this.bus=i;this.apiClient=s;this.projectName=r;this.markers=o;this.t=a;this.locale=l;this.root=u("div",{class:"sp-panel"}),this.root.setAttribute("role","complementary"),this.root.setAttribute("aria-label","Siteping feedback panel"),this.root.setAttribute("aria-hidden","true");let p=u("div",{class:"sp-panel-header"}),d=u("span",{class:"sp-panel-title"});m(d,this.t("panel.title")),this.closeBtn=document.createElement("button"),this.closeBtn.className="sp-panel-close",this.closeBtn.setAttribute("aria-label",this.t("panel.close")),this.closeBtn.appendChild(C(K)),this.closeBtn.addEventListener("click",()=>this.close()),this.deleteAllBtn=document.createElement("button"),this.deleteAllBtn.className="sp-btn-delete-all",this.deleteAllBtn.setAttribute("aria-label",this.t("panel.deleteAll")),this.deleteAllBtn.appendChild(C(ae));let c=document.createElement("span");m(c,` ${this.t("panel.deleteAll")}`),this.deleteAllBtn.appendChild(c),this.deleteAllBtn.addEventListener("click",()=>this.confirmDeleteAll());let h=u("div",{class:"sp-panel-header-right"});h.appendChild(this.deleteAllBtn),h.appendChild(this.closeBtn),p.appendChild(d),p.appendChild(h);let b=u("div",{class:"sp-filters"}),g=u("div",{class:"sp-search-wrap"}),k=C(Fe);k.setAttribute("class","sp-search-icon"),this.searchInput=document.createElement("input"),this.searchInput.type="text",this.searchInput.className="sp-search",this.searchInput.placeholder=this.t("panel.search"),this.searchInput.setAttribute("aria-label",this.t("panel.searchAria")),this.searchInput.addEventListener("input",()=>{this.searchTimeout&&clearTimeout(this.searchTimeout),this.searchTimeout=setTimeout(()=>this.loadFeedbacks().catch(()=>{}),200)}),g.appendChild(k),g.appendChild(this.searchInput);let v=u("div",{class:"sp-chips"}),w=[{value:"all",label:this.t("panel.filterAll")},{value:"question",label:this.t("type.question")},{value:"change",label:this.t("type.change")},{value:"bug",label:this.t("type.bug")},{value:"other",label:this.t("type.other")}];for(let x of w){let f=document.createElement("button");f.className=`sp-chip ${x.value==="all"?"sp-chip--active":""}`,x.value!=="all"&&(f.style.borderColor=A(x.value,this.colors)),m(f,x.label),f.dataset.filter=x.value,f.setAttribute("aria-pressed",x.value==="all"?"true":"false"),f.addEventListener("click",()=>this.toggleFilter(x.value,v)),v.appendChild(f)}b.appendChild(g),b.appendChild(v),this.listContainer=u("div",{class:"sp-list"}),this.listContainer.setAttribute("role","list"),this.listContainer.setAttribute("aria-label","Liste des feedbacks"),this.root.appendChild(p),this.root.appendChild(b),this.root.appendChild(this.listContainer),e.appendChild(this.root),this.bus.on("panel:toggle",x=>{x?this.open():this.close()}),e.addEventListener("keydown",x=>{x.key==="Escape"&&this.isOpen&&this.close()}),this.onMarkerClick=(x=>{this.scrollToFeedback(x.detail.feedbackId)}),document.addEventListener("sp-marker-click",this.onMarkerClick)}colors;bus;apiClient;projectName;markers;t;locale;root;listContainer;searchInput;closeBtn;deleteAllBtn;activeFilters=new Set(["all"]);feedbacks=[];isOpen=!1;searchTimeout=null;onMarkerClick;async open(){this.isOpen||(this.isOpen=!0,this.root.classList.add("sp-panel--open"),this.root.setAttribute("aria-hidden","false"),this.bus.emit("open"),await this.loadFeedbacks(),requestAnimationFrame(()=>{this.searchInput?this.searchInput.focus():this.closeBtn.focus()}))}close(){if(!this.isOpen)return;this.isOpen=!1,this.root.classList.remove("sp-panel--open"),this.root.setAttribute("aria-hidden","true"),this.bus.emit("close"),this.root.getRootNode().querySelector(".sp-fab")?.focus()}showLoading(){this.listContainer.replaceChildren();let e=u("div",{class:"sp-loading"});e.setAttribute("role","status"),e.setAttribute("aria-live","polite"),e.setAttribute("aria-label","Chargement des feedbacks");let t=u("div",{class:"sp-spinner"});e.appendChild(t),this.listContainer.appendChild(e)}showError(){this.listContainer.replaceChildren();let e=u("div",{class:"sp-empty"});e.setAttribute("role","status"),e.setAttribute("aria-live","polite");let t=u("div",{class:"sp-empty-text"});m(t,this.t("panel.loadError"));let i=document.createElement("button");i.className="sp-btn-ghost",i.style.marginTop="8px",m(i,this.t("panel.retry")),i.addEventListener("click",()=>this.loadFeedbacks().catch(()=>{})),e.appendChild(t),e.appendChild(i),this.listContainer.appendChild(e)}async loadFeedbacks(){let e=this.searchInput.value.trim()||void 0,t=this.activeFilters.has("all")?void 0:Array.from(this.activeFilters)[0],i={limit:50};t&&(i.type=t),e&&(i.search=e);let s=this.feedbacks.length>0;s||this.showLoading();try{let{feedbacks:r}=await this.apiClient.getFeedbacks(this.projectName,i);this.feedbacks=r,this.renderList(),this.markers.render(r)}catch(r){s||this.showError(),this.bus.emit("feedback:error",r instanceof Error?r:new Error(String(r)))}}renderList(){if(this.listContainer.replaceChildren(),this.feedbacks.length===0){let e=u("div",{class:"sp-empty"});e.setAttribute("role","status"),e.setAttribute("aria-live","polite");let t=u("div",{class:"sp-empty-text"});m(t,this.t("panel.empty")),e.appendChild(t),this.listContainer.appendChild(e);return}this.feedbacks.forEach((e,t)=>{let i=this.createCard(e,t+1);i.style.setProperty("--sp-card-i",String(t)),this.listContainer.appendChild(i)})}createCard(e,t){let i=e.status==="resolved",s=A(e.type,this.colors),r=u("div",{class:`sp-card ${i?"sp-card--resolved":""}`});r.setAttribute("role","listitem"),r.setAttribute("tabindex","0"),r.setAttribute("aria-label",`Feedback #${t}: ${B(e.type,this.t)} \u2014 ${e.message.slice(0,80)}`),r.dataset.feedbackId=e.id;let o=u("div",{class:"sp-card-bar"});o.style.background=i?"#9ca3af":s;let a=u("div",{class:"sp-card-body"}),l=u("div",{class:"sp-card-header"}),p=u("span",{class:"sp-card-number"});m(p,`#${t}`);let d=u("span",{class:"sp-badge"}),c=F(e.type,this.colors);d.style.background=c,d.style.color=s,m(d,B(e.type,this.t));let h=u("span",{class:"sp-card-date"});m(h,q(e.createdAt,this.locale)),l.appendChild(p),l.appendChild(d),l.appendChild(h);let b=u("div",{class:"sp-card-message"});m(b,e.message);let g=document.createElement("button");g.className="sp-card-expand",m(g,this.t("panel.showMore")),g.style.display="none",g.setAttribute("aria-expanded","false"),g.addEventListener("click",y=>{y.stopPropagation();let E=b.classList.toggle("sp-card-message--expanded");m(g,E?this.t("panel.showLess"):this.t("panel.showMore")),g.setAttribute("aria-expanded",String(E))}),requestAnimationFrame(()=>{b.scrollHeight>b.clientHeight&&(g.style.display="block")});let k=u("div",{class:"sp-card-footer"}),v=document.createElement("button");if(v.className="sp-btn-resolve",i){v.appendChild(C(Be));let y=document.createElement("span");m(y,` ${this.t("panel.reopen")}`),v.appendChild(y)}else{v.appendChild(C(Ie));let y=document.createElement("span");m(y,` ${this.t("panel.resolve")}`),v.appendChild(y)}v.addEventListener("click",async y=>{y.stopPropagation(),await this.toggleResolve(e,v)});let w=document.createElement("button");w.className="sp-btn-delete",w.appendChild(C(ae));let x=document.createElement("span");m(x,` ${this.t("panel.delete")}`),w.appendChild(x),w.addEventListener("click",async y=>{y.stopPropagation(),await this.deleteFeedback(e,w)}),k.appendChild(v),k.appendChild(w),a.appendChild(l),a.appendChild(b),a.appendChild(g),a.appendChild(k),r.appendChild(o),r.appendChild(a),r.addEventListener("mouseenter",()=>{this.markers.highlight(e.id)});let f=()=>{if(e.annotations.length>0){let y=e.annotations[0];window.scrollTo({left:y.scrollX,top:y.scrollY,behavior:"smooth"}),this.markers.pinHighlight(e)}};return r.addEventListener("click",f),r.addEventListener("keydown",y=>{(y.key==="Enter"||y.key===" ")&&(y.preventDefault(),f())}),r}async deleteFeedback(e,t){t.disabled=!0;try{await this.apiClient.deleteFeedback(e.id),this.bus.emit("feedback:deleted",e.id),await this.loadFeedbacks()}catch(i){t.disabled=!1,this.bus.emit("feedback:error",i instanceof Error?i:new Error(String(i)))}}async confirmDeleteAll(){if(await this.showConfirmDialog(this.t("panel.deleteAllConfirmTitle"),this.t("panel.deleteAllConfirmMessage"))){this.deleteAllBtn.disabled=!0;try{await this.apiClient.deleteAllFeedbacks(this.projectName),this.bus.emit("feedback:all-deleted"),await this.loadFeedbacks()}catch(t){this.bus.emit("feedback:error",t instanceof Error?t:new Error(String(t)))}finally{this.deleteAllBtn.disabled=!1}}}showConfirmDialog(e,t){return new Promise(i=>{let s=u("div",{class:"sp-confirm-backdrop"}),r=`sp-confirm-title-${Date.now()}`,o=`sp-confirm-msg-${Date.now()}`,a=u("div",{class:"sp-confirm-dialog"});a.setAttribute("role","alertdialog"),a.setAttribute("aria-modal","true"),a.setAttribute("aria-labelledby",r),a.setAttribute("aria-describedby",o);let l=u("div",{class:"sp-confirm-title"});l.id=r,m(l,e);let p=u("div",{class:"sp-confirm-message"});p.id=o,m(p,t);let d=u("div",{class:"sp-confirm-actions"}),c=document.createElement("button");c.type="button",c.className="sp-btn-ghost",m(c,this.t("panel.cancel"));let h=document.createElement("button");h.type="button",h.className="sp-btn-danger",m(h,this.t("panel.confirmDelete"));let b=!1,g=v=>{b||(b=!0,s.removeEventListener("keydown",k),s.style.opacity="0",a.style.transform="translateY(8px) scale(0.97)",setTimeout(()=>{s.remove(),i(v)},200))},k=v=>{let w=v;if(w.key==="Escape"){g(!1);return}w.key==="Tab"&&(w.preventDefault(),s.getRootNode().activeElement===c?h.focus():c.focus())};s.addEventListener("keydown",k),c.addEventListener("click",()=>g(!1)),h.addEventListener("click",()=>g(!0)),s.addEventListener("click",v=>{v.target===s&&g(!1)}),d.appendChild(c),d.appendChild(h),a.appendChild(l),a.appendChild(p),a.appendChild(d),s.appendChild(a),this.root.getRootNode()instanceof ShadowRoot?this.root.getRootNode().appendChild(s):this.root.appendChild(s),requestAnimationFrame(()=>{s.style.opacity="1",a.style.transform="translateY(0) scale(1)",c.focus()})})}async toggleResolve(e,t){t.disabled=!0;try{let i=e.status!=="resolved";await this.apiClient.resolveFeedback(e.id,i),await this.loadFeedbacks()}catch(i){t.disabled=!1,this.bus.emit("feedback:error",i instanceof Error?i:new Error(String(i)))}}toggleFilter(e,t){this.activeFilters.clear(),this.activeFilters.add(e);let i=t.querySelectorAll(".sp-chip");for(let s of i){let r=this.activeFilters.has(s.dataset.filter??"");s.classList.toggle("sp-chip--active",r),s.setAttribute("aria-pressed",String(r))}this.loadFeedbacks().catch(()=>{})}scrollToFeedback(e){let t=CSS.escape(e),i=this.listContainer.querySelector(`[data-feedback-id="${t}"]`);i&&(i.scrollIntoView({behavior:"smooth",block:"center"}),i.classList.add("sp-anim-flash"),i.addEventListener("animationend",()=>{i.classList.remove("sp-anim-flash")},{once:!0}))}async refresh(){this.isOpen&&await this.loadFeedbacks()}destroy(){this.searchTimeout&&clearTimeout(this.searchTimeout),document.removeEventListener("sp-marker-click",this.onMarkerClick),this.root.remove()}};var Mt="linear(0, 0.006, 0.025, 0.06, 0.11, 0.17, 0.25, 0.34, 0.45, 0.56, 0.67, 0.78, 0.88, 0.95, 1.01, 1.04, 1.05, 1.04, 1.02, 1, 0.99, 1)",ue="cubic-bezier(0.16, 1, 0.3, 1)",he="cubic-bezier(0.34, 1.56, 0.64, 1)",Ft="cubic-bezier(0.25, 1, 0.5, 1)",Je=`
2351
174
  /* ---- Keyframes ---- */
2352
175
 
2353
176
  @keyframes sp-fab-in {
@@ -2421,11 +244,11 @@ var ANIMATION_CSS = `
2421
244
  /* ---- Animation classes ---- */
2422
245
 
2423
246
  .sp-anim-fab-in {
2424
- animation: sp-fab-in 0.5s ${SPRING_LINEAR} both;
247
+ animation: sp-fab-in 0.5s ${Mt} both;
2425
248
  }
2426
249
 
2427
250
  .sp-anim-marker-in {
2428
- animation: sp-marker-in 0.35s ${SPRING_OVERSHOOT} both;
251
+ animation: sp-marker-in 0.35s ${he} both;
2429
252
  }
2430
253
 
2431
254
  .sp-anim-pulse {
@@ -2433,11 +256,11 @@ var ANIMATION_CSS = `
2433
256
  }
2434
257
 
2435
258
  .sp-anim-flash {
2436
- animation: sp-flash-bg 0.5s ${EASE_OUT_QUART};
259
+ animation: sp-flash-bg 0.5s ${Ft};
2437
260
  }
2438
261
 
2439
262
  .sp-anim-slide-up {
2440
- animation: sp-slide-up 0.3s ${EASE_OUT_EXPO} both;
263
+ animation: sp-slide-up 0.3s ${ue} both;
2441
264
  }
2442
265
 
2443
266
  .sp-anim-fade-in {
@@ -2448,7 +271,7 @@ var ANIMATION_CSS = `
2448
271
 
2449
272
  .sp-panel {
2450
273
  transform: translateX(110%);
2451
- transition: transform 0.4s ${EASE_OUT_EXPO};
274
+ transition: transform 0.4s ${ue};
2452
275
  }
2453
276
 
2454
277
  .sp-panel.sp-panel--open {
@@ -2460,7 +283,7 @@ var ANIMATION_CSS = `
2460
283
  pointer-events: none;
2461
284
  transform: translate(0, 0) scale(0.8);
2462
285
  transition:
2463
- transform 0.35s ${SPRING_OVERSHOOT},
286
+ transform 0.35s ${he},
2464
287
  opacity 0.2s ease,
2465
288
  background 0.2s ease,
2466
289
  border-color 0.2s ease,
@@ -2491,7 +314,7 @@ var ANIMATION_CSS = `
2491
314
  }
2492
315
 
2493
316
  .sp-card {
2494
- animation: sp-card-in 0.35s ${EASE_OUT_EXPO} both;
317
+ animation: sp-card-in 0.35s ${ue} both;
2495
318
  animation-delay: calc(var(--sp-card-i, 0) * 40ms);
2496
319
  }
2497
320
 
@@ -2519,7 +342,7 @@ var ANIMATION_CSS = `
2519
342
  }
2520
343
 
2521
344
  .sp-fab-badge {
2522
- animation: sp-badge-in 0.4s ${SPRING_OVERSHOOT} both;
345
+ animation: sp-badge-in 0.4s ${he} both;
2523
346
  }
2524
347
 
2525
348
  /* ---- Reduced motion ---- */
@@ -2532,11 +355,7 @@ var ANIMATION_CSS = `
2532
355
  }
2533
356
  }
2534
357
 
2535
- `;
2536
-
2537
- // src/styles/base.ts
2538
- function buildStyles(colors) {
2539
- return `
358
+ `;function Ze(n){return`
2540
359
  :host {
2541
360
  all: initial;
2542
361
  position: fixed;
@@ -2547,7 +366,7 @@ function buildStyles(colors) {
2547
366
  color: var(--sp-text);
2548
367
  -webkit-font-smoothing: antialiased;
2549
368
  -moz-osx-font-smoothing: grayscale;
2550
- ${cssVariables(colors)}
369
+ ${ze(n)}
2551
370
  }
2552
371
 
2553
372
  *, *::before, *::after {
@@ -3330,18 +1149,8 @@ function buildStyles(colors) {
3330
1149
  font-weight: 500;
3331
1150
  }
3332
1151
 
3333
- ${ANIMATION_CSS}
3334
- `;
3335
- }
3336
-
3337
- // src/tooltip.ts
3338
- var SHOW_DELAY = 120;
3339
- var HIDE_DELAY = 80;
3340
- var Tooltip = class {
3341
- constructor(colors) {
3342
- this.colors = colors;
3343
- this.root = el("div", {
3344
- style: `
1152
+ ${Je}
1153
+ `}var It=120,Rt=80,ee=class{constructor(e,t="fr"){this.colors=e;this.locale=t;this.root=u("div",{style:`
3345
1154
  position: fixed;
3346
1155
  z-index: 2147483647;
3347
1156
  max-width: 280px;
@@ -3359,10 +1168,7 @@ var Tooltip = class {
3359
1168
  transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
3360
1169
  visibility: hidden;
3361
1170
  -webkit-font-smoothing: antialiased;
3362
- `
3363
- });
3364
- this.arrow = el("div", {
3365
- style: `
1171
+ `}),this.root.setAttribute("role","tooltip"),this.root.id=this.tooltipId,this.arrow=u("div",{style:`
3366
1172
  position: absolute;
3367
1173
  width: 12px;
3368
1174
  height: 12px;
@@ -3370,104 +1176,12 @@ var Tooltip = class {
3370
1176
  border: 1px solid rgba(255, 255, 255, 0.35);
3371
1177
  transform: rotate(45deg);
3372
1178
  pointer-events: none;
3373
- `
3374
- });
3375
- this.root.appendChild(this.arrow);
3376
- this.root.addEventListener("mouseenter", () => this.cancelHide());
3377
- this.root.addEventListener("mouseleave", () => this.scheduleHide());
3378
- document.body.appendChild(this.root);
3379
- }
3380
- colors;
3381
- root;
3382
- arrow;
3383
- showTimer = null;
3384
- hideTimer = null;
3385
- currentFeedbackId = null;
3386
- show(feedback, anchorRect) {
3387
- if (this.currentFeedbackId === feedback.id) return;
3388
- this.cancelHide();
3389
- this.cancelShow();
3390
- this.showTimer = setTimeout(() => {
3391
- this.currentFeedbackId = feedback.id;
3392
- this.render(feedback);
3393
- this.position(anchorRect);
3394
- this.root.style.visibility = "visible";
3395
- this.root.style.opacity = "1";
3396
- this.root.style.transform = "translateY(0) scale(1)";
3397
- }, SHOW_DELAY);
3398
- }
3399
- scheduleHide() {
3400
- this.cancelHide();
3401
- this.hideTimer = setTimeout(() => this.hide(), HIDE_DELAY);
3402
- }
3403
- hide() {
3404
- this.cancelShow();
3405
- this.currentFeedbackId = null;
3406
- this.root.style.opacity = "0";
3407
- this.root.style.transform = "translateY(6px) scale(0.97)";
3408
- setTimeout(() => {
3409
- if (!this.currentFeedbackId) {
3410
- this.root.style.visibility = "hidden";
3411
- }
3412
- }, 200);
3413
- }
3414
- cancelShow() {
3415
- if (this.showTimer) {
3416
- clearTimeout(this.showTimer);
3417
- this.showTimer = null;
3418
- }
3419
- }
3420
- cancelHide() {
3421
- if (this.hideTimer) {
3422
- clearTimeout(this.hideTimer);
3423
- this.hideTimer = null;
3424
- }
3425
- }
3426
- render(feedback) {
3427
- const children = Array.from(this.root.children);
3428
- for (const child of children) {
3429
- if (child !== this.arrow) child.remove();
3430
- }
3431
- const typeColor = getTypeColor(feedback.type, this.colors);
3432
- const typeBg = getTypeBgColor(feedback.type, this.colors);
3433
- const typeLabel = feedback.type.charAt(0).toUpperCase() + feedback.type.slice(1);
3434
- const header = el("div", { style: "display:flex;align-items:center;gap:8px;margin-bottom:8px;" });
3435
- const badge = el("span", {
3436
- style: `
1179
+ `}),this.root.appendChild(this.arrow),this.root.addEventListener("mouseenter",()=>this.cancelHide()),this.root.addEventListener("mouseleave",()=>this.scheduleHide()),document.body.appendChild(this.root)}colors;locale;root;arrow;showTimer=null;hideTimer=null;currentFeedbackId=null;tooltipId="sp-tooltip";show(e,t){this.currentFeedbackId!==e.id&&(this.cancelHide(),this.cancelShow(),this.showTimer=setTimeout(()=>{this.currentFeedbackId=e.id,this.render(e),this.position(t),this.root.style.visibility="visible",this.root.style.opacity="1",this.root.style.transform="translateY(0) scale(1)"},It))}scheduleHide(){this.cancelHide(),this.hideTimer=setTimeout(()=>this.hide(),Rt)}hide(){this.cancelShow(),this.currentFeedbackId=null,this.root.style.opacity="0",this.root.style.transform="translateY(6px) scale(0.97)",setTimeout(()=>{this.currentFeedbackId||(this.root.style.visibility="hidden")},200)}cancelShow(){this.showTimer&&(clearTimeout(this.showTimer),this.showTimer=null)}cancelHide(){this.hideTimer&&(clearTimeout(this.hideTimer),this.hideTimer=null)}render(e){let t=Array.from(this.root.children);for(let c of t)c!==this.arrow&&c.remove();let i=A(e.type,this.colors),s=F(e.type,this.colors),r=U(this.locale),o=B(e.type,r),a=u("div",{style:"display:flex;align-items:center;gap:8px;margin-bottom:8px;"}),l=u("span",{style:`
3437
1180
  padding:3px 10px;border-radius:9999px;
3438
1181
  font-size:11px;font-weight:600;
3439
- color:${typeColor};background:${typeBg};
1182
+ color:${i};background:${s};
3440
1183
  letter-spacing:0.02em;
3441
- `
3442
- });
3443
- setText(badge, typeLabel);
3444
- const date = el("span", { style: "font-size:11px;color:#64748b;margin-left:auto;" });
3445
- setText(date, formatRelativeDate(feedback.createdAt));
3446
- header.appendChild(badge);
3447
- header.appendChild(date);
3448
- const body = el("div", {
3449
- style: "font-size:13px;line-height:1.55;color:#0f172a;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"
3450
- });
3451
- setText(body, feedback.message);
3452
- this.root.insertBefore(header, this.arrow);
3453
- this.root.insertBefore(body, this.arrow);
3454
- }
3455
- position(anchorRect) {
3456
- const tooltipRect = this.root.getBoundingClientRect();
3457
- const gap = 10;
3458
- let top = anchorRect.top - tooltipRect.height - gap;
3459
- let left = anchorRect.left + anchorRect.width / 2 - tooltipRect.width / 2;
3460
- let isAbove = true;
3461
- if (top < 8) {
3462
- top = anchorRect.bottom + gap;
3463
- isAbove = false;
3464
- }
3465
- left = Math.max(8, Math.min(left, window.innerWidth - tooltipRect.width - 8));
3466
- this.root.style.top = `${top}px`;
3467
- this.root.style.left = `${left}px`;
3468
- const arrowLeft = Math.max(16, Math.min(anchorRect.left + anchorRect.width / 2 - left - 6, tooltipRect.width - 22));
3469
- if (isAbove) {
3470
- this.arrow.style.cssText = `
1184
+ `});m(l,o);let p=u("span",{style:"font-size:11px;color:#64748b;margin-left:auto;"});m(p,q(e.createdAt,this.locale)),a.appendChild(l),a.appendChild(p);let d=u("div",{style:"font-size:13px;line-height:1.55;color:#0f172a;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"});m(d,e.message),this.root.insertBefore(a,this.arrow),this.root.insertBefore(d,this.arrow)}position(e){let t=this.root.getBoundingClientRect(),i=10,s=e.top-t.height-i,r=e.left+e.width/2-t.width/2,o=!0;s<8&&(s=e.bottom+i,o=!1),r=Math.max(8,Math.min(r,window.innerWidth-t.width-8)),this.root.style.top=`${s}px`,this.root.style.left=`${r}px`;let a=Math.max(16,Math.min(e.left+e.width/2-r-6,t.width-22));o?this.arrow.style.cssText=`
3471
1185
  position:absolute;
3472
1186
  width:12px;height:12px;
3473
1187
  background:rgba(255, 255, 255, 0.88);
@@ -3476,10 +1190,8 @@ var Tooltip = class {
3476
1190
  transform:rotate(45deg);
3477
1191
  pointer-events:none;
3478
1192
  bottom:-6px;
3479
- left:${arrowLeft}px;
3480
- `;
3481
- } else {
3482
- this.arrow.style.cssText = `
1193
+ left:${a}px;
1194
+ `:this.arrow.style.cssText=`
3483
1195
  position:absolute;
3484
1196
  width:12px;height:12px;
3485
1197
  background:rgba(255, 255, 255, 0.88);
@@ -3488,112 +1200,8 @@ var Tooltip = class {
3488
1200
  transform:rotate(45deg);
3489
1201
  pointer-events:none;
3490
1202
  top:-6px;
3491
- left:${arrowLeft}px;
3492
- `;
3493
- }
3494
- }
3495
- /** Check if a DOM node belongs to this tooltip (for MutationObserver filtering). */
3496
- contains(node) {
3497
- return this.root.contains(node);
3498
- }
3499
- destroy() {
3500
- this.cancelShow();
3501
- this.cancelHide();
3502
- this.root.remove();
3503
- }
3504
- };
3505
-
3506
- // src/launcher.ts
3507
- function launch(config2) {
3508
- if (!config2.forceShow) {
3509
- try {
3510
- const meta = import.meta;
3511
- const proc = globalThis.process;
3512
- const mode = meta.env?.MODE ?? proc?.env?.NODE_ENV;
3513
- if (mode === "production") {
3514
- return { destroy: () => {
3515
- } };
3516
- }
3517
- } catch {
3518
- }
3519
- }
3520
- if (window.innerWidth < 768) {
3521
- return { destroy: () => {
3522
- } };
3523
- }
3524
- const colors = buildThemeColors(config2.accentColor);
3525
- const bus = new EventBus();
3526
- const apiClient = new ApiClient(config2.endpoint);
3527
- if (config2.onOpen) bus.on("open", config2.onOpen);
3528
- if (config2.onClose) bus.on("close", config2.onClose);
3529
- if (config2.onFeedbackSent) bus.on("feedback:sent", config2.onFeedbackSent);
3530
- if (config2.onError) bus.on("feedback:error", config2.onError);
3531
- if (config2.onAnnotationStart) bus.on("annotation:start", config2.onAnnotationStart);
3532
- if (config2.onAnnotationEnd) bus.on("annotation:end", config2.onAnnotationEnd);
3533
- const host = document.createElement("siteping-widget");
3534
- host.style.cssText = "position:fixed;z-index:2147483647;";
3535
- const shadowMode = config2.__testMode ? "open" : "closed";
3536
- const shadow = host.attachShadow({ mode: shadowMode });
3537
- const sheet = new CSSStyleSheet();
3538
- sheet.replaceSync(buildStyles(colors));
3539
- shadow.adoptedStyleSheets = [sheet];
3540
- document.body.appendChild(host);
3541
- const tooltip = new Tooltip(colors);
3542
- const markers = new MarkerManager(colors, tooltip, bus);
3543
- const fab = new Fab(shadow, config2, bus);
3544
- const panel = new Panel(shadow, colors, bus, apiClient, config2.projectName, markers);
3545
- const annotator = new Annotator(config2, colors, bus);
3546
- const unsubAnnotation = bus.on("annotation:complete", async (data) => {
3547
- const { annotation, type, message } = data;
3548
- let identity = getIdentity();
3549
- if (!identity) {
3550
- identity = await promptIdentity(shadow);
3551
- if (!identity) return;
3552
- saveIdentity(identity);
3553
- }
3554
- const payload = {
3555
- projectName: config2.projectName,
3556
- type,
3557
- message,
3558
- url: window.location.href,
3559
- viewport: `${window.innerWidth}x${window.innerHeight}`,
3560
- userAgent: navigator.userAgent,
3561
- authorName: identity.name,
3562
- authorEmail: identity.email,
3563
- annotations: [annotation],
3564
- clientId: crypto.randomUUID()
3565
- };
3566
- try {
3567
- const response = await apiClient.sendFeedback(payload);
3568
- bus.emit("feedback:sent", response);
3569
- markers.addFeedback(response, markers.count + 1);
3570
- await panel.refresh();
3571
- } catch (error) {
3572
- bus.emit("feedback:error", error instanceof Error ? error : new Error(String(error)));
3573
- }
3574
- });
3575
- apiClient.getFeedbacks(config2.projectName, { limit: 50 }).then(({ feedbacks }) => {
3576
- markers.render(feedbacks);
3577
- }).catch(() => {
3578
- });
3579
- flushRetryQueue(config2.endpoint);
3580
- return {
3581
- destroy: () => {
3582
- unsubAnnotation();
3583
- fab.destroy();
3584
- panel.destroy();
3585
- annotator.destroy();
3586
- markers.destroy();
3587
- tooltip.destroy();
3588
- bus.removeAll();
3589
- host.remove();
3590
- }
3591
- };
3592
- }
3593
- function promptIdentity(shadowRoot) {
3594
- return new Promise((resolve) => {
3595
- const backdrop = document.createElement("div");
3596
- backdrop.style.cssText = `
1203
+ left:${a}px;
1204
+ `}contains(e){return this.root.contains(e)}destroy(){this.cancelShow(),this.cancelHide(),this.root.remove()}};function et(){let n=()=>{};return{destroy:n,open:n,close:n,refresh:n,on:()=>n,off:n}}function tt(n){let e=n.debug?(...f)=>console.debug("[siteping]",...f):()=>{};if(!n.forceShow)try{let f=import.meta,y=globalThis.process;if((f.env?.MODE??y?.env?.NODE_ENV)==="production"){let L="production";return console.info("[siteping] Widget not loaded: production mode detected. Use forceShow: true to override."),n.onSkip?.(L),et()}}catch{}if(window.innerWidth<768){let f="mobile";return console.info("[siteping] Widget not loaded: viewport width < 768px (mobile not supported)."),n.onSkip?.(f),et()}let t=n.locale??"fr",i=U(t);e("Initializing widget",{projectName:n.projectName,theme:n.theme??"light",locale:t});let s=Pe(n.accentColor,n.theme),r=new D,o=new D,a=new X(n.endpoint);n.onOpen&&r.on("open",n.onOpen),n.onClose&&r.on("close",n.onClose),n.onFeedbackSent&&r.on("feedback:sent",n.onFeedbackSent),n.onError&&r.on("feedback:error",n.onError),n.onAnnotationStart&&r.on("annotation:start",n.onAnnotationStart),n.onAnnotationEnd&&r.on("annotation:end",n.onAnnotationEnd),r.on("feedback:sent",f=>o.emit("feedback:sent",f)),r.on("feedback:deleted",f=>o.emit("feedback:deleted",f)),r.on("open",()=>o.emit("panel:open")),r.on("close",()=>o.emit("panel:close")),r.on("open",()=>e("Panel opened")),r.on("close",()=>e("Panel closed")),r.on("feedback:sent",f=>e("Feedback sent",f.id)),r.on("feedback:error",f=>e("Feedback failed",f.message)),r.on("annotation:start",()=>e("Annotation started")),r.on("annotation:end",()=>e("Annotation ended"));let l=document.createElement("siteping-widget");l.style.cssText="position:fixed;z-index:2147483647;";let d=(()=>{try{if(import.meta.env?.MODE==="test")return!0}catch{}try{if(globalThis.process?.env?.NODE_ENV==="test")return!0}catch{}return!1})()?"open":"closed",c=l.attachShadow({mode:d}),h=new CSSStyleSheet;h.replaceSync(Ze(s)),c.adoptedStyleSheets=[h],document.body.appendChild(l);let b=new ee(s,t),g=new J(s,b,r,i),k=new V(c,n,r,i),v=new Z(c,s,r,a,n.projectName,g,i,t),w=new G(s,r,i),x=r.on("annotation:complete",async f=>{let{annotation:y,type:E,message:L}=f,M=We();if(!M){if(M=await Nt(c,i),!M)return;Ge(M)}let H={projectName:n.projectName,type:E,message:L,url:window.location.href,viewport:`${window.innerWidth}x${window.innerHeight}`,userAgent:navigator.userAgent,authorName:M.name,authorEmail:M.email,annotations:[y],clientId:crypto.randomUUID()};try{let S=await a.sendFeedback(H);r.emit("feedback:sent",S),g.addFeedback(S,g.count+1),await v.refresh()}catch(S){r.emit("feedback:error",S instanceof Error?S:new Error(String(S)))}});return a.getFeedbacks(n.projectName,{limit:50}).then(({feedbacks:f})=>{g.render(f)}).catch(()=>{}),je(n.endpoint).then(()=>e("Retry queue flushed")).catch(()=>{}),{destroy:()=>{e("Destroying widget"),x(),k.destroy(),v.destroy(),w.destroy(),g.destroy(),b.destroy(),r.removeAll(),o.removeAll(),l.remove()},open:()=>{v.open()},close:()=>{v.close()},refresh:()=>{v.refresh()},on:(f,y)=>o.on(f,y),off:(f,y)=>{o.off(f,y)}}}function Nt(n,e){return new Promise(t=>{let i=n.activeElement??document.activeElement,s=document.createElement("div");s.style.cssText=`
3597
1205
  position:fixed;inset:0;
3598
1206
  background:rgba(15, 23, 42, 0.2);
3599
1207
  backdrop-filter:blur(8px);
@@ -3601,9 +1209,7 @@ function promptIdentity(shadowRoot) {
3601
1209
  display:flex;align-items:center;justify-content:center;
3602
1210
  z-index:2147483647;
3603
1211
  opacity:0;transition:opacity 0.25s ease;
3604
- `;
3605
- const modal = document.createElement("div");
3606
- modal.style.cssText = `
1212
+ `;let r=document.createElement("div");r.style.cssText=`
3607
1213
  width:340px;padding:28px;border-radius:20px;
3608
1214
  background:rgba(255, 255, 255, 0.85);
3609
1215
  backdrop-filter:blur(32px);
@@ -3614,76 +1220,4 @@ function promptIdentity(shadowRoot) {
3614
1220
  transform:translateY(12px) scale(0.97);
3615
1221
  transition:transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
3616
1222
  -webkit-font-smoothing:antialiased;
3617
- `;
3618
- const title = document.createElement("div");
3619
- title.className = "sp-identity-title";
3620
- title.textContent = "Identifiez-vous";
3621
- title.style.marginBottom = "20px";
3622
- const nameLabel = document.createElement("label");
3623
- nameLabel.className = "sp-input-label";
3624
- nameLabel.textContent = "Nom";
3625
- const nameInput = document.createElement("input");
3626
- nameInput.className = "sp-input";
3627
- nameInput.type = "text";
3628
- nameInput.placeholder = "Votre nom";
3629
- nameInput.style.marginBottom = "14px";
3630
- const emailLabel = document.createElement("label");
3631
- emailLabel.className = "sp-input-label";
3632
- emailLabel.textContent = "Email";
3633
- const emailInput = document.createElement("input");
3634
- emailInput.className = "sp-input";
3635
- emailInput.type = "email";
3636
- emailInput.placeholder = "votre@email.com";
3637
- const btnRow = document.createElement("div");
3638
- btnRow.style.cssText = "display:flex;gap:8px;justify-content:flex-end;margin-top:20px;";
3639
- const cancelBtn = document.createElement("button");
3640
- cancelBtn.className = "sp-btn-ghost";
3641
- cancelBtn.textContent = "Annuler";
3642
- cancelBtn.addEventListener("click", () => {
3643
- backdrop.style.opacity = "0";
3644
- modal.style.transform = "translateY(12px) scale(0.97)";
3645
- setTimeout(() => {
3646
- backdrop.remove();
3647
- resolve(null);
3648
- }, 250);
3649
- });
3650
- const submitBtn = document.createElement("button");
3651
- submitBtn.className = "sp-btn-primary";
3652
- submitBtn.textContent = "Continuer";
3653
- submitBtn.addEventListener("click", () => {
3654
- const name = nameInput.value.trim();
3655
- const email = emailInput.value.trim();
3656
- if (!name || !email) return;
3657
- backdrop.style.opacity = "0";
3658
- modal.style.transform = "translateY(12px) scale(0.97)";
3659
- setTimeout(() => {
3660
- backdrop.remove();
3661
- resolve({ name, email });
3662
- }, 250);
3663
- });
3664
- btnRow.appendChild(cancelBtn);
3665
- btnRow.appendChild(submitBtn);
3666
- modal.appendChild(title);
3667
- modal.appendChild(nameLabel);
3668
- modal.appendChild(nameInput);
3669
- modal.appendChild(emailLabel);
3670
- modal.appendChild(emailInput);
3671
- modal.appendChild(btnRow);
3672
- backdrop.appendChild(modal);
3673
- shadowRoot.appendChild(backdrop);
3674
- requestAnimationFrame(() => {
3675
- backdrop.style.opacity = "1";
3676
- modal.style.transform = "translateY(0) scale(1)";
3677
- nameInput.focus();
3678
- });
3679
- });
3680
- }
3681
-
3682
- // src/index.ts
3683
- function initSiteping(config2) {
3684
- return launch(config2);
3685
- }
3686
- export {
3687
- initSiteping
3688
- };
3689
- //# sourceMappingURL=index.js.map
1223
+ `;let o=`sp-identity-title-${Date.now()}`;r.setAttribute("role","dialog"),r.setAttribute("aria-modal","true"),r.setAttribute("aria-labelledby",o);let a=document.createElement("div");a.className="sp-identity-title",a.id=o,a.textContent=e("identity.title"),a.style.marginBottom="20px";let l=`sp-identity-name-${Date.now()}`,p=`sp-identity-email-${Date.now()}`,d=document.createElement("label");d.className="sp-input-label",d.textContent=e("identity.nameLabel"),d.setAttribute("for",l);let c=document.createElement("input");c.className="sp-input",c.id=l,c.type="text",c.placeholder=e("identity.namePlaceholder"),c.style.marginBottom="14px";let h=document.createElement("label");h.className="sp-input-label",h.textContent=e("identity.emailLabel"),h.setAttribute("for",p);let b=document.createElement("input");b.className="sp-input",b.id=p,b.type="email",b.placeholder=e("identity.emailPlaceholder");let g=document.createElement("div");g.style.cssText="display:flex;gap:8px;justify-content:flex-end;margin-top:20px;";let k=y=>{s.removeEventListener("keydown",f),s.style.opacity="0",r.style.transform="translateY(12px) scale(0.97)",setTimeout(()=>{s.remove(),i?.focus(),t(y)},250)},v=document.createElement("button");v.className="sp-btn-ghost",v.textContent=e("identity.cancel"),v.addEventListener("click",()=>k(null));let w=document.createElement("button");w.className="sp-btn-primary",w.textContent=e("identity.submit"),w.addEventListener("click",()=>{let y=c.value.trim(),E=b.value.trim();if(!y||!E)return;if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(E)){b.style.borderColor="#ef4444";return}k({name:y,email:E})});let x='input, button, [tabindex]:not([tabindex="-1"])',f=y=>{let E=y;if(E.key==="Escape"){k(null);return}if(E.key==="Tab"){let L=Array.from(r.querySelectorAll(x));if(L.length===0)return;let M=L[0],H=L[L.length-1],S=n.activeElement;E.shiftKey?(S===M||!r.contains(S))&&(E.preventDefault(),H.focus()):(S===H||!r.contains(S))&&(E.preventDefault(),M.focus())}};s.addEventListener("keydown",f),s.addEventListener("click",y=>{y.target===s&&k(null)}),g.appendChild(v),g.appendChild(w),r.appendChild(a),r.appendChild(d),r.appendChild(c),r.appendChild(h),r.appendChild(b),r.appendChild(g),s.appendChild(r),n.appendChild(s),requestAnimationFrame(()=>{s.style.opacity="1",r.style.transform="translateY(0) scale(1)",c.focus()})})}function Gn(n){return tt(n)}export{Gn as initSiteping};