@capillarytech/creatives-library 8.0.236-alpha.7 → 8.0.236

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 (86) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +1 -1
  4. package/initialReducer.js +0 -2
  5. package/package.json +1 -1
  6. package/services/api.js +0 -5
  7. package/services/tests/api.test.js +0 -18
  8. package/utils/common.js +2 -1
  9. package/utils/commonUtils.js +1 -14
  10. package/utils/tests/commonUtil.test.js +0 -224
  11. package/utils/transformTemplateConfig.js +10 -0
  12. package/v2Components/CapDeviceContent/index.js +56 -61
  13. package/v2Components/CapTagList/index.js +0 -4
  14. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  15. package/v2Components/HtmlEditor/HTMLEditor.js +83 -235
  16. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +19 -932
  17. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
  18. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -2
  19. package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
  20. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -2
  21. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +131 -105
  22. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  23. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  24. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
  25. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  26. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  27. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  28. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  29. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  30. package/v2Components/HtmlEditor/constants.js +20 -29
  31. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  32. package/v2Components/HtmlEditor/hooks/useInAppContent.js +148 -130
  33. package/v2Components/HtmlEditor/index.js +1 -1
  34. package/v2Components/HtmlEditor/messages.js +85 -85
  35. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  36. package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
  37. package/v2Components/TemplatePreview/index.js +32 -47
  38. package/v2Components/TemplatePreview/messages.js +0 -4
  39. package/v2Containers/BeeEditor/index.js +80 -82
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +34 -69
  41. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -2
  42. package/v2Containers/CreativesContainer/constants.js +0 -1
  43. package/v2Containers/CreativesContainer/index.js +32 -92
  44. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +12 -4
  45. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -15
  46. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  47. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  48. package/v2Containers/InApp/actions.js +0 -7
  49. package/v2Containers/InApp/constants.js +4 -20
  50. package/v2Containers/InApp/index.js +386 -984
  51. package/v2Containers/InApp/index.scss +3 -4
  52. package/v2Containers/InApp/messages.js +3 -7
  53. package/v2Containers/InApp/reducer.js +3 -21
  54. package/v2Containers/InApp/sagas.js +9 -29
  55. package/v2Containers/InApp/selectors.js +5 -25
  56. package/v2Containers/InApp/tests/index.test.js +50 -154
  57. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  58. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  59. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
  60. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
  61. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
  62. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
  63. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
  64. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
  65. package/v2Containers/TagList/index.js +1 -65
  66. package/v2Containers/Templates/_templates.scss +1 -60
  67. package/v2Containers/Templates/index.js +5 -99
  68. package/v2Containers/Templates/messages.js +0 -4
  69. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -35
  70. package/v2Containers/BeePopupEditor/constants.js +0 -10
  71. package/v2Containers/BeePopupEditor/index.js +0 -193
  72. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  73. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  74. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  75. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  76. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
  77. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  78. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
  79. package/v2Containers/InAppWrapper/constants.js +0 -16
  80. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  81. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  82. package/v2Containers/InAppWrapper/index.js +0 -148
  83. package/v2Containers/InAppWrapper/messages.js +0 -49
  84. package/v2Containers/InappAdvance/index.js +0 -1115
  85. package/v2Containers/InappAdvance/index.scss +0 -10
  86. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
@@ -4,30 +4,133 @@
4
4
  * Manages separate HTML content for Android and iOS devices with sync functionality
5
5
  */
6
6
 
7
- import {
8
- useState, useCallback, useRef, useEffect, useMemo,
9
- } from 'react';
7
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
10
8
  import { DEVICE_TYPES, PERFORMANCE } from '../constants';
11
9
 
12
10
  // Constants for better maintainability
13
11
  const CONTENT_VALIDATION = {
14
12
  MIN_CONTENT_LENGTH: 0,
15
- DEFAULT_CONTENT_TYPE: 'string',
13
+ DEFAULT_CONTENT_TYPE: 'string'
16
14
  };
17
15
 
18
16
  const AUTO_SAVE_CONFIG = {
19
17
  DEFAULT_ENABLED: true,
20
18
  DEFAULT_INTERVAL: PERFORMANCE.AUTO_SAVE_INTERVAL,
21
- MIN_AUTO_SAVE_INTERVAL_MS: 1000, // Minimum 1 second between auto-saves
19
+ MIN_AUTO_SAVE_INTERVAL_MS: 1000 // Minimum 1 second between auto-saves
22
20
  };
23
21
 
24
22
  /**
25
23
  * Default InApp content for different devices
26
- * Empty strings - no default content for new templates
27
24
  */
28
25
  const DEFAULT_INAPP_CONTENT = {
29
- [DEVICE_TYPES.ANDROID]: '',
30
- [DEVICE_TYPES.IOS]: '',
26
+ [DEVICE_TYPES.ANDROID]: `<!DOCTYPE html>
27
+ <html lang="en">
28
+ <head>
29
+ <meta charset="UTF-8">
30
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
31
+ <title>In-App Notification</title>
32
+ <style>
33
+ body {
34
+ margin: 0;
35
+ padding: 16px;
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif;
37
+ background-color: #ffffff;
38
+ color: #212121;
39
+ }
40
+ .notification {
41
+ max-width: 100%;
42
+ background: white;
43
+ border-radius: 8px;
44
+ padding: 16px;
45
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
46
+ }
47
+ .title {
48
+ font-size: 16px;
49
+ font-weight: 500;
50
+ margin: 0 0 8px 0;
51
+ color: #212121;
52
+ }
53
+ .message {
54
+ font-size: 14px;
55
+ line-height: 1.4;
56
+ margin: 0 0 16px 0;
57
+ color: #424242;
58
+ }
59
+ .cta-button {
60
+ background-color: #42b040;
61
+ color: white;
62
+ border: none;
63
+ border-radius: 4px;
64
+ padding: 8px 16px;
65
+ font-size: 12px;
66
+ font-weight: 500;
67
+ cursor: pointer;
68
+ width: 100%;
69
+ }
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <div class="notification">
74
+ <h2 class="title">Sample template</h2>
75
+ <p class="message">This is a sample template for in-app notification content. This can be triggered on any behavioural event while the user is on the app</p>
76
+ <button class="cta-button">Add to cart</button>
77
+ </div>
78
+ </body>
79
+ </html>`,
80
+ [DEVICE_TYPES.IOS]: `<!DOCTYPE html>
81
+ <html lang="en">
82
+ <head>
83
+ <meta charset="UTF-8">
84
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
85
+ <title>In-App Notification</title>
86
+ <style>
87
+ body {
88
+ margin: 0;
89
+ padding: 16px;
90
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;
91
+ background-color: #ffffff;
92
+ color: #000000;
93
+ }
94
+ .notification {
95
+ max-width: 100%;
96
+ background: white;
97
+ border-radius: 12px;
98
+ padding: 16px;
99
+ box-shadow: 0 4px 16px rgba(0,0,0,0.1);
100
+ }
101
+ .title {
102
+ font-size: 17px;
103
+ font-weight: 600;
104
+ margin: 0 0 8px 0;
105
+ color: #000000;
106
+ }
107
+ .message {
108
+ font-size: 15px;
109
+ line-height: 1.4;
110
+ margin: 0 0 16px 0;
111
+ color: #3c3c43;
112
+ }
113
+ .cta-button {
114
+ background-color: #007AFF;
115
+ color: white;
116
+ border: none;
117
+ border-radius: 8px;
118
+ padding: 12px 16px;
119
+ font-size: 16px;
120
+ font-weight: 600;
121
+ cursor: pointer;
122
+ width: 100%;
123
+ }
124
+ </style>
125
+ </head>
126
+ <body>
127
+ <div class="notification">
128
+ <h2 class="title">Sample template</h2>
129
+ <p class="message">This is a sample template for in-app notification content. This can be triggered on any behavioural event while the user is on the app</p>
130
+ <button class="cta-button">Add to cart</button>
131
+ </div>
132
+ </body>
133
+ </html>`
31
134
  };
32
135
 
33
136
  /**
@@ -47,38 +150,23 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
47
150
  autoSave = AUTO_SAVE_CONFIG.DEFAULT_ENABLED,
48
151
  autoSaveInterval = AUTO_SAVE_CONFIG.DEFAULT_INTERVAL,
49
152
  onSave,
50
- onChange,
153
+ onChange
51
154
  } = options;
52
155
 
53
156
  // Destructure device types for cleaner code
54
157
  const { ANDROID, IOS } = DEVICE_TYPES;
55
158
 
56
159
  // Device-specific content state with optional chaining
57
- // FIX: If iOS content is empty but Android has content, use Android content for iOS
58
- // This handles the case where "keepContentSame" was used during save
59
- const initialAndroidContent = initialContent?.[ANDROID] || DEFAULT_INAPP_CONTENT[ANDROID];
60
- const initialIosContent = initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS];
61
- const iosContentToUse = (!initialIosContent || initialIosContent.trim() === '') && initialAndroidContent && initialAndroidContent.trim() !== ''
62
- ? initialAndroidContent
63
- : initialIosContent;
64
-
65
- // Check if both devices have the same content initially
66
- const initialHasSameContent = initialAndroidContent && iosContentToUse
67
- && initialAndroidContent.trim() !== ''
68
- && iosContentToUse.trim() !== ''
69
- && initialAndroidContent.trim() === iosContentToUse.trim();
70
-
71
160
  const [deviceContent, setDeviceContent] = useState(() => ({
72
- [ANDROID]: initialAndroidContent,
73
- [IOS]: iosContentToUse,
161
+ [ANDROID]: initialContent?.[ANDROID] || DEFAULT_INAPP_CONTENT[ANDROID],
162
+ [IOS]: initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS]
74
163
  }));
75
164
 
76
165
  // Current active device
77
166
  const [activeDevice, setActiveDevice] = useState(ANDROID);
78
167
 
79
168
  // Content sync state
80
- // FIX: Initialize keepContentSame to true if both devices have the same content initially
81
- const [keepContentSame, setKeepContentSame] = useState(initialHasSameContent);
169
+ const [keepContentSame, setKeepContentSame] = useState(false);
82
170
 
83
171
  // Dirty state tracking
84
172
  const [isDirty, setIsDirty] = useState(false);
@@ -101,92 +189,15 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
101
189
  deviceContentRef.current = deviceContent;
102
190
  }, [deviceContent]);
103
191
 
104
- // Ref to track if we've loaded initial content to prevent overriding user edits
105
- const initialContentLoadedRef = useRef(false);
106
- const previousContentRef = useRef({ android: '', ios: '' });
107
-
108
- // Update content when initialContent prop changes (for edit mode)
109
- // This should only run when loading a template for editing, NOT during active editing
110
- useEffect(() => {
111
- const newAndroidContent = initialContent?.[ANDROID];
112
- const newIosContent = initialContent?.[IOS];
113
-
114
- // Check if this is meaningful initialContent (has actual content)
115
- const hasMeaningfulContent = (newAndroidContent && newAndroidContent.trim() !== '')
116
- || (newIosContent && newIosContent.trim() !== '');
117
-
118
- // Check if we're transitioning from empty to meaningful content (library mode scenario)
119
- const wasEmpty = (!previousContentRef.current.android || previousContentRef.current.android.trim() === '')
120
- && (!previousContentRef.current.ios || previousContentRef.current.ios.trim() === '');
121
- const isTransitioningToContent = wasEmpty && hasMeaningfulContent;
122
-
123
- // FIX: Check if both devices have the same content (indicates "keepContentSame" was used)
124
- // If they do, automatically enable content sync
125
- const hasSameContent = newAndroidContent && newIosContent
126
- && newAndroidContent.trim() !== ''
127
- && newIosContent.trim() !== ''
128
- && newAndroidContent.trim() === newIosContent.trim();
129
-
130
- // Only update if:
131
- // 1. We haven't loaded initial content yet (first load), OR
132
- // 2. We're transitioning from empty to meaningful content (library mode data fetch)
133
- // This prevents the effect from overriding user edits during active editing
134
- // while still allowing content to load properly in library mode
135
- if (!initialContentLoadedRef.current || isTransitioningToContent) {
136
- if (hasMeaningfulContent) {
137
- // Mark as loaded to prevent future updates from overriding user edits
138
- initialContentLoadedRef.current = true;
139
-
140
- setDeviceContent((prev) => {
141
- let updated = false;
142
- const updatedContent = { ...prev };
143
-
144
- if (newAndroidContent !== undefined && newAndroidContent !== prev[ANDROID]) {
145
- updatedContent[ANDROID] = newAndroidContent;
146
- updated = true;
147
- }
148
- if (newIosContent !== undefined && newIosContent !== prev[IOS]) {
149
- updatedContent[IOS] = newIosContent;
150
- updated = true;
151
- }
152
-
153
- return updated ? updatedContent : prev;
154
- });
155
-
156
- // FIX: If both devices have the same content, enable content sync
157
- // This ensures the "keepContentSame" checkbox is checked when loading templates
158
- // that were saved with the same content for both devices
159
- // Also check if iOS is empty but Android has content (same content scenario)
160
- const iosEmptyButAndroidHasContent = (!newIosContent || newIosContent.trim() === '')
161
- && newAndroidContent && newAndroidContent.trim() !== '';
162
-
163
- if ((hasSameContent || iosEmptyButAndroidHasContent) && !keepContentSame) {
164
- setKeepContentSame(true);
165
- // If iOS is empty but Android has content, sync iOS to Android
166
- if (iosEmptyButAndroidHasContent) {
167
- setDeviceContent((prev) => ({
168
- ...prev,
169
- [IOS]: newAndroidContent,
170
- }));
171
- }
172
- }
173
-
174
- // Update previous content ref
175
- previousContentRef.current = {
176
- android: newAndroidContent || '',
177
- ios: newIosContent || '',
178
- };
179
- }
180
- }
181
- }, [initialContent, ANDROID, IOS, keepContentSame]);
182
-
183
192
  // Get current active content
184
- const currentContent = useMemo(() => deviceContent?.[activeDevice] || '', [deviceContent, activeDevice]);
193
+ const currentContent = useMemo(() => {
194
+ return deviceContent[activeDevice] || '';
195
+ }, [deviceContent, activeDevice]);
185
196
 
186
197
  // Update content for current device
187
198
  const updateContent = useCallback((newContent) => {
188
199
  // Validate input
189
- if (typeof newContent !== CONTENT_VALIDATION?.DEFAULT_CONTENT_TYPE) {
200
+ if (typeof newContent !== CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE) {
190
201
  console.warn('useInAppContent: newContent must be a string');
191
202
  return;
192
203
  }
@@ -198,14 +209,14 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
198
209
  // When sync is enabled, update both devices with the same content
199
210
  updatedDeviceContent = {
200
211
  [ANDROID]: newContent,
201
- [IOS]: newContent,
212
+ [IOS]: newContent
202
213
  };
203
214
  } else {
204
215
  // When sync is disabled, update only the current device
205
- setDeviceContent((prev) => {
216
+ setDeviceContent(prev => {
206
217
  updatedDeviceContent = {
207
218
  ...prev,
208
- [activeDevice]: newContent,
219
+ [activeDevice]: newContent
209
220
  };
210
221
  return updatedDeviceContent;
211
222
  });
@@ -214,9 +225,8 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
214
225
  setIsDirty(true);
215
226
  changeTimestampRef.current = Date.now();
216
227
 
217
- // Trigger onChange callback - pass only changed device content
218
- // This prevents parent from updating both device states when only one changed
219
- onChange?.({ [activeDevice]: newContent }, activeDevice);
228
+ // Trigger onChange callback with optional chaining
229
+ onChange?.(updatedDeviceContent, activeDevice);
220
230
 
221
231
  // Setup auto-save for independent mode
222
232
  if (autoSave && autoSaveInterval > AUTO_SAVE_CONFIG.MIN_AUTO_SAVE_INTERVAL_MS && newContent.length > CONTENT_VALIDATION.MIN_CONTENT_LENGTH) {
@@ -247,7 +257,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
247
257
  setIsDirty(true);
248
258
  changeTimestampRef.current = Date.now();
249
259
 
250
- // Trigger onChange callback - in sync mode, pass full content
260
+ // Trigger onChange callback with optional chaining
251
261
  onChange?.(updatedDeviceContent, activeDevice);
252
262
 
253
263
  // Setup auto-save
@@ -291,7 +301,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
291
301
  const currentActiveContent = deviceContent[activeDevice];
292
302
  const syncedContent = {
293
303
  [ANDROID]: currentActiveContent,
294
- [IOS]: currentActiveContent,
304
+ [IOS]: currentActiveContent
295
305
  };
296
306
 
297
307
  setDeviceContent(syncedContent);
@@ -315,16 +325,22 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
315
325
  }, [deviceContent, onSave]);
316
326
 
317
327
  // Check if content exists for current device
318
- const hasContent = useMemo(() => typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
319
- && currentContent?.trim()?.length > CONTENT_VALIDATION.MIN_CONTENT_LENGTH, [currentContent]);
328
+ const hasContent = useMemo(() => {
329
+ return typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE &&
330
+ currentContent.trim().length > CONTENT_VALIDATION.MIN_CONTENT_LENGTH;
331
+ }, [currentContent]);
320
332
 
321
333
  // Get content size for current device
322
- const getContentSize = useCallback(() => typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
323
- ? currentContent?.length
324
- : CONTENT_VALIDATION?.MIN_CONTENT_LENGTH, [currentContent]);
334
+ const getContentSize = useCallback(() => {
335
+ return typeof currentContent === CONTENT_VALIDATION.DEFAULT_CONTENT_TYPE
336
+ ? currentContent.length
337
+ : CONTENT_VALIDATION.MIN_CONTENT_LENGTH;
338
+ }, [currentContent]);
325
339
 
326
340
  // Get content for specific device
327
- const getDeviceContent = useCallback((device) => deviceContent[device] || '', [deviceContent]);
341
+ const getDeviceContent = useCallback((device) => {
342
+ return deviceContent[device] || '';
343
+ }, [deviceContent]);
328
344
 
329
345
  // Set content for specific device
330
346
  const setDeviceContent_ = useCallback((device, content) => {
@@ -344,23 +360,25 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
344
360
  // Update both devices when sync is enabled
345
361
  setDeviceContent({
346
362
  [ANDROID]: content,
347
- [IOS]: content,
363
+ [IOS]: content
348
364
  });
349
365
  } else {
350
366
  // Update specific device
351
- setDeviceContent((prev) => ({
367
+ setDeviceContent(prev => ({
352
368
  ...prev,
353
- [device]: content,
369
+ [device]: content
354
370
  }));
355
371
  }
356
372
  setIsDirty(true);
357
373
  }, [keepContentSame, ANDROID, IOS]);
358
374
 
359
375
  // Cleanup on unmount
360
- useEffect(() => () => {
361
- if (autoSaveTimerRef.current) {
362
- clearTimeout(autoSaveTimerRef.current);
363
- }
376
+ useEffect(() => {
377
+ return () => {
378
+ if (autoSaveTimerRef.current) {
379
+ clearTimeout(autoSaveTimerRef.current);
380
+ }
381
+ };
364
382
  }, []);
365
383
 
366
384
  return {
@@ -384,6 +402,6 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
384
402
  toggleContentSync,
385
403
 
386
404
  // Save management
387
- markAsSaved,
405
+ markAsSaved
388
406
  };
389
407
  };
@@ -26,4 +26,4 @@ export { HTML_EDITOR_VARIANTS, DEVICE_TYPES } from './constants';
26
26
  * - Default export (lazy): ~0KB initial bundle (loads ~400KB when needed)
27
27
  * - HTMLEditorSync: ~400KB added to initial bundle
28
28
  * - Constants: ~1KB added to initial bundle
29
- */
29
+ */