@useclickly/react 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1027 +1,19 @@
1
- // packages/react/src/Clickly.tsx
1
+ // ../react/src/Clickly.tsx
2
2
  import { useEffect as useEffect7, useState as useState7 } from "react";
3
3
  import { createPortal } from "react-dom";
4
+ import {
5
+ Overlay,
6
+ SelectionEngine,
7
+ createShadowHost
8
+ } from "@useclickly/core";
4
9
 
5
- // packages/core/dist/index.js
6
- function pickElementAt(doc, x, y, excludeHost) {
7
- if (typeof doc.elementsFromPoint !== "function") return null;
8
- const chain = doc.elementsFromPoint(x, y);
9
- for (const el of chain) {
10
- if (!isInExcludedSubtree(el, excludeHost)) return el;
11
- }
12
- return null;
13
- }
14
- function pickElementsInRect(root, rect, excludeHost) {
15
- const out = [];
16
- const stack = [root];
17
- while (stack.length) {
18
- const el = stack.pop();
19
- if (isInExcludedSubtree(el, excludeHost)) continue;
20
- const box = el.getBoundingClientRect();
21
- if (box.width > 0 && box.height > 0 && containedIn(box, rect)) {
22
- out.push(el);
23
- }
24
- for (let i = 0; i < el.children.length; i++) {
25
- const child = el.children[i];
26
- if (child) stack.push(child);
27
- }
28
- }
29
- return out;
30
- }
31
- function containedIn(el, sel) {
32
- return el.left >= sel.x && el.top >= sel.y && el.right <= sel.x + sel.width && el.bottom <= sel.y + sel.height;
33
- }
34
- function isInExcludedSubtree(el, host) {
35
- if (!host) return false;
36
- let cur = el;
37
- while (cur) {
38
- if (cur === host) return true;
39
- cur = cur.parentNode;
40
- }
41
- return false;
42
- }
43
- function manhattan(a, b) {
44
- return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
45
- }
46
- function rectFromPoints(start, current) {
47
- const x = Math.min(start.x, current.x);
48
- const y = Math.min(start.y, current.y);
49
- const width = Math.abs(current.x - start.x);
50
- const height = Math.abs(current.y - start.y);
51
- return { x, y, width, height };
52
- }
53
- var DRAG_THRESHOLD_PX = 12;
54
- var initialState = { kind: "idle" };
55
- function reduce(state, event) {
56
- if (event.type === "DEACTIVATE") return { kind: "idle" };
57
- if (event.type === "ESCAPE") {
58
- if (state.kind === "idle") return state;
59
- if (state.kind === "annotating") return resumeInspect(
60
- [],
61
- /* mode */
62
- void 0
63
- );
64
- if (state.kind === "inspect" && state.pinned.length > 0) {
65
- return { ...state, pinned: [], hoverTarget: null };
66
- }
67
- return resumeInspect("pinned" in state ? state.pinned : []);
68
- }
69
- if (event.type === "CLEAR_PINNED") {
70
- if (state.kind === "idle") return state;
71
- if (state.kind === "inspect") return { ...state, pinned: [] };
72
- return state;
73
- }
74
- switch (state.kind) {
75
- case "idle":
76
- if (event.type === "ACTIVATE") {
77
- return {
78
- kind: "inspect",
79
- mode: event.mode ?? "single",
80
- hoverTarget: null,
81
- pinned: []
82
- };
83
- }
84
- return state;
85
- case "inspect":
86
- if (event.type === "MODE_CHANGE") return { ...state, mode: event.mode };
87
- if (event.type === "POINTER_MOVE") return { ...state, hoverTarget: event.target };
88
- if (event.type === "POINTER_DOWN") {
89
- return {
90
- kind: "pressed",
91
- mode: state.mode,
92
- start: event.point,
93
- target: event.target,
94
- additive: event.additive,
95
- pinned: state.pinned
96
- };
97
- }
98
- if (event.type === "ANNOTATE_PINNED") {
99
- if (state.pinned.length === 0) return state;
100
- return {
101
- kind: "annotating",
102
- selection: { kind: "multi", elements: state.pinned },
103
- pinned: state.pinned
104
- };
105
- }
106
- return state;
107
- case "pressed":
108
- if (event.type === "POINTER_MOVE") {
109
- if (manhattan(state.start, event.point) >= DRAG_THRESHOLD_PX) {
110
- return {
111
- kind: "dragging",
112
- mode: state.mode,
113
- start: state.start,
114
- current: event.point,
115
- pinned: state.pinned
116
- };
117
- }
118
- return state;
119
- }
120
- if (event.type === "POINTER_UP") {
121
- if (!state.target) {
122
- return resumeInspect(state.pinned, state.mode);
123
- }
124
- if (state.additive || state.mode === "multi") {
125
- const nextPinned = togglePinned(state.pinned, state.target);
126
- return {
127
- kind: "inspect",
128
- mode: state.mode,
129
- hoverTarget: state.target,
130
- pinned: nextPinned
131
- };
132
- }
133
- return {
134
- kind: "annotating",
135
- selection: { kind: "single", element: state.target },
136
- pinned: state.pinned
137
- };
138
- }
139
- return state;
140
- case "dragging":
141
- if (event.type === "POINTER_MOVE") return { ...state, current: event.point };
142
- if (event.type === "POINTER_UP") {
143
- const rect = rectFromPoints(state.start, state.current);
144
- const selection = { kind: "area", rect, elements: [] };
145
- return { kind: "annotating", selection, pinned: state.pinned };
146
- }
147
- return state;
148
- case "annotating":
149
- if (event.type === "COMMIT") {
150
- return resumeInspect([]);
151
- }
152
- return state;
153
- }
154
- }
155
- function resumeInspect(pinned, mode) {
156
- return {
157
- kind: "inspect",
158
- mode: mode ?? "single",
159
- hoverTarget: null,
160
- pinned
161
- };
162
- }
163
- function togglePinned(pinned, el) {
164
- const idx = pinned.indexOf(el);
165
- if (idx === -1) return [...pinned, el];
166
- const next = pinned.slice();
167
- next.splice(idx, 1);
168
- return next;
169
- }
170
- var SelectionEngine = class {
171
- state = initialState;
172
- listeners = /* @__PURE__ */ new Set();
173
- doc;
174
- host;
175
- raf;
176
- caf;
177
- searchRoot;
178
- pendingPointer = null;
179
- rafHandle = null;
180
- /** Guard for the pointermove RAF coalescer. Separate from `rafHandle`
181
- * because a synchronous `raf` (used in tests) returns a handle the cb
182
- * has already invalidated — boolean is the safe sentinel. */
183
- rafPending = false;
184
- attached = false;
185
- boundHandlers = [];
186
- constructor(deps = {}) {
187
- this.doc = deps.document ?? (typeof document !== "undefined" ? document : null);
188
- if (!this.doc) {
189
- throw new Error("SelectionEngine: no Document available (pass `deps.document`).");
190
- }
191
- this.host = deps.host ?? null;
192
- this.raf = deps.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
193
- this.caf = deps.caf ?? ((h) => {
194
- if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
195
- else clearTimeout(h);
196
- });
197
- this.searchRoot = deps.searchRoot ?? this.doc.body;
198
- }
199
- /* ─── Subscribable<EngineState> ───────────────────────────────── */
200
- subscribe(listener) {
201
- this.listeners.add(listener);
202
- return () => this.listeners.delete(listener);
203
- }
204
- getSnapshot() {
205
- return this.state;
206
- }
207
- /* ─── Public control ──────────────────────────────────────────── */
208
- activate(mode) {
209
- perfMark("clickly:engine:activate");
210
- this.dispatch(mode ? { type: "ACTIVATE", mode } : { type: "ACTIVATE" });
211
- this.attach();
212
- }
213
- deactivate() {
214
- perfMark("clickly:engine:deactivate");
215
- this.detach();
216
- this.dispatch({ type: "DEACTIVATE" });
217
- }
218
- setMode(mode) {
219
- this.dispatch({ type: "MODE_CHANGE", mode });
220
- }
221
- commit() {
222
- this.dispatch({ type: "COMMIT" });
223
- }
224
- clearPinned() {
225
- this.dispatch({ type: "CLEAR_PINNED" });
226
- }
227
- /** Open the annotation popup populated with everything currently pinned. */
228
- annotatePinned() {
229
- this.dispatch({ type: "ANNOTATE_PINNED" });
230
- }
231
- /**
232
- * Returns the resolved selection from the current annotating state, with
233
- * area-mode element enumeration filled in (the reducer leaves it empty).
234
- * Returns null if not currently annotating.
235
- */
236
- resolveSelection() {
237
- if (this.state.kind !== "annotating") return null;
238
- const sel = this.state.selection;
239
- if (sel.kind !== "area") return sel;
240
- const elements = pickElementsInRect(this.searchRoot, sel.rect, this.host);
241
- return { ...sel, elements };
242
- }
243
- /* ─── Lifecycle ───────────────────────────────────────────────── */
244
- attach() {
245
- if (this.attached) return;
246
- this.attached = true;
247
- const win = this.doc.defaultView ?? globalThis;
248
- this.bind(this.doc, "pointermove", this.onPointerMove, { passive: true });
249
- this.bind(this.doc, "pointerdown", this.onPointerDown);
250
- this.bind(this.doc, "pointerup", this.onPointerUp);
251
- this.bind(win, "keydown", this.onKeyDown);
252
- }
253
- detach() {
254
- if (!this.attached) return;
255
- for (const [t, ev, fn, opts] of this.boundHandlers) t.removeEventListener(ev, fn, opts);
256
- this.boundHandlers = [];
257
- this.attached = false;
258
- if (this.rafHandle !== null) {
259
- this.caf(this.rafHandle);
260
- this.rafHandle = null;
261
- }
262
- this.rafPending = false;
263
- }
264
- destroy() {
265
- this.detach();
266
- this.listeners.clear();
267
- }
268
- bind(target, ev, fn, opts) {
269
- target.addEventListener(ev, fn, opts);
270
- this.boundHandlers.push([target, ev, fn, opts]);
271
- }
272
- /* ─── DOM event handlers ──────────────────────────────────────── */
273
- onPointerMove = (e) => {
274
- this.pendingPointer = { x: e.clientX, y: e.clientY };
275
- if (this.rafPending) return;
276
- this.rafPending = true;
277
- this.rafHandle = this.raf(() => {
278
- this.rafPending = false;
279
- this.rafHandle = null;
280
- const pt = this.pendingPointer;
281
- this.pendingPointer = null;
282
- if (!pt) return;
283
- const target = pickElementAt(this.doc, pt.x, pt.y, this.host);
284
- this.dispatch({ type: "POINTER_MOVE", point: pt, target });
285
- });
286
- };
287
- onPointerDown = (e) => {
288
- if (this.host && e.composedPath().includes(this.host)) return;
289
- const target = pickElementAt(this.doc, e.clientX, e.clientY, this.host);
290
- this.dispatch({
291
- type: "POINTER_DOWN",
292
- point: { x: e.clientX, y: e.clientY },
293
- target,
294
- additive: e.shiftKey || e.metaKey || e.ctrlKey
295
- });
296
- };
297
- onPointerUp = (e) => {
298
- this.dispatch({ type: "POINTER_UP", point: { x: e.clientX, y: e.clientY } });
299
- };
300
- onKeyDown = (e) => {
301
- if (e.key === "Escape") this.dispatch({ type: "ESCAPE" });
302
- };
303
- /* ─── Reducer plumbing ────────────────────────────────────────── */
304
- dispatch(event) {
305
- const next = reduce(this.state, event);
306
- if (next === this.state) return;
307
- this.state = next;
308
- for (const l of this.listeners) l(next);
309
- }
310
- };
311
- function perfMark(name) {
312
- if (typeof performance !== "undefined" && typeof performance.mark === "function") {
313
- try {
314
- performance.mark(name);
315
- } catch {
316
- }
317
- }
318
- }
319
- var OVERLAY_CSS = `
320
- :host {
321
- --clickly-hover: #06b6d4;
322
- --clickly-pinned: #f59e0b;
323
- --clickly-selected: #10b981;
324
- --clickly-marquee-stroke: #10b981;
325
- --clickly-marquee-fill: rgba(16, 185, 129, 0.10);
326
- --clickly-label-bg: rgba(15, 23, 42, 0.92);
327
- --clickly-label-fg: #f8fafc;
328
- --clickly-shadow: 0 0 0 1px rgba(255,255,255,0.5);
329
-
330
- all: initial;
331
- position: fixed;
332
- inset: 0;
333
- z-index: 2147483647;
334
- pointer-events: none;
335
- contain: layout style paint;
336
- isolation: isolate;
337
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
338
- }
339
-
340
- .layer {
341
- position: fixed;
342
- left: 0;
343
- top: 0;
344
- width: 0;
345
- height: 0;
346
- pointer-events: none;
347
- will-change: transform, width, height, opacity;
348
- }
349
-
350
- .marker {
351
- position: fixed;
352
- left: 0;
353
- top: 0;
354
- box-sizing: border-box;
355
- border-radius: 2px;
356
- pointer-events: none;
357
- will-change: transform, width, height, opacity;
358
- transition: opacity 80ms linear;
359
- }
360
-
361
- .marker[hidden] { display: none; }
362
-
363
- .marker.hover { box-shadow: 0 0 0 2px var(--clickly-hover), var(--clickly-shadow); }
364
- .marker.pinned { box-shadow: 0 0 0 2px var(--clickly-pinned), var(--clickly-shadow); }
365
- .marker.selected { box-shadow: 0 0 0 2px var(--clickly-selected), var(--clickly-shadow); }
366
-
367
- /* Marquee \u2014 used during drag (dashed) AND for the committed union
368
- box around multi/area selections (solid). */
369
- .marker.marquee {
370
- border: 2px dashed var(--clickly-marquee-stroke);
371
- background: var(--clickly-marquee-fill);
372
- border-radius: 8px;
373
- }
374
- .marker.marquee.is-committed {
375
- border-style: solid;
376
- box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.18);
377
- }
378
-
379
- .label {
380
- position: fixed;
381
- left: 0;
382
- top: 0;
383
- padding: 2px 6px;
384
- background: var(--clickly-label-bg);
385
- color: var(--clickly-label-fg);
386
- font-size: 11px;
387
- line-height: 1.4;
388
- border-radius: 4px;
389
- white-space: nowrap;
390
- pointer-events: none;
391
- user-select: none;
392
- max-width: 50vw;
393
- overflow: hidden;
394
- text-overflow: ellipsis;
395
- }
396
- `;
397
- var HOST_TAG = "clickly-root";
398
- function createShadowHost(deps = {}) {
399
- const doc = deps.document ?? (typeof document !== "undefined" ? document : null);
400
- if (!doc) throw new Error("createShadowHost: no Document available");
401
- const existing = doc.querySelector(HOST_TAG);
402
- const host = existing ?? doc.createElement(HOST_TAG);
403
- if (!existing) doc.body.appendChild(host);
404
- const root = host.shadowRoot ?? host.attachShadow({ mode: "open" });
405
- if (!root.querySelector("style[data-clickly]")) {
406
- const style = doc.createElement("style");
407
- style.setAttribute("data-clickly", "");
408
- style.textContent = OVERLAY_CSS;
409
- root.appendChild(style);
410
- }
411
- return {
412
- host,
413
- root,
414
- destroy() {
415
- host.remove();
416
- }
417
- };
418
- }
419
- var OverlayRenderer = class {
420
- root;
421
- document;
422
- layer;
423
- hover;
424
- hoverLabel;
425
- /** Marquee rect — used both for live drag AND for the union box
426
- * drawn around multi-element selections after commit. */
427
- marquee;
428
- /** Pinned/selected single-element rings (recycled across renders). */
429
- pinnedPool = [];
430
- selectedPool = [];
431
- /** Last rendered state — used to re-render on scroll without a state change. */
432
- lastState = null;
433
- constructor(root, document2) {
434
- this.root = root;
435
- this.document = document2 ?? root.ownerDocument ?? (typeof globalThis !== "undefined" && globalThis.document ? globalThis.document : null);
436
- if (!this.document) throw new Error("OverlayRenderer: no Document available");
437
- this.layer = this.div("layer");
438
- this.root.appendChild(this.layer);
439
- this.hover = this.div("marker hover");
440
- this.hover.hidden = true;
441
- this.layer.appendChild(this.hover);
442
- this.hoverLabel = this.div("label");
443
- this.hoverLabel.hidden = true;
444
- this.layer.appendChild(this.hoverLabel);
445
- this.marquee = this.div("marker marquee");
446
- this.marquee.hidden = true;
447
- this.layer.appendChild(this.marquee);
448
- this.document.addEventListener("scroll", this.onScroll, {
449
- passive: true,
450
- capture: true
451
- });
452
- }
453
- onScroll = () => {
454
- if (this.lastState) this.renderState(this.lastState);
455
- };
456
- render(state) {
457
- this.lastState = state;
458
- this.renderState(state);
459
- }
460
- renderState(state) {
461
- this.renderSingle(this.hover, state.hover);
462
- if (state.hover) {
463
- this.hoverLabel.hidden = false;
464
- this.hoverLabel.textContent = describeElement(state.hover);
465
- const rect = state.hover.getBoundingClientRect();
466
- const labelY = rect.top - 20 < 0 ? rect.bottom + 4 : rect.top - 20;
467
- moveTo(this.hoverLabel, rect.left, labelY);
468
- } else {
469
- this.hoverLabel.hidden = true;
470
- }
471
- this.renderList(this.pinnedPool, state.pinned, "marker pinned");
472
- const sel = state.selection ?? [];
473
- const isMultiUnion = sel.length > 1 || state.marquee !== null && sel.length === 0;
474
- if (sel.length === 1) {
475
- this.renderList(this.selectedPool, [sel[0]], "marker selected");
476
- } else {
477
- this.renderList(this.selectedPool, [], "marker selected");
478
- }
479
- const marqueeRect = state.marquee ?? (sel.length > 1 ? unionOf(sel) : null);
480
- if (marqueeRect) {
481
- this.marquee.classList.toggle("is-committed", state.marquee === null);
482
- this.placeRect(this.marquee, marqueeRect);
483
- this.marquee.hidden = false;
484
- } else {
485
- this.marquee.hidden = true;
486
- }
487
- void isMultiUnion;
488
- }
489
- destroy() {
490
- this.document.removeEventListener("scroll", this.onScroll, { capture: true });
491
- this.lastState = null;
492
- this.layer.remove();
493
- }
494
- /* ─── Internals ───────────────────────────────────────────────── */
495
- renderSingle(node, target) {
496
- if (!target) {
497
- node.hidden = true;
498
- return;
499
- }
500
- const r = target.getBoundingClientRect();
501
- if (r.width === 0 && r.height === 0) {
502
- node.hidden = true;
503
- return;
504
- }
505
- this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
506
- node.hidden = false;
507
- }
508
- renderList(pool, targets, className) {
509
- while (pool.length < targets.length) {
510
- const el = this.div(className);
511
- el.hidden = true;
512
- this.layer.appendChild(el);
513
- pool.push(el);
514
- }
515
- for (let i = 0; i < pool.length; i++) {
516
- const node = pool[i];
517
- const target = targets[i];
518
- if (!target) {
519
- node.hidden = true;
520
- continue;
521
- }
522
- const r = target.getBoundingClientRect();
523
- if (r.width === 0 && r.height === 0) {
524
- node.hidden = true;
525
- continue;
526
- }
527
- this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
528
- node.hidden = false;
529
- }
530
- }
531
- placeRect(node, rect) {
532
- moveTo(node, rect.x, rect.y);
533
- node.style.width = `${rect.width}px`;
534
- node.style.height = `${rect.height}px`;
535
- }
536
- div(className) {
537
- const el = this.document.createElement("div");
538
- el.className = className;
539
- return el;
540
- }
541
- };
542
- function moveTo(node, x, y) {
543
- const tx = Math.round(x);
544
- const ty = Math.round(y);
545
- node.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
546
- }
547
- var TAG_LABELS = {
548
- p: "paragraph",
549
- h1: "heading",
550
- h2: "heading",
551
- h3: "heading",
552
- h4: "heading",
553
- h5: "heading",
554
- h6: "heading",
555
- a: "link",
556
- button: "button",
557
- input: "input",
558
- textarea: "textarea",
559
- select: "select",
560
- img: "image",
561
- video: "video",
562
- audio: "audio",
563
- form: "form",
564
- nav: "nav",
565
- header: "header",
566
- footer: "footer",
567
- main: "main",
568
- section: "section",
569
- article: "article",
570
- aside: "aside",
571
- ul: "list",
572
- ol: "list",
573
- li: "list item",
574
- table: "table",
575
- thead: "table head",
576
- tbody: "table body",
577
- tr: "table row",
578
- td: "cell",
579
- th: "header cell",
580
- span: "span",
581
- div: "div",
582
- label: "label",
583
- code: "code",
584
- pre: "code block",
585
- blockquote: "quote",
586
- strong: "bold",
587
- em: "italic",
588
- kbd: "key",
589
- svg: "svg",
590
- canvas: "canvas"
591
- };
592
- function describeElement(el) {
593
- const tag = el.tagName.toLowerCase();
594
- const type = TAG_LABELS[tag] ?? tag;
595
- const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
596
- if (text.length > 0) {
597
- const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
598
- return `${type}: "${preview}"`;
599
- }
600
- if (el.id) return `${type}: #${el.id}`;
601
- const cls = el.classList[0];
602
- if (cls) return `${type}: .${cls}`;
603
- return type;
604
- }
605
- function unionOf(elements) {
606
- let minX = Infinity;
607
- let minY = Infinity;
608
- let maxX = -Infinity;
609
- let maxY = -Infinity;
610
- let any = false;
611
- for (const el of elements) {
612
- const r = el.getBoundingClientRect();
613
- if (r.width === 0 && r.height === 0) continue;
614
- any = true;
615
- if (r.left < minX) minX = r.left;
616
- if (r.top < minY) minY = r.top;
617
- if (r.right > maxX) maxX = r.right;
618
- if (r.bottom > maxY) maxY = r.bottom;
619
- }
620
- if (!any) return null;
621
- const PAD = 4;
622
- return {
623
- x: minX - PAD,
624
- y: minY - PAD,
625
- width: maxX - minX + PAD * 2,
626
- height: maxY - minY + PAD * 2
627
- };
628
- }
629
- var emptyRenderState = {
630
- hover: null,
631
- pinned: [],
632
- selection: null,
633
- marquee: null
634
- };
635
- var Overlay = class {
636
- renderer;
637
- engine;
638
- doc;
639
- win;
640
- searchRoot;
641
- excludeHost;
642
- raf;
643
- caf;
644
- state = emptyRenderState;
645
- unsubscribe = null;
646
- rafHandle = null;
647
- /** Guard for scheduleRender re-entry. Tracked separately from `rafHandle`
648
- * because a synchronous `raf` (used in tests) returns a handle the cb has
649
- * already invalidated — boolean is the safe sentinel. */
650
- renderPending = false;
651
- destroyed = false;
652
- bound = [];
653
- constructor(opts) {
654
- this.engine = opts.engine;
655
- this.doc = opts.document ?? opts.root.ownerDocument ?? null;
656
- if (!this.doc) throw new Error("Overlay: no Document available");
657
- this.win = this.doc.defaultView ?? globalThis;
658
- this.searchRoot = opts.searchRoot ?? this.doc.body;
659
- this.excludeHost = opts.excludeHost ?? null;
660
- this.raf = opts.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
661
- this.caf = opts.caf ?? ((h) => {
662
- if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
663
- else clearTimeout(h);
664
- });
665
- this.renderer = new OverlayRenderer(opts.root, this.doc);
666
- this.bindEvent(this.doc, "scroll", this.onReposition, { passive: true, capture: true });
667
- this.bindEvent(this.win, "resize", this.onReposition, { passive: true });
668
- this.unsubscribe = this.engine.subscribe((s) => this.onEngineState(s));
669
- this.onEngineState(this.engine.getSnapshot());
670
- }
671
- destroy() {
672
- this.destroyed = true;
673
- if (this.unsubscribe) this.unsubscribe();
674
- this.unsubscribe = null;
675
- if (this.rafHandle !== null) this.caf(this.rafHandle);
676
- this.rafHandle = null;
677
- for (const [t, ev, fn, opts] of this.bound) t.removeEventListener(ev, fn, opts);
678
- this.bound = [];
679
- this.renderer.destroy();
680
- }
681
- /* ─── Internals ───────────────────────────────────────────────── */
682
- onReposition = () => {
683
- if (this.destroyed) return;
684
- if (hasAnythingToTrack(this.state)) this.scheduleRender();
685
- };
686
- onEngineState(s) {
687
- this.state = this.derive(s);
688
- this.scheduleRender();
689
- }
690
- /** Engine state → render state. */
691
- derive(s) {
692
- switch (s.kind) {
693
- case "idle":
694
- return emptyRenderState;
695
- case "inspect":
696
- return { hover: s.hoverTarget, pinned: s.pinned, selection: null, marquee: null };
697
- case "pressed":
698
- return { hover: s.target, pinned: s.pinned, selection: null, marquee: null };
699
- case "dragging": {
700
- const rect = rectFromPoints(s.start, s.current);
701
- return { hover: null, pinned: s.pinned, selection: null, marquee: rect };
702
- }
703
- case "annotating": {
704
- const sel = s.selection;
705
- const elements = sel.kind === "area" ? pickElementsInRect(this.searchRoot, sel.rect, this.excludeHost) : sel.kind === "multi" ? sel.elements : [sel.element];
706
- return { hover: null, pinned: s.pinned, selection: elements, marquee: null };
707
- }
708
- }
709
- }
710
- scheduleRender() {
711
- if (this.destroyed || this.renderPending) return;
712
- this.renderPending = true;
713
- this.rafHandle = this.raf(() => {
714
- this.renderPending = false;
715
- this.rafHandle = null;
716
- if (this.destroyed) return;
717
- this.renderer.render(this.state);
718
- });
719
- }
720
- bindEvent(target, ev, fn, opts) {
721
- target.addEventListener(ev, fn, opts);
722
- this.bound.push([target, ev, fn, opts]);
723
- }
724
- };
725
- function hasAnythingToTrack(s) {
726
- return s.hover !== null || s.pinned.length > 0 || s.selection !== null && s.selection.length > 0 || s.marquee !== null;
727
- }
728
- var FIBER_PROP_PREFIX = "__reactFiber$";
729
- function getFiber(node) {
730
- if (!node) return null;
731
- for (const key of Object.keys(node)) {
732
- if (key.startsWith(FIBER_PROP_PREFIX)) return node[key];
733
- }
734
- return null;
735
- }
736
- function getComponentChain(node) {
737
- const fiber = getFiber(node);
738
- if (!fiber) return [];
739
- const names = [];
740
- let cur = fiber;
741
- while (cur) {
742
- const name = getComponentName(cur);
743
- if (name) names.unshift(name);
744
- cur = cur.return;
745
- }
746
- return dedupeConsecutive(names);
747
- }
748
- function getComponentName(fiber) {
749
- const t = fiber.type;
750
- if (!t) return null;
751
- if (typeof t === "string") return null;
752
- if (typeof t === "function") {
753
- const fn = t;
754
- return fn.displayName || fn.name || null;
755
- }
756
- if (typeof t === "object") {
757
- const o = t;
758
- if (o.displayName) return o.displayName;
759
- if (o.render) return o.render.displayName || o.render.name || null;
760
- if (o.type) return o.type.displayName || o.type.name || null;
761
- }
762
- return null;
763
- }
764
- function dedupeConsecutive(names) {
765
- const out = [];
766
- for (const n of names) {
767
- if (out[out.length - 1] !== n) out.push(n);
768
- }
769
- return out;
770
- }
771
- function getSourceInfo(node) {
772
- const fiber = getFiber(node);
773
- if (!fiber) return null;
774
- let cur = fiber._debugOwner;
775
- while (cur) {
776
- if (cur._debugSource) return cur._debugSource;
777
- cur = cur._debugOwner;
778
- }
779
- cur = fiber;
780
- while (cur) {
781
- if (cur._debugSource) return cur._debugSource;
782
- cur = cur.return;
783
- }
784
- return null;
785
- }
786
- function collectAccessibility(el) {
787
- const parts = [];
788
- const role = el.getAttribute("role");
789
- if (role) parts.push(`role=${role}`);
790
- for (const attr of Array.from(el.attributes)) {
791
- if (attr.name.startsWith("aria-") && attr.value) {
792
- parts.push(`${attr.name}=${attr.value}`);
793
- }
794
- }
795
- const tabindex = el.getAttribute("tabindex");
796
- if (tabindex !== null && tabindex !== "") parts.push(`tabindex=${tabindex}`);
797
- const title = el.getAttribute("title");
798
- if (title) parts.push(`title=${title}`);
799
- return parts.join("; ");
800
- }
801
- var GROUPS = {
802
- layout: [
803
- "display",
804
- "position",
805
- "top",
806
- "right",
807
- "bottom",
808
- "left",
809
- "width",
810
- "height",
811
- "margin",
812
- "padding",
813
- "box-sizing"
814
- ],
815
- visual: [
816
- "color",
817
- "background-color",
818
- "border",
819
- "border-radius",
820
- "outline"
821
- ],
822
- text: [
823
- "font-family",
824
- "font-size",
825
- "font-weight",
826
- "line-height",
827
- "letter-spacing",
828
- "text-align",
829
- "white-space"
830
- ],
831
- flexgrid: [
832
- "flex-direction",
833
- "justify-content",
834
- "align-items",
835
- "gap",
836
- "grid-template-columns",
837
- "grid-template-rows"
838
- ],
839
- effects: ["transform", "box-shadow", "filter"],
840
- misc: ["cursor", "z-index", "overflow", "opacity", "transition", "animation"]
841
- };
842
- var TIER_GROUPS = {
843
- compact: [],
844
- standard: ["layout", "visual", "text"],
845
- detailed: ["layout", "visual", "text", "flexgrid", "effects"],
846
- forensic: ["layout", "visual", "text", "flexgrid", "effects", "misc"]
847
- };
848
- function collectComputedStyles(el, detail) {
849
- if (detail === "compact") return {};
850
- const doc = el.ownerDocument;
851
- const win = doc?.defaultView ?? globalThis;
852
- if (typeof win.getComputedStyle !== "function") return {};
853
- const cs = win.getComputedStyle(el);
854
- const props = /* @__PURE__ */ new Set();
855
- for (const g of TIER_GROUPS[detail]) {
856
- for (const p of GROUPS[g]) props.add(p);
857
- }
858
- const out = {};
859
- for (const p of props) {
860
- const v = cs.getPropertyValue(p);
861
- if (!v) continue;
862
- const trimmed = v.trim();
863
- if (isUninterestingDefault(p, trimmed)) continue;
864
- out[p] = trimmed;
865
- }
866
- return out;
867
- }
868
- function isUninterestingDefault(prop, value) {
869
- if (!value || value === "none" || value === "auto" || value === "normal") return true;
870
- if (value === "0px" || value === "0%") return true;
871
- if (value === "rgba(0, 0, 0, 0)") return true;
872
- if (prop === "color" || prop === "background-color") {
873
- return value === "rgba(0, 0, 0, 0)";
874
- }
875
- return false;
876
- }
877
- var SHORT_MAX_DEPTH = 5;
878
- var FULL_MAX_DEPTH = 8;
879
- function buildSelector(target, doc = target.ownerDocument ?? document) {
880
- if (target.id && isStableId(target.id)) {
881
- const escaped = cssEscape(target.id);
882
- const id = `#${escaped}`;
883
- return { short: id, full: id };
884
- }
885
- const segments = [];
886
- let cur = target;
887
- let short = null;
888
- for (let depth = 0; cur && depth < FULL_MAX_DEPTH; depth++) {
889
- segments.unshift(segmentFor(cur));
890
- const candidate = segments.join(" > ");
891
- if (short === null && depth < SHORT_MAX_DEPTH && isUnique(doc, candidate)) {
892
- short = candidate;
893
- }
894
- cur = cur.parentElement;
895
- if (!cur || cur === doc.documentElement || cur.tagName.toLowerCase() === "html") break;
896
- }
897
- const full = segments.join(" > ");
898
- return { short: short ?? full, full };
899
- }
900
- function segmentFor(el) {
901
- const tag = el.tagName.toLowerCase();
902
- if (el.id && isStableId(el.id)) return `${tag}#${cssEscape(el.id)}`;
903
- const classes = Array.from(el.classList).filter(isUseableClass).slice(0, 3);
904
- let segment = classes.length ? `${tag}.${classes.map(cssEscape).join(".")}` : tag;
905
- const parent = el.parentElement;
906
- if (parent) {
907
- const sameTag = Array.from(parent.children).filter(
908
- (c) => c.tagName === el.tagName
909
- );
910
- if (sameTag.length > 1) {
911
- const idx = sameTag.indexOf(el) + 1;
912
- if (idx > 0) segment += `:nth-of-type(${idx})`;
913
- }
914
- }
915
- return segment;
916
- }
917
- function isUseableClass(c) {
918
- if (!c) return false;
919
- if (c.length > 30) return false;
920
- if (/^css-[a-z0-9]+$/i.test(c)) return false;
921
- if (/^jsx-\d+$/i.test(c)) return false;
922
- if (/^_.+_[a-z0-9]{5,}$/i.test(c)) return false;
923
- if (/^\d/.test(c)) return false;
924
- return true;
925
- }
926
- function isStableId(id) {
927
- if (/^:[a-z]\d+:/i.test(id)) return false;
928
- if (/^radix-/i.test(id)) return false;
929
- if (/^headlessui-/i.test(id)) return false;
930
- return true;
931
- }
932
- function isUnique(doc, selector) {
933
- try {
934
- return doc.querySelectorAll(selector).length === 1;
935
- } catch {
936
- return false;
937
- }
938
- }
939
- function cssEscape(s) {
940
- const g = globalThis;
941
- if (g.CSS?.escape) return g.CSS.escape(s);
942
- return s.replace(/[^a-zA-Z0-9_-]/g, (c) => "\\" + c);
943
- }
944
- var NEARBY_MAX = 200;
945
- function collectNearbyText(el) {
946
- const own = readText(el);
947
- if (own) return truncate(own, NEARBY_MAX);
948
- const parent = el.parentElement;
949
- if (parent) {
950
- const t = readText(parent);
951
- if (t) return truncate(t, NEARBY_MAX);
952
- }
953
- return "";
954
- }
955
- function collectSelectedText(doc = document) {
956
- const win = doc.defaultView ?? globalThis;
957
- if (typeof win.getSelection !== "function") return "";
958
- const sel = win.getSelection();
959
- if (!sel) return "";
960
- return sel.toString().trim();
961
- }
962
- function readText(el) {
963
- const t = el.innerText ?? el.textContent ?? "";
964
- return t.replace(/\s+/g, " ").trim();
965
- }
966
- function truncate(s, n) {
967
- return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
968
- }
969
- var POSITIONED_FIXED = /* @__PURE__ */ new Set(["fixed", "sticky"]);
970
- function collectMetadata(el, options = {}) {
971
- perfMark2("clickly:metadata:collect");
972
- const detail = options.detail ?? "standard";
973
- const includeReact = options.includeReact !== false && detail !== "compact";
974
- const doc = options.document ?? el.ownerDocument ?? document;
975
- const { short, full } = buildSelector(el, doc);
976
- const rect = el.getBoundingClientRect();
977
- const boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
978
- const reactComponents = includeReact ? getComponentChain(el).join(" > ") : "";
979
- const source = includeReact ? getSourceInfo(el) : null;
980
- return {
981
- element: el.tagName.toLowerCase(),
982
- elementPath: short,
983
- fullPath: full,
984
- cssClasses: typeof el.className === "string" ? el.className.trim() : "",
985
- computedStyles: collectComputedStyles(el, detail),
986
- accessibility: collectAccessibility(el),
987
- nearbyText: collectNearbyText(el),
988
- selectedText: options.selectedText ?? collectSelectedText(doc),
989
- boundingBox,
990
- isFixed: hasFixedAncestor(el),
991
- reactComponents,
992
- sourceFile: source?.fileName ?? "",
993
- sourceLine: source?.lineNumber ?? 0,
994
- sourceColumn: source?.columnNumber ?? 0
995
- };
996
- }
997
- function perfMark2(name) {
998
- if (typeof performance !== "undefined" && typeof performance.mark === "function") {
999
- try {
1000
- performance.mark(name);
1001
- } catch {
1002
- }
1003
- }
1004
- }
1005
- function hasFixedAncestor(el) {
1006
- const doc = el.ownerDocument;
1007
- const win = doc?.defaultView ?? globalThis;
1008
- if (typeof win.getComputedStyle !== "function") return false;
1009
- let cur = el;
1010
- for (let i = 0; cur && i < 8; i++) {
1011
- const pos = win.getComputedStyle(cur).getPropertyValue("position");
1012
- if (POSITIONED_FIXED.has(pos)) return true;
1013
- cur = cur.parentElement;
1014
- }
1015
- return false;
1016
- }
1017
-
1018
- // packages/react/src/internal/ClicklyRoot.tsx
10
+ // ../react/src/internal/ClicklyRoot.tsx
1019
11
  import { useEffect as useEffect6, useState as useState6 } from "react";
1020
12
 
1021
- // packages/react/src/internal/Toolbar.tsx
13
+ // ../react/src/internal/Toolbar.tsx
1022
14
  import { useRef as useRef4, useState as useState3 } from "react";
1023
15
 
1024
- // packages/react/src/hooks/useDraggable.ts
16
+ // ../react/src/hooks/useDraggable.ts
1025
17
  import { useCallback, useEffect, useRef, useState } from "react";
1026
18
  function useDraggable(defaultPos, size) {
1027
19
  const [position, setPosition] = useState(defaultPos);
@@ -1078,7 +70,7 @@ function clamp(n, lo, hi) {
1078
70
  return Math.max(lo, Math.min(hi, n));
1079
71
  }
1080
72
 
1081
- // packages/react/src/state/useEngineState.ts
73
+ // ../react/src/state/useEngineState.ts
1082
74
  import { useSyncExternalStore } from "react";
1083
75
  function useEngineState(engine) {
1084
76
  return useSyncExternalStore(
@@ -1090,118 +82,9 @@ function useEngineState(engine) {
1090
82
  }
1091
83
  var IDLE = { kind: "idle" };
1092
84
 
1093
- // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/vanilla.mjs
1094
- var createStoreImpl = (createState) => {
1095
- let state;
1096
- const listeners = /* @__PURE__ */ new Set();
1097
- const setState = (partial, replace) => {
1098
- const nextState = typeof partial === "function" ? partial(state) : partial;
1099
- if (!Object.is(nextState, state)) {
1100
- const previousState = state;
1101
- state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
1102
- listeners.forEach((listener) => listener(state, previousState));
1103
- }
1104
- };
1105
- const getState = () => state;
1106
- const getInitialState = () => initialState2;
1107
- const subscribe = (listener) => {
1108
- listeners.add(listener);
1109
- return () => listeners.delete(listener);
1110
- };
1111
- const api = { setState, getState, getInitialState, subscribe };
1112
- const initialState2 = state = createState(setState, getState, api);
1113
- return api;
1114
- };
1115
- var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
1116
-
1117
- // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react.mjs
1118
- import React from "react";
1119
- var identity = (arg) => arg;
1120
- function useStore(api, selector = identity) {
1121
- const slice = React.useSyncExternalStore(
1122
- api.subscribe,
1123
- React.useCallback(() => selector(api.getState()), [api, selector]),
1124
- React.useCallback(() => selector(api.getInitialState()), [api, selector])
1125
- );
1126
- React.useDebugValue(slice);
1127
- return slice;
1128
- }
1129
- var createImpl = (createState) => {
1130
- const api = createStore(createState);
1131
- const useBoundStore = (selector) => useStore(api, selector);
1132
- Object.assign(useBoundStore, api);
1133
- return useBoundStore;
1134
- };
1135
- var create = ((createState) => createState ? createImpl(createState) : createImpl);
1136
-
1137
- // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
1138
- import React2 from "react";
1139
-
1140
- // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/vanilla/shallow.mjs
1141
- var isIterable = (obj) => Symbol.iterator in obj;
1142
- var hasIterableEntries = (value) => (
1143
- // HACK: avoid checking entries type
1144
- "entries" in value
1145
- );
1146
- var compareEntries = (valueA, valueB) => {
1147
- const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
1148
- const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
1149
- if (mapA.size !== mapB.size) {
1150
- return false;
1151
- }
1152
- for (const [key, value] of mapA) {
1153
- if (!mapB.has(key) || !Object.is(value, mapB.get(key))) {
1154
- return false;
1155
- }
1156
- }
1157
- return true;
1158
- };
1159
- var compareIterables = (valueA, valueB) => {
1160
- const iteratorA = valueA[Symbol.iterator]();
1161
- const iteratorB = valueB[Symbol.iterator]();
1162
- let nextA = iteratorA.next();
1163
- let nextB = iteratorB.next();
1164
- while (!nextA.done && !nextB.done) {
1165
- if (!Object.is(nextA.value, nextB.value)) {
1166
- return false;
1167
- }
1168
- nextA = iteratorA.next();
1169
- nextB = iteratorB.next();
1170
- }
1171
- return !!nextA.done && !!nextB.done;
1172
- };
1173
- function shallow(valueA, valueB) {
1174
- if (Object.is(valueA, valueB)) {
1175
- return true;
1176
- }
1177
- if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
1178
- return false;
1179
- }
1180
- if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
1181
- return false;
1182
- }
1183
- if (isIterable(valueA) && isIterable(valueB)) {
1184
- if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
1185
- return compareEntries(valueA, valueB);
1186
- }
1187
- return compareIterables(valueA, valueB);
1188
- }
1189
- return compareEntries(
1190
- { entries: () => Object.entries(valueA) },
1191
- { entries: () => Object.entries(valueB) }
1192
- );
1193
- }
1194
-
1195
- // node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
1196
- function useShallow(selector) {
1197
- const prev = React2.useRef(void 0);
1198
- return (state) => {
1199
- const next = selector(state);
1200
- return shallow(prev.current, next) ? prev.current : prev.current = next;
1201
- };
1202
- }
1203
-
1204
- // packages/react/src/state/annotations.ts
85
+ // ../react/src/state/annotations.ts
86
+ import { create } from "zustand";
87
+ import { useShallow } from "zustand/react/shallow";
1205
88
  var useAnnotations = create((set, get) => ({
1206
89
  byId: {},
1207
90
  order: [],
@@ -1232,7 +115,8 @@ function useAnnotationsList() {
1232
115
  );
1233
116
  }
1234
117
 
1235
- // packages/react/src/state/settings.ts
118
+ // ../react/src/state/settings.ts
119
+ import { create as create2 } from "zustand";
1236
120
  var DEFAULTS = {
1237
121
  outputDetail: "standard",
1238
122
  copyOnAdd: true,
@@ -1258,7 +142,7 @@ function persist(s) {
1258
142
  } catch {
1259
143
  }
1260
144
  }
1261
- var useSettings = create((set) => ({
145
+ var useSettings = create2((set) => ({
1262
146
  ...load(),
1263
147
  set: (patch) => set((cur) => {
1264
148
  const next = { ...cur, ...patch };
@@ -1271,7 +155,7 @@ var useSettings = create((set) => ({
1271
155
  }
1272
156
  }));
1273
157
 
1274
- // packages/react/src/output/markdown.ts
158
+ // ../react/src/output/markdown.ts
1275
159
  function annotationsToMarkdown(annotations, detail = "standard") {
1276
160
  if (!annotations.length) return "(no annotations)";
1277
161
  return annotations.map((a, i) => formatOne(a, i + 1, detail)).join("\n\n");
@@ -1294,7 +178,7 @@ function formatOne(a, index, detail) {
1294
178
  }
1295
179
  if (detail === "detailed" || detail === "forensic") {
1296
180
  if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
1297
- if (a.nearbyText) lines.push(`**Nearby text:** ${truncate2(a.nearbyText, 120)}`);
181
+ if (a.nearbyText) lines.push(`**Nearby text:** ${truncate(a.nearbyText, 120)}`);
1298
182
  }
1299
183
  if (detail === "forensic" && a.computedStyles) {
1300
184
  lines.push("**Computed styles:**\n```css\n" + a.computedStyles + "\n```");
@@ -1306,11 +190,11 @@ function formatOne(a, index, detail) {
1306
190
  if (a.severity) lines.push(`**Severity:** ${a.severity}`);
1307
191
  return lines.join("\n");
1308
192
  }
1309
- function truncate2(s, n) {
193
+ function truncate(s, n) {
1310
194
  return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
1311
195
  }
1312
196
 
1313
- // packages/react/src/internal/icons.tsx
197
+ // ../react/src/internal/icons.tsx
1314
198
  import { jsx, jsxs } from "react/jsx-runtime";
1315
199
  function Icon({
1316
200
  children,
@@ -1332,6 +216,18 @@ function Icon({
1332
216
  }
1333
217
  );
1334
218
  }
219
+ var IconFreeze = () => /* @__PURE__ */ jsxs(Icon, { size: 15, children: [
220
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "2", x2: "12", y2: "22" }),
221
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
222
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "5", x2: "19", y2: "19" }),
223
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "5", x2: "5", y2: "19" }),
224
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor", stroke: "none" })
225
+ ] });
226
+ var IconInfo = () => /* @__PURE__ */ jsxs(Icon, { size: 14, children: [
227
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
228
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "8", strokeWidth: "2.5" }),
229
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "12", x2: "12", y2: "16" })
230
+ ] });
1335
231
  var IconCursor = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M4 4l6 16 2-7 7-2z" }) });
1336
232
  var IconLayers = () => /* @__PURE__ */ jsxs(Icon, { children: [
1337
233
  /* @__PURE__ */ jsx("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
@@ -1359,7 +255,7 @@ var IconClose = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx(
1359
255
  var IconCheck = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12l5 5L20 7" }) });
1360
256
  var IconList = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
1361
257
 
1362
- // packages/react/src/internal/SettingsPopover.tsx
258
+ // ../react/src/internal/SettingsPopover.tsx
1363
259
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
1364
260
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1365
261
  function SettingsPopover({ anchor, width, onClose }) {
@@ -1460,7 +356,7 @@ function SettingsPopover({ anchor, width, onClose }) {
1460
356
  ] });
1461
357
  }
1462
358
 
1463
- // packages/react/src/internal/AnnotationList.tsx
359
+ // ../react/src/internal/AnnotationList.tsx
1464
360
  import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
1465
361
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1466
362
  function AnnotationList({ anchor, width, onClose }) {
@@ -1548,9 +444,48 @@ function AnnotationCard({
1548
444
  ] });
1549
445
  }
1550
446
 
1551
- // packages/react/src/internal/Toolbar.tsx
447
+ // ../react/src/internal/Toolbar.tsx
1552
448
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1553
- var TOOLBAR_SIZE = { width: 320, height: 44 };
449
+ var TOOLBAR_SIZE = { width: 360, height: 44 };
450
+ var SHORTCUTS = [
451
+ { keys: ["\u2318", "\u21E7", "F"], label: "Toggle toolbar" },
452
+ { keys: ["1"], label: "Single select mode" },
453
+ { keys: ["2"], label: "Multi select mode" },
454
+ { keys: ["3"], label: "Area drag mode" },
455
+ { keys: ["\u21B5"], label: "Annotate pinned elements" },
456
+ { keys: ["Esc"], label: "Cancel / clear / close" },
457
+ { keys: ["\u2318", "\u21B5"], label: "Submit annotation" },
458
+ { keys: ["C"], label: "Copy all annotations" },
459
+ { keys: ["X"], label: "Clear all annotations" }
460
+ ];
461
+ function ShortcutsPanel() {
462
+ const [visible, setVisible] = useState3(false);
463
+ return /* @__PURE__ */ jsxs4(
464
+ "span",
465
+ {
466
+ className: "clickly-tip shortcuts-trigger",
467
+ onMouseEnter: () => setVisible(true),
468
+ onMouseLeave: () => setVisible(false),
469
+ children: [
470
+ /* @__PURE__ */ jsx4(
471
+ "button",
472
+ {
473
+ className: "clickly-btn icon-only",
474
+ "aria-label": "Show keyboard shortcuts",
475
+ children: /* @__PURE__ */ jsx4(IconInfo, {})
476
+ }
477
+ ),
478
+ visible && /* @__PURE__ */ jsxs4("div", { className: "shortcuts-panel", role: "tooltip", children: [
479
+ /* @__PURE__ */ jsx4("div", { className: "shortcuts-title", children: "Keyboard shortcuts" }),
480
+ SHORTCUTS.map((s) => /* @__PURE__ */ jsxs4("div", { className: "shortcuts-row", children: [
481
+ /* @__PURE__ */ jsx4("span", { className: "shortcuts-label", children: s.label }),
482
+ /* @__PURE__ */ jsx4("span", { className: "shortcuts-keys", children: s.keys.map((k) => /* @__PURE__ */ jsx4("kbd", { children: k }, k)) })
483
+ ] }, s.label))
484
+ ] })
485
+ ]
486
+ }
487
+ );
488
+ }
1554
489
  function Tip({
1555
490
  label,
1556
491
  shortcut,
@@ -1564,7 +499,7 @@ function Tip({
1564
499
  ] })
1565
500
  ] });
1566
501
  }
1567
- function Toolbar({ engine, onCollapse }) {
502
+ function Toolbar({ engine, onCollapse, isClosing, onCloseEnd, frozen, onFreezeToggle }) {
1568
503
  const state = useEngineState(engine);
1569
504
  const annotations = useAnnotationsList();
1570
505
  const clearAnnotations = useAnnotations((s) => s.clear);
@@ -1597,8 +532,9 @@ function Toolbar({ engine, onCollapse }) {
1597
532
  "div",
1598
533
  {
1599
534
  ref: anchorRef,
1600
- className: "clickly-toolbar",
535
+ className: `clickly-toolbar${isClosing ? " is-closing" : ""}`,
1601
536
  style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
537
+ onAnimationEnd: isClosing ? onCloseEnd : void 0,
1602
538
  "aria-label": "Clickly toolbar",
1603
539
  children: [
1604
540
  /* @__PURE__ */ jsx4(Tip, { label: "Move", children: /* @__PURE__ */ jsx4(
@@ -1654,6 +590,16 @@ function Toolbar({ engine, onCollapse }) {
1654
590
  ) }),
1655
591
  /* @__PURE__ */ jsx4("div", { className: "divider" })
1656
592
  ] }),
593
+ /* @__PURE__ */ jsx4(Tip, { label: frozen ? "Unfreeze animations" : "Freeze animations", children: /* @__PURE__ */ jsx4(
594
+ "button",
595
+ {
596
+ className: `clickly-btn icon-only${frozen ? " is-freeze" : ""}`,
597
+ onClick: onFreezeToggle,
598
+ "aria-label": frozen ? "Unfreeze page animations" : "Freeze page animations",
599
+ "aria-pressed": frozen,
600
+ children: /* @__PURE__ */ jsx4(IconFreeze, {})
601
+ }
602
+ ) }),
1657
603
  /* @__PURE__ */ jsx4(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ jsxs4(
1658
604
  "button",
1659
605
  {
@@ -1687,6 +633,7 @@ function Toolbar({ engine, onCollapse }) {
1687
633
  }
1688
634
  ) }),
1689
635
  /* @__PURE__ */ jsx4("div", { className: "divider" }),
636
+ /* @__PURE__ */ jsx4(ShortcutsPanel, {}),
1690
637
  /* @__PURE__ */ jsx4(Tip, { label: "Settings", children: /* @__PURE__ */ jsx4(
1691
638
  "button",
1692
639
  {
@@ -1727,7 +674,7 @@ function Toolbar({ engine, onCollapse }) {
1727
674
  );
1728
675
  }
1729
676
 
1730
- // packages/react/src/internal/CollapsedFAB.tsx
677
+ // ../react/src/internal/CollapsedFAB.tsx
1731
678
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1732
679
  function CollapsedFAB({ onExpand }) {
1733
680
  const annotations = useAnnotationsList();
@@ -1746,9 +693,27 @@ function CollapsedFAB({ onExpand }) {
1746
693
  );
1747
694
  }
1748
695
 
1749
- // packages/react/src/internal/AnnotationPopup.tsx
696
+ // ../react/src/internal/AnnotationPopup.tsx
1750
697
  import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef5, useState as useState4 } from "react";
1751
- import { nanoid } from "nanoid";
698
+
699
+ // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/url-alphabet/index.js
700
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
701
+
702
+ // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/index.browser.js
703
+ var nanoid = (size = 21) => {
704
+ let id = "";
705
+ let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
706
+ while (size--) {
707
+ id += urlAlphabet[bytes[size] & 63];
708
+ }
709
+ return id;
710
+ };
711
+
712
+ // ../react/src/internal/AnnotationPopup.tsx
713
+ import {
714
+ collectMetadata,
715
+ collectComputedStyles
716
+ } from "@useclickly/core";
1752
717
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1753
718
  var POPUP_W = 320;
1754
719
  var POPUP_H_EST = 240;
@@ -2024,7 +989,7 @@ function anchorRect(sel) {
2024
989
  if (sel.elements.length === 0) return null;
2025
990
  return sel.elements[0].getBoundingClientRect();
2026
991
  }
2027
- var TAG_LABELS2 = {
992
+ var TAG_LABELS = {
2028
993
  p: "paragraph",
2029
994
  h1: "heading",
2030
995
  h2: "heading",
@@ -2066,9 +1031,9 @@ var TAG_LABELS2 = {
2066
1031
  svg: "svg",
2067
1032
  canvas: "canvas"
2068
1033
  };
2069
- function describeElement2(el) {
1034
+ function describeElement(el) {
2070
1035
  const tag = el.tagName.toLowerCase();
2071
- const type = TAG_LABELS2[tag] ?? tag;
1036
+ const type = TAG_LABELS[tag] ?? tag;
2072
1037
  const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
2073
1038
  if (text.length > 0) {
2074
1039
  const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
@@ -2080,13 +1045,13 @@ function describeElement2(el) {
2080
1045
  return type;
2081
1046
  }
2082
1047
  function describeSelection(sel) {
2083
- if (sel.kind === "single") return describeElement2(sel.element);
1048
+ if (sel.kind === "single") return describeElement(sel.element);
2084
1049
  if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
2085
- if (sel.elements.length === 1) return describeElement2(sel.elements[0]);
1050
+ if (sel.elements.length === 1) return describeElement(sel.elements[0]);
2086
1051
  return `${sel.elements.length} element(s)`;
2087
1052
  }
2088
1053
 
2089
- // packages/react/src/internal/AnnotationPins.tsx
1054
+ // ../react/src/internal/AnnotationPins.tsx
2090
1055
  import { useEffect as useEffect5, useRef as useRef6, useState as useState5 } from "react";
2091
1056
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2092
1057
  function AnnotationPins() {
@@ -2111,7 +1076,7 @@ function AnnotationPins() {
2111
1076
  }, []);
2112
1077
  return /* @__PURE__ */ jsx7(Fragment2, { children: annotations.map((a, i) => /* @__PURE__ */ jsx7(Pin, { number: i + 1, annotation: a }, a.id)) });
2113
1078
  }
2114
- var TAG_LABELS3 = {
1079
+ var TAG_LABELS2 = {
2115
1080
  p: "paragraph",
2116
1081
  h1: "heading",
2117
1082
  h2: "heading",
@@ -2151,7 +1116,7 @@ var TAG_LABELS3 = {
2151
1116
  function pinLabel(annotation) {
2152
1117
  const raw = (annotation.element ?? "").toLowerCase();
2153
1118
  const tag = raw.split(/[.#\s]/)[0] ?? "";
2154
- return (TAG_LABELS3[tag] ?? tag) || "element";
1119
+ return (TAG_LABELS2[tag] ?? tag) || "element";
2155
1120
  }
2156
1121
  function parseStyles(raw) {
2157
1122
  if (!raw) return [];
@@ -2329,13 +1294,22 @@ function resolvePosition(a) {
2329
1294
  return null;
2330
1295
  }
2331
1296
 
2332
- // packages/react/src/internal/ClicklyRoot.tsx
1297
+ // ../react/src/internal/ClicklyRoot.tsx
2333
1298
  import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2334
1299
  function ClicklyRoot({
2335
1300
  engine,
2336
1301
  host
2337
1302
  }) {
2338
1303
  const [expanded, setExpanded] = useState6(false);
1304
+ const [toolbarClosing, setToolbarClosing] = useState6(false);
1305
+ const [frozen, setFrozen] = useState6(false);
1306
+ const collapse = () => {
1307
+ setToolbarClosing(true);
1308
+ };
1309
+ const onToolbarCloseEnd = () => {
1310
+ setToolbarClosing(false);
1311
+ setExpanded(false);
1312
+ };
2339
1313
  const clearAnnotations = useAnnotations((s) => s.clear);
2340
1314
  const outputDetail = useSettings((s) => s.outputDetail);
2341
1315
  const markerColor = useSettings((s) => s.markerColor);
@@ -2343,6 +1317,32 @@ function ClicklyRoot({
2343
1317
  useEffect6(() => {
2344
1318
  host.style.setProperty("--clickly-hover", markerColor);
2345
1319
  }, [host, markerColor]);
1320
+ useEffect6(() => {
1321
+ const STYLE_ID = "clickly-freeze-animations";
1322
+ const gsap = window.gsap;
1323
+ if (frozen) {
1324
+ if (!document.getElementById(STYLE_ID)) {
1325
+ const el = document.createElement("style");
1326
+ el.id = STYLE_ID;
1327
+ el.textContent = `
1328
+ *, *::before, *::after {
1329
+ animation-play-state: paused !important;
1330
+ transition-duration: 0ms !important;
1331
+ transition-delay: 0ms !important;
1332
+ }
1333
+ `;
1334
+ document.head.appendChild(el);
1335
+ }
1336
+ if (gsap?.globalTimeline) gsap.globalTimeline.pause();
1337
+ } else {
1338
+ document.getElementById(STYLE_ID)?.remove();
1339
+ if (gsap?.globalTimeline) gsap.globalTimeline.resume();
1340
+ }
1341
+ return () => {
1342
+ document.getElementById(STYLE_ID)?.remove();
1343
+ if (gsap?.globalTimeline) gsap.globalTimeline.resume();
1344
+ };
1345
+ }, [frozen]);
2346
1346
  useEffect6(() => {
2347
1347
  if (expanded) {
2348
1348
  if (engine.getSnapshot().kind === "idle") engine.activate("single");
@@ -2377,12 +1377,13 @@ function ClicklyRoot({
2377
1377
  if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
2378
1378
  if (e.key.toLowerCase() === "f" && e.shiftKey && (e.metaKey || e.ctrlKey)) {
2379
1379
  e.preventDefault();
2380
- setExpanded((v) => !v);
1380
+ if (expanded) collapse();
1381
+ else setExpanded(true);
2381
1382
  return;
2382
1383
  }
2383
1384
  if (e.key === "Escape") {
2384
1385
  if (engine.getSnapshot().kind === "annotating") return;
2385
- if (expanded) setExpanded(false);
1386
+ if (expanded) collapse();
2386
1387
  return;
2387
1388
  }
2388
1389
  if (!expanded) return;
@@ -2413,14 +1414,24 @@ function ClicklyRoot({
2413
1414
  }, [clearAnnotations, outputDetail, expanded, engine]);
2414
1415
  return /* @__PURE__ */ jsxs8("div", { className: "clickly-ui", children: [
2415
1416
  /* @__PURE__ */ jsx8(AnnotationPins, {}),
2416
- expanded ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
2417
- /* @__PURE__ */ jsx8(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
2418
- /* @__PURE__ */ jsx8(AnnotationPopup, { engine })
1417
+ expanded || toolbarClosing ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
1418
+ /* @__PURE__ */ jsx8(
1419
+ Toolbar,
1420
+ {
1421
+ engine,
1422
+ onCollapse: collapse,
1423
+ isClosing: toolbarClosing,
1424
+ onCloseEnd: onToolbarCloseEnd,
1425
+ frozen,
1426
+ onFreezeToggle: () => setFrozen((v) => !v)
1427
+ }
1428
+ ),
1429
+ !toolbarClosing && /* @__PURE__ */ jsx8(AnnotationPopup, { engine })
2419
1430
  ] }) : /* @__PURE__ */ jsx8(CollapsedFAB, { onExpand: () => setExpanded(true) })
2420
1431
  ] });
2421
1432
  }
2422
1433
 
2423
- // packages/react/src/internal/styles.ts
1434
+ // ../react/src/internal/styles.ts
2424
1435
  var REACT_UI_CSS = `
2425
1436
  .clickly-ui, .clickly-ui * { box-sizing: border-box; }
2426
1437
 
@@ -2446,6 +1457,11 @@ var REACT_UI_CSS = `
2446
1457
  0 0 0 1px rgba(255,255,255,0.06) inset;
2447
1458
  transition: transform 140ms ease, box-shadow 140ms ease;
2448
1459
  z-index: 1;
1460
+ animation: clickly-fab-open 280ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
1461
+ }
1462
+ @keyframes clickly-fab-open {
1463
+ from { opacity: 0; transform: scale(0.5); }
1464
+ to { opacity: 1; transform: scale(1); }
2449
1465
  }
2450
1466
  .clickly-fab:hover {
2451
1467
  transform: scale(1.06);
@@ -2492,12 +1508,21 @@ var REACT_UI_CSS = `
2492
1508
  pointer-events: auto;
2493
1509
  user-select: none;
2494
1510
  z-index: 1;
2495
- animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
1511
+ transform-origin: bottom right;
1512
+ animation: clickly-toolbar-open 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
1513
+ }
1514
+ .clickly-toolbar.is-closing {
1515
+ animation: clickly-toolbar-close 200ms cubic-bezier(0.4, 0, 1, 1) both;
1516
+ pointer-events: none;
2496
1517
  }
2497
1518
 
2498
- @keyframes clickly-fade-in {
2499
- from { opacity: 0; transform: translateY(6px) scale(0.97); }
2500
- to { opacity: 1; transform: translateY(0) scale(1); }
1519
+ @keyframes clickly-toolbar-open {
1520
+ from { opacity: 0; transform: scale(0.82) translateY(10px); }
1521
+ to { opacity: 1; transform: scale(1) translateY(0); }
1522
+ }
1523
+ @keyframes clickly-toolbar-close {
1524
+ from { opacity: 1; transform: scale(1) translateY(0); }
1525
+ to { opacity: 0; transform: scale(0.82) translateY(10px); }
2501
1526
  }
2502
1527
 
2503
1528
  .clickly-toolbar .grip {
@@ -2548,6 +1573,14 @@ var REACT_UI_CSS = `
2548
1573
  .clickly-btn.is-active:hover { background: #0284c7; }
2549
1574
  .clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
2550
1575
 
1576
+ /* Freeze-animations active state \u2014 icy blue */
1577
+ .clickly-btn.is-freeze {
1578
+ background: rgba(99, 179, 237, 0.18);
1579
+ color: #63b3ed;
1580
+ box-shadow: 0 0 0 1px rgba(99,179,237,0.35), 0 2px 8px rgba(99,179,237,0.2);
1581
+ }
1582
+ .clickly-btn.is-freeze:hover { background: rgba(99, 179, 237, 0.26); }
1583
+
2551
1584
  .clickly-btn.icon-only {
2552
1585
  width: 32px;
2553
1586
  padding: 0;
@@ -2625,6 +1658,91 @@ var REACT_UI_CSS = `
2625
1658
  color: #94a3b8;
2626
1659
  }
2627
1660
 
1661
+ /* \u2500\u2500\u2500 Shortcuts panel \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 */
1662
+
1663
+ .shortcuts-trigger {
1664
+ position: relative;
1665
+ display: inline-flex;
1666
+ align-items: center;
1667
+ justify-content: center;
1668
+ }
1669
+
1670
+ .shortcuts-panel {
1671
+ position: absolute;
1672
+ bottom: calc(100% + 12px);
1673
+ left: 50%;
1674
+ transform: translateX(-50%);
1675
+ width: 260px;
1676
+ background: rgba(9, 14, 28, 0.97);
1677
+ border: 1px solid rgba(255,255,255,0.08);
1678
+ border-radius: 12px;
1679
+ box-shadow: 0 16px 40px rgba(0,0,0,0.45);
1680
+ padding: 10px;
1681
+ z-index: 10;
1682
+ animation: clickly-fade-in 100ms ease-out;
1683
+ pointer-events: none;
1684
+ }
1685
+
1686
+ /* Arrow pointing down */
1687
+ .shortcuts-panel::after {
1688
+ content: "";
1689
+ position: absolute;
1690
+ top: 100%;
1691
+ left: 50%;
1692
+ transform: translateX(-50%);
1693
+ border: 6px solid transparent;
1694
+ border-top-color: rgba(9, 14, 28, 0.97);
1695
+ }
1696
+
1697
+ .shortcuts-title {
1698
+ font: 600 11px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1699
+ color: #64748b;
1700
+ text-transform: uppercase;
1701
+ letter-spacing: 0.06em;
1702
+ padding: 2px 4px 8px;
1703
+ border-bottom: 1px solid rgba(255,255,255,0.06);
1704
+ margin-bottom: 6px;
1705
+ }
1706
+
1707
+ .shortcuts-row {
1708
+ display: flex;
1709
+ align-items: center;
1710
+ justify-content: space-between;
1711
+ padding: 4px 4px;
1712
+ border-radius: 6px;
1713
+ gap: 8px;
1714
+ }
1715
+ .shortcuts-row:hover { background: rgba(255,255,255,0.04); }
1716
+
1717
+ .shortcuts-label {
1718
+ font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1719
+ color: #94a3b8;
1720
+ flex: 1;
1721
+ min-width: 0;
1722
+ }
1723
+
1724
+ .shortcuts-keys {
1725
+ display: flex;
1726
+ gap: 3px;
1727
+ align-items: center;
1728
+ flex-shrink: 0;
1729
+ }
1730
+
1731
+ .shortcuts-keys kbd {
1732
+ display: inline-flex;
1733
+ align-items: center;
1734
+ justify-content: center;
1735
+ min-width: 20px;
1736
+ height: 20px;
1737
+ padding: 0 5px;
1738
+ background: rgba(255,255,255,0.08);
1739
+ border: 1px solid rgba(255,255,255,0.10);
1740
+ border-bottom-width: 2px;
1741
+ border-radius: 5px;
1742
+ font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
1743
+ color: #e2e8f0;
1744
+ }
1745
+
2628
1746
  /* \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 */
2629
1747
 
2630
1748
  .clickly-popup, .clickly-popover {
@@ -3376,7 +2494,7 @@ var REACT_UI_CSS = `
3376
2494
  }
3377
2495
  `;
3378
2496
 
3379
- // packages/react/src/internal/globalStyles.ts
2497
+ // ../react/src/internal/globalStyles.ts
3380
2498
  var GLOBAL_PAGE_CSS = `
3381
2499
  body[data-clickly-active] {
3382
2500
  -webkit-user-select: none !important;
@@ -3397,7 +2515,7 @@ body[data-clickly-annotating] {
3397
2515
  }
3398
2516
  `;
3399
2517
 
3400
- // packages/react/src/Clickly.tsx
2518
+ // ../react/src/Clickly.tsx
3401
2519
  import { jsx as jsx9 } from "react/jsx-runtime";
3402
2520
  function Clickly({ className } = {}) {
3403
2521
  const [mount, setMount] = useState7(null);