UNPKG

maz-ui

Version:

A standalone components library for Vue.Js 3 & Nuxt.Js 3

424 lines (423 loc) 13.1 kB
import { nextTick, getCurrentInstance, inject } from "vue"; import { useFreezeValue } from "../composables/useFreezeValue.js"; function i$1(e) { return e == null || typeof e == "string" || typeof e == "number" || typeof e == "boolean" || typeof e == "symbol" || typeof e == "bigint"; } function o(e, r) { if (e.length !== r.length) return !1; for (const [t, n] of e.entries()) if (!s(n, r[t])) return !1; return !0; } function y(e, r) { const t = Object.keys(e), n = Object.keys(r); if (t.length !== n.length) return !1; for (const f of t) if (!n.includes(f) || !s(e[f], r[f])) return !1; return !0; } function s(e, r) { return i$1(e) || i$1(r) ? e === r : e instanceof Date && r instanceof Date ? e.getTime() === r.getTime() : typeof e != typeof r || Array.isArray(e) !== Array.isArray(r) ? !1 : Array.isArray(e) && Array.isArray(r) ? o(e, r) : typeof e == "object" && typeof r == "object" ? y(e, r) : !1; } const CONFIG = { mode: "lazy", scrollToErrorSelector: ".has-field-error", debounceTime: 300, throttleTime: 1e3 }; function i(o2, n, r) { const t = {}; return function(...c2) { t[o2] || (t[o2] = { timer: null, promise: null }); const e = t[o2]; return e.timer && clearTimeout(e.timer), e.promise = new Promise((u, m) => { e.timer = setTimeout(async () => { try { u(await n(...c2)); } catch (s2) { m(s2); } finally { delete t[o2]; } }, r); }), e.promise; }; } function c(s2, o2, t) { const l = {}; return async (...m) => { const a = Date.now(); return l[s2] || (l[s2] = { promise: null, lastCall: 0, lastArgs: [] }), a - l[s2].lastCall >= t ? (l[s2].lastCall = a, o2(...m)) : (l[s2].lastArgs = m, l[s2].promise || (l[s2].promise = new Promise((u) => { setTimeout( async () => { l[s2].lastCall = Date.now(); const p = await o2(...l[s2].lastArgs); l[s2].promise = null, u(p); }, t - (a - l[s2].lastCall) ); })), l[s2].promise); }; } function isEmptyValue(value) { return value == null || value === ""; } const storeValidbot = {}; async function getValibotValidationMethod(methodName) { if (storeValidbot[methodName]) return storeValidbot[methodName]; const valibot = await import("valibot"); return storeValidbot[methodName] = valibot[methodName], valibot[methodName]; } async function getValidationSchema(formSchema) { return (await getValibotValidationMethod("objectAsync"))(formSchema); } async function getFieldValidationResult(name, schema, value) { const fieldSchema = await getValidationSchema(schema), result = await (await getValibotValidationMethod("safeParseAsync"))(fieldSchema.entries[name], value ?? ""); return { result, isValid: result.success }; } async function setFieldValidationState({ name, fieldState, schema, payload, setError = !0, setErrorIfInvalidAndNotEmpty = !1 }) { if (await nextTick(), fieldState.validating) return Promise.resolve(); fieldState.validating = !0; try { if (!schema[name]) { fieldState.valid = !0, fieldState.validating = !1, fieldState.validated = !0, fieldState.errors = [], fieldState.error = !1; return; } const { result, isValid } = await getFieldValidationResult(name, schema, payload[name]); fieldState.valid = isValid; const shouldSetError = setError || setErrorIfInvalidAndNotEmpty && !isValid && !isEmptyValue(payload[name]), shouldPreserveError = fieldState.blurred && !isValid && fieldState.mode && ["eager", "blur", "progressive"].includes(fieldState.mode); (shouldSetError || shouldPreserveError) && (fieldState.error = !isValid), fieldState.errors = result.issues ?? [], fieldState.validated = !0; } finally { fieldState.validating = !1; } } function validateForm({ fieldsStates, payload, setErrors = !0, schema }) { return Promise.all( Object.keys(fieldsStates).map( (name) => setFieldValidationState({ name, setError: setErrors, fieldState: fieldsStates[name], payload, schema, setErrorIfInvalidAndNotEmpty: fieldsStates[name].mode === "lazy" }) ) ); } function getErrorMessages(errors, fieldsStates) { const errorMessages = {}; for (const [name, value] of Object.entries(errors)) { const issues = value; errorMessages[name] = fieldsStates[name].error && issues[0] ? issues[0].message : void 0; } return errorMessages; } function getFieldsErrors(fieldsStates) { const fieldsErrors = {}; for (const [name, fieldState] of Object.entries(fieldsStates)) fieldsErrors[name] = fieldState.errors; return fieldsErrors; } function getValidateFunction({ name, hasValidation, debouncedFields, throttledFields }) { if (!hasValidation) return; const fieldName = String(name); if (debouncedFields?.[fieldName] && throttledFields?.[fieldName]) throw new Error(`The field "${String(fieldName)}" cannot be both debounced and throttled`); return debouncedFields?.[fieldName] ? i( String(fieldName), setFieldValidationState, typeof debouncedFields[fieldName] == "number" ? debouncedFields[fieldName] : CONFIG.debounceTime ) : throttledFields?.[fieldName] ? c( String(fieldName), setFieldValidationState, typeof throttledFields[fieldName] == "number" ? throttledFields[fieldName] : CONFIG.throttleTime ) : setFieldValidationState; } function getFieldState({ name, schema, initialValue, fieldState, options }) { const hasValidation = schema ? fieldHasValidation(name, schema) : !1, validateFunction = getValidateFunction({ name, hasValidation, debouncedFields: options?.debouncedFields, throttledFields: options?.throttledFields }); return { blurred: !1, dirty: !1, errors: [], error: !1, valid: !hasValidation, validating: !1, validated: !1, initialValue: useFreezeValue(initialValue), validateFunction, mode: hasValidation ? options?.mode ?? fieldState?.mode ?? CONFIG.mode : void 0 }; } function fieldHasValidation(field, schema) { return Object.keys(schema).includes(field); } function getFieldsStates({ schema, payload, options }) { const fieldsStates = {}; for (const fieldName in schema) { const name = fieldName; fieldsStates[name] = getFieldState({ name, schema, options, fieldState: fieldsStates[name], initialValue: payload?.[name] }); } return fieldsStates; } function updateFieldsStates({ fieldsStates, payload, schema, options, updateMode = !1 }) { for (const fieldName in schema) { const name = fieldName; fieldsStates[name] = updateFieldState({ name, fieldState: fieldsStates[name], payload, schema, options, updateMode }); } } function updateFieldState({ name, fieldState, payload, schema, options, updateMode = !0 }) { const { initialValue, mode, ...rest } = getFieldState({ name, schema, initialValue: options.defaultValue ?? payload[name], fieldState, options }), newMode = updateMode ? mode ?? fieldState.mode ?? CONFIG.mode : fieldState.mode; return { ...fieldState, initialValue, mode: newMode, ...fieldState?.mode && newMode !== fieldState.mode ? rest : {} }; } function canExecuteValidation({ eventName, fieldState, isSubmitted }) { const { dirty, blurred, mode, valid } = fieldState; return eventName === "blur" && (hasModeIncludes(["lazy", "aggressive"], mode) || valid) || eventName === "input" && mode === "blur" || !mode ? !1 : isSubmitted || mode === "eager" && blurred || mode === "blur" && blurred || mode === "aggressive" && dirty || mode === "lazy" && dirty || mode === "progressive"; } function handleFieldBlur({ name, force = !1, payload, fieldState, schema, isSubmitted }) { const fieldValue = payload[name], isDirty = !s(fieldValue, fieldState.initialValue); if (fieldState.dirty = isDirty, fieldState.blurred = fieldState.blurred || (fieldState.mode === "eager" ? isDirty : !0), !!(force || canExecuteValidation({ eventName: "blur", fieldState, isSubmitted }))) return fieldState.validateFunction?.({ name, fieldState, schema, payload, setError: fieldState.mode === "progressive" ? fieldState.validated || fieldState.blurred : !0 }); } function handleFieldInput({ name, payload, fieldState, schema, isSubmitted, forceValidation = !1 }) { const fieldValue = payload[name]; fieldState.validated = !1; const isDirty = !s(fieldValue, fieldState.initialValue); if (fieldState.dirty = isDirty, !!(forceValidation || canExecuteValidation({ eventName: "input", fieldState, isSubmitted }))) return fieldState.validateFunction?.({ name, fieldState, schema, payload, setError: fieldState.mode === "progressive" ? fieldState.validated || fieldState.blurred : !0 }); } function hasModeIncludes(modes, value) { return value ? modes.includes(value) : !1; } function getInstance(composableName) { const instance = getCurrentInstance(); if (!instance) throw new Error(`${composableName} must be called within setup()`); return instance; } function getContext(identifier, composableName) { const context = getInstance(composableName).formContexts?.get(identifier) ?? inject(identifier); if (!context) throw new Error("useFormField must be used within a form (useFormValidator)"); return context; } function scrollToError(selector = CONFIG.scrollToErrorSelector) { const element = document.querySelector(selector); element && element.scrollIntoView({ behavior: "smooth", block: "center" }); } function findInteractiveElements(el) { if (isInteractiveElement(el)) return [el]; const selector = [ // Standard form elements "input", "select", "textarea", "button", // Focusable elements "a[href]", '[tabindex]:not([tabindex="-1"])', // Elements with interactive ARIA roles '[role="button"]', '[role="textbox"]', '[role="combobox"]', '[role="listbox"]', '[role="slider"]', '[role="spinbutton"]', '[role="switch"]', '[role="checkbox"]', '[role="radio"]', '[role="menuitem"]', '[role="option"]', // Contenteditable elements '[contenteditable="true"]', // Custom interactive elements (common patterns) "[data-interactive]", ".interactive", "[data-clickable]" ].join(", "); return Array.from(el.querySelectorAll(selector)).filter(isInteractiveElement); } function isInteractiveElement(el) { if (el.getAttribute("tabindex") === "-1") return !1; if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement || el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement && el.href || el.hasAttribute("tabindex") && el.getAttribute("tabindex") !== "-1") return !0; const interactiveRoles = [ "button", "textbox", "combobox", "listbox", "slider", "spinbutton", "switch", "checkbox", "radio", "menuitem", "option" ], role = el.getAttribute("role"); return role && interactiveRoles.includes(role) || el.getAttribute("contenteditable") === "true" ? !0 : el.hasAttribute("data-interactive") || el.hasAttribute("data-clickable") || el.classList.contains("interactive"); } const activeListeners = /* @__PURE__ */ new WeakMap(); function addEventToInteractiveElements({ interactiveElements, onBlur, mode }) { interactiveElements.forEach((element) => { if (hasModeIncludes(["eager", "blur", "progressive"], mode)) { const existingListeners = activeListeners.get(element); existingListeners?.has("blur") && element.removeEventListener("blur", existingListeners.get("blur")), element.addEventListener("blur", onBlur), activeListeners.has(element) || activeListeners.set(element, /* @__PURE__ */ new Map()), activeListeners.get(element).set("blur", onBlur); } }); } function removeEventFromInteractiveElements({ interactiveElements, onBlur }) { interactiveElements.forEach((element) => { const existingListeners = activeListeners.get(element); existingListeners?.has("blur") && (element.removeEventListener("blur", onBlur), existingListeners.delete("blur"), existingListeners.size === 0 && activeListeners.delete(element)); }); } function getValidationEvents({ hasRef, fieldState, onBlur }) { if (!(hasRef || hasModeIncludes(["aggressive", "lazy"], fieldState.mode))) return { onBlur }; } export { CONFIG as C, getFieldsStates as a, getFieldsErrors as b, getErrorMessages as c, handleFieldInput as d, getContext as e, fieldHasValidation as f, getInstance as g, hasModeIncludes as h, updateFieldState as i, s as j, setFieldValidationState as k, getValidationEvents as l, findInteractiveElements as m, addEventToInteractiveElements as n, handleFieldBlur as o, removeEventFromInteractiveElements as r, scrollToError as s, updateFieldsStates as u, validateForm as v };