@useclickly/react 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2619 -373
- package/dist/index.d.ts +12 -69
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2549 -329
- 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");
|
|
@@ -161,18 +1294,24 @@ function formatOne(a, index, detail) {
|
|
|
161
1294
|
}
|
|
162
1295
|
if (detail === "detailed" || detail === "forensic") {
|
|
163
1296
|
if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
|
|
164
|
-
if (a.nearbyText) lines.push(`**Nearby text:** ${
|
|
1297
|
+
if (a.nearbyText) lines.push(`**Nearby text:** ${truncate2(a.nearbyText, 120)}`);
|
|
165
1298
|
}
|
|
166
1299
|
if (detail === "forensic" && a.computedStyles) {
|
|
167
|
-
lines.push("**Computed styles:**\n
|
|
1300
|
+
lines.push("**Computed styles:**\n```css\n" + a.computedStyles + "\n```");
|
|
168
1301
|
}
|
|
169
1302
|
lines.push(`**Feedback:** ${a.comment}`);
|
|
1303
|
+
if (a.suggestedCss) {
|
|
1304
|
+
lines.push("**Suggested CSS:**\n```css\n" + a.suggestedCss + "\n```");
|
|
1305
|
+
}
|
|
170
1306
|
if (a.severity) lines.push(`**Severity:** ${a.severity}`);
|
|
171
1307
|
return lines.join("\n");
|
|
172
1308
|
}
|
|
173
|
-
function
|
|
1309
|
+
function truncate2(s, n) {
|
|
174
1310
|
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
175
1311
|
}
|
|
1312
|
+
|
|
1313
|
+
// packages/react/src/internal/icons.tsx
|
|
1314
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
176
1315
|
function Icon({
|
|
177
1316
|
children,
|
|
178
1317
|
size = 16
|
|
@@ -199,7 +1338,6 @@ var IconLayers = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
|
199
1338
|
/* @__PURE__ */ jsx("path", { d: "M3 12l9 5 9-5" }),
|
|
200
1339
|
/* @__PURE__ */ jsx("path", { d: "M3 17l9 5 9-5" })
|
|
201
1340
|
] });
|
|
202
|
-
var IconSquare = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "16", height: "16", rx: "2", strokeDasharray: "3 3" }) });
|
|
203
1341
|
var IconCopy = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
204
1342
|
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
|
|
205
1343
|
/* @__PURE__ */ jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
|
|
@@ -220,109 +1358,220 @@ var IconGrip = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
|
220
1358
|
var IconClose = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) });
|
|
221
1359
|
var IconCheck = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12l5 5L20 7" }) });
|
|
222
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";
|
|
223
1365
|
function SettingsPopover({ anchor, width, onClose }) {
|
|
224
|
-
const ref =
|
|
1366
|
+
const ref = useRef2(null);
|
|
225
1367
|
const s = useSettings();
|
|
226
|
-
|
|
1368
|
+
useEffect2(() => {
|
|
227
1369
|
const onDown = (e) => {
|
|
228
|
-
if (ref.current &&
|
|
1370
|
+
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1371
|
+
onClose();
|
|
229
1372
|
};
|
|
230
1373
|
window.addEventListener("pointerdown", onDown, true);
|
|
231
1374
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
232
1375
|
}, [onClose]);
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
/* @__PURE__ */
|
|
238
|
-
/* @__PURE__ */
|
|
239
|
-
/* @__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(
|
|
240
1390
|
"select",
|
|
241
1391
|
{
|
|
242
1392
|
id: "clickly-detail",
|
|
1393
|
+
className: "settings-select",
|
|
243
1394
|
value: s.outputDetail,
|
|
244
1395
|
onChange: (e) => s.set({ outputDetail: e.target.value }),
|
|
245
1396
|
children: [
|
|
246
|
-
/* @__PURE__ */
|
|
247
|
-
/* @__PURE__ */
|
|
248
|
-
/* @__PURE__ */
|
|
249
|
-
/* @__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" })
|
|
250
1401
|
]
|
|
251
1402
|
}
|
|
252
1403
|
)
|
|
253
1404
|
] }),
|
|
254
|
-
/* @__PURE__ */
|
|
255
|
-
|
|
256
|
-
/* @__PURE__ */
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
+
] })
|
|
277
1441
|
] }),
|
|
278
|
-
/* @__PURE__ */
|
|
279
|
-
|
|
280
|
-
/* @__PURE__ */
|
|
281
|
-
"
|
|
282
|
-
{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
] });
|
|
291
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";
|
|
292
1466
|
function AnnotationList({ anchor, width, onClose }) {
|
|
293
1467
|
const items = useAnnotationsList();
|
|
294
1468
|
const remove = useAnnotations((s) => s.remove);
|
|
295
|
-
const
|
|
296
|
-
|
|
1469
|
+
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1470
|
+
const ref = useRef3(null);
|
|
1471
|
+
useEffect3(() => {
|
|
297
1472
|
const onDown = (e) => {
|
|
298
|
-
if (ref.current &&
|
|
1473
|
+
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1474
|
+
onClose();
|
|
299
1475
|
};
|
|
300
1476
|
window.addEventListener("pointerdown", onDown, true);
|
|
301
1477
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
302
1478
|
}, [onClose]);
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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 })
|
|
309
1486
|
] }),
|
|
310
|
-
/* @__PURE__ */
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
] })
|
|
313
1542
|
] }),
|
|
314
|
-
/* @__PURE__ */
|
|
315
|
-
|
|
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
|
+
] });
|
|
316
1566
|
}
|
|
317
|
-
var TOOLBAR_SIZE = { width: 360, height: 40 };
|
|
318
1567
|
function Toolbar({ engine, onCollapse }) {
|
|
319
1568
|
const state = useEngineState(engine);
|
|
320
1569
|
const annotations = useAnnotationsList();
|
|
321
1570
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
322
1571
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
323
|
-
const [showSettings, setShowSettings] =
|
|
324
|
-
const [showList, setShowList] =
|
|
325
|
-
const anchorRef =
|
|
1572
|
+
const [showSettings, setShowSettings] = useState3(false);
|
|
1573
|
+
const [showList, setShowList] = useState3(false);
|
|
1574
|
+
const anchorRef = useRef4(null);
|
|
326
1575
|
const { position, handleProps } = useDraggable(
|
|
327
1576
|
{
|
|
328
1577
|
x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
|
|
@@ -344,11 +1593,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
344
1593
|
} catch {
|
|
345
1594
|
}
|
|
346
1595
|
};
|
|
347
|
-
|
|
348
|
-
if (annotations.length === 0) return;
|
|
349
|
-
if (confirm(`Clear ${annotations.length} annotation(s)?`)) clearAnnotations();
|
|
350
|
-
};
|
|
351
|
-
return /* @__PURE__ */ jsxs(
|
|
1596
|
+
return /* @__PURE__ */ jsxs4(
|
|
352
1597
|
"div",
|
|
353
1598
|
{
|
|
354
1599
|
ref: anchorRef,
|
|
@@ -356,134 +1601,112 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
356
1601
|
style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
|
|
357
1602
|
"aria-label": "Clickly toolbar",
|
|
358
1603
|
children: [
|
|
359
|
-
/* @__PURE__ */
|
|
1604
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Move", children: /* @__PURE__ */ jsx4(
|
|
360
1605
|
"div",
|
|
361
1606
|
{
|
|
362
1607
|
className: "grip",
|
|
363
1608
|
...handleProps,
|
|
364
|
-
title: "Drag to move",
|
|
365
1609
|
role: "separator",
|
|
366
1610
|
"aria-orientation": "vertical",
|
|
367
|
-
children: /* @__PURE__ */
|
|
1611
|
+
children: /* @__PURE__ */ jsx4(IconGrip, {})
|
|
368
1612
|
}
|
|
369
|
-
),
|
|
370
|
-
/* @__PURE__ */
|
|
1613
|
+
) }),
|
|
1614
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Select", shortcut: "1", children: /* @__PURE__ */ jsx4(
|
|
371
1615
|
"button",
|
|
372
1616
|
{
|
|
373
1617
|
className: `clickly-btn icon-only${currentMode === "single" ? " is-active" : ""}`,
|
|
374
1618
|
onClick: () => setMode("single"),
|
|
375
|
-
title: "Single (1)",
|
|
376
1619
|
"aria-label": "Single-element selection mode",
|
|
377
1620
|
"aria-pressed": currentMode === "single",
|
|
378
|
-
children: /* @__PURE__ */
|
|
1621
|
+
children: /* @__PURE__ */ jsx4(IconCursor, {})
|
|
379
1622
|
}
|
|
380
|
-
),
|
|
381
|
-
/* @__PURE__ */
|
|
1623
|
+
) }),
|
|
1624
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Multi-select", shortcut: "2", children: /* @__PURE__ */ jsx4(
|
|
382
1625
|
"button",
|
|
383
1626
|
{
|
|
384
1627
|
className: `clickly-btn icon-only${currentMode === "multi" ? " is-active" : ""}`,
|
|
385
1628
|
onClick: () => setMode("multi"),
|
|
386
|
-
title: "Multi-select (2 / shift-click)",
|
|
387
1629
|
"aria-label": "Multi-element selection mode",
|
|
388
1630
|
"aria-pressed": currentMode === "multi",
|
|
389
|
-
children: /* @__PURE__ */
|
|
390
|
-
}
|
|
391
|
-
),
|
|
392
|
-
/* @__PURE__ */ jsx(
|
|
393
|
-
"button",
|
|
394
|
-
{
|
|
395
|
-
className: `clickly-btn icon-only${currentMode === "area" ? " is-active" : ""}`,
|
|
396
|
-
onClick: () => setMode("area"),
|
|
397
|
-
title: "Area drag (3)",
|
|
398
|
-
"aria-label": "Area drag-selection mode",
|
|
399
|
-
"aria-pressed": currentMode === "area",
|
|
400
|
-
children: /* @__PURE__ */ jsx(IconSquare, {})
|
|
1631
|
+
children: /* @__PURE__ */ jsx4(IconLayers, {})
|
|
401
1632
|
}
|
|
402
|
-
),
|
|
403
|
-
/* @__PURE__ */
|
|
404
|
-
pinnedCount > 0 && /* @__PURE__ */
|
|
405
|
-
/* @__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(
|
|
406
1637
|
"button",
|
|
407
1638
|
{
|
|
408
1639
|
className: "clickly-btn primary-pinned",
|
|
409
1640
|
onClick: () => engine.annotatePinned(),
|
|
410
|
-
title: `Annotate the ${pinnedCount} pinned element(s) (Enter)`,
|
|
411
1641
|
children: [
|
|
412
|
-
/* @__PURE__ */
|
|
413
|
-
|
|
414
|
-
pinnedCount,
|
|
415
|
-
")"
|
|
1642
|
+
/* @__PURE__ */ jsx4(IconCheck, {}),
|
|
1643
|
+
/* @__PURE__ */ jsx4("span", { children: pinnedCount })
|
|
416
1644
|
]
|
|
417
1645
|
}
|
|
418
|
-
),
|
|
419
|
-
/* @__PURE__ */
|
|
1646
|
+
) }),
|
|
1647
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Clear selection", shortcut: "Esc", children: /* @__PURE__ */ jsx4(
|
|
420
1648
|
"button",
|
|
421
1649
|
{
|
|
422
1650
|
className: "clickly-btn icon-only",
|
|
423
1651
|
onClick: () => engine.clearPinned(),
|
|
424
|
-
|
|
425
|
-
children: /* @__PURE__ */ jsx(IconClose, {})
|
|
1652
|
+
children: /* @__PURE__ */ jsx4(IconClose, {})
|
|
426
1653
|
}
|
|
427
|
-
),
|
|
428
|
-
/* @__PURE__ */
|
|
1654
|
+
) }),
|
|
1655
|
+
/* @__PURE__ */ jsx4("div", { className: "divider" })
|
|
429
1656
|
] }),
|
|
430
|
-
/* @__PURE__ */
|
|
1657
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ jsxs4(
|
|
431
1658
|
"button",
|
|
432
1659
|
{
|
|
433
|
-
className: "clickly-btn",
|
|
1660
|
+
className: "clickly-btn icon-only",
|
|
434
1661
|
onClick: () => setShowList((v) => !v),
|
|
435
|
-
|
|
1662
|
+
"aria-label": "Show annotation list",
|
|
436
1663
|
children: [
|
|
437
|
-
/* @__PURE__ */
|
|
438
|
-
annotations.length > 0 && /* @__PURE__ */
|
|
1664
|
+
/* @__PURE__ */ jsx4(IconList, {}),
|
|
1665
|
+
annotations.length > 0 && /* @__PURE__ */ jsx4("span", { className: "clickly-counter", children: annotations.length })
|
|
439
1666
|
]
|
|
440
1667
|
}
|
|
441
|
-
),
|
|
442
|
-
/* @__PURE__ */
|
|
1668
|
+
) }),
|
|
1669
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Copy feedback", shortcut: "C", children: /* @__PURE__ */ jsx4(
|
|
443
1670
|
"button",
|
|
444
1671
|
{
|
|
445
1672
|
className: "clickly-btn icon-only",
|
|
446
1673
|
onClick: onCopy,
|
|
447
|
-
title: "Copy markdown (C)",
|
|
448
1674
|
"aria-label": "Copy all annotations as markdown",
|
|
449
1675
|
disabled: annotations.length === 0,
|
|
450
|
-
children: /* @__PURE__ */
|
|
1676
|
+
children: /* @__PURE__ */ jsx4(IconCopy, {})
|
|
451
1677
|
}
|
|
452
|
-
),
|
|
453
|
-
/* @__PURE__ */
|
|
1678
|
+
) }),
|
|
1679
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Clear all", shortcut: "X", children: /* @__PURE__ */ jsx4(
|
|
454
1680
|
"button",
|
|
455
1681
|
{
|
|
456
1682
|
className: "clickly-btn icon-only",
|
|
457
|
-
onClick:
|
|
458
|
-
title: "Clear (X)",
|
|
1683
|
+
onClick: () => clearAnnotations(),
|
|
459
1684
|
"aria-label": "Clear all annotations",
|
|
460
1685
|
disabled: annotations.length === 0,
|
|
461
|
-
children: /* @__PURE__ */
|
|
1686
|
+
children: /* @__PURE__ */ jsx4(IconTrash, {})
|
|
462
1687
|
}
|
|
463
|
-
),
|
|
464
|
-
/* @__PURE__ */
|
|
465
|
-
/* @__PURE__ */
|
|
1688
|
+
) }),
|
|
1689
|
+
/* @__PURE__ */ jsx4("div", { className: "divider" }),
|
|
1690
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Settings", children: /* @__PURE__ */ jsx4(
|
|
466
1691
|
"button",
|
|
467
1692
|
{
|
|
468
|
-
className:
|
|
1693
|
+
className: `clickly-btn icon-only${showSettings ? " is-active" : ""}`,
|
|
469
1694
|
onClick: () => setShowSettings((v) => !v),
|
|
470
|
-
title: "Settings",
|
|
471
1695
|
"aria-label": "Open settings",
|
|
472
1696
|
"aria-expanded": showSettings,
|
|
473
|
-
children: /* @__PURE__ */
|
|
1697
|
+
children: /* @__PURE__ */ jsx4(IconSettings, {})
|
|
474
1698
|
}
|
|
475
|
-
),
|
|
476
|
-
/* @__PURE__ */
|
|
1699
|
+
) }),
|
|
1700
|
+
/* @__PURE__ */ jsx4(Tip, { label: "Close", shortcut: "Esc", children: /* @__PURE__ */ jsx4(
|
|
477
1701
|
"button",
|
|
478
1702
|
{
|
|
479
1703
|
className: "clickly-btn icon-only",
|
|
480
1704
|
onClick: onCollapse,
|
|
481
|
-
title: "Collapse (Esc)",
|
|
482
1705
|
"aria-label": "Collapse Clickly toolbar",
|
|
483
|
-
children: /* @__PURE__ */
|
|
1706
|
+
children: /* @__PURE__ */ jsx4(IconClose, {})
|
|
484
1707
|
}
|
|
485
|
-
),
|
|
486
|
-
showSettings && /* @__PURE__ */
|
|
1708
|
+
) }),
|
|
1709
|
+
showSettings && /* @__PURE__ */ jsx4(
|
|
487
1710
|
SettingsPopover,
|
|
488
1711
|
{
|
|
489
1712
|
anchor: { x: position.x, y: position.y },
|
|
@@ -491,7 +1714,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
491
1714
|
onClose: () => setShowSettings(false)
|
|
492
1715
|
}
|
|
493
1716
|
),
|
|
494
|
-
showList && /* @__PURE__ */
|
|
1717
|
+
showList && /* @__PURE__ */ jsx4(
|
|
495
1718
|
AnnotationList,
|
|
496
1719
|
{
|
|
497
1720
|
anchor: { x: position.x, y: position.y },
|
|
@@ -503,9 +1726,12 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
503
1726
|
}
|
|
504
1727
|
);
|
|
505
1728
|
}
|
|
1729
|
+
|
|
1730
|
+
// packages/react/src/internal/CollapsedFAB.tsx
|
|
1731
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
506
1732
|
function CollapsedFAB({ onExpand }) {
|
|
507
1733
|
const annotations = useAnnotationsList();
|
|
508
|
-
return /* @__PURE__ */
|
|
1734
|
+
return /* @__PURE__ */ jsxs5(
|
|
509
1735
|
"button",
|
|
510
1736
|
{
|
|
511
1737
|
className: "clickly-fab",
|
|
@@ -513,27 +1739,75 @@ function CollapsedFAB({ onExpand }) {
|
|
|
513
1739
|
title: "Open Clickly (\u2318/Ctrl+Shift+F)",
|
|
514
1740
|
"aria-label": "Open Clickly toolbar",
|
|
515
1741
|
children: [
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
annotations.length > 0 && /* @__PURE__ */
|
|
1742
|
+
/* @__PURE__ */ jsx5(IconCursor, {}),
|
|
1743
|
+
annotations.length > 0 && /* @__PURE__ */ jsx5("span", { className: "clickly-fab-badge", children: annotations.length })
|
|
518
1744
|
]
|
|
519
1745
|
}
|
|
520
1746
|
);
|
|
521
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";
|
|
522
1753
|
var POPUP_W = 320;
|
|
523
|
-
var POPUP_H_EST =
|
|
1754
|
+
var POPUP_H_EST = 240;
|
|
524
1755
|
function AnnotationPopup({ engine }) {
|
|
525
1756
|
const state = useEngineState(engine);
|
|
526
1757
|
const addAnnotation = useAnnotations((s) => s.add);
|
|
527
1758
|
const copyOnAdd = useSettings((s) => s.copyOnAdd);
|
|
528
1759
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
529
|
-
const [comment, setComment] =
|
|
530
|
-
const
|
|
531
|
-
|
|
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(() => {
|
|
532
1769
|
if (state.kind === "annotating") {
|
|
533
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
|
+
}
|
|
534
1782
|
requestAnimationFrame(() => taRef.current?.focus());
|
|
535
1783
|
}
|
|
536
|
-
}, [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
|
+
};
|
|
537
1811
|
const onKey = (e) => {
|
|
538
1812
|
if (e.key === "Escape") {
|
|
539
1813
|
e.preventDefault();
|
|
@@ -544,14 +1818,16 @@ function AnnotationPopup({ engine }) {
|
|
|
544
1818
|
submit();
|
|
545
1819
|
}
|
|
546
1820
|
};
|
|
547
|
-
const cancel = () => engine.commit();
|
|
548
1821
|
const submit = () => {
|
|
549
1822
|
if (state.kind !== "annotating") return;
|
|
550
1823
|
const sel = engine.resolveSelection();
|
|
551
1824
|
if (!sel) return;
|
|
552
1825
|
const elements = sel.kind === "single" ? [sel.element] : sel.elements;
|
|
553
1826
|
if (elements.length === 0) return;
|
|
1827
|
+
const cssDiff = buildCssDiff(styles, editedStyles);
|
|
554
1828
|
const sharedComment = comment.trim() || "(no comment)";
|
|
1829
|
+
revertAll(targetElRef.current, editedStyles);
|
|
1830
|
+
setEditedStyles({});
|
|
555
1831
|
const isMulti = sel.kind !== "single";
|
|
556
1832
|
const showReact = useSettings.getState().showReactComponents;
|
|
557
1833
|
for (const el of elements) {
|
|
@@ -579,7 +1855,8 @@ function AnnotationPopup({ engine }) {
|
|
|
579
1855
|
sourceColumn: md.sourceColumn || void 0,
|
|
580
1856
|
isMultiSelect: isMulti,
|
|
581
1857
|
kind: "feedback",
|
|
582
|
-
status: "pending"
|
|
1858
|
+
status: "pending",
|
|
1859
|
+
suggestedCss: cssDiff || void 0
|
|
583
1860
|
};
|
|
584
1861
|
addAnnotation(annotation);
|
|
585
1862
|
}
|
|
@@ -589,29 +1866,118 @@ function AnnotationPopup({ engine }) {
|
|
|
589
1866
|
}
|
|
590
1867
|
engine.commit();
|
|
591
1868
|
};
|
|
592
|
-
const placement =
|
|
593
|
-
|
|
1869
|
+
const [placement, setPlacement] = useState4(null);
|
|
1870
|
+
const calcPlacement = useCallback2(() => {
|
|
1871
|
+
if (state.kind !== "annotating") {
|
|
1872
|
+
setPlacement(null);
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
594
1875
|
const sel = engine.resolveSelection();
|
|
595
|
-
if (!sel)
|
|
1876
|
+
if (!sel) {
|
|
1877
|
+
setPlacement(null);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
596
1880
|
const rect = anchorRect(sel);
|
|
597
|
-
if (!rect)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (top + POPUP_H_EST > window.innerHeight) {
|
|
601
|
-
top = Math.max(8, rect.top - POPUP_H_EST - 8);
|
|
1881
|
+
if (!rect) {
|
|
1882
|
+
setPlacement(null);
|
|
1883
|
+
return;
|
|
602
1884
|
}
|
|
603
|
-
|
|
604
|
-
|
|
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;
|
|
605
1896
|
}
|
|
606
|
-
|
|
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 });
|
|
607
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]);
|
|
608
1912
|
if (state.kind !== "annotating" || !placement) return null;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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." })
|
|
613
1979
|
] }),
|
|
614
|
-
/* @__PURE__ */
|
|
1980
|
+
/* @__PURE__ */ jsx6(
|
|
615
1981
|
"textarea",
|
|
616
1982
|
{
|
|
617
1983
|
ref: taRef,
|
|
@@ -622,38 +1988,111 @@ function AnnotationPopup({ engine }) {
|
|
|
622
1988
|
onKeyDown: onKey
|
|
623
1989
|
}
|
|
624
1990
|
),
|
|
625
|
-
/* @__PURE__ */
|
|
626
|
-
/* @__PURE__ */
|
|
627
|
-
/* @__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" })
|
|
628
1994
|
] })
|
|
629
1995
|
] });
|
|
630
1996
|
}
|
|
631
|
-
function
|
|
632
|
-
|
|
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);
|
|
633
2017
|
if (entries.length === 0) return void 0;
|
|
634
2018
|
return entries.map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
635
2019
|
}
|
|
636
2020
|
function anchorRect(sel) {
|
|
637
2021
|
if (!sel) return null;
|
|
638
2022
|
if (sel.kind === "single") return sel.element.getBoundingClientRect();
|
|
639
|
-
if (sel.kind === "area")
|
|
640
|
-
return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
|
|
641
|
-
}
|
|
2023
|
+
if (sel.kind === "area") return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
|
|
642
2024
|
if (sel.elements.length === 0) return null;
|
|
643
2025
|
return sel.elements[0].getBoundingClientRect();
|
|
644
2026
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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}"`;
|
|
649
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);
|
|
650
2084
|
if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
|
|
2085
|
+
if (sel.elements.length === 1) return describeElement2(sel.elements[0]);
|
|
651
2086
|
return `${sel.elements.length} element(s)`;
|
|
652
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";
|
|
653
2092
|
function AnnotationPins() {
|
|
654
2093
|
const annotations = useAnnotationsList();
|
|
655
|
-
const [, setVersion] =
|
|
656
|
-
|
|
2094
|
+
const [, setVersion] = useState5(0);
|
|
2095
|
+
useEffect5(() => {
|
|
657
2096
|
let raf = null;
|
|
658
2097
|
const update = () => {
|
|
659
2098
|
if (raf !== null) return;
|
|
@@ -670,40 +2109,151 @@ function AnnotationPins() {
|
|
|
670
2109
|
window.removeEventListener("resize", update);
|
|
671
2110
|
};
|
|
672
2111
|
}, []);
|
|
673
|
-
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";
|
|
674
2155
|
}
|
|
675
2156
|
function Pin({ number, annotation }) {
|
|
676
2157
|
const remove = useAnnotations((s) => s.remove);
|
|
677
|
-
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);
|
|
678
2164
|
const pos = resolvePosition(annotation);
|
|
679
2165
|
if (!pos) return null;
|
|
680
|
-
|
|
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(
|
|
681
2193
|
"div",
|
|
682
2194
|
{
|
|
683
2195
|
className: "clickly-pin",
|
|
684
2196
|
style: { left: pos.x, top: pos.y },
|
|
685
|
-
onMouseEnter: () =>
|
|
2197
|
+
onMouseEnter: () => {
|
|
2198
|
+
if (!editing) setHovered(true);
|
|
2199
|
+
},
|
|
686
2200
|
onMouseLeave: () => setHovered(false),
|
|
2201
|
+
onClick: openEdit,
|
|
687
2202
|
role: "button",
|
|
688
2203
|
tabIndex: 0,
|
|
689
|
-
|
|
2204
|
+
"aria-label": `Annotation ${number}: ${annotation.comment}`,
|
|
690
2205
|
children: [
|
|
691
|
-
/* @__PURE__ */
|
|
692
|
-
hovered && /* @__PURE__ */
|
|
693
|
-
/* @__PURE__ */
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
+
)
|
|
707
2257
|
]
|
|
708
2258
|
}
|
|
709
2259
|
);
|
|
@@ -727,26 +2277,29 @@ function resolvePosition(a) {
|
|
|
727
2277
|
}
|
|
728
2278
|
return null;
|
|
729
2279
|
}
|
|
2280
|
+
|
|
2281
|
+
// packages/react/src/internal/ClicklyRoot.tsx
|
|
2282
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
730
2283
|
function ClicklyRoot({
|
|
731
2284
|
engine,
|
|
732
2285
|
host
|
|
733
2286
|
}) {
|
|
734
|
-
const [expanded, setExpanded] =
|
|
2287
|
+
const [expanded, setExpanded] = useState6(false);
|
|
735
2288
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
736
2289
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
737
2290
|
const markerColor = useSettings((s) => s.markerColor);
|
|
738
2291
|
const engineState = useEngineState(engine);
|
|
739
|
-
|
|
2292
|
+
useEffect6(() => {
|
|
740
2293
|
host.style.setProperty("--clickly-hover", markerColor);
|
|
741
2294
|
}, [host, markerColor]);
|
|
742
|
-
|
|
2295
|
+
useEffect6(() => {
|
|
743
2296
|
if (expanded) {
|
|
744
2297
|
if (engine.getSnapshot().kind === "idle") engine.activate("single");
|
|
745
2298
|
} else {
|
|
746
2299
|
if (engine.getSnapshot().kind !== "idle") engine.deactivate();
|
|
747
2300
|
}
|
|
748
2301
|
}, [expanded, engine]);
|
|
749
|
-
|
|
2302
|
+
useEffect6(() => {
|
|
750
2303
|
const active = engineState.kind !== "idle";
|
|
751
2304
|
if (active) document.body.setAttribute("data-clickly-active", "");
|
|
752
2305
|
else document.body.removeAttribute("data-clickly-active");
|
|
@@ -764,7 +2317,7 @@ function ClicklyRoot({
|
|
|
764
2317
|
document.body.removeAttribute("data-clickly-annotating");
|
|
765
2318
|
};
|
|
766
2319
|
}, [engineState]);
|
|
767
|
-
|
|
2320
|
+
useEffect6(() => {
|
|
768
2321
|
const onKey = (e) => {
|
|
769
2322
|
const tag = e.target?.tagName;
|
|
770
2323
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
@@ -807,16 +2360,16 @@ function ClicklyRoot({
|
|
|
807
2360
|
window.addEventListener("keydown", onKey);
|
|
808
2361
|
return () => window.removeEventListener("keydown", onKey);
|
|
809
2362
|
}, [clearAnnotations, outputDetail, expanded, engine]);
|
|
810
|
-
return /* @__PURE__ */
|
|
811
|
-
/* @__PURE__ */
|
|
812
|
-
expanded ? /* @__PURE__ */
|
|
813
|
-
/* @__PURE__ */
|
|
814
|
-
/* @__PURE__ */
|
|
815
|
-
] }) : /* @__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) })
|
|
816
2369
|
] });
|
|
817
2370
|
}
|
|
818
2371
|
|
|
819
|
-
// src/internal/styles.ts
|
|
2372
|
+
// packages/react/src/internal/styles.ts
|
|
820
2373
|
var REACT_UI_CSS = `
|
|
821
2374
|
.clickly-ui, .clickly-ui * { box-sizing: border-box; }
|
|
822
2375
|
|
|
@@ -874,72 +2427,151 @@ var REACT_UI_CSS = `
|
|
|
874
2427
|
position: fixed;
|
|
875
2428
|
display: flex;
|
|
876
2429
|
align-items: center;
|
|
877
|
-
gap:
|
|
878
|
-
padding:
|
|
879
|
-
|
|
2430
|
+
gap: 2px;
|
|
2431
|
+
padding: 6px;
|
|
2432
|
+
height: 44px;
|
|
2433
|
+
background: rgba(9, 14, 28, 0.97);
|
|
880
2434
|
color: #f8fafc;
|
|
881
|
-
border-radius:
|
|
2435
|
+
border-radius: 16px;
|
|
882
2436
|
font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
883
|
-
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;
|
|
884
2441
|
pointer-events: auto;
|
|
885
2442
|
user-select: none;
|
|
886
2443
|
z-index: 1;
|
|
887
|
-
animation: clickly-fade-in
|
|
2444
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
888
2445
|
}
|
|
889
2446
|
|
|
890
2447
|
@keyframes clickly-fade-in {
|
|
891
|
-
from { opacity: 0; transform: translateY(
|
|
892
|
-
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); }
|
|
893
2450
|
}
|
|
894
2451
|
|
|
895
2452
|
.clickly-toolbar .grip {
|
|
896
2453
|
display: grid;
|
|
897
2454
|
place-items: center;
|
|
898
|
-
width:
|
|
899
|
-
height:
|
|
900
|
-
color: #
|
|
2455
|
+
width: 22px;
|
|
2456
|
+
height: 32px;
|
|
2457
|
+
color: #475569;
|
|
901
2458
|
touch-action: none;
|
|
2459
|
+
cursor: grab;
|
|
2460
|
+
border-radius: 6px;
|
|
2461
|
+
transition: color 120ms ease;
|
|
902
2462
|
}
|
|
2463
|
+
.clickly-toolbar .grip:hover { color: #94a3b8; }
|
|
2464
|
+
.clickly-toolbar .grip:active { cursor: grabbing; }
|
|
903
2465
|
|
|
904
2466
|
.clickly-toolbar .divider {
|
|
905
2467
|
width: 1px;
|
|
906
|
-
height:
|
|
907
|
-
background: rgba(255,255,255,0.
|
|
908
|
-
margin: 0
|
|
2468
|
+
height: 20px;
|
|
2469
|
+
background: rgba(255,255,255,0.08);
|
|
2470
|
+
margin: 0 3px;
|
|
2471
|
+
flex-shrink: 0;
|
|
909
2472
|
}
|
|
910
2473
|
|
|
911
2474
|
.clickly-btn {
|
|
912
2475
|
display: inline-flex;
|
|
913
2476
|
align-items: center;
|
|
2477
|
+
justify-content: center;
|
|
914
2478
|
gap: 4px;
|
|
915
|
-
height:
|
|
2479
|
+
height: 32px;
|
|
916
2480
|
padding: 0 8px;
|
|
917
2481
|
background: transparent;
|
|
918
2482
|
border: none;
|
|
919
|
-
border-radius:
|
|
920
|
-
color: #
|
|
2483
|
+
border-radius: 10px;
|
|
2484
|
+
color: #94a3b8;
|
|
921
2485
|
cursor: pointer;
|
|
922
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);
|
|
923
2496
|
}
|
|
924
|
-
.clickly-btn:hover
|
|
925
|
-
.clickly-btn
|
|
926
|
-
.clickly-btn.is-active { background: #0ea5e9; color: #fff; }
|
|
927
|
-
.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; }
|
|
928
2499
|
|
|
929
2500
|
.clickly-btn.icon-only {
|
|
930
|
-
width:
|
|
2501
|
+
width: 32px;
|
|
931
2502
|
padding: 0;
|
|
932
|
-
justify-content: center;
|
|
933
2503
|
}
|
|
934
2504
|
|
|
935
2505
|
.clickly-btn.primary-pinned {
|
|
936
2506
|
background: #10b981;
|
|
937
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;
|
|
938
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;
|
|
939
2548
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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;
|
|
943
2575
|
}
|
|
944
2576
|
|
|
945
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 */
|
|
@@ -956,17 +2588,159 @@ var REACT_UI_CSS = `
|
|
|
956
2588
|
animation: clickly-fade-in 120ms ease-out;
|
|
957
2589
|
}
|
|
958
2590
|
|
|
959
|
-
.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
|
+
}
|
|
960
2599
|
|
|
961
|
-
|
|
2600
|
+
/* Collapsible header row \u2014 shows element label + CSS toggle */
|
|
2601
|
+
.clickly-popup .popup-header {
|
|
962
2602
|
display: flex;
|
|
963
2603
|
align-items: center;
|
|
964
|
-
|
|
965
|
-
gap: 8px;
|
|
2604
|
+
gap: 5px;
|
|
966
2605
|
margin-bottom: 8px;
|
|
2606
|
+
padding: 4px 2px;
|
|
2607
|
+
border-radius: 4px;
|
|
2608
|
+
cursor: pointer;
|
|
2609
|
+
user-select: none;
|
|
967
2610
|
color: #475569;
|
|
968
2611
|
font-size: 11px;
|
|
969
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;
|
|
970
2744
|
}
|
|
971
2745
|
|
|
972
2746
|
.clickly-popup textarea {
|
|
@@ -1014,121 +2788,560 @@ var REACT_UI_CSS = `
|
|
|
1014
2788
|
cursor: pointer;
|
|
1015
2789
|
}
|
|
1016
2790
|
|
|
2791
|
+
/* Old popover kept for any legacy use */
|
|
1017
2792
|
.clickly-popover { width: 260px; padding: 12px; }
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
.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); }
|
|
1022
2977
|
|
|
1023
2978
|
.clickly-counter {
|
|
1024
|
-
|
|
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;
|
|
1025
2992
|
align-items: center;
|
|
1026
2993
|
justify-content: center;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
padding: 0 5px;
|
|
1030
|
-
border-radius: 9px;
|
|
1031
|
-
background: #f59e0b;
|
|
1032
|
-
color: #1f2937;
|
|
1033
|
-
font-size: 11px;
|
|
1034
|
-
font-weight: 600;
|
|
1035
|
-
margin-left: 4px;
|
|
2994
|
+
pointer-events: none;
|
|
2995
|
+
border: 1.5px solid rgba(9, 14, 28, 0.97);
|
|
1036
2996
|
}
|
|
1037
2997
|
|
|
1038
2998
|
/* \u2500\u2500\u2500 Annotation pins (persistent numbered markers) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1039
2999
|
|
|
1040
3000
|
.clickly-pin {
|
|
1041
3001
|
position: fixed;
|
|
1042
|
-
width:
|
|
1043
|
-
height:
|
|
3002
|
+
width: 24px;
|
|
3003
|
+
height: 24px;
|
|
1044
3004
|
border-radius: 999px;
|
|
1045
|
-
background: #
|
|
1046
|
-
color: #
|
|
1047
|
-
font:
|
|
3005
|
+
background: #10b981;
|
|
3006
|
+
color: #fff;
|
|
3007
|
+
font: 700 11px/24px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1048
3008
|
text-align: center;
|
|
1049
3009
|
cursor: pointer;
|
|
1050
3010
|
pointer-events: auto;
|
|
1051
3011
|
user-select: none;
|
|
1052
|
-
box-shadow: 0 2px
|
|
1053
|
-
z-index:
|
|
1054
|
-
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;
|
|
1055
3019
|
}
|
|
1056
|
-
.clickly-pin:hover { transform: scale(1.12); }
|
|
1057
3020
|
.clickly-pin-num { display: block; }
|
|
1058
3021
|
|
|
1059
|
-
|
|
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 {
|
|
1060
3025
|
position: absolute;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
3026
|
+
right: calc(100% + 10px);
|
|
3027
|
+
top: 50%;
|
|
3028
|
+
transform: translateY(-50%);
|
|
3029
|
+
width: 220px;
|
|
1064
3030
|
padding: 8px 10px;
|
|
1065
|
-
background:
|
|
1066
|
-
color: #
|
|
1067
|
-
border-radius:
|
|
1068
|
-
box-shadow: 0 8px 24px rgba(
|
|
1069
|
-
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;
|
|
1070
3036
|
text-align: left;
|
|
1071
3037
|
cursor: default;
|
|
1072
|
-
|
|
3038
|
+
pointer-events: none;
|
|
3039
|
+
z-index: 11;
|
|
1073
3040
|
animation: clickly-fade-in 100ms ease-out;
|
|
3041
|
+
white-space: normal;
|
|
1074
3042
|
}
|
|
1075
|
-
|
|
1076
|
-
.
|
|
1077
|
-
|
|
3043
|
+
|
|
3044
|
+
.pin-preview-meta {
|
|
3045
|
+
font-size: 10.5px;
|
|
3046
|
+
color: #64748b;
|
|
1078
3047
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
1079
|
-
|
|
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;
|
|
1080
3092
|
color: #475569;
|
|
1081
|
-
word-break: break-all;
|
|
1082
3093
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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;
|
|
1087
3118
|
background: #fff;
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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;
|
|
1091
3161
|
cursor: pointer;
|
|
3162
|
+
border: none;
|
|
3163
|
+
transition: background 100ms;
|
|
1092
3164
|
}
|
|
1093
|
-
|
|
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; }
|
|
1094
3177
|
|
|
1095
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 */
|
|
1096
3179
|
|
|
1097
3180
|
.clickly-list {
|
|
1098
3181
|
position: fixed;
|
|
1099
|
-
max-height: 50vh;
|
|
1100
3182
|
width: 320px;
|
|
1101
|
-
|
|
3183
|
+
max-height: 55vh;
|
|
1102
3184
|
background: #fff;
|
|
1103
|
-
border-radius:
|
|
1104
|
-
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);
|
|
1105
3187
|
color: #0f172a;
|
|
1106
|
-
font:
|
|
3188
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1107
3189
|
pointer-events: auto;
|
|
1108
3190
|
z-index: 2;
|
|
1109
|
-
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;
|
|
1110
3263
|
}
|
|
1111
|
-
|
|
1112
|
-
.
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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;
|
|
1118
3287
|
background: transparent;
|
|
1119
3288
|
border: none;
|
|
3289
|
+
border-radius: 6px;
|
|
1120
3290
|
color: #94a3b8;
|
|
1121
3291
|
cursor: pointer;
|
|
1122
|
-
padding:
|
|
1123
|
-
|
|
1124
|
-
|
|
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;
|
|
1125
3341
|
}
|
|
1126
|
-
.clickly-list button.remove svg { width: 12px; height: 12px; }
|
|
1127
|
-
.clickly-list button.remove:hover { color: #ef4444; }
|
|
1128
|
-
.clickly-list .empty { padding: 20px; text-align: center; color: #94a3b8; }
|
|
1129
3342
|
`;
|
|
1130
3343
|
|
|
1131
|
-
// src/internal/globalStyles.ts
|
|
3344
|
+
// packages/react/src/internal/globalStyles.ts
|
|
1132
3345
|
var GLOBAL_PAGE_CSS = `
|
|
1133
3346
|
body[data-clickly-active] {
|
|
1134
3347
|
-webkit-user-select: none !important;
|
|
@@ -1148,9 +3361,12 @@ body[data-clickly-annotating] {
|
|
|
1148
3361
|
cursor: default !important;
|
|
1149
3362
|
}
|
|
1150
3363
|
`;
|
|
3364
|
+
|
|
3365
|
+
// packages/react/src/Clickly.tsx
|
|
3366
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1151
3367
|
function Clickly({ className } = {}) {
|
|
1152
|
-
const [mount, setMount] =
|
|
1153
|
-
|
|
3368
|
+
const [mount, setMount] = useState7(null);
|
|
3369
|
+
useEffect7(() => {
|
|
1154
3370
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1155
3371
|
let shadow = null;
|
|
1156
3372
|
let engine = null;
|
|
@@ -1199,7 +3415,7 @@ function Clickly({ className } = {}) {
|
|
|
1199
3415
|
document.body.removeAttribute("data-clickly-mode");
|
|
1200
3416
|
};
|
|
1201
3417
|
}, []);
|
|
1202
|
-
|
|
3418
|
+
useEffect7(() => {
|
|
1203
3419
|
if (!mount || !className) return;
|
|
1204
3420
|
mount.shadow.host.className = className;
|
|
1205
3421
|
return () => {
|
|
@@ -1208,11 +3424,15 @@ function Clickly({ className } = {}) {
|
|
|
1208
3424
|
}, [mount, className]);
|
|
1209
3425
|
if (!mount) return null;
|
|
1210
3426
|
return createPortal(
|
|
1211
|
-
/* @__PURE__ */
|
|
3427
|
+
/* @__PURE__ */ jsx9(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
|
|
1212
3428
|
mount.portal
|
|
1213
3429
|
);
|
|
1214
3430
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
3431
|
+
export {
|
|
3432
|
+
Clickly,
|
|
3433
|
+
DEFAULTS as DEFAULT_SETTINGS,
|
|
3434
|
+
annotationsToMarkdown,
|
|
3435
|
+
useAnnotations,
|
|
3436
|
+
useAnnotationsList,
|
|
3437
|
+
useSettings
|
|
3438
|
+
};
|