@syntrologie/adapt-overlays 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/WorkflowWidgetLit.d.ts +123 -0
  2. package/dist/WorkflowWidgetLit.d.ts.map +1 -0
  3. package/dist/WorkflowWidgetLit.js +617 -0
  4. package/dist/runtime-lit.d.ts +94 -0
  5. package/dist/runtime-lit.d.ts.map +1 -0
  6. package/dist/runtime-lit.js +402 -0
  7. package/dist/runtime.d.ts.map +1 -1
  8. package/dist/runtime.js +10 -0
  9. package/dist/schema.d.ts +4 -4
  10. package/node_modules/@syntro/design-system/dist/tokens/index.d.ts +2 -0
  11. package/node_modules/@syntro/design-system/dist/tokens/index.d.ts.map +1 -1
  12. package/node_modules/@syntro/design-system/dist/tokens/index.js +2 -0
  13. package/node_modules/@syntro/design-system/dist/tokens/panel-shell.d.ts +93 -0
  14. package/node_modules/@syntro/design-system/dist/tokens/panel-shell.d.ts.map +1 -0
  15. package/node_modules/@syntro/design-system/dist/tokens/panel-shell.js +72 -0
  16. package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.d.ts +84 -0
  17. package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.d.ts.map +1 -0
  18. package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.js +323 -0
  19. package/node_modules/@syntrologie/shared-editor-ui/dist/components/BeforeAfterToggleLit.d.ts +25 -0
  20. package/node_modules/@syntrologie/shared-editor-ui/dist/components/BeforeAfterToggleLit.d.ts.map +1 -0
  21. package/node_modules/@syntrologie/shared-editor-ui/dist/components/BeforeAfterToggleLit.js +55 -0
  22. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ConditionStatusLineLit.d.ts +33 -0
  23. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ConditionStatusLineLit.d.ts.map +1 -0
  24. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ConditionStatusLineLit.js +118 -0
  25. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DetectionBadgeLit.d.ts +32 -0
  26. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DetectionBadgeLit.d.ts.map +1 -0
  27. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DetectionBadgeLit.js +68 -0
  28. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DismissedSectionLit.d.ts +34 -0
  29. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DismissedSectionLit.d.ts.map +1 -0
  30. package/node_modules/@syntrologie/shared-editor-ui/dist/components/DismissedSectionLit.js +57 -0
  31. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditBackButtonLit.d.ts +13 -0
  32. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditBackButtonLit.d.ts.map +1 -0
  33. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditBackButtonLit.js +31 -0
  34. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorBodyLit.d.ts +7 -0
  35. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorBodyLit.d.ts.map +1 -0
  36. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorBodyLit.js +15 -0
  37. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorCardLit.d.ts +36 -0
  38. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorCardLit.d.ts.map +1 -0
  39. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorCardLit.js +102 -0
  40. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorFooterLit.d.ts +20 -0
  41. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorFooterLit.d.ts.map +1 -0
  42. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorFooterLit.js +48 -0
  43. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorHeaderLit.d.ts +16 -0
  44. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorHeaderLit.d.ts.map +1 -0
  45. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorHeaderLit.js +25 -0
  46. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorInputLit.d.ts +66 -0
  47. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorInputLit.d.ts.map +1 -0
  48. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorInputLit.js +87 -0
  49. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorLayoutLit.d.ts +7 -0
  50. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorLayoutLit.d.ts.map +1 -0
  51. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorLayoutLit.js +15 -0
  52. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShell.d.ts.map +1 -1
  53. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShell.js +28 -17
  54. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.d.ts +66 -0
  55. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.d.ts.map +1 -0
  56. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.js +528 -0
  57. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorSelectLit.d.ts +41 -0
  58. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorSelectLit.d.ts.map +1 -0
  59. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorSelectLit.js +63 -0
  60. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorTextareaLit.d.ts +55 -0
  61. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorTextareaLit.d.ts.map +1 -0
  62. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorTextareaLit.js +92 -0
  63. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlightLit.d.ts +90 -0
  64. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlightLit.d.ts.map +1 -0
  65. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlightLit.js +242 -0
  66. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EmptyStateLit.d.ts +12 -0
  67. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EmptyStateLit.d.ts.map +1 -0
  68. package/node_modules/@syntrologie/shared-editor-ui/dist/components/EmptyStateLit.js +21 -0
  69. package/node_modules/@syntrologie/shared-editor-ui/dist/components/GroupHeaderLit.d.ts +21 -0
  70. package/node_modules/@syntrologie/shared-editor-ui/dist/components/GroupHeaderLit.d.ts.map +1 -0
  71. package/node_modules/@syntrologie/shared-editor-ui/dist/components/GroupHeaderLit.js +33 -0
  72. package/node_modules/@syntrologie/shared-editor-ui/dist/components/TriggerJourneyLit.d.ts +28 -0
  73. package/node_modules/@syntrologie/shared-editor-ui/dist/components/TriggerJourneyLit.d.ts.map +1 -0
  74. package/node_modules/@syntrologie/shared-editor-ui/dist/components/TriggerJourneyLit.js +121 -0
  75. package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.d.ts +110 -0
  76. package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.d.ts.map +1 -0
  77. package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.js +476 -0
  78. package/node_modules/@syntrologie/shared-editor-ui/dist/index.d.ts +2 -0
  79. package/node_modules/@syntrologie/shared-editor-ui/dist/index.d.ts.map +1 -1
  80. package/node_modules/@syntrologie/shared-editor-ui/dist/index.js +1 -0
  81. package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.d.ts +15 -0
  82. package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.d.ts.map +1 -0
  83. package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.js +14 -0
  84. package/node_modules/@syntrologie/shared-editor-ui/dist/utils/elementChainRecommender.d.ts +0 -4
  85. package/node_modules/@syntrologie/shared-editor-ui/dist/utils/elementChainRecommender.d.ts.map +1 -1
  86. package/node_modules/@syntrologie/shared-editor-ui/dist/utils/elementChainRecommender.js +17 -1
  87. package/node_modules/@syntrologie/shared-editor-ui/package.json +9 -1
  88. package/package.json +9 -2
@@ -0,0 +1,617 @@
1
+ /**
2
+ * WorkflowWidgetLit — Lit web component for the WorkflowTracker.
3
+ *
4
+ * Lit equivalent of WorkflowWidget.tsx / WorkflowWidgetInner.
5
+ * Tag name: <syntro-workflow-tracker>
6
+ *
7
+ * Responsibilities (mirrors the React version):
8
+ * 1. Scan runtime.actions.getActive() for tours with `workflow` field
9
+ * 2. Re-scan on tour.started / tour.resumed events
10
+ * 3. Load persisted state from runtime.state.user namespace
11
+ * 4. Subscribe to tour.* events and update workflow entries
12
+ * 5. Render workflow cards with progress indicators and step lists
13
+ * 6. Handle step clicks (publish workflow:jump_to_step)
14
+ * 7. Handle dismiss (persist and re-render)
15
+ * 8. Show toast notifications for newly discovered workflows
16
+ *
17
+ * Decorator-free: uses `static override properties` (tsconfig has no
18
+ * experimentalDecorators).
19
+ *
20
+ * Uses createRenderRoot() { return this; } for light DOM so host-page CSS
21
+ * variables (--se-color-*, --se-font-family, etc.) flow through without a
22
+ * nested shadow boundary.
23
+ */
24
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
25
+ if (kind === "m") throw new TypeError("Private method is not writable");
26
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
27
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
28
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
29
+ };
30
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
31
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
32
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
33
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
34
+ };
35
+ var _WorkflowTrackerLit_unsubTourStarted, _WorkflowTrackerLit_unsubTourEvents, _WorkflowTrackerLit_toastCleanups, _WorkflowTrackerLit_notified, _WorkflowTrackerLit_completedMap, _WorkflowTrackerLit_persistInitialized, _WorkflowTrackerLit_tourWorkflows;
36
+ import { html, LitElement, nothing } from 'lit';
37
+ import { styleMap } from 'lit/directives/style-map.js';
38
+ // Design-system fallback tokens — inlined because this adaptive ships to
39
+ // customer pages and themes via `--se-color-*` CSS variables. The TOKEN_*
40
+ // values are only used when a customer hasn't set their own theme.
41
+ // Values match packages/design-system/src/tokens/colors.ts.
42
+ // blue[4] #1969fe · green[4] #24ad32
43
+ // slateGrey[2] #0e1114 · slateGrey[7] #677384 · slateGrey[12] #f6f7f9
44
+ // slateGrey[9] #a8afba · base.white #ffffff
45
+ const TOKEN_BLUE_4 = '#1969fe';
46
+ const TOKEN_GREEN_4 = '#24ad32';
47
+ const TOKEN_SLATE_2 = '#0e1114';
48
+ const TOKEN_SLATE_7 = '#677384';
49
+ const TOKEN_SLATE_9 = '#a8afba';
50
+ const TOKEN_SLATE_12 = '#f6f7f9';
51
+ const TOKEN_WHITE = '#ffffff';
52
+ // ============================================================================
53
+ // Helpers (mirrored from WorkflowWidget.tsx)
54
+ // ============================================================================
55
+ /**
56
+ * Show a toast notification for a workflow tour.
57
+ * Appends to document.body so position:fixed is scoped to the viewport,
58
+ * not to a shadow DOM container.
59
+ */
60
+ function showWorkflowToast(notification) {
61
+ const toast = document.createElement('div');
62
+ toast.setAttribute('data-testid', 'workflow-toast');
63
+ Object.assign(toast.style, {
64
+ position: 'fixed',
65
+ bottom: '16px',
66
+ right: '16px',
67
+ zIndex: '2147483646',
68
+ padding: '12px 16px',
69
+ borderRadius: '8px',
70
+ backgroundColor: `var(--se-color-bg-surface, ${TOKEN_WHITE})`,
71
+ color: `var(--se-color-text-primary, ${TOKEN_SLATE_2})`,
72
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
73
+ maxWidth: '320px',
74
+ fontFamily: 'var(--se-font-family, system-ui, sans-serif)',
75
+ fontSize: '14px',
76
+ lineHeight: '1.4',
77
+ transition: 'opacity 0.3s ease',
78
+ });
79
+ const titleEl = document.createElement('div');
80
+ titleEl.style.fontWeight = '600';
81
+ titleEl.textContent = notification.title;
82
+ toast.appendChild(titleEl);
83
+ if (notification.body) {
84
+ const bodyEl = document.createElement('div');
85
+ bodyEl.style.marginTop = '4px';
86
+ bodyEl.style.fontSize = '13px';
87
+ bodyEl.style.color = 'var(--se-color-text-secondary, #666)';
88
+ bodyEl.textContent = notification.body;
89
+ toast.appendChild(bodyEl);
90
+ }
91
+ document.body.appendChild(toast);
92
+ let removeTimer;
93
+ const fadeTimer = setTimeout(() => {
94
+ toast.style.opacity = '0';
95
+ removeTimer = setTimeout(() => {
96
+ toast.remove();
97
+ }, 300);
98
+ }, 4000);
99
+ return () => {
100
+ clearTimeout(fadeTimer);
101
+ clearTimeout(removeTimer);
102
+ toast.remove();
103
+ };
104
+ }
105
+ /**
106
+ * Extract workflow-enabled tours from active actions (runtime.actions.getActive()).
107
+ * Only actions with kind 'overlays:tour', a tourId, and a workflow field are included.
108
+ */
109
+ function extractWorkflowsFromActive(activeActions) {
110
+ const workflows = new Map();
111
+ for (const entry of activeActions) {
112
+ const action = entry.action;
113
+ if (action.kind === 'overlays:tour' && action.workflow && action.tourId) {
114
+ const meta = action.workflow;
115
+ const rawSteps = action.steps || [];
116
+ const steps = rawSteps.map((s) => ({
117
+ id: s.id,
118
+ title: meta.stepTitles?.[s.id] || s.id,
119
+ }));
120
+ workflows.set(action.tourId, { meta, steps });
121
+ }
122
+ }
123
+ return workflows;
124
+ }
125
+ // ============================================================================
126
+ // WorkflowTrackerLit — Lit Element
127
+ // ============================================================================
128
+ const TAG_NAME = 'syntro-workflow-tracker';
129
+ export class WorkflowTrackerLit extends LitElement {
130
+ constructor() {
131
+ // ── Static properties (no decorators) ────────────────────────────────────
132
+ super(...arguments);
133
+ // ── Public properties ─────────────────────────────────────────────────────
134
+ this.runtimeRef = null;
135
+ // ── Internal reactive state ───────────────────────────────────────────────
136
+ /** @internal */ this._workflowEntries = [];
137
+ /**
138
+ * @internal
139
+ * Bumped on tour.started / tour.resumed to trigger re-scan of active actions.
140
+ */
141
+ /** @internal */ this._actionVersion = 0;
142
+ // ── Private (non-reactive) fields ─────────────────────────────────────────
143
+ // Subscription cleanup functions
144
+ _WorkflowTrackerLit_unsubTourStarted.set(this, null);
145
+ _WorkflowTrackerLit_unsubTourEvents.set(this, null);
146
+ // Toast cleanup tracking
147
+ _WorkflowTrackerLit_toastCleanups.set(this, []);
148
+ // Notifications already shown (mirrors notifiedRef)
149
+ _WorkflowTrackerLit_notified.set(this, new Set());
150
+ // Completed timestamps (mirrors completedMapRef)
151
+ _WorkflowTrackerLit_completedMap.set(this, {});
152
+ // Whether persisted state has been loaded
153
+ _WorkflowTrackerLit_persistInitialized.set(this, false);
154
+ // Cache of the last scanned tourWorkflows map (used by event handler)
155
+ _WorkflowTrackerLit_tourWorkflows.set(this, new Map());
156
+ }
157
+ // ── Light DOM ─────────────────────────────────────────────────────────────
158
+ /**
159
+ * Render into the element itself (light DOM) so host-page CSS variables
160
+ * flow through without a nested shadow boundary.
161
+ */
162
+ createRenderRoot() {
163
+ return this;
164
+ }
165
+ // ── Helpers ───────────────────────────────────────────────────────────────
166
+ get _stateNs() {
167
+ return this.runtimeRef?.state?.user?.ns?.('workflows') ?? null;
168
+ }
169
+ /**
170
+ * Re-scan active actions and update _tourWorkflows + entries.
171
+ * Called initially and whenever _actionVersion bumps.
172
+ */
173
+ _rescanWorkflows() {
174
+ const active = this.runtimeRef?.actions?.getActive?.() ?? [];
175
+ const workflows = extractWorkflowsFromActive(active);
176
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_tourWorkflows, workflows, "f");
177
+ if (workflows.size === 0)
178
+ return;
179
+ const stateNs = this._stateNs;
180
+ const dismissed = stateNs?.get('dismissed') ?? [];
181
+ const completed = stateNs?.get('completed') ?? {};
182
+ this._workflowEntries = (() => {
183
+ const existingIds = new Set(this._workflowEntries.map((e) => e.tourId));
184
+ const newEntries = [];
185
+ for (const [tourId, { meta, steps }] of workflows) {
186
+ if (existingIds.has(tourId))
187
+ continue;
188
+ let status = 'active';
189
+ if (dismissed.includes(tourId)) {
190
+ status = 'dismissed';
191
+ }
192
+ else if (completed[tourId]) {
193
+ status = 'completed';
194
+ }
195
+ newEntries.push({
196
+ tourId,
197
+ meta,
198
+ steps,
199
+ currentStepId: null,
200
+ completedSteps: [],
201
+ status,
202
+ completedAt: completed[tourId] || undefined,
203
+ });
204
+ }
205
+ return newEntries.length > 0
206
+ ? [...this._workflowEntries, ...newEntries]
207
+ : this._workflowEntries;
208
+ })();
209
+ // Fire toast notifications for newly discovered active tours
210
+ for (const [tourId, { meta }] of workflows) {
211
+ const dismissed2 = stateNs?.get('dismissed') ?? [];
212
+ const completed2 = stateNs?.get('completed') ?? {};
213
+ if (!__classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f").has(tourId) &&
214
+ meta.notification &&
215
+ !dismissed2.includes(tourId) &&
216
+ !completed2[tourId]) {
217
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f").add(tourId);
218
+ stateNs?.set('notified', [...__classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f")]);
219
+ const cleanup = showWorkflowToast(meta.notification);
220
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_toastCleanups, "f").push(cleanup);
221
+ }
222
+ }
223
+ }
224
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
225
+ connectedCallback() {
226
+ super.connectedCallback();
227
+ this._initSubscriptions();
228
+ }
229
+ disconnectedCallback() {
230
+ super.disconnectedCallback();
231
+ this._teardownSubscriptions();
232
+ for (const cleanup of __classPrivateFieldGet(this, _WorkflowTrackerLit_toastCleanups, "f")) {
233
+ cleanup();
234
+ }
235
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_toastCleanups, [], "f");
236
+ }
237
+ updated(changed) {
238
+ if (changed.has('runtimeRef')) {
239
+ // Re-wire subscriptions when the runtime ref changes
240
+ this._teardownSubscriptions();
241
+ this._initSubscriptions();
242
+ }
243
+ if (changed.has('_actionVersion')) {
244
+ this._rescanWorkflows();
245
+ }
246
+ }
247
+ // ── Subscription management ───────────────────────────────────────────────
248
+ _initSubscriptions() {
249
+ if (!this.runtimeRef?.events?.subscribe)
250
+ return;
251
+ // Initialize persisted state once
252
+ if (!__classPrivateFieldGet(this, _WorkflowTrackerLit_persistInitialized, "f") && this._stateNs) {
253
+ const notified = this._stateNs.get('notified') ?? [];
254
+ for (const id of notified) {
255
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f").add(id);
256
+ }
257
+ const completed = this._stateNs.get('completed') ?? {};
258
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_completedMap, { ...completed }, "f");
259
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_persistInitialized, true, "f");
260
+ }
261
+ // Subscribe to tour.started / tour.resumed to re-scan getActive()
262
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_unsubTourStarted, this.runtimeRef.events.subscribe({ names: ['tour.started', 'tour.resumed'] }, () => {
263
+ this._actionVersion += 1;
264
+ }), "f");
265
+ // Subscribe to all tour.* events for state updates
266
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_unsubTourEvents, this.runtimeRef.events.subscribe({ patterns: ['^tour\\.'] }, (event) => {
267
+ this._handleTourEvent(event);
268
+ }), "f");
269
+ // Initial scan
270
+ this._rescanWorkflows();
271
+ }
272
+ _teardownSubscriptions() {
273
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_unsubTourStarted, "f")?.call(this);
274
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_unsubTourStarted, null, "f");
275
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_unsubTourEvents, "f")?.call(this);
276
+ __classPrivateFieldSet(this, _WorkflowTrackerLit_unsubTourEvents, null, "f");
277
+ }
278
+ // ── Event handler ─────────────────────────────────────────────────────────
279
+ _handleTourEvent(event) {
280
+ const tourId = event.props?.tourId;
281
+ if (!tourId)
282
+ return;
283
+ // If the tour isn't tracked yet and this is a start event, bump actionVersion
284
+ // so updated() triggers a re-scan.
285
+ if (!__classPrivateFieldGet(this, _WorkflowTrackerLit_tourWorkflows, "f").has(tourId) && event.name === 'tour.started') {
286
+ this._actionVersion += 1;
287
+ return;
288
+ }
289
+ if (!__classPrivateFieldGet(this, _WorkflowTrackerLit_tourWorkflows, "f").has(tourId))
290
+ return;
291
+ const stateNs = this._stateNs;
292
+ this._workflowEntries = this._workflowEntries.map((entry) => {
293
+ if (entry.tourId !== tourId)
294
+ return entry;
295
+ switch (event.name) {
296
+ case 'tour.started': {
297
+ const startStepId = event.props?.startStepId || entry.steps[0]?.id || null;
298
+ // Show toast if not already notified
299
+ if (!__classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f").has(tourId)) {
300
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f").add(tourId);
301
+ stateNs?.set('notified', [...__classPrivateFieldGet(this, _WorkflowTrackerLit_notified, "f")]);
302
+ const workflow = __classPrivateFieldGet(this, _WorkflowTrackerLit_tourWorkflows, "f").get(tourId);
303
+ if (workflow?.meta.notification) {
304
+ const cleanup = showWorkflowToast(workflow.meta.notification);
305
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_toastCleanups, "f").push(cleanup);
306
+ }
307
+ }
308
+ // Persist active state
309
+ const activeIds = this._workflowEntries
310
+ .filter((e) => e.status === 'active' || e.tourId === tourId)
311
+ .map((e) => e.tourId);
312
+ if (!activeIds.includes(tourId)) {
313
+ activeIds.push(tourId);
314
+ }
315
+ stateNs?.set('active', [...new Set(activeIds)]);
316
+ return {
317
+ ...entry,
318
+ status: 'active',
319
+ currentStepId: startStepId,
320
+ completedSteps: entry.status === 'active' ? entry.completedSteps : [],
321
+ };
322
+ }
323
+ case 'tour.step_started': {
324
+ const stepId = event.props?.stepId;
325
+ return {
326
+ ...entry,
327
+ currentStepId: stepId || entry.currentStepId,
328
+ };
329
+ }
330
+ case 'tour.step_changed': {
331
+ const previousStepId = event.props?.previousStepId;
332
+ const nextStepId = event.props?.nextStepId;
333
+ const completedSteps = previousStepId && !entry.completedSteps.includes(previousStepId)
334
+ ? [...entry.completedSteps, previousStepId]
335
+ : entry.completedSteps;
336
+ return {
337
+ ...entry,
338
+ currentStepId: nextStepId || entry.currentStepId,
339
+ completedSteps,
340
+ };
341
+ }
342
+ case 'tour.completed': {
343
+ const completedAt = Date.now();
344
+ __classPrivateFieldGet(this, _WorkflowTrackerLit_completedMap, "f")[tourId] = completedAt;
345
+ stateNs?.set('completed', { ...__classPrivateFieldGet(this, _WorkflowTrackerLit_completedMap, "f") });
346
+ return {
347
+ ...entry,
348
+ status: 'completed',
349
+ currentStepId: null,
350
+ completedSteps: entry.steps.map((s) => s.id),
351
+ completedAt,
352
+ };
353
+ }
354
+ case 'tour.paused':
355
+ return entry;
356
+ default:
357
+ return entry;
358
+ }
359
+ });
360
+ }
361
+ // ── User action handlers ──────────────────────────────────────────────────
362
+ _handleStepClick(tourId, stepId) {
363
+ this.runtimeRef?.events?.publish('workflow:jump_to_step', { tourId, stepId });
364
+ this.dispatchEvent(new CustomEvent('workflow-step-click', {
365
+ bubbles: true,
366
+ detail: { tourId, stepId },
367
+ }));
368
+ }
369
+ _handleDismiss(tourId) {
370
+ this._workflowEntries = this._workflowEntries.map((entry) => entry.tourId === tourId ? { ...entry, status: 'dismissed' } : entry);
371
+ const dismissedIds = this._workflowEntries
372
+ .filter((e) => e.status === 'dismissed')
373
+ .map((e) => e.tourId);
374
+ this._stateNs?.set('dismissed', dismissedIds);
375
+ this.dispatchEvent(new CustomEvent('workflow-dismissed', {
376
+ bubbles: true,
377
+ detail: { tourId },
378
+ }));
379
+ }
380
+ // ── Render helpers ────────────────────────────────────────────────────────
381
+ _renderProgressBar(completed, total) {
382
+ const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
383
+ const trackStyles = {
384
+ width: '100%',
385
+ height: '6px',
386
+ borderRadius: '9999px',
387
+ background: 'rgba(255,255,255,0.08)',
388
+ overflow: 'hidden',
389
+ };
390
+ const fillStyles = {
391
+ height: '100%',
392
+ borderRadius: '9999px',
393
+ background: `var(--se-color-primary, ${TOKEN_BLUE_4})`,
394
+ transition: 'width 0.3s ease',
395
+ width: `${percent}%`,
396
+ };
397
+ return html `
398
+ <div style=${styleMap(trackStyles)}>
399
+ <div
400
+ role="progressbar"
401
+ aria-valuenow=${percent}
402
+ aria-valuemin="0"
403
+ aria-valuemax="100"
404
+ style=${styleMap(fillStyles)}
405
+ ></div>
406
+ </div>
407
+ `;
408
+ }
409
+ _renderStepItem(step, isCompleted, isCurrent, tourId) {
410
+ const btnStyles = {
411
+ display: 'flex',
412
+ alignItems: 'center',
413
+ gap: '8px',
414
+ width: '100%',
415
+ padding: '6px 8px',
416
+ borderRadius: '4px',
417
+ border: 'none',
418
+ background: 'transparent',
419
+ cursor: 'pointer',
420
+ textAlign: 'left',
421
+ fontSize: '12px',
422
+ lineHeight: '1.4',
423
+ color: isCurrent
424
+ ? `var(--se-color-text-primary, ${TOKEN_SLATE_12})`
425
+ : `var(--se-color-text-secondary, ${TOKEN_SLATE_9})`,
426
+ fontWeight: isCurrent ? '600' : '400',
427
+ };
428
+ const indicatorWrapStyles = {
429
+ flexShrink: '0',
430
+ width: '16px',
431
+ textAlign: 'center',
432
+ };
433
+ const dotStyles = {
434
+ display: 'inline-block',
435
+ width: '6px',
436
+ height: '6px',
437
+ borderRadius: '50%',
438
+ background: isCurrent ? `var(--se-color-primary, ${TOKEN_BLUE_4})` : 'rgba(255,255,255,0.12)',
439
+ };
440
+ const checkStyles = {
441
+ color: `var(--se-color-success, ${TOKEN_GREEN_4})`,
442
+ };
443
+ const labelStyles = {
444
+ flex: '1',
445
+ overflow: 'hidden',
446
+ textOverflow: 'ellipsis',
447
+ whiteSpace: 'nowrap',
448
+ };
449
+ const indicator = isCompleted
450
+ ? html `<span role="img" aria-label="completed" style=${styleMap(checkStyles)}>&#10003;</span>`
451
+ : isCurrent
452
+ ? html `<span style=${styleMap(dotStyles)}></span>`
453
+ : html `<span style=${styleMap(dotStyles)}></span>`;
454
+ return html `
455
+ <button
456
+ type="button"
457
+ data-testid=${`step-${step.id}`}
458
+ data-current=${isCurrent ? 'true' : nothing}
459
+ data-completed=${isCompleted ? 'true' : nothing}
460
+ aria-current=${isCurrent ? 'step' : nothing}
461
+ style=${styleMap(btnStyles)}
462
+ @click=${() => this._handleStepClick(tourId, step.id)}
463
+ >
464
+ <span style=${styleMap(indicatorWrapStyles)}>${indicator}</span>
465
+ <span style=${styleMap(labelStyles)}>${step.title}</span>
466
+ </button>
467
+ `;
468
+ }
469
+ _renderWorkflowCard(workflow) {
470
+ const completedCount = workflow.completedSteps.length;
471
+ const totalSteps = workflow.steps.length;
472
+ const cardStyles = {
473
+ padding: '12px',
474
+ borderRadius: '8px',
475
+ border: '1px solid rgba(255,255,255,0.08)',
476
+ background: 'rgba(255,255,255,0.02)',
477
+ };
478
+ const headerStyles = {
479
+ display: 'flex',
480
+ alignItems: 'center',
481
+ gap: '8px',
482
+ marginBottom: '8px',
483
+ };
484
+ const titleStyles = {
485
+ flex: '1',
486
+ fontSize: '13px',
487
+ fontWeight: '600',
488
+ color: `var(--se-color-text-primary, ${TOKEN_SLATE_12})`,
489
+ overflow: 'hidden',
490
+ textOverflow: 'ellipsis',
491
+ whiteSpace: 'nowrap',
492
+ };
493
+ const dismissBtnStyles = {
494
+ flexShrink: '0',
495
+ padding: '2px 6px',
496
+ border: 'none',
497
+ borderRadius: '4px',
498
+ background: 'transparent',
499
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
500
+ fontSize: '12px',
501
+ cursor: 'pointer',
502
+ lineHeight: '1',
503
+ };
504
+ const progressWrapStyles = {
505
+ marginBottom: '8px',
506
+ };
507
+ const progressLabelStyles = {
508
+ fontSize: '10px',
509
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
510
+ marginTop: '4px',
511
+ };
512
+ const stepsColStyles = {
513
+ display: 'flex',
514
+ flexDirection: 'column',
515
+ };
516
+ return html `
517
+ <div style=${styleMap(cardStyles)}>
518
+ <!-- Header: icon + title + dismiss -->
519
+ <div style=${styleMap(headerStyles)}>
520
+ ${workflow.meta.icon
521
+ ? html `<span data-testid="workflow-icon" style="flex-shrink:0;font-size:14px">${workflow.meta.icon}</span>`
522
+ : nothing}
523
+ <span style=${styleMap(titleStyles)}>${workflow.meta.title}</span>
524
+ <button
525
+ type="button"
526
+ data-testid=${`dismiss-${workflow.tourId}`}
527
+ style=${styleMap(dismissBtnStyles)}
528
+ aria-label=${`Dismiss ${workflow.meta.title}`}
529
+ @click=${() => this._handleDismiss(workflow.tourId)}
530
+ >&#10005;</button>
531
+ </div>
532
+
533
+ <!-- Progress bar + label -->
534
+ <div style=${styleMap(progressWrapStyles)}>
535
+ ${this._renderProgressBar(completedCount, totalSteps)}
536
+ <div style=${styleMap(progressLabelStyles)}>
537
+ ${completedCount} of ${totalSteps} steps
538
+ </div>
539
+ </div>
540
+
541
+ <!-- Step list -->
542
+ <div style=${styleMap(stepsColStyles)}>
543
+ ${workflow.steps.map((step) => this._renderStepItem(step, workflow.completedSteps.includes(step.id), workflow.currentStepId === step.id, workflow.tourId))}
544
+ </div>
545
+ </div>
546
+ `;
547
+ }
548
+ // ── Render ────────────────────────────────────────────────────────────────
549
+ render() {
550
+ const activeWorkflows = this._workflowEntries.filter((w) => w.status === 'active');
551
+ if (activeWorkflows.length === 0) {
552
+ const emptyStyles = {
553
+ display: 'flex',
554
+ alignItems: 'center',
555
+ justifyContent: 'center',
556
+ padding: '24px 0',
557
+ fontSize: '12px',
558
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
559
+ };
560
+ return html `<div style=${styleMap(emptyStyles)}>No active workflows</div>`;
561
+ }
562
+ const containerStyles = {
563
+ display: 'flex',
564
+ flexDirection: 'column',
565
+ gap: '8px',
566
+ };
567
+ return html `
568
+ <div style=${styleMap(containerStyles)}>
569
+ ${activeWorkflows.map((workflow) => this._renderWorkflowCard(workflow))}
570
+ </div>
571
+ `;
572
+ }
573
+ }
574
+ _WorkflowTrackerLit_unsubTourStarted = new WeakMap(), _WorkflowTrackerLit_unsubTourEvents = new WeakMap(), _WorkflowTrackerLit_toastCleanups = new WeakMap(), _WorkflowTrackerLit_notified = new WeakMap(), _WorkflowTrackerLit_completedMap = new WeakMap(), _WorkflowTrackerLit_persistInitialized = new WeakMap(), _WorkflowTrackerLit_tourWorkflows = new WeakMap();
575
+ WorkflowTrackerLit.properties = {
576
+ // Public input: runtime ref injected by MountableWidget
577
+ runtimeRef: { attribute: false },
578
+ // Internal reactive state
579
+ _workflowEntries: { state: true },
580
+ _actionVersion: { state: true },
581
+ };
582
+ // ── Custom element registration ───────────────────────────────────────────────
583
+ if (typeof window !== 'undefined' && !customElements.get(TAG_NAME)) {
584
+ customElements.define(TAG_NAME, WorkflowTrackerLit);
585
+ }
586
+ // ============================================================================
587
+ // WorkflowWidgetLitMountable — MountableWidget interface
588
+ // ============================================================================
589
+ /**
590
+ * MountableWidget that instantiates a <syntro-workflow-tracker> Lit element
591
+ * and injects the runtime ref.
592
+ *
593
+ * Follows the same MountableWidget contract as WorkflowMountableWidget.
594
+ * Config is enriched with `runtime` by WidgetRegistry.bindRuntime().
595
+ */
596
+ export const WorkflowWidgetLitMountable = {
597
+ mount(container, config) {
598
+ const runtime = config?.runtime ?? null;
599
+ // Ensure the custom element is registered
600
+ if (typeof window !== 'undefined' && !customElements.get(TAG_NAME)) {
601
+ customElements.define(TAG_NAME, WorkflowTrackerLit);
602
+ }
603
+ const el = document.createElement(TAG_NAME);
604
+ el.runtimeRef = runtime;
605
+ container.appendChild(el);
606
+ return () => {
607
+ el.remove();
608
+ };
609
+ },
610
+ update(container, config) {
611
+ const el = container.querySelector(TAG_NAME);
612
+ if (!el)
613
+ return;
614
+ const runtime = config?.runtime ?? null;
615
+ el.runtimeRef = runtime;
616
+ },
617
+ };