@useclickly/react 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Clickly.d.ts +25 -0
- package/dist/Clickly.d.ts.map +1 -0
- package/dist/hooks/useDraggable.d.ts +28 -0
- package/dist/hooks/useDraggable.d.ts.map +1 -0
- package/dist/hooks/usePersistedState.d.ts +6 -0
- package/dist/hooks/usePersistedState.d.ts.map +1 -0
- package/dist/index.cjs +2667 -386
- package/dist/index.d.ts +12 -69
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2597 -342
- package/dist/internal/AnnotationList.d.ts +11 -0
- package/dist/internal/AnnotationList.d.ts.map +1 -0
- package/dist/internal/AnnotationPins.d.ts +2 -0
- package/dist/internal/AnnotationPins.d.ts.map +1 -0
- package/dist/internal/AnnotationPopup.d.ts +5 -0
- package/dist/internal/AnnotationPopup.d.ts.map +1 -0
- package/dist/internal/ClicklyRoot.d.ts +15 -0
- package/dist/internal/ClicklyRoot.d.ts.map +1 -0
- package/dist/internal/CollapsedFAB.d.ts +10 -0
- package/dist/internal/CollapsedFAB.d.ts.map +1 -0
- package/dist/internal/SettingsPopover.d.ts +11 -0
- package/dist/internal/SettingsPopover.d.ts.map +1 -0
- package/dist/internal/Toolbar.d.ts +8 -0
- package/dist/internal/Toolbar.d.ts.map +1 -0
- package/dist/internal/globalStyles.d.ts +13 -0
- package/dist/internal/globalStyles.d.ts.map +1 -0
- package/dist/internal/icons.d.ts +12 -0
- package/dist/internal/icons.d.ts.map +1 -0
- package/dist/internal/styles.d.ts +7 -0
- package/dist/internal/styles.d.ts.map +1 -0
- package/dist/output/markdown.d.ts +5 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.test.d.ts +2 -0
- package/dist/output/markdown.test.d.ts.map +1 -0
- package/dist/state/annotations.d.ts +21 -0
- package/dist/state/annotations.d.ts.map +1 -0
- package/dist/state/annotations.test.d.ts +2 -0
- package/dist/state/annotations.test.d.ts.map +1 -0
- package/dist/state/settings.d.ts +14 -0
- package/dist/state/settings.d.ts.map +1 -0
- package/dist/state/settings.test.d.ts +2 -0
- package/dist/state/settings.test.d.ts.map +1 -0
- package/dist/state/useEngineState.d.ts +7 -0
- package/dist/state/useEngineState.d.ts.map +1 -0
- package/dist/test/setup.d.ts +7 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +12 -12
- package/LICENSE +0 -21
package/dist/index.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");
|
|
@@ -163,23 +1335,29 @@ function formatOne(a, index, detail) {
|
|
|
163
1335
|
}
|
|
164
1336
|
if (detail === "detailed" || detail === "forensic") {
|
|
165
1337
|
if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
|
|
166
|
-
if (a.nearbyText) lines.push(`**Nearby text:** ${
|
|
1338
|
+
if (a.nearbyText) lines.push(`**Nearby text:** ${truncate2(a.nearbyText, 120)}`);
|
|
167
1339
|
}
|
|
168
1340
|
if (detail === "forensic" && a.computedStyles) {
|
|
169
|
-
lines.push("**Computed styles:**\n
|
|
1341
|
+
lines.push("**Computed styles:**\n```css\n" + a.computedStyles + "\n```");
|
|
170
1342
|
}
|
|
171
1343
|
lines.push(`**Feedback:** ${a.comment}`);
|
|
1344
|
+
if (a.suggestedCss) {
|
|
1345
|
+
lines.push("**Suggested CSS:**\n```css\n" + a.suggestedCss + "\n```");
|
|
1346
|
+
}
|
|
172
1347
|
if (a.severity) lines.push(`**Severity:** ${a.severity}`);
|
|
173
1348
|
return lines.join("\n");
|
|
174
1349
|
}
|
|
175
|
-
function
|
|
1350
|
+
function truncate2(s, n) {
|
|
176
1351
|
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
177
1352
|
}
|
|
1353
|
+
|
|
1354
|
+
// packages/react/src/internal/icons.tsx
|
|
1355
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
178
1356
|
function Icon({
|
|
179
1357
|
children,
|
|
180
1358
|
size = 16
|
|
181
1359
|
}) {
|
|
182
|
-
return /* @__PURE__ */
|
|
1360
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
183
1361
|
"svg",
|
|
184
1362
|
{
|
|
185
1363
|
width: size,
|
|
@@ -195,136 +1373,246 @@ function Icon({
|
|
|
195
1373
|
}
|
|
196
1374
|
);
|
|
197
1375
|
}
|
|
198
|
-
var IconCursor = () => /* @__PURE__ */
|
|
199
|
-
var IconLayers = () => /* @__PURE__ */
|
|
200
|
-
/* @__PURE__ */
|
|
201
|
-
/* @__PURE__ */
|
|
202
|
-
/* @__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" })
|
|
203
1381
|
] });
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
/* @__PURE__ */
|
|
207
|
-
/* @__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" })
|
|
208
1385
|
] });
|
|
209
|
-
var IconTrash = () => /* @__PURE__ */
|
|
210
|
-
var IconSettings = () => /* @__PURE__ */
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
/* @__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" })
|
|
213
1390
|
] });
|
|
214
|
-
var IconGrip = () => /* @__PURE__ */
|
|
215
|
-
/* @__PURE__ */
|
|
216
|
-
/* @__PURE__ */
|
|
217
|
-
/* @__PURE__ */
|
|
218
|
-
/* @__PURE__ */
|
|
219
|
-
/* @__PURE__ */
|
|
220
|
-
/* @__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" })
|
|
221
1398
|
] });
|
|
222
|
-
var IconClose = () => /* @__PURE__ */
|
|
223
|
-
var IconCheck = () => /* @__PURE__ */
|
|
224
|
-
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");
|
|
225
1406
|
function SettingsPopover({ anchor, width, onClose }) {
|
|
226
|
-
const ref =
|
|
1407
|
+
const ref = (0, import_react5.useRef)(null);
|
|
227
1408
|
const s = useSettings();
|
|
228
|
-
|
|
1409
|
+
(0, import_react5.useEffect)(() => {
|
|
229
1410
|
const onDown = (e) => {
|
|
230
|
-
if (ref.current &&
|
|
1411
|
+
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1412
|
+
onClose();
|
|
231
1413
|
};
|
|
232
1414
|
window.addEventListener("pointerdown", onDown, true);
|
|
233
1415
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
234
1416
|
}, [onClose]);
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
/* @__PURE__ */
|
|
240
|
-
/* @__PURE__ */
|
|
241
|
-
/* @__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)(
|
|
242
1431
|
"select",
|
|
243
1432
|
{
|
|
244
1433
|
id: "clickly-detail",
|
|
1434
|
+
className: "settings-select",
|
|
245
1435
|
value: s.outputDetail,
|
|
246
1436
|
onChange: (e) => s.set({ outputDetail: e.target.value }),
|
|
247
1437
|
children: [
|
|
248
|
-
/* @__PURE__ */
|
|
249
|
-
/* @__PURE__ */
|
|
250
|
-
/* @__PURE__ */
|
|
251
|
-
/* @__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" })
|
|
252
1442
|
]
|
|
253
1443
|
}
|
|
254
1444
|
)
|
|
255
1445
|
] }),
|
|
256
|
-
/* @__PURE__ */
|
|
257
|
-
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
-
"
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
] })
|
|
279
1482
|
] }),
|
|
280
|
-
/* @__PURE__ */
|
|
281
|
-
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
"
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
] }) })
|
|
292
1501
|
] });
|
|
293
1502
|
}
|
|
1503
|
+
|
|
1504
|
+
// packages/react/src/internal/AnnotationList.tsx
|
|
1505
|
+
var import_react6 = require("react");
|
|
1506
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
294
1507
|
function AnnotationList({ anchor, width, onClose }) {
|
|
295
1508
|
const items = useAnnotationsList();
|
|
296
1509
|
const remove = useAnnotations((s) => s.remove);
|
|
297
|
-
const
|
|
298
|
-
|
|
1510
|
+
const outputDetail = useSettings((s) => s.outputDetail);
|
|
1511
|
+
const ref = (0, import_react6.useRef)(null);
|
|
1512
|
+
(0, import_react6.useEffect)(() => {
|
|
299
1513
|
const onDown = (e) => {
|
|
300
|
-
if (ref.current &&
|
|
1514
|
+
if (ref.current && e.composedPath().includes(ref.current)) return;
|
|
1515
|
+
onClose();
|
|
301
1516
|
};
|
|
302
1517
|
window.addEventListener("pointerdown", onDown, true);
|
|
303
1518
|
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
304
1519
|
}, [onClose]);
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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 })
|
|
311
1527
|
] }),
|
|
312
|
-
/* @__PURE__ */
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
] })
|
|
315
1583
|
] }),
|
|
316
|
-
/* @__PURE__ */
|
|
317
|
-
|
|
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
|
+
] });
|
|
318
1607
|
}
|
|
319
|
-
var TOOLBAR_SIZE = { width: 360, height: 40 };
|
|
320
1608
|
function Toolbar({ engine, onCollapse }) {
|
|
321
1609
|
const state = useEngineState(engine);
|
|
322
1610
|
const annotations = useAnnotationsList();
|
|
323
1611
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
324
1612
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
325
|
-
const [showSettings, setShowSettings] =
|
|
326
|
-
const [showList, setShowList] =
|
|
327
|
-
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);
|
|
328
1616
|
const { position, handleProps } = useDraggable(
|
|
329
1617
|
{
|
|
330
1618
|
x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
|
|
@@ -346,11 +1634,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
346
1634
|
} catch {
|
|
347
1635
|
}
|
|
348
1636
|
};
|
|
349
|
-
|
|
350
|
-
if (annotations.length === 0) return;
|
|
351
|
-
if (confirm(`Clear ${annotations.length} annotation(s)?`)) clearAnnotations();
|
|
352
|
-
};
|
|
353
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1637
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
354
1638
|
"div",
|
|
355
1639
|
{
|
|
356
1640
|
ref: anchorRef,
|
|
@@ -358,134 +1642,112 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
358
1642
|
style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
|
|
359
1643
|
"aria-label": "Clickly toolbar",
|
|
360
1644
|
children: [
|
|
361
|
-
/* @__PURE__ */
|
|
1645
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Move", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
362
1646
|
"div",
|
|
363
1647
|
{
|
|
364
1648
|
className: "grip",
|
|
365
1649
|
...handleProps,
|
|
366
|
-
title: "Drag to move",
|
|
367
1650
|
role: "separator",
|
|
368
1651
|
"aria-orientation": "vertical",
|
|
369
|
-
children: /* @__PURE__ */
|
|
1652
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconGrip, {})
|
|
370
1653
|
}
|
|
371
|
-
),
|
|
372
|
-
/* @__PURE__ */
|
|
1654
|
+
) }),
|
|
1655
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Select", shortcut: "1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
373
1656
|
"button",
|
|
374
1657
|
{
|
|
375
1658
|
className: `clickly-btn icon-only${currentMode === "single" ? " is-active" : ""}`,
|
|
376
1659
|
onClick: () => setMode("single"),
|
|
377
|
-
title: "Single (1)",
|
|
378
1660
|
"aria-label": "Single-element selection mode",
|
|
379
1661
|
"aria-pressed": currentMode === "single",
|
|
380
|
-
children: /* @__PURE__ */
|
|
1662
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCursor, {})
|
|
381
1663
|
}
|
|
382
|
-
),
|
|
383
|
-
/* @__PURE__ */
|
|
1664
|
+
) }),
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Multi-select", shortcut: "2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
384
1666
|
"button",
|
|
385
1667
|
{
|
|
386
1668
|
className: `clickly-btn icon-only${currentMode === "multi" ? " is-active" : ""}`,
|
|
387
1669
|
onClick: () => setMode("multi"),
|
|
388
|
-
title: "Multi-select (2 / shift-click)",
|
|
389
1670
|
"aria-label": "Multi-element selection mode",
|
|
390
1671
|
"aria-pressed": currentMode === "multi",
|
|
391
|
-
children: /* @__PURE__ */
|
|
392
|
-
}
|
|
393
|
-
),
|
|
394
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
395
|
-
"button",
|
|
396
|
-
{
|
|
397
|
-
className: `clickly-btn icon-only${currentMode === "area" ? " is-active" : ""}`,
|
|
398
|
-
onClick: () => setMode("area"),
|
|
399
|
-
title: "Area drag (3)",
|
|
400
|
-
"aria-label": "Area drag-selection mode",
|
|
401
|
-
"aria-pressed": currentMode === "area",
|
|
402
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(IconSquare, {})
|
|
1672
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconLayers, {})
|
|
403
1673
|
}
|
|
404
|
-
),
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
pinnedCount > 0 && /* @__PURE__ */
|
|
407
|
-
/* @__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)(
|
|
408
1678
|
"button",
|
|
409
1679
|
{
|
|
410
1680
|
className: "clickly-btn primary-pinned",
|
|
411
1681
|
onClick: () => engine.annotatePinned(),
|
|
412
|
-
title: `Annotate the ${pinnedCount} pinned element(s) (Enter)`,
|
|
413
1682
|
children: [
|
|
414
|
-
/* @__PURE__ */
|
|
415
|
-
|
|
416
|
-
pinnedCount,
|
|
417
|
-
")"
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCheck, {}),
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: pinnedCount })
|
|
418
1685
|
]
|
|
419
1686
|
}
|
|
420
|
-
),
|
|
421
|
-
/* @__PURE__ */
|
|
1687
|
+
) }),
|
|
1688
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Clear selection", shortcut: "Esc", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
422
1689
|
"button",
|
|
423
1690
|
{
|
|
424
1691
|
className: "clickly-btn icon-only",
|
|
425
1692
|
onClick: () => engine.clearPinned(),
|
|
426
|
-
|
|
427
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {})
|
|
1693
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconClose, {})
|
|
428
1694
|
}
|
|
429
|
-
),
|
|
430
|
-
/* @__PURE__ */
|
|
1695
|
+
) }),
|
|
1696
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "divider" })
|
|
431
1697
|
] }),
|
|
432
|
-
/* @__PURE__ */
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
433
1699
|
"button",
|
|
434
1700
|
{
|
|
435
|
-
className: "clickly-btn",
|
|
1701
|
+
className: "clickly-btn icon-only",
|
|
436
1702
|
onClick: () => setShowList((v) => !v),
|
|
437
|
-
|
|
1703
|
+
"aria-label": "Show annotation list",
|
|
438
1704
|
children: [
|
|
439
|
-
/* @__PURE__ */
|
|
440
|
-
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 })
|
|
441
1707
|
]
|
|
442
1708
|
}
|
|
443
|
-
),
|
|
444
|
-
/* @__PURE__ */
|
|
1709
|
+
) }),
|
|
1710
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Copy feedback", shortcut: "C", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
445
1711
|
"button",
|
|
446
1712
|
{
|
|
447
1713
|
className: "clickly-btn icon-only",
|
|
448
1714
|
onClick: onCopy,
|
|
449
|
-
title: "Copy markdown (C)",
|
|
450
1715
|
"aria-label": "Copy all annotations as markdown",
|
|
451
1716
|
disabled: annotations.length === 0,
|
|
452
|
-
children: /* @__PURE__ */
|
|
1717
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconCopy, {})
|
|
453
1718
|
}
|
|
454
|
-
),
|
|
455
|
-
/* @__PURE__ */
|
|
1719
|
+
) }),
|
|
1720
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Clear all", shortcut: "X", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
456
1721
|
"button",
|
|
457
1722
|
{
|
|
458
1723
|
className: "clickly-btn icon-only",
|
|
459
|
-
onClick:
|
|
460
|
-
title: "Clear (X)",
|
|
1724
|
+
onClick: () => clearAnnotations(),
|
|
461
1725
|
"aria-label": "Clear all annotations",
|
|
462
1726
|
disabled: annotations.length === 0,
|
|
463
|
-
children: /* @__PURE__ */
|
|
1727
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconTrash, {})
|
|
464
1728
|
}
|
|
465
|
-
),
|
|
466
|
-
/* @__PURE__ */
|
|
467
|
-
/* @__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)(
|
|
468
1732
|
"button",
|
|
469
1733
|
{
|
|
470
|
-
className:
|
|
1734
|
+
className: `clickly-btn icon-only${showSettings ? " is-active" : ""}`,
|
|
471
1735
|
onClick: () => setShowSettings((v) => !v),
|
|
472
|
-
title: "Settings",
|
|
473
1736
|
"aria-label": "Open settings",
|
|
474
1737
|
"aria-expanded": showSettings,
|
|
475
|
-
children: /* @__PURE__ */
|
|
1738
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconSettings, {})
|
|
476
1739
|
}
|
|
477
|
-
),
|
|
478
|
-
/* @__PURE__ */
|
|
1740
|
+
) }),
|
|
1741
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tip, { label: "Close", shortcut: "Esc", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
479
1742
|
"button",
|
|
480
1743
|
{
|
|
481
1744
|
className: "clickly-btn icon-only",
|
|
482
1745
|
onClick: onCollapse,
|
|
483
|
-
title: "Collapse (Esc)",
|
|
484
1746
|
"aria-label": "Collapse Clickly toolbar",
|
|
485
|
-
children: /* @__PURE__ */
|
|
1747
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconClose, {})
|
|
486
1748
|
}
|
|
487
|
-
),
|
|
488
|
-
showSettings && /* @__PURE__ */
|
|
1749
|
+
) }),
|
|
1750
|
+
showSettings && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
489
1751
|
SettingsPopover,
|
|
490
1752
|
{
|
|
491
1753
|
anchor: { x: position.x, y: position.y },
|
|
@@ -493,7 +1755,7 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
493
1755
|
onClose: () => setShowSettings(false)
|
|
494
1756
|
}
|
|
495
1757
|
),
|
|
496
|
-
showList && /* @__PURE__ */
|
|
1758
|
+
showList && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
497
1759
|
AnnotationList,
|
|
498
1760
|
{
|
|
499
1761
|
anchor: { x: position.x, y: position.y },
|
|
@@ -505,9 +1767,12 @@ function Toolbar({ engine, onCollapse }) {
|
|
|
505
1767
|
}
|
|
506
1768
|
);
|
|
507
1769
|
}
|
|
1770
|
+
|
|
1771
|
+
// packages/react/src/internal/CollapsedFAB.tsx
|
|
1772
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
508
1773
|
function CollapsedFAB({ onExpand }) {
|
|
509
1774
|
const annotations = useAnnotationsList();
|
|
510
|
-
return /* @__PURE__ */
|
|
1775
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
511
1776
|
"button",
|
|
512
1777
|
{
|
|
513
1778
|
className: "clickly-fab",
|
|
@@ -515,27 +1780,75 @@ function CollapsedFAB({ onExpand }) {
|
|
|
515
1780
|
title: "Open Clickly (\u2318/Ctrl+Shift+F)",
|
|
516
1781
|
"aria-label": "Open Clickly toolbar",
|
|
517
1782
|
children: [
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
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 })
|
|
520
1785
|
]
|
|
521
1786
|
}
|
|
522
1787
|
);
|
|
523
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");
|
|
524
1794
|
var POPUP_W = 320;
|
|
525
|
-
var POPUP_H_EST =
|
|
1795
|
+
var POPUP_H_EST = 240;
|
|
526
1796
|
function AnnotationPopup({ engine }) {
|
|
527
1797
|
const state = useEngineState(engine);
|
|
528
1798
|
const addAnnotation = useAnnotations((s) => s.add);
|
|
529
1799
|
const copyOnAdd = useSettings((s) => s.copyOnAdd);
|
|
530
1800
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
531
|
-
const [comment, setComment] =
|
|
532
|
-
const
|
|
533
|
-
|
|
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)(() => {
|
|
534
1810
|
if (state.kind === "annotating") {
|
|
535
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
|
+
}
|
|
536
1823
|
requestAnimationFrame(() => taRef.current?.focus());
|
|
537
1824
|
}
|
|
538
|
-
}, [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
|
+
};
|
|
539
1852
|
const onKey = (e) => {
|
|
540
1853
|
if (e.key === "Escape") {
|
|
541
1854
|
e.preventDefault();
|
|
@@ -546,20 +1859,22 @@ function AnnotationPopup({ engine }) {
|
|
|
546
1859
|
submit();
|
|
547
1860
|
}
|
|
548
1861
|
};
|
|
549
|
-
const cancel = () => engine.commit();
|
|
550
1862
|
const submit = () => {
|
|
551
1863
|
if (state.kind !== "annotating") return;
|
|
552
1864
|
const sel = engine.resolveSelection();
|
|
553
1865
|
if (!sel) return;
|
|
554
1866
|
const elements = sel.kind === "single" ? [sel.element] : sel.elements;
|
|
555
1867
|
if (elements.length === 0) return;
|
|
1868
|
+
const cssDiff = buildCssDiff(styles, editedStyles);
|
|
556
1869
|
const sharedComment = comment.trim() || "(no comment)";
|
|
1870
|
+
revertAll(targetElRef.current, editedStyles);
|
|
1871
|
+
setEditedStyles({});
|
|
557
1872
|
const isMulti = sel.kind !== "single";
|
|
558
1873
|
const showReact = useSettings.getState().showReactComponents;
|
|
559
1874
|
for (const el of elements) {
|
|
560
|
-
const md =
|
|
1875
|
+
const md = collectMetadata(el, { detail: outputDetail, includeReact: showReact });
|
|
561
1876
|
const annotation = {
|
|
562
|
-
id: "ann_" +
|
|
1877
|
+
id: "ann_" + (0, import_nanoid.nanoid)(8),
|
|
563
1878
|
comment: sharedComment,
|
|
564
1879
|
element: md.element,
|
|
565
1880
|
elementPath: md.elementPath,
|
|
@@ -581,7 +1896,8 @@ function AnnotationPopup({ engine }) {
|
|
|
581
1896
|
sourceColumn: md.sourceColumn || void 0,
|
|
582
1897
|
isMultiSelect: isMulti,
|
|
583
1898
|
kind: "feedback",
|
|
584
|
-
status: "pending"
|
|
1899
|
+
status: "pending",
|
|
1900
|
+
suggestedCss: cssDiff || void 0
|
|
585
1901
|
};
|
|
586
1902
|
addAnnotation(annotation);
|
|
587
1903
|
}
|
|
@@ -591,29 +1907,118 @@ function AnnotationPopup({ engine }) {
|
|
|
591
1907
|
}
|
|
592
1908
|
engine.commit();
|
|
593
1909
|
};
|
|
594
|
-
const placement =
|
|
595
|
-
|
|
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
|
+
}
|
|
596
1916
|
const sel = engine.resolveSelection();
|
|
597
|
-
if (!sel)
|
|
1917
|
+
if (!sel) {
|
|
1918
|
+
setPlacement(null);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
598
1921
|
const rect = anchorRect(sel);
|
|
599
|
-
if (!rect)
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (top + POPUP_H_EST > window.innerHeight) {
|
|
603
|
-
top = Math.max(8, rect.top - POPUP_H_EST - 8);
|
|
1922
|
+
if (!rect) {
|
|
1923
|
+
setPlacement(null);
|
|
1924
|
+
return;
|
|
604
1925
|
}
|
|
605
|
-
|
|
606
|
-
|
|
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;
|
|
607
1937
|
}
|
|
608
|
-
|
|
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 });
|
|
609
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]);
|
|
610
1953
|
if (state.kind !== "annotating" || !placement) return null;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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." })
|
|
615
2020
|
] }),
|
|
616
|
-
/* @__PURE__ */
|
|
2021
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
617
2022
|
"textarea",
|
|
618
2023
|
{
|
|
619
2024
|
ref: taRef,
|
|
@@ -624,38 +2029,111 @@ function AnnotationPopup({ engine }) {
|
|
|
624
2029
|
onKeyDown: onKey
|
|
625
2030
|
}
|
|
626
2031
|
),
|
|
627
|
-
/* @__PURE__ */
|
|
628
|
-
/* @__PURE__ */
|
|
629
|
-
/* @__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" })
|
|
630
2035
|
] })
|
|
631
2036
|
] });
|
|
632
2037
|
}
|
|
633
|
-
function
|
|
634
|
-
|
|
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);
|
|
635
2058
|
if (entries.length === 0) return void 0;
|
|
636
2059
|
return entries.map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
637
2060
|
}
|
|
638
2061
|
function anchorRect(sel) {
|
|
639
2062
|
if (!sel) return null;
|
|
640
2063
|
if (sel.kind === "single") return sel.element.getBoundingClientRect();
|
|
641
|
-
if (sel.kind === "area")
|
|
642
|
-
return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
|
|
643
|
-
}
|
|
2064
|
+
if (sel.kind === "area") return new DOMRect(sel.rect.x, sel.rect.y, sel.rect.width, sel.rect.height);
|
|
644
2065
|
if (sel.elements.length === 0) return null;
|
|
645
2066
|
return sel.elements[0].getBoundingClientRect();
|
|
646
2067
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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}"`;
|
|
651
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);
|
|
652
2125
|
if (sel.kind === "area") return `area \xB7 ${sel.elements.length} element(s)`;
|
|
2126
|
+
if (sel.elements.length === 1) return describeElement2(sel.elements[0]);
|
|
653
2127
|
return `${sel.elements.length} element(s)`;
|
|
654
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
// packages/react/src/internal/AnnotationPins.tsx
|
|
2131
|
+
var import_react9 = require("react");
|
|
2132
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
655
2133
|
function AnnotationPins() {
|
|
656
2134
|
const annotations = useAnnotationsList();
|
|
657
|
-
const [, setVersion] =
|
|
658
|
-
|
|
2135
|
+
const [, setVersion] = (0, import_react9.useState)(0);
|
|
2136
|
+
(0, import_react9.useEffect)(() => {
|
|
659
2137
|
let raf = null;
|
|
660
2138
|
const update = () => {
|
|
661
2139
|
if (raf !== null) return;
|
|
@@ -672,40 +2150,205 @@ function AnnotationPins() {
|
|
|
672
2150
|
window.removeEventListener("resize", update);
|
|
673
2151
|
};
|
|
674
2152
|
}, []);
|
|
675
|
-
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";
|
|
2196
|
+
}
|
|
2197
|
+
function parseStyles(raw) {
|
|
2198
|
+
if (!raw) return [];
|
|
2199
|
+
return raw.split(";").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
2200
|
+
const colon = s.indexOf(":");
|
|
2201
|
+
if (colon === -1) return null;
|
|
2202
|
+
return [s.slice(0, colon).trim(), s.slice(colon + 1).trim()];
|
|
2203
|
+
}).filter(Boolean);
|
|
676
2204
|
}
|
|
677
2205
|
function Pin({ number, annotation }) {
|
|
678
2206
|
const remove = useAnnotations((s) => s.remove);
|
|
679
|
-
const
|
|
2207
|
+
const update = useAnnotations((s) => s.update);
|
|
2208
|
+
const [hovered, setHovered] = (0, import_react9.useState)(false);
|
|
2209
|
+
const [editing, setEditing] = (0, import_react9.useState)(false);
|
|
2210
|
+
const [draft, setDraft] = (0, import_react9.useState)(annotation.comment);
|
|
2211
|
+
const [showStyles, setShowStyles] = (0, import_react9.useState)(false);
|
|
2212
|
+
const taRef = (0, import_react9.useRef)(null);
|
|
2213
|
+
const editRef = (0, import_react9.useRef)(null);
|
|
2214
|
+
const styleEntries = parseStyles(annotation.computedStyles);
|
|
2215
|
+
const label = pinLabel(annotation);
|
|
2216
|
+
const headerLabel = annotation.isMultiSelect ? `${label} (multi-select)` : `${label}: "${annotation.elementPath}"`;
|
|
2217
|
+
const openEdit = () => {
|
|
2218
|
+
setDraft(annotation.comment);
|
|
2219
|
+
setEditing(true);
|
|
2220
|
+
setHovered(false);
|
|
2221
|
+
requestAnimationFrame(() => taRef.current?.focus());
|
|
2222
|
+
};
|
|
2223
|
+
const closeEdit = () => {
|
|
2224
|
+
setEditing(false);
|
|
2225
|
+
setShowStyles(false);
|
|
2226
|
+
};
|
|
2227
|
+
const save = () => {
|
|
2228
|
+
const trimmed = draft.trim();
|
|
2229
|
+
if (trimmed) update(annotation.id, { comment: trimmed });
|
|
2230
|
+
closeEdit();
|
|
2231
|
+
};
|
|
2232
|
+
const onKeyDown = (e) => {
|
|
2233
|
+
if (e.key === "Escape") closeEdit();
|
|
2234
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
|
|
2235
|
+
};
|
|
2236
|
+
(0, import_react9.useEffect)(() => {
|
|
2237
|
+
if (!editing) return;
|
|
2238
|
+
const onDown = (e) => {
|
|
2239
|
+
if (editRef.current && e.composedPath().includes(editRef.current)) return;
|
|
2240
|
+
closeEdit();
|
|
2241
|
+
};
|
|
2242
|
+
window.addEventListener("pointerdown", onDown, true);
|
|
2243
|
+
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
2244
|
+
}, [editing]);
|
|
2245
|
+
(0, import_react9.useEffect)(() => {
|
|
2246
|
+
if (!editing) return;
|
|
2247
|
+
let el = null;
|
|
2248
|
+
if (annotation.elementPath) {
|
|
2249
|
+
try {
|
|
2250
|
+
el = document.querySelector(annotation.elementPath);
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
if (!el) return;
|
|
2255
|
+
const prevOutline = el.style.outline;
|
|
2256
|
+
const prevOffset = el.style.outlineOffset;
|
|
2257
|
+
el.style.outline = "2px solid #10b981";
|
|
2258
|
+
el.style.outlineOffset = "3px";
|
|
2259
|
+
return () => {
|
|
2260
|
+
el.style.outline = prevOutline;
|
|
2261
|
+
el.style.outlineOffset = prevOffset;
|
|
2262
|
+
};
|
|
2263
|
+
}, [editing, annotation.elementPath]);
|
|
680
2264
|
const pos = resolvePosition(annotation);
|
|
681
2265
|
if (!pos) return null;
|
|
682
|
-
return /* @__PURE__ */
|
|
2266
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
683
2267
|
"div",
|
|
684
2268
|
{
|
|
685
2269
|
className: "clickly-pin",
|
|
686
2270
|
style: { left: pos.x, top: pos.y },
|
|
687
|
-
onMouseEnter: () =>
|
|
2271
|
+
onMouseEnter: () => {
|
|
2272
|
+
if (!editing) setHovered(true);
|
|
2273
|
+
},
|
|
688
2274
|
onMouseLeave: () => setHovered(false),
|
|
2275
|
+
onClick: openEdit,
|
|
689
2276
|
role: "button",
|
|
690
2277
|
tabIndex: 0,
|
|
691
|
-
|
|
2278
|
+
"aria-label": `Annotation ${number}: ${annotation.comment}`,
|
|
692
2279
|
children: [
|
|
693
|
-
/* @__PURE__ */
|
|
694
|
-
hovered && /* @__PURE__ */
|
|
695
|
-
/* @__PURE__ */
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
2280
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "clickly-pin-num", children: number }),
|
|
2281
|
+
hovered && !editing && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-preview", children: [
|
|
2282
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-preview-meta", children: [
|
|
2283
|
+
label,
|
|
2284
|
+
": ",
|
|
2285
|
+
annotation.elementPath
|
|
2286
|
+
] }),
|
|
2287
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pin-preview-comment", children: annotation.comment })
|
|
2288
|
+
] }),
|
|
2289
|
+
editing && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2290
|
+
"div",
|
|
2291
|
+
{
|
|
2292
|
+
ref: editRef,
|
|
2293
|
+
className: "pin-edit",
|
|
2294
|
+
onClick: (e) => e.stopPropagation(),
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2297
|
+
"div",
|
|
2298
|
+
{
|
|
2299
|
+
className: "popup-header",
|
|
2300
|
+
role: "button",
|
|
2301
|
+
"aria-expanded": showStyles,
|
|
2302
|
+
onClick: (e) => {
|
|
2303
|
+
e.stopPropagation();
|
|
2304
|
+
setShowStyles((v) => !v);
|
|
2305
|
+
},
|
|
2306
|
+
children: [
|
|
2307
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "popup-chevron", children: showStyles ? "\u25BE" : "\u203A" }),
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "popup-label", children: headerLabel })
|
|
2309
|
+
]
|
|
2310
|
+
}
|
|
2311
|
+
),
|
|
2312
|
+
showStyles && styleEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "popup-styles", children: [
|
|
2313
|
+
styleEntries.map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "style-row", children: [
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "style-key", children: k }),
|
|
2315
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "style-val", children: v })
|
|
2316
|
+
] }, k)),
|
|
2317
|
+
annotation.suggestedCss && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "style-hint", style: { color: "#7c3aed", borderTopColor: "rgba(124,58,237,0.2)" }, children: [
|
|
2318
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "Suggested changes:" }),
|
|
2319
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("br", {}),
|
|
2320
|
+
annotation.suggestedCss
|
|
2321
|
+
] })
|
|
2322
|
+
] }),
|
|
2323
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2324
|
+
"textarea",
|
|
2325
|
+
{
|
|
2326
|
+
ref: taRef,
|
|
2327
|
+
value: draft,
|
|
2328
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2329
|
+
onKeyDown,
|
|
2330
|
+
placeholder: "Describe the issue\u2026 (\u2318/Ctrl + Enter to save)",
|
|
2331
|
+
"aria-label": "Annotation comment"
|
|
2332
|
+
}
|
|
2333
|
+
),
|
|
2334
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pin-edit-actions", children: [
|
|
2335
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2336
|
+
"button",
|
|
2337
|
+
{
|
|
2338
|
+
className: "pin-edit-delete",
|
|
2339
|
+
onClick: () => remove(annotation.id),
|
|
2340
|
+
"aria-label": "Delete annotation",
|
|
2341
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconTrash, {})
|
|
2342
|
+
}
|
|
2343
|
+
),
|
|
2344
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "row", style: { margin: 0, flex: 1, justifyContent: "flex-end" }, children: [
|
|
2345
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "ghost", onClick: closeEdit, children: "Cancel" }),
|
|
2346
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "primary", onClick: save, children: "Save" })
|
|
2347
|
+
] })
|
|
2348
|
+
] })
|
|
2349
|
+
]
|
|
2350
|
+
}
|
|
2351
|
+
)
|
|
709
2352
|
]
|
|
710
2353
|
}
|
|
711
2354
|
);
|
|
@@ -722,33 +2365,33 @@ function resolvePosition(a) {
|
|
|
722
2365
|
}
|
|
723
2366
|
}
|
|
724
2367
|
if (a.boundingBox) {
|
|
725
|
-
return {
|
|
726
|
-
x: a.boundingBox.x + a.boundingBox.width - 11,
|
|
727
|
-
y: a.boundingBox.y - 11
|
|
728
|
-
};
|
|
2368
|
+
return { x: a.boundingBox.x + a.boundingBox.width - 11, y: a.boundingBox.y - 11 };
|
|
729
2369
|
}
|
|
730
2370
|
return null;
|
|
731
2371
|
}
|
|
2372
|
+
|
|
2373
|
+
// packages/react/src/internal/ClicklyRoot.tsx
|
|
2374
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
732
2375
|
function ClicklyRoot({
|
|
733
2376
|
engine,
|
|
734
2377
|
host
|
|
735
2378
|
}) {
|
|
736
|
-
const [expanded, setExpanded] =
|
|
2379
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
737
2380
|
const clearAnnotations = useAnnotations((s) => s.clear);
|
|
738
2381
|
const outputDetail = useSettings((s) => s.outputDetail);
|
|
739
2382
|
const markerColor = useSettings((s) => s.markerColor);
|
|
740
2383
|
const engineState = useEngineState(engine);
|
|
741
|
-
|
|
2384
|
+
(0, import_react10.useEffect)(() => {
|
|
742
2385
|
host.style.setProperty("--clickly-hover", markerColor);
|
|
743
2386
|
}, [host, markerColor]);
|
|
744
|
-
|
|
2387
|
+
(0, import_react10.useEffect)(() => {
|
|
745
2388
|
if (expanded) {
|
|
746
2389
|
if (engine.getSnapshot().kind === "idle") engine.activate("single");
|
|
747
2390
|
} else {
|
|
748
2391
|
if (engine.getSnapshot().kind !== "idle") engine.deactivate();
|
|
749
2392
|
}
|
|
750
2393
|
}, [expanded, engine]);
|
|
751
|
-
|
|
2394
|
+
(0, import_react10.useEffect)(() => {
|
|
752
2395
|
const active = engineState.kind !== "idle";
|
|
753
2396
|
if (active) document.body.setAttribute("data-clickly-active", "");
|
|
754
2397
|
else document.body.removeAttribute("data-clickly-active");
|
|
@@ -766,7 +2409,7 @@ function ClicklyRoot({
|
|
|
766
2409
|
document.body.removeAttribute("data-clickly-annotating");
|
|
767
2410
|
};
|
|
768
2411
|
}, [engineState]);
|
|
769
|
-
|
|
2412
|
+
(0, import_react10.useEffect)(() => {
|
|
770
2413
|
const onKey = (e) => {
|
|
771
2414
|
const tag = e.target?.tagName;
|
|
772
2415
|
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
@@ -809,16 +2452,16 @@ function ClicklyRoot({
|
|
|
809
2452
|
window.addEventListener("keydown", onKey);
|
|
810
2453
|
return () => window.removeEventListener("keydown", onKey);
|
|
811
2454
|
}, [clearAnnotations, outputDetail, expanded, engine]);
|
|
812
|
-
return /* @__PURE__ */
|
|
813
|
-
/* @__PURE__ */
|
|
814
|
-
expanded ? /* @__PURE__ */
|
|
815
|
-
/* @__PURE__ */
|
|
816
|
-
/* @__PURE__ */
|
|
817
|
-
] }) : /* @__PURE__ */
|
|
2455
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "clickly-ui", children: [
|
|
2456
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPins, {}),
|
|
2457
|
+
expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2458
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Toolbar, { engine, onCollapse: () => setExpanded(false) }),
|
|
2459
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AnnotationPopup, { engine })
|
|
2460
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CollapsedFAB, { onExpand: () => setExpanded(true) })
|
|
818
2461
|
] });
|
|
819
2462
|
}
|
|
820
2463
|
|
|
821
|
-
// src/internal/styles.ts
|
|
2464
|
+
// packages/react/src/internal/styles.ts
|
|
822
2465
|
var REACT_UI_CSS = `
|
|
823
2466
|
.clickly-ui, .clickly-ui * { box-sizing: border-box; }
|
|
824
2467
|
|
|
@@ -876,72 +2519,151 @@ var REACT_UI_CSS = `
|
|
|
876
2519
|
position: fixed;
|
|
877
2520
|
display: flex;
|
|
878
2521
|
align-items: center;
|
|
879
|
-
gap:
|
|
880
|
-
padding:
|
|
881
|
-
|
|
2522
|
+
gap: 2px;
|
|
2523
|
+
padding: 6px;
|
|
2524
|
+
height: 44px;
|
|
2525
|
+
background: rgba(9, 14, 28, 0.97);
|
|
882
2526
|
color: #f8fafc;
|
|
883
|
-
border-radius:
|
|
2527
|
+
border-radius: 16px;
|
|
884
2528
|
font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
885
|
-
box-shadow:
|
|
2529
|
+
box-shadow:
|
|
2530
|
+
0 16px 40px rgba(0,0,0,0.45),
|
|
2531
|
+
0 0 0 1px rgba(255,255,255,0.08) inset,
|
|
2532
|
+
0 1px 0 rgba(255,255,255,0.06) inset;
|
|
886
2533
|
pointer-events: auto;
|
|
887
2534
|
user-select: none;
|
|
888
2535
|
z-index: 1;
|
|
889
|
-
animation: clickly-fade-in
|
|
2536
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
890
2537
|
}
|
|
891
2538
|
|
|
892
2539
|
@keyframes clickly-fade-in {
|
|
893
|
-
from { opacity: 0; transform: translateY(
|
|
894
|
-
to { opacity: 1; transform: translateY(0); }
|
|
2540
|
+
from { opacity: 0; transform: translateY(6px) scale(0.97); }
|
|
2541
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
895
2542
|
}
|
|
896
2543
|
|
|
897
2544
|
.clickly-toolbar .grip {
|
|
898
2545
|
display: grid;
|
|
899
2546
|
place-items: center;
|
|
900
|
-
width:
|
|
901
|
-
height:
|
|
902
|
-
color: #
|
|
2547
|
+
width: 22px;
|
|
2548
|
+
height: 32px;
|
|
2549
|
+
color: #475569;
|
|
903
2550
|
touch-action: none;
|
|
2551
|
+
cursor: grab;
|
|
2552
|
+
border-radius: 6px;
|
|
2553
|
+
transition: color 120ms ease;
|
|
904
2554
|
}
|
|
2555
|
+
.clickly-toolbar .grip:hover { color: #94a3b8; }
|
|
2556
|
+
.clickly-toolbar .grip:active { cursor: grabbing; }
|
|
905
2557
|
|
|
906
2558
|
.clickly-toolbar .divider {
|
|
907
2559
|
width: 1px;
|
|
908
|
-
height:
|
|
909
|
-
background: rgba(255,255,255,0.
|
|
910
|
-
margin: 0
|
|
2560
|
+
height: 20px;
|
|
2561
|
+
background: rgba(255,255,255,0.08);
|
|
2562
|
+
margin: 0 3px;
|
|
2563
|
+
flex-shrink: 0;
|
|
911
2564
|
}
|
|
912
2565
|
|
|
913
2566
|
.clickly-btn {
|
|
914
2567
|
display: inline-flex;
|
|
915
2568
|
align-items: center;
|
|
2569
|
+
justify-content: center;
|
|
916
2570
|
gap: 4px;
|
|
917
|
-
height:
|
|
2571
|
+
height: 32px;
|
|
918
2572
|
padding: 0 8px;
|
|
919
2573
|
background: transparent;
|
|
920
2574
|
border: none;
|
|
921
|
-
border-radius:
|
|
922
|
-
color: #
|
|
2575
|
+
border-radius: 10px;
|
|
2576
|
+
color: #94a3b8;
|
|
923
2577
|
cursor: pointer;
|
|
924
2578
|
font: inherit;
|
|
2579
|
+
transition: background 100ms ease, color 100ms ease;
|
|
2580
|
+
position: relative;
|
|
925
2581
|
}
|
|
926
|
-
.clickly-btn:hover { background: rgba(255,255,255,0.
|
|
927
|
-
.clickly-btn:active { background: rgba(255,255,255,0.
|
|
928
|
-
.clickly-btn.is-active {
|
|
929
|
-
|
|
2582
|
+
.clickly-btn:hover { background: rgba(255,255,255,0.09); color: #e2e8f0; }
|
|
2583
|
+
.clickly-btn:active { background: rgba(255,255,255,0.15); color: #fff; transform: scale(0.96); }
|
|
2584
|
+
.clickly-btn.is-active {
|
|
2585
|
+
background: #0ea5e9;
|
|
2586
|
+
color: #fff;
|
|
2587
|
+
box-shadow: 0 0 0 1px rgba(14,165,233,0.4), 0 2px 8px rgba(14,165,233,0.3);
|
|
2588
|
+
}
|
|
2589
|
+
.clickly-btn.is-active:hover { background: #0284c7; }
|
|
2590
|
+
.clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
|
|
930
2591
|
|
|
931
2592
|
.clickly-btn.icon-only {
|
|
932
|
-
width:
|
|
2593
|
+
width: 32px;
|
|
933
2594
|
padding: 0;
|
|
934
|
-
justify-content: center;
|
|
935
2595
|
}
|
|
936
2596
|
|
|
937
2597
|
.clickly-btn.primary-pinned {
|
|
938
2598
|
background: #10b981;
|
|
939
2599
|
color: #fff;
|
|
2600
|
+
font-weight: 600;
|
|
2601
|
+
font-size: 12px;
|
|
2602
|
+
padding: 0 10px;
|
|
2603
|
+
gap: 5px;
|
|
2604
|
+
box-shadow: 0 0 0 1px rgba(16,185,129,0.4), 0 2px 8px rgba(16,185,129,0.25);
|
|
2605
|
+
}
|
|
2606
|
+
.clickly-btn.primary-pinned:hover { background: #059669; }
|
|
2607
|
+
|
|
2608
|
+
/* \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 */
|
|
2609
|
+
|
|
2610
|
+
.clickly-tip {
|
|
2611
|
+
position: relative;
|
|
2612
|
+
display: inline-flex;
|
|
2613
|
+
align-items: center;
|
|
2614
|
+
justify-content: center;
|
|
2615
|
+
/* Ensures the absolute-positioned counter badge clips correctly */
|
|
2616
|
+
isolation: isolate;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
.clickly-tip .tip-bubble {
|
|
2620
|
+
position: absolute;
|
|
2621
|
+
bottom: calc(100% + 10px);
|
|
2622
|
+
left: 50%;
|
|
2623
|
+
transform: translateX(-50%);
|
|
2624
|
+
background: rgba(15, 23, 42, 0.98);
|
|
2625
|
+
color: #f1f5f9;
|
|
2626
|
+
font-size: 12px;
|
|
940
2627
|
font-weight: 500;
|
|
2628
|
+
white-space: nowrap;
|
|
2629
|
+
padding: 5px 10px;
|
|
2630
|
+
border-radius: 8px;
|
|
2631
|
+
pointer-events: none;
|
|
2632
|
+
opacity: 0;
|
|
2633
|
+
transition: opacity 80ms ease;
|
|
2634
|
+
transition-delay: 200ms;
|
|
2635
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
|
|
2636
|
+
display: flex;
|
|
2637
|
+
align-items: center;
|
|
2638
|
+
gap: 6px;
|
|
2639
|
+
z-index: 10;
|
|
941
2640
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
2641
|
+
|
|
2642
|
+
/* Arrow */
|
|
2643
|
+
.clickly-tip .tip-bubble::after {
|
|
2644
|
+
content: "";
|
|
2645
|
+
position: absolute;
|
|
2646
|
+
top: 100%;
|
|
2647
|
+
left: 50%;
|
|
2648
|
+
transform: translateX(-50%);
|
|
2649
|
+
border: 5px solid transparent;
|
|
2650
|
+
border-top-color: rgba(15, 23, 42, 0.98);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
.clickly-tip:hover .tip-bubble { opacity: 1; }
|
|
2654
|
+
|
|
2655
|
+
.clickly-tip .tip-bubble kbd {
|
|
2656
|
+
display: inline-flex;
|
|
2657
|
+
align-items: center;
|
|
2658
|
+
justify-content: center;
|
|
2659
|
+
min-width: 18px;
|
|
2660
|
+
height: 18px;
|
|
2661
|
+
padding: 0 4px;
|
|
2662
|
+
background: rgba(255,255,255,0.12);
|
|
2663
|
+
border: 1px solid rgba(255,255,255,0.10);
|
|
2664
|
+
border-radius: 4px;
|
|
2665
|
+
font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
|
|
2666
|
+
color: #94a3b8;
|
|
945
2667
|
}
|
|
946
2668
|
|
|
947
2669
|
/* \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 */
|
|
@@ -958,20 +2680,181 @@ var REACT_UI_CSS = `
|
|
|
958
2680
|
animation: clickly-fade-in 120ms ease-out;
|
|
959
2681
|
}
|
|
960
2682
|
|
|
961
|
-
.clickly-popup {
|
|
2683
|
+
.clickly-popup {
|
|
2684
|
+
width: 320px;
|
|
2685
|
+
padding: 12px;
|
|
2686
|
+
max-height: calc(100vh - 80px);
|
|
2687
|
+
overflow: hidden;
|
|
2688
|
+
display: flex;
|
|
2689
|
+
flex-direction: column;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
/* \u2500\u2500\u2500 Shared inner styles \u2014 apply to both popup and pin-edit \u2500\u2500\u2500 */
|
|
962
2693
|
|
|
963
|
-
.clickly-popup .
|
|
2694
|
+
.clickly-popup .popup-header,
|
|
2695
|
+
.pin-edit .popup-header {
|
|
964
2696
|
display: flex;
|
|
965
2697
|
align-items: center;
|
|
966
|
-
|
|
967
|
-
gap: 8px;
|
|
2698
|
+
gap: 5px;
|
|
968
2699
|
margin-bottom: 8px;
|
|
2700
|
+
padding: 4px 2px;
|
|
2701
|
+
border-radius: 4px;
|
|
2702
|
+
cursor: pointer;
|
|
2703
|
+
user-select: none;
|
|
969
2704
|
color: #475569;
|
|
970
2705
|
font-size: 11px;
|
|
971
2706
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
2707
|
+
transition: background 80ms ease;
|
|
2708
|
+
}
|
|
2709
|
+
.clickly-popup .popup-header:hover,
|
|
2710
|
+
.pin-edit .popup-header:hover { background: #f1f5f9; }
|
|
2711
|
+
|
|
2712
|
+
.clickly-popup .popup-chevron,
|
|
2713
|
+
.pin-edit .popup-chevron {
|
|
2714
|
+
font-size: 12px;
|
|
2715
|
+
color: #94a3b8;
|
|
2716
|
+
flex-shrink: 0;
|
|
2717
|
+
width: 12px;
|
|
2718
|
+
text-align: center;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
.clickly-popup .popup-label,
|
|
2722
|
+
.pin-edit .popup-label {
|
|
2723
|
+
flex: 1;
|
|
2724
|
+
overflow: hidden;
|
|
2725
|
+
white-space: nowrap;
|
|
2726
|
+
text-overflow: ellipsis;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/* Edit-mode toggle button \u2014 top-right of CSS panel header */
|
|
2730
|
+
.clickly-popup .popup-edit-toggle {
|
|
2731
|
+
flex-shrink: 0;
|
|
2732
|
+
display: grid;
|
|
2733
|
+
place-items: center;
|
|
2734
|
+
width: 20px;
|
|
2735
|
+
height: 20px;
|
|
2736
|
+
background: transparent;
|
|
2737
|
+
border: 1px solid #e2e8f0;
|
|
2738
|
+
border-radius: 5px;
|
|
2739
|
+
color: #94a3b8;
|
|
2740
|
+
cursor: pointer;
|
|
2741
|
+
padding: 0;
|
|
2742
|
+
margin-left: auto;
|
|
2743
|
+
transition: background 100ms ease, color 100ms ease, border-color 100ms ease;
|
|
2744
|
+
}
|
|
2745
|
+
.clickly-popup .popup-edit-toggle:hover {
|
|
2746
|
+
background: #f1f5f9;
|
|
2747
|
+
color: #475569;
|
|
2748
|
+
border-color: #cbd5e1;
|
|
2749
|
+
}
|
|
2750
|
+
.clickly-popup .popup-edit-toggle.is-editing {
|
|
2751
|
+
background: #0ea5e9;
|
|
2752
|
+
color: #fff;
|
|
2753
|
+
border-color: #0ea5e9;
|
|
2754
|
+
}
|
|
2755
|
+
.clickly-popup .popup-edit-toggle.is-editing:hover {
|
|
2756
|
+
background: #0284c7;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
/* Computed-styles panel */
|
|
2760
|
+
.clickly-popup .popup-styles,
|
|
2761
|
+
.pin-edit .popup-styles {
|
|
2762
|
+
margin-bottom: 8px;
|
|
2763
|
+
padding: 8px;
|
|
2764
|
+
background: #f8fafc;
|
|
2765
|
+
border: 1px solid #e2e8f0;
|
|
2766
|
+
border-radius: 6px;
|
|
2767
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
2768
|
+
font-size: 11px;
|
|
2769
|
+
max-height: 160px;
|
|
2770
|
+
overflow-y: auto;
|
|
2771
|
+
overscroll-behavior: contain;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
.clickly-popup .style-row,
|
|
2775
|
+
.pin-edit .style-row {
|
|
2776
|
+
display: flex;
|
|
2777
|
+
align-items: center;
|
|
2778
|
+
gap: 6px;
|
|
2779
|
+
line-height: 1.7;
|
|
2780
|
+
border-radius: 4px;
|
|
2781
|
+
padding: 0 2px;
|
|
2782
|
+
transition: background 80ms ease;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
.clickly-popup .style-row--changed,
|
|
2786
|
+
.pin-edit .style-row--changed {
|
|
2787
|
+
background: rgba(245, 158, 11, 0.10);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
.clickly-popup .style-key,
|
|
2791
|
+
.pin-edit .style-key {
|
|
2792
|
+
color: #7c3aed;
|
|
2793
|
+
flex-shrink: 0;
|
|
2794
|
+
min-width: 90px;
|
|
2795
|
+
font-size: 11px;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
.clickly-popup .style-val,
|
|
2799
|
+
.pin-edit .style-val {
|
|
2800
|
+
color: #0f172a;
|
|
2801
|
+
font-size: 11px;
|
|
2802
|
+
word-break: break-all;
|
|
2803
|
+
flex: 1;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
/* Editable value input \u2014 live CSS preview */
|
|
2807
|
+
.clickly-popup .style-val-input {
|
|
2808
|
+
flex: 1;
|
|
2809
|
+
min-width: 0;
|
|
2810
|
+
background: transparent;
|
|
2811
|
+
border: none;
|
|
2812
|
+
border-bottom: 1px solid transparent;
|
|
2813
|
+
color: #0f172a;
|
|
2814
|
+
font: 11px/1.7 ui-monospace, "SF Mono", Menlo, monospace;
|
|
2815
|
+
padding: 0;
|
|
2816
|
+
outline: none;
|
|
2817
|
+
transition: border-color 100ms ease;
|
|
2818
|
+
word-break: break-all;
|
|
2819
|
+
}
|
|
2820
|
+
.clickly-popup .style-val-input:focus {
|
|
2821
|
+
border-bottom-color: #0ea5e9;
|
|
2822
|
+
background: rgba(14, 165, 233, 0.05);
|
|
2823
|
+
border-radius: 2px 2px 0 0;
|
|
972
2824
|
}
|
|
973
2825
|
|
|
974
|
-
|
|
2826
|
+
/* Revert button shown only on changed rows */
|
|
2827
|
+
.clickly-popup .style-revert-btn {
|
|
2828
|
+
flex-shrink: 0;
|
|
2829
|
+
background: transparent;
|
|
2830
|
+
border: none;
|
|
2831
|
+
color: #94a3b8;
|
|
2832
|
+
font-size: 13px;
|
|
2833
|
+
cursor: pointer;
|
|
2834
|
+
padding: 0 2px;
|
|
2835
|
+
line-height: 1;
|
|
2836
|
+
border-radius: 3px;
|
|
2837
|
+
transition: color 80ms ease, background 80ms ease;
|
|
2838
|
+
}
|
|
2839
|
+
.clickly-popup .style-revert-btn:hover {
|
|
2840
|
+
color: #f59e0b;
|
|
2841
|
+
background: rgba(245,158,11,0.12);
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
/* Hint text at bottom of CSS panel */
|
|
2845
|
+
.clickly-popup .style-hint,
|
|
2846
|
+
.pin-edit .style-hint {
|
|
2847
|
+
margin-top: 6px;
|
|
2848
|
+
padding-top: 6px;
|
|
2849
|
+
border-top: 1px solid #e2e8f0;
|
|
2850
|
+
font-size: 10.5px;
|
|
2851
|
+
color: #94a3b8;
|
|
2852
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2853
|
+
line-height: 1.4;
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
.clickly-popup textarea,
|
|
2857
|
+
.pin-edit textarea {
|
|
975
2858
|
width: 100%;
|
|
976
2859
|
min-height: 64px;
|
|
977
2860
|
max-height: 160px;
|
|
@@ -982,33 +2865,40 @@ var REACT_UI_CSS = `
|
|
|
982
2865
|
font: inherit;
|
|
983
2866
|
color: inherit;
|
|
984
2867
|
}
|
|
985
|
-
.clickly-popup textarea:focus
|
|
2868
|
+
.clickly-popup textarea:focus,
|
|
2869
|
+
.pin-edit textarea:focus {
|
|
986
2870
|
outline: none;
|
|
987
2871
|
border-color: #0ea5e9;
|
|
988
2872
|
box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
|
|
989
2873
|
}
|
|
990
2874
|
|
|
991
|
-
.clickly-popup .row
|
|
2875
|
+
.clickly-popup .row,
|
|
2876
|
+
.pin-edit .row {
|
|
992
2877
|
display: flex;
|
|
993
2878
|
justify-content: flex-end;
|
|
994
2879
|
gap: 6px;
|
|
995
2880
|
margin-top: 10px;
|
|
996
2881
|
}
|
|
997
2882
|
|
|
998
|
-
.clickly-popup .row .ghost
|
|
2883
|
+
.clickly-popup .row .ghost,
|
|
2884
|
+
.pin-edit .row .ghost {
|
|
999
2885
|
background: transparent;
|
|
1000
2886
|
color: #475569;
|
|
1001
2887
|
border: 1px solid #e2e8f0;
|
|
1002
2888
|
}
|
|
1003
|
-
.clickly-popup .row .ghost:hover
|
|
2889
|
+
.clickly-popup .row .ghost:hover,
|
|
2890
|
+
.pin-edit .row .ghost:hover { background: #f8fafc; }
|
|
1004
2891
|
|
|
1005
|
-
.clickly-popup .row .primary
|
|
2892
|
+
.clickly-popup .row .primary,
|
|
2893
|
+
.pin-edit .row .primary {
|
|
1006
2894
|
background: #0ea5e9;
|
|
1007
2895
|
color: #fff;
|
|
1008
2896
|
border: none;
|
|
1009
2897
|
}
|
|
1010
|
-
.clickly-popup .row .primary:hover
|
|
1011
|
-
.
|
|
2898
|
+
.clickly-popup .row .primary:hover,
|
|
2899
|
+
.pin-edit .row .primary:hover { background: #0284c7; }
|
|
2900
|
+
.clickly-popup .row button,
|
|
2901
|
+
.pin-edit .row button {
|
|
1012
2902
|
height: 28px;
|
|
1013
2903
|
padding: 0 12px;
|
|
1014
2904
|
border-radius: 6px;
|
|
@@ -1016,121 +2906,518 @@ var REACT_UI_CSS = `
|
|
|
1016
2906
|
cursor: pointer;
|
|
1017
2907
|
}
|
|
1018
2908
|
|
|
2909
|
+
/* Old popover kept for any legacy use */
|
|
1019
2910
|
.clickly-popover { width: 260px; padding: 12px; }
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
.clickly-
|
|
2911
|
+
|
|
2912
|
+
/* \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 */
|
|
2913
|
+
|
|
2914
|
+
.clickly-settings {
|
|
2915
|
+
position: fixed;
|
|
2916
|
+
width: 284px;
|
|
2917
|
+
background: #fff;
|
|
2918
|
+
border-radius: 14px;
|
|
2919
|
+
box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
|
|
2920
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2921
|
+
color: #0f172a;
|
|
2922
|
+
pointer-events: auto;
|
|
2923
|
+
z-index: 2;
|
|
2924
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2925
|
+
overflow: hidden;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
.settings-header {
|
|
2929
|
+
display: flex;
|
|
2930
|
+
align-items: center;
|
|
2931
|
+
justify-content: space-between;
|
|
2932
|
+
padding: 14px 14px 12px;
|
|
2933
|
+
border-bottom: 1px solid #f1f5f9;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
.settings-title {
|
|
2937
|
+
font-size: 13px;
|
|
2938
|
+
font-weight: 600;
|
|
2939
|
+
color: #0f172a;
|
|
2940
|
+
letter-spacing: -0.01em;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
.settings-close {
|
|
2944
|
+
display: grid;
|
|
2945
|
+
place-items: center;
|
|
2946
|
+
width: 24px;
|
|
2947
|
+
height: 24px;
|
|
2948
|
+
background: #f1f5f9;
|
|
2949
|
+
border: none;
|
|
2950
|
+
border-radius: 6px;
|
|
2951
|
+
color: #64748b;
|
|
2952
|
+
cursor: pointer;
|
|
2953
|
+
padding: 0;
|
|
2954
|
+
transition: background 100ms, color 100ms;
|
|
2955
|
+
}
|
|
2956
|
+
.settings-close:hover { background: #e2e8f0; color: #0f172a; }
|
|
2957
|
+
.settings-close svg { width: 13px; height: 13px; }
|
|
2958
|
+
|
|
2959
|
+
.settings-section { padding: 10px 14px; }
|
|
2960
|
+
|
|
2961
|
+
.settings-divider {
|
|
2962
|
+
height: 1px;
|
|
2963
|
+
background: #f1f5f9;
|
|
2964
|
+
margin: 0;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
.settings-label {
|
|
2968
|
+
display: flex;
|
|
2969
|
+
flex-direction: column;
|
|
2970
|
+
gap: 2px;
|
|
2971
|
+
font-size: 13px;
|
|
2972
|
+
font-weight: 500;
|
|
2973
|
+
color: #1e293b;
|
|
2974
|
+
margin-bottom: 8px;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
.settings-hint {
|
|
2978
|
+
font-size: 11.5px;
|
|
2979
|
+
font-weight: 400;
|
|
2980
|
+
color: #94a3b8;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
.settings-select {
|
|
2984
|
+
width: 100%;
|
|
2985
|
+
padding: 7px 10px;
|
|
2986
|
+
background: #f8fafc;
|
|
2987
|
+
border: 1px solid #e2e8f0;
|
|
2988
|
+
border-radius: 8px;
|
|
2989
|
+
font: inherit;
|
|
2990
|
+
font-size: 13px;
|
|
2991
|
+
color: #1e293b;
|
|
2992
|
+
cursor: pointer;
|
|
2993
|
+
appearance: none;
|
|
2994
|
+
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");
|
|
2995
|
+
background-repeat: no-repeat;
|
|
2996
|
+
background-position: right 10px center;
|
|
2997
|
+
padding-right: 32px;
|
|
2998
|
+
transition: border-color 120ms, box-shadow 120ms;
|
|
2999
|
+
}
|
|
3000
|
+
.settings-select:focus {
|
|
3001
|
+
outline: none;
|
|
3002
|
+
border-color: #0ea5e9;
|
|
3003
|
+
box-shadow: 0 0 0 3px rgba(14,165,233,0.15);
|
|
3004
|
+
background-color: #fff;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
.settings-row {
|
|
3008
|
+
display: flex;
|
|
3009
|
+
align-items: center;
|
|
3010
|
+
justify-content: space-between;
|
|
3011
|
+
gap: 12px;
|
|
3012
|
+
padding: 6px 0;
|
|
3013
|
+
}
|
|
3014
|
+
.settings-row + .settings-row {
|
|
3015
|
+
border-top: 1px solid #f8fafc;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
.settings-row-label {
|
|
3019
|
+
display: flex;
|
|
3020
|
+
flex-direction: column;
|
|
3021
|
+
gap: 2px;
|
|
3022
|
+
font-size: 13px;
|
|
3023
|
+
font-weight: 500;
|
|
3024
|
+
color: #1e293b;
|
|
3025
|
+
min-width: 0;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/* Toggle switch */
|
|
3029
|
+
.clickly-toggle {
|
|
3030
|
+
position: relative;
|
|
3031
|
+
flex-shrink: 0;
|
|
3032
|
+
width: 38px;
|
|
3033
|
+
height: 22px;
|
|
3034
|
+
cursor: pointer;
|
|
3035
|
+
}
|
|
3036
|
+
.clickly-toggle input {
|
|
3037
|
+
position: absolute;
|
|
3038
|
+
opacity: 0;
|
|
3039
|
+
width: 0;
|
|
3040
|
+
height: 0;
|
|
3041
|
+
}
|
|
3042
|
+
.toggle-track {
|
|
3043
|
+
position: absolute;
|
|
3044
|
+
inset: 0;
|
|
3045
|
+
background: #e2e8f0;
|
|
3046
|
+
border-radius: 11px;
|
|
3047
|
+
transition: background 180ms ease;
|
|
3048
|
+
}
|
|
3049
|
+
.toggle-track::after {
|
|
3050
|
+
content: "";
|
|
3051
|
+
position: absolute;
|
|
3052
|
+
top: 3px;
|
|
3053
|
+
left: 3px;
|
|
3054
|
+
width: 16px;
|
|
3055
|
+
height: 16px;
|
|
3056
|
+
background: #fff;
|
|
3057
|
+
border-radius: 50%;
|
|
3058
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
|
|
3059
|
+
transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
3060
|
+
}
|
|
3061
|
+
.clickly-toggle input:checked + .toggle-track {
|
|
3062
|
+
background: #0ea5e9;
|
|
3063
|
+
}
|
|
3064
|
+
.clickly-toggle input:checked + .toggle-track::after {
|
|
3065
|
+
transform: translateX(16px);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/* Color picker */
|
|
3069
|
+
.settings-color-wrap {
|
|
3070
|
+
display: flex;
|
|
3071
|
+
align-items: center;
|
|
3072
|
+
gap: 0;
|
|
3073
|
+
cursor: pointer;
|
|
3074
|
+
border-radius: 8px;
|
|
3075
|
+
overflow: hidden;
|
|
3076
|
+
border: 1px solid #e2e8f0;
|
|
3077
|
+
flex-shrink: 0;
|
|
3078
|
+
}
|
|
3079
|
+
.settings-color-wrap input[type="color"] {
|
|
3080
|
+
position: absolute;
|
|
3081
|
+
opacity: 0;
|
|
3082
|
+
width: 0;
|
|
3083
|
+
height: 0;
|
|
3084
|
+
pointer-events: none;
|
|
3085
|
+
}
|
|
3086
|
+
.color-swatch {
|
|
3087
|
+
display: block;
|
|
3088
|
+
width: 32px;
|
|
3089
|
+
height: 24px;
|
|
3090
|
+
border-radius: 7px;
|
|
3091
|
+
border: 1px solid rgba(0,0,0,0.08);
|
|
3092
|
+
transition: transform 100ms ease;
|
|
3093
|
+
}
|
|
3094
|
+
.settings-color-wrap:hover .color-swatch { transform: scale(1.08); }
|
|
1024
3095
|
|
|
1025
3096
|
.clickly-counter {
|
|
1026
|
-
|
|
3097
|
+
/* Float as a badge \u2014 positioned absolutely so it doesn't widen the button */
|
|
3098
|
+
position: absolute;
|
|
3099
|
+
top: -5px;
|
|
3100
|
+
right: -5px;
|
|
3101
|
+
min-width: 16px;
|
|
3102
|
+
height: 16px;
|
|
3103
|
+
padding: 0 4px;
|
|
3104
|
+
border-radius: 8px;
|
|
3105
|
+
background: #f59e0b;
|
|
3106
|
+
color: #0f172a;
|
|
3107
|
+
font-size: 10px;
|
|
3108
|
+
font-weight: 700;
|
|
3109
|
+
display: flex;
|
|
1027
3110
|
align-items: center;
|
|
1028
3111
|
justify-content: center;
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
padding: 0 5px;
|
|
1032
|
-
border-radius: 9px;
|
|
1033
|
-
background: #f59e0b;
|
|
1034
|
-
color: #1f2937;
|
|
1035
|
-
font-size: 11px;
|
|
1036
|
-
font-weight: 600;
|
|
1037
|
-
margin-left: 4px;
|
|
3112
|
+
pointer-events: none;
|
|
3113
|
+
border: 1.5px solid rgba(9, 14, 28, 0.97);
|
|
1038
3114
|
}
|
|
1039
3115
|
|
|
1040
3116
|
/* \u2500\u2500\u2500 Annotation pins (persistent numbered markers) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1041
3117
|
|
|
1042
3118
|
.clickly-pin {
|
|
1043
3119
|
position: fixed;
|
|
1044
|
-
width:
|
|
1045
|
-
height:
|
|
3120
|
+
width: 24px;
|
|
3121
|
+
height: 24px;
|
|
1046
3122
|
border-radius: 999px;
|
|
1047
|
-
background: #
|
|
1048
|
-
color: #
|
|
1049
|
-
font:
|
|
3123
|
+
background: #10b981;
|
|
3124
|
+
color: #fff;
|
|
3125
|
+
font: 700 11px/24px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1050
3126
|
text-align: center;
|
|
1051
3127
|
cursor: pointer;
|
|
1052
3128
|
pointer-events: auto;
|
|
1053
3129
|
user-select: none;
|
|
1054
|
-
box-shadow: 0 2px
|
|
1055
|
-
z-index:
|
|
1056
|
-
transition: transform
|
|
3130
|
+
box-shadow: 0 2px 8px rgba(16,185,129,0.4), 0 0 0 2px #fff;
|
|
3131
|
+
z-index: 10;
|
|
3132
|
+
transition: transform 120ms cubic-bezier(0.34,1.56,0.64,1), box-shadow 120ms ease;
|
|
3133
|
+
}
|
|
3134
|
+
.clickly-pin:hover {
|
|
3135
|
+
transform: scale(1.18);
|
|
3136
|
+
box-shadow: 0 4px 16px rgba(16,185,129,0.5), 0 0 0 2px #fff;
|
|
1057
3137
|
}
|
|
1058
|
-
.clickly-pin:hover { transform: scale(1.12); }
|
|
1059
3138
|
.clickly-pin-num { display: block; }
|
|
1060
3139
|
|
|
1061
|
-
|
|
3140
|
+
/* \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 */
|
|
3141
|
+
|
|
3142
|
+
.pin-preview {
|
|
1062
3143
|
position: absolute;
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
3144
|
+
right: calc(100% + 10px);
|
|
3145
|
+
top: 50%;
|
|
3146
|
+
transform: translateY(-50%);
|
|
3147
|
+
width: 220px;
|
|
1066
3148
|
padding: 8px 10px;
|
|
1067
|
-
background:
|
|
1068
|
-
color: #
|
|
1069
|
-
border-radius:
|
|
1070
|
-
box-shadow: 0 8px 24px rgba(
|
|
1071
|
-
font:
|
|
3149
|
+
background: rgba(9, 14, 28, 0.96);
|
|
3150
|
+
color: #f1f5f9;
|
|
3151
|
+
border-radius: 10px;
|
|
3152
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.07) inset;
|
|
3153
|
+
font: 12px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1072
3154
|
text-align: left;
|
|
1073
3155
|
cursor: default;
|
|
1074
|
-
|
|
3156
|
+
pointer-events: none;
|
|
3157
|
+
z-index: 11;
|
|
1075
3158
|
animation: clickly-fade-in 100ms ease-out;
|
|
3159
|
+
white-space: normal;
|
|
1076
3160
|
}
|
|
1077
|
-
|
|
1078
|
-
.
|
|
1079
|
-
|
|
3161
|
+
|
|
3162
|
+
.pin-preview-meta {
|
|
3163
|
+
font-size: 10.5px;
|
|
3164
|
+
color: #64748b;
|
|
1080
3165
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
3166
|
+
margin-bottom: 4px;
|
|
3167
|
+
overflow: hidden;
|
|
3168
|
+
white-space: nowrap;
|
|
3169
|
+
text-overflow: ellipsis;
|
|
1084
3170
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
3171
|
+
|
|
3172
|
+
.pin-preview-comment {
|
|
3173
|
+
font-size: 12px;
|
|
3174
|
+
color: #e2e8f0;
|
|
3175
|
+
word-break: break-word;
|
|
3176
|
+
display: -webkit-box;
|
|
3177
|
+
-webkit-line-clamp: 3;
|
|
3178
|
+
-webkit-box-orient: vertical;
|
|
3179
|
+
overflow: hidden;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
/* \u2500\u2500\u2500 Pin edit popup \u2014 same white card as .clickly-popup \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
3183
|
+
|
|
3184
|
+
.pin-edit {
|
|
3185
|
+
/* Positioning: floats to the LEFT of the pin */
|
|
3186
|
+
position: absolute;
|
|
3187
|
+
right: calc(100% + 12px);
|
|
3188
|
+
top: 50%;
|
|
3189
|
+
transform: translateY(-50%);
|
|
3190
|
+
/* Appearance: identical to .clickly-popup */
|
|
3191
|
+
width: 300px;
|
|
3192
|
+
padding: 12px;
|
|
1089
3193
|
background: #fff;
|
|
1090
|
-
color: #
|
|
1091
|
-
border-radius:
|
|
1092
|
-
|
|
3194
|
+
color: #0f172a;
|
|
3195
|
+
border-radius: 10px;
|
|
3196
|
+
box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
|
|
3197
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
3198
|
+
text-align: left;
|
|
3199
|
+
cursor: default;
|
|
3200
|
+
z-index: 11;
|
|
3201
|
+
animation: clickly-fade-in 120ms cubic-bezier(0.16,1,0.3,1);
|
|
3202
|
+
/* Allow inner popup-styles to scroll */
|
|
3203
|
+
max-height: 80vh;
|
|
3204
|
+
overflow: hidden;
|
|
3205
|
+
display: flex;
|
|
3206
|
+
flex-direction: column;
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
/* Reuse .clickly-popup textarea inside pin-edit */
|
|
3210
|
+
.pin-edit textarea {
|
|
3211
|
+
width: 100%;
|
|
3212
|
+
min-height: 64px;
|
|
3213
|
+
max-height: 120px;
|
|
3214
|
+
padding: 8px;
|
|
3215
|
+
border: 1px solid #e2e8f0;
|
|
3216
|
+
border-radius: 6px;
|
|
3217
|
+
resize: vertical;
|
|
3218
|
+
font: inherit;
|
|
3219
|
+
color: inherit;
|
|
3220
|
+
background: #fff;
|
|
3221
|
+
box-sizing: border-box;
|
|
3222
|
+
}
|
|
3223
|
+
.pin-edit textarea:focus {
|
|
3224
|
+
outline: none;
|
|
3225
|
+
border-color: #0ea5e9;
|
|
3226
|
+
box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
/* Actions row */
|
|
3230
|
+
.pin-edit-actions {
|
|
3231
|
+
display: flex;
|
|
3232
|
+
align-items: center;
|
|
3233
|
+
gap: 6px;
|
|
3234
|
+
margin-top: 10px;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
.pin-edit-delete {
|
|
3238
|
+
display: grid;
|
|
3239
|
+
place-items: center;
|
|
3240
|
+
width: 28px;
|
|
3241
|
+
height: 28px;
|
|
3242
|
+
flex-shrink: 0;
|
|
3243
|
+
background: transparent;
|
|
3244
|
+
border: 1px solid #fee2e2;
|
|
3245
|
+
border-radius: 6px;
|
|
3246
|
+
color: #ef4444;
|
|
1093
3247
|
cursor: pointer;
|
|
3248
|
+
padding: 0;
|
|
3249
|
+
transition: background 100ms, border-color 100ms;
|
|
1094
3250
|
}
|
|
1095
|
-
.
|
|
3251
|
+
.pin-edit-delete:hover { background: #fef2f2; border-color: #fca5a5; }
|
|
3252
|
+
.pin-edit-delete svg { width: 13px; height: 13px; }
|
|
1096
3253
|
|
|
1097
3254
|
/* \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 */
|
|
1098
3255
|
|
|
1099
3256
|
.clickly-list {
|
|
1100
3257
|
position: fixed;
|
|
1101
|
-
max-height: 50vh;
|
|
1102
3258
|
width: 320px;
|
|
1103
|
-
|
|
3259
|
+
max-height: 55vh;
|
|
1104
3260
|
background: #fff;
|
|
1105
|
-
border-radius:
|
|
1106
|
-
box-shadow: 0
|
|
3261
|
+
border-radius: 14px;
|
|
3262
|
+
box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
|
|
1107
3263
|
color: #0f172a;
|
|
1108
|
-
font:
|
|
3264
|
+
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
1109
3265
|
pointer-events: auto;
|
|
1110
3266
|
z-index: 2;
|
|
1111
|
-
animation: clickly-fade-in
|
|
3267
|
+
animation: clickly-fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
3268
|
+
display: flex;
|
|
3269
|
+
flex-direction: column;
|
|
3270
|
+
overflow: hidden;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
.list-header {
|
|
3274
|
+
display: flex;
|
|
3275
|
+
align-items: center;
|
|
3276
|
+
gap: 8px;
|
|
3277
|
+
padding: 12px 14px 10px;
|
|
3278
|
+
border-bottom: 1px solid #f1f5f9;
|
|
3279
|
+
flex-shrink: 0;
|
|
1112
3280
|
}
|
|
1113
|
-
|
|
1114
|
-
.
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
3281
|
+
|
|
3282
|
+
.list-title {
|
|
3283
|
+
font-size: 13px;
|
|
3284
|
+
font-weight: 600;
|
|
3285
|
+
color: #0f172a;
|
|
3286
|
+
letter-spacing: -0.01em;
|
|
3287
|
+
flex: 1;
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
.list-count {
|
|
3291
|
+
min-width: 20px;
|
|
3292
|
+
height: 20px;
|
|
3293
|
+
padding: 0 6px;
|
|
3294
|
+
background: #f1f5f9;
|
|
3295
|
+
color: #475569;
|
|
3296
|
+
font-size: 11px;
|
|
3297
|
+
font-weight: 600;
|
|
3298
|
+
border-radius: 10px;
|
|
3299
|
+
display: grid;
|
|
3300
|
+
place-items: center;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
.list-items {
|
|
3304
|
+
overflow-y: auto;
|
|
3305
|
+
overscroll-behavior: contain;
|
|
3306
|
+
flex: 1;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
.list-empty {
|
|
3310
|
+
padding: 24px;
|
|
3311
|
+
text-align: center;
|
|
3312
|
+
color: #94a3b8;
|
|
3313
|
+
font-size: 12px;
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
/* \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 */
|
|
3317
|
+
|
|
3318
|
+
.list-card {
|
|
3319
|
+
padding: 10px 12px;
|
|
3320
|
+
border-bottom: 1px solid #f8fafc;
|
|
3321
|
+
transition: background 80ms ease;
|
|
3322
|
+
}
|
|
3323
|
+
.list-card:last-child { border-bottom: none; }
|
|
3324
|
+
.list-card:hover { background: #fafafa; }
|
|
3325
|
+
|
|
3326
|
+
.list-card-header {
|
|
3327
|
+
display: flex;
|
|
3328
|
+
align-items: center;
|
|
3329
|
+
gap: 6px;
|
|
3330
|
+
margin-bottom: 4px;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
.list-card-num {
|
|
3334
|
+
font-size: 11px;
|
|
3335
|
+
font-weight: 700;
|
|
3336
|
+
color: #94a3b8;
|
|
3337
|
+
flex-shrink: 0;
|
|
3338
|
+
min-width: 20px;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
.list-card-path {
|
|
3342
|
+
flex: 1;
|
|
3343
|
+
min-width: 0;
|
|
3344
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
3345
|
+
font-size: 10.5px;
|
|
3346
|
+
color: #475569;
|
|
3347
|
+
white-space: nowrap;
|
|
3348
|
+
overflow: hidden;
|
|
3349
|
+
text-overflow: ellipsis;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
.list-card-actions {
|
|
3353
|
+
display: flex;
|
|
3354
|
+
gap: 3px;
|
|
3355
|
+
flex-shrink: 0;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
.list-action-btn {
|
|
3359
|
+
display: grid;
|
|
3360
|
+
place-items: center;
|
|
3361
|
+
width: 24px;
|
|
3362
|
+
height: 24px;
|
|
1120
3363
|
background: transparent;
|
|
1121
3364
|
border: none;
|
|
3365
|
+
border-radius: 6px;
|
|
1122
3366
|
color: #94a3b8;
|
|
1123
3367
|
cursor: pointer;
|
|
1124
|
-
padding:
|
|
1125
|
-
|
|
1126
|
-
|
|
3368
|
+
padding: 0;
|
|
3369
|
+
transition: background 80ms ease, color 80ms ease;
|
|
3370
|
+
}
|
|
3371
|
+
.list-action-btn svg { width: 12px; height: 12px; }
|
|
3372
|
+
.list-action-btn:hover { background: #f1f5f9; color: #475569; }
|
|
3373
|
+
.list-action-btn.copied { color: #10b981; }
|
|
3374
|
+
.list-action-btn.list-action-delete:hover { background: #fef2f2; color: #ef4444; }
|
|
3375
|
+
|
|
3376
|
+
.list-card-comment {
|
|
3377
|
+
font-size: 12px;
|
|
3378
|
+
color: #1e293b;
|
|
3379
|
+
line-height: 1.45;
|
|
3380
|
+
margin: 0 0 6px;
|
|
3381
|
+
word-break: break-word;
|
|
3382
|
+
display: -webkit-box;
|
|
3383
|
+
-webkit-line-clamp: 2;
|
|
3384
|
+
-webkit-box-orient: vertical;
|
|
3385
|
+
overflow: hidden;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
/* CSS changes badge */
|
|
3389
|
+
.list-card-css {
|
|
3390
|
+
background: rgba(124, 58, 237, 0.05);
|
|
3391
|
+
border: 1px solid rgba(124, 58, 237, 0.12);
|
|
3392
|
+
border-radius: 6px;
|
|
3393
|
+
padding: 5px 8px;
|
|
3394
|
+
margin-top: 4px;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
.list-card-css-label {
|
|
3398
|
+
display: block;
|
|
3399
|
+
font-size: 10px;
|
|
3400
|
+
font-weight: 600;
|
|
3401
|
+
color: #7c3aed;
|
|
3402
|
+
text-transform: uppercase;
|
|
3403
|
+
letter-spacing: 0.04em;
|
|
3404
|
+
margin-bottom: 3px;
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
.list-card-css-code {
|
|
3408
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
3409
|
+
font-size: 10px;
|
|
3410
|
+
color: #475569;
|
|
3411
|
+
line-height: 1.5;
|
|
3412
|
+
margin: 0;
|
|
3413
|
+
white-space: pre-wrap;
|
|
3414
|
+
word-break: break-all;
|
|
3415
|
+
max-height: 60px;
|
|
3416
|
+
overflow: hidden;
|
|
1127
3417
|
}
|
|
1128
|
-
.clickly-list button.remove svg { width: 12px; height: 12px; }
|
|
1129
|
-
.clickly-list button.remove:hover { color: #ef4444; }
|
|
1130
|
-
.clickly-list .empty { padding: 20px; text-align: center; color: #94a3b8; }
|
|
1131
3418
|
`;
|
|
1132
3419
|
|
|
1133
|
-
// src/internal/globalStyles.ts
|
|
3420
|
+
// packages/react/src/internal/globalStyles.ts
|
|
1134
3421
|
var GLOBAL_PAGE_CSS = `
|
|
1135
3422
|
body[data-clickly-active] {
|
|
1136
3423
|
-webkit-user-select: none !important;
|
|
@@ -1150,16 +3437,19 @@ body[data-clickly-annotating] {
|
|
|
1150
3437
|
cursor: default !important;
|
|
1151
3438
|
}
|
|
1152
3439
|
`;
|
|
3440
|
+
|
|
3441
|
+
// packages/react/src/Clickly.tsx
|
|
3442
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1153
3443
|
function Clickly({ className } = {}) {
|
|
1154
|
-
const [mount, setMount] =
|
|
1155
|
-
|
|
3444
|
+
const [mount, setMount] = (0, import_react11.useState)(null);
|
|
3445
|
+
(0, import_react11.useEffect)(() => {
|
|
1156
3446
|
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1157
3447
|
let shadow = null;
|
|
1158
3448
|
let engine = null;
|
|
1159
3449
|
let overlay = null;
|
|
1160
3450
|
let portal = null;
|
|
1161
3451
|
try {
|
|
1162
|
-
shadow =
|
|
3452
|
+
shadow = createShadowHost({ document });
|
|
1163
3453
|
portal = document.createElement("div");
|
|
1164
3454
|
portal.setAttribute("data-clickly-react-root", "");
|
|
1165
3455
|
shadow.root.appendChild(portal);
|
|
@@ -1175,8 +3465,8 @@ function Clickly({ className } = {}) {
|
|
|
1175
3465
|
gstyle.textContent = GLOBAL_PAGE_CSS;
|
|
1176
3466
|
document.head.appendChild(gstyle);
|
|
1177
3467
|
}
|
|
1178
|
-
engine = new
|
|
1179
|
-
overlay = new
|
|
3468
|
+
engine = new SelectionEngine({ document, host: shadow.host });
|
|
3469
|
+
overlay = new Overlay({
|
|
1180
3470
|
engine,
|
|
1181
3471
|
root: shadow.root,
|
|
1182
3472
|
document,
|
|
@@ -1201,7 +3491,7 @@ function Clickly({ className } = {}) {
|
|
|
1201
3491
|
document.body.removeAttribute("data-clickly-mode");
|
|
1202
3492
|
};
|
|
1203
3493
|
}, []);
|
|
1204
|
-
|
|
3494
|
+
(0, import_react11.useEffect)(() => {
|
|
1205
3495
|
if (!mount || !className) return;
|
|
1206
3496
|
mount.shadow.host.className = className;
|
|
1207
3497
|
return () => {
|
|
@@ -1209,17 +3499,8 @@ function Clickly({ className } = {}) {
|
|
|
1209
3499
|
};
|
|
1210
3500
|
}, [mount, className]);
|
|
1211
3501
|
if (!mount) return null;
|
|
1212
|
-
return
|
|
1213
|
-
/* @__PURE__ */
|
|
3502
|
+
return (0, import_react_dom.createPortal)(
|
|
3503
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClicklyRoot, { engine: mount.engine, host: mount.shadow.host }),
|
|
1214
3504
|
mount.portal
|
|
1215
3505
|
);
|
|
1216
3506
|
}
|
|
1217
|
-
|
|
1218
|
-
exports.Clickly = Clickly;
|
|
1219
|
-
exports.DEFAULT_SETTINGS = DEFAULTS;
|
|
1220
|
-
exports.annotationsToMarkdown = annotationsToMarkdown;
|
|
1221
|
-
exports.useAnnotations = useAnnotations;
|
|
1222
|
-
exports.useAnnotationsList = useAnnotationsList;
|
|
1223
|
-
exports.useSettings = useSettings;
|
|
1224
|
-
//# sourceMappingURL=index.cjs.map
|
|
1225
|
-
//# sourceMappingURL=index.cjs.map
|