npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@benjaminor-dev/quasar-app-extension-form-builder

v0.7.8

Published

A Form Builder for Quasar Framework

Downloads

942

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:

  1. Definir tipo de payload.
  2. Construir config con useFormBuilder().defineForm(...).
  3. Renderizar FormBuilder con esa config.
  4. 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-builder

Remover extensión:

quasar ext remove @benjaminor-dev/form-builder

Qué 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:

  1. Boot Pinia del host.
  2. Boot store de Form Builder.
  3. Resto de boots.

Comandos scaffold en host

Comando principal:

npm run form-builder:make -- login

Alias corto:

npm run fb:make -- login

Resultado 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 --force

Genera:

  • 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>) => boolean

Recomendaciones:

  • 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:

  1. defineForm crea (si no existe) la estructura del formulario en el store.
  2. Cada input escribe en forms[formName][campo].
  3. Errores de campo viven en formsErrors[formName][campo].
  4. 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:

  1. Override local del host en scripts/form-builder-make.stub
  2. Stub default de extensión en src/configs/host-files/scripts/form-builder-make.stub
  3. 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 build

Release 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-builder

4) 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