@open-mercato/ui 0.6.5-develop.4691.1.bb409545b3 → 0.6.5-develop.4703.1.59a086a9ee

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.6.5-develop.4691.1.bb409545b3",
3
+ "version": "0.6.5-develop.4703.1.59a086a9ee",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -149,13 +149,13 @@
149
149
  "remark-gfm": "^4.0.1"
150
150
  },
151
151
  "peerDependencies": {
152
- "@open-mercato/shared": "0.6.5-develop.4691.1.bb409545b3",
152
+ "@open-mercato/shared": "0.6.5-develop.4703.1.59a086a9ee",
153
153
  "react": ">=18.0.0",
154
154
  "react-dom": ">=18.0.0",
155
155
  "react-is": ">=18.0.0"
156
156
  },
157
157
  "devDependencies": {
158
- "@open-mercato/shared": "0.6.5-develop.4691.1.bb409545b3",
158
+ "@open-mercato/shared": "0.6.5-develop.4703.1.59a086a9ee",
159
159
  "@testing-library/dom": "^10.4.1",
160
160
  "@testing-library/jest-dom": "^6.9.1",
161
161
  "@testing-library/react": "^16.3.1",
@@ -2211,12 +2211,22 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2211
2211
 
2212
2212
  const setValue = React.useCallback((id: string, nextValue: unknown) => {
2213
2213
  let nextData: CrudFormValues<TValues> | null = null
2214
+ let nextDirty: boolean | null = null
2214
2215
  setValues((prev) => {
2215
2216
  if (Object.is(prev[id], nextValue)) return prev
2216
2217
  nextData = { ...prev, [id]: nextValue } as CrudFormValues<TValues>
2217
2218
  valuesRef.current = nextData
2219
+ if (!(embedded && !trackDirtyWhenEmbedded)) {
2220
+ const baseline = dirtyBaselineSnapshotRef.current ?? createDirtySnapshot(prev as Record<string, unknown>)
2221
+ dirtyBaselineSnapshotRef.current = baseline
2222
+ nextDirty = createDirtySnapshot(nextData as Record<string, unknown>) !== baseline
2223
+ }
2218
2224
  return nextData
2219
2225
  })
2226
+ if (nextDirty !== null) {
2227
+ isDirtyRef.current = nextDirty
2228
+ setHasUnsavedChanges(nextDirty)
2229
+ }
2220
2230
  const clearedMessages: string[] = []
2221
2231
  setErrors((prev) => {
2222
2232
  if (!Object.keys(prev).length) return prev
@@ -2281,7 +2291,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2281
2291
  }).catch((err) => {
2282
2292
  console.error('[CrudForm] Error in onFieldChange:', err)
2283
2293
  })
2284
- }, [extendedInjectionEventsEnabled, flash, t, translateValidationMessage, triggerInjectionEvent])
2294
+ }, [embedded, extendedInjectionEventsEnabled, flash, t, trackDirtyWhenEmbedded, translateValidationMessage, triggerInjectionEvent])
2285
2295
 
2286
2296
  const onBlurRequest = React.useCallback((fieldId: string) => {
2287
2297
  void validateFieldOnBlur(fieldId)
@@ -62,6 +62,32 @@ describe('CrudForm unsaved navigation guard', () => {
62
62
  expect(event.returnValue).toBe('')
63
63
  })
64
64
 
65
+ it('marks the form dirty on the first edit even when no baseline has been initialized yet', async () => {
66
+ const onDirtyChange = jest.fn()
67
+ const { container } = renderWithProviders(
68
+ <CrudForm title="Form" fields={fields} onSubmit={() => {}} onDirtyChange={onDirtyChange} />,
69
+ {
70
+ dict: {
71
+ 'ui.forms.actions.save': 'Save',
72
+ 'ui.forms.confirmUnsavedChanges': 'Unsaved changes',
73
+ },
74
+ },
75
+ )
76
+
77
+ const input = container.querySelector('[data-crud-field-id="name"] input[type="text"]') as HTMLInputElement
78
+ await act(async () => {
79
+ fireEvent.change(input, { target: { value: 'Alice updated' } })
80
+ })
81
+
82
+ expect(onDirtyChange).toHaveBeenLastCalledWith(true)
83
+
84
+ const event = new Event('beforeunload', { cancelable: true }) as BeforeUnloadEvent
85
+ Object.defineProperty(event, 'returnValue', { writable: true, value: undefined })
86
+ window.dispatchEvent(event)
87
+
88
+ expect(event.defaultPrevented).toBe(true)
89
+ })
90
+
65
91
  it('blocks router-driven history pushes until the user confirms leaving', async () => {
66
92
  confirmDialogMock.mockResolvedValueOnce(false)
67
93