@benjaminor-dev/quasar-app-extension-form-builder
v0.7.8
Published
A Form Builder for Quasar Framework
Downloads
942
Maintainers
Readme
Form Builder para Quasar
Librería para construir formularios tipados en Vue 3 + Quasar con estado centralizado en Pinia, catálogo de campos estable y APIs públicas para host apps.
Este repositorio es una Quasar App Extension library: no es una app final ejecutable.
Tabla de contenidos
- Resumen
- Qué es esta librería y alcance
- Features principales
- Requisitos y compatibilidad
- Instalación en app Quasar
- Qué agrega la extensión al host
- Comandos scaffold en host
- Uso rápido
- Guía de configuración de formularios
- API pública
- Catálogo de campos soportados
- Directivas registradas
- Store y mapa conceptual de estado/errores
- SlotField y uso de refs
- Customizar plantilla scaffold
- Entrypoints públicos
- Desarrollo local
- Release y dist-tags
- Troubleshooting
- Documentos relacionados
Resumen
Flujo base recomendado:
- Definir tipo de payload.
- Construir config con useFormBuilder().defineForm(...).
- Renderizar FormBuilder con esa config.
- Manejar submit/reset por eventos.
La extensión instala boots, directivas y scripts en el proyecto host para que el uso sea inmediato.
Qué es esta librería y alcance
Form Builder resuelve el armado de formularios declarativos sobre Quasar con:
- Modelo de estado único por formName.
- Contratos tipados para campos y payload.
- Render dinámico de campos desde configuración.
- Integración con reglas reutilizables de validación.
Arquitectura principal:
- src/components: render dinámico del formulario (FormBuilder).
- src/components/forms: inputs publicados (InputText, InputDate, InputDateRange, etc.).
- src/composables: defineForm, useInput, inputRules y utilidades.
- src/stores: estado Pinia de valores, defaults y errores.
- src/directives: directivas runtime con prefijo form-builder-.
- src/boot: integración con Quasar boot (store + directivas).
- src/entry: superficie pública exportada por el paquete.
- src/types: contratos TypeScript.
Features principales
- Configuración tipada de formularios con defineForm.
- Campos especializados (email, url, date, date-range, numeric-code, etc.).
- Reglas de validación centralizadas vía inputRules.
- Soporte de requiredOn condicional (inyecta required automáticamente).
- Control de estado/errores vía store reactivo de Pinia.
- Integración de directivas para restricciones de captura.
- Scaffold de formularios para host con comandos npm.
Requisitos y compatibilidad
| Item | Valor | | --- | --- | | Node | >=20.0.0 | | Quasar | ^2.6.0 | | Vue | ^3.4.18 | | Pinia | ^2.0.11 || ^3.0.0 | | moment | ^2.30.1 | | Formato de salida | ES modules |
Notas importantes:
- Validación principal del repo: npm run build.
- No existe script dedicado de pruebas unitarias/integración actualmente.
- El boot de Pinia del host debe correr antes del boot de store de esta extensión.
Instalación en app Quasar
Agregar extensión:
quasar ext add @benjaminor-dev/form-builderRemover extensión:
quasar ext remove @benjaminor-dev/form-builderQué agrega la extensión al host
Durante la instalación/invocación, la extensión registra en el host:
- Boot store: ~@benjaminor-dev/quasar-app-extension-form-builder/boot/store
- Boot directivas: ~@benjaminor-dev/quasar-app-extension-form-builder/boot/directives
- CSS: ~@benjaminor-dev/quasar-app-extension-form-builder/main.css
- Scripts npm scaffold: form-builder:make y fb:make
Orden recomendado de boots:
- Boot Pinia del host.
- Boot store de Form Builder.
- Resto de boots.
Comandos scaffold en host
Comando principal:
npm run form-builder:make -- loginAlias corto:
npm run fb:make -- loginResultado esperado:
- src/components/FormBuilder/LoginForm.vue
Reglas de naming:
- El nombre final del archivo se normaliza a PascalCase.
- Siempre termina en Form.vue.
- Si envías RegisterForm, no duplica FormForm.
- Puedes forzar overwrite con --force.
Ejemplo:
npm run fb:make -- users/register --forceGenera:
- src/components/FormBuilder/users/RegisterForm.vue
Uso rápido
<script setup lang="ts">
import FormBuilder, { useFormBuilder } from "@benjaminor-dev/quasar-app-extension-form-builder";
import moment from "moment";
type RegisterForm = {
fullName: string | null;
email: string | null;
birthDate: string | null;
vacationDate: string | null;
acceptTerms: boolean;
};
const { defineForm, inputRules } = useFormBuilder();
const config = defineForm<RegisterForm>({
formName: "registerForm",
defaultValues: {
fullName: null,
email: null,
birthDate: null,
vacationDate: null,
acceptTerms: false,
},
fieldDesign: "filled",
fields: [
{
type: "InputText",
model: "fullName",
label: "Nombre",
requiredOn: () => true,
rules: [inputRules.onlyLettersAndSpaces()],
},
{
type: "InputEmail",
model: "email",
requiredOn: (form) => form?.fullName !== "Test User", // El email solo es requerido si el nombre es distinto a "Test User"
},
{
type: "InputDate",
model: "birthDate",
label: "Fecha de nacimiento",
props: {
maxDate: () => moment().subtract(18, 'years').format("YYYY-MM-DD"), // No permite seleccionar fechas que indiquen menos de 18 años de edad
},
},
{
type: "InputDateRange",
model: "vacationDate",
label: "Fechas de vacaciones",
props: {
minDate: () => moment().format("YYYY-MM-DD"), // no permite seleccionar fechas pasadas
maxRange: () => 30, // permite seleccionar un rango de hasta 30 días
},
},
{
type: "ToggleButton",
model: "acceptTerms",
label: "Aceptar términos",
requiredOn: () => true,
},
],
});
function onSubmit(payload: RegisterForm) {
console.log("submit", payload);
}
</script>
<template>
<FormBuilder :config="config" @submit="onSubmit" />
</template>Guía de configuración de formularios
Callbacks condicionales que reciben el formulario actual:
- requiredOn
- unmountOn
- hideOn
- disabledOn
- readonlyOn
Firma tipada:
(form?: FormBuilderSubmitPayload<T>) => booleanRecomendaciones:
- Usa form? o form?.campo porque el tipo acepta undefined.
- Si no necesitas el form, puedes usar () => true/false.
- requiredOn() ya inyecta la validación required internamente (vía useInput).
- Evita duplicar required en rules salvo que quieras una regla extra independiente.
API pública
useFormBuilder() retorna:
- defineForm(config)
- inputRules
- store
- utils
defineForm(config)
Construye y normaliza la configuración del formulario.
| Propiedad | Tipo | Descripción | | --- | --- | --- | | formName | string | Identificador único del formulario | | fields | FormField[] | Listado de campos | | defaultValues | Partial | Valores iniciales | | fieldDesign | standard | outlined | filled | standout | borderless | Diseño visual global. Por defecto outlined. No aplica a SlotField ni ToggleButton. | | labelPosition | inner | stacked | top | Posición del label global. Por defecto inner. |
inputRules
Reglas disponibles (ejemplos):
- required, email, url, password
- onlyLetters, onlyNumbers, onlyLettersAndSpaces
- date, dateBeforeNow, dateLEnow, dateAfterNow, dateGEnow
- minLength, maxLength, length
- minNumber, maxNumber, minMaxNumber
- file, image
store
Acceso al estado central de formularios (Pinia) sin export directo de store en entrypoint raíz.
Catálogo de campos soportados
Fuente de verdad estable: src/types/FieldTypes.type.ts.
Los componentes importables de la superficie pública deben mantenerse alineados con src/entry/inputs.js.
| Campo | Soporta fieldDesign / labelPosition | Notas | | --- | --- | --- | | InputText | Sí | | | InputEmail | Sí | | | InputNumber | Sí | | | InputNumericCode | Sí | Para códigos de solo dígitos que pueden iniciar con cero | | InputPassword | Sí | | | InputDecimal | Sí | | | InputCurrency | Sí | | | InputDate | Sí | Texto DD/MM/AAAA + picker modal | | InputDateRange | Sí | Texto DD/MM/AAAA - DD/MM/AAAA + picker range modal | | InputTel | Sí | | | InputUrl | Sí | | | InputSearch | Sí | | | ToggleButton | No | Campo booleano | | SlotField | No | Contenedor custom para template propio |
Ejemplo: InputDate con límites por función
<script setup lang="ts">
import moment from "moment";
const birthDateField = {
type: "InputDate",
model: "birthDate",
label: "Fecha de nacimiento",
props: {
minDate: () => "1900-01-01",
maxDate: () => moment().format("YYYY-MM-DD"),
},
};
</script>InputDate usa minDate y maxDate como funciones () => string que devuelven YYYY-MM-DD. El literal now ya no forma parte del contrato; usa () => moment().format("YYYY-MM-DD").
Ejemplo: InputDateRange
<script setup lang="ts">
import moment from "moment";
const vacationDateField = {
type: "InputDateRange",
model: "vacationDate",
label: "Vacaciones",
props: {
minDate: () => moment().format("YYYY-MM-DD"),
maxDate: () => moment().add(1, "year").format("YYYY-MM-DD"),
maxRange: () => 45,
},
};
</script>modelValue en InputDateRange usa formato YYYY-MM-DD/YYYY-MM-DD (inicio/fin).
Directivas registradas
Convención runtime: form-builder-{directiveName}. En templates Vue se usan como v-form-builder-*.
- v-form-builder-max-length
- v-form-builder-only-numbers
- v-form-builder-only-decimal-number
- v-form-builder-format-number
- v-form-builder-max-number
- v-form-builder-currency-symbol
Store y mapa conceptual de estado/errores
Acceso: useFormBuilder().store
Nodos principales por formName:
- forms: valores actuales
- formsDefaults: snapshot de defaults
- formsErrors: errores por campo
Métodos principales:
- checkForm(formName)
- makeForm(formName, defaultValues)
- getForm(formName)
- getFormOriginal(formName)
- setFormValues(formName, values)
- setFormErrors(formName, errors)
- getFormErrors(formName)
- clearFormErrors(formName)
- resetForm(formName)
Flujo conceptual:
- defineForm crea (si no existe) la estructura del formulario en el store.
- Cada input escribe en forms[formName][campo].
- Errores de campo viven en formsErrors[formName][campo].
- resetForm restaura formsDefaults y limpia errores.
SlotField y uso de refs
En slots de SlotField, las propiedades model y error son refs y deben usarse con .value.
<script setup lang="ts">
import FormBuilder, { useFormBuilder } from "@benjaminor-dev/quasar-app-extension-form-builder";
type DemoForm = {
customText: string | null;
};
const { defineForm } = useFormBuilder();
const config = defineForm<DemoForm>({
formName: "demoForm",
defaultValues: {
customText: null,
},
fields: [
{
type: "SlotField",
model: "customText",
props: {
slotName: "custom-text",
},
},
],
});
</script>
<template>
<FormBuilder :config="config">
<template #slot-field-custom-text="{ model, error, label, hint, required, readonly }">
<q-input
v-model="model.value"
:error="!!error.value"
:error-message="error.value || ''"
:label="label"
:hint="hint"
:readonly="readonly"
>
<template v-if="required" #append>
<span class="text-negative">*</span>
</template>
</q-input>
</template>
</FormBuilder>
</template>Customizar plantilla scaffold
Prioridad de plantilla usada por el comando fb:make:
- Override local del host en scripts/form-builder-make.stub
- Stub default de extensión en src/configs/host-files/scripts/form-builder-make.stub
- Fallback interno embebido
Entrypoints públicos
| Entrypoint | Propósito | | --- | --- | | @benjaminor-dev/quasar-app-extension-form-builder | FormBuilder, useFormBuilder, boots e inputs reexportados | | @benjaminor-dev/quasar-app-extension-form-builder/boot/store | Boot de integración Pinia para formularios | | @benjaminor-dev/quasar-app-extension-form-builder/boot/directives | Boot de registro de directivas | | @benjaminor-dev/quasar-app-extension-form-builder/inputs | Componentes de input publicados | | @benjaminor-dev/quasar-app-extension-form-builder/scaffold | Utilidades de scaffold para host | | @benjaminor-dev/quasar-app-extension-form-builder/types | Tipos públicos para configs y campos | | @benjaminor-dev/quasar-app-extension-form-builder/main.css | Estilos de la librería |
Desarrollo local
| Script | Propósito | | --- | --- | | npm run clean | Limpia dist y tsbuildinfo | | npm run build:js | Build de librería con Vite | | npm run build:types | Emite declaraciones d.ts | | npm run build | Flujo completo de validación/build |
Validación principal del repositorio:
npm run buildRelease y dist-tags
| Script | Propósito | | --- | --- | | release:patch | bump patch + tag + push | | release:minor | bump minor + tag + push | | release:major | bump major + tag + push | | release:prepatch:beta | prepatch beta + tag + push | | release:preminor:beta | preminor beta + tag + push | | release:prerelease:beta | prerelease beta + tag + push | | release:prerelease:rc | prerelease rc + tag + push | | mark:beta | ajusta dist-tag beta | | mark:latest | ajusta dist-tag latest |
Troubleshooting
1) El callback condicional marca error de tipos
Si declaras requiredOn: (form: MyForm) => ..., TypeScript puede fallar por contravarianza. La firma real acepta form opcional.
Usa:
requiredOn: (form) => form?.phone === "123"o:
requiredOn: (form?: FormBuilderSubmitPayload<MyForm>) => form?.phone === "123"2) Repetí inputRules.required(...) y requiredOn()
requiredOn ya agrega la validación required internamente. Mantén requiredOn y deja rules para validaciones adicionales.
3) El comando npm no aparece en host
Ejecuta en el host:
npx quasar ext invoke @benjaminor-dev/form-builder4) Pinia no inicializada
Asegura que el boot de Pinia corra antes del boot store de la extensión.
5) InputDate/InputDateRange no respetan límites
Verifica formato de funciones:
- minDate y maxDate: () => "YYYY-MM-DD"
- maxRange: () => number (días)
Documentos relacionados
- PorHacer.md
- src/configs/CheckCompatibility.js
- src/types/FieldTypes.type.ts
- src/entry/inputs.js
