@useclickly/react 1.0.0 → 1.0.2
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 +2667 -386
- package/dist/index.d.ts +12 -69
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2597 -342
- package/dist/internal/AnnotationList.d.ts +11 -0
- package/dist/internal/AnnotationList.d.ts.map +1 -0
- package/dist/internal/AnnotationPins.d.ts +2 -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,205 @@ 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";
|
|
2155
|
+
}
|
|
2156
|
+
function parseStyles(raw) {
|
|
2157
|
+
if (!raw) return [];
|
|
2158
|
+
return raw.split(";").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
2159
|
+
const colon = s.indexOf(":");
|
|
2160
|
+
if (colon === -1) return null;
|
|
2161
|
+
return [s.slice(0, colon).trim(), s.slice(colon + 1).trim()];
|
|
2162
|
+
}).filter(Boolean);
|
|
674
2163
|
}
|
|
675
2164
|
function Pin({ number, annotation }) {
|
|
676
2165
|
const remove = useAnnotations((s) => s.remove);
|
|
677
|
-
const
|
|
2166
|
+
const update = useAnnotations((s) => s.update);
|
|
2167
|
+
const [hovered, setHovered] = useState5(false);
|
|
2168
|
+
const [editing, setEditing] = useState5(false);
|
|
2169
|
+
const [draft, setDraft] = useState5(annotation.comment);
|
|
2170
|
+
const [showStyles, setShowStyles] = useState5(false);
|
|
2171
|
+
const taRef = useRef6(null);
|
|
2172
|
+
const editRef = useRef6(null);
|
|
2173
|
+
const styleEntries = parseStyles(annotation.computedStyles);
|
|
2174
|
+
const label = pinLabel(annotation);
|
|
2175
|
+
const headerLabel = annotation.isMultiSelect ? `${label} (multi-select)` : `${label}: "${annotation.elementPath}"`;
|
|
2176
|
+
const openEdit = () => {
|
|
2177
|
+
setDraft(annotation.comment);
|
|
2178
|
+
setEditing(true);
|
|
2179
|
+
setHovered(false);
|
|
2180
|
+
requestAnimationFrame(() => taRef.current?.focus());
|
|
2181
|
+
};
|
|
2182
|
+
const closeEdit = () => {
|
|
2183
|
+
setEditing(false);
|
|
2184
|
+
setShowStyles(false);
|
|
2185
|
+
};
|
|
2186
|
+
const save = () => {
|
|
2187
|
+
const trimmed = draft.trim();
|
|
2188
|
+
if (trimmed) update(annotation.id, { comment: trimmed });
|
|
2189
|
+
closeEdit();
|
|
2190
|
+
};
|
|
2191
|
+
const onKeyDown = (e) => {
|
|
2192
|
+
if (e.key === "Escape") closeEdit();
|
|
2193
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
|
|
2194
|
+
};
|
|
2195
|
+
useEffect5(() => {
|
|
2196
|
+
if (!editing) return;
|
|
2197
|
+
const onDown = (e) => {
|
|
2198
|
+
if (editRef.current && e.composedPath().includes(editRef.current)) return;
|
|
2199
|
+
closeEdit();
|
|
2200
|
+
};
|
|
2201
|
+
window.addEventListener("pointerdown", onDown, true);
|
|
2202
|
+
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
2203
|
+
}, [editing]);
|
|
2204
|
+
useEffect5(() => {
|
|
2205
|
+
if (!editing) return;
|
|
2206
|
+
let el = null;
|
|
2207
|
+
if (annotation.elementPath) {
|
|
2208
|
+
try {
|
|
2209
|
+
el = document.querySelector(annotation.elementPath);
|
|
2210
|
+
} catch {
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
if (!el) return;
|
|
2214
|
+
const prevOutline = el.style.outline;
|
|
2215
|
+
const prevOffset = el.style.outlineOffset;
|
|
2216
|
+
el.style.outline = "2px solid #10b981";
|
|
2217
|
+
el.style.outlineOffset = "3px";
|
|
2218
|
+
return () => {
|
|
2219
|
+
el.style.outline = prevOutline;
|
|
2220
|
+
el.style.outlineOffset = prevOffset;
|
|
2221
|
+
};
|
|
2222
|
+
}, [editing, annotation.elementPath]);
|
|
678
2223
|
const pos = resolvePosition(annotation);
|
|
679
2224
|
if (!pos) return null;
|
|
680
|
-
return /* @__PURE__ */
|
|
2225
|
+
return /* @__PURE__ */ jsxs7(
|
|
681
2226
|
"div",
|
|
682
2227
|
{
|
|
683
2228
|
className: "clickly-pin",
|
|
684
2229
|
style: { left: pos.x, top: pos.y },
|
|
685
|
-
onMouseEnter: () =>
|
|
2230
|
+
onMouseEnter: () => {
|
|
2231
|
+
if (!editing) setHovered(true);
|
|
2232
|
+
},
|
|
686
2233
|
onMouseLeave: () => setHovered(false),
|
|
2234
|
+
onClick: openEdit,
|
|
687
2235
|
role: "button",
|
|
688
2236
|
tabIndex: 0,
|
|
689
|
-
|
|
2237
|
+
"aria-label": `Annotation ${number}: ${annotation.comment}`,
|
|
690
2238
|
children: [
|
|
691
|
-
/* @__PURE__ */
|
|
692
|
-
hovered && /* @__PURE__ */
|
|
693
|
-
/* @__PURE__ */
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
2239
|
+
/* @__PURE__ */ jsx7("span", { className: "clickly-pin-num", children: number }),
|
|
2240
|
+
hovered && !editing && /* @__PURE__ */ jsxs7("div", { className: "pin-preview", children: [
|
|
2241
|
+
/* @__PURE__ */ jsxs7("div", { className: "pin-preview-meta", children: [
|
|
2242
|
+
label,
|
|
2243
|
+
": ",
|
|
2244
|
+
annotation.elementPath
|
|
2245
|
+
] }),
|
|
2246
|
+
/* @__PURE__ */ jsx7("div", { className: "pin-preview-comment", children: annotation.comment })
|
|
2247
|
+
] }),
|
|
2248
|
+
editing && /* @__PURE__ */ jsxs7(
|
|
2249
|
+
"div",
|
|
2250
|
+
{
|
|
2251
|
+
ref: editRef,
|
|
2252
|
+
className: "pin-edit",
|
|
2253
|
+
onClick: (e) => e.stopPropagation(),
|
|
2254
|
+
children: [
|
|
2255
|
+
/* @__PURE__ */ jsxs7(
|
|
2256
|
+
"div",
|
|
2257
|
+
{
|
|
2258
|
+
className: "popup-header",
|
|
2259
|
+
role: "button",
|
|
2260
|
+
"aria-expanded": showStyles,
|
|
2261
|
+
onClick: (e) => {
|
|
2262
|
+
e.stopPropagation();
|
|
2263
|
+
setShowStyles((v) => !v);
|
|
2264
|
+
},
|
|
2265
|
+
children: [
|
|
2266
|
+
/* @__PURE__ */ jsx7("span", { className: "popup-chevron", children: showStyles ? "\u25BE" : "\u203A" }),
|
|
2267
|
+
/* @__PURE__ */ jsx7("span", { className: "popup-label", children: headerLabel })
|
|
2268
|
+
]
|
|
2269
|
+
}
|
|
2270
|
+
),
|
|
2271
|
+
showStyles && styleEntries.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "popup-styles", children: [
|
|
2272
|
+
styleEntries.map(([k, v]) => /* @__PURE__ */ jsxs7("div", { className: "style-row", children: [
|
|
2273
|
+
/* @__PURE__ */ jsx7("span", { className: "style-key", children: k }),
|
|
2274
|
+
/* @__PURE__ */ jsx7("span", { className: "style-val", children: v })
|
|
2275
|
+
] }, k)),
|
|
2276
|
+
annotation.suggestedCss && /* @__PURE__ */ jsxs7("div", { className: "style-hint", style: { color: "#7c3aed", borderTopColor: "rgba(124,58,237,0.2)" }, children: [
|
|
2277
|
+
/* @__PURE__ */ jsx7("strong", { children: "Suggested changes:" }),
|
|
2278
|
+
/* @__PURE__ */ jsx7("br", {}),
|
|
2279
|
+
annotation.suggestedCss
|
|
2280
|
+
] })
|
|
2281
|
+
] }),
|
|
2282
|
+
/* @__PURE__ */ jsx7(
|
|
2283
|
+
"textarea",
|
|
2284
|
+
{
|
|
2285
|
+
ref: taRef,
|
|
2286
|
+
value: draft,
|
|
2287
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2288
|
+
onKeyDown,
|
|
2289
|
+
placeholder: "Describe the issue\u2026 (\u2318/Ctrl + Enter to save)",
|
|
2290
|
+
"aria-label": "Annotation comment"
|
|
2291
|
+
}
|
|
2292
|
+
),
|
|
2293
|
+
/* @__PURE__ */ jsxs7("div", { className: "pin-edit-actions", children: [
|
|
2294
|
+
/* @__PURE__ */ jsx7(
|
|
2295
|
+
"button",
|
|
2296
|
+
{
|
|
2297
|
+
className: "pin-edit-delete",
|
|
2298
|
+
onClick: () => remove(annotation.id),
|
|
2299
|
+
"aria-label": "Delete annotation",
|
|
2300
|
+
children: /* @__PURE__ */ jsx7(IconTrash, {})
|
|
2301
|
+
}
|
|
2302
|
+
),
|
|
2303
|
+
/* @__PURE__ */ jsxs7("div", { className: "row", style: { margin: 0, flex: 1, justifyContent: "flex-end" }, children: [
|
|
2304
|
+
/* @__PURE__ */ jsx7("button", { className: "ghost", onClick: closeEdit, children: "Cancel" }),
|
|
2305
|
+
/* @__PURE__ */ jsx7("button", { className: "primary", onClick: save, children: "Save" })
|
|
2306
|
+
] })
|
|
2307
|
+
] })
|
|
2308
|
+
]
|
|
2309
|
+
}
|
|
2310
|
+
)
|
|
707
2311
|
]
|
|
708
2312
|
}
|
|
709
2313
|
);
|
|
@@ -720,33 +2324,33 @@ function resolvePosition(a) {
|
|
|
720
2324
|
}
|
|
721
2325
|
}
|
|
722
2326
|
if (a.boundingBox) {
|
|
723
|
-
return {
|
|
724
|
-
x: a.boundingBox.x + a.boundingBox.width - 11,
|
|
725
|
-
y: a.boundingBox.y - 11
|
|
726
|
-
};
|
|
2327
|
+
return { x: a.boundingBox.x + a.boundingBox.width - 11, y: a.boundingBox.y - 11 };
|
|
727
2328
|
}
|
|
728
2329
|
return null;
|
|
729
2330
|
}
|
|
2331
|
+
|
|
2332
|
+
// packages/react/src/internal/ClicklyRoot.tsx
|
|
2333
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
730
2334
|
function ClicklyRoot({
|
|
731
2335
|
engine,
|
|
732
2336
|
host
|
|
733
2337
|
}) {
|
|
734
|
-
const [expanded, setExpanded] =
|
|
2338
|
+
const [expanded, setExpanded] = useState6(false);
|
|
735
2339
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
736
2340
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
737
2341
|
const markerColor = useSettings((s) => s.markerColor);
|
|
738
2342
|
const engineState = useEngineState(engine);
|
|
739
|
-
|
|
2343
|
+
useEffect6(() => {
|
|
740
2344
|
host.style.setProperty("--clickly-hover", markerColor);
|
|
741
2345
|
}, [host, markerColor]);
|
|
742
|
-
|
|
2346
|
+
useEffect6(() => {
|
|
743
2347
|
if (expanded) {
|
|
744
2348
|
if (engine.getSnapshot().kind === "idle") engine.activate("single");
|
|
745
2349
|
} else {
|
|
746
2350
|
if (engine.getSnapshot().kind !== "idle") engine.deactivate();
|
|
747
2351
|
}
|
|
748
2352
|
}, [expanded, engine]);
|
|
749
|
-
|
|
2353
|
+
useEffect6(() => {
|
|
750
2354
|
const active = engineState.kind !== "idle";
|
|
751
2355
|
if (active) document.body.setAttribute("data-clickly-active", "");
|
|
752
2356
|
else document.body.removeAttribute("data-clickly-active");
|
|
@@ -764,7 +2368,7 @@ function ClicklyRoot({
|
|
|
764
2368
|
document.body.removeAttribute("data-clickly-annotating");
|
|
765
2369
|
};
|
|
766
2370
|
}, [engineState]);
|
|
767
|
-
|
|
2371
|
+
useEffect6(() => {
|
|
768
2372
|
const onKey = (e) => {
|
|
769
2373
|
const tag = e.target?.tagName;
|
|
770
2374
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
@@ -807,16 +2411,16 @@ function ClicklyRoot({
|
|
|
807
2411
|
window.addEventListener("keydown", onKey);
|
|
808
2412
|
return () => window.removeEventListener("keydown", onKey);
|
|
809
2413
|
}, [clearAnnotations, outputDetail, expanded, engine]);
|
|
810
|
-
return /* @__PURE__ */
|
|
811
|
-
/* @__PURE__ */
|
|
812
|
-
expanded ? /* @__PURE__ */
|
|
813
|
-
/* @__PURE__ */
|
|
814
|
-
/* @__PURE__ */
|
|
815
|
-
] }) : /* @__PURE__ */
|
|
2414
|
+
return /* @__PURE__ */ jsxs8("div", { className: "clickly-ui", children: [
|
|
2415
|
+
/* @__PURE__ */ jsx8(AnnotationPins, {}),
|
|
2416
|
+
expanded ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2417
|
+
/* @__PURE__ */ jsx8(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
|
|
2418
|
+
/* @__PURE__ */ jsx8(AnnotationPopup, { engine })
|
|
2419
|
+
] }) : /* @__PURE__ */ jsx8(CollapsedFAB, { onExpand: () => setExpanded(true) })
|
|
816
2420
|
] });
|
|
817
2421
|
}
|
|
818
2422
|
|
|
819
|
-
// src/internal/styles.ts
|
|
2423
|
+
// packages/react/src/internal/styles.ts
|
|
820
2424
|
var REACT_UI_CSS = `
|
|
821
2425
|
.clickly-ui, .clickly-ui * { box-sizing: border-box; }
|
|
822
2426
|
|
|
@@ -874,72 +2478,151 @@ var REACT_UI_CSS = `
|
|
|
874
2478
|
position: fixed;
|
|
875
2479
|
display: flex;
|
|
876
2480
|
align-items: center;
|
|
877
|
-
gap:
|
|
878
|
-
padding:
|
|
879
|
-
|
|
2481
|
+
gap: 2px;
|
|
2482
|
+
padding: 6px;
|
|
2483
|
+
height: 44px;
|
|
2484
|
+
background: rgba(9, 14, 28, 0.97);
|
|
880
2485
|
color: #f8fafc;
|
|
881
|
-
border-radius:
|
|
2486
|
+
border-radius: 16px;
|
|
882
2487
|
font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
883
|
-
box-shadow:
|
|
2488
|
+
box-shadow:
|
|
2489
|
+
0 16px 40px rgba(0,0,0,0.45),
|
|
2490
|
+
0 0 0 1px rgba(255,255,255,0.08) inset,
|
|
2491
|
+
0 1px 0 rgba(255,255,255,0.06) inset;
|
|
884
2492
|
pointer-events: auto;
|
|
885
2493
|
user-select: none;
|
|
886
2494
|
z-index: 1;
|
|
887
|
-
animation: clickly-fade-in
|
|
2495
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
888
2496
|
}
|
|
889
2497
|
|
|
890
2498
|
@keyframes clickly-fade-in {
|
|
891
|
-
from { opacity: 0; transform: translateY(
|
|
892
|
-
to { opacity: 1; transform: translateY(0); }
|
|
2499
|
+
from { opacity: 0; transform: translateY(6px) scale(0.97); }
|
|
2500
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
893
2501
|
}
|
|
894
2502
|
|
|
895
2503
|
.clickly-toolbar .grip {
|
|
896
2504
|
display: grid;
|
|
897
2505
|
place-items: center;
|
|
898
|
-
width:
|
|
899
|
-
height:
|
|
900
|
-
color: #
|
|
2506
|
+
width: 22px;
|
|
2507
|
+
height: 32px;
|
|
2508
|
+
color: #475569;
|
|
901
2509
|
touch-action: none;
|
|
2510
|
+
cursor: grab;
|
|
2511
|
+
border-radius: 6px;
|
|
2512
|
+
transition: color 120ms ease;
|
|
902
2513
|
}
|
|
2514
|
+
.clickly-toolbar .grip:hover { color: #94a3b8; }
|
|
2515
|
+
.clickly-toolbar .grip:active { cursor: grabbing; }
|
|
903
2516
|
|
|
904
2517
|
.clickly-toolbar .divider {
|
|
905
2518
|
width: 1px;
|
|
906
|
-
height:
|
|
907
|
-
background: rgba(255,255,255,0.
|
|
908
|
-
margin: 0
|
|
2519
|
+
height: 20px;
|
|
2520
|
+
background: rgba(255,255,255,0.08);
|
|
2521
|
+
margin: 0 3px;
|
|
2522
|
+
flex-shrink: 0;
|
|
909
2523
|
}
|
|
910
2524
|
|
|
911
2525
|
.clickly-btn {
|
|
912
2526
|
display: inline-flex;
|
|
913
2527
|
align-items: center;
|
|
2528
|
+
justify-content: center;
|
|
914
2529
|
gap: 4px;
|
|
915
|
-
height:
|
|
2530
|
+
height: 32px;
|
|
916
2531
|
padding: 0 8px;
|
|
917
2532
|
background: transparent;
|
|
918
2533
|
border: none;
|
|
919
|
-
border-radius:
|
|
920
|
-
color: #
|
|
2534
|
+
border-radius: 10px;
|
|
2535
|
+
color: #94a3b8;
|
|
921
2536
|
cursor: pointer;
|
|
922
2537
|
font: inherit;
|
|
2538
|
+
transition: background 100ms ease, color 100ms ease;
|
|
2539
|
+
position: relative;
|
|
2540
|
+
}
|
|
2541
|
+
.clickly-btn:hover { background: rgba(255,255,255,0.09); color: #e2e8f0; }
|
|
2542
|
+
.clickly-btn:active { background: rgba(255,255,255,0.15); color: #fff; transform: scale(0.96); }
|
|
2543
|
+
.clickly-btn.is-active {
|
|
2544
|
+
background: #0ea5e9;
|
|
2545
|
+
color: #fff;
|
|
2546
|
+
box-shadow: 0 0 0 1px rgba(14,165,233,0.4), 0 2px 8px rgba(14,165,233,0.3);
|
|
923
2547
|
}
|
|
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; }
|
|
2548
|
+
.clickly-btn.is-active:hover { background: #0284c7; }
|
|
2549
|
+
.clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
|
|
928
2550
|
|
|
929
2551
|
.clickly-btn.icon-only {
|
|
930
|
-
width:
|
|
2552
|
+
width: 32px;
|
|
931
2553
|
padding: 0;
|
|
932
|
-
justify-content: center;
|
|
933
2554
|
}
|
|
934
2555
|
|
|
935
2556
|
.clickly-btn.primary-pinned {
|
|
936
2557
|
background: #10b981;
|
|
937
2558
|
color: #fff;
|
|
2559
|
+
font-weight: 600;
|
|
2560
|
+
font-size: 12px;
|
|
2561
|
+
padding: 0 10px;
|
|
2562
|
+
gap: 5px;
|
|
2563
|
+
box-shadow: 0 0 0 1px rgba(16,185,129,0.4), 0 2px 8px rgba(16,185,129,0.25);
|
|
2564
|
+
}
|
|
2565
|
+
.clickly-btn.primary-pinned:hover { background: #059669; }
|
|
2566
|
+
|
|
2567
|
+
/* \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 */
|
|
2568
|
+
|
|
2569
|
+
.clickly-tip {
|
|
2570
|
+
position: relative;
|
|
2571
|
+
display: inline-flex;
|
|
2572
|
+
align-items: center;
|
|
2573
|
+
justify-content: center;
|
|
2574
|
+
/* Ensures the absolute-positioned counter badge clips correctly */
|
|
2575
|
+
isolation: isolate;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
.clickly-tip .tip-bubble {
|
|
2579
|
+
position: absolute;
|
|
2580
|
+
bottom: calc(100% + 10px);
|
|
2581
|
+
left: 50%;
|
|
2582
|
+
transform: translateX(-50%);
|
|
2583
|
+
background: rgba(15, 23, 42, 0.98);
|
|
2584
|
+
color: #f1f5f9;
|
|
2585
|
+
font-size: 12px;
|
|
938
2586
|
font-weight: 500;
|
|
2587
|
+
white-space: nowrap;
|
|
2588
|
+
padding: 5px 10px;
|
|
2589
|
+
border-radius: 8px;
|
|
2590
|
+
pointer-events: none;
|
|
2591
|
+
opacity: 0;
|
|
2592
|
+
transition: opacity 80ms ease;
|
|
2593
|
+
transition-delay: 200ms;
|
|
2594
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
|
|
2595
|
+
display: flex;
|
|
2596
|
+
align-items: center;
|
|
2597
|
+
gap: 6px;
|
|
2598
|
+
z-index: 10;
|
|
939
2599
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
2600
|
+
|
|
2601
|
+
/* Arrow */
|
|
2602
|
+
.clickly-tip .tip-bubble::after {
|
|
2603
|
+
content: "";
|
|
2604
|
+
position: absolute;
|
|
2605
|
+
top: 100%;
|
|
2606
|
+
left: 50%;
|
|
2607
|
+
transform: translateX(-50%);
|
|
2608
|
+
border: 5px solid transparent;
|
|
2609
|
+
border-top-color: rgba(15, 23, 42, 0.98);
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
.clickly-tip:hover .tip-bubble { opacity: 1; }
|
|
2613
|
+
|
|
2614
|
+
.clickly-tip .tip-bubble kbd {
|
|
2615
|
+
display: inline-flex;
|
|
2616
|
+
align-items: center;
|
|
2617
|
+
justify-content: center;
|
|
2618
|
+
min-width: 18px;
|
|
2619
|
+
height: 18px;
|
|
2620
|
+
padding: 0 4px;
|
|
2621
|
+
background: rgba(255,255,255,0.12);
|
|
2622
|
+
border: 1px solid rgba(255,255,255,0.10);
|
|
2623
|
+
border-radius: 4px;
|
|
2624
|
+
font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
|
|
2625
|
+
color: #94a3b8;
|
|
943
2626
|
}
|
|
944
2627
|
|
|
945
2628
|
/* \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,20 +2639,181 @@ var REACT_UI_CSS = `
|
|
|
956
2639
|
animation: clickly-fade-in 120ms ease-out;
|
|
957
2640
|
}
|
|
958
2641
|
|
|
959
|
-
.clickly-popup {
|
|
2642
|
+
.clickly-popup {
|
|
2643
|
+
width: 320px;
|
|
2644
|
+
padding: 12px;
|
|
2645
|
+
max-height: calc(100vh - 80px);
|
|
2646
|
+
overflow: hidden;
|
|
2647
|
+
display: flex;
|
|
2648
|
+
flex-direction: column;
|
|
2649
|
+
}
|
|
960
2650
|
|
|
961
|
-
|
|
2651
|
+
/* \u2500\u2500\u2500 Shared inner styles \u2014 apply to both popup and pin-edit \u2500\u2500\u2500 */
|
|
2652
|
+
|
|
2653
|
+
.clickly-popup .popup-header,
|
|
2654
|
+
.pin-edit .popup-header {
|
|
962
2655
|
display: flex;
|
|
963
2656
|
align-items: center;
|
|
964
|
-
|
|
965
|
-
gap: 8px;
|
|
2657
|
+
gap: 5px;
|
|
966
2658
|
margin-bottom: 8px;
|
|
2659
|
+
padding: 4px 2px;
|
|
2660
|
+
border-radius: 4px;
|
|
2661
|
+
cursor: pointer;
|
|
2662
|
+
user-select: none;
|
|
967
2663
|
color: #475569;
|
|
968
2664
|
font-size: 11px;
|
|
969
2665
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
2666
|
+
transition: background 80ms ease;
|
|
2667
|
+
}
|
|
2668
|
+
.clickly-popup .popup-header:hover,
|
|
2669
|
+
.pin-edit .popup-header:hover { background: #f1f5f9; }
|
|
2670
|
+
|
|
2671
|
+
.clickly-popup .popup-chevron,
|
|
2672
|
+
.pin-edit .popup-chevron {
|
|
2673
|
+
font-size: 12px;
|
|
2674
|
+
color: #94a3b8;
|
|
2675
|
+
flex-shrink: 0;
|
|
2676
|
+
width: 12px;
|
|
2677
|
+
text-align: center;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
.clickly-popup .popup-label,
|
|
2681
|
+
.pin-edit .popup-label {
|
|
2682
|
+
flex: 1;
|
|
2683
|
+
overflow: hidden;
|
|
2684
|
+
white-space: nowrap;
|
|
2685
|
+
text-overflow: ellipsis;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
/* Edit-mode toggle button \u2014 top-right of CSS panel header */
|
|
2689
|
+
.clickly-popup .popup-edit-toggle {
|
|
2690
|
+
flex-shrink: 0;
|
|
2691
|
+
display: grid;
|
|
2692
|
+
place-items: center;
|
|
2693
|
+
width: 20px;
|
|
2694
|
+
height: 20px;
|
|
2695
|
+
background: transparent;
|
|
2696
|
+
border: 1px solid #e2e8f0;
|
|
2697
|
+
border-radius: 5px;
|
|
2698
|
+
color: #94a3b8;
|
|
2699
|
+
cursor: pointer;
|
|
2700
|
+
padding: 0;
|
|
2701
|
+
margin-left: auto;
|
|
2702
|
+
transition: background 100ms ease, color 100ms ease, border-color 100ms ease;
|
|
2703
|
+
}
|
|
2704
|
+
.clickly-popup .popup-edit-toggle:hover {
|
|
2705
|
+
background: #f1f5f9;
|
|
2706
|
+
color: #475569;
|
|
2707
|
+
border-color: #cbd5e1;
|
|
2708
|
+
}
|
|
2709
|
+
.clickly-popup .popup-edit-toggle.is-editing {
|
|
2710
|
+
background: #0ea5e9;
|
|
2711
|
+
color: #fff;
|
|
2712
|
+
border-color: #0ea5e9;
|
|
2713
|
+
}
|
|
2714
|
+
.clickly-popup .popup-edit-toggle.is-editing:hover {
|
|
2715
|
+
background: #0284c7;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
/* Computed-styles panel */
|
|
2719
|
+
.clickly-popup .popup-styles,
|
|
2720
|
+
.pin-edit .popup-styles {
|
|
2721
|
+
margin-bottom: 8px;
|
|
2722
|
+
padding: 8px;
|
|
2723
|
+
background: #f8fafc;
|
|
2724
|
+
border: 1px solid #e2e8f0;
|
|
2725
|
+
border-radius: 6px;
|
|
2726
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
2727
|
+
font-size: 11px;
|
|
2728
|
+
max-height: 160px;
|
|
2729
|
+
overflow-y: auto;
|
|
2730
|
+
overscroll-behavior: contain;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
.clickly-popup .style-row,
|
|
2734
|
+
.pin-edit .style-row {
|
|
2735
|
+
display: flex;
|
|
2736
|
+
align-items: center;
|
|
2737
|
+
gap: 6px;
|
|
2738
|
+
line-height: 1.7;
|
|
2739
|
+
border-radius: 4px;
|
|
2740
|
+
padding: 0 2px;
|
|
2741
|
+
transition: background 80ms ease;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
.clickly-popup .style-row--changed,
|
|
2745
|
+
.pin-edit .style-row--changed {
|
|
2746
|
+
background: rgba(245, 158, 11, 0.10);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
.clickly-popup .style-key,
|
|
2750
|
+
.pin-edit .style-key {
|
|
2751
|
+
color: #7c3aed;
|
|
2752
|
+
flex-shrink: 0;
|
|
2753
|
+
min-width: 90px;
|
|
2754
|
+
font-size: 11px;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
.clickly-popup .style-val,
|
|
2758
|
+
.pin-edit .style-val {
|
|
2759
|
+
color: #0f172a;
|
|
2760
|
+
font-size: 11px;
|
|
2761
|
+
word-break: break-all;
|
|
2762
|
+
flex: 1;
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
/* Editable value input \u2014 live CSS preview */
|
|
2766
|
+
.clickly-popup .style-val-input {
|
|
2767
|
+
flex: 1;
|
|
2768
|
+
min-width: 0;
|
|
2769
|
+
background: transparent;
|
|
2770
|
+
border: none;
|
|
2771
|
+
border-bottom: 1px solid transparent;
|
|
2772
|
+
color: #0f172a;
|
|
2773
|
+
font: 11px/1.7 ui-monospace, "SF Mono", Menlo, monospace;
|
|
2774
|
+
padding: 0;
|
|
2775
|
+
outline: none;
|
|
2776
|
+
transition: border-color 100ms ease;
|
|
2777
|
+
word-break: break-all;
|
|
2778
|
+
}
|
|
2779
|
+
.clickly-popup .style-val-input:focus {
|
|
2780
|
+
border-bottom-color: #0ea5e9;
|
|
2781
|
+
background: rgba(14, 165, 233, 0.05);
|
|
2782
|
+
border-radius: 2px 2px 0 0;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
/* Revert button shown only on changed rows */
|
|
2786
|
+
.clickly-popup .style-revert-btn {
|
|
2787
|
+
flex-shrink: 0;
|
|
2788
|
+
background: transparent;
|
|
2789
|
+
border: none;
|
|
2790
|
+
color: #94a3b8;
|
|
2791
|
+
font-size: 13px;
|
|
2792
|
+
cursor: pointer;
|
|
2793
|
+
padding: 0 2px;
|
|
2794
|
+
line-height: 1;
|
|
2795
|
+
border-radius: 3px;
|
|
2796
|
+
transition: color 80ms ease, background 80ms ease;
|
|
2797
|
+
}
|
|
2798
|
+
.clickly-popup .style-revert-btn:hover {
|
|
2799
|
+
color: #f59e0b;
|
|
2800
|
+
background: rgba(245,158,11,0.12);
|
|
970
2801
|
}
|
|
971
2802
|
|
|
972
|
-
|
|
2803
|
+
/* Hint text at bottom of CSS panel */
|
|
2804
|
+
.clickly-popup .style-hint,
|
|
2805
|
+
.pin-edit .style-hint {
|
|
2806
|
+
margin-top: 6px;
|
|
2807
|
+
padding-top: 6px;
|
|
2808
|
+
border-top: 1px solid #e2e8f0;
|
|
2809
|
+
font-size: 10.5px;
|
|
2810
|
+
color: #94a3b8;
|
|
2811
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2812
|
+
line-height: 1.4;
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
.clickly-popup textarea,
|
|
2816
|
+
.pin-edit textarea {
|
|
973
2817
|
width: 100%;
|
|
974
2818
|
min-height: 64px;
|
|
975
2819
|
max-height: 160px;
|
|
@@ -980,33 +2824,40 @@ var REACT_UI_CSS = `
|
|
|
980
2824
|
font: inherit;
|
|
981
2825
|
color: inherit;
|
|
982
2826
|
}
|
|
983
|
-
.clickly-popup textarea:focus
|
|
2827
|
+
.clickly-popup textarea:focus,
|
|
2828
|
+
.pin-edit textarea:focus {
|
|
984
2829
|
outline: none;
|
|
985
2830
|
border-color: #0ea5e9;
|
|
986
2831
|
box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
|
|
987
2832
|
}
|
|
988
2833
|
|
|
989
|
-
.clickly-popup .row
|
|
2834
|
+
.clickly-popup .row,
|
|
2835
|
+
.pin-edit .row {
|
|
990
2836
|
display: flex;
|
|
991
2837
|
justify-content: flex-end;
|
|
992
2838
|
gap: 6px;
|
|
993
2839
|
margin-top: 10px;
|
|
994
2840
|
}
|
|
995
2841
|
|
|
996
|
-
.clickly-popup .row .ghost
|
|
2842
|
+
.clickly-popup .row .ghost,
|
|
2843
|
+
.pin-edit .row .ghost {
|
|
997
2844
|
background: transparent;
|
|
998
2845
|
color: #475569;
|
|
999
2846
|
border: 1px solid #e2e8f0;
|
|
1000
2847
|
}
|
|
1001
|
-
.clickly-popup .row .ghost:hover
|
|
2848
|
+
.clickly-popup .row .ghost:hover,
|
|
2849
|
+
.pin-edit .row .ghost:hover { background: #f8fafc; }
|
|
1002
2850
|
|
|
1003
|
-
.clickly-popup .row .primary
|
|
2851
|
+
.clickly-popup .row .primary,
|
|
2852
|
+
.pin-edit .row .primary {
|
|
1004
2853
|
background: #0ea5e9;
|
|
1005
2854
|
color: #fff;
|
|
1006
2855
|
border: none;
|
|
1007
2856
|
}
|
|
1008
|
-
.clickly-popup .row .primary:hover
|
|
1009
|
-
.
|
|
2857
|
+
.clickly-popup .row .primary:hover,
|
|
2858
|
+
.pin-edit .row .primary:hover { background: #0284c7; }
|
|
2859
|
+
.clickly-popup .row button,
|
|
2860
|
+
.pin-edit .row button {
|
|
1010
2861
|
height: 28px;
|
|
1011
2862
|
padding: 0 12px;
|
|
1012
2863
|
border-radius: 6px;
|
|
@@ -1014,121 +2865,518 @@ var REACT_UI_CSS = `
|
|
|
1014
2865
|
cursor: pointer;
|
|
1015
2866
|
}
|
|
1016
2867
|
|
|
2868
|
+
/* Old popover kept for any legacy use */
|
|
1017
2869
|
.clickly-popover { width: 260px; padding: 12px; }
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
.clickly-
|
|
2870
|
+
|
|
2871
|
+
/* \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 */
|
|
2872
|
+
|
|
2873
|
+
.clickly-settings {
|
|
2874
|
+
position: fixed;
|
|
2875
|
+
width: 284px;
|
|
2876
|
+
background: #fff;
|
|
2877
|
+
border-radius: 14px;
|
|
2878
|
+
box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
|
|
2879
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2880
|
+
color: #0f172a;
|
|
2881
|
+
pointer-events: auto;
|
|
2882
|
+
z-index: 2;
|
|
2883
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2884
|
+
overflow: hidden;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
.settings-header {
|
|
2888
|
+
display: flex;
|
|
2889
|
+
align-items: center;
|
|
2890
|
+
justify-content: space-between;
|
|
2891
|
+
padding: 14px 14px 12px;
|
|
2892
|
+
border-bottom: 1px solid #f1f5f9;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
.settings-title {
|
|
2896
|
+
font-size: 13px;
|
|
2897
|
+
font-weight: 600;
|
|
2898
|
+
color: #0f172a;
|
|
2899
|
+
letter-spacing: -0.01em;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
.settings-close {
|
|
2903
|
+
display: grid;
|
|
2904
|
+
place-items: center;
|
|
2905
|
+
width: 24px;
|
|
2906
|
+
height: 24px;
|
|
2907
|
+
background: #f1f5f9;
|
|
2908
|
+
border: none;
|
|
2909
|
+
border-radius: 6px;
|
|
2910
|
+
color: #64748b;
|
|
2911
|
+
cursor: pointer;
|
|
2912
|
+
padding: 0;
|
|
2913
|
+
transition: background 100ms, color 100ms;
|
|
2914
|
+
}
|
|
2915
|
+
.settings-close:hover { background: #e2e8f0; color: #0f172a; }
|
|
2916
|
+
.settings-close svg { width: 13px; height: 13px; }
|
|
2917
|
+
|
|
2918
|
+
.settings-section { padding: 10px 14px; }
|
|
2919
|
+
|
|
2920
|
+
.settings-divider {
|
|
2921
|
+
height: 1px;
|
|
2922
|
+
background: #f1f5f9;
|
|
2923
|
+
margin: 0;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
.settings-label {
|
|
2927
|
+
display: flex;
|
|
2928
|
+
flex-direction: column;
|
|
2929
|
+
gap: 2px;
|
|
2930
|
+
font-size: 13px;
|
|
2931
|
+
font-weight: 500;
|
|
2932
|
+
color: #1e293b;
|
|
2933
|
+
margin-bottom: 8px;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
.settings-hint {
|
|
2937
|
+
font-size: 11.5px;
|
|
2938
|
+
font-weight: 400;
|
|
2939
|
+
color: #94a3b8;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
.settings-select {
|
|
2943
|
+
width: 100%;
|
|
2944
|
+
padding: 7px 10px;
|
|
2945
|
+
background: #f8fafc;
|
|
2946
|
+
border: 1px solid #e2e8f0;
|
|
2947
|
+
border-radius: 8px;
|
|
2948
|
+
font: inherit;
|
|
2949
|
+
font-size: 13px;
|
|
2950
|
+
color: #1e293b;
|
|
2951
|
+
cursor: pointer;
|
|
2952
|
+
appearance: none;
|
|
2953
|
+
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");
|
|
2954
|
+
background-repeat: no-repeat;
|
|
2955
|
+
background-position: right 10px center;
|
|
2956
|
+
padding-right: 32px;
|
|
2957
|
+
transition: border-color 120ms, box-shadow 120ms;
|
|
2958
|
+
}
|
|
2959
|
+
.settings-select:focus {
|
|
2960
|
+
outline: none;
|
|
2961
|
+
border-color: #0ea5e9;
|
|
2962
|
+
box-shadow: 0 0 0 3px rgba(14,165,233,0.15);
|
|
2963
|
+
background-color: #fff;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
.settings-row {
|
|
2967
|
+
display: flex;
|
|
2968
|
+
align-items: center;
|
|
2969
|
+
justify-content: space-between;
|
|
2970
|
+
gap: 12px;
|
|
2971
|
+
padding: 6px 0;
|
|
2972
|
+
}
|
|
2973
|
+
.settings-row + .settings-row {
|
|
2974
|
+
border-top: 1px solid #f8fafc;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
.settings-row-label {
|
|
2978
|
+
display: flex;
|
|
2979
|
+
flex-direction: column;
|
|
2980
|
+
gap: 2px;
|
|
2981
|
+
font-size: 13px;
|
|
2982
|
+
font-weight: 500;
|
|
2983
|
+
color: #1e293b;
|
|
2984
|
+
min-width: 0;
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
/* Toggle switch */
|
|
2988
|
+
.clickly-toggle {
|
|
2989
|
+
position: relative;
|
|
2990
|
+
flex-shrink: 0;
|
|
2991
|
+
width: 38px;
|
|
2992
|
+
height: 22px;
|
|
2993
|
+
cursor: pointer;
|
|
2994
|
+
}
|
|
2995
|
+
.clickly-toggle input {
|
|
2996
|
+
position: absolute;
|
|
2997
|
+
opacity: 0;
|
|
2998
|
+
width: 0;
|
|
2999
|
+
height: 0;
|
|
3000
|
+
}
|
|
3001
|
+
.toggle-track {
|
|
3002
|
+
position: absolute;
|
|
3003
|
+
inset: 0;
|
|
3004
|
+
background: #e2e8f0;
|
|
3005
|
+
border-radius: 11px;
|
|
3006
|
+
transition: background 180ms ease;
|
|
3007
|
+
}
|
|
3008
|
+
.toggle-track::after {
|
|
3009
|
+
content: "";
|
|
3010
|
+
position: absolute;
|
|
3011
|
+
top: 3px;
|
|
3012
|
+
left: 3px;
|
|
3013
|
+
width: 16px;
|
|
3014
|
+
height: 16px;
|
|
3015
|
+
background: #fff;
|
|
3016
|
+
border-radius: 50%;
|
|
3017
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
|
|
3018
|
+
transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
3019
|
+
}
|
|
3020
|
+
.clickly-toggle input:checked + .toggle-track {
|
|
3021
|
+
background: #0ea5e9;
|
|
3022
|
+
}
|
|
3023
|
+
.clickly-toggle input:checked + .toggle-track::after {
|
|
3024
|
+
transform: translateX(16px);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
/* Color picker */
|
|
3028
|
+
.settings-color-wrap {
|
|
3029
|
+
display: flex;
|
|
3030
|
+
align-items: center;
|
|
3031
|
+
gap: 0;
|
|
3032
|
+
cursor: pointer;
|
|
3033
|
+
border-radius: 8px;
|
|
3034
|
+
overflow: hidden;
|
|
3035
|
+
border: 1px solid #e2e8f0;
|
|
3036
|
+
flex-shrink: 0;
|
|
3037
|
+
}
|
|
3038
|
+
.settings-color-wrap input[type="color"] {
|
|
3039
|
+
position: absolute;
|
|
3040
|
+
opacity: 0;
|
|
3041
|
+
width: 0;
|
|
3042
|
+
height: 0;
|
|
3043
|
+
pointer-events: none;
|
|
3044
|
+
}
|
|
3045
|
+
.color-swatch {
|
|
3046
|
+
display: block;
|
|
3047
|
+
width: 32px;
|
|
3048
|
+
height: 24px;
|
|
3049
|
+
border-radius: 7px;
|
|
3050
|
+
border: 1px solid rgba(0,0,0,0.08);
|
|
3051
|
+
transition: transform 100ms ease;
|
|
3052
|
+
}
|
|
3053
|
+
.settings-color-wrap:hover .color-swatch { transform: scale(1.08); }
|
|
1022
3054
|
|
|
1023
3055
|
.clickly-counter {
|
|
1024
|
-
|
|
3056
|
+
/* Float as a badge \u2014 positioned absolutely so it doesn't widen the button */
|
|
3057
|
+
position: absolute;
|
|
3058
|
+
top: -5px;
|
|
3059
|
+
right: -5px;
|
|
3060
|
+
min-width: 16px;
|
|
3061
|
+
height: 16px;
|
|
3062
|
+
padding: 0 4px;
|
|
3063
|
+
border-radius: 8px;
|
|
3064
|
+
background: #f59e0b;
|
|
3065
|
+
color: #0f172a;
|
|
3066
|
+
font-size: 10px;
|
|
3067
|
+
font-weight: 700;
|
|
3068
|
+
display: flex;
|
|
1025
3069
|
align-items: center;
|
|
1026
3070
|
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;
|
|
3071
|
+
pointer-events: none;
|
|
3072
|
+
border: 1.5px solid rgba(9, 14, 28, 0.97);
|
|
1036
3073
|
}
|
|
1037
3074
|
|
|
1038
3075
|
/* \u2500\u2500\u2500 Annotation pins (persistent numbered markers) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1039
3076
|
|
|
1040
3077
|
.clickly-pin {
|
|
1041
3078
|
position: fixed;
|
|
1042
|
-
width:
|
|
1043
|
-
height:
|
|
3079
|
+
width: 24px;
|
|
3080
|
+
height: 24px;
|
|
1044
3081
|
border-radius: 999px;
|
|
1045
|
-
background: #
|
|
1046
|
-
color: #
|
|
1047
|
-
font:
|
|
3082
|
+
background: #10b981;
|
|
3083
|
+
color: #fff;
|
|
3084
|
+
font: 700 11px/24px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1048
3085
|
text-align: center;
|
|
1049
3086
|
cursor: pointer;
|
|
1050
3087
|
pointer-events: auto;
|
|
1051
3088
|
user-select: none;
|
|
1052
|
-
box-shadow: 0 2px
|
|
1053
|
-
z-index:
|
|
1054
|
-
transition: transform
|
|
3089
|
+
box-shadow: 0 2px 8px rgba(16,185,129,0.4), 0 0 0 2px #fff;
|
|
3090
|
+
z-index: 10;
|
|
3091
|
+
transition: transform 120ms cubic-bezier(0.34,1.56,0.64,1), box-shadow 120ms ease;
|
|
3092
|
+
}
|
|
3093
|
+
.clickly-pin:hover {
|
|
3094
|
+
transform: scale(1.18);
|
|
3095
|
+
box-shadow: 0 4px 16px rgba(16,185,129,0.5), 0 0 0 2px #fff;
|
|
1055
3096
|
}
|
|
1056
|
-
.clickly-pin:hover { transform: scale(1.12); }
|
|
1057
3097
|
.clickly-pin-num { display: block; }
|
|
1058
3098
|
|
|
1059
|
-
|
|
3099
|
+
/* \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 */
|
|
3100
|
+
|
|
3101
|
+
.pin-preview {
|
|
1060
3102
|
position: absolute;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
3103
|
+
right: calc(100% + 10px);
|
|
3104
|
+
top: 50%;
|
|
3105
|
+
transform: translateY(-50%);
|
|
3106
|
+
width: 220px;
|
|
1064
3107
|
padding: 8px 10px;
|
|
1065
|
-
background:
|
|
1066
|
-
color: #
|
|
1067
|
-
border-radius:
|
|
1068
|
-
box-shadow: 0 8px 24px rgba(
|
|
1069
|
-
font:
|
|
3108
|
+
background: rgba(9, 14, 28, 0.96);
|
|
3109
|
+
color: #f1f5f9;
|
|
3110
|
+
border-radius: 10px;
|
|
3111
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
|
|
3112
|
+
font: 12px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1070
3113
|
text-align: left;
|
|
1071
3114
|
cursor: default;
|
|
1072
|
-
|
|
3115
|
+
pointer-events: none;
|
|
3116
|
+
z-index: 11;
|
|
1073
3117
|
animation: clickly-fade-in 100ms ease-out;
|
|
3118
|
+
white-space: normal;
|
|
1074
3119
|
}
|
|
1075
|
-
|
|
1076
|
-
.
|
|
1077
|
-
|
|
3120
|
+
|
|
3121
|
+
.pin-preview-meta {
|
|
3122
|
+
font-size: 10.5px;
|
|
3123
|
+
color: #64748b;
|
|
1078
3124
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
3125
|
+
margin-bottom: 4px;
|
|
3126
|
+
overflow: hidden;
|
|
3127
|
+
white-space: nowrap;
|
|
3128
|
+
text-overflow: ellipsis;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
.pin-preview-comment {
|
|
3132
|
+
font-size: 12px;
|
|
3133
|
+
color: #e2e8f0;
|
|
3134
|
+
word-break: break-word;
|
|
3135
|
+
display: -webkit-box;
|
|
3136
|
+
-webkit-line-clamp: 3;
|
|
3137
|
+
-webkit-box-orient: vertical;
|
|
3138
|
+
overflow: hidden;
|
|
1082
3139
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
3140
|
+
|
|
3141
|
+
/* \u2500\u2500\u2500 Pin edit popup \u2014 same white card as .clickly-popup \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
3142
|
+
|
|
3143
|
+
.pin-edit {
|
|
3144
|
+
/* Positioning: floats to the LEFT of the pin */
|
|
3145
|
+
position: absolute;
|
|
3146
|
+
right: calc(100% + 12px);
|
|
3147
|
+
top: 50%;
|
|
3148
|
+
transform: translateY(-50%);
|
|
3149
|
+
/* Appearance: identical to .clickly-popup */
|
|
3150
|
+
width: 300px;
|
|
3151
|
+
padding: 12px;
|
|
1087
3152
|
background: #fff;
|
|
1088
|
-
color: #
|
|
1089
|
-
border-radius:
|
|
1090
|
-
|
|
3153
|
+
color: #0f172a;
|
|
3154
|
+
border-radius: 10px;
|
|
3155
|
+
box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
|
|
3156
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
3157
|
+
text-align: left;
|
|
3158
|
+
cursor: default;
|
|
3159
|
+
z-index: 11;
|
|
3160
|
+
animation: clickly-fade-in 120ms cubic-bezier(0.16,1,0.3,1);
|
|
3161
|
+
/* Allow inner popup-styles to scroll */
|
|
3162
|
+
max-height: 80vh;
|
|
3163
|
+
overflow: hidden;
|
|
3164
|
+
display: flex;
|
|
3165
|
+
flex-direction: column;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
/* Reuse .clickly-popup textarea inside pin-edit */
|
|
3169
|
+
.pin-edit textarea {
|
|
3170
|
+
width: 100%;
|
|
3171
|
+
min-height: 64px;
|
|
3172
|
+
max-height: 120px;
|
|
3173
|
+
padding: 8px;
|
|
3174
|
+
border: 1px solid #e2e8f0;
|
|
3175
|
+
border-radius: 6px;
|
|
3176
|
+
resize: vertical;
|
|
3177
|
+
font: inherit;
|
|
3178
|
+
color: inherit;
|
|
3179
|
+
background: #fff;
|
|
3180
|
+
box-sizing: border-box;
|
|
3181
|
+
}
|
|
3182
|
+
.pin-edit textarea:focus {
|
|
3183
|
+
outline: none;
|
|
3184
|
+
border-color: #0ea5e9;
|
|
3185
|
+
box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
/* Actions row */
|
|
3189
|
+
.pin-edit-actions {
|
|
3190
|
+
display: flex;
|
|
3191
|
+
align-items: center;
|
|
3192
|
+
gap: 6px;
|
|
3193
|
+
margin-top: 10px;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
.pin-edit-delete {
|
|
3197
|
+
display: grid;
|
|
3198
|
+
place-items: center;
|
|
3199
|
+
width: 28px;
|
|
3200
|
+
height: 28px;
|
|
3201
|
+
flex-shrink: 0;
|
|
3202
|
+
background: transparent;
|
|
3203
|
+
border: 1px solid #fee2e2;
|
|
3204
|
+
border-radius: 6px;
|
|
3205
|
+
color: #ef4444;
|
|
1091
3206
|
cursor: pointer;
|
|
3207
|
+
padding: 0;
|
|
3208
|
+
transition: background 100ms, border-color 100ms;
|
|
1092
3209
|
}
|
|
1093
|
-
.
|
|
3210
|
+
.pin-edit-delete:hover { background: #fef2f2; border-color: #fca5a5; }
|
|
3211
|
+
.pin-edit-delete svg { width: 13px; height: 13px; }
|
|
1094
3212
|
|
|
1095
3213
|
/* \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
3214
|
|
|
1097
3215
|
.clickly-list {
|
|
1098
3216
|
position: fixed;
|
|
1099
|
-
max-height: 50vh;
|
|
1100
3217
|
width: 320px;
|
|
1101
|
-
|
|
3218
|
+
max-height: 55vh;
|
|
1102
3219
|
background: #fff;
|
|
1103
|
-
border-radius:
|
|
1104
|
-
box-shadow: 0
|
|
3220
|
+
border-radius: 14px;
|
|
3221
|
+
box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
|
|
1105
3222
|
color: #0f172a;
|
|
1106
|
-
font:
|
|
3223
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1107
3224
|
pointer-events: auto;
|
|
1108
3225
|
z-index: 2;
|
|
1109
|
-
animation: clickly-fade-in
|
|
3226
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
3227
|
+
display: flex;
|
|
3228
|
+
flex-direction: column;
|
|
3229
|
+
overflow: hidden;
|
|
1110
3230
|
}
|
|
1111
|
-
|
|
1112
|
-
.
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
3231
|
+
|
|
3232
|
+
.list-header {
|
|
3233
|
+
display: flex;
|
|
3234
|
+
align-items: center;
|
|
3235
|
+
gap: 8px;
|
|
3236
|
+
padding: 12px 14px 10px;
|
|
3237
|
+
border-bottom: 1px solid #f1f5f9;
|
|
3238
|
+
flex-shrink: 0;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
.list-title {
|
|
3242
|
+
font-size: 13px;
|
|
3243
|
+
font-weight: 600;
|
|
3244
|
+
color: #0f172a;
|
|
3245
|
+
letter-spacing: -0.01em;
|
|
3246
|
+
flex: 1;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
.list-count {
|
|
3250
|
+
min-width: 20px;
|
|
3251
|
+
height: 20px;
|
|
3252
|
+
padding: 0 6px;
|
|
3253
|
+
background: #f1f5f9;
|
|
3254
|
+
color: #475569;
|
|
3255
|
+
font-size: 11px;
|
|
3256
|
+
font-weight: 600;
|
|
3257
|
+
border-radius: 10px;
|
|
3258
|
+
display: grid;
|
|
3259
|
+
place-items: center;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
.list-items {
|
|
3263
|
+
overflow-y: auto;
|
|
3264
|
+
overscroll-behavior: contain;
|
|
3265
|
+
flex: 1;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
.list-empty {
|
|
3269
|
+
padding: 24px;
|
|
3270
|
+
text-align: center;
|
|
3271
|
+
color: #94a3b8;
|
|
3272
|
+
font-size: 12px;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
/* \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 */
|
|
3276
|
+
|
|
3277
|
+
.list-card {
|
|
3278
|
+
padding: 10px 12px;
|
|
3279
|
+
border-bottom: 1px solid #f8fafc;
|
|
3280
|
+
transition: background 80ms ease;
|
|
3281
|
+
}
|
|
3282
|
+
.list-card:last-child { border-bottom: none; }
|
|
3283
|
+
.list-card:hover { background: #fafafa; }
|
|
3284
|
+
|
|
3285
|
+
.list-card-header {
|
|
3286
|
+
display: flex;
|
|
3287
|
+
align-items: center;
|
|
3288
|
+
gap: 6px;
|
|
3289
|
+
margin-bottom: 4px;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
.list-card-num {
|
|
3293
|
+
font-size: 11px;
|
|
3294
|
+
font-weight: 700;
|
|
3295
|
+
color: #94a3b8;
|
|
3296
|
+
flex-shrink: 0;
|
|
3297
|
+
min-width: 20px;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
.list-card-path {
|
|
3301
|
+
flex: 1;
|
|
3302
|
+
min-width: 0;
|
|
3303
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
3304
|
+
font-size: 10.5px;
|
|
3305
|
+
color: #475569;
|
|
3306
|
+
white-space: nowrap;
|
|
3307
|
+
overflow: hidden;
|
|
3308
|
+
text-overflow: ellipsis;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
.list-card-actions {
|
|
3312
|
+
display: flex;
|
|
3313
|
+
gap: 3px;
|
|
3314
|
+
flex-shrink: 0;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
.list-action-btn {
|
|
3318
|
+
display: grid;
|
|
3319
|
+
place-items: center;
|
|
3320
|
+
width: 24px;
|
|
3321
|
+
height: 24px;
|
|
1118
3322
|
background: transparent;
|
|
1119
3323
|
border: none;
|
|
3324
|
+
border-radius: 6px;
|
|
1120
3325
|
color: #94a3b8;
|
|
1121
3326
|
cursor: pointer;
|
|
1122
|
-
padding:
|
|
1123
|
-
|
|
1124
|
-
|
|
3327
|
+
padding: 0;
|
|
3328
|
+
transition: background 80ms ease, color 80ms ease;
|
|
3329
|
+
}
|
|
3330
|
+
.list-action-btn svg { width: 12px; height: 12px; }
|
|
3331
|
+
.list-action-btn:hover { background: #f1f5f9; color: #475569; }
|
|
3332
|
+
.list-action-btn.copied { color: #10b981; }
|
|
3333
|
+
.list-action-btn.list-action-delete:hover { background: #fef2f2; color: #ef4444; }
|
|
3334
|
+
|
|
3335
|
+
.list-card-comment {
|
|
3336
|
+
font-size: 12px;
|
|
3337
|
+
color: #1e293b;
|
|
3338
|
+
line-height: 1.45;
|
|
3339
|
+
margin: 0 0 6px;
|
|
3340
|
+
word-break: break-word;
|
|
3341
|
+
display: -webkit-box;
|
|
3342
|
+
-webkit-line-clamp: 2;
|
|
3343
|
+
-webkit-box-orient: vertical;
|
|
3344
|
+
overflow: hidden;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
/* CSS changes badge */
|
|
3348
|
+
.list-card-css {
|
|
3349
|
+
background: rgba(124, 58, 237, 0.05);
|
|
3350
|
+
border: 1px solid rgba(124, 58, 237, 0.12);
|
|
3351
|
+
border-radius: 6px;
|
|
3352
|
+
padding: 5px 8px;
|
|
3353
|
+
margin-top: 4px;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
.list-card-css-label {
|
|
3357
|
+
display: block;
|
|
3358
|
+
font-size: 10px;
|
|
3359
|
+
font-weight: 600;
|
|
3360
|
+
color: #7c3aed;
|
|
3361
|
+
text-transform: uppercase;
|
|
3362
|
+
letter-spacing: 0.04em;
|
|
3363
|
+
margin-bottom: 3px;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.list-card-css-code {
|
|
3367
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
3368
|
+
font-size: 10px;
|
|
3369
|
+
color: #475569;
|
|
3370
|
+
line-height: 1.5;
|
|
3371
|
+
margin: 0;
|
|
3372
|
+
white-space: pre-wrap;
|
|
3373
|
+
word-break: break-all;
|
|
3374
|
+
max-height: 60px;
|
|
3375
|
+
overflow: hidden;
|
|
1125
3376
|
}
|
|
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
3377
|
`;
|
|
1130
3378
|
|
|
1131
|
-
// src/internal/globalStyles.ts
|
|
3379
|
+
// packages/react/src/internal/globalStyles.ts
|
|
1132
3380
|
var GLOBAL_PAGE_CSS = `
|
|
1133
3381
|
body[data-clickly-active] {
|
|
1134
3382
|
-webkit-user-select: none !important;
|
|
@@ -1148,9 +3396,12 @@ body[data-clickly-annotating] {
|
|
|
1148
3396
|
cursor: default !important;
|
|
1149
3397
|
}
|
|
1150
3398
|
`;
|
|
3399
|
+
|
|
3400
|
+
// packages/react/src/Clickly.tsx
|
|
3401
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1151
3402
|
function Clickly({ className } = {}) {
|
|
1152
|
-
const [mount, setMount] =
|
|
1153
|
-
|
|
3403
|
+
const [mount, setMount] = useState7(null);
|
|
3404
|
+
useEffect7(() => {
|
|
1154
3405
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1155
3406
|
let shadow = null;
|
|
1156
3407
|
let engine = null;
|
|
@@ -1199,7 +3450,7 @@ function Clickly({ className } = {}) {
|
|
|
1199
3450
|
document.body.removeAttribute("data-clickly-mode");
|
|
1200
3451
|
};
|
|
1201
3452
|
}, []);
|
|
1202
|
-
|
|
3453
|
+
useEffect7(() => {
|
|
1203
3454
|
if (!mount || !className) return;
|
|
1204
3455
|
mount.shadow.host.className = className;
|
|
1205
3456
|
return () => {
|
|
@@ -1208,11 +3459,15 @@ function Clickly({ className } = {}) {
|
|
|
1208
3459
|
}, [mount, className]);
|
|
1209
3460
|
if (!mount) return null;
|
|
1210
3461
|
return createPortal(
|
|
1211
|
-
/* @__PURE__ */
|
|
3462
|
+
/* @__PURE__ */ jsx9(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
|
|
1212
3463
|
mount.portal
|
|
1213
3464
|
);
|
|
1214
3465
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
3466
|
+
export {
|
|
3467
|
+
Clickly,
|
|
3468
|
+
DEFAULTS as DEFAULT_SETTINGS,
|
|
3469
|
+
annotationsToMarkdown,
|
|
3470
|
+
useAnnotations,
|
|
3471
|
+
useAnnotationsList,
|
|
3472
|
+
useSettings
|
|
3473
|
+
};
|