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