better code: simplified dom
Massimo Melina committed
Apr 22, 2024 at 16:43 UTC
49c0634495bba1724023dd069f391bda84172671
2 files changed
+88
-102
admin/src/AccountForm.ts
+1
-1
@@ -31,7 +31,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
31
const ref = useRef<HTMLFormElement>()
32
const expired = Boolean(values.expire)
33
return h(Form, {
34
- formRef: ref,
34
+ formRef: ref,
35
values,
36
set(v, k) {
37
setValues(values => ({ ...values, [k]: v }))
mui-grid-form/index.ts
+87
-101
@@ -1,16 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import {
4
- createElement as h,
5
- FC,
6
- Fragment,
7
- HTMLProps,
8
- isValidElement,
9
- ReactElement,
10
- ReactNode,
11
- useEffect,
12
- useState,
13
- useRef,
4
+ createElement as h, FC, Fragment, isValidElement, ReactElement, ReactNode, useEffect, useState, useRef,
5
MutableRefObject
6
} from 'react'
7
import { Box, BoxProps, Button, Grid } from '@mui/material'
@@ -73,7 +64,6 @@ export interface FormProps<Values> extends Partial<BoxProps> {
64
formRef?: MutableRefObject<HTMLFormElement | undefined>
65
saveOnEnter?: boolean
66
gridProps?: Partial<GridProps>
76
- formProps?: HTMLProps<HTMLFormElement>
67
}
68
enum Phase { Idle, WaitValues, Validating }
69
@@ -90,7 +80,6 @@ export function Form<Values extends Dict>({
80
onError,
81
saveOnEnter,
82
gridProps,
93
- formProps,
83
...rest
84
}: FormProps<Values>) {
85
const mounted = useRef(false)
@@ -111,104 +100,101 @@ export function Form<Values extends Dict>({
100
const keyMet: Dict<number> = {}
101
102
const apis: Dict<FieldApi<unknown>> = {} // consider { [K in keyof Values]?: FieldApi<Values[K]> }
114
- return h('form', {
115
- ref: formRef && (x => formRef.current = x ? x as HTMLFormElement : undefined),
116
- ...formProps,
103
+ return h(Box, {
104
+ component: 'form',
105
+ display: 'flex',
106
+ flexDirection: 'column',
107
+ gap: 3,
108
+ ref: formRef,
109
onSubmit(ev) {
110
ev.preventDefault()
111
},
112
onKeyDown(ev) {
113
if (saveBtn && !saveBtn.disabled && (ev.ctrlKey || ev.metaKey) && ev.key === 'Enter')
114
pleaseSubmit()
123
- }
115
+ },
116
+ ...rest,
117
},
125
- h(Box, {
118
+ h(Grid, { container:true, rowSpacing:3, columnSpacing:1, ...gridProps },
119
+ fields.map((row, idx) => {
120
+ if (!row)
121
+ return null
122
+ if (isValidElement(row))
123
+ return h(Grid, { key: idx, item: true, xs: 12 }, row)
124
+ const { k, fromField=_.identity, toField=_.identity, getError, error, ...field } = row
125
+ let errMsg = errors[k] || error || fieldExceptions[k]
126
+ if (errMsg === true)
127
+ errMsg = "Not valid"
128
+ if (k) {
129
+ const originalValue = row.hasOwnProperty('value') ? row.value : values?.[k]
130
+ const whole = { ...row, ...field }
131
+ Object.assign(field, {
132
+ value: toField(originalValue),
133
+ error: Boolean(errMsg || error) || undefined,
134
+ setApi(api) { apis[k] = api },
135
+ onKeyDown(event: any) {
136
+ if (saveOnEnter && event.key === 'Enter')
137
+ pleaseSubmit()
138
+ },
139
+ onChange(v: unknown) {
140
+ try {
141
+ v = fromField(v, { originalValue })
142
+ setFieldExceptions(x => ({ ...x, [k]: false }))
143
+ if ((apis[k]?.isEqual || _.isEqual)(v, originalValue)) return
144
+ set(v, k)
145
+ pleaseValidate(k)
146
+ }
147
+ catch (e) {
148
+ setFieldExceptions(x => ({ ...x, [k]: (e as any)?.message || String(e) || true }))
149
+ }
150
+ },
151
+ } as Partial<FieldProps<any>>)
152
+ if (Array.isArray(field.helperText))
153
+ field.helperText = h(Fragment, {}, ...field.helperText)
154
+ if (errMsg) // special rendering when we have both error and helperText. "hr" would be nice but issues a warning because contained in a <p>
155
+ field.helperText = !field.helperText ? errMsg
156
+ : h(Fragment, {},
157
+ h('span', { style: { borderBottom: '1px solid' } }, errMsg),
158
+ h(Box, { color: 'text.primary', component: 'span', /*avoid console warning*/ display: 'block' },
159
+ field.helperText),
160
+ )
161
+ if (field.label === undefined)
162
+ field.label = labelFromKey(k)
163
+ _.defaults(field, defaults?.(whole))
164
+ }
165
+ {
166
+ const { xs=12, sm, md, lg, xl, comp=StringField, before, after,
167
+ fromField, toField, // don't propagate
168
+ ...rest } = field
169
+ Object.assign(rest, { name: k })
170
+ const n = (keyMet[k] = (keyMet[k] || 0) + 1)
171
+ return h(Grid, { key: k ? k + n : idx, item: true, xs, sm, md, lg, xl },
172
+ before,
173
+ isValidElement(comp) ? comp : h(comp, rest),
174
+ after
175
+ )
176
+ }
177
+ })
178
+ ),
179
+ saveBtn && h(Box, {
180
display: 'flex',
127
- flexDirection: 'column',
128
- gap: 3,
129
- ...rest
181
+ alignItems: 'center',
182
+ sx: Object.assign({},
183
+ stickyBar && {
184
+ width: 'fit-content', zIndex: 2, backgroundColor: 'background.paper', borderRadius: 1,
185
+ position: 'sticky', bottom: 0, p: 1, m: -1, boxShadow: '0px 0px 30px #000',
186
+ },
187
+ barSx)
188
},
131
- h(Grid, { container:true, rowSpacing:3, columnSpacing:1, ...gridProps },
132
- fields.map((row, idx) => {
133
- if (!row)
134
- return null
135
- if (isValidElement(row))
136
- return h(Grid, { key: idx, item: true, xs: 12 }, row)
137
- const { k, fromField=_.identity, toField=_.identity, getError, error, ...field } = row
138
- let errMsg = errors[k] || error || fieldExceptions[k]
139
- if (errMsg === true)
140
- errMsg = "Not valid"
141
- if (k) {
142
- const originalValue = row.hasOwnProperty('value') ? row.value : values?.[k]
143
- const whole = { ...row, ...field }
144
- Object.assign(field, {
145
- value: toField(originalValue),
146
- error: Boolean(errMsg || error) || undefined,
147
- setApi(api) { apis[k] = api },
148
- onKeyDown(event: any) {
149
- if (saveOnEnter && event.key === 'Enter')
150
- pleaseSubmit()
151
- },
152
- onChange(v: unknown) {
153
- try {
154
- v = fromField(v, { originalValue })
155
- setFieldExceptions(x => ({ ...x, [k]: false }))
156
- if ((apis[k]?.isEqual || _.isEqual)(v, originalValue)) return
157
- set(v, k)
158
- pleaseValidate(k)
159
- }
160
- catch (e) {
161
- setFieldExceptions(x => ({ ...x, [k]: (e as any)?.message || String(e) || true }))
162
- }
163
- },
164
- } as Partial<FieldProps<any>>)
165
- if (Array.isArray(field.helperText))
166
- field.helperText = h(Fragment, {}, ...field.helperText)
167
- if (errMsg) // special rendering when we have both error and helperText. "hr" would be nice but issues a warning because contained in a <p>
168
- field.helperText = !field.helperText ? errMsg
169
- : h(Fragment, {},
170
- h('span', { style: { borderBottom: '1px solid' } }, errMsg),
171
- h(Box, { color: 'text.primary', component: 'span', /*avoid console warning*/ display: 'block' },
172
- field.helperText),
173
- )
174
- if (field.label === undefined)
175
- field.label = labelFromKey(k)
176
- _.defaults(field, defaults?.(whole))
177
- }
178
- {
179
- const { xs=12, sm, md, lg, xl, comp=StringField, before, after,
180
- fromField, toField, // don't propagate
181
- ...rest } = field
182
- Object.assign(rest, { name: k })
183
- const n = (keyMet[k] = (keyMet[k] || 0) + 1)
184
- return h(Grid, { key: k ? k + n : idx, item: true, xs, sm, md, lg, xl },
185
- before,
186
- isValidElement(comp) ? comp : h(comp, rest),
187
- after
188
- )
189
- }
190
- })
191
- ),
192
- saveBtn && h(Box, {
193
- display: 'flex',
194
- alignItems: 'center',
195
- sx: Object.assign({},
196
- stickyBar && {
197
- width: 'fit-content', zIndex: 2, backgroundColor: 'background.paper', borderRadius: 1,
198
- position: 'sticky', bottom: 0, p: 1, m: -1, boxShadow: '0px 0px 30px #000',
199
- },
200
- barSx)
201
- },
202
- h(LoadingButton, {
203
- variant: 'contained',
204
- startIcon: h(Save),
205
- children: "Save",
206
- loading: useDebounce(phase !== Phase.Idle), // debounce fixes click being ignored at state change, and flickering
207
- ...saveBtn,
208
- onClick: pleaseSubmit,
209
- }),
210
- ...addToBar,
211
- )
189
+ h(LoadingButton, {
190
+ variant: 'contained',
191
+ startIcon: h(Save),
192
+ children: "Save",
193
+ loading: useDebounce(phase !== Phase.Idle), // debounce fixes click being ignored at state change, and flickering
194
+ ...saveBtn,
195
+ onClick: pleaseSubmit,
196
+ }),
197
+ ...addToBar,
198
)
199
)
200