maz-ui
Version:
A standalone components library for Vue.Js 3 & Nuxt.Js 3
424 lines (423 loc) • 13.1 kB
JavaScript
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
};