dx: ArrayField.saveOn and formDialog.save:false
Massimo Melina committed
Jun 30, 2025 at 23:48 UTC
5b1f28b11c01482dd38e0af925fd40be5bb33532
3 files changed
+37
-15
admin/src/ArrayField.ts
+32
-12
@@ -27,18 +27,23 @@ type ArrayFieldProps<T> = FieldProps<T[] | Dict<T>> & {
27
dialog?: Partial<DialogOptions>,
28
details?: boolean
29
objectK?: string
30
+ saveOn?: 'change' | 'close'
31
}
31
-export function ArrayField<T extends object>({ label, helperText, fields, value, onChange, onError, setApi, reorder, prepend, noRows, valuesForAdd, autoRowHeight, dialog, form, details, objectK, ...rest }: ArrayFieldProps<T>) {
32
+export function ArrayField<T extends object>({
33
+ label, helperText, fields, value, onChange, onError, setApi, reorder, prepend, noRows, valuesForAdd, autoRowHeight,
34
+ dialog, form, details, objectK, saveOn, ...rest
35
+}: ArrayFieldProps<T>) {
36
const valueA = Array.isArray(value) ? value
37
: !objectK || !value ? [] // avoid crash if non-array values are passed, especially developing plugins
38
: Object.entries(value).map(([k, v]) => ({ [objectK]: k, ...v }))
39
const rows = useMemo(() => valueA!.map((x,$idx) =>
40
setHidden({ ...x } as any, x.hasOwnProperty('id') ? { $idx } : { id: $idx })),
41
[JSON.stringify(valueA)]) //eslint-disable-line
38
- const formProp = (values: any) => ({
42
+ const getFormProp = (more: any) => (values: any) => ({
43
fields: callable(fields, values).map(({ $width, $column, $type, $hideUnder, showIf, $render, $mergeRender, ...rest }) =>
44
(!showIf || showIf(values)) && _.defaults(rest, byType[$type]?.field)),
45
...form,
46
+ ...more,
47
})
48
setApi?.({ isEqual: isOrderedEqual }) // don't rely on stringify, as it wouldn't work with non-json values
49
const [undo, setUndo] = useState<typeof valueA>()
@@ -98,14 +103,19 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
103
icon: Add,
104
title,
105
size: 'small',
101
- onClick: ev =>
102
- formDialog<T>({ form: formProp, title, values: valuesForAdd, dialogProps: dialog }).then(x => {
103
- if (!x) return
104
- const newValue = valueA.slice() || []
105
- if (prepend) newValue.unshift(x)
106
- else newValue.push(x)
107
- set(newValue, ev)
106
+ async onClick(ev) {
107
+ const res = await formDialog<T>({
108
+ form: getFormProp({}),
109
+ title,
110
+ values: valuesForAdd,
111
+ dialogProps: dialog
112
})
113
+ if (!res) return
114
+ const newValue = valueA.slice() || []
115
+ if (prepend) newValue.unshift(res)
116
+ else newValue.push(res)
117
+ set(newValue, ev)
118
+ }
119
}),
120
undo !== undefined && h(IconBtn, {
121
icon: Undo,
@@ -127,12 +137,17 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
137
ev.stopPropagation()
138
const res = await formDialog<T>({
139
values: row as any,
130
- form: formProp,
140
+ form: getFormProp(saveOn === 'change' && {
141
+ save: false,
142
+ onChange(values: T) {
143
+ updateRec($idx, values, 'change')
144
+ }
145
+ } || saveOn && { save: false }),
146
title: h(Fragment, {}, title, label && ' - ', label),
147
dialogProps: dialog
148
})
149
if (res)
135
- set(valueA.map((oldRec, i) => i === $idx ? res : oldRec), ev)
150
+ updateRec($idx, res, ev)
151
}
152
}),
153
h(GridActionsCellItem as any, {
@@ -170,10 +185,15 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
185
)
186
)
187
188
+ function updateRec($idx: number, rec: T, ev?: any) {
189
+ set(valueA.map((oldRec, i) => i === $idx ? rec : oldRec), ev)
190
+ }
191
+
192
function set(newValue: typeof valueA, event?: any) {
193
onChange(!objectK ? newValue : Object.fromEntries((newValue as any).map(({ [objectK]: k, ...v }) => [k, v])),
194
{ was: value, event })
176
- setUndo(valueA)
195
+ if (saveOn !== 'change')
196
+ setUndo(valueA)
197
}
198
199
}
admin/src/dialog.ts
+5
-2
@@ -153,15 +153,18 @@ export async function formDialog<T>(
153
Wrapper?: FC
154
},
155
) : Promise<T> {
156
+ let exposedValues: typeof values
157
return new Promise(resolve => {
158
const dialog = newDialog({
159
className: 'dialog-form',
159
- onClose: resolve,
160
+ onClose: x => resolve(x || exposedValues),
161
...options,
162
Content() {
163
const [curValues, setCurValues] = useState<Partial<T>>(values||{})
164
const { onChange, before, ...props } = typeof form === 'function' ? form(curValues) : form
164
- return h(Wrapper || Fragment, {},
165
+ if (props.save === false)
166
+ exposedValues = curValues
167
+ return h(Fragment, {},
168
before,
169
h(Form, {
170
...props,
mui-grid-form/StringField.ts
-1
@@ -78,7 +78,6 @@ export function StringField({ value, onChange, min, max, required, setApi, typin
78
})
79
return !suggestions ? render(null)
80
: h(Autocomplete, {
81
- value,
81
freeSolo: true,
82
options: suggestions,
83
renderInput: render,