@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/src/backend/CrudForm.tsx
CHANGED
|
@@ -68,11 +68,16 @@ import { useConfirmDialog } from './confirm-dialog'
|
|
|
68
68
|
import { useInjectionSpotEvents, InjectionSpot, useInjectionWidgets } from './injection/InjectionSpot'
|
|
69
69
|
import { dispatchBackendMutationError } from './injection/mutationEvents'
|
|
70
70
|
import { VersionHistoryAction } from './version-history/VersionHistoryAction'
|
|
71
|
+
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
71
72
|
|
|
72
73
|
// Stable empty options array to avoid creating a new [] every render
|
|
73
74
|
const EMPTY_OPTIONS: CrudFieldOption[] = []
|
|
74
75
|
const FOCUSABLE_SELECTOR =
|
|
75
76
|
'[data-crud-focus-target], input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
77
|
+
const CRUDFORM_EXTENDED_EVENTS_ENABLED = parseBooleanWithDefault(
|
|
78
|
+
process.env.NEXT_PUBLIC_OM_CRUDFORM_EXTENDED_EVENTS_ENABLED,
|
|
79
|
+
true,
|
|
80
|
+
)
|
|
76
81
|
|
|
77
82
|
export type CrudFieldBase = {
|
|
78
83
|
id: string
|
|
@@ -343,6 +348,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
343
348
|
const [values, setValues] = React.useState<CrudFormValues<TValues>>(
|
|
344
349
|
() => ({ ...(initialValues ?? {}) } as CrudFormValues<TValues>)
|
|
345
350
|
)
|
|
351
|
+
const valuesRef = React.useRef(values)
|
|
346
352
|
const [errors, setErrors] = React.useState<Record<string, string>>({})
|
|
347
353
|
const [pending, setPending] = React.useState(false)
|
|
348
354
|
const [formError, setFormError] = React.useState<string | null>(null)
|
|
@@ -406,7 +412,14 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
406
412
|
recordId: fallbackRecordId,
|
|
407
413
|
isLoading,
|
|
408
414
|
pending,
|
|
409
|
-
}), [formId, primaryEntityId, versionHistory?.resourceKind, versionHistory?.resourceId, fallbackRecordId, isLoading, pending])
|
|
415
|
+
}), [formId, primaryEntityId, versionHistory?.resourceKind, versionHistory?.resourceId, recordId, fallbackRecordId, isLoading, pending])
|
|
416
|
+
const injectionContextRef = React.useRef(injectionContext)
|
|
417
|
+
React.useEffect(() => {
|
|
418
|
+
injectionContextRef.current = injectionContext
|
|
419
|
+
}, [injectionContext])
|
|
420
|
+
React.useEffect(() => {
|
|
421
|
+
valuesRef.current = values
|
|
422
|
+
}, [values])
|
|
410
423
|
|
|
411
424
|
const { widgets: injectionWidgets } = useInjectionWidgets(resolvedInjectionSpotId, {
|
|
412
425
|
context: injectionContext,
|
|
@@ -414,6 +427,131 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
414
427
|
})
|
|
415
428
|
|
|
416
429
|
const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? '', injectionWidgets)
|
|
430
|
+
const extendedInjectionEventsEnabled = CRUDFORM_EXTENDED_EVENTS_ENABLED && Boolean(resolvedInjectionSpotId)
|
|
431
|
+
|
|
432
|
+
const transformValidationErrors = React.useCallback(
|
|
433
|
+
async (fieldErrors: Record<string, string>): Promise<Record<string, string>> => {
|
|
434
|
+
if (!extendedInjectionEventsEnabled || !Object.keys(fieldErrors).length) return fieldErrors
|
|
435
|
+
try {
|
|
436
|
+
const result = await triggerInjectionEvent(
|
|
437
|
+
'transformValidation',
|
|
438
|
+
fieldErrors as unknown as TValues,
|
|
439
|
+
injectionContextRef.current,
|
|
440
|
+
{ originalData: valuesRef.current as TValues },
|
|
441
|
+
)
|
|
442
|
+
const transformed = result.data
|
|
443
|
+
if (!transformed || typeof transformed !== 'object' || Array.isArray(transformed)) return fieldErrors
|
|
444
|
+
return Object.fromEntries(
|
|
445
|
+
Object.entries(transformed as Record<string, unknown>).map(([key, value]) => [key, String(value)]),
|
|
446
|
+
)
|
|
447
|
+
} catch (err) {
|
|
448
|
+
console.error('[CrudForm] Error in transformValidation:', err)
|
|
449
|
+
return fieldErrors
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
[extendedInjectionEventsEnabled, triggerInjectionEvent],
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
const canNavigateTo = React.useCallback(
|
|
456
|
+
async (target: string): Promise<boolean> => {
|
|
457
|
+
if (!extendedInjectionEventsEnabled) return true
|
|
458
|
+
try {
|
|
459
|
+
const result = await triggerInjectionEvent(
|
|
460
|
+
'onBeforeNavigate',
|
|
461
|
+
valuesRef.current as TValues,
|
|
462
|
+
injectionContextRef.current,
|
|
463
|
+
{ target },
|
|
464
|
+
)
|
|
465
|
+
if (!result.ok) {
|
|
466
|
+
flash(result.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
|
|
467
|
+
return false
|
|
468
|
+
}
|
|
469
|
+
return true
|
|
470
|
+
} catch (err) {
|
|
471
|
+
const message = err instanceof Error && err.message ? err.message : t('ui.forms.flash.saveBlocked', 'Save blocked by validation')
|
|
472
|
+
flash(message, 'error')
|
|
473
|
+
return false
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
[extendedInjectionEventsEnabled, t, triggerInjectionEvent],
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
const navigateWithGuard = React.useCallback(
|
|
480
|
+
async (target: string) => {
|
|
481
|
+
if (!target) return
|
|
482
|
+
const allowed = await canNavigateTo(target)
|
|
483
|
+
if (allowed) router.push(target)
|
|
484
|
+
},
|
|
485
|
+
[canNavigateTo, router],
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
React.useEffect(() => {
|
|
489
|
+
if (!extendedInjectionEventsEnabled || typeof window === 'undefined') return
|
|
490
|
+
const handleEvent = (event: Event) => {
|
|
491
|
+
const customEvent = event as CustomEvent<unknown>
|
|
492
|
+
void triggerInjectionEvent('onAppEvent', valuesRef.current as TValues, injectionContextRef.current, {
|
|
493
|
+
appEvent: customEvent.detail,
|
|
494
|
+
}).catch((err) => {
|
|
495
|
+
console.error('[CrudForm] Error in onAppEvent:', err)
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
window.addEventListener('om:event', handleEvent as EventListener)
|
|
499
|
+
return () => {
|
|
500
|
+
window.removeEventListener('om:event', handleEvent as EventListener)
|
|
501
|
+
}
|
|
502
|
+
}, [extendedInjectionEventsEnabled, triggerInjectionEvent])
|
|
503
|
+
|
|
504
|
+
React.useEffect(() => {
|
|
505
|
+
if (!extendedInjectionEventsEnabled || typeof document === 'undefined') return
|
|
506
|
+
const emitVisibility = () => {
|
|
507
|
+
void triggerInjectionEvent('onVisibilityChange', valuesRef.current as TValues, injectionContextRef.current, {
|
|
508
|
+
visible: document.visibilityState === 'visible',
|
|
509
|
+
}).catch((err) => {
|
|
510
|
+
console.error('[CrudForm] Error in onVisibilityChange:', err)
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
document.addEventListener('visibilitychange', emitVisibility)
|
|
514
|
+
emitVisibility()
|
|
515
|
+
return () => {
|
|
516
|
+
document.removeEventListener('visibilitychange', emitVisibility)
|
|
517
|
+
}
|
|
518
|
+
}, [extendedInjectionEventsEnabled, triggerInjectionEvent])
|
|
519
|
+
|
|
520
|
+
React.useEffect(() => {
|
|
521
|
+
if (!extendedInjectionEventsEnabled) return
|
|
522
|
+
const root = rootRef.current
|
|
523
|
+
if (!root || typeof window === 'undefined') return
|
|
524
|
+
const handleClickCapture = (event: MouseEvent) => {
|
|
525
|
+
if (event.defaultPrevented) return
|
|
526
|
+
if (event.button !== 0) return
|
|
527
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
|
|
528
|
+
const targetElement = event.target instanceof Element ? event.target : null
|
|
529
|
+
const linkElement = targetElement?.closest('a[href]')
|
|
530
|
+
if (!(linkElement instanceof HTMLAnchorElement)) return
|
|
531
|
+
if (!root.contains(linkElement)) return
|
|
532
|
+
if (linkElement.target && linkElement.target !== '_self') return
|
|
533
|
+
const rawHref = linkElement.getAttribute('href')
|
|
534
|
+
if (!rawHref || rawHref.startsWith('#')) return
|
|
535
|
+
let target = rawHref
|
|
536
|
+
if (rawHref.startsWith('http://') || rawHref.startsWith('https://')) {
|
|
537
|
+
try {
|
|
538
|
+
const parsed = new URL(rawHref)
|
|
539
|
+
if (parsed.origin !== window.location.origin) return
|
|
540
|
+
target = `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
541
|
+
} catch {
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
} else if (!rawHref.startsWith('/')) {
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
event.preventDefault()
|
|
548
|
+
void navigateWithGuard(target)
|
|
549
|
+
}
|
|
550
|
+
root.addEventListener('click', handleClickCapture, true)
|
|
551
|
+
return () => {
|
|
552
|
+
root.removeEventListener('click', handleClickCapture, true)
|
|
553
|
+
}
|
|
554
|
+
}, [extendedInjectionEventsEnabled, navigateWithGuard])
|
|
417
555
|
|
|
418
556
|
React.useEffect(() => {
|
|
419
557
|
const root = rootRef.current
|
|
@@ -476,7 +614,8 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
476
614
|
// ignore event dispatch failures
|
|
477
615
|
}
|
|
478
616
|
if (result.fieldErrors && Object.keys(result.fieldErrors).length) {
|
|
479
|
-
|
|
617
|
+
const transformedErrors = await transformValidationErrors(result.fieldErrors)
|
|
618
|
+
setErrors(transformedErrors)
|
|
480
619
|
}
|
|
481
620
|
const message = result.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation')
|
|
482
621
|
flash(message, 'error')
|
|
@@ -519,7 +658,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
519
658
|
try { flash(deleteSuccessMessage, 'success') } catch {}
|
|
520
659
|
// Redirect if requested by caller
|
|
521
660
|
if (typeof deleteRedirect === 'string' && deleteRedirect) {
|
|
522
|
-
|
|
661
|
+
await navigateWithGuard(deleteRedirect)
|
|
523
662
|
}
|
|
524
663
|
} catch (err) {
|
|
525
664
|
if (resolvedInjectionSpotId) {
|
|
@@ -561,8 +700,9 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
561
700
|
injectionContext,
|
|
562
701
|
onDelete,
|
|
563
702
|
resolvedInjectionSpotId,
|
|
564
|
-
|
|
703
|
+
navigateWithGuard,
|
|
565
704
|
t,
|
|
705
|
+
transformValidationErrors,
|
|
566
706
|
triggerInjectionEvent,
|
|
567
707
|
values,
|
|
568
708
|
])
|
|
@@ -1055,11 +1195,43 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1055
1195
|
}, [errors, formId])
|
|
1056
1196
|
|
|
1057
1197
|
const setValue = React.useCallback((id: string, nextValue: unknown) => {
|
|
1198
|
+
let nextData: CrudFormValues<TValues> | null = null
|
|
1058
1199
|
setValues((prev) => {
|
|
1059
1200
|
if (Object.is(prev[id], nextValue)) return prev
|
|
1060
|
-
|
|
1201
|
+
nextData = { ...prev, [id]: nextValue } as CrudFormValues<TValues>
|
|
1202
|
+
return nextData
|
|
1061
1203
|
})
|
|
1062
|
-
|
|
1204
|
+
if (!nextData || !extendedInjectionEventsEnabled) return
|
|
1205
|
+
void triggerInjectionEvent('onFieldChange', nextData as TValues, injectionContextRef.current, {
|
|
1206
|
+
fieldId: id,
|
|
1207
|
+
fieldValue: nextValue,
|
|
1208
|
+
}).then((result) => {
|
|
1209
|
+
if (!result.ok) return
|
|
1210
|
+
const change = result.fieldChange
|
|
1211
|
+
if (!change) return
|
|
1212
|
+
const updates: Record<string, unknown> = { ...(change.sideEffects ?? {}) }
|
|
1213
|
+
if (change.value !== undefined) {
|
|
1214
|
+
updates[id] = change.value
|
|
1215
|
+
}
|
|
1216
|
+
if (Object.keys(updates).length > 0) {
|
|
1217
|
+
setValues((prev) => {
|
|
1218
|
+
let changed = false
|
|
1219
|
+
const next = { ...prev } as Record<string, unknown>
|
|
1220
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
1221
|
+
if (Object.is(next[key], value)) continue
|
|
1222
|
+
next[key] = value
|
|
1223
|
+
changed = true
|
|
1224
|
+
}
|
|
1225
|
+
return changed ? (next as CrudFormValues<TValues>) : prev
|
|
1226
|
+
})
|
|
1227
|
+
}
|
|
1228
|
+
for (const message of change.messages ?? []) {
|
|
1229
|
+
flash(message.text, message.severity)
|
|
1230
|
+
}
|
|
1231
|
+
}).catch((err) => {
|
|
1232
|
+
console.error('[CrudForm] Error in onFieldChange:', err)
|
|
1233
|
+
})
|
|
1234
|
+
}, [extendedInjectionEventsEnabled, flash, triggerInjectionEvent])
|
|
1063
1235
|
|
|
1064
1236
|
const handleFieldsetSelectionChange = React.useCallback(
|
|
1065
1237
|
(entityId: string, nextCode: string | null) => {
|
|
@@ -1087,8 +1259,32 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1087
1259
|
const snapshot = JSON.stringify(initialValues)
|
|
1088
1260
|
if (initialValuesSnapshotRef.current === snapshot) return
|
|
1089
1261
|
initialValuesSnapshotRef.current = snapshot
|
|
1090
|
-
|
|
1091
|
-
|
|
1262
|
+
let mergedValues: CrudFormValues<TValues> | null = null
|
|
1263
|
+
setValues((prev) => {
|
|
1264
|
+
mergedValues = { ...prev, ...initialValues } as CrudFormValues<TValues>
|
|
1265
|
+
return mergedValues
|
|
1266
|
+
})
|
|
1267
|
+
if (!extendedInjectionEventsEnabled || !mergedValues) return
|
|
1268
|
+
let cancelled = false
|
|
1269
|
+
const run = async () => {
|
|
1270
|
+
try {
|
|
1271
|
+
const result = await triggerInjectionEvent(
|
|
1272
|
+
'transformDisplayData',
|
|
1273
|
+
mergedValues as TValues,
|
|
1274
|
+
injectionContextRef.current,
|
|
1275
|
+
)
|
|
1276
|
+
const transformed = result.data
|
|
1277
|
+
if (cancelled || !transformed) return
|
|
1278
|
+
setValues(transformed as CrudFormValues<TValues>)
|
|
1279
|
+
} catch (err) {
|
|
1280
|
+
console.error('[CrudForm] Error in transformDisplayData:', err)
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
void run()
|
|
1284
|
+
return () => {
|
|
1285
|
+
cancelled = true
|
|
1286
|
+
}
|
|
1287
|
+
}, [extendedInjectionEventsEnabled, initialValues, triggerInjectionEvent])
|
|
1092
1288
|
|
|
1093
1289
|
const buildFieldsetEditorHref = React.useCallback(
|
|
1094
1290
|
(includeViewParam: boolean) => {
|
|
@@ -1157,7 +1353,8 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1157
1353
|
if (process.env.NODE_ENV !== 'production') {
|
|
1158
1354
|
console.debug('[crud-form] Required field errors prevented submit', requiredErrors)
|
|
1159
1355
|
}
|
|
1160
|
-
|
|
1356
|
+
const transformedErrors = await transformValidationErrors(requiredErrors)
|
|
1357
|
+
setErrors(transformedErrors)
|
|
1161
1358
|
flash(highlightedMessage, 'error')
|
|
1162
1359
|
return
|
|
1163
1360
|
}
|
|
@@ -1187,9 +1384,15 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1187
1384
|
if (customEntity) {
|
|
1188
1385
|
const mapped: Record<string, string> = {}
|
|
1189
1386
|
for (const [ek, ev] of Object.entries(result.fieldErrors)) mapped[ek.replace(/^cf_/, '')] = String(ev)
|
|
1190
|
-
|
|
1387
|
+
const transformedErrors = await transformValidationErrors(mapped)
|
|
1388
|
+
setErrors((prev) => ({ ...prev, ...transformedErrors }))
|
|
1191
1389
|
} else {
|
|
1192
|
-
|
|
1390
|
+
const transformedErrors = await transformValidationErrors(
|
|
1391
|
+
Object.fromEntries(
|
|
1392
|
+
Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)]),
|
|
1393
|
+
),
|
|
1394
|
+
)
|
|
1395
|
+
setErrors((prev) => ({ ...prev, ...transformedErrors }))
|
|
1193
1396
|
}
|
|
1194
1397
|
flash(highlightedMessage, 'error')
|
|
1195
1398
|
return
|
|
@@ -1210,7 +1413,8 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1210
1413
|
if (process.env.NODE_ENV !== 'production') {
|
|
1211
1414
|
console.debug('[crud-form] Schema validation failed', res.error.issues)
|
|
1212
1415
|
}
|
|
1213
|
-
|
|
1416
|
+
const transformedErrors = await transformValidationErrors(fieldErrors)
|
|
1417
|
+
setErrors(transformedErrors)
|
|
1214
1418
|
flash(highlightedMessage, 'error')
|
|
1215
1419
|
return
|
|
1216
1420
|
}
|
|
@@ -1218,12 +1422,23 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1218
1422
|
} else {
|
|
1219
1423
|
parsedValues = values as TValues
|
|
1220
1424
|
}
|
|
1425
|
+
let submitValues = parsedValues
|
|
1426
|
+
if (extendedInjectionEventsEnabled) {
|
|
1427
|
+
try {
|
|
1428
|
+
const result = await triggerInjectionEvent('transformFormData', submitValues, injectionContext)
|
|
1429
|
+
if (result.data) {
|
|
1430
|
+
submitValues = result.data as TValues
|
|
1431
|
+
}
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
console.error('[CrudForm] Error in transformFormData:', err)
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1221
1436
|
|
|
1222
1437
|
// Trigger onBeforeSave event for injection widgets
|
|
1223
1438
|
let injectionRequestHeaders: Record<string, string> | undefined
|
|
1224
1439
|
if (resolvedInjectionSpotId) {
|
|
1225
1440
|
try {
|
|
1226
|
-
const result = await triggerInjectionEvent('onBeforeSave',
|
|
1441
|
+
const result = await triggerInjectionEvent('onBeforeSave', submitValues, injectionContext)
|
|
1227
1442
|
if (!result.ok) {
|
|
1228
1443
|
try {
|
|
1229
1444
|
if (typeof window !== 'undefined') {
|
|
@@ -1243,7 +1458,8 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1243
1458
|
// ignore event dispatch failures
|
|
1244
1459
|
}
|
|
1245
1460
|
if (result.fieldErrors && Object.keys(result.fieldErrors).length) {
|
|
1246
|
-
|
|
1461
|
+
const transformedErrors = await transformValidationErrors(result.fieldErrors)
|
|
1462
|
+
setErrors(transformedErrors)
|
|
1247
1463
|
}
|
|
1248
1464
|
const message = result.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation')
|
|
1249
1465
|
flash(message, 'error')
|
|
@@ -1264,7 +1480,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1264
1480
|
// Trigger onSave event for injection widgets
|
|
1265
1481
|
if (resolvedInjectionSpotId) {
|
|
1266
1482
|
try {
|
|
1267
|
-
await triggerInjectionEvent('onSave',
|
|
1483
|
+
await triggerInjectionEvent('onSave', submitValues, injectionContext)
|
|
1268
1484
|
} catch (err) {
|
|
1269
1485
|
console.error('[CrudForm] Error in onSave:', err)
|
|
1270
1486
|
flash(t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
|
|
@@ -1276,22 +1492,22 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1276
1492
|
try {
|
|
1277
1493
|
if (injectionRequestHeaders && Object.keys(injectionRequestHeaders).length > 0) {
|
|
1278
1494
|
await withScopedApiRequestHeaders(injectionRequestHeaders, async () => {
|
|
1279
|
-
await onSubmit?.(
|
|
1495
|
+
await onSubmit?.(submitValues)
|
|
1280
1496
|
})
|
|
1281
1497
|
} else {
|
|
1282
|
-
await onSubmit?.(
|
|
1498
|
+
await onSubmit?.(submitValues)
|
|
1283
1499
|
}
|
|
1284
1500
|
|
|
1285
1501
|
// Trigger onAfterSave event for injection widgets
|
|
1286
1502
|
if (resolvedInjectionSpotId) {
|
|
1287
1503
|
try {
|
|
1288
|
-
await triggerInjectionEvent('onAfterSave',
|
|
1504
|
+
await triggerInjectionEvent('onAfterSave', submitValues, injectionContext)
|
|
1289
1505
|
} catch (err) {
|
|
1290
1506
|
console.error('[CrudForm] Error in onAfterSave:', err)
|
|
1291
1507
|
}
|
|
1292
1508
|
}
|
|
1293
1509
|
|
|
1294
|
-
if (successRedirect)
|
|
1510
|
+
if (successRedirect) await navigateWithGuard(successRedirect)
|
|
1295
1511
|
} catch (err: unknown) {
|
|
1296
1512
|
try {
|
|
1297
1513
|
if (typeof window !== 'undefined') {
|
|
@@ -1322,9 +1538,10 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
1322
1538
|
})()
|
|
1323
1539
|
: null
|
|
1324
1540
|
if (hasFieldErrors) {
|
|
1325
|
-
|
|
1541
|
+
const transformedErrors = await transformValidationErrors(combinedFieldErrors)
|
|
1542
|
+
setErrors(transformedErrors)
|
|
1326
1543
|
if (process.env.NODE_ENV !== 'production') {
|
|
1327
|
-
console.debug('[crud-form] Submission failed with field errors',
|
|
1544
|
+
console.debug('[crud-form] Submission failed with field errors', transformedErrors)
|
|
1328
1545
|
}
|
|
1329
1546
|
}
|
|
1330
1547
|
|