diff --git a/ivette/src/dome/renderer/layout/forms.tsx b/ivette/src/dome/renderer/layout/forms.tsx index 166b6352c34439eb2acae1a2db935444c989c285..12cc162f1f588fda7bd846e7b161af5f93d51943 100644 --- a/ivette/src/dome/renderer/layout/forms.tsx +++ b/ivette/src/dome/renderer/layout/forms.tsx @@ -162,6 +162,13 @@ export class BufferController { removeError(): void { this.errors--; } } +export type Equal<A> = (a:A, b:A) => boolean; + +function compare<A>(equal: Equal<A> | undefined, a: A, b: A): boolean +{ + return equal ? equal(a, b) : a === b; +} + /** Insert a temporary buffer to stack modifications. Values are imported from the input state, and modifications are stacked into an internal buffer. @@ -181,7 +188,7 @@ export class BufferController { export function useBuffer<A>( remote : BufferController, state: FieldState<A>, - equal?: (a: A, b: A) => boolean + equal?: Equal<A>, ): FieldState<A> { const { value, error, reset, onChanged } = state; @@ -189,53 +196,56 @@ export function useBuffer<A>( const [ buffer, setBuffer ] = React.useState<A>(value); const [ berror, setBerror ] = React.useState<FieldError>(error); + const valid = !isValid(berror); + const rollback = reset ?? value; + + // --- Error Count + React.useEffect(() => { + if (valid) return; + remote.addError(); + return () => remote.removeError(); + }, [remote, valid]); + // --- Reset React.useEffect(() => { if (modified) { const doReset = (): void => { - !isValid(berror) && remote.removeError(); setModified(false); - setBuffer(reset ?? value); + setBuffer(rollback); setBerror(undefined); }; remote.onReset(doReset); return () => remote.offReset(doReset); } else return; - }, [remote, modified, value, berror, reset]); + }, [remote, modified, rollback]); // --- Commit React.useEffect(() => { if(modified) { const doCommit = (): void => { - if(isValid(berror)) { + if (valid) { setModified(false); onChanged(buffer, undefined, false); } else { - remote.removeError(); setModified(false); - setBuffer(reset ?? value); + setBuffer(rollback); setBerror(undefined); } }; remote.onCommit(doCommit); return () => remote.offCommit(doCommit); } else return; - }, [remote, modified, value, berror, buffer, reset, onChanged]); + }, [remote, modified, valid, buffer, rollback, onChanged]); // --- Callback const onLocalChange = React.useCallback( (newValue, newError, isReset) => { - if(!isValid(berror) && isValid(newError)) { - remote.removeError(); - } else if(isValid(berror) && !isValid(newError)) { - remote.addError(); - } setModified(!isReset); setBuffer(newValue); setBerror(newError); - if (isReset && (equal ? !equal(newValue, value) : (newValue !== value))) + if (isReset && !compare(equal, newValue, value)) onChanged(newValue, newError, isReset); - }, [value, onChanged, equal, berror, remote]); + }, [equal, value, onChanged]); return { value: modified ? buffer : value, diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 25f6d98e07aa7a7a0ec3be96693506073f530ac7..f7e4f52f1aa74f27084b091525efea40a2dc1751 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -327,8 +327,8 @@ export function useServerField<A>( }, [setState]); return { - value: isValid(error) ? stateValue : local, error, + value: isValid(error) ? stateValue : local, reset: isValid(error) ? undefined : stateValue, onChanged: update };