UNPKG

@modular-forms/qwik

Version:

The modular and type-safe form library for Qwik

1,265 lines (1,264 loc) 44.5 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2; var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2); import { $, noSerialize, implicit$FirstArg, component$, useVisibleTask$, Slot, useStore } from "@builder.io/qwik"; import { globalActionQrl } from "@builder.io/qwik-city"; import { AbortMessage } from "@builder.io/qwik-city/middleware/request-handler"; import { isDev, isServer } from "@builder.io/qwik/build"; import { decode } from "decode-formdata"; import { safeParseAsync, getDotPath } from "valibot"; import { jsx } from "@builder.io/qwik/jsx-runtime"; class FormError extends Error { constructor(arg1, arg2) { super(typeof arg1 === "string" ? arg1 : ""); __publicField(this, "name", "FormError"); __publicField(this, "errors"); this.errors = typeof arg1 === "string" ? arg2 || {} : arg1; } } function formActionQrl(action, arg2) { return globalActionQrl($(async (jsonData, event) => { const { validate: validate2, ...formDataInfo } = typeof arg2 === "object" ? arg2 : { validate: arg2 }; const type = event.request.headers.get("content-type")?.split(/[;,]/, 1)[0]; const values = type === "application/x-www-form-urlencoded" || type === "multipart/form-data" ? decode(event.sharedMap.get("@actionFormData"), formDataInfo, ({ output }) => output instanceof Blob ? noSerialize(output) : output) : jsonData; const errors = validate2 ? await validate2(values) : {}; let formActionStore = { values, errors, response: {} }; if (!Object.keys(errors).length) { try { const result = await action(values, event); if (result && typeof result === "object") { formActionStore = { values, errors: result.errors || {}, response: { status: result.status, message: result.message, data: result.data } }; } } catch (error) { if (error instanceof AbortMessage || isDev && (error?.constructor?.name === "AbortMessage" || error?.constructor?.name === "RedirectMessage")) { throw error; } else { console.error(error); if (error instanceof FormError) { formActionStore = { values, errors: error.errors, response: { status: "error", message: error.message } }; } else { formActionStore.response = { status: "error", message: "An unknown error has occurred." }; } } } } return formActionStore; }), { id: action.getHash() }); } const formAction$ = implicit$FirstArg(formActionQrl); function valiFieldQrl(schema) { return $(async (value2) => { const resolvedSchema = await schema.resolve(); const result = await safeParseAsync(typeof resolvedSchema === "function" ? resolvedSchema() : resolvedSchema, value2, { abortPipeEarly: true }); return result.issues?.[0].message || ""; }); } const valiField$ = implicit$FirstArg(valiFieldQrl); function valiFormQrl(schema) { return $(async (values) => { const resolvedSchema = await schema.resolve(); const result = await safeParseAsync(typeof resolvedSchema === "function" ? resolvedSchema() : resolvedSchema, values, { abortPipeEarly: true }); const formErrors = {}; if (result.issues) { for (const issue of result.issues) { formErrors[getDotPath(issue)] = issue.message; } } return formErrors; }); } const valiForm$ = implicit$FirstArg(valiFormQrl); function getElementInput(element, field, type) { const { checked, files, options, value: value2, valueAsDate, valueAsNumber } = element; return !type || type === "string" ? value2 : type === "string[]" ? options ? [ ...options ].filter((e) => e.selected && !e.disabled).map((e) => e.value) : checked ? [ ...field.value || [], value2 ] : (field.value || []).filter((v) => v !== value2) : type === "number" ? valueAsNumber : type === "boolean" ? checked : type === "File" && files ? noSerialize(files[0]) : type === "File[]" && files ? [ ...files ].map((file) => noSerialize(file)) : type === "Date" && valueAsDate ? valueAsDate : field.value; } function getFieldAndArrayStores(form) { return [ ...Object.values(form.internal.fields), ...Object.values(form.internal.fieldArrays) ]; } function getFieldArrayStore(form, name) { return form.internal.fieldArrays[name]; } function getPathIndex(name, path) { return +path.replace(`${name}.`, "").split(".")[0]; } function removeInvalidNames(form, names) { getFieldArrayNames(form, false).forEach((fieldArrayName) => { const lastIndex = getFieldArrayStore(form, fieldArrayName).items.length - 1; names.filter((name) => name.startsWith(`${fieldArrayName}.`) && getPathIndex(fieldArrayName, name) > lastIndex).forEach((name) => { names.splice(names.indexOf(name), 1); }); }); } function getFieldArrayNames(form, shouldValid = true) { const fieldArrayNames = Object.keys(form.internal.fieldArrays); if (shouldValid) { removeInvalidNames(form, fieldArrayNames); } return fieldArrayNames; } function getFieldArrayState(form, name) { const fieldArray = getFieldArrayStore(form, name); return fieldArray ? { startItems: fieldArray.internal.startItems, items: fieldArray.items, error: fieldArray.error, touched: fieldArray.touched, dirty: fieldArray.dirty } : void 0; } function getFieldNames(form, shouldValid = true) { const fieldNames = Object.keys(form.internal.fields); if (shouldValid) { removeInvalidNames(form, fieldNames); } return fieldNames; } function getFieldStore(form, name) { return form.internal.fields[name]; } function getFieldState(form, name) { const field = getFieldStore(form, name); return field ? { startValue: field.internal.startValue, value: field.value, error: field.error, touched: field.touched, dirty: field.dirty } : void 0; } function getFilteredNames(form, arg2, shouldValid) { const allFieldNames = getFieldNames(form, shouldValid); const allFieldArrayNames = getFieldArrayNames(form, shouldValid); if (typeof arg2 === "string" || Array.isArray(arg2)) { return (typeof arg2 === "string" ? [ arg2 ] : arg2).reduce((tuple, name) => { const [fieldNames, fieldArrayNames] = tuple; if (allFieldArrayNames.includes(name)) { allFieldArrayNames.forEach((fieldArrayName) => { if (fieldArrayName.startsWith(name)) { fieldArrayNames.add(fieldArrayName); } }); allFieldNames.forEach((fieldName) => { if (fieldName.startsWith(name)) { fieldNames.add(fieldName); } }); } else { fieldNames.add(name); } return tuple; }, [ /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set() ]).map((set) => [ ...set ]); } return [ allFieldNames, allFieldArrayNames ]; } function getInitialFieldArrayStore(name, { items, initialItems, error } = { items: [], initialItems: [], error: "" }) { const dirty = initialItems.join() !== items.join(); return { internal: { initialItems: [ ...initialItems ], startItems: [ ...initialItems ], validate: [], validateOn: void 0, revalidateOn: void 0, consumers: [] }, name, items, error, active: false, touched: dirty, dirty }; } function isFieldDirty(startValue, currentValue) { const toValue = (item) => item instanceof Blob ? item.size : item; return Array.isArray(startValue) && Array.isArray(currentValue) ? startValue.map(toValue).join() !== currentValue.map(toValue).join() : startValue instanceof Date && currentValue instanceof Date ? startValue.getTime() !== currentValue.getTime() : Number.isNaN(startValue) && Number.isNaN(currentValue) ? false : startValue !== currentValue; } function getInitialFieldStore(name, { value: value2, initialValue, error } = { value: void 0, initialValue: void 0, error: "" }) { const dirty = isFieldDirty(initialValue, value2); return { internal: { initialValue, startValue: initialValue, validate: [], validateOn: void 0, revalidateOn: void 0, transform: [], elements: [], consumers: [] }, name, value: value2, error, active: false, touched: dirty, dirty }; } function getPathValue(path, object) { return path.split(".").reduce((value2, key) => value2?.[key], object); } let counter = 0; function getUniqueId() { return counter++; } function getInitialStores({ loader, action, fieldArrays }) { function getActionValue(name) { return action?.value?.values && getPathValue(name, action.value.values); } const generateItems = () => getUniqueId(); const getActionError = (name) => action?.value?.errors[name] || ""; const createInitialStores = (stores, data, prevPath) => Object.entries(data).reduce((stores2, [path, value2]) => { const compoundPath = prevPath ? `${prevPath}.${path}` : path; if (fieldArrays?.includes(compoundPath.replace(/.\d+./g, ".$."))) { const initialItems = value2.map(generateItems); stores2[1][compoundPath] = getInitialFieldArrayStore(compoundPath, { initialItems, items: getActionValue(compoundPath)?.map(generateItems) || [ ...initialItems ], error: getActionError(compoundPath) }); } else if (!value2 || typeof value2 !== "object" || Array.isArray(value2) || value2 instanceof Date) { stores2[0][compoundPath] = getInitialFieldStore(compoundPath, { initialValue: value2, value: getActionValue(compoundPath) ?? value2, error: getActionError(compoundPath) }); } if (value2 && typeof value2 === "object") { createInitialStores(stores2, value2, compoundPath); } return stores2; }, stores); return createInitialStores([ {}, {} ], loader.value); } async function getParsedZodSchema(schema, value2) { const zodSchema = await schema.resolve(); return (typeof zodSchema === "function" ? zodSchema() : zodSchema).safeParseAsync(value2); } function getOptions(arg1, arg2) { return (typeof arg1 !== "string" && !Array.isArray(arg1) ? arg1 : arg2) || {}; } function updateFormDirty(form, dirty) { form.dirty = dirty || getFieldAndArrayStores(form).some((fieldOrFieldArray) => fieldOrFieldArray.active && fieldOrFieldArray.dirty); } function updateFieldDirty(form, field) { const dirty = isFieldDirty(field.internal.startValue, field.value); if (dirty !== field.dirty) { field.dirty = dirty; updateFormDirty(form, dirty); } } function focus(form, name) { getFieldStore(form, name)?.internal.elements[0]?.focus(); } function setError(form, name, error, { shouldActive = true, shouldTouched = false, shouldDirty = false, shouldFocus = !!error } = {}) { for (const fieldOrFieldArray of [ getFieldStore(form, name), getFieldArrayStore(form, name) ]) { if (fieldOrFieldArray && (!shouldActive || fieldOrFieldArray.active) && (!shouldTouched || fieldOrFieldArray.touched) && (!shouldDirty || fieldOrFieldArray.dirty)) { fieldOrFieldArray.error = error; if (error && "value" in fieldOrFieldArray && shouldFocus) { focus(form, name); } } } updateFormInvalid(form, !!error); } function clearError(form, name, options) { setError(form, name, "", options); } function clearResponse(form) { form.response = {}; } function getError(form, name, { shouldActive = true, shouldTouched = false, shouldDirty = false } = {}) { for (const fieldOrFieldArray of [ getFieldStore(form, name), getFieldArrayStore(form, name) ]) { if (fieldOrFieldArray && (!shouldActive || fieldOrFieldArray.active) && (!shouldTouched || fieldOrFieldArray.touched) && (!shouldDirty || fieldOrFieldArray.dirty)) { return fieldOrFieldArray.error; } } return void 0; } function getErrors(form, arg2, arg3) { const [fieldNames, fieldArrayNames] = getFilteredNames(form, arg2); const { shouldActive = true, shouldTouched = false, shouldDirty = false } = getOptions(arg2, arg3); return [ ...fieldNames.map((name) => [ name, getFieldStore(form, name) ]), ...fieldArrayNames.map((name) => [ name, getFieldArrayStore(form, name) ]) ].reduce((formErrors, [name, fieldOrFieldArray]) => { if (fieldOrFieldArray.error && (!shouldActive || fieldOrFieldArray.active) && (!shouldTouched || fieldOrFieldArray.touched) && (!shouldDirty || fieldOrFieldArray.dirty)) { formErrors[name] = fieldOrFieldArray.error; } return formErrors; }, {}); } function getValue(form, name, { shouldActive = true, shouldTouched = false, shouldDirty = false, shouldValid = false } = {}) { const field = getFieldStore(form, name); if (field && (!shouldActive || field.active) && (!shouldTouched || field.touched) && (!shouldDirty || field.dirty) && (!shouldValid || !field.error)) { return field.value; } return void 0; } function getValues(form, arg2, arg3) { const { shouldActive = true, shouldTouched = false, shouldDirty = false, shouldValid = false } = getOptions(arg2, arg3); return getFilteredNames(form, arg2)[0].reduce((values, name) => { const field = getFieldStore(form, name); if ((!shouldActive || field.active) && (!shouldTouched || field.touched) && (!shouldDirty || field.dirty) && (!shouldValid || !field.error)) { (typeof arg2 === "string" ? name.replace(`${arg2}.`, "") : name).split(".").reduce((object, key, index, keys) => object[key] = index === keys.length - 1 ? field.value : typeof object[key] === "object" && object[key] || (isNaN(+keys[index + 1]) ? {} : []), values); } return values; }, typeof arg2 === "string" ? [] : {}); } function hasField(form, name, { shouldActive = true, shouldTouched = false, shouldDirty = false, shouldValid = false } = {}) { const field = getFieldStore(form, name); return !!field && (!shouldActive || field.active) && (!shouldTouched || field.touched) && (!shouldDirty || field.dirty) && (!shouldValid || !field.error); } function hasFieldArray(form, name, { shouldActive = true, shouldTouched = false, shouldDirty = false, shouldValid = false } = {}) { const fieldArray = getFieldArrayStore(form, name); return !!fieldArray && (!shouldActive || fieldArray.active) && (!shouldTouched || fieldArray.touched) && (!shouldDirty || fieldArray.dirty) && (!shouldValid || !fieldArray.error); } function insert(form, name, options) { const fieldArray = getFieldArrayStore(form, name); if (fieldArray) { const arrayLength = fieldArray.items.length; const { at: index = arrayLength, value: value2 } = options; if (index >= 0 && index <= arrayLength) { if (index < arrayLength) { const filterName = (value22) => value22.startsWith(`${name}.`) && getPathIndex(name, value22) >= index; const getNextIndexName = (fieldOrFieldArrayName, fieldOrFieldArrayIndex) => fieldOrFieldArrayName.replace(`${name}.${fieldOrFieldArrayIndex}`, `${name}.${fieldOrFieldArrayIndex + 1}`); getFieldNames(form).filter(filterName).sort(sortArrayPathIndex(name)).reverse().forEach((fieldName) => { setFieldState(form, getNextIndexName(fieldName, getPathIndex(name, fieldName)), getFieldState(form, fieldName)); }); getFieldArrayNames(form).filter(filterName).sort(sortArrayPathIndex(name)).reverse().forEach((fieldArrayName) => { setFieldArrayState(form, getNextIndexName(fieldArrayName, getPathIndex(name, fieldArrayName)), getFieldArrayState(form, fieldArrayName)); }); } setFieldArrayValue(form, name, { at: index, value: value2 }); fieldArray.items.splice(index, 0, getUniqueId()); fieldArray.touched = true; form.touched = true; fieldArray.dirty = true; form.dirty = true; setTimeout(() => validateIfRequired(form, fieldArray, name, { on: [ "touched", "input" ] }), 250); } } } function move(form, name, { from: fromIndex, to: toIndex }) { const fieldArray = getFieldArrayStore(form, name); if (fieldArray) { const lastIndex = fieldArray.items.length - 1; if (fromIndex >= 0 && fromIndex <= lastIndex && toIndex >= 0 && toIndex <= lastIndex && fromIndex !== toIndex) { const filterName = (value2) => { if (value2.startsWith(name)) { const fieldIndex = getPathIndex(name, value2); return fieldIndex >= fromIndex && fieldIndex <= toIndex || fieldIndex <= fromIndex && fieldIndex >= toIndex; } }; const getPrevIndexName = (fieldOrFieldArrayName, fieldOrFieldArrayIndex) => fieldOrFieldArrayName.replace(`${name}.${fieldOrFieldArrayIndex}`, fromIndex < toIndex ? `${name}.${fieldOrFieldArrayIndex - 1}` : `${name}.${fieldOrFieldArrayIndex + 1}`); const getToIndexName = (fieldOrFieldArrayName) => fieldOrFieldArrayName.replace(`${name}.${fromIndex}`, `${name}.${toIndex}`); const fieldNames = getFieldNames(form).filter(filterName).sort(sortArrayPathIndex(name)); const fieldArrayNames = getFieldArrayNames(form).filter(filterName).sort(sortArrayPathIndex(name)); if (fromIndex > toIndex) { fieldNames.reverse(); fieldArrayNames.reverse(); } const fieldStateMap = /* @__PURE__ */ new Map(); const fieldArrayStateMap = /* @__PURE__ */ new Map(); fieldNames.forEach((fieldName) => { const fieldState = getFieldState(form, fieldName); const fieldIndex = getPathIndex(name, fieldName); if (fieldIndex === fromIndex) { fieldStateMap.set(fieldName, fieldState); } else { setFieldState(form, getPrevIndexName(fieldName, fieldIndex), fieldState); } }); fieldStateMap.forEach((fieldState, fieldName) => { setFieldState(form, getToIndexName(fieldName), fieldState); }); fieldArrayNames.forEach((fieldArrayName) => { const fieldArrayState = getFieldArrayState(form, fieldArrayName); const fieldArrayIndex = getPathIndex(name, fieldArrayName); if (fieldArrayIndex === fromIndex) { fieldArrayStateMap.set(fieldArrayName, fieldArrayState); } else { setFieldArrayState(form, getPrevIndexName(fieldArrayName, fieldArrayIndex), fieldArrayState); } }); fieldArrayStateMap.forEach((fieldArrayState, fieldArrayName) => { setFieldArrayState(form, getToIndexName(fieldArrayName), fieldArrayState); }); fieldArray.items.splice(toIndex, 0, fieldArray.items.splice(fromIndex, 1)[0]); fieldArray.touched = true; form.touched = true; updateFieldArrayDirty(form, fieldArray); } } } function remove(form, name, { at: index }) { const fieldArray = getFieldArrayStore(form, name); if (fieldArray) { const lastIndex = fieldArray.items.length - 1; if (index >= 0 && index <= lastIndex) { const filterName = (value2) => value2.startsWith(`${name}.`) && getPathIndex(name, value2) > index; const getPrevIndexName = (fieldOrFieldArrayName, fieldOrFieldArrayIndex) => fieldOrFieldArrayName.replace(`${name}.${fieldOrFieldArrayIndex}`, `${name}.${fieldOrFieldArrayIndex - 1}`); getFieldNames(form).filter(filterName).sort(sortArrayPathIndex(name)).forEach((fieldName) => { setFieldState(form, getPrevIndexName(fieldName, getPathIndex(name, fieldName)), getFieldState(form, fieldName)); }); getFieldArrayNames(form).filter(filterName).sort(sortArrayPathIndex(name)).forEach((fieldArrayName) => { setFieldArrayState(form, getPrevIndexName(fieldArrayName, getPathIndex(name, fieldArrayName)), getFieldArrayState(form, fieldArrayName)); }); fieldArray.items.splice(index, 1); fieldArray.touched = true; form.touched = true; updateFieldArrayDirty(form, fieldArray); validateIfRequired(form, fieldArray, name, { on: [ "touched", "input" ] }); } } } function replace(form, name, options) { const fieldArray = getFieldArrayStore(form, name); if (fieldArray) { const { at: index } = options; const lastIndex = fieldArray.items.length - 1; if (index >= 0 && index <= lastIndex) { setFieldArrayValue(form, name, options); fieldArray.items[index] = getUniqueId(); fieldArray.touched = true; form.touched = true; fieldArray.dirty = true; form.dirty = true; } } } function reset(form, arg2, arg3) { const [fieldNames, fieldArrayNames] = getFilteredNames(form, arg2, false); const resetSingleField = typeof arg2 === "string" && fieldNames.length === 1; const resetEntireForm = !resetSingleField && !Array.isArray(arg2); const options = getOptions(arg2, arg3); const { initialValue, initialValues, keepResponse = false, keepSubmitCount = false, keepSubmitted = false, keepValues = false, keepDirtyValues = false, keepItems = false, keepDirtyItems = false, keepErrors = false, keepTouched = false, keepDirty = false } = options; fieldNames.forEach((name) => { const field = getFieldStore(form, name); if (resetSingleField ? "initialValue" in options : initialValues) { field.internal.initialValue = resetSingleField ? initialValue : getPathValue(name, initialValues); } const keepDirtyValue = keepDirtyValues && field.dirty; if (!keepValues && !keepDirtyValue) { field.internal.startValue = field.internal.initialValue; field.value = field.internal.initialValue; field.internal.elements.forEach((element) => { if (element.type === "file") { element.value = ""; } }); } if (!keepTouched) { field.touched = false; } if (!keepDirty && !keepValues && !keepDirtyValue) { field.dirty = false; } if (!keepErrors) { field.error = ""; } }); fieldArrayNames.forEach((name) => { const fieldArray = getFieldArrayStore(form, name); const keepCurrentDirtyItems = keepDirtyItems && fieldArray.dirty; if (!keepItems && !keepCurrentDirtyItems) { if (initialValues) { fieldArray.internal.initialItems = getPathValue(name, initialValues)?.map(() => getUniqueId()) || []; } fieldArray.internal.startItems = [ ...fieldArray.internal.initialItems ]; fieldArray.items = [ ...fieldArray.internal.initialItems ]; } if (!keepTouched) { fieldArray.touched = false; } if (!keepDirty && !keepItems && !keepCurrentDirtyItems) { fieldArray.dirty = false; } if (!keepErrors) { fieldArray.error = ""; } }); if (resetEntireForm) { if (!keepResponse) { form.response = {}; } if (!keepSubmitCount) { form.submitCount = 0; } if (!keepSubmitted) { form.submitted = false; } } updateFormState(form); } function setResponse(form, response, { duration } = {}) { form.response = response; if (duration) { setTimeout(() => { if (form.response === response) { form.response = {}; } }, duration); } } function setValue(form, name, value2, { shouldTouched = true, shouldDirty = true, shouldValidate = true, shouldFocus = true } = {}) { const field = initializeFieldStore(form, name); field.value = value2; if (shouldTouched) { field.touched = true; form.touched = true; } if (shouldDirty) { updateFieldDirty(form, field); } if (shouldValidate) { validateIfRequired(form, field, name, { on: [ "touched", "input" ], shouldFocus }); } } async function validate(form, arg2, arg3) { const [fieldNames, fieldArrayNames] = getFilteredNames(form, arg2); const { shouldActive = true, shouldFocus = true } = getOptions(arg2, arg3); const validator = getUniqueId(); form.internal.validators.push(validator); form.validating = true; const formErrors = form.internal.validate ? await form.internal.validate(getValues(form, { shouldActive })) : {}; let valid = typeof arg2 !== "string" && !Array.isArray(arg2) ? !Object.keys(formErrors).length : true; const [errorFields] = await Promise.all([ // Validate each field in list Promise.all(fieldNames.map(async (name) => { const field = getFieldStore(form, name); if (!shouldActive || field.active) { let localError; for (const validation of field.internal.validate) { localError = await validation(field.value); if (localError) { break; } } const fieldError = localError || formErrors[name] || ""; if (fieldError) { valid = false; } field.error = fieldError; return fieldError ? name : null; } })), // Validate each field array in list Promise.all(fieldArrayNames.map(async (name) => { const fieldArray = getFieldArrayStore(form, name); if (!shouldActive || fieldArray.active) { let localError = ""; for (const validation of fieldArray.internal.validate) { localError = await validation(fieldArray.items); if (localError) { break; } } const fieldArrayError = localError || formErrors[name] || ""; if (fieldArrayError) { valid = false; } fieldArray.error = fieldArrayError; } })) ]); setErrorResponse(form, formErrors, { shouldActive }); if (shouldFocus) { const name = errorFields.find((name2) => name2); if (name) { focus(form, name); } } updateFormInvalid(form, !valid); form.internal.validators.splice(form.internal.validators.indexOf(validator), 1); if (!form.internal.validators.length) { form.validating = false; } return valid; } function setValues(form, arg2, arg3, arg4) { const isFieldArray = typeof arg2 === "string"; const values = isFieldArray ? arg3 : arg2; const options = (isFieldArray ? arg4 : arg3) || {}; const { shouldTouched = true, shouldDirty = true, shouldValidate = true, shouldFocus = true } = options; const names = isFieldArray ? [ arg2 ] : []; const setFieldArrayItems = (name, value2) => { const fieldArray = initializeFieldArrayStore(form, name); fieldArray.items = value2.map(() => getUniqueId()); if (shouldTouched) { fieldArray.touched = true; form.touched = true; } if (shouldDirty) { fieldArray.dirty = true; form.dirty = true; } }; const setNestedValues = (data, prevPath) => Object.entries(data).forEach(([path, value2]) => { const compoundPath = prevPath ? `${prevPath}.${path}` : path; if (!value2 || typeof value2 !== "object" || !Array.isArray(value2)) { setValue(form, compoundPath, value2, { ...options, shouldValidate: false }); names.push(compoundPath); } if (Array.isArray(value2)) { setFieldArrayItems(compoundPath, value2); } if (value2 && typeof value2 === "object") { setNestedValues(value2, compoundPath); } }); if (isFieldArray) { setFieldArrayItems(arg2, arg3); } setNestedValues(values, isFieldArray ? arg2 : void 0); if (shouldValidate && [ "touched", "input" ].includes(form.internal.validateOn === "submit" && form.submitted ? form.internal.revalidateOn : form.internal.validateOn)) { validate(form, names, { shouldFocus }); } } function submit(form) { form.element?.requestSubmit(); } function swap(form, name, { at: index1, and: index2 }) { const fieldArray = getFieldArrayStore(form, name); if (fieldArray) { const lastIndex = fieldArray.items.length - 1; if (index1 >= 0 && index1 <= lastIndex && index2 >= 0 && index2 <= lastIndex && index1 !== index2) { const index1Prefix = `${name}.${index1}`; const index2Prefix = `${name}.${index2}`; const fieldStateMap = /* @__PURE__ */ new Map(); const fieldArrayStateMap = /* @__PURE__ */ new Map(); const filterName = (value2) => value2.startsWith(`${name}.`) && [ index1, index2 ].includes(getPathIndex(name, value2)); const swapIndex = (value2) => value2.startsWith(index1Prefix) ? value2.replace(index1Prefix, index2Prefix) : value2.replace(index2Prefix, index1Prefix); getFieldNames(form).filter(filterName).forEach((fieldName) => fieldStateMap.set(fieldName, getFieldState(form, fieldName))); fieldStateMap.forEach((fieldState, fieldName) => setFieldState(form, swapIndex(fieldName), fieldState)); getFieldArrayNames(form).filter(filterName).forEach((fieldArrayName) => fieldArrayStateMap.set(fieldArrayName, getFieldArrayState(form, fieldArrayName))); fieldArrayStateMap.forEach((fieldArrayState, fieldArrayName) => setFieldArrayState(form, swapIndex(fieldArrayName), fieldArrayState)); const itemIndex1 = fieldArray.items[index1]; fieldArray.items[index1] = fieldArray.items[index2]; fieldArray.items[index2] = itemIndex1; fieldArray.touched = true; form.touched = true; updateFieldArrayDirty(form, fieldArray); } } } function validateIfRequired(form, fieldOrFieldArray, name, { on: modes, shouldFocus = false }) { const validateOn = fieldOrFieldArray.internal.validateOn ?? form.internal.validateOn; const revalidateOn = fieldOrFieldArray.internal.revalidateOn ?? form.internal.revalidateOn; if (modes.includes((validateOn === "submit" ? form.submitted : fieldOrFieldArray.error) ? revalidateOn : validateOn)) { validate(form, name, { shouldFocus }); } } async function handleFieldEvent(form, field, name, event, element, validationModes, inputValue) { if (inputValue !== void 0) { field.value = inputValue; } for (const transformation of field.internal.transform) { field.value = await transformation(field.value, event, element); } field.touched = true; form.touched = true; updateFieldDirty(form, field); validateIfRequired(form, field, name, { on: validationModes }); } function initializeFieldArrayStore(form, name) { if (!getFieldArrayStore(form, name)) { form.internal.fieldArrays[name] = getInitialFieldArrayStore(name); } return getFieldArrayStore(form, name); } function initializeFieldStore(form, name) { if (!getFieldStore(form, name)) { form.internal.fields[name] = getInitialFieldStore(name); } return getFieldStore(form, name); } function setErrorResponse(form, formErrors, { duration, shouldActive = true }) { const message = Object.entries(formErrors).reduce((errors, [name, error]) => { if ([ getFieldStore(form, name), getFieldArrayStore(form, name) ].every((fieldOrFieldArray) => !fieldOrFieldArray || shouldActive && !fieldOrFieldArray.active)) { errors.push(error); } return errors; }, []).join(" "); if (message) { setResponse(form, { status: "error", message }, { duration }); } } function setFieldArrayState(form, name, state) { const fieldArray = initializeFieldArrayStore(form, name); fieldArray.internal.startItems = state.startItems; fieldArray.items = state.items; fieldArray.error = state.error; fieldArray.touched = state.touched; fieldArray.dirty = state.dirty; } function setFieldState(form, name, state) { const field = initializeFieldStore(form, name); field.internal.startValue = state.startValue; field.value = state.value; field.error = state.error; field.touched = state.touched; field.dirty = state.dirty; } function setFieldArrayValue(form, name, { at: index, value: value2 }) { const updateStores = (prevPath, data) => { Object.entries(data).forEach(([path, value22]) => { const compoundPath = `${prevPath}.${path}`; if (form.internal.fieldArrayPaths?.includes(compoundPath.replace(/.\d+./g, ".$."))) { const items = value22.map(() => getUniqueId()); setFieldArrayState(form, compoundPath, { startItems: [ ...items ], items, error: "", touched: false, dirty: false }); } else if (!value22 || typeof value22 !== "object" || Array.isArray(value22) || value22 instanceof Date || value22 instanceof Blob) { setFieldState(form, compoundPath, { startValue: value22, value: value22, error: "", touched: false, dirty: false }); } if (value22 && typeof value22 === "object") { updateStores(compoundPath, value22); } }); }; updateStores(name, { [index]: value2 }); } function setFieldErrors(form, errors, options) { Object.entries(errors).forEach(([name, error]) => { if (error) { setError(form, name, error, { ...options, shouldFocus: false }); } }); } function sortArrayPathIndex(name) { return (pathA, pathB) => getPathIndex(name, pathA) - getPathIndex(name, pathB); } function updateFieldArrayDirty(form, fieldArray) { const dirty = fieldArray.internal.startItems.join() !== fieldArray.items.join(); if (dirty !== fieldArray.dirty) { fieldArray.dirty = dirty; updateFormDirty(form, dirty); } } function updateFormInvalid(form, invalid) { form.invalid = invalid || getFieldAndArrayStores(form).some((fieldOrFieldArray) => fieldOrFieldArray.active && fieldOrFieldArray.error); } function updateFormState(form) { let touched = false, dirty = false, invalid = false; for (const fieldOrFieldArray of getFieldAndArrayStores(form)) { if (fieldOrFieldArray.active) { if (fieldOrFieldArray.touched) { touched = true; } if (fieldOrFieldArray.dirty) { dirty = true; } if (fieldOrFieldArray.error) { invalid = true; } } if (touched && dirty && invalid) { break; } } form.touched = touched; form.dirty = dirty; form.invalid = invalid; } function zodFieldQrl(schema) { return $(async (value2) => { const result = await getParsedZodSchema(schema, value2); return result.success ? "" : result.error.issues[0].message; }); } const zodField$ = implicit$FirstArg(zodFieldQrl); function zodFormQrl(schema) { return $(async (values) => { const result = await getParsedZodSchema(schema, values); const formErrors = {}; if (!result.success) { for (const issue of result.error.issues) { const path = issue.path.join("."); if (!formErrors[path]) { formErrors[path] = issue.message; } } } return formErrors; }); } const zodForm$ = implicit$FirstArg(zodFormQrl); function Lifecycle(props, key, flags) { return component$(({ of: form, store, validate: validate2, validateOn, revalidateOn, transform, keepActive = false, keepState = true }) => { useVisibleTask$(({ cleanup }) => { store.internal.validate = validate2 ? Array.isArray(validate2) ? validate2 : [ validate2 ] : []; store.internal.validateOn = validateOn; store.internal.revalidateOn = revalidateOn; if ("value" in store) { store.internal.transform = transform ? Array.isArray(transform) ? transform : [ transform ] : []; } const consumer = getUniqueId(); store.internal.consumers.push(consumer); if (!store.active) { store.active = true; updateFormState(form); } cleanup(() => setTimeout(() => { store.internal.consumers.splice(store.internal.consumers.indexOf(consumer), 1); if (!keepActive && !store.internal.consumers.length) { store.active = false; if (!keepState) { reset(form, store.name); } else { updateFormState(form); } } if ("value" in store) { store.internal.elements = store.internal.elements.filter((element) => element.isConnected); } }, 15)); }); return /* @__PURE__ */ jsx(Slot, {}); })(props, key, flags); } function Field({ children, name, type, ...props }) { const { of: form } = props; const field = getFieldStore(form, name); return /* @__PURE__ */ jsx(Lifecycle, { store: field, ...props, children: children(field, { name, autoFocus: isServer && !!field.error, ref: $((element) => { field.internal.elements.push(element); }), onInput$: $((event, element) => { handleFieldEvent(form, field, name, event, element, [ "touched", "input" ], getElementInput(element, field, type)); }), onChange$: $((event, element) => { handleFieldEvent(form, field, name, event, element, [ "change" ]); }), onBlur$: $((event, element) => { handleFieldEvent(form, field, name, event, element, [ "touched", "blur" ]); }) }) }, name); } function FieldArray({ children, name, ...props }) { const fieldArray = getFieldArrayStore(props.of, name); return /* @__PURE__ */ jsx(Lifecycle, { store: fieldArray, ...props, children: children(fieldArray) }, name); } function Form({ of: form, action, onSubmit$, responseDuration: duration, keepResponse, shouldActive, shouldTouched, shouldDirty, shouldFocus, reloadDocument, children, ...formProps }) { const { encType } = formProps; const options = { duration, shouldActive, shouldTouched, shouldDirty, shouldFocus }; return /* @__PURE__ */ jsx("form", { noValidate: true, ...formProps, method: "post", action: action?.actionPath, "preventdefault:submit": !reloadDocument, ref: (element) => { form.element = element; }, onSubmit$: async (event, element) => { if (!keepResponse) { form.response = {}; } form.submitCount++; form.submitted = true; form.submitting = true; try { if (await validate(form, options)) { const values = getValues(form, options); const [actionResult] = await Promise.all([ !reloadDocument ? action?.submit(encType ? new FormData(element) : values) : void 0, // eslint-disable-next-line qwik/valid-lexical-scope onSubmit$?.(values, event) ]); if (actionResult?.value) { const { errors, response } = actionResult.value; setFieldErrors(form, errors, { ...options, shouldFocus: false }); if (Object.keys(response).length) { setResponse(form, response, options); } else { setErrorResponse(form, errors, options); } } } } catch (error) { if (error instanceof FormError) { setFieldErrors(form, error.errors, { ...options, shouldFocus: false }); } if (!(error instanceof FormError) || error.message) { setResponse(form, { status: "error", message: error?.message || "An unknown error has occurred." }, options); } } finally { form.submitting = false; } }, children }); } function useFormStore({ validate: validate2, validateOn = "submit", revalidateOn = "input", ...options }) { return useStore(() => { const [fields, fieldArrays] = getInitialStores(options); return { internal: { fields, fieldArrays, fieldArrayPaths: options.fieldArrays, validate: validate2, validators: [], validateOn, revalidateOn }, // FIXME: Set state based on `action` element: void 0, submitCount: 0, submitting: false, submitted: false, validating: false, touched: false, dirty: false, invalid: false, response: options.action?.value?.response || {} }; }); } function useForm(options) { const form = useFormStore(options); return [ form, { Form: (props) => Form({ of: form, action: options.action, ...props }), Field: (props) => Field({ of: form, ...props }), FieldArray: (props) => FieldArray({ of: form, ...props }) } ]; } function toCustomQrl(action, { on: mode }) { return $((value2, event, element) => event.type === mode ? action(value2, event, element) : value2); } const toCustom$ = implicit$FirstArg(toCustomQrl); function toLowerCase(options) { return toCustom$((value2) => value2 && value2.toLowerCase(), options); } function toTrimmed(options) { return toCustom$((value2) => value2 && value2.trim(), options); } function toUpperCase(options) { return toCustom$((value2) => value2 && value2.toUpperCase(), options); } function customQrl(requirement, error) { return $(async (value2) => (Array.isArray(value2) ? value2.length : value2 || value2 === 0) && !await requirement(value2) ? error : ""); } const custom$ = implicit$FirstArg(customQrl); function email(error) { return $((value2) => value2 && !/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(value2) ? error : ""); } function maxLength(requirement, error) { return $((value2) => value2?.length && value2.length > requirement ? error : ""); } function maxRange(requirement, error) { return $((value2) => (value2 || value2 === 0) && value2 > requirement ? error : ""); } function maxSize(requirement, error) { return $((value2) => value2 && (Array.isArray(value2) ? [ ...value2 ].some((file) => file.size > requirement) : value2.size > requirement) ? error : ""); } function maxTotalSize(requirement, error) { return $((value2) => value2?.length && [ ...value2 ].reduce((size, file) => size + file.size, 0) > requirement ? error : ""); } function mimeType(requirement, error) { const mimeTypes = Array.isArray(requirement) ? requirement : [ requirement ]; return $((value2) => value2 && (Array.isArray(value2) ? [ ...value2 ].some((file) => !mimeTypes.includes(file.type)) : !mimeTypes.includes(value2.type)) ? error : ""); } function minLength(requirement, error) { return $((value2) => value2?.length && value2.length < requirement ? error : ""); } function minRange(requirement, error) { return $((value2) => (value2 || value2 === 0) && value2 < requirement ? error : ""); } function minSize(requirement, error) { return $((value2) => value2 && (Array.isArray(value2) ? [ ...value2 ].some((file) => file.size < requirement) : value2.size < requirement) ? error : ""); } function minTotalSize(requirement, error) { return $((value2) => value2?.length && [ ...value2 ].reduce((size, file) => size + file.size, 0) < requirement ? error : ""); } function pattern(requirement, error) { return $((value2) => value2 && !requirement.test(value2) ? error : ""); } function required(error) { return $((value2) => !value2 && value2 !== 0 || Array.isArray(value2) && !value2.length ? error : ""); } function url(error) { return $((value2) => { try { value2 && new URL(value2); return ""; } catch (_) { return error; } }); } function value(requirement, error) { return $((value2) => (value2 || value2 === 0) && value2 !== requirement ? error : ""); } export { Field, FieldArray, Form, FormError, clearError, clearResponse, custom$, customQrl, email, focus, formAction$, formActionQrl, getError, getErrors, getValue, getValues, hasField, hasFieldArray, insert, maxLength, maxRange, maxSize, maxTotalSize, mimeType, minLength, minRange, minSize, minTotalSize, move, pattern, remove, replace, required, reset, setError, setResponse, setValue, setValues, submit, swap, toCustom$, toCustomQrl, toLowerCase, toTrimmed, toUpperCase, url, useForm, useFormStore, valiField$, valiFieldQrl, valiForm$, valiFormQrl, validate, value, zodField$, zodFieldQrl, zodForm$, zodFormQrl };