UNPKG

mobx-easy-form

Version:

Simple and performant form library built with MobX

247 lines (246 loc) 7.24 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); let mobx = require("mobx"); let react = require("react"); //#region src/isPromise.ts function isPromise(value) { return !!(value && typeof value === "object" && "then" in value && typeof value.then === "function"); } //#endregion //#region src/mapValues.ts function mapValues(object, callbackFn) { return Object.fromEntries(Object.entries(object).map(([key, value]) => { return [key, callbackFn(value)]; })); } //#endregion //#region src/createForm.ts function createForm({ onSubmit }) { const fields = (0, mobx.observable)({}); const state = (0, mobx.observable)({ isSubmitting: false, valuesAtLastSubmit: void 0, submitCount: 0 }); const computed = (0, mobx.observable)({ get isDirty() { return Object.values(fields).some((field) => field.computed.isDirty); }, get errorList() { return Object.values(fields).map((field) => field.computed.error).filter((error) => error !== void 0); }, get isError() { return Object.values(fields).some((field) => !!field.computed.error); }, get isValid() { return !this.isError; }, get valueList() { return String(Object.values(fields).map((field) => field.state.value)); }, get isChangedSinceLastSubmit() { if (state.submitCount === 0) return this.isDirty; return this.valueList !== state.valuesAtLastSubmit; } }); return { fields, state, computed, actions: { add(field) { fields[field.state.id] = field; }, submit: (0, mobx.action)(function submit() { state.isSubmitting = true; state.submitCount++; state.valuesAtLastSubmit = computed.valueList; for (const fieldId in fields) { const field = fields[fieldId]; field.state.wasEverFocused = true; field.state.wasEverBlurred = true; } if (computed.isError) { state.isSubmitting = false; return; } const maybePromise = onSubmit({ fields, rawValues: mapValues(fields, (field) => field.state.value), values: mapValues(fields, (field) => field.computed.parsed) }); if (isPromise(maybePromise)) return Promise.resolve(maybePromise).finally(() => { (0, mobx.runInAction)(() => { state.isSubmitting = false; }); }); (0, mobx.runInAction)(() => { state.isSubmitting = false; }); return maybePromise; }) } }; } //#endregion //#region src/createField.ts function isStandardSchema(schema) { return typeof schema === "object" && schema !== null && "~standard" in schema; } function isYupLikeSchema(schema) { return typeof schema === "object" && schema !== null && "validateSync" in schema && typeof schema.validateSync === "function"; } function createField(args) { const { id, initialValue, initialError, form } = args; const validate = args.validate; const validationSchema = args.validationSchema; const runValidation = getValidateFunction(validate, validationSchema); const state = (0, mobx.observable)({ id, errorOverride: initialError, value: initialValue, isFocused: false, wasEverFocused: false, wasEverBlurred: false }); const computed = (0, mobx.observable)({ get parsed() { const result = runValidation(state.value); if (result.error) return void 0; return result.parsed; }, get isDirty() { return JSON.stringify(state.value) !== JSON.stringify(initialValue); }, get error() { const { error } = runValidation(state.value); if (state.errorOverride) return state.errorOverride; if (error instanceof Error && error.name === "ValidationError") { const msg = error.message; if (msg !== null && typeof msg === "object" && "value" in msg && msg.value !== void 0) return String(msg.value); if (typeof msg === "string") return msg; return String(error); } if (error instanceof Error) return error.message; return error; }, get ifWasEverFocusedThenError() { if (!state.wasEverFocused) return void 0; if (!computed.error) return void 0; return String(computed.error); }, get ifWasEverBlurredThenError() { if (!state.wasEverBlurred) return void 0; if (!computed.error) return void 0; return String(computed.error); } }); const field = { state, computed, actions: { onFocus: (0, mobx.action)(function onFocus() { state.isFocused = true; state.wasEverFocused = true; }), onBlur: (0, mobx.action)(function onBlur() { state.isFocused = false; state.wasEverBlurred = true; }), onChange: (0, mobx.action)(function onChange(value) { if (state.errorOverride) state.errorOverride = void 0; state.value = value; }), setError: (0, mobx.action)(function setError(value) { state.errorOverride = value; }) } }; form.actions.add(field); return field; } function getValidateFunction(validate, validationSchema) { if (validate) return validate; if (validationSchema) { if (isYupLikeSchema(validationSchema)) { const schema = validationSchema; return function validateWithYup(value) { try { return { parsed: schema.validateSync(value, { abortEarly: true }), error: void 0 }; } catch (error) { if (error instanceof Error && error.name === "ValidationError") return { parsed: void 0, error }; throw error; } }; } if (isStandardSchema(validationSchema)) { const schema = validationSchema; return function validateWithStandardSchema(value) { const result = schema["~standard"].validate(value); if (result instanceof Promise) throw new TypeError("mobx-easy-form: Standard Schema async validation is not supported. Use a synchronous schema."); if (result.issues) return { parsed: void 0, error: result.issues[0]?.message ?? "Invalid" }; return { parsed: result.value, error: void 0 }; }; } throw new TypeError("mobx-easy-form: validationSchema must implement Standard Schema or expose a `validateSync` method (Yup-like)."); } return function passthrough(value) { return { parsed: value, error: void 0 }; }; } //#endregion //#region src/useMemoOne.ts function areInputsEqual(next, prev) { if (next.length !== prev.length) return false; for (let i = 0; i < next.length; i++) if (next[i] !== prev[i]) return false; return true; } function useMemoOne(factory, inputs = []) { const cache = (0, react.useRef)(null); if (cache.current === null || !areInputsEqual(inputs, cache.current.inputs)) cache.current = { inputs, value: factory() }; return cache.current.value; } //#endregion //#region src/useForm.ts function useEvent(handler) { const handlerRef = (0, react.useRef)(handler); (0, react.useLayoutEffect)(() => { handlerRef.current = handler; }); return (0, react.useCallback)((...args) => handlerRef.current(...args), []); } function useForm(args, deps = []) { const onSubmit = useEvent(args.onSubmit); return useMemoOne(() => createForm({ ...args, onSubmit }), deps); } //#endregion //#region src/useField.ts function useField(args, deps = []) { return useMemoOne(() => createField(args), deps); } //#endregion exports.createField = createField; exports.createForm = createForm; exports.useField = useField; exports.useForm = useForm; //# sourceMappingURL=index.cjs.map