@mikuexe/annotator-react 0.1.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.cjs ADDED
@@ -0,0 +1,749 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ SourceAnnotator: () => SourceAnnotator,
25
+ captureElementAnnotation: () => captureElementAnnotation,
26
+ copyTextToClipboard: () => copyTextToClipboard,
27
+ createAnnotationCollection: () => createAnnotationCollection,
28
+ formatAnnotationCollection: () => formatAnnotationCollection,
29
+ formatMarkdown: () => formatMarkdown,
30
+ getElementSelector: () => getElementSelector,
31
+ trimText: () => trimText
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/SourceAnnotator.tsx
36
+ var import_react = require("react");
37
+ var import_sonner = require("sonner");
38
+
39
+ // src/capture.ts
40
+ var import_element_source = require("element-source");
41
+ var MAX_TEXT_LENGTH = 240;
42
+ var MAX_HTML_LENGTH = 640;
43
+ var MAX_SELECTOR_DEPTH = 6;
44
+ async function captureElementAnnotation(element, note, id = createAnnotationId()) {
45
+ const elementInfo = await safeResolveElementInfo(element);
46
+ const source = normalizeSource(elementInfo?.source, elementInfo?.componentName);
47
+ const sourceStack = normalizeSourceStack(elementInfo?.stack, source);
48
+ const componentPath = getComponentPath(sourceStack, source);
49
+ return {
50
+ id,
51
+ note,
52
+ source,
53
+ sourceStack,
54
+ componentPath,
55
+ element: {
56
+ tagName: element.tagName.toLowerCase(),
57
+ text: trimText(element.textContent ?? "", MAX_TEXT_LENGTH),
58
+ html: trimText(getOuterHtml(element), MAX_HTML_LENGTH),
59
+ selector: getElementSelector(element)
60
+ }
61
+ };
62
+ }
63
+ function createAnnotationId() {
64
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
65
+ return crypto.randomUUID();
66
+ }
67
+ return `annotation-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
68
+ }
69
+ function getElementSelector(element) {
70
+ const parts = [];
71
+ let current = element;
72
+ while (current && current.nodeType === Node.ELEMENT_NODE && parts.length < MAX_SELECTOR_DEPTH) {
73
+ parts.unshift(getSelectorPart(current));
74
+ if (current.id) {
75
+ break;
76
+ }
77
+ current = current.parentElement;
78
+ if (current?.tagName.toLowerCase() === "html") {
79
+ break;
80
+ }
81
+ }
82
+ return parts.join(" ");
83
+ }
84
+ function trimText(value, maxLength) {
85
+ const normalized = value.replace(/\s+/g, " ").trim();
86
+ if (normalized.length <= maxLength) {
87
+ return normalized;
88
+ }
89
+ return `${normalized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}\u2026`;
90
+ }
91
+ async function safeResolveElementInfo(element) {
92
+ try {
93
+ return await (0, import_element_source.resolveElementInfo)(element);
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ function normalizeSource(source, componentName) {
99
+ if (!source) {
100
+ return componentName ? { filePath: "", lineNumber: null, columnNumber: null, componentName } : null;
101
+ }
102
+ return {
103
+ filePath: source.filePath,
104
+ lineNumber: source.lineNumber ?? null,
105
+ columnNumber: source.columnNumber ?? null,
106
+ componentName: source.componentName ?? componentName ?? null
107
+ };
108
+ }
109
+ function normalizeSourceStack(stack, source) {
110
+ const normalizedStack = (stack ?? []).map((frame) => ({
111
+ filePath: frame.filePath,
112
+ lineNumber: frame.lineNumber ?? null,
113
+ columnNumber: frame.columnNumber ?? null,
114
+ componentName: frame.componentName ?? null
115
+ }));
116
+ if (source && !normalizedStack.some((frame) => isSameSourceFrame(frame, source))) {
117
+ normalizedStack.unshift(source);
118
+ }
119
+ return normalizedStack;
120
+ }
121
+ function getComponentPath(stack, source) {
122
+ const names = stack.map((frame) => frame.componentName).filter((name) => Boolean(name));
123
+ return Array.from(new Set(names.length ? names : source?.componentName ? [source.componentName] : []));
124
+ }
125
+ function isSameSourceFrame(a, b) {
126
+ return a.filePath === b.filePath && a.lineNumber === b.lineNumber && a.columnNumber === b.columnNumber;
127
+ }
128
+ function getOuterHtml(element) {
129
+ if ("outerHTML" in element && typeof element.outerHTML === "string") {
130
+ return element.outerHTML;
131
+ }
132
+ return `<${element.tagName.toLowerCase()}>`;
133
+ }
134
+ function getSelectorPart(element) {
135
+ const tagName = element.tagName.toLowerCase();
136
+ if (element.id) {
137
+ return `#${escapeIdentifier(element.id)}`;
138
+ }
139
+ const testId = element.getAttribute("data-testid");
140
+ if (testId) {
141
+ return `${tagName}[data-testid="${escapeAttribute(testId)}"]`;
142
+ }
143
+ const classes = Array.from(element.classList).filter(Boolean).slice(0, 2).map((className) => `.${escapeIdentifier(className)}`).join("");
144
+ const siblingIndex = getNthOfType(element);
145
+ const needsNth = siblingIndex > 1 || hasFollowingSiblingOfSameType(element);
146
+ return `${tagName}${classes}${needsNth ? `:nth-of-type(${siblingIndex})` : ""}`;
147
+ }
148
+ function getNthOfType(element) {
149
+ let index = 1;
150
+ let sibling = element.previousElementSibling;
151
+ while (sibling) {
152
+ if (sibling.tagName === element.tagName) {
153
+ index += 1;
154
+ }
155
+ sibling = sibling.previousElementSibling;
156
+ }
157
+ return index;
158
+ }
159
+ function hasFollowingSiblingOfSameType(element) {
160
+ let sibling = element.nextElementSibling;
161
+ while (sibling) {
162
+ if (sibling.tagName === element.tagName) {
163
+ return true;
164
+ }
165
+ sibling = sibling.nextElementSibling;
166
+ }
167
+ return false;
168
+ }
169
+ function escapeIdentifier(value) {
170
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
171
+ return CSS.escape(value);
172
+ }
173
+ return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
174
+ }
175
+ function escapeAttribute(value) {
176
+ return value.replace(/"/g, '\\"');
177
+ }
178
+
179
+ // src/clipboard.ts
180
+ async function copyTextToClipboard(text) {
181
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
182
+ await navigator.clipboard.writeText(text);
183
+ return;
184
+ }
185
+ if (typeof document === "undefined") {
186
+ throw new Error("Clipboard is not available outside a browser environment.");
187
+ }
188
+ const textarea = document.createElement("textarea");
189
+ textarea.value = text;
190
+ textarea.setAttribute("readonly", "");
191
+ textarea.style.position = "fixed";
192
+ textarea.style.left = "-9999px";
193
+ textarea.style.top = "0";
194
+ document.body.appendChild(textarea);
195
+ textarea.select();
196
+ const copied = document.execCommand("copy");
197
+ textarea.remove();
198
+ if (!copied) {
199
+ throw new Error("Clipboard copy failed.");
200
+ }
201
+ }
202
+
203
+ // src/format.ts
204
+ var TASK_FRAMING = "Please update the UI based on these source-linked annotations.";
205
+ function createAnnotationCollection(annotations) {
206
+ return {
207
+ annotations,
208
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
209
+ };
210
+ }
211
+ function formatAnnotationCollection(collection, output = "markdown") {
212
+ if (output === "json") {
213
+ return formatJson(collection);
214
+ }
215
+ const markdown = formatMarkdown(collection);
216
+ if (output === "markdown") {
217
+ return markdown;
218
+ }
219
+ return `${markdown}
220
+
221
+ ## JSON Payload
222
+
223
+ \`\`\`json
224
+ ${formatJson(collection)}
225
+ \`\`\``;
226
+ }
227
+ function formatMarkdown(collection) {
228
+ const lines = [TASK_FRAMING, "", `Collected at: ${collection.createdAt}`, ""];
229
+ if (collection.annotations.length === 0) {
230
+ lines.push("No annotations were collected.");
231
+ return lines.join("\n");
232
+ }
233
+ collection.annotations.forEach((annotation, index) => {
234
+ const source = formatSource(annotation);
235
+ const sourceStack = formatSourceStack(annotation);
236
+ const nearestComponent = annotation.source?.componentName;
237
+ const ownerPath = annotation.componentPath.join(" \u203A ");
238
+ lines.push(`## Annotation ${index + 1}`);
239
+ lines.push("");
240
+ lines.push(`ID: ${annotation.id}`);
241
+ lines.push(`Note: ${annotation.note || "(no note provided)"}`);
242
+ if (source) {
243
+ lines.push(`Source: ${source}`);
244
+ }
245
+ if (nearestComponent) {
246
+ lines.push(`Nearest React component: ${nearestComponent}`);
247
+ }
248
+ if (ownerPath && ownerPath !== nearestComponent) {
249
+ lines.push(`React owner path: ${ownerPath}`);
250
+ }
251
+ if (sourceStack.length) {
252
+ lines.push("React source stack:");
253
+ sourceStack.forEach((frame) => lines.push(`- ${frame}`));
254
+ }
255
+ lines.push(`Element tag: ${annotation.element.tagName}`);
256
+ if (annotation.element.html) {
257
+ lines.push(`Element HTML: ${annotation.element.html}`);
258
+ }
259
+ if (annotation.element.text) {
260
+ lines.push(`Element text: ${annotation.element.text}`);
261
+ }
262
+ if (annotation.element.selector) {
263
+ lines.push(`Selector: ${annotation.element.selector}`);
264
+ }
265
+ lines.push("");
266
+ });
267
+ return lines.join("\n").trimEnd();
268
+ }
269
+ function formatJson(collection) {
270
+ return JSON.stringify(collection, null, 2);
271
+ }
272
+ function formatSource(annotation) {
273
+ const source = annotation.source;
274
+ if (!source?.filePath) {
275
+ return "";
276
+ }
277
+ const line = source.lineNumber ? `:${source.lineNumber}` : "";
278
+ const column = source.columnNumber ? `:${source.columnNumber}` : "";
279
+ return `${source.filePath}${line}${column}`;
280
+ }
281
+ function formatSourceStack(annotation) {
282
+ return annotation.sourceStack.map((frame) => {
283
+ const location = formatSourceFrame(frame);
284
+ const component = frame.componentName ? ` (${frame.componentName})` : "";
285
+ return location ? `${location}${component}` : frame.componentName || "";
286
+ }).filter(Boolean);
287
+ }
288
+ function formatSourceFrame(frame) {
289
+ if (!frame.filePath) {
290
+ return "";
291
+ }
292
+ const line = frame.lineNumber ? `:${frame.lineNumber}` : "";
293
+ const column = frame.columnNumber ? `:${frame.columnNumber}` : "";
294
+ return `${frame.filePath}${line}${column}`;
295
+ }
296
+
297
+ // src/SourceAnnotator.tsx
298
+ var import_jsx_runtime = require("react/jsx-runtime");
299
+ var ROOT_ATTR = "data-mikuexe-annotator-root";
300
+ var DEFAULT_HOTKEY = "alt+a";
301
+ var DEFAULT_OUTPUT = "markdown";
302
+ function SourceAnnotator({
303
+ enabled = true,
304
+ hotkey = DEFAULT_HOTKEY,
305
+ output = DEFAULT_OUTPUT,
306
+ onCollect,
307
+ renderToaster = true
308
+ }) {
309
+ const [isAnnotating, setIsAnnotating] = (0, import_react.useState)(false);
310
+ const [hoverRect, setHoverRect] = (0, import_react.useState)(null);
311
+ const [selected, setSelected] = (0, import_react.useState)(null);
312
+ const [note, setNote] = (0, import_react.useState)("");
313
+ const [annotations, setAnnotations] = (0, import_react.useState)([]);
314
+ const [status, setStatus] = (0, import_react.useState)(null);
315
+ const selectedRef = (0, import_react.useRef)(null);
316
+ selectedRef.current = selected;
317
+ const collection = (0, import_react.useMemo)(
318
+ () => createAnnotationCollection(annotations.map(({ rect: _rect, targetElement: _targetElement, ...annotation }) => annotation)),
319
+ [annotations]
320
+ );
321
+ const refreshTrackedRects = (0, import_react.useCallback)(() => {
322
+ setHoverRect(null);
323
+ setSelected((current) => current ? { ...current, rect: getRect(current.element) } : current);
324
+ setAnnotations((existing) => existing.map((annotation) => ({ ...annotation, rect: getRect(annotation.targetElement) })));
325
+ }, []);
326
+ (0, import_react.useEffect)(() => {
327
+ if (!enabled) {
328
+ setIsAnnotating(false);
329
+ }
330
+ }, [enabled]);
331
+ (0, import_react.useEffect)(() => {
332
+ const onKeyDown = (event) => {
333
+ if (!matchesHotkey(event, hotkey)) {
334
+ return;
335
+ }
336
+ event.preventDefault();
337
+ setIsAnnotating((current) => enabled && !current);
338
+ };
339
+ document.addEventListener("keydown", onKeyDown);
340
+ return () => document.removeEventListener("keydown", onKeyDown);
341
+ }, [enabled, hotkey]);
342
+ (0, import_react.useEffect)(() => {
343
+ if (!enabled || !isAnnotating) {
344
+ setHoverRect(null);
345
+ return;
346
+ }
347
+ const onPointerOver = (event) => {
348
+ const target = getAnnotatableTarget(event.target);
349
+ if (!target) {
350
+ setHoverRect(null);
351
+ return;
352
+ }
353
+ setHoverRect(getRect(target));
354
+ };
355
+ const onClick = (event) => {
356
+ const target = getAnnotatableTarget(event.target);
357
+ if (!target) {
358
+ return;
359
+ }
360
+ event.preventDefault();
361
+ event.stopPropagation();
362
+ const rect = getRect(target);
363
+ setSelected({ element: target, rect, annotation: null, loading: true });
364
+ setNote("");
365
+ setStatus("Resolving source\u2026");
366
+ captureElementAnnotation(target, "").then((annotation) => {
367
+ setSelected((current) => {
368
+ if (current?.element !== target) {
369
+ return current;
370
+ }
371
+ return { ...current, annotation, loading: false };
372
+ });
373
+ setStatus(annotation.source ? "Source captured." : "Element captured without source info.");
374
+ }).catch(() => {
375
+ setSelected((current) => current?.element === target ? { ...current, loading: false } : current);
376
+ setStatus("Element captured without source info.");
377
+ });
378
+ };
379
+ document.addEventListener("pointerover", onPointerOver, true);
380
+ document.addEventListener("click", onClick, true);
381
+ return () => {
382
+ document.removeEventListener("pointerover", onPointerOver, true);
383
+ document.removeEventListener("click", onClick, true);
384
+ };
385
+ }, [enabled, isAnnotating]);
386
+ (0, import_react.useEffect)(() => {
387
+ if (!enabled || !isAnnotating) {
388
+ return;
389
+ }
390
+ document.addEventListener("scroll", refreshTrackedRects, true);
391
+ window.addEventListener("resize", refreshTrackedRects);
392
+ return () => {
393
+ document.removeEventListener("scroll", refreshTrackedRects, true);
394
+ window.removeEventListener("resize", refreshTrackedRects);
395
+ };
396
+ }, [enabled, isAnnotating, refreshTrackedRects]);
397
+ const addAnnotation = (0, import_react.useCallback)(async () => {
398
+ const current = selectedRef.current;
399
+ if (!current || current.loading) {
400
+ return;
401
+ }
402
+ const trimmedNote = note.trim();
403
+ if (!trimmedNote) {
404
+ setStatus("Add a note before saving this annotation.");
405
+ return;
406
+ }
407
+ const annotation = current.annotation ? { ...current.annotation, note: trimmedNote } : await captureElementAnnotation(current.element, trimmedNote);
408
+ setAnnotations((existing) => [...existing, { ...annotation, targetElement: current.element, rect: getRect(current.element) }]);
409
+ setSelected(null);
410
+ setNote("");
411
+ setStatus("Annotation saved.");
412
+ }, [note]);
413
+ const collect = (0, import_react.useCallback)(async () => {
414
+ const payload = createAnnotationCollection(annotations.map(({ rect: _rect, targetElement: _targetElement, ...annotation }) => annotation));
415
+ const text = formatAnnotationCollection(payload, output);
416
+ try {
417
+ await copyTextToClipboard(text);
418
+ onCollect?.(payload);
419
+ setIsAnnotating(false);
420
+ setSelected(null);
421
+ setHoverRect(null);
422
+ setNote("");
423
+ import_sonner.toast.success("Annotations copied", { description: `${payload.annotations.length} copied to clipboard.` });
424
+ setStatus(`Copied ${payload.annotations.length} annotation${payload.annotations.length === 1 ? "" : "s"}.`);
425
+ } catch (error) {
426
+ import_sonner.toast.error("Copy failed", { description: error instanceof Error ? error.message : "Clipboard copy failed." });
427
+ setStatus(error instanceof Error ? error.message : "Clipboard copy failed.");
428
+ }
429
+ }, [annotations, onCollect, output]);
430
+ if (!enabled) {
431
+ return null;
432
+ }
433
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...{ [ROOT_ATTR]: "" }, style: styles.root, "aria-live": "polite", children: [
434
+ renderToaster ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sonner.Toaster, { position: "bottom-right", richColors: true }) : null,
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
436
+ "button",
437
+ {
438
+ type: "button",
439
+ onClick: () => setIsAnnotating((current) => !current),
440
+ style: { ...styles.floatingButton, ...isAnnotating ? styles.floatingButtonActive : null },
441
+ "aria-pressed": isAnnotating,
442
+ title: `Toggle annotator (${hotkey})`,
443
+ children: isAnnotating ? "Annotating" : "Annotate"
444
+ }
445
+ ),
446
+ isAnnotating && hoverRect ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, { rect: hoverRect, kind: "hover" }) : null,
447
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, { rect: selected.rect, kind: "selected" }) : null,
448
+ isAnnotating ? annotations.map((annotation, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pin, { annotation, index }, annotation.id)) : null,
449
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: getPopoverStyle(selected.rect), role: "dialog", "aria-label": "Add source annotation", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.popoverTitle, children: "Annotation" }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.metaText, children: selected.loading ? "Resolving source\u2026" : formatSelectedSource(selected.annotation) }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
453
+ "textarea",
454
+ {
455
+ value: note,
456
+ onChange: (event) => setNote(event.target.value),
457
+ placeholder: "What should change here?",
458
+ style: styles.textarea,
459
+ rows: 4,
460
+ autoFocus: true
461
+ }
462
+ ),
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.popoverActions, children: [
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setSelected(null), style: styles.secondaryButton, children: "Cancel" }),
465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: addAnnotation, style: styles.primaryButton, disabled: selected.loading, children: "Save note" })
466
+ ] })
467
+ ] }) : null,
468
+ isAnnotating ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { style: styles.panel, "aria-label": "Collected annotations", children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.panelHeader, children: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Annotations" }),
471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.badge, children: collection.annotations.length })
472
+ ] }),
473
+ annotations.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { style: styles.annotationList, children: annotations.map((annotation) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: styles.annotationItem, children: [
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.noteText, children: annotation.note }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.metaText, children: formatSelectedSource(annotation) })
476
+ ] }, annotation.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.emptyText, children: "Hover an element, click it, then add a note." }),
477
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: collect, style: styles.collectButton, disabled: !annotations.length, children: "Collect" }),
478
+ status ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.status, children: status }) : null
479
+ ] }) : null
480
+ ] });
481
+ }
482
+ function Box({ rect, kind }) {
483
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
484
+ "div",
485
+ {
486
+ style: {
487
+ ...styles.box,
488
+ ...kind === "selected" ? styles.selectedBox : styles.hoverBox,
489
+ top: rect.top,
490
+ left: rect.left,
491
+ width: rect.width,
492
+ height: rect.height
493
+ }
494
+ }
495
+ );
496
+ }
497
+ function Pin({ annotation, index }) {
498
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
499
+ "div",
500
+ {
501
+ style: { ...styles.pin, top: Math.max(8, annotation.rect.top - 10), left: Math.max(8, annotation.rect.left - 10) },
502
+ title: annotation.note,
503
+ children: index + 1
504
+ }
505
+ );
506
+ }
507
+ function getAnnotatableTarget(target) {
508
+ if (!(target instanceof Element)) {
509
+ return null;
510
+ }
511
+ if (target.closest(`[${ROOT_ATTR}]`)) {
512
+ return null;
513
+ }
514
+ if (target === document.body || target === document.documentElement) {
515
+ return null;
516
+ }
517
+ return target;
518
+ }
519
+ function getRect(element) {
520
+ const rect = element.getBoundingClientRect();
521
+ return {
522
+ top: rect.top,
523
+ left: rect.left,
524
+ width: rect.width,
525
+ height: rect.height
526
+ };
527
+ }
528
+ function getPopoverStyle(rect) {
529
+ const top = Math.min(window.innerHeight - 260, rect.top + rect.height + 8);
530
+ const left = Math.min(window.innerWidth - 340, Math.max(8, rect.left));
531
+ return {
532
+ ...styles.popover,
533
+ top: Math.max(8, top),
534
+ left
535
+ };
536
+ }
537
+ function formatSelectedSource(annotation) {
538
+ const componentPath = annotation?.componentPath.length ? annotation.componentPath.join(" \u203A ") : null;
539
+ if (!annotation) {
540
+ return "Source unavailable";
541
+ }
542
+ if (!annotation.source?.filePath) {
543
+ return `${annotation.element.selector} \xB7 source unavailable`;
544
+ }
545
+ const line = annotation.source.lineNumber ? `:${annotation.source.lineNumber}` : "";
546
+ const component = componentPath ? ` \xB7 ${componentPath}` : "";
547
+ return `${annotation.source.filePath}${line}${component}`;
548
+ }
549
+ function matchesHotkey(event, hotkey) {
550
+ const parts = hotkey.toLowerCase().split("+").map((part) => part.trim()).filter(Boolean);
551
+ const key = parts.find((part) => !["ctrl", "control", "cmd", "meta", "mod", "shift", "alt", "option"].includes(part));
552
+ const wantsMeta = parts.includes("meta") || parts.includes("cmd") || parts.includes("mod") && isMac();
553
+ const wantsCtrl = parts.includes("ctrl") || parts.includes("control") || parts.includes("mod") && !isMac();
554
+ const wantsShift = parts.includes("shift");
555
+ const wantsAlt = parts.includes("alt") || parts.includes("option");
556
+ return event.key.toLowerCase() === key && event.metaKey === wantsMeta && event.ctrlKey === wantsCtrl && event.shiftKey === wantsShift && event.altKey === wantsAlt;
557
+ }
558
+ function isMac() {
559
+ return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform);
560
+ }
561
+ var baseFont = '13px/1.35 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
562
+ var styles = {
563
+ root: {
564
+ position: "fixed",
565
+ inset: 0,
566
+ zIndex: 2147483647,
567
+ pointerEvents: "none",
568
+ font: baseFont,
569
+ color: "#0f172a"
570
+ },
571
+ floatingButton: {
572
+ position: "fixed",
573
+ right: 16,
574
+ bottom: 16,
575
+ pointerEvents: "auto",
576
+ borderWidth: 1,
577
+ borderStyle: "solid",
578
+ borderColor: "#cbd5e1",
579
+ background: "#ffffff",
580
+ color: "#0f172a",
581
+ borderRadius: 999,
582
+ padding: "10px 14px",
583
+ font: baseFont,
584
+ fontWeight: 700,
585
+ boxShadow: "0 10px 25px rgba(15, 23, 42, 0.16)",
586
+ cursor: "pointer"
587
+ },
588
+ floatingButtonActive: {
589
+ background: "#0f172a",
590
+ color: "#ffffff",
591
+ borderColor: "#0f172a"
592
+ },
593
+ box: {
594
+ position: "fixed",
595
+ borderRadius: 6,
596
+ pointerEvents: "none",
597
+ boxSizing: "border-box"
598
+ },
599
+ hoverBox: {
600
+ border: "2px solid #38bdf8",
601
+ background: "rgba(56, 189, 248, 0.08)"
602
+ },
603
+ selectedBox: {
604
+ border: "2px solid #f97316",
605
+ background: "rgba(249, 115, 22, 0.1)"
606
+ },
607
+ popover: {
608
+ position: "fixed",
609
+ width: 320,
610
+ pointerEvents: "auto",
611
+ background: "#ffffff",
612
+ border: "1px solid #cbd5e1",
613
+ borderRadius: 12,
614
+ padding: 12,
615
+ boxShadow: "0 18px 45px rgba(15, 23, 42, 0.22)"
616
+ },
617
+ popoverTitle: {
618
+ fontWeight: 800,
619
+ marginBottom: 4
620
+ },
621
+ metaText: {
622
+ color: "#64748b",
623
+ fontSize: 12,
624
+ overflow: "hidden",
625
+ textOverflow: "ellipsis",
626
+ whiteSpace: "nowrap"
627
+ },
628
+ textarea: {
629
+ width: "100%",
630
+ boxSizing: "border-box",
631
+ marginTop: 10,
632
+ border: "1px solid #cbd5e1",
633
+ borderRadius: 8,
634
+ padding: 10,
635
+ resize: "vertical",
636
+ font: baseFont
637
+ },
638
+ popoverActions: {
639
+ display: "flex",
640
+ justifyContent: "flex-end",
641
+ gap: 8,
642
+ marginTop: 10
643
+ },
644
+ secondaryButton: {
645
+ border: "1px solid #cbd5e1",
646
+ borderRadius: 8,
647
+ background: "#ffffff",
648
+ padding: "7px 10px",
649
+ cursor: "pointer"
650
+ },
651
+ primaryButton: {
652
+ border: "1px solid #0f172a",
653
+ borderRadius: 8,
654
+ background: "#0f172a",
655
+ color: "#ffffff",
656
+ padding: "7px 10px",
657
+ cursor: "pointer"
658
+ },
659
+ panel: {
660
+ position: "fixed",
661
+ right: 16,
662
+ bottom: 68,
663
+ width: 300,
664
+ pointerEvents: "auto",
665
+ background: "#ffffff",
666
+ border: "1px solid #cbd5e1",
667
+ borderRadius: 12,
668
+ padding: 12,
669
+ boxShadow: "0 18px 45px rgba(15, 23, 42, 0.18)"
670
+ },
671
+ panelHeader: {
672
+ display: "flex",
673
+ justifyContent: "space-between",
674
+ alignItems: "center",
675
+ marginBottom: 8
676
+ },
677
+ badge: {
678
+ minWidth: 20,
679
+ height: 20,
680
+ borderRadius: 999,
681
+ display: "inline-flex",
682
+ alignItems: "center",
683
+ justifyContent: "center",
684
+ background: "#e2e8f0",
685
+ color: "#334155",
686
+ fontWeight: 700,
687
+ fontSize: 12
688
+ },
689
+ annotationList: {
690
+ listStyle: "decimal",
691
+ margin: "0 0 10px 18px",
692
+ padding: 0,
693
+ maxHeight: 180,
694
+ overflow: "auto"
695
+ },
696
+ annotationItem: {
697
+ marginBottom: 8
698
+ },
699
+ noteText: {
700
+ color: "#0f172a",
701
+ fontWeight: 650
702
+ },
703
+ emptyText: {
704
+ color: "#64748b",
705
+ margin: "6px 0 10px"
706
+ },
707
+ collectButton: {
708
+ width: "100%",
709
+ border: "1px solid #0f172a",
710
+ borderRadius: 8,
711
+ background: "#0f172a",
712
+ color: "#ffffff",
713
+ padding: "9px 10px",
714
+ fontWeight: 800,
715
+ cursor: "pointer"
716
+ },
717
+ status: {
718
+ marginTop: 8,
719
+ color: "#475569",
720
+ fontSize: 12
721
+ },
722
+ pin: {
723
+ position: "fixed",
724
+ width: 20,
725
+ height: 20,
726
+ borderRadius: 999,
727
+ display: "inline-flex",
728
+ alignItems: "center",
729
+ justifyContent: "center",
730
+ pointerEvents: "none",
731
+ background: "#f97316",
732
+ color: "#ffffff",
733
+ fontSize: 12,
734
+ fontWeight: 800,
735
+ boxShadow: "0 8px 18px rgba(15, 23, 42, 0.2)"
736
+ }
737
+ };
738
+ // Annotate the CommonJS export names for ESM import in node:
739
+ 0 && (module.exports = {
740
+ SourceAnnotator,
741
+ captureElementAnnotation,
742
+ copyTextToClipboard,
743
+ createAnnotationCollection,
744
+ formatAnnotationCollection,
745
+ formatMarkdown,
746
+ getElementSelector,
747
+ trimText
748
+ });
749
+ //# sourceMappingURL=index.cjs.map