@open-mercato/ui 0.5.1-develop.2935.357c9db339 → 0.5.1-develop.2949.009dcdd2d5
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2949.009dcdd2d5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -132,12 +132,12 @@
|
|
|
132
132
|
"recharts": "^3.8.1"
|
|
133
133
|
},
|
|
134
134
|
"peerDependencies": {
|
|
135
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
135
|
+
"@open-mercato/shared": "0.5.1-develop.2949.009dcdd2d5",
|
|
136
136
|
"react": ">=18.0.0",
|
|
137
137
|
"react-dom": ">=18.0.0"
|
|
138
138
|
},
|
|
139
139
|
"devDependencies": {
|
|
140
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
140
|
+
"@open-mercato/shared": "0.5.1-develop.2949.009dcdd2d5",
|
|
141
141
|
"@testing-library/dom": "^10.4.1",
|
|
142
142
|
"@testing-library/jest-dom": "^6.9.1",
|
|
143
143
|
"@testing-library/react": "^16.3.1",
|
package/src/backend/CrudForm.tsx
CHANGED
|
@@ -360,6 +360,20 @@ function readByDotPath(source: Record<string, unknown> | undefined, path: string
|
|
|
360
360
|
return current
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
function readInitialCustomFieldValue(
|
|
364
|
+
source: Record<string, unknown> | undefined,
|
|
365
|
+
key: string,
|
|
366
|
+
): unknown {
|
|
367
|
+
if (!source || !key) return undefined
|
|
368
|
+
const candidates = [`cf_${key}`, `cf:${key}`, key]
|
|
369
|
+
for (const candidate of candidates) {
|
|
370
|
+
if (Object.prototype.hasOwnProperty.call(source, candidate)) {
|
|
371
|
+
return source[candidate]
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return undefined
|
|
375
|
+
}
|
|
376
|
+
|
|
363
377
|
function serializeIssuePath(path: ReadonlyArray<string | number | symbol>): string | null {
|
|
364
378
|
if (!Array.isArray(path) || path.length === 0) return null
|
|
365
379
|
const segments = path
|
|
@@ -2024,19 +2038,32 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
2024
2038
|
const dirtyBaselineSnapshotRef = React.useRef<string | undefined>(undefined)
|
|
2025
2039
|
React.useLayoutEffect(() => {
|
|
2026
2040
|
if (!initialValues) return
|
|
2027
|
-
const snapshot = JSON.stringify(
|
|
2041
|
+
const snapshot = JSON.stringify({
|
|
2042
|
+
initialValues,
|
|
2043
|
+
injectedFieldIds: injectedFieldDefinitions.map((definition) => definition.id),
|
|
2044
|
+
customFieldMappings: cfDefinitions.map((definition) => definition.key),
|
|
2045
|
+
})
|
|
2028
2046
|
if (appliedInitialValuesSnapshotRef.current === snapshot) return
|
|
2029
2047
|
appliedInitialValuesSnapshotRef.current = snapshot
|
|
2048
|
+
const initialRecord = initialValues as Record<string, unknown>
|
|
2030
2049
|
let mergedValues: CrudFormValues<TValues> | null = null
|
|
2031
2050
|
setValues((prev) => {
|
|
2032
2051
|
const merged = { ...prev, ...initialValues } as CrudFormValues<TValues>
|
|
2033
2052
|
for (const definition of injectedFieldDefinitions) {
|
|
2034
2053
|
if (merged[definition.id] !== undefined) continue
|
|
2035
|
-
const extracted = readByDotPath(
|
|
2054
|
+
const extracted = readByDotPath(initialRecord, definition.id)
|
|
2036
2055
|
if (extracted !== undefined) {
|
|
2037
2056
|
;(merged as Record<string, unknown>)[definition.id] = extracted
|
|
2038
2057
|
}
|
|
2039
2058
|
}
|
|
2059
|
+
for (const definition of cfDefinitions) {
|
|
2060
|
+
const targetId = customEntity ? definition.key : `cf_${definition.key}`
|
|
2061
|
+
if (!targetId || merged[targetId] !== undefined) continue
|
|
2062
|
+
const extracted = readInitialCustomFieldValue(initialRecord, definition.key)
|
|
2063
|
+
if (extracted !== undefined) {
|
|
2064
|
+
;(merged as Record<string, unknown>)[targetId] = extracted
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2040
2067
|
mergedValues = merged
|
|
2041
2068
|
return mergedValues
|
|
2042
2069
|
})
|
|
@@ -2064,7 +2091,14 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
2064
2091
|
return () => {
|
|
2065
2092
|
cancelled = true
|
|
2066
2093
|
}
|
|
2067
|
-
}, [
|
|
2094
|
+
}, [
|
|
2095
|
+
cfDefinitions,
|
|
2096
|
+
customEntity,
|
|
2097
|
+
extendedInjectionEventsEnabled,
|
|
2098
|
+
initialValues,
|
|
2099
|
+
injectedFieldDefinitions,
|
|
2100
|
+
triggerInjectionEvent,
|
|
2101
|
+
])
|
|
2068
2102
|
|
|
2069
2103
|
// Apply custom field defaults on create flows (one-time, ref-guarded).
|
|
2070
2104
|
// Mode is determined from the host-provided initialValues prop, not from
|
|
@@ -121,6 +121,89 @@ describe('CrudForm custom field loading', () => {
|
|
|
121
121
|
expect(fetchCustomFieldFormStructureMock).toHaveBeenCalledTimes(1)
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
+
it('hydrates bare-key custom field initialValues after definitions load and after remount', async () => {
|
|
125
|
+
const loadOptions = jest.fn().mockResolvedValue([
|
|
126
|
+
{ value: 'CNY', label: 'CNY - Chinese Yuan' },
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
buildFormFieldFromCustomFieldDefMock.mockImplementation((definition: any) => ({
|
|
130
|
+
id: `cf_${definition.key}`,
|
|
131
|
+
label: definition.label ?? definition.key,
|
|
132
|
+
type: 'select',
|
|
133
|
+
loadOptions,
|
|
134
|
+
}))
|
|
135
|
+
fetchCustomFieldFormStructureMock.mockResolvedValue({
|
|
136
|
+
fields: [],
|
|
137
|
+
definitions: [
|
|
138
|
+
{
|
|
139
|
+
entityId: 'customers:customer_company_profile',
|
|
140
|
+
key: 'preferred_currency',
|
|
141
|
+
label: 'Preferred currency',
|
|
142
|
+
kind: 'currency',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
metadata: {
|
|
146
|
+
items: [],
|
|
147
|
+
fieldsetsByEntity: {},
|
|
148
|
+
entitySettings: {},
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
function Host({ visible }: { visible: boolean }) {
|
|
153
|
+
if (!visible) return null
|
|
154
|
+
return (
|
|
155
|
+
<CrudForm
|
|
156
|
+
embedded
|
|
157
|
+
title="Form"
|
|
158
|
+
entityId="customers:customer_company_profile"
|
|
159
|
+
fields={fields}
|
|
160
|
+
groups={groups}
|
|
161
|
+
initialValues={{ name: 'Acme', preferred_currency: 'CNY' }}
|
|
162
|
+
onSubmit={() => {}}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { container, rerender } = renderWithProviders(
|
|
168
|
+
<Host visible />,
|
|
169
|
+
{
|
|
170
|
+
dict: {
|
|
171
|
+
'ui.forms.actions.save': 'Save',
|
|
172
|
+
'entities.customFields.manageFieldset': 'Manage fields',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const getCurrencySelect = () =>
|
|
178
|
+
container.querySelector('[data-crud-field-id="cf_preferred_currency"] select') as HTMLSelectElement | null
|
|
179
|
+
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
expect(fetchCustomFieldFormStructureMock).toHaveBeenCalledTimes(1)
|
|
182
|
+
})
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(getCurrencySelect()).not.toBeNull()
|
|
185
|
+
})
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(getCurrencySelect()?.value).toBe('CNY')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
await act(async () => {
|
|
191
|
+
rerender(<Host visible={false} />)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
await act(async () => {
|
|
195
|
+
rerender(<Host visible />)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(getCurrencySelect()).not.toBeNull()
|
|
200
|
+
})
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
expect(getCurrencySelect()?.value).toBe('CNY')
|
|
203
|
+
})
|
|
204
|
+
expect(loadOptions).toHaveBeenCalled()
|
|
205
|
+
})
|
|
206
|
+
|
|
124
207
|
it('opens the field manager without submitting the parent form', async () => {
|
|
125
208
|
const handleSubmit = jest.fn().mockResolvedValue(undefined)
|
|
126
209
|
|