@kevinkosterr/vue3-form-generator
Version:
A schema-based form generator component for Vue 3, based on the original [`vue-form-generator`](https://github.com/vue-generators/vue-form-generator) library.
1,313 lines (1,312 loc) • 70.3 kB
JavaScript
import { computed, ref, defineComponent, useTemplateRef, openBlock, createElementBlock, normalizeStyle, createElementVNode, toDisplayString, createCommentVNode, createBlock, resolveDynamicComponent, unref, Fragment, renderList, createTextVNode, withModifiers, toRefs, getCurrentScope, onScopeDispose, watch, reactive, toValue, shallowRef, withDirectives, normalizeClass, onBeforeMount, createVNode } from "vue";
let messages = {
required: "Field is required",
string: "Value must be a string",
number: "Value must be a number",
email: "E-mail is invalid",
phoneNumberE164andE123: "Phone number is invalid (must be valid E164 or E123 format, e.g. +31 612345678)",
mobilePhoneNL: "Phone number is invalid (must be a valid Dutch phone number, e.g. +31612345678)",
hexColorValue: "Invalid hex value (e.g. #ff0000 or #ff0)"
};
function getMessage(validatorName) {
return messages[validatorName] || "Field is invalid";
}
function setMessages(_messages) {
messages = { ...messages, ..._messages };
}
function isFunction(val) {
return typeof val === "function";
}
function isObject$1(val) {
return typeof val === "object";
}
function isString(val) {
return typeof val === "string";
}
function toUniqueArray(arr) {
if (!Array.isArray(arr)) throw new Error("Argument must be of type array");
return Array.from(new Set(arr));
}
function getFieldComponentName(field) {
const uniqueFieldTypes = ["mask"];
const hasType = "type" in field;
const isUniqueFieldType = hasType && uniqueFieldTypes.includes(field.type);
let fieldAttribute;
if ((!("inputType" in field) || field.inputType == void 0) && "type" in field || isUniqueFieldType) {
fieldAttribute = field.type;
} else if ("inputType" in field && !isUniqueFieldType) {
fieldAttribute = field.inputType;
}
if (!fieldAttribute) throw new Error("No input or input type specified for " + field);
return "Field" + fieldAttribute.charAt(0).toUpperCase() + fieldAttribute.slice(1);
}
function isEmpty(value) {
if (Array.isArray(value)) return value.length === 0;
return value === void 0 || value === null || value === "";
}
function isNotEmpty(value) {
return !isEmpty(value);
}
function resetObjectProperties(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === "string") {
obj[key] = "";
} else if (typeof value === "number") {
obj[key] = 0;
} else if (typeof value === "boolean") {
obj[key] = false;
} else if (Array.isArray(value)) {
obj[key] = [];
} else if (typeof value === "object" && value !== null) {
obj[key] = resetObjectProperties(value);
} else {
obj[key] = null;
}
}
}
return obj;
}
function useFormModel(model, field) {
const currentModelValue = computed(() => {
return "model" in field ? model[field.model] : void 0;
});
return { currentModelValue };
}
const validators = {
/**
* Checks if a field meets the 'required' validation criteria.
* @returns {boolean} - Returns 'true' if the field is required and the value is not empty, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
required(value, field, model) {
return isNotEmpty(value);
},
/**
* Check if field value is the minimum required length, value or amount of values
* @returns {boolean} - Returns 'true' if the field is required and the value is not empty, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
min(value, field, model) {
if (!("min" in field) || !field.min || !value) return true;
if (typeof value === "number") {
return value >= field.min;
} else if (typeof value === "string") {
return value.length >= field.min;
} else if (Array.isArray(value)) {
return value.length >= field.min;
}
return true;
},
/**
* Check if field value is the maximum provided length
* @returns {boolean} - Returns 'true' if the field is required and the value is not empty, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
max(value, field, model) {
if (!("max" in field) || !value || !field.max) return true;
if (typeof value === "number") {
return value <= field.max;
} else if (typeof value === "string") {
return value.length <= field.max;
} else if (Array.isArray(value)) {
return value.length <= field.max;
}
return true;
},
/**
* Checks if the field's value is of type string.
* @returns {boolean} - Returns 'true' if the field is required and the value is not empty, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
string(value, field, model) {
return isString(value);
},
/**
* Checks if the field's value is of type number.
* @returns {boolean} - Returns 'true' if the field is a number.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
number(value, field, model) {
return Number.isNaN(value);
},
/**
* Check if the field's value is of a valid e-mail address format.
* @returns {boolean} - Returns 'true' if the field's format is a valid email format, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
email(value, field, model) {
if (typeof value !== "string") return false;
const regex = new RegExp('^([^<>()\\[\\]\\\\.,;:\\s@"]+(?:\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*|".+")@(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}]|(?:[a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,})$', "i");
return Boolean(value.match(regex));
},
/**
* Check if a value is a phone number in E164 or E123 format.
* @returns {boolean} - Returns 'true' if value matches the format, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
phoneNumberE164andE123(value, field, model) {
if (typeof value !== "string") return false;
const regex = new RegExp(
"^\\+\\d{1,3}\\s\\d{2,3}\\s\\d{2,3}\\s\\d{4}|^\\+\\d{1,3}\\s\\d{1,14}(\\s\\d{1,13})?|^\\(\\d{3}\\)\\s\\d{3}\\s\\d{4}?",
"i"
);
return Boolean(value.match(regex));
},
/**
* Check if value is a valid Dutch mobile phone number
* @returns {boolean} - Returns 'true' if value matches the format, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mobilePhoneNL(value, field, model) {
if (typeof value !== "string") return false;
const regex = new RegExp("(\\+316[0-9]{8})|(06[0-9]{8})", "i");
return Boolean(value.match(regex));
},
/**
* Check if a value is a valid HEX color value.
* @returns {boolean} - Returns `true` if value matches the format, otherwise false.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
hexColorValue(value, field, model) {
if (typeof value !== "string") return false;
const regex = new RegExp("^#([a-f0-9]{3}|[a-f0-9]{6})$", "i");
return Boolean(value.match(regex));
}
};
function getValidator(validator) {
if (validator === void 0) return () => true;
if (isFunction(validator)) return validator;
if (isString(validator)) {
if (validators[validator] === void 0) {
throw new Error("Invalid validator: " + validator);
}
return validators[validator];
}
return () => true;
}
function useFieldValidate(model, field, isDisabled = false, isRequired = false, isReadOnly = false) {
const errors = ref([]);
const defaultValidators = computed(() => {
const fieldValidators = [];
if (!isDisabled && !isReadOnly) {
if (isRequired && !fieldValidators.includes(validators.required)) {
fieldValidators.push(validators.required);
}
if ("min" in field && field.min) {
fieldValidators.push(validators.min);
}
if ("max" in field && field.max) {
fieldValidators.push(validators.max);
}
}
return fieldValidators;
});
const validate = async (currentModelValue) => {
if (!("validator" in field)) return [];
const results = [];
const fieldValidators = [...defaultValidators.value];
if (Array.isArray(field.validator)) {
field.validator.forEach((validator) => fieldValidators.push(getValidator(validator)));
} else {
fieldValidators.push(getValidator(field.validator));
}
fieldValidators.forEach((validator) => {
const isValid = validator(currentModelValue, field, model);
if (!isValid) results.push(getMessage(validator.name));
});
const uniqueResults = toUniqueArray(results);
if ("onValidated" in field && field.onValidated) {
if (isFunction(field.onValidated)) {
field.onValidated.call(null, model, uniqueResults, field);
} else {
throw new Error("onValidated property must be of type `function` on field: " + field.name);
}
}
errors.value = uniqueResults;
return uniqueResults;
};
return { errors, validate };
}
function useFieldAttributes(model, field) {
function determineDynamicBooleanAttribute(attribute, defaultValue = false) {
const attributeValue = field[attribute];
if (typeof attributeValue === "function") {
return attributeValue(model, field);
}
return typeof attributeValue !== "boolean" ? defaultValue : attributeValue;
}
function determineDynamicStringAttribute(attribute, defaultValue = "") {
const attributeValue = field[attribute];
if (typeof attributeValue === "function") {
return attributeValue(model, field);
}
return typeof attributeValue !== "string" ? defaultValue : attributeValue;
}
const isDisabled = computed(() => {
return determineDynamicBooleanAttribute("disabled");
});
const isRequired = computed(() => {
return determineDynamicBooleanAttribute("required");
});
const isReadonly = computed(() => {
return determineDynamicBooleanAttribute("readonly");
});
const isVisible = computed(() => {
return determineDynamicBooleanAttribute("visible", true);
});
const hint = computed(() => {
return determineDynamicStringAttribute("hint", "");
});
return {
hint,
isVisible,
isDisabled,
isRequired,
isReadonly
};
}
function useFieldProps() {
return {
id: String,
formOptions: Object,
field: {
type: Object,
required: true
},
model: {
type: Object,
required: true
}
};
}
function useFieldEmits() {
return ["onInput", "validated"];
}
const _hoisted_1$h = ["for"];
const _hoisted_2$9 = { class: "field-wrap" };
const _hoisted_3$5 = {
key: 1,
class: "hints"
};
const _hoisted_4$1 = { class: "hint" };
const _hoisted_5$1 = {
key: 2,
class: "errors help-block"
};
const _hoisted_6$1 = { class: "error" };
const _sfc_main$h = /* @__PURE__ */ defineComponent({
__name: "FormGroup",
props: {
formOptions: { default: () => ({}) },
model: {},
field: {},
errors: { default: () => [] }
},
emits: ["value-updated", "validated"],
setup(__props, { emit: __emit }) {
const fieldComponent = useTemplateRef("fieldComponent");
const props = __props;
const emit = __emit;
function onInput(value) {
emit("value-updated", { model: props.field.model, value });
}
function onValidated(isValid, fieldErrors, field) {
emit("validated", { isValid, fieldErrors, field });
}
const fieldId = computed(() => {
return `${props.formOptions.idPrefix ? props.formOptions.idPrefix + "_" : ""}${props.field.name}`;
});
const fieldStyle = computed(() => ({
display: fieldComponent.value && fieldComponent.value.isVisible ? void 0 : "none"
}));
const fieldHasErrors = computed(() => {
return Boolean(fieldComponent.value && fieldComponent.value.errors && fieldComponent.value.errors.length);
});
const fieldHasHint = computed(() => {
return Boolean(fieldComponent.value && fieldComponent.value.hint);
});
const shouldHaveLabel = computed(() => {
var _a;
if (((_a = fieldComponent.value) == null ? void 0 : _a.noLabel) || props.field.noLabel === true) {
return false;
}
return Boolean(props.field.label);
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: "form-group",
style: normalizeStyle(fieldStyle.value)
}, [
shouldHaveLabel.value ? (openBlock(), createElementBlock("label", {
key: 0,
for: fieldId.value
}, [
createElementVNode("span", null, toDisplayString(props.field.label), 1)
], 8, _hoisted_1$h)) : createCommentVNode("", true),
createElementVNode("div", _hoisted_2$9, [
(openBlock(), createBlock(resolveDynamicComponent(unref(getFieldComponentName)(props.field)), {
id: fieldId.value,
ref_key: "fieldComponent",
ref: fieldComponent,
"form-options": props.formOptions,
model: _ctx.model,
field: props.field,
onOnInput: onInput,
onValidated
}, null, 40, ["id", "form-options", "model", "field"]))
]),
unref(fieldComponent) && fieldHasHint.value ? (openBlock(), createElementBlock("div", _hoisted_3$5, [
createElementVNode("span", _hoisted_4$1, toDisplayString(unref(fieldComponent).hint), 1)
])) : createCommentVNode("", true),
unref(fieldComponent) && fieldHasErrors.value ? (openBlock(), createElementBlock("div", _hoisted_5$1, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(fieldComponent).errors, (error) => {
return openBlock(), createElementBlock(Fragment, { key: error }, [
createElementVNode("span", _hoisted_6$1, toDisplayString(error), 1),
_cache[0] || (_cache[0] = createTextVNode()),
_cache[1] || (_cache[1] = createElementVNode("br", null, null, -1))
], 64);
}), 128))
])) : createCommentVNode("", true)
], 4);
};
}
});
const _hoisted_1$g = ["id", "enctype"];
const _hoisted_2$8 = { key: 0 };
const _hoisted_3$4 = { key: 0 };
const _sfc_main$g = /* @__PURE__ */ defineComponent({
__name: "FormGenerator",
props: {
id: { default: "" },
idPrefix: { default: "" },
options: { default: () => ({}) },
schema: {},
model: {},
enctype: { default: "application/x-www-form-urlencoded" }
},
emits: ["submit", "field-validated"],
setup(__props, { expose: __expose, emit: __emit }) {
const emits = __emit;
const props = __props;
const fieldElements = ref([]);
const formErrors = ref({});
const formOptions = computed(() => ({ ...props.options, idPrefix: props.idPrefix }));
const updateGeneratorModel = ({ model, value }) => {
props.model[model] = value;
};
const onFieldValidated = ({ fieldErrors, field }) => {
emits("field-validated", { fieldErrors, field });
if (!fieldErrors.length) {
if (!(field.model in formErrors.value)) return;
else {
delete formErrors.value[field.model];
return;
}
}
formErrors.value[field.model] = toUniqueArray(fieldErrors);
};
const hasErrors = computed(() => {
return Boolean(Object.values(formErrors.value).map((e) => Boolean(e.length)).filter((e) => e).length);
});
const onSubmit = () => {
if (!hasErrors.value) emits("submit");
};
const onReset = () => {
props.model = resetObjectProperties(props.model);
};
__expose({ hasErrors, formErrors });
return (_ctx, _cache) => {
return props.schema !== void 0 ? (openBlock(), createElementBlock("form", {
key: 0,
id: props.id ?? "",
class: "vue-form-generator",
enctype: _ctx.enctype,
onSubmit: withModifiers(onSubmit, ["prevent"]),
onReset: withModifiers(onReset, ["prevent"])
}, [
props.schema.fields ? (openBlock(), createElementBlock("fieldset", _hoisted_2$8, [
(openBlock(true), createElementBlock(Fragment, null, renderList(props.schema.fields, (field) => {
return openBlock(), createBlock(_sfc_main$h, {
key: field,
ref_for: true,
ref: (el) => el && "$el" in el ? fieldElements.value.push(el) : null,
"form-options": formOptions.value,
field,
model: props.model,
onValueUpdated: updateGeneratorModel,
onValidated: onFieldValidated
}, null, 8, ["form-options", "field", "model"]);
}), 128)),
(openBlock(true), createElementBlock(Fragment, null, renderList(props.schema.groups, (group) => {
return openBlock(), createElementBlock("fieldset", { key: group }, [
group.legend ? (openBlock(), createElementBlock("legend", _hoisted_3$4, toDisplayString(group.legend), 1)) : createCommentVNode("", true),
(openBlock(true), createElementBlock(Fragment, null, renderList(group.fields, (field) => {
return openBlock(), createBlock(_sfc_main$h, {
key: field,
ref_for: true,
ref: (el) => el && "$el" in el ? fieldElements.value.push(el) : null,
"form-options": formOptions.value,
field,
model: props.model,
onValueUpdated: updateGeneratorModel,
onValidated: onFieldValidated
}, null, 8, ["form-options", "field", "model"]);
}), 128))
]);
}), 128))
])) : createCommentVNode("", true)
], 40, _hoisted_1$g)) : createCommentVNode("", true);
};
}
});
const _hoisted_1$f = ["id", "name", "required", "disabled", "readonly", "placeholder", "autocomplete", "value"];
const _sfc_main$f = /* @__PURE__ */ defineComponent({
__name: "FieldText",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const emits = __emit;
const props = __props;
const { field, model } = toRefs(props);
const autoCompleteState = computed(() => field.value.autocomplete ? "on" : "off");
const { currentModelValue } = useFormModel(model.value, field.value);
const { isRequired, isDisabled, isReadonly, isVisible, hint } = useFieldAttributes(model.value, field.value);
const { errors, validate } = useFieldValidate(
model.value,
field.value,
isDisabled.value,
isRequired.value,
false
);
const onBlur = () => {
validate(currentModelValue.value).then((validationErrors) => {
emits(
"validated",
validationErrors.length === 0,
validationErrors,
field.value
);
});
};
const onFieldValueChanged = (event) => {
errors.value = [];
emits("onInput", event.target.value);
};
__expose({ errors, hint, isVisible });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("input", {
id: props.id,
class: "field-input",
type: "text",
name: unref(field).name,
required: unref(isRequired),
disabled: unref(isDisabled),
readonly: unref(isReadonly),
placeholder: unref(field).placeholder,
autocomplete: autoCompleteState.value,
value: unref(currentModelValue),
onInput: onFieldValueChanged,
onBlur
}, null, 40, _hoisted_1$f);
};
}
});
const _hoisted_1$e = { class: "wrapper" };
const _hoisted_2$7 = ["id", "name", "required", "disabled", "placeholder", "value"];
const _sfc_main$e = /* @__PURE__ */ defineComponent({
__name: "FieldPassword",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const mediumRegex = new RegExp("^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})");
const strongRegex = new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})");
const props = __props;
const emits = __emit;
const { model, field } = toRefs(props);
const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value);
const { currentModelValue } = useFormModel(model.value, field.value);
const { errors, validate } = useFieldValidate(
model.value,
field.value,
isDisabled.value,
isRequired.value,
false
);
const passwordStrength = computed(() => {
if (strongRegex.test(currentModelValue.value)) {
return 3;
} else if (mediumRegex.test(currentModelValue.value)) {
return 2;
} else if (currentModelValue.value.length) {
return 1;
}
return 0;
});
const meterStyle = computed(() => {
return {
0: "",
1: "width:15%;background:red;",
2: "width:50%;background:orange;",
3: "width:100%;background:green;"
}[passwordStrength.value] ?? "";
});
const onFieldValueChanged = (event) => {
errors.value = [];
emits("onInput", event.target.value);
};
const onBlur = () => {
validate(currentModelValue.value).then((validationErrors) => {
emits(
"validated",
validationErrors.length === 0,
validationErrors,
field.value
);
});
};
__expose({ hint, errors, isVisible });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1$e, [
createElementVNode("input", {
id: _ctx.id,
type: "password",
name: unref(field).name,
required: unref(isRequired),
disabled: unref(isDisabled),
placeholder: unref(field).placeholder,
value: unref(currentModelValue),
onInput: onFieldValueChanged,
onBlur
}, null, 40, _hoisted_2$7),
unref(field).indicator ? (openBlock(), createElementBlock("div", {
key: 0,
class: "password-strength-indicator",
style: normalizeStyle(meterStyle.value)
}, null, 4)) : createCommentVNode("", true)
]);
};
}
});
function tryOnScopeDispose(fn) {
if (getCurrentScope()) {
onScopeDispose(fn);
return true;
}
return false;
}
const isClient = typeof window !== "undefined" && typeof document !== "undefined";
typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
const toString = Object.prototype.toString;
const isObject = (val) => toString.call(val) === "[object Object]";
const noop = () => {
};
function toArray(value) {
return Array.isArray(value) ? value : [value];
}
function watchImmediate(source, cb, options) {
return watch(
source,
cb,
{
...options,
immediate: true
}
);
}
const defaultWindow = isClient ? window : void 0;
function unrefElement(elRef) {
var _a;
const plain = toValue(elRef);
return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
}
function useEventListener(...args) {
const cleanups = [];
const cleanup = () => {
cleanups.forEach((fn) => fn());
cleanups.length = 0;
};
const register = (el, event, listener, options) => {
el.addEventListener(event, listener, options);
return () => el.removeEventListener(event, listener, options);
};
const firstParamTargets = computed(() => {
const test = toArray(toValue(args[0])).filter((e) => e != null);
return test.every((e) => typeof e !== "string") ? test : void 0;
});
const stopWatch = watchImmediate(
() => {
var _a, _b;
return [
(_b = (_a = firstParamTargets.value) == null ? void 0 : _a.map((e) => unrefElement(e))) != null ? _b : [defaultWindow].filter((e) => e != null),
toArray(toValue(firstParamTargets.value ? args[1] : args[0])),
toArray(unref(firstParamTargets.value ? args[2] : args[1])),
// @ts-expect-error - TypeScript gets the correct types, but somehow still complains
toValue(firstParamTargets.value ? args[3] : args[2])
];
},
([raw_targets, raw_events, raw_listeners, raw_options]) => {
cleanup();
if (!(raw_targets == null ? void 0 : raw_targets.length) || !(raw_events == null ? void 0 : raw_events.length) || !(raw_listeners == null ? void 0 : raw_listeners.length))
return;
const optionsClone = isObject(raw_options) ? { ...raw_options } : raw_options;
cleanups.push(
...raw_targets.flatMap(
(el) => raw_events.flatMap(
(event) => raw_listeners.map((listener) => register(el, event, listener, optionsClone))
)
)
);
},
{ flush: "post" }
);
const stop = () => {
stopWatch();
cleanup();
};
tryOnScopeDispose(cleanup);
return stop;
}
const DefaultMagicKeysAliasMap = {
ctrl: "control",
command: "meta",
cmd: "meta",
option: "alt",
up: "arrowup",
down: "arrowdown",
left: "arrowleft",
right: "arrowright"
};
function useMagicKeys(options = {}) {
const {
reactive: useReactive = false,
target = defaultWindow,
aliasMap = DefaultMagicKeysAliasMap,
passive = true,
onEventFired = noop
} = options;
const current = reactive(/* @__PURE__ */ new Set());
const obj = {
toJSON() {
return {};
},
current
};
const refs = useReactive ? reactive(obj) : obj;
const metaDeps = /* @__PURE__ */ new Set();
const usedKeys = /* @__PURE__ */ new Set();
function setRefs(key, value) {
if (key in refs) {
if (useReactive)
refs[key] = value;
else
refs[key].value = value;
}
}
function reset() {
current.clear();
for (const key of usedKeys)
setRefs(key, false);
}
function updateRefs(e, value) {
var _a, _b;
const key = (_a = e.key) == null ? void 0 : _a.toLowerCase();
const code = (_b = e.code) == null ? void 0 : _b.toLowerCase();
const values = [code, key].filter(Boolean);
if (key) {
if (value)
current.add(key);
else
current.delete(key);
}
for (const key2 of values) {
usedKeys.add(key2);
setRefs(key2, value);
}
if (key === "meta" && !value) {
metaDeps.forEach((key2) => {
current.delete(key2);
setRefs(key2, false);
});
metaDeps.clear();
} else if (typeof e.getModifierState === "function" && e.getModifierState("Meta") && value) {
[...current, ...values].forEach((key2) => metaDeps.add(key2));
}
}
useEventListener(target, "keydown", (e) => {
updateRefs(e, true);
return onEventFired(e);
}, { passive });
useEventListener(target, "keyup", (e) => {
updateRefs(e, false);
return onEventFired(e);
}, { passive });
useEventListener("blur", reset, { passive });
useEventListener("focus", reset, { passive });
const proxy = new Proxy(
refs,
{
get(target2, prop, rec) {
if (typeof prop !== "string")
return Reflect.get(target2, prop, rec);
prop = prop.toLowerCase();
if (prop in aliasMap)
prop = aliasMap[prop];
if (!(prop in refs)) {
if (/[+_-]/.test(prop)) {
const keys = prop.split(/[+_-]/g).map((i) => i.trim());
refs[prop] = computed(() => keys.map((key) => toValue(proxy[key])).every(Boolean));
} else {
refs[prop] = shallowRef(false);
}
}
const r = Reflect.get(target2, prop, rec);
return useReactive ? toValue(r) : r;
}
}
);
return proxy;
}
const onClickOutside = {
beforeMount(el, binding) {
el.clickOutsideEvent = (event) => {
if (!(el === event.target || el.contains(event.target))) {
if (typeof binding.value === "function") {
binding.value(event);
}
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted(el) {
if (el.clickOutsideEvent) {
document.removeEventListener("click", el.clickOutsideEvent);
}
}
};
const _hoisted_1$d = { class: "vfg-select" };
const _hoisted_2$6 = ["id"];
const _hoisted_3$3 = { key: 0 };
const _hoisted_4 = { class: "vfg-fi vfg-fi-right" };
const _hoisted_5 = ["id"];
const _hoisted_6 = ["id"];
const _hoisted_7 = ["id", "onClick"];
const _hoisted_8 = {
key: 0,
class: "vfg-fi vfg-fi-right"
};
const _sfc_main$d = /* @__PURE__ */ defineComponent({
__name: "FieldSelect",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emits = __emit;
const { controlLeft, metaLeft } = useMagicKeys();
const isOpened = ref(false);
const { field, model } = toRefs(props);
const { hint, isVisible } = useFieldAttributes(model.value, field.value);
const { errors, validate } = useFieldValidate(model.value, field.value);
const selectedNames = computed(() => {
if (!currentModelValue.value) return [];
const findOptionName = (value) => {
var _a;
return ((_a = field.value.options.find((o) => o.value === value)) == null ? void 0 : _a.name) ?? false;
};
if (Array.isArray(currentModelValue.value) && field.value.multiple) {
return currentModelValue.value.map(findOptionName).filter((o) => o !== false);
} else {
const optionName = findOptionName(currentModelValue.value);
return optionName ? [optionName] : [];
}
});
const hasValue = computed(() => field.value.multiple ? currentModelValue.value.length : currentModelValue.value);
const isPressingModifierKey = computed(() => metaLeft.value || controlLeft.value);
const { currentModelValue } = useFormModel(model.value, field.value);
const onClickInput = () => isOpened.value = !isOpened.value;
const resetSelection = () => emits("onInput", field.value.multiple ? [] : "");
function isSelected(option) {
var _a;
if (!field.value.multiple) return currentModelValue.value === option.value;
return ((_a = currentModelValue.value) == null ? void 0 : _a.includes(option.value)) ?? false;
}
function handleClickOutside(event) {
if (!field.value.multiple && !isPressingModifierKey.value || !event.target.id.startsWith(props.id + "vfg-select")) {
isOpened.value = false;
}
}
function selectOption(option) {
errors.value = [];
const optionSelected = isSelected(option);
if (!field.value.multiple) {
emits("onInput", optionSelected ? "" : option.value);
} else {
let selectedValues = [...currentModelValue.value];
if (optionSelected) {
selectedValues = selectedValues.filter((o) => o !== option.value);
} else {
selectedValues.push(option.value);
}
emits("onInput", selectedValues);
}
if (!(metaLeft.value || controlLeft.value)) {
isOpened.value = false;
}
validate(currentModelValue.value).then((validationErrors) => {
emits(
"validated",
validationErrors.length === 0,
validationErrors,
field.value
);
});
}
__expose({ hint, isVisible, errors });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1$d, [
withDirectives((openBlock(), createElementBlock("span", {
id: props.id + "vfg-select-label",
class: normalizeClass(["vfg-select-label", { "text-muted": !selectedNames.value.length }]),
onClick: withModifiers(onClickInput, ["prevent"])
}, [
selectedNames.value.length ? (openBlock(), createElementBlock("span", _hoisted_3$3, [
(openBlock(true), createElementBlock(Fragment, null, renderList(selectedNames.value, (selectedName, index) => {
return openBlock(), createElementBlock(Fragment, { key: selectedName }, [
index !== 0 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
createTextVNode(", ")
], 64)) : createCommentVNode("", true),
createTextVNode(toDisplayString(selectedName), 1)
], 64);
}), 128))
])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
createTextVNode(toDisplayString(unref(field).placeholder || "Select an option"), 1)
], 64)),
createElementVNode("span", _hoisted_4, [
hasValue.value ? (openBlock(), createElementBlock("span", {
key: 0,
onClick: withModifiers(resetSelection, ["prevent"])
}, _cache[0] || (_cache[0] = [
createElementVNode("svg", {
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor"
}, [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M6 18 18 6M6 6l12 12"
})
], -1)
]))) : createCommentVNode("", true),
_cache[1] || (_cache[1] = createElementVNode("svg", {
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor"
}, [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "m19.5 8.25-7.5 7.5-7.5-7.5"
})
], -1))
])
], 10, _hoisted_2$6)), [
[unref(onClickOutside), handleClickOutside]
]),
isOpened.value ? (openBlock(), createElementBlock("div", {
key: 0,
id: props.id + "vfg-select-list-container",
class: "vfg-select-list-container"
}, [
createElementVNode("div", {
id: props.id + "vfg-select-list",
class: "vfg-select-list"
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(field).options, (option) => {
return openBlock(), createElementBlock("div", {
id: props.id + "vfg-select-option-" + option.value,
key: option.value,
class: normalizeClass(["vfg-select-option", { "selected": isSelected(option) }]),
onClick: withModifiers(($event) => selectOption(option), ["prevent"])
}, [
createTextVNode(toDisplayString(option.name) + " ", 1),
isSelected(option) ? (openBlock(), createElementBlock("span", _hoisted_8, _cache[2] || (_cache[2] = [
createElementVNode("svg", {
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor"
}, [
createElementVNode("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M6 18 18 6M6 6l12 12"
})
], -1)
]))) : createCommentVNode("", true)
], 10, _hoisted_7);
}), 128))
], 8, _hoisted_6)
], 8, _hoisted_5)) : createCommentVNode("", true)
]);
};
}
});
const _hoisted_1$c = ["id", "name", "value", "required", "disabled"];
const _hoisted_2$5 = {
disabled: "",
value: ""
};
const _hoisted_3$2 = ["value"];
const _sfc_main$c = /* @__PURE__ */ defineComponent({
__name: "FieldSelectNative",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emits = __emit;
const { field, model } = toRefs(props);
const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value);
const { currentModelValue } = useFormModel(model.value, field.value);
const { validate, errors } = useFieldValidate(model.value, field.value);
const onBlur = () => {
validate(currentModelValue.value).then((validationErrors) => {
emits(
"validated",
validationErrors.length === 0,
validationErrors,
field.value
);
});
};
const onFieldValueChanged = (event) => {
errors.value = [];
emits("onInput", event.target.value);
};
__expose({ hint, isVisible, errors });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("select", {
id: _ctx.id,
name: unref(field).name,
value: unref(currentModelValue),
required: unref(isRequired),
disabled: unref(isDisabled),
onChange: onFieldValueChanged,
onBlur
}, [
createElementVNode("option", _hoisted_2$5, toDisplayString(unref(field).placeholder ?? "Select a " + unref(field).name), 1),
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(field).options, (option) => {
return openBlock(), createElementBlock("option", {
key: option.value,
value: option.value
}, toDisplayString(option.name), 9, _hoisted_3$2);
}), 128))
], 40, _hoisted_1$c);
};
}
});
const _hoisted_1$b = ["id", "name", "value", "required", "checked"];
const _hoisted_2$4 = ["for"];
const _sfc_main$b = /* @__PURE__ */ defineComponent({
__name: "FieldRadio",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emits = __emit;
const { field, model } = toRefs(props);
const { isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value);
const { currentModelValue } = useFormModel(model.value, field.value);
const getFieldId = (optionName) => `${field.value.name}_${optionName}`;
const onFieldValueChanged = (event) => {
emits("onInput", event.target.value);
};
__expose({ hint, isVisible });
return (_ctx, _cache) => {
return openBlock(true), createElementBlock(Fragment, null, renderList(unref(field).options, (option) => {
return openBlock(), createElementBlock("div", {
key: option.value,
class: "option-wrap field-radio"
}, [
createElementVNode("input", {
id: getFieldId(option.name),
type: "radio",
name: unref(field).name,
value: option.value,
required: unref(isRequired),
checked: unref(currentModelValue) === option.value,
onChange: onFieldValueChanged
}, null, 40, _hoisted_1$b),
createElementVNode("label", {
for: getFieldId(option.name)
}, toDisplayString(option.name), 9, _hoisted_2$4)
]);
}), 128);
};
}
});
var I = Object.defineProperty;
var S = (n, t, s) => t in n ? I(n, t, { enumerable: true, configurable: true, writable: true, value: s }) : n[t] = s;
var A = (n, t, s) => S(n, typeof t != "symbol" ? t + "" : t, s);
const N = {
"#": { pattern: /[0-9]/ },
"@": { pattern: /[a-zA-Z]/ },
"*": { pattern: /[a-zA-Z0-9]/ }
}, R = (n, t, s) => n.replaceAll(t, "").replace(s, ".").replace("..", ".").replace(/[^.\d]/g, ""), C = (n, t, s) => {
var e;
return new Intl.NumberFormat(((e = s.number) == null ? void 0 : e.locale) ?? "en", {
minimumFractionDigits: n,
maximumFractionDigits: t,
roundingMode: "trunc"
});
}, P = (n, t = true, s) => {
var k2, g, b, d;
const e = ((k2 = s.number) == null ? void 0 : k2.unsigned) !== true && n.startsWith("-") ? "-" : "", r = ((g = s.number) == null ? void 0 : g.fraction) ?? 0;
let a = C(0, r, s);
const u = a.formatToParts(1000.12), p = ((b = u.find((o) => o.type === "group")) == null ? void 0 : b.value) ?? " ", f = ((d = u.find((o) => o.type === "decimal")) == null ? void 0 : d.value) ?? ".", i = R(n, p, f);
if (Number.isNaN(parseFloat(i))) return e;
const h = i.split(".");
if (h[1] != null && h[1].length >= 1) {
const o = h[1].length <= r ? h[1].length : r;
a = C(o, r, s);
}
let l2 = a.format(parseFloat(i));
return t ? r > 0 && i.endsWith(".") && !i.slice(0, -1).includes(".") && (l2 += f) : l2 = R(l2, p, f), e + l2;
};
class F {
constructor(t = {}) {
A(this, "opts", {});
A(this, "memo", /* @__PURE__ */ new Map());
const s = { ...t };
if (s.tokens != null) {
s.tokens = s.tokensReplace ? { ...s.tokens } : { ...N, ...s.tokens };
for (const e of Object.values(s.tokens))
typeof e.pattern == "string" && (e.pattern = new RegExp(e.pattern));
} else
s.tokens = N;
Array.isArray(s.mask) && (s.mask.length > 1 ? s.mask = [...s.mask].sort((e, r) => e.length - r.length) : s.mask = s.mask[0] ?? ""), s.mask === "" && (s.mask = null), this.opts = s;
}
masked(t) {
return this.process(String(t), this.findMask(String(t)));
}
unmasked(t) {
return this.process(String(t), this.findMask(String(t)), false);
}
isEager() {
return this.opts.eager === true;
}
isReversed() {
return this.opts.reversed === true;
}
completed(t) {
const s = this.findMask(String(t));
if (this.opts.mask == null || s == null) return false;
const e = this.process(String(t), s).length;
return typeof this.opts.mask == "string" ? e >= this.opts.mask.length : e >= s.length;
}
findMask(t) {
const s = this.opts.mask;
if (s == null)
return null;
if (typeof s == "string")
return s;
if (typeof s == "function")
return s(t);
const e = this.process(t, s.slice(-1).pop() ?? "", false);
return s.find((r) => this.process(t, r, false).length >= e.length) ?? "";
}
escapeMask(t) {
const s = [], e = [];
return t.split("").forEach((r, a) => {
r === "!" && t[a - 1] !== "!" ? e.push(a - e.length) : s.push(r);
}), { mask: s.join(""), escaped: e };
}
process(t, s, e = true) {
if (this.opts.number != null) return P(t, e, this.opts);
if (s == null) return t;
const r = `v=${t},mr=${s},m=${e ? 1 : 0}`;
if (this.memo.has(r)) return this.memo.get(r);
const { mask: a, escaped: u } = this.escapeMask(s), p = [], f = this.opts.tokens != null ? this.opts.tokens : {}, i = this.isReversed() ? -1 : 1, h = this.isReversed() ? "unshift" : "push", l2 = this.isReversed() ? 0 : a.length - 1, k2 = this.isReversed() ? () => o > -1 && c2 > -1 : () => o < a.length && c2 < t.length, g = (v) => !this.isReversed() && v <= l2 || this.isReversed() && v >= l2;
let b, d = -1, o = this.isReversed() ? a.length - 1 : 0, c2 = this.isReversed() ? t.length - 1 : 0, E = false;
for (; k2(); ) {
const v = a.charAt(o), m = f[v], y = (m == null ? void 0 : m.transform) != null ? m.transform(t.charAt(c2)) : t.charAt(c2);
if (!u.includes(o) && m != null ? (y.match(m.pattern) != null ? (p[h](y), m.repeated ? (d === -1 ? d = o : o === l2 && o !== d && (o = d - i), l2 === d && (o -= i)) : m.multiple && (E = true, o -= i), o += i) : m.multiple ? E && (o += i, c2 -= i, E = false) : y === b ? b = void 0 : m.optional && (o += i, c2 -= i), c2 += i) : (e && !this.isEager() && p[h](v), y === v && !this.isEager() ? c2 += i : b = v, this.isEager() || (o += i)), this.isEager())
for (; g(o) && (f[a.charAt(o)] == null || u.includes(o)); ) {
if (e) {
if (p[h](a.charAt(o)), t.charAt(c2) === a.charAt(o)) {
o += i, c2 += i;
continue;
}
} else a.charAt(o) === t.charAt(c2) && (c2 += i);
o += i;
}
}
return this.memo.set(r, p.join("")), this.memo.get(r);
}
}
const w = (n) => JSON.parse(n.replaceAll("'", '"')), T = (n, t = {}) => {
const s = { ...t };
n.dataset.maska != null && n.dataset.maska !== "" && (s.mask = x(n.dataset.maska)), n.dataset.maskaEager != null && (s.eager = M(n.dataset.maskaEager)), n.dataset.maskaReversed != null && (s.reversed = M(n.dataset.maskaReversed)), n.dataset.maskaTokensReplace != null && (s.tokensReplace = M(n.dataset.maskaTokensReplace)), n.dataset.maskaTokens != null && (s.tokens = W(n.dataset.maskaTokens));
const e = {};
return n.dataset.maskaNumberLocale != null && (e.locale = n.dataset.maskaNumberLocale), n.dataset.maskaNumberFraction != null && (e.fraction = parseInt(n.dataset.maskaNumberFraction)), n.dataset.maskaNumberUnsigned != null && (e.unsigned = M(n.dataset.maskaNumberUnsigned)), (n.dataset.maskaNumber != null || Object.values(e).length > 0) && (s.number = e), s;
}, M = (n) => n !== "" ? !!JSON.parse(n) : true, x = (n) => n.startsWith("[") && n.endsWith("]") ? w(n) : n, W = (n) => {
if (n.startsWith("{") && n.endsWith("}"))
return w(n);
const t = {};
return n.split("|").forEach((s) => {
const e = s.split(":");
t[e[0]] = {
pattern: new RegExp(e[1]),
optional: e[2] === "optional",
multiple: e[2] === "multiple",
repeated: e[2] === "repeated"
};
}), t;
};
class V {
constructor(t, s = {}) {
A(this, "items", /* @__PURE__ */ new Map());
A(this, "eventAbortController");
A(this, "onInput", (t2) => {
if (t2 instanceof CustomEvent && t2.type === "input" && !t2.isTrusted && !t2.bubbles)
return;
const s2 = t2.target, e = this.items.get(s2);
if (e === void 0) return;
const r = "inputType" in t2 && t2.inputType.startsWith("delete"), a = e.isEager(), u = r && a && e.unmasked(s2.value) === "" ? "" : s2.value;
this.fixCursor(s2, r, () => this.setValue(s2, u));
});
this.options = s, this.eventAbortController = new AbortController(), this.init(this.getInputs(t));
}
update(t = {}) {
this.options = { ...t }, this.init(Array.from(this.items.keys()));
}
updateValue(t) {
var s;
t.value !== "" && t.value !== ((s = this.processInput(t)) == null ? void 0 : s.masked) && this.setValue(t, t.value);
}
destroy() {
this.eventAbortController.abort(), this.items.clear();
}
init(t) {
const s = this.getOptions(this.options);
for (const e of t) {
if (!this.items.has(e)) {
const { signal: a } = this.eventAbortController;
e.addEventListener("input", this.onInput, { capture: true, signal: a });
}
const r = new F(T(e, s));
this.items.set(e, r), queueMicrotask(() => this.updateValue(e)), e.selectionStart === null && r.isEager() && console.warn("Maska: input of `%s` type is not supported", e.type);
}
}
getInputs(t) {
return typeof t == "string" ? Array.from(document.querySelectorAll(t)) : "length" in t ? Array.from(t) : [t];
}
getOptions(t) {
const { onMaska: s, preProcess: e, postProcess: r, ...a } = t;
return a;
}
fixCursor(t, s, e) {
var k2, g;
const r = t.selectionStart, a = t.value;
if (e(), r === null || r === a.length && !s) return;
const u = t.value, p = a.slice(0, r), f = u.slice(0, r), i = (k2 = this.processInput(t, p)) == null ? void 0 : k2.unmasked, h = (g = this.processInput(t, f)) == null ? void 0 : g.unmasked;
if (i === void 0 || h === void 0) return;
let l2 = r;
p !== f && (l2 += s ? u.length - a.length : i.length - h.length), t.setSelectionRange(l2, l2);
}
setValue(t, s) {
const e = this.processInput(t, s);
e !== void 0 && (t.value = e.masked, this.options.onMaska != null && (Array.isArray(this.options.onMaska) ? this.options.onMaska.forEach((r) => r(e)) : this.options.onMaska(e)), t.dispatchEvent(new CustomEvent("maska", { detail: e })), t.dispatchEvent(new CustomEvent("input", { detail: e.masked })));
}
processInput(t, s) {
const e = this.items.get(t);
if (e === void 0) return;
let r = s ?? t.value;
this.options.preProcess != null && (r = this.options.preProcess(r));
let a = e.masked(r);
return this.options.postProcess != null && (a = this.options.postProcess(a)), {
masked: a,
unmasked: e.unmasked(r),
completed: e.completed(r)
};
}
}
const l = /* @__PURE__ */ new WeakMap(), c = (e, s) => {
if (e.arg == null || e.instance == null) return;
const a = "setup" in e.instance.$.type;
e.arg in e.instance ? e.instance[e.arg] = s : a && console.warn("Maska: please expose `%s` using defineExpose", e.arg);
}, k = (e, s) => {
var u;
const a = e instanceof HTMLInputElement ? e : e.querySelector("input");
if (a == null || (a == null ? void 0 : a.type) === "file") return;
let t = {};
if (s.value != null && (t = typeof s.value == "string" ? { mask: s.value } : { ...s.value }), s.arg != null) {
const o = (r) => {
const p = s.modifiers.unmasked ? r.unmasked : s.modifiers.completed ? r.completed : r.masked;
c(s, p);
};
t.onMaska = t.onMaska == null ? o : Array.isArray(t.onMaska) ? [...t.onMaska, o] : [t.onMaska, o];
}
l.has(a) ? (u = l.get(a)) == null || u.update(t) : l.set(a, new V(a, t));
};
const _hoisted_1$a = { class: "field-color-wrapper" };
const _hoisted_2$3 = ["value"];
const _hoisted_3$1 = ["id", "name", "value", "required"];
const _sfc_main$a = /* @__PURE__ */ defineComponent({
__name: "FieldColor",
props: {
id: {},
formGenerator: {},
formOptions: {},
field: {},
model: {}
},
emits: useFieldEmits(),
setup(__props, { expose: __expose, emit: __emit }) {
const emits = __emit;
const props = __props;
const maskOptions = {
mask: "!#HHHHHH",
tokens: {
H: {
pattern: /[A-Fa-f0-9]/
}
}
};
const { field, model } = toRefs(props);