UNPKG

@conform-to/react

Version:

Conform view adapter for react

265 lines (260 loc) 9.28 kB
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs'; import { STATE, formatPaths, getPaths, unstable_createFormContext, INTENT, isPrefix } from '@conform-to/dom'; import { useContext, useMemo, createContext, useCallback, useSyncExternalStore, useRef } from 'react'; import { jsx } from 'react/jsx-runtime'; var Form = /*#__PURE__*/createContext([]); // To hide the FormContext type from the public API var wrappedSymbol = Symbol('wrapped'); function getWrappedFormContext(context) { return context[wrappedSymbol]; } function useFormContext(formId) { var contexts = useContext(Form); var form = formId ? contexts.find(context => formId === context.getFormId()) : contexts[0]; if (!form) { throw new Error('Form context is not available'); } return form; } function useFormState(form, subjectRef) { var subscribe = useCallback(callback => form.subscribe(callback, () => subjectRef === null || subjectRef === void 0 ? void 0 : subjectRef.current), [form, subjectRef]); return useSyncExternalStore(subscribe, form.getState, form.getState); } function FormProvider(props) { var forms = useContext(Form); var context = getWrappedFormContext(props.context); var value = useMemo(() => [context].concat(forms), // Put the latest form context first [forms, context]); return /*#__PURE__*/jsx(Form.Provider, { value: value, children: props.children }); } function FormStateInput(props) { var context = useFormContext(props.formId); return /*#__PURE__*/jsx("input", { type: "hidden", name: STATE, value: context.getSerializedState(), form: props.formId }); } function useSubjectRef() { var initialSubject = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var subjectRef = useRef(initialSubject); // Reset the subject everytime the component is rerendered // This let us subscribe to data used in the last render only subjectRef.current = initialSubject; return subjectRef; } function updateSubjectRef(ref, subject, scope, name) { if (subject === 'status' || subject === 'formId' || subject === 'pendingIntents') { ref.current[subject] = true; } else if (typeof scope !== 'undefined' && typeof name !== 'undefined') { var _ref$current$subject$, _ref$current$subject; ref.current[subject] = _objectSpread2(_objectSpread2({}, ref.current[subject]), {}, { [scope]: ((_ref$current$subject$ = (_ref$current$subject = ref.current[subject]) === null || _ref$current$subject === void 0 ? void 0 : _ref$current$subject[scope]) !== null && _ref$current$subject$ !== void 0 ? _ref$current$subject$ : []).concat(name) }); } } function getMetadata(context, subjectRef, stateSnapshot) { var name = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; var id = name ? "".concat(context.getFormId(), "-").concat(name) : context.getFormId(); var state = context.getState(); return new Proxy({ id, name, errorId: "".concat(id, "-error"), descriptionId: "".concat(id, "-description"), get defaultValue() { var initialValue = this.initialValue; if (typeof initialValue === 'string') { return initialValue; } if (Array.isArray(initialValue)) { return initialValue[0]; } }, get defaultOptions() { var initialValue = this.initialValue; if (typeof initialValue === 'string') { return [initialValue]; } if (Array.isArray(initialValue) && initialValue.every(item => typeof item === 'string')) { return initialValue; } }, get defaultChecked() { if (this.initialValue === 'on') { return true; } }, get initialValue() { return state.initialValue[name]; }, get value() { return state.value[name]; }, get errors() { return state.error[name]; }, get key() { return state.key[name]; }, get valid() { return state.valid[name]; }, get dirty() { return state.dirty[name]; }, get allErrors() { if (name === '') { return state.error; } var result = {}; for (var [key, error] of Object.entries(state.error)) { if (isPrefix(key, name)) { result[key] = error; } } return result; }, get getFieldset() { return () => new Proxy({}, { get(target, key, receiver) { if (typeof key === 'string') { return getFieldMetadata(context, subjectRef, stateSnapshot, name, key); } return Reflect.get(target, key, receiver); } }); } }, { get(target, key, receiver) { // We want to minize re-render by identifying whether the field is used in a callback only // but there is no clear way to know if it is accessed during render or not // if the stateSnapshot is not the latest, then it must be accessed in a callback if (state === stateSnapshot) { switch (key) { case 'id': case 'errorId': case 'descriptionId': updateSubjectRef(subjectRef, 'formId'); break; case 'key': case 'initialValue': case 'value': case 'valid': case 'dirty': updateSubjectRef(subjectRef, key, 'name', name); break; case 'errors': case 'allErrors': updateSubjectRef(subjectRef, 'error', key === 'errors' ? 'name' : 'prefix', name); break; } } return Reflect.get(target, key, receiver); } }); } function getFieldMetadata(context, subjectRef, stateSnapshot) { var prefix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; var key = arguments.length > 4 ? arguments[4] : undefined; var name = typeof key === 'undefined' ? prefix : formatPaths([...getPaths(prefix), key]); return new Proxy({}, { get(_, key, receiver) { var _state$constraint$nam; var metadata = getMetadata(context, subjectRef, stateSnapshot, name); var state = context.getState(); switch (key) { case 'formId': if (state === stateSnapshot) { updateSubjectRef(subjectRef, 'formId'); } return context.getFormId(); case 'required': case 'minLength': case 'maxLength': case 'min': case 'max': case 'pattern': case 'step': case 'multiple': return (_state$constraint$nam = state.constraint[name]) === null || _state$constraint$nam === void 0 ? void 0 : _state$constraint$nam[key]; case 'getFieldList': { return () => { var _state$initialValue$n; var initialValue = (_state$initialValue$n = state.initialValue[name]) !== null && _state$initialValue$n !== void 0 ? _state$initialValue$n : []; if (state === stateSnapshot) { updateSubjectRef(subjectRef, 'initialValue', 'name', name); } if (!Array.isArray(initialValue)) { throw new Error('The initial value at the given name is not a list'); } return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(context, subjectRef, stateSnapshot, name, index)); }; } } return Reflect.get(metadata, key, receiver); } }); } function getFormMetadata(context, subjectRef, stateSnapshot, noValidate) { return new Proxy({}, { get(_, key, receiver) { var metadata = getMetadata(context, subjectRef, stateSnapshot); var state = context.getState(); switch (key) { case 'context': return { [wrappedSymbol]: context }; case 'status': if (state === stateSnapshot) { updateSubjectRef(subjectRef, 'status'); } return state.submissionStatus; case 'validate': case 'update': case 'reset': case 'insert': case 'remove': case 'reorder': return context[key]; case 'onSubmit': return context.submit; case 'noValidate': return noValidate; } return Reflect.get(metadata, key, receiver); } }); } function createFormContext(options) { var { onSubmit } = options; var context = unstable_createFormContext(options); return _objectSpread2(_objectSpread2({}, context), {}, { submit(event) { var submitEvent = event.nativeEvent; var result = context.submit(submitEvent); if (!result.submission || result.submission.status === 'success' || result.submission.error === null) { if (!result.formData.has(INTENT)) { var _onSubmit; (_onSubmit = onSubmit) === null || _onSubmit === void 0 || _onSubmit(event, result); } } else { event.preventDefault(); } }, onUpdate(options) { onSubmit = options.onSubmit; context.onUpdate(options); } }); } export { Form, FormProvider, FormStateInput, createFormContext, getFieldMetadata, getFormMetadata, getMetadata, getWrappedFormContext, updateSubjectRef, useFormContext, useFormState, useSubjectRef };