@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,650 @@
1
+ /**
2
+ * Validation Interceptor
3
+ * Disables browser-native validation tooltips and replaces them with DAP tooltips
4
+ */
5
+
6
+ export class ValidationInterceptor {
7
+ private static instance: ValidationInterceptor | null = null;
8
+ private observerRef: MutationObserver | null = null;
9
+ private isInitialized: boolean = false;
10
+
11
+ static getInstance(): ValidationInterceptor {
12
+ if (!ValidationInterceptor.instance) {
13
+ ValidationInterceptor.instance = new ValidationInterceptor();
14
+ }
15
+ return ValidationInterceptor.instance;
16
+ }
17
+
18
+ /**
19
+ * Inject CSS styles for validation tooltips and error states
20
+ */
21
+ private injectValidationStyles(): void {
22
+ const existingStyles = document.querySelector('#dap-validation-styles');
23
+ if (existingStyles) return;
24
+
25
+ const style = document.createElement('style');
26
+ style.id = 'dap-validation-styles';
27
+ style.textContent = `
28
+ /* Validation error state for inputs */
29
+ .dap-validation-error {
30
+ border-color: #ef4444 !important;
31
+ box-shadow: 0 0 0 1px #ef4444 !important;
32
+ }
33
+
34
+ /* Fallback validation tooltip */
35
+ .dap-validation-tooltip-fallback {
36
+ position: absolute !important;
37
+ background: #ef4444 !important;
38
+ color: white !important;
39
+ padding: 8px 12px !important;
40
+ border-radius: 4px !important;
41
+ font-size: 14px !important;
42
+ font-family: system-ui, -apple-system, sans-serif !important;
43
+ z-index: 10000 !important;
44
+ max-width: 200px !important;
45
+ word-wrap: break-word !important;
46
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2) !important;
47
+ pointer-events: none !important;
48
+ transform: translateY(-2px) !important;
49
+ }
50
+
51
+ .dap-validation-tooltip-fallback::before {
52
+ content: '';
53
+ position: absolute;
54
+ top: 100%;
55
+ left: 12px;
56
+ width: 0;
57
+ height: 0;
58
+ border-left: 6px solid transparent;
59
+ border-right: 6px solid transparent;
60
+ border-top: 6px solid #ef4444;
61
+ }
62
+
63
+ /* Hide native validation bubbles */
64
+ input:invalid {
65
+ box-shadow: none !important;
66
+ }
67
+
68
+ /* Ensure tooltips appear above everything */
69
+ .dap-tip-layer {
70
+ z-index: 10001 !important;
71
+ }
72
+ `;
73
+
74
+ document.head.appendChild(style);
75
+ }
76
+
77
+ /**
78
+ * Initialize validation interception for the entire page
79
+ */
80
+ public initialize(): void {
81
+ if (this.isInitialized) {
82
+ console.debug("[DAP] Validation interceptor already initialized");
83
+ return;
84
+ }
85
+
86
+ console.debug("[DAP] Initializing validation interceptor for DAP tooltips");
87
+
88
+ // Add validation tooltip styles immediately
89
+ this.injectValidationStyles();
90
+
91
+ // Focus on DAP tooltip replacement rather than prevention (handled by immediate prevention)
92
+ this.setupDAPTooltipTriggers();
93
+
94
+ this.isInitialized = true;
95
+ console.debug("[DAP] Validation interceptor initialized successfully");
96
+ }
97
+
98
+ /**
99
+ * Set up DAP tooltip triggers for validation errors
100
+ */
101
+ private setupDAPTooltipTriggers(): void {
102
+ // Listen for validation events to trigger DAP tooltips
103
+ document.addEventListener('submit', async (event) => {
104
+ const form = event.target as HTMLFormElement;
105
+ if (form.tagName === 'FORM') {
106
+ // Check for validation manually and show DAP tooltips
107
+ const firstInvalidInput = this.validateForm(form);
108
+ if (firstInvalidInput) {
109
+ event.preventDefault();
110
+ firstInvalidInput.focus();
111
+ await this.triggerDAPValidation(firstInvalidInput);
112
+ }
113
+ }
114
+ }, { passive: false });
115
+
116
+ // Also listen for input blur events for real-time validation
117
+ document.addEventListener('blur', async (event) => {
118
+ const input = event.target as HTMLInputElement;
119
+ if (input && (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') && input.hasAttribute('required')) {
120
+ if (!this.isInputValid(input)) {
121
+ await this.triggerDAPValidation(input);
122
+ }
123
+ }
124
+ }, { capture: true });
125
+ }
126
+
127
+ /**
128
+ * Disable native validation on all forms in the document
129
+ */
130
+ private disableNativeValidationOnForms(): void {
131
+ const forms = document.querySelectorAll('form');
132
+ forms.forEach(form => {
133
+ this.disableFormValidation(form);
134
+ });
135
+
136
+ if (forms.length > 0) {
137
+ console.debug("[DAP] Browser validation suppressed on", forms.length, "forms");
138
+ }
139
+
140
+ // Also run after DOM is fully loaded in case forms are added dynamically
141
+ if (document.readyState === 'loading') {
142
+ document.addEventListener('DOMContentLoaded', () => {
143
+ setTimeout(() => {
144
+ const newForms = document.querySelectorAll('form:not([novalidate])');
145
+ newForms.forEach(form => {
146
+ this.disableFormValidation(form as HTMLFormElement);
147
+ });
148
+ if (newForms.length > 0) {
149
+ console.debug("[DAP] Additional forms processed after DOM load:", newForms.length);
150
+ }
151
+ }, 100);
152
+ });
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Disable native validation on a specific form
158
+ */
159
+ private disableFormValidation(form: HTMLFormElement): void {
160
+ // Add novalidate attribute to prevent browser validation UI
161
+ form.setAttribute('novalidate', '');
162
+
163
+ // Also set the noValidate property for extra security
164
+ form.noValidate = true;
165
+
166
+ // Add event listeners to handle validation manually
167
+ form.addEventListener('submit', this.handleFormSubmit.bind(this), { capture: true, passive: false });
168
+
169
+ // Add listeners to form inputs for real-time validation
170
+ const inputs = form.querySelectorAll('input, select, textarea');
171
+ inputs.forEach(input => {
172
+ this.setupInputValidation(input as HTMLElement);
173
+ });
174
+
175
+ console.debug("[DAP] Form validation disabled for:", form);
176
+ }
177
+
178
+ /**
179
+ * Set up validation listeners on individual inputs
180
+ */
181
+ private setupInputValidation(input: HTMLElement): void {
182
+ // Prevent browser validation tooltips aggressively
183
+ input.addEventListener('invalid', this.preventBrowserTooltip.bind(this), { capture: true, passive: false });
184
+
185
+ // Clear any existing validation state
186
+ if ('setCustomValidity' in input) {
187
+ (input as HTMLInputElement).setCustomValidity('');
188
+ }
189
+
190
+ // Add validation on blur/change for better UX
191
+ input.addEventListener('blur', this.validateInput.bind(this));
192
+ input.addEventListener('input', this.clearValidationErrors.bind(this));
193
+
194
+ // Also prevent validation on focus
195
+ input.addEventListener('focus', (event) => {
196
+ const inputElement = event.target as HTMLInputElement;
197
+ if ('setCustomValidity' in inputElement) {
198
+ inputElement.setCustomValidity('');
199
+ }
200
+ });
201
+
202
+ console.debug("[DAP] Input validation setup for:", input);
203
+ }
204
+
205
+ /**
206
+ * Prevent browser validation tooltips from appearing
207
+ */
208
+ private preventBrowserTooltip(event: Event): void {
209
+ console.debug("[DAP] Browser validation suppressed for element:", event.target);
210
+ event.preventDefault();
211
+ event.stopPropagation();
212
+ event.stopImmediatePropagation();
213
+
214
+ // Clear any validation state on the element
215
+ const target = event.target as HTMLInputElement;
216
+ if (target && 'setCustomValidity' in target) {
217
+ target.setCustomValidity('');
218
+ }
219
+
220
+ // Trigger DAP validation instead
221
+ this.triggerDAPValidation(event.target as HTMLElement);
222
+ }
223
+
224
+ /**
225
+ * Handle form submission with custom validation
226
+ */
227
+ private handleFormSubmit(event: Event): void {
228
+ const form = event.target as HTMLFormElement;
229
+ const firstInvalidInput = this.validateForm(form);
230
+
231
+ if (firstInvalidInput) {
232
+ event.preventDefault();
233
+ event.stopPropagation();
234
+
235
+ // Focus the first invalid input and show DAP tooltip
236
+ firstInvalidInput.focus();
237
+ this.triggerDAPValidation(firstInvalidInput);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Validate a form and return first invalid input
243
+ */
244
+ private validateForm(form: HTMLFormElement): HTMLElement | null {
245
+ const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
246
+
247
+ for (let i = 0; i < inputs.length; i++) {
248
+ const element = inputs[i] as HTMLInputElement;
249
+ if (!this.isInputValid(element)) {
250
+ return element;
251
+ }
252
+ }
253
+
254
+ return null;
255
+ }
256
+
257
+ /**
258
+ * Validate individual input
259
+ */
260
+ private validateInput(event: Event): void {
261
+ const input = event.target as HTMLInputElement;
262
+
263
+ if (!this.isInputValid(input)) {
264
+ this.triggerDAPValidation(input);
265
+ } else {
266
+ this.clearValidationErrors(event);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Check if an input is valid
272
+ */
273
+ private isInputValid(input: HTMLInputElement): boolean {
274
+ // Check required fields
275
+ if (input.hasAttribute('required') && !input.value.trim()) {
276
+ return false;
277
+ }
278
+
279
+ // Check email format
280
+ if (input.type === 'email' && input.value) {
281
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
282
+ return emailRegex.test(input.value);
283
+ }
284
+
285
+ // Check URL format
286
+ if (input.type === 'url' && input.value) {
287
+ try {
288
+ new URL(input.value);
289
+ return true;
290
+ } catch {
291
+ return false;
292
+ }
293
+ }
294
+
295
+ // Add more validation rules as needed
296
+ return true;
297
+ }
298
+
299
+ /**
300
+ * Trigger DAP validation tooltip
301
+ */
302
+ private async triggerDAPValidation(input: HTMLElement): Promise<void> {
303
+ // Prevent duplicate tooltips
304
+ this.clearAllTooltips();
305
+
306
+ console.debug("[DAP] DAP validation tooltip triggered for:", input);
307
+
308
+ const validationMessage = this.getValidationMessage(input);
309
+
310
+ // Create a validation tooltip experience
311
+ const validationExperience = {
312
+ elementSelector: input.id ? `#${input.id}` : this.generateSelector(input),
313
+ elementTrigger: 'validation_error',
314
+ elementLocation: window.location.pathname,
315
+ content: {
316
+ text: validationMessage,
317
+ placement: 'top'
318
+ }
319
+ };
320
+
321
+ // Add visual error state to input
322
+ input.classList.add('dap-validation-error');
323
+
324
+ // Trigger DAP tooltip programmatically
325
+ await this.showDAPTooltip(input, validationExperience);
326
+ }
327
+
328
+ /**
329
+ * Get appropriate validation message for an input
330
+ */
331
+ private getValidationMessage(input: HTMLElement): string {
332
+ const inputElement = input as HTMLInputElement;
333
+ const fieldName = this.getFieldName(input);
334
+
335
+ if (inputElement.hasAttribute('required') && !inputElement.value.trim()) {
336
+ return `Please enter ${fieldName}`;
337
+ }
338
+
339
+ if (inputElement.type === 'email' && inputElement.value) {
340
+ return 'Please enter a valid email address';
341
+ }
342
+
343
+ if (inputElement.type === 'url' && inputElement.value) {
344
+ return 'Please enter a valid URL';
345
+ }
346
+
347
+ return `Please check the ${fieldName} field`;
348
+ }
349
+
350
+ /**
351
+ * Get user-friendly field name
352
+ */
353
+ private getFieldName(input: HTMLElement): string {
354
+ // Try label text
355
+ const label = document.querySelector(`label[for="${input.id}"]`);
356
+ if (label) {
357
+ return label.textContent?.trim().replace(':', '') || 'field';
358
+ }
359
+
360
+ // Try placeholder
361
+ const placeholder = (input as HTMLInputElement).placeholder;
362
+ if (placeholder) {
363
+ return placeholder;
364
+ }
365
+
366
+ // Try name attribute
367
+ const name = (input as HTMLInputElement).name;
368
+ if (name) {
369
+ return name.replace(/[_-]/g, ' ').toLowerCase();
370
+ }
371
+
372
+ return 'field';
373
+ }
374
+
375
+ /**
376
+ * Generate CSS selector for element
377
+ */
378
+ private generateSelector(element: HTMLElement): string {
379
+ if (element.id) {
380
+ return `#${element.id}`;
381
+ }
382
+
383
+ if (element.className) {
384
+ return `.${element.className.split(' ')[0]}`;
385
+ }
386
+
387
+ return element.tagName.toLowerCase();
388
+ }
389
+
390
+ /**
391
+ * Show DAP tooltip for validation
392
+ */
393
+ private async showDAPTooltip(element: HTMLElement, experience: any): Promise<void> {
394
+ try {
395
+ // Import tooltip renderer
396
+ const { renderDirectTooltip } = await import('../experiences/tooltip');
397
+
398
+ const tooltipPayload = {
399
+ targetSelector: experience.elementSelector,
400
+ text: experience.content.text,
401
+ placement: experience.content.placement || 'top',
402
+ trigger: 'click' as const // Use click trigger for validation tooltips
403
+ };
404
+
405
+ console.debug('[DAP] Showing DAP validation tooltip:', tooltipPayload);
406
+ await renderDirectTooltip(tooltipPayload);
407
+
408
+ } catch (error) {
409
+ console.error('[DAP] Failed to load tooltip renderer:', error);
410
+ console.debug('[DAP] Using enhanced fallback tooltip');
411
+ // Enhanced fallback with DAP-like styling
412
+ this.showDAPStyledFallbackTooltip(element, experience.content.text);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Enhanced fallback tooltip with DAP styling
418
+ */
419
+ private showDAPStyledFallbackTooltip(element: HTMLElement, message: string): void {
420
+ // Remove any existing tooltips
421
+ this.clearAllTooltips();
422
+
423
+ // Create tooltip wrapper similar to DAP tooltip structure
424
+ const tooltipWrapper = document.createElement('div');
425
+ tooltipWrapper.className = 'dap-validation-tooltip-wrap';
426
+ tooltipWrapper.style.cssText = `
427
+ position: absolute;
428
+ z-index: 10001;
429
+ pointer-events: none;
430
+ `;
431
+
432
+ // Create tooltip bubble with DAP styling
433
+ const tooltip = document.createElement('div');
434
+ tooltip.className = 'dap-validation-tooltip-bubble';
435
+ tooltip.style.cssText = `
436
+ background: #2563eb;
437
+ color: white;
438
+ padding: 12px 16px;
439
+ border-radius: 8px;
440
+ font-size: 14px;
441
+ font-family: system-ui, -apple-system, sans-serif;
442
+ max-width: 280px;
443
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
444
+ position: relative;
445
+ word-wrap: break-word;
446
+ line-height: 1.4;
447
+ `;
448
+ tooltip.textContent = message;
449
+
450
+ // Create arrow pointing to the element
451
+ const arrow = document.createElement('div');
452
+ arrow.className = 'dap-validation-tooltip-arrow';
453
+ arrow.style.cssText = `
454
+ position: absolute;
455
+ top: 100%;
456
+ left: 20px;
457
+ width: 0;
458
+ height: 0;
459
+ border-left: 8px solid transparent;
460
+ border-right: 8px solid transparent;
461
+ border-top: 8px solid #2563eb;
462
+ `;
463
+
464
+ tooltip.appendChild(arrow);
465
+ tooltipWrapper.appendChild(tooltip);
466
+
467
+ // Position relative to element
468
+ const rect = element.getBoundingClientRect();
469
+ const tooltipTop = Math.max(10, rect.top - 50 + window.scrollY);
470
+ const tooltipLeft = Math.max(10, Math.min(rect.left + window.scrollX, window.innerWidth - 300));
471
+
472
+ tooltipWrapper.style.top = `${tooltipTop}px`;
473
+ tooltipWrapper.style.left = `${tooltipLeft}px`;
474
+
475
+ console.debug('[DAP] DAP-styled fallback tooltip positioned at:', tooltipTop, tooltipLeft);
476
+
477
+ document.body.appendChild(tooltipWrapper);
478
+
479
+ // Auto-remove after 4 seconds
480
+ setTimeout(() => {
481
+ if (tooltipWrapper.parentNode) {
482
+ tooltipWrapper.parentNode.removeChild(tooltipWrapper);
483
+ }
484
+ }, 4000);
485
+ }
486
+
487
+ /**
488
+ * Clear all validation tooltips (both DAP and fallback)
489
+ */
490
+ private clearAllTooltips(): void {
491
+ // Clear DAP tooltips
492
+ const dapTooltips = document.querySelectorAll('.dap-tip-layer, .dap-tooltip-wrap');
493
+ dapTooltips.forEach(tooltip => tooltip.remove());
494
+
495
+ // Clear fallback tooltips
496
+ const fallbackTooltips = document.querySelectorAll('.dap-validation-tooltip-fallback, .dap-validation-tooltip-wrap');
497
+ fallbackTooltips.forEach(tooltip => tooltip.remove());
498
+
499
+ console.debug('[DAP] All validation tooltips cleared');
500
+ }
501
+
502
+ /**
503
+ * Fallback tooltip if DAP tooltip fails (DEPRECATED - use DAP styled version)
504
+ */
505
+ private showFallbackTooltip(element: HTMLElement, message: string): void {
506
+ // Remove any existing fallback tooltips
507
+ this.clearFallbackTooltips();
508
+
509
+ const tooltip = document.createElement('div');
510
+ tooltip.className = 'dap-validation-tooltip-fallback';
511
+ tooltip.textContent = message;
512
+ tooltip.style.cssText = `
513
+ position: absolute;
514
+ background: #ef4444;
515
+ color: white;
516
+ padding: 8px 12px;
517
+ border-radius: 4px;
518
+ font-size: 14px;
519
+ z-index: 10000;
520
+ max-width: 200px;
521
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
522
+ pointer-events: none;
523
+ `;
524
+
525
+ // Position relative to element with better bounds checking
526
+ const rect = element.getBoundingClientRect();
527
+ const tooltipTop = Math.max(10, rect.top - 35 + window.scrollY); // Ensure minimum 10px from top
528
+ const tooltipLeft = Math.max(10, Math.min(rect.left + window.scrollX, window.innerWidth - 220)); // Stay in viewport
529
+
530
+ tooltip.style.top = `${tooltipTop}px`;
531
+ tooltip.style.left = `${tooltipLeft}px`;
532
+
533
+ console.debug('[DAP] Fallback tooltip positioned at:', tooltipTop, tooltipLeft);
534
+
535
+ document.body.appendChild(tooltip);
536
+
537
+ // Auto-remove after 3 seconds (shorter duration)
538
+ setTimeout(() => {
539
+ if (tooltip.parentNode) {
540
+ tooltip.parentNode.removeChild(tooltip);
541
+ }
542
+ }, 3000);
543
+ }
544
+
545
+ /**
546
+ * Clear validation errors when user starts typing
547
+ */
548
+ private clearValidationErrors(event: Event): void {
549
+ const input = event.target as HTMLElement;
550
+
551
+ // Remove custom validation classes
552
+ input.classList.remove('dap-validation-error');
553
+
554
+ // Clear all validation tooltips
555
+ this.clearAllTooltips();
556
+ }
557
+
558
+ /**
559
+ * Clear fallback tooltips
560
+ */
561
+ private clearFallbackTooltips(): void {
562
+ const tooltips = document.querySelectorAll('.dap-validation-tooltip-fallback');
563
+ tooltips.forEach(tooltip => tooltip.remove());
564
+ }
565
+
566
+ /**
567
+ * Dismiss DAP tooltips
568
+ */
569
+ private dismissDAPTooltips(): void {
570
+ console.debug("[DAP] DAP tooltip dismissed");
571
+
572
+ // Try to dismiss any active DAP tooltips
573
+ const activeTooltips = document.querySelectorAll('.dap-tooltip-wrap');
574
+ activeTooltips.forEach(tooltip => {
575
+ const closeButton = tooltip.querySelector('.dap-tooltip-close') as HTMLElement;
576
+ if (closeButton) {
577
+ closeButton.click();
578
+ } else {
579
+ // Force remove if no close button
580
+ tooltip.remove();
581
+ }
582
+ });
583
+ }
584
+
585
+ /**
586
+ * Set up observer for dynamically added forms
587
+ */
588
+ private setupFormObserver(): void {
589
+ this.observerRef = new MutationObserver((mutations) => {
590
+ mutations.forEach((mutation) => {
591
+ mutation.addedNodes.forEach((node) => {
592
+ if (node.nodeType === Node.ELEMENT_NODE) {
593
+ const element = node as HTMLElement;
594
+
595
+ // Check if the added node is a form
596
+ if (element.tagName === 'FORM') {
597
+ this.disableFormValidation(element as HTMLFormElement);
598
+ }
599
+
600
+ // Check for forms within the added node
601
+ const forms = element.querySelectorAll?.('form');
602
+ forms?.forEach(form => {
603
+ this.disableFormValidation(form);
604
+ });
605
+ }
606
+ });
607
+ });
608
+ });
609
+
610
+ this.observerRef.observe(document.body, {
611
+ childList: true,
612
+ subtree: true
613
+ });
614
+ }
615
+
616
+ /**
617
+ * Set up additional validation listeners for enhanced coverage
618
+ */
619
+ private setupValidationListeners(): void {
620
+ // Additional validation prevention as backup
621
+ window.addEventListener('invalid', this.preventBrowserTooltip.bind(this), { capture: true, passive: false });
622
+
623
+ // Prevent any validation tooltips on focus/blur
624
+ document.addEventListener('focusin', (event) => {
625
+ const input = event.target as HTMLInputElement;
626
+ if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA' || input.tagName === 'SELECT') {
627
+ // Remove any browser validation state
628
+ input.setCustomValidity('');
629
+ }
630
+ }, { capture: true });
631
+ }
632
+
633
+ /**
634
+ * Cleanup and destroy the interceptor
635
+ */
636
+ public destroy(): void {
637
+ if (this.observerRef) {
638
+ this.observerRef.disconnect();
639
+ this.observerRef = null;
640
+ }
641
+
642
+ // Remove event listeners
643
+ document.removeEventListener('invalid', this.preventBrowserTooltip.bind(this), true);
644
+
645
+ // Clear any active tooltips
646
+ this.clearFallbackTooltips();
647
+
648
+ ValidationInterceptor.instance = null;
649
+ }
650
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "noEmit": true,
10
+ "resolveJsonModule": true
11
+ },
12
+ "include": ["src/**/*"]
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ clean: true,
6
+ format: ["esm", "iife"], // ESM + browser global
7
+ globalName: "DAP", // window.DAP for IIFE
8
+ sourcemap: true,
9
+ minify: true,
10
+ outDir: "public/dist", // serve directly in /public
11
+ target: "es2020",
12
+ dts: { entry: "src/index.ts" } // types for consumers of ESM entry
13
+ });