@open-mercato/ui 0.4.5-develop-03023b2707 → 0.4.5-develop-0c30cb4b11
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/AGENTS.md +8 -0
- package/dist/backend/AppShell.js +395 -134
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +232 -21
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/ProfileDropdown.js +214 -94
- package/dist/backend/ProfileDropdown.js.map +2 -2
- package/dist/backend/injection/InjectionSpot.js +74 -4
- package/dist/backend/injection/InjectionSpot.js.map +2 -2
- package/dist/backend/injection/SseEventIndicator.js +16 -0
- package/dist/backend/injection/SseEventIndicator.js.map +7 -0
- package/dist/backend/injection/WidgetSharedState.js +49 -0
- package/dist/backend/injection/WidgetSharedState.js.map +7 -0
- package/dist/backend/injection/eventBridge.js +105 -0
- package/dist/backend/injection/eventBridge.js.map +7 -0
- package/dist/backend/injection/mergeMenuItems.js +43 -0
- package/dist/backend/injection/mergeMenuItems.js.map +7 -0
- package/dist/backend/injection/resolveInjectedIcon.js +23 -0
- package/dist/backend/injection/resolveInjectedIcon.js.map +7 -0
- package/dist/backend/injection/spotIds.js +40 -1
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/injection/useAppEvent.js +35 -0
- package/dist/backend/injection/useAppEvent.js.map +7 -0
- package/dist/backend/injection/useInjectedMenuItems.js +92 -0
- package/dist/backend/injection/useInjectedMenuItems.js.map +7 -0
- package/dist/backend/injection/useInjectionDataWidgets.js +36 -0
- package/dist/backend/injection/useInjectionDataWidgets.js.map +7 -0
- package/dist/backend/injection/useOperationProgress.js +64 -0
- package/dist/backend/injection/useOperationProgress.js.map +7 -0
- package/dist/backend/injection/useWidgetSharedState.js +26 -0
- package/dist/backend/injection/useWidgetSharedState.js.map +7 -0
- package/dist/backend/section-page/SectionNav.js +22 -2
- package/dist/backend/section-page/SectionNav.js.map +2 -2
- package/dist/backend/utils/api.js +9 -1
- package/dist/backend/utils/api.js.map +2 -2
- package/package.json +2 -2
- package/src/backend/AGENTS.md +50 -0
- package/src/backend/AppShell.tsx +317 -30
- package/src/backend/CrudForm.tsx +238 -21
- package/src/backend/ProfileDropdown.tsx +199 -78
- package/src/backend/injection/InjectionSpot.tsx +118 -16
- package/src/backend/injection/SseEventIndicator.tsx +24 -0
- package/src/backend/injection/WidgetSharedState.ts +58 -0
- package/src/backend/injection/eventBridge.ts +134 -0
- package/src/backend/injection/mergeMenuItems.ts +71 -0
- package/src/backend/injection/resolveInjectedIcon.tsx +30 -0
- package/src/backend/injection/spotIds.ts +38 -0
- package/src/backend/injection/useAppEvent.ts +76 -0
- package/src/backend/injection/useInjectedMenuItems.ts +125 -0
- package/src/backend/injection/useInjectionDataWidgets.ts +41 -0
- package/src/backend/injection/useOperationProgress.ts +105 -0
- package/src/backend/injection/useWidgetSharedState.ts +28 -0
- package/src/backend/section-page/SectionNav.tsx +22 -1
- package/src/backend/utils/api.ts +14 -5
package/dist/backend/CrudForm.js
CHANGED
|
@@ -64,8 +64,13 @@ import { useConfirmDialog } from "./confirm-dialog/index.js";
|
|
|
64
64
|
import { useInjectionSpotEvents, InjectionSpot, useInjectionWidgets } from "./injection/InjectionSpot.js";
|
|
65
65
|
import { dispatchBackendMutationError } from "./injection/mutationEvents.js";
|
|
66
66
|
import { VersionHistoryAction } from "./version-history/VersionHistoryAction.js";
|
|
67
|
+
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
67
68
|
const EMPTY_OPTIONS = [];
|
|
68
69
|
const FOCUSABLE_SELECTOR = '[data-crud-focus-target], input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
70
|
+
const CRUDFORM_EXTENDED_EVENTS_ENABLED = parseBooleanWithDefault(
|
|
71
|
+
process.env.NEXT_PUBLIC_OM_CRUDFORM_EXTENDED_EVENTS_ENABLED,
|
|
72
|
+
true
|
|
73
|
+
);
|
|
69
74
|
const FIELDSET_ICON_COMPONENTS = {
|
|
70
75
|
layers: Layers,
|
|
71
76
|
tag: Tag,
|
|
@@ -162,6 +167,7 @@ function CrudForm({
|
|
|
162
167
|
const [values, setValues] = React.useState(
|
|
163
168
|
() => ({ ...initialValues ?? {} })
|
|
164
169
|
);
|
|
170
|
+
const valuesRef = React.useRef(values);
|
|
165
171
|
const [errors, setErrors] = React.useState({});
|
|
166
172
|
const [pending, setPending] = React.useState(false);
|
|
167
173
|
const [formError, setFormError] = React.useState(null);
|
|
@@ -217,12 +223,138 @@ function CrudForm({
|
|
|
217
223
|
recordId: fallbackRecordId,
|
|
218
224
|
isLoading,
|
|
219
225
|
pending
|
|
220
|
-
}), [formId, primaryEntityId, versionHistory?.resourceKind, versionHistory?.resourceId, fallbackRecordId, isLoading, pending]);
|
|
226
|
+
}), [formId, primaryEntityId, versionHistory?.resourceKind, versionHistory?.resourceId, recordId, fallbackRecordId, isLoading, pending]);
|
|
227
|
+
const injectionContextRef = React.useRef(injectionContext);
|
|
228
|
+
React.useEffect(() => {
|
|
229
|
+
injectionContextRef.current = injectionContext;
|
|
230
|
+
}, [injectionContext]);
|
|
231
|
+
React.useEffect(() => {
|
|
232
|
+
valuesRef.current = values;
|
|
233
|
+
}, [values]);
|
|
221
234
|
const { widgets: injectionWidgets } = useInjectionWidgets(resolvedInjectionSpotId, {
|
|
222
235
|
context: injectionContext,
|
|
223
236
|
triggerOnLoad: true
|
|
224
237
|
});
|
|
225
238
|
const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? "", injectionWidgets);
|
|
239
|
+
const extendedInjectionEventsEnabled = CRUDFORM_EXTENDED_EVENTS_ENABLED && Boolean(resolvedInjectionSpotId);
|
|
240
|
+
const transformValidationErrors = React.useCallback(
|
|
241
|
+
async (fieldErrors) => {
|
|
242
|
+
if (!extendedInjectionEventsEnabled || !Object.keys(fieldErrors).length) return fieldErrors;
|
|
243
|
+
try {
|
|
244
|
+
const result = await triggerInjectionEvent(
|
|
245
|
+
"transformValidation",
|
|
246
|
+
fieldErrors,
|
|
247
|
+
injectionContextRef.current,
|
|
248
|
+
{ originalData: valuesRef.current }
|
|
249
|
+
);
|
|
250
|
+
const transformed = result.data;
|
|
251
|
+
if (!transformed || typeof transformed !== "object" || Array.isArray(transformed)) return fieldErrors;
|
|
252
|
+
return Object.fromEntries(
|
|
253
|
+
Object.entries(transformed).map(([key, value]) => [key, String(value)])
|
|
254
|
+
);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error("[CrudForm] Error in transformValidation:", err);
|
|
257
|
+
return fieldErrors;
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
[extendedInjectionEventsEnabled, triggerInjectionEvent]
|
|
261
|
+
);
|
|
262
|
+
const canNavigateTo = React.useCallback(
|
|
263
|
+
async (target) => {
|
|
264
|
+
if (!extendedInjectionEventsEnabled) return true;
|
|
265
|
+
try {
|
|
266
|
+
const result = await triggerInjectionEvent(
|
|
267
|
+
"onBeforeNavigate",
|
|
268
|
+
valuesRef.current,
|
|
269
|
+
injectionContextRef.current,
|
|
270
|
+
{ target }
|
|
271
|
+
);
|
|
272
|
+
if (!result.ok) {
|
|
273
|
+
flash(result.message || t("ui.forms.flash.saveBlocked", "Save blocked by validation"), "error");
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const message = err instanceof Error && err.message ? err.message : t("ui.forms.flash.saveBlocked", "Save blocked by validation");
|
|
279
|
+
flash(message, "error");
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
[extendedInjectionEventsEnabled, t, triggerInjectionEvent]
|
|
284
|
+
);
|
|
285
|
+
const navigateWithGuard = React.useCallback(
|
|
286
|
+
async (target) => {
|
|
287
|
+
if (!target) return;
|
|
288
|
+
const allowed = await canNavigateTo(target);
|
|
289
|
+
if (allowed) router.push(target);
|
|
290
|
+
},
|
|
291
|
+
[canNavigateTo, router]
|
|
292
|
+
);
|
|
293
|
+
React.useEffect(() => {
|
|
294
|
+
if (!extendedInjectionEventsEnabled || typeof window === "undefined") return;
|
|
295
|
+
const handleEvent = (event) => {
|
|
296
|
+
const customEvent = event;
|
|
297
|
+
void triggerInjectionEvent("onAppEvent", valuesRef.current, injectionContextRef.current, {
|
|
298
|
+
appEvent: customEvent.detail
|
|
299
|
+
}).catch((err) => {
|
|
300
|
+
console.error("[CrudForm] Error in onAppEvent:", err);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
window.addEventListener("om:event", handleEvent);
|
|
304
|
+
return () => {
|
|
305
|
+
window.removeEventListener("om:event", handleEvent);
|
|
306
|
+
};
|
|
307
|
+
}, [extendedInjectionEventsEnabled, triggerInjectionEvent]);
|
|
308
|
+
React.useEffect(() => {
|
|
309
|
+
if (!extendedInjectionEventsEnabled || typeof document === "undefined") return;
|
|
310
|
+
const emitVisibility = () => {
|
|
311
|
+
void triggerInjectionEvent("onVisibilityChange", valuesRef.current, injectionContextRef.current, {
|
|
312
|
+
visible: document.visibilityState === "visible"
|
|
313
|
+
}).catch((err) => {
|
|
314
|
+
console.error("[CrudForm] Error in onVisibilityChange:", err);
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
document.addEventListener("visibilitychange", emitVisibility);
|
|
318
|
+
emitVisibility();
|
|
319
|
+
return () => {
|
|
320
|
+
document.removeEventListener("visibilitychange", emitVisibility);
|
|
321
|
+
};
|
|
322
|
+
}, [extendedInjectionEventsEnabled, triggerInjectionEvent]);
|
|
323
|
+
React.useEffect(() => {
|
|
324
|
+
if (!extendedInjectionEventsEnabled) return;
|
|
325
|
+
const root = rootRef.current;
|
|
326
|
+
if (!root || typeof window === "undefined") return;
|
|
327
|
+
const handleClickCapture = (event) => {
|
|
328
|
+
if (event.defaultPrevented) return;
|
|
329
|
+
if (event.button !== 0) return;
|
|
330
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
331
|
+
const targetElement = event.target instanceof Element ? event.target : null;
|
|
332
|
+
const linkElement = targetElement?.closest("a[href]");
|
|
333
|
+
if (!(linkElement instanceof HTMLAnchorElement)) return;
|
|
334
|
+
if (!root.contains(linkElement)) return;
|
|
335
|
+
if (linkElement.target && linkElement.target !== "_self") return;
|
|
336
|
+
const rawHref = linkElement.getAttribute("href");
|
|
337
|
+
if (!rawHref || rawHref.startsWith("#")) return;
|
|
338
|
+
let target = rawHref;
|
|
339
|
+
if (rawHref.startsWith("http://") || rawHref.startsWith("https://")) {
|
|
340
|
+
try {
|
|
341
|
+
const parsed = new URL(rawHref);
|
|
342
|
+
if (parsed.origin !== window.location.origin) return;
|
|
343
|
+
target = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
344
|
+
} catch {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
} else if (!rawHref.startsWith("/")) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
void navigateWithGuard(target);
|
|
352
|
+
};
|
|
353
|
+
root.addEventListener("click", handleClickCapture, true);
|
|
354
|
+
return () => {
|
|
355
|
+
root.removeEventListener("click", handleClickCapture, true);
|
|
356
|
+
};
|
|
357
|
+
}, [extendedInjectionEventsEnabled, navigateWithGuard]);
|
|
226
358
|
React.useEffect(() => {
|
|
227
359
|
const root = rootRef.current;
|
|
228
360
|
if (!root) return;
|
|
@@ -276,7 +408,8 @@ function CrudForm({
|
|
|
276
408
|
} catch {
|
|
277
409
|
}
|
|
278
410
|
if (result.fieldErrors && Object.keys(result.fieldErrors).length) {
|
|
279
|
-
|
|
411
|
+
const transformedErrors = await transformValidationErrors(result.fieldErrors);
|
|
412
|
+
setErrors(transformedErrors);
|
|
280
413
|
}
|
|
281
414
|
const message = result.message || t("ui.forms.flash.saveBlocked", "Save blocked by validation");
|
|
282
415
|
flash(message, "error");
|
|
@@ -318,7 +451,7 @@ function CrudForm({
|
|
|
318
451
|
} catch {
|
|
319
452
|
}
|
|
320
453
|
if (typeof deleteRedirect === "string" && deleteRedirect) {
|
|
321
|
-
|
|
454
|
+
await navigateWithGuard(deleteRedirect);
|
|
322
455
|
}
|
|
323
456
|
} catch (err) {
|
|
324
457
|
if (resolvedInjectionSpotId) {
|
|
@@ -362,8 +495,9 @@ function CrudForm({
|
|
|
362
495
|
injectionContext,
|
|
363
496
|
onDelete,
|
|
364
497
|
resolvedInjectionSpotId,
|
|
365
|
-
|
|
498
|
+
navigateWithGuard,
|
|
366
499
|
t,
|
|
500
|
+
transformValidationErrors,
|
|
367
501
|
triggerInjectionEvent,
|
|
368
502
|
values
|
|
369
503
|
]);
|
|
@@ -780,11 +914,43 @@ function CrudForm({
|
|
|
780
914
|
}
|
|
781
915
|
}, [errors, formId]);
|
|
782
916
|
const setValue = React.useCallback((id, nextValue) => {
|
|
917
|
+
let nextData = null;
|
|
783
918
|
setValues((prev) => {
|
|
784
919
|
if (Object.is(prev[id], nextValue)) return prev;
|
|
785
|
-
|
|
920
|
+
nextData = { ...prev, [id]: nextValue };
|
|
921
|
+
return nextData;
|
|
786
922
|
});
|
|
787
|
-
|
|
923
|
+
if (!nextData || !extendedInjectionEventsEnabled) return;
|
|
924
|
+
void triggerInjectionEvent("onFieldChange", nextData, injectionContextRef.current, {
|
|
925
|
+
fieldId: id,
|
|
926
|
+
fieldValue: nextValue
|
|
927
|
+
}).then((result) => {
|
|
928
|
+
if (!result.ok) return;
|
|
929
|
+
const change = result.fieldChange;
|
|
930
|
+
if (!change) return;
|
|
931
|
+
const updates = { ...change.sideEffects ?? {} };
|
|
932
|
+
if (change.value !== void 0) {
|
|
933
|
+
updates[id] = change.value;
|
|
934
|
+
}
|
|
935
|
+
if (Object.keys(updates).length > 0) {
|
|
936
|
+
setValues((prev) => {
|
|
937
|
+
let changed = false;
|
|
938
|
+
const next = { ...prev };
|
|
939
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
940
|
+
if (Object.is(next[key], value)) continue;
|
|
941
|
+
next[key] = value;
|
|
942
|
+
changed = true;
|
|
943
|
+
}
|
|
944
|
+
return changed ? next : prev;
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
for (const message of change.messages ?? []) {
|
|
948
|
+
flash(message.text, message.severity);
|
|
949
|
+
}
|
|
950
|
+
}).catch((err) => {
|
|
951
|
+
console.error("[CrudForm] Error in onFieldChange:", err);
|
|
952
|
+
});
|
|
953
|
+
}, [extendedInjectionEventsEnabled, flash, triggerInjectionEvent]);
|
|
788
954
|
const handleFieldsetSelectionChange = React.useCallback(
|
|
789
955
|
(entityId2, nextCode) => {
|
|
790
956
|
setCfFieldsetSelections((prev) => ({ ...prev, [entityId2]: nextCode }));
|
|
@@ -809,8 +975,32 @@ function CrudForm({
|
|
|
809
975
|
const snapshot = JSON.stringify(initialValues);
|
|
810
976
|
if (initialValuesSnapshotRef.current === snapshot) return;
|
|
811
977
|
initialValuesSnapshotRef.current = snapshot;
|
|
812
|
-
|
|
813
|
-
|
|
978
|
+
let mergedValues = null;
|
|
979
|
+
setValues((prev) => {
|
|
980
|
+
mergedValues = { ...prev, ...initialValues };
|
|
981
|
+
return mergedValues;
|
|
982
|
+
});
|
|
983
|
+
if (!extendedInjectionEventsEnabled || !mergedValues) return;
|
|
984
|
+
let cancelled = false;
|
|
985
|
+
const run = async () => {
|
|
986
|
+
try {
|
|
987
|
+
const result = await triggerInjectionEvent(
|
|
988
|
+
"transformDisplayData",
|
|
989
|
+
mergedValues,
|
|
990
|
+
injectionContextRef.current
|
|
991
|
+
);
|
|
992
|
+
const transformed = result.data;
|
|
993
|
+
if (cancelled || !transformed) return;
|
|
994
|
+
setValues(transformed);
|
|
995
|
+
} catch (err) {
|
|
996
|
+
console.error("[CrudForm] Error in transformDisplayData:", err);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
void run();
|
|
1000
|
+
return () => {
|
|
1001
|
+
cancelled = true;
|
|
1002
|
+
};
|
|
1003
|
+
}, [extendedInjectionEventsEnabled, initialValues, triggerInjectionEvent]);
|
|
814
1004
|
const buildFieldsetEditorHref = React.useCallback(
|
|
815
1005
|
(includeViewParam) => {
|
|
816
1006
|
if (!fieldsetEditorTarget) return null;
|
|
@@ -864,7 +1054,8 @@ function CrudForm({
|
|
|
864
1054
|
if (process.env.NODE_ENV !== "production") {
|
|
865
1055
|
console.debug("[crud-form] Required field errors prevented submit", requiredErrors);
|
|
866
1056
|
}
|
|
867
|
-
|
|
1057
|
+
const transformedErrors = await transformValidationErrors(requiredErrors);
|
|
1058
|
+
setErrors(transformedErrors);
|
|
868
1059
|
flash(highlightedMessage, "error");
|
|
869
1060
|
return;
|
|
870
1061
|
}
|
|
@@ -891,9 +1082,15 @@ function CrudForm({
|
|
|
891
1082
|
if (customEntity) {
|
|
892
1083
|
const mapped = {};
|
|
893
1084
|
for (const [ek, ev] of Object.entries(result.fieldErrors)) mapped[ek.replace(/^cf_/, "")] = String(ev);
|
|
894
|
-
|
|
1085
|
+
const transformedErrors = await transformValidationErrors(mapped);
|
|
1086
|
+
setErrors((prev) => ({ ...prev, ...transformedErrors }));
|
|
895
1087
|
} else {
|
|
896
|
-
|
|
1088
|
+
const transformedErrors = await transformValidationErrors(
|
|
1089
|
+
Object.fromEntries(
|
|
1090
|
+
Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)])
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
setErrors((prev) => ({ ...prev, ...transformedErrors }));
|
|
897
1094
|
}
|
|
898
1095
|
flash(highlightedMessage, "error");
|
|
899
1096
|
return;
|
|
@@ -912,7 +1109,8 @@ function CrudForm({
|
|
|
912
1109
|
if (process.env.NODE_ENV !== "production") {
|
|
913
1110
|
console.debug("[crud-form] Schema validation failed", res.error.issues);
|
|
914
1111
|
}
|
|
915
|
-
|
|
1112
|
+
const transformedErrors = await transformValidationErrors(fieldErrors);
|
|
1113
|
+
setErrors(transformedErrors);
|
|
916
1114
|
flash(highlightedMessage, "error");
|
|
917
1115
|
return;
|
|
918
1116
|
}
|
|
@@ -920,10 +1118,21 @@ function CrudForm({
|
|
|
920
1118
|
} else {
|
|
921
1119
|
parsedValues = values;
|
|
922
1120
|
}
|
|
1121
|
+
let submitValues = parsedValues;
|
|
1122
|
+
if (extendedInjectionEventsEnabled) {
|
|
1123
|
+
try {
|
|
1124
|
+
const result = await triggerInjectionEvent("transformFormData", submitValues, injectionContext);
|
|
1125
|
+
if (result.data) {
|
|
1126
|
+
submitValues = result.data;
|
|
1127
|
+
}
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
console.error("[CrudForm] Error in transformFormData:", err);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
923
1132
|
let injectionRequestHeaders;
|
|
924
1133
|
if (resolvedInjectionSpotId) {
|
|
925
1134
|
try {
|
|
926
|
-
const result = await triggerInjectionEvent("onBeforeSave",
|
|
1135
|
+
const result = await triggerInjectionEvent("onBeforeSave", submitValues, injectionContext);
|
|
927
1136
|
if (!result.ok) {
|
|
928
1137
|
try {
|
|
929
1138
|
if (typeof window !== "undefined") {
|
|
@@ -942,7 +1151,8 @@ function CrudForm({
|
|
|
942
1151
|
} catch {
|
|
943
1152
|
}
|
|
944
1153
|
if (result.fieldErrors && Object.keys(result.fieldErrors).length) {
|
|
945
|
-
|
|
1154
|
+
const transformedErrors = await transformValidationErrors(result.fieldErrors);
|
|
1155
|
+
setErrors(transformedErrors);
|
|
946
1156
|
}
|
|
947
1157
|
const message = result.message || t("ui.forms.flash.saveBlocked", "Save blocked by validation");
|
|
948
1158
|
flash(message, "error");
|
|
@@ -960,7 +1170,7 @@ function CrudForm({
|
|
|
960
1170
|
setPending(true);
|
|
961
1171
|
if (resolvedInjectionSpotId) {
|
|
962
1172
|
try {
|
|
963
|
-
await triggerInjectionEvent("onSave",
|
|
1173
|
+
await triggerInjectionEvent("onSave", submitValues, injectionContext);
|
|
964
1174
|
} catch (err) {
|
|
965
1175
|
console.error("[CrudForm] Error in onSave:", err);
|
|
966
1176
|
flash(t("ui.forms.flash.saveBlocked", "Save blocked by validation"), "error");
|
|
@@ -971,19 +1181,19 @@ function CrudForm({
|
|
|
971
1181
|
try {
|
|
972
1182
|
if (injectionRequestHeaders && Object.keys(injectionRequestHeaders).length > 0) {
|
|
973
1183
|
await withScopedApiRequestHeaders(injectionRequestHeaders, async () => {
|
|
974
|
-
await onSubmit?.(
|
|
1184
|
+
await onSubmit?.(submitValues);
|
|
975
1185
|
});
|
|
976
1186
|
} else {
|
|
977
|
-
await onSubmit?.(
|
|
1187
|
+
await onSubmit?.(submitValues);
|
|
978
1188
|
}
|
|
979
1189
|
if (resolvedInjectionSpotId) {
|
|
980
1190
|
try {
|
|
981
|
-
await triggerInjectionEvent("onAfterSave",
|
|
1191
|
+
await triggerInjectionEvent("onAfterSave", submitValues, injectionContext);
|
|
982
1192
|
} catch (err) {
|
|
983
1193
|
console.error("[CrudForm] Error in onAfterSave:", err);
|
|
984
1194
|
}
|
|
985
1195
|
}
|
|
986
|
-
if (successRedirect)
|
|
1196
|
+
if (successRedirect) await navigateWithGuard(successRedirect);
|
|
987
1197
|
} catch (err) {
|
|
988
1198
|
try {
|
|
989
1199
|
if (typeof window !== "undefined") {
|
|
@@ -1011,9 +1221,10 @@ function CrudForm({
|
|
|
1011
1221
|
return typeof value === "string" && value.trim().length ? value.trim() : null;
|
|
1012
1222
|
})() : null;
|
|
1013
1223
|
if (hasFieldErrors) {
|
|
1014
|
-
|
|
1224
|
+
const transformedErrors = await transformValidationErrors(combinedFieldErrors);
|
|
1225
|
+
setErrors(transformedErrors);
|
|
1015
1226
|
if (process.env.NODE_ENV !== "production") {
|
|
1016
|
-
console.debug("[crud-form] Submission failed with field errors",
|
|
1227
|
+
console.debug("[crud-form] Submission failed with field errors", transformedErrors);
|
|
1017
1228
|
}
|
|
1018
1229
|
}
|
|
1019
1230
|
let displayMessage = typeof helperMessage === "string" && helperMessage.trim() ? helperMessage.trim() : "";
|