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