UNPKG

@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
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);