@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.56
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/constants/unified.js +9 -4
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -12
- package/utils/templateVarUtils.js +16 -0
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CapActionButton/index.js +73 -60
- package/v2Components/CapActionButton/index.scss +44 -28
- package/v2Components/CapActionButton/messages.js +7 -3
- package/v2Components/CapActionButton/tests/index.test.js +17 -1
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
- package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/actions.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +8 -0
- package/v2Components/CommonTestAndPreview/index.js +55 -3
- package/v2Components/CommonTestAndPreview/reducer.js +20 -0
- package/v2Components/CommonTestAndPreview/sagas.js +28 -0
- package/v2Components/CommonTestAndPreview/selectors.js +6 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +46 -320
- package/v2Components/FormBuilder/Functional/channels/registry.js +0 -2
- package/v2Components/FormBuilder/Functional/constants.js +8 -43
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +28 -175
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +6 -75
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +20 -10
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +1 -9
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +3 -20
- package/v2Components/FormBuilder/Functional/layout/Section.js +1 -6
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +2 -9
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -17
- package/v2Components/FormBuilder/index.js +12 -34
- package/v2Components/FormBuilder/tests/entryGate.test.js +7 -94
- package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +65 -4
- package/v2Containers/CommunicationFlow/index.js +16 -23
- package/v2Containers/CommunicationFlow/messages.js +11 -3
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
- package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/index.js +29 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
- package/v2Containers/MobilePush/Create/index.js +18 -1
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
- package/v2Containers/MobilePush/Edit/index.js +18 -1
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
- package/v2Containers/MobilePush/commonMethods.js +49 -1
- package/v2Containers/MobilePushNew/index.js +29 -4
- package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
- package/v2Containers/MobilePushNew/utils.js +34 -1
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +34 -0
- package/v2Containers/Rcs/index.scss +15 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
- package/v2Containers/Rcs/tests/index.test.js +67 -0
- package/v2Containers/Sms/Create/index.js +3 -1
- package/v2Containers/Sms/Edit/index.js +25 -0
- package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
- package/v2Containers/Viber/index.js +24 -1
- package/v2Containers/Viber/tests/index.test.js +103 -0
- package/v2Containers/WebPush/Create/index.js +19 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
- package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
- package/v2Containers/Whatsapp/tests/index.test.js +115 -0
- package/v2Containers/Zalo/index.js +28 -1
- package/v2Containers/Zalo/tests/index.test.js +99 -0
- package/v2Containers/mockdata.js +25 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +0 -10
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +0 -82
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +0 -74
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +0 -28
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +0 -19
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +0 -45
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +0 -162
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +0 -37
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +0 -70
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +0 -274
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +0 -251
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +0 -89
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +0 -440
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +0 -430
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +0 -282
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +0 -141
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +0 -306
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +0 -151
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +0 -342
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +0 -110
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +0 -459
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FormBuilderShell — the functional FormBuilder (Phase 1: SMS
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* channel containers unchanged.
|
|
2
|
+
* FormBuilderShell — the functional FormBuilder (Phase 1: SMS). Owns form state in a
|
|
3
|
+
* useReducer, bridges to the legacy formData contract via the codec, and validates
|
|
4
|
+
* through the channel adapter. Stage A keeps the channel containers unchanged.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import React, {
|
|
@@ -11,19 +10,14 @@ import React, {
|
|
|
11
10
|
import PropTypes from 'prop-types';
|
|
12
11
|
import debounce from 'lodash/debounce';
|
|
13
12
|
import isEqual from 'lodash/isEqual';
|
|
14
|
-
import cloneDeep from 'lodash/cloneDeep';
|
|
15
13
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
16
|
-
import CapModal from '@capillarytech/cap-ui-library/CapModal';
|
|
17
|
-
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
18
14
|
import formReducer, {
|
|
19
15
|
createInitialState,
|
|
20
16
|
hydrate,
|
|
21
17
|
fieldChanged,
|
|
22
|
-
setActiveTab,
|
|
23
|
-
reconcileSchema,
|
|
24
18
|
} from './core/store/formReducer';
|
|
25
19
|
import { toLegacy, fromLegacy } from './core/store/toLegacyFormData';
|
|
26
|
-
import initializeFormState
|
|
20
|
+
import initializeFormState from './core/schema/initializeFormState';
|
|
27
21
|
import { getChannelAdapter } from './channels/registry';
|
|
28
22
|
import { createSmsRegistry } from './renderers/smsRenderers';
|
|
29
23
|
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
@@ -32,9 +26,7 @@ import { SMS } from '../../../v2Containers/CreativesContainer/constants';
|
|
|
32
26
|
import { DEFAULT as DEFAULT_MODULE, EMBEDDED } from '../../../constants/unified';
|
|
33
27
|
import formMessages from '../messages';
|
|
34
28
|
import SchemaForm from './layout/SchemaForm';
|
|
35
|
-
import {
|
|
36
|
-
ACTIVE_TAB_INDEX, DATA_COMMIT_EVENTS, DATA_COMMIT_FIELD_TYPES, EDITOR_ERROR_KIND, HIGH_FREQ_FIELDS,
|
|
37
|
-
} from './constants';
|
|
29
|
+
import { ACTIVE_TAB_INDEX, EDITOR_ERROR_KIND, HIGH_FREQ_FIELDS } from './constants';
|
|
38
30
|
|
|
39
31
|
const isNonEmpty = (obj) => obj && Object.keys(obj).length > 0;
|
|
40
32
|
|
|
@@ -42,6 +34,9 @@ const buildInitialState = (schema, parentFormData, channel) => (isNonEmpty(paren
|
|
|
42
34
|
? fromLegacy(parentFormData, { channel })
|
|
43
35
|
: initializeFormState(schema || {}));
|
|
44
36
|
|
|
37
|
+
// Phase 1: only SMS has renderers. Later channels return their own registry.
|
|
38
|
+
const registryFor = () => createSmsRegistry();
|
|
39
|
+
|
|
45
40
|
const FormBuilderShell = (props) => {
|
|
46
41
|
const {
|
|
47
42
|
schema,
|
|
@@ -63,19 +58,11 @@ const FormBuilderShell = (props) => {
|
|
|
63
58
|
waitEventContextTags,
|
|
64
59
|
restrictPersonalization,
|
|
65
60
|
refs,
|
|
66
|
-
currentTab: currentTabProp,
|
|
67
|
-
showModal,
|
|
68
|
-
modal,
|
|
69
61
|
} = props;
|
|
70
62
|
|
|
71
63
|
const channel = (channelProp || schema?.channel || SMS).toUpperCase();
|
|
72
64
|
const adapter = getChannelAdapter(channel);
|
|
73
|
-
|
|
74
|
-
// stays on its original factory.
|
|
75
|
-
const registry = useMemo(
|
|
76
|
-
() => (adapter?.createRegistry ? adapter.createRegistry() : createSmsRegistry()),
|
|
77
|
-
[channel],
|
|
78
|
-
);
|
|
65
|
+
const registry = useMemo(() => registryFor(), [channel]);
|
|
79
66
|
|
|
80
67
|
const [state, dispatch] = useReducer(formReducer, channel, createInitialState);
|
|
81
68
|
const [errorData, setErrorData] = useState({});
|
|
@@ -84,10 +71,6 @@ const FormBuilderShell = (props) => {
|
|
|
84
71
|
// is still computed + emitted via onFormValidityChange on mount; only the inline
|
|
85
72
|
// display is gated. Can also be driven by the container's checkValidation prop.
|
|
86
73
|
const [internalCheckValidation, setInternalCheckValidation] = useState(false);
|
|
87
|
-
// Container-driven confirmation modal (MOBILEPUSH single-platform save). The
|
|
88
|
-
// container owns the content (setModalContent -> showModal/modal props); the
|
|
89
|
-
// shell owns visibility after a click, mirroring Classic's local showModal state.
|
|
90
|
-
const [modalVisible, setModalVisible] = useState(false);
|
|
91
74
|
|
|
92
75
|
// refs to latest values for use inside stable/debounced callbacks
|
|
93
76
|
const stateRef = useRef(state);
|
|
@@ -97,25 +80,6 @@ const FormBuilderShell = (props) => {
|
|
|
97
80
|
const lastEmittedRef = useRef(null);
|
|
98
81
|
const didInitRef = useRef(false);
|
|
99
82
|
const prevStartValidationRef = useRef(startValidation);
|
|
100
|
-
const prevSchemaRef = useRef(null);
|
|
101
|
-
// Deep snapshot of the last parent formData we processed. The MOBILEPUSH
|
|
102
|
-
// containers mutate their formData IN PLACE (copy-content, tag insertion), so a
|
|
103
|
-
// reference-keyed effect would miss genuine pushes — we deep-compare on every
|
|
104
|
-
// render instead, exactly like Classic's componentWillReceiveProps.
|
|
105
|
-
const lastSeenParentRef = useRef(null);
|
|
106
|
-
// Platform validity flags from the last save-validation (MOBILEPUSH android/ios
|
|
107
|
-
// modal contract): Classic keeps these in state only after a validate pass.
|
|
108
|
-
const platformRef = useRef(null);
|
|
109
|
-
// Content snapshot of the tags prop for the tags-change re-validation
|
|
110
|
-
// (Classic.js:569-578) — containers rebuild the array each render, so identity
|
|
111
|
-
// is useless. Seeded by the init effect (init already validates with those tags).
|
|
112
|
-
const lastTagsRef = useRef(null);
|
|
113
|
-
const lastNextStateRef = useRef(null);
|
|
114
|
-
// GOLDEN parity (pinned by mpush.characterization): Classic emits
|
|
115
|
-
// this.state.tabCount in onChange, which starts at 1 and is mirrored from the
|
|
116
|
-
// tabCount PROP only when hydrating from a parent formData push (edit sync) —
|
|
117
|
-
// so the MPUSH create flow emits tabCount 1 even though the containers pass 2.
|
|
118
|
-
const emittedTabCountRef = useRef(1);
|
|
119
83
|
|
|
120
84
|
// Stable handler identities for the memoized FieldSlot: these wrappers delegate to
|
|
121
85
|
// the latest handler bodies (via a ref), so React.memo can bail out on sibling changes.
|
|
@@ -127,20 +91,11 @@ const FormBuilderShell = (props) => {
|
|
|
127
91
|
const isEmbedded = location?.query?.type === EMBEDDED;
|
|
128
92
|
const currentModule = tagModule || location?.query?.module || DEFAULT_MODULE;
|
|
129
93
|
|
|
130
|
-
const
|
|
131
|
-
const currentTab = activeTabIndex + 1; // legacy 1-based (MOBILEPUSH: 1=Android, 2=iOS)
|
|
132
|
-
const tabCount = state?.tabs?.length || 1;
|
|
133
|
-
const hasTabs = Boolean(adapter?.config?.features?.tabs);
|
|
134
|
-
|
|
135
|
-
const runValidate = (legacy, extra = {}) => adapter.validate(legacy, {
|
|
94
|
+
const runValidate = (legacy) => adapter.validate(legacy, {
|
|
136
95
|
tags: propsRef.current.tags,
|
|
137
96
|
isFullMode,
|
|
138
97
|
isEmbedded,
|
|
139
98
|
currentModule,
|
|
140
|
-
restrictPersonalization: propsRef.current.restrictPersonalization,
|
|
141
|
-
currentTab: (stateRef.current?.meta?.activeTabIndex ?? ACTIVE_TAB_INDEX) + 1,
|
|
142
|
-
schema: propsRef.current.schema,
|
|
143
|
-
...extra,
|
|
144
99
|
});
|
|
145
100
|
|
|
146
101
|
// Footer state (mirrors Classic's `liquidErrorMessage`). Holds the last
|
|
@@ -148,66 +103,45 @@ const FormBuilderShell = (props) => {
|
|
|
148
103
|
// clobber each other before forwarding to the container's showLiquidErrorInFooter.
|
|
149
104
|
const footerRef = useRef({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
|
|
150
105
|
const liquidEnabled = Boolean(adapter.config?.features?.liquid);
|
|
151
|
-
// Classic passes null as the footer's tab argument for SMS and the live 1-based
|
|
152
|
-
// currentTab for every other channel (Classic.js:1352, 1487).
|
|
153
|
-
const footerTabArg = () => (adapter.config?.footerTabArg === 'currentTab'
|
|
154
|
-
? (stateRef.current?.meta?.activeTabIndex ?? ACTIVE_TAB_INDEX) + 1
|
|
155
|
-
: null);
|
|
156
106
|
|
|
157
107
|
const pushFooter = (next) => {
|
|
158
108
|
footerRef.current = { ...footerRef.current, ...next };
|
|
159
109
|
if (propsRef.current.showLiquidErrorInFooter) {
|
|
160
|
-
propsRef.current.showLiquidErrorInFooter(
|
|
110
|
+
propsRef.current.showLiquidErrorInFooter(
|
|
111
|
+
footerRef.current,
|
|
112
|
+
channel === SMS ? null : propsRef.current.currentTab,
|
|
113
|
+
);
|
|
161
114
|
}
|
|
162
115
|
};
|
|
163
116
|
|
|
164
|
-
// Footer-eligible body message for `legacy`, or [] when none.
|
|
165
|
-
//
|
|
166
|
-
// the old flow (message goes to the footer).
|
|
167
|
-
|
|
168
|
-
// descriptor derives everything from `legacy` and ignores the extras.
|
|
169
|
-
const standardFooterMsg = (legacy, result) => {
|
|
117
|
+
// Footer-eligible body message (brace/missing-tag) for `legacy`, or [] when none.
|
|
118
|
+
// Full mode shows the brace error inline instead, so the footer is suppressed there;
|
|
119
|
+
// library mode keeps the old flow (message goes to the footer).
|
|
120
|
+
const standardFooterMsg = (legacy) => {
|
|
170
121
|
if (isFullMode || !adapter.getEditorErrorDescriptor || !intl) return [];
|
|
171
122
|
const descriptor = adapter.getEditorErrorDescriptor(legacy, {
|
|
172
123
|
tags: propsRef.current.tags,
|
|
173
124
|
isFullMode,
|
|
174
125
|
currentModule,
|
|
175
|
-
errorData: result?.errorData,
|
|
176
|
-
currentTab: (stateRef.current?.meta?.activeTabIndex ?? ACTIVE_TAB_INDEX) + 1,
|
|
177
|
-
schema: propsRef.current.schema,
|
|
178
|
-
checkValidation: internalCheckValidation
|
|
179
|
-
|| Boolean(propsRef.current.checkValidation)
|
|
180
|
-
|| Boolean(propsRef.current.startValidation),
|
|
181
|
-
restrictPersonalization: propsRef.current.restrictPersonalization,
|
|
182
126
|
});
|
|
183
|
-
|
|
184
|
-
if (descriptor.text) return [descriptor.text];
|
|
185
|
-
return [intl.formatMessage(descriptor.message, descriptor.values)];
|
|
127
|
+
return descriptor ? [intl.formatMessage(descriptor.message, descriptor.values)] : [];
|
|
186
128
|
};
|
|
187
129
|
|
|
188
130
|
const emitValidity = (result, legacy) => {
|
|
189
131
|
setErrorData(result.errorData);
|
|
190
|
-
if (result.platform) platformRef.current = result.platform;
|
|
191
132
|
if (propsRef.current.onFormValidityChange) {
|
|
192
133
|
propsRef.current.onFormValidityChange(result.isValid, result.errorData);
|
|
193
134
|
}
|
|
194
135
|
// Footer: clear standard errors when the form is valid; otherwise surface the
|
|
195
|
-
//
|
|
136
|
+
// brace/missing-tag message (empty/generic => none). Liquid errors are preserved.
|
|
196
137
|
if (liquidEnabled) {
|
|
197
|
-
pushFooter({ STANDARD_ERROR_MSG: result.isValid ? [] : standardFooterMsg(legacy
|
|
138
|
+
pushFooter({ STANDARD_ERROR_MSG: result.isValid ? [] : standardFooterMsg(legacy) });
|
|
198
139
|
}
|
|
199
140
|
};
|
|
200
141
|
|
|
201
142
|
const emitChange = (legacy, field) => {
|
|
202
143
|
lastEmittedRef.current = legacy;
|
|
203
|
-
if (propsRef.current.onChange)
|
|
204
|
-
propsRef.current.onChange(
|
|
205
|
-
legacy,
|
|
206
|
-
emittedTabCountRef.current,
|
|
207
|
-
(stateRef.current?.meta?.activeTabIndex ?? ACTIVE_TAB_INDEX) + 1,
|
|
208
|
-
field,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
144
|
+
if (propsRef.current.onChange) propsRef.current.onChange(legacy, 1, 1, field);
|
|
211
145
|
};
|
|
212
146
|
|
|
213
147
|
const debouncedEmit = useMemo(
|
|
@@ -217,130 +151,30 @@ const FormBuilderShell = (props) => {
|
|
|
217
151
|
useEffect(() => () => debouncedEmit.cancel(), [debouncedEmit]);
|
|
218
152
|
|
|
219
153
|
// ---- initialize once the (possibly async) schema is ready, then validate+emit ----
|
|
220
|
-
const schemaReady = Boolean(schema?.standalone
|
|
154
|
+
const schemaReady = Boolean(schema?.standalone);
|
|
221
155
|
useEffect(() => {
|
|
222
156
|
if (schemaReady && !didInitRef.current) {
|
|
223
157
|
didInitRef.current = true;
|
|
224
|
-
prevSchemaRef.current = schema;
|
|
225
158
|
const init = buildInitialState(schema, propsRef.current.formData, channel);
|
|
226
|
-
if (isNonEmpty(propsRef.current.formData)) {
|
|
227
|
-
lastSeenParentRef.current = cloneDeep(propsRef.current.formData);
|
|
228
|
-
// Classic mirrors the tabCount prop only alongside a parent formData sync.
|
|
229
|
-
emittedTabCountRef.current = propsRef.current.tabCount || init.tabs?.length || 1;
|
|
230
|
-
}
|
|
231
159
|
dispatch(hydrate(init));
|
|
232
|
-
lastTagsRef.current = cloneDeep(propsRef.current.tags || null);
|
|
233
160
|
const legacy = toLegacy(init);
|
|
234
161
|
emitValidity(runValidate(legacy), legacy);
|
|
235
162
|
}
|
|
236
163
|
}, [schemaReady]);
|
|
237
164
|
|
|
238
|
-
//
|
|
239
|
-
// and runs on every schema change (542-559), deep-unequal parent push (516-537)
|
|
240
|
-
// and tags change (569-578) — display stays gated. The MPUSH containers gate
|
|
241
|
-
// their save flows on that maintained validity (Create/index.js:101), so these
|
|
242
|
-
// background emissions are load-bearing. SMS keeps its shipped behavior.
|
|
243
|
-
const validateOnExternalChange = Boolean(adapter.config?.features?.validateOnExternalChange);
|
|
244
|
-
|
|
245
|
-
// Background emissions must read the POST-dispatch state (a stateRef snapshot can
|
|
246
|
-
// be stale mid container-setState-cascade): hydration-family effects only ARM this
|
|
247
|
-
// flag; the [state]-keyed effect below emits once the new state has rendered.
|
|
248
|
-
const pendingBackgroundValidateRef = useRef(false);
|
|
249
|
-
|
|
250
|
-
// ---- reconcile on a genuine schema change (CTA splices, text<->image swap) ----
|
|
251
|
-
// Runs inside the reducer (reconcileSchema) — see the action's note on stale snapshots.
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
if (!didInitRef.current || !schema || prevSchemaRef.current === schema) return;
|
|
254
|
-
prevSchemaRef.current = schema;
|
|
255
|
-
dispatch(reconcileSchema(schema, reconcileFormState));
|
|
256
|
-
// Classic re-validates on EVERY schema change (Classic.js:542-559).
|
|
257
|
-
const validationActive = propsRef.current.startValidation || internalCheckValidation || Boolean(propsRef.current.checkValidation);
|
|
258
|
-
if (validationActive || validateOnExternalChange) {
|
|
259
|
-
pendingBackgroundValidateRef.current = true;
|
|
260
|
-
}
|
|
261
|
-
}, [schema]);
|
|
262
|
-
|
|
263
|
-
// ---- re-hydrate on a GENUINE external parent push (edit load / discard / copy) ----
|
|
264
|
-
// No dependency array on purpose: the parent may mutate the same formData object
|
|
265
|
-
// in place (MOBILEPUSH copy-content, tag insertion), which a [parentFormData]-keyed
|
|
266
|
-
// effect would never see. The deep-compare guard keeps this a no-op on quiet renders.
|
|
165
|
+
// ---- re-hydrate on a GENUINE external parent push (edit load / discard) ----
|
|
267
166
|
useEffect(() => {
|
|
268
167
|
if (!didInitRef.current || !isNonEmpty(parentFormData)) return;
|
|
269
|
-
if (isEqual(parentFormData, lastSeenParentRef.current)) return; // nothing new from the parent
|
|
270
|
-
lastSeenParentRef.current = cloneDeep(parentFormData);
|
|
271
168
|
const current = toLegacy(stateRef.current);
|
|
272
169
|
const isEcho = isEqual(parentFormData, lastEmittedRef.current) || isEqual(parentFormData, current);
|
|
273
|
-
if (!isEcho) {
|
|
274
|
-
|
|
275
|
-
// Classic mirrors the tabCount prop only alongside a parent formData sync.
|
|
276
|
-
emittedTabCountRef.current = propsRef.current.tabCount || emittedTabCountRef.current;
|
|
277
|
-
// Classic validates on every deep-unequal parent push (Classic.js:516-537) —
|
|
278
|
-
// this is what turns the container's isFormValid true before Create/Done.
|
|
279
|
-
if (validateOnExternalChange) {
|
|
280
|
-
pendingBackgroundValidateRef.current = true;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// ---- background validity emission from the SETTLED state (see flag above) ----
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
if (!didInitRef.current || !pendingBackgroundValidateRef.current) return;
|
|
288
|
-
pendingBackgroundValidateRef.current = false;
|
|
289
|
-
const legacy = toLegacy(state);
|
|
290
|
-
emitValidity(runValidate(legacy), legacy);
|
|
291
|
-
}, [state]);
|
|
292
|
-
|
|
293
|
-
// ---- re-validate when the tag catalog changes (Classic.js:569-578) ----
|
|
294
|
-
// The containers rebuild the tags array every render (Create/index.js:1928), so
|
|
295
|
-
// compare by content, not identity. Emits validity only; display stays gated.
|
|
296
|
-
useEffect(() => {
|
|
297
|
-
if (!didInitRef.current || !validateOnExternalChange) return;
|
|
298
|
-
const nextTags = propsRef.current.tags;
|
|
299
|
-
if (!Array.isArray(nextTags) || !nextTags.length) return;
|
|
300
|
-
if (isEqual(nextTags, lastTagsRef.current)) return;
|
|
301
|
-
lastTagsRef.current = cloneDeep(nextTags);
|
|
302
|
-
const legacy = toLegacy(stateRef.current);
|
|
303
|
-
emitValidity(runValidate(legacy), legacy);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// ---- container-driven tab position (MOBILEPUSH: Create forces iOS when Android unsupported) ----
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
if (!hasTabs || typeof currentTabProp !== 'number') return;
|
|
309
|
-
dispatch(setActiveTab(currentTabProp - 1));
|
|
310
|
-
}, [currentTabProp, hasTabs]);
|
|
311
|
-
|
|
312
|
-
// ---- container-driven modal visibility (mirrors Classic's props->state modal sync) ----
|
|
313
|
-
useEffect(() => {
|
|
314
|
-
setModalVisible(Boolean(showModal));
|
|
315
|
-
}, [showModal, modal]);
|
|
170
|
+
if (!isEcho) dispatch(hydrate(fromLegacy(parentFormData, { channel })));
|
|
171
|
+
}, [parentFormData]);
|
|
316
172
|
|
|
317
173
|
// Submit a form that passed sync validation (mirrors Classic onSubmitWrapper).
|
|
318
|
-
//
|
|
319
|
-
// through adapter.runSubmitPipeline; otherwise the default liquid flow applies:
|
|
320
|
-
// full mode submits directly; outside it, liquid-supported channels run liquid-tag
|
|
174
|
+
// Full mode submits directly; outside it, liquid-supported channels run liquid-tag
|
|
321
175
|
// validation first — a clean result submits, an error goes to the footer and blocks.
|
|
322
|
-
const submitValidForm = (legacy, validErrorData
|
|
176
|
+
const submitValidForm = (legacy, validErrorData) => {
|
|
323
177
|
const currentProps = propsRef.current;
|
|
324
|
-
|
|
325
|
-
if (typeof adapter.runSubmitPipeline === 'function') {
|
|
326
|
-
adapter.runSubmitPipeline(legacy, {
|
|
327
|
-
isFullMode,
|
|
328
|
-
singleTab,
|
|
329
|
-
currentTab: (stateRef.current?.meta?.activeTabIndex ?? ACTIVE_TAB_INDEX) + 1,
|
|
330
|
-
baseLanguage: currentProps.baseLanguage,
|
|
331
|
-
getLiquidTags: currentProps.actions?.getLiquidTags,
|
|
332
|
-
formatMessage: currentProps.intl?.formatMessage,
|
|
333
|
-
messages: formMessages,
|
|
334
|
-
}, {
|
|
335
|
-
onSubmit: (payload) => currentProps.onSubmit && currentProps.onSubmit(payload),
|
|
336
|
-
pushFooter,
|
|
337
|
-
stopValidation: () => currentProps.stopValidation && currentProps.stopValidation(),
|
|
338
|
-
onFormValidityChange: (isValid, nextErrorData) => currentProps.onFormValidityChange
|
|
339
|
-
&& currentProps.onFormValidityChange(isValid, nextErrorData !== undefined ? nextErrorData : validErrorData),
|
|
340
|
-
});
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
178
|
const runLiquid = liquidEnabled && !isFullMode;
|
|
345
179
|
const getLiquidTags = currentProps.actions?.getLiquidTags;
|
|
346
180
|
if (!runLiquid || typeof getLiquidTags !== 'function') {
|
|
@@ -367,28 +201,18 @@ const FormBuilderShell = (props) => {
|
|
|
367
201
|
});
|
|
368
202
|
};
|
|
369
203
|
|
|
370
|
-
// ---- startValidation rising edge -> validate, then save
|
|
204
|
+
// ---- startValidation rising edge -> validate, then save or stop ----
|
|
371
205
|
useEffect(() => {
|
|
372
206
|
if (startValidation && !prevStartValidationRef.current) {
|
|
373
207
|
setInternalCheckValidation(true); // reveal field error messages from now on
|
|
374
208
|
debouncedEmit.flush();
|
|
375
209
|
const legacy = toLegacy(stateRef.current);
|
|
376
|
-
const result = runValidate(legacy
|
|
377
|
-
|
|
378
|
-
if (result.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// stopValidation, no submit. The modal is the only continuation.
|
|
383
|
-
setErrorData(result.errorData);
|
|
384
|
-
if (propsRef.current.setModalContent) propsRef.current.setModalContent(result.singlePlatformPrompt);
|
|
385
|
-
} else {
|
|
386
|
-
emitValidity(result, legacy);
|
|
387
|
-
if (result.isValid) {
|
|
388
|
-
submitValidForm(legacy, result.errorData);
|
|
389
|
-
} else if (propsRef.current.stopValidation) {
|
|
390
|
-
propsRef.current.stopValidation();
|
|
391
|
-
}
|
|
210
|
+
const result = runValidate(legacy);
|
|
211
|
+
emitValidity(result, legacy);
|
|
212
|
+
if (result.isValid) {
|
|
213
|
+
submitValidForm(legacy, result.errorData);
|
|
214
|
+
} else if (propsRef.current.stopValidation) {
|
|
215
|
+
propsRef.current.stopValidation();
|
|
392
216
|
}
|
|
393
217
|
}
|
|
394
218
|
prevStartValidationRef.current = startValidation;
|
|
@@ -400,20 +224,18 @@ const FormBuilderShell = (props) => {
|
|
|
400
224
|
const resolvedFieldErrors = useMemo(() => {
|
|
401
225
|
if (!isFullMode) return {}; // library mode: unchanged old flow (footer only)
|
|
402
226
|
const editorId = adapter.config?.fieldIds?.editor;
|
|
403
|
-
const editorError = editorId && errorData?.[
|
|
227
|
+
const editorError = editorId && errorData?.[ACTIVE_TAB_INDEX]?.[editorId];
|
|
404
228
|
if (!editorError || !adapter.getEditorErrorDescriptor || !intl) return {};
|
|
405
229
|
const descriptor = adapter.getEditorErrorDescriptor(toLegacy(stateRef.current), { tags, isFullMode, currentModule });
|
|
406
230
|
if (descriptor?.kind !== EDITOR_ERROR_KIND.BRACE) return {};
|
|
407
231
|
return { [editorId]: intl.formatMessage(descriptor.message, descriptor.values) };
|
|
408
|
-
}, [errorData, intl, isFullMode, currentModule
|
|
232
|
+
}, [errorData, intl, isFullMode, currentModule]);
|
|
409
233
|
|
|
410
234
|
// ---- field change ----
|
|
411
235
|
const onFieldChange = (field, value) => {
|
|
412
|
-
const
|
|
413
|
-
const tabIndex = field.standalone ? null : liveActiveTab;
|
|
236
|
+
const tabIndex = field.standalone ? null : ACTIVE_TAB_INDEX;
|
|
414
237
|
const action = fieldChanged({ fieldId: field.id, value, tabIndex });
|
|
415
238
|
const nextState = formReducer(stateRef.current, action);
|
|
416
|
-
lastNextStateRef.current = nextState;
|
|
417
239
|
dispatch(action); // immediate UI update on next render
|
|
418
240
|
const legacy = toLegacy(nextState);
|
|
419
241
|
|
|
@@ -423,13 +245,11 @@ const FormBuilderShell = (props) => {
|
|
|
423
245
|
emitChange(legacy, field);
|
|
424
246
|
}
|
|
425
247
|
|
|
426
|
-
// Re-validate on change while errors are shown, so a revealed error clears
|
|
427
|
-
// soon as the field becomes valid.
|
|
248
|
+
// Re-validate on change while errors are being shown, so a revealed error clears
|
|
249
|
+
// as soon as the field becomes valid. Focus-safe because renderers don't toggle
|
|
250
|
+
// CapInput's antd suffix (see InputField), so the <input> never remounts.
|
|
428
251
|
const validationActive = propsRef.current.startValidation || internalCheckValidation || Boolean(propsRef.current.checkValidation);
|
|
429
|
-
|
|
430
|
-
// MPUSH opts out of live validation via features.liveValidation:false, SMS keeps it.
|
|
431
|
-
const validateLive = liquidEnabled && !isFullMode
|
|
432
|
-
&& adapter.config?.features?.liveValidation !== false;
|
|
252
|
+
const validateLive = liquidEnabled && !isFullMode;
|
|
433
253
|
if (validationActive || validateLive) {
|
|
434
254
|
emitValidity(runValidate(legacy), legacy);
|
|
435
255
|
}
|
|
@@ -446,78 +266,14 @@ const FormBuilderShell = (props) => {
|
|
|
446
266
|
};
|
|
447
267
|
|
|
448
268
|
// ---- parent bridge: invoke container-injected handlers (Stage A compat) ----
|
|
449
|
-
// Signatures mirror Classic exactly:
|
|
450
|
-
// - onTagSelect -> (data, currentTab, field) [Classic.js:2588-2591]
|
|
451
|
-
// - data-commit events -> (true, updatedLegacyFormData, field) [performFormDataUpdate]
|
|
452
|
-
// (checkbox submit-actions, radio onChange, select onSelect — MOBILEPUSH only,
|
|
453
|
-
// gated on config.features.bridgeFieldEvents so SMS behavior is untouched)
|
|
454
|
-
// - everything else -> (data, field.id) [callChildEvent tail]
|
|
455
269
|
const onEvent = (field, eventName, data) => {
|
|
456
270
|
const injected = field.injectedEvents?.[eventName];
|
|
457
271
|
if (typeof injected !== 'function') return;
|
|
458
|
-
const parent = propsRef.current.parent;
|
|
459
|
-
if (eventName === 'discardValues') {
|
|
460
|
-
// Classic resets its own state on discard (callChildEvent 2617-2625); the
|
|
461
|
-
// create-flow container then pushes EMPTY formData, which rehydrate ignores —
|
|
462
|
-
// so the local re-init must happen here.
|
|
463
|
-
injected.call(parent, data, field.id);
|
|
464
|
-
const fresh = initializeFormState(propsRef.current.schema || {});
|
|
465
|
-
lastEmittedRef.current = null;
|
|
466
|
-
lastSeenParentRef.current = null;
|
|
467
|
-
dispatch(hydrate(fresh));
|
|
468
|
-
setErrorData({});
|
|
469
|
-
setInternalCheckValidation(false);
|
|
470
|
-
const legacy = toLegacy(fresh);
|
|
471
|
-
emitValidity(runValidate(legacy), legacy);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
272
|
if (eventName === 'onTagSelect') {
|
|
475
|
-
injected.call(parent, data,
|
|
476
|
-
} else if (
|
|
477
|
-
DATA_COMMIT_EVENTS.has(eventName)
|
|
478
|
-
&& DATA_COMMIT_FIELD_TYPES.has(field.type)
|
|
479
|
-
&& adapter.config?.features?.bridgeFieldEvents
|
|
480
|
-
) {
|
|
481
|
-
injected.call(parent, true, toLegacy(lastNextStateRef.current || stateRef.current), field);
|
|
273
|
+
injected.call(propsRef.current.parent, data, 1, field); // Classic signature
|
|
482
274
|
} else {
|
|
483
|
-
injected.call(parent, data, field.id); // saveFormData / onClick
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
// ---- tab switch (from TabsContainer): state dispatch + container bridge ----
|
|
488
|
-
// Replaces Classic's DOM scan (Classic.js:2526-2551) with the same observable
|
|
489
|
-
// contract: the container's injected onTabChange receives the 1-based index.
|
|
490
|
-
const onTabSwitch = (container, key) => {
|
|
491
|
-
const tabs = stateRef.current?.tabs || [];
|
|
492
|
-
let index = tabs.findIndex((tab) => `${tab?.tabKey}` === `${key}`);
|
|
493
|
-
if (index === -1) {
|
|
494
|
-
const numeric = Number(key);
|
|
495
|
-
index = Number.isNaN(numeric) ? 0 : numeric;
|
|
275
|
+
injected.call(propsRef.current.parent, data, field.id); // saveFormData / onClick
|
|
496
276
|
}
|
|
497
|
-
dispatch(setActiveTab(index));
|
|
498
|
-
const injected = container?.injectedEvents?.onTabChange;
|
|
499
|
-
if (typeof injected === 'function') injected.call(propsRef.current.parent, index + 1);
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
// ---- confirmation modal (container-owned content, adapter-owned OK semantics) ----
|
|
503
|
-
const handleModalOk = (ev) => {
|
|
504
|
-
// antd v6 wraps button text in a <span>; use currentTarget so the button id resolves.
|
|
505
|
-
const id = (ev?.currentTarget && ev.currentTarget.id) || ev?.target?.id;
|
|
506
|
-
setModalVisible(false);
|
|
507
|
-
if (adapter.modals?.handleOk) {
|
|
508
|
-
adapter.modals.handleOk(id, {
|
|
509
|
-
platform: platformRef.current,
|
|
510
|
-
submitSingleTab: (singleTab) => {
|
|
511
|
-
const legacy = toLegacy(stateRef.current);
|
|
512
|
-
submitValidForm(legacy, errorData, { singleTab });
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
const handleModalCancel = () => {
|
|
519
|
-
setModalVisible(false);
|
|
520
|
-
if (propsRef.current.handleCancelModal) propsRef.current.handleCancelModal();
|
|
521
277
|
};
|
|
522
278
|
|
|
523
279
|
// Point the stable wrappers at the latest handler bodies (read at call time).
|
|
@@ -530,16 +286,11 @@ const FormBuilderShell = (props) => {
|
|
|
530
286
|
errorData,
|
|
531
287
|
registry,
|
|
532
288
|
checkValidation: internalCheckValidation || Boolean(checkValidation),
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
startValidation: Boolean(startValidation),
|
|
536
|
-
activeTabIndex,
|
|
537
|
-
currentTab,
|
|
538
|
-
tabCount,
|
|
289
|
+
activeTabIndex: ACTIVE_TAB_INDEX,
|
|
290
|
+
currentTab: 1,
|
|
539
291
|
onFieldChange: stableOnFieldChange,
|
|
540
292
|
onFieldBlur: stableOnFieldBlur,
|
|
541
293
|
onEvent: stableOnEvent,
|
|
542
|
-
onTabSwitch,
|
|
543
294
|
resolvedFieldErrors,
|
|
544
295
|
legacyFormData: toLegacy(state),
|
|
545
296
|
channel,
|
|
@@ -563,27 +314,9 @@ const FormBuilderShell = (props) => {
|
|
|
563
314
|
const spinning = Boolean((liquidEnabled && liquidExtractionInProgress) || metaDataStatus === REQUEST);
|
|
564
315
|
const spinTip = intl?.formatMessage ? intl.formatMessage(formMessages.liquidSpinText) : '';
|
|
565
316
|
|
|
566
|
-
// Confirmation modal (Classic getModal 'confirm' variant, Classic.js:597-641).
|
|
567
|
-
// Only channels that declare modal semantics render it (MOBILEPUSH); the footer's
|
|
568
|
-
// primary button carries the modal id — that id IS the handleOk dispatch key.
|
|
569
|
-
const confirmModal = adapter.modals && modal && (
|
|
570
|
-
<CapModal
|
|
571
|
-
visible={modalVisible}
|
|
572
|
-
title={modal.title || ''}
|
|
573
|
-
onCancel={handleModalCancel}
|
|
574
|
-
footer={[
|
|
575
|
-
<CapButton key="back" onClick={handleModalCancel}>Cancel</CapButton>,
|
|
576
|
-
<CapButton key="submit" type="primary" id={modal.id} onClick={handleModalOk}>Yes</CapButton>,
|
|
577
|
-
]}
|
|
578
|
-
>
|
|
579
|
-
{modal.body || ''}
|
|
580
|
-
</CapModal>
|
|
581
|
-
);
|
|
582
|
-
|
|
583
317
|
return (
|
|
584
318
|
<CapSpin spinning={spinning} tip={spinTip}>
|
|
585
319
|
<SchemaForm schema={schema} renderContext={renderContext} />
|
|
586
|
-
{confirmModal}
|
|
587
320
|
</CapSpin>
|
|
588
321
|
);
|
|
589
322
|
};
|
|
@@ -615,10 +348,6 @@ FormBuilderShell.propTypes = {
|
|
|
615
348
|
waitEventContextTags: PropTypes.object,
|
|
616
349
|
restrictPersonalization: PropTypes.bool,
|
|
617
350
|
refs: PropTypes.object,
|
|
618
|
-
// Tabbed channels (MOBILEPUSH): container-pushed tab position + confirmation modal.
|
|
619
|
-
currentTab: PropTypes.number,
|
|
620
|
-
showModal: PropTypes.bool,
|
|
621
|
-
modal: PropTypes.object,
|
|
622
351
|
};
|
|
623
352
|
|
|
624
353
|
FormBuilderShell.defaultProps = {
|
|
@@ -632,9 +361,6 @@ FormBuilderShell.defaultProps = {
|
|
|
632
361
|
waitEventContextTags: {},
|
|
633
362
|
restrictPersonalization: false,
|
|
634
363
|
refs: undefined,
|
|
635
|
-
currentTab: undefined,
|
|
636
|
-
showModal: false,
|
|
637
|
-
modal: null,
|
|
638
364
|
};
|
|
639
365
|
|
|
640
366
|
export default FormBuilderShell;
|
|
@@ -9,13 +9,7 @@ export const SECTION_TYPE = {
|
|
|
9
9
|
COL_LABEL: 'col-label',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
// single 'tabs' container carrying the Android/iOS panes.
|
|
14
|
-
export const CONTAINER_TYPE = {
|
|
15
|
-
TABS: 'tabs',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Leaf field types the migrated channels use (registry keys + memoization decisions).
|
|
12
|
+
// Leaf field types the SMS schema uses (registry keys + memoization decisions).
|
|
19
13
|
export const FIELD_TYPE = {
|
|
20
14
|
INPUT: 'input',
|
|
21
15
|
TEXTAREA: 'textarea',
|
|
@@ -24,12 +18,6 @@ export const FIELD_TYPE = {
|
|
|
24
18
|
TAG_LIST: 'tag-list',
|
|
25
19
|
SMS_PREVIEW: 'sms-preview',
|
|
26
20
|
DIV: 'div',
|
|
27
|
-
// MOBILEPUSH (Phase 2)
|
|
28
|
-
RADIO_GROUP: 'radioGroup',
|
|
29
|
-
SELECT: 'select',
|
|
30
|
-
UPLOAD: 'upload',
|
|
31
|
-
ICON: 'icon',
|
|
32
|
-
MOBILE_PUSH_PREVIEW: 'mobile-push-preview',
|
|
33
21
|
};
|
|
34
22
|
|
|
35
23
|
// Editor-error descriptor kinds (getEditorErrorDescriptor): the brace error is shown
|
|
@@ -39,39 +27,16 @@ export const EDITOR_ERROR_KIND = {
|
|
|
39
27
|
MISSING: 'missing',
|
|
40
28
|
};
|
|
41
29
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
FIELD_TYPE.TEXTAREA,
|
|
47
|
-
FIELD_TYPE.CHECKBOX,
|
|
48
|
-
]);
|
|
30
|
+
// Field types whose render depends only on their own value/error (+ the
|
|
31
|
+
// checkValidation/channel primitives). These go through the memoized FieldSlot;
|
|
32
|
+
// derived/control fields (sms-preview, sms-count, tag-list, button) render directly.
|
|
33
|
+
export const MEMOIZED_TYPES = new Set([FIELD_TYPE.INPUT, FIELD_TYPE.TEXTAREA, FIELD_TYPE.CHECKBOX]);
|
|
49
34
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
35
|
+
// Phase 1 renders a single active tab (index 0); multi-tab / version machinery
|
|
36
|
+
// arrives with later channels.
|
|
52
37
|
export const ACTIVE_TAB_INDEX = 0;
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
// (true, updatedLegacyFormData, field); everything else takes (data, field.id).
|
|
56
|
-
export const DATA_COMMIT_EVENTS = new Set([
|
|
57
|
-
'addPrimaryCta',
|
|
58
|
-
'addSecondaryCta',
|
|
59
|
-
'addSecondaryCtaIos',
|
|
60
|
-
'onChange',
|
|
61
|
-
'onSelect',
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
// Only these types reach Classic's commit path — a div's injected 'onChange'
|
|
65
|
-
// must keep the tail signature, hence the type guard.
|
|
66
|
-
export const DATA_COMMIT_FIELD_TYPES = new Set([
|
|
67
|
-
FIELD_TYPE.CHECKBOX,
|
|
68
|
-
FIELD_TYPE.RADIO_GROUP,
|
|
69
|
-
FIELD_TYPE.SELECT,
|
|
70
|
-
]);
|
|
71
|
-
|
|
72
|
-
export const TEMPLATE_NAME_FIELD = 'template-name';
|
|
73
|
-
|
|
74
|
-
export const HIGH_FREQ_FIELDS = [TEMPLATE_NAME_FIELD, 'template-subject'];
|
|
39
|
+
export const HIGH_FREQ_FIELDS = ['template-name', 'template-subject'];
|
|
75
40
|
|
|
76
41
|
export const ASCII = 'ASCII';
|
|
77
42
|
export const UNICODE = 'Unicode';
|