@ourroadmaps/web-sdk 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +314 -0
- package/dist/chunk-4DE2IREA.cjs +9 -0
- package/dist/chunk-4DE2IREA.cjs.map +1 -0
- package/dist/chunk-KGQKTDB4.cjs +537 -0
- package/dist/chunk-KGQKTDB4.cjs.map +1 -0
- package/dist/chunk-MKPDPQXT.js +534 -0
- package/dist/chunk-MKPDPQXT.js.map +1 -0
- package/dist/chunk-V6TY7KAL.js +7 -0
- package/dist/chunk-V6TY7KAL.js.map +1 -0
- package/dist/chunk-XFAAEDJK.js +1251 -0
- package/dist/chunk-XFAAEDJK.js.map +1 -0
- package/dist/chunk-ZPPQHIRO.cjs +1254 -0
- package/dist/chunk-ZPPQHIRO.cjs.map +1 -0
- package/dist/feedback/index.cjs +13 -0
- package/dist/feedback/index.cjs.map +1 -0
- package/dist/feedback/index.d.cts +20 -0
- package/dist/feedback/index.d.ts +20 -0
- package/dist/feedback/index.js +4 -0
- package/dist/feedback/index.js.map +1 -0
- package/dist/feedback.global.js +389 -0
- package/dist/feedback.global.js.map +1 -0
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay/index.cjs +17 -0
- package/dist/overlay/index.cjs.map +1 -0
- package/dist/overlay/index.d.cts +205 -0
- package/dist/overlay/index.d.ts +205 -0
- package/dist/overlay/index.js +4 -0
- package/dist/overlay/index.js.map +1 -0
- package/dist/overlay.global.js +123 -0
- package/dist/overlay.global.js.map +1 -0
- package/dist/types-CEewztln.d.cts +11 -0
- package/dist/types-CEewztln.d.ts +11 -0
- package/package.json +60 -0
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
import { __publicField } from './chunk-V6TY7KAL.js';
|
|
2
|
+
|
|
3
|
+
// src/overlay/targeting.ts
|
|
4
|
+
function isSelectorTarget(target) {
|
|
5
|
+
return typeof target === "object" && "selector" in target;
|
|
6
|
+
}
|
|
7
|
+
function isCursorTarget(target) {
|
|
8
|
+
return typeof target === "object" && "cursor" in target && target.cursor === true;
|
|
9
|
+
}
|
|
10
|
+
function isCoordinates(target) {
|
|
11
|
+
return typeof target === "object" && "x" in target && "y" in target && !("selector" in target);
|
|
12
|
+
}
|
|
13
|
+
function resolveElement(target) {
|
|
14
|
+
if (isSelectorTarget(target)) {
|
|
15
|
+
return document.querySelector(target.selector);
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function getAnchorPosition(rect, anchor = "center") {
|
|
20
|
+
switch (anchor) {
|
|
21
|
+
case "top":
|
|
22
|
+
return { x: rect.left + rect.width / 2, y: rect.top };
|
|
23
|
+
case "bottom":
|
|
24
|
+
return { x: rect.left + rect.width / 2, y: rect.bottom };
|
|
25
|
+
case "left":
|
|
26
|
+
return { x: rect.left, y: rect.top + rect.height / 2 };
|
|
27
|
+
case "right":
|
|
28
|
+
return { x: rect.right, y: rect.top + rect.height / 2 };
|
|
29
|
+
case "top-left":
|
|
30
|
+
return { x: rect.left, y: rect.top };
|
|
31
|
+
case "top-right":
|
|
32
|
+
return { x: rect.right, y: rect.top };
|
|
33
|
+
case "bottom-left":
|
|
34
|
+
return { x: rect.left, y: rect.bottom };
|
|
35
|
+
case "bottom-right":
|
|
36
|
+
return { x: rect.right, y: rect.bottom };
|
|
37
|
+
case "center":
|
|
38
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function resolveCoordinates(target, cursorPosition) {
|
|
42
|
+
if (isCoordinates(target)) {
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
if (isCursorTarget(target)) {
|
|
46
|
+
return cursorPosition;
|
|
47
|
+
}
|
|
48
|
+
if (isSelectorTarget(target)) {
|
|
49
|
+
const element = document.querySelector(target.selector);
|
|
50
|
+
if (!element) {
|
|
51
|
+
return target.fallback ?? null;
|
|
52
|
+
}
|
|
53
|
+
const rect = element.getBoundingClientRect();
|
|
54
|
+
const position = getAnchorPosition(rect, target.anchor);
|
|
55
|
+
if (target.offset) {
|
|
56
|
+
position.x += target.offset.x;
|
|
57
|
+
position.y += target.offset.y;
|
|
58
|
+
}
|
|
59
|
+
return position;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function isInViewport(element) {
|
|
64
|
+
const rect = element.getBoundingClientRect();
|
|
65
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/overlay/animations.ts
|
|
69
|
+
var DURATIONS = {
|
|
70
|
+
cursorMove: 600,
|
|
71
|
+
cursorClick: 150,
|
|
72
|
+
clickRipple: 400,
|
|
73
|
+
annotationIn: 200,
|
|
74
|
+
annotationOut: 150,
|
|
75
|
+
scrollPause: 300
|
|
76
|
+
};
|
|
77
|
+
var EASINGS = {
|
|
78
|
+
easeOut: "cubic-bezier(0, 0, 0.2, 1)",
|
|
79
|
+
easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
80
|
+
linear: "linear"
|
|
81
|
+
};
|
|
82
|
+
function delay(ms) {
|
|
83
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
|
+
}
|
|
85
|
+
function fadeInKeyframes() {
|
|
86
|
+
return [
|
|
87
|
+
{ opacity: 0, transform: "scale(0.9)" },
|
|
88
|
+
{ opacity: 1, transform: "scale(1)" }
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function fadeOutKeyframes() {
|
|
92
|
+
return [{ opacity: 1 }, { opacity: 0 }];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/overlay/annotations/Base.ts
|
|
96
|
+
var BaseAnnotation = class {
|
|
97
|
+
constructor(id, config, container, reducedMotion) {
|
|
98
|
+
__publicField(this, "id");
|
|
99
|
+
__publicField(this, "config");
|
|
100
|
+
__publicField(this, "element", null);
|
|
101
|
+
__publicField(this, "container");
|
|
102
|
+
__publicField(this, "reducedMotion");
|
|
103
|
+
__publicField(this, "hideTimeout", null);
|
|
104
|
+
__publicField(this, "currentAnimation", null);
|
|
105
|
+
this.id = id;
|
|
106
|
+
this.config = config;
|
|
107
|
+
this.container = container;
|
|
108
|
+
this.reducedMotion = reducedMotion;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Render the annotation with animation
|
|
112
|
+
*/
|
|
113
|
+
async show() {
|
|
114
|
+
if (this.config.delay && this.config.delay > 0) {
|
|
115
|
+
await new Promise((r) => setTimeout(r, this.config.delay));
|
|
116
|
+
}
|
|
117
|
+
this.element = this.createElement();
|
|
118
|
+
if (this.config.color) {
|
|
119
|
+
this.element.style.setProperty("--annotation-color", this.config.color);
|
|
120
|
+
}
|
|
121
|
+
this.container.appendChild(this.element);
|
|
122
|
+
const duration = this.reducedMotion ? 1 : DURATIONS.annotationIn;
|
|
123
|
+
this.currentAnimation = this.element.animate(fadeInKeyframes(), {
|
|
124
|
+
duration,
|
|
125
|
+
easing: EASINGS.easeOut,
|
|
126
|
+
fill: "forwards"
|
|
127
|
+
});
|
|
128
|
+
await this.currentAnimation.finished;
|
|
129
|
+
this.currentAnimation = null;
|
|
130
|
+
if (this.config.duration && this.config.duration > 0) {
|
|
131
|
+
this.hideTimeout = setTimeout(() => this.hide(), this.config.duration);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Hide and remove the annotation
|
|
136
|
+
*/
|
|
137
|
+
async hide() {
|
|
138
|
+
if (this.hideTimeout) {
|
|
139
|
+
clearTimeout(this.hideTimeout);
|
|
140
|
+
this.hideTimeout = null;
|
|
141
|
+
}
|
|
142
|
+
if (!this.element) return;
|
|
143
|
+
const duration = this.reducedMotion ? 1 : DURATIONS.annotationOut;
|
|
144
|
+
this.currentAnimation = this.element.animate(fadeOutKeyframes(), {
|
|
145
|
+
duration,
|
|
146
|
+
easing: EASINGS.linear,
|
|
147
|
+
fill: "forwards"
|
|
148
|
+
});
|
|
149
|
+
await this.currentAnimation.finished;
|
|
150
|
+
this.currentAnimation = null;
|
|
151
|
+
this.element.remove();
|
|
152
|
+
this.element = null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Cancel any running animations
|
|
156
|
+
*/
|
|
157
|
+
cancelAnimations() {
|
|
158
|
+
this.currentAnimation?.cancel();
|
|
159
|
+
this.currentAnimation = null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Clean up resources
|
|
163
|
+
*/
|
|
164
|
+
destroy() {
|
|
165
|
+
if (this.hideTimeout) {
|
|
166
|
+
clearTimeout(this.hideTimeout);
|
|
167
|
+
}
|
|
168
|
+
this.cancelAnimations();
|
|
169
|
+
this.element?.remove();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/overlay/annotations/Arrow.ts
|
|
174
|
+
var ARROWHEAD_LENGTH = 12;
|
|
175
|
+
var ARROWHEAD_WIDTH = 8;
|
|
176
|
+
var Arrow = class extends BaseAnnotation {
|
|
177
|
+
constructor(id, config, container, reducedMotion, cursorPosition) {
|
|
178
|
+
super(id, config, container, reducedMotion);
|
|
179
|
+
__publicField(this, "cursorPosition");
|
|
180
|
+
__publicField(this, "pathElement", null);
|
|
181
|
+
__publicField(this, "markerDef", null);
|
|
182
|
+
this.cursorPosition = cursorPosition;
|
|
183
|
+
}
|
|
184
|
+
createElement() {
|
|
185
|
+
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
186
|
+
g.classList.add("arrow");
|
|
187
|
+
const markerId = `arrowhead-${this.id}`;
|
|
188
|
+
this.markerDef = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
189
|
+
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
190
|
+
marker.setAttribute("id", markerId);
|
|
191
|
+
marker.setAttribute("markerWidth", String(ARROWHEAD_LENGTH));
|
|
192
|
+
marker.setAttribute("markerHeight", String(ARROWHEAD_WIDTH));
|
|
193
|
+
marker.setAttribute("refX", String(ARROWHEAD_LENGTH));
|
|
194
|
+
marker.setAttribute("refY", String(ARROWHEAD_WIDTH / 2));
|
|
195
|
+
marker.setAttribute("orient", "auto");
|
|
196
|
+
marker.setAttribute("markerUnits", "userSpaceOnUse");
|
|
197
|
+
const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
198
|
+
polygon.setAttribute(
|
|
199
|
+
"points",
|
|
200
|
+
`0,0 ${ARROWHEAD_LENGTH},${ARROWHEAD_WIDTH / 2} 0,${ARROWHEAD_WIDTH}`
|
|
201
|
+
);
|
|
202
|
+
polygon.setAttribute("fill", "currentColor");
|
|
203
|
+
marker.appendChild(polygon);
|
|
204
|
+
this.markerDef.appendChild(marker);
|
|
205
|
+
g.appendChild(this.markerDef);
|
|
206
|
+
this.pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
207
|
+
this.pathElement.setAttribute("marker-end", `url(#${markerId})`);
|
|
208
|
+
this.pathElement.setAttribute("fill", "none");
|
|
209
|
+
this.pathElement.setAttribute("stroke", "currentColor");
|
|
210
|
+
g.appendChild(this.pathElement);
|
|
211
|
+
return g;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Update the cursor position for relative positioning
|
|
215
|
+
*/
|
|
216
|
+
setCursorPosition(coords) {
|
|
217
|
+
this.cursorPosition = coords;
|
|
218
|
+
if (this.element) {
|
|
219
|
+
this.updatePosition();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
resolveTargetPoint(target) {
|
|
223
|
+
if (isCoordinates(target)) {
|
|
224
|
+
return resolveCoordinates(target, this.cursorPosition);
|
|
225
|
+
}
|
|
226
|
+
if (isCursorTarget(target)) {
|
|
227
|
+
return this.cursorPosition;
|
|
228
|
+
}
|
|
229
|
+
if (isSelectorTarget(target)) {
|
|
230
|
+
const element = document.querySelector(target.selector);
|
|
231
|
+
if (!element) {
|
|
232
|
+
return target.fallback ?? null;
|
|
233
|
+
}
|
|
234
|
+
const rect = element.getBoundingClientRect();
|
|
235
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
updatePosition() {
|
|
240
|
+
if (!this.pathElement) return;
|
|
241
|
+
const from = this.config.from;
|
|
242
|
+
const to = this.config.to;
|
|
243
|
+
const startPoint = this.resolveTargetPoint(from);
|
|
244
|
+
const endPoint = this.resolveTargetPoint(to);
|
|
245
|
+
if (!startPoint || !endPoint) return;
|
|
246
|
+
const d = `M ${startPoint.x} ${startPoint.y} L ${endPoint.x} ${endPoint.y}`;
|
|
247
|
+
this.pathElement.setAttribute("d", d);
|
|
248
|
+
}
|
|
249
|
+
async show() {
|
|
250
|
+
await super.show();
|
|
251
|
+
this.updatePosition();
|
|
252
|
+
}
|
|
253
|
+
destroy() {
|
|
254
|
+
this.pathElement = null;
|
|
255
|
+
this.markerDef = null;
|
|
256
|
+
super.destroy();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/overlay/annotations/Badge.ts
|
|
261
|
+
var Badge = class extends BaseAnnotation {
|
|
262
|
+
constructor(id, config, container, reducedMotion, cursorPosition) {
|
|
263
|
+
super(id, config, container, reducedMotion);
|
|
264
|
+
__publicField(this, "cursorPosition");
|
|
265
|
+
this.cursorPosition = cursorPosition;
|
|
266
|
+
}
|
|
267
|
+
createElement() {
|
|
268
|
+
const badge = document.createElement("div");
|
|
269
|
+
badge.className = "badge";
|
|
270
|
+
badge.textContent = String(this.config.number);
|
|
271
|
+
const coords = resolveCoordinates(this.config.target, this.cursorPosition);
|
|
272
|
+
if (coords) {
|
|
273
|
+
badge.style.left = `${coords.x}px`;
|
|
274
|
+
badge.style.top = `${coords.y}px`;
|
|
275
|
+
}
|
|
276
|
+
return badge;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/overlay/annotations/Box.ts
|
|
281
|
+
var DEFAULT_PADDING = 4;
|
|
282
|
+
var DEFAULT_RADIUS = 4;
|
|
283
|
+
var Box = class extends BaseAnnotation {
|
|
284
|
+
constructor(id, config, container, reducedMotion, cursorPosition) {
|
|
285
|
+
super(id, config, container, reducedMotion);
|
|
286
|
+
__publicField(this, "cursorPosition");
|
|
287
|
+
this.cursorPosition = cursorPosition;
|
|
288
|
+
}
|
|
289
|
+
createElement() {
|
|
290
|
+
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
291
|
+
rect.classList.add("box");
|
|
292
|
+
rect.setAttribute("rx", String(DEFAULT_RADIUS));
|
|
293
|
+
rect.setAttribute("ry", String(DEFAULT_RADIUS));
|
|
294
|
+
if (this.config.dashed) {
|
|
295
|
+
rect.setAttribute("stroke-dasharray", "8 4");
|
|
296
|
+
}
|
|
297
|
+
return rect;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Update the cursor position for relative positioning
|
|
301
|
+
*/
|
|
302
|
+
setCursorPosition(coords) {
|
|
303
|
+
this.cursorPosition = coords;
|
|
304
|
+
if (this.element) {
|
|
305
|
+
this.updatePosition();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
updatePosition() {
|
|
309
|
+
if (!this.element) return;
|
|
310
|
+
const rect = this.element;
|
|
311
|
+
const target = this.config.target;
|
|
312
|
+
const padding = this.config.padding ?? DEFAULT_PADDING;
|
|
313
|
+
let bounds;
|
|
314
|
+
if (isSelectorTarget(target)) {
|
|
315
|
+
const el = document.querySelector(target.selector);
|
|
316
|
+
if (!el) return;
|
|
317
|
+
bounds = el.getBoundingClientRect();
|
|
318
|
+
} else if (isCoordinates(target)) {
|
|
319
|
+
const resolved = resolveCoordinates(target, this.cursorPosition);
|
|
320
|
+
if (!resolved) return;
|
|
321
|
+
const width = this.config.size?.width ?? 100;
|
|
322
|
+
const height = this.config.size?.height ?? 100;
|
|
323
|
+
bounds = new DOMRect(resolved.x, resolved.y, width, height);
|
|
324
|
+
} else {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
rect.setAttribute("x", String(bounds.left - padding));
|
|
328
|
+
rect.setAttribute("y", String(bounds.top - padding));
|
|
329
|
+
rect.setAttribute("width", String(bounds.width + padding * 2));
|
|
330
|
+
rect.setAttribute("height", String(bounds.height + padding * 2));
|
|
331
|
+
}
|
|
332
|
+
async show() {
|
|
333
|
+
await super.show();
|
|
334
|
+
this.updatePosition();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/overlay/annotations/Circle.ts
|
|
339
|
+
var DEFAULT_PADDING2 = 4;
|
|
340
|
+
var Circle = class extends BaseAnnotation {
|
|
341
|
+
constructor(id, config, container, reducedMotion, cursorPosition) {
|
|
342
|
+
super(id, config, container, reducedMotion);
|
|
343
|
+
__publicField(this, "cursorPosition");
|
|
344
|
+
this.cursorPosition = cursorPosition;
|
|
345
|
+
}
|
|
346
|
+
createElement() {
|
|
347
|
+
const ellipse = document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
|
|
348
|
+
ellipse.classList.add("circle");
|
|
349
|
+
return ellipse;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Update the cursor position for relative positioning
|
|
353
|
+
*/
|
|
354
|
+
setCursorPosition(coords) {
|
|
355
|
+
this.cursorPosition = coords;
|
|
356
|
+
if (this.element) {
|
|
357
|
+
this.updatePosition();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
updatePosition() {
|
|
361
|
+
if (!this.element) return;
|
|
362
|
+
const ellipse = this.element;
|
|
363
|
+
const target = this.config.target;
|
|
364
|
+
const padding = this.config.padding ?? DEFAULT_PADDING2;
|
|
365
|
+
let bounds;
|
|
366
|
+
if (isSelectorTarget(target)) {
|
|
367
|
+
const el = document.querySelector(target.selector);
|
|
368
|
+
if (!el) return;
|
|
369
|
+
bounds = el.getBoundingClientRect();
|
|
370
|
+
} else if (isCoordinates(target)) {
|
|
371
|
+
const resolved = resolveCoordinates(target, this.cursorPosition);
|
|
372
|
+
if (!resolved) return;
|
|
373
|
+
const width = this.config.size?.width ?? 100;
|
|
374
|
+
const height = this.config.size?.height ?? 100;
|
|
375
|
+
bounds = new DOMRect(resolved.x, resolved.y, width, height);
|
|
376
|
+
} else {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const cx = bounds.left + bounds.width / 2;
|
|
380
|
+
const cy = bounds.top + bounds.height / 2;
|
|
381
|
+
const rx = bounds.width / 2 + padding;
|
|
382
|
+
const ry = bounds.height / 2 + padding;
|
|
383
|
+
ellipse.setAttribute("cx", String(cx));
|
|
384
|
+
ellipse.setAttribute("cy", String(cy));
|
|
385
|
+
ellipse.setAttribute("rx", String(rx));
|
|
386
|
+
ellipse.setAttribute("ry", String(ry));
|
|
387
|
+
}
|
|
388
|
+
async show() {
|
|
389
|
+
await super.show();
|
|
390
|
+
this.updatePosition();
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/overlay/annotations/Label.ts
|
|
395
|
+
var DEFAULT_GAP = 8;
|
|
396
|
+
var Label = class extends BaseAnnotation {
|
|
397
|
+
constructor(id, config, container, reducedMotion, cursorPosition) {
|
|
398
|
+
super(id, config, container, reducedMotion);
|
|
399
|
+
__publicField(this, "cursorPosition");
|
|
400
|
+
this.cursorPosition = cursorPosition;
|
|
401
|
+
}
|
|
402
|
+
createElement() {
|
|
403
|
+
const el = document.createElement("div");
|
|
404
|
+
el.className = "label";
|
|
405
|
+
el.textContent = this.config.text;
|
|
406
|
+
return el;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Update the cursor position for relative positioning
|
|
410
|
+
*/
|
|
411
|
+
setCursorPosition(coords) {
|
|
412
|
+
this.cursorPosition = coords;
|
|
413
|
+
if (this.element) {
|
|
414
|
+
this.updatePosition();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
updatePosition() {
|
|
418
|
+
if (!this.element) return;
|
|
419
|
+
const target = this.config.target;
|
|
420
|
+
const position = this.config.position || "top";
|
|
421
|
+
const gap = this.config.gap ?? DEFAULT_GAP;
|
|
422
|
+
let anchorRect;
|
|
423
|
+
if (isCursorTarget(target)) {
|
|
424
|
+
if (!this.cursorPosition) return;
|
|
425
|
+
anchorRect = new DOMRect(this.cursorPosition.x, this.cursorPosition.y, 0, 0);
|
|
426
|
+
} else if (isCoordinates(target)) {
|
|
427
|
+
const resolved = resolveCoordinates(target, this.cursorPosition);
|
|
428
|
+
if (!resolved) return;
|
|
429
|
+
anchorRect = new DOMRect(resolved.x, resolved.y, 0, 0);
|
|
430
|
+
} else if (isSelectorTarget(target)) {
|
|
431
|
+
const el = document.querySelector(target.selector);
|
|
432
|
+
if (!el) return;
|
|
433
|
+
anchorRect = el.getBoundingClientRect();
|
|
434
|
+
} else {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const coords = this.calculatePosition(anchorRect, position, gap);
|
|
438
|
+
this.element.style.left = `${coords.x}px`;
|
|
439
|
+
this.element.style.top = `${coords.y}px`;
|
|
440
|
+
this.element.style.transform = this.getTransform(position);
|
|
441
|
+
}
|
|
442
|
+
calculatePosition(rect, position, gap) {
|
|
443
|
+
const centerX = rect.left + rect.width / 2;
|
|
444
|
+
const centerY = rect.top + rect.height / 2;
|
|
445
|
+
switch (position) {
|
|
446
|
+
case "top":
|
|
447
|
+
return { x: centerX, y: rect.top - gap };
|
|
448
|
+
case "top-left":
|
|
449
|
+
return { x: rect.left, y: rect.top - gap };
|
|
450
|
+
case "top-right":
|
|
451
|
+
return { x: rect.right, y: rect.top - gap };
|
|
452
|
+
case "bottom":
|
|
453
|
+
return { x: centerX, y: rect.bottom + gap };
|
|
454
|
+
case "bottom-left":
|
|
455
|
+
return { x: rect.left, y: rect.bottom + gap };
|
|
456
|
+
case "bottom-right":
|
|
457
|
+
return { x: rect.right, y: rect.bottom + gap };
|
|
458
|
+
case "left":
|
|
459
|
+
return { x: rect.left - gap, y: centerY };
|
|
460
|
+
case "right":
|
|
461
|
+
return { x: rect.right + gap, y: centerY };
|
|
462
|
+
default:
|
|
463
|
+
return { x: centerX, y: rect.top - gap };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
getTransform(position) {
|
|
467
|
+
switch (position) {
|
|
468
|
+
case "top":
|
|
469
|
+
case "bottom":
|
|
470
|
+
return "translate(-50%, -100%)";
|
|
471
|
+
case "top-left":
|
|
472
|
+
case "bottom-left":
|
|
473
|
+
return "translate(0, -100%)";
|
|
474
|
+
case "top-right":
|
|
475
|
+
case "bottom-right":
|
|
476
|
+
return "translate(-100%, -100%)";
|
|
477
|
+
case "left":
|
|
478
|
+
return "translate(-100%, -50%)";
|
|
479
|
+
case "right":
|
|
480
|
+
return "translate(0, -50%)";
|
|
481
|
+
default:
|
|
482
|
+
return "translate(-50%, -100%)";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async show() {
|
|
486
|
+
await super.show();
|
|
487
|
+
this.updatePosition();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/overlay/AnnotationManager.ts
|
|
492
|
+
var idCounter = 0;
|
|
493
|
+
function generateId() {
|
|
494
|
+
return `ann-${++idCounter}`;
|
|
495
|
+
}
|
|
496
|
+
var AnnotationManager = class {
|
|
497
|
+
constructor(config, domContainer, svgContainer, reducedMotion, getCursorPosition) {
|
|
498
|
+
__publicField(this, "config");
|
|
499
|
+
__publicField(this, "domContainer");
|
|
500
|
+
__publicField(this, "svgContainer");
|
|
501
|
+
__publicField(this, "annotations", /* @__PURE__ */ new Map());
|
|
502
|
+
__publicField(this, "reducedMotion");
|
|
503
|
+
__publicField(this, "getCursorPosition");
|
|
504
|
+
this.config = config;
|
|
505
|
+
this.domContainer = domContainer;
|
|
506
|
+
this.svgContainer = svgContainer;
|
|
507
|
+
this.reducedMotion = reducedMotion;
|
|
508
|
+
this.getCursorPosition = getCursorPosition;
|
|
509
|
+
}
|
|
510
|
+
get activeIds() {
|
|
511
|
+
return Array.from(this.annotations.keys());
|
|
512
|
+
}
|
|
513
|
+
show(input) {
|
|
514
|
+
if (Array.isArray(input)) {
|
|
515
|
+
return input.map((a) => this.showOne(a));
|
|
516
|
+
}
|
|
517
|
+
return this.showOne(input);
|
|
518
|
+
}
|
|
519
|
+
showOne(annotation) {
|
|
520
|
+
const id = annotation.id ?? generateId();
|
|
521
|
+
const cursorPos = this.getCursorPosition();
|
|
522
|
+
const annotationWithDefaults = {
|
|
523
|
+
...annotation,
|
|
524
|
+
color: annotation.color ?? this.config.color
|
|
525
|
+
};
|
|
526
|
+
let instance;
|
|
527
|
+
switch (annotation.type) {
|
|
528
|
+
case "arrow":
|
|
529
|
+
instance = new Arrow(
|
|
530
|
+
id,
|
|
531
|
+
annotationWithDefaults,
|
|
532
|
+
this.svgContainer,
|
|
533
|
+
// SVG for arrows
|
|
534
|
+
this.reducedMotion,
|
|
535
|
+
cursorPos
|
|
536
|
+
);
|
|
537
|
+
break;
|
|
538
|
+
case "badge":
|
|
539
|
+
instance = new Badge(
|
|
540
|
+
id,
|
|
541
|
+
annotationWithDefaults,
|
|
542
|
+
this.domContainer,
|
|
543
|
+
this.reducedMotion,
|
|
544
|
+
cursorPos
|
|
545
|
+
);
|
|
546
|
+
break;
|
|
547
|
+
case "box":
|
|
548
|
+
instance = new Box(
|
|
549
|
+
id,
|
|
550
|
+
annotationWithDefaults,
|
|
551
|
+
this.svgContainer,
|
|
552
|
+
// SVG for boxes
|
|
553
|
+
this.reducedMotion,
|
|
554
|
+
cursorPos
|
|
555
|
+
);
|
|
556
|
+
break;
|
|
557
|
+
case "circle":
|
|
558
|
+
instance = new Circle(
|
|
559
|
+
id,
|
|
560
|
+
annotationWithDefaults,
|
|
561
|
+
this.svgContainer,
|
|
562
|
+
// SVG for circles
|
|
563
|
+
this.reducedMotion,
|
|
564
|
+
cursorPos
|
|
565
|
+
);
|
|
566
|
+
break;
|
|
567
|
+
case "label":
|
|
568
|
+
instance = new Label(
|
|
569
|
+
id,
|
|
570
|
+
annotationWithDefaults,
|
|
571
|
+
this.domContainer,
|
|
572
|
+
this.reducedMotion,
|
|
573
|
+
cursorPos
|
|
574
|
+
);
|
|
575
|
+
break;
|
|
576
|
+
default:
|
|
577
|
+
throw new Error(`Unknown annotation type: ${annotation.type}`);
|
|
578
|
+
}
|
|
579
|
+
this.annotations.set(id, instance);
|
|
580
|
+
instance.show();
|
|
581
|
+
return id;
|
|
582
|
+
}
|
|
583
|
+
hide(input) {
|
|
584
|
+
const ids = Array.isArray(input) ? input : [input];
|
|
585
|
+
for (const id of ids) {
|
|
586
|
+
const annotation = this.annotations.get(id);
|
|
587
|
+
if (annotation) {
|
|
588
|
+
annotation.hide();
|
|
589
|
+
this.annotations.delete(id);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
hideAll() {
|
|
594
|
+
for (const annotation of this.annotations.values()) {
|
|
595
|
+
annotation.hide();
|
|
596
|
+
}
|
|
597
|
+
this.annotations.clear();
|
|
598
|
+
}
|
|
599
|
+
// Convenience methods
|
|
600
|
+
arrow(from, to, options) {
|
|
601
|
+
return this.show({ type: "arrow", from, to, ...options });
|
|
602
|
+
}
|
|
603
|
+
badge(target, number, options) {
|
|
604
|
+
return this.show({ type: "badge", target, number, ...options });
|
|
605
|
+
}
|
|
606
|
+
box(target, options) {
|
|
607
|
+
return this.show({ type: "box", target, ...options });
|
|
608
|
+
}
|
|
609
|
+
circle(target, options) {
|
|
610
|
+
return this.show({ type: "circle", target, ...options });
|
|
611
|
+
}
|
|
612
|
+
label(target, text, options) {
|
|
613
|
+
return this.show({ type: "label", target, text, ...options });
|
|
614
|
+
}
|
|
615
|
+
cancelAllAnimations() {
|
|
616
|
+
for (const annotation of this.annotations.values()) {
|
|
617
|
+
annotation.cancelAnimations();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
destroy() {
|
|
621
|
+
for (const annotation of this.annotations.values()) {
|
|
622
|
+
annotation.destroy();
|
|
623
|
+
}
|
|
624
|
+
this.annotations.clear();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/shared/colors.ts
|
|
629
|
+
function hexToRgb(hex) {
|
|
630
|
+
const cleaned = hex.replace("#", "");
|
|
631
|
+
const bigint = parseInt(cleaned, 16);
|
|
632
|
+
return [bigint >> 16 & 255, bigint >> 8 & 255, bigint & 255];
|
|
633
|
+
}
|
|
634
|
+
function getLuminance(hex) {
|
|
635
|
+
const [r, g, b] = hexToRgb(hex).map((c) => {
|
|
636
|
+
const sRGB = c / 255;
|
|
637
|
+
return sRGB <= 0.03928 ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4;
|
|
638
|
+
});
|
|
639
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
640
|
+
}
|
|
641
|
+
function getContrastColor(hex) {
|
|
642
|
+
return getLuminance(hex) > 0.5 ? "black" : "white";
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/overlay/scroller.ts
|
|
646
|
+
async function scrollIntoViewIfNeeded(element) {
|
|
647
|
+
const rect = element.getBoundingClientRect();
|
|
648
|
+
const isFullyVisible = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
|
|
649
|
+
if (isFullyVisible) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
return new Promise((resolve) => {
|
|
653
|
+
element.scrollIntoView({
|
|
654
|
+
behavior: "smooth",
|
|
655
|
+
block: "center",
|
|
656
|
+
inline: "center"
|
|
657
|
+
});
|
|
658
|
+
const observer = new IntersectionObserver(
|
|
659
|
+
(entries) => {
|
|
660
|
+
const entry = entries[0];
|
|
661
|
+
if (entry?.isIntersecting) {
|
|
662
|
+
observer.disconnect();
|
|
663
|
+
resolve();
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
{ threshold: 0.5 }
|
|
667
|
+
);
|
|
668
|
+
observer.observe(element);
|
|
669
|
+
setTimeout(() => {
|
|
670
|
+
observer.disconnect();
|
|
671
|
+
resolve();
|
|
672
|
+
}, 1e3);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/overlay/Cursor.ts
|
|
677
|
+
var CURSOR_ARROW_SVG = `<svg class="cursor-arrow" viewBox="0 0 24 24">
|
|
678
|
+
<path d="M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87a.5.5 0 0 0 .35-.85L6.35 2.85a.5.5 0 0 0-.85.36Z"/>
|
|
679
|
+
</svg>`;
|
|
680
|
+
var Cursor = class {
|
|
681
|
+
constructor(container, reducedMotion) {
|
|
682
|
+
__publicField(this, "container");
|
|
683
|
+
__publicField(this, "element");
|
|
684
|
+
__publicField(this, "labelElement");
|
|
685
|
+
__publicField(this, "rippleElement");
|
|
686
|
+
__publicField(this, "currentAnimation", null);
|
|
687
|
+
__publicField(this, "activeRipples", /* @__PURE__ */ new Set());
|
|
688
|
+
__publicField(this, "_position", null);
|
|
689
|
+
__publicField(this, "_isVisible", true);
|
|
690
|
+
__publicField(this, "reducedMotion");
|
|
691
|
+
this.container = container;
|
|
692
|
+
this.reducedMotion = reducedMotion;
|
|
693
|
+
this.element = document.createElement("div");
|
|
694
|
+
this.element.className = "cursor";
|
|
695
|
+
this.element.innerHTML = `${CURSOR_ARROW_SVG}<div class="cursor-label">Guide</div>`;
|
|
696
|
+
this.labelElement = this.element.querySelector(".cursor-label");
|
|
697
|
+
this.rippleElement = document.createElement("div");
|
|
698
|
+
this.rippleElement.className = "click-ripple";
|
|
699
|
+
container.appendChild(this.element);
|
|
700
|
+
container.appendChild(this.rippleElement);
|
|
701
|
+
}
|
|
702
|
+
get position() {
|
|
703
|
+
return this._position;
|
|
704
|
+
}
|
|
705
|
+
get isVisible() {
|
|
706
|
+
return this._isVisible;
|
|
707
|
+
}
|
|
708
|
+
show() {
|
|
709
|
+
this._isVisible = true;
|
|
710
|
+
this.element.classList.remove("hidden");
|
|
711
|
+
}
|
|
712
|
+
hide() {
|
|
713
|
+
this._isVisible = false;
|
|
714
|
+
this.element.classList.add("hidden");
|
|
715
|
+
}
|
|
716
|
+
setName(name) {
|
|
717
|
+
this.labelElement.textContent = name;
|
|
718
|
+
}
|
|
719
|
+
setColor(color) {
|
|
720
|
+
this.element.style.setProperty("--cursor-color", color);
|
|
721
|
+
this.element.style.setProperty("--cursor-text-color", getContrastColor(color));
|
|
722
|
+
}
|
|
723
|
+
async moveTo(target, options) {
|
|
724
|
+
const el = resolveElement(target);
|
|
725
|
+
if (el && !isInViewport(el)) {
|
|
726
|
+
await scrollIntoViewIfNeeded(el);
|
|
727
|
+
await delay(DURATIONS.scrollPause);
|
|
728
|
+
}
|
|
729
|
+
const coords = resolveCoordinates(target, this._position);
|
|
730
|
+
if (!coords) {
|
|
731
|
+
console.warn("[Overlay] Target not found:", target);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
this.currentAnimation?.cancel();
|
|
735
|
+
const duration = this.reducedMotion ? 1 : options?.duration ?? DURATIONS.cursorMove;
|
|
736
|
+
const from = this._position ?? coords;
|
|
737
|
+
this.currentAnimation = this.element.animate(
|
|
738
|
+
[
|
|
739
|
+
{ transform: `translate(${from.x}px, ${from.y}px)` },
|
|
740
|
+
{ transform: `translate(${coords.x}px, ${coords.y}px)` }
|
|
741
|
+
],
|
|
742
|
+
{ duration, easing: EASINGS.easeInOut, fill: "forwards" }
|
|
743
|
+
);
|
|
744
|
+
try {
|
|
745
|
+
await this.currentAnimation.finished;
|
|
746
|
+
this._position = coords;
|
|
747
|
+
} catch {
|
|
748
|
+
} finally {
|
|
749
|
+
this.currentAnimation = null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async click(target) {
|
|
753
|
+
if (target) {
|
|
754
|
+
await this.moveTo(target);
|
|
755
|
+
}
|
|
756
|
+
if (!this._position) return;
|
|
757
|
+
const duration = this.reducedMotion ? 1 : DURATIONS.cursorClick;
|
|
758
|
+
const pressAnimation = this.element.animate(
|
|
759
|
+
[
|
|
760
|
+
{ transform: `translate(${this._position.x}px, ${this._position.y}px) scale(1)` },
|
|
761
|
+
{ transform: `translate(${this._position.x}px, ${this._position.y}px) scale(0.85)` },
|
|
762
|
+
{ transform: `translate(${this._position.x}px, ${this._position.y}px) scale(1)` }
|
|
763
|
+
],
|
|
764
|
+
{ duration, easing: EASINGS.easeOut }
|
|
765
|
+
);
|
|
766
|
+
this.showRipple(this._position);
|
|
767
|
+
await pressAnimation.finished;
|
|
768
|
+
}
|
|
769
|
+
showRipple(position) {
|
|
770
|
+
const ripple = this.rippleElement.cloneNode();
|
|
771
|
+
ripple.style.left = `${position.x}px`;
|
|
772
|
+
ripple.style.top = `${position.y}px`;
|
|
773
|
+
this.container.appendChild(ripple);
|
|
774
|
+
this.activeRipples.add(ripple);
|
|
775
|
+
const duration = this.reducedMotion ? 1 : DURATIONS.clickRipple;
|
|
776
|
+
const animation = ripple.animate(
|
|
777
|
+
[
|
|
778
|
+
{ opacity: 0.4, transform: "translate(-50%, -50%) scale(0)" },
|
|
779
|
+
{ opacity: 0, transform: "translate(-50%, -50%) scale(2)" }
|
|
780
|
+
],
|
|
781
|
+
{ duration, easing: EASINGS.easeOut }
|
|
782
|
+
);
|
|
783
|
+
animation.onfinish = () => {
|
|
784
|
+
ripple.remove();
|
|
785
|
+
this.activeRipples.delete(ripple);
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
cancelAnimations() {
|
|
789
|
+
this.currentAnimation?.cancel();
|
|
790
|
+
this.currentAnimation = null;
|
|
791
|
+
for (const r of this.activeRipples) {
|
|
792
|
+
r.remove();
|
|
793
|
+
}
|
|
794
|
+
this.activeRipples.clear();
|
|
795
|
+
}
|
|
796
|
+
destroy() {
|
|
797
|
+
this.cancelAnimations();
|
|
798
|
+
this.element.remove();
|
|
799
|
+
this.rippleElement.remove();
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// src/overlay/styles.ts
|
|
804
|
+
var OVERLAY_STYLES = `
|
|
805
|
+
:host {
|
|
806
|
+
all: initial;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.overlay-container {
|
|
810
|
+
position: fixed;
|
|
811
|
+
inset: 0;
|
|
812
|
+
pointer-events: none;
|
|
813
|
+
z-index: var(--overlay-z-index, 9999);
|
|
814
|
+
overflow: hidden;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/* Cursor */
|
|
818
|
+
.cursor {
|
|
819
|
+
position: absolute;
|
|
820
|
+
display: flex;
|
|
821
|
+
align-items: flex-start;
|
|
822
|
+
gap: 2px;
|
|
823
|
+
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
|
|
824
|
+
will-change: transform;
|
|
825
|
+
transition: opacity 0.15s ease-out;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.cursor.hidden {
|
|
829
|
+
opacity: 0;
|
|
830
|
+
pointer-events: none;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.cursor-arrow {
|
|
834
|
+
width: 20px;
|
|
835
|
+
height: 20px;
|
|
836
|
+
fill: var(--cursor-color, #3B82F6);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.cursor-label {
|
|
840
|
+
background: var(--cursor-color, #3B82F6);
|
|
841
|
+
color: var(--cursor-text-color, white);
|
|
842
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
843
|
+
font-size: 12px;
|
|
844
|
+
font-weight: 500;
|
|
845
|
+
padding: 4px 8px;
|
|
846
|
+
border-radius: 4px;
|
|
847
|
+
white-space: nowrap;
|
|
848
|
+
margin-top: 12px;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* Click Ripple */
|
|
852
|
+
.click-ripple {
|
|
853
|
+
position: absolute;
|
|
854
|
+
width: 24px;
|
|
855
|
+
height: 24px;
|
|
856
|
+
border-radius: 50%;
|
|
857
|
+
background: var(--cursor-color, #3B82F6);
|
|
858
|
+
opacity: 0;
|
|
859
|
+
transform: translate(-50%, -50%) scale(0);
|
|
860
|
+
pointer-events: none;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* Annotations SVG Layer */
|
|
864
|
+
.annotations-svg {
|
|
865
|
+
position: absolute;
|
|
866
|
+
inset: 0;
|
|
867
|
+
width: 100%;
|
|
868
|
+
height: 100%;
|
|
869
|
+
overflow: visible;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* Annotations DOM Layer */
|
|
873
|
+
.annotations-dom {
|
|
874
|
+
position: absolute;
|
|
875
|
+
inset: 0;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/* Badge */
|
|
879
|
+
.badge {
|
|
880
|
+
position: absolute;
|
|
881
|
+
width: 28px;
|
|
882
|
+
height: 28px;
|
|
883
|
+
border-radius: 50%;
|
|
884
|
+
background: var(--annotation-color, #EF4444);
|
|
885
|
+
color: white;
|
|
886
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
887
|
+
font-size: 14px;
|
|
888
|
+
font-weight: 600;
|
|
889
|
+
display: flex;
|
|
890
|
+
align-items: center;
|
|
891
|
+
justify-content: center;
|
|
892
|
+
transform: translate(-50%, -50%);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/* Label */
|
|
896
|
+
.label {
|
|
897
|
+
position: absolute;
|
|
898
|
+
background: var(--annotation-color, #EF4444);
|
|
899
|
+
color: white;
|
|
900
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
901
|
+
font-size: 14px;
|
|
902
|
+
font-weight: 500;
|
|
903
|
+
padding: 6px 12px;
|
|
904
|
+
border-radius: 6px;
|
|
905
|
+
max-width: 280px;
|
|
906
|
+
overflow: hidden;
|
|
907
|
+
text-overflow: ellipsis;
|
|
908
|
+
display: -webkit-box;
|
|
909
|
+
-webkit-line-clamp: 3;
|
|
910
|
+
-webkit-box-orient: vertical;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/* Note: Box, Circle, and Arrow annotations use SVG elements
|
|
914
|
+
styled via attributes, not CSS classes */
|
|
915
|
+
|
|
916
|
+
/* Reduced Motion */
|
|
917
|
+
@media (prefers-reduced-motion: reduce) {
|
|
918
|
+
*, *::before, *::after {
|
|
919
|
+
animation-duration: 0.01ms !important;
|
|
920
|
+
transition-duration: 0.01ms !important;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
`;
|
|
924
|
+
|
|
925
|
+
// src/overlay/Timeline.ts
|
|
926
|
+
var Timeline = class {
|
|
927
|
+
constructor(cursor, annotations, callbacks) {
|
|
928
|
+
__publicField(this, "cursor");
|
|
929
|
+
__publicField(this, "annotations");
|
|
930
|
+
__publicField(this, "callbacks");
|
|
931
|
+
__publicField(this, "isRunning", false);
|
|
932
|
+
__publicField(this, "currentIndex", 0);
|
|
933
|
+
__publicField(this, "abortController", null);
|
|
934
|
+
this.cursor = cursor;
|
|
935
|
+
this.annotations = annotations;
|
|
936
|
+
this.callbacks = callbacks;
|
|
937
|
+
}
|
|
938
|
+
get running() {
|
|
939
|
+
return this.isRunning;
|
|
940
|
+
}
|
|
941
|
+
get actionIndex() {
|
|
942
|
+
return this.isRunning ? this.currentIndex : null;
|
|
943
|
+
}
|
|
944
|
+
async play(script) {
|
|
945
|
+
if (this.isRunning) {
|
|
946
|
+
console.warn("[Overlay] Stopping current playback to start new script");
|
|
947
|
+
this.stop();
|
|
948
|
+
}
|
|
949
|
+
this.isRunning = true;
|
|
950
|
+
this.currentIndex = 0;
|
|
951
|
+
this.abortController = new AbortController();
|
|
952
|
+
if (script.cursor) {
|
|
953
|
+
if (script.cursor.name) this.cursor.setName(script.cursor.name);
|
|
954
|
+
if (script.cursor.color) this.cursor.setColor(script.cursor.color);
|
|
955
|
+
if (script.cursor.visible === false) this.cursor.hide();
|
|
956
|
+
else this.cursor.show();
|
|
957
|
+
}
|
|
958
|
+
this.callbacks.onStart();
|
|
959
|
+
try {
|
|
960
|
+
for (let i = 0; i < script.actions.length; i++) {
|
|
961
|
+
if (!this.isRunning) break;
|
|
962
|
+
this.currentIndex = i;
|
|
963
|
+
const action = script.actions[i];
|
|
964
|
+
this.callbacks.onAction(action, i, "start");
|
|
965
|
+
try {
|
|
966
|
+
await this.executeAction(action);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
this.callbacks.onError(err, action);
|
|
972
|
+
}
|
|
973
|
+
this.callbacks.onAction(action, i, "complete");
|
|
974
|
+
}
|
|
975
|
+
if (this.isRunning) {
|
|
976
|
+
this.callbacks.onComplete();
|
|
977
|
+
}
|
|
978
|
+
} finally {
|
|
979
|
+
this.isRunning = false;
|
|
980
|
+
this.abortController = null;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
stop() {
|
|
984
|
+
if (!this.isRunning) return;
|
|
985
|
+
this.isRunning = false;
|
|
986
|
+
this.abortController?.abort();
|
|
987
|
+
this.cursor.cancelAnimations();
|
|
988
|
+
this.annotations.cancelAllAnimations();
|
|
989
|
+
this.callbacks.onStop();
|
|
990
|
+
}
|
|
991
|
+
async executeAction(action) {
|
|
992
|
+
switch (action.type) {
|
|
993
|
+
case "move":
|
|
994
|
+
await this.executeMove(action);
|
|
995
|
+
break;
|
|
996
|
+
case "click":
|
|
997
|
+
await this.executeClick(action);
|
|
998
|
+
break;
|
|
999
|
+
case "wait":
|
|
1000
|
+
await this.executeWait(action);
|
|
1001
|
+
break;
|
|
1002
|
+
case "setCursor":
|
|
1003
|
+
this.executeSetCursor(action);
|
|
1004
|
+
break;
|
|
1005
|
+
case "showAnnotations":
|
|
1006
|
+
this.executeShowAnnotations(action);
|
|
1007
|
+
break;
|
|
1008
|
+
case "hideAnnotations":
|
|
1009
|
+
this.executeHideAnnotations(action);
|
|
1010
|
+
break;
|
|
1011
|
+
default:
|
|
1012
|
+
console.warn("[Overlay] Unknown action type:", action.type);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async executeMove(action) {
|
|
1016
|
+
await this.cursor.moveTo(action.target, { duration: action.duration });
|
|
1017
|
+
if (action.showAnnotations) {
|
|
1018
|
+
this.annotations.show(action.showAnnotations);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async executeClick(action) {
|
|
1022
|
+
await this.cursor.click(action.target);
|
|
1023
|
+
}
|
|
1024
|
+
async executeWait(action) {
|
|
1025
|
+
await delay(action.duration);
|
|
1026
|
+
}
|
|
1027
|
+
executeSetCursor(action) {
|
|
1028
|
+
if (action.visible !== void 0) {
|
|
1029
|
+
if (action.visible) this.cursor.show();
|
|
1030
|
+
else this.cursor.hide();
|
|
1031
|
+
}
|
|
1032
|
+
if (action.name) this.cursor.setName(action.name);
|
|
1033
|
+
if (action.color) this.cursor.setColor(action.color);
|
|
1034
|
+
}
|
|
1035
|
+
executeShowAnnotations(action) {
|
|
1036
|
+
this.annotations.show(action.annotations);
|
|
1037
|
+
}
|
|
1038
|
+
executeHideAnnotations(action) {
|
|
1039
|
+
if (action.ids) {
|
|
1040
|
+
this.annotations.hide(action.ids);
|
|
1041
|
+
} else {
|
|
1042
|
+
this.annotations.hideAll();
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
destroy() {
|
|
1046
|
+
this.stop();
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// src/overlay/Overlay.ts
|
|
1051
|
+
var DEFAULT_CURSOR_COLOR = "#3B82F6";
|
|
1052
|
+
var DEFAULT_ANNOTATION_COLOR = "#EF4444";
|
|
1053
|
+
var DEFAULT_Z_INDEX = 9999;
|
|
1054
|
+
var _Overlay = class _Overlay {
|
|
1055
|
+
constructor(options = {}) {
|
|
1056
|
+
__publicField(this, "root");
|
|
1057
|
+
__publicField(this, "shadow");
|
|
1058
|
+
__publicField(this, "container");
|
|
1059
|
+
__publicField(this, "svgContainer");
|
|
1060
|
+
__publicField(this, "domContainer");
|
|
1061
|
+
__publicField(this, "_cursor");
|
|
1062
|
+
__publicField(this, "_annotations");
|
|
1063
|
+
__publicField(this, "timeline");
|
|
1064
|
+
__publicField(this, "reducedMotion");
|
|
1065
|
+
__publicField(this, "motionQuery");
|
|
1066
|
+
__publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
|
|
1067
|
+
__publicField(this, "_isDestroyed", false);
|
|
1068
|
+
// Note: Reduced motion preference is checked at construction time for cursor/annotations.
|
|
1069
|
+
// This handler updates our flag but doesn't propagate to already-constructed components.
|
|
1070
|
+
// This is acceptable since reduced motion is typically set before page interaction.
|
|
1071
|
+
__publicField(this, "handleMotionChange", (e) => {
|
|
1072
|
+
this.reducedMotion = e.matches;
|
|
1073
|
+
});
|
|
1074
|
+
const parentContainer = options.container ?? document.body;
|
|
1075
|
+
const zIndex = options.zIndex ?? DEFAULT_Z_INDEX;
|
|
1076
|
+
this.motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
1077
|
+
this.reducedMotion = this.motionQuery.matches;
|
|
1078
|
+
this.motionQuery.addEventListener("change", this.handleMotionChange);
|
|
1079
|
+
this.root = document.createElement("div");
|
|
1080
|
+
this.root.id = "ourroadmaps-overlay";
|
|
1081
|
+
this.shadow = this.root.attachShadow({ mode: "open" });
|
|
1082
|
+
const styleEl = document.createElement("style");
|
|
1083
|
+
styleEl.textContent = OVERLAY_STYLES;
|
|
1084
|
+
this.shadow.appendChild(styleEl);
|
|
1085
|
+
this.container = document.createElement("div");
|
|
1086
|
+
this.container.className = "overlay-container";
|
|
1087
|
+
this.container.style.setProperty("--overlay-z-index", String(zIndex));
|
|
1088
|
+
this.shadow.appendChild(this.container);
|
|
1089
|
+
this.svgContainer = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1090
|
+
this.svgContainer.setAttribute("class", "annotations-svg");
|
|
1091
|
+
this.container.appendChild(this.svgContainer);
|
|
1092
|
+
this.domContainer = document.createElement("div");
|
|
1093
|
+
this.domContainer.className = "annotations-dom";
|
|
1094
|
+
this.container.appendChild(this.domContainer);
|
|
1095
|
+
const cursorConfig = {
|
|
1096
|
+
name: options.cursor?.name ?? "Guide",
|
|
1097
|
+
color: options.cursor?.color ?? DEFAULT_CURSOR_COLOR,
|
|
1098
|
+
visible: options.cursor?.visible ?? true
|
|
1099
|
+
};
|
|
1100
|
+
this._cursor = new Cursor(this.container, this.reducedMotion);
|
|
1101
|
+
this._cursor.setName(cursorConfig.name);
|
|
1102
|
+
this._cursor.setColor(cursorConfig.color);
|
|
1103
|
+
if (!cursorConfig.visible) this._cursor.hide();
|
|
1104
|
+
const annotationsConfig = {
|
|
1105
|
+
color: options.annotations?.color ?? DEFAULT_ANNOTATION_COLOR,
|
|
1106
|
+
strokeWidth: options.annotations?.strokeWidth ?? 3
|
|
1107
|
+
};
|
|
1108
|
+
this._annotations = new AnnotationManager(
|
|
1109
|
+
annotationsConfig,
|
|
1110
|
+
this.domContainer,
|
|
1111
|
+
this.svgContainer,
|
|
1112
|
+
this.reducedMotion,
|
|
1113
|
+
() => this._cursor.position
|
|
1114
|
+
);
|
|
1115
|
+
this.timeline = new Timeline(this._cursor, this._annotations, {
|
|
1116
|
+
onStart: () => this.emit("start"),
|
|
1117
|
+
onComplete: () => this.emit("complete"),
|
|
1118
|
+
onStop: () => this.emit("stop"),
|
|
1119
|
+
onError: (error, action) => {
|
|
1120
|
+
this.emit("error", {
|
|
1121
|
+
code: "ANIMATION_FAILED",
|
|
1122
|
+
message: error.message,
|
|
1123
|
+
action
|
|
1124
|
+
});
|
|
1125
|
+
},
|
|
1126
|
+
onAction: (action, index, phase) => this.emit("action", action, index, phase)
|
|
1127
|
+
});
|
|
1128
|
+
parentContainer.appendChild(this.root);
|
|
1129
|
+
}
|
|
1130
|
+
// Controller interface
|
|
1131
|
+
get cursor() {
|
|
1132
|
+
return this._cursor;
|
|
1133
|
+
}
|
|
1134
|
+
get annotations() {
|
|
1135
|
+
return this._annotations;
|
|
1136
|
+
}
|
|
1137
|
+
get isPlaying() {
|
|
1138
|
+
return this.timeline.running;
|
|
1139
|
+
}
|
|
1140
|
+
get currentActionIndex() {
|
|
1141
|
+
return this.timeline.actionIndex;
|
|
1142
|
+
}
|
|
1143
|
+
get isDestroyed() {
|
|
1144
|
+
return this._isDestroyed;
|
|
1145
|
+
}
|
|
1146
|
+
validateAction(action, index) {
|
|
1147
|
+
const errors = [];
|
|
1148
|
+
if (!action || typeof action !== "object") {
|
|
1149
|
+
errors.push(`Action ${index} must be an object`);
|
|
1150
|
+
return errors;
|
|
1151
|
+
}
|
|
1152
|
+
const a = action;
|
|
1153
|
+
if (!_Overlay.VALID_ACTION_TYPES.includes(a.type)) {
|
|
1154
|
+
errors.push(`Action ${index} has invalid type: ${a.type}`);
|
|
1155
|
+
}
|
|
1156
|
+
if (a.type === "wait" && typeof a.duration !== "number") {
|
|
1157
|
+
errors.push(`Wait action ${index} must have a numeric duration`);
|
|
1158
|
+
}
|
|
1159
|
+
if (a.type === "move" && !a.target) {
|
|
1160
|
+
errors.push(`Move action ${index} must have a target`);
|
|
1161
|
+
}
|
|
1162
|
+
return errors;
|
|
1163
|
+
}
|
|
1164
|
+
validate(script) {
|
|
1165
|
+
const errors = [];
|
|
1166
|
+
const warnings = [];
|
|
1167
|
+
if (!script || typeof script !== "object") {
|
|
1168
|
+
errors.push("Script must be an object");
|
|
1169
|
+
return { valid: false, errors, warnings };
|
|
1170
|
+
}
|
|
1171
|
+
const s = script;
|
|
1172
|
+
if (s.version !== 1) {
|
|
1173
|
+
errors.push("Script version must be 1");
|
|
1174
|
+
}
|
|
1175
|
+
if (!Array.isArray(s.actions)) {
|
|
1176
|
+
errors.push("Script must have an actions array");
|
|
1177
|
+
} else {
|
|
1178
|
+
for (let i = 0; i < s.actions.length; i++) {
|
|
1179
|
+
errors.push(...this.validateAction(s.actions[i], i));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
valid: errors.length === 0,
|
|
1184
|
+
errors,
|
|
1185
|
+
warnings
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async play(script) {
|
|
1189
|
+
if (this._isDestroyed) {
|
|
1190
|
+
throw new Error("Overlay has been destroyed");
|
|
1191
|
+
}
|
|
1192
|
+
const validation = this.validate(script);
|
|
1193
|
+
if (!validation.valid) {
|
|
1194
|
+
throw new Error(`Invalid script: ${validation.errors.join(", ")}`);
|
|
1195
|
+
}
|
|
1196
|
+
await this.timeline.play(script);
|
|
1197
|
+
}
|
|
1198
|
+
stop() {
|
|
1199
|
+
this.timeline.stop();
|
|
1200
|
+
}
|
|
1201
|
+
on(event, handler) {
|
|
1202
|
+
if (!this.eventHandlers.has(event)) {
|
|
1203
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
1204
|
+
}
|
|
1205
|
+
this.eventHandlers.get(event).add(handler);
|
|
1206
|
+
return () => {
|
|
1207
|
+
this.eventHandlers.get(event)?.delete(handler);
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
emit(event, ...args) {
|
|
1211
|
+
const handlers = this.eventHandlers.get(event);
|
|
1212
|
+
if (handlers) {
|
|
1213
|
+
for (const handler of handlers) {
|
|
1214
|
+
try {
|
|
1215
|
+
;
|
|
1216
|
+
handler(...args);
|
|
1217
|
+
} catch (err) {
|
|
1218
|
+
console.error(`[Overlay] Error in ${event} handler:`, err);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
destroy() {
|
|
1224
|
+
if (this._isDestroyed) return;
|
|
1225
|
+
this._isDestroyed = true;
|
|
1226
|
+
this.timeline.destroy();
|
|
1227
|
+
this._cursor.destroy();
|
|
1228
|
+
this._annotations.destroy();
|
|
1229
|
+
this.motionQuery.removeEventListener("change", this.handleMotionChange);
|
|
1230
|
+
this.eventHandlers.clear();
|
|
1231
|
+
this.root.remove();
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
__publicField(_Overlay, "VALID_ACTION_TYPES", [
|
|
1235
|
+
"move",
|
|
1236
|
+
"click",
|
|
1237
|
+
"wait",
|
|
1238
|
+
"setCursor",
|
|
1239
|
+
"showAnnotations",
|
|
1240
|
+
"hideAnnotations"
|
|
1241
|
+
]);
|
|
1242
|
+
var Overlay = _Overlay;
|
|
1243
|
+
|
|
1244
|
+
// src/overlay/index.ts
|
|
1245
|
+
function createOverlay(options) {
|
|
1246
|
+
return new Overlay(options);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
export { Overlay, createOverlay };
|
|
1250
|
+
//# sourceMappingURL=chunk-XFAAEDJK.js.map
|
|
1251
|
+
//# sourceMappingURL=chunk-XFAAEDJK.js.map
|