dx: removed unused packages and added form props
Massimo Melina committed
Jul 4, 2025 at 11:34 UTC
a6324016b89dee5302ef841dce83a9f0f363d18a
3 files changed
+19
-14
mui-grid-form/index.ts
+14
-4
@@ -51,18 +51,20 @@ export interface FieldProps<T> {
51
[rest: string]: any
52
}
53
54
-type Dict<T=any> = Record<string,T>
54
+export type Dict<T=any> = Record<string,T>
55
56
export interface FormProps<Values> extends Partial<BoxProps> {
57
fields: (FieldDescriptor | ReactElement<unknown> | null | undefined | false)[]
58
defaults?: (f:FieldDescriptor) => Partial<FieldDescriptor>
59
values: Values
60
set: (v: any, fieldK: keyof Values) => void
61
+ get?: (fieldK: keyof Values | string) => any // the string is for a strange TS behavior on templated types
62
save: false | Partial<Parameters<typeof Button>[0]> | (()=>any)
63
stickyBar?: boolean
64
addToBar?: ReactNode[]
65
barSx?: Dict
66
onError?: (err: any) => any
67
+ onValidation?: (errs: false | Dict<ValidationError>) => any
68
formRef?: MutableRefObject<HTMLFormElement | undefined>
69
saveOnEnter?: boolean
70
gridProps?: Partial<GridProps>
@@ -73,6 +75,7 @@ export function Form<Values extends Dict>({
75
fields,
76
values,
77
set,
78
+ get,
79
defaults,
80
save,
81
stickyBar,
@@ -80,6 +83,7 @@ export function Form<Values extends Dict>({
83
barSx,
84
formRef,
85
onError,
86
+ onValidation,
87
saveOnEnter,
88
gridProps,
89
...rest
@@ -132,7 +136,7 @@ export function Form<Values extends Dict>({
136
if (errMsg === true)
137
errMsg = "Not valid"
138
if (k) {
135
- const originalValue = row.hasOwnProperty('value') ? row.value : values?.[k]
139
+ const originalValue = row.hasOwnProperty('value') ? row.value : getValueFor(k)
140
Object.assign(field, {
141
name: k,
142
value: toField(originalValue),
@@ -211,6 +215,10 @@ export function Form<Values extends Dict>({
215
setPhase(cur => cur === Phase.Idle ? Phase.WaitValues : cur)) // don't interfere with ongoing process
216
}
217
218
+ function getValueFor(k : string) {
219
+ return get ? get(k) : values?.[k]
220
+ }
221
+
222
async function phaseChange() {
223
if (phase === Phase.Idle) return
224
if (phase === Phase.WaitValues)
@@ -220,7 +228,7 @@ export function Form<Values extends Dict>({
228
for (const f of fields) {
229
if (!f || isValidElement(f) || !f.k) continue
230
const { k } = f
223
- const v = values?.[k]
231
+ const v = getValueFor(k)
232
let err: ReactNode
233
try {
234
err = await apis[k]?.getError?.(v, { values, fields })
@@ -236,9 +244,11 @@ export function Form<Values extends Dict>({
244
if (!mounted.current) return // abort
245
}
246
setErrors(errs)
247
+ const anyError = Object.values(errs).some(Boolean)
248
+ onValidation?.(anyError && errs)
249
try {
250
if (!submitAfterValidation.current) return
241
- if (Object.values(errs).some(Boolean))
251
+ if (anyError)
252
return await onError?.(MSG)
253
const cb = saveBtn && saveBtn.onClick
254
if (cb) // @ts-ignore
package.json
-5
@@ -101,8 +101,6 @@
101
},
102
"devDependencies": {
103
"@playwright/test": "^1.51.1",
104
- "@types/archiver": "^5.1.1",
105
- "@types/basic-auth": "^1.1.3",
104
"@types/formidable": "^3.4.1",
105
"@types/koa": "^2.13.4",
106
"@types/koa__router": "^8.0.11",
@@ -115,14 +113,11 @@
113
"@types/node": "^20.17.30",
114
"@types/node-forge": "^1.3.11",
115
"@types/picomatch": "^2.3.3",
118
- "@types/tough-cookie": "^4.0.2",
119
- "@types/unzipper": "^0.10.5",
116
"@yao-pkg/pkg": "^6.4.0",
117
"cross-env": "^7.0.3",
118
"koa-better-http-proxy": "^0.2.9",
119
"nm-prune": "^5.0.0",
120
"nodemon": "^3.1.9",
125
- "tough-cookie": "^4.0.0",
121
"tsx": "^4.19.3",
122
"typescript": "^5.8.3"
123
}
tests/test.ts
+5
-5
@@ -203,7 +203,7 @@ describe('after-login', () => {
203
await rm(fn, {force: true})
204
const neededTime = 300
205
const makeAbortedRequest = (afterMs: number) => {
206
- const r = reqUpload(UPLOAD_DEST, 0, makeReadableThatTakes(neededTime))()
206
+ const r = reqUpload(UPLOAD_DEST + '?supposedToAbort', 0, makeReadableThatTakes(neededTime))()
207
setTimeout(r.abort, afterMs)
208
return r.catch(() => {}) // wait for it to fail
209
.then(() => wait(1)) // aborted requests don't guarantee that the server has finished and released the file, so we wait some arbitrary time
@@ -212,13 +212,13 @@ describe('after-login', () => {
212
await makeAbortedRequest(timeFirstRequest)
213
const getTempSize = () => try_(() => statSync(fn)?.size)
214
const size = getTempSize()
215
- if (!size) // shouldn't be empty
215
+ if (!size) // temp file is left, not empty
216
throw Error("missing temp file")
217
await makeAbortedRequest(timeFirstRequest * .5) // upload less than r1
218
- if (size !== getTempSize()) // shouldn't change, as r2 is smaller
218
+ if (size !== getTempSize()) // shouldn't change, as r2 is smaller, and therefore only wrote to secondary temp file
219
throw Error("modified temp file")
220
await makeAbortedRequest(timeFirstRequest * 1.5) // upload more than r1
221
- if (!(size < getTempSize()!)) // should be increased
221
+ if (!(size < getTempSize()!)) // should be increased, as secondary temp file got bigger and replaced primary one
222
throw Error("temp file not enlarged")
223
await reqUpload(UPLOAD_DEST, 200, makeReadableThatTakes(0))() // quickly complete the upload, and check for final size
224
if (getTempSize())
@@ -264,7 +264,7 @@ function login(usr: string, pwd=password) {
264
265
function reqUpload(dest: string, tester: Tester, body?: string | Readable, size?: number, resume=0) {
266
if (resume)
267
- dest += '?resume=' + resume
267
+ dest += (dest.includes('?') ? '&' : '?') + 'resume=' + resume
268
size ??= (body as any)?.length ?? statSync(SAMPLE_FILE_PATH).size // it's ok that Readable.length is undefined
269
if (tester === 200)
270
tester = {