UNPKG

use-inertia-form

Version:

Extra functionality for Inertia.js useForm hook

1,352 lines (1,310 loc) 37.3 kB
import React, { useState, useReducer, useCallback, useMemo, useRef, useEffect } from 'react'; import { useRemember, router } from '@inertiajs/react'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import axios from 'axios'; const createContext$1 = () => { const context = /*#__PURE__*/React.createContext(null); const useContext = () => { const c = React.useContext(context); if (c === null) { throw new Error("useContext must be inside a Provider with a value"); } return c; }; return [useContext, context.Provider]; }; function noop() { } function isTypedArray$1(x) { return ArrayBuffer.isView(x) && !(x instanceof DataView); } function getSymbols(object) { return Object.getOwnPropertySymbols(object).filter(symbol => Object.prototype.propertyIsEnumerable.call(object, symbol)); } function getTag(value) { if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]'; } return Object.prototype.toString.call(value); } const regexpTag = '[object RegExp]'; const stringTag = '[object String]'; const numberTag = '[object Number]'; const booleanTag = '[object Boolean]'; const argumentsTag = '[object Arguments]'; const symbolTag = '[object Symbol]'; const dateTag = '[object Date]'; const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const functionTag = '[object Function]'; const arrayBufferTag = '[object ArrayBuffer]'; const objectTag = '[object Object]'; const errorTag = '[object Error]'; const dataViewTag = '[object DataView]'; const uint8ArrayTag = '[object Uint8Array]'; const uint8ClampedArrayTag = '[object Uint8ClampedArray]'; const uint16ArrayTag = '[object Uint16Array]'; const uint32ArrayTag = '[object Uint32Array]'; const bigUint64ArrayTag = '[object BigUint64Array]'; const int8ArrayTag = '[object Int8Array]'; const int16ArrayTag = '[object Int16Array]'; const int32ArrayTag = '[object Int32Array]'; const bigInt64ArrayTag = '[object BigInt64Array]'; const float32ArrayTag = '[object Float32Array]'; const float64ArrayTag = '[object Float64Array]'; function isPlainObject(value) { if (!value || typeof value !== 'object') { return false; } const proto = Object.getPrototypeOf(value); const hasObjectPrototype = proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null; if (!hasObjectPrototype) { return false; } return Object.prototype.toString.call(value) === '[object Object]'; } function eq(value, other) { return value === other || (Number.isNaN(value) && Number.isNaN(other)); } function isEqualWith(a, b, areValuesEqual) { return isEqualWithImpl(a, b, undefined, undefined, undefined, undefined, areValuesEqual); } function isEqualWithImpl(a, b, property, aParent, bParent, stack, areValuesEqual) { const result = areValuesEqual(a, b, property, aParent, bParent, stack); if (result !== undefined) { return result; } if (typeof a === typeof b) { switch (typeof a) { case 'bigint': case 'string': case 'boolean': case 'symbol': case 'undefined': { return a === b; } case 'number': { return a === b || Object.is(a, b); } case 'function': { return a === b; } case 'object': { return areObjectsEqual(a, b, stack, areValuesEqual); } } } return areObjectsEqual(a, b, stack, areValuesEqual); } function areObjectsEqual(a, b, stack, areValuesEqual) { if (Object.is(a, b)) { return true; } let aTag = getTag(a); let bTag = getTag(b); if (aTag === argumentsTag) { aTag = objectTag; } if (bTag === argumentsTag) { bTag = objectTag; } if (aTag !== bTag) { return false; } switch (aTag) { case stringTag: return a.toString() === b.toString(); case numberTag: { const x = a.valueOf(); const y = b.valueOf(); return eq(x, y); } case booleanTag: case dateTag: case symbolTag: return Object.is(a.valueOf(), b.valueOf()); case regexpTag: { return a.source === b.source && a.flags === b.flags; } case functionTag: { return a === b; } } stack = stack ?? new Map(); const aStack = stack.get(a); const bStack = stack.get(b); if (aStack != null && bStack != null) { return aStack === b; } stack.set(a, b); stack.set(b, a); try { switch (aTag) { case mapTag: { if (a.size !== b.size) { return false; } for (const [key, value] of a.entries()) { if (!b.has(key) || !isEqualWithImpl(value, b.get(key), key, a, b, stack, areValuesEqual)) { return false; } } return true; } case setTag: { if (a.size !== b.size) { return false; } const aValues = Array.from(a.values()); const bValues = Array.from(b.values()); for (let i = 0; i < aValues.length; i++) { const aValue = aValues[i]; const index = bValues.findIndex(bValue => { return isEqualWithImpl(aValue, bValue, undefined, a, b, stack, areValuesEqual); }); if (index === -1) { return false; } bValues.splice(index, 1); } return true; } case arrayTag: case uint8ArrayTag: case uint8ClampedArrayTag: case uint16ArrayTag: case uint32ArrayTag: case bigUint64ArrayTag: case int8ArrayTag: case int16ArrayTag: case int32ArrayTag: case bigInt64ArrayTag: case float32ArrayTag: case float64ArrayTag: { if (typeof Buffer !== 'undefined' && Buffer.isBuffer(a) !== Buffer.isBuffer(b)) { return false; } if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (!isEqualWithImpl(a[i], b[i], i, a, b, stack, areValuesEqual)) { return false; } } return true; } case arrayBufferTag: { if (a.byteLength !== b.byteLength) { return false; } return areObjectsEqual(new Uint8Array(a), new Uint8Array(b), stack, areValuesEqual); } case dataViewTag: { if (a.byteLength !== b.byteLength || a.byteOffset !== b.byteOffset) { return false; } return areObjectsEqual(new Uint8Array(a), new Uint8Array(b), stack, areValuesEqual); } case errorTag: { return a.name === b.name && a.message === b.message; } case objectTag: { const areEqualInstances = areObjectsEqual(a.constructor, b.constructor, stack, areValuesEqual) || (isPlainObject(a) && isPlainObject(b)); if (!areEqualInstances) { return false; } const aKeys = [...Object.keys(a), ...getSymbols(a)]; const bKeys = [...Object.keys(b), ...getSymbols(b)]; if (aKeys.length !== bKeys.length) { return false; } for (let i = 0; i < aKeys.length; i++) { const propKey = aKeys[i]; const aProp = a[propKey]; if (!Object.hasOwn(b, propKey)) { return false; } const bProp = b[propKey]; if (!isEqualWithImpl(aProp, bProp, propKey, a, b, stack, areValuesEqual)) { return false; } } return true; } default: { return false; } } } finally { stack.delete(a); stack.delete(b); } } function isEqual(a, b) { return isEqualWith(a, b, noop); } function isLength(value) { return Number.isSafeInteger(value) && value >= 0; } const fillEmptyValues = data => { const clone = structuredClone(data ?? {}); for (const key in clone) { if (isPlainObject(clone[key])) { clone[key] = fillEmptyValues(clone[key]); } else if (Array.isArray(clone[key])) { clone[key] = clone[key].map(el => fillEmptyValues(el)); } else if (clone[key] === undefined || clone[key] === null) { clone[key] = ""; } } return clone; }; function isArrayLike(value) { return value != null && typeof value !== 'function' && isLength(value.length); } function isDeepKey(key) { switch (typeof key) { case 'number': case 'symbol': { return false; } case 'string': { return key.includes('.') || key.includes('[') || key.includes(']'); } } } function toKey(value) { if (Object.is(value, -0)) { return '-0'; } return value.toString(); } function toPath(deepKey) { const result = []; const length = deepKey.length; if (length === 0) { return result; } let index = 0; let key = ''; let quoteChar = ''; let bracket = false; if (deepKey.charCodeAt(0) === 46) { result.push(''); index++; } while (index < length) { const char = deepKey[index]; if (quoteChar) { if (char === '\\' && index + 1 < length) { index++; key += deepKey[index]; } else if (char === quoteChar) { quoteChar = ''; } else { key += char; } } else if (bracket) { if (char === '"' || char === "'") { quoteChar = char; } else if (char === ']') { bracket = false; result.push(key); key = ''; } else { key += char; } } else { if (char === '[') { bracket = true; if (key) { result.push(key); key = ''; } } else if (char === '.') { if (key) { result.push(key); key = ''; } } else { key += char; } } index++; } if (key) { result.push(key); } return result; } function get(object, path, defaultValue) { if (object == null) { return defaultValue; } switch (typeof path) { case 'string': { const result = object[path]; if (result === undefined) { if (isDeepKey(path)) { return get(object, toPath(path), defaultValue); } else { return defaultValue; } } return result; } case 'number': case 'symbol': { if (typeof path === 'number') { path = toKey(path); } const result = object[path]; if (result === undefined) { return defaultValue; } return result; } default: { if (Array.isArray(path)) { return getWithPath(object, path, defaultValue); } if (Object.is(path?.valueOf(), -0)) { path = '-0'; } else { path = String(path); } const result = object[path]; if (result === undefined) { return defaultValue; } return result; } } } function getWithPath(object, path, defaultValue) { if (path.length === 0) { return defaultValue; } let current = object; for (let index = 0; index < path.length; index++) { if (current == null) { return defaultValue; } current = current[path[index]]; } if (current === undefined) { return defaultValue; } return current; } const IS_UNSIGNED_INTEGER = /^(?:0|[1-9]\d*)$/; function isIndex(value, length = Number.MAX_SAFE_INTEGER) { switch (typeof value) { case 'number': { return Number.isInteger(value) && value >= 0 && value < length; } case 'symbol': { return false; } case 'string': { return IS_UNSIGNED_INTEGER.test(value); } } } function isArguments(value) { return value !== null && typeof value === 'object' && getTag(value) === '[object Arguments]'; } function set(obj, path, value) { const resolvedPath = Array.isArray(path) ? path : typeof path === 'string' ? toPath(path) : [path]; let current = obj; for (let i = 0; i < resolvedPath.length - 1; i++) { const key = resolvedPath[i]; const nextKey = resolvedPath[i + 1]; if (current[key] == null) { current[key] = isIndex(nextKey) ? [] : {}; } current = current[key]; } const lastKey = resolvedPath[resolvedPath.length - 1]; current[lastKey] = value; return obj; } function isPrototype(value) { const constructor = value?.constructor; const prototype = typeof constructor === 'function' ? constructor.prototype : Object.prototype; return value === prototype; } function isTypedArray(x) { return isTypedArray$1(x); } function unset(obj, path) { if (obj == null) { return true; } switch (typeof path) { case 'symbol': case 'number': case 'object': { if (Array.isArray(path)) { return unsetWithPath(obj, path); } if (typeof path === 'number') { path = toKey(path); } else if (typeof path === 'object') { if (Object.is(path?.valueOf(), -0)) { path = '-0'; } else { path = String(path); } } if (obj?.[path] === undefined) { return true; } try { delete obj[path]; return true; } catch { return false; } } case 'string': { if (obj?.[path] === undefined && isDeepKey(path)) { return unsetWithPath(obj, toPath(path)); } try { delete obj[path]; return true; } catch { return false; } } } } function unsetWithPath(obj, path) { const parent = get(obj, path.slice(0, -1), obj); const lastKey = path[path.length - 1]; if (parent?.[lastKey] === undefined) { return true; } try { delete parent[lastKey]; return true; } catch { return false; } } function isEmpty(value) { if (value == null) { return true; } if (isArrayLike(value)) { if (typeof value.splice !== 'function' && typeof value !== 'string' && (typeof Buffer === 'undefined' || !Buffer.isBuffer(value)) && !isTypedArray(value) && !isArguments(value)) { return false; } return value.length === 0; } if (typeof value === 'object') { if (value instanceof Map || value instanceof Set) { return value.size === 0; } const keys = Object.keys(value); if (isPrototype(value)) { return keys.filter(x => x !== 'constructor').length === 0; } return keys.length === 0; } return true; } const isUnset = v => { if (v === undefined || v === null) { return true; } if (typeof v === "string") { return v === ""; } if (typeof v === "number") { return v === 0 ? false : !Boolean(v); } if (v instanceof Date) { return isNaN(v.valueOf()); } if (typeof v === "boolean") { return false; } return isEmpty(v); }; const renameObjectWithAttributes = (data, str = "_attributes") => { const clone = structuredClone(data); Object.values(clone).forEach(value => { if (isPlainObject(value)) { recursiveAppendString(value, str); } }); return clone; }; const recursiveAppendString = (data, str) => { Object.entries(data).forEach(([key, value]) => { if (isPlainObject(value)) { renameKey(data, key, `${key}${str}`); recursiveAppendString(value, str); } else if (Array.isArray(value)) { renameKey(data, key, `${key}${str}`); } }); }; const renameKey = (obj, oldKey, newKey) => { if (oldKey !== newKey) { obj[newKey] = obj[oldKey]; delete obj[oldKey]; } }; const unsetCompact = (data, path) => { const sanitizedPath = path.replace(/\[\]$/, ""); if (sanitizedPath.includes("[]")) { const emptyArrayPosition = sanitizedPath.indexOf("[]"); const startPath = sanitizedPath.slice(0, emptyArrayPosition); const restPath = sanitizedPath.slice(emptyArrayPosition + 2); const arr = get(data, startPath); if (Array.isArray(arr)) { for (let i = 0; i < arr.length; i++) { unsetCompact(data, `${startPath}[${i}]${restPath}`); } } } if (sanitizedPath.charAt(sanitizedPath.length - 1) === "]") { const match = sanitizedPath.match(/(?<index>\d*)\]$/); const arr = get(data, sanitizedPath.slice(0, sanitizedPath.lastIndexOf("["))); if (Array.isArray(arr) && match?.groups?.index !== undefined) { arr.splice(Number(match.groups.index), 1); } } else { unset(data, sanitizedPath); } }; const useMaybeRemember = (initialValue, rememberKey) => { const [rememberedData, setRememberedData] = useRemember(initialValue, rememberKey); const [localData, setLocalData] = useState(initialValue); if (rememberKey) { return [rememberedData, setRememberedData]; } return [localData, setLocalData]; }; const coerceArray = arg => Array.isArray(arg) ? arg : [arg]; const [useFormMeta, FormMetaProvider] = createContext$1(); const attributesReducer = (state, attribute) => { const newState = new Set(state); newState.add(attribute); return newState; }; const FormMetaWrapper = ({ children, model, railsAttributes }) => { const [nestedAttributes, addAttribute] = useReducer(attributesReducer, new Set()); const metaValues = { nestedAttributes, addAttribute, model, railsAttributes }; return jsx(FormMetaProvider, { value: metaValues, children: children }); }; function useInertiaForm(rememberKeyOrInitialValues, maybeInitialValues) { const getFormArguments = useCallback(() => { let rememberKey = null; let transformedData = rememberKeyOrInitialValues; if (typeof rememberKeyOrInitialValues === "string") { rememberKey = rememberKeyOrInitialValues; transformedData = maybeInitialValues; } return [rememberKey, fillEmptyValues(transformedData)]; }, [rememberKeyOrInitialValues, maybeInitialValues]); const [rememberKey, transformedData] = getFormArguments(); const [defaults, setDefaults] = useState(transformedData || {}); const [data, setData] = useMaybeRemember(transformedData, rememberKey ? `${rememberKey}:data` : undefined); const rootModelKey = useMemo(() => { const keys = data ? Object.keys(data) : []; if (keys.length === 1 && isPlainObject(data[keys[0]])) { return keys[0]; } return undefined; }, [data]); const [errors, setErrors] = useMaybeRemember({}, rememberKey ? `${rememberKey}:errors` : undefined); const [hasErrors, setHasErrors] = useState(false); const rewriteErrorKeys = errors => { if (!errors || !rootModelKey) return errors; const newErrors = {}; Object.keys(errors).forEach(key => { newErrors[`${rootModelKey}.${key}`] = errors[key]; }); return newErrors; }; const [processing, setProcessing] = useState(false); const [progress, setProgress] = useState(); const [wasSuccessful, setWasSuccessful] = useState(false); const [recentlySuccessful, setRecentlySuccessful] = useState(false); const cancelToken = useRef(null); const recentlySuccessfulTimeoutId = useRef(null); let transformRef = useRef(data => data); const isMounted = useRef(null); useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; }; }, []); let onChangeRef = useRef(null); let onChangeArgsRef = useRef(null); useEffect(() => { if (onChangeRef.current && onChangeArgsRef.current) { onChangeRef.current(...onChangeArgsRef.current); } }, [data]); let railsAttributes = false; try { const meta = useFormMeta(); railsAttributes = meta.railsAttributes; } catch (e) {} const submit = (method, url, options = { async: false }) => { const _options = { ...options, onCancelToken: token => { cancelToken.current = token; if (options.onCancelToken) { return options.onCancelToken(token); } }, onBefore: visit => { setWasSuccessful(false); setRecentlySuccessful(false); clearTimeout(recentlySuccessfulTimeoutId.current); if (options.onBefore) { return options.onBefore(visit); } }, onStart: visit => { setProcessing(true); if (options.onStart) { return options.onStart(visit); } }, onProgress: event => { setProgress(event); if (options.onProgress) { return options.onProgress(event); } }, onSuccess: page => { if (isMounted.current) { setProcessing(false); setProgress(null); setErrors({}); setHasErrors(false); setWasSuccessful(true); setRecentlySuccessful(true); recentlySuccessfulTimeoutId.current = setTimeout(() => { if (isMounted.current) { setRecentlySuccessful(false); } }, 2000); } if (options.onSuccess) { return options.onSuccess(page); } }, onError: errors => { if (isMounted.current) { setProcessing(false); setProgress(null); setErrors(rewriteErrorKeys(errors)); setHasErrors(true); } if (options.onError) { return options.onError(errors); } }, onCancel: () => { if (isMounted.current) { setProcessing(false); setProgress(null); } if (options.onCancel) { return options.onCancel(); } }, onFinish: visit => { if (isMounted.current) { setProcessing(false); setProgress(null); } cancelToken.current = null; if (options.onFinish) { return options.onFinish(visit); } } }; let transformedData = transformRef.current(structuredClone(data)); if (railsAttributes) { transformedData = renameObjectWithAttributes(transformedData); } if (options.async === true) { _options.onBefore(undefined); _options.onStart(undefined); axios[method](url, transformedData, { onUploadProgress: progressEvent => { _options.onProgress(progressEvent); } }).then(response => { _options.onSuccess(response); }).catch(error => { _options.onError(error); }).finally(() => { _options.onFinish(undefined); }); } else { if (method === "delete") { router.delete(url, { ..._options, data: transformedData }); } else { router[method](url, transformedData, _options); } } }; const clearErrors = fields => { if (!fields) { setErrors({}); return; } const arrFields = coerceArray(fields); setErrors(errors => { const newErrors = Object.keys(errors).reduce((carry, field) => ({ ...carry, ...(arrFields.length > 0 && !arrFields.includes(String(field)) ? { [field]: errors[field] } : {}) }), {}); setHasErrors(Object.keys(newErrors).length > 0); return newErrors; }); }; return { data, isDirty: !isEqual(data, defaults), errors, hasErrors, processing, progress, wasSuccessful, recentlySuccessful, transform: callback => { transformRef.current = callback; }, onChange: callback => { onChangeRef.current = callback; }, setData: (keyOrData, maybeValue) => { if (typeof keyOrData === "string") { return setData(data => { const clone = structuredClone(data); if (onChangeRef.current) { onChangeArgsRef.current = [keyOrData, maybeValue, get(data, keyOrData)]; } set(clone, keyOrData, maybeValue); return clone; }); } if (keyOrData instanceof Function) { setData(data => { const clone = keyOrData(structuredClone(data)); if (onChangeRef.current) { onChangeArgsRef.current = [undefined, clone, data]; } return clone; }); return; } if (onChangeRef.current) { onChangeArgsRef.current = [undefined, data, keyOrData]; } setData(keyOrData); }, getData: key => { return get(data, key); }, unsetData: key => { setData(data => { const clone = structuredClone(data); if (onChangeRef.current) { onChangeArgsRef.current = [key, get(data, key), undefined]; } unsetCompact(clone, key); return clone; }); }, setDefaults: (fieldOrFields, maybeValue) => { if (fieldOrFields === undefined) { setDefaults(() => data); return; } setDefaults(defaults => ({ ...defaults, ...(typeof fieldOrFields === "string" ? { [fieldOrFields]: maybeValue } : fieldOrFields) })); }, reset: fields => { if (!fields) { if (onChangeRef.current) { onChangeArgsRef.current = [undefined, defaults, data]; } setData(defaults); setErrors({}); return; } const arrFields = coerceArray(fields); const clone = structuredClone(data); arrFields.forEach(field => { set(clone, field, get(defaults, field)); }); clearErrors(fields); if (onChangeRef.current) { onChangeArgsRef.current = [undefined, clone, data]; } setData(clone); }, setError: (fieldOrFields, maybeValue) => { setErrors(errors => { const newErrors = { ...errors, ...(typeof fieldOrFields === "string" ? { [fieldOrFields]: maybeValue } : fieldOrFields) }; setHasErrors(Object.keys(newErrors).length > 0); return newErrors; }); }, getError: key => { return get(errors, key); }, clearErrors, submit, get: (url, options) => { submit("get", url, options); }, post: (url, options) => { submit("post", url, options); }, put: (url, options) => { submit("put", url, options); }, patch: (url, options) => { submit("patch", url, options); }, delete: (url, options) => { submit("delete", url, options); }, cancel: () => { if (cancelToken.current) { cancelToken.current.cancel(); } } }; } const createContext = () => { const context = /*#__PURE__*/React.createContext(null); const useContext = () => { const c = React.useContext(context); if (c === null) { throw new Error("useContext must be inside a Provider with a value"); } return c; }; return [useContext, context.Provider]; }; const [useForm, FormProvider] = createContext(); const Form = ({ children, model, data, method = "post", to, async = false, resetAfterSubmit, remember = true, filter, onChange, onSubmit, onBefore, onStart, onSuccess, onError, onFinish, ...props }) => { const filteredData = useCallback(data => { if (!filter) return data; const clone = structuredClone(data); filter.forEach(path => { unsetCompact(clone, path); }); return clone; }, [data, filter]); const form = remember ? useInertiaForm(`${method}/${model || to}`, filteredData(data)) : useInertiaForm(filteredData(data)); const contextValueObject = useCallback(() => ({ ...form, model, method, to, submit }), [data, form, form.data, form.errors, model, method, to]); const submit = async options => { let shouldSubmit = to && onSubmit?.(contextValueObject()) === false ? false : true; if (!shouldSubmit) return; return form.submit(method, to, { ...options, async: async === true ? true : false }); }; const handleSubmit = e => { e.preventDefault(); e.stopPropagation(); const submitOptions = { onSuccess: () => { if (resetAfterSubmit || resetAfterSubmit !== false && async === true) { form.reset(); } onSuccess?.(contextValueObject()); } }; if (onBefore) { submitOptions.onBefore = () => { onBefore(contextValueObject()); }; } if (onStart) { submitOptions.onStart = () => { onStart(contextValueObject()); }; } if (onFinish) { submitOptions.onFinish = () => { onFinish(contextValueObject()); }; } submit(submitOptions); }; useEffect(() => { const url = new URL(window.location.href); url.searchParams.forEach((value, key) => { form.setData(key, value); }); }, []); useEffect(() => { onChange?.(contextValueObject()); }, [form.data]); useEffect(() => { onError?.(contextValueObject()); }, [form.errors]); return jsx(FormProvider, { value: contextValueObject(), children: jsx("form", { onSubmit: handleSubmit, ...props, children: children }) }); }; const WrappedForm = ({ children, model, railsAttributes = false, ...props }) => { return jsx(FormMetaWrapper, { model: model, railsAttributes: railsAttributes, children: jsx(Form, { model: model, ...props, children: children }) }); }; const [useNestedAttribute, NestedAttributeProvider] = createContext$1(); const NestedFields = ({ children, model }) => { let inputModel = model; try { const nested = useNestedAttribute(); if (model.charAt(0) === "[") { inputModel = `${nested}${model}`; } else { inputModel = `${nested}.${model}`; } } catch (e) {} const { addAttribute } = useFormMeta(); useEffect(() => { addAttribute(model); }, []); return jsx(NestedAttributeProvider, { value: inputModel, children: Array.isArray(children) ? children.map((child, i) => /*#__PURE__*/React.cloneElement(child, { key: i })) : children }); }; const inputStrategy = (name, model) => { if (!model) { return { inputId: name, inputName: name }; } let inputName; if (name.charAt(0) === "[") { inputName = `${model}${name}`; } else { inputName = `${model}.${name}`; } return { inputId: `${model}_${name}`.replace(".", "_").replace(/\[(\d)\]/, "_$1"), inputName: inputName }; }; const useInertiaInput = ({ name, model, defaultValue, errorKey, strategy = inputStrategy, clearErrorsOnChange = true }) => { const form = useForm(); let usedModel = model ?? form.model; try { const nested = useNestedAttribute(); usedModel += `.${nested}`; } catch (e) {} const { inputName, inputId } = strategy(name, usedModel); const initializingRef = useRef(true); useEffect(() => { if (!initializingRef.current) return; const inputValue = form.getData(inputName); if (inputValue === null || inputValue === undefined) { form.setData(inputName, defaultValue || ""); } initializingRef.current = false; }, []); const value = form.getData(inputName); const usedErrorKey = errorKey ?? inputName; const error = form.getError(usedErrorKey); useEffect(() => { if (initializingRef.current || !clearErrorsOnChange || !error) return; form.clearErrors(usedErrorKey); }, [value]); return { form, inputName: inputName, inputId, value: value ?? "", setValue: value => { return form.setData(inputName, value); }, error }; }; const Input = ({ name, component = "input", type = "text", model, onChange, errorKey, defaultValue, clearErrorsOnChange, ...props }) => { const { form, inputName, inputId, value, setValue } = useInertiaInput({ name, model, errorKey, defaultValue, clearErrorsOnChange }); const handleChange = e => { const value = e.target?.checked || e.target.value; setValue(value); onChange?.(value, form); }; const Element = component; return jsx(Element, { type: type, name: inputName, id: inputId, value: value, onChange: handleChange, ...props }); }; const Submit = /*#__PURE__*/React.forwardRef(({ children, type = "submit", disabled = false, component: Component = "button", requiredFields, ...props }, ref) => { const { data, getData, processing } = useForm(); const hasEmptyRequiredFields = useCallback(() => { if (!requiredFields || requiredFields.length === 0) return false; return requiredFields.some(field => isUnset(getData(field))); }, [data, requiredFields]); return jsx(Component, { children, type, disabled: disabled || processing || requiredFields && hasEmptyRequiredFields(), ref, ...props }); }); var Submit$1 = /*#__PURE__*/React.memo(Submit); const useDynamicInputs = ({ model, emptyData }) => { const { setData, unsetData, getData } = useForm(); const { model: formModel } = useFormMeta(); let inputModel = formModel ?? ""; try { const nestedModel = useNestedAttribute(); inputModel = formModel ? `${inputModel}.${nestedModel}` : nestedModel; } catch (e) {} inputModel = `${inputModel}.${model || ""}`; const handleAddInputs = useCallback(override => { setData(formData => { const clone = structuredClone(formData); let node = get(clone, inputModel); if (!node || !Array.isArray(node)) { set(clone, inputModel, []); node = get(clone, inputModel); } let merge = {}; if (override instanceof Function) { merge = override(node); } else if (override !== undefined) { merge = override; } node.push(Object.assign(emptyData, merge)); set(clone, inputModel, node); return clone; }); }, []); const handleRemoveInputs = useCallback(i => { const record = getData(`${inputModel}[${i}]`); unsetData(`${inputModel}[${i}]`); return record; }, []); const data = getData(inputModel); const generatePaths = useCallback(() => { if (!Array.isArray(data)) return []; return data.map((_, i) => `${model || ""}[${i}]`); }, [data]); return { addInput: handleAddInputs, removeInput: handleRemoveInputs, paths: generatePaths() }; }; const DynamicInputs = ({ children, model, emptyData, addInputButton = jsx("button", { children: "+" }), removeInputButton = jsx("button", { children: "-" }) }) => { const { addInput, removeInput, paths } = useDynamicInputs({ model, emptyData }); return jsxs(Fragment, { children: [/*#__PURE__*/React.cloneElement(addInputButton, { onClick: () => addInput(), type: "button" }), paths.map((path, i) => jsxs(NestedFields, { model: path, children: [jsx("div", { children: children }), /*#__PURE__*/React.cloneElement(removeInputButton, { onClick: () => removeInput(i), type: "button" })] }, i))] }); }; export { DynamicInputs, WrappedForm as Form, Input, NestedFields, Submit$1 as Submit, useDynamicInputs, useForm, useInertiaForm, useInertiaInput }; //# sourceMappingURL=useInertiaForm.js.map