@capillarytech/creatives-library 8.0.292-alpha.11 → 8.0.292-alpha.12
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.
package/package.json
CHANGED
|
@@ -113,6 +113,8 @@ const HTMLEditor = forwardRef(({
|
|
|
113
113
|
const mainEditorRef = useRef(null);
|
|
114
114
|
const modalEditorRef = useRef(null);
|
|
115
115
|
const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
|
|
116
|
+
// InApp only: last initialContent value we applied (by value) to avoid reverting user edits on parent re-render
|
|
117
|
+
const inAppLastAppliedInitialRef = useRef({ android: '', ios: '' });
|
|
116
118
|
|
|
117
119
|
// Get the currently active editor ref based on fullscreen state
|
|
118
120
|
const getActiveEditorRef = useCallback(() => isFullscreenModalOpen ? modalEditorRef : mainEditorRef, [isFullscreenModalOpen]);
|
|
@@ -174,19 +176,22 @@ const HTMLEditor = forwardRef(({
|
|
|
174
176
|
const contentToUpdate = typeof initialContent === 'string'
|
|
175
177
|
? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
|
|
176
178
|
: initialContent;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
const newAndroid = contentToUpdate[DEVICE_TYPES.ANDROID] ?? '';
|
|
180
|
+
const newIos = contentToUpdate[DEVICE_TYPES.IOS] ?? '';
|
|
181
|
+
const last = inAppLastAppliedInitialRef.current;
|
|
182
|
+
// InApp: only sync when initialContent value actually changed (first load or template switch)
|
|
183
|
+
// so we don't revert user typing when parent re-renders with same content (new ref)
|
|
184
|
+
const valueChanged = newAndroid !== (last.android ?? '') || newIos !== (last.ios ?? '');
|
|
185
|
+
if (!valueChanged) return;
|
|
186
|
+
if (!inAppContent.updateContent) return;
|
|
187
|
+
const activeDevice = inAppContent.activeDevice;
|
|
188
|
+
const newContent = activeDevice in contentToUpdate
|
|
189
|
+
? (contentToUpdate[activeDevice] ?? '')
|
|
190
|
+
: (contentToUpdate[DEVICE_TYPES.ANDROID] ?? '');
|
|
191
|
+
inAppContent.updateContent(newContent, true);
|
|
192
|
+
inAppLastAppliedInitialRef.current = { android: newAndroid, ios: newIos };
|
|
188
193
|
}
|
|
189
|
-
}, [initialContent, isEmailVariant, isInAppVariant]);
|
|
194
|
+
}, [initialContent, isEmailVariant, isInAppVariant, emailContent, inAppContent]);
|
|
190
195
|
// Handle context change for tag API calls
|
|
191
196
|
// If variant is INAPP, use SMS layout; otherwise use the channel (EMAIL)
|
|
192
197
|
const handleContextChange = useCallback((contextData) => {
|
|
@@ -95,52 +95,51 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
|
|
|
95
95
|
const previousContentRef = useRef({ android: '', ios: '' });
|
|
96
96
|
|
|
97
97
|
// Update content when initialContent prop changes (for edit mode)
|
|
98
|
-
//
|
|
98
|
+
// Only update when: first load, empty->content transition, or template switch (value changed).
|
|
99
|
+
// Value-based comparison prevents reverting user edits when parent re-renders with same content (new ref).
|
|
99
100
|
useEffect(() => {
|
|
100
101
|
const newAndroidContent = initialContent?.[ANDROID];
|
|
101
102
|
const newIosContent = initialContent?.[IOS];
|
|
103
|
+
const prevRef = previousContentRef.current;
|
|
102
104
|
|
|
103
|
-
// Check if this is meaningful initialContent (has actual content)
|
|
104
105
|
const hasMeaningfulContent = (newAndroidContent && newAndroidContent.trim() !== '')
|
|
105
106
|
|| (newIosContent && newIosContent.trim() !== '');
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
&& (!previousContentRef.current.ios || previousContentRef.current.ios.trim() === '');
|
|
108
|
+
const wasEmpty = (!prevRef.android || prevRef.android.trim() === '')
|
|
109
|
+
&& (!prevRef.ios || prevRef.ios.trim() === '');
|
|
110
110
|
const isTransitioningToContent = wasEmpty && hasMeaningfulContent;
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// This prevents the effect from overriding user edits during active editing
|
|
116
|
-
// while still allowing content to load properly in library mode
|
|
117
|
-
if (!initialContentLoadedRef.current || isTransitioningToContent) {
|
|
118
|
-
if (hasMeaningfulContent) {
|
|
119
|
-
// Mark as loaded to prevent future updates from overriding user edits
|
|
120
|
-
initialContentLoadedRef.current = true;
|
|
121
|
-
|
|
122
|
-
setDeviceContent((prev) => {
|
|
123
|
-
let updated = false;
|
|
124
|
-
const updatedContent = { ...prev };
|
|
125
|
-
|
|
126
|
-
if (newAndroidContent !== undefined && newAndroidContent !== prev[ANDROID]) {
|
|
127
|
-
updatedContent[ANDROID] = newAndroidContent;
|
|
128
|
-
updated = true;
|
|
129
|
-
}
|
|
130
|
-
if (newIosContent !== undefined && newIosContent !== prev[IOS]) {
|
|
131
|
-
updatedContent[IOS] = newIosContent;
|
|
132
|
-
updated = true;
|
|
133
|
-
}
|
|
112
|
+
// Value-based: did initialContent content actually change? (template switch)
|
|
113
|
+
const initialValueChanged = (newAndroidContent || '') !== (prevRef.android || '')
|
|
114
|
+
|| (newIosContent || '') !== (prevRef.ios || '');
|
|
134
115
|
|
|
135
|
-
|
|
136
|
-
|
|
116
|
+
const shouldSync = !initialContentLoadedRef.current
|
|
117
|
+
|| isTransitioningToContent
|
|
118
|
+
|| initialValueChanged;
|
|
137
119
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
120
|
+
if (shouldSync && hasMeaningfulContent) {
|
|
121
|
+
initialContentLoadedRef.current = true;
|
|
122
|
+
|
|
123
|
+
setDeviceContent((prev) => {
|
|
124
|
+
let updated = false;
|
|
125
|
+
const updatedContent = { ...prev };
|
|
126
|
+
|
|
127
|
+
if (newAndroidContent !== undefined && newAndroidContent !== prev[ANDROID]) {
|
|
128
|
+
updatedContent[ANDROID] = newAndroidContent;
|
|
129
|
+
updated = true;
|
|
130
|
+
}
|
|
131
|
+
if (newIosContent !== undefined && newIosContent !== prev[IOS]) {
|
|
132
|
+
updatedContent[IOS] = newIosContent;
|
|
133
|
+
updated = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return updated ? updatedContent : prev;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
previousContentRef.current = {
|
|
140
|
+
android: newAndroidContent || '',
|
|
141
|
+
ios: newIosContent || '',
|
|
142
|
+
};
|
|
144
143
|
}
|
|
145
144
|
}, [initialContent, ANDROID, IOS]);
|
|
146
145
|
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# DeviceToggle – Click flow and when clicks are blocked
|
|
2
|
-
|
|
3
|
-
## 1. Click flow (button → end)
|
|
4
|
-
|
|
5
|
-
1. **User clicks Android or iOS tab**
|
|
6
|
-
- `CapButton` receives the click and runs
|
|
7
|
-
- `onClick={() => handleDeviceClick(DEVICE_TYPES.ANDROID)}` or
|
|
8
|
-
- `onClick={() => handleDeviceClick(DEVICE_TYPES.IOS)}`.
|
|
9
|
-
|
|
10
|
-
2. **`handleDeviceClick(device)`** (in `DeviceToggle/index.js`):
|
|
11
|
-
- Only does something if **both**:
|
|
12
|
-
- `onDeviceChange` is truthy (passed from parent), and
|
|
13
|
-
- `device !== activeDevice` (clicked tab is not already active).
|
|
14
|
-
- If both hold: calls `onDeviceChange(device)`.
|
|
15
|
-
|
|
16
|
-
3. **Parent wiring** (`HTMLEditor.js`):
|
|
17
|
-
- `onDeviceChange={switchDevice}`
|
|
18
|
-
- So `onDeviceChange` is the `switchDevice` from `useInAppContent`.
|
|
19
|
-
|
|
20
|
-
4. **`switchDevice(device)`** (`hooks/useInAppContent.js`):
|
|
21
|
-
- `validDevices = [ANDROID, IOS]` (from `DEVICE_TYPES`: `'android'`, `'ios'`).
|
|
22
|
-
- If `device !== activeDevice` and `validDevices.includes(device)` → `setActiveDevice(device)`.
|
|
23
|
-
- If `device` is invalid → logs a warning and does nothing.
|
|
24
|
-
|
|
25
|
-
5. **Result**
|
|
26
|
-
- `activeDevice` updates → DeviceToggle and preview re-render with the new device; no explicit “block” inside this flow.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## 2. When the click is effectively “blocked” or does nothing
|
|
31
|
-
|
|
32
|
-
- **No handler**
|
|
33
|
-
If the parent does not pass `onDeviceChange` (or passes `undefined`/`null`), `handleDeviceClick` never calls anything. The button still receives the click; there is no visual “disabled” in DeviceToggle.
|
|
34
|
-
|
|
35
|
-
- **Same device**
|
|
36
|
-
If the user clicks the already-active tab (`device === activeDevice`), `handleDeviceClick` does nothing by design.
|
|
37
|
-
|
|
38
|
-
- **Invalid device**
|
|
39
|
-
If `device` is not `'android'` or `'ios'` (e.g. wrong constant when using the repo as a library), `switchDevice` only warns and does not update state.
|
|
40
|
-
|
|
41
|
-
- **Click never reaches the button (layout/CSS)**
|
|
42
|
-
Another element is on top of the tab and captures the click (see below). This is the likely cause when “the iOS button is not clickable in edit mode” when using the repo as a library.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 3. Why the iOS button can be unclickable in library (edit) mode
|
|
47
|
-
|
|
48
|
-
In **library mode** (`isFullMode === false`), InApp uses a special layout:
|
|
49
|
-
|
|
50
|
-
- **Header**: flex row with **DeviceToggle** first and **EditorToolbar** second.
|
|
51
|
-
- **DeviceToggle** has `flex: 1` (takes remaining space).
|
|
52
|
-
- **EditorToolbar** gets `position: absolute` (no `top`/`right`/`left`), so it is taken out of flow.
|
|
53
|
-
|
|
54
|
-
Because the toolbar is out of flow, the only flex child in flow is the DeviceToggle, so it expands to the full width of the header. The absolutely positioned toolbar is then drawn at its “static” position, which is the **right side** of the header. So the toolbar sits **on top of the right part of the header**, which is exactly where the **iOS** tab and the “Keep content same” checkbox are. Clicks on the iOS tab are then hit by the toolbar first, so the **iOS button appears not clickable** even though the handler logic is fine.
|
|
55
|
-
|
|
56
|
-
So the problem is **not** that the button is disabled or that the click handler is blocked; it’s that **the EditorToolbar overlaps the iOS tab in library mode** and steals the click.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 4. Recommended fix (library mode)
|
|
61
|
-
|
|
62
|
-
Ensure the toolbar does not overlap the DeviceToggle:
|
|
63
|
-
|
|
64
|
-
- Either **remove** the library-mode override that sets `position: absolute` on the toolbar so the header stays in normal flex flow, **or**
|
|
65
|
-
- If you keep `position: absolute` for the toolbar:
|
|
66
|
-
- Give the header a **positioning context** (e.g. `position: relative`).
|
|
67
|
-
- Pin the toolbar to the right (e.g. `right: 0; top: 0`).
|
|
68
|
-
- Reserve space for the toolbar so DeviceToggle doesn’t extend under it (e.g. `padding-right` on the header or a fixed/min width for the toolbar and corresponding margin on the DeviceToggle).
|
|
69
|
-
|
|
70
|
-
With that, the DeviceToggle (including the iOS tab) remains fully clickable in edit/library mode.
|