@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.2935.357c9db339",
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.2935.357c9db339",
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.2935.357c9db339",
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",
@@ -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(initialValues)
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(initialValues as Record<string, unknown>, definition.id)
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
- }, [extendedInjectionEventsEnabled, initialValues, injectedFieldDefinitions, triggerInjectionEvent])
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