@syntrologie/adapt-overlays 2.28.0 → 2.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.js CHANGED
@@ -1,2679 +1,14 @@
1
1
  import {
2
- __privateAdd,
3
- __privateGet,
4
- __privateSet
5
- } from "./chunk-VHAA22YE.js";
6
-
7
- // src/celebrations/effects/confetti.ts
8
- var INTENSITY_COUNTS = { light: 50, medium: 100, heavy: 200 };
9
- var DEFAULT_COLORS = [
10
- "#ff0000",
11
- "#00ff00",
12
- "#0000ff",
13
- "#ffff00",
14
- "#ff00ff",
15
- "#00ffff",
16
- "#ff8800",
17
- "#8800ff"
18
- ];
19
- var confettiEffect = {
20
- init(width, height, config) {
21
- const count = INTENSITY_COUNTS[config.intensity];
22
- const colors = config.colors.length > 0 ? config.colors : DEFAULT_COLORS;
23
- const particles = [];
24
- for (let i = 0; i < count; i++) {
25
- particles.push({
26
- x: Math.random() * width,
27
- y: Math.random() * -height * 0.3,
28
- vx: (Math.random() - 0.5) * 4,
29
- vy: Math.random() * 2 + 1,
30
- rotation: Math.random() * Math.PI * 2,
31
- rotationSpeed: (Math.random() - 0.5) * 0.2,
32
- size: Math.random() * 6 + 4,
33
- color: colors[Math.floor(Math.random() * colors.length)],
34
- opacity: 1,
35
- shape: Math.random() > 0.5 ? "rect" : "circle"
36
- });
37
- }
38
- return particles;
39
- },
40
- update(particles, _dt, _elapsed) {
41
- let anyVisible = false;
42
- for (const p of particles) {
43
- p.vy += 0.15;
44
- p.vx *= 0.99;
45
- p.x += p.vx;
46
- p.y += p.vy;
47
- p.rotation += p.rotationSpeed;
48
- if (p.y > 0 && p.opacity > 0) {
49
- if (p.y > 500) {
50
- p.opacity -= 0.02;
51
- if (p.opacity < 0) p.opacity = 0;
52
- }
53
- }
54
- if (p.opacity > 0.01) {
55
- anyVisible = true;
56
- }
57
- }
58
- return anyVisible;
59
- },
60
- render(ctx, particles) {
61
- for (const p of particles) {
62
- if (p.opacity < 0.01) continue;
63
- ctx.save();
64
- ctx.globalAlpha = p.opacity;
65
- ctx.fillStyle = p.color;
66
- ctx.translate(p.x, p.y);
67
- ctx.rotate(p.rotation);
68
- if (p.shape === "circle") {
69
- ctx.beginPath();
70
- ctx.arc(0, 0, p.size / 2, 0, Math.PI * 2);
71
- ctx.fill();
72
- } else {
73
- ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
74
- }
75
- ctx.restore();
76
- }
77
- }
78
- };
79
-
80
- // src/celebrations/effects/emoji-rain.ts
81
- var INTENSITY_COUNTS2 = { light: 20, medium: 40, heavy: 80 };
82
- var DEFAULT_EMOJI = "\u{1F389}";
83
- var emojiRainEffect = {
84
- init(width, _height, config) {
85
- const count = INTENSITY_COUNTS2[config.intensity];
86
- const emoji = typeof config.props?.emoji === "string" ? config.props.emoji : DEFAULT_EMOJI;
87
- const particles = [];
88
- for (let i = 0; i < count; i++) {
89
- particles.push({
90
- x: Math.random() * width,
91
- y: Math.random() * -200,
92
- vx: 0,
93
- vy: Math.random() * 1.5 + 1,
94
- rotation: 0,
95
- rotationSpeed: 0,
96
- size: Math.random() * 12 + 16,
97
- color: "#000000",
98
- opacity: 1,
99
- shape: "emoji",
100
- emoji,
101
- data: {
102
- /** Phase offset for horizontal wobble */
103
- wobblePhase: Math.random() * Math.PI * 2,
104
- /** Amplitude of horizontal wobble */
105
- wobbleAmp: Math.random() * 1.5 + 0.5,
106
- /** Original x for wobble base */
107
- originX: 0
108
- }
109
- });
110
- }
111
- for (const p of particles) {
112
- if (p.data) p.data.originX = p.x;
113
- }
114
- return particles;
115
- },
116
- update(particles, _dt, elapsed) {
117
- let anyVisible = false;
118
- for (const p of particles) {
119
- p.y += p.vy;
120
- const phase = p.data?.wobblePhase ?? 0;
121
- const amp = p.data?.wobbleAmp ?? 1;
122
- const originX = p.data?.originX ?? p.x;
123
- p.x = originX + Math.sin(elapsed * 3e-3 + phase) * amp * 20;
124
- if (p.y > 600) {
125
- p.opacity -= 0.02;
126
- if (p.opacity < 0) p.opacity = 0;
127
- }
128
- if (p.opacity > 0.01) {
129
- anyVisible = true;
130
- }
131
- }
132
- return anyVisible;
133
- },
134
- render(ctx, particles) {
135
- for (const p of particles) {
136
- if (p.opacity < 0.01 || !p.emoji) continue;
137
- ctx.save();
138
- ctx.globalAlpha = p.opacity;
139
- ctx.font = `${p.size}px serif`;
140
- ctx.textAlign = "center";
141
- ctx.textBaseline = "middle";
142
- ctx.fillText(p.emoji, p.x, p.y);
143
- ctx.restore();
144
- }
145
- }
146
- };
147
-
148
- // src/celebrations/effects/fireworks.ts
149
- var PARTICLES_PER_BURST = { light: 20, medium: 40, heavy: 80 };
150
- var BURST_COUNTS = { light: 3, medium: 4, heavy: 5 };
151
- var fireworksEffect = {
152
- init(width, height, config) {
153
- const perBurst = PARTICLES_PER_BURST[config.intensity];
154
- const burstCount = BURST_COUNTS[config.intensity];
155
- const colors = config.colors.length > 0 ? config.colors : ["#ff4444", "#44ff44", "#4444ff"];
156
- const particles = [];
157
- for (let b = 0; b < burstCount; b++) {
158
- const cx = Math.random() * width * 0.8 + width * 0.1;
159
- const cy = Math.random() * height * 0.6;
160
- const burstColor = colors[Math.floor(Math.random() * colors.length)];
161
- for (let i = 0; i < perBurst; i++) {
162
- const angle = Math.PI * 2 * i / perBurst + (Math.random() - 0.5) * 0.3;
163
- const speed = Math.random() * 3 + 2;
164
- particles.push({
165
- x: cx,
166
- y: cy,
167
- vx: Math.cos(angle) * speed,
168
- vy: Math.sin(angle) * speed,
169
- rotation: 0,
170
- rotationSpeed: 0,
171
- size: Math.random() * 3 + 2,
172
- color: burstColor,
173
- opacity: 1,
174
- shape: "circle",
175
- data: { centerX: cx, centerY: cy }
176
- });
177
- }
178
- }
179
- return particles;
180
- },
181
- update(particles, _dt, _elapsed) {
182
- let anyVisible = false;
183
- for (const p of particles) {
184
- p.vx *= 0.97;
185
- p.vy *= 0.97;
186
- p.vy += 0.03;
187
- p.x += p.vx;
188
- p.y += p.vy;
189
- p.opacity -= 8e-3;
190
- if (p.opacity < 0) p.opacity = 0;
191
- if (p.opacity > 0.01) {
192
- anyVisible = true;
193
- }
194
- }
195
- return anyVisible;
196
- },
197
- render(ctx, particles) {
198
- for (const p of particles) {
199
- if (p.opacity < 0.01) continue;
200
- ctx.save();
201
- ctx.globalAlpha = p.opacity;
202
- ctx.fillStyle = p.color;
203
- ctx.shadowBlur = 12;
204
- ctx.shadowColor = p.color;
205
- ctx.beginPath();
206
- ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
207
- ctx.fill();
208
- ctx.restore();
209
- }
210
- }
211
- };
212
-
213
- // src/celebrations/effects/sparkles.ts
214
- var INTENSITY_COUNTS3 = { light: 30, medium: 60, heavy: 120 };
215
- var sparklesEffect = {
216
- init(width, height, config) {
217
- const count = INTENSITY_COUNTS3[config.intensity];
218
- const colors = config.colors.length > 0 ? config.colors : ["#ffd700", "#ffffff", "#fffacd"];
219
- const particles = [];
220
- for (let i = 0; i < count; i++) {
221
- particles.push({
222
- x: Math.random() * width,
223
- y: Math.random() * height,
224
- vx: (Math.random() - 0.5) * 0.3,
225
- vy: -(Math.random() * 0.5 + 0.2),
226
- rotation: Math.random() * Math.PI * 2,
227
- rotationSpeed: (Math.random() - 0.5) * 0.1,
228
- size: Math.random() * 4 + 2,
229
- color: colors[Math.floor(Math.random() * colors.length)],
230
- opacity: Math.random() * 0.5 + 0.5,
231
- shape: "circle",
232
- data: {
233
- /** Phase offset for sine-wave twinkle */
234
- phase: Math.random() * Math.PI * 2,
235
- /** Base opacity before twinkle modulation */
236
- baseOpacity: Math.random() * 0.5 + 0.5
237
- }
238
- });
239
- }
240
- return particles;
241
- },
242
- update(particles, _dt, elapsed) {
243
- let anyVisible = false;
244
- for (const p of particles) {
245
- p.x += p.vx;
246
- p.y += p.vy;
247
- p.rotation += p.rotationSpeed;
248
- if (p.data) {
249
- p.data.baseOpacity = (p.data.baseOpacity ?? 1) - 1e-3;
250
- if (p.data.baseOpacity < 0) p.data.baseOpacity = 0;
251
- }
252
- const phase = p.data?.phase ?? 0;
253
- const baseOpacity = p.data?.baseOpacity ?? 1;
254
- if (baseOpacity <= 0.01) {
255
- p.opacity = 0;
256
- } else {
257
- const twinkle = Math.sin(elapsed * 5e-3 + phase) * 0.4 + 0.6;
258
- p.opacity = baseOpacity * twinkle;
259
- }
260
- if (p.opacity > 0.01) {
261
- anyVisible = true;
262
- }
263
- }
264
- return anyVisible;
265
- },
266
- render(ctx, particles) {
267
- for (const p of particles) {
268
- if (p.opacity < 0.01) continue;
269
- ctx.save();
270
- ctx.globalAlpha = p.opacity;
271
- ctx.fillStyle = p.color;
272
- ctx.translate(p.x, p.y);
273
- ctx.rotate(p.rotation);
274
- const s = p.size;
275
- ctx.beginPath();
276
- ctx.moveTo(0, -s);
277
- ctx.lineTo(s * 0.3, -s * 0.3);
278
- ctx.lineTo(s, 0);
279
- ctx.lineTo(s * 0.3, s * 0.3);
280
- ctx.lineTo(0, s);
281
- ctx.lineTo(-s * 0.3, s * 0.3);
282
- ctx.lineTo(-s, 0);
283
- ctx.lineTo(-s * 0.3, -s * 0.3);
284
- ctx.closePath();
285
- ctx.fill();
286
- ctx.restore();
287
- }
288
- }
289
- };
290
-
291
- // src/celebrations/engine.ts
292
- var CelebrationEngine = class {
293
- constructor() {
294
- this.canvas = null;
295
- this.ctx = null;
296
- this.rafId = null;
297
- this.particles = [];
298
- this.startTime = 0;
299
- this.lastFrame = 0;
300
- this.duration = 0;
301
- this.effect = null;
302
- this.container = null;
303
- }
304
- start(container, effect, config) {
305
- const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
306
- if (prefersReducedMotion) {
307
- return;
308
- }
309
- this.container = container;
310
- this.effect = effect;
311
- this.duration = config.duration;
312
- const canvas = document.createElement("canvas");
313
- canvas.setAttribute("data-syntro-celebrate", "");
314
- Object.assign(canvas.style, {
315
- position: "fixed",
316
- inset: "0",
317
- pointerEvents: "none",
318
- zIndex: "2147483646"
319
- });
320
- const dpr = window.devicePixelRatio || 1;
321
- const width = window.innerWidth;
322
- const height = window.innerHeight;
323
- canvas.width = width * dpr;
324
- canvas.height = height * dpr;
325
- canvas.style.width = `${width}px`;
326
- canvas.style.height = `${height}px`;
327
- const ctx = canvas.getContext("2d");
328
- if (!ctx) {
329
- return;
330
- }
331
- ctx.scale(dpr, dpr);
332
- this.canvas = canvas;
333
- this.ctx = ctx;
334
- container.appendChild(canvas);
335
- this.particles = effect.init(width, height, config);
336
- this.startTime = performance.now();
337
- this.lastFrame = this.startTime;
338
- this.tick = this.tick.bind(this);
339
- this.rafId = requestAnimationFrame(this.tick);
340
- }
341
- stop() {
342
- if (this.rafId !== null) {
343
- cancelAnimationFrame(this.rafId);
344
- this.rafId = null;
345
- }
346
- if (this.canvas && this.container) {
347
- this.canvas.remove();
348
- this.canvas = null;
349
- }
350
- this.ctx = null;
351
- this.effect = null;
352
- this.container = null;
353
- this.particles = [];
354
- }
355
- tick(now) {
356
- if (!this.ctx || !this.canvas || !this.effect) return;
357
- const elapsed = now - this.startTime;
358
- const dt = now - this.lastFrame;
359
- this.lastFrame = now;
360
- if (elapsed >= this.duration) {
361
- this.stop();
362
- return;
363
- }
364
- const width = this.canvas.width / (window.devicePixelRatio || 1);
365
- const height = this.canvas.height / (window.devicePixelRatio || 1);
366
- this.ctx.clearRect(0, 0, width, height);
367
- const alive = this.effect.update(this.particles, dt, elapsed);
368
- if (!alive) {
369
- this.stop();
370
- return;
371
- }
372
- this.effect.render(this.ctx, this.particles);
373
- this.rafId = requestAnimationFrame(this.tick);
374
- }
375
- };
376
-
377
- // src/celebrations/index.ts
378
- var FALLBACK_COLORS = [
379
- "#ff0000",
380
- "#00ff00",
381
- "#0000ff",
382
- "#ffff00",
383
- "#ff00ff",
384
- "#00ffff",
385
- "#ff8800",
386
- "#8800ff"
387
- ];
388
- function buildThemePalette(primary, hover) {
389
- return [primary, hover, `${primary}cc`, `${hover}cc`, "#ffffff", `${primary}80`];
390
- }
391
- function readThemeColors(overlayRoot) {
392
- try {
393
- const styles = getComputedStyle(overlayRoot);
394
- const primary = styles.getPropertyValue("--sc-color-primary")?.trim();
395
- const hover = styles.getPropertyValue("--sc-color-primary-hover")?.trim();
396
- if (primary?.startsWith("#") && primary.length >= 7) {
397
- return buildThemePalette(primary, hover || primary);
398
- }
399
- } catch {
400
- }
401
- return null;
402
- }
403
- var effectRegistry = /* @__PURE__ */ new Map([
404
- ["confetti", confettiEffect],
405
- ["fireworks", fireworksEffect],
406
- ["sparkles", sparklesEffect],
407
- ["emoji-rain", emojiRainEffect]
408
- ]);
409
- var executeCelebrate = async (action, context) => {
410
- const effect = effectRegistry.get(action.effect);
411
- if (!effect) {
412
- console.warn(
413
- `[adaptive-overlays] Unknown celebration effect: "${action.effect}". Available: ${[...effectRegistry.keys()].join(", ")}`
414
- );
415
- return { cleanup: () => {
416
- } };
417
- }
418
- const colors = action.colors ?? readThemeColors(context.overlayRoot) ?? FALLBACK_COLORS;
419
- const config = {
420
- duration: action.duration ?? 3e3,
421
- intensity: action.intensity ?? "medium",
422
- colors,
423
- props: action.props
424
- };
425
- const engine = new CelebrationEngine();
426
- engine.start(context.overlayRoot, effect, config);
427
- context.publishEvent("action.applied", {
428
- id: context.generateId(),
429
- kind: "overlays:celebrate",
430
- effect: action.effect
431
- });
432
- return {
433
- cleanup: () => {
434
- engine.stop();
435
- }
436
- };
437
- };
438
-
439
- // src/cta-navigation.ts
440
- var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
441
- function isSafeNavigationHref(href) {
442
- if (typeof href !== "string" || href.trim().length === 0) return false;
443
- let parsed;
444
- try {
445
- parsed = new URL(href, "https://syntro.local/");
446
- } catch {
447
- return false;
448
- }
449
- return ALLOWED_PROTOCOLS.has(parsed.protocol);
450
- }
451
- function navigateForCta(input) {
452
- if (input.actionId === "dismiss") return false;
453
- if (!input.href || !isSafeNavigationHref(input.href)) return false;
454
- const href = input.href;
455
- if (input.target === "_blank") {
456
- window.open(href, "_blank", "noopener,noreferrer");
457
- } else {
458
- window.location.assign(href);
459
- }
460
- return true;
461
- }
462
-
463
- // src/executors/tour.ts
464
- var ACTIVE_TOUR_KEY = "syntro_active_tour";
465
- var activeTours = /* @__PURE__ */ new Map();
466
- function getTourState(tourId) {
467
- try {
468
- const data = localStorage.getItem(ACTIVE_TOUR_KEY);
469
- if (!data) return null;
470
- const state = JSON.parse(data);
471
- if (state.tourId !== tourId) return null;
472
- return state;
473
- } catch {
474
- return null;
475
- }
476
- }
477
- function saveTourState(state) {
478
- try {
479
- localStorage.setItem(ACTIVE_TOUR_KEY, JSON.stringify(state));
480
- } catch {
481
- }
482
- }
483
- function clearTourState() {
484
- try {
485
- localStorage.removeItem(ACTIVE_TOUR_KEY);
486
- } catch {
487
- }
488
- }
489
- function getCurrentRoute() {
490
- return window.location.pathname;
491
- }
492
- function stepMatchesRoute(step) {
493
- if (!step.route) return true;
494
- const currentRoute = getCurrentRoute();
495
- if (step.route.includes("*")) {
496
- const pattern = new RegExp(`^${step.route.replace(/\*/g, ".*")}$`);
497
- return pattern.test(currentRoute);
498
- }
499
- return currentRoute === step.route;
500
- }
501
- var executeTour = async (action, context) => {
502
- const { tourId, steps, startStep, autoStart = true } = action;
503
- if (steps.length === 0) {
504
- throw new Error(`Tour "${tourId}" has no steps`);
505
- }
506
- if (activeTours.has(tourId)) {
507
- return {
508
- cleanup: async () => {
509
- const existing = activeTours.get(tourId);
510
- if (existing) {
511
- await existing.cleanup();
512
- }
513
- }
514
- };
515
- }
516
- if (!context.applyAction) {
517
- throw new Error("Tour executor requires applyAction in context");
518
- }
519
- let state = getTourState(tourId);
520
- const isResumingTour = !!state;
521
- if (!isResumingTour && !autoStart) {
522
- return {
523
- cleanup: () => {
524
- }
525
- };
526
- }
527
- if (!state) {
528
- state = {
529
- tourId,
530
- currentStepId: startStep || steps[0].id,
531
- startedAt: Date.now()
532
- };
533
- saveTourState(state);
534
- }
535
- let currentStepIndex = steps.findIndex((s) => s.id === state.currentStepId);
536
- if (currentStepIndex === -1) {
537
- const initialStepId = startStep || steps[0].id;
538
- currentStepIndex = steps.findIndex((s) => s.id === initialStepId);
539
- if (currentStepIndex === -1) currentStepIndex = 0;
540
- state.currentStepId = steps[currentStepIndex].id;
541
- saveTourState(state);
542
- }
543
- const currentStep = steps[currentStepIndex];
544
- if (!stepMatchesRoute(currentStep)) {
545
- context.publishEvent("tour.waiting_for_route", {
546
- tourId,
547
- stepId: currentStep.id,
548
- expectedRoute: currentStep.route,
549
- currentRoute: getCurrentRoute()
550
- });
551
- return {
552
- cleanup: () => {
553
- }
554
- };
555
- }
556
- let isDestroyed = false;
557
- let currentActionHandle = null;
558
- let eventUnsubscribe = null;
559
- let routeWatcher = null;
560
- const cleanupCurrentStep = async () => {
561
- if (eventUnsubscribe) {
562
- eventUnsubscribe();
563
- eventUnsubscribe = null;
564
- }
565
- if (currentActionHandle?.isApplied()) {
566
- await currentActionHandle.revert();
567
- currentActionHandle = null;
568
- }
569
- };
570
- const advanceToStep = async (nextStepId) => {
571
- if (isDestroyed) return;
572
- await cleanupCurrentStep();
573
- if (nextStepId === "end") {
574
- clearTourState();
575
- context.publishEvent("tour.completed", {
576
- tourId,
577
- totalSteps: steps.length
578
- });
579
- isDestroyed = true;
580
- return;
581
- }
582
- const nextStep = steps.find((s) => s.id === nextStepId);
583
- if (!nextStep) {
584
- console.error(`[Tour] Step "${nextStepId}" not found`);
585
- return;
586
- }
587
- state.currentStepId = nextStepId;
588
- saveTourState(state);
589
- context.publishEvent("tour.step_changed", {
590
- tourId,
591
- previousStepId: currentStep.id,
592
- nextStepId
593
- });
594
- if (nextStep.route && nextStep.route !== getCurrentRoute()) {
595
- context.publishEvent("tour.awaiting_navigation", {
596
- tourId,
597
- stepId: nextStepId,
598
- targetRoute: nextStep.route
599
- });
600
- return;
601
- }
602
- await executeStep(nextStep);
603
- };
604
- const executeStep = async (step) => {
605
- if (isDestroyed) return;
606
- context.publishEvent("tour.step_started", {
607
- tourId,
608
- stepId: step.id,
609
- stepIndex: steps.findIndex((s) => s.id === step.id),
610
- totalSteps: steps.length
611
- });
612
- try {
613
- currentActionHandle = await context.applyAction(step.action);
614
- } catch (error) {
615
- console.error(`[Tour] Failed to execute step "${step.id}":`, error);
616
- context.publishEvent("tour.step_failed", {
617
- tourId,
618
- stepId: step.id,
619
- error: String(error)
620
- });
621
- return;
622
- }
623
- if (step.onAction && context.subscribeEvent) {
624
- eventUnsubscribe = context.subscribeEvent("action.modal_cta_clicked", (props) => {
625
- const actionId = props?.actionId;
626
- if (actionId && step.onAction) {
627
- const nextStepId = step.onAction[actionId];
628
- if (nextStepId) {
629
- advanceToStep(nextStepId);
630
- }
631
- }
632
- });
633
- const tooltipUnsubscribe = context.subscribeEvent("action.tooltip_cta_clicked", (props) => {
634
- const actionId = props?.actionId;
635
- if (actionId && step.onAction) {
636
- const nextStepId = step.onAction[actionId];
637
- if (nextStepId) {
638
- advanceToStep(nextStepId);
639
- }
640
- }
641
- });
642
- const originalUnsubscribe = eventUnsubscribe;
643
- eventUnsubscribe = () => {
644
- originalUnsubscribe();
645
- tooltipUnsubscribe();
646
- };
647
- }
648
- };
649
- const setupRouteWatcher = () => {
650
- let lastPath = getCurrentRoute();
651
- const checkRoute = () => {
652
- const currentPath = getCurrentRoute();
653
- if (currentPath !== lastPath) {
654
- lastPath = currentPath;
655
- context.publishEvent("tour.route_changed", {
656
- tourId,
657
- newRoute: currentPath
658
- });
659
- }
660
- };
661
- if (context.subscribeNavigation) {
662
- return context.subscribeNavigation(() => checkRoute());
663
- }
664
- window.addEventListener("popstate", checkRoute);
665
- const origPushState = history.pushState.bind(history);
666
- const origReplaceState = history.replaceState.bind(history);
667
- history.pushState = (...args) => {
668
- origPushState(...args);
669
- queueMicrotask(checkRoute);
670
- };
671
- history.replaceState = (...args) => {
672
- origReplaceState(...args);
673
- queueMicrotask(checkRoute);
674
- };
675
- return () => {
676
- window.removeEventListener("popstate", checkRoute);
677
- history.pushState = origPushState;
678
- history.replaceState = origReplaceState;
679
- };
680
- };
681
- routeWatcher = setupRouteWatcher();
682
- if (!isResumingTour) {
683
- context.publishEvent("tour.started", {
684
- tourId,
685
- totalSteps: steps.length,
686
- startStepId: state.currentStepId
687
- });
688
- } else {
689
- context.publishEvent("tour.resumed", {
690
- tourId,
691
- stepId: state.currentStepId
692
- });
693
- }
694
- await executeStep(currentStep);
695
- const cleanup = async () => {
696
- isDestroyed = true;
697
- activeTours.delete(tourId);
698
- await cleanupCurrentStep();
699
- if (routeWatcher) {
700
- routeWatcher();
701
- }
702
- context.publishEvent("tour.paused", {
703
- tourId,
704
- stepId: state.currentStepId
705
- });
706
- };
707
- activeTours.set(tourId, { cleanup });
708
- return { cleanup };
709
- };
710
-
711
- // ../../design-system/dist/tokens/colors.js
712
- var base = {
713
- white: "#ffffff",
714
- black: "#000000"
715
- };
716
- var brand = {
717
- 0: "#2c0b0a",
718
- 1: "#5b1715",
719
- 2: "#89221f",
720
- 3: "#b72e2a",
721
- 4: "#d44844",
722
- 5: "#dd6d69",
723
- 6: "#e5918f",
724
- 7: "#eeb6b4",
725
- 8: "#f6dada",
726
- 9: "#faebea"
727
- };
728
- var slateGrey = {
729
- 0: "#07080a",
730
- 1: "#0f1318",
731
- 2: "#0e1114",
732
- 3: "#1c222a",
733
- 4: "#2b333f",
734
- 5: "#394454",
735
- 6: "#475569",
736
- 7: "#677384",
737
- 8: "#87919f",
738
- 9: "#a8afba",
739
- 10: "#cbd0d7",
740
- 11: "#e8eaee",
741
- 12: "#f6f7f9"
742
- };
743
- var green = {
744
- 0: "#07230a",
745
- 1: "#0e4514",
746
- 2: "#16681e",
747
- 3: "#1d8a28",
748
- 4: "#24ad32",
749
- 5: "#4fbd5a",
750
- 6: "#7acd82",
751
- 7: "#a5deab",
752
- 8: "#d0eed3",
753
- 9: "#e5f6e7"
754
- };
755
- var yellow = {
756
- 0: "#301f09",
757
- 1: "#5f3e12",
758
- 2: "#8f5e1b",
759
- 3: "#be7d24",
760
- 4: "#ee9c2d",
761
- 5: "#f1b057",
762
- 6: "#f5c481",
763
- 7: "#f8d7ab",
764
- 8: "#fcebd5",
765
- 9: "#fdf5ea"
766
- };
767
- var red = {
768
- 0: "#330707",
769
- 1: "#660f0e",
770
- 2: "#991616",
771
- 3: "#cc1e1d",
772
- 4: "#ff2524",
773
- 5: "#ff5150",
774
- 6: "#ff7c7c",
775
- 7: "#ffa8a7",
776
- 8: "#ffd3d3",
777
- 9: "#ffe9e9"
778
- };
779
- var blue = {
780
- 0: "#051533",
781
- 1: "#0a2a66",
782
- 2: "#0f3f98",
783
- 3: "#1454cb",
784
- 4: "#1969fe",
785
- 5: "#4787fe",
786
- 6: "#75a5fe",
787
- 7: "#a3c3ff",
788
- 8: "#d1e1ff",
789
- 9: "#e8f0ff"
790
- };
791
- var orange = {
792
- 0: "#662500",
793
- 1: "#993d00",
794
- 2: "#cc5800",
795
- 3: "#ff7700",
796
- 4: "#fea85d",
797
- 5: "#fec58f",
798
- 6: "#ffd6ae",
799
- 7: "#fee6cd",
800
- 8: "#fff1e1",
801
- 9: "#fff8f0"
802
- };
803
- var purple = {
804
- 0: "#151229",
805
- 1: "#2a2452",
806
- 2: "#40357c",
807
- 3: "#5547a5",
808
- 4: "#6a59ce",
809
- 5: "#887ad8",
810
- 6: "#a69be2",
811
- 7: "#c3bdeb",
812
- 8: "#e1def5",
813
- 9: "#f0eefa"
814
- };
815
- var pink = {
816
- 0: "#37091f",
817
- 1: "#69123c",
818
- 2: "#9b1c58",
819
- 3: "#cd2575",
820
- 4: "#ff2e92",
821
- 5: "#ff58a8",
822
- 6: "#ff82be",
823
- 7: "#ffabd3",
824
- 8: "#ffd5e9",
825
- 9: "#ffeaf4"
826
- };
827
- var text = {
828
- primary: slateGrey[10],
829
- secondary: slateGrey[9],
830
- tertiary: slateGrey[8]
831
- };
832
- var background = {
833
- primary: slateGrey[2],
834
- secondary: slateGrey[0]
835
- };
836
- var border = {
837
- primary: slateGrey[4],
838
- secondary: slateGrey[3]
839
- };
840
- var button = {
841
- primary: {
842
- text: base.white,
843
- icon: base.white,
844
- border: brand[3],
845
- backgroundDefault: brand[3],
846
- backgroundHover: brand[2]
847
- },
848
- neutral: {
849
- text: slateGrey[10],
850
- textHover: base.white,
851
- icon: slateGrey[10],
852
- iconHover: base.white,
853
- border: slateGrey[4],
854
- background: slateGrey[2]
855
- },
856
- link: {
857
- text: base.white,
858
- icon: base.white,
859
- hover: brand[5]
860
- },
861
- error: {
862
- text: red[5],
863
- hover: red[6]
864
- },
865
- success: {
866
- text: green[5],
867
- hover: green[6]
868
- }
869
- };
870
- var badge = {
871
- slateGrey: {
872
- content: slateGrey[10],
873
- pillOutline: slateGrey[10],
874
- borderPrimary: slateGrey[5],
875
- borderSecondary: slateGrey[5],
876
- background: slateGrey[3]
877
- },
878
- brand: {
879
- content: brand[9],
880
- pillOutline: brand[9],
881
- borderPrimary: brand[6],
882
- borderSecondary: brand[6],
883
- background: brand[0]
884
- },
885
- red: {
886
- content: red[8],
887
- pillOutline: red[4],
888
- borderPrimary: red[2],
889
- borderSecondary: red[2],
890
- background: red[0]
891
- },
892
- yellow: {
893
- content: yellow[8],
894
- pillOutline: yellow[4],
895
- borderPrimary: yellow[2],
896
- borderSecondary: yellow[2],
897
- background: yellow[0]
898
- },
899
- green: {
900
- content: green[8],
901
- pillOutline: green[4],
902
- borderPrimary: green[2],
903
- borderSecondary: green[2],
904
- background: green[0]
905
- },
906
- purple: {
907
- content: purple[8],
908
- pillOutline: purple[4],
909
- borderPrimary: purple[2],
910
- borderSecondary: purple[2],
911
- background: purple[0]
912
- },
913
- blue: {
914
- content: blue[8],
915
- pillOutline: blue[4],
916
- borderPrimary: blue[2],
917
- borderSecondary: blue[2],
918
- background: blue[0]
919
- },
920
- orange: {
921
- content: orange[8],
922
- pillOutline: orange[4],
923
- borderPrimary: orange[2],
924
- borderSecondary: orange[2],
925
- background: orange[0]
926
- },
927
- pink: {
928
- content: pink[8],
929
- pillOutline: pink[4],
930
- borderPrimary: pink[2],
931
- borderSecondary: pink[2],
932
- background: pink[0]
933
- }
934
- };
935
- var badgeBanner = {
936
- green: {
937
- content: green[8],
938
- border: green[2],
939
- background: green[0]
940
- },
941
- yellow: {
942
- content: yellow[8],
943
- border: yellow[2],
944
- background: yellow[0]
945
- },
946
- red: {
947
- content: red[8],
948
- border: red[2],
949
- background: red[0]
950
- }
951
- };
952
- var alert = {
953
- green: {
954
- content: green[1],
955
- background: green[9]
956
- },
957
- yellow: {
958
- content: yellow[1],
959
- background: yellow[9]
960
- },
961
- red: {
962
- content: red[1],
963
- background: red[9]
964
- }
965
- };
966
- var tag = {
967
- content: slateGrey[10],
968
- border: slateGrey[4],
969
- background: slateGrey[3]
970
- };
971
- var menu = {
972
- backgroundDefault: slateGrey[2],
973
- backgroundHover: slateGrey[1],
974
- selected: slateGrey[3]
975
- };
976
- var inputDropdown = {
977
- background: slateGrey[2],
978
- icon: slateGrey[10],
979
- borderDefault: slateGrey[4],
980
- borderSelected: brand[3],
981
- textLabel: slateGrey[9],
982
- textPlaceholder: slateGrey[8],
983
- textHint: slateGrey[8]
984
- };
985
- var inputField = {
986
- backgroundDefault: slateGrey[2],
987
- backgroundDisabled: slateGrey[0],
988
- textLabel: slateGrey[9],
989
- textPlaceholder: slateGrey[8],
990
- textHint: slateGrey[8],
991
- textError: red[5],
992
- iconDefault: slateGrey[9],
993
- iconPlaceholder: slateGrey[10],
994
- iconError: red[5],
995
- borderDefault: slateGrey[4],
996
- borderSelected: brand[3],
997
- borderError: red[5]
998
- };
999
- var toggle = {
1000
- handleDefault: base.white,
1001
- handleDisabled: slateGrey[10],
1002
- off: {
1003
- backgroundDefault: slateGrey[4],
1004
- backgroundHover: slateGrey[5],
1005
- backgroundDisabled: slateGrey[4]
1006
- },
1007
- on: {
1008
- backgroundDefault: green[3],
1009
- backgroundHover: green[2],
1010
- backgroundDisabled: slateGrey[4]
1011
- }
1012
- };
1013
- var checkbox = {
1014
- off: {
1015
- backgroundDefault: "#00000000",
1016
- backgroundHover: slateGrey[5],
1017
- backgroundDisabled: slateGrey[2],
1018
- border: slateGrey[6]
1019
- },
1020
- on: {
1021
- backgroundDefault: green[0],
1022
- backgroundHover: green[1],
1023
- backgroundDisabled: slateGrey[2],
1024
- border: green[3]
1025
- }
1026
- };
1027
- var avatar = {
1028
- content: slateGrey[10],
1029
- background: slateGrey[4]
1030
- };
1031
- var progressBarSlider = {
1032
- background: slateGrey[4],
1033
- active: green[3]
1034
- };
1035
- var card = {
1036
- background: slateGrey[1],
1037
- content: slateGrey[9],
1038
- border: slateGrey[4]
1039
- };
1040
- var sidebar = {
1041
- backgroundDefault: slateGrey[1],
1042
- backgroundHover: slateGrey[3],
1043
- backgroundActive: slateGrey[4],
1044
- border: slateGrey[4],
1045
- contentPrimary: slateGrey[10],
1046
- contentSecondary: slateGrey[9],
1047
- contentTertiary: slateGrey[8]
1048
- };
1049
- var modal = {
1050
- background: slateGrey[1],
1051
- content: slateGrey[9],
1052
- border: slateGrey[4]
1053
- };
1054
- var tab = {
1055
- activeBackground: slateGrey[3],
1056
- activeContent: brand[5],
1057
- inactiveContent: slateGrey[9],
1058
- border: slateGrey[4]
1059
- };
1060
- var table = {
1061
- header: {
1062
- textDefault: slateGrey[9],
1063
- textHover: slateGrey[8],
1064
- backgroundDefault: slateGrey[1]
1065
- },
1066
- border: slateGrey[4],
1067
- cell: {
1068
- textPrimary: slateGrey[10],
1069
- textSecondary: slateGrey[9],
1070
- backgroundDefault: slateGrey[2],
1071
- backgroundHover: slateGrey[1]
1072
- }
1073
- };
1074
- var breadcrumbs = {
1075
- textPrimaryDefault: slateGrey[10],
1076
- textPrimaryHover: slateGrey[10],
1077
- textSecondaryDefault: slateGrey[8],
1078
- textSecondaryHover: slateGrey[9],
1079
- iconPrimary: slateGrey[10],
1080
- iconSecondary: slateGrey[8]
1081
- };
1082
- var loadingIndicator = {
1083
- background: green[1],
1084
- active: green[5]
1085
- };
1086
- var datePicker = {
1087
- textDefault: slateGrey[10],
1088
- textSelected: base.white,
1089
- textDisabled: slateGrey[7],
1090
- backgroundDefault: slateGrey[2],
1091
- backgroundMiddle: slateGrey[3],
1092
- backgroundSelected: brand[3],
1093
- border: slateGrey[4]
1094
- };
1095
- var scroll = slateGrey[9];
1096
-
1097
- // ../../design-system/dist/tokens/panel-shell.js
1098
- var fab = {
1099
- /** Diameter in pixels. */
1100
- size: 56,
1101
- /** Inset from the panel's top-left corner in pixels. */
1102
- inset: 12,
1103
- /** Background color (always the brand black). */
1104
- background: base.black,
1105
- /** Icon / logo color. */
1106
- color: base.white,
1107
- /** Border — 2px brand red ring. */
1108
- border: `2px solid ${brand[3]}`,
1109
- /** Shadow when the panel is open (inner ring for "active" state). */
1110
- shadowOpen: "0 4px 24px rgba(0,0,0,0.6), 0 0 0 2px rgba(255,255,255,0.08)",
1111
- /** Shadow when the panel is closed. */
1112
- shadowClosed: "0 4px 24px rgba(0,0,0,0.6)"
1113
- };
1114
-
1115
- // src/highlight.ts
1116
- var supportsPathClip = typeof CSS !== "undefined" && CSS.supports?.("clip-path", "path('M0 0 H1 V1 Z')");
1117
- function showHighlight(anchorEl, overlayRoot, opts) {
1118
- const padding = opts?.paddingPx ?? 12;
1119
- const radius = opts?.radiusPx ?? 12;
1120
- const opacity = Math.min(Math.max(opts?.scrimOpacity ?? 0.55, 0), 1);
1121
- const ringColor = opts?.ringColor ?? `var(--syntro-ring, ${blue[5]})`;
1122
- const blocking = opts?.blocking ?? false;
1123
- const onClickOutside = opts?.onClickOutside ?? true;
1124
- const onEsc = opts?.onEsc ?? true;
1125
- const rootStyles = getComputedStyle(document.documentElement);
1126
- const tokenScrim = rootStyles.getPropertyValue("--syntro-spotlight-backdrop").trim();
1127
- const tokenRing = rootStyles.getPropertyValue("--syntro-ring").trim();
1128
- const scrim = document.createElement("div");
1129
- scrim.className = "syntro-spotlight-scrim";
1130
- const needsPointerEvents = blocking || onClickOutside;
1131
- Object.assign(scrim.style, {
1132
- position: "fixed",
1133
- inset: "0",
1134
- zIndex: "2147483646",
1135
- pointerEvents: needsPointerEvents ? "auto" : "none",
1136
- background: tokenScrim || `rgba(2, 6, 23, ${opacity})`,
1137
- transition: "opacity 220ms ease",
1138
- opacity: "0"
1139
- });
1140
- overlayRoot.appendChild(scrim);
1141
- requestAnimationFrame(() => scrim.style.opacity = "1");
1142
- const ring = document.createElement("div");
1143
- ring.className = "syntro-spotlight-ring";
1144
- Object.assign(ring.style, {
1145
- position: "fixed",
1146
- pointerEvents: "none",
1147
- borderRadius: `${radius}px`,
1148
- border: `2px solid ${ringColor || tokenRing || blue[5]}`,
1149
- boxShadow: `0 0 0 4px rgba(255,255,255,0.35)`,
1150
- zIndex: "2147483647",
1151
- transition: "all 220ms cubic-bezier(0.16,1,0.3,1)"
1152
- });
1153
- overlayRoot.appendChild(ring);
1154
- const fallbackSlices = [];
1155
- if (!supportsPathClip) {
1156
- for (let i = 0; i < 4; i++) {
1157
- const slice = document.createElement("div");
1158
- slice.style.position = "fixed";
1159
- slice.style.background = "inherit";
1160
- fallbackSlices.push(slice);
1161
- scrim.appendChild(slice);
1162
- }
1163
- }
1164
- const setClipPath = (path) => {
1165
- scrim.style.clipPath = path;
1166
- scrim.style.webkitClipPath = path;
1167
- };
1168
- const update = () => {
1169
- if (!anchorEl.isConnected) {
1170
- handle.destroy();
1171
- return;
1172
- }
1173
- const rect = anchorEl.getBoundingClientRect();
1174
- const x = rect.left - padding;
1175
- const y = rect.top - padding;
1176
- const w = rect.width + padding * 2;
1177
- const h = rect.height + padding * 2;
1178
- Object.assign(ring.style, {
1179
- left: `${x}px`,
1180
- top: `${y}px`,
1181
- width: `${w}px`,
1182
- height: `${h}px`
1183
- });
1184
- if (supportsPathClip) {
1185
- const vw = window.innerWidth;
1186
- const vh = window.innerHeight;
1187
- const r = Math.min(radius, w / 2, h / 2);
1188
- const outer = `M 0 0 L ${vw} 0 L ${vw} ${vh} L 0 ${vh} Z`;
1189
- const inner = `M ${x + r} ${y} A ${r} ${r} 0 0 0 ${x} ${y + r} L ${x} ${y + h - r} A ${r} ${r} 0 0 0 ${x + r} ${y + h} L ${x + w - r} ${y + h} A ${r} ${r} 0 0 0 ${x + w} ${y + h - r} L ${x + w} ${y + r} A ${r} ${r} 0 0 0 ${x + w - r} ${y} L ${x + r} ${y} Z`;
1190
- setClipPath(`path('${outer} ${inner}')`);
1191
- } else {
1192
- const [top, right, bottom, left] = fallbackSlices;
1193
- Object.assign(top.style, {
1194
- left: "0px",
1195
- top: "0px",
1196
- width: "100vw",
1197
- height: `${y}px`
1198
- });
1199
- Object.assign(bottom.style, {
1200
- left: "0px",
1201
- top: `${y + h}px`,
1202
- width: "100vw",
1203
- height: `${Math.max(0, window.innerHeight - (y + h))}px`
1204
- });
1205
- Object.assign(left.style, {
1206
- left: "0px",
1207
- top: `${y}px`,
1208
- width: `${x}px`,
1209
- height: `${h}px`
1210
- });
1211
- Object.assign(right.style, {
1212
- left: `${x + w}px`,
1213
- top: `${y}px`,
1214
- width: `${Math.max(0, window.innerWidth - (x + w))}px`,
1215
- height: `${h}px`
1216
- });
1217
- }
1218
- };
1219
- const ro = new ResizeObserver(() => requestAnimationFrame(update));
1220
- ro.observe(anchorEl);
1221
- const onScroll = () => requestAnimationFrame(update);
1222
- const onResize = () => requestAnimationFrame(update);
1223
- window.addEventListener("scroll", onScroll, true);
1224
- window.addEventListener("resize", onResize);
1225
- const onKey = (e) => {
1226
- if (e.key === "Escape" && onEsc) {
1227
- opts?.onDismiss?.();
1228
- handle.destroy();
1229
- }
1230
- };
1231
- if (onEsc) {
1232
- window.addEventListener("keydown", onKey);
1233
- }
1234
- const onClick = (event) => {
1235
- if (blocking) {
1236
- event.preventDefault();
1237
- event.stopPropagation();
1238
- } else if (onClickOutside) {
1239
- opts?.onDismiss?.();
1240
- handle.destroy();
1241
- }
1242
- };
1243
- scrim.addEventListener("click", onClick);
1244
- const handle = {
1245
- destroy() {
1246
- ro.disconnect();
1247
- window.removeEventListener("scroll", onScroll, true);
1248
- window.removeEventListener("resize", onResize);
1249
- if (onEsc) {
1250
- window.removeEventListener("keydown", onKey);
1251
- }
1252
- scrim.removeEventListener("click", onClick);
1253
- scrim.style.pointerEvents = "none";
1254
- scrim.style.opacity = "0";
1255
- setTimeout(() => {
1256
- try {
1257
- scrim.remove();
1258
- } catch {
1259
- }
1260
- try {
1261
- ring.remove();
1262
- } catch {
1263
- }
1264
- }, 220);
1265
- }
1266
- };
1267
- update();
1268
- return handle;
1269
- }
1270
-
1271
- // src/sanitizer.ts
1272
- var ALLOWED_TAGS = /* @__PURE__ */ new Set([
1273
- "b",
1274
- "strong",
1275
- "i",
1276
- "em",
1277
- "u",
1278
- "span",
1279
- "div",
1280
- "p",
1281
- "br",
1282
- "ul",
1283
- "ol",
1284
- "li",
1285
- "code",
1286
- "pre",
1287
- "small",
1288
- "sup",
1289
- "sub",
1290
- "a",
1291
- "button",
1292
- // SVG elements (for inline Lucide icons in config HTML)
1293
- "svg",
1294
- "path",
1295
- "circle",
1296
- "line",
1297
- "polyline",
1298
- "polygon",
1299
- "rect",
1300
- "g"
1301
- ]);
1302
- function sanitizeHtml(html2) {
1303
- const hasNative = typeof window.Sanitizer === "function";
1304
- if (hasNative) {
1305
- try {
1306
- const s = new window.Sanitizer({});
1307
- const frag = s.sanitizeToFragment(html2);
1308
- const div = document.createElement("div");
1309
- div.append(frag);
1310
- return div.innerHTML;
1311
- } catch {
1312
- }
1313
- }
1314
- const tpl = document.createElement("template");
1315
- tpl.innerHTML = html2;
1316
- const root = tpl.content;
1317
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
1318
- const toRemove = [];
1319
- while (walker.nextNode()) {
1320
- const el = walker.currentNode;
1321
- const tag2 = el.tagName.toLowerCase();
1322
- if (!ALLOWED_TAGS.has(tag2)) {
1323
- toRemove.push(el);
1324
- continue;
1325
- }
1326
- for (const attr of Array.from(el.attributes)) {
1327
- const name = attr.name.toLowerCase();
1328
- const value = attr.value.trim().toLowerCase();
1329
- const isEvent = name.startsWith("on");
1330
- const isJsUrl = (name === "href" || name === "src") && value.startsWith("javascript:");
1331
- if (isEvent || isJsUrl) {
1332
- el.removeAttribute(attr.name);
1333
- }
1334
- }
1335
- }
1336
- for (const el of toRemove) {
1337
- while (el.firstChild) {
1338
- el.parentNode?.insertBefore(el.firstChild, el);
1339
- }
1340
- el.remove();
1341
- }
1342
- return tpl.innerHTML;
1343
- }
1344
-
1345
- // src/modal.ts
1346
- var V = {
1347
- bg: "var(--sc-overlay-background, #ffffff)",
1348
- title: "var(--sc-overlay-title-color, var(--sc-overlay-text-color, #111827))",
1349
- text: "var(--sc-overlay-text-color, #4b5563)",
1350
- accent: "var(--sc-color-primary, #4f46e5)",
1351
- radius: "var(--sc-border-radius, 12px)"
1352
- };
1353
- var executeModal = async (action, context) => {
1354
- const { content, size = "md", blocking = false, scrim, dismiss, ctaButtons } = action;
1355
- const scrimEl = document.createElement("div");
1356
- scrimEl.className = "syntro-modal-scrim";
1357
- scrimEl.style.cssText = `
1358
- position: fixed;
1359
- inset: 0;
1360
- background: rgba(0, 0, 0, ${scrim?.opacity ?? 0.6});
1361
- z-index: 2147483645;
1362
- opacity: 0;
1363
- transition: opacity 200ms ease-out;
1364
- `;
1365
- context.overlayRoot.appendChild(scrimEl);
1366
- const modal2 = document.createElement("div");
1367
- modal2.className = `syntro-modal syntro-modal-${size}`;
1368
- modal2.setAttribute("role", "dialog");
1369
- modal2.setAttribute("aria-modal", "true");
1370
- const sizeMap = { sm: "360px", md: "480px", lg: "640px" };
1371
- modal2.style.cssText = `
1372
- position: fixed;
1373
- top: 50%;
1374
- left: 50%;
1375
- transform: translate(-50%, -50%) scale(0.95);
1376
- max-width: ${sizeMap[size]};
1377
- width: 90%;
1378
- background: ${V.bg};
1379
- border-radius: ${V.radius};
1380
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1381
- z-index: 2147483646;
1382
- opacity: 0;
1383
- transition: opacity 200ms ease-out, transform 200ms ease-out;
1384
- padding: 24px;
1385
- `;
1386
- let html2 = "";
1387
- if (content.title) {
1388
- html2 += `<h2 class="syntro-modal-title" style="margin: 0 0 12px 0; font-size: 18px; font-weight: 600; color: ${V.title};">${sanitizeHtml(content.title)}</h2>`;
1389
- }
1390
- html2 += `<div class="syntro-modal-body" style="color: ${V.text}; line-height: 1.5;">${sanitizeHtml(content.body)}</div>`;
1391
- if (dismiss?.closeButton !== false) {
1392
- html2 += `
1393
- <button class="syntro-modal-close" data-syntro-action="dismiss" style="
1394
- position: absolute;
1395
- top: 16px;
1396
- right: 16px;
1397
- background: none;
1398
- border: none;
1399
- cursor: pointer;
1400
- padding: 4px;
1401
- color: ${V.text};
1402
- opacity: 0.6;
1403
- " aria-label="Close">
1404
- <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
1405
- <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
1406
- </svg>
1407
- </button>
1408
- `;
1409
- }
1410
- if (ctaButtons && ctaButtons.length > 0) {
1411
- html2 += `<div class="syntro-modal-actions" style="display: flex; gap: 12px; margin-top: 24px; justify-content: flex-end;">`;
1412
- for (const btn of ctaButtons) {
1413
- const isPrimary = btn.primary ?? false;
1414
- html2 += `
1415
- <button
1416
- class="syntro-modal-btn ${isPrimary ? "syntro-modal-btn-primary" : ""}"
1417
- data-syntro-action="${sanitizeHtml(btn.actionId)}"
1418
- style="
1419
- padding: 10px 20px;
1420
- border-radius: 8px;
1421
- font-size: 14px;
1422
- font-weight: 500;
1423
- cursor: pointer;
1424
- transition: background 150ms ease;
1425
- ${isPrimary ? `background: ${V.accent}; color: white; border: none;` : `background: transparent; color: ${V.accent}; border: 1px solid currentColor; opacity: 0.7;`}
1426
- "
1427
- >
1428
- ${sanitizeHtml(btn.label)}
1429
- </button>
1430
- `;
1431
- }
1432
- html2 += `</div>`;
1433
- }
1434
- modal2.innerHTML = html2;
1435
- context.overlayRoot.appendChild(modal2);
1436
- let actionClicked = null;
1437
- const actionBtns = modal2.querySelectorAll("[data-syntro-action]");
1438
- const actionHandler = (e) => {
1439
- const btn = e.currentTarget;
1440
- const actionId = btn.getAttribute("data-syntro-action");
1441
- if (actionId) {
1442
- actionClicked = actionId;
1443
- const matchingBtn = ctaButtons?.find((b) => b.actionId === actionId);
1444
- const href = matchingBtn?.href;
1445
- context.publishEvent("action.modal_cta_clicked", {
1446
- actionId,
1447
- ...href ? { href } : {}
1448
- });
1449
- handle.destroy();
1450
- if (matchingBtn) {
1451
- navigateForCta(matchingBtn);
1452
- }
1453
- }
1454
- };
1455
- actionBtns.forEach((btn) => btn.addEventListener("click", actionHandler));
1456
- const onKey = (e) => {
1457
- if (e.key === "Escape" && dismiss?.onEsc !== false) {
1458
- handle.destroy();
1459
- }
1460
- };
1461
- window.addEventListener("keydown", onKey);
1462
- const onScrimClick = () => {
1463
- if (!blocking) {
1464
- handle.destroy();
1465
- }
1466
- };
1467
- scrimEl.addEventListener("click", onScrimClick);
1468
- const originalInert = [];
1469
- if (blocking) {
1470
- Array.from(document.body.children).forEach((el) => {
1471
- if (el !== context.overlayRoot && el.getAttribute("inert") === null && !el.querySelector("[data-syntro-editor-panel]") && !el.hasAttribute("data-syntro-editor-panel")) {
1472
- el.setAttribute("inert", "");
1473
- originalInert.push(el);
1474
- }
1475
- });
1476
- }
1477
- let timeoutId;
1478
- if (dismiss?.timeoutMs) {
1479
- timeoutId = setTimeout(() => {
1480
- handle.destroy();
1481
- }, dismiss.timeoutMs);
1482
- }
1483
- const focusableEls = modal2.querySelectorAll(
1484
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1485
- );
1486
- if (focusableEls.length > 0) {
1487
- requestAnimationFrame(() => focusableEls[0].focus());
1488
- }
1489
- requestAnimationFrame(() => {
1490
- scrimEl.style.opacity = "1";
1491
- modal2.style.opacity = "1";
1492
- modal2.style.transform = "translate(-50%, -50%) scale(1)";
1493
- });
1494
- context.publishEvent("action.applied", {
1495
- id: context.generateId(),
1496
- kind: "overlays:modal",
1497
- size,
1498
- blocking
1499
- });
1500
- const handle = {
1501
- destroy() {
1502
- if (timeoutId) {
1503
- clearTimeout(timeoutId);
1504
- }
1505
- window.removeEventListener("keydown", onKey);
1506
- scrimEl.removeEventListener("click", onScrimClick);
1507
- actionBtns.forEach((btn) => btn.removeEventListener("click", actionHandler));
1508
- originalInert.forEach((el) => el.removeAttribute("inert"));
1509
- modal2.style.pointerEvents = "none";
1510
- scrimEl.style.pointerEvents = "none";
1511
- modal2.style.opacity = "0";
1512
- modal2.style.transform = "translate(-50%, -50%) scale(0.95)";
1513
- scrimEl.style.opacity = "0";
1514
- setTimeout(() => {
1515
- try {
1516
- modal2.remove();
1517
- } catch {
1518
- }
1519
- try {
1520
- scrimEl.remove();
1521
- } catch {
1522
- }
1523
- }, 200);
1524
- context.publishEvent("action.modal_dismissed", {
1525
- actionClicked
1526
- });
1527
- }
1528
- };
1529
- return {
1530
- cleanup: () => {
1531
- handle.destroy();
1532
- }
1533
- };
1534
- };
1535
-
1536
- // src/tooltip.ts
1537
- import {
1538
- arrow as arrowMiddleware,
1539
- autoUpdate,
1540
- computePosition,
1541
- flip,
1542
- hide,
1543
- offset,
1544
- shift
1545
- } from "@floating-ui/dom";
1546
- function getAnchorReference(anchorEl) {
1547
- const rect = anchorEl.getBoundingClientRect();
1548
- const viewportWidth = window.innerWidth;
1549
- const viewportHeight = window.innerHeight;
1550
- const isLargeElement = rect.width > viewportWidth * 0.8 || rect.height > viewportHeight * 0.8;
1551
- if (!isLargeElement) {
1552
- return anchorEl;
1553
- }
1554
- const visibleLeft = Math.max(rect.left, 0);
1555
- const visibleTop = Math.max(rect.top, 0);
1556
- const visibleRight = Math.min(rect.right, viewportWidth);
1557
- const visibleBottom = Math.min(rect.bottom, viewportHeight);
1558
- const centerX = (visibleLeft + visibleRight) / 2;
1559
- const centerY = (visibleTop + visibleBottom) / 2;
1560
- return {
1561
- getBoundingClientRect() {
1562
- return {
1563
- width: 0,
1564
- height: 0,
1565
- x: centerX,
1566
- y: centerY,
1567
- top: centerY,
1568
- left: centerX,
1569
- right: centerX,
1570
- bottom: centerY
1571
- };
1572
- }
1573
- };
1574
- }
1575
- function showTooltip(anchorEl, overlayRoot, opts) {
1576
- if (!opts.trigger || opts.trigger === "immediate") {
1577
- const rect = anchorEl.getBoundingClientRect();
1578
- const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
1579
- if (!isLargeElement) {
1580
- anchorEl.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
1581
- }
1582
- }
1583
- const div = document.createElement("div");
1584
- div.className = "syntro-tooltip";
1585
- div.setAttribute("role", "tooltip");
1586
- div.innerHTML = sanitizeHtml(opts.html);
1587
- if (!opts.trigger || opts.trigger === "immediate") {
1588
- const closeBtn = document.createElement("button");
1589
- closeBtn.className = "syntro-tooltip-close";
1590
- closeBtn.setAttribute("aria-label", "Close");
1591
- closeBtn.textContent = "\xD7";
1592
- Object.assign(closeBtn.style, {
1593
- position: "absolute",
1594
- top: "4px",
1595
- right: "4px",
1596
- background: "none",
1597
- border: "none",
1598
- color: "inherit",
1599
- fontSize: "16px",
1600
- lineHeight: "1",
1601
- cursor: "pointer",
1602
- opacity: "0.6",
1603
- padding: "2px 4px"
1604
- });
1605
- closeBtn.addEventListener("mouseenter", () => {
1606
- closeBtn.style.opacity = "1";
1607
- });
1608
- closeBtn.addEventListener("mouseleave", () => {
1609
- closeBtn.style.opacity = "0.6";
1610
- });
1611
- closeBtn.addEventListener("click", () => handle.destroy());
1612
- div.style.position = "relative";
1613
- div.appendChild(closeBtn);
1614
- }
1615
- const actionBtns = div.querySelectorAll("[data-syntro-action]");
1616
- const actionHandler = (e) => {
1617
- const btn = e.currentTarget;
1618
- const actionId = btn.getAttribute("data-syntro-action");
1619
- if (actionId && opts.onAction) {
1620
- opts.onAction(actionId);
1621
- }
1622
- };
1623
- actionBtns.forEach((btn) => btn.addEventListener("click", actionHandler));
1624
- const arrowEl = document.createElement("div");
1625
- arrowEl.className = "syntro-tooltip-arrow";
1626
- div.appendChild(arrowEl);
1627
- overlayRoot.appendChild(div);
1628
- const middleware = [
1629
- offset(opts.offsetPx ?? 8),
1630
- flip(),
1631
- shift({ padding: 8 }),
1632
- hide(),
1633
- arrowMiddleware({ element: arrowEl })
1634
- ];
1635
- const placement = opts.placement && opts.placement !== "auto" ? opts.placement : "top";
1636
- const cleanup = autoUpdate(anchorEl, div, async () => {
1637
- if (!anchorEl.isConnected) {
1638
- handle.destroy();
1639
- return;
1640
- }
1641
- const currentAnchorRef = getAnchorReference(anchorEl);
1642
- const result = await computePosition(currentAnchorRef, div, {
1643
- placement,
1644
- strategy: "fixed",
1645
- middleware
1646
- });
1647
- const { x, y, strategy, middlewareData, placement: finalPlacement } = result;
1648
- Object.assign(div.style, {
1649
- left: `${x}px`,
1650
- top: `${y}px`,
1651
- position: strategy
1652
- });
1653
- if (middlewareData.arrow) {
1654
- const { x: arrowX, y: arrowY } = middlewareData.arrow;
1655
- const side = finalPlacement.split("-")[0];
1656
- const staticSide = {
1657
- top: "bottom",
1658
- right: "left",
1659
- bottom: "top",
1660
- left: "right"
1661
- };
1662
- Object.assign(arrowEl.style, {
1663
- left: arrowX != null ? `${arrowX}px` : "",
1664
- top: arrowY != null ? `${arrowY}px` : "",
1665
- right: "",
1666
- bottom: "",
1667
- [staticSide[side]]: "-4px"
1668
- });
1669
- const rotation = {
1670
- top: "0deg",
1671
- right: "90deg",
1672
- bottom: "180deg",
1673
- left: "270deg"
1674
- };
1675
- arrowEl.style.transform = `rotate(${rotation[side] || "0deg"})`;
1676
- }
1677
- });
1678
- const onKey = (e) => {
1679
- if (e.key === "Escape") handle.destroy();
1680
- };
1681
- window.addEventListener("keydown", onKey);
1682
- const originalInert = [];
1683
- if (opts.blocking) {
1684
- Array.from(document.body.children).forEach((el) => {
1685
- if (el !== overlayRoot && el.getAttribute("inert") === null) {
1686
- el.setAttribute("inert", "");
1687
- originalInert.push(el.id || el.tagName);
1688
- }
1689
- });
1690
- const focusableEls = Array.from(
1691
- div.querySelectorAll(
1692
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1693
- )
1694
- );
1695
- if (focusableEls.length > 0) {
1696
- const firstFocusable = focusableEls[0];
1697
- const lastFocusable = focusableEls[focusableEls.length - 1];
1698
- const trapFocus = (e) => {
1699
- if (e.key !== "Tab") return;
1700
- if (e.shiftKey) {
1701
- if (document.activeElement === firstFocusable) {
1702
- lastFocusable.focus();
1703
- e.preventDefault();
1704
- }
1705
- } else if (document.activeElement === lastFocusable) {
1706
- firstFocusable.focus();
1707
- e.preventDefault();
1708
- }
1709
- };
1710
- div.addEventListener("keydown", trapFocus);
1711
- requestAnimationFrame(() => firstFocusable.focus());
1712
- }
1713
- }
1714
- const attachTrigger = () => {
1715
- if (opts.trigger === "hover") {
1716
- let hideTimeout = null;
1717
- const show = () => {
1718
- if (hideTimeout) {
1719
- clearTimeout(hideTimeout);
1720
- hideTimeout = null;
1721
- }
1722
- div.style.visibility = "visible";
1723
- div.style.opacity = "1";
1724
- };
1725
- const scheduleHide = () => {
1726
- hideTimeout = setTimeout(() => {
1727
- div.style.visibility = "hidden";
1728
- div.style.opacity = "0";
1729
- hideTimeout = null;
1730
- }, 100);
1731
- };
1732
- div.style.visibility = "hidden";
1733
- div.style.opacity = "0";
1734
- div.style.transition = "opacity 200ms ease, visibility 200ms";
1735
- anchorEl.addEventListener("mouseenter", show);
1736
- anchorEl.addEventListener("mouseleave", scheduleHide);
1737
- div.addEventListener("mouseenter", show);
1738
- div.addEventListener("mouseleave", scheduleHide);
1739
- anchorEl.addEventListener("focus", show);
1740
- anchorEl.addEventListener("blur", scheduleHide);
1741
- return () => {
1742
- if (hideTimeout) clearTimeout(hideTimeout);
1743
- anchorEl.removeEventListener("mouseenter", show);
1744
- anchorEl.removeEventListener("mouseleave", scheduleHide);
1745
- div.removeEventListener("mouseenter", show);
1746
- div.removeEventListener("mouseleave", scheduleHide);
1747
- anchorEl.removeEventListener("focus", show);
1748
- anchorEl.removeEventListener("blur", scheduleHide);
1749
- };
1750
- }
1751
- if (opts.trigger === "click") {
1752
- const toggle2 = () => {
1753
- const isVisible = div.style.visibility === "visible";
1754
- if (isVisible) {
1755
- handle.destroy();
1756
- } else {
1757
- div.style.visibility = "visible";
1758
- div.style.opacity = "1";
1759
- }
1760
- };
1761
- div.style.visibility = "hidden";
1762
- div.style.opacity = "0";
1763
- div.style.transition = "opacity 200ms ease, visibility 200ms";
1764
- anchorEl.addEventListener("click", toggle2);
1765
- return () => anchorEl.removeEventListener("click", toggle2);
1766
- }
1767
- div.style.opacity = "0";
1768
- div.style.transition = "opacity 200ms ease";
1769
- requestAnimationFrame(() => {
1770
- div.style.opacity = "1";
1771
- });
1772
- return () => {
1773
- };
1774
- };
1775
- const removeTrigger = attachTrigger();
1776
- const handle = {
1777
- el: div,
1778
- destroy() {
1779
- cleanup();
1780
- removeTrigger();
1781
- window.removeEventListener("keydown", onKey);
1782
- actionBtns.forEach((btn) => btn.removeEventListener("click", actionHandler));
1783
- if (opts.blocking) {
1784
- Array.from(document.body.children).forEach((el) => {
1785
- if (el !== overlayRoot) {
1786
- el.removeAttribute("inert");
1787
- }
1788
- });
1789
- }
1790
- div.style.pointerEvents = "none";
1791
- div.style.opacity = "0";
1792
- setTimeout(() => {
1793
- try {
1794
- div.remove();
1795
- } catch {
1796
- }
1797
- }, 200);
1798
- }
1799
- };
1800
- return handle;
1801
- }
1802
-
1803
- // src/WorkflowWidgetLit.ts
1804
- import { html, LitElement, nothing } from "lit";
1805
- import { styleMap } from "lit/directives/style-map.js";
1806
- var TOKEN_BLUE_4 = "#1969fe";
1807
- var TOKEN_GREEN_4 = "#24ad32";
1808
- var TOKEN_SLATE_2 = "#0e1114";
1809
- var TOKEN_SLATE_7 = "#677384";
1810
- var TOKEN_SLATE_9 = "#a8afba";
1811
- var TOKEN_SLATE_12 = "#f6f7f9";
1812
- var TOKEN_WHITE = "#ffffff";
1813
- function showWorkflowToast(notification) {
1814
- const toast = document.createElement("div");
1815
- toast.setAttribute("data-testid", "workflow-toast");
1816
- Object.assign(toast.style, {
1817
- position: "fixed",
1818
- bottom: "16px",
1819
- right: "16px",
1820
- zIndex: "2147483646",
1821
- padding: "12px 16px",
1822
- borderRadius: "8px",
1823
- backgroundColor: `var(--se-color-bg-surface, ${TOKEN_WHITE})`,
1824
- color: `var(--se-color-text-primary, ${TOKEN_SLATE_2})`,
1825
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
1826
- maxWidth: "320px",
1827
- fontFamily: "var(--se-font-family, system-ui, sans-serif)",
1828
- fontSize: "14px",
1829
- lineHeight: "1.4",
1830
- transition: "opacity 0.3s ease"
1831
- });
1832
- const titleEl = document.createElement("div");
1833
- titleEl.style.fontWeight = "600";
1834
- titleEl.textContent = notification.title;
1835
- toast.appendChild(titleEl);
1836
- if (notification.body) {
1837
- const bodyEl = document.createElement("div");
1838
- bodyEl.style.marginTop = "4px";
1839
- bodyEl.style.fontSize = "13px";
1840
- bodyEl.style.color = "var(--se-color-text-secondary, #666)";
1841
- bodyEl.textContent = notification.body;
1842
- toast.appendChild(bodyEl);
1843
- }
1844
- document.body.appendChild(toast);
1845
- let removeTimer;
1846
- const fadeTimer = setTimeout(() => {
1847
- toast.style.opacity = "0";
1848
- removeTimer = setTimeout(() => {
1849
- toast.remove();
1850
- }, 300);
1851
- }, 4e3);
1852
- return () => {
1853
- clearTimeout(fadeTimer);
1854
- clearTimeout(removeTimer);
1855
- toast.remove();
1856
- };
1857
- }
1858
- function extractWorkflowsFromActive(activeActions) {
1859
- const workflows = /* @__PURE__ */ new Map();
1860
- for (const entry of activeActions) {
1861
- const action = entry.action;
1862
- if (action.kind === "overlays:tour" && action.workflow && action.tourId) {
1863
- const meta = action.workflow;
1864
- const rawSteps = action.steps || [];
1865
- const steps = rawSteps.map((s) => ({
1866
- id: s.id,
1867
- title: meta.stepTitles?.[s.id] || s.id
1868
- }));
1869
- workflows.set(action.tourId, { meta, steps });
1870
- }
1871
- }
1872
- return workflows;
1873
- }
1874
- var TAG_NAME = "syntro-workflow-tracker";
1875
- var _unsubTourStarted, _unsubTourEvents, _toastCleanups, _notified, _completedMap, _persistInitialized, _tourWorkflows;
1876
- var WorkflowTrackerLit = class extends LitElement {
1877
- constructor() {
1878
- super(...arguments);
1879
- // ── Public properties ─────────────────────────────────────────────────────
1880
- this.runtimeRef = null;
1881
- // ── Internal reactive state ───────────────────────────────────────────────
1882
- /** @internal */
1883
- this._workflowEntries = [];
1884
- /**
1885
- * @internal
1886
- * Bumped on tour.started / tour.resumed to trigger re-scan of active actions.
1887
- */
1888
- /** @internal */
1889
- this._actionVersion = 0;
1890
- // ── Private (non-reactive) fields ─────────────────────────────────────────
1891
- // Subscription cleanup functions
1892
- __privateAdd(this, _unsubTourStarted, null);
1893
- __privateAdd(this, _unsubTourEvents, null);
1894
- // Toast cleanup tracking
1895
- __privateAdd(this, _toastCleanups, []);
1896
- // Notifications already shown (mirrors notifiedRef)
1897
- __privateAdd(this, _notified, /* @__PURE__ */ new Set());
1898
- // Completed timestamps (mirrors completedMapRef)
1899
- __privateAdd(this, _completedMap, {});
1900
- // Whether persisted state has been loaded
1901
- __privateAdd(this, _persistInitialized, false);
1902
- // Cache of the last scanned tourWorkflows map (used by event handler)
1903
- __privateAdd(this, _tourWorkflows, /* @__PURE__ */ new Map());
1904
- }
1905
- // ── Light DOM ─────────────────────────────────────────────────────────────
1906
- /**
1907
- * Render into the element itself (light DOM) so host-page CSS variables
1908
- * flow through without a nested shadow boundary.
1909
- */
1910
- createRenderRoot() {
1911
- return this;
1912
- }
1913
- // ── Helpers ───────────────────────────────────────────────────────────────
1914
- get _stateNs() {
1915
- return this.runtimeRef?.state?.user?.ns?.("workflows") ?? null;
1916
- }
1917
- /**
1918
- * Re-scan active actions and update _tourWorkflows + entries.
1919
- * Called initially and whenever _actionVersion bumps.
1920
- */
1921
- _rescanWorkflows() {
1922
- const active = this.runtimeRef?.actions?.getActive?.() ?? [];
1923
- const workflows = extractWorkflowsFromActive(active);
1924
- __privateSet(this, _tourWorkflows, workflows);
1925
- if (workflows.size === 0) return;
1926
- const stateNs = this._stateNs;
1927
- const dismissed = stateNs?.get("dismissed") ?? [];
1928
- const completed = stateNs?.get("completed") ?? {};
1929
- this._workflowEntries = (() => {
1930
- const existingIds = new Set(this._workflowEntries.map((e) => e.tourId));
1931
- const newEntries = [];
1932
- for (const [tourId, { meta, steps }] of workflows) {
1933
- if (existingIds.has(tourId)) continue;
1934
- let status = "active";
1935
- if (dismissed.includes(tourId)) {
1936
- status = "dismissed";
1937
- } else if (completed[tourId]) {
1938
- status = "completed";
1939
- }
1940
- newEntries.push({
1941
- tourId,
1942
- meta,
1943
- steps,
1944
- currentStepId: null,
1945
- completedSteps: [],
1946
- status,
1947
- completedAt: completed[tourId] || void 0
1948
- });
1949
- }
1950
- return newEntries.length > 0 ? [...this._workflowEntries, ...newEntries] : this._workflowEntries;
1951
- })();
1952
- for (const [tourId, { meta }] of workflows) {
1953
- const dismissed2 = stateNs?.get("dismissed") ?? [];
1954
- const completed2 = stateNs?.get("completed") ?? {};
1955
- if (!__privateGet(this, _notified).has(tourId) && meta.notification && !dismissed2.includes(tourId) && !completed2[tourId]) {
1956
- __privateGet(this, _notified).add(tourId);
1957
- stateNs?.set("notified", [...__privateGet(this, _notified)]);
1958
- const cleanup = showWorkflowToast(meta.notification);
1959
- __privateGet(this, _toastCleanups).push(cleanup);
1960
- }
1961
- }
1962
- }
1963
- // ── Lifecycle ─────────────────────────────────────────────────────────────
1964
- connectedCallback() {
1965
- super.connectedCallback();
1966
- this._initSubscriptions();
1967
- }
1968
- disconnectedCallback() {
1969
- super.disconnectedCallback();
1970
- this._teardownSubscriptions();
1971
- for (const cleanup of __privateGet(this, _toastCleanups)) {
1972
- cleanup();
1973
- }
1974
- __privateSet(this, _toastCleanups, []);
1975
- }
1976
- updated(changed) {
1977
- if (changed.has("runtimeRef")) {
1978
- this._teardownSubscriptions();
1979
- this._initSubscriptions();
1980
- }
1981
- if (changed.has("_actionVersion")) {
1982
- this._rescanWorkflows();
1983
- }
1984
- }
1985
- // ── Subscription management ───────────────────────────────────────────────
1986
- _initSubscriptions() {
1987
- if (!this.runtimeRef?.events?.subscribe) return;
1988
- if (!__privateGet(this, _persistInitialized) && this._stateNs) {
1989
- const notified = this._stateNs.get("notified") ?? [];
1990
- for (const id of notified) {
1991
- __privateGet(this, _notified).add(id);
1992
- }
1993
- const completed = this._stateNs.get("completed") ?? {};
1994
- __privateSet(this, _completedMap, { ...completed });
1995
- __privateSet(this, _persistInitialized, true);
1996
- }
1997
- __privateSet(this, _unsubTourStarted, this.runtimeRef.events.subscribe(
1998
- { names: ["tour.started", "tour.resumed"] },
1999
- () => {
2000
- this._actionVersion += 1;
2001
- }
2002
- ));
2003
- __privateSet(this, _unsubTourEvents, this.runtimeRef.events.subscribe(
2004
- { patterns: ["^tour\\."] },
2005
- (event) => {
2006
- this._handleTourEvent(event);
2007
- }
2008
- ));
2009
- this._rescanWorkflows();
2010
- }
2011
- _teardownSubscriptions() {
2012
- var _a, _b;
2013
- (_a = __privateGet(this, _unsubTourStarted)) == null ? void 0 : _a.call(this);
2014
- __privateSet(this, _unsubTourStarted, null);
2015
- (_b = __privateGet(this, _unsubTourEvents)) == null ? void 0 : _b.call(this);
2016
- __privateSet(this, _unsubTourEvents, null);
2017
- }
2018
- // ── Event handler ─────────────────────────────────────────────────────────
2019
- _handleTourEvent(event) {
2020
- const tourId = event.props?.tourId;
2021
- if (!tourId) return;
2022
- if (!__privateGet(this, _tourWorkflows).has(tourId) && event.name === "tour.started") {
2023
- this._actionVersion += 1;
2024
- return;
2025
- }
2026
- if (!__privateGet(this, _tourWorkflows).has(tourId)) return;
2027
- const stateNs = this._stateNs;
2028
- this._workflowEntries = this._workflowEntries.map((entry) => {
2029
- if (entry.tourId !== tourId) return entry;
2030
- switch (event.name) {
2031
- case "tour.started": {
2032
- const startStepId = event.props?.startStepId || entry.steps[0]?.id || null;
2033
- if (!__privateGet(this, _notified).has(tourId)) {
2034
- __privateGet(this, _notified).add(tourId);
2035
- stateNs?.set("notified", [...__privateGet(this, _notified)]);
2036
- const workflow = __privateGet(this, _tourWorkflows).get(tourId);
2037
- if (workflow?.meta.notification) {
2038
- const cleanup = showWorkflowToast(workflow.meta.notification);
2039
- __privateGet(this, _toastCleanups).push(cleanup);
2040
- }
2041
- }
2042
- const activeIds = this._workflowEntries.filter((e) => e.status === "active" || e.tourId === tourId).map((e) => e.tourId);
2043
- if (!activeIds.includes(tourId)) {
2044
- activeIds.push(tourId);
2045
- }
2046
- stateNs?.set("active", [...new Set(activeIds)]);
2047
- return {
2048
- ...entry,
2049
- status: "active",
2050
- currentStepId: startStepId,
2051
- completedSteps: entry.status === "active" ? entry.completedSteps : []
2052
- };
2053
- }
2054
- case "tour.step_started": {
2055
- const stepId = event.props?.stepId;
2056
- return {
2057
- ...entry,
2058
- currentStepId: stepId || entry.currentStepId
2059
- };
2060
- }
2061
- case "tour.step_changed": {
2062
- const previousStepId = event.props?.previousStepId;
2063
- const nextStepId = event.props?.nextStepId;
2064
- const completedSteps = previousStepId && !entry.completedSteps.includes(previousStepId) ? [...entry.completedSteps, previousStepId] : entry.completedSteps;
2065
- return {
2066
- ...entry,
2067
- currentStepId: nextStepId || entry.currentStepId,
2068
- completedSteps
2069
- };
2070
- }
2071
- case "tour.completed": {
2072
- const completedAt = Date.now();
2073
- __privateGet(this, _completedMap)[tourId] = completedAt;
2074
- stateNs?.set("completed", { ...__privateGet(this, _completedMap) });
2075
- return {
2076
- ...entry,
2077
- status: "completed",
2078
- currentStepId: null,
2079
- completedSteps: entry.steps.map((s) => s.id),
2080
- completedAt
2081
- };
2082
- }
2083
- case "tour.paused":
2084
- return entry;
2085
- default:
2086
- return entry;
2087
- }
2088
- });
2089
- }
2090
- // ── User action handlers ──────────────────────────────────────────────────
2091
- _handleStepClick(tourId, stepId) {
2092
- this.runtimeRef?.events?.publish("workflow:jump_to_step", { tourId, stepId });
2093
- this.dispatchEvent(
2094
- new CustomEvent("workflow-step-click", {
2095
- bubbles: true,
2096
- detail: { tourId, stepId }
2097
- })
2098
- );
2099
- }
2100
- _handleDismiss(tourId) {
2101
- this._workflowEntries = this._workflowEntries.map(
2102
- (entry) => entry.tourId === tourId ? { ...entry, status: "dismissed" } : entry
2103
- );
2104
- const dismissedIds = this._workflowEntries.filter((e) => e.status === "dismissed").map((e) => e.tourId);
2105
- this._stateNs?.set("dismissed", dismissedIds);
2106
- this.dispatchEvent(
2107
- new CustomEvent("workflow-dismissed", {
2108
- bubbles: true,
2109
- detail: { tourId }
2110
- })
2111
- );
2112
- }
2113
- // ── Render helpers ────────────────────────────────────────────────────────
2114
- _renderProgressBar(completed, total) {
2115
- const percent = total > 0 ? Math.round(completed / total * 100) : 0;
2116
- const trackStyles = {
2117
- width: "100%",
2118
- height: "6px",
2119
- borderRadius: "9999px",
2120
- background: "rgba(255,255,255,0.08)",
2121
- overflow: "hidden"
2122
- };
2123
- const fillStyles = {
2124
- height: "100%",
2125
- borderRadius: "9999px",
2126
- background: `var(--se-color-primary, ${TOKEN_BLUE_4})`,
2127
- transition: "width 0.3s ease",
2128
- width: `${percent}%`
2129
- };
2130
- return html`
2131
- <div style=${styleMap(trackStyles)}>
2132
- <div
2133
- role="progressbar"
2134
- aria-valuenow=${percent}
2135
- aria-valuemin="0"
2136
- aria-valuemax="100"
2137
- style=${styleMap(fillStyles)}
2138
- ></div>
2139
- </div>
2140
- `;
2141
- }
2142
- _renderStepItem(step, isCompleted, isCurrent, tourId) {
2143
- const btnStyles = {
2144
- display: "flex",
2145
- alignItems: "center",
2146
- gap: "8px",
2147
- width: "100%",
2148
- padding: "6px 8px",
2149
- borderRadius: "4px",
2150
- border: "none",
2151
- background: "transparent",
2152
- cursor: "pointer",
2153
- textAlign: "left",
2154
- fontSize: "12px",
2155
- lineHeight: "1.4",
2156
- color: isCurrent ? `var(--se-color-text-primary, ${TOKEN_SLATE_12})` : `var(--se-color-text-secondary, ${TOKEN_SLATE_9})`,
2157
- fontWeight: isCurrent ? "600" : "400"
2158
- };
2159
- const indicatorWrapStyles = {
2160
- flexShrink: "0",
2161
- width: "16px",
2162
- textAlign: "center"
2163
- };
2164
- const dotStyles = {
2165
- display: "inline-block",
2166
- width: "6px",
2167
- height: "6px",
2168
- borderRadius: "50%",
2169
- background: isCurrent ? `var(--se-color-primary, ${TOKEN_BLUE_4})` : "rgba(255,255,255,0.12)"
2170
- };
2171
- const checkStyles = {
2172
- color: `var(--se-color-success, ${TOKEN_GREEN_4})`
2173
- };
2174
- const labelStyles = {
2175
- flex: "1",
2176
- overflow: "hidden",
2177
- textOverflow: "ellipsis",
2178
- whiteSpace: "nowrap"
2179
- };
2180
- const indicator = isCompleted ? html`<span role="img" aria-label="completed" style=${styleMap(checkStyles)}>&#10003;</span>` : isCurrent ? html`<span style=${styleMap(dotStyles)}></span>` : html`<span style=${styleMap(dotStyles)}></span>`;
2181
- return html`
2182
- <button
2183
- type="button"
2184
- data-testid=${`step-${step.id}`}
2185
- data-current=${isCurrent ? "true" : nothing}
2186
- data-completed=${isCompleted ? "true" : nothing}
2187
- aria-current=${isCurrent ? "step" : nothing}
2188
- style=${styleMap(btnStyles)}
2189
- @click=${() => this._handleStepClick(tourId, step.id)}
2190
- >
2191
- <span style=${styleMap(indicatorWrapStyles)}>${indicator}</span>
2192
- <span style=${styleMap(labelStyles)}>${step.title}</span>
2193
- </button>
2194
- `;
2195
- }
2196
- _renderWorkflowCard(workflow) {
2197
- const completedCount = workflow.completedSteps.length;
2198
- const totalSteps = workflow.steps.length;
2199
- const cardStyles = {
2200
- padding: "12px",
2201
- borderRadius: "8px",
2202
- border: "1px solid rgba(255,255,255,0.08)",
2203
- background: "rgba(255,255,255,0.02)"
2204
- };
2205
- const headerStyles = {
2206
- display: "flex",
2207
- alignItems: "center",
2208
- gap: "8px",
2209
- marginBottom: "8px"
2210
- };
2211
- const titleStyles = {
2212
- flex: "1",
2213
- fontSize: "13px",
2214
- fontWeight: "600",
2215
- color: `var(--se-color-text-primary, ${TOKEN_SLATE_12})`,
2216
- overflow: "hidden",
2217
- textOverflow: "ellipsis",
2218
- whiteSpace: "nowrap"
2219
- };
2220
- const dismissBtnStyles = {
2221
- flexShrink: "0",
2222
- padding: "2px 6px",
2223
- border: "none",
2224
- borderRadius: "4px",
2225
- background: "transparent",
2226
- color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
2227
- fontSize: "12px",
2228
- cursor: "pointer",
2229
- lineHeight: "1"
2230
- };
2231
- const progressWrapStyles = {
2232
- marginBottom: "8px"
2233
- };
2234
- const progressLabelStyles = {
2235
- fontSize: "10px",
2236
- color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
2237
- marginTop: "4px"
2238
- };
2239
- const stepsColStyles = {
2240
- display: "flex",
2241
- flexDirection: "column"
2242
- };
2243
- return html`
2244
- <div style=${styleMap(cardStyles)}>
2245
- <!-- Header: icon + title + dismiss -->
2246
- <div style=${styleMap(headerStyles)}>
2247
- ${workflow.meta.icon ? html`<span data-testid="workflow-icon" style="flex-shrink:0;font-size:14px">${workflow.meta.icon}</span>` : nothing}
2248
- <span style=${styleMap(titleStyles)}>${workflow.meta.title}</span>
2249
- <button
2250
- type="button"
2251
- data-testid=${`dismiss-${workflow.tourId}`}
2252
- style=${styleMap(dismissBtnStyles)}
2253
- aria-label=${`Dismiss ${workflow.meta.title}`}
2254
- @click=${() => this._handleDismiss(workflow.tourId)}
2255
- >&#10005;</button>
2256
- </div>
2257
-
2258
- <!-- Progress bar + label -->
2259
- <div style=${styleMap(progressWrapStyles)}>
2260
- ${this._renderProgressBar(completedCount, totalSteps)}
2261
- <div style=${styleMap(progressLabelStyles)}>
2262
- ${completedCount} of ${totalSteps} steps
2263
- </div>
2264
- </div>
2265
-
2266
- <!-- Step list -->
2267
- <div style=${styleMap(stepsColStyles)}>
2268
- ${workflow.steps.map(
2269
- (step) => this._renderStepItem(
2270
- step,
2271
- workflow.completedSteps.includes(step.id),
2272
- workflow.currentStepId === step.id,
2273
- workflow.tourId
2274
- )
2275
- )}
2276
- </div>
2277
- </div>
2278
- `;
2279
- }
2280
- // ── Render ────────────────────────────────────────────────────────────────
2281
- render() {
2282
- const activeWorkflows = this._workflowEntries.filter((w) => w.status === "active");
2283
- if (activeWorkflows.length === 0) {
2284
- const emptyStyles = {
2285
- display: "flex",
2286
- alignItems: "center",
2287
- justifyContent: "center",
2288
- padding: "24px 0",
2289
- fontSize: "12px",
2290
- color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`
2291
- };
2292
- return html`<div style=${styleMap(emptyStyles)}>No active workflows</div>`;
2293
- }
2294
- const containerStyles = {
2295
- display: "flex",
2296
- flexDirection: "column",
2297
- gap: "8px"
2298
- };
2299
- return html`
2300
- <div style=${styleMap(containerStyles)}>
2301
- ${activeWorkflows.map((workflow) => this._renderWorkflowCard(workflow))}
2302
- </div>
2303
- `;
2304
- }
2305
- };
2306
- _unsubTourStarted = new WeakMap();
2307
- _unsubTourEvents = new WeakMap();
2308
- _toastCleanups = new WeakMap();
2309
- _notified = new WeakMap();
2310
- _completedMap = new WeakMap();
2311
- _persistInitialized = new WeakMap();
2312
- _tourWorkflows = new WeakMap();
2313
- // ── Static properties (no decorators) ────────────────────────────────────
2314
- WorkflowTrackerLit.properties = {
2315
- // Public input: runtime ref injected by MountableWidget
2316
- runtimeRef: { attribute: false },
2317
- // Internal reactive state
2318
- _workflowEntries: { state: true },
2319
- _actionVersion: { state: true }
2320
- };
2321
- if (typeof window !== "undefined" && !customElements.get(TAG_NAME)) {
2322
- customElements.define(TAG_NAME, WorkflowTrackerLit);
2323
- }
2324
- var WorkflowWidgetLitMountable = {
2325
- mount(container, config) {
2326
- const runtime2 = config?.runtime ?? null;
2327
- if (typeof window !== "undefined" && !customElements.get(TAG_NAME)) {
2328
- customElements.define(TAG_NAME, WorkflowTrackerLit);
2329
- }
2330
- const el = document.createElement(TAG_NAME);
2331
- el.runtimeRef = runtime2;
2332
- container.appendChild(el);
2333
- return () => {
2334
- el.remove();
2335
- };
2336
- },
2337
- update(container, config) {
2338
- const el = container.querySelector(TAG_NAME);
2339
- if (!el) return;
2340
- const runtime2 = config?.runtime ?? null;
2341
- el.runtimeRef = runtime2;
2342
- }
2343
- };
2344
-
2345
- // src/runtime.ts
2346
- var executeHighlight = async (action, context) => {
2347
- let anchorEl = context.resolveAnchor(action.anchorId);
2348
- if (!anchorEl && context.waitForAnchor) {
2349
- anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2350
- }
2351
- if (!anchorEl) {
2352
- console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2353
- return { cleanup: () => {
2354
- } };
2355
- }
2356
- if (anchorEl.getAttribute("data-syntro-highlight-dismissed")) {
2357
- return { cleanup: () => {
2358
- } };
2359
- }
2360
- const existing = anchorEl.getAttribute("data-syntro-highlight");
2361
- if (existing) {
2362
- const prev = context.overlayRoot.querySelectorAll(
2363
- ".syntro-spotlight-scrim, .syntro-spotlight-ring"
2364
- );
2365
- prev.forEach((el) => el.remove());
2366
- }
2367
- anchorEl.setAttribute("data-syntro-highlight", "true");
2368
- let ringColor = action.style?.color;
2369
- if (!ringColor) {
2370
- try {
2371
- const primary = getComputedStyle(context.overlayRoot).getPropertyValue("--sc-color-primary")?.trim();
2372
- if (primary) ringColor = primary;
2373
- } catch {
2374
- }
2375
- }
2376
- const handle = showHighlight(anchorEl, context.overlayRoot, {
2377
- paddingPx: action.style?.paddingPx ?? 12,
2378
- radiusPx: action.style?.radiusPx ?? 12,
2379
- scrimOpacity: action.style?.scrimOpacity ?? 0.55,
2380
- ringColor,
2381
- blocking: action.blocking ?? false,
2382
- onClickOutside: action.onClickOutside ?? true,
2383
- onEsc: action.onEsc ?? true,
2384
- onDismiss: () => {
2385
- anchorEl.setAttribute("data-syntro-highlight-dismissed", "true");
2386
- }
2387
- });
2388
- context.publishEvent("action.applied", {
2389
- id: context.generateId(),
2390
- kind: "overlays:highlight",
2391
- anchorId: action.anchorId
2392
- });
2393
- return {
2394
- cleanup: () => {
2395
- handle.destroy();
2396
- anchorEl.removeAttribute("data-syntro-highlight");
2397
- anchorEl.removeAttribute("data-syntro-highlight-dismissed");
2398
- }
2399
- };
2400
- };
2401
- var executePulse = async (action, context) => {
2402
- let anchorEl = context.resolveAnchor(action.anchorId);
2403
- if (!anchorEl && context.waitForAnchor) {
2404
- anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2405
- }
2406
- if (!anchorEl) {
2407
- console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2408
- return { cleanup: () => {
2409
- } };
2410
- }
2411
- const duration = action.duration ?? 4e3;
2412
- await new Promise((resolve) => requestAnimationFrame(resolve));
2413
- const parseHex = (hex) => ({
2414
- r: parseInt(hex.slice(1, 3), 16),
2415
- g: parseInt(hex.slice(3, 5), 16),
2416
- b: parseInt(hex.slice(5, 7), 16)
2417
- });
2418
- const fallback = { r: 79, g: 70, b: 229 };
2419
- let primary = fallback;
2420
- let secondary = null;
2421
- try {
2422
- const styles = getComputedStyle(context.overlayRoot);
2423
- const pHex = styles.getPropertyValue("--sc-color-primary")?.trim();
2424
- const sHex = styles.getPropertyValue("--sc-color-primary-hover")?.trim();
2425
- if (pHex?.startsWith("#") && pHex.length >= 7) {
2426
- primary = parseHex(pHex);
2427
- }
2428
- if (sHex?.startsWith("#") && sHex.length >= 7) {
2429
- secondary = parseHex(sHex);
2430
- }
2431
- } catch {
2432
- }
2433
- const existing = document.querySelector("[data-syntro-pulse-styles]");
2434
- if (existing) existing.remove();
2435
- const style = document.createElement("style");
2436
- style.setAttribute("data-syntro-pulse-styles", "");
2437
- const { r: pr, g: pg, b: pb } = primary;
2438
- if (secondary) {
2439
- const { r: sr, g: sg, b: sb } = secondary;
2440
- style.textContent = `
2441
- @keyframes syntro-pulse-anim {
2442
- 0%, 100% {
2443
- box-shadow: 0 0 0 0 rgba(${pr}, ${pg}, ${pb}, 0.35);
2444
- }
2445
- 25% {
2446
- box-shadow: 0 0 0 12px rgba(${pr}, ${pg}, ${pb}, 0);
2447
- }
2448
- 50% {
2449
- box-shadow: 0 0 0 0 rgba(${sr}, ${sg}, ${sb}, 0.35);
2450
- }
2451
- 75% {
2452
- box-shadow: 0 0 0 12px rgba(${sr}, ${sg}, ${sb}, 0);
2453
- }
2454
- }
2455
- `;
2456
- } else {
2457
- style.textContent = `
2458
- @keyframes syntro-pulse-anim {
2459
- 0%, 100% {
2460
- box-shadow: 0 0 0 0 rgba(${pr}, ${pg}, ${pb}, 0.35);
2461
- }
2462
- 50% {
2463
- box-shadow: 0 0 0 12px rgba(${pr}, ${pg}, ${pb}, 0);
2464
- }
2465
- }
2466
- `;
2467
- }
2468
- document.head.appendChild(style);
2469
- const originalAnimation = anchorEl.style.animation;
2470
- anchorEl.style.animation = "syntro-pulse-anim 2.5s cubic-bezier(0.4, 0, 0.6, 1) infinite";
2471
- anchorEl.setAttribute("data-syntro-pulse", "true");
2472
- const timeoutId = setTimeout(() => {
2473
- anchorEl.style.animation = originalAnimation;
2474
- anchorEl.removeAttribute("data-syntro-pulse");
2475
- }, duration);
2476
- context.publishEvent("action.applied", {
2477
- id: context.generateId(),
2478
- kind: "overlays:pulse",
2479
- anchorId: action.anchorId,
2480
- duration
2481
- });
2482
- return {
2483
- cleanup: () => {
2484
- clearTimeout(timeoutId);
2485
- if (!anchorEl.isConnected) return;
2486
- anchorEl.style.animation = originalAnimation;
2487
- anchorEl.removeAttribute("data-syntro-pulse");
2488
- }
2489
- };
2490
- };
2491
- var executeBadge = async (action, context) => {
2492
- let anchorEl = context.resolveAnchor(action.anchorId);
2493
- if (!anchorEl && context.waitForAnchor) {
2494
- anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2495
- }
2496
- if (!anchorEl) {
2497
- console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2498
- return { cleanup: () => {
2499
- } };
2500
- }
2501
- let badgeColor = "#4f46e5";
2502
- try {
2503
- const primary = getComputedStyle(context.overlayRoot).getPropertyValue("--sc-color-primary")?.trim();
2504
- if (primary?.startsWith("#") && primary.length >= 7) {
2505
- badgeColor = primary;
2506
- }
2507
- } catch {
2508
- }
2509
- const badge2 = document.createElement("div");
2510
- badge2.textContent = action.content;
2511
- badge2.setAttribute("data-syntro-badge", action.anchorId.selector);
2512
- Object.assign(badge2.style, {
2513
- position: "absolute",
2514
- padding: "2px 6px",
2515
- fontSize: "12px",
2516
- fontWeight: "600",
2517
- lineHeight: "1",
2518
- color: "white",
2519
- background: badgeColor,
2520
- borderRadius: "9999px",
2521
- pointerEvents: "none",
2522
- zIndex: "2147483646",
2523
- whiteSpace: "nowrap"
2524
- });
2525
- const position = action.position ?? "top-right";
2526
- const originalPosition = anchorEl.style.position;
2527
- if (getComputedStyle(anchorEl).position === "static") {
2528
- anchorEl.style.position = "relative";
2529
- }
2530
- anchorEl.appendChild(badge2);
2531
- switch (position) {
2532
- case "top-left":
2533
- Object.assign(badge2.style, { top: "-8px", left: "-8px" });
2534
- break;
2535
- case "top-right":
2536
- Object.assign(badge2.style, { top: "-8px", right: "-8px" });
2537
- break;
2538
- case "bottom-left":
2539
- Object.assign(badge2.style, { bottom: "-8px", left: "-8px" });
2540
- break;
2541
- case "bottom-right":
2542
- Object.assign(badge2.style, { bottom: "-8px", right: "-8px" });
2543
- break;
2544
- }
2545
- context.publishEvent("action.applied", {
2546
- id: context.generateId(),
2547
- kind: "overlays:badge",
2548
- anchorId: action.anchorId,
2549
- content: action.content,
2550
- position
2551
- });
2552
- return {
2553
- cleanup: () => {
2554
- try {
2555
- badge2.remove();
2556
- } catch {
2557
- }
2558
- if (!anchorEl.isConnected) return;
2559
- if (originalPosition !== void 0) {
2560
- anchorEl.style.position = originalPosition;
2561
- }
2562
- },
2563
- updateFn: (changes) => {
2564
- if ("content" in changes && typeof changes.content === "string") {
2565
- badge2.textContent = changes.content;
2566
- }
2567
- }
2568
- };
2569
- };
2570
- var executeTooltip = async (action, context) => {
2571
- let anchorEl = context.resolveAnchor(action.anchorId);
2572
- if (!anchorEl && context.waitForAnchor) {
2573
- anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2574
- }
2575
- if (!anchorEl) {
2576
- console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2577
- return { cleanup: () => {
2578
- } };
2579
- }
2580
- const { content } = action;
2581
- let html2 = "";
2582
- if (content.title) {
2583
- html2 += `<div class="syntro-tt-title">${sanitizeHtml(content.title)}</div>`;
2584
- }
2585
- html2 += `<div class="syntro-tt-body">${sanitizeHtml(content.body)}</div>`;
2586
- if (content.ctaButtons && content.ctaButtons.length > 0) {
2587
- html2 += `<div class="syntro-tt-actions">`;
2588
- for (const btn of content.ctaButtons) {
2589
- const isPrimary = btn.primary ?? false;
2590
- html2 += `
2591
- <button
2592
- class="syntro-tt-btn ${isPrimary ? "syntro-tt-btn-primary" : ""}"
2593
- data-syntro-action="${sanitizeHtml(btn.actionId)}"
2594
- >
2595
- ${sanitizeHtml(btn.label)}
2596
- </button>
2597
- `;
2598
- }
2599
- html2 += `</div>`;
2600
- } else if (content.cta) {
2601
- html2 += `<div class="syntro-tt-actions">
2602
- <button class="syntro-tt-btn syntro-tt-btn-primary" data-syntro-action="cta">
2603
- ${sanitizeHtml(content.cta.label)}
2604
- </button>
2605
- </div>`;
2606
- }
2607
- const handle = showTooltip(anchorEl, context.overlayRoot, {
2608
- html: html2,
2609
- placement: action.placement ?? "top",
2610
- trigger: action.trigger ?? "immediate",
2611
- onAction: (actionId) => {
2612
- if (actionId === "dismiss") {
2613
- handle.destroy();
2614
- return;
2615
- }
2616
- if (actionId === "cta" && content.cta) {
2617
- context.publishEvent("action.cta_clicked", {
2618
- anchorId: action.anchorId,
2619
- ctaLabel: content.cta.label
2620
- });
2621
- } else if (content.ctaButtons) {
2622
- const clickedBtn = content.ctaButtons.find((b) => b.actionId === actionId);
2623
- if (clickedBtn) {
2624
- context.publishEvent("action.tooltip_cta_clicked", {
2625
- anchorId: action.anchorId,
2626
- actionId,
2627
- label: clickedBtn.label,
2628
- ...clickedBtn.href ? { href: clickedBtn.href } : {}
2629
- });
2630
- handle.destroy();
2631
- navigateForCta(clickedBtn);
2632
- return;
2633
- }
2634
- }
2635
- handle.destroy();
2636
- }
2637
- });
2638
- context.publishEvent("action.applied", {
2639
- id: context.generateId(),
2640
- kind: "overlays:tooltip",
2641
- anchorId: action.anchorId,
2642
- trigger: action.trigger ?? "immediate"
2643
- });
2644
- return {
2645
- cleanup: () => {
2646
- handle.destroy();
2647
- }
2648
- };
2649
- };
2650
- var executors = [
2651
- { kind: "overlays:highlight", executor: executeHighlight },
2652
- { kind: "overlays:pulse", executor: executePulse },
2653
- { kind: "overlays:badge", executor: executeBadge },
2654
- { kind: "overlays:tooltip", executor: executeTooltip },
2655
- { kind: "overlays:modal", executor: executeModal },
2656
- { kind: "overlays:tour", executor: executeTour },
2657
- { kind: "overlays:celebrate", executor: executeCelebrate }
2658
- ];
2659
- var runtime = {
2660
- id: "adaptive-overlays",
2661
- version: "1.0.0",
2662
- name: "Overlays",
2663
- description: "Tooltips, highlights, badges, modals, celebrations, visual overlays, and workflow tracking",
2
+ executeBadge,
3
+ executeHighlight,
4
+ executeModal,
5
+ executePulse,
6
+ executeTooltip,
7
+ executeTour,
2664
8
  executors,
2665
- widgets: [
2666
- {
2667
- id: "adaptive-overlays:workflow-tracker",
2668
- component: WorkflowWidgetLitMountable,
2669
- metadata: {
2670
- name: "Workflow Tracker",
2671
- icon: "\u{1F4CB}",
2672
- description: "Tracks multi-step workflow progress across tours"
2673
- }
2674
- }
2675
- ]
2676
- };
9
+ runtime
10
+ } from "./chunk-4ANURPES.js";
11
+ import "./chunk-VHAA22YE.js";
2677
12
  export {
2678
13
  executeBadge,
2679
14
  executeHighlight,