@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.cjs +333 -1232
- package/dist/index.js +284 -1166
- package/dist/internal/Toolbar.d.ts.map +1 -1
- package/dist/internal/icons.d.ts +2 -0
- package/dist/internal/icons.d.ts.map +1 -1
- package/dist/internal/styles.d.ts +1 -1
- package/dist/internal/styles.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,17 +15,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
|
-
//
|
|
20
|
+
// ../react/src/index.ts
|
|
31
21
|
var index_exports = {};
|
|
32
22
|
__export(index_exports, {
|
|
33
23
|
Clickly: () => Clickly,
|
|
@@ -39,1030 +29,18 @@ __export(index_exports, {
|
|
|
39
29
|
});
|
|
40
30
|
module.exports = __toCommonJS(index_exports);
|
|
41
31
|
|
|
42
|
-
//
|
|
43
|
-
var
|
|
32
|
+
// ../react/src/Clickly.tsx
|
|
33
|
+
var import_react9 = require("react");
|
|
44
34
|
var import_react_dom = require("react-dom");
|
|
35
|
+
var import_core2 = require("@useclickly/core");
|
|
45
36
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
if (typeof doc.elementsFromPoint !== "function") return null;
|
|
49
|
-
const chain = doc.elementsFromPoint(x, y);
|
|
50
|
-
for (const el of chain) {
|
|
51
|
-
if (!isInExcludedSubtree(el, excludeHost)) return el;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
function pickElementsInRect(root, rect, excludeHost) {
|
|
56
|
-
const out = [];
|
|
57
|
-
const stack = [root];
|
|
58
|
-
while (stack.length) {
|
|
59
|
-
const el = stack.pop();
|
|
60
|
-
if (isInExcludedSubtree(el, excludeHost)) continue;
|
|
61
|
-
const box = el.getBoundingClientRect();
|
|
62
|
-
if (box.width > 0 && box.height > 0 && containedIn(box, rect)) {
|
|
63
|
-
out.push(el);
|
|
64
|
-
}
|
|
65
|
-
for (let i = 0; i < el.children.length; i++) {
|
|
66
|
-
const child = el.children[i];
|
|
67
|
-
if (child) stack.push(child);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return out;
|
|
71
|
-
}
|
|
72
|
-
function containedIn(el, sel) {
|
|
73
|
-
return el.left >= sel.x && el.top >= sel.y && el.right <= sel.x + sel.width && el.bottom <= sel.y + sel.height;
|
|
74
|
-
}
|
|
75
|
-
function isInExcludedSubtree(el, host) {
|
|
76
|
-
if (!host) return false;
|
|
77
|
-
let cur = el;
|
|
78
|
-
while (cur) {
|
|
79
|
-
if (cur === host) return true;
|
|
80
|
-
cur = cur.parentNode;
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
function manhattan(a, b) {
|
|
85
|
-
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
86
|
-
}
|
|
87
|
-
function rectFromPoints(start, current) {
|
|
88
|
-
const x = Math.min(start.x, current.x);
|
|
89
|
-
const y = Math.min(start.y, current.y);
|
|
90
|
-
const width = Math.abs(current.x - start.x);
|
|
91
|
-
const height = Math.abs(current.y - start.y);
|
|
92
|
-
return { x, y, width, height };
|
|
93
|
-
}
|
|
94
|
-
var DRAG_THRESHOLD_PX = 12;
|
|
95
|
-
var initialState = { kind: "idle" };
|
|
96
|
-
function reduce(state, event) {
|
|
97
|
-
if (event.type === "DEACTIVATE") return { kind: "idle" };
|
|
98
|
-
if (event.type === "ESCAPE") {
|
|
99
|
-
if (state.kind === "idle") return state;
|
|
100
|
-
if (state.kind === "annotating") return resumeInspect(
|
|
101
|
-
[],
|
|
102
|
-
/* mode */
|
|
103
|
-
void 0
|
|
104
|
-
);
|
|
105
|
-
if (state.kind === "inspect" && state.pinned.length > 0) {
|
|
106
|
-
return { ...state, pinned: [], hoverTarget: null };
|
|
107
|
-
}
|
|
108
|
-
return resumeInspect("pinned" in state ? state.pinned : []);
|
|
109
|
-
}
|
|
110
|
-
if (event.type === "CLEAR_PINNED") {
|
|
111
|
-
if (state.kind === "idle") return state;
|
|
112
|
-
if (state.kind === "inspect") return { ...state, pinned: [] };
|
|
113
|
-
return state;
|
|
114
|
-
}
|
|
115
|
-
switch (state.kind) {
|
|
116
|
-
case "idle":
|
|
117
|
-
if (event.type === "ACTIVATE") {
|
|
118
|
-
return {
|
|
119
|
-
kind: "inspect",
|
|
120
|
-
mode: event.mode ?? "single",
|
|
121
|
-
hoverTarget: null,
|
|
122
|
-
pinned: []
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
return state;
|
|
126
|
-
case "inspect":
|
|
127
|
-
if (event.type === "MODE_CHANGE") return { ...state, mode: event.mode };
|
|
128
|
-
if (event.type === "POINTER_MOVE") return { ...state, hoverTarget: event.target };
|
|
129
|
-
if (event.type === "POINTER_DOWN") {
|
|
130
|
-
return {
|
|
131
|
-
kind: "pressed",
|
|
132
|
-
mode: state.mode,
|
|
133
|
-
start: event.point,
|
|
134
|
-
target: event.target,
|
|
135
|
-
additive: event.additive,
|
|
136
|
-
pinned: state.pinned
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (event.type === "ANNOTATE_PINNED") {
|
|
140
|
-
if (state.pinned.length === 0) return state;
|
|
141
|
-
return {
|
|
142
|
-
kind: "annotating",
|
|
143
|
-
selection: { kind: "multi", elements: state.pinned },
|
|
144
|
-
pinned: state.pinned
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
return state;
|
|
148
|
-
case "pressed":
|
|
149
|
-
if (event.type === "POINTER_MOVE") {
|
|
150
|
-
if (manhattan(state.start, event.point) >= DRAG_THRESHOLD_PX) {
|
|
151
|
-
return {
|
|
152
|
-
kind: "dragging",
|
|
153
|
-
mode: state.mode,
|
|
154
|
-
start: state.start,
|
|
155
|
-
current: event.point,
|
|
156
|
-
pinned: state.pinned
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
return state;
|
|
160
|
-
}
|
|
161
|
-
if (event.type === "POINTER_UP") {
|
|
162
|
-
if (!state.target) {
|
|
163
|
-
return resumeInspect(state.pinned, state.mode);
|
|
164
|
-
}
|
|
165
|
-
if (state.additive || state.mode === "multi") {
|
|
166
|
-
const nextPinned = togglePinned(state.pinned, state.target);
|
|
167
|
-
return {
|
|
168
|
-
kind: "inspect",
|
|
169
|
-
mode: state.mode,
|
|
170
|
-
hoverTarget: state.target,
|
|
171
|
-
pinned: nextPinned
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
kind: "annotating",
|
|
176
|
-
selection: { kind: "single", element: state.target },
|
|
177
|
-
pinned: state.pinned
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
return state;
|
|
181
|
-
case "dragging":
|
|
182
|
-
if (event.type === "POINTER_MOVE") return { ...state, current: event.point };
|
|
183
|
-
if (event.type === "POINTER_UP") {
|
|
184
|
-
const rect = rectFromPoints(state.start, state.current);
|
|
185
|
-
const selection = { kind: "area", rect, elements: [] };
|
|
186
|
-
return { kind: "annotating", selection, pinned: state.pinned };
|
|
187
|
-
}
|
|
188
|
-
return state;
|
|
189
|
-
case "annotating":
|
|
190
|
-
if (event.type === "COMMIT") {
|
|
191
|
-
return resumeInspect([]);
|
|
192
|
-
}
|
|
193
|
-
return state;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
function resumeInspect(pinned, mode) {
|
|
197
|
-
return {
|
|
198
|
-
kind: "inspect",
|
|
199
|
-
mode: mode ?? "single",
|
|
200
|
-
hoverTarget: null,
|
|
201
|
-
pinned
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function togglePinned(pinned, el) {
|
|
205
|
-
const idx = pinned.indexOf(el);
|
|
206
|
-
if (idx === -1) return [...pinned, el];
|
|
207
|
-
const next = pinned.slice();
|
|
208
|
-
next.splice(idx, 1);
|
|
209
|
-
return next;
|
|
210
|
-
}
|
|
211
|
-
var SelectionEngine = class {
|
|
212
|
-
state = initialState;
|
|
213
|
-
listeners = /* @__PURE__ */ new Set();
|
|
214
|
-
doc;
|
|
215
|
-
host;
|
|
216
|
-
raf;
|
|
217
|
-
caf;
|
|
218
|
-
searchRoot;
|
|
219
|
-
pendingPointer = null;
|
|
220
|
-
rafHandle = null;
|
|
221
|
-
/** Guard for the pointermove RAF coalescer. Separate from `rafHandle`
|
|
222
|
-
* because a synchronous `raf` (used in tests) returns a handle the cb
|
|
223
|
-
* has already invalidated — boolean is the safe sentinel. */
|
|
224
|
-
rafPending = false;
|
|
225
|
-
attached = false;
|
|
226
|
-
boundHandlers = [];
|
|
227
|
-
constructor(deps = {}) {
|
|
228
|
-
this.doc = deps.document ?? (typeof document !== "undefined" ? document : null);
|
|
229
|
-
if (!this.doc) {
|
|
230
|
-
throw new Error("SelectionEngine: no Document available (pass `deps.document`).");
|
|
231
|
-
}
|
|
232
|
-
this.host = deps.host ?? null;
|
|
233
|
-
this.raf = deps.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
|
|
234
|
-
this.caf = deps.caf ?? ((h) => {
|
|
235
|
-
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
|
|
236
|
-
else clearTimeout(h);
|
|
237
|
-
});
|
|
238
|
-
this.searchRoot = deps.searchRoot ?? this.doc.body;
|
|
239
|
-
}
|
|
240
|
-
/* ─── Subscribable<EngineState> ───────────────────────────────── */
|
|
241
|
-
subscribe(listener) {
|
|
242
|
-
this.listeners.add(listener);
|
|
243
|
-
return () => this.listeners.delete(listener);
|
|
244
|
-
}
|
|
245
|
-
getSnapshot() {
|
|
246
|
-
return this.state;
|
|
247
|
-
}
|
|
248
|
-
/* ─── Public control ──────────────────────────────────────────── */
|
|
249
|
-
activate(mode) {
|
|
250
|
-
perfMark("clickly:engine:activate");
|
|
251
|
-
this.dispatch(mode ? { type: "ACTIVATE", mode } : { type: "ACTIVATE" });
|
|
252
|
-
this.attach();
|
|
253
|
-
}
|
|
254
|
-
deactivate() {
|
|
255
|
-
perfMark("clickly:engine:deactivate");
|
|
256
|
-
this.detach();
|
|
257
|
-
this.dispatch({ type: "DEACTIVATE" });
|
|
258
|
-
}
|
|
259
|
-
setMode(mode) {
|
|
260
|
-
this.dispatch({ type: "MODE_CHANGE", mode });
|
|
261
|
-
}
|
|
262
|
-
commit() {
|
|
263
|
-
this.dispatch({ type: "COMMIT" });
|
|
264
|
-
}
|
|
265
|
-
clearPinned() {
|
|
266
|
-
this.dispatch({ type: "CLEAR_PINNED" });
|
|
267
|
-
}
|
|
268
|
-
/** Open the annotation popup populated with everything currently pinned. */
|
|
269
|
-
annotatePinned() {
|
|
270
|
-
this.dispatch({ type: "ANNOTATE_PINNED" });
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Returns the resolved selection from the current annotating state, with
|
|
274
|
-
* area-mode element enumeration filled in (the reducer leaves it empty).
|
|
275
|
-
* Returns null if not currently annotating.
|
|
276
|
-
*/
|
|
277
|
-
resolveSelection() {
|
|
278
|
-
if (this.state.kind !== "annotating") return null;
|
|
279
|
-
const sel = this.state.selection;
|
|
280
|
-
if (sel.kind !== "area") return sel;
|
|
281
|
-
const elements = pickElementsInRect(this.searchRoot, sel.rect, this.host);
|
|
282
|
-
return { ...sel, elements };
|
|
283
|
-
}
|
|
284
|
-
/* ─── Lifecycle ───────────────────────────────────────────────── */
|
|
285
|
-
attach() {
|
|
286
|
-
if (this.attached) return;
|
|
287
|
-
this.attached = true;
|
|
288
|
-
const win = this.doc.defaultView ?? globalThis;
|
|
289
|
-
this.bind(this.doc, "pointermove", this.onPointerMove, { passive: true });
|
|
290
|
-
this.bind(this.doc, "pointerdown", this.onPointerDown);
|
|
291
|
-
this.bind(this.doc, "pointerup", this.onPointerUp);
|
|
292
|
-
this.bind(win, "keydown", this.onKeyDown);
|
|
293
|
-
}
|
|
294
|
-
detach() {
|
|
295
|
-
if (!this.attached) return;
|
|
296
|
-
for (const [t, ev, fn, opts] of this.boundHandlers) t.removeEventListener(ev, fn, opts);
|
|
297
|
-
this.boundHandlers = [];
|
|
298
|
-
this.attached = false;
|
|
299
|
-
if (this.rafHandle !== null) {
|
|
300
|
-
this.caf(this.rafHandle);
|
|
301
|
-
this.rafHandle = null;
|
|
302
|
-
}
|
|
303
|
-
this.rafPending = false;
|
|
304
|
-
}
|
|
305
|
-
destroy() {
|
|
306
|
-
this.detach();
|
|
307
|
-
this.listeners.clear();
|
|
308
|
-
}
|
|
309
|
-
bind(target, ev, fn, opts) {
|
|
310
|
-
target.addEventListener(ev, fn, opts);
|
|
311
|
-
this.boundHandlers.push([target, ev, fn, opts]);
|
|
312
|
-
}
|
|
313
|
-
/* ─── DOM event handlers ──────────────────────────────────────── */
|
|
314
|
-
onPointerMove = (e) => {
|
|
315
|
-
this.pendingPointer = { x: e.clientX, y: e.clientY };
|
|
316
|
-
if (this.rafPending) return;
|
|
317
|
-
this.rafPending = true;
|
|
318
|
-
this.rafHandle = this.raf(() => {
|
|
319
|
-
this.rafPending = false;
|
|
320
|
-
this.rafHandle = null;
|
|
321
|
-
const pt = this.pendingPointer;
|
|
322
|
-
this.pendingPointer = null;
|
|
323
|
-
if (!pt) return;
|
|
324
|
-
const target = pickElementAt(this.doc, pt.x, pt.y, this.host);
|
|
325
|
-
this.dispatch({ type: "POINTER_MOVE", point: pt, target });
|
|
326
|
-
});
|
|
327
|
-
};
|
|
328
|
-
onPointerDown = (e) => {
|
|
329
|
-
if (this.host && e.composedPath().includes(this.host)) return;
|
|
330
|
-
const target = pickElementAt(this.doc, e.clientX, e.clientY, this.host);
|
|
331
|
-
this.dispatch({
|
|
332
|
-
type: "POINTER_DOWN",
|
|
333
|
-
point: { x: e.clientX, y: e.clientY },
|
|
334
|
-
target,
|
|
335
|
-
additive: e.shiftKey || e.metaKey || e.ctrlKey
|
|
336
|
-
});
|
|
337
|
-
};
|
|
338
|
-
onPointerUp = (e) => {
|
|
339
|
-
this.dispatch({ type: "POINTER_UP", point: { x: e.clientX, y: e.clientY } });
|
|
340
|
-
};
|
|
341
|
-
onKeyDown = (e) => {
|
|
342
|
-
if (e.key === "Escape") this.dispatch({ type: "ESCAPE" });
|
|
343
|
-
};
|
|
344
|
-
/* ─── Reducer plumbing ────────────────────────────────────────── */
|
|
345
|
-
dispatch(event) {
|
|
346
|
-
const next = reduce(this.state, event);
|
|
347
|
-
if (next === this.state) return;
|
|
348
|
-
this.state = next;
|
|
349
|
-
for (const l of this.listeners) l(next);
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
function perfMark(name) {
|
|
353
|
-
if (typeof performance !== "undefined" && typeof performance.mark === "function") {
|
|
354
|
-
try {
|
|
355
|
-
performance.mark(name);
|
|
356
|
-
} catch {
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
var OVERLAY_CSS = `
|
|
361
|
-
:host {
|
|
362
|
-
--clickly-hover: #06b6d4;
|
|
363
|
-
--clickly-pinned: #f59e0b;
|
|
364
|
-
--clickly-selected: #10b981;
|
|
365
|
-
--clickly-marquee-stroke: #10b981;
|
|
366
|
-
--clickly-marquee-fill: rgba(16, 185, 129, 0.10);
|
|
367
|
-
--clickly-label-bg: rgba(15, 23, 42, 0.92);
|
|
368
|
-
--clickly-label-fg: #f8fafc;
|
|
369
|
-
--clickly-shadow: 0 0 0 1px rgba(255,255,255,0.5);
|
|
370
|
-
|
|
371
|
-
all: initial;
|
|
372
|
-
position: fixed;
|
|
373
|
-
inset: 0;
|
|
374
|
-
z-index: 2147483647;
|
|
375
|
-
pointer-events: none;
|
|
376
|
-
contain: layout style paint;
|
|
377
|
-
isolation: isolate;
|
|
378
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.layer {
|
|
382
|
-
position: fixed;
|
|
383
|
-
left: 0;
|
|
384
|
-
top: 0;
|
|
385
|
-
width: 0;
|
|
386
|
-
height: 0;
|
|
387
|
-
pointer-events: none;
|
|
388
|
-
will-change: transform, width, height, opacity;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.marker {
|
|
392
|
-
position: fixed;
|
|
393
|
-
left: 0;
|
|
394
|
-
top: 0;
|
|
395
|
-
box-sizing: border-box;
|
|
396
|
-
border-radius: 2px;
|
|
397
|
-
pointer-events: none;
|
|
398
|
-
will-change: transform, width, height, opacity;
|
|
399
|
-
transition: opacity 80ms linear;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.marker[hidden] { display: none; }
|
|
403
|
-
|
|
404
|
-
.marker.hover { box-shadow: 0 0 0 2px var(--clickly-hover), var(--clickly-shadow); }
|
|
405
|
-
.marker.pinned { box-shadow: 0 0 0 2px var(--clickly-pinned), var(--clickly-shadow); }
|
|
406
|
-
.marker.selected { box-shadow: 0 0 0 2px var(--clickly-selected), var(--clickly-shadow); }
|
|
407
|
-
|
|
408
|
-
/* Marquee \u2014 used during drag (dashed) AND for the committed union
|
|
409
|
-
box around multi/area selections (solid). */
|
|
410
|
-
.marker.marquee {
|
|
411
|
-
border: 2px dashed var(--clickly-marquee-stroke);
|
|
412
|
-
background: var(--clickly-marquee-fill);
|
|
413
|
-
border-radius: 8px;
|
|
414
|
-
}
|
|
415
|
-
.marker.marquee.is-committed {
|
|
416
|
-
border-style: solid;
|
|
417
|
-
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.18);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.label {
|
|
421
|
-
position: fixed;
|
|
422
|
-
left: 0;
|
|
423
|
-
top: 0;
|
|
424
|
-
padding: 2px 6px;
|
|
425
|
-
background: var(--clickly-label-bg);
|
|
426
|
-
color: var(--clickly-label-fg);
|
|
427
|
-
font-size: 11px;
|
|
428
|
-
line-height: 1.4;
|
|
429
|
-
border-radius: 4px;
|
|
430
|
-
white-space: nowrap;
|
|
431
|
-
pointer-events: none;
|
|
432
|
-
user-select: none;
|
|
433
|
-
max-width: 50vw;
|
|
434
|
-
overflow: hidden;
|
|
435
|
-
text-overflow: ellipsis;
|
|
436
|
-
}
|
|
437
|
-
`;
|
|
438
|
-
var HOST_TAG = "clickly-root";
|
|
439
|
-
function createShadowHost(deps = {}) {
|
|
440
|
-
const doc = deps.document ?? (typeof document !== "undefined" ? document : null);
|
|
441
|
-
if (!doc) throw new Error("createShadowHost: no Document available");
|
|
442
|
-
const existing = doc.querySelector(HOST_TAG);
|
|
443
|
-
const host = existing ?? doc.createElement(HOST_TAG);
|
|
444
|
-
if (!existing) doc.body.appendChild(host);
|
|
445
|
-
const root = host.shadowRoot ?? host.attachShadow({ mode: "open" });
|
|
446
|
-
if (!root.querySelector("style[data-clickly]")) {
|
|
447
|
-
const style = doc.createElement("style");
|
|
448
|
-
style.setAttribute("data-clickly", "");
|
|
449
|
-
style.textContent = OVERLAY_CSS;
|
|
450
|
-
root.appendChild(style);
|
|
451
|
-
}
|
|
452
|
-
return {
|
|
453
|
-
host,
|
|
454
|
-
root,
|
|
455
|
-
destroy() {
|
|
456
|
-
host.remove();
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
var OverlayRenderer = class {
|
|
461
|
-
root;
|
|
462
|
-
document;
|
|
463
|
-
layer;
|
|
464
|
-
hover;
|
|
465
|
-
hoverLabel;
|
|
466
|
-
/** Marquee rect — used both for live drag AND for the union box
|
|
467
|
-
* drawn around multi-element selections after commit. */
|
|
468
|
-
marquee;
|
|
469
|
-
/** Pinned/selected single-element rings (recycled across renders). */
|
|
470
|
-
pinnedPool = [];
|
|
471
|
-
selectedPool = [];
|
|
472
|
-
/** Last rendered state — used to re-render on scroll without a state change. */
|
|
473
|
-
lastState = null;
|
|
474
|
-
constructor(root, document2) {
|
|
475
|
-
this.root = root;
|
|
476
|
-
this.document = document2 ?? root.ownerDocument ?? (typeof globalThis !== "undefined" && globalThis.document ? globalThis.document : null);
|
|
477
|
-
if (!this.document) throw new Error("OverlayRenderer: no Document available");
|
|
478
|
-
this.layer = this.div("layer");
|
|
479
|
-
this.root.appendChild(this.layer);
|
|
480
|
-
this.hover = this.div("marker hover");
|
|
481
|
-
this.hover.hidden = true;
|
|
482
|
-
this.layer.appendChild(this.hover);
|
|
483
|
-
this.hoverLabel = this.div("label");
|
|
484
|
-
this.hoverLabel.hidden = true;
|
|
485
|
-
this.layer.appendChild(this.hoverLabel);
|
|
486
|
-
this.marquee = this.div("marker marquee");
|
|
487
|
-
this.marquee.hidden = true;
|
|
488
|
-
this.layer.appendChild(this.marquee);
|
|
489
|
-
this.document.addEventListener("scroll", this.onScroll, {
|
|
490
|
-
passive: true,
|
|
491
|
-
capture: true
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
onScroll = () => {
|
|
495
|
-
if (this.lastState) this.renderState(this.lastState);
|
|
496
|
-
};
|
|
497
|
-
render(state) {
|
|
498
|
-
this.lastState = state;
|
|
499
|
-
this.renderState(state);
|
|
500
|
-
}
|
|
501
|
-
renderState(state) {
|
|
502
|
-
this.renderSingle(this.hover, state.hover);
|
|
503
|
-
if (state.hover) {
|
|
504
|
-
this.hoverLabel.hidden = false;
|
|
505
|
-
this.hoverLabel.textContent = describeElement(state.hover);
|
|
506
|
-
const rect = state.hover.getBoundingClientRect();
|
|
507
|
-
const labelY = rect.top - 20 < 0 ? rect.bottom + 4 : rect.top - 20;
|
|
508
|
-
moveTo(this.hoverLabel, rect.left, labelY);
|
|
509
|
-
} else {
|
|
510
|
-
this.hoverLabel.hidden = true;
|
|
511
|
-
}
|
|
512
|
-
this.renderList(this.pinnedPool, state.pinned, "marker pinned");
|
|
513
|
-
const sel = state.selection ?? [];
|
|
514
|
-
const isMultiUnion = sel.length > 1 || state.marquee !== null && sel.length === 0;
|
|
515
|
-
if (sel.length === 1) {
|
|
516
|
-
this.renderList(this.selectedPool, [sel[0]], "marker selected");
|
|
517
|
-
} else {
|
|
518
|
-
this.renderList(this.selectedPool, [], "marker selected");
|
|
519
|
-
}
|
|
520
|
-
const marqueeRect = state.marquee ?? (sel.length > 1 ? unionOf(sel) : null);
|
|
521
|
-
if (marqueeRect) {
|
|
522
|
-
this.marquee.classList.toggle("is-committed", state.marquee === null);
|
|
523
|
-
this.placeRect(this.marquee, marqueeRect);
|
|
524
|
-
this.marquee.hidden = false;
|
|
525
|
-
} else {
|
|
526
|
-
this.marquee.hidden = true;
|
|
527
|
-
}
|
|
528
|
-
void isMultiUnion;
|
|
529
|
-
}
|
|
530
|
-
destroy() {
|
|
531
|
-
this.document.removeEventListener("scroll", this.onScroll, { capture: true });
|
|
532
|
-
this.lastState = null;
|
|
533
|
-
this.layer.remove();
|
|
534
|
-
}
|
|
535
|
-
/* ─── Internals ───────────────────────────────────────────────── */
|
|
536
|
-
renderSingle(node, target) {
|
|
537
|
-
if (!target) {
|
|
538
|
-
node.hidden = true;
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
const r = target.getBoundingClientRect();
|
|
542
|
-
if (r.width === 0 && r.height === 0) {
|
|
543
|
-
node.hidden = true;
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
|
|
547
|
-
node.hidden = false;
|
|
548
|
-
}
|
|
549
|
-
renderList(pool, targets, className) {
|
|
550
|
-
while (pool.length < targets.length) {
|
|
551
|
-
const el = this.div(className);
|
|
552
|
-
el.hidden = true;
|
|
553
|
-
this.layer.appendChild(el);
|
|
554
|
-
pool.push(el);
|
|
555
|
-
}
|
|
556
|
-
for (let i = 0; i < pool.length; i++) {
|
|
557
|
-
const node = pool[i];
|
|
558
|
-
const target = targets[i];
|
|
559
|
-
if (!target) {
|
|
560
|
-
node.hidden = true;
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
const r = target.getBoundingClientRect();
|
|
564
|
-
if (r.width === 0 && r.height === 0) {
|
|
565
|
-
node.hidden = true;
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
this.placeRect(node, { x: r.left, y: r.top, width: r.width, height: r.height });
|
|
569
|
-
node.hidden = false;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
placeRect(node, rect) {
|
|
573
|
-
moveTo(node, rect.x, rect.y);
|
|
574
|
-
node.style.width = `${rect.width}px`;
|
|
575
|
-
node.style.height = `${rect.height}px`;
|
|
576
|
-
}
|
|
577
|
-
div(className) {
|
|
578
|
-
const el = this.document.createElement("div");
|
|
579
|
-
el.className = className;
|
|
580
|
-
return el;
|
|
581
|
-
}
|
|
582
|
-
};
|
|
583
|
-
function moveTo(node, x, y) {
|
|
584
|
-
const tx = Math.round(x);
|
|
585
|
-
const ty = Math.round(y);
|
|
586
|
-
node.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
|
|
587
|
-
}
|
|
588
|
-
var TAG_LABELS = {
|
|
589
|
-
p: "paragraph",
|
|
590
|
-
h1: "heading",
|
|
591
|
-
h2: "heading",
|
|
592
|
-
h3: "heading",
|
|
593
|
-
h4: "heading",
|
|
594
|
-
h5: "heading",
|
|
595
|
-
h6: "heading",
|
|
596
|
-
a: "link",
|
|
597
|
-
button: "button",
|
|
598
|
-
input: "input",
|
|
599
|
-
textarea: "textarea",
|
|
600
|
-
select: "select",
|
|
601
|
-
img: "image",
|
|
602
|
-
video: "video",
|
|
603
|
-
audio: "audio",
|
|
604
|
-
form: "form",
|
|
605
|
-
nav: "nav",
|
|
606
|
-
header: "header",
|
|
607
|
-
footer: "footer",
|
|
608
|
-
main: "main",
|
|
609
|
-
section: "section",
|
|
610
|
-
article: "article",
|
|
611
|
-
aside: "aside",
|
|
612
|
-
ul: "list",
|
|
613
|
-
ol: "list",
|
|
614
|
-
li: "list item",
|
|
615
|
-
table: "table",
|
|
616
|
-
thead: "table head",
|
|
617
|
-
tbody: "table body",
|
|
618
|
-
tr: "table row",
|
|
619
|
-
td: "cell",
|
|
620
|
-
th: "header cell",
|
|
621
|
-
span: "span",
|
|
622
|
-
div: "div",
|
|
623
|
-
label: "label",
|
|
624
|
-
code: "code",
|
|
625
|
-
pre: "code block",
|
|
626
|
-
blockquote: "quote",
|
|
627
|
-
strong: "bold",
|
|
628
|
-
em: "italic",
|
|
629
|
-
kbd: "key",
|
|
630
|
-
svg: "svg",
|
|
631
|
-
canvas: "canvas"
|
|
632
|
-
};
|
|
633
|
-
function describeElement(el) {
|
|
634
|
-
const tag = el.tagName.toLowerCase();
|
|
635
|
-
const type = TAG_LABELS[tag] ?? tag;
|
|
636
|
-
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
637
|
-
if (text.length > 0) {
|
|
638
|
-
const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
|
|
639
|
-
return `${type}: "${preview}"`;
|
|
640
|
-
}
|
|
641
|
-
if (el.id) return `${type}: #${el.id}`;
|
|
642
|
-
const cls = el.classList[0];
|
|
643
|
-
if (cls) return `${type}: .${cls}`;
|
|
644
|
-
return type;
|
|
645
|
-
}
|
|
646
|
-
function unionOf(elements) {
|
|
647
|
-
let minX = Infinity;
|
|
648
|
-
let minY = Infinity;
|
|
649
|
-
let maxX = -Infinity;
|
|
650
|
-
let maxY = -Infinity;
|
|
651
|
-
let any = false;
|
|
652
|
-
for (const el of elements) {
|
|
653
|
-
const r = el.getBoundingClientRect();
|
|
654
|
-
if (r.width === 0 && r.height === 0) continue;
|
|
655
|
-
any = true;
|
|
656
|
-
if (r.left < minX) minX = r.left;
|
|
657
|
-
if (r.top < minY) minY = r.top;
|
|
658
|
-
if (r.right > maxX) maxX = r.right;
|
|
659
|
-
if (r.bottom > maxY) maxY = r.bottom;
|
|
660
|
-
}
|
|
661
|
-
if (!any) return null;
|
|
662
|
-
const PAD = 4;
|
|
663
|
-
return {
|
|
664
|
-
x: minX - PAD,
|
|
665
|
-
y: minY - PAD,
|
|
666
|
-
width: maxX - minX + PAD * 2,
|
|
667
|
-
height: maxY - minY + PAD * 2
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
var emptyRenderState = {
|
|
671
|
-
hover: null,
|
|
672
|
-
pinned: [],
|
|
673
|
-
selection: null,
|
|
674
|
-
marquee: null
|
|
675
|
-
};
|
|
676
|
-
var Overlay = class {
|
|
677
|
-
renderer;
|
|
678
|
-
engine;
|
|
679
|
-
doc;
|
|
680
|
-
win;
|
|
681
|
-
searchRoot;
|
|
682
|
-
excludeHost;
|
|
683
|
-
raf;
|
|
684
|
-
caf;
|
|
685
|
-
state = emptyRenderState;
|
|
686
|
-
unsubscribe = null;
|
|
687
|
-
rafHandle = null;
|
|
688
|
-
/** Guard for scheduleRender re-entry. Tracked separately from `rafHandle`
|
|
689
|
-
* because a synchronous `raf` (used in tests) returns a handle the cb has
|
|
690
|
-
* already invalidated — boolean is the safe sentinel. */
|
|
691
|
-
renderPending = false;
|
|
692
|
-
destroyed = false;
|
|
693
|
-
bound = [];
|
|
694
|
-
constructor(opts) {
|
|
695
|
-
this.engine = opts.engine;
|
|
696
|
-
this.doc = opts.document ?? opts.root.ownerDocument ?? null;
|
|
697
|
-
if (!this.doc) throw new Error("Overlay: no Document available");
|
|
698
|
-
this.win = this.doc.defaultView ?? globalThis;
|
|
699
|
-
this.searchRoot = opts.searchRoot ?? this.doc.body;
|
|
700
|
-
this.excludeHost = opts.excludeHost ?? null;
|
|
701
|
-
this.raf = opts.raf ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : setTimeout(() => cb(performance.now()), 16));
|
|
702
|
-
this.caf = opts.caf ?? ((h) => {
|
|
703
|
-
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(h);
|
|
704
|
-
else clearTimeout(h);
|
|
705
|
-
});
|
|
706
|
-
this.renderer = new OverlayRenderer(opts.root, this.doc);
|
|
707
|
-
this.bindEvent(this.doc, "scroll", this.onReposition, { passive: true, capture: true });
|
|
708
|
-
this.bindEvent(this.win, "resize", this.onReposition, { passive: true });
|
|
709
|
-
this.unsubscribe = this.engine.subscribe((s) => this.onEngineState(s));
|
|
710
|
-
this.onEngineState(this.engine.getSnapshot());
|
|
711
|
-
}
|
|
712
|
-
destroy() {
|
|
713
|
-
this.destroyed = true;
|
|
714
|
-
if (this.unsubscribe) this.unsubscribe();
|
|
715
|
-
this.unsubscribe = null;
|
|
716
|
-
if (this.rafHandle !== null) this.caf(this.rafHandle);
|
|
717
|
-
this.rafHandle = null;
|
|
718
|
-
for (const [t, ev, fn, opts] of this.bound) t.removeEventListener(ev, fn, opts);
|
|
719
|
-
this.bound = [];
|
|
720
|
-
this.renderer.destroy();
|
|
721
|
-
}
|
|
722
|
-
/* ─── Internals ───────────────────────────────────────────────── */
|
|
723
|
-
onReposition = () => {
|
|
724
|
-
if (this.destroyed) return;
|
|
725
|
-
if (hasAnythingToTrack(this.state)) this.scheduleRender();
|
|
726
|
-
};
|
|
727
|
-
onEngineState(s) {
|
|
728
|
-
this.state = this.derive(s);
|
|
729
|
-
this.scheduleRender();
|
|
730
|
-
}
|
|
731
|
-
/** Engine state → render state. */
|
|
732
|
-
derive(s) {
|
|
733
|
-
switch (s.kind) {
|
|
734
|
-
case "idle":
|
|
735
|
-
return emptyRenderState;
|
|
736
|
-
case "inspect":
|
|
737
|
-
return { hover: s.hoverTarget, pinned: s.pinned, selection: null, marquee: null };
|
|
738
|
-
case "pressed":
|
|
739
|
-
return { hover: s.target, pinned: s.pinned, selection: null, marquee: null };
|
|
740
|
-
case "dragging": {
|
|
741
|
-
const rect = rectFromPoints(s.start, s.current);
|
|
742
|
-
return { hover: null, pinned: s.pinned, selection: null, marquee: rect };
|
|
743
|
-
}
|
|
744
|
-
case "annotating": {
|
|
745
|
-
const sel = s.selection;
|
|
746
|
-
const elements = sel.kind === "area" ? pickElementsInRect(this.searchRoot, sel.rect, this.excludeHost) : sel.kind === "multi" ? sel.elements : [sel.element];
|
|
747
|
-
return { hover: null, pinned: s.pinned, selection: elements, marquee: null };
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
scheduleRender() {
|
|
752
|
-
if (this.destroyed || this.renderPending) return;
|
|
753
|
-
this.renderPending = true;
|
|
754
|
-
this.rafHandle = this.raf(() => {
|
|
755
|
-
this.renderPending = false;
|
|
756
|
-
this.rafHandle = null;
|
|
757
|
-
if (this.destroyed) return;
|
|
758
|
-
this.renderer.render(this.state);
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
bindEvent(target, ev, fn, opts) {
|
|
762
|
-
target.addEventListener(ev, fn, opts);
|
|
763
|
-
this.bound.push([target, ev, fn, opts]);
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
function hasAnythingToTrack(s) {
|
|
767
|
-
return s.hover !== null || s.pinned.length > 0 || s.selection !== null && s.selection.length > 0 || s.marquee !== null;
|
|
768
|
-
}
|
|
769
|
-
var FIBER_PROP_PREFIX = "__reactFiber$";
|
|
770
|
-
function getFiber(node) {
|
|
771
|
-
if (!node) return null;
|
|
772
|
-
for (const key of Object.keys(node)) {
|
|
773
|
-
if (key.startsWith(FIBER_PROP_PREFIX)) return node[key];
|
|
774
|
-
}
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
function getComponentChain(node) {
|
|
778
|
-
const fiber = getFiber(node);
|
|
779
|
-
if (!fiber) return [];
|
|
780
|
-
const names = [];
|
|
781
|
-
let cur = fiber;
|
|
782
|
-
while (cur) {
|
|
783
|
-
const name = getComponentName(cur);
|
|
784
|
-
if (name) names.unshift(name);
|
|
785
|
-
cur = cur.return;
|
|
786
|
-
}
|
|
787
|
-
return dedupeConsecutive(names);
|
|
788
|
-
}
|
|
789
|
-
function getComponentName(fiber) {
|
|
790
|
-
const t = fiber.type;
|
|
791
|
-
if (!t) return null;
|
|
792
|
-
if (typeof t === "string") return null;
|
|
793
|
-
if (typeof t === "function") {
|
|
794
|
-
const fn = t;
|
|
795
|
-
return fn.displayName || fn.name || null;
|
|
796
|
-
}
|
|
797
|
-
if (typeof t === "object") {
|
|
798
|
-
const o = t;
|
|
799
|
-
if (o.displayName) return o.displayName;
|
|
800
|
-
if (o.render) return o.render.displayName || o.render.name || null;
|
|
801
|
-
if (o.type) return o.type.displayName || o.type.name || null;
|
|
802
|
-
}
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
function dedupeConsecutive(names) {
|
|
806
|
-
const out = [];
|
|
807
|
-
for (const n of names) {
|
|
808
|
-
if (out[out.length - 1] !== n) out.push(n);
|
|
809
|
-
}
|
|
810
|
-
return out;
|
|
811
|
-
}
|
|
812
|
-
function getSourceInfo(node) {
|
|
813
|
-
const fiber = getFiber(node);
|
|
814
|
-
if (!fiber) return null;
|
|
815
|
-
let cur = fiber._debugOwner;
|
|
816
|
-
while (cur) {
|
|
817
|
-
if (cur._debugSource) return cur._debugSource;
|
|
818
|
-
cur = cur._debugOwner;
|
|
819
|
-
}
|
|
820
|
-
cur = fiber;
|
|
821
|
-
while (cur) {
|
|
822
|
-
if (cur._debugSource) return cur._debugSource;
|
|
823
|
-
cur = cur.return;
|
|
824
|
-
}
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
function collectAccessibility(el) {
|
|
828
|
-
const parts = [];
|
|
829
|
-
const role = el.getAttribute("role");
|
|
830
|
-
if (role) parts.push(`role=${role}`);
|
|
831
|
-
for (const attr of Array.from(el.attributes)) {
|
|
832
|
-
if (attr.name.startsWith("aria-") && attr.value) {
|
|
833
|
-
parts.push(`${attr.name}=${attr.value}`);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
const tabindex = el.getAttribute("tabindex");
|
|
837
|
-
if (tabindex !== null && tabindex !== "") parts.push(`tabindex=${tabindex}`);
|
|
838
|
-
const title = el.getAttribute("title");
|
|
839
|
-
if (title) parts.push(`title=${title}`);
|
|
840
|
-
return parts.join("; ");
|
|
841
|
-
}
|
|
842
|
-
var GROUPS = {
|
|
843
|
-
layout: [
|
|
844
|
-
"display",
|
|
845
|
-
"position",
|
|
846
|
-
"top",
|
|
847
|
-
"right",
|
|
848
|
-
"bottom",
|
|
849
|
-
"left",
|
|
850
|
-
"width",
|
|
851
|
-
"height",
|
|
852
|
-
"margin",
|
|
853
|
-
"padding",
|
|
854
|
-
"box-sizing"
|
|
855
|
-
],
|
|
856
|
-
visual: [
|
|
857
|
-
"color",
|
|
858
|
-
"background-color",
|
|
859
|
-
"border",
|
|
860
|
-
"border-radius",
|
|
861
|
-
"outline"
|
|
862
|
-
],
|
|
863
|
-
text: [
|
|
864
|
-
"font-family",
|
|
865
|
-
"font-size",
|
|
866
|
-
"font-weight",
|
|
867
|
-
"line-height",
|
|
868
|
-
"letter-spacing",
|
|
869
|
-
"text-align",
|
|
870
|
-
"white-space"
|
|
871
|
-
],
|
|
872
|
-
flexgrid: [
|
|
873
|
-
"flex-direction",
|
|
874
|
-
"justify-content",
|
|
875
|
-
"align-items",
|
|
876
|
-
"gap",
|
|
877
|
-
"grid-template-columns",
|
|
878
|
-
"grid-template-rows"
|
|
879
|
-
],
|
|
880
|
-
effects: ["transform", "box-shadow", "filter"],
|
|
881
|
-
misc: ["cursor", "z-index", "overflow", "opacity", "transition", "animation"]
|
|
882
|
-
};
|
|
883
|
-
var TIER_GROUPS = {
|
|
884
|
-
compact: [],
|
|
885
|
-
standard: ["layout", "visual", "text"],
|
|
886
|
-
detailed: ["layout", "visual", "text", "flexgrid", "effects"],
|
|
887
|
-
forensic: ["layout", "visual", "text", "flexgrid", "effects", "misc"]
|
|
888
|
-
};
|
|
889
|
-
function collectComputedStyles(el, detail) {
|
|
890
|
-
if (detail === "compact") return {};
|
|
891
|
-
const doc = el.ownerDocument;
|
|
892
|
-
const win = doc?.defaultView ?? globalThis;
|
|
893
|
-
if (typeof win.getComputedStyle !== "function") return {};
|
|
894
|
-
const cs = win.getComputedStyle(el);
|
|
895
|
-
const props = /* @__PURE__ */ new Set();
|
|
896
|
-
for (const g of TIER_GROUPS[detail]) {
|
|
897
|
-
for (const p of GROUPS[g]) props.add(p);
|
|
898
|
-
}
|
|
899
|
-
const out = {};
|
|
900
|
-
for (const p of props) {
|
|
901
|
-
const v = cs.getPropertyValue(p);
|
|
902
|
-
if (!v) continue;
|
|
903
|
-
const trimmed = v.trim();
|
|
904
|
-
if (isUninterestingDefault(p, trimmed)) continue;
|
|
905
|
-
out[p] = trimmed;
|
|
906
|
-
}
|
|
907
|
-
return out;
|
|
908
|
-
}
|
|
909
|
-
function isUninterestingDefault(prop, value) {
|
|
910
|
-
if (!value || value === "none" || value === "auto" || value === "normal") return true;
|
|
911
|
-
if (value === "0px" || value === "0%") return true;
|
|
912
|
-
if (value === "rgba(0, 0, 0, 0)") return true;
|
|
913
|
-
if (prop === "color" || prop === "background-color") {
|
|
914
|
-
return value === "rgba(0, 0, 0, 0)";
|
|
915
|
-
}
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
var SHORT_MAX_DEPTH = 5;
|
|
919
|
-
var FULL_MAX_DEPTH = 8;
|
|
920
|
-
function buildSelector(target, doc = target.ownerDocument ?? document) {
|
|
921
|
-
if (target.id && isStableId(target.id)) {
|
|
922
|
-
const escaped = cssEscape(target.id);
|
|
923
|
-
const id = `#${escaped}`;
|
|
924
|
-
return { short: id, full: id };
|
|
925
|
-
}
|
|
926
|
-
const segments = [];
|
|
927
|
-
let cur = target;
|
|
928
|
-
let short = null;
|
|
929
|
-
for (let depth = 0; cur && depth < FULL_MAX_DEPTH; depth++) {
|
|
930
|
-
segments.unshift(segmentFor(cur));
|
|
931
|
-
const candidate = segments.join(" > ");
|
|
932
|
-
if (short === null && depth < SHORT_MAX_DEPTH && isUnique(doc, candidate)) {
|
|
933
|
-
short = candidate;
|
|
934
|
-
}
|
|
935
|
-
cur = cur.parentElement;
|
|
936
|
-
if (!cur || cur === doc.documentElement || cur.tagName.toLowerCase() === "html") break;
|
|
937
|
-
}
|
|
938
|
-
const full = segments.join(" > ");
|
|
939
|
-
return { short: short ?? full, full };
|
|
940
|
-
}
|
|
941
|
-
function segmentFor(el) {
|
|
942
|
-
const tag = el.tagName.toLowerCase();
|
|
943
|
-
if (el.id && isStableId(el.id)) return `${tag}#${cssEscape(el.id)}`;
|
|
944
|
-
const classes = Array.from(el.classList).filter(isUseableClass).slice(0, 3);
|
|
945
|
-
let segment = classes.length ? `${tag}.${classes.map(cssEscape).join(".")}` : tag;
|
|
946
|
-
const parent = el.parentElement;
|
|
947
|
-
if (parent) {
|
|
948
|
-
const sameTag = Array.from(parent.children).filter(
|
|
949
|
-
(c) => c.tagName === el.tagName
|
|
950
|
-
);
|
|
951
|
-
if (sameTag.length > 1) {
|
|
952
|
-
const idx = sameTag.indexOf(el) + 1;
|
|
953
|
-
if (idx > 0) segment += `:nth-of-type(${idx})`;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return segment;
|
|
957
|
-
}
|
|
958
|
-
function isUseableClass(c) {
|
|
959
|
-
if (!c) return false;
|
|
960
|
-
if (c.length > 30) return false;
|
|
961
|
-
if (/^css-[a-z0-9]+$/i.test(c)) return false;
|
|
962
|
-
if (/^jsx-\d+$/i.test(c)) return false;
|
|
963
|
-
if (/^_.+_[a-z0-9]{5,}$/i.test(c)) return false;
|
|
964
|
-
if (/^\d/.test(c)) return false;
|
|
965
|
-
return true;
|
|
966
|
-
}
|
|
967
|
-
function isStableId(id) {
|
|
968
|
-
if (/^:[a-z]\d+:/i.test(id)) return false;
|
|
969
|
-
if (/^radix-/i.test(id)) return false;
|
|
970
|
-
if (/^headlessui-/i.test(id)) return false;
|
|
971
|
-
return true;
|
|
972
|
-
}
|
|
973
|
-
function isUnique(doc, selector) {
|
|
974
|
-
try {
|
|
975
|
-
return doc.querySelectorAll(selector).length === 1;
|
|
976
|
-
} catch {
|
|
977
|
-
return false;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
function cssEscape(s) {
|
|
981
|
-
const g = globalThis;
|
|
982
|
-
if (g.CSS?.escape) return g.CSS.escape(s);
|
|
983
|
-
return s.replace(/[^a-zA-Z0-9_-]/g, (c) => "\\" + c);
|
|
984
|
-
}
|
|
985
|
-
var NEARBY_MAX = 200;
|
|
986
|
-
function collectNearbyText(el) {
|
|
987
|
-
const own = readText(el);
|
|
988
|
-
if (own) return truncate(own, NEARBY_MAX);
|
|
989
|
-
const parent = el.parentElement;
|
|
990
|
-
if (parent) {
|
|
991
|
-
const t = readText(parent);
|
|
992
|
-
if (t) return truncate(t, NEARBY_MAX);
|
|
993
|
-
}
|
|
994
|
-
return "";
|
|
995
|
-
}
|
|
996
|
-
function collectSelectedText(doc = document) {
|
|
997
|
-
const win = doc.defaultView ?? globalThis;
|
|
998
|
-
if (typeof win.getSelection !== "function") return "";
|
|
999
|
-
const sel = win.getSelection();
|
|
1000
|
-
if (!sel) return "";
|
|
1001
|
-
return sel.toString().trim();
|
|
1002
|
-
}
|
|
1003
|
-
function readText(el) {
|
|
1004
|
-
const t = el.innerText ?? el.textContent ?? "";
|
|
1005
|
-
return t.replace(/\s+/g, " ").trim();
|
|
1006
|
-
}
|
|
1007
|
-
function truncate(s, n) {
|
|
1008
|
-
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1009
|
-
}
|
|
1010
|
-
var POSITIONED_FIXED = /* @__PURE__ */ new Set(["fixed", "sticky"]);
|
|
1011
|
-
function collectMetadata(el, options = {}) {
|
|
1012
|
-
perfMark2("clickly:metadata:collect");
|
|
1013
|
-
const detail = options.detail ?? "standard";
|
|
1014
|
-
const includeReact = options.includeReact !== false && detail !== "compact";
|
|
1015
|
-
const doc = options.document ?? el.ownerDocument ?? document;
|
|
1016
|
-
const { short, full } = buildSelector(el, doc);
|
|
1017
|
-
const rect = el.getBoundingClientRect();
|
|
1018
|
-
const boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
1019
|
-
const reactComponents = includeReact ? getComponentChain(el).join(" > ") : "";
|
|
1020
|
-
const source = includeReact ? getSourceInfo(el) : null;
|
|
1021
|
-
return {
|
|
1022
|
-
element: el.tagName.toLowerCase(),
|
|
1023
|
-
elementPath: short,
|
|
1024
|
-
fullPath: full,
|
|
1025
|
-
cssClasses: typeof el.className === "string" ? el.className.trim() : "",
|
|
1026
|
-
computedStyles: collectComputedStyles(el, detail),
|
|
1027
|
-
accessibility: collectAccessibility(el),
|
|
1028
|
-
nearbyText: collectNearbyText(el),
|
|
1029
|
-
selectedText: options.selectedText ?? collectSelectedText(doc),
|
|
1030
|
-
boundingBox,
|
|
1031
|
-
isFixed: hasFixedAncestor(el),
|
|
1032
|
-
reactComponents,
|
|
1033
|
-
sourceFile: source?.fileName ?? "",
|
|
1034
|
-
sourceLine: source?.lineNumber ?? 0,
|
|
1035
|
-
sourceColumn: source?.columnNumber ?? 0
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
function perfMark2(name) {
|
|
1039
|
-
if (typeof performance !== "undefined" && typeof performance.mark === "function") {
|
|
1040
|
-
try {
|
|
1041
|
-
performance.mark(name);
|
|
1042
|
-
} catch {
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function hasFixedAncestor(el) {
|
|
1047
|
-
const doc = el.ownerDocument;
|
|
1048
|
-
const win = doc?.defaultView ?? globalThis;
|
|
1049
|
-
if (typeof win.getComputedStyle !== "function") return false;
|
|
1050
|
-
let cur = el;
|
|
1051
|
-
for (let i = 0; cur && i < 8; i++) {
|
|
1052
|
-
const pos = win.getComputedStyle(cur).getPropertyValue("position");
|
|
1053
|
-
if (POSITIONED_FIXED.has(pos)) return true;
|
|
1054
|
-
cur = cur.parentElement;
|
|
1055
|
-
}
|
|
1056
|
-
return false;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// packages/react/src/internal/ClicklyRoot.tsx
|
|
1060
|
-
var import_react10 = require("react");
|
|
37
|
+
// ../react/src/internal/ClicklyRoot.tsx
|
|
38
|
+
var import_react8 = require("react");
|
|
1061
39
|
|
|
1062
|
-
//
|
|
1063
|
-
var
|
|
40
|
+
// ../react/src/internal/Toolbar.tsx
|
|
41
|
+
var import_react5 = require("react");
|
|
1064
42
|
|
|
1065
|
-
//
|
|
43
|
+
// ../react/src/hooks/useDraggable.ts
|
|
1066
44
|
var import_react = require("react");
|
|
1067
45
|
function useDraggable(defaultPos, size) {
|
|
1068
46
|
const [position, setPosition] = (0, import_react.useState)(defaultPos);
|
|
@@ -1119,7 +97,7 @@ function clamp(n, lo, hi) {
|
|
|
1119
97
|
return Math.max(lo, Math.min(hi, n));
|
|
1120
98
|
}
|
|
1121
99
|
|
|
1122
|
-
//
|
|
100
|
+
// ../react/src/state/useEngineState.ts
|
|
1123
101
|
var import_react2 = require("react");
|
|
1124
102
|
function useEngineState(engine) {
|
|
1125
103
|
return (0, import_react2.useSyncExternalStore)(
|
|
@@ -1131,119 +109,10 @@ function useEngineState(engine) {
|
|
|
1131
109
|
}
|
|
1132
110
|
var IDLE = { kind: "idle" };
|
|
1133
111
|
|
|
1134
|
-
//
|
|
1135
|
-
var
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const setState = (partial, replace) => {
|
|
1139
|
-
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
1140
|
-
if (!Object.is(nextState, state)) {
|
|
1141
|
-
const previousState = state;
|
|
1142
|
-
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
1143
|
-
listeners.forEach((listener) => listener(state, previousState));
|
|
1144
|
-
}
|
|
1145
|
-
};
|
|
1146
|
-
const getState = () => state;
|
|
1147
|
-
const getInitialState = () => initialState2;
|
|
1148
|
-
const subscribe = (listener) => {
|
|
1149
|
-
listeners.add(listener);
|
|
1150
|
-
return () => listeners.delete(listener);
|
|
1151
|
-
};
|
|
1152
|
-
const api = { setState, getState, getInitialState, subscribe };
|
|
1153
|
-
const initialState2 = state = createState(setState, getState, api);
|
|
1154
|
-
return api;
|
|
1155
|
-
};
|
|
1156
|
-
var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
|
|
1157
|
-
|
|
1158
|
-
// node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react.mjs
|
|
1159
|
-
var import_react3 = __toESM(require("react"), 1);
|
|
1160
|
-
var identity = (arg) => arg;
|
|
1161
|
-
function useStore(api, selector = identity) {
|
|
1162
|
-
const slice = import_react3.default.useSyncExternalStore(
|
|
1163
|
-
api.subscribe,
|
|
1164
|
-
import_react3.default.useCallback(() => selector(api.getState()), [api, selector]),
|
|
1165
|
-
import_react3.default.useCallback(() => selector(api.getInitialState()), [api, selector])
|
|
1166
|
-
);
|
|
1167
|
-
import_react3.default.useDebugValue(slice);
|
|
1168
|
-
return slice;
|
|
1169
|
-
}
|
|
1170
|
-
var createImpl = (createState) => {
|
|
1171
|
-
const api = createStore(createState);
|
|
1172
|
-
const useBoundStore = (selector) => useStore(api, selector);
|
|
1173
|
-
Object.assign(useBoundStore, api);
|
|
1174
|
-
return useBoundStore;
|
|
1175
|
-
};
|
|
1176
|
-
var create = ((createState) => createState ? createImpl(createState) : createImpl);
|
|
1177
|
-
|
|
1178
|
-
// node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
|
|
1179
|
-
var import_react4 = __toESM(require("react"), 1);
|
|
1180
|
-
|
|
1181
|
-
// node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/vanilla/shallow.mjs
|
|
1182
|
-
var isIterable = (obj) => Symbol.iterator in obj;
|
|
1183
|
-
var hasIterableEntries = (value) => (
|
|
1184
|
-
// HACK: avoid checking entries type
|
|
1185
|
-
"entries" in value
|
|
1186
|
-
);
|
|
1187
|
-
var compareEntries = (valueA, valueB) => {
|
|
1188
|
-
const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
|
|
1189
|
-
const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
|
|
1190
|
-
if (mapA.size !== mapB.size) {
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
for (const [key, value] of mapA) {
|
|
1194
|
-
if (!mapB.has(key) || !Object.is(value, mapB.get(key))) {
|
|
1195
|
-
return false;
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return true;
|
|
1199
|
-
};
|
|
1200
|
-
var compareIterables = (valueA, valueB) => {
|
|
1201
|
-
const iteratorA = valueA[Symbol.iterator]();
|
|
1202
|
-
const iteratorB = valueB[Symbol.iterator]();
|
|
1203
|
-
let nextA = iteratorA.next();
|
|
1204
|
-
let nextB = iteratorB.next();
|
|
1205
|
-
while (!nextA.done && !nextB.done) {
|
|
1206
|
-
if (!Object.is(nextA.value, nextB.value)) {
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
nextA = iteratorA.next();
|
|
1210
|
-
nextB = iteratorB.next();
|
|
1211
|
-
}
|
|
1212
|
-
return !!nextA.done && !!nextB.done;
|
|
1213
|
-
};
|
|
1214
|
-
function shallow(valueA, valueB) {
|
|
1215
|
-
if (Object.is(valueA, valueB)) {
|
|
1216
|
-
return true;
|
|
1217
|
-
}
|
|
1218
|
-
if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
|
|
1219
|
-
return false;
|
|
1220
|
-
}
|
|
1221
|
-
if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
|
|
1222
|
-
return false;
|
|
1223
|
-
}
|
|
1224
|
-
if (isIterable(valueA) && isIterable(valueB)) {
|
|
1225
|
-
if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
|
|
1226
|
-
return compareEntries(valueA, valueB);
|
|
1227
|
-
}
|
|
1228
|
-
return compareIterables(valueA, valueB);
|
|
1229
|
-
}
|
|
1230
|
-
return compareEntries(
|
|
1231
|
-
{ entries: () => Object.entries(valueA) },
|
|
1232
|
-
{ entries: () => Object.entries(valueB) }
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// node_modules/.pnpm/zustand@5.0.14_@types+react@18.3.31_react@18.3.1/node_modules/zustand/esm/react/shallow.mjs
|
|
1237
|
-
function useShallow(selector) {
|
|
1238
|
-
const prev = import_react4.default.useRef(void 0);
|
|
1239
|
-
return (state) => {
|
|
1240
|
-
const next = selector(state);
|
|
1241
|
-
return shallow(prev.current, next) ? prev.current : prev.current = next;
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// packages/react/src/state/annotations.ts
|
|
1246
|
-
var useAnnotations = create((set, get) => ({
|
|
112
|
+
// ../react/src/state/annotations.ts
|
|
113
|
+
var import_zustand = require("zustand");
|
|
114
|
+
var import_shallow = require("zustand/react/shallow");
|
|
115
|
+
var useAnnotations = (0, import_zustand.create)((set, get) => ({
|
|
1247
116
|
byId: {},
|
|
1248
117
|
order: [],
|
|
1249
118
|
add: (a) => set((s) => ({
|
|
@@ -1269,11 +138,12 @@ var useAnnotations = create((set, get) => ({
|
|
|
1269
138
|
}));
|
|
1270
139
|
function useAnnotationsList() {
|
|
1271
140
|
return useAnnotations(
|
|
1272
|
-
useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
|
|
141
|
+
(0, import_shallow.useShallow)((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
|
|
1273
142
|
);
|
|
1274
143
|
}
|
|
1275
144
|
|
|
1276
|
-
//
|
|
145
|
+
// ../react/src/state/settings.ts
|
|
146
|
+
var import_zustand2 = require("zustand");
|
|
1277
147
|
var DEFAULTS = {
|
|
1278
148
|
outputDetail: "standard",
|
|
1279
149
|
copyOnAdd: true,
|
|
@@ -1299,7 +169,7 @@ function persist(s) {
|
|
|
1299
169
|
} catch {
|
|
1300
170
|
}
|
|
1301
171
|
}
|
|
1302
|
-
var useSettings = create((set) => ({
|
|
172
|
+
var useSettings = (0, import_zustand2.create)((set) => ({
|
|
1303
173
|
...load(),
|
|
1304
174
|
set: (patch) => set((cur) => {
|
|
1305
175
|
const next = { ...cur, ...patch };
|
|
@@ -1312,7 +182,7 @@ var useSettings = create((set) => ({
|
|
|
1312
182
|
}
|
|
1313
183
|
}));
|
|
1314
184
|
|
|
1315
|
-
//
|
|
185
|
+
// ../react/src/output/markdown.ts
|
|
1316
186
|
function annotationsToMarkdown(annotations, detail = "standard") {
|
|
1317
187
|
if (!annotations.length) return "(no annotations)";
|
|
1318
188
|
return annotations.map((a, i) => formatOne(a, i + 1, detail)).join("\n\n");
|
|
@@ -1335,7 +205,7 @@ function formatOne(a, index, detail) {
|
|
|
1335
205
|
}
|
|
1336
206
|
if (detail === "detailed" || detail === "forensic") {
|
|
1337
207
|
if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
|
|
1338
|
-
if (a.nearbyText) lines.push(`**Nearby text:** ${
|
|
208
|
+
if (a.nearbyText) lines.push(`**Nearby text:** ${truncate(a.nearbyText, 120)}`);
|
|
1339
209
|
}
|
|
1340
210
|
if (detail === "forensic" && a.computedStyles) {
|
|
1341
211
|
lines.push("**Computed styles:**\n```css\n" + a.computedStyles + "\n```");
|
|
@@ -1347,11 +217,11 @@ function formatOne(a, index, detail) {
|
|
|
1347
217
|
if (a.severity) lines.push(`**Severity:** ${a.severity}`);
|
|
1348
218
|
return lines.join("\n");
|
|
1349
219
|
}
|
|
1350
|
-
function
|
|
220
|
+
function truncate(s, n) {
|
|
1351
221
|
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1352
222
|
}
|
|
1353
223
|
|
|
1354
|
-
//
|
|
224
|
+
// ../react/src/internal/icons.tsx
|
|
1355
225
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1356
226
|
function Icon({
|
|
1357
227
|
children,
|
|
@@ -1373,6 +243,18 @@ function Icon({
|
|
|
1373
243
|
}
|
|
1374
244
|
);
|
|
1375
245
|
}
|
|
246
|
+
var IconFreeze = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { size: 15, children: [
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "2", x2: "12", y2: "22" }),
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "5", y1: "5", x2: "19", y2: "19" }),
|
|
250
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "19", y1: "5", x2: "5", y2: "19" }),
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor", stroke: "none" })
|
|
252
|
+
] });
|
|
253
|
+
var IconInfo = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { size: 14, children: [
|
|
254
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "8", strokeWidth: "2.5" }),
|
|
256
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "12", x2: "12", y2: "16" })
|
|
257
|
+
] });
|
|
1376
258
|
var IconCursor = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4l6 16 2-7 7-2z" }) });
|
|
1377
259
|
var IconLayers = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { children: [
|
|
1378
260
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
|
|
@@ -1400,13 +282,13 @@ var IconClose = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { childr
|
|
|
1400
282
|
var IconCheck = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12l5 5L20 7" }) });
|
|
1401
283
|
var IconList = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }) });
|
|
1402
284
|
|
|
1403
|
-
//
|
|
1404
|
-
var
|
|
285
|
+
// ../react/src/internal/SettingsPopover.tsx
|
|
286
|
+
var import_react3 = require("react");
|
|
1405
287
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1406
288
|
function SettingsPopover({ anchor, width, onClose }) {
|
|
1407
|
-
const ref = (0,
|
|
289
|
+
const ref = (0, import_react3.useRef)(null);
|
|
1408
290
|
const s = useSettings();
|
|
1409
|
-
(0,
|
|
291
|
+
(0, import_react3.useEffect)(() => {
|
|
1410
292
|
const onDown = (e) => {
|
|
1411
293
|
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1412
294
|
onClose();
|
|
@@ -1501,15 +383,15 @@ function SettingsPopover({ anchor, width, onClose }) {
|
|
|
1501
383
|
] });
|
|
1502
384
|
}
|
|
1503
385
|
|
|
1504
|
-
//
|
|
1505
|
-
var
|
|
386
|
+
// ../react/src/internal/AnnotationList.tsx
|
|
387
|
+
var import_react4 = require("react");
|
|
1506
388
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1507
389
|
function AnnotationList({ anchor, width, onClose }) {
|
|
1508
390
|
const items = useAnnotationsList();
|
|
1509
391
|
const remove = useAnnotations((s) => s.remove);
|
|
1510
392
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1511
|
-
const ref = (0,
|
|
1512
|
-
(0,
|
|
393
|
+
const ref = (0, import_react4.useRef)(null);
|
|
394
|
+
(0, import_react4.useEffect)(() => {
|
|
1513
395
|
const onDown = (e) => {
|
|
1514
396
|
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1515
397
|
onClose();
|
|
@@ -1543,7 +425,7 @@ function AnnotationCard({
|
|
|
1543
425
|
outputDetail,
|
|
1544
426
|
onRemove
|
|
1545
427
|
}) {
|
|
1546
|
-
const [copied, setCopied] = (0,
|
|
428
|
+
const [copied, setCopied] = (0, import_react4.useState)(false);
|
|
1547
429
|
const copyOne = async () => {
|
|
1548
430
|
const md = formatOne(a, index, outputDetail);
|
|
1549
431
|
try {
|
|
@@ -1589,9 +471,48 @@ function AnnotationCard({
|
|
|
1589
471
|
] });
|
|
1590
472
|
}
|
|
1591
473
|
|
|
1592
|
-
//
|
|
474
|
+
// ../react/src/internal/Toolbar.tsx
|
|
1593
475
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1594
|
-
var TOOLBAR_SIZE = { width:
|
|
476
|
+
var TOOLBAR_SIZE = { width: 360, height: 44 };
|
|
477
|
+
var SHORTCUTS = [
|
|
478
|
+
{ keys: ["\u2318", "\u21E7", "F"], label: "Toggle toolbar" },
|
|
479
|
+
{ keys: ["1"], label: "Single select mode" },
|
|
480
|
+
{ keys: ["2"], label: "Multi select mode" },
|
|
481
|
+
{ keys: ["3"], label: "Area drag mode" },
|
|
482
|
+
{ keys: ["\u21B5"], label: "Annotate pinned elements" },
|
|
483
|
+
{ keys: ["Esc"], label: "Cancel / clear / close" },
|
|
484
|
+
{ keys: ["\u2318", "\u21B5"], label: "Submit annotation" },
|
|
485
|
+
{ keys: ["C"], label: "Copy all annotations" },
|
|
486
|
+
{ keys: ["X"], label: "Clear all annotations" }
|
|
487
|
+
];
|
|
488
|
+
function ShortcutsPanel() {
|
|
489
|
+
const [visible, setVisible] = (0, import_react5.useState)(false);
|
|
490
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
491
|
+
"span",
|
|
492
|
+
{
|
|
493
|
+
className: "clickly-tip shortcuts-trigger",
|
|
494
|
+
onMouseEnter: () => setVisible(true),
|
|
495
|
+
onMouseLeave: () => setVisible(false),
|
|
496
|
+
children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
498
|
+
"button",
|
|
499
|
+
{
|
|
500
|
+
className: "clickly-btn icon-only",
|
|
501
|
+
"aria-label": "Show keyboard shortcuts",
|
|
502
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconInfo, {})
|
|
503
|
+
}
|
|
504
|
+
),
|
|
505
|
+
visible && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "shortcuts-panel", role: "tooltip", children: [
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "shortcuts-title", children: "Keyboard shortcuts" }),
|
|
507
|
+
SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "shortcuts-row", children: [
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shortcuts-label", children: s.label }),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shortcuts-keys", children: s.keys.map((k) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("kbd", { children: k }, k)) })
|
|
510
|
+
] }, s.label))
|
|
511
|
+
] })
|
|
512
|
+
]
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
}
|
|
1595
516
|
function Tip({
|
|
1596
517
|
label,
|
|
1597
518
|
shortcut,
|
|
@@ -1605,14 +526,14 @@ function Tip({
|
|
|
1605
526
|
] })
|
|
1606
527
|
] });
|
|
1607
528
|
}
|
|
1608
|
-
function Toolbar({ engine, onCollapse }) {
|
|
529
|
+
function Toolbar({ engine, onCollapse, isClosing, onCloseEnd, frozen, onFreezeToggle }) {
|
|
1609
530
|
const state = useEngineState(engine);
|
|
1610
531
|
const annotations = useAnnotationsList();
|
|
1611
532
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
1612
533
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1613
|
-
const [showSettings, setShowSettings] = (0,
|
|
1614
|
-
const [showList, setShowList] = (0,
|
|
1615
|
-
const anchorRef = (0,
|
|
534
|
+
const [showSettings, setShowSettings] = (0, import_react5.useState)(false);
|
|
535
|
+
const [showList, setShowList] = (0, import_react5.useState)(false);
|
|
536
|
+
const anchorRef = (0, import_react5.useRef)(null);
|
|
1616
537
|
const { position, handleProps } = useDraggable(
|
|
1617
538
|
{
|
|
1618
539
|
x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
|
|
@@ -1638,8 +559,9 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1638
559
|
"div",
|
|
1639
560
|
{
|
|
1640
561
|
ref: anchorRef,
|
|
1641
|
-
className:
|
|
562
|
+
className: `clickly-toolbar${isClosing ? " is-closing" : ""}`,
|
|
1642
563
|
style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
|
|
564
|
+
onAnimationEnd: isClosing ? onCloseEnd : void 0,
|
|
1643
565
|
"aria-label": "Clickly toolbar",
|
|
1644
566
|
children: [
|
|
1645
567
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Move", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -1695,6 +617,16 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1695
617
|
) }),
|
|
1696
618
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" })
|
|
1697
619
|
] }),
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: frozen ? "Unfreeze animations" : "Freeze animations", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
621
|
+
"button",
|
|
622
|
+
{
|
|
623
|
+
className: `clickly-btn icon-only${frozen ? " is-freeze" : ""}`,
|
|
624
|
+
onClick: onFreezeToggle,
|
|
625
|
+
"aria-label": frozen ? "Unfreeze page animations" : "Freeze page animations",
|
|
626
|
+
"aria-pressed": frozen,
|
|
627
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconFreeze, {})
|
|
628
|
+
}
|
|
629
|
+
) }),
|
|
1698
630
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1699
631
|
"button",
|
|
1700
632
|
{
|
|
@@ -1728,6 +660,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1728
660
|
}
|
|
1729
661
|
) }),
|
|
1730
662
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ShortcutsPanel, {}),
|
|
1731
664
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Settings", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1732
665
|
"button",
|
|
1733
666
|
{
|
|
@@ -1768,7 +701,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
1768
701
|
);
|
|
1769
702
|
}
|
|
1770
703
|
|
|
1771
|
-
//
|
|
704
|
+
// ../react/src/internal/CollapsedFAB.tsx
|
|
1772
705
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1773
706
|
function CollapsedFAB({ onExpand }) {
|
|
1774
707
|
const annotations = useAnnotationsList();
|
|
@@ -1787,9 +720,24 @@ function CollapsedFAB({ onExpand }) {
|
|
|
1787
720
|
);
|
|
1788
721
|
}
|
|
1789
722
|
|
|
1790
|
-
//
|
|
1791
|
-
var
|
|
1792
|
-
|
|
723
|
+
// ../react/src/internal/AnnotationPopup.tsx
|
|
724
|
+
var import_react6 = require("react");
|
|
725
|
+
|
|
726
|
+
// ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/url-alphabet/index.js
|
|
727
|
+
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
728
|
+
|
|
729
|
+
// ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/index.browser.js
|
|
730
|
+
var nanoid = (size = 21) => {
|
|
731
|
+
let id = "";
|
|
732
|
+
let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
|
|
733
|
+
while (size--) {
|
|
734
|
+
id += urlAlphabet[bytes[size] & 63];
|
|
735
|
+
}
|
|
736
|
+
return id;
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// ../react/src/internal/AnnotationPopup.tsx
|
|
740
|
+
var import_core = require("@useclickly/core");
|
|
1793
741
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1794
742
|
var POPUP_W = 320;
|
|
1795
743
|
var POPUP_H_EST = 240;
|
|
@@ -1798,15 +746,15 @@ function AnnotationPopup({ engine }) {
|
|
|
1798
746
|
const addAnnotation = useAnnotations((s) => s.add);
|
|
1799
747
|
const copyOnAdd = useSettings((s) => s.copyOnAdd);
|
|
1800
748
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1801
|
-
const [comment, setComment] = (0,
|
|
1802
|
-
const [showStyles, setShowStyles] = (0,
|
|
1803
|
-
const [editMode, setEditMode] = (0,
|
|
1804
|
-
const [styles, setStyles] = (0,
|
|
1805
|
-
const [editedStyles, setEditedStyles] = (0,
|
|
1806
|
-
const targetElRef = (0,
|
|
1807
|
-
const taRef = (0,
|
|
1808
|
-
const popupRef = (0,
|
|
1809
|
-
(0,
|
|
749
|
+
const [comment, setComment] = (0, import_react6.useState)("");
|
|
750
|
+
const [showStyles, setShowStyles] = (0, import_react6.useState)(false);
|
|
751
|
+
const [editMode, setEditMode] = (0, import_react6.useState)(false);
|
|
752
|
+
const [styles, setStyles] = (0, import_react6.useState)({});
|
|
753
|
+
const [editedStyles, setEditedStyles] = (0, import_react6.useState)({});
|
|
754
|
+
const targetElRef = (0, import_react6.useRef)(null);
|
|
755
|
+
const taRef = (0, import_react6.useRef)(null);
|
|
756
|
+
const popupRef = (0, import_react6.useRef)(null);
|
|
757
|
+
(0, import_react6.useEffect)(() => {
|
|
1810
758
|
if (state.kind === "annotating") {
|
|
1811
759
|
setComment("");
|
|
1812
760
|
setShowStyles(false);
|
|
@@ -1816,14 +764,14 @@ function AnnotationPopup({ engine }) {
|
|
|
1816
764
|
const el = sel?.kind === "single" ? sel.element : sel?.kind === "multi" || sel?.kind === "area" ? sel.elements[0] : null;
|
|
1817
765
|
targetElRef.current = el instanceof HTMLElement ? el : null;
|
|
1818
766
|
if (el) {
|
|
1819
|
-
setStyles(collectComputedStyles(el, "standard"));
|
|
767
|
+
setStyles((0, import_core.collectComputedStyles)(el, "standard"));
|
|
1820
768
|
} else {
|
|
1821
769
|
setStyles({});
|
|
1822
770
|
}
|
|
1823
771
|
requestAnimationFrame(() => taRef.current?.focus());
|
|
1824
772
|
}
|
|
1825
773
|
}, [state.kind, engine]);
|
|
1826
|
-
(0,
|
|
774
|
+
(0, import_react6.useEffect)(() => {
|
|
1827
775
|
if (state.kind !== "annotating") return;
|
|
1828
776
|
const onDown = (e) => {
|
|
1829
777
|
if (popupRef.current && e.composedPath().includes(popupRef.current)) return;
|
|
@@ -1872,9 +820,9 @@ function AnnotationPopup({ engine }) {
|
|
|
1872
820
|
const isMulti = sel.kind !== "single";
|
|
1873
821
|
const showReact = useSettings.getState().showReactComponents;
|
|
1874
822
|
for (const el of elements) {
|
|
1875
|
-
const md = collectMetadata(el, { detail: outputDetail, includeReact: showReact });
|
|
823
|
+
const md = (0, import_core.collectMetadata)(el, { detail: outputDetail, includeReact: showReact });
|
|
1876
824
|
const annotation = {
|
|
1877
|
-
id: "ann_" +
|
|
825
|
+
id: "ann_" + nanoid(8),
|
|
1878
826
|
comment: sharedComment,
|
|
1879
827
|
element: md.element,
|
|
1880
828
|
elementPath: md.elementPath,
|
|
@@ -1907,8 +855,8 @@ function AnnotationPopup({ engine }) {
|
|
|
1907
855
|
}
|
|
1908
856
|
engine.commit();
|
|
1909
857
|
};
|
|
1910
|
-
const [placement, setPlacement] = (0,
|
|
1911
|
-
const calcPlacement = (0,
|
|
858
|
+
const [placement, setPlacement] = (0, import_react6.useState)(null);
|
|
859
|
+
const calcPlacement = (0, import_react6.useCallback)(() => {
|
|
1912
860
|
if (state.kind !== "annotating") {
|
|
1913
861
|
setPlacement(null);
|
|
1914
862
|
return;
|
|
@@ -1941,10 +889,10 @@ function AnnotationPopup({ engine }) {
|
|
|
1941
889
|
if (left + POPUP_W > vw) left = Math.max(8, vw - POPUP_W - 8);
|
|
1942
890
|
setPlacement({ top, left, label });
|
|
1943
891
|
}, [state, engine]);
|
|
1944
|
-
(0,
|
|
892
|
+
(0, import_react6.useEffect)(() => {
|
|
1945
893
|
calcPlacement();
|
|
1946
894
|
}, [calcPlacement]);
|
|
1947
|
-
(0,
|
|
895
|
+
(0, import_react6.useEffect)(() => {
|
|
1948
896
|
if (state.kind !== "annotating") return;
|
|
1949
897
|
const onScroll = () => calcPlacement();
|
|
1950
898
|
window.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
@@ -2065,7 +1013,7 @@ function anchorRect(sel) {
|
|
|
2065
1013
|
if (sel.elements.length === 0) return null;
|
|
2066
1014
|
return sel.elements[0].getBoundingClientRect();
|
|
2067
1015
|
}
|
|
2068
|
-
var
|
|
1016
|
+
var TAG_LABELS = {
|
|
2069
1017
|
p: "paragraph",
|
|
2070
1018
|
h1: "heading",
|
|
2071
1019
|
h2: "heading",
|
|
@@ -2107,9 +1055,9 @@ var TAG_LABELS2 = {
|
|
|
2107
1055
|
svg: "svg",
|
|
2108
1056
|
canvas: "canvas"
|
|
2109
1057
|
};
|
|
2110
|
-
function
|
|
1058
|
+
function describeElement(el) {
|
|
2111
1059
|
const tag = el.tagName.toLowerCase();
|
|
2112
|
-
const type =
|
|
1060
|
+
const type = TAG_LABELS[tag] ?? tag;
|
|
2113
1061
|
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
2114
1062
|
if (text.length > 0) {
|
|
2115
1063
|
const preview = text.length > 48 ? text.slice(0, 48) + "\u2026" : text;
|
|
@@ -2121,19 +1069,19 @@ function describeElement2(el) {
|
|
|
2121
1069
|
return type;
|
|
2122
1070
|
}
|
|
2123
1071
|
function describeSelection(sel) {
|
|
2124
|
-
if (sel.kind === "single") return
|
|
1072
|
+
if (sel.kind === "single") return describeElement(sel.element);
|
|
2125
1073
|
if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
|
|
2126
|
-
if (sel.elements.length === 1) return
|
|
1074
|
+
if (sel.elements.length === 1) return describeElement(sel.elements[0]);
|
|
2127
1075
|
return `${sel.elements.length} element(s)`;
|
|
2128
1076
|
}
|
|
2129
1077
|
|
|
2130
|
-
//
|
|
2131
|
-
var
|
|
1078
|
+
// ../react/src/internal/AnnotationPins.tsx
|
|
1079
|
+
var import_react7 = require("react");
|
|
2132
1080
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2133
1081
|
function AnnotationPins() {
|
|
2134
1082
|
const annotations = useAnnotationsList();
|
|
2135
|
-
const [, setVersion] = (0,
|
|
2136
|
-
(0,
|
|
1083
|
+
const [, setVersion] = (0, import_react7.useState)(0);
|
|
1084
|
+
(0, import_react7.useEffect)(() => {
|
|
2137
1085
|
let raf = null;
|
|
2138
1086
|
const update = () => {
|
|
2139
1087
|
if (raf !== null) return;
|
|
@@ -2152,7 +1100,7 @@ function AnnotationPins() {
|
|
|
2152
1100
|
}, []);
|
|
2153
1101
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: annotations.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Pin, { number: i + 1, annotation: a }, a.id)) });
|
|
2154
1102
|
}
|
|
2155
|
-
var
|
|
1103
|
+
var TAG_LABELS2 = {
|
|
2156
1104
|
p: "paragraph",
|
|
2157
1105
|
h1: "heading",
|
|
2158
1106
|
h2: "heading",
|
|
@@ -2192,7 +1140,7 @@ var TAG_LABELS3 = {
|
|
|
2192
1140
|
function pinLabel(annotation) {
|
|
2193
1141
|
const raw = (annotation.element ?? "").toLowerCase();
|
|
2194
1142
|
const tag = raw.split(/[.#\s]/)[0] ?? "";
|
|
2195
|
-
return (
|
|
1143
|
+
return (TAG_LABELS2[tag] ?? tag) || "element";
|
|
2196
1144
|
}
|
|
2197
1145
|
function parseStyles(raw) {
|
|
2198
1146
|
if (!raw) return [];
|
|
@@ -2205,12 +1153,12 @@ function parseStyles(raw) {
|
|
|
2205
1153
|
function Pin({ number, annotation }) {
|
|
2206
1154
|
const remove = useAnnotations((s) => s.remove);
|
|
2207
1155
|
const update = useAnnotations((s) => s.update);
|
|
2208
|
-
const [hovered, setHovered] = (0,
|
|
2209
|
-
const [editing, setEditing] = (0,
|
|
2210
|
-
const [draft, setDraft] = (0,
|
|
2211
|
-
const [showStyles, setShowStyles] = (0,
|
|
2212
|
-
const taRef = (0,
|
|
2213
|
-
const editRef = (0,
|
|
1156
|
+
const [hovered, setHovered] = (0, import_react7.useState)(false);
|
|
1157
|
+
const [editing, setEditing] = (0, import_react7.useState)(false);
|
|
1158
|
+
const [draft, setDraft] = (0, import_react7.useState)(annotation.comment);
|
|
1159
|
+
const [showStyles, setShowStyles] = (0, import_react7.useState)(false);
|
|
1160
|
+
const taRef = (0, import_react7.useRef)(null);
|
|
1161
|
+
const editRef = (0, import_react7.useRef)(null);
|
|
2214
1162
|
const styleEntries = parseStyles(annotation.computedStyles);
|
|
2215
1163
|
const label = pinLabel(annotation);
|
|
2216
1164
|
const headerLabel = annotation.isMultiSelect ? `${label} (multi-select)` : `${label}: "${annotation.elementPath}"`;
|
|
@@ -2233,7 +1181,7 @@ function Pin({ number, annotation }) {
|
|
|
2233
1181
|
if (e.key === "Escape") closeEdit();
|
|
2234
1182
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
|
|
2235
1183
|
};
|
|
2236
|
-
(0,
|
|
1184
|
+
(0, import_react7.useEffect)(() => {
|
|
2237
1185
|
if (!editing) return;
|
|
2238
1186
|
const onDown = (e) => {
|
|
2239
1187
|
if (editRef.current && e.composedPath().includes(editRef.current)) return;
|
|
@@ -2242,7 +1190,7 @@ function Pin({ number, annotation }) {
|
|
|
2242
1190
|
window.addEventListener("pointerdown", onDown, true);
|
|
2243
1191
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
2244
1192
|
}, [editing]);
|
|
2245
|
-
(0,
|
|
1193
|
+
(0, import_react7.useEffect)(() => {
|
|
2246
1194
|
if (!editing) return;
|
|
2247
1195
|
let el = null;
|
|
2248
1196
|
if (annotation.elementPath) {
|
|
@@ -2370,28 +1318,63 @@ function resolvePosition(a) {
|
|
|
2370
1318
|
return null;
|
|
2371
1319
|
}
|
|
2372
1320
|
|
|
2373
|
-
//
|
|
1321
|
+
// ../react/src/internal/ClicklyRoot.tsx
|
|
2374
1322
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2375
1323
|
function ClicklyRoot({
|
|
2376
1324
|
engine,
|
|
2377
1325
|
host
|
|
2378
1326
|
}) {
|
|
2379
|
-
const [expanded, setExpanded] = (0,
|
|
1327
|
+
const [expanded, setExpanded] = (0, import_react8.useState)(false);
|
|
1328
|
+
const [toolbarClosing, setToolbarClosing] = (0, import_react8.useState)(false);
|
|
1329
|
+
const [frozen, setFrozen] = (0, import_react8.useState)(false);
|
|
1330
|
+
const collapse = () => {
|
|
1331
|
+
setToolbarClosing(true);
|
|
1332
|
+
};
|
|
1333
|
+
const onToolbarCloseEnd = () => {
|
|
1334
|
+
setToolbarClosing(false);
|
|
1335
|
+
setExpanded(false);
|
|
1336
|
+
};
|
|
2380
1337
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
2381
1338
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
2382
1339
|
const markerColor = useSettings((s) => s.markerColor);
|
|
2383
1340
|
const engineState = useEngineState(engine);
|
|
2384
|
-
(0,
|
|
1341
|
+
(0, import_react8.useEffect)(() => {
|
|
2385
1342
|
host.style.setProperty("--clickly-hover", markerColor);
|
|
2386
1343
|
}, [host, markerColor]);
|
|
2387
|
-
(0,
|
|
1344
|
+
(0, import_react8.useEffect)(() => {
|
|
1345
|
+
const STYLE_ID = "clickly-freeze-animations";
|
|
1346
|
+
const gsap = window.gsap;
|
|
1347
|
+
if (frozen) {
|
|
1348
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
1349
|
+
const el = document.createElement("style");
|
|
1350
|
+
el.id = STYLE_ID;
|
|
1351
|
+
el.textContent = `
|
|
1352
|
+
*, *::before, *::after {
|
|
1353
|
+
animation-play-state: paused !important;
|
|
1354
|
+
transition-duration: 0ms !important;
|
|
1355
|
+
transition-delay: 0ms !important;
|
|
1356
|
+
}
|
|
1357
|
+
`;
|
|
1358
|
+
document.head.appendChild(el);
|
|
1359
|
+
}
|
|
1360
|
+
if (gsap?.globalTimeline) gsap.globalTimeline.pause();
|
|
1361
|
+
} else {
|
|
1362
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
1363
|
+
if (gsap?.globalTimeline) gsap.globalTimeline.resume();
|
|
1364
|
+
}
|
|
1365
|
+
return () => {
|
|
1366
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
1367
|
+
if (gsap?.globalTimeline) gsap.globalTimeline.resume();
|
|
1368
|
+
};
|
|
1369
|
+
}, [frozen]);
|
|
1370
|
+
(0, import_react8.useEffect)(() => {
|
|
2388
1371
|
if (expanded) {
|
|
2389
1372
|
if (engine.getSnapshot().kind === "idle") engine.activate("single");
|
|
2390
1373
|
} else {
|
|
2391
1374
|
if (engine.getSnapshot().kind !== "idle") engine.deactivate();
|
|
2392
1375
|
}
|
|
2393
1376
|
}, [expanded, engine]);
|
|
2394
|
-
(0,
|
|
1377
|
+
(0, import_react8.useEffect)(() => {
|
|
2395
1378
|
const active = engineState.kind !== "idle";
|
|
2396
1379
|
if (active) document.body.setAttribute("data-clickly-active", "");
|
|
2397
1380
|
else document.body.removeAttribute("data-clickly-active");
|
|
@@ -2409,7 +1392,7 @@ function ClicklyRoot({
|
|
|
2409
1392
|
document.body.removeAttribute("data-clickly-annotating");
|
|
2410
1393
|
};
|
|
2411
1394
|
}, [engineState]);
|
|
2412
|
-
(0,
|
|
1395
|
+
(0, import_react8.useEffect)(() => {
|
|
2413
1396
|
const onKey = (e) => {
|
|
2414
1397
|
const tag = e.target?.tagName;
|
|
2415
1398
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
@@ -2418,12 +1401,13 @@ function ClicklyRoot({
|
|
|
2418
1401
|
if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
|
|
2419
1402
|
if (e.key.toLowerCase() === "f" && e.shiftKey && (e.metaKey || e.ctrlKey)) {
|
|
2420
1403
|
e.preventDefault();
|
|
2421
|
-
|
|
1404
|
+
if (expanded) collapse();
|
|
1405
|
+
else setExpanded(true);
|
|
2422
1406
|
return;
|
|
2423
1407
|
}
|
|
2424
1408
|
if (e.key === "Escape") {
|
|
2425
1409
|
if (engine.getSnapshot().kind === "annotating") return;
|
|
2426
|
-
if (expanded)
|
|
1410
|
+
if (expanded) collapse();
|
|
2427
1411
|
return;
|
|
2428
1412
|
}
|
|
2429
1413
|
if (!expanded) return;
|
|
@@ -2454,14 +1438,24 @@ function ClicklyRoot({
|
|
|
2454
1438
|
}, [clearAnnotations, outputDetail, expanded, engine]);
|
|
2455
1439
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "clickly-ui", children: [
|
|
2456
1440
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPins, {}),
|
|
2457
|
-
expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2458
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2459
|
-
|
|
1441
|
+
expanded || toolbarClosing ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
1442
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1443
|
+
Toolbar,
|
|
1444
|
+
{
|
|
1445
|
+
engine,
|
|
1446
|
+
onCollapse: collapse,
|
|
1447
|
+
isClosing: toolbarClosing,
|
|
1448
|
+
onCloseEnd: onToolbarCloseEnd,
|
|
1449
|
+
frozen,
|
|
1450
|
+
onFreezeToggle: () => setFrozen((v) => !v)
|
|
1451
|
+
}
|
|
1452
|
+
),
|
|
1453
|
+
!toolbarClosing && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPopup, { engine })
|
|
2460
1454
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CollapsedFAB, { onExpand: () => setExpanded(true) })
|
|
2461
1455
|
] });
|
|
2462
1456
|
}
|
|
2463
1457
|
|
|
2464
|
-
//
|
|
1458
|
+
// ../react/src/internal/styles.ts
|
|
2465
1459
|
var REACT_UI_CSS = `
|
|
2466
1460
|
.clickly-ui, .clickly-ui * { box-sizing: border-box; }
|
|
2467
1461
|
|
|
@@ -2487,6 +1481,11 @@ var REACT_UI_CSS = `
|
|
|
2487
1481
|
0 0 0 1px rgba(255,255,255,0.06) inset;
|
|
2488
1482
|
transition: transform 140ms ease, box-shadow 140ms ease;
|
|
2489
1483
|
z-index: 1;
|
|
1484
|
+
animation: clickly-fab-open 280ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
1485
|
+
}
|
|
1486
|
+
@keyframes clickly-fab-open {
|
|
1487
|
+
from { opacity: 0; transform: scale(0.5); }
|
|
1488
|
+
to { opacity: 1; transform: scale(1); }
|
|
2490
1489
|
}
|
|
2491
1490
|
.clickly-fab:hover {
|
|
2492
1491
|
transform: scale(1.06);
|
|
@@ -2533,12 +1532,21 @@ var REACT_UI_CSS = `
|
|
|
2533
1532
|
pointer-events: auto;
|
|
2534
1533
|
user-select: none;
|
|
2535
1534
|
z-index: 1;
|
|
2536
|
-
|
|
1535
|
+
transform-origin: bottom right;
|
|
1536
|
+
animation: clickly-toolbar-open 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1537
|
+
}
|
|
1538
|
+
.clickly-toolbar.is-closing {
|
|
1539
|
+
animation: clickly-toolbar-close 200ms cubic-bezier(0.4, 0, 1, 1) both;
|
|
1540
|
+
pointer-events: none;
|
|
2537
1541
|
}
|
|
2538
1542
|
|
|
2539
|
-
@keyframes clickly-
|
|
2540
|
-
from { opacity: 0; transform:
|
|
2541
|
-
to { opacity: 1; transform:
|
|
1543
|
+
@keyframes clickly-toolbar-open {
|
|
1544
|
+
from { opacity: 0; transform: scale(0.82) translateY(10px); }
|
|
1545
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
1546
|
+
}
|
|
1547
|
+
@keyframes clickly-toolbar-close {
|
|
1548
|
+
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
1549
|
+
to { opacity: 0; transform: scale(0.82) translateY(10px); }
|
|
2542
1550
|
}
|
|
2543
1551
|
|
|
2544
1552
|
.clickly-toolbar .grip {
|
|
@@ -2589,6 +1597,14 @@ var REACT_UI_CSS = `
|
|
|
2589
1597
|
.clickly-btn.is-active:hover { background: #0284c7; }
|
|
2590
1598
|
.clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
|
|
2591
1599
|
|
|
1600
|
+
/* Freeze-animations active state \u2014 icy blue */
|
|
1601
|
+
.clickly-btn.is-freeze {
|
|
1602
|
+
background: rgba(99, 179, 237, 0.18);
|
|
1603
|
+
color: #63b3ed;
|
|
1604
|
+
box-shadow: 0 0 0 1px rgba(99,179,237,0.35), 0 2px 8px rgba(99,179,237,0.2);
|
|
1605
|
+
}
|
|
1606
|
+
.clickly-btn.is-freeze:hover { background: rgba(99, 179, 237, 0.26); }
|
|
1607
|
+
|
|
2592
1608
|
.clickly-btn.icon-only {
|
|
2593
1609
|
width: 32px;
|
|
2594
1610
|
padding: 0;
|
|
@@ -2666,6 +1682,91 @@ var REACT_UI_CSS = `
|
|
|
2666
1682
|
color: #94a3b8;
|
|
2667
1683
|
}
|
|
2668
1684
|
|
|
1685
|
+
/* \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 */
|
|
1686
|
+
|
|
1687
|
+
.shortcuts-trigger {
|
|
1688
|
+
position: relative;
|
|
1689
|
+
display: inline-flex;
|
|
1690
|
+
align-items: center;
|
|
1691
|
+
justify-content: center;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
.shortcuts-panel {
|
|
1695
|
+
position: absolute;
|
|
1696
|
+
bottom: calc(100% + 12px);
|
|
1697
|
+
left: 50%;
|
|
1698
|
+
transform: translateX(-50%);
|
|
1699
|
+
width: 260px;
|
|
1700
|
+
background: rgba(9, 14, 28, 0.97);
|
|
1701
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
1702
|
+
border-radius: 12px;
|
|
1703
|
+
box-shadow: 0 16px 40px rgba(0,0,0,0.45);
|
|
1704
|
+
padding: 10px;
|
|
1705
|
+
z-index: 10;
|
|
1706
|
+
animation: clickly-fade-in 100ms ease-out;
|
|
1707
|
+
pointer-events: none;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/* Arrow pointing down */
|
|
1711
|
+
.shortcuts-panel::after {
|
|
1712
|
+
content: "";
|
|
1713
|
+
position: absolute;
|
|
1714
|
+
top: 100%;
|
|
1715
|
+
left: 50%;
|
|
1716
|
+
transform: translateX(-50%);
|
|
1717
|
+
border: 6px solid transparent;
|
|
1718
|
+
border-top-color: rgba(9, 14, 28, 0.97);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
.shortcuts-title {
|
|
1722
|
+
font: 600 11px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1723
|
+
color: #64748b;
|
|
1724
|
+
text-transform: uppercase;
|
|
1725
|
+
letter-spacing: 0.06em;
|
|
1726
|
+
padding: 2px 4px 8px;
|
|
1727
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
1728
|
+
margin-bottom: 6px;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
.shortcuts-row {
|
|
1732
|
+
display: flex;
|
|
1733
|
+
align-items: center;
|
|
1734
|
+
justify-content: space-between;
|
|
1735
|
+
padding: 4px 4px;
|
|
1736
|
+
border-radius: 6px;
|
|
1737
|
+
gap: 8px;
|
|
1738
|
+
}
|
|
1739
|
+
.shortcuts-row:hover { background: rgba(255,255,255,0.04); }
|
|
1740
|
+
|
|
1741
|
+
.shortcuts-label {
|
|
1742
|
+
font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1743
|
+
color: #94a3b8;
|
|
1744
|
+
flex: 1;
|
|
1745
|
+
min-width: 0;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.shortcuts-keys {
|
|
1749
|
+
display: flex;
|
|
1750
|
+
gap: 3px;
|
|
1751
|
+
align-items: center;
|
|
1752
|
+
flex-shrink: 0;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
.shortcuts-keys kbd {
|
|
1756
|
+
display: inline-flex;
|
|
1757
|
+
align-items: center;
|
|
1758
|
+
justify-content: center;
|
|
1759
|
+
min-width: 20px;
|
|
1760
|
+
height: 20px;
|
|
1761
|
+
padding: 0 5px;
|
|
1762
|
+
background: rgba(255,255,255,0.08);
|
|
1763
|
+
border: 1px solid rgba(255,255,255,0.10);
|
|
1764
|
+
border-bottom-width: 2px;
|
|
1765
|
+
border-radius: 5px;
|
|
1766
|
+
font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
|
|
1767
|
+
color: #e2e8f0;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
2669
1770
|
/* \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 */
|
|
2670
1771
|
|
|
2671
1772
|
.clickly-popup, .clickly-popover {
|
|
@@ -3417,7 +2518,7 @@ var REACT_UI_CSS = `
|
|
|
3417
2518
|
}
|
|
3418
2519
|
`;
|
|
3419
2520
|
|
|
3420
|
-
//
|
|
2521
|
+
// ../react/src/internal/globalStyles.ts
|
|
3421
2522
|
var GLOBAL_PAGE_CSS = `
|
|
3422
2523
|
body[data-clickly-active] {
|
|
3423
2524
|
-webkit-user-select: none !important;
|
|
@@ -3438,18 +2539,18 @@ body[data-clickly-annotating] {
|
|
|
3438
2539
|
}
|
|
3439
2540
|
`;
|
|
3440
2541
|
|
|
3441
|
-
//
|
|
2542
|
+
// ../react/src/Clickly.tsx
|
|
3442
2543
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
3443
2544
|
function Clickly({ className } = {}) {
|
|
3444
|
-
const [mount, setMount] = (0,
|
|
3445
|
-
(0,
|
|
2545
|
+
const [mount, setMount] = (0, import_react9.useState)(null);
|
|
2546
|
+
(0, import_react9.useEffect)(() => {
|
|
3446
2547
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
3447
2548
|
let shadow = null;
|
|
3448
2549
|
let engine = null;
|
|
3449
2550
|
let overlay = null;
|
|
3450
2551
|
let portal = null;
|
|
3451
2552
|
try {
|
|
3452
|
-
shadow = createShadowHost({ document });
|
|
2553
|
+
shadow = (0, import_core2.createShadowHost)({ document });
|
|
3453
2554
|
portal = document.createElement("div");
|
|
3454
2555
|
portal.setAttribute("data-clickly-react-root", "");
|
|
3455
2556
|
shadow.root.appendChild(portal);
|
|
@@ -3465,8 +2566,8 @@ function Clickly({ className } = {}) {
|
|
|
3465
2566
|
gstyle.textContent = GLOBAL_PAGE_CSS;
|
|
3466
2567
|
document.head.appendChild(gstyle);
|
|
3467
2568
|
}
|
|
3468
|
-
engine = new SelectionEngine({ document, host: shadow.host });
|
|
3469
|
-
overlay = new Overlay({
|
|
2569
|
+
engine = new import_core2.SelectionEngine({ document, host: shadow.host });
|
|
2570
|
+
overlay = new import_core2.Overlay({
|
|
3470
2571
|
engine,
|
|
3471
2572
|
root: shadow.root,
|
|
3472
2573
|
document,
|
|
@@ -3491,7 +2592,7 @@ function Clickly({ className } = {}) {
|
|
|
3491
2592
|
document.body.removeAttribute("data-clickly-mode");
|
|
3492
2593
|
};
|
|
3493
2594
|
}, []);
|
|
3494
|
-
(0,
|
|
2595
|
+
(0, import_react9.useEffect)(() => {
|
|
3495
2596
|
if (!mount || !className) return;
|
|
3496
2597
|
mount.shadow.host.className = className;
|
|
3497
2598
|
return () => {
|