@useclickly/react 0.2.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 ADDED
@@ -0,0 +1,1184 @@
1
+ "use client";
2
+ import { useState, useEffect, useSyncExternalStore, useRef, useMemo, useCallback } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { createShadowHost, SelectionEngine, Overlay, collectMetadata } from '@useclickly/core';
5
+ import { create } from 'zustand';
6
+ import { useShallow } from 'zustand/react/shallow';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { nanoid } from 'nanoid';
9
+
10
+ // src/Clickly.tsx
11
+ function useDraggable(defaultPos, size) {
12
+ const [position, setPosition] = useState(defaultPos);
13
+ const [isDragging, setDragging] = useState(false);
14
+ const startRef = useRef(null);
15
+ const onPointerDown = useCallback(
16
+ (e) => {
17
+ e.preventDefault();
18
+ e.target.setPointerCapture?.(e.pointerId);
19
+ startRef.current = {
20
+ pointer: { x: e.clientX, y: e.clientY },
21
+ pos: position
22
+ };
23
+ setDragging(true);
24
+ },
25
+ [position]
26
+ );
27
+ useEffect(() => {
28
+ if (!isDragging) return;
29
+ const onMove = (e) => {
30
+ const s = startRef.current;
31
+ if (!s) return;
32
+ const dx = e.clientX - s.pointer.x;
33
+ const dy = e.clientY - s.pointer.y;
34
+ const maxX = window.innerWidth - size.width - 8;
35
+ const maxY = window.innerHeight - size.height - 8;
36
+ setPosition({
37
+ x: clamp(s.pos.x + dx, 8, Math.max(8, maxX)),
38
+ y: clamp(s.pos.y + dy, 8, Math.max(8, maxY))
39
+ });
40
+ };
41
+ const onUp = () => {
42
+ setDragging(false);
43
+ startRef.current = null;
44
+ };
45
+ window.addEventListener("pointermove", onMove);
46
+ window.addEventListener("pointerup", onUp);
47
+ return () => {
48
+ window.removeEventListener("pointermove", onMove);
49
+ window.removeEventListener("pointerup", onUp);
50
+ };
51
+ }, [isDragging, size.height, size.width]);
52
+ return {
53
+ position,
54
+ setPosition,
55
+ handleProps: {
56
+ onPointerDown,
57
+ style: { cursor: isDragging ? "grabbing" : "grab", touchAction: "none" }
58
+ },
59
+ isDragging
60
+ };
61
+ }
62
+ function clamp(n, lo, hi) {
63
+ return Math.max(lo, Math.min(hi, n));
64
+ }
65
+ function useEngineState(engine) {
66
+ return useSyncExternalStore(
67
+ (cb) => engine ? engine.subscribe(cb) : () => {
68
+ },
69
+ () => engine ? engine.getSnapshot() : IDLE,
70
+ () => IDLE
71
+ );
72
+ }
73
+ var IDLE = { kind: "idle" };
74
+ var useAnnotations = create((set, get) => ({
75
+ byId: {},
76
+ order: [],
77
+ add: (a) => set((s) => ({
78
+ byId: { ...s.byId, [a.id]: a },
79
+ order: s.order.includes(a.id) ? s.order : [...s.order, a.id]
80
+ })),
81
+ remove: (id) => set((s) => {
82
+ if (!(id in s.byId)) return s;
83
+ const next = { ...s.byId };
84
+ delete next[id];
85
+ return { byId: next, order: s.order.filter((x) => x !== id) };
86
+ }),
87
+ update: (id, patch) => set((s) => {
88
+ const cur = s.byId[id];
89
+ if (!cur) return s;
90
+ return { byId: { ...s.byId, [id]: { ...cur, ...patch } } };
91
+ }),
92
+ clear: () => set({ byId: {}, order: [] }),
93
+ list: () => {
94
+ const { byId, order } = get();
95
+ return order.map((id) => byId[id]).filter(Boolean);
96
+ }
97
+ }));
98
+ function useAnnotationsList() {
99
+ return useAnnotations(
100
+ useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
101
+ );
102
+ }
103
+ var DEFAULTS = {
104
+ outputDetail: "standard",
105
+ copyOnAdd: true,
106
+ showReactComponents: true,
107
+ markerColor: "#06b6d4"
108
+ };
109
+ var STORAGE_KEY = "clickly:settings";
110
+ function load() {
111
+ if (typeof localStorage === "undefined") return DEFAULTS;
112
+ try {
113
+ const raw = localStorage.getItem(STORAGE_KEY);
114
+ if (!raw) return DEFAULTS;
115
+ const parsed = JSON.parse(raw);
116
+ return { ...DEFAULTS, ...parsed };
117
+ } catch {
118
+ return DEFAULTS;
119
+ }
120
+ }
121
+ function persist(s) {
122
+ if (typeof localStorage === "undefined") return;
123
+ try {
124
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
125
+ } catch {
126
+ }
127
+ }
128
+ var useSettings = create((set) => ({
129
+ ...load(),
130
+ set: (patch) => set((cur) => {
131
+ const next = { ...cur, ...patch };
132
+ persist(next);
133
+ return next;
134
+ }),
135
+ reset: () => {
136
+ persist(DEFAULTS);
137
+ set(DEFAULTS);
138
+ }
139
+ }));
140
+
141
+ // src/output/markdown.ts
142
+ function annotationsToMarkdown(annotations, detail = "standard") {
143
+ if (!annotations.length) return "(no annotations)";
144
+ return annotations.map((a, i) => formatOne(a, i + 1, detail)).join("\n\n");
145
+ }
146
+ function formatOne(a, index, detail) {
147
+ const lines = [];
148
+ lines.push(`## Annotation #${index}`);
149
+ lines.push(`**Element:** \`${a.element}${a.cssClasses ? "." + a.cssClasses.split(" ").join(".") : ""}\``);
150
+ lines.push(`**Path:** \`${a.elementPath}\``);
151
+ if (detail !== "compact" && a.boundingBox) {
152
+ const b = a.boundingBox;
153
+ lines.push(`**Position:** ${Math.round(b.x)}px, ${Math.round(b.y)}px (${Math.round(b.width)}\xD7${Math.round(b.height)}px)`);
154
+ }
155
+ if (detail === "detailed" || detail === "forensic") {
156
+ if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
157
+ if (a.nearbyText) lines.push(`**Nearby text:** ${truncate(a.nearbyText, 120)}`);
158
+ }
159
+ if (detail === "forensic" && a.computedStyles) {
160
+ lines.push("**Computed styles:**\n```\n" + a.computedStyles + "\n```");
161
+ }
162
+ lines.push(`**Feedback:** ${a.comment}`);
163
+ if (a.severity) lines.push(`**Severity:** ${a.severity}`);
164
+ return lines.join("\n");
165
+ }
166
+ function truncate(s, n) {
167
+ return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
168
+ }
169
+ function Icon({
170
+ children,
171
+ size = 16
172
+ }) {
173
+ return /* @__PURE__ */ jsx(
174
+ "svg",
175
+ {
176
+ width: size,
177
+ height: size,
178
+ viewBox: "0 0 24 24",
179
+ fill: "none",
180
+ stroke: "currentColor",
181
+ strokeWidth: "2",
182
+ strokeLinecap: "round",
183
+ strokeLinejoin: "round",
184
+ "aria-hidden": "true",
185
+ children
186
+ }
187
+ );
188
+ }
189
+ var IconCursor = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M4 4l6 16 2-7 7-2z" }) });
190
+ var IconLayers = () => /* @__PURE__ */ jsxs(Icon, { children: [
191
+ /* @__PURE__ */ jsx("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
192
+ /* @__PURE__ */ jsx("path", { d: "M3 12l9 5 9-5" }),
193
+ /* @__PURE__ */ jsx("path", { d: "M3 17l9 5 9-5" })
194
+ ] });
195
+ var IconSquare = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "16", height: "16", rx: "2", strokeDasharray: "3 3" }) });
196
+ var IconCopy = () => /* @__PURE__ */ jsxs(Icon, { children: [
197
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
198
+ /* @__PURE__ */ jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
199
+ ] });
200
+ var IconTrash = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) });
201
+ var IconSettings = () => /* @__PURE__ */ jsxs(Icon, { children: [
202
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
203
+ /* @__PURE__ */ jsx("path", { d: "M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15 1.7 1.7 0 0 0 3.04 14H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.64 8.9a1.7 1.7 0 0 0-.34-1.87l-.06-.06A2 2 0 1 1 7.07 4.14l.06.06A1.7 1.7 0 0 0 9 4.54 1.7 1.7 0 0 0 10 3.03V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9a1.7 1.7 0 0 0 1.55 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.5 1z" })
204
+ ] });
205
+ var IconGrip = () => /* @__PURE__ */ jsxs(Icon, { children: [
206
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "6", r: "1" }),
207
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "12", r: "1" }),
208
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "18", r: "1" }),
209
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "6", r: "1" }),
210
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "12", r: "1" }),
211
+ /* @__PURE__ */ jsx("circle", { cx: "15", cy: "18", r: "1" })
212
+ ] });
213
+ var IconClose = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) });
214
+ var IconCheck = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12l5 5L20 7" }) });
215
+ var IconList = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
216
+ function SettingsPopover({ anchor, width, onClose }) {
217
+ const ref = useRef(null);
218
+ const s = useSettings();
219
+ useEffect(() => {
220
+ const onDown = (e) => {
221
+ if (ref.current && !ref.current.contains(e.target)) onClose();
222
+ };
223
+ window.addEventListener("pointerdown", onDown, true);
224
+ return () => window.removeEventListener("pointerdown", onDown, true);
225
+ }, [onClose]);
226
+ const top = Math.max(8, anchor.y - 200);
227
+ const left = Math.min(window.innerWidth - 268, anchor.x + width - 260);
228
+ return /* @__PURE__ */ jsxs("div", { ref, className: "clickly-popover", style: { left, top }, children: [
229
+ /* @__PURE__ */ jsx("h4", { children: "Settings" }),
230
+ /* @__PURE__ */ jsxs("div", { className: "field", children: [
231
+ /* @__PURE__ */ jsx("label", { htmlFor: "clickly-detail", children: "Output detail" }),
232
+ /* @__PURE__ */ jsxs(
233
+ "select",
234
+ {
235
+ id: "clickly-detail",
236
+ value: s.outputDetail,
237
+ onChange: (e) => s.set({ outputDetail: e.target.value }),
238
+ children: [
239
+ /* @__PURE__ */ jsx("option", { value: "compact", children: "Compact" }),
240
+ /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard" }),
241
+ /* @__PURE__ */ jsx("option", { value: "detailed", children: "Detailed" }),
242
+ /* @__PURE__ */ jsx("option", { value: "forensic", children: "Forensic" })
243
+ ]
244
+ }
245
+ )
246
+ ] }),
247
+ /* @__PURE__ */ jsxs("div", { className: "field", children: [
248
+ /* @__PURE__ */ jsx("label", { htmlFor: "clickly-copy", children: "Copy on add" }),
249
+ /* @__PURE__ */ jsx(
250
+ "input",
251
+ {
252
+ id: "clickly-copy",
253
+ type: "checkbox",
254
+ checked: s.copyOnAdd,
255
+ onChange: (e) => s.set({ copyOnAdd: e.target.checked })
256
+ }
257
+ )
258
+ ] }),
259
+ /* @__PURE__ */ jsxs("div", { className: "field", children: [
260
+ /* @__PURE__ */ jsx("label", { htmlFor: "clickly-react", children: "React components" }),
261
+ /* @__PURE__ */ jsx(
262
+ "input",
263
+ {
264
+ id: "clickly-react",
265
+ type: "checkbox",
266
+ checked: s.showReactComponents,
267
+ onChange: (e) => s.set({ showReactComponents: e.target.checked })
268
+ }
269
+ )
270
+ ] }),
271
+ /* @__PURE__ */ jsxs("div", { className: "field", children: [
272
+ /* @__PURE__ */ jsx("label", { htmlFor: "clickly-color", children: "Marker color" }),
273
+ /* @__PURE__ */ jsx(
274
+ "input",
275
+ {
276
+ id: "clickly-color",
277
+ type: "color",
278
+ value: s.markerColor,
279
+ onChange: (e) => s.set({ markerColor: e.target.value })
280
+ }
281
+ )
282
+ ] })
283
+ ] });
284
+ }
285
+ function AnnotationList({ anchor, width, onClose }) {
286
+ const items = useAnnotationsList();
287
+ const remove = useAnnotations((s) => s.remove);
288
+ const ref = useRef(null);
289
+ useEffect(() => {
290
+ const onDown = (e) => {
291
+ if (ref.current && !ref.current.contains(e.target)) onClose();
292
+ };
293
+ window.addEventListener("pointerdown", onDown, true);
294
+ return () => window.removeEventListener("pointerdown", onDown, true);
295
+ }, [onClose]);
296
+ const top = Math.max(8, anchor.y - 8 - Math.min(window.innerHeight * 0.5, items.length * 60 + 40));
297
+ const left = Math.min(window.innerWidth - 328, anchor.x);
298
+ return /* @__PURE__ */ jsx("div", { ref, className: "clickly-list", style: { left, top }, children: items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "empty", children: "No annotations yet." }) : items.map((a, i) => /* @__PURE__ */ jsxs("div", { className: "row", children: [
299
+ /* @__PURE__ */ jsxs("span", { className: "num", children: [
300
+ "#",
301
+ i + 1
302
+ ] }),
303
+ /* @__PURE__ */ jsxs("div", { className: "body", children: [
304
+ /* @__PURE__ */ jsx("div", { className: "selector", children: a.elementPath }),
305
+ /* @__PURE__ */ jsx("p", { children: a.comment })
306
+ ] }),
307
+ /* @__PURE__ */ jsx("button", { className: "remove", onClick: () => remove(a.id), title: "Remove", children: /* @__PURE__ */ jsx(IconClose, {}) })
308
+ ] }, a.id)) });
309
+ }
310
+ var TOOLBAR_SIZE = { width: 360, height: 40 };
311
+ function Toolbar({ engine, onCollapse }) {
312
+ const state = useEngineState(engine);
313
+ const annotations = useAnnotationsList();
314
+ const clearAnnotations = useAnnotations((s) => s.clear);
315
+ const outputDetail = useSettings((s) => s.outputDetail);
316
+ const [showSettings, setShowSettings] = useState(false);
317
+ const [showList, setShowList] = useState(false);
318
+ const anchorRef = useRef(null);
319
+ const { position, handleProps } = useDraggable(
320
+ {
321
+ x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
322
+ y: Math.max(8, window.innerHeight - TOOLBAR_SIZE.height - 16)
323
+ },
324
+ TOOLBAR_SIZE
325
+ );
326
+ const isActive = state.kind !== "idle";
327
+ const currentMode = state.kind === "inspect" || state.kind === "pressed" || state.kind === "dragging" ? state.mode : null;
328
+ const pinnedCount = state.kind === "inspect" || state.kind === "pressed" || state.kind === "dragging" ? state.pinned.length : 0;
329
+ const setMode = (m) => {
330
+ if (!isActive) engine.activate(m);
331
+ else engine.setMode(m);
332
+ };
333
+ const onCopy = async () => {
334
+ const md = annotationsToMarkdown(annotations, outputDetail);
335
+ try {
336
+ await navigator.clipboard.writeText(md);
337
+ } catch {
338
+ }
339
+ };
340
+ const onClear = () => {
341
+ if (annotations.length === 0) return;
342
+ if (confirm(`Clear ${annotations.length} annotation(s)?`)) clearAnnotations();
343
+ };
344
+ return /* @__PURE__ */ jsxs(
345
+ "div",
346
+ {
347
+ ref: anchorRef,
348
+ className: "clickly-toolbar",
349
+ style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
350
+ "aria-label": "Clickly toolbar",
351
+ children: [
352
+ /* @__PURE__ */ jsx("div", { className: "grip", ...handleProps, title: "Drag to move", children: /* @__PURE__ */ jsx(IconGrip, {}) }),
353
+ /* @__PURE__ */ jsx(
354
+ "button",
355
+ {
356
+ className: `clickly-btn icon-only${currentMode === "single" ? " is-active" : ""}`,
357
+ onClick: () => setMode("single"),
358
+ title: "Single (1)",
359
+ children: /* @__PURE__ */ jsx(IconCursor, {})
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx(
363
+ "button",
364
+ {
365
+ className: `clickly-btn icon-only${currentMode === "multi" ? " is-active" : ""}`,
366
+ onClick: () => setMode("multi"),
367
+ title: "Multi-select (2 / shift-click)",
368
+ children: /* @__PURE__ */ jsx(IconLayers, {})
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsx(
372
+ "button",
373
+ {
374
+ className: `clickly-btn icon-only${currentMode === "area" ? " is-active" : ""}`,
375
+ onClick: () => setMode("area"),
376
+ title: "Area drag (3)",
377
+ children: /* @__PURE__ */ jsx(IconSquare, {})
378
+ }
379
+ ),
380
+ /* @__PURE__ */ jsx("div", { className: "divider" }),
381
+ pinnedCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
382
+ /* @__PURE__ */ jsxs(
383
+ "button",
384
+ {
385
+ className: "clickly-btn primary-pinned",
386
+ onClick: () => engine.annotatePinned(),
387
+ title: `Annotate the ${pinnedCount} pinned element(s) (Enter)`,
388
+ children: [
389
+ /* @__PURE__ */ jsx(IconCheck, {}),
390
+ "Annotate (",
391
+ pinnedCount,
392
+ ")"
393
+ ]
394
+ }
395
+ ),
396
+ /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ className: "clickly-btn icon-only",
400
+ onClick: () => engine.clearPinned(),
401
+ title: "Clear pinned (Esc)",
402
+ children: /* @__PURE__ */ jsx(IconClose, {})
403
+ }
404
+ ),
405
+ /* @__PURE__ */ jsx("div", { className: "divider" })
406
+ ] }),
407
+ /* @__PURE__ */ jsxs(
408
+ "button",
409
+ {
410
+ className: "clickly-btn",
411
+ onClick: () => setShowList((v) => !v),
412
+ title: "Annotations (L)",
413
+ children: [
414
+ /* @__PURE__ */ jsx(IconList, {}),
415
+ annotations.length > 0 && /* @__PURE__ */ jsx("span", { className: "clickly-counter", children: annotations.length })
416
+ ]
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ className: "clickly-btn icon-only",
423
+ onClick: onCopy,
424
+ title: "Copy markdown (C)",
425
+ disabled: annotations.length === 0,
426
+ children: /* @__PURE__ */ jsx(IconCopy, {})
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx(
430
+ "button",
431
+ {
432
+ className: "clickly-btn icon-only",
433
+ onClick: onClear,
434
+ title: "Clear (X)",
435
+ disabled: annotations.length === 0,
436
+ children: /* @__PURE__ */ jsx(IconTrash, {})
437
+ }
438
+ ),
439
+ /* @__PURE__ */ jsx("div", { className: "divider" }),
440
+ /* @__PURE__ */ jsx(
441
+ "button",
442
+ {
443
+ className: "clickly-btn icon-only",
444
+ onClick: () => setShowSettings((v) => !v),
445
+ title: "Settings",
446
+ children: /* @__PURE__ */ jsx(IconSettings, {})
447
+ }
448
+ ),
449
+ /* @__PURE__ */ jsx(
450
+ "button",
451
+ {
452
+ className: "clickly-btn icon-only",
453
+ onClick: onCollapse,
454
+ title: "Collapse (Esc)",
455
+ children: /* @__PURE__ */ jsx(IconClose, {})
456
+ }
457
+ ),
458
+ showSettings && /* @__PURE__ */ jsx(
459
+ SettingsPopover,
460
+ {
461
+ anchor: { x: position.x, y: position.y },
462
+ width: TOOLBAR_SIZE.width,
463
+ onClose: () => setShowSettings(false)
464
+ }
465
+ ),
466
+ showList && /* @__PURE__ */ jsx(
467
+ AnnotationList,
468
+ {
469
+ anchor: { x: position.x, y: position.y },
470
+ width: TOOLBAR_SIZE.width,
471
+ onClose: () => setShowList(false)
472
+ }
473
+ )
474
+ ]
475
+ }
476
+ );
477
+ }
478
+ function CollapsedFAB({ onExpand }) {
479
+ const annotations = useAnnotationsList();
480
+ return /* @__PURE__ */ jsxs(
481
+ "button",
482
+ {
483
+ className: "clickly-fab",
484
+ onClick: onExpand,
485
+ title: "Open Clickly (\u2318/Ctrl+Shift+F)",
486
+ "aria-label": "Open Clickly toolbar",
487
+ children: [
488
+ /* @__PURE__ */ jsx(IconCursor, {}),
489
+ annotations.length > 0 && /* @__PURE__ */ jsx("span", { className: "clickly-fab-badge", children: annotations.length })
490
+ ]
491
+ }
492
+ );
493
+ }
494
+ var POPUP_W = 320;
495
+ var POPUP_H_EST = 200;
496
+ function AnnotationPopup({ engine }) {
497
+ const state = useEngineState(engine);
498
+ const addAnnotation = useAnnotations((s) => s.add);
499
+ const copyOnAdd = useSettings((s) => s.copyOnAdd);
500
+ const outputDetail = useSettings((s) => s.outputDetail);
501
+ const [comment, setComment] = useState("");
502
+ const taRef = useRef(null);
503
+ useEffect(() => {
504
+ if (state.kind === "annotating") {
505
+ setComment("");
506
+ requestAnimationFrame(() => taRef.current?.focus());
507
+ }
508
+ }, [state.kind]);
509
+ const onKey = (e) => {
510
+ if (e.key === "Escape") {
511
+ e.preventDefault();
512
+ cancel();
513
+ }
514
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
515
+ e.preventDefault();
516
+ submit();
517
+ }
518
+ };
519
+ const cancel = () => engine.commit();
520
+ const submit = () => {
521
+ if (state.kind !== "annotating") return;
522
+ const sel = engine.resolveSelection();
523
+ if (!sel) return;
524
+ const elements = sel.kind === "single" ? [sel.element] : sel.elements;
525
+ if (elements.length === 0) return;
526
+ const sharedComment = comment.trim() || "(no comment)";
527
+ const isMulti = sel.kind !== "single";
528
+ for (const el of elements) {
529
+ const md = collectMetadata(el, { detail: outputDetail });
530
+ const annotation = {
531
+ id: "ann_" + nanoid(8),
532
+ comment: sharedComment,
533
+ element: md.element,
534
+ elementPath: md.elementPath,
535
+ fullPath: md.fullPath,
536
+ timestamp: Date.now(),
537
+ x: md.boundingBox.x + md.boundingBox.width / 2,
538
+ y: md.boundingBox.y + window.scrollY,
539
+ url: location.href,
540
+ boundingBox: md.boundingBox,
541
+ cssClasses: md.cssClasses || void 0,
542
+ computedStyles: stringifyStyles(md.computedStyles),
543
+ accessibility: md.accessibility || void 0,
544
+ nearbyText: md.nearbyText || void 0,
545
+ selectedText: md.selectedText || void 0,
546
+ isFixed: md.isFixed || void 0,
547
+ isMultiSelect: isMulti,
548
+ kind: "feedback",
549
+ status: "pending"
550
+ };
551
+ addAnnotation(annotation);
552
+ }
553
+ if (copyOnAdd) {
554
+ const all = useAnnotations.getState().list();
555
+ navigator.clipboard?.writeText(annotationsToMarkdown(all, outputDetail)).catch(() => void 0);
556
+ }
557
+ engine.commit();
558
+ };
559
+ const placement = useMemo(() => {
560
+ if (state.kind !== "annotating") return null;
561
+ const sel = engine.resolveSelection();
562
+ if (!sel) return null;
563
+ const rect = anchorRect(sel);
564
+ if (!rect) return null;
565
+ let top = rect.bottom + 8;
566
+ let left = rect.left;
567
+ if (top + POPUP_H_EST > window.innerHeight) {
568
+ top = Math.max(8, rect.top - POPUP_H_EST - 8);
569
+ }
570
+ if (left + POPUP_W > window.innerWidth) {
571
+ left = Math.max(8, window.innerWidth - POPUP_W - 8);
572
+ }
573
+ return { top, left, label: describeSelection(sel) };
574
+ }, [state, engine]);
575
+ if (state.kind !== "annotating" || !placement) return null;
576
+ return /* @__PURE__ */ jsxs("div", { className: "clickly-popup", style: { top: placement.top, left: placement.left }, children: [
577
+ /* @__PURE__ */ jsxs("div", { className: "target-info", children: [
578
+ /* @__PURE__ */ jsx("span", { children: placement.label }),
579
+ /* @__PURE__ */ jsx("span", { children: state.selection.kind })
580
+ ] }),
581
+ /* @__PURE__ */ jsx(
582
+ "textarea",
583
+ {
584
+ ref: taRef,
585
+ value: comment,
586
+ placeholder: "Describe the issue or change\u2026 (\u2318/Ctrl + Enter to submit)",
587
+ onChange: (e) => setComment(e.target.value),
588
+ onKeyDown: onKey
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsxs("div", { className: "row", children: [
592
+ /* @__PURE__ */ jsx("button", { className: "ghost", onClick: cancel, children: "Cancel" }),
593
+ /* @__PURE__ */ jsx("button", { className: "primary", onClick: submit, children: "Add" })
594
+ ] })
595
+ ] });
596
+ }
597
+ function stringifyStyles(styles) {
598
+ const entries = Object.entries(styles);
599
+ if (entries.length === 0) return void 0;
600
+ return entries.map(([k, v]) => `${k}: ${v}`).join("; ");
601
+ }
602
+ function anchorRect(sel) {
603
+ if (!sel) return null;
604
+ if (sel.kind === "single") return sel.element.getBoundingClientRect();
605
+ if (sel.kind === "area") {
606
+ return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
607
+ }
608
+ if (sel.elements.length === 0) return null;
609
+ return sel.elements[0].getBoundingClientRect();
610
+ }
611
+ function describeSelection(sel) {
612
+ if (sel.kind === "single") {
613
+ const m = collectMetadata(sel.element, { detail: "compact" });
614
+ return m.elementPath;
615
+ }
616
+ if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
617
+ return `${sel.elements.length} element(s)`;
618
+ }
619
+ function AnnotationPins() {
620
+ const annotations = useAnnotationsList();
621
+ const [, setVersion] = useState(0);
622
+ useEffect(() => {
623
+ let raf = null;
624
+ const update = () => {
625
+ if (raf !== null) return;
626
+ raf = requestAnimationFrame(() => {
627
+ raf = null;
628
+ setVersion((v) => v + 1);
629
+ });
630
+ };
631
+ window.addEventListener("scroll", update, { capture: true, passive: true });
632
+ window.addEventListener("resize", update, { passive: true });
633
+ return () => {
634
+ if (raf !== null) cancelAnimationFrame(raf);
635
+ window.removeEventListener("scroll", update, { capture: true });
636
+ window.removeEventListener("resize", update);
637
+ };
638
+ }, []);
639
+ return /* @__PURE__ */ jsx(Fragment, { children: annotations.map((a, i) => /* @__PURE__ */ jsx(Pin, { number: i + 1, annotation: a }, a.id)) });
640
+ }
641
+ function Pin({ number, annotation }) {
642
+ const remove = useAnnotations((s) => s.remove);
643
+ const [hovered, setHovered] = useState(false);
644
+ const pos = resolvePosition(annotation);
645
+ if (!pos) return null;
646
+ return /* @__PURE__ */ jsxs(
647
+ "div",
648
+ {
649
+ className: "clickly-pin",
650
+ style: { left: pos.x, top: pos.y },
651
+ onMouseEnter: () => setHovered(true),
652
+ onMouseLeave: () => setHovered(false),
653
+ role: "button",
654
+ tabIndex: 0,
655
+ title: annotation.comment,
656
+ children: [
657
+ /* @__PURE__ */ jsx("span", { className: "clickly-pin-num", children: number }),
658
+ hovered && /* @__PURE__ */ jsxs("div", { className: "clickly-pin-bubble", children: [
659
+ /* @__PURE__ */ jsx("div", { className: "clickly-pin-comment", children: annotation.comment }),
660
+ /* @__PURE__ */ jsx("div", { className: "clickly-pin-meta", children: annotation.elementPath }),
661
+ /* @__PURE__ */ jsx(
662
+ "button",
663
+ {
664
+ className: "clickly-pin-remove",
665
+ onClick: (e) => {
666
+ e.stopPropagation();
667
+ remove(annotation.id);
668
+ },
669
+ children: "Remove"
670
+ }
671
+ )
672
+ ] })
673
+ ]
674
+ }
675
+ );
676
+ }
677
+ function resolvePosition(a) {
678
+ if (a.elementPath) {
679
+ try {
680
+ const el = document.querySelector(a.elementPath);
681
+ if (el) {
682
+ const r = el.getBoundingClientRect();
683
+ if (r.width || r.height) return { x: r.right - 11, y: r.top - 11 };
684
+ }
685
+ } catch {
686
+ }
687
+ }
688
+ if (a.boundingBox) {
689
+ return {
690
+ x: a.boundingBox.x + a.boundingBox.width - 11,
691
+ y: a.boundingBox.y - 11
692
+ };
693
+ }
694
+ return null;
695
+ }
696
+ function ClicklyRoot({
697
+ engine,
698
+ host
699
+ }) {
700
+ const [expanded, setExpanded] = useState(false);
701
+ const clearAnnotations = useAnnotations((s) => s.clear);
702
+ const outputDetail = useSettings((s) => s.outputDetail);
703
+ const markerColor = useSettings((s) => s.markerColor);
704
+ const engineState = useEngineState(engine);
705
+ useEffect(() => {
706
+ host.style.setProperty("--clickly-hover", markerColor);
707
+ }, [host, markerColor]);
708
+ useEffect(() => {
709
+ if (expanded) {
710
+ if (engine.getSnapshot().kind === "idle") engine.activate("single");
711
+ } else {
712
+ if (engine.getSnapshot().kind !== "idle") engine.deactivate();
713
+ }
714
+ }, [expanded, engine]);
715
+ useEffect(() => {
716
+ const active = engineState.kind !== "idle";
717
+ if (active) document.body.setAttribute("data-clickly-active", "");
718
+ else document.body.removeAttribute("data-clickly-active");
719
+ const mode = engineState.kind === "inspect" || engineState.kind === "pressed" || engineState.kind === "dragging" ? engineState.mode : null;
720
+ if (mode) document.body.setAttribute("data-clickly-mode", mode);
721
+ else document.body.removeAttribute("data-clickly-mode");
722
+ if (engineState.kind === "annotating") {
723
+ document.body.setAttribute("data-clickly-annotating", "");
724
+ } else {
725
+ document.body.removeAttribute("data-clickly-annotating");
726
+ }
727
+ return () => {
728
+ document.body.removeAttribute("data-clickly-active");
729
+ document.body.removeAttribute("data-clickly-mode");
730
+ document.body.removeAttribute("data-clickly-annotating");
731
+ };
732
+ }, [engineState]);
733
+ useEffect(() => {
734
+ const onKey = (e) => {
735
+ const tag = e.target?.tagName;
736
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
737
+ if (e.target?.isContentEditable) return;
738
+ const active = document.activeElement;
739
+ if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
740
+ if (e.key.toLowerCase() === "f" && e.shiftKey && (e.metaKey || e.ctrlKey)) {
741
+ e.preventDefault();
742
+ setExpanded((v) => !v);
743
+ return;
744
+ }
745
+ if (e.key === "Escape") {
746
+ if (engine.getSnapshot().kind === "annotating") return;
747
+ if (expanded) setExpanded(false);
748
+ return;
749
+ }
750
+ if (!expanded) return;
751
+ if (e.key === "1") engine.setMode("single");
752
+ if (e.key === "2") engine.setMode("multi");
753
+ if (e.key === "3") engine.setMode("area");
754
+ if (e.key === "Enter") {
755
+ const s = engine.getSnapshot();
756
+ if (s.kind === "inspect" && s.pinned.length > 0) {
757
+ e.preventDefault();
758
+ engine.annotatePinned();
759
+ }
760
+ }
761
+ if (e.key === "c" || e.key === "C") {
762
+ const list = useAnnotations.getState().list();
763
+ if (list.length === 0) return;
764
+ e.preventDefault();
765
+ navigator.clipboard?.writeText(annotationsToMarkdown(list, outputDetail));
766
+ }
767
+ if (e.key === "x" || e.key === "X") {
768
+ if (useAnnotations.getState().list().length === 0) return;
769
+ e.preventDefault();
770
+ clearAnnotations();
771
+ }
772
+ };
773
+ window.addEventListener("keydown", onKey);
774
+ return () => window.removeEventListener("keydown", onKey);
775
+ }, [clearAnnotations, outputDetail, expanded, engine]);
776
+ return /* @__PURE__ */ jsxs("div", { className: "clickly-ui", children: [
777
+ /* @__PURE__ */ jsx(AnnotationPins, {}),
778
+ expanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
779
+ /* @__PURE__ */ jsx(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
780
+ /* @__PURE__ */ jsx(AnnotationPopup, { engine })
781
+ ] }) : /* @__PURE__ */ jsx(CollapsedFAB, { onExpand: () => setExpanded(true) })
782
+ ] });
783
+ }
784
+
785
+ // src/internal/styles.ts
786
+ var REACT_UI_CSS = `
787
+ .clickly-ui, .clickly-ui * { box-sizing: border-box; }
788
+
789
+ /* \u2500\u2500\u2500 Floating action button (collapsed state) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
790
+
791
+ .clickly-fab {
792
+ position: fixed;
793
+ right: 16px;
794
+ bottom: 16px;
795
+ width: 48px;
796
+ height: 48px;
797
+ border: none;
798
+ border-radius: 999px;
799
+ background: #0f172a;
800
+ color: #f8fafc;
801
+ display: grid;
802
+ place-items: center;
803
+ cursor: pointer;
804
+ pointer-events: auto;
805
+ user-select: none;
806
+ box-shadow:
807
+ 0 8px 24px rgba(0,0,0,0.30),
808
+ 0 0 0 1px rgba(255,255,255,0.06) inset;
809
+ transition: transform 140ms ease, box-shadow 140ms ease;
810
+ z-index: 1;
811
+ }
812
+ .clickly-fab:hover {
813
+ transform: scale(1.06);
814
+ box-shadow:
815
+ 0 10px 28px rgba(0,0,0,0.36),
816
+ 0 0 0 1px rgba(255,255,255,0.10) inset;
817
+ }
818
+ .clickly-fab:active { transform: scale(0.96); }
819
+ .clickly-fab svg { width: 18px; height: 18px; color: #f8fafc; }
820
+
821
+ .clickly-fab-badge {
822
+ position: absolute;
823
+ top: -2px;
824
+ right: -2px;
825
+ min-width: 18px;
826
+ height: 18px;
827
+ padding: 0 5px;
828
+ background: #f59e0b;
829
+ color: #0f172a;
830
+ font: 600 11px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
831
+ border-radius: 9px;
832
+ display: grid;
833
+ place-items: center;
834
+ pointer-events: none;
835
+ }
836
+
837
+ /* \u2500\u2500\u2500 Toolbar (expanded state) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
838
+
839
+ .clickly-toolbar {
840
+ position: fixed;
841
+ display: flex;
842
+ align-items: center;
843
+ gap: 4px;
844
+ padding: 4px;
845
+ background: rgba(15, 23, 42, 0.94);
846
+ color: #f8fafc;
847
+ border-radius: 12px;
848
+ font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
849
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.06) inset;
850
+ pointer-events: auto;
851
+ user-select: none;
852
+ z-index: 1;
853
+ animation: clickly-fade-in 120ms ease-out;
854
+ }
855
+
856
+ @keyframes clickly-fade-in {
857
+ from { opacity: 0; transform: translateY(4px); }
858
+ to { opacity: 1; transform: translateY(0); }
859
+ }
860
+
861
+ .clickly-toolbar .grip {
862
+ display: grid;
863
+ place-items: center;
864
+ width: 24px;
865
+ height: 28px;
866
+ color: #94a3b8;
867
+ touch-action: none;
868
+ }
869
+
870
+ .clickly-toolbar .divider {
871
+ width: 1px;
872
+ height: 18px;
873
+ background: rgba(255,255,255,0.12);
874
+ margin: 0 2px;
875
+ }
876
+
877
+ .clickly-btn {
878
+ display: inline-flex;
879
+ align-items: center;
880
+ gap: 4px;
881
+ height: 28px;
882
+ padding: 0 8px;
883
+ background: transparent;
884
+ border: none;
885
+ border-radius: 8px;
886
+ color: #cbd5e1;
887
+ cursor: pointer;
888
+ font: inherit;
889
+ }
890
+ .clickly-btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
891
+ .clickly-btn:active { background: rgba(255,255,255,0.14); }
892
+ .clickly-btn.is-active { background: #0ea5e9; color: #fff; }
893
+ .clickly-btn[disabled] { opacity: 0.4; cursor: not-allowed; }
894
+
895
+ .clickly-btn.icon-only {
896
+ width: 28px;
897
+ padding: 0;
898
+ justify-content: center;
899
+ }
900
+
901
+ .clickly-btn.primary-pinned {
902
+ background: #10b981;
903
+ color: #fff;
904
+ font-weight: 500;
905
+ }
906
+ .clickly-btn.primary-pinned:hover {
907
+ background: #059669;
908
+ color: #fff;
909
+ }
910
+
911
+ /* \u2500\u2500\u2500 Popup & popovers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
912
+
913
+ .clickly-popup, .clickly-popover {
914
+ position: fixed;
915
+ background: #fff;
916
+ color: #0f172a;
917
+ border-radius: 10px;
918
+ box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
919
+ font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
920
+ pointer-events: auto;
921
+ z-index: 2;
922
+ animation: clickly-fade-in 120ms ease-out;
923
+ }
924
+
925
+ .clickly-popup { width: 320px; padding: 12px; }
926
+
927
+ .clickly-popup .target-info {
928
+ display: flex;
929
+ align-items: center;
930
+ justify-content: space-between;
931
+ gap: 8px;
932
+ margin-bottom: 8px;
933
+ color: #475569;
934
+ font-size: 11px;
935
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
936
+ }
937
+
938
+ .clickly-popup textarea {
939
+ width: 100%;
940
+ min-height: 64px;
941
+ max-height: 160px;
942
+ padding: 8px;
943
+ border: 1px solid #e2e8f0;
944
+ border-radius: 6px;
945
+ resize: vertical;
946
+ font: inherit;
947
+ color: inherit;
948
+ }
949
+ .clickly-popup textarea:focus {
950
+ outline: none;
951
+ border-color: #0ea5e9;
952
+ box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
953
+ }
954
+
955
+ .clickly-popup .row {
956
+ display: flex;
957
+ justify-content: flex-end;
958
+ gap: 6px;
959
+ margin-top: 10px;
960
+ }
961
+
962
+ .clickly-popup .row .ghost {
963
+ background: transparent;
964
+ color: #475569;
965
+ border: 1px solid #e2e8f0;
966
+ }
967
+ .clickly-popup .row .ghost:hover { background: #f8fafc; }
968
+
969
+ .clickly-popup .row .primary {
970
+ background: #0ea5e9;
971
+ color: #fff;
972
+ border: none;
973
+ }
974
+ .clickly-popup .row .primary:hover { background: #0284c7; }
975
+ .clickly-popup .row button {
976
+ height: 28px;
977
+ padding: 0 12px;
978
+ border-radius: 6px;
979
+ font: inherit;
980
+ cursor: pointer;
981
+ }
982
+
983
+ .clickly-popover { width: 260px; padding: 12px; }
984
+ .clickly-popover h4 { margin: 0 0 8px; font-size: 12px; color: #475569; text-transform: uppercase; letter-spacing: 0.05em; }
985
+ .clickly-popover .field { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; font-size: 13px; }
986
+ .clickly-popover select { font: inherit; padding: 4px 6px; border-radius: 4px; border: 1px solid #e2e8f0; }
987
+ .clickly-popover input[type="color"] { width: 28px; height: 24px; padding: 0; border: 1px solid #e2e8f0; border-radius: 4px; background: none; }
988
+
989
+ .clickly-counter {
990
+ display: inline-flex;
991
+ align-items: center;
992
+ justify-content: center;
993
+ min-width: 18px;
994
+ height: 18px;
995
+ padding: 0 5px;
996
+ border-radius: 9px;
997
+ background: #f59e0b;
998
+ color: #1f2937;
999
+ font-size: 11px;
1000
+ font-weight: 600;
1001
+ margin-left: 4px;
1002
+ }
1003
+
1004
+ /* \u2500\u2500\u2500 Annotation pins (persistent numbered markers) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1005
+
1006
+ .clickly-pin {
1007
+ position: fixed;
1008
+ width: 22px;
1009
+ height: 22px;
1010
+ border-radius: 999px;
1011
+ background: #f59e0b;
1012
+ color: #0f172a;
1013
+ font: 600 12px/22px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1014
+ text-align: center;
1015
+ cursor: pointer;
1016
+ pointer-events: auto;
1017
+ user-select: none;
1018
+ box-shadow: 0 2px 6px rgba(0,0,0,0.25), 0 0 0 2px #fff;
1019
+ z-index: 1;
1020
+ transition: transform 100ms ease;
1021
+ }
1022
+ .clickly-pin:hover { transform: scale(1.12); }
1023
+ .clickly-pin-num { display: block; }
1024
+
1025
+ .clickly-pin-bubble {
1026
+ position: absolute;
1027
+ top: 28px;
1028
+ left: 0;
1029
+ width: 240px;
1030
+ padding: 8px 10px;
1031
+ background: #fff;
1032
+ color: #0f172a;
1033
+ border-radius: 8px;
1034
+ box-shadow: 0 8px 24px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
1035
+ font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1036
+ text-align: left;
1037
+ cursor: default;
1038
+ z-index: 3;
1039
+ animation: clickly-fade-in 100ms ease-out;
1040
+ }
1041
+ .clickly-pin-comment { word-break: break-word; }
1042
+ .clickly-pin-meta {
1043
+ margin-top: 6px;
1044
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
1045
+ font-size: 11px;
1046
+ color: #475569;
1047
+ word-break: break-all;
1048
+ }
1049
+ .clickly-pin-remove {
1050
+ margin-top: 8px;
1051
+ padding: 4px 8px;
1052
+ border: 1px solid #fecaca;
1053
+ background: #fff;
1054
+ color: #b91c1c;
1055
+ border-radius: 4px;
1056
+ font: 11px inherit;
1057
+ cursor: pointer;
1058
+ }
1059
+ .clickly-pin-remove:hover { background: #fef2f2; }
1060
+
1061
+ /* \u2500\u2500\u2500 Annotation list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1062
+
1063
+ .clickly-list {
1064
+ position: fixed;
1065
+ max-height: 50vh;
1066
+ width: 320px;
1067
+ overflow-y: auto;
1068
+ background: #fff;
1069
+ border-radius: 10px;
1070
+ box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
1071
+ color: #0f172a;
1072
+ font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1073
+ pointer-events: auto;
1074
+ z-index: 2;
1075
+ animation: clickly-fade-in 120ms ease-out;
1076
+ }
1077
+ .clickly-list .row { display: flex; gap: 8px; padding: 8px 10px; border-bottom: 1px solid #f1f5f9; }
1078
+ .clickly-list .row:last-child { border-bottom: none; }
1079
+ .clickly-list .num { color: #94a3b8; min-width: 18px; }
1080
+ .clickly-list .selector { color: #475569; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11px; word-break: break-all; }
1081
+ .clickly-list .body { flex: 1; min-width: 0; }
1082
+ .clickly-list .body p { margin: 2px 0 0; word-break: break-word; }
1083
+ .clickly-list button.remove {
1084
+ background: transparent;
1085
+ border: none;
1086
+ color: #94a3b8;
1087
+ cursor: pointer;
1088
+ padding: 2px;
1089
+ display: grid;
1090
+ place-items: center;
1091
+ }
1092
+ .clickly-list button.remove svg { width: 12px; height: 12px; }
1093
+ .clickly-list button.remove:hover { color: #ef4444; }
1094
+ .clickly-list .empty { padding: 20px; text-align: center; color: #94a3b8; }
1095
+ `;
1096
+
1097
+ // src/internal/globalStyles.ts
1098
+ var GLOBAL_PAGE_CSS = `
1099
+ body[data-clickly-active] {
1100
+ -webkit-user-select: none !important;
1101
+ user-select: none !important;
1102
+ }
1103
+ body[data-clickly-active],
1104
+ body[data-clickly-active] *:not(input):not(textarea):not(select) {
1105
+ cursor: crosshair !important;
1106
+ }
1107
+ body[data-clickly-mode="multi"],
1108
+ body[data-clickly-mode="multi"] *:not(input):not(textarea):not(select) {
1109
+ cursor: copy !important;
1110
+ }
1111
+ /* Restore normal cursor while the annotation popup is open so the user
1112
+ can comfortably type in the textarea + click the buttons. */
1113
+ body[data-clickly-annotating] {
1114
+ cursor: default !important;
1115
+ }
1116
+ `;
1117
+ function Clickly({ className } = {}) {
1118
+ const [mount, setMount] = useState(null);
1119
+ useEffect(() => {
1120
+ if (typeof window === "undefined" || typeof document === "undefined") return;
1121
+ let shadow = null;
1122
+ let engine = null;
1123
+ let overlay = null;
1124
+ let portal = null;
1125
+ try {
1126
+ shadow = createShadowHost({ document });
1127
+ portal = document.createElement("div");
1128
+ portal.setAttribute("data-clickly-react-root", "");
1129
+ shadow.root.appendChild(portal);
1130
+ if (!shadow.root.querySelector("style[data-clickly-react]")) {
1131
+ const style = document.createElement("style");
1132
+ style.setAttribute("data-clickly-react", "");
1133
+ style.textContent = REACT_UI_CSS;
1134
+ shadow.root.appendChild(style);
1135
+ }
1136
+ if (!document.head.querySelector("style[data-clickly-globals]")) {
1137
+ const gstyle = document.createElement("style");
1138
+ gstyle.setAttribute("data-clickly-globals", "");
1139
+ gstyle.textContent = GLOBAL_PAGE_CSS;
1140
+ document.head.appendChild(gstyle);
1141
+ }
1142
+ engine = new SelectionEngine({ document, host: shadow.host });
1143
+ overlay = new Overlay({
1144
+ engine,
1145
+ root: shadow.root,
1146
+ document,
1147
+ excludeHost: shadow.host
1148
+ });
1149
+ setMount({ shadow, engine, overlay, portal });
1150
+ } catch (err) {
1151
+ console.warn("[clickly] failed to mount", err);
1152
+ overlay?.destroy();
1153
+ engine?.destroy();
1154
+ portal?.remove();
1155
+ shadow?.destroy();
1156
+ }
1157
+ return () => {
1158
+ setMount(null);
1159
+ overlay?.destroy();
1160
+ engine?.destroy();
1161
+ portal?.remove();
1162
+ shadow?.destroy();
1163
+ document.head.querySelector("style[data-clickly-globals]")?.remove();
1164
+ document.body.removeAttribute("data-clickly-active");
1165
+ document.body.removeAttribute("data-clickly-mode");
1166
+ };
1167
+ }, []);
1168
+ useEffect(() => {
1169
+ if (!mount || !className) return;
1170
+ mount.shadow.host.className = className;
1171
+ return () => {
1172
+ if (mount.shadow.host.isConnected) mount.shadow.host.className = "";
1173
+ };
1174
+ }, [mount, className]);
1175
+ if (!mount) return null;
1176
+ return createPortal(
1177
+ /* @__PURE__ */ jsx(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
1178
+ mount.portal
1179
+ );
1180
+ }
1181
+
1182
+ export { Clickly, DEFAULTS as DEFAULT_SETTINGS, annotationsToMarkdown, useAnnotations, useAnnotationsList, useSettings };
1183
+ //# sourceMappingURL=index.js.map
1184
+ //# sourceMappingURL=index.js.map