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 = {