@useclickly/react 1.0.0 → 1.0.1

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.
Files changed (48) hide show
  1. package/dist/Clickly.d.ts +25 -0
  2. package/dist/Clickly.d.ts.map +1 -0
  3. package/dist/hooks/useDraggable.d.ts +28 -0
  4. package/dist/hooks/useDraggable.d.ts.map +1 -0
  5. package/dist/hooks/usePersistedState.d.ts +6 -0
  6. package/dist/hooks/usePersistedState.d.ts.map +1 -0
  7. package/dist/index.cjs +2619 -373
  8. package/dist/index.d.ts +12 -69
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2549 -329
  11. package/dist/internal/AnnotationList.d.ts +11 -0
  12. package/dist/internal/AnnotationList.d.ts.map +1 -0
  13. package/dist/internal/AnnotationPins.d.ts +7 -0
  14. package/dist/internal/AnnotationPins.d.ts.map +1 -0
  15. package/dist/internal/AnnotationPopup.d.ts +5 -0
  16. package/dist/internal/AnnotationPopup.d.ts.map +1 -0
  17. package/dist/internal/ClicklyRoot.d.ts +15 -0
  18. package/dist/internal/ClicklyRoot.d.ts.map +1 -0
  19. package/dist/internal/CollapsedFAB.d.ts +10 -0
  20. package/dist/internal/CollapsedFAB.d.ts.map +1 -0
  21. package/dist/internal/SettingsPopover.d.ts +11 -0
  22. package/dist/internal/SettingsPopover.d.ts.map +1 -0
  23. package/dist/internal/Toolbar.d.ts +8 -0
  24. package/dist/internal/Toolbar.d.ts.map +1 -0
  25. package/dist/internal/globalStyles.d.ts +13 -0
  26. package/dist/internal/globalStyles.d.ts.map +1 -0
  27. package/dist/internal/icons.d.ts +12 -0
  28. package/dist/internal/icons.d.ts.map +1 -0
  29. package/dist/internal/styles.d.ts +7 -0
  30. package/dist/internal/styles.d.ts.map +1 -0
  31. package/dist/output/markdown.d.ts +5 -0
  32. package/dist/output/markdown.d.ts.map +1 -0
  33. package/dist/output/markdown.test.d.ts +2 -0
  34. package/dist/output/markdown.test.d.ts.map +1 -0
  35. package/dist/state/annotations.d.ts +21 -0
  36. package/dist/state/annotations.d.ts.map +1 -0
  37. package/dist/state/annotations.test.d.ts +2 -0
  38. package/dist/state/annotations.test.d.ts.map +1 -0
  39. package/dist/state/settings.d.ts +14 -0
  40. package/dist/state/settings.d.ts.map +1 -0
  41. package/dist/state/settings.test.d.ts +2 -0
  42. package/dist/state/settings.test.d.ts.map +1 -0
  43. package/dist/state/useEngineState.d.ts +7 -0
  44. package/dist/state/useEngineState.d.ts.map +1 -0
  45. package/dist/test/setup.d.ts +7 -0
  46. package/dist/test/setup.d.ts.map +1 -0
  47. package/package.json +12 -12
  48. package/LICENSE +0 -21
package/dist/index.cjs CHANGED
@@ -1,20 +1,1074 @@
1
- "use client";
2
- 'use strict';
3
-
4
- var react = require('react');
5
- var reactDom = require('react-dom');
6
- var core = require('@useclickly/core');
7
- var zustand = require('zustand');
8
- var shallow = require('zustand/react/shallow');
9
- var jsxRuntime = require('react/jsx-runtime');
10
- var nanoid = require('nanoid');
11
-
12
- // src/Clickly.tsx
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // packages/react/src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Clickly: () => Clickly,
34
+ DEFAULT_SETTINGS: () => DEFAULTS,
35
+ annotationsToMarkdown: () => annotationsToMarkdown,
36
+ useAnnotations: () => useAnnotations,
37
+ useAnnotationsList: () => useAnnotationsList,
38
+ useSettings: () => useSettings
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // packages/react/src/Clickly.tsx
43
+ var import_react11 = require("react");
44
+ var import_react_dom = require("react-dom");
45
+
46
+ // packages/core/dist/index.js
47
+ function pickElementAt(doc, x, y, excludeHost) {
48
+ if (typeof doc.elementsFromPoint !== "function") return null;
49
+ const chain = doc.elementsFromPoint(x, y);
50
+ for (const el of chain) {
51
+ if (!isInExcludedSubtree(el, excludeHost)) return el;
52
+ }
53
+ return null;
54
+ }
55
+ function pickElementsInRect(root, rect, excludeHost) {
56
+ const out = [];
57
+ const stack = [root];
58
+ while (stack.length) {
59
+ const el = stack.pop();
60
+ if (isInExcludedSubtree(el, excludeHost)) continue;
61
+ const box = el.getBoundingClientRect();
62
+ if (box.width > 0 && box.height > 0 && containedIn(box, rect)) {
63
+ out.push(el);
64
+ }
65
+ for (let i = 0; i < el.children.length; i++) {
66
+ const child = el.children[i];
67
+ if (child) stack.push(child);
68
+ }
69
+ }
70
+ return out;
71
+ }
72
+ function containedIn(el, sel) {
73
+ return el.left >= sel.x && el.top >= sel.y && el.right <= sel.x + sel.width && el.bottom <= sel.y + sel.height;
74
+ }
75
+ function isInExcludedSubtree(el, host) {
76
+ if (!host) return false;
77
+ let cur = el;
78
+ while (cur) {
79
+ if (cur === host) return true;
80
+ cur = cur.parentNode;
81
+ }
82
+ return false;
83
+ }
84
+ function manhattan(a, b) {
85
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
86
+ }
87
+ function rectFromPoints(start, current) {
88
+ const x = Math.min(start.x, current.x);
89
+ const y = Math.min(start.y, current.y);
90
+ const width = Math.abs(current.x - start.x);
91
+ const height = Math.abs(current.y - start.y);
92
+ return { x, y, width, height };
93
+ }
94
+ var DRAG_THRESHOLD_PX = 12;
95
+ var initialState = { kind: "idle" };
96
+ function reduce(state, event) {
97
+ if (event.type === "DEACTIVATE") return { kind: "idle" };
98
+ if (event.type === "ESCAPE") {
99
+ if (state.kind === "idle") return state;
100
+ if (state.kind === "annotating") return resumeInspect(
101
+ [],
102
+ /* mode */
103
+ void 0
104
+ );
105
+ if (state.kind === "inspect" && state.pinned.length > 0) {
106
+ return { ...state, pinned: [], hoverTarget: null };
107
+ }
108
+ return resumeInspect("pinned" in state ? state.pinned : []);
109
+ }
110
+ if (event.type === "CLEAR_PINNED") {
111
+ if (state.kind === "idle") return state;
112
+ if (state.kind === "inspect") return { ...state, pinned: [] };
113
+ return state;
114
+ }
115
+ switch (state.kind) {
116
+ case "idle":
117
+ if (event.type === "ACTIVATE") {
118
+ return {
119
+ kind: "inspect",
120
+ mode: event.mode ?? "single",
121
+ hoverTarget: null,
122
+ pinned: []
123
+ };
124
+ }
125
+ return state;
126
+ case "inspect":
127
+ if (event.type === "MODE_CHANGE") return { ...state, mode: event.mode };
128
+ if (event.type === "POINTER_MOVE") return { ...state, hoverTarget: event.target };
129
+ if (event.type === "POINTER_DOWN") {
130
+ return {
131
+ kind: "pressed",
132
+ mode: state.mode,
133
+ start: event.point,
134
+ target: event.target,
135
+ additive: event.additive,
136
+ pinned: state.pinned
137
+ };
138
+ }
139
+ if (event.type === "ANNOTATE_PINNED") {
140
+ if (state.pinned.length === 0) return state;
141
+ return {
142
+ kind: "annotating",
143
+ selection: { kind: "multi", elements: state.pinned },
144
+ pinned: state.pinned
145
+ };
146
+ }
147
+ return state;
148
+ case "pressed":
149
+ if (event.type === "POINTER_MOVE") {
150
+ if (manhattan(state.start, event.point) >= DRAG_THRESHOLD_PX) {
151
+ return {
152
+ kind: "dragging",
153
+ mode: state.mode,
154
+ start: state.start,
155
+ current: event.point,
156
+ pinned: state.pinned
157
+ };
158
+ }
159
+ return state;
160
+ }
161
+ if (event.type === "POINTER_UP") {
162
+ if (!state.target) {
163
+ return resumeInspect(state.pinned, state.mode);
164
+ }
165
+ if (state.additive || state.mode === "multi") {
166
+ const nextPinned = togglePinned(state.pinned, state.target);
167
+ return {
168
+ kind: "inspect",
169
+ mode: state.mode,
170
+ hoverTarget: state.target,
171
+ pinned: nextPinned
172
+ };
173
+ }
174
+ return {
175
+ kind: "annotating",
176
+ selection: { kind: "single", element: state.target },
177
+ pinned: state.pinned
178
+ };
179
+ }
180
+ return state;
181
+ case "dragging":
182
+ if (event.type === "POINTER_MOVE") return { ...state, current: event.point };
183
+ if (event.type === "POINTER_UP") {
184
+ const rect = rectFromPoints(state.start, state.current);
185
+ const selection = { kind: "area", rect, elements: [] };
186
+ return { kind: "annotating", selection, pinned: state.pinned };
187
+ }
188
+ return state;
189
+ case "annotating":
190
+ if (event.type === "COMMIT") {
191
+ return resumeInspect([]);
192
+ }
193
+ return state;
194
+ }
195
+ }
196
+ function resumeInspect(pinned, mode) {
197
+ return {
198
+ kind: "inspect",
199
+ mode: mode ?? "single",
200
+ hoverTarget: null,
201
+ pinned
202
+ };
203
+ }
204
+ function togglePinned(pinned, el) {
205
+ const idx = pinned.indexOf(el);
206
+ if (idx === -1) return [...pinned, el];
207
+ const next = pinned.slice();
208
+ next.splice(idx, 1);
209
+ return next;
210
+ }
211
+ var SelectionEngine = class {
212
+ state = initialState;
213
+ listeners = /* @__PURE__ */ new Set();
214
+ doc;
215
+ host;
216
+ raf;
217
+ caf;
218
+ searchRoot;
219
+ pendingPointer = null;
220
+ rafHandle = null;
221
+ /** Guard for the pointermove RAF coalescer. Separate from `rafHandle`
222
+ * because a synchronous `raf` (used in tests) returns a handle the cb
223
+ * has already invalidated — boolean is the safe sentinel. */
224
+ rafPending = false;
225
+ attached = false;
226
+ boundHandlers = [];
227
+ constructor(deps = {}) {
228
+ this.doc = deps.document ?? (typeof document !== "undefined" ? document : null);
229
+ if (!this.doc) {
230
+ throw new Error("SelectionEngine: no Document available (pass `deps.document`).");
231
+ }
232
+ this.host = deps.host ?? null;
233
+ this.raf = deps.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
234
+ this.caf = deps.caf ?? ((h) => {
235
+ if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
236
+ else clearTimeout(h);
237
+ });
238
+ this.searchRoot = deps.searchRoot ?? this.doc.body;
239
+ }
240
+ /* ─── Subscribable<EngineState> ───────────────────────────────── */
241
+ subscribe(listener) {
242
+ this.listeners.add(listener);
243
+ return () => this.listeners.delete(listener);
244
+ }
245
+ getSnapshot() {
246
+ return this.state;
247
+ }
248
+ /* ─── Public control ──────────────────────────────────────────── */
249
+ activate(mode) {
250
+ perfMark("clickly:engine:activate");
251
+ this.dispatch(mode ? { type: "ACTIVATE", mode } : { type: "ACTIVATE" });
252
+ this.attach();
253
+ }
254
+ deactivate() {
255
+ perfMark("clickly:engine:deactivate");
256
+ this.detach();
257
+ this.dispatch({ type: "DEACTIVATE" });
258
+ }
259
+ setMode(mode) {
260
+ this.dispatch({ type: "MODE_CHANGE", mode });
261
+ }
262
+ commit() {
263
+ this.dispatch({ type: "COMMIT" });
264
+ }
265
+ clearPinned() {
266
+ this.dispatch({ type: "CLEAR_PINNED" });
267
+ }
268
+ /** Open the annotation popup populated with everything currently pinned. */
269
+ annotatePinned() {
270
+ this.dispatch({ type: "ANNOTATE_PINNED" });
271
+ }
272
+ /**
273
+ * Returns the resolved selection from the current annotating state, with
274
+ * area-mode element enumeration filled in (the reducer leaves it empty).
275
+ * Returns null if not currently annotating.
276
+ */
277
+ resolveSelection() {
278
+ if (this.state.kind !== "annotating") return null;
279
+ const sel = this.state.selection;
280
+ if (sel.kind !== "area") return sel;
281
+ const elements = pickElementsInRect(this.searchRoot, sel.rect, this.host);
282
+ return { ...sel, elements };
283
+ }
284
+ /* ─── Lifecycle ───────────────────────────────────────────────── */
285
+ attach() {
286
+ if (this.attached) return;
287
+ this.attached = true;
288
+ const win = this.doc.defaultView ?? globalThis;
289
+ this.bind(this.doc, "pointermove", this.onPointerMove, { passive: true });
290
+ this.bind(this.doc, "pointerdown", this.onPointerDown);
291
+ this.bind(this.doc, "pointerup", this.onPointerUp);
292
+ this.bind(win, "keydown", this.onKeyDown);
293
+ }
294
+ detach() {
295
+ if (!this.attached) return;
296
+ for (const [t, ev, fn, opts] of this.boundHandlers) t.removeEventListener(ev, fn, opts);
297
+ this.boundHandlers = [];
298
+ this.attached = false;
299
+ if (this.rafHandle !== null) {
300
+ this.caf(this.rafHandle);
301
+ this.rafHandle = null;
302
+ }
303
+ this.rafPending = false;
304
+ }
305
+ destroy() {
306
+ this.detach();
307
+ this.listeners.clear();
308
+ }
309
+ bind(target, ev, fn, opts) {
310
+ target.addEventListener(ev, fn, opts);
311
+ this.boundHandlers.push([target, ev, fn, opts]);
312
+ }
313
+ /* ─── DOM event handlers ──────────────────────────────────────── */
314
+ onPointerMove = (e) => {
315
+ this.pendingPointer = { x: e.clientX, y: e.clientY };
316
+ if (this.rafPending) return;
317
+ this.rafPending = true;
318
+ this.rafHandle = this.raf(() => {
319
+ this.rafPending = false;
320
+ this.rafHandle = null;
321
+ const pt = this.pendingPointer;
322
+ this.pendingPointer = null;
323
+ if (!pt) return;
324
+ const target = pickElementAt(this.doc, pt.x, pt.y, this.host);
325
+ this.dispatch({ type: "POINTER_MOVE", point: pt, target });
326
+ });
327
+ };
328
+ onPointerDown = (e) => {
329
+ if (this.host && e.composedPath().includes(this.host)) return;
330
+ const target = pickElementAt(this.doc, e.clientX, e.clientY, this.host);
331
+ this.dispatch({
332
+ type: "POINTER_DOWN",
333
+ point: { x: e.clientX, y: e.clientY },
334
+ target,
335
+ additive: e.shiftKey || e.metaKey || e.ctrlKey
336
+ });
337
+ };
338
+ onPointerUp = (e) => {
339
+ this.dispatch({ type: "POINTER_UP", point: { x: e.clientX, y: e.clientY } });
340
+ };
341
+ onKeyDown = (e) => {
342
+ if (e.key === "Escape") this.dispatch({ type: "ESCAPE" });
343
+ };
344
+ /* ─── Reducer plumbing ────────────────────────────────────────── */
345
+ dispatch(event) {
346
+ const next = reduce(this.state, event);
347
+ if (next === this.state) return;
348
+ this.state = next;
349
+ for (const l of this.listeners) l(next);
350
+ }
351
+ };
352
+ function perfMark(name) {
353
+ if (typeof performance !== "undefined" && typeof performance.mark === "function") {
354
+ try {
355
+ performance.mark(name);
356
+ } catch {
357
+ }
358
+ }
359
+ }
360
+ var OVERLAY_CSS = `
361
+ :host {
362
+ --clickly-hover: #06b6d4;
363
+ --clickly-pinned: #f59e0b;
364
+ --clickly-selected: #10b981;
365
+ --clickly-marquee-stroke: #10b981;
366
+ --clickly-marquee-fill: rgba(16, 185, 129, 0.10);
367
+ --clickly-label-bg: rgba(15, 23, 42, 0.92);
368
+ --clickly-label-fg: #f8fafc;
369
+ --clickly-shadow: 0 0 0 1px rgba(255,255,255,0.5);
370
+
371
+ all: initial;
372
+ position: fixed;
373
+ inset: 0;
374
+ z-index: 2147483647;
375
+ pointer-events: none;
376
+ contain: layout style paint;
377
+ isolation: isolate;
378
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
379
+ }
380
+
381
+ .layer {
382
+ position: fixed;
383
+ left: 0;
384
+ top: 0;
385
+ width: 0;
386
+ height: 0;
387
+ pointer-events: none;
388
+ will-change: transform, width, height, opacity;
389
+ }
390
+
391
+ .marker {
392
+ position: fixed;
393
+ left: 0;
394
+ top: 0;
395
+ box-sizing: border-box;
396
+ border-radius: 2px;
397
+ pointer-events: none;
398
+ will-change: transform, width, height, opacity;
399
+ transition: opacity 80ms linear;
400
+ }
401
+
402
+ .marker[hidden] { display: none; }
403
+
404
+ .marker.hover { box-shadow: 0 0 0 2px var(--clickly-hover), var(--clickly-shadow); }
405
+ .marker.pinned { box-shadow: 0 0 0 2px var(--clickly-pinned), var(--clickly-shadow); }
406
+ .marker.selected { box-shadow: 0 0 0 2px var(--clickly-selected), var(--clickly-shadow); }
407
+
408
+ /* Marquee \u2014 used during drag (dashed) AND for the committed union
409
+ box around multi/area selections (solid). */
410
+ .marker.marquee {
411
+ border: 2px dashed var(--clickly-marquee-stroke);
412
+ background: var(--clickly-marquee-fill);
413
+ border-radius: 8px;
414
+ }
415
+ .marker.marquee.is-committed {
416
+ border-style: solid;
417
+ box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.18);
418
+ }
419
+
420
+ .label {
421
+ position: fixed;
422
+ left: 0;
423
+ top: 0;
424
+ padding: 2px 6px;
425
+ background: var(--clickly-label-bg);
426
+ color: var(--clickly-label-fg);
427
+ font-size: 11px;
428
+ line-height: 1.4;
429
+ border-radius: 4px;
430
+ white-space: nowrap;
431
+ pointer-events: none;
432
+ user-select: none;
433
+ max-width: 50vw;
434
+ overflow: hidden;
435
+ text-overflow: ellipsis;
436
+ }
437
+ `;
438
+ var HOST_TAG = "clickly-root";
439
+ function createShadowHost(deps = {}) {
440
+ const doc = deps.document ?? (typeof document !== "undefined" ? document : null);
441
+ if (!doc) throw new Error("createShadowHost: no Document available");
442
+ const existing = doc.querySelector(HOST_TAG);
443
+ const host = existing ?? doc.createElement(HOST_TAG);
444
+ if (!existing) doc.body.appendChild(host);
445
+ const root = host.shadowRoot ?? host.attachShadow({ mode: "open" });
446
+ if (!root.querySelector("style[data-clickly]")) {
447
+ const style = doc.createElement("style");
448
+ style.setAttribute("data-clickly", "");
449
+ style.textContent = OVERLAY_CSS;
450
+ root.appendChild(style);
451
+ }
452
+ return {
453
+ host,
454
+ root,
455
+ destroy() {
456
+ host.remove();
457
+ }
458
+ };
459
+ }
460
+ var OverlayRenderer = class {
461
+ root;
462
+ document;
463
+ layer;
464
+ hover;
465
+ hoverLabel;
466
+ /** Marquee rect — used both for live drag AND for the union box
467
+ * drawn around multi-element selections after commit. */
468
+ marquee;
469
+ /** Pinned/selected single-element rings (recycled across renders). */
470
+ pinnedPool = [];
471
+ selectedPool = [];
472
+ /** Last rendered state — used to re-render on scroll without a state change. */
473
+ lastState = null;
474
+ constructor(root, document2) {
475
+ this.root = root;
476
+ this.document = document2 ?? root.ownerDocument ?? (typeof globalThis !== "undefined" && globalThis.document ? globalThis.document : null);
477
+ if (!this.document) throw new Error("OverlayRenderer: no Document available");
478
+ this.layer = this.div("layer");
479
+ this.root.appendChild(this.layer);
480
+ this.hover = this.div("marker hover");
481
+ this.hover.hidden = true;
482
+ this.layer.appendChild(this.hover);
483
+ this.hoverLabel = this.div("label");
484
+ this.hoverLabel.hidden = true;
485
+ this.layer.appendChild(this.hoverLabel);
486
+ this.marquee = this.div("marker marquee");
487
+ this.marquee.hidden = true;
488
+ this.layer.appendChild(this.marquee);
489
+ this.document.addEventListener("scroll", this.onScroll, {
490
+ passive: true,
491
+ capture: true
492
+ });
493
+ }
494
+ onScroll = () => {
495
+ if (this.lastState) this.renderState(this.lastState);
496
+ };
497
+ render(state) {
498
+ this.lastState = state;
499
+ this.renderState(state);
500
+ }
501
+ renderState(state) {
502
+ this.renderSingle(this.hover, state.hover);
503
+ if (state.hover) {
504
+ this.hoverLabel.hidden = false;
505
+ this.hoverLabel.textContent = describeElement(state.hover);
506
+ const rect = state.hover.getBoundingClientRect();
507
+ const labelY = rect.top - 20 < 0 ? rect.bottom + 4 : rect.top - 20;
508
+ moveTo(this.hoverLabel, rect.left, labelY);
509
+ } else {
510
+ this.hoverLabel.hidden = true;
511
+ }
512
+ this.renderList(this.pinnedPool, state.pinned, "marker pinned");
513
+ const sel = state.selection ?? [];
514
+ const isMultiUnion = sel.length > 1 || state.marquee !== null && sel.length === 0;
515
+ if (sel.length === 1) {
516
+ this.renderList(this.selectedPool, [sel[0]], "marker selected");
517
+ } else {
518
+ this.renderList(this.selectedPool, [], "marker selected");
519
+ }
520
+ const marqueeRect = state.marquee ?? (sel.length > 1 ? unionOf(sel) : null);
521
+ if (marqueeRect) {
522
+ this.marquee.classList.toggle("is-committed", state.marquee === null);
523
+ this.placeRect(this.marquee, marqueeRect);
524
+ this.marquee.hidden = false;
525
+ } else {
526
+ this.marquee.hidden = true;
527
+ }
528
+ void isMultiUnion;
529
+ }
530
+ destroy() {
531
+ this.document.removeEventListener("scroll", this.onScroll, { capture: true });
532
+ this.lastState = null;
533
+ this.layer.remove();
534
+ }
535
+ /* ─── Internals ───────────────────────────────────────────────── */
536
+ renderSingle(node, target) {
537
+ if (!target) {
538
+ node.hidden = true;
539
+ return;
540
+ }
541
+ const r = target.getBoundingClientRect();
542
+ if (r.width === 0 && r.height === 0) {
543
+ node.hidden = true;
544
+ return;
545
+ }
546
+ this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
547
+ node.hidden = false;
548
+ }
549
+ renderList(pool, targets, className) {
550
+ while (pool.length < targets.length) {
551
+ const el = this.div(className);
552
+ el.hidden = true;
553
+ this.layer.appendChild(el);
554
+ pool.push(el);
555
+ }
556
+ for (let i = 0; i < pool.length; i++) {
557
+ const node = pool[i];
558
+ const target = targets[i];
559
+ if (!target) {
560
+ node.hidden = true;
561
+ continue;
562
+ }
563
+ const r = target.getBoundingClientRect();
564
+ if (r.width === 0 && r.height === 0) {
565
+ node.hidden = true;
566
+ continue;
567
+ }
568
+ this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
569
+ node.hidden = false;
570
+ }
571
+ }
572
+ placeRect(node, rect) {
573
+ moveTo(node, rect.x, rect.y);
574
+ node.style.width = `${rect.width}px`;
575
+ node.style.height = `${rect.height}px`;
576
+ }
577
+ div(className) {
578
+ const el = this.document.createElement("div");
579
+ el.className = className;
580
+ return el;
581
+ }
582
+ };
583
+ function moveTo(node, x, y) {
584
+ const tx = Math.round(x);
585
+ const ty = Math.round(y);
586
+ node.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
587
+ }
588
+ var TAG_LABELS = {
589
+ p: "paragraph",
590
+ h1: "heading",
591
+ h2: "heading",
592
+ h3: "heading",
593
+ h4: "heading",
594
+ h5: "heading",
595
+ h6: "heading",
596
+ a: "link",
597
+ button: "button",
598
+ input: "input",
599
+ textarea: "textarea",
600
+ select: "select",
601
+ img: "image",
602
+ video: "video",
603
+ audio: "audio",
604
+ form: "form",
605
+ nav: "nav",
606
+ header: "header",
607
+ footer: "footer",
608
+ main: "main",
609
+ section: "section",
610
+ article: "article",
611
+ aside: "aside",
612
+ ul: "list",
613
+ ol: "list",
614
+ li: "list item",
615
+ table: "table",
616
+ thead: "table head",
617
+ tbody: "table body",
618
+ tr: "table row",
619
+ td: "cell",
620
+ th: "header cell",
621
+ span: "span",
622
+ div: "div",
623
+ label: "label",
624
+ code: "code",
625
+ pre: "code block",
626
+ blockquote: "quote",
627
+ strong: "bold",
628
+ em: "italic",
629
+ kbd: "key",
630
+ svg: "svg",
631
+ canvas: "canvas"
632
+ };
633
+ function describeElement(el) {
634
+ const tag = el.tagName.toLowerCase();
635
+ const type = TAG_LABELS[tag] ?? tag;
636
+ const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
637
+ if (text.length > 0) {
638
+ const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
639
+ return `${type}: "${preview}"`;
640
+ }
641
+ if (el.id) return `${type}: #${el.id}`;
642
+ const cls = el.classList[0];
643
+ if (cls) return `${type}: .${cls}`;
644
+ return type;
645
+ }
646
+ function unionOf(elements) {
647
+ let minX = Infinity;
648
+ let minY = Infinity;
649
+ let maxX = -Infinity;
650
+ let maxY = -Infinity;
651
+ let any = false;
652
+ for (const el of elements) {
653
+ const r = el.getBoundingClientRect();
654
+ if (r.width === 0 && r.height === 0) continue;
655
+ any = true;
656
+ if (r.left < minX) minX = r.left;
657
+ if (r.top < minY) minY = r.top;
658
+ if (r.right > maxX) maxX = r.right;
659
+ if (r.bottom > maxY) maxY = r.bottom;
660
+ }
661
+ if (!any) return null;
662
+ const PAD = 4;
663
+ return {
664
+ x: minX - PAD,
665
+ y: minY - PAD,
666
+ width: maxX - minX + PAD * 2,
667
+ height: maxY - minY + PAD * 2
668
+ };
669
+ }
670
+ var emptyRenderState = {
671
+ hover: null,
672
+ pinned: [],
673
+ selection: null,
674
+ marquee: null
675
+ };
676
+ var Overlay = class {
677
+ renderer;
678
+ engine;
679
+ doc;
680
+ win;
681
+ searchRoot;
682
+ excludeHost;
683
+ raf;
684
+ caf;
685
+ state = emptyRenderState;
686
+ unsubscribe = null;
687
+ rafHandle = null;
688
+ /** Guard for scheduleRender re-entry. Tracked separately from `rafHandle`
689
+ * because a synchronous `raf` (used in tests) returns a handle the cb has
690
+ * already invalidated — boolean is the safe sentinel. */
691
+ renderPending = false;
692
+ destroyed = false;
693
+ bound = [];
694
+ constructor(opts) {
695
+ this.engine = opts.engine;
696
+ this.doc = opts.document ?? opts.root.ownerDocument ?? null;
697
+ if (!this.doc) throw new Error("Overlay: no Document available");
698
+ this.win = this.doc.defaultView ?? globalThis;
699
+ this.searchRoot = opts.searchRoot ?? this.doc.body;
700
+ this.excludeHost = opts.excludeHost ?? null;
701
+ this.raf = opts.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
702
+ this.caf = opts.caf ?? ((h) => {
703
+ if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
704
+ else clearTimeout(h);
705
+ });
706
+ this.renderer = new OverlayRenderer(opts.root, this.doc);
707
+ this.bindEvent(this.doc, "scroll", this.onReposition, { passive: true, capture: true });
708
+ this.bindEvent(this.win, "resize", this.onReposition, { passive: true });
709
+ this.unsubscribe = this.engine.subscribe((s) => this.onEngineState(s));
710
+ this.onEngineState(this.engine.getSnapshot());
711
+ }
712
+ destroy() {
713
+ this.destroyed = true;
714
+ if (this.unsubscribe) this.unsubscribe();
715
+ this.unsubscribe = null;
716
+ if (this.rafHandle !== null) this.caf(this.rafHandle);
717
+ this.rafHandle = null;
718
+ for (const [t, ev, fn, opts] of this.bound) t.removeEventListener(ev, fn, opts);
719
+ this.bound = [];
720
+ this.renderer.destroy();
721
+ }
722
+ /* ─── Internals ───────────────────────────────────────────────── */
723
+ onReposition = () => {
724
+ if (this.destroyed) return;
725
+ if (hasAnythingToTrack(this.state)) this.scheduleRender();
726
+ };
727
+ onEngineState(s) {
728
+ this.state = this.derive(s);
729
+ this.scheduleRender();
730
+ }
731
+ /** Engine state → render state. */
732
+ derive(s) {
733
+ switch (s.kind) {
734
+ case "idle":
735
+ return emptyRenderState;
736
+ case "inspect":
737
+ return { hover: s.hoverTarget, pinned: s.pinned, selection: null, marquee: null };
738
+ case "pressed":
739
+ return { hover: s.target, pinned: s.pinned, selection: null, marquee: null };
740
+ case "dragging": {
741
+ const rect = rectFromPoints(s.start, s.current);
742
+ return { hover: null, pinned: s.pinned, selection: null, marquee: rect };
743
+ }
744
+ case "annotating": {
745
+ const sel = s.selection;
746
+ const elements = sel.kind === "area" ? pickElementsInRect(this.searchRoot, sel.rect, this.excludeHost) : sel.kind === "multi" ? sel.elements : [sel.element];
747
+ return { hover: null, pinned: s.pinned, selection: elements, marquee: null };
748
+ }
749
+ }
750
+ }
751
+ scheduleRender() {
752
+ if (this.destroyed || this.renderPending) return;
753
+ this.renderPending = true;
754
+ this.rafHandle = this.raf(() => {
755
+ this.renderPending = false;
756
+ this.rafHandle = null;
757
+ if (this.destroyed) return;
758
+ this.renderer.render(this.state);
759
+ });
760
+ }
761
+ bindEvent(target, ev, fn, opts) {
762
+ target.addEventListener(ev, fn, opts);
763
+ this.bound.push([target, ev, fn, opts]);
764
+ }
765
+ };
766
+ function hasAnythingToTrack(s) {
767
+ return s.hover !== null || s.pinned.length > 0 || s.selection !== null && s.selection.length > 0 || s.marquee !== null;
768
+ }
769
+ var FIBER_PROP_PREFIX = "__reactFiber$";
770
+ function getFiber(node) {
771
+ if (!node) return null;
772
+ for (const key of Object.keys(node)) {
773
+ if (key.startsWith(FIBER_PROP_PREFIX)) return node[key];
774
+ }
775
+ return null;
776
+ }
777
+ function getComponentChain(node) {
778
+ const fiber = getFiber(node);
779
+ if (!fiber) return [];
780
+ const names = [];
781
+ let cur = fiber;
782
+ while (cur) {
783
+ const name = getComponentName(cur);
784
+ if (name) names.unshift(name);
785
+ cur = cur.return;
786
+ }
787
+ return dedupeConsecutive(names);
788
+ }
789
+ function getComponentName(fiber) {
790
+ const t = fiber.type;
791
+ if (!t) return null;
792
+ if (typeof t === "string") return null;
793
+ if (typeof t === "function") {
794
+ const fn = t;
795
+ return fn.displayName || fn.name || null;
796
+ }
797
+ if (typeof t === "object") {
798
+ const o = t;
799
+ if (o.displayName) return o.displayName;
800
+ if (o.render) return o.render.displayName || o.render.name || null;
801
+ if (o.type) return o.type.displayName || o.type.name || null;
802
+ }
803
+ return null;
804
+ }
805
+ function dedupeConsecutive(names) {
806
+ const out = [];
807
+ for (const n of names) {
808
+ if (out[out.length - 1] !== n) out.push(n);
809
+ }
810
+ return out;
811
+ }
812
+ function getSourceInfo(node) {
813
+ const fiber = getFiber(node);
814
+ if (!fiber) return null;
815
+ let cur = fiber._debugOwner;
816
+ while (cur) {
817
+ if (cur._debugSource) return cur._debugSource;
818
+ cur = cur._debugOwner;
819
+ }
820
+ cur = fiber;
821
+ while (cur) {
822
+ if (cur._debugSource) return cur._debugSource;
823
+ cur = cur.return;
824
+ }
825
+ return null;
826
+ }
827
+ function collectAccessibility(el) {
828
+ const parts = [];
829
+ const role = el.getAttribute("role");
830
+ if (role) parts.push(`role=${role}`);
831
+ for (const attr of Array.from(el.attributes)) {
832
+ if (attr.name.startsWith("aria-") && attr.value) {
833
+ parts.push(`${attr.name}=${attr.value}`);
834
+ }
835
+ }
836
+ const tabindex = el.getAttribute("tabindex");
837
+ if (tabindex !== null && tabindex !== "") parts.push(`tabindex=${tabindex}`);
838
+ const title = el.getAttribute("title");
839
+ if (title) parts.push(`title=${title}`);
840
+ return parts.join("; ");
841
+ }
842
+ var GROUPS = {
843
+ layout: [
844
+ "display",
845
+ "position",
846
+ "top",
847
+ "right",
848
+ "bottom",
849
+ "left",
850
+ "width",
851
+ "height",
852
+ "margin",
853
+ "padding",
854
+ "box-sizing"
855
+ ],
856
+ visual: [
857
+ "color",
858
+ "background-color",
859
+ "border",
860
+ "border-radius",
861
+ "outline"
862
+ ],
863
+ text: [
864
+ "font-family",
865
+ "font-size",
866
+ "font-weight",
867
+ "line-height",
868
+ "letter-spacing",
869
+ "text-align",
870
+ "white-space"
871
+ ],
872
+ flexgrid: [
873
+ "flex-direction",
874
+ "justify-content",
875
+ "align-items",
876
+ "gap",
877
+ "grid-template-columns",
878
+ "grid-template-rows"
879
+ ],
880
+ effects: ["transform", "box-shadow", "filter"],
881
+ misc: ["cursor", "z-index", "overflow", "opacity", "transition", "animation"]
882
+ };
883
+ var TIER_GROUPS = {
884
+ compact: [],
885
+ standard: ["layout", "visual", "text"],
886
+ detailed: ["layout", "visual", "text", "flexgrid", "effects"],
887
+ forensic: ["layout", "visual", "text", "flexgrid", "effects", "misc"]
888
+ };
889
+ function collectComputedStyles(el, detail) {
890
+ if (detail === "compact") return {};
891
+ const doc = el.ownerDocument;
892
+ const win = doc?.defaultView ?? globalThis;
893
+ if (typeof win.getComputedStyle !== "function") return {};
894
+ const cs = win.getComputedStyle(el);
895
+ const props = /* @__PURE__ */ new Set();
896
+ for (const g of TIER_GROUPS[detail]) {
897
+ for (const p of GROUPS[g]) props.add(p);
898
+ }
899
+ const out = {};
900
+ for (const p of props) {
901
+ const v = cs.getPropertyValue(p);
902
+ if (!v) continue;
903
+ const trimmed = v.trim();
904
+ if (isUninterestingDefault(p, trimmed)) continue;
905
+ out[p] = trimmed;
906
+ }
907
+ return out;
908
+ }
909
+ function isUninterestingDefault(prop, value) {
910
+ if (!value || value === "none" || value === "auto" || value === "normal") return true;
911
+ if (value === "0px" || value === "0%") return true;
912
+ if (value === "rgba(0, 0, 0, 0)") return true;
913
+ if (prop === "color" || prop === "background-color") {
914
+ return value === "rgba(0, 0, 0, 0)";
915
+ }
916
+ return false;
917
+ }
918
+ var SHORT_MAX_DEPTH = 5;
919
+ var FULL_MAX_DEPTH = 8;
920
+ function buildSelector(target, doc = target.ownerDocument ?? document) {
921
+ if (target.id && isStableId(target.id)) {
922
+ const escaped = cssEscape(target.id);
923
+ const id = `#${escaped}`;
924
+ return { short: id, full: id };
925
+ }
926
+ const segments = [];
927
+ let cur = target;
928
+ let short = null;
929
+ for (let depth = 0; cur && depth < FULL_MAX_DEPTH; depth++) {
930
+ segments.unshift(segmentFor(cur));
931
+ const candidate = segments.join(" > ");
932
+ if (short === null && depth < SHORT_MAX_DEPTH && isUnique(doc, candidate)) {
933
+ short = candidate;
934
+ }
935
+ cur = cur.parentElement;
936
+ if (!cur || cur === doc.documentElement || cur.tagName.toLowerCase() === "html") break;
937
+ }
938
+ const full = segments.join(" > ");
939
+ return { short: short ?? full, full };
940
+ }
941
+ function segmentFor(el) {
942
+ const tag = el.tagName.toLowerCase();
943
+ if (el.id && isStableId(el.id)) return `${tag}#${cssEscape(el.id)}`;
944
+ const classes = Array.from(el.classList).filter(isUseableClass).slice(0, 3);
945
+ let segment = classes.length ? `${tag}.${classes.map(cssEscape).join(".")}` : tag;
946
+ const parent = el.parentElement;
947
+ if (parent) {
948
+ const sameTag = Array.from(parent.children).filter(
949
+ (c) => c.tagName === el.tagName
950
+ );
951
+ if (sameTag.length > 1) {
952
+ const idx = sameTag.indexOf(el) + 1;
953
+ if (idx > 0) segment += `:nth-of-type(${idx})`;
954
+ }
955
+ }
956
+ return segment;
957
+ }
958
+ function isUseableClass(c) {
959
+ if (!c) return false;
960
+ if (c.length > 30) return false;
961
+ if (/^css-[a-z0-9]+$/i.test(c)) return false;
962
+ if (/^jsx-\d+$/i.test(c)) return false;
963
+ if (/^_.+_[a-z0-9]{5,}$/i.test(c)) return false;
964
+ if (/^\d/.test(c)) return false;
965
+ return true;
966
+ }
967
+ function isStableId(id) {
968
+ if (/^:[a-z]\d+:/i.test(id)) return false;
969
+ if (/^radix-/i.test(id)) return false;
970
+ if (/^headlessui-/i.test(id)) return false;
971
+ return true;
972
+ }
973
+ function isUnique(doc, selector) {
974
+ try {
975
+ return doc.querySelectorAll(selector).length === 1;
976
+ } catch {
977
+ return false;
978
+ }
979
+ }
980
+ function cssEscape(s) {
981
+ const g = globalThis;
982
+ if (g.CSS?.escape) return g.CSS.escape(s);
983
+ return s.replace(/[^a-zA-Z0-9_-]/g, (c) => "\\" + c);
984
+ }
985
+ var NEARBY_MAX = 200;
986
+ function collectNearbyText(el) {
987
+ const own = readText(el);
988
+ if (own) return truncate(own, NEARBY_MAX);
989
+ const parent = el.parentElement;
990
+ if (parent) {
991
+ const t = readText(parent);
992
+ if (t) return truncate(t, NEARBY_MAX);
993
+ }
994
+ return "";
995
+ }
996
+ function collectSelectedText(doc = document) {
997
+ const win = doc.defaultView ?? globalThis;
998
+ if (typeof win.getSelection !== "function") return "";
999
+ const sel = win.getSelection();
1000
+ if (!sel) return "";
1001
+ return sel.toString().trim();
1002
+ }
1003
+ function readText(el) {
1004
+ const t = el.innerText ?? el.textContent ?? "";
1005
+ return t.replace(/\s+/g, " ").trim();
1006
+ }
1007
+ function truncate(s, n) {
1008
+ return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
1009
+ }
1010
+ var POSITIONED_FIXED = /* @__PURE__ */ new Set(["fixed", "sticky"]);
1011
+ function collectMetadata(el, options = {}) {
1012
+ perfMark2("clickly:metadata:collect");
1013
+ const detail = options.detail ?? "standard";
1014
+ const includeReact = options.includeReact !== false && detail !== "compact";
1015
+ const doc = options.document ?? el.ownerDocument ?? document;
1016
+ const { short, full } = buildSelector(el, doc);
1017
+ const rect = el.getBoundingClientRect();
1018
+ const boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
1019
+ const reactComponents = includeReact ? getComponentChain(el).join(" > ") : "";
1020
+ const source = includeReact ? getSourceInfo(el) : null;
1021
+ return {
1022
+ element: el.tagName.toLowerCase(),
1023
+ elementPath: short,
1024
+ fullPath: full,
1025
+ cssClasses: typeof el.className === "string" ? el.className.trim() : "",
1026
+ computedStyles: collectComputedStyles(el, detail),
1027
+ accessibility: collectAccessibility(el),
1028
+ nearbyText: collectNearbyText(el),
1029
+ selectedText: options.selectedText ?? collectSelectedText(doc),
1030
+ boundingBox,
1031
+ isFixed: hasFixedAncestor(el),
1032
+ reactComponents,
1033
+ sourceFile: source?.fileName ?? "",
1034
+ sourceLine: source?.lineNumber ?? 0,
1035
+ sourceColumn: source?.columnNumber ?? 0
1036
+ };
1037
+ }
1038
+ function perfMark2(name) {
1039
+ if (typeof performance !== "undefined" && typeof performance.mark === "function") {
1040
+ try {
1041
+ performance.mark(name);
1042
+ } catch {
1043
+ }
1044
+ }
1045
+ }
1046
+ function hasFixedAncestor(el) {
1047
+ const doc = el.ownerDocument;
1048
+ const win = doc?.defaultView ?? globalThis;
1049
+ if (typeof win.getComputedStyle !== "function") return false;
1050
+ let cur = el;
1051
+ for (let i = 0; cur && i < 8; i++) {
1052
+ const pos = win.getComputedStyle(cur).getPropertyValue("position");
1053
+ if (POSITIONED_FIXED.has(pos)) return true;
1054
+ cur = cur.parentElement;
1055
+ }
1056
+ return false;
1057
+ }
1058
+
1059
+ // packages/react/src/internal/ClicklyRoot.tsx
1060
+ var import_react10 = require("react");
1061
+
1062
+ // packages/react/src/internal/Toolbar.tsx
1063
+ var import_react7 = require("react");
1064
+
1065
+ // packages/react/src/hooks/useDraggable.ts
1066
+ var import_react = require("react");
13
1067
  function useDraggable(defaultPos, size) {
14
- const [position, setPosition] = react.useState(defaultPos);
15
- const [isDragging, setDragging] = react.useState(false);
16
- const startRef = react.useRef(null);
17
- const onPointerDown = react.useCallback(
1068
+ const [position, setPosition] = (0, import_react.useState)(defaultPos);
1069
+ const [isDragging, setDragging] = (0, import_react.useState)(false);
1070
+ const startRef = (0, import_react.useRef)(null);
1071
+ const onPointerDown = (0, import_react.useCallback)(
18
1072
  (e) => {
19
1073
  e.preventDefault();
20
1074
  e.target.setPointerCapture?.(e.pointerId);
@@ -26,7 +1080,7 @@ function useDraggable(defaultPos, size) {
26
1080
  },
27
1081
  [position]
28
1082
  );
29
- react.useEffect(() => {
1083
+ (0, import_react.useEffect)(() => {
30
1084
  if (!isDragging) return;
31
1085
  const onMove = (e) => {
32
1086
  const s = startRef.current;
@@ -64,8 +1118,11 @@ function useDraggable(defaultPos, size) {
64
1118
  function clamp(n, lo, hi) {
65
1119
  return Math.max(lo, Math.min(hi, n));
66
1120
  }
1121
+
1122
+ // packages/react/src/state/useEngineState.ts
1123
+ var import_react2 = require("react");
67
1124
  function useEngineState(engine) {
68
- return react.useSyncExternalStore(
1125
+ return (0, import_react2.useSyncExternalStore)(
69
1126
  (cb) => engine ? engine.subscribe(cb) : () => {
70
1127
  },
71
1128
  () => engine ? engine.getSnapshot() : IDLE,
@@ -73,7 +1130,120 @@ function useEngineState(engine) {
73
1130
  );
74
1131
  }
75
1132
  var IDLE = { kind: "idle" };
76
- var useAnnotations = zustand.create((set, get) => ({
1133
+
1134
+ // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/vanilla.mjs
1135
+ var createStoreImpl = (createState) => {
1136
+ let state;
1137
+ const listeners = /* @__PURE__ */ new Set();
1138
+ const setState = (partial, replace) => {
1139
+ const nextState = typeof partial === "function" ? partial(state) : partial;
1140
+ if (!Object.is(nextState, state)) {
1141
+ const previousState = state;
1142
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
1143
+ listeners.forEach((listener) => listener(state, previousState));
1144
+ }
1145
+ };
1146
+ const getState = () => state;
1147
+ const getInitialState = () => initialState2;
1148
+ const subscribe = (listener) => {
1149
+ listeners.add(listener);
1150
+ return () => listeners.delete(listener);
1151
+ };
1152
+ const api = { setState, getState, getInitialState, subscribe };
1153
+ const initialState2 = state = createState(setState, getState, api);
1154
+ return api;
1155
+ };
1156
+ var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
1157
+
1158
+ // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react.mjs
1159
+ var import_react3 = __toESM(require("react"), 1);
1160
+ var identity = (arg) => arg;
1161
+ function useStore(api, selector = identity) {
1162
+ const slice = import_react3.default.useSyncExternalStore(
1163
+ api.subscribe,
1164
+ import_react3.default.useCallback(() => selector(api.getState()), [api, selector]),
1165
+ import_react3.default.useCallback(() => selector(api.getInitialState()), [api, selector])
1166
+ );
1167
+ import_react3.default.useDebugValue(slice);
1168
+ return slice;
1169
+ }
1170
+ var createImpl = (createState) => {
1171
+ const api = createStore(createState);
1172
+ const useBoundStore = (selector) => useStore(api, selector);
1173
+ Object.assign(useBoundStore, api);
1174
+ return useBoundStore;
1175
+ };
1176
+ var create = ((createState) => createState ? createImpl(createState) : createImpl);
1177
+
1178
+ // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
1179
+ var import_react4 = __toESM(require("react"), 1);
1180
+
1181
+ // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/vanilla/shallow.mjs
1182
+ var isIterable = (obj) => Symbol.iterator in obj;
1183
+ var hasIterableEntries = (value) => (
1184
+ // HACK: avoid checking entries type
1185
+ "entries" in value
1186
+ );
1187
+ var compareEntries = (valueA, valueB) => {
1188
+ const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
1189
+ const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
1190
+ if (mapA.size !== mapB.size) {
1191
+ return false;
1192
+ }
1193
+ for (const [key, value] of mapA) {
1194
+ if (!mapB.has(key) || !Object.is(value, mapB.get(key))) {
1195
+ return false;
1196
+ }
1197
+ }
1198
+ return true;
1199
+ };
1200
+ var compareIterables = (valueA, valueB) => {
1201
+ const iteratorA = valueA[Symbol.iterator]();
1202
+ const iteratorB = valueB[Symbol.iterator]();
1203
+ let nextA = iteratorA.next();
1204
+ let nextB = iteratorB.next();
1205
+ while (!nextA.done && !nextB.done) {
1206
+ if (!Object.is(nextA.value, nextB.value)) {
1207
+ return false;
1208
+ }
1209
+ nextA = iteratorA.next();
1210
+ nextB = iteratorB.next();
1211
+ }
1212
+ return !!nextA.done && !!nextB.done;
1213
+ };
1214
+ function shallow(valueA, valueB) {
1215
+ if (Object.is(valueA, valueB)) {
1216
+ return true;
1217
+ }
1218
+ if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
1219
+ return false;
1220
+ }
1221
+ if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
1222
+ return false;
1223
+ }
1224
+ if (isIterable(valueA) && isIterable(valueB)) {
1225
+ if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
1226
+ return compareEntries(valueA, valueB);
1227
+ }
1228
+ return compareIterables(valueA, valueB);
1229
+ }
1230
+ return compareEntries(
1231
+ { entries: () => Object.entries(valueA) },
1232
+ { entries: () => Object.entries(valueB) }
1233
+ );
1234
+ }
1235
+
1236
+ // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
1237
+ function useShallow(selector) {
1238
+ const prev = import_react4.default.useRef(void 0);
1239
+ return (state) => {
1240
+ const next = selector(state);
1241
+ return shallow(prev.current, next) ? prev.current : prev.current = next;
1242
+ };
1243
+ }
1244
+
1245
+ // packages/react/src/state/annotations.ts
1246
+ var useAnnotations = create((set, get) => ({
77
1247
  byId: {},
78
1248
  order: [],
79
1249
  add: (a) => set((s) => ({
@@ -99,9 +1269,11 @@ var useAnnotations = zustand.create((set, get) => ({
99
1269
  }));
100
1270
  function useAnnotationsList() {
101
1271
  return useAnnotations(
102
- shallow.useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
1272
+ useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
103
1273
  );
104
1274
  }
1275
+
1276
+ // packages/react/src/state/settings.ts
105
1277
  var DEFAULTS = {
106
1278
  outputDetail: "standard",
107
1279
  copyOnAdd: true,
@@ -127,7 +1299,7 @@ function persist(s) {
127
1299
  } catch {
128
1300
  }
129
1301
  }
130
- var useSettings = zustand.create((set) => ({
1302
+ var useSettings = create((set) => ({
131
1303
  ...load(),
132
1304
  set: (patch) => set((cur) => {
133
1305
  const next = { ...cur, ...patch };
@@ -140,7 +1312,7 @@ var useSettings = zustand.create((set) => ({
140
1312
  }
141
1313
  }));
142
1314
 
143
- // src/output/markdown.ts
1315
+ // packages/react/src/output/markdown.ts
144
1316
  function annotationsToMarkdown(annotations, detail = "standard") {
145
1317
  if (!annotations.length) return "(no annotations)";
146
1318
  return annotations.map((a, i) => formatOne(a, i + 1, detail)).join("\n\n");
@@ -163,23 +1335,29 @@ function formatOne(a, index, detail) {
163
1335
  }
164
1336
  if (detail === "detailed" || detail === "forensic") {
165
1337
  if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
166
- if (a.nearbyText) lines.push(`**Nearby text:** ${truncate(a.nearbyText, 120)}`);
1338
+ if (a.nearbyText) lines.push(`**Nearby text:** ${truncate2(a.nearbyText, 120)}`);
167
1339
  }
168
1340
  if (detail === "forensic" && a.computedStyles) {
169
- lines.push("**Computed styles:**\n```\n" + a.computedStyles + "\n```");
1341
+ lines.push("**Computed styles:**\n```css\n" + a.computedStyles + "\n```");
170
1342
  }
171
1343
  lines.push(`**Feedback:** ${a.comment}`);
1344
+ if (a.suggestedCss) {
1345
+ lines.push("**Suggested CSS:**\n```css\n" + a.suggestedCss + "\n```");
1346
+ }
172
1347
  if (a.severity) lines.push(`**Severity:** ${a.severity}`);
173
1348
  return lines.join("\n");
174
1349
  }
175
- function truncate(s, n) {
1350
+ function truncate2(s, n) {
176
1351
  return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
177
1352
  }
1353
+
1354
+ // packages/react/src/internal/icons.tsx
1355
+ var import_jsx_runtime = require("react/jsx-runtime");
178
1356
  function Icon({
179
1357
  children,
180
1358
  size = 16
181
1359
  }) {
182
- return /* @__PURE__ */ jsxRuntime.jsx(
1360
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
183
1361
  "svg",
184
1362
  {
185
1363
  width: size,
@@ -195,136 +1373,246 @@ function Icon({
195
1373
  }
196
1374
  );
197
1375
  }
198
- var IconCursor = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4l6 16 2-7 7-2z" }) });
199
- var IconLayers = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
200
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
201
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12l9 5 9-5" }),
202
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 17l9 5 9-5" })
1376
+ var IconCursor = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4l6 16 2-7 7-2z" }) });
1377
+ var IconLayers = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { children: [
1378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
1379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12l9 5 9-5" }),
1380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 17l9 5 9-5" })
203
1381
  ] });
204
- var IconSquare = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4", y: "4", width: "16", height: "16", rx: "2", strokeDasharray: "3 3" }) });
205
- var IconCopy = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
206
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
207
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
1382
+ var IconCopy = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { children: [
1383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
208
1385
  ] });
209
- var IconTrash = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.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" }) });
210
- var IconSettings = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
211
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" }),
212
- /* @__PURE__ */ jsxRuntime.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" })
1386
+ var IconTrash = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.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" }) });
1387
+ var IconSettings = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { children: [
1388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
1389
+ /* @__PURE__ */ (0, import_jsx_runtime.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" })
213
1390
  ] });
214
- var IconGrip = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
215
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "6", r: "1" }),
216
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "12", r: "1" }),
217
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "18", r: "1" }),
218
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "6", r: "1" }),
219
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "12", r: "1" }),
220
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "18", r: "1" })
1391
+ var IconGrip = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { children: [
1392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "6", r: "1" }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "12", r: "1" }),
1394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "18", r: "1" }),
1395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "15", cy: "6", r: "1" }),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "15", cy: "12", r: "1" }),
1397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "15", cy: "18", r: "1" })
221
1398
  ] });
222
- var IconClose = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12" }) });
223
- var IconCheck = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12l5 5L20 7" }) });
224
- var IconList = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
1399
+ var IconClose = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) });
1400
+ var IconCheck = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12l5 5L20 7" }) });
1401
+ var IconList = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
1402
+
1403
+ // packages/react/src/internal/SettingsPopover.tsx
1404
+ var import_react5 = require("react");
1405
+ var import_jsx_runtime2 = require("react/jsx-runtime");
225
1406
  function SettingsPopover({ anchor, width, onClose }) {
226
- const ref = react.useRef(null);
1407
+ const ref = (0, import_react5.useRef)(null);
227
1408
  const s = useSettings();
228
- react.useEffect(() => {
1409
+ (0, import_react5.useEffect)(() => {
229
1410
  const onDown = (e) => {
230
- if (ref.current && !ref.current.contains(e.target)) onClose();
1411
+ if (ref.current && e.composedPath().includes(ref.current)) return;
1412
+ onClose();
231
1413
  };
232
1414
  window.addEventListener("pointerdown", onDown, true);
233
1415
  return () => window.removeEventListener("pointerdown", onDown, true);
234
1416
  }, [onClose]);
235
- const top = Math.max(8, anchor.y - 200);
236
- const left = Math.min(window.innerWidth - 268, anchor.x + width - 260);
237
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: "clickly-popover", style: { left, top }, children: [
238
- /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Settings" }),
239
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "field", children: [
240
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "clickly-detail", children: "Output detail" }),
241
- /* @__PURE__ */ jsxRuntime.jsxs(
1417
+ const PANEL_H = 330;
1418
+ const top = Math.max(8, anchor.y - PANEL_H - 12);
1419
+ const left = Math.min(window.innerWidth - 300, Math.max(8, anchor.x + width - 284));
1420
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref, className: "clickly-settings", style: { left, top }, children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-header", children: [
1422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "settings-title", children: "Settings" }),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "settings-close", onClick: onClose, "aria-label": "Close settings", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconClose, {}) })
1424
+ ] }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-section", children: [
1426
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "settings-label", htmlFor: "clickly-detail", children: [
1427
+ "Output detail",
1428
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "settings-hint", children: "How much metadata is captured" })
1429
+ ] }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
242
1431
  "select",
243
1432
  {
244
1433
  id: "clickly-detail",
1434
+ className: "settings-select",
245
1435
  value: s.outputDetail,
246
1436
  onChange: (e) => s.set({ outputDetail: e.target.value }),
247
1437
  children: [
248
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "compact", children: "Compact" }),
249
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "standard", children: "Standard" }),
250
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "detailed", children: "Detailed" }),
251
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "forensic", children: "Forensic" })
1438
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "compact", children: "Compact" }),
1439
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "standard", children: "Standard" }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "detailed", children: "Detailed" }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "forensic", children: "Forensic" })
252
1442
  ]
253
1443
  }
254
1444
  )
255
1445
  ] }),
256
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "field", children: [
257
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "clickly-copy", children: "Copy on add" }),
258
- /* @__PURE__ */ jsxRuntime.jsx(
259
- "input",
260
- {
261
- id: "clickly-copy",
262
- type: "checkbox",
263
- checked: s.copyOnAdd,
264
- onChange: (e) => s.set({ copyOnAdd: e.target.checked })
265
- }
266
- )
267
- ] }),
268
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "field", children: [
269
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "clickly-react", children: "React components" }),
270
- /* @__PURE__ */ jsxRuntime.jsx(
271
- "input",
272
- {
273
- id: "clickly-react",
274
- type: "checkbox",
275
- checked: s.showReactComponents,
276
- onChange: (e) => s.set({ showReactComponents: e.target.checked })
277
- }
278
- )
1446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "settings-divider" }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-section", children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row", children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row-label", children: [
1450
+ "Copy on add",
1451
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "settings-hint", children: "Auto-copy markdown when annotating" })
1452
+ ] }),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "clickly-toggle", children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1455
+ "input",
1456
+ {
1457
+ type: "checkbox",
1458
+ checked: s.copyOnAdd,
1459
+ onChange: (e) => s.set({ copyOnAdd: e.target.checked })
1460
+ }
1461
+ ),
1462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "toggle-track" })
1463
+ ] })
1464
+ ] }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row", children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row-label", children: [
1467
+ "React components",
1468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "settings-hint", children: "Include component tree in output" })
1469
+ ] }),
1470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "clickly-toggle", children: [
1471
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1472
+ "input",
1473
+ {
1474
+ type: "checkbox",
1475
+ checked: s.showReactComponents,
1476
+ onChange: (e) => s.set({ showReactComponents: e.target.checked })
1477
+ }
1478
+ ),
1479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "toggle-track" })
1480
+ ] })
1481
+ ] })
279
1482
  ] }),
280
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "field", children: [
281
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "clickly-color", children: "Marker color" }),
282
- /* @__PURE__ */ jsxRuntime.jsx(
283
- "input",
284
- {
285
- id: "clickly-color",
286
- type: "color",
287
- value: s.markerColor,
288
- onChange: (e) => s.set({ markerColor: e.target.value })
289
- }
290
- )
291
- ] })
1483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "settings-divider" }),
1484
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "settings-section", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row", children: [
1485
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "settings-row-label", children: [
1486
+ "Marker color",
1487
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "settings-hint", children: "Color used for annotation pins" })
1488
+ ] }),
1489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "settings-color-wrap", "aria-label": "Marker color", children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "color-swatch", style: { background: s.markerColor } }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1492
+ "input",
1493
+ {
1494
+ type: "color",
1495
+ value: s.markerColor,
1496
+ onChange: (e) => s.set({ markerColor: e.target.value })
1497
+ }
1498
+ )
1499
+ ] })
1500
+ ] }) })
292
1501
  ] });
293
1502
  }
1503
+
1504
+ // packages/react/src/internal/AnnotationList.tsx
1505
+ var import_react6 = require("react");
1506
+ var import_jsx_runtime3 = require("react/jsx-runtime");
294
1507
  function AnnotationList({ anchor, width, onClose }) {
295
1508
  const items = useAnnotationsList();
296
1509
  const remove = useAnnotations((s) => s.remove);
297
- const ref = react.useRef(null);
298
- react.useEffect(() => {
1510
+ const outputDetail = useSettings((s) => s.outputDetail);
1511
+ const ref = (0, import_react6.useRef)(null);
1512
+ (0, import_react6.useEffect)(() => {
299
1513
  const onDown = (e) => {
300
- if (ref.current && !ref.current.contains(e.target)) onClose();
1514
+ if (ref.current && e.composedPath().includes(ref.current)) return;
1515
+ onClose();
301
1516
  };
302
1517
  window.addEventListener("pointerdown", onDown, true);
303
1518
  return () => window.removeEventListener("pointerdown", onDown, true);
304
1519
  }, [onClose]);
305
- const top = Math.max(8, anchor.y - 8 - Math.min(window.innerHeight * 0.5, items.length * 60 + 40));
306
- const left = Math.min(window.innerWidth - 328, anchor.x);
307
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "clickly-list", style: { left, top }, children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty", children: "No annotations yet." }) : items.map((a, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row", children: [
308
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "num", children: [
309
- "#",
310
- i + 1
1520
+ const estimatedHeight = Math.min(window.innerHeight * 0.55, items.length * 88 + 48);
1521
+ const top = Math.max(8, anchor.y - estimatedHeight - 12);
1522
+ const left = Math.min(window.innerWidth - 336, Math.max(8, anchor.x));
1523
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref, className: "clickly-list", style: { left, top }, children: [
1524
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "list-header", children: [
1525
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "list-title", children: "Annotations" }),
1526
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "list-count", children: items.length })
311
1527
  ] }),
312
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "body", children: [
313
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "selector", children: a.elementPath }),
314
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: a.comment })
1528
+ items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "list-empty", children: "No annotations yet." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "list-items", children: items.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1529
+ AnnotationCard,
1530
+ {
1531
+ annotation: a,
1532
+ index: i + 1,
1533
+ outputDetail,
1534
+ onRemove: () => remove(a.id)
1535
+ },
1536
+ a.id
1537
+ )) })
1538
+ ] });
1539
+ }
1540
+ function AnnotationCard({
1541
+ annotation: a,
1542
+ index,
1543
+ outputDetail,
1544
+ onRemove
1545
+ }) {
1546
+ const [copied, setCopied] = (0, import_react6.useState)(false);
1547
+ const copyOne = async () => {
1548
+ const md = formatOne(a, index, outputDetail);
1549
+ try {
1550
+ await navigator.clipboard.writeText(md);
1551
+ setCopied(true);
1552
+ setTimeout(() => setCopied(false), 1500);
1553
+ } catch {
1554
+ }
1555
+ };
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "list-card", children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "list-card-header", children: [
1558
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "list-card-num", children: [
1559
+ "#",
1560
+ index
1561
+ ] }),
1562
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "list-card-path", children: a.elementPath }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "list-card-actions", children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1565
+ "button",
1566
+ {
1567
+ className: `list-action-btn${copied ? " copied" : ""}`,
1568
+ onClick: copyOne,
1569
+ title: `Copy annotation #${index} (${outputDetail})`,
1570
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "20 6 9 17 4 12" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconCopy, {})
1571
+ }
1572
+ ),
1573
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1574
+ "button",
1575
+ {
1576
+ className: "list-action-btn list-action-delete",
1577
+ onClick: onRemove,
1578
+ title: "Remove annotation",
1579
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconClose, {})
1580
+ }
1581
+ )
1582
+ ] })
315
1583
  ] }),
316
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "remove", onClick: () => remove(a.id), title: "Remove", children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {}) })
317
- ] }, a.id)) });
1584
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "list-card-comment", children: a.comment }),
1585
+ a.suggestedCss && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "list-card-css", children: [
1586
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "list-card-css-label", children: "CSS changes" }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className: "list-card-css-code", children: a.suggestedCss })
1588
+ ] })
1589
+ ] });
1590
+ }
1591
+
1592
+ // packages/react/src/internal/Toolbar.tsx
1593
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1594
+ var TOOLBAR_SIZE = { width: 320, height: 44 };
1595
+ function Tip({
1596
+ label,
1597
+ shortcut,
1598
+ children
1599
+ }) {
1600
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "clickly-tip", children: [
1601
+ children,
1602
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tip-bubble", "aria-hidden": "true", children: [
1603
+ label,
1604
+ shortcut && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("kbd", { children: shortcut })
1605
+ ] })
1606
+ ] });
318
1607
  }
319
- var TOOLBAR_SIZE = { width: 360, height: 40 };
320
1608
  function Toolbar({ engine, onCollapse }) {
321
1609
  const state = useEngineState(engine);
322
1610
  const annotations = useAnnotationsList();
323
1611
  const clearAnnotations = useAnnotations((s) => s.clear);
324
1612
  const outputDetail = useSettings((s) => s.outputDetail);
325
- const [showSettings, setShowSettings] = react.useState(false);
326
- const [showList, setShowList] = react.useState(false);
327
- const anchorRef = react.useRef(null);
1613
+ const [showSettings, setShowSettings] = (0, import_react7.useState)(false);
1614
+ const [showList, setShowList] = (0, import_react7.useState)(false);
1615
+ const anchorRef = (0, import_react7.useRef)(null);
328
1616
  const { position, handleProps } = useDraggable(
329
1617
  {
330
1618
  x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
@@ -346,11 +1634,7 @@ function Toolbar({ engine, onCollapse }) {
346
1634
  } catch {
347
1635
  }
348
1636
  };
349
- const onClear = () => {
350
- if (annotations.length === 0) return;
351
- if (confirm(`Clear ${annotations.length} annotation(s)?`)) clearAnnotations();
352
- };
353
- return /* @__PURE__ */ jsxRuntime.jsxs(
1637
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
354
1638
  "div",
355
1639
  {
356
1640
  ref: anchorRef,
@@ -358,134 +1642,112 @@ function Toolbar({ engine, onCollapse }) {
358
1642
  style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
359
1643
  "aria-label": "Clickly toolbar",
360
1644
  children: [
361
- /* @__PURE__ */ jsxRuntime.jsx(
1645
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Move", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
362
1646
  "div",
363
1647
  {
364
1648
  className: "grip",
365
1649
  ...handleProps,
366
- title: "Drag to move",
367
1650
  role: "separator",
368
1651
  "aria-orientation": "vertical",
369
- children: /* @__PURE__ */ jsxRuntime.jsx(IconGrip, {})
1652
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconGrip, {})
370
1653
  }
371
- ),
372
- /* @__PURE__ */ jsxRuntime.jsx(
1654
+ ) }),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Select", shortcut: "1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
373
1656
  "button",
374
1657
  {
375
1658
  className: `clickly-btn icon-only${currentMode === "single" ? " is-active" : ""}`,
376
1659
  onClick: () => setMode("single"),
377
- title: "Single (1)",
378
1660
  "aria-label": "Single-element selection mode",
379
1661
  "aria-pressed": currentMode === "single",
380
- children: /* @__PURE__ */ jsxRuntime.jsx(IconCursor, {})
1662
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCursor, {})
381
1663
  }
382
- ),
383
- /* @__PURE__ */ jsxRuntime.jsx(
1664
+ ) }),
1665
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Multi-select", shortcut: "2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
384
1666
  "button",
385
1667
  {
386
1668
  className: `clickly-btn icon-only${currentMode === "multi" ? " is-active" : ""}`,
387
1669
  onClick: () => setMode("multi"),
388
- title: "Multi-select (2 / shift-click)",
389
1670
  "aria-label": "Multi-element selection mode",
390
1671
  "aria-pressed": currentMode === "multi",
391
- children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, {})
392
- }
393
- ),
394
- /* @__PURE__ */ jsxRuntime.jsx(
395
- "button",
396
- {
397
- className: `clickly-btn icon-only${currentMode === "area" ? " is-active" : ""}`,
398
- onClick: () => setMode("area"),
399
- title: "Area drag (3)",
400
- "aria-label": "Area drag-selection mode",
401
- "aria-pressed": currentMode === "area",
402
- children: /* @__PURE__ */ jsxRuntime.jsx(IconSquare, {})
1672
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconLayers, {})
403
1673
  }
404
- ),
405
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divider" }),
406
- pinnedCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
407
- /* @__PURE__ */ jsxRuntime.jsxs(
1674
+ ) }),
1675
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" }),
1676
+ pinnedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1677
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: `Annotate ${pinnedCount} element(s)`, shortcut: "\u21B5", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
408
1678
  "button",
409
1679
  {
410
1680
  className: "clickly-btn primary-pinned",
411
1681
  onClick: () => engine.annotatePinned(),
412
- title: `Annotate the ${pinnedCount} pinned element(s) (Enter)`,
413
1682
  children: [
414
- /* @__PURE__ */ jsxRuntime.jsx(IconCheck, {}),
415
- "Annotate (",
416
- pinnedCount,
417
- ")"
1683
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCheck, {}),
1684
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: pinnedCount })
418
1685
  ]
419
1686
  }
420
- ),
421
- /* @__PURE__ */ jsxRuntime.jsx(
1687
+ ) }),
1688
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Clear selection", shortcut: "Esc", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
422
1689
  "button",
423
1690
  {
424
1691
  className: "clickly-btn icon-only",
425
1692
  onClick: () => engine.clearPinned(),
426
- title: "Clear pinned (Esc)",
427
- children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {})
1693
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconClose, {})
428
1694
  }
429
- ),
430
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divider" })
1695
+ ) }),
1696
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" })
431
1697
  ] }),
432
- /* @__PURE__ */ jsxRuntime.jsxs(
1698
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
433
1699
  "button",
434
1700
  {
435
- className: "clickly-btn",
1701
+ className: "clickly-btn icon-only",
436
1702
  onClick: () => setShowList((v) => !v),
437
- title: "Annotations (L)",
1703
+ "aria-label": "Show annotation list",
438
1704
  children: [
439
- /* @__PURE__ */ jsxRuntime.jsx(IconList, {}),
440
- annotations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "clickly-counter", children: annotations.length })
1705
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconList, {}),
1706
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "clickly-counter", children: annotations.length })
441
1707
  ]
442
1708
  }
443
- ),
444
- /* @__PURE__ */ jsxRuntime.jsx(
1709
+ ) }),
1710
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Copy feedback", shortcut: "C", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
445
1711
  "button",
446
1712
  {
447
1713
  className: "clickly-btn icon-only",
448
1714
  onClick: onCopy,
449
- title: "Copy markdown (C)",
450
1715
  "aria-label": "Copy all annotations as markdown",
451
1716
  disabled: annotations.length === 0,
452
- children: /* @__PURE__ */ jsxRuntime.jsx(IconCopy, {})
1717
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCopy, {})
453
1718
  }
454
- ),
455
- /* @__PURE__ */ jsxRuntime.jsx(
1719
+ ) }),
1720
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Clear all", shortcut: "X", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
456
1721
  "button",
457
1722
  {
458
1723
  className: "clickly-btn icon-only",
459
- onClick: onClear,
460
- title: "Clear (X)",
1724
+ onClick: () => clearAnnotations(),
461
1725
  "aria-label": "Clear all annotations",
462
1726
  disabled: annotations.length === 0,
463
- children: /* @__PURE__ */ jsxRuntime.jsx(IconTrash, {})
1727
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconTrash, {})
464
1728
  }
465
- ),
466
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divider" }),
467
- /* @__PURE__ */ jsxRuntime.jsx(
1729
+ ) }),
1730
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" }),
1731
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
468
1732
  "button",
469
1733
  {
470
- className: "clickly-btn icon-only",
1734
+ className: `clickly-btn icon-only${showSettings ? " is-active" : ""}`,
471
1735
  onClick: () => setShowSettings((v) => !v),
472
- title: "Settings",
473
1736
  "aria-label": "Open settings",
474
1737
  "aria-expanded": showSettings,
475
- children: /* @__PURE__ */ jsxRuntime.jsx(IconSettings, {})
1738
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconSettings, {})
476
1739
  }
477
- ),
478
- /* @__PURE__ */ jsxRuntime.jsx(
1740
+ ) }),
1741
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Close", shortcut: "Esc", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
479
1742
  "button",
480
1743
  {
481
1744
  className: "clickly-btn icon-only",
482
1745
  onClick: onCollapse,
483
- title: "Collapse (Esc)",
484
1746
  "aria-label": "Collapse Clickly toolbar",
485
- children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {})
1747
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconClose, {})
486
1748
  }
487
- ),
488
- showSettings && /* @__PURE__ */ jsxRuntime.jsx(
1749
+ ) }),
1750
+ showSettings && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
489
1751
  SettingsPopover,
490
1752
  {
491
1753
  anchor: { x: position.x, y: position.y },
@@ -493,7 +1755,7 @@ function Toolbar({ engine, onCollapse }) {
493
1755
  onClose: () => setShowSettings(false)
494
1756
  }
495
1757
  ),
496
- showList && /* @__PURE__ */ jsxRuntime.jsx(
1758
+ showList && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
497
1759
  AnnotationList,
498
1760
  {
499
1761
  anchor: { x: position.x, y: position.y },
@@ -505,9 +1767,12 @@ function Toolbar({ engine, onCollapse }) {
505
1767
  }
506
1768
  );
507
1769
  }
1770
+
1771
+ // packages/react/src/internal/CollapsedFAB.tsx
1772
+ var import_jsx_runtime5 = require("react/jsx-runtime");
508
1773
  function CollapsedFAB({ onExpand }) {
509
1774
  const annotations = useAnnotationsList();
510
- return /* @__PURE__ */ jsxRuntime.jsxs(
1775
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
511
1776
  "button",
512
1777
  {
513
1778
  className: "clickly-fab",
@@ -515,27 +1780,75 @@ function CollapsedFAB({ onExpand }) {
515
1780
  title: "Open Clickly (\u2318/Ctrl+Shift+F)",
516
1781
  "aria-label": "Open Clickly toolbar",
517
1782
  children: [
518
- /* @__PURE__ */ jsxRuntime.jsx(IconCursor, {}),
519
- annotations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "clickly-fab-badge", children: annotations.length })
1783
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconCursor, {}),
1784
+ annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "clickly-fab-badge", children: annotations.length })
520
1785
  ]
521
1786
  }
522
1787
  );
523
1788
  }
1789
+
1790
+ // packages/react/src/internal/AnnotationPopup.tsx
1791
+ var import_react8 = require("react");
1792
+ var import_nanoid = require("nanoid");
1793
+ var import_jsx_runtime6 = require("react/jsx-runtime");
524
1794
  var POPUP_W = 320;
525
- var POPUP_H_EST = 200;
1795
+ var POPUP_H_EST = 240;
526
1796
  function AnnotationPopup({ engine }) {
527
1797
  const state = useEngineState(engine);
528
1798
  const addAnnotation = useAnnotations((s) => s.add);
529
1799
  const copyOnAdd = useSettings((s) => s.copyOnAdd);
530
1800
  const outputDetail = useSettings((s) => s.outputDetail);
531
- const [comment, setComment] = react.useState("");
532
- const taRef = react.useRef(null);
533
- react.useEffect(() => {
1801
+ const [comment, setComment] = (0, import_react8.useState)("");
1802
+ const [showStyles, setShowStyles] = (0, import_react8.useState)(false);
1803
+ const [editMode, setEditMode] = (0, import_react8.useState)(false);
1804
+ const [styles, setStyles] = (0, import_react8.useState)({});
1805
+ const [editedStyles, setEditedStyles] = (0, import_react8.useState)({});
1806
+ const targetElRef = (0, import_react8.useRef)(null);
1807
+ const taRef = (0, import_react8.useRef)(null);
1808
+ const popupRef = (0, import_react8.useRef)(null);
1809
+ (0, import_react8.useEffect)(() => {
534
1810
  if (state.kind === "annotating") {
535
1811
  setComment("");
1812
+ setShowStyles(false);
1813
+ setEditMode(false);
1814
+ setEditedStyles({});
1815
+ const sel = engine.resolveSelection();
1816
+ const el = sel?.kind === "single" ? sel.element : sel?.kind === "multi" || sel?.kind === "area" ? sel.elements[0] : null;
1817
+ targetElRef.current = el instanceof HTMLElement ? el : null;
1818
+ if (el) {
1819
+ setStyles(collectComputedStyles(el, "standard"));
1820
+ } else {
1821
+ setStyles({});
1822
+ }
536
1823
  requestAnimationFrame(() => taRef.current?.focus());
537
1824
  }
538
- }, [state.kind]);
1825
+ }, [state.kind, engine]);
1826
+ (0, import_react8.useEffect)(() => {
1827
+ if (state.kind !== "annotating") return;
1828
+ const onDown = (e) => {
1829
+ if (popupRef.current && e.composedPath().includes(popupRef.current)) return;
1830
+ engine.commit();
1831
+ };
1832
+ window.addEventListener("pointerdown", onDown, true);
1833
+ return () => window.removeEventListener("pointerdown", onDown, true);
1834
+ }, [state.kind, engine]);
1835
+ const onStyleChange = (prop, value) => {
1836
+ setEditedStyles((prev) => ({ ...prev, [prop]: value }));
1837
+ targetElRef.current?.style.setProperty(prop, value);
1838
+ };
1839
+ const revertProp = (prop) => {
1840
+ targetElRef.current?.style.removeProperty(prop);
1841
+ setEditedStyles((prev) => {
1842
+ const next = { ...prev };
1843
+ delete next[prop];
1844
+ return next;
1845
+ });
1846
+ };
1847
+ const cancel = () => {
1848
+ revertAll(targetElRef.current, editedStyles);
1849
+ setEditedStyles({});
1850
+ engine.commit();
1851
+ };
539
1852
  const onKey = (e) => {
540
1853
  if (e.key === "Escape") {
541
1854
  e.preventDefault();
@@ -546,20 +1859,22 @@ function AnnotationPopup({ engine }) {
546
1859
  submit();
547
1860
  }
548
1861
  };
549
- const cancel = () => engine.commit();
550
1862
  const submit = () => {
551
1863
  if (state.kind !== "annotating") return;
552
1864
  const sel = engine.resolveSelection();
553
1865
  if (!sel) return;
554
1866
  const elements = sel.kind === "single" ? [sel.element] : sel.elements;
555
1867
  if (elements.length === 0) return;
1868
+ const cssDiff = buildCssDiff(styles, editedStyles);
556
1869
  const sharedComment = comment.trim() || "(no comment)";
1870
+ revertAll(targetElRef.current, editedStyles);
1871
+ setEditedStyles({});
557
1872
  const isMulti = sel.kind !== "single";
558
1873
  const showReact = useSettings.getState().showReactComponents;
559
1874
  for (const el of elements) {
560
- const md = core.collectMetadata(el, { detail: outputDetail, includeReact: showReact });
1875
+ const md = collectMetadata(el, { detail: outputDetail, includeReact: showReact });
561
1876
  const annotation = {
562
- id: "ann_" + nanoid.nanoid(8),
1877
+ id: "ann_" + (0, import_nanoid.nanoid)(8),
563
1878
  comment: sharedComment,
564
1879
  element: md.element,
565
1880
  elementPath: md.elementPath,
@@ -581,7 +1896,8 @@ function AnnotationPopup({ engine }) {
581
1896
  sourceColumn: md.sourceColumn || void 0,
582
1897
  isMultiSelect: isMulti,
583
1898
  kind: "feedback",
584
- status: "pending"
1899
+ status: "pending",
1900
+ suggestedCss: cssDiff || void 0
585
1901
  };
586
1902
  addAnnotation(annotation);
587
1903
  }
@@ -591,29 +1907,118 @@ function AnnotationPopup({ engine }) {
591
1907
  }
592
1908
  engine.commit();
593
1909
  };
594
- const placement = react.useMemo(() => {
595
- if (state.kind !== "annotating") return null;
1910
+ const [placement, setPlacement] = (0, import_react8.useState)(null);
1911
+ const calcPlacement = (0, import_react8.useCallback)(() => {
1912
+ if (state.kind !== "annotating") {
1913
+ setPlacement(null);
1914
+ return;
1915
+ }
596
1916
  const sel = engine.resolveSelection();
597
- if (!sel) return null;
1917
+ if (!sel) {
1918
+ setPlacement(null);
1919
+ return;
1920
+ }
598
1921
  const rect = anchorRect(sel);
599
- if (!rect) return null;
600
- let top = rect.bottom + 8;
601
- let left = rect.left;
602
- if (top + POPUP_H_EST > window.innerHeight) {
603
- top = Math.max(8, rect.top - POPUP_H_EST - 8);
1922
+ if (!rect) {
1923
+ setPlacement(null);
1924
+ return;
604
1925
  }
605
- if (left + POPUP_W > window.innerWidth) {
606
- left = Math.max(8, window.innerWidth - POPUP_W - 8);
1926
+ const vw = window.innerWidth;
1927
+ const vh = window.innerHeight;
1928
+ const label = describeSelection(sel);
1929
+ const elementArea = rect.width * rect.height;
1930
+ if (elementArea > vw * vh * 0.7) {
1931
+ setPlacement({
1932
+ top: Math.round((vh - POPUP_H_EST) / 2),
1933
+ left: Math.round((vw - POPUP_W) / 2),
1934
+ label
1935
+ });
1936
+ return;
607
1937
  }
608
- return { top, left, label: describeSelection(sel) };
1938
+ let top = rect.bottom + 8;
1939
+ let left = rect.left;
1940
+ if (top + POPUP_H_EST > vh) top = Math.max(8, rect.top - POPUP_H_EST - 8);
1941
+ if (left + POPUP_W > vw) left = Math.max(8, vw - POPUP_W - 8);
1942
+ setPlacement({ top, left, label });
609
1943
  }, [state, engine]);
1944
+ (0, import_react8.useEffect)(() => {
1945
+ calcPlacement();
1946
+ }, [calcPlacement]);
1947
+ (0, import_react8.useEffect)(() => {
1948
+ if (state.kind !== "annotating") return;
1949
+ const onScroll = () => calcPlacement();
1950
+ window.addEventListener("scroll", onScroll, { capture: true, passive: true });
1951
+ return () => window.removeEventListener("scroll", onScroll, { capture: true });
1952
+ }, [state.kind, calcPlacement]);
610
1953
  if (state.kind !== "annotating" || !placement) return null;
611
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "clickly-popup", style: { top: placement.top, left: placement.left }, children: [
612
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "target-info", children: [
613
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: placement.label }),
614
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.selection.kind })
1954
+ const styleEntries = Object.entries(styles);
1955
+ const hasEdits = Object.keys(editedStyles).length > 0;
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: popupRef, className: "clickly-popup", style: { top: placement.top, left: placement.left }, children: [
1957
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1958
+ "div",
1959
+ {
1960
+ className: "popup-header",
1961
+ role: "button",
1962
+ "aria-expanded": showStyles,
1963
+ onClick: () => setShowStyles((v) => !v),
1964
+ children: [
1965
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "popup-chevron", children: showStyles ? "\u25BE" : "\u203A" }),
1966
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "popup-label", children: placement.label }),
1967
+ showStyles && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1968
+ "button",
1969
+ {
1970
+ className: `popup-edit-toggle${editMode ? " is-editing" : ""}`,
1971
+ onClick: (e) => {
1972
+ e.stopPropagation();
1973
+ setEditMode((v) => !v);
1974
+ },
1975
+ title: editMode ? "Done editing" : "Edit CSS live",
1976
+ "aria-label": editMode ? "Exit CSS edit mode" : "Edit CSS values live",
1977
+ children: editMode ? (
1978
+ // checkmark when in edit mode
1979
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "20 6 9 17 4 12" }) })
1980
+ ) : (
1981
+ // pencil icon
1982
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1983
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
1984
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
1985
+ ] })
1986
+ )
1987
+ }
1988
+ )
1989
+ ]
1990
+ }
1991
+ ),
1992
+ showStyles && styleEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "popup-styles", children: [
1993
+ styleEntries.map(([prop, origVal]) => {
1994
+ const currentVal = editedStyles[prop] ?? origVal;
1995
+ const changed = editedStyles[prop] !== void 0 && editedStyles[prop] !== origVal;
1996
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `style-row${changed ? " style-row--changed" : ""}`, children: [
1997
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "style-key", children: prop }),
1998
+ editMode ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1999
+ "input",
2000
+ {
2001
+ className: "style-val-input",
2002
+ value: currentVal,
2003
+ onChange: (e) => onStyleChange(prop, e.target.value),
2004
+ spellCheck: false,
2005
+ "aria-label": prop
2006
+ }
2007
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "style-val", children: currentVal }),
2008
+ changed && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2009
+ "button",
2010
+ {
2011
+ className: "style-revert-btn",
2012
+ onClick: () => revertProp(prop),
2013
+ title: "Revert to original",
2014
+ children: "\u21A9"
2015
+ }
2016
+ )
2017
+ ] }, prop);
2018
+ }),
2019
+ hasEdits && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "style-hint", children: "Changes are live on page. Cancel reverts them." })
615
2020
  ] }),
616
- /* @__PURE__ */ jsxRuntime.jsx(
2021
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
617
2022
  "textarea",
618
2023
  {
619
2024
  ref: taRef,
@@ -624,38 +2029,111 @@ function AnnotationPopup({ engine }) {
624
2029
  onKeyDown: onKey
625
2030
  }
626
2031
  ),
627
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row", children: [
628
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "ghost", onClick: cancel, children: "Cancel" }),
629
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "primary", onClick: submit, children: "Add" })
2032
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "row", children: [
2033
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "ghost", onClick: cancel, children: "Cancel" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "primary", onClick: submit, children: "Add" })
630
2035
  ] })
631
2036
  ] });
632
2037
  }
633
- function stringifyStyles(styles) {
634
- const entries = Object.entries(styles);
2038
+ function revertAll(el, edits) {
2039
+ if (!el) return;
2040
+ for (const prop of Object.keys(edits)) {
2041
+ el.style.removeProperty(prop);
2042
+ }
2043
+ }
2044
+ function buildCssDiff(original, edited) {
2045
+ const lines = [];
2046
+ for (const [prop, val] of Object.entries(edited)) {
2047
+ if (val !== original[prop]) {
2048
+ lines.push(` ${prop}: ${val}; /* was: ${original[prop] ?? "unset"} */`);
2049
+ }
2050
+ }
2051
+ if (lines.length === 0) return "";
2052
+ return `{
2053
+ ${lines.join("\n")}
2054
+ }`;
2055
+ }
2056
+ function stringifyStyles(s) {
2057
+ const entries = Object.entries(s);
635
2058
  if (entries.length === 0) return void 0;
636
2059
  return entries.map(([k, v]) => `${k}: ${v}`).join("; ");
637
2060
  }
638
2061
  function anchorRect(sel) {
639
2062
  if (!sel) return null;
640
2063
  if (sel.kind === "single") return sel.element.getBoundingClientRect();
641
- if (sel.kind === "area") {
642
- return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
643
- }
2064
+ if (sel.kind === "area") return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
644
2065
  if (sel.elements.length === 0) return null;
645
2066
  return sel.elements[0].getBoundingClientRect();
646
2067
  }
647
- function describeSelection(sel) {
648
- if (sel.kind === "single") {
649
- const m = core.collectMetadata(sel.element, { detail: "compact" });
650
- return m.elementPath;
2068
+ var TAG_LABELS2 = {
2069
+ p: "paragraph",
2070
+ h1: "heading",
2071
+ h2: "heading",
2072
+ h3: "heading",
2073
+ h4: "heading",
2074
+ h5: "heading",
2075
+ h6: "heading",
2076
+ a: "link",
2077
+ button: "button",
2078
+ input: "input",
2079
+ textarea: "textarea",
2080
+ select: "select",
2081
+ img: "image",
2082
+ video: "video",
2083
+ audio: "audio",
2084
+ form: "form",
2085
+ nav: "nav",
2086
+ header: "header",
2087
+ footer: "footer",
2088
+ main: "main",
2089
+ section: "section",
2090
+ article: "article",
2091
+ aside: "aside",
2092
+ ul: "list",
2093
+ ol: "list",
2094
+ li: "list item",
2095
+ table: "table",
2096
+ td: "cell",
2097
+ th: "header cell",
2098
+ span: "span",
2099
+ div: "div",
2100
+ label: "label",
2101
+ code: "code",
2102
+ pre: "code block",
2103
+ blockquote: "quote",
2104
+ strong: "bold",
2105
+ em: "italic",
2106
+ kbd: "key",
2107
+ svg: "svg",
2108
+ canvas: "canvas"
2109
+ };
2110
+ function describeElement2(el) {
2111
+ const tag = el.tagName.toLowerCase();
2112
+ const type = TAG_LABELS2[tag] ?? tag;
2113
+ const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
2114
+ if (text.length > 0) {
2115
+ const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
2116
+ return `${type}: "${preview}"`;
651
2117
  }
2118
+ if (el.id) return `${type}: #${el.id}`;
2119
+ const cls = el.classList[0];
2120
+ if (cls) return `${type}: .${cls}`;
2121
+ return type;
2122
+ }
2123
+ function describeSelection(sel) {
2124
+ if (sel.kind === "single") return describeElement2(sel.element);
652
2125
  if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
2126
+ if (sel.elements.length === 1) return describeElement2(sel.elements[0]);
653
2127
  return `${sel.elements.length} element(s)`;
654
2128
  }
2129
+
2130
+ // packages/react/src/internal/AnnotationPins.tsx
2131
+ var import_react9 = require("react");
2132
+ var import_jsx_runtime7 = require("react/jsx-runtime");
655
2133
  function AnnotationPins() {
656
2134
  const annotations = useAnnotationsList();
657
- const [, setVersion] = react.useState(0);
658
- react.useEffect(() => {
2135
+ const [, setVersion] = (0, import_react9.useState)(0);
2136
+ (0, import_react9.useEffect)(() => {
659
2137
  let raf = null;
660
2138
  const update = () => {
661
2139
  if (raf !== null) return;
@@ -672,40 +2150,151 @@ function AnnotationPins() {
672
2150
  window.removeEventListener("resize", update);
673
2151
  };
674
2152
  }, []);
675
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: annotations.map((a, i) => /* @__PURE__ */ jsxRuntime.jsx(Pin, { number: i + 1, annotation: a }, a.id)) });
2153
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: annotations.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Pin, { number: i + 1, annotation: a }, a.id)) });
2154
+ }
2155
+ var TAG_LABELS3 = {
2156
+ p: "paragraph",
2157
+ h1: "heading",
2158
+ h2: "heading",
2159
+ h3: "heading",
2160
+ h4: "heading",
2161
+ h5: "heading",
2162
+ h6: "heading",
2163
+ a: "link",
2164
+ button: "button",
2165
+ input: "input",
2166
+ textarea: "textarea",
2167
+ select: "select",
2168
+ img: "image",
2169
+ video: "video",
2170
+ audio: "audio",
2171
+ form: "form",
2172
+ nav: "nav",
2173
+ header: "header",
2174
+ footer: "footer",
2175
+ main: "main",
2176
+ section: "section",
2177
+ article: "article",
2178
+ aside: "aside",
2179
+ ul: "list",
2180
+ ol: "list",
2181
+ li: "list item",
2182
+ table: "table",
2183
+ td: "cell",
2184
+ th: "header cell",
2185
+ span: "span",
2186
+ div: "div",
2187
+ label: "label",
2188
+ code: "code",
2189
+ pre: "code block",
2190
+ blockquote: "quote"
2191
+ };
2192
+ function pinLabel(annotation) {
2193
+ const raw = (annotation.element ?? "").toLowerCase();
2194
+ const tag = raw.split(/[.#\s]/)[0] ?? "";
2195
+ return (TAG_LABELS3[tag] ?? tag) || "element";
676
2196
  }
677
2197
  function Pin({ number, annotation }) {
678
2198
  const remove = useAnnotations((s) => s.remove);
679
- const [hovered, setHovered] = react.useState(false);
2199
+ const update = useAnnotations((s) => s.update);
2200
+ const [hovered, setHovered] = (0, import_react9.useState)(false);
2201
+ const [editing, setEditing] = (0, import_react9.useState)(false);
2202
+ const [draft, setDraft] = (0, import_react9.useState)(annotation.comment);
2203
+ const taRef = (0, import_react9.useRef)(null);
2204
+ const editRef = (0, import_react9.useRef)(null);
680
2205
  const pos = resolvePosition(annotation);
681
2206
  if (!pos) return null;
682
- return /* @__PURE__ */ jsxRuntime.jsxs(
2207
+ const label = pinLabel(annotation);
2208
+ const openEdit = () => {
2209
+ setDraft(annotation.comment);
2210
+ setEditing(true);
2211
+ setHovered(false);
2212
+ requestAnimationFrame(() => taRef.current?.focus());
2213
+ };
2214
+ const closeEdit = () => setEditing(false);
2215
+ const save = () => {
2216
+ const trimmed = draft.trim();
2217
+ if (trimmed) update(annotation.id, { comment: trimmed });
2218
+ closeEdit();
2219
+ };
2220
+ const onKeyDown = (e) => {
2221
+ if (e.key === "Escape") closeEdit();
2222
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
2223
+ };
2224
+ (0, import_react9.useEffect)(() => {
2225
+ if (!editing) return;
2226
+ const onDown = (e) => {
2227
+ if (editRef.current && e.composedPath().includes(editRef.current)) return;
2228
+ closeEdit();
2229
+ };
2230
+ window.addEventListener("pointerdown", onDown, true);
2231
+ return () => window.removeEventListener("pointerdown", onDown, true);
2232
+ }, [editing]);
2233
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
683
2234
  "div",
684
2235
  {
685
2236
  className: "clickly-pin",
686
2237
  style: { left: pos.x, top: pos.y },
687
- onMouseEnter: () => setHovered(true),
2238
+ onMouseEnter: () => {
2239
+ if (!editing) setHovered(true);
2240
+ },
688
2241
  onMouseLeave: () => setHovered(false),
2242
+ onClick: openEdit,
689
2243
  role: "button",
690
2244
  tabIndex: 0,
691
- title: annotation.comment,
2245
+ "aria-label": `Annotation ${number}: ${annotation.comment}`,
692
2246
  children: [
693
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "clickly-pin-num", children: number }),
694
- hovered && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "clickly-pin-bubble", children: [
695
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "clickly-pin-comment", children: annotation.comment }),
696
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "clickly-pin-meta", children: annotation.elementPath }),
697
- /* @__PURE__ */ jsxRuntime.jsx(
698
- "button",
699
- {
700
- className: "clickly-pin-remove",
701
- onClick: (e) => {
702
- e.stopPropagation();
703
- remove(annotation.id);
704
- },
705
- children: "Remove"
706
- }
707
- )
708
- ] })
2247
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "clickly-pin-num", children: number }),
2248
+ hovered && !editing && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-preview", children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-preview-meta", children: [
2250
+ label,
2251
+ ": ",
2252
+ annotation.elementPath
2253
+ ] }),
2254
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pin-preview-comment", children: annotation.comment })
2255
+ ] }),
2256
+ editing && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2257
+ "div",
2258
+ {
2259
+ ref: editRef,
2260
+ className: "pin-edit",
2261
+ onClick: (e) => e.stopPropagation(),
2262
+ children: [
2263
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pin-edit-header", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "pin-edit-label", children: [
2264
+ label,
2265
+ ": ",
2266
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pin-edit-path", children: annotation.elementPath })
2267
+ ] }) }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2269
+ "textarea",
2270
+ {
2271
+ ref: taRef,
2272
+ className: "pin-edit-textarea",
2273
+ value: draft,
2274
+ onChange: (e) => setDraft(e.target.value),
2275
+ onKeyDown,
2276
+ placeholder: "Describe the issue\u2026",
2277
+ rows: 3
2278
+ }
2279
+ ),
2280
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-edit-actions", children: [
2281
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2282
+ "button",
2283
+ {
2284
+ className: "pin-edit-delete",
2285
+ onClick: () => remove(annotation.id),
2286
+ "aria-label": "Delete annotation",
2287
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconTrash, {})
2288
+ }
2289
+ ),
2290
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-edit-right", children: [
2291
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "pin-edit-cancel", onClick: closeEdit, children: "Cancel" }),
2292
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "pin-edit-save", onClick: save, children: "Save" })
2293
+ ] })
2294
+ ] })
2295
+ ]
2296
+ }
2297
+ )
709
2298
  ]
710
2299
  }
711
2300
  );
@@ -729,26 +2318,29 @@ function resolvePosition(a) {
729
2318
  }
730
2319
  return null;
731
2320
  }
2321
+
2322
+ // packages/react/src/internal/ClicklyRoot.tsx
2323
+ var import_jsx_runtime8 = require("react/jsx-runtime");
732
2324
  function ClicklyRoot({
733
2325
  engine,
734
2326
  host
735
2327
  }) {
736
- const [expanded, setExpanded] = react.useState(false);
2328
+ const [expanded, setExpanded] = (0, import_react10.useState)(false);
737
2329
  const clearAnnotations = useAnnotations((s) => s.clear);
738
2330
  const outputDetail = useSettings((s) => s.outputDetail);
739
2331
  const markerColor = useSettings((s) => s.markerColor);
740
2332
  const engineState = useEngineState(engine);
741
- react.useEffect(() => {
2333
+ (0, import_react10.useEffect)(() => {
742
2334
  host.style.setProperty("--clickly-hover", markerColor);
743
2335
  }, [host, markerColor]);
744
- react.useEffect(() => {
2336
+ (0, import_react10.useEffect)(() => {
745
2337
  if (expanded) {
746
2338
  if (engine.getSnapshot().kind === "idle") engine.activate("single");
747
2339
  } else {
748
2340
  if (engine.getSnapshot().kind !== "idle") engine.deactivate();
749
2341
  }
750
2342
  }, [expanded, engine]);
751
- react.useEffect(() => {
2343
+ (0, import_react10.useEffect)(() => {
752
2344
  const active = engineState.kind !== "idle";
753
2345
  if (active) document.body.setAttribute("data-clickly-active", "");
754
2346
  else document.body.removeAttribute("data-clickly-active");
@@ -766,7 +2358,7 @@ function ClicklyRoot({
766
2358
  document.body.removeAttribute("data-clickly-annotating");
767
2359
  };
768
2360
  }, [engineState]);
769
- react.useEffect(() => {
2361
+ (0, import_react10.useEffect)(() => {
770
2362
  const onKey = (e) => {
771
2363
  const tag = e.target?.tagName;
772
2364
  if (tag === "INPUT" || tag === "TEXTAREA") return;
@@ -809,16 +2401,16 @@ function ClicklyRoot({
809
2401
  window.addEventListener("keydown", onKey);
810
2402
  return () => window.removeEventListener("keydown", onKey);
811
2403
  }, [clearAnnotations, outputDetail, expanded, engine]);
812
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "clickly-ui", children: [
813
- /* @__PURE__ */ jsxRuntime.jsx(AnnotationPins, {}),
814
- expanded ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
815
- /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
816
- /* @__PURE__ */ jsxRuntime.jsx(AnnotationPopup, { engine })
817
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(CollapsedFAB, { onExpand: () => setExpanded(true) })
2404
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "clickly-ui", children: [
2405
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPins, {}),
2406
+ expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2407
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
2408
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPopup, { engine })
2409
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CollapsedFAB, { onExpand: () => setExpanded(true) })
818
2410
  ] });
819
2411
  }
820
2412
 
821
- // src/internal/styles.ts
2413
+ // packages/react/src/internal/styles.ts
822
2414
  var REACT_UI_CSS = `
823
2415
  .clickly-ui, .clickly-ui * { box-sizing: border-box; }
824
2416
 
@@ -876,72 +2468,151 @@ var REACT_UI_CSS = `
876
2468
  position: fixed;
877
2469
  display: flex;
878
2470
  align-items: center;
879
- gap: 4px;
880
- padding: 4px;
881
- background: rgba(15, 23, 42, 0.94);
2471
+ gap: 2px;
2472
+ padding: 6px;
2473
+ height: 44px;
2474
+ background: rgba(9, 14, 28, 0.97);
882
2475
  color: #f8fafc;
883
- border-radius: 12px;
2476
+ border-radius: 16px;
884
2477
  font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
885
- box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.06) inset;
2478
+ box-shadow:
2479
+ 0 16px 40px rgba(0,0,0,0.45),
2480
+ 0 0 0 1px rgba(255,255,255,0.08) inset,
2481
+ 0 1px 0 rgba(255,255,255,0.06) inset;
886
2482
  pointer-events: auto;
887
2483
  user-select: none;
888
2484
  z-index: 1;
889
- animation: clickly-fade-in 120ms ease-out;
2485
+ animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
890
2486
  }
891
2487
 
892
2488
  @keyframes clickly-fade-in {
893
- from { opacity: 0; transform: translateY(4px); }
894
- to { opacity: 1; transform: translateY(0); }
2489
+ from { opacity: 0; transform: translateY(6px) scale(0.97); }
2490
+ to { opacity: 1; transform: translateY(0) scale(1); }
895
2491
  }
896
2492
 
897
2493
  .clickly-toolbar .grip {
898
2494
  display: grid;
899
2495
  place-items: center;
900
- width: 24px;
901
- height: 28px;
902
- color: #94a3b8;
2496
+ width: 22px;
2497
+ height: 32px;
2498
+ color: #475569;
903
2499
  touch-action: none;
2500
+ cursor: grab;
2501
+ border-radius: 6px;
2502
+ transition: color 120ms ease;
904
2503
  }
2504
+ .clickly-toolbar .grip:hover { color: #94a3b8; }
2505
+ .clickly-toolbar .grip:active { cursor: grabbing; }
905
2506
 
906
2507
  .clickly-toolbar .divider {
907
2508
  width: 1px;
908
- height: 18px;
909
- background: rgba(255,255,255,0.12);
910
- margin: 0 2px;
2509
+ height: 20px;
2510
+ background: rgba(255,255,255,0.08);
2511
+ margin: 0 3px;
2512
+ flex-shrink: 0;
911
2513
  }
912
2514
 
913
2515
  .clickly-btn {
914
2516
  display: inline-flex;
915
2517
  align-items: center;
2518
+ justify-content: center;
916
2519
  gap: 4px;
917
- height: 28px;
2520
+ height: 32px;
918
2521
  padding: 0 8px;
919
2522
  background: transparent;
920
2523
  border: none;
921
- border-radius: 8px;
922
- color: #cbd5e1;
2524
+ border-radius: 10px;
2525
+ color: #94a3b8;
923
2526
  cursor: pointer;
924
2527
  font: inherit;
2528
+ transition: background 100ms ease, color 100ms ease;
2529
+ position: relative;
925
2530
  }
926
- .clickly-btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
927
- .clickly-btn:active { background: rgba(255,255,255,0.14); }
928
- .clickly-btn.is-active { background: #0ea5e9; color: #fff; }
929
- .clickly-btn[disabled] { opacity: 0.4; cursor: not-allowed; }
2531
+ .clickly-btn:hover { background: rgba(255,255,255,0.09); color: #e2e8f0; }
2532
+ .clickly-btn:active { background: rgba(255,255,255,0.15); color: #fff; transform: scale(0.96); }
2533
+ .clickly-btn.is-active {
2534
+ background: #0ea5e9;
2535
+ color: #fff;
2536
+ box-shadow: 0 0 0 1px rgba(14,165,233,0.4), 0 2px 8px rgba(14,165,233,0.3);
2537
+ }
2538
+ .clickly-btn.is-active:hover { background: #0284c7; }
2539
+ .clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
930
2540
 
931
2541
  .clickly-btn.icon-only {
932
- width: 28px;
2542
+ width: 32px;
933
2543
  padding: 0;
934
- justify-content: center;
935
2544
  }
936
2545
 
937
2546
  .clickly-btn.primary-pinned {
938
2547
  background: #10b981;
939
2548
  color: #fff;
2549
+ font-weight: 600;
2550
+ font-size: 12px;
2551
+ padding: 0 10px;
2552
+ gap: 5px;
2553
+ box-shadow: 0 0 0 1px rgba(16,185,129,0.4), 0 2px 8px rgba(16,185,129,0.25);
2554
+ }
2555
+ .clickly-btn.primary-pinned:hover { background: #059669; }
2556
+
2557
+ /* \u2500\u2500\u2500 Tooltip \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2558
+
2559
+ .clickly-tip {
2560
+ position: relative;
2561
+ display: inline-flex;
2562
+ align-items: center;
2563
+ justify-content: center;
2564
+ /* Ensures the absolute-positioned counter badge clips correctly */
2565
+ isolation: isolate;
2566
+ }
2567
+
2568
+ .clickly-tip .tip-bubble {
2569
+ position: absolute;
2570
+ bottom: calc(100% + 10px);
2571
+ left: 50%;
2572
+ transform: translateX(-50%);
2573
+ background: rgba(15, 23, 42, 0.98);
2574
+ color: #f1f5f9;
2575
+ font-size: 12px;
940
2576
  font-weight: 500;
2577
+ white-space: nowrap;
2578
+ padding: 5px 10px;
2579
+ border-radius: 8px;
2580
+ pointer-events: none;
2581
+ opacity: 0;
2582
+ transition: opacity 80ms ease;
2583
+ transition-delay: 200ms;
2584
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
2585
+ display: flex;
2586
+ align-items: center;
2587
+ gap: 6px;
2588
+ z-index: 10;
941
2589
  }
942
- .clickly-btn.primary-pinned:hover {
943
- background: #059669;
944
- color: #fff;
2590
+
2591
+ /* Arrow */
2592
+ .clickly-tip .tip-bubble::after {
2593
+ content: "";
2594
+ position: absolute;
2595
+ top: 100%;
2596
+ left: 50%;
2597
+ transform: translateX(-50%);
2598
+ border: 5px solid transparent;
2599
+ border-top-color: rgba(15, 23, 42, 0.98);
2600
+ }
2601
+
2602
+ .clickly-tip:hover .tip-bubble { opacity: 1; }
2603
+
2604
+ .clickly-tip .tip-bubble kbd {
2605
+ display: inline-flex;
2606
+ align-items: center;
2607
+ justify-content: center;
2608
+ min-width: 18px;
2609
+ height: 18px;
2610
+ padding: 0 4px;
2611
+ background: rgba(255,255,255,0.12);
2612
+ border: 1px solid rgba(255,255,255,0.10);
2613
+ border-radius: 4px;
2614
+ font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
2615
+ color: #94a3b8;
945
2616
  }
946
2617
 
947
2618
  /* \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 */
@@ -958,17 +2629,159 @@ var REACT_UI_CSS = `
958
2629
  animation: clickly-fade-in 120ms ease-out;
959
2630
  }
960
2631
 
961
- .clickly-popup { width: 320px; padding: 12px; }
2632
+ .clickly-popup {
2633
+ width: 320px;
2634
+ padding: 12px;
2635
+ max-height: calc(100vh - 80px);
2636
+ overflow: hidden;
2637
+ display: flex;
2638
+ flex-direction: column;
2639
+ }
962
2640
 
963
- .clickly-popup .target-info {
2641
+ /* Collapsible header row \u2014 shows element label + CSS toggle */
2642
+ .clickly-popup .popup-header {
964
2643
  display: flex;
965
2644
  align-items: center;
966
- justify-content: space-between;
967
- gap: 8px;
2645
+ gap: 5px;
968
2646
  margin-bottom: 8px;
2647
+ padding: 4px 2px;
2648
+ border-radius: 4px;
2649
+ cursor: pointer;
2650
+ user-select: none;
969
2651
  color: #475569;
970
2652
  font-size: 11px;
971
2653
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
2654
+ transition: background 80ms ease;
2655
+ }
2656
+ .clickly-popup .popup-header:hover { background: #f1f5f9; }
2657
+
2658
+ .clickly-popup .popup-chevron {
2659
+ font-size: 12px;
2660
+ color: #94a3b8;
2661
+ flex-shrink: 0;
2662
+ width: 12px;
2663
+ text-align: center;
2664
+ }
2665
+
2666
+ .clickly-popup .popup-label {
2667
+ flex: 1;
2668
+ overflow: hidden;
2669
+ white-space: nowrap;
2670
+ text-overflow: ellipsis;
2671
+ }
2672
+
2673
+ /* Edit-mode toggle button \u2014 top-right of CSS panel header */
2674
+ .clickly-popup .popup-edit-toggle {
2675
+ flex-shrink: 0;
2676
+ display: grid;
2677
+ place-items: center;
2678
+ width: 20px;
2679
+ height: 20px;
2680
+ background: transparent;
2681
+ border: 1px solid #e2e8f0;
2682
+ border-radius: 5px;
2683
+ color: #94a3b8;
2684
+ cursor: pointer;
2685
+ padding: 0;
2686
+ margin-left: auto;
2687
+ transition: background 100ms ease, color 100ms ease, border-color 100ms ease;
2688
+ }
2689
+ .clickly-popup .popup-edit-toggle:hover {
2690
+ background: #f1f5f9;
2691
+ color: #475569;
2692
+ border-color: #cbd5e1;
2693
+ }
2694
+ .clickly-popup .popup-edit-toggle.is-editing {
2695
+ background: #0ea5e9;
2696
+ color: #fff;
2697
+ border-color: #0ea5e9;
2698
+ }
2699
+ .clickly-popup .popup-edit-toggle.is-editing:hover {
2700
+ background: #0284c7;
2701
+ }
2702
+
2703
+ /* Computed-styles panel */
2704
+ .clickly-popup .popup-styles {
2705
+ margin-bottom: 8px;
2706
+ padding: 8px;
2707
+ background: #f8fafc;
2708
+ border: 1px solid #e2e8f0;
2709
+ border-radius: 6px;
2710
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
2711
+ font-size: 11px;
2712
+ max-height: 160px;
2713
+ overflow-y: auto;
2714
+ overscroll-behavior: contain;
2715
+ }
2716
+
2717
+ .clickly-popup .style-row {
2718
+ display: flex;
2719
+ align-items: center;
2720
+ gap: 6px;
2721
+ line-height: 1.7;
2722
+ border-radius: 4px;
2723
+ padding: 0 2px;
2724
+ transition: background 80ms ease;
2725
+ }
2726
+
2727
+ .clickly-popup .style-row--changed {
2728
+ background: rgba(245, 158, 11, 0.10);
2729
+ }
2730
+
2731
+ .clickly-popup .style-key {
2732
+ color: #7c3aed;
2733
+ flex-shrink: 0;
2734
+ min-width: 90px;
2735
+ font-size: 11px;
2736
+ }
2737
+
2738
+ /* Editable value input \u2014 live CSS preview */
2739
+ .clickly-popup .style-val-input {
2740
+ flex: 1;
2741
+ min-width: 0;
2742
+ background: transparent;
2743
+ border: none;
2744
+ border-bottom: 1px solid transparent;
2745
+ color: #0f172a;
2746
+ font: 11px/1.7 ui-monospace, "SF Mono", Menlo, monospace;
2747
+ padding: 0;
2748
+ outline: none;
2749
+ transition: border-color 100ms ease;
2750
+ word-break: break-all;
2751
+ }
2752
+ .clickly-popup .style-val-input:focus {
2753
+ border-bottom-color: #0ea5e9;
2754
+ background: rgba(14, 165, 233, 0.05);
2755
+ border-radius: 2px 2px 0 0;
2756
+ }
2757
+
2758
+ /* Revert button shown only on changed rows */
2759
+ .clickly-popup .style-revert-btn {
2760
+ flex-shrink: 0;
2761
+ background: transparent;
2762
+ border: none;
2763
+ color: #94a3b8;
2764
+ font-size: 13px;
2765
+ cursor: pointer;
2766
+ padding: 0 2px;
2767
+ line-height: 1;
2768
+ border-radius: 3px;
2769
+ transition: color 80ms ease, background 80ms ease;
2770
+ }
2771
+ .clickly-popup .style-revert-btn:hover {
2772
+ color: #f59e0b;
2773
+ background: rgba(245,158,11,0.12);
2774
+ }
2775
+
2776
+ /* Hint text at bottom of CSS panel when edits exist */
2777
+ .clickly-popup .style-hint {
2778
+ margin-top: 6px;
2779
+ padding-top: 6px;
2780
+ border-top: 1px solid #e2e8f0;
2781
+ font-size: 10.5px;
2782
+ color: #94a3b8;
2783
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2784
+ line-height: 1.4;
972
2785
  }
973
2786
 
974
2787
  .clickly-popup textarea {
@@ -1016,121 +2829,560 @@ var REACT_UI_CSS = `
1016
2829
  cursor: pointer;
1017
2830
  }
1018
2831
 
2832
+ /* Old popover kept for any legacy use */
1019
2833
  .clickly-popover { width: 260px; padding: 12px; }
1020
- .clickly-popover h4 { margin: 0 0 8px; font-size: 12px; color: #475569; text-transform: uppercase; letter-spacing: 0.05em; }
1021
- .clickly-popover .field { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; font-size: 13px; }
1022
- .clickly-popover select { font: inherit; padding: 4px 6px; border-radius: 4px; border: 1px solid #e2e8f0; }
1023
- .clickly-popover input[type="color"] { width: 28px; height: 24px; padding: 0; border: 1px solid #e2e8f0; border-radius: 4px; background: none; }
2834
+
2835
+ /* \u2500\u2500\u2500 Settings panel (redesigned) \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 */
2836
+
2837
+ .clickly-settings {
2838
+ position: fixed;
2839
+ width: 284px;
2840
+ background: #fff;
2841
+ border-radius: 14px;
2842
+ box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
2843
+ font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2844
+ color: #0f172a;
2845
+ pointer-events: auto;
2846
+ z-index: 2;
2847
+ animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
2848
+ overflow: hidden;
2849
+ }
2850
+
2851
+ .settings-header {
2852
+ display: flex;
2853
+ align-items: center;
2854
+ justify-content: space-between;
2855
+ padding: 14px 14px 12px;
2856
+ border-bottom: 1px solid #f1f5f9;
2857
+ }
2858
+
2859
+ .settings-title {
2860
+ font-size: 13px;
2861
+ font-weight: 600;
2862
+ color: #0f172a;
2863
+ letter-spacing: -0.01em;
2864
+ }
2865
+
2866
+ .settings-close {
2867
+ display: grid;
2868
+ place-items: center;
2869
+ width: 24px;
2870
+ height: 24px;
2871
+ background: #f1f5f9;
2872
+ border: none;
2873
+ border-radius: 6px;
2874
+ color: #64748b;
2875
+ cursor: pointer;
2876
+ padding: 0;
2877
+ transition: background 100ms, color 100ms;
2878
+ }
2879
+ .settings-close:hover { background: #e2e8f0; color: #0f172a; }
2880
+ .settings-close svg { width: 13px; height: 13px; }
2881
+
2882
+ .settings-section { padding: 10px 14px; }
2883
+
2884
+ .settings-divider {
2885
+ height: 1px;
2886
+ background: #f1f5f9;
2887
+ margin: 0;
2888
+ }
2889
+
2890
+ .settings-label {
2891
+ display: flex;
2892
+ flex-direction: column;
2893
+ gap: 2px;
2894
+ font-size: 13px;
2895
+ font-weight: 500;
2896
+ color: #1e293b;
2897
+ margin-bottom: 8px;
2898
+ }
2899
+
2900
+ .settings-hint {
2901
+ font-size: 11.5px;
2902
+ font-weight: 400;
2903
+ color: #94a3b8;
2904
+ }
2905
+
2906
+ .settings-select {
2907
+ width: 100%;
2908
+ padding: 7px 10px;
2909
+ background: #f8fafc;
2910
+ border: 1px solid #e2e8f0;
2911
+ border-radius: 8px;
2912
+ font: inherit;
2913
+ font-size: 13px;
2914
+ color: #1e293b;
2915
+ cursor: pointer;
2916
+ appearance: none;
2917
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
2918
+ background-repeat: no-repeat;
2919
+ background-position: right 10px center;
2920
+ padding-right: 32px;
2921
+ transition: border-color 120ms, box-shadow 120ms;
2922
+ }
2923
+ .settings-select:focus {
2924
+ outline: none;
2925
+ border-color: #0ea5e9;
2926
+ box-shadow: 0 0 0 3px rgba(14,165,233,0.15);
2927
+ background-color: #fff;
2928
+ }
2929
+
2930
+ .settings-row {
2931
+ display: flex;
2932
+ align-items: center;
2933
+ justify-content: space-between;
2934
+ gap: 12px;
2935
+ padding: 6px 0;
2936
+ }
2937
+ .settings-row + .settings-row {
2938
+ border-top: 1px solid #f8fafc;
2939
+ }
2940
+
2941
+ .settings-row-label {
2942
+ display: flex;
2943
+ flex-direction: column;
2944
+ gap: 2px;
2945
+ font-size: 13px;
2946
+ font-weight: 500;
2947
+ color: #1e293b;
2948
+ min-width: 0;
2949
+ }
2950
+
2951
+ /* Toggle switch */
2952
+ .clickly-toggle {
2953
+ position: relative;
2954
+ flex-shrink: 0;
2955
+ width: 38px;
2956
+ height: 22px;
2957
+ cursor: pointer;
2958
+ }
2959
+ .clickly-toggle input {
2960
+ position: absolute;
2961
+ opacity: 0;
2962
+ width: 0;
2963
+ height: 0;
2964
+ }
2965
+ .toggle-track {
2966
+ position: absolute;
2967
+ inset: 0;
2968
+ background: #e2e8f0;
2969
+ border-radius: 11px;
2970
+ transition: background 180ms ease;
2971
+ }
2972
+ .toggle-track::after {
2973
+ content: "";
2974
+ position: absolute;
2975
+ top: 3px;
2976
+ left: 3px;
2977
+ width: 16px;
2978
+ height: 16px;
2979
+ background: #fff;
2980
+ border-radius: 50%;
2981
+ box-shadow: 0 1px 4px rgba(0,0,0,0.2);
2982
+ transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
2983
+ }
2984
+ .clickly-toggle input:checked + .toggle-track {
2985
+ background: #0ea5e9;
2986
+ }
2987
+ .clickly-toggle input:checked + .toggle-track::after {
2988
+ transform: translateX(16px);
2989
+ }
2990
+
2991
+ /* Color picker */
2992
+ .settings-color-wrap {
2993
+ display: flex;
2994
+ align-items: center;
2995
+ gap: 0;
2996
+ cursor: pointer;
2997
+ border-radius: 8px;
2998
+ overflow: hidden;
2999
+ border: 1px solid #e2e8f0;
3000
+ flex-shrink: 0;
3001
+ }
3002
+ .settings-color-wrap input[type="color"] {
3003
+ position: absolute;
3004
+ opacity: 0;
3005
+ width: 0;
3006
+ height: 0;
3007
+ pointer-events: none;
3008
+ }
3009
+ .color-swatch {
3010
+ display: block;
3011
+ width: 32px;
3012
+ height: 24px;
3013
+ border-radius: 7px;
3014
+ border: 1px solid rgba(0,0,0,0.08);
3015
+ transition: transform 100ms ease;
3016
+ }
3017
+ .settings-color-wrap:hover .color-swatch { transform: scale(1.08); }
1024
3018
 
1025
3019
  .clickly-counter {
1026
- display: inline-flex;
3020
+ /* Float as a badge \u2014 positioned absolutely so it doesn't widen the button */
3021
+ position: absolute;
3022
+ top: -5px;
3023
+ right: -5px;
3024
+ min-width: 16px;
3025
+ height: 16px;
3026
+ padding: 0 4px;
3027
+ border-radius: 8px;
3028
+ background: #f59e0b;
3029
+ color: #0f172a;
3030
+ font-size: 10px;
3031
+ font-weight: 700;
3032
+ display: flex;
1027
3033
  align-items: center;
1028
3034
  justify-content: center;
1029
- min-width: 18px;
1030
- height: 18px;
1031
- padding: 0 5px;
1032
- border-radius: 9px;
1033
- background: #f59e0b;
1034
- color: #1f2937;
1035
- font-size: 11px;
1036
- font-weight: 600;
1037
- margin-left: 4px;
3035
+ pointer-events: none;
3036
+ border: 1.5px solid rgba(9, 14, 28, 0.97);
1038
3037
  }
1039
3038
 
1040
3039
  /* \u2500\u2500\u2500 Annotation pins (persistent numbered markers) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1041
3040
 
1042
3041
  .clickly-pin {
1043
3042
  position: fixed;
1044
- width: 22px;
1045
- height: 22px;
3043
+ width: 24px;
3044
+ height: 24px;
1046
3045
  border-radius: 999px;
1047
- background: #f59e0b;
1048
- color: #0f172a;
1049
- font: 600 12px/22px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3046
+ background: #10b981;
3047
+ color: #fff;
3048
+ font: 700 11px/24px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1050
3049
  text-align: center;
1051
3050
  cursor: pointer;
1052
3051
  pointer-events: auto;
1053
3052
  user-select: none;
1054
- box-shadow: 0 2px 6px rgba(0,0,0,0.25), 0 0 0 2px #fff;
1055
- z-index: 1;
1056
- transition: transform 100ms ease;
3053
+ box-shadow: 0 2px 8px rgba(16,185,129,0.4), 0 0 0 2px #fff;
3054
+ z-index: 10;
3055
+ transition: transform 120ms cubic-bezier(0.34,1.56,0.64,1), box-shadow 120ms ease;
3056
+ }
3057
+ .clickly-pin:hover {
3058
+ transform: scale(1.18);
3059
+ box-shadow: 0 4px 16px rgba(16,185,129,0.5), 0 0 0 2px #fff;
1057
3060
  }
1058
- .clickly-pin:hover { transform: scale(1.12); }
1059
3061
  .clickly-pin-num { display: block; }
1060
3062
 
1061
- .clickly-pin-bubble {
3063
+ /* \u2500\u2500\u2500 Pin hover preview (dark tooltip) \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 */
3064
+
3065
+ .pin-preview {
1062
3066
  position: absolute;
1063
- top: 28px;
1064
- left: 0;
1065
- width: 240px;
3067
+ right: calc(100% + 10px);
3068
+ top: 50%;
3069
+ transform: translateY(-50%);
3070
+ width: 220px;
1066
3071
  padding: 8px 10px;
1067
- background: #fff;
1068
- color: #0f172a;
1069
- border-radius: 8px;
1070
- box-shadow: 0 8px 24px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
1071
- font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3072
+ background: rgba(9, 14, 28, 0.96);
3073
+ color: #f1f5f9;
3074
+ border-radius: 10px;
3075
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
3076
+ font: 12px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1072
3077
  text-align: left;
1073
3078
  cursor: default;
1074
- z-index: 3;
3079
+ pointer-events: none;
3080
+ z-index: 11;
1075
3081
  animation: clickly-fade-in 100ms ease-out;
3082
+ white-space: normal;
1076
3083
  }
1077
- .clickly-pin-comment { word-break: break-word; }
1078
- .clickly-pin-meta {
1079
- margin-top: 6px;
3084
+
3085
+ .pin-preview-meta {
3086
+ font-size: 10.5px;
3087
+ color: #64748b;
1080
3088
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
1081
- font-size: 11px;
3089
+ margin-bottom: 4px;
3090
+ overflow: hidden;
3091
+ white-space: nowrap;
3092
+ text-overflow: ellipsis;
3093
+ }
3094
+
3095
+ .pin-preview-comment {
3096
+ font-size: 12px;
3097
+ color: #e2e8f0;
3098
+ word-break: break-word;
3099
+ display: -webkit-box;
3100
+ -webkit-line-clamp: 3;
3101
+ -webkit-box-orient: vertical;
3102
+ overflow: hidden;
3103
+ }
3104
+
3105
+ /* \u2500\u2500\u2500 Pin edit popup \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 */
3106
+
3107
+ .pin-edit {
3108
+ position: absolute;
3109
+ right: calc(100% + 10px);
3110
+ top: 50%;
3111
+ transform: translateY(-50%);
3112
+ width: 260px;
3113
+ background: #fff;
3114
+ border-radius: 12px;
3115
+ box-shadow: 0 12px 40px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
3116
+ font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3117
+ color: #0f172a;
3118
+ text-align: left;
3119
+ cursor: default;
3120
+ z-index: 11;
3121
+ animation: clickly-fade-in 120ms cubic-bezier(0.16,1,0.3,1);
3122
+ overflow: hidden;
3123
+ }
3124
+
3125
+ .pin-edit-header {
3126
+ padding: 10px 12px 8px;
3127
+ border-bottom: 1px solid #f1f5f9;
3128
+ }
3129
+
3130
+ .pin-edit-label {
3131
+ font-size: 11.5px;
3132
+ font-weight: 600;
1082
3133
  color: #475569;
1083
- word-break: break-all;
1084
3134
  }
1085
- .clickly-pin-remove {
1086
- margin-top: 8px;
1087
- padding: 4px 8px;
1088
- border: 1px solid #fecaca;
3135
+
3136
+ .pin-edit-path {
3137
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
3138
+ font-weight: 400;
3139
+ color: #94a3b8;
3140
+ font-size: 10.5px;
3141
+ overflow: hidden;
3142
+ white-space: nowrap;
3143
+ text-overflow: ellipsis;
3144
+ display: inline-block;
3145
+ max-width: 160px;
3146
+ vertical-align: bottom;
3147
+ }
3148
+
3149
+ .pin-edit-textarea {
3150
+ display: block;
3151
+ width: 100%;
3152
+ min-height: 72px;
3153
+ padding: 10px 12px;
3154
+ border: none;
3155
+ border-bottom: 1px solid #f1f5f9;
3156
+ resize: vertical;
3157
+ font: 13px/1.5 inherit;
3158
+ color: #0f172a;
1089
3159
  background: #fff;
1090
- color: #b91c1c;
1091
- border-radius: 4px;
1092
- font: 11px inherit;
3160
+ box-sizing: border-box;
3161
+ }
3162
+ .pin-edit-textarea:focus {
3163
+ outline: none;
3164
+ background: #f8fafc;
3165
+ }
3166
+
3167
+ .pin-edit-actions {
3168
+ display: flex;
3169
+ align-items: center;
3170
+ justify-content: space-between;
3171
+ padding: 8px 10px;
3172
+ gap: 6px;
3173
+ }
3174
+
3175
+ .pin-edit-right {
3176
+ display: flex;
3177
+ gap: 6px;
3178
+ }
3179
+
3180
+ .pin-edit-delete {
3181
+ display: grid;
3182
+ place-items: center;
3183
+ width: 30px;
3184
+ height: 30px;
3185
+ background: transparent;
3186
+ border: 1px solid #fee2e2;
3187
+ border-radius: 8px;
3188
+ color: #ef4444;
3189
+ cursor: pointer;
3190
+ padding: 0;
3191
+ transition: background 100ms, border-color 100ms;
3192
+ }
3193
+ .pin-edit-delete:hover { background: #fef2f2; border-color: #fca5a5; }
3194
+ .pin-edit-delete svg { width: 14px; height: 14px; }
3195
+
3196
+ .pin-edit-cancel, .pin-edit-save {
3197
+ height: 30px;
3198
+ padding: 0 12px;
3199
+ border-radius: 8px;
3200
+ font: 12px/1 inherit;
3201
+ font-weight: 500;
1093
3202
  cursor: pointer;
3203
+ border: none;
3204
+ transition: background 100ms;
1094
3205
  }
1095
- .clickly-pin-remove:hover { background: #fef2f2; }
3206
+
3207
+ .pin-edit-cancel {
3208
+ background: #f1f5f9;
3209
+ color: #475569;
3210
+ }
3211
+ .pin-edit-cancel:hover { background: #e2e8f0; }
3212
+
3213
+ .pin-edit-save {
3214
+ background: #10b981;
3215
+ color: #fff;
3216
+ }
3217
+ .pin-edit-save:hover { background: #059669; }
1096
3218
 
1097
3219
  /* \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 */
1098
3220
 
1099
3221
  .clickly-list {
1100
3222
  position: fixed;
1101
- max-height: 50vh;
1102
3223
  width: 320px;
1103
- overflow-y: auto;
3224
+ max-height: 55vh;
1104
3225
  background: #fff;
1105
- border-radius: 10px;
1106
- box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
3226
+ border-radius: 14px;
3227
+ box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
1107
3228
  color: #0f172a;
1108
- font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3229
+ font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1109
3230
  pointer-events: auto;
1110
3231
  z-index: 2;
1111
- animation: clickly-fade-in 120ms ease-out;
3232
+ animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
3233
+ display: flex;
3234
+ flex-direction: column;
3235
+ overflow: hidden;
3236
+ }
3237
+
3238
+ .list-header {
3239
+ display: flex;
3240
+ align-items: center;
3241
+ gap: 8px;
3242
+ padding: 12px 14px 10px;
3243
+ border-bottom: 1px solid #f1f5f9;
3244
+ flex-shrink: 0;
3245
+ }
3246
+
3247
+ .list-title {
3248
+ font-size: 13px;
3249
+ font-weight: 600;
3250
+ color: #0f172a;
3251
+ letter-spacing: -0.01em;
3252
+ flex: 1;
3253
+ }
3254
+
3255
+ .list-count {
3256
+ min-width: 20px;
3257
+ height: 20px;
3258
+ padding: 0 6px;
3259
+ background: #f1f5f9;
3260
+ color: #475569;
3261
+ font-size: 11px;
3262
+ font-weight: 600;
3263
+ border-radius: 10px;
3264
+ display: grid;
3265
+ place-items: center;
3266
+ }
3267
+
3268
+ .list-items {
3269
+ overflow-y: auto;
3270
+ overscroll-behavior: contain;
3271
+ flex: 1;
3272
+ }
3273
+
3274
+ .list-empty {
3275
+ padding: 24px;
3276
+ text-align: center;
3277
+ color: #94a3b8;
3278
+ font-size: 12px;
3279
+ }
3280
+
3281
+ /* \u2500\u2500\u2500 Annotation card \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 */
3282
+
3283
+ .list-card {
3284
+ padding: 10px 12px;
3285
+ border-bottom: 1px solid #f8fafc;
3286
+ transition: background 80ms ease;
3287
+ }
3288
+ .list-card:last-child { border-bottom: none; }
3289
+ .list-card:hover { background: #fafafa; }
3290
+
3291
+ .list-card-header {
3292
+ display: flex;
3293
+ align-items: center;
3294
+ gap: 6px;
3295
+ margin-bottom: 4px;
3296
+ }
3297
+
3298
+ .list-card-num {
3299
+ font-size: 11px;
3300
+ font-weight: 700;
3301
+ color: #94a3b8;
3302
+ flex-shrink: 0;
3303
+ min-width: 20px;
3304
+ }
3305
+
3306
+ .list-card-path {
3307
+ flex: 1;
3308
+ min-width: 0;
3309
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
3310
+ font-size: 10.5px;
3311
+ color: #475569;
3312
+ white-space: nowrap;
3313
+ overflow: hidden;
3314
+ text-overflow: ellipsis;
3315
+ }
3316
+
3317
+ .list-card-actions {
3318
+ display: flex;
3319
+ gap: 3px;
3320
+ flex-shrink: 0;
1112
3321
  }
1113
- .clickly-list .row { display: flex; gap: 8px; padding: 8px 10px; border-bottom: 1px solid #f1f5f9; }
1114
- .clickly-list .row:last-child { border-bottom: none; }
1115
- .clickly-list .num { color: #94a3b8; min-width: 18px; }
1116
- .clickly-list .selector { color: #475569; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11px; word-break: break-all; }
1117
- .clickly-list .body { flex: 1; min-width: 0; }
1118
- .clickly-list .body p { margin: 2px 0 0; word-break: break-word; }
1119
- .clickly-list button.remove {
3322
+
3323
+ .list-action-btn {
3324
+ display: grid;
3325
+ place-items: center;
3326
+ width: 24px;
3327
+ height: 24px;
1120
3328
  background: transparent;
1121
3329
  border: none;
3330
+ border-radius: 6px;
1122
3331
  color: #94a3b8;
1123
3332
  cursor: pointer;
1124
- padding: 2px;
1125
- display: grid;
1126
- place-items: center;
3333
+ padding: 0;
3334
+ transition: background 80ms ease, color 80ms ease;
3335
+ }
3336
+ .list-action-btn svg { width: 12px; height: 12px; }
3337
+ .list-action-btn:hover { background: #f1f5f9; color: #475569; }
3338
+ .list-action-btn.copied { color: #10b981; }
3339
+ .list-action-btn.list-action-delete:hover { background: #fef2f2; color: #ef4444; }
3340
+
3341
+ .list-card-comment {
3342
+ font-size: 12px;
3343
+ color: #1e293b;
3344
+ line-height: 1.45;
3345
+ margin: 0 0 6px;
3346
+ word-break: break-word;
3347
+ display: -webkit-box;
3348
+ -webkit-line-clamp: 2;
3349
+ -webkit-box-orient: vertical;
3350
+ overflow: hidden;
3351
+ }
3352
+
3353
+ /* CSS changes badge */
3354
+ .list-card-css {
3355
+ background: rgba(124, 58, 237, 0.05);
3356
+ border: 1px solid rgba(124, 58, 237, 0.12);
3357
+ border-radius: 6px;
3358
+ padding: 5px 8px;
3359
+ margin-top: 4px;
3360
+ }
3361
+
3362
+ .list-card-css-label {
3363
+ display: block;
3364
+ font-size: 10px;
3365
+ font-weight: 600;
3366
+ color: #7c3aed;
3367
+ text-transform: uppercase;
3368
+ letter-spacing: 0.04em;
3369
+ margin-bottom: 3px;
3370
+ }
3371
+
3372
+ .list-card-css-code {
3373
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
3374
+ font-size: 10px;
3375
+ color: #475569;
3376
+ line-height: 1.5;
3377
+ margin: 0;
3378
+ white-space: pre-wrap;
3379
+ word-break: break-all;
3380
+ max-height: 60px;
3381
+ overflow: hidden;
1127
3382
  }
1128
- .clickly-list button.remove svg { width: 12px; height: 12px; }
1129
- .clickly-list button.remove:hover { color: #ef4444; }
1130
- .clickly-list .empty { padding: 20px; text-align: center; color: #94a3b8; }
1131
3383
  `;
1132
3384
 
1133
- // src/internal/globalStyles.ts
3385
+ // packages/react/src/internal/globalStyles.ts
1134
3386
  var GLOBAL_PAGE_CSS = `
1135
3387
  body[data-clickly-active] {
1136
3388
  -webkit-user-select: none !important;
@@ -1150,16 +3402,19 @@ body[data-clickly-annotating] {
1150
3402
  cursor: default !important;
1151
3403
  }
1152
3404
  `;
3405
+
3406
+ // packages/react/src/Clickly.tsx
3407
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1153
3408
  function Clickly({ className } = {}) {
1154
- const [mount, setMount] = react.useState(null);
1155
- react.useEffect(() => {
3409
+ const [mount, setMount] = (0, import_react11.useState)(null);
3410
+ (0, import_react11.useEffect)(() => {
1156
3411
  if (typeof window === "undefined" || typeof document === "undefined") return;
1157
3412
  let shadow = null;
1158
3413
  let engine = null;
1159
3414
  let overlay = null;
1160
3415
  let portal = null;
1161
3416
  try {
1162
- shadow = core.createShadowHost({ document });
3417
+ shadow = createShadowHost({ document });
1163
3418
  portal = document.createElement("div");
1164
3419
  portal.setAttribute("data-clickly-react-root", "");
1165
3420
  shadow.root.appendChild(portal);
@@ -1175,8 +3430,8 @@ function Clickly({ className } = {}) {
1175
3430
  gstyle.textContent = GLOBAL_PAGE_CSS;
1176
3431
  document.head.appendChild(gstyle);
1177
3432
  }
1178
- engine = new core.SelectionEngine({ document, host: shadow.host });
1179
- overlay = new core.Overlay({
3433
+ engine = new SelectionEngine({ document, host: shadow.host });
3434
+ overlay = new Overlay({
1180
3435
  engine,
1181
3436
  root: shadow.root,
1182
3437
  document,
@@ -1201,7 +3456,7 @@ function Clickly({ className } = {}) {
1201
3456
  document.body.removeAttribute("data-clickly-mode");
1202
3457
  };
1203
3458
  }, []);
1204
- react.useEffect(() => {
3459
+ (0, import_react11.useEffect)(() => {
1205
3460
  if (!mount || !className) return;
1206
3461
  mount.shadow.host.className = className;
1207
3462
  return () => {
@@ -1209,17 +3464,8 @@ function Clickly({ className } = {}) {
1209
3464
  };
1210
3465
  }, [mount, className]);
1211
3466
  if (!mount) return null;
1212
- return reactDom.createPortal(
1213
- /* @__PURE__ */ jsxRuntime.jsx(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
3467
+ return (0, import_react_dom.createPortal)(
3468
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
1214
3469
  mount.portal
1215
3470
  );
1216
3471
  }
1217
-
1218
- exports.Clickly = Clickly;
1219
- exports.DEFAULT_SETTINGS = DEFAULTS;
1220
- exports.annotationsToMarkdown = annotationsToMarkdown;
1221
- exports.useAnnotations = useAnnotations;
1222
- exports.useAnnotationsList = useAnnotationsList;
1223
- exports.useSettings = useSettings;
1224
- //# sourceMappingURL=index.cjs.map
1225
- //# sourceMappingURL=index.cjs.map