@syntrologie/runtime-sdk 0.2.1 → 0.2.4

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.
Files changed (144) hide show
  1. package/CAPABILITIES.md +400 -0
  2. package/dist/SmartCanvasApp.js +28 -10
  3. package/dist/SmartCanvasApp.js.map +1 -1
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +23 -5
  6. package/dist/api.js.map +1 -1
  7. package/dist/blocks/data/ComparisonBlock.d.ts +10 -0
  8. package/dist/blocks/data/ComparisonBlock.js +92 -0
  9. package/dist/blocks/data/ComparisonBlock.js.map +1 -0
  10. package/dist/blocks/data/StatsBlock.d.ts +10 -0
  11. package/dist/blocks/data/StatsBlock.js +103 -0
  12. package/dist/blocks/data/StatsBlock.js.map +1 -0
  13. package/dist/blocks/data/index.d.ts +2 -0
  14. package/dist/blocks/data/index.js +3 -0
  15. package/dist/blocks/data/index.js.map +1 -0
  16. package/dist/blocks/index.d.ts +26 -0
  17. package/dist/blocks/index.js +94 -0
  18. package/dist/blocks/index.js.map +1 -0
  19. package/dist/blocks/interactive/ChecklistBlock.d.ts +11 -0
  20. package/dist/blocks/interactive/ChecklistBlock.js +110 -0
  21. package/dist/blocks/interactive/ChecklistBlock.js.map +1 -0
  22. package/dist/blocks/interactive/RatingBlock.d.ts +11 -0
  23. package/dist/blocks/interactive/RatingBlock.js +131 -0
  24. package/dist/blocks/interactive/RatingBlock.js.map +1 -0
  25. package/dist/blocks/interactive/index.d.ts +2 -0
  26. package/dist/blocks/interactive/index.js +3 -0
  27. package/dist/blocks/interactive/index.js.map +1 -0
  28. package/dist/blocks/notification/NotificationBlock.d.ts +26 -0
  29. package/dist/blocks/notification/NotificationBlock.js +166 -0
  30. package/dist/blocks/notification/NotificationBlock.js.map +1 -0
  31. package/dist/blocks/notification/index.d.ts +1 -0
  32. package/dist/blocks/notification/index.js +2 -0
  33. package/dist/blocks/notification/index.js.map +1 -0
  34. package/dist/bootstrap.d.ts +19 -1
  35. package/dist/bootstrap.js +118 -17
  36. package/dist/bootstrap.js.map +1 -1
  37. package/dist/components/ShadowCanvasOverlay.d.ts +12 -3
  38. package/dist/components/ShadowCanvasOverlay.js +75 -24
  39. package/dist/components/ShadowCanvasOverlay.js.map +1 -1
  40. package/dist/components/{RectangleCard.d.ts → TileCard.d.ts} +4 -4
  41. package/dist/components/{RectangleCard.js → TileCard.js} +57 -6
  42. package/dist/components/TileCard.js.map +1 -0
  43. package/dist/components/TileWheel.d.ts +8 -0
  44. package/dist/components/{RectangleWheel.js → TileWheel.js} +7 -7
  45. package/dist/components/TileWheel.js.map +1 -0
  46. package/dist/configFetcher.js.map +1 -1
  47. package/dist/earlyPatcher.js +1 -2
  48. package/dist/earlyPatcher.js.map +1 -1
  49. package/dist/editorLoader.js +74 -17
  50. package/dist/editorLoader.js.map +1 -1
  51. package/dist/experiments/adapters/growthbook.d.ts +18 -2
  52. package/dist/experiments/adapters/growthbook.js +17 -3
  53. package/dist/experiments/adapters/growthbook.js.map +1 -1
  54. package/dist/experiments/registry.d.ts +18 -4
  55. package/dist/experiments/registry.js +1 -1
  56. package/dist/experiments/registry.js.map +1 -1
  57. package/dist/experiments/types.d.ts +8 -3
  58. package/dist/fetchers/cdnFetcher.js.map +1 -1
  59. package/dist/fetchers/experimentsFetcher.js +20 -0
  60. package/dist/fetchers/experimentsFetcher.js.map +1 -1
  61. package/dist/fetchers/types.d.ts +2 -2
  62. package/dist/hooks/useCanvasOverlays.d.ts +5 -1
  63. package/dist/hooks/useCanvasOverlays.js +33 -13
  64. package/dist/hooks/useCanvasOverlays.js.map +1 -1
  65. package/dist/hooks/useShadowCanvasConfig.d.ts +9 -2
  66. package/dist/hooks/useShadowCanvasConfig.js +8 -8
  67. package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
  68. package/dist/hostPatcher/core/patcher.js +14 -15
  69. package/dist/hostPatcher/core/patcher.js.map +1 -1
  70. package/dist/hostPatcher/core/sanitizer.js +1 -1
  71. package/dist/hostPatcher/core/sanitizer.js.map +1 -1
  72. package/dist/hostPatcher/core/types.d.ts +8 -9
  73. package/dist/hostPatcher/policy/defaultPolicy.d.ts +4 -0
  74. package/dist/hostPatcher/policy/defaultPolicy.js +9 -37
  75. package/dist/hostPatcher/policy/defaultPolicy.js.map +1 -1
  76. package/dist/hostPatcher/utils/anchors.js +3 -3
  77. package/dist/hostPatcher/utils/anchors.js.map +1 -1
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.js +2 -2
  80. package/dist/index.js.map +1 -1
  81. package/dist/overlays/recipeRegistry.d.ts +14 -0
  82. package/dist/overlays/recipeRegistry.js +32 -0
  83. package/dist/overlays/recipeRegistry.js.map +1 -0
  84. package/dist/overlays/runtime/index.d.ts +1 -0
  85. package/dist/overlays/runtime/index.js +1 -0
  86. package/dist/overlays/runtime/index.js.map +1 -1
  87. package/dist/overlays/runtime/overlay/modal.d.ts +11 -0
  88. package/dist/overlays/runtime/overlay/modal.js +78 -0
  89. package/dist/overlays/runtime/overlay/modal.js.map +1 -0
  90. package/dist/overlays/runtime/overlay/root.js +132 -0
  91. package/dist/overlays/runtime/overlay/root.js.map +1 -1
  92. package/dist/overlays/runtime/overlay/runner.d.ts +2 -0
  93. package/dist/overlays/runtime/overlay/runner.js +441 -2
  94. package/dist/overlays/runtime/overlay/runner.js.map +1 -1
  95. package/dist/overlays/runtime/overlay/tooltip.d.ts +1 -0
  96. package/dist/overlays/runtime/overlay/tooltip.js +61 -1
  97. package/dist/overlays/runtime/overlay/tooltip.js.map +1 -1
  98. package/dist/overlays/schema.d.ts +6 -6
  99. package/dist/overlays/types.d.ts +55 -1
  100. package/dist/react.d.ts +6 -1
  101. package/dist/react.js +31 -9
  102. package/dist/react.js.map +1 -1
  103. package/dist/render/RenderContext.d.ts +39 -0
  104. package/dist/render/RenderContext.js +67 -0
  105. package/dist/render/RenderContext.js.map +1 -0
  106. package/dist/render/index.d.ts +3 -0
  107. package/dist/render/index.js +3 -0
  108. package/dist/render/index.js.map +1 -0
  109. package/dist/render/types.d.ts +81 -0
  110. package/dist/render/types.js +2 -0
  111. package/dist/render/types.js.map +1 -0
  112. package/dist/smart-canvas.esm.js +192 -25
  113. package/dist/smart-canvas.esm.js.map +4 -4
  114. package/dist/smart-canvas.js +25962 -26846
  115. package/dist/smart-canvas.js.map +4 -4
  116. package/dist/smart-canvas.min.js +192 -25
  117. package/dist/smart-canvas.min.js.map +4 -4
  118. package/dist/telemetry/adapters/posthog.d.ts +6 -0
  119. package/dist/telemetry/adapters/posthog.js +48 -0
  120. package/dist/telemetry/adapters/posthog.js.map +1 -1
  121. package/dist/telemetry/types.d.ts +30 -0
  122. package/dist/theme/ThemeProvider.d.ts +31 -0
  123. package/dist/theme/ThemeProvider.js +109 -0
  124. package/dist/theme/ThemeProvider.js.map +1 -0
  125. package/dist/theme/defaultTheme.d.ts +18 -0
  126. package/dist/theme/defaultTheme.js +163 -0
  127. package/dist/theme/defaultTheme.js.map +1 -0
  128. package/dist/theme/extractHostTheme.d.ts +14 -0
  129. package/dist/theme/extractHostTheme.js +261 -0
  130. package/dist/theme/extractHostTheme.js.map +1 -0
  131. package/dist/theme/index.d.ts +5 -0
  132. package/dist/theme/index.js +7 -0
  133. package/dist/theme/index.js.map +1 -0
  134. package/dist/theme/types.d.ts +91 -0
  135. package/dist/theme/types.js +6 -0
  136. package/dist/theme/types.js.map +1 -0
  137. package/dist/token.d.ts +4 -0
  138. package/dist/token.js.map +1 -1
  139. package/dist/types.d.ts +228 -47
  140. package/package.json +8 -4
  141. package/schema/canvas-config.schema.json +24 -15
  142. package/dist/components/RectangleCard.js.map +0 -1
  143. package/dist/components/RectangleWheel.d.ts +0 -8
  144. package/dist/components/RectangleWheel.js.map +0 -1
@@ -99,6 +99,40 @@ export function injectBaseStyles() {
99
99
  opacity: 1;
100
100
  }
101
101
 
102
+ /* Tooltip action buttons (for tours) */
103
+ .syntro-tt-actions {
104
+ display: flex;
105
+ gap: 8px;
106
+ margin-top: 12px;
107
+ justify-content: flex-end;
108
+ }
109
+
110
+ .syntro-tt-btn {
111
+ padding: 8px 16px;
112
+ border-radius: 6px;
113
+ font-size: 13px;
114
+ font-weight: 500;
115
+ cursor: pointer;
116
+ transition: all 150ms;
117
+ border: 1px solid rgba(255, 255, 255, 0.2);
118
+ background: transparent;
119
+ color: inherit;
120
+ }
121
+
122
+ .syntro-tt-btn:hover {
123
+ background: rgba(255, 255, 255, 0.1);
124
+ }
125
+
126
+ .syntro-tt-btn-primary {
127
+ background: var(--syntro-accent, #4f46e5);
128
+ border-color: transparent;
129
+ }
130
+
131
+ .syntro-tt-btn-primary:hover {
132
+ background: var(--syntro-accent, #4338ca);
133
+ filter: brightness(1.1);
134
+ }
135
+
102
136
  /* Buttons inside tooltips inherit font */
103
137
  .syntro-tooltip button {
104
138
  font-family: inherit;
@@ -147,6 +181,104 @@ export function injectBaseStyles() {
147
181
  transform: scale(1) translateY(0);
148
182
  }
149
183
  }
184
+
185
+ /* Modal scrim */
186
+ .syntro-modal-scrim {
187
+ position: fixed;
188
+ inset: 0;
189
+ background: rgba(0, 0, 0, 0.6);
190
+ backdrop-filter: blur(4px);
191
+ opacity: 0;
192
+ transition: opacity 200ms ease;
193
+ pointer-events: auto;
194
+ }
195
+
196
+ /* Modal container */
197
+ .syntro-modal {
198
+ position: fixed;
199
+ top: 50%;
200
+ left: 50%;
201
+ transform: translate(-50%, -50%) scale(0.95);
202
+ background: var(--syntro-surface, #0f172a);
203
+ color: var(--syntro-fg, #fff);
204
+ border-radius: 16px;
205
+ padding: 24px;
206
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4);
207
+ pointer-events: auto;
208
+ opacity: 0;
209
+ transition: opacity 200ms ease, transform 200ms ease;
210
+ z-index: 2147483647;
211
+ }
212
+
213
+ .syntro-modal-sm { max-width: 320px; width: 90vw; }
214
+ .syntro-modal-md { max-width: 480px; width: 90vw; }
215
+ .syntro-modal-lg { max-width: 640px; width: 90vw; }
216
+
217
+ /* Modal content */
218
+ .syntro-modal-title {
219
+ font-size: 20px;
220
+ font-weight: 600;
221
+ margin-bottom: 12px;
222
+ }
223
+
224
+ .syntro-modal-body {
225
+ font-size: 15px;
226
+ line-height: 1.6;
227
+ opacity: 0.9;
228
+ margin-bottom: 20px;
229
+ }
230
+
231
+ .syntro-modal-close {
232
+ position: absolute;
233
+ top: 12px;
234
+ right: 12px;
235
+ background: transparent;
236
+ border: none;
237
+ color: inherit;
238
+ font-size: 24px;
239
+ line-height: 1;
240
+ cursor: pointer;
241
+ opacity: 0.6;
242
+ transition: opacity 150ms;
243
+ padding: 4px;
244
+ }
245
+
246
+ .syntro-modal-close:hover {
247
+ opacity: 1;
248
+ }
249
+
250
+ /* Modal buttons */
251
+ .syntro-modal-actions {
252
+ display: flex;
253
+ gap: 12px;
254
+ justify-content: flex-end;
255
+ }
256
+
257
+ .syntro-modal-btn {
258
+ padding: 10px 20px;
259
+ border-radius: 8px;
260
+ font-size: 14px;
261
+ font-weight: 500;
262
+ cursor: pointer;
263
+ transition: all 150ms;
264
+ border: 1px solid rgba(255, 255, 255, 0.2);
265
+ background: transparent;
266
+ color: inherit;
267
+ }
268
+
269
+ .syntro-modal-btn:hover {
270
+ background: rgba(255, 255, 255, 0.1);
271
+ }
272
+
273
+ .syntro-modal-btn-primary {
274
+ background: var(--syntro-accent, #4f46e5);
275
+ border-color: transparent;
276
+ }
277
+
278
+ .syntro-modal-btn-primary:hover {
279
+ background: var(--syntro-accent, #4338ca);
280
+ filter: brightness(1.1);
281
+ }
150
282
  `;
151
283
  const style = document.createElement('style');
152
284
  style.setAttribute('data-syntro', 'base-overlay');
@@ -1 +1 @@
1
- {"version":3,"file":"root.js","sourceRoot":"","sources":["../../../../src/overlays/runtime/overlay/root.ts"],"names":[],"mappings":"AAAA,IAAI,aAAa,GAAuB,IAAI,CAAC;AAE7C,MAAM,UAAU,iBAAiB;IAC/B,IAAI,aAAa,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,aAAa,CAAC;IACjF,IAAI,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,iBAAiB,CAAuB,CAAC;IAC1E,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACnC,EAAE,CAAC,EAAE,GAAG,iBAAiB,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;YACtB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,YAAY;YACpB,aAAa,EAAE,MAAM;SACtB,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,aAAa,GAAG,EAAE,CAAC;IACnB,gBAAgB,EAAE,CAAC;IACnB,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,MAAM,UAAU,gBAAgB;IAC9B,IAAI,cAAc;QAAE,OAAO;IAC3B,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Hb,CAAC;IACA,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAClD,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;IAChF,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC;AACD,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAClF,CAAC"}
1
+ {"version":3,"file":"root.js","sourceRoot":"","sources":["../../../../src/overlays/runtime/overlay/root.ts"],"names":[],"mappings":"AAAA,IAAI,aAAa,GAAuB,IAAI,CAAC;AAE7C,MAAM,UAAU,iBAAiB;IAC/B,IAAI,aAAa,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,aAAa,CAAC;IACjF,IAAI,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,iBAAiB,CAAuB,CAAC;IAC1E,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACnC,EAAE,CAAC,EAAE,GAAG,iBAAiB,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;YACtB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,YAAY;YACpB,aAAa,EAAE,MAAM;SACtB,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,aAAa,GAAG,EAAE,CAAC;IACnB,gBAAgB,EAAE,CAAC;IACnB,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,MAAM,UAAU,gBAAgB;IAC9B,IAAI,cAAc;QAAE,OAAO;IAC3B,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiQb,CAAC;IACA,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAClD,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;IAChF,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC;AACD,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAClF,CAAC"}
@@ -1,2 +1,4 @@
1
1
  import type { CanvasRecipe, OverlayContext } from '../../types';
2
2
  export declare function runOverlays(recipe: CanvasRecipe, ctx: OverlayContext): Promise<() => void>;
3
+ /** Start a tour by ID - clears completion state and begins */
4
+ export declare function startTour(tourId: string): void;
@@ -1,11 +1,112 @@
1
1
  import { showTooltip } from './tooltip';
2
2
  import { showHighlight } from './highlight';
3
+ import { showModal } from './modal';
4
+ // Storage keys for cross-page tour state
5
+ const ACTIVE_TOUR_KEY = 'syntro_active_tour';
6
+ // Tour state expires after 5 minutes of inactivity
7
+ // This prevents stale tours from resuming after hard refresh hours/days later
8
+ const TOUR_STATE_EXPIRY_MS = 5 * 60 * 1000;
9
+ /** Get active tour state from localStorage (returns null if expired) */
10
+ function getActiveTourState() {
11
+ try {
12
+ const state = localStorage.getItem(ACTIVE_TOUR_KEY);
13
+ if (!state)
14
+ return null;
15
+ const parsed = JSON.parse(state);
16
+ // Check if state has expired
17
+ if (parsed.timestamp && Date.now() - parsed.timestamp > TOUR_STATE_EXPIRY_MS) {
18
+ console.log(`[SmartCanvas] Tour state expired (${Math.round((Date.now() - parsed.timestamp) / 1000)}s old), clearing`);
19
+ localStorage.removeItem(ACTIVE_TOUR_KEY);
20
+ return null;
21
+ }
22
+ return parsed;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ /** Save active tour state to localStorage with timestamp */
29
+ function setActiveTourState(state) {
30
+ try {
31
+ if (state) {
32
+ const stateWithTimestamp = {
33
+ ...state,
34
+ timestamp: Date.now()
35
+ };
36
+ localStorage.setItem(ACTIVE_TOUR_KEY, JSON.stringify(stateWithTimestamp));
37
+ }
38
+ else {
39
+ localStorage.removeItem(ACTIVE_TOUR_KEY);
40
+ }
41
+ }
42
+ catch { }
43
+ }
44
+ /** Check if a step's route matches the current page */
45
+ function stepRouteMatches(step, path) {
46
+ const stepRoute = step.route;
47
+ if (!stepRoute)
48
+ return true; // No route restriction = matches any page
49
+ // Exact match
50
+ if (stepRoute === path)
51
+ return true;
52
+ // Match with trailing path segments (e.g., /dashboard matches /dashboard/settings)
53
+ // But /dashboard should NOT match /dashboard-other or /login
54
+ if (path.startsWith(stepRoute + '/'))
55
+ return true;
56
+ return false;
57
+ }
58
+ /**
59
+ * Watch for route changes in SPAs
60
+ * Handles: popstate (back/forward), pushState, replaceState
61
+ */
62
+ function watchRouteChanges(callback) {
63
+ let lastPath = location.pathname;
64
+ const checkAndNotify = () => {
65
+ const currentPath = location.pathname;
66
+ if (currentPath !== lastPath) {
67
+ lastPath = currentPath;
68
+ callback(currentPath);
69
+ }
70
+ };
71
+ // Handle back/forward button navigation
72
+ const onPopState = () => {
73
+ checkAndNotify();
74
+ };
75
+ window.addEventListener('popstate', onPopState);
76
+ // Patch pushState/replaceState to detect SPA soft navigation
77
+ // React Router, Next.js, etc. use these for client-side routing
78
+ const originalPushState = history.pushState.bind(history);
79
+ const originalReplaceState = history.replaceState.bind(history);
80
+ history.pushState = function (...args) {
81
+ originalPushState(...args);
82
+ checkAndNotify();
83
+ };
84
+ history.replaceState = function (...args) {
85
+ originalReplaceState(...args);
86
+ checkAndNotify();
87
+ };
88
+ return () => {
89
+ window.removeEventListener('popstate', onPopState);
90
+ history.pushState = originalPushState;
91
+ history.replaceState = originalReplaceState;
92
+ };
93
+ }
3
94
  export async function runOverlays(recipe, ctx) {
4
95
  var _a;
5
96
  if (recipe.routes && !routeMatches(recipe.routes, location.pathname))
6
97
  return () => { };
98
+ // Sequential (tour) mode
99
+ if (recipe.mode === 'sequential') {
100
+ return runSequentialOverlays(recipe, ctx);
101
+ }
102
+ // Parallel mode (default) - show all steps at once
7
103
  const cleanups = [];
8
104
  for (const step of recipe.steps) {
105
+ // Modal steps don't need an anchor
106
+ if (step.kind === 'modal') {
107
+ cleanups.push(runModal(step, ctx));
108
+ continue;
109
+ }
9
110
  const el = await ctx.resolve(step.anchor);
10
111
  if (!el) {
11
112
  (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_anchor_missing', { stepId: step.id, anchor: step.anchor });
@@ -27,10 +128,260 @@ export async function runOverlays(recipe, ctx) {
27
128
  });
28
129
  };
29
130
  }
131
+ /** Run steps one at a time, advancing based on onAction mappings */
132
+ async function runSequentialOverlays(recipe, ctx) {
133
+ var _a;
134
+ const storageKey = `syntro_tour_${recipe.id}`;
135
+ // Check if user already completed/dismissed this tour
136
+ try {
137
+ if (localStorage.getItem(storageKey) === 'done') {
138
+ return () => { }; // Tour already completed, don't show again
139
+ }
140
+ }
141
+ catch { }
142
+ // Check for active cross-page tour state
143
+ const activeState = getActiveTourState();
144
+ const isResumingTour = (activeState === null || activeState === void 0 ? void 0 : activeState.tourId) === recipe.id;
145
+ // If autoStart is false and we're not resuming an active tour, don't auto-start
146
+ // Tours with autoStart: false should only start via explicit user action (e.g., clicking "Start Tour")
147
+ if (!isResumingTour && recipe.autoStart === false) {
148
+ return () => { };
149
+ }
150
+ const stepMap = new Map();
151
+ for (const step of recipe.steps) {
152
+ stepMap.set(step.id, step);
153
+ }
154
+ let currentCleanup = null;
155
+ let isDestroyed = false;
156
+ const runStep = async (stepId) => {
157
+ var _a, _b, _c, _d;
158
+ if (isDestroyed)
159
+ return;
160
+ const step = stepMap.get(stepId);
161
+ if (!step) {
162
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_tour_end', { recipeId: recipe.id, reason: 'step_not_found', stepId });
163
+ setActiveTourState(null);
164
+ return;
165
+ }
166
+ // Check if this step should show on current page
167
+ const currentPath = location.pathname;
168
+ const stepRoute = step.route;
169
+ console.log(`[SmartCanvas] runStep: stepId="${stepId}", kind="${step.kind}", stepRoute="${stepRoute}", currentPath="${currentPath}"`);
170
+ if (!stepRouteMatches(step, currentPath)) {
171
+ // Step is for a different page - save state but DON'T auto-navigate
172
+ // Auto-navigation can cause redirect wars with multiple tours
173
+ // Instead, just pause and wait for user to return to the correct page
174
+ console.log(`[SmartCanvas] Route mismatch - step "${stepId}" is for "${stepRoute}", currently on "${currentPath}". Pausing tour.`);
175
+ setActiveTourState({ tourId: recipe.id, stepId });
176
+ return;
177
+ }
178
+ console.log(`[SmartCanvas] Route matches, showing step "${stepId}"`);
179
+ // Save current step state for cross-page resume
180
+ setActiveTourState({ tourId: recipe.id, stepId });
181
+ // Clean up previous step
182
+ if (currentCleanup) {
183
+ currentCleanup();
184
+ currentCleanup = null;
185
+ }
186
+ const onStepAction = (actionId) => {
187
+ var _a, _b, _c, _d, _e;
188
+ const nextStepId = (_a = step.onAction) === null || _a === void 0 ? void 0 : _a[actionId];
189
+ (_b = ctx.onEvent) === null || _b === void 0 ? void 0 : _b.call(ctx, 'syntro_tour_action', { recipeId: recipe.id, stepId: step.id, actionId, nextStepId });
190
+ if (!nextStepId || nextStepId === 'end') {
191
+ // Tour ends - mark as done
192
+ try {
193
+ localStorage.setItem(storageKey, 'done');
194
+ }
195
+ catch { }
196
+ (_d = (_c = ctx.telemetry) === null || _c === void 0 ? void 0 : _c.setPersonPropertiesOnce) === null || _d === void 0 ? void 0 : _d.call(_c, { [storageKey]: 'done' });
197
+ setActiveTourState(null);
198
+ if (currentCleanup) {
199
+ currentCleanup();
200
+ currentCleanup = null;
201
+ }
202
+ (_e = ctx.onEvent) === null || _e === void 0 ? void 0 : _e.call(ctx, 'syntro_tour_end', { recipeId: recipe.id, reason: 'completed' });
203
+ }
204
+ else {
205
+ // Advance to next step
206
+ const nextStep = stepMap.get(nextStepId);
207
+ const nextStepRoute = nextStep === null || nextStep === void 0 ? void 0 : nextStep.route;
208
+ const nowPath = location.pathname; // Get current path at action time
209
+ // Update tracked step ID
210
+ currentStepId = nextStepId;
211
+ // If next step is on a different page, navigate there (user-initiated)
212
+ if (nextStep && nextStepRoute && !stepRouteMatches(nextStep, nowPath)) {
213
+ console.log(`[SmartCanvas] User advancing to step "${nextStepId}" on route "${nextStepRoute}" - navigating...`);
214
+ setActiveTourState({ tourId: recipe.id, stepId: nextStepId });
215
+ if (currentCleanup) {
216
+ currentCleanup();
217
+ currentCleanup = null;
218
+ }
219
+ window.location.href = nextStepRoute;
220
+ }
221
+ else {
222
+ // Same page - just run the step
223
+ runStep(nextStepId);
224
+ }
225
+ }
226
+ };
227
+ if (step.kind === 'modal') {
228
+ currentCleanup = runModalWithCallback(step, ctx, onStepAction);
229
+ }
230
+ else if (step.kind === 'tooltip') {
231
+ const el = await ctx.resolve(step.anchor);
232
+ if (!el) {
233
+ (_b = ctx.onEvent) === null || _b === void 0 ? void 0 : _b.call(ctx, 'syntro_anchor_missing', { stepId: step.id, anchor: step.anchor });
234
+ // Don't end tour - element might appear later or on different page
235
+ console.log(`[SmartCanvas] Anchor not found for step "${stepId}", waiting...`);
236
+ return;
237
+ }
238
+ currentCleanup = runTooltipWithCallback(step, el, ctx, onStepAction);
239
+ }
240
+ else if (step.kind === 'highlight') {
241
+ const el = await ctx.resolve(step.anchor);
242
+ if (!el) {
243
+ (_c = ctx.onEvent) === null || _c === void 0 ? void 0 : _c.call(ctx, 'syntro_anchor_missing', { stepId: step.id, anchor: step.anchor });
244
+ return;
245
+ }
246
+ currentCleanup = runHighlight(step, el, ctx);
247
+ }
248
+ (_d = ctx.onEvent) === null || _d === void 0 ? void 0 : _d.call(ctx, 'syntro_tour_step', { recipeId: recipe.id, stepId: step.id });
249
+ };
250
+ // Track current step for route change handling
251
+ let currentStepId;
252
+ // Determine starting step
253
+ if (isResumingTour && (activeState === null || activeState === void 0 ? void 0 : activeState.stepId)) {
254
+ // Resume from saved step
255
+ currentStepId = activeState.stepId;
256
+ console.log(`[SmartCanvas] Resuming tour "${recipe.id}" from step "${currentStepId}"`);
257
+ }
258
+ else {
259
+ // Start fresh
260
+ currentStepId = recipe.startStep || ((_a = recipe.steps[0]) === null || _a === void 0 ? void 0 : _a.id);
261
+ }
262
+ // Watch for SPA route changes (back/forward, pushState, replaceState)
263
+ // When route changes, re-evaluate if current step should show
264
+ const unwatchRoutes = watchRouteChanges((newPath) => {
265
+ if (isDestroyed || !currentStepId)
266
+ return;
267
+ const step = stepMap.get(currentStepId);
268
+ if (!step)
269
+ return;
270
+ const stepRoute = step.route;
271
+ const wasShowingStep = currentCleanup !== null;
272
+ const shouldShowStep = stepRouteMatches(step, newPath);
273
+ console.log(`[SmartCanvas] Route changed to "${newPath}" - step "${currentStepId}" route="${stepRoute}", wasShowing=${wasShowingStep}, shouldShow=${shouldShowStep}`);
274
+ if (wasShowingStep && !shouldShowStep) {
275
+ // User navigated away - hide the step
276
+ console.log(`[SmartCanvas] Hiding step "${currentStepId}" - user navigated away`);
277
+ if (currentCleanup) {
278
+ currentCleanup();
279
+ currentCleanup = null;
280
+ }
281
+ // Check if any next step from onAction matches the new route
282
+ // This handles cases where user clicks the actual app button instead of tour button
283
+ const currentStep = stepMap.get(currentStepId);
284
+ if (currentStep) {
285
+ const onActionMap = currentStep.onAction || {};
286
+ // Find a next step that matches the new route
287
+ for (const nextStepId of Object.values(onActionMap)) {
288
+ if (nextStepId === 'end' || typeof nextStepId !== 'string')
289
+ continue;
290
+ const nextStep = stepMap.get(nextStepId);
291
+ if (nextStep && stepRouteMatches(nextStep, newPath)) {
292
+ console.log(`[SmartCanvas] Auto-advancing to step "${nextStepId}" - matches new route "${newPath}"`);
293
+ currentStepId = nextStepId;
294
+ setActiveTourState({ tourId: recipe.id, stepId: nextStepId });
295
+ runStep(nextStepId);
296
+ return;
297
+ }
298
+ }
299
+ // No matching next step - just pause (save state for later resume)
300
+ console.log(`[SmartCanvas] No next step matches new route "${newPath}", pausing tour`);
301
+ setActiveTourState({ tourId: recipe.id, stepId: currentStepId });
302
+ }
303
+ }
304
+ else if (!wasShowingStep && shouldShowStep) {
305
+ // User navigated back - show the step (resume tour)
306
+ console.log(`[SmartCanvas] Resuming step "${currentStepId}" - user returned to correct route`);
307
+ runStep(currentStepId);
308
+ }
309
+ });
310
+ if (currentStepId) {
311
+ await runStep(currentStepId);
312
+ }
313
+ return () => {
314
+ isDestroyed = true;
315
+ unwatchRoutes();
316
+ if (currentCleanup) {
317
+ currentCleanup();
318
+ currentCleanup = null;
319
+ }
320
+ };
321
+ }
322
+ /** Start a tour by ID - clears completion state and begins */
323
+ export function startTour(tourId) {
324
+ const storageKey = `syntro_tour_${tourId}`;
325
+ try {
326
+ localStorage.removeItem(storageKey);
327
+ setActiveTourState({ tourId, stepId: '' }); // Will use startStep
328
+ window.location.reload();
329
+ }
330
+ catch { }
331
+ }
30
332
  function routeMatches(routes, path) {
31
333
  // simple contains/prefix; replace with real matcher as needed
32
334
  return routes.some(r => r === path || path.startsWith(r));
33
335
  }
336
+ function runModal(step, ctx) {
337
+ var _a, _b, _c, _d, _e, _f;
338
+ const html = modalHtml(step);
339
+ const handle = showModal(ctx.overlayRoot, {
340
+ html,
341
+ size: (_a = step.size) !== null && _a !== void 0 ? _a : 'md',
342
+ blocking: (_b = step.blocking) !== null && _b !== void 0 ? _b : true,
343
+ scrimOpacity: (_d = (_c = step.scrim) === null || _c === void 0 ? void 0 : _c.opacity) !== null && _d !== void 0 ? _d : 0.6,
344
+ onAction: (actionId) => {
345
+ var _a;
346
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_action', { stepId: step.id, actionId });
347
+ handle.destroy();
348
+ }
349
+ });
350
+ (_e = ctx.onEvent) === null || _e === void 0 ? void 0 : _e.call(ctx, 'syntro_overlay_exposed', { kind: 'modal', stepId: step.id, recipeId: ctx.recipeId });
351
+ const timers = [];
352
+ if ((_f = step.dismiss) === null || _f === void 0 ? void 0 : _f.timeoutMs) {
353
+ const id = window.setTimeout(() => {
354
+ var _a;
355
+ handle.destroy();
356
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'timeout' });
357
+ }, step.dismiss.timeoutMs);
358
+ timers.push(id);
359
+ }
360
+ const closeBtn = handle.el.querySelector('[data-syntro-role="close"]');
361
+ const onClose = () => {
362
+ var _a;
363
+ handle.destroy();
364
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'close' });
365
+ };
366
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener('click', onClose);
367
+ return () => {
368
+ timers.forEach((id) => clearTimeout(id));
369
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.removeEventListener('click', onClose);
370
+ handle.destroy();
371
+ };
372
+ }
373
+ function modalHtml(step) {
374
+ var _a, _b, _c;
375
+ const title = step.content.title ? `<div class="syntro-modal-title">${escapeHtml(step.content.title)}</div>` : '';
376
+ const body = `<div class="syntro-modal-body">${escapeHtml(step.content.body)}</div>`;
377
+ const close = ((_b = (_a = step.dismiss) === null || _a === void 0 ? void 0 : _a.closeButton) !== null && _b !== void 0 ? _b : true) ? `<button data-syntro-role="close" class="syntro-modal-close" aria-label="Close">&times;</button>` : '';
378
+ let actions = '';
379
+ if ((_c = step.ctaButtons) === null || _c === void 0 ? void 0 : _c.length) {
380
+ const btns = step.ctaButtons.map(btn => `<button class="syntro-modal-btn${btn.primary ? ' syntro-modal-btn-primary' : ''}" data-syntro-action="${escapeHtml(btn.actionId)}">${escapeHtml(btn.label)}</button>`).join('');
381
+ actions = `<div class="syntro-modal-actions">${btns}</div>`;
382
+ }
383
+ return `${close}${title}${body}${actions}`;
384
+ }
34
385
  function runTooltip(step, anchorEl, ctx) {
35
386
  var _a, _b, _c, _d;
36
387
  const html = tooltipHtml(step);
@@ -89,13 +440,101 @@ function runHighlight(step, anchorEl, ctx) {
89
440
  };
90
441
  }
91
442
  function tooltipHtml(step) {
92
- var _a, _b;
443
+ var _a, _b, _c;
93
444
  const title = step.content.title ? `<div class="syntro-tt-title">${escapeHtml(step.content.title)}</div>` : '';
94
445
  const body = `<div class="syntro-tt-body">${escapeHtml(step.content.body)}</div>`;
95
446
  const close = ((_b = (_a = step.dismiss) === null || _a === void 0 ? void 0 : _a.closeButton) !== null && _b !== void 0 ? _b : true) ? `<button data-syntro-role="close" class="syntro-tt-close" aria-label="Close">×</button>` : '';
96
- return `<div class="syntro-tt">${title}${body}${close}</div>`;
447
+ let actions = '';
448
+ if ((_c = step.ctaButtons) === null || _c === void 0 ? void 0 : _c.length) {
449
+ const btns = step.ctaButtons.map(btn => `<button class="syntro-tt-btn${btn.primary ? ' syntro-tt-btn-primary' : ''}" data-syntro-action="${escapeHtml(btn.actionId)}">${escapeHtml(btn.label)}</button>`).join('');
450
+ actions = `<div class="syntro-tt-actions">${btns}</div>`;
451
+ }
452
+ return `<div class="syntro-tt">${close}${title}${body}${actions}</div>`;
97
453
  }
98
454
  function escapeHtml(s) {
99
455
  return s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
100
456
  }
457
+ /** Modal runner for sequential tours with external action callback */
458
+ function runModalWithCallback(step, ctx, onAction) {
459
+ var _a, _b, _c, _d, _e, _f;
460
+ const html = modalHtml(step);
461
+ const handle = showModal(ctx.overlayRoot, {
462
+ html,
463
+ size: (_a = step.size) !== null && _a !== void 0 ? _a : 'md',
464
+ blocking: (_b = step.blocking) !== null && _b !== void 0 ? _b : true,
465
+ scrimOpacity: (_d = (_c = step.scrim) === null || _c === void 0 ? void 0 : _c.opacity) !== null && _d !== void 0 ? _d : 0.6,
466
+ onAction: (actionId) => {
467
+ var _a;
468
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_action', { stepId: step.id, actionId });
469
+ handle.destroy();
470
+ onAction(actionId);
471
+ }
472
+ });
473
+ (_e = ctx.onEvent) === null || _e === void 0 ? void 0 : _e.call(ctx, 'syntro_overlay_exposed', { kind: 'modal', stepId: step.id, recipeId: ctx.recipeId });
474
+ const timers = [];
475
+ if ((_f = step.dismiss) === null || _f === void 0 ? void 0 : _f.timeoutMs) {
476
+ const id = window.setTimeout(() => {
477
+ var _a;
478
+ handle.destroy();
479
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'timeout' });
480
+ onAction('dismiss');
481
+ }, step.dismiss.timeoutMs);
482
+ timers.push(id);
483
+ }
484
+ const closeBtn = handle.el.querySelector('[data-syntro-role="close"]');
485
+ const onClose = () => {
486
+ var _a;
487
+ handle.destroy();
488
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'close' });
489
+ onAction('dismiss');
490
+ };
491
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener('click', onClose);
492
+ return () => {
493
+ timers.forEach((id) => clearTimeout(id));
494
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.removeEventListener('click', onClose);
495
+ handle.destroy();
496
+ };
497
+ }
498
+ /** Tooltip runner for sequential tours with CTA buttons and external action callback */
499
+ function runTooltipWithCallback(step, anchorEl, ctx, onAction) {
500
+ var _a, _b, _c, _d;
501
+ const html = tooltipHtml(step);
502
+ const handle = showTooltip(anchorEl, ctx.overlayRoot, {
503
+ html,
504
+ placement: (_a = step.placement) !== null && _a !== void 0 ? _a : 'top',
505
+ offsetPx: (_b = step.offsetPx) !== null && _b !== void 0 ? _b : 8,
506
+ blocking: !!step.blocking,
507
+ trigger: step.trigger,
508
+ onAction: (actionId) => {
509
+ var _a;
510
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_action', { stepId: step.id, actionId });
511
+ handle.destroy();
512
+ onAction(actionId);
513
+ }
514
+ });
515
+ (_c = ctx.onEvent) === null || _c === void 0 ? void 0 : _c.call(ctx, 'syntro_overlay_exposed', { kind: 'tooltip', stepId: step.id, recipeId: ctx.recipeId });
516
+ const timers = [];
517
+ if ((_d = step.dismiss) === null || _d === void 0 ? void 0 : _d.timeoutMs) {
518
+ const id = window.setTimeout(() => {
519
+ var _a;
520
+ handle.destroy();
521
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'timeout' });
522
+ onAction('dismiss');
523
+ }, step.dismiss.timeoutMs);
524
+ timers.push(id);
525
+ }
526
+ const closeBtn = handle.el.querySelector('[data-syntro-role="close"]');
527
+ const onClose = () => {
528
+ var _a;
529
+ handle.destroy();
530
+ (_a = ctx.onEvent) === null || _a === void 0 ? void 0 : _a.call(ctx, 'syntro_overlay_dismissed', { stepId: step.id, reason: 'close' });
531
+ onAction('dismiss');
532
+ };
533
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener('click', onClose);
534
+ return () => {
535
+ timers.forEach((id) => clearTimeout(id));
536
+ closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.removeEventListener('click', onClose);
537
+ handle.destroy();
538
+ };
539
+ }
101
540
  //# sourceMappingURL=runner.js.map