@cognior/iap-sdk 0.1.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.

Potentially problematic release.


This version of @cognior/iap-sdk might be problematic. Click here for more details.

Files changed (60) hide show
  1. package/.github/copilot-instructions.md +95 -0
  2. package/README.md +79 -0
  3. package/TRACKING.md +105 -0
  4. package/USER_CONTEXT_README.md +284 -0
  5. package/package.json +154 -0
  6. package/src/config.ts +25 -0
  7. package/src/core/flowEngine.ts +1833 -0
  8. package/src/core/triggerManager.ts +1011 -0
  9. package/src/experiences/banner.ts +366 -0
  10. package/src/experiences/beacon.ts +668 -0
  11. package/src/experiences/hotspotTour.ts +654 -0
  12. package/src/experiences/hotspots.ts +566 -0
  13. package/src/experiences/modal.ts +1337 -0
  14. package/src/experiences/modalSequence.ts +1247 -0
  15. package/src/experiences/popover.ts +652 -0
  16. package/src/experiences/registry.ts +21 -0
  17. package/src/experiences/survey.ts +1639 -0
  18. package/src/experiences/taskList.ts +625 -0
  19. package/src/experiences/tooltip.ts +740 -0
  20. package/src/experiences/types.ts +395 -0
  21. package/src/experiences/walkthrough.ts +670 -0
  22. package/src/flow-sequence.ts +177 -0
  23. package/src/flows.ts +512 -0
  24. package/src/http.ts +61 -0
  25. package/src/index.ts +355 -0
  26. package/src/services/flowManager.ts +905 -0
  27. package/src/services/flowNormalizer.ts +74 -0
  28. package/src/services/locationContextService.ts +189 -0
  29. package/src/services/pageContextService.ts +221 -0
  30. package/src/services/userContextService.ts +286 -0
  31. package/src/state/appState.ts +0 -0
  32. package/src/state/hooks.ts +0 -0
  33. package/src/state/index.ts +0 -0
  34. package/src/state/migration.ts +0 -0
  35. package/src/state/store.ts +0 -0
  36. package/src/styles/banner.css.ts +0 -0
  37. package/src/styles/hotspot.css.ts +0 -0
  38. package/src/styles/hotspotTour.css.ts +0 -0
  39. package/src/styles/modal.css.ts +564 -0
  40. package/src/styles/survey.css.ts +1013 -0
  41. package/src/styles/taskList.css.ts +0 -0
  42. package/src/styles/tooltip.css.ts +149 -0
  43. package/src/styles/walkthrough.css.ts +0 -0
  44. package/src/tourUtils.ts +0 -0
  45. package/src/tracking.ts +223 -0
  46. package/src/utils/debounce.ts +66 -0
  47. package/src/utils/eventSequenceValidator.ts +124 -0
  48. package/src/utils/flowTrackingSystem.ts +524 -0
  49. package/src/utils/idGenerator.ts +155 -0
  50. package/src/utils/immediateValidationPrevention.ts +184 -0
  51. package/src/utils/normalize.ts +50 -0
  52. package/src/utils/privacyManager.ts +166 -0
  53. package/src/utils/ruleEvaluator.ts +199 -0
  54. package/src/utils/sanitize.ts +79 -0
  55. package/src/utils/selectors.ts +107 -0
  56. package/src/utils/stepExecutor.ts +345 -0
  57. package/src/utils/triggerNormalizer.ts +149 -0
  58. package/src/utils/validationInterceptor.ts +650 -0
  59. package/tsconfig.json +13 -0
  60. package/tsup.config.ts +13 -0
@@ -0,0 +1,366 @@
1
+ // src/experiences/banner.ts
2
+ // Banner notification experience renderer
3
+
4
+ import { sanitizeHtml } from "../utils/sanitize";
5
+ import { register } from "./registry";
6
+ import type { BannerPayload } from "./types";
7
+
8
+ type BannerFlow = { id: string; type: "banner"; payload: BannerPayload };
9
+
10
+ const bannerCssText = `
11
+ :root {
12
+ --dap-z-banner: 2147483620;
13
+ --dap-banner-bg-info: #eff6ff;
14
+ --dap-banner-bg-warning: #fefce8;
15
+ --dap-banner-bg-error: #fef2f2;
16
+ --dap-banner-bg-success: #f0f9ff;
17
+ --dap-banner-text-info: #1e40af;
18
+ --dap-banner-text-warning: #92400e;
19
+ --dap-banner-text-error: #dc2626;
20
+ --dap-banner-text-success: #059669;
21
+ --dap-banner-border-info: #3b82f6;
22
+ --dap-banner-border-warning: #f59e0b;
23
+ --dap-banner-border-error: #ef4444;
24
+ --dap-banner-border-success: #10b981;
25
+ }
26
+
27
+ .dap-banner-wrap {
28
+ position: fixed;
29
+ left: 0;
30
+ right: 0;
31
+ z-index: var(--dap-z-banner);
32
+ padding: 0 16px;
33
+ pointer-events: none;
34
+ }
35
+
36
+ .dap-banner-wrap.top {
37
+ top: 16px;
38
+ }
39
+
40
+ .dap-banner-wrap.bottom {
41
+ bottom: 16px;
42
+ }
43
+
44
+ .dap-banner {
45
+ max-width: 800px;
46
+ margin: 0 auto;
47
+ padding: 16px 20px;
48
+ border-radius: 8px;
49
+ border-left: 4px solid;
50
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 12px;
54
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
55
+ line-height: 1.5;
56
+ pointer-events: auto;
57
+ animation: bannerSlideIn 0.3s ease-out;
58
+ }
59
+
60
+ @keyframes bannerSlideIn {
61
+ from {
62
+ opacity: 0;
63
+ transform: translateY(-20px);
64
+ }
65
+ to {
66
+ opacity: 1;
67
+ transform: translateY(0);
68
+ }
69
+ }
70
+
71
+ .dap-banner.bottom {
72
+ animation: bannerSlideInBottom 0.3s ease-out;
73
+ }
74
+
75
+ @keyframes bannerSlideInBottom {
76
+ from {
77
+ opacity: 0;
78
+ transform: translateY(20px);
79
+ }
80
+ to {
81
+ opacity: 1;
82
+ transform: translateY(0);
83
+ }
84
+ }
85
+
86
+ .dap-banner.info {
87
+ background: var(--dap-banner-bg-info);
88
+ color: var(--dap-banner-text-info);
89
+ border-color: var(--dap-banner-border-info);
90
+ }
91
+
92
+ .dap-banner.warning {
93
+ background: var(--dap-banner-bg-warning);
94
+ color: var(--dap-banner-text-warning);
95
+ border-color: var(--dap-banner-border-warning);
96
+ }
97
+
98
+ .dap-banner.error {
99
+ background: var(--dap-banner-bg-error);
100
+ color: var(--dap-banner-text-error);
101
+ border-color: var(--dap-banner-border-error);
102
+ }
103
+
104
+ .dap-banner.success {
105
+ background: var(--dap-banner-bg-success);
106
+ color: var(--dap-banner-text-success);
107
+ border-color: var(--dap-banner-border-success);
108
+ }
109
+
110
+ .dap-banner-icon {
111
+ width: 20px;
112
+ height: 20px;
113
+ flex-shrink: 0;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ font-size: 16px;
118
+ font-weight: bold;
119
+ }
120
+
121
+ .dap-banner-message {
122
+ flex: 1;
123
+ font-size: 14px;
124
+ font-weight: 500;
125
+ }
126
+
127
+ .dap-banner-actions {
128
+ display: flex;
129
+ gap: 8px;
130
+ align-items: center;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .dap-banner-btn {
135
+ padding: 6px 12px;
136
+ border: 1px solid currentColor;
137
+ border-radius: 4px;
138
+ background: transparent;
139
+ color: inherit;
140
+ font-size: 12px;
141
+ font-weight: 500;
142
+ cursor: pointer;
143
+ text-decoration: none;
144
+ display: inline-flex;
145
+ align-items: center;
146
+ transition: all 0.15s ease;
147
+ }
148
+
149
+ .dap-banner-btn:hover {
150
+ background: currentColor;
151
+ color: white;
152
+ }
153
+
154
+ .dap-banner-close {
155
+ background: none;
156
+ border: none;
157
+ color: inherit;
158
+ cursor: pointer;
159
+ padding: 4px;
160
+ border-radius: 4px;
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ width: 24px;
165
+ height: 24px;
166
+ font-size: 14px;
167
+ opacity: 0.7;
168
+ transition: opacity 0.15s ease;
169
+ }
170
+
171
+ .dap-banner-close:hover {
172
+ opacity: 1;
173
+ background: rgba(0, 0, 0, 0.1);
174
+ }
175
+
176
+ @media (max-width: 640px) {
177
+ .dap-banner-wrap {
178
+ padding: 0 12px;
179
+ }
180
+
181
+ .dap-banner {
182
+ padding: 14px 16px;
183
+ }
184
+
185
+ .dap-banner-actions {
186
+ flex-direction: column;
187
+ gap: 6px;
188
+ }
189
+
190
+ .dap-banner-btn {
191
+ font-size: 11px;
192
+ padding: 4px 8px;
193
+ }
194
+ }
195
+ `;
196
+
197
+ export function registerBanner() {
198
+ register("banner", renderBanner);
199
+ }
200
+
201
+ export async function renderBanner(flow: BannerFlow): Promise<void> {
202
+ const { payload, id } = flow;
203
+
204
+ // Ensure we have a root container
205
+ const root = ensureRoot();
206
+
207
+ // Extract completion tracker if provided
208
+ const completionTracker = payload._completionTracker;
209
+
210
+ // Create banner shell
211
+ const shell = createBannerShell(payload);
212
+
213
+ // Add message content
214
+ shell.messageEl.innerHTML = sanitizeHtml(payload.message);
215
+
216
+ // Add actions if provided
217
+ if (payload.actions?.length) {
218
+ payload.actions.forEach(action => {
219
+ const btn = document.createElement(action.action === "navigate" ? "a" : "button");
220
+ btn.className = "dap-banner-btn";
221
+ btn.textContent = action.label;
222
+
223
+ if (action.action === "navigate" && action.href) {
224
+ (btn as HTMLAnchorElement).href = action.href;
225
+ (btn as HTMLAnchorElement).target = "_blank";
226
+ (btn as HTMLAnchorElement).rel = "noopener noreferrer";
227
+ } else {
228
+ btn.addEventListener("click", () => {
229
+ if (action.action === "dismiss") {
230
+ dismissBanner();
231
+ } else if (action.action === "custom" && action.customAction) {
232
+ // Trigger custom action event
233
+ window.dispatchEvent(new CustomEvent("dap-banner-action", {
234
+ detail: { action: action.customAction, bannerId: id }
235
+ }));
236
+ dismissBanner();
237
+ }
238
+ });
239
+ }
240
+
241
+ shell.actionsEl.appendChild(btn);
242
+ });
243
+ }
244
+
245
+ // Auto-hide functionality
246
+ let autoHideTimer: number | undefined;
247
+ if (payload.autoHide && payload.autoHide > 0) {
248
+ autoHideTimer = window.setTimeout(() => {
249
+ dismissBanner();
250
+ }, payload.autoHide * 1000);
251
+ }
252
+
253
+ function dismissBanner() {
254
+ if (autoHideTimer) {
255
+ clearTimeout(autoHideTimer);
256
+ }
257
+
258
+ shell.wrap.style.animation = payload.position === "bottom" ? "bannerSlideOutBottom 0.2s ease-in" : "bannerSlideOut 0.2s ease-in";
259
+
260
+ setTimeout(() => {
261
+ shell.wrap.remove();
262
+
263
+ // Signal completion if tracker provided
264
+ if (completionTracker?.onComplete) {
265
+ console.debug(`[DAP] Completing banner flow: ${id}`);
266
+ completionTracker.onComplete();
267
+ }
268
+ }, 200);
269
+ }
270
+
271
+ // Close button functionality (if dismissible)
272
+ if (payload.dismissible !== false) {
273
+ shell.closeBtn.addEventListener("click", dismissBanner);
274
+ } else {
275
+ shell.closeBtn.style.display = "none";
276
+ }
277
+
278
+ // Add dismiss animation styles
279
+ if (!document.getElementById("dap-banner-dismiss-styles")) {
280
+ const style = document.createElement("style");
281
+ style.id = "dap-banner-dismiss-styles";
282
+ style.textContent = `
283
+ @keyframes bannerSlideOut {
284
+ from { opacity: 1; transform: translateY(0); }
285
+ to { opacity: 0; transform: translateY(-20px); }
286
+ }
287
+ @keyframes bannerSlideOutBottom {
288
+ from { opacity: 1; transform: translateY(0); }
289
+ to { opacity: 0; transform: translateY(20px); }
290
+ }
291
+ `;
292
+ document.head.appendChild(style);
293
+ }
294
+
295
+ // Append to root and focus for accessibility
296
+ root.appendChild(shell.wrap);
297
+ shell.banner.setAttribute("role", "alert");
298
+ shell.banner.setAttribute("aria-live", "polite");
299
+ }
300
+
301
+ function createBannerShell(payload: BannerPayload) {
302
+ const wrap = document.createElement("div");
303
+ wrap.className = `dap-banner-wrap ${payload.position || "top"}`;
304
+
305
+ const banner = document.createElement("div");
306
+ banner.className = `dap-banner ${payload.variant || "info"}`;
307
+
308
+ // Apply theme if provided
309
+ if (payload.theme) {
310
+ for (const [key, value] of Object.entries(payload.theme)) {
311
+ banner.style.setProperty(key, value);
312
+ }
313
+ }
314
+
315
+ const iconEl = document.createElement("div");
316
+ iconEl.className = "dap-banner-icon";
317
+
318
+ // Set icon based on variant
319
+ const icons = {
320
+ info: "ℹ",
321
+ warning: "⚠",
322
+ error: "✕",
323
+ success: "✓"
324
+ };
325
+ iconEl.textContent = icons[payload.variant || "info"];
326
+
327
+ const messageEl = document.createElement("div");
328
+ messageEl.className = "dap-banner-message";
329
+
330
+ const actionsEl = document.createElement("div");
331
+ actionsEl.className = "dap-banner-actions";
332
+
333
+ const closeBtn = document.createElement("button");
334
+ closeBtn.className = "dap-banner-close";
335
+ closeBtn.innerHTML = "×";
336
+ closeBtn.setAttribute("aria-label", "Close banner");
337
+
338
+ banner.appendChild(iconEl);
339
+ banner.appendChild(messageEl);
340
+ banner.appendChild(actionsEl);
341
+ banner.appendChild(closeBtn);
342
+ wrap.appendChild(banner);
343
+
344
+ return { wrap, banner, iconEl, messageEl, actionsEl, closeBtn };
345
+ }
346
+
347
+ function ensureRoot(): HTMLElement {
348
+ let host = document.querySelector("dap-banner-root") as HTMLElement | null;
349
+ if (!host) {
350
+ host = document.createElement("dap-banner-root");
351
+ host.style.position = "fixed";
352
+ host.style.zIndex = "2147483620";
353
+ host.style.inset = "0";
354
+ host.style.pointerEvents = "none";
355
+ document.documentElement.appendChild(host);
356
+
357
+ // Inject styles
358
+ if (!host.shadowRoot && !document.getElementById("dap-banner-style")) {
359
+ const style = document.createElement("style");
360
+ style.id = "dap-banner-style";
361
+ style.textContent = bannerCssText;
362
+ document.head.appendChild(style);
363
+ }
364
+ }
365
+ return host;
366
+ }