@absmartly/sdk-plugins 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +1101 -0
  2. package/dist/absmartly-cookie.min.js +1 -0
  3. package/dist/absmartly-dom-changes-core.min.js +1 -0
  4. package/dist/absmartly-dom-changes-lite.min.js +1 -0
  5. package/dist/absmartly-overrides-full.min.js +1 -0
  6. package/dist/absmartly-overrides-lite.min.js +1 -0
  7. package/dist/absmartly-sdk-plugins.min.js +1 -0
  8. package/dist/absmartly-vitals.min.js +1 -0
  9. package/es/cookies/CookiePlugin.js +410 -0
  10. package/es/cookies/CookiePlugin.js.map +1 -0
  11. package/es/cookies/index.js +2 -0
  12. package/es/cookies/index.js.map +1 -0
  13. package/es/core/DOMChangesPluginLite.js +1124 -0
  14. package/es/core/DOMChangesPluginLite.js.map +1 -0
  15. package/es/core/DOMManipulatorLite.js +410 -0
  16. package/es/core/DOMManipulatorLite.js.map +1 -0
  17. package/es/core/ExposureTracker.js +501 -0
  18. package/es/core/ExposureTracker.js.map +1 -0
  19. package/es/core/HTMLInjector.js +333 -0
  20. package/es/core/HTMLInjector.js.map +1 -0
  21. package/es/core/PendingChangeManager.js +440 -0
  22. package/es/core/PendingChangeManager.js.map +1 -0
  23. package/es/core/StyleSheetManager.js +78 -0
  24. package/es/core/StyleSheetManager.js.map +1 -0
  25. package/es/entries/cookie-only.js +10 -0
  26. package/es/entries/cookie-only.js.map +1 -0
  27. package/es/entries/dom-only.js +13 -0
  28. package/es/entries/dom-only.js.map +1 -0
  29. package/es/entries/dom-with-overrides-full.js +19 -0
  30. package/es/entries/dom-with-overrides-full.js.map +1 -0
  31. package/es/entries/dom-with-overrides-lite.js +15 -0
  32. package/es/entries/dom-with-overrides-lite.js.map +1 -0
  33. package/es/entries/overrides-full-only.js +15 -0
  34. package/es/entries/overrides-full-only.js.map +1 -0
  35. package/es/entries/overrides-lite-only.js +12 -0
  36. package/es/entries/overrides-lite-only.js.map +1 -0
  37. package/es/entries/vitals-only.js +10 -0
  38. package/es/entries/vitals-only.js.map +1 -0
  39. package/es/index.js +17 -0
  40. package/es/index.js.map +1 -0
  41. package/es/overrides/BrowserCookieAdapter.js +76 -0
  42. package/es/overrides/BrowserCookieAdapter.js.map +1 -0
  43. package/es/overrides/OverridesPlugin.js +903 -0
  44. package/es/overrides/OverridesPlugin.js.map +1 -0
  45. package/es/overrides/OverridesPluginFull.js +32 -0
  46. package/es/overrides/OverridesPluginFull.js.map +1 -0
  47. package/es/overrides/OverridesPluginLite.js +352 -0
  48. package/es/overrides/OverridesPluginLite.js.map +1 -0
  49. package/es/overrides/index.js +4 -0
  50. package/es/overrides/index.js.map +1 -0
  51. package/es/overrides/types.js +2 -0
  52. package/es/overrides/types.js.map +1 -0
  53. package/es/parsers/VariantExtractor.js +570 -0
  54. package/es/parsers/VariantExtractor.js.map +1 -0
  55. package/es/types/index.js +2 -0
  56. package/es/types/index.js.map +1 -0
  57. package/es/utils/URLMatcher.js +114 -0
  58. package/es/utils/URLMatcher.js.map +1 -0
  59. package/es/utils/debug.js +184 -0
  60. package/es/utils/debug.js.map +1 -0
  61. package/es/vitals/WebVitalsPlugin.js +379 -0
  62. package/es/vitals/WebVitalsPlugin.js.map +1 -0
  63. package/es/vitals/index.js +2 -0
  64. package/es/vitals/index.js.map +1 -0
  65. package/lib/cookies/CookiePlugin.d.ts +55 -0
  66. package/lib/cookies/CookiePlugin.js +413 -0
  67. package/lib/cookies/CookiePlugin.js.map +1 -0
  68. package/lib/cookies/index.d.ts +2 -0
  69. package/lib/cookies/index.js +6 -0
  70. package/lib/cookies/index.js.map +1 -0
  71. package/lib/core/DOMChangesPluginLite.d.ts +89 -0
  72. package/lib/core/DOMChangesPluginLite.js +1127 -0
  73. package/lib/core/DOMChangesPluginLite.js.map +1 -0
  74. package/lib/core/DOMManipulatorLite.d.ts +21 -0
  75. package/lib/core/DOMManipulatorLite.js +413 -0
  76. package/lib/core/DOMManipulatorLite.js.map +1 -0
  77. package/lib/core/ExposureTracker.d.ts +81 -0
  78. package/lib/core/ExposureTracker.js +504 -0
  79. package/lib/core/ExposureTracker.js.map +1 -0
  80. package/lib/core/HTMLInjector.d.ts +19 -0
  81. package/lib/core/HTMLInjector.js +336 -0
  82. package/lib/core/HTMLInjector.js.map +1 -0
  83. package/lib/core/PendingChangeManager.d.ts +31 -0
  84. package/lib/core/PendingChangeManager.js +443 -0
  85. package/lib/core/PendingChangeManager.js.map +1 -0
  86. package/lib/core/StyleSheetManager.d.ts +16 -0
  87. package/lib/core/StyleSheetManager.js +81 -0
  88. package/lib/core/StyleSheetManager.js.map +1 -0
  89. package/lib/entries/cookie-only.d.ts +8 -0
  90. package/lib/entries/cookie-only.js +12 -0
  91. package/lib/entries/cookie-only.js.map +1 -0
  92. package/lib/entries/dom-only.d.ts +9 -0
  93. package/lib/entries/dom-only.js +29 -0
  94. package/lib/entries/dom-only.js.map +1 -0
  95. package/lib/entries/dom-with-overrides-full.d.ts +15 -0
  96. package/lib/entries/dom-with-overrides-full.js +39 -0
  97. package/lib/entries/dom-with-overrides-full.js.map +1 -0
  98. package/lib/entries/dom-with-overrides-lite.d.ts +11 -0
  99. package/lib/entries/dom-with-overrides-lite.js +32 -0
  100. package/lib/entries/dom-with-overrides-lite.js.map +1 -0
  101. package/lib/entries/overrides-full-only.d.ts +11 -0
  102. package/lib/entries/overrides-full-only.js +33 -0
  103. package/lib/entries/overrides-full-only.js.map +1 -0
  104. package/lib/entries/overrides-lite-only.d.ts +8 -0
  105. package/lib/entries/overrides-lite-only.js +28 -0
  106. package/lib/entries/overrides-lite-only.js.map +1 -0
  107. package/lib/entries/vitals-only.d.ts +8 -0
  108. package/lib/entries/vitals-only.js +12 -0
  109. package/lib/entries/vitals-only.js.map +1 -0
  110. package/lib/index.d.ts +13 -0
  111. package/lib/index.js +34 -0
  112. package/lib/index.js.map +1 -0
  113. package/lib/overrides/BrowserCookieAdapter.d.ts +6 -0
  114. package/lib/overrides/BrowserCookieAdapter.js +79 -0
  115. package/lib/overrides/BrowserCookieAdapter.js.map +1 -0
  116. package/lib/overrides/OverridesPlugin.d.ts +33 -0
  117. package/lib/overrides/OverridesPlugin.js +906 -0
  118. package/lib/overrides/OverridesPlugin.js.map +1 -0
  119. package/lib/overrides/OverridesPluginFull.d.ts +9 -0
  120. package/lib/overrides/OverridesPluginFull.js +35 -0
  121. package/lib/overrides/OverridesPluginFull.js.map +1 -0
  122. package/lib/overrides/OverridesPluginLite.d.ts +27 -0
  123. package/lib/overrides/OverridesPluginLite.js +355 -0
  124. package/lib/overrides/OverridesPluginLite.js.map +1 -0
  125. package/lib/overrides/index.d.ts +3 -0
  126. package/lib/overrides/index.js +23 -0
  127. package/lib/overrides/index.js.map +1 -0
  128. package/lib/overrides/types.d.ts +43 -0
  129. package/lib/overrides/types.js +3 -0
  130. package/lib/overrides/types.js.map +1 -0
  131. package/lib/parsers/VariantExtractor.d.ts +49 -0
  132. package/lib/parsers/VariantExtractor.js +573 -0
  133. package/lib/parsers/VariantExtractor.js.map +1 -0
  134. package/lib/types/index.d.ts +177 -0
  135. package/lib/types/index.js +3 -0
  136. package/lib/types/index.js.map +1 -0
  137. package/lib/utils/URLMatcher.d.ts +20 -0
  138. package/lib/utils/URLMatcher.js +117 -0
  139. package/lib/utils/URLMatcher.js.map +1 -0
  140. package/lib/utils/debug.d.ts +41 -0
  141. package/lib/utils/debug.js +195 -0
  142. package/lib/utils/debug.js.map +1 -0
  143. package/lib/vitals/WebVitalsPlugin.d.ts +37 -0
  144. package/lib/vitals/WebVitalsPlugin.js +382 -0
  145. package/lib/vitals/WebVitalsPlugin.js.map +1 -0
  146. package/lib/vitals/index.d.ts +2 -0
  147. package/lib/vitals/index.js +6 -0
  148. package/lib/vitals/index.js.map +1 -0
  149. package/package.json +94 -0
  150. package/types/cookies/CookiePlugin.d.ts +55 -0
  151. package/types/cookies/index.d.ts +2 -0
  152. package/types/core/DOMChangesPluginLite.d.ts +89 -0
  153. package/types/core/DOMManipulatorLite.d.ts +21 -0
  154. package/types/core/ExposureTracker.d.ts +81 -0
  155. package/types/core/HTMLInjector.d.ts +19 -0
  156. package/types/core/PendingChangeManager.d.ts +31 -0
  157. package/types/core/StyleSheetManager.d.ts +16 -0
  158. package/types/entries/cookie-only.d.ts +8 -0
  159. package/types/entries/dom-only.d.ts +9 -0
  160. package/types/entries/dom-with-overrides-full.d.ts +15 -0
  161. package/types/entries/dom-with-overrides-lite.d.ts +11 -0
  162. package/types/entries/overrides-full-only.d.ts +11 -0
  163. package/types/entries/overrides-lite-only.d.ts +8 -0
  164. package/types/entries/vitals-only.d.ts +8 -0
  165. package/types/index.d.ts +13 -0
  166. package/types/overrides/BrowserCookieAdapter.d.ts +6 -0
  167. package/types/overrides/OverridesPlugin.d.ts +33 -0
  168. package/types/overrides/OverridesPluginFull.d.ts +9 -0
  169. package/types/overrides/OverridesPluginLite.d.ts +27 -0
  170. package/types/overrides/index.d.ts +3 -0
  171. package/types/overrides/types.d.ts +43 -0
  172. package/types/parsers/VariantExtractor.d.ts +49 -0
  173. package/types/types/index.d.ts +177 -0
  174. package/types/utils/URLMatcher.d.ts +20 -0
  175. package/types/utils/debug.d.ts +41 -0
  176. package/types/vitals/WebVitalsPlugin.d.ts +37 -0
  177. package/types/vitals/index.d.ts +2 -0
package/README.md ADDED
@@ -0,0 +1,1101 @@
1
+ # ABsmartly SDK Plugins
2
+
3
+ A comprehensive collection of plugins for the ABsmartly JavaScript SDK including DOM manipulation, experiment overrides, cookie management, and web vitals tracking.
4
+
5
+ ## Available Plugins
6
+
7
+ ### 1. DOMChangesPlugin
8
+ - **Complete DOM Manipulation**: All change types including styleRules with pseudo-states
9
+ - **Smart Exposure Tracking**: Cross-variant tracking prevents sample ratio mismatch
10
+ - **Dynamic Content Support**: Pending changes with `waitForElement` for SPAs
11
+ - **React/Vue Compatibility**: StyleRules survive re-renders
12
+ - **Browser Extension Integration**: Two-way communication for visual editing
13
+
14
+ ### 2. OverridesPlugin
15
+ - **Query String Overrides**: Force experiments via URL parameters
16
+ - **Cookie Persistence**: Server-compatible experiment overrides
17
+ - **API Integration**: Fetch non-running experiments (Full version)
18
+ - **Development Support**: Test experiments in any environment
19
+
20
+ ### 3. CookiePlugin
21
+ - **Unit ID Management**: Generate and persist user identifiers
22
+ - **UTM Tracking**: Capture and store UTM parameters
23
+ - **Landing Page Tracking**: Track first visits and referrers
24
+ - **Storage Fallbacks**: Cookie → localStorage → memory
25
+
26
+ ### 4. WebVitalsPlugin
27
+ - **Core Web Vitals**: Track CLS, LCP, FCP, INP, TTFB
28
+ - **Page Metrics**: Network timing, DOM processing, resource counts
29
+ - **Performance Ratings**: Automatic good/needs-improvement/poor classification
30
+ - **Compression Metrics**: Track page size and compression ratios
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @absmartly/sdk-plugins
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ### Basic Usage
41
+
42
+ ```javascript
43
+ import { DOMChangesPlugin, CookiePlugin, WebVitalsPlugin } from '@absmartly/sdk-plugins';
44
+ import absmartly from '@absmartly/javascript-sdk';
45
+
46
+ // Initialize ABsmartly SDK
47
+ const sdk = new absmartly.SDK({
48
+ endpoint: 'https://your-endpoint.absmartly.io/v1',
49
+ apiKey: 'YOUR_API_KEY',
50
+ environment: 'production',
51
+ application: 'your-app'
52
+ });
53
+
54
+ // Create context
55
+ const context = sdk.createContext(request);
56
+
57
+ // Initialize plugins
58
+ const domPlugin = new DOMChangesPlugin({
59
+ context: context,
60
+ autoApply: true, // Automatically apply changes on init
61
+ spa: true, // Enable SPA support
62
+ visibilityTracking: true, // Track when changes become visible
63
+ extensionBridge: true, // Enable browser extension communication
64
+ dataSource: 'variable', // Use variables (or 'customField')
65
+ dataFieldName: '__dom_changes', // Variable/field name for DOM changes
66
+ debug: true // Enable debug logging
67
+ });
68
+
69
+ // Initialize without blocking
70
+ domPlugin.ready().then(() => {
71
+ console.log('DOMChangesPlugin ready');
72
+ });
73
+
74
+ // Initialize cookie management
75
+ const cookiePlugin = new CookiePlugin({
76
+ context: context,
77
+ cookieDomain: '.yourdomain.com',
78
+ autoUpdateExpiry: true
79
+ });
80
+ cookiePlugin.ready().then(() => {
81
+ console.log('CookiePlugin ready');
82
+ });
83
+
84
+ // Initialize web vitals tracking
85
+ const vitalsPlugin = new WebVitalsPlugin({
86
+ context: context,
87
+ trackWebVitals: true,
88
+ trackPageMetrics: true
89
+ });
90
+ vitalsPlugin.ready().then(() => {
91
+ console.log('WebVitalsPlugin ready');
92
+ });
93
+ ```
94
+
95
+ ### With Experiment Overrides (Browser Extension Support)
96
+
97
+ The OverridesPlugin enables experiment overrides for internal testing and the ABsmartly Browser Extension. Simply load it before SDK initialization and it will automatically check for and apply any overrides:
98
+
99
+ ```javascript
100
+ import {
101
+ DOMChangesPlugin,
102
+ OverridesPlugin
103
+ } from '@absmartly/dom-changes-plugin';
104
+ import absmartly from '@absmartly/javascript-sdk';
105
+
106
+ // Initialize SDK and create context
107
+ const sdk = new absmartly.SDK({ /* ... */ });
108
+ const context = sdk.createContext({ /* ... */ });
109
+
110
+ // Initialize OverridesPlugin - it will automatically check for overrides
111
+ const overridesPlugin = new OverridesPlugin({
112
+ context: context,
113
+ cookieName: 'absmartly_overrides',
114
+ useQueryString: true,
115
+ queryPrefix: '_exp_',
116
+ envParam: 'env',
117
+ persistQueryToCookie: true, // Save query overrides to cookie
118
+ sdkEndpoint: 'https://your-endpoint.absmartly.io',
119
+ debug: true
120
+ });
121
+
122
+ overridesPlugin.ready().then(() => {
123
+ console.log('OverridesPlugin ready - overrides applied if present');
124
+ });
125
+
126
+ // Initialize DOMChangesPlugin for all experiments
127
+ const domPlugin = new DOMChangesPlugin({
128
+ context: context,
129
+ autoApply: true,
130
+ dataSource: 'variable',
131
+ dataFieldName: '__dom_changes',
132
+ debug: true
133
+ });
134
+
135
+ domPlugin.ready().then(() => {
136
+ console.log('DOMChangesPlugin ready');
137
+ });
138
+ ```
139
+
140
+ #### Override Configuration Options
141
+
142
+ ```javascript
143
+ const overridesPlugin = new OverridesPlugin({
144
+ context: context, // Required: ABsmartly context
145
+
146
+ // Cookie configuration
147
+ cookieName: 'absmartly_overrides', // Cookie name (omit to disable cookies)
148
+ cookieOptions: {
149
+ path: '/',
150
+ secure: true,
151
+ sameSite: 'Lax'
152
+ },
153
+
154
+ // Query string configuration
155
+ useQueryString: true, // Enable query string parsing (default: true on client)
156
+ queryPrefix: '_exp_', // Prefix for experiment params (default: '_exp_')
157
+ envParam: 'env', // Environment parameter name (default: 'env')
158
+ persistQueryToCookie: false, // Save query overrides to cookie (default: false)
159
+
160
+ // Endpoints
161
+ sdkEndpoint: 'https://...', // SDK endpoint (required if not in context)
162
+ absmartlyEndpoint: 'https://...', // API endpoint for fetching experiments
163
+
164
+ // Server-side configuration
165
+ url: req.url, // URL for server-side (Node.js)
166
+ cookieAdapter: customAdapter, // Custom cookie adapter for server-side
167
+
168
+ debug: true // Enable debug logging
169
+ });
170
+ ```
171
+
172
+ #### Query String Format (New)
173
+
174
+ Use individual query parameters with configurable prefix:
175
+ ```
176
+ # Single experiment
177
+ https://example.com?_exp_button_color=1
178
+
179
+ # Multiple experiments
180
+ https://example.com?_exp_hero_title=0&_exp_nav_style=2
181
+
182
+ # With environment
183
+ https://example.com?env=staging&_exp_dev_feature=1,1
184
+
185
+ # With experiment ID
186
+ https://example.com?_exp_archived_test=1,2,12345
187
+ ```
188
+
189
+ #### Cookie Format
190
+
191
+ Cookies use comma as separator (no encoding needed):
192
+ ```javascript
193
+ // Simple overrides (comma-separated experiments)
194
+ document.cookie = 'absmartly_overrides=exp1:1,exp2:0';
195
+
196
+ // With environment flags (dot-separated values)
197
+ document.cookie = 'absmartly_overrides=exp1:1.0,exp2:0.1';
198
+
199
+ // With experiment ID
200
+ document.cookie = 'absmartly_overrides=exp1:1.2.12345';
201
+
202
+ // With dev environment
203
+ document.cookie = 'absmartly_overrides=devEnv=staging|exp1:1.1,exp2:0.1';
204
+ ```
205
+
206
+ **Format**: `name:variant[.env][.id]` where:
207
+ - Experiments are separated by `,` (comma)
208
+ - Values within an experiment are separated by `.` (dot)
209
+ - Environment prefix uses `|` (pipe) separator
210
+
211
+ #### How Overrides Work
212
+
213
+ 1. **Query String Priority**: Query parameters take precedence over cookies
214
+ 2. **Environment Support**: Use `env` parameter for dev/staging experiments
215
+ 3. **API Fetching**: Non-running experiments are fetched from ABsmartly API
216
+ 4. **Context Injection**: Experiments are transparently injected into context.data()
217
+ 5. **DOM Application**: DOMChangesPlugin applies changes from all experiments
218
+
219
+ ## DOM Change Types
220
+
221
+ The plugin supports comprehensive DOM manipulation with advanced features:
222
+
223
+ ### Core Change Types
224
+
225
+ ### Text Change
226
+ ```javascript
227
+ {
228
+ selector: '.headline',
229
+ type: 'text',
230
+ value: 'New Headline Text'
231
+ }
232
+ ```
233
+
234
+ ### HTML Change
235
+ ```javascript
236
+ {
237
+ selector: '.content',
238
+ type: 'html',
239
+ value: '<p>New <strong>HTML</strong> content</p>'
240
+ }
241
+ ```
242
+
243
+ ### Style Change (Inline)
244
+ ```javascript
245
+ {
246
+ selector: '.button',
247
+ type: 'style',
248
+ value: {
249
+ backgroundColor: 'red', // Use camelCase for CSS properties
250
+ color: '#ffffff',
251
+ fontSize: '18px'
252
+ },
253
+ trigger_on_view: false // Control exposure timing
254
+ }
255
+ ```
256
+
257
+ ### Style Rules (With Pseudo-States) ⭐ NEW
258
+ ```javascript
259
+ {
260
+ selector: '.button',
261
+ type: 'styleRules',
262
+ states: {
263
+ normal: {
264
+ backgroundColor: '#007bff',
265
+ color: 'white',
266
+ padding: '10px 20px',
267
+ borderRadius: '4px',
268
+ transition: 'all 0.2s ease'
269
+ },
270
+ hover: {
271
+ backgroundColor: '#0056b3',
272
+ transform: 'translateY(-2px)',
273
+ boxShadow: '0 4px 8px rgba(0,0,0,0.2)'
274
+ },
275
+ active: {
276
+ backgroundColor: '#004085',
277
+ transform: 'translateY(0)'
278
+ },
279
+ focus: {
280
+ outline: '2px solid #007bff',
281
+ outlineOffset: '2px'
282
+ }
283
+ },
284
+ important: true, // default is true
285
+ trigger_on_view: true
286
+ }
287
+ ```
288
+
289
+ **Benefits of styleRules:**
290
+ - Handles CSS pseudo-states properly (hover, active, focus)
291
+ - Survives React re-renders through stylesheet injection
292
+ - More performant than inline styles for complex interactions
293
+
294
+ ### Class Change
295
+ ```javascript
296
+ {
297
+ selector: '.card',
298
+ type: 'class',
299
+ add: ['highlighted', 'featured'],
300
+ remove: ['default']
301
+ }
302
+ ```
303
+
304
+ ### Attribute Change
305
+ ```javascript
306
+ {
307
+ selector: 'input[name="email"]',
308
+ type: 'attribute',
309
+ value: {
310
+ 'placeholder': 'Enter your email',
311
+ 'required': 'true'
312
+ }
313
+ }
314
+ ```
315
+
316
+ ### JavaScript Execution
317
+ ```javascript
318
+ {
319
+ selector: '.dynamic-element',
320
+ type: 'javascript',
321
+ value: 'element.addEventListener("click", () => console.log("Clicked!"))'
322
+ }
323
+ ```
324
+
325
+ ### Element Move
326
+ ```javascript
327
+ {
328
+ selector: '.sidebar',
329
+ type: 'move',
330
+ targetSelector: '.main-content',
331
+ position: 'before' // 'before', 'after', 'firstChild', 'lastChild'
332
+ }
333
+ ```
334
+
335
+ ### Element Creation
336
+ ```javascript
337
+ {
338
+ selector: '.new-banner', // For identification
339
+ type: 'create',
340
+ element: '<div class="banner">Special Offer!</div>',
341
+ targetSelector: 'body',
342
+ position: 'firstChild',
343
+ trigger_on_view: false
344
+ }
345
+ ```
346
+
347
+ ### Pending Changes (Elements Not Yet in DOM) ⭐ NEW
348
+ ```javascript
349
+ {
350
+ selector: '.lazy-loaded-button',
351
+ type: 'style',
352
+ value: {
353
+ backgroundColor: 'red',
354
+ color: 'white'
355
+ },
356
+ waitForElement: true, // Wait for element to appear
357
+ observerRoot: '.main-content', // Optional: specific container to watch
358
+ trigger_on_view: true
359
+ }
360
+ ```
361
+
362
+ **Perfect for:**
363
+ - Lazy-loaded content
364
+ - React components that mount/unmount
365
+ - Modal dialogs
366
+ - API-loaded content
367
+ - Infinite scroll items
368
+
369
+ ## Key Features
370
+
371
+ ### Exposure Tracking with trigger_on_view
372
+
373
+ The `trigger_on_view` property prevents sample ratio mismatch by controlling when A/B test exposures are recorded:
374
+
375
+ ```javascript
376
+ {
377
+ selector: '.below-fold-element',
378
+ type: 'style',
379
+ value: { backgroundColor: 'blue' },
380
+ trigger_on_view: true // Only trigger when element becomes visible
381
+ }
382
+ ```
383
+
384
+ - **`false` (default)**: Exposure triggers immediately
385
+ - **`true`**: Exposure triggers when element enters viewport
386
+ - **Cross-variant tracking**: Tracks elements from ALL variants for unbiased exposure
387
+
388
+ ### Core API Methods
389
+
390
+ ```javascript
391
+ // Apply changes from ABsmartly context
392
+ await plugin.applyChanges('experiment-name');
393
+
394
+ // Apply individual change
395
+ const success = plugin.applyChange(change, 'experiment-name');
396
+
397
+ // Remove changes
398
+ plugin.removeChanges('experiment-name');
399
+
400
+ // Get applied changes
401
+ const changes = plugin.getAppliedChanges('experiment-name');
402
+
403
+ // Clean up resources
404
+ plugin.destroy();
405
+ ```
406
+
407
+ ## Browser Extension Integration
408
+
409
+ The plugin communicates with the ABsmartly Browser Extension for visual editing:
410
+
411
+ - **Automatic Detection**: Detects and connects to the extension
412
+ - **Message Protocol**: Two-way communication for preview mode and visual editing
413
+ - **Preview Support**: Test changes before creating experiments
414
+
415
+ ## Documentation
416
+
417
+ For detailed documentation:
418
+
419
+ - **[Extension Integration Guide](docs/EXTENSION_INTEGRATION_GUIDE.md)** - Complete guide for browser extension integration, visual editing, preview mode, and advanced features
420
+ - **[Exposure Tracking Guide](docs/EXPOSURE_TRACKING_GUIDE.md)** - Understanding trigger_on_view and preventing sample ratio mismatch
421
+
422
+ ## Anti-Flicker Support
423
+
424
+ Prevent content flash before experiments load with two modes:
425
+
426
+ ### Quick Start
427
+
428
+ **Hide entire page with smooth fade-in:**
429
+ ```javascript
430
+ const domPlugin = new DOMChangesPlugin({
431
+ context: context,
432
+ hideUntilReady: 'body',
433
+ hideTransition: '0.3s ease-in' // Smooth fade-in
434
+ });
435
+ ```
436
+
437
+ **Hide only experiment elements (recommended):**
438
+ ```html
439
+ <!-- Mark elements that need anti-flicker -->
440
+ <div data-absmartly-hide>
441
+ Hero section with experiment
442
+ </div>
443
+
444
+ <div data-absmartly-hide>
445
+ CTA button being tested
446
+ </div>
447
+ ```
448
+
449
+ ```javascript
450
+ const domPlugin = new DOMChangesPlugin({
451
+ context: context,
452
+ hideUntilReady: 'elements', // Hide only marked elements
453
+ hideTransition: '0.4s ease-out'
454
+ });
455
+ ```
456
+
457
+ ### How It Works
458
+
459
+ 1. **During Load (Before Experiments Applied):**
460
+ ```css
461
+ /* No transition: */
462
+ body { visibility: hidden !important; }
463
+
464
+ /* With transition: */
465
+ body {
466
+ visibility: hidden !important;
467
+ opacity: 0 !important;
468
+ }
469
+ ```
470
+
471
+ 2. **After Experiments Applied:**
472
+ - **No transition**: Instant reveal (removes `visibility:hidden`)
473
+ - **With transition**: Smooth 4-step fade-in:
474
+ 1. Remove `visibility:hidden`
475
+ 2. Add CSS transition
476
+ 3. Animate opacity 0 → 1
477
+ 4. Clean up after transition completes
478
+
479
+ ### Configuration Options
480
+
481
+ ```javascript
482
+ new DOMChangesPlugin({
483
+ // Anti-flicker configuration
484
+ hideUntilReady: 'body', // 'body' | 'elements' | true | false
485
+ // 'body': hide entire page
486
+ // 'elements': hide only marked elements
487
+ // true: same as 'elements'
488
+ // false: disabled (default)
489
+
490
+ hideTimeout: 3000, // Max wait time in ms (default: 3000)
491
+ // Content auto-shows after timeout
492
+ // even if experiments fail to load
493
+
494
+ hideTransition: '0.3s ease-in', // CSS transition for fade-in
495
+ // Examples: '0.3s ease-in', '0.5s linear'
496
+ // false: instant reveal (default)
497
+
498
+ hideSelector: '[data-absmartly-hide]' // Custom selector for 'elements' mode
499
+ // default: '[data-absmartly-hide]'
500
+ })
501
+ ```
502
+
503
+ ### Why This Matters
504
+
505
+ **Without anti-flicker:**
506
+ ```
507
+ User sees: Original → FLASH → Experiment Version
508
+ ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^
509
+ (bad UX) (jarring)
510
+ ```
511
+
512
+ **With anti-flicker:**
513
+ ```
514
+ User sees: [Hidden] → Experiment Version (smooth fade-in)
515
+ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
516
+ (no flash) (professional)
517
+ ```
518
+
519
+ ### Best Practices
520
+
521
+ **✅ Use `hideUntilReady: 'elements'` (Recommended)**
522
+ - Mark only elements being tested
523
+ - Faster perceived load time
524
+ - Better user experience
525
+ - No CLS (Cumulative Layout Shift)
526
+
527
+ ```html
528
+ <nav>Normal navigation</nav>
529
+ <div data-absmartly-hide>
530
+ <!-- Only this hero is hidden -->
531
+ <h1>Hero headline being tested</h1>
532
+ </div>
533
+ <main>Rest of content visible immediately</main>
534
+ ```
535
+
536
+ **✅ Use `hideUntilReady: 'body'` (For whole-page experiments)**
537
+ - Complete redesigns
538
+ - Multiple changes across page
539
+ - Ensures zero flicker
540
+
541
+ **✅ Set appropriate timeout**
542
+ ```javascript
543
+ {
544
+ hideTimeout: 2000 // Faster for good connections
545
+ hideTimeout: 3000 // Balanced (default)
546
+ hideTimeout: 5000 // Safer for slow connections
547
+ }
548
+ ```
549
+
550
+ **⚠️ Avoid long transitions**
551
+ ```javascript
552
+ hideTransition: '0.2s ease-in' // ✅ Subtle, professional
553
+ hideTransition: '0.3s ease-out' // ✅ Smooth
554
+ hideTransition: '1s linear' // ❌ Too slow, feels broken
555
+ ```
556
+
557
+ ### Industry Standard Comparison
558
+
559
+ | Tool | Method | Our Implementation |
560
+ |------|--------|-------------------|
561
+ | Google Optimize | `opacity: 0` | ✅ `visibility:hidden` (better) |
562
+ | Optimizely | `opacity: 0` | ✅ `visibility:hidden` (better) |
563
+ | VWO | `opacity: 0` on body | ✅ Both modes supported |
564
+ | **ABsmartly** | - | ✅ `visibility:hidden` + optional smooth fade |
565
+
566
+ **Why `visibility:hidden` is better:**
567
+ - ✅ Hides from screen readers
568
+ - ✅ Prevents interaction
569
+ - ✅ No CLS (Cumulative Layout Shift)
570
+ - ✅ Can add smooth opacity transition
571
+
572
+ ## Style Persistence
573
+
574
+ Automatically reapply experiment styles when frameworks (React/Vue/Angular) overwrite them:
575
+
576
+ ### The Problem
577
+
578
+ ```javascript
579
+ // Experiment changes button to red
580
+ { selector: '.button', type: 'style', value: { backgroundColor: 'red' } }
581
+
582
+ // User hovers → React's hover handler changes it back to blue
583
+ // Experiment style is lost! ❌
584
+ ```
585
+
586
+ ### The Solution
587
+
588
+ ```javascript
589
+ {
590
+ selector: '.button',
591
+ type: 'style',
592
+ value: { backgroundColor: 'red' },
593
+ persistStyle: true // ✅ Reapply when React overwrites it
594
+ }
595
+ ```
596
+
597
+ ### How It Works
598
+
599
+ 1. **MutationObserver** watches for style attribute changes
600
+ 2. **Detects** when framework overwrites experiment styles
601
+ 3. **Automatically reapplies** experiment styles
602
+ 4. **Throttled logging** (max once per 5 seconds to avoid spam)
603
+
604
+ ### When It's Enabled
605
+
606
+ **Automatically in SPA mode:**
607
+ ```javascript
608
+ new DOMChangesPlugin({
609
+ context: context,
610
+ spa: true // ✅ Style persistence auto-enabled for all style changes
611
+ })
612
+ ```
613
+
614
+ **Opt-in per change:**
615
+ ```javascript
616
+ {
617
+ selector: '.button',
618
+ type: 'style',
619
+ value: { backgroundColor: 'red' },
620
+ persistStyle: true // ✅ Explicit opt-in (works even if spa: false)
621
+ }
622
+ ```
623
+
624
+ ### Use Cases
625
+
626
+ **✅ React components with hover states:**
627
+ ```javascript
628
+ // Button has React onClick that changes style
629
+ {
630
+ selector: '.cta-button',
631
+ type: 'style',
632
+ value: { backgroundColor: '#ff6b6b' },
633
+ persistStyle: true // Survives React re-renders
634
+ }
635
+ ```
636
+
637
+ **✅ Vue reactive styles:**
638
+ ```javascript
639
+ {
640
+ selector: '.dynamic-price',
641
+ type: 'style',
642
+ value: { color: 'green', fontWeight: 'bold' },
643
+ persistStyle: true // Persists through Vue updates
644
+ }
645
+ ```
646
+
647
+ **✅ Angular material components:**
648
+ ```javascript
649
+ {
650
+ selector: 'mat-button',
651
+ type: 'style',
652
+ value: { backgroundColor: '#1976d2' },
653
+ persistStyle: true // Works with Angular's style binding
654
+ }
655
+ ```
656
+
657
+ **❌ Static HTML (persistence not needed):**
658
+ ```javascript
659
+ {
660
+ selector: '.static-banner',
661
+ type: 'style',
662
+ value: { display: 'none' },
663
+ persistStyle: false // Not needed, saves performance
664
+ }
665
+ ```
666
+
667
+ ## URL Filtering
668
+
669
+ Target experiments to specific pages or URL patterns using the `urlFilter` property. This enables precise control over where experiments run without code changes.
670
+
671
+ ### Basic URL Filtering
672
+
673
+ Wrap your DOM changes in a configuration object with `urlFilter`:
674
+
675
+ ```javascript
676
+ {
677
+ urlFilter: '/products', // Simple path match
678
+ changes: [
679
+ {
680
+ selector: '.product-title',
681
+ type: 'style',
682
+ value: { color: 'red' }
683
+ }
684
+ ]
685
+ }
686
+ ```
687
+
688
+ ### URL Filter Configuration
689
+
690
+ ```javascript
691
+ {
692
+ urlFilter: {
693
+ include: ['/checkout', '/cart'], // Run on these URLs
694
+ exclude: ['/checkout/success'], // But NOT on these
695
+ mode: 'simple', // 'simple' or 'regex'
696
+ matchType: 'path' // 'full-url', 'path', 'domain', 'query', 'hash'
697
+ },
698
+ changes: [ /* ... */ ]
699
+ }
700
+ ```
701
+
702
+ ### Match Types
703
+
704
+ **`path` (default)** - Match against pathname + hash:
705
+ ```javascript
706
+ // URL: https://example.com/products/shoes#new
707
+ // Matches: "/products/shoes#new"
708
+ {
709
+ urlFilter: { include: ['/products/*'], matchType: 'path' },
710
+ changes: [ /* ... */ ]
711
+ }
712
+ ```
713
+
714
+ **`full-url`** - Match complete URL including protocol and domain:
715
+ ```javascript
716
+ // URL: https://example.com/products
717
+ // Matches: "https://example.com/products"
718
+ {
719
+ urlFilter: { include: ['https://example.com/products'], matchType: 'full-url' },
720
+ changes: [ /* ... */ ]
721
+ }
722
+ ```
723
+
724
+ **`domain`** - Match only the hostname:
725
+ ```javascript
726
+ // URL: https://shop.example.com/products
727
+ // Matches: "shop.example.com"
728
+ {
729
+ urlFilter: { include: ['shop.example.com'], matchType: 'domain' },
730
+ changes: [ /* ... */ ]
731
+ }
732
+ ```
733
+
734
+ **`query`** - Match only query parameters:
735
+ ```javascript
736
+ // URL: https://example.com/products?category=shoes
737
+ // Matches: "?category=shoes"
738
+ {
739
+ urlFilter: { include: ['*category=shoes*'], matchType: 'query' },
740
+ changes: [ /* ... */ ]
741
+ }
742
+ ```
743
+
744
+ **`hash`** - Match only hash fragment:
745
+ ```javascript
746
+ // URL: https://example.com/page#section-1
747
+ // Matches: "#section-1"
748
+ {
749
+ urlFilter: { include: ['#section-*'], matchType: 'hash' },
750
+ changes: [ /* ... */ ]
751
+ }
752
+ ```
753
+
754
+ ### Pattern Matching Modes
755
+
756
+ **Simple Mode (default)** - Glob-style wildcards:
757
+ ```javascript
758
+ {
759
+ urlFilter: {
760
+ include: [
761
+ '/products/*', // All product pages
762
+ '/checkout*', // Checkout and checkout/*
763
+ '/about', // Exact match
764
+ '*/special-offer' // Any path ending with /special-offer
765
+ ],
766
+ mode: 'simple'
767
+ },
768
+ changes: [ /* ... */ ]
769
+ }
770
+ ```
771
+
772
+ **Simple Mode Wildcards:**
773
+ - `*` = Match any characters (0 or more)
774
+ - `?` = Match single character
775
+ - No wildcards = Exact match
776
+
777
+ **Regex Mode** - Full regex power:
778
+ ```javascript
779
+ {
780
+ urlFilter: {
781
+ include: [
782
+ '^/products/(shoes|bags)', // Products in specific categories
783
+ '/checkout/(step-[1-3])', // Checkout steps 1-3 only
784
+ '\\?utm_source=email' // URLs with email traffic
785
+ ],
786
+ mode: 'regex'
787
+ },
788
+ changes: [ /* ... */ ]
789
+ }
790
+ ```
791
+
792
+ ### Include and Exclude Logic
793
+
794
+ **Include only:**
795
+ ```javascript
796
+ {
797
+ urlFilter: { include: ['/products/*'] },
798
+ changes: [ /* ... */ ]
799
+ }
800
+ // ✅ Runs on: /products/shoes, /products/bags
801
+ // ❌ Skips: /about, /checkout
802
+ ```
803
+
804
+ **Include with exclusions:**
805
+ ```javascript
806
+ {
807
+ urlFilter: {
808
+ include: ['/products/*'],
809
+ exclude: ['/products/admin', '/products/*/edit']
810
+ },
811
+ changes: [ /* ... */ ]
812
+ }
813
+ // ✅ Runs on: /products/shoes, /products/bags
814
+ // ❌ Skips: /products/admin, /products/shoes/edit
815
+ ```
816
+
817
+ **Exclude only (match all except):**
818
+ ```javascript
819
+ {
820
+ urlFilter: { exclude: ['/admin/*', '/api/*'] },
821
+ changes: [ /* ... */ ]
822
+ }
823
+ // ✅ Runs on: /products, /checkout, /about
824
+ // ❌ Skips: /admin/users, /api/data
825
+ ```
826
+
827
+ **No filter (match all):**
828
+ ```javascript
829
+ {
830
+ urlFilter: {}, // or omit urlFilter entirely
831
+ changes: [ /* ... */ ]
832
+ }
833
+ // ✅ Runs on all pages
834
+ ```
835
+
836
+ **Match nothing (disable experiment):**
837
+ ```javascript
838
+ {
839
+ urlFilter: { include: [] }, // Empty array = explicit "match nothing"
840
+ changes: [ /* ... */ ]
841
+ }
842
+ // ❌ Never runs (useful for temporary disabling)
843
+ ```
844
+
845
+ ### SRM Prevention with URL Filters
846
+
847
+ **Critical:** When using `urlFilter`, the plugin implements **Sample Ratio Mismatch (SRM) prevention** by tracking ALL variants, not just the current variant:
848
+
849
+ ```javascript
850
+ // Variant 0 (Control): No URL filter
851
+ {
852
+ changes: [
853
+ { selector: '.hero', type: 'text', value: 'Welcome' }
854
+ ]
855
+ }
856
+
857
+ // Variant 1: Only runs on /products
858
+ {
859
+ urlFilter: '/products',
860
+ changes: [
861
+ { selector: '.hero', type: 'text', value: 'Shop Now' }
862
+ ]
863
+ }
864
+ ```
865
+
866
+ **Without SRM prevention:**
867
+ - Users on `/products` → Tracked in both variants ✅
868
+ - Users on `/about` → Only tracked in variant 0 ❌
869
+ - **Result:** Biased sample sizes!
870
+
871
+ **With SRM prevention (automatic):**
872
+ - Users on `/products` → Tracked in both variants ✅
873
+ - Users on `/about` → Tracked in both variants ✅
874
+ - **Result:** Unbiased sample sizes! ✅
875
+
876
+ The plugin checks if **ANY variant** matches the current URL. If ANY variant matches, ALL variants are tracked for that user, ensuring fair sample distribution.
877
+
878
+ ### URL Change Detection (SPA Mode)
879
+
880
+ In SPA mode (`spa: true`), the plugin automatically detects URL changes and re-evaluates experiments:
881
+
882
+ ```javascript
883
+ new DOMChangesPlugin({
884
+ context: context,
885
+ spa: true, // Enable URL change detection
886
+ autoApply: true
887
+ });
888
+ ```
889
+
890
+ **What it tracks:**
891
+ - ✅ `history.pushState()` - Client-side navigation
892
+ - ✅ `history.replaceState()` - URL updates
893
+ - ✅ `popstate` events - Browser back/forward buttons
894
+
895
+ **What happens on URL change:**
896
+ 1. Remove all currently applied DOM changes
897
+ 2. Re-evaluate URL filters with the new URL
898
+ 3. Apply changes for experiments matching the new URL
899
+ 4. Re-track exposure for newly applied experiments
900
+
901
+ **Example flow:**
902
+ ```
903
+ User lands on: /products → Apply product page experiments
904
+ User navigates to: /checkout → Remove product experiments, apply checkout experiments
905
+ User goes back: /products → Remove checkout experiments, re-apply product experiments
906
+ ```
907
+
908
+ **Without SPA mode:**
909
+ - URL changes are NOT detected
910
+ - Experiments applied on initial page load remain active
911
+ - New experiments on the new URL are NOT applied
912
+ - Use `spa: false` only for static sites without client-side routing
913
+
914
+ ### Complete Examples
915
+
916
+ **E-commerce experiment on specific pages:**
917
+ ```javascript
918
+ {
919
+ urlFilter: {
920
+ include: ['/products/*', '/collections/*'],
921
+ exclude: ['/products/admin', '*/edit'],
922
+ matchType: 'path',
923
+ mode: 'simple'
924
+ },
925
+ changes: [
926
+ {
927
+ selector: '.add-to-cart-button',
928
+ type: 'style',
929
+ value: { backgroundColor: '#ff6b6b', color: 'white' },
930
+ trigger_on_view: true
931
+ }
932
+ ]
933
+ }
934
+ ```
935
+
936
+ **Blog post experiment with regex:**
937
+ ```javascript
938
+ {
939
+ urlFilter: {
940
+ include: ['^/blog/20(24|25)'], // Only 2024-2025 blog posts
941
+ mode: 'regex',
942
+ matchType: 'path'
943
+ },
944
+ changes: [
945
+ {
946
+ selector: '.blog-cta',
947
+ type: 'text',
948
+ value: 'Subscribe to our newsletter!'
949
+ }
950
+ ]
951
+ }
952
+ ```
953
+
954
+ **Landing page experiment with UTM parameters:**
955
+ ```javascript
956
+ {
957
+ urlFilter: {
958
+ include: ['*utm_campaign=summer*'],
959
+ matchType: 'query'
960
+ },
961
+ changes: [
962
+ {
963
+ selector: '.hero-banner',
964
+ type: 'html',
965
+ value: '<h1>Summer Sale - 50% Off!</h1>'
966
+ }
967
+ ]
968
+ }
969
+ ```
970
+
971
+ **Homepage only (exact match):**
972
+ ```javascript
973
+ {
974
+ urlFilter: '/', // Shorthand for { include: ['/'] }
975
+ changes: [
976
+ {
977
+ selector: '.hero-title',
978
+ type: 'text',
979
+ value: 'Limited Time Offer!'
980
+ }
981
+ ]
982
+ }
983
+ ```
984
+
985
+ **Multiple pages (array shorthand):**
986
+ ```javascript
987
+ {
988
+ urlFilter: ['/pricing', '/features'], // Shorthand for { include: [...] }
989
+ changes: [ /* ... */ ]
990
+ }
991
+ ```
992
+
993
+ ## SPA Mode (React/Vue/Angular Support)
994
+
995
+ Enable `spa: true` for Single Page Applications. This automatically activates three critical features:
996
+
997
+ ### 1. URL Change Detection
998
+
999
+ Automatically detects client-side navigation and re-evaluates experiments:
1000
+
1001
+ - Intercepts `history.pushState()` and `history.replaceState()`
1002
+ - Listens to `popstate` events (browser back/forward)
1003
+ - Re-applies appropriate experiments when URL changes
1004
+
1005
+ See [URL Change Detection](#url-change-detection-spa-mode) above for details.
1006
+
1007
+ ### 2. Wait for Element (Lazy-Loaded Content)
1008
+
1009
+ Automatically waits for elements that don't exist yet in the DOM:
1010
+
1011
+ ```javascript
1012
+ {
1013
+ selector: '.lazy-loaded-button',
1014
+ type: 'style',
1015
+ value: { backgroundColor: 'red' }
1016
+ // No waitForElement needed - SPA mode handles it automatically!
1017
+ }
1018
+ ```
1019
+
1020
+ **Without SPA mode:** Change skipped if element doesn't exist
1021
+ **With SPA mode:** Observes DOM and applies when element appears
1022
+
1023
+ ### 3. Style Persistence (Framework Conflicts)
1024
+
1025
+ Automatically re-applies styles when frameworks overwrite them:
1026
+
1027
+ ```javascript
1028
+ {
1029
+ selector: '.button',
1030
+ type: 'style',
1031
+ value: { backgroundColor: 'red' }
1032
+ // No persistStyle needed - SPA mode handles it automatically!
1033
+ }
1034
+ ```
1035
+
1036
+ **Without SPA mode:** React hover states can overwrite experiment styles
1037
+ **With SPA mode:** Detects and re-applies styles automatically
1038
+
1039
+ ### When to Use SPA Mode
1040
+
1041
+ **✅ Enable `spa: true` for:**
1042
+ - React applications
1043
+ - Vue.js applications
1044
+ - Angular applications
1045
+ - Any app with client-side routing
1046
+ - Apps with lazy-loaded components
1047
+ - Apps with dynamic DOM updates
1048
+
1049
+ **❌ Keep `spa: false` for:**
1050
+ - Static HTML sites
1051
+ - Server-rendered pages without client-side routing
1052
+ - Sites where performance is critical and you don't need dynamic features
1053
+
1054
+ ### Manual Override
1055
+
1056
+ You can still use flags explicitly if you need granular control:
1057
+
1058
+ ```javascript
1059
+ {
1060
+ selector: '.button',
1061
+ type: 'style',
1062
+ value: { backgroundColor: 'red' },
1063
+ persistStyle: true, // Force enable (even if spa: false)
1064
+ waitForElement: true // Force enable (even if spa: false)
1065
+ }
1066
+ ```
1067
+
1068
+ ## Configuration Options
1069
+
1070
+ ```javascript
1071
+ new DOMChangesPlugin({
1072
+ // Required
1073
+ context: absmartlyContext, // ABsmartly context
1074
+
1075
+ // Core options
1076
+ autoApply: true, // Auto-apply changes from SDK
1077
+ spa: true, // Enable SPA support
1078
+ // ✅ Auto-enables: waitForElement + persistStyle
1079
+ visibilityTracking: true, // Track viewport visibility
1080
+ extensionBridge: true, // Enable browser extension communication
1081
+ dataSource: 'variable', // 'variable' or 'customField'
1082
+ dataFieldName: '__dom_changes', // Variable/field name for changes
1083
+ debug: false, // Enable debug logging
1084
+
1085
+ // Anti-flicker options
1086
+ hideUntilReady: false, // 'body' | 'elements' | true | false
1087
+ hideTimeout: 3000, // Max wait time (ms)
1088
+ hideTransition: false, // CSS transition (e.g., '0.3s ease-in') or false
1089
+ hideSelector: '[data-absmartly-hide]' // Custom selector for elements mode
1090
+ })
1091
+ ```
1092
+
1093
+ ## Browser Compatibility
1094
+
1095
+ - Modern browsers (Chrome, Firefox, Safari, Edge)
1096
+ - Internet Explorer 11+ (with polyfills)
1097
+ - Mobile browsers (iOS Safari, Chrome Mobile)
1098
+
1099
+ ## License
1100
+
1101
+ MIT