@useclickly/react 1.0.3 → 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.cjs +179 -1264
- package/dist/index.js +147 -1215
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,1027 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
import { useEffect as
|
|
1
|
+
// ../react/src/Clickly.tsx
|
|
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
|
-
//
|
|
6
|
-
|
|
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
|
|
1019
|
-
import { useEffect as useEffect7, useState as useState6 } from "react";
|
|
10
|
+
// ../react/src/internal/ClicklyRoot.tsx
|
|
11
|
+
import { useEffect as useEffect6, useState as useState6 } from "react";
|
|
1020
12
|
|
|
1021
|
-
//
|
|
1022
|
-
import {
|
|
13
|
+
// ../react/src/internal/Toolbar.tsx
|
|
14
|
+
import { useRef as useRef4, useState as useState3 } from "react";
|
|
1023
15
|
|
|
1024
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
-
//
|
|
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 =
|
|
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
|
-
//
|
|
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:** ${
|
|
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
|
|
193
|
+
function truncate(s, n) {
|
|
1310
194
|
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1311
195
|
}
|
|
1312
196
|
|
|
1313
|
-
//
|
|
197
|
+
// ../react/src/internal/icons.tsx
|
|
1314
198
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1315
199
|
function Icon({
|
|
1316
200
|
children,
|
|
@@ -1371,7 +255,7 @@ var IconClose = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx(
|
|
|
1371
255
|
var IconCheck = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12l5 5L20 7" }) });
|
|
1372
256
|
var IconList = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
|
|
1373
257
|
|
|
1374
|
-
//
|
|
258
|
+
// ../react/src/internal/SettingsPopover.tsx
|
|
1375
259
|
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
1376
260
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1377
261
|
function SettingsPopover({ anchor, width, onClose }) {
|
|
@@ -1472,7 +356,7 @@ function SettingsPopover({ anchor, width, onClose }) {
|
|
|
1472
356
|
] });
|
|
1473
357
|
}
|
|
1474
358
|
|
|
1475
|
-
//
|
|
359
|
+
// ../react/src/internal/AnnotationList.tsx
|
|
1476
360
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
1477
361
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1478
362
|
function AnnotationList({ anchor, width, onClose }) {
|
|
@@ -1560,7 +444,7 @@ function AnnotationCard({
|
|
|
1560
444
|
] });
|
|
1561
445
|
}
|
|
1562
446
|
|
|
1563
|
-
//
|
|
447
|
+
// ../react/src/internal/Toolbar.tsx
|
|
1564
448
|
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1565
449
|
var TOOLBAR_SIZE = { width: 360, height: 44 };
|
|
1566
450
|
var SHORTCUTS = [
|
|
@@ -1615,45 +499,14 @@ function Tip({
|
|
|
1615
499
|
] })
|
|
1616
500
|
] });
|
|
1617
501
|
}
|
|
1618
|
-
function Toolbar({ engine, onCollapse }) {
|
|
502
|
+
function Toolbar({ engine, onCollapse, isClosing, onCloseEnd, frozen, onFreezeToggle }) {
|
|
1619
503
|
const state = useEngineState(engine);
|
|
1620
504
|
const annotations = useAnnotationsList();
|
|
1621
505
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
1622
506
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1623
507
|
const [showSettings, setShowSettings] = useState3(false);
|
|
1624
508
|
const [showList, setShowList] = useState3(false);
|
|
1625
|
-
const [frozen, setFrozen] = useState3(false);
|
|
1626
509
|
const anchorRef = useRef4(null);
|
|
1627
|
-
useEffect4(() => {
|
|
1628
|
-
const STYLE_ID = "clickly-freeze-animations";
|
|
1629
|
-
const gsap = window.gsap;
|
|
1630
|
-
if (frozen) {
|
|
1631
|
-
if (!document.getElementById(STYLE_ID)) {
|
|
1632
|
-
const el = document.createElement("style");
|
|
1633
|
-
el.id = STYLE_ID;
|
|
1634
|
-
el.textContent = `
|
|
1635
|
-
*, *::before, *::after {
|
|
1636
|
-
animation-play-state: paused !important;
|
|
1637
|
-
transition-duration: 0ms !important;
|
|
1638
|
-
transition-delay: 0ms !important;
|
|
1639
|
-
}
|
|
1640
|
-
`;
|
|
1641
|
-
document.head.appendChild(el);
|
|
1642
|
-
}
|
|
1643
|
-
if (gsap?.globalTimeline) {
|
|
1644
|
-
gsap.globalTimeline.pause();
|
|
1645
|
-
}
|
|
1646
|
-
} else {
|
|
1647
|
-
document.getElementById(STYLE_ID)?.remove();
|
|
1648
|
-
if (gsap?.globalTimeline) {
|
|
1649
|
-
gsap.globalTimeline.resume();
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
return () => {
|
|
1653
|
-
document.getElementById(STYLE_ID)?.remove();
|
|
1654
|
-
if (gsap?.globalTimeline) gsap.globalTimeline.resume();
|
|
1655
|
-
};
|
|
1656
|
-
}, [frozen]);
|
|
1657
510
|
const { position, handleProps } = useDraggable(
|
|
1658
511
|
{
|
|
1659
512
|
x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
|
|
@@ -1679,8 +532,9 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1679
532
|
"div",
|
|
1680
533
|
{
|
|
1681
534
|
ref: anchorRef,
|
|
1682
|
-
className:
|
|
535
|
+
className: `clickly-toolbar${isClosing ? " is-closing" : ""}`,
|
|
1683
536
|
style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
|
|
537
|
+
onAnimationEnd: isClosing ? onCloseEnd : void 0,
|
|
1684
538
|
"aria-label": "Clickly toolbar",
|
|
1685
539
|
children: [
|
|
1686
540
|
/* @__PURE__ */ jsx4(Tip, { label: "Move", children: /* @__PURE__ */ jsx4(
|
|
@@ -1740,7 +594,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1740
594
|
"button",
|
|
1741
595
|
{
|
|
1742
596
|
className: `clickly-btn icon-only${frozen ? " is-freeze" : ""}`,
|
|
1743
|
-
onClick:
|
|
597
|
+
onClick: onFreezeToggle,
|
|
1744
598
|
"aria-label": frozen ? "Unfreeze page animations" : "Freeze page animations",
|
|
1745
599
|
"aria-pressed": frozen,
|
|
1746
600
|
children: /* @__PURE__ */ jsx4(IconFreeze, {})
|
|
@@ -1820,7 +674,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1820
674
|
);
|
|
1821
675
|
}
|
|
1822
676
|
|
|
1823
|
-
//
|
|
677
|
+
// ../react/src/internal/CollapsedFAB.tsx
|
|
1824
678
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1825
679
|
function CollapsedFAB({ onExpand }) {
|
|
1826
680
|
const annotations = useAnnotationsList();
|
|
@@ -1839,9 +693,27 @@ function CollapsedFAB({ onExpand }) {
|
|
|
1839
693
|
);
|
|
1840
694
|
}
|
|
1841
695
|
|
|
1842
|
-
//
|
|
1843
|
-
import { useCallback as useCallback2, useEffect as
|
|
1844
|
-
|
|
696
|
+
// ../react/src/internal/AnnotationPopup.tsx
|
|
697
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef5, useState as useState4 } from "react";
|
|
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";
|
|
1845
717
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1846
718
|
var POPUP_W = 320;
|
|
1847
719
|
var POPUP_H_EST = 240;
|
|
@@ -1858,7 +730,7 @@ function AnnotationPopup({ engine }) {
|
|
|
1858
730
|
const targetElRef = useRef5(null);
|
|
1859
731
|
const taRef = useRef5(null);
|
|
1860
732
|
const popupRef = useRef5(null);
|
|
1861
|
-
|
|
733
|
+
useEffect4(() => {
|
|
1862
734
|
if (state.kind === "annotating") {
|
|
1863
735
|
setComment("");
|
|
1864
736
|
setShowStyles(false);
|
|
@@ -1875,7 +747,7 @@ function AnnotationPopup({ engine }) {
|
|
|
1875
747
|
requestAnimationFrame(() => taRef.current?.focus());
|
|
1876
748
|
}
|
|
1877
749
|
}, [state.kind, engine]);
|
|
1878
|
-
|
|
750
|
+
useEffect4(() => {
|
|
1879
751
|
if (state.kind !== "annotating") return;
|
|
1880
752
|
const onDown = (e) => {
|
|
1881
753
|
if (popupRef.current && e.composedPath().includes(popupRef.current)) return;
|
|
@@ -1993,10 +865,10 @@ function AnnotationPopup({ engine }) {
|
|
|
1993
865
|
if (left + POPUP_W > vw) left = Math.max(8, vw - POPUP_W - 8);
|
|
1994
866
|
setPlacement({ top, left, label });
|
|
1995
867
|
}, [state, engine]);
|
|
1996
|
-
|
|
868
|
+
useEffect4(() => {
|
|
1997
869
|
calcPlacement();
|
|
1998
870
|
}, [calcPlacement]);
|
|
1999
|
-
|
|
871
|
+
useEffect4(() => {
|
|
2000
872
|
if (state.kind !== "annotating") return;
|
|
2001
873
|
const onScroll = () => calcPlacement();
|
|
2002
874
|
window.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
@@ -2117,7 +989,7 @@ function anchorRect(sel) {
|
|
|
2117
989
|
if (sel.elements.length === 0) return null;
|
|
2118
990
|
return sel.elements[0].getBoundingClientRect();
|
|
2119
991
|
}
|
|
2120
|
-
var
|
|
992
|
+
var TAG_LABELS = {
|
|
2121
993
|
p: "paragraph",
|
|
2122
994
|
h1: "heading",
|
|
2123
995
|
h2: "heading",
|
|
@@ -2159,9 +1031,9 @@ var TAG_LABELS2 = {
|
|
|
2159
1031
|
svg: "svg",
|
|
2160
1032
|
canvas: "canvas"
|
|
2161
1033
|
};
|
|
2162
|
-
function
|
|
1034
|
+
function describeElement(el) {
|
|
2163
1035
|
const tag = el.tagName.toLowerCase();
|
|
2164
|
-
const type =
|
|
1036
|
+
const type = TAG_LABELS[tag] ?? tag;
|
|
2165
1037
|
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
2166
1038
|
if (text.length > 0) {
|
|
2167
1039
|
const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
|
|
@@ -2173,19 +1045,19 @@ function describeElement2(el) {
|
|
|
2173
1045
|
return type;
|
|
2174
1046
|
}
|
|
2175
1047
|
function describeSelection(sel) {
|
|
2176
|
-
if (sel.kind === "single") return
|
|
1048
|
+
if (sel.kind === "single") return describeElement(sel.element);
|
|
2177
1049
|
if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
|
|
2178
|
-
if (sel.elements.length === 1) return
|
|
1050
|
+
if (sel.elements.length === 1) return describeElement(sel.elements[0]);
|
|
2179
1051
|
return `${sel.elements.length} element(s)`;
|
|
2180
1052
|
}
|
|
2181
1053
|
|
|
2182
|
-
//
|
|
2183
|
-
import { useEffect as
|
|
1054
|
+
// ../react/src/internal/AnnotationPins.tsx
|
|
1055
|
+
import { useEffect as useEffect5, useRef as useRef6, useState as useState5 } from "react";
|
|
2184
1056
|
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2185
1057
|
function AnnotationPins() {
|
|
2186
1058
|
const annotations = useAnnotationsList();
|
|
2187
1059
|
const [, setVersion] = useState5(0);
|
|
2188
|
-
|
|
1060
|
+
useEffect5(() => {
|
|
2189
1061
|
let raf = null;
|
|
2190
1062
|
const update = () => {
|
|
2191
1063
|
if (raf !== null) return;
|
|
@@ -2204,7 +1076,7 @@ function AnnotationPins() {
|
|
|
2204
1076
|
}, []);
|
|
2205
1077
|
return /* @__PURE__ */ jsx7(Fragment2, { children: annotations.map((a, i) => /* @__PURE__ */ jsx7(Pin, { number: i + 1, annotation: a }, a.id)) });
|
|
2206
1078
|
}
|
|
2207
|
-
var
|
|
1079
|
+
var TAG_LABELS2 = {
|
|
2208
1080
|
p: "paragraph",
|
|
2209
1081
|
h1: "heading",
|
|
2210
1082
|
h2: "heading",
|
|
@@ -2244,7 +1116,7 @@ var TAG_LABELS3 = {
|
|
|
2244
1116
|
function pinLabel(annotation) {
|
|
2245
1117
|
const raw = (annotation.element ?? "").toLowerCase();
|
|
2246
1118
|
const tag = raw.split(/[.#\s]/)[0] ?? "";
|
|
2247
|
-
return (
|
|
1119
|
+
return (TAG_LABELS2[tag] ?? tag) || "element";
|
|
2248
1120
|
}
|
|
2249
1121
|
function parseStyles(raw) {
|
|
2250
1122
|
if (!raw) return [];
|
|
@@ -2285,7 +1157,7 @@ function Pin({ number, annotation }) {
|
|
|
2285
1157
|
if (e.key === "Escape") closeEdit();
|
|
2286
1158
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
|
|
2287
1159
|
};
|
|
2288
|
-
|
|
1160
|
+
useEffect5(() => {
|
|
2289
1161
|
if (!editing) return;
|
|
2290
1162
|
const onDown = (e) => {
|
|
2291
1163
|
if (editRef.current && e.composedPath().includes(editRef.current)) return;
|
|
@@ -2294,7 +1166,7 @@ function Pin({ number, annotation }) {
|
|
|
2294
1166
|
window.addEventListener("pointerdown", onDown, true);
|
|
2295
1167
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
2296
1168
|
}, [editing]);
|
|
2297
|
-
|
|
1169
|
+
useEffect5(() => {
|
|
2298
1170
|
if (!editing) return;
|
|
2299
1171
|
let el = null;
|
|
2300
1172
|
if (annotation.elementPath) {
|
|
@@ -2422,28 +1294,63 @@ function resolvePosition(a) {
|
|
|
2422
1294
|
return null;
|
|
2423
1295
|
}
|
|
2424
1296
|
|
|
2425
|
-
//
|
|
1297
|
+
// ../react/src/internal/ClicklyRoot.tsx
|
|
2426
1298
|
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2427
1299
|
function ClicklyRoot({
|
|
2428
1300
|
engine,
|
|
2429
1301
|
host
|
|
2430
1302
|
}) {
|
|
2431
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
|
+
};
|
|
2432
1313
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
2433
1314
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
2434
1315
|
const markerColor = useSettings((s) => s.markerColor);
|
|
2435
1316
|
const engineState = useEngineState(engine);
|
|
2436
|
-
|
|
1317
|
+
useEffect6(() => {
|
|
2437
1318
|
host.style.setProperty("--clickly-hover", markerColor);
|
|
2438
1319
|
}, [host, markerColor]);
|
|
2439
|
-
|
|
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]);
|
|
1346
|
+
useEffect6(() => {
|
|
2440
1347
|
if (expanded) {
|
|
2441
1348
|
if (engine.getSnapshot().kind === "idle") engine.activate("single");
|
|
2442
1349
|
} else {
|
|
2443
1350
|
if (engine.getSnapshot().kind !== "idle") engine.deactivate();
|
|
2444
1351
|
}
|
|
2445
1352
|
}, [expanded, engine]);
|
|
2446
|
-
|
|
1353
|
+
useEffect6(() => {
|
|
2447
1354
|
const active = engineState.kind !== "idle";
|
|
2448
1355
|
if (active) document.body.setAttribute("data-clickly-active", "");
|
|
2449
1356
|
else document.body.removeAttribute("data-clickly-active");
|
|
@@ -2461,7 +1368,7 @@ function ClicklyRoot({
|
|
|
2461
1368
|
document.body.removeAttribute("data-clickly-annotating");
|
|
2462
1369
|
};
|
|
2463
1370
|
}, [engineState]);
|
|
2464
|
-
|
|
1371
|
+
useEffect6(() => {
|
|
2465
1372
|
const onKey = (e) => {
|
|
2466
1373
|
const tag = e.target?.tagName;
|
|
2467
1374
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
@@ -2470,12 +1377,13 @@ function ClicklyRoot({
|
|
|
2470
1377
|
if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
|
|
2471
1378
|
if (e.key.toLowerCase() === "f" && e.shiftKey && (e.metaKey || e.ctrlKey)) {
|
|
2472
1379
|
e.preventDefault();
|
|
2473
|
-
|
|
1380
|
+
if (expanded) collapse();
|
|
1381
|
+
else setExpanded(true);
|
|
2474
1382
|
return;
|
|
2475
1383
|
}
|
|
2476
1384
|
if (e.key === "Escape") {
|
|
2477
1385
|
if (engine.getSnapshot().kind === "annotating") return;
|
|
2478
|
-
if (expanded)
|
|
1386
|
+
if (expanded) collapse();
|
|
2479
1387
|
return;
|
|
2480
1388
|
}
|
|
2481
1389
|
if (!expanded) return;
|
|
@@ -2506,14 +1414,24 @@ function ClicklyRoot({
|
|
|
2506
1414
|
}, [clearAnnotations, outputDetail, expanded, engine]);
|
|
2507
1415
|
return /* @__PURE__ */ jsxs8("div", { className: "clickly-ui", children: [
|
|
2508
1416
|
/* @__PURE__ */ jsx8(AnnotationPins, {}),
|
|
2509
|
-
expanded ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2510
|
-
/* @__PURE__ */ jsx8(
|
|
2511
|
-
|
|
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 })
|
|
2512
1430
|
] }) : /* @__PURE__ */ jsx8(CollapsedFAB, { onExpand: () => setExpanded(true) })
|
|
2513
1431
|
] });
|
|
2514
1432
|
}
|
|
2515
1433
|
|
|
2516
|
-
//
|
|
1434
|
+
// ../react/src/internal/styles.ts
|
|
2517
1435
|
var REACT_UI_CSS = `
|
|
2518
1436
|
.clickly-ui, .clickly-ui * { box-sizing: border-box; }
|
|
2519
1437
|
|
|
@@ -2539,6 +1457,11 @@ var REACT_UI_CSS = `
|
|
|
2539
1457
|
0 0 0 1px rgba(255,255,255,0.06) inset;
|
|
2540
1458
|
transition: transform 140ms ease, box-shadow 140ms ease;
|
|
2541
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); }
|
|
2542
1465
|
}
|
|
2543
1466
|
.clickly-fab:hover {
|
|
2544
1467
|
transform: scale(1.06);
|
|
@@ -2585,12 +1508,21 @@ var REACT_UI_CSS = `
|
|
|
2585
1508
|
pointer-events: auto;
|
|
2586
1509
|
user-select: none;
|
|
2587
1510
|
z-index: 1;
|
|
2588
|
-
|
|
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;
|
|
2589
1517
|
}
|
|
2590
1518
|
|
|
2591
|
-
@keyframes clickly-
|
|
2592
|
-
from { opacity: 0; transform:
|
|
2593
|
-
to { opacity: 1; transform:
|
|
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); }
|
|
2594
1526
|
}
|
|
2595
1527
|
|
|
2596
1528
|
.clickly-toolbar .grip {
|
|
@@ -3562,7 +2494,7 @@ var REACT_UI_CSS = `
|
|
|
3562
2494
|
}
|
|
3563
2495
|
`;
|
|
3564
2496
|
|
|
3565
|
-
//
|
|
2497
|
+
// ../react/src/internal/globalStyles.ts
|
|
3566
2498
|
var GLOBAL_PAGE_CSS = `
|
|
3567
2499
|
body[data-clickly-active] {
|
|
3568
2500
|
-webkit-user-select: none !important;
|
|
@@ -3583,11 +2515,11 @@ body[data-clickly-annotating] {
|
|
|
3583
2515
|
}
|
|
3584
2516
|
`;
|
|
3585
2517
|
|
|
3586
|
-
//
|
|
2518
|
+
// ../react/src/Clickly.tsx
|
|
3587
2519
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
3588
2520
|
function Clickly({ className } = {}) {
|
|
3589
2521
|
const [mount, setMount] = useState7(null);
|
|
3590
|
-
|
|
2522
|
+
useEffect7(() => {
|
|
3591
2523
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
3592
2524
|
let shadow = null;
|
|
3593
2525
|
let engine = null;
|
|
@@ -3636,7 +2568,7 @@ function Clickly({ className } = {}) {
|
|
|
3636
2568
|
document.body.removeAttribute("data-clickly-mode");
|
|
3637
2569
|
};
|
|
3638
2570
|
}, []);
|
|
3639
|
-
|
|
2571
|
+
useEffect7(() => {
|
|
3640
2572
|
if (!mount || !className) return;
|
|
3641
2573
|
mount.shadow.host.className = className;
|
|
3642
2574
|
return () => {
|