Dialog Loader para Quasar

🇺🇸 English | 🇧🇷 Versão em português

npm version license Vue Quasar TypeScript Node

Extensión Quasar para diálogo modal de carga global en apps Vue 3 + Quasar, con API imperativa basada en composable.

Tabla de contenidos

En 30 segundos

Flujo base recomendado:

  1. Instalar la extensión en tu app Quasar (quasar ext add).
  2. Asegurar que el boot de Pinia del host corre antes que el boot de esta extensión.
  3. Importar useDialogLoader() donde ejecutes peticiones async.
  4. Envolver la tarea con await loader.run({ task: () => api.save() }) — o { label, task } si quieres override puntual.

La extensión registra boots y CSS en el proyecto host automáticamente tras quasar ext add.

Qué es y qué incluye

Diálogo modal global controlado por useDialogLoader() — no es un componente embebible.

Requisitos y compatibilidad

Item Valor
Node >= 20.0.0
Quasar ^2.6.0
Vue ^3.4.18
Pinia ^2.0.11 | ^3.0.0
Formato del paquete ES modules
CLI recomendado @quasar/app-vite ^2.x o ^3.x

El boot de Pinia del host debe ejecutarse antes del boot de esta extensión.

Instalación en app Quasar

quasar ext add @benjaminor-dev/dialog-loader

Remover:

quasar ext remove @benjaminor-dev/dialog-loader

Tras quasar ext add, la extensión registra boots y CSS en tu app automáticamente. No necesitas pasos adicionales en una instalación normal desde npm.

Qué agrega la extensión al host

Recursos que instala o registra la extensión:

Recurso Ruta npm
Boot del diálogo ~@benjaminor-dev/quasar-app-extension-dialog-loader/boot/dialog
Estilos ~@benjaminor-dev/quasar-app-extension-dialog-loader/main.css
Defaults boot (host) src/boot/bor/bor-dialog-loader-defaults.ts — creado en install; Skip/Overwrite si ya existe

Install (quasar ext add / invoke): crea src/boot/bor/bor-dialog-loader-defaults.ts (o .js) y lo registra en quasar.configboot: [] automáticamente. No hace falta editarlo a mano en una instalación normal. El boot de paquete (boot/dialog) y main.css los inyecta la extensión en cada dev/build (no aparecen como entradas cortas en quasar.config).

Remove (quasar ext remove): elimina el defaults boot y su entrada en quasar.config.

Orden de boots mínimo:

1. Pinia (host)
2. Dialog Loader — boot/dialog
3. bor/bor-dialog-loader-defaults (host)

En quasar.config solo verás bor/bor-dialog-loader-defaults; el boot boot/dialog lo añade el runner al compilar.

El boot monta el diálogo en el documento y conecta la store de Pinia del host. No necesitas importar DialogLoader en App.vue.

Defaults globales (boot)

Evita repetir variant, spinner, gap, font, delays y zIndex en cada llamada. Prioridad del label: run({ label }) / show() → boot label → i18n (locale / $q.lang / en).

Tras quasar ext add, revisa src/boot/bor/bor-dialog-loader-defaults.ts — plantilla con todas las opciones boot (// recomendado: …) e instrucciones de idioma en el comentario del archivo.

// src/boot/bor/bor-dialog-loader-defaults.ts
import { configureDialogLoaderDefaults } from "@benjaminor-dev/quasar-app-extension-dialog-loader";

configureDialogLoaderDefaults({
  // locale: "es", // descomenta para fijar un idioma concreto en Dialog Loader
  variant: "glass",
  spinner: "ios",
  openDelayMs: 120,
  hideDelayMs: 450,
  minVisibleMs: 600,
  backdropBlur: "blur(10px)",
  zIndex: 6000,
  gap: "2rem",
  font: {
    family: "Arial, sans-serif",
    size: "1.35rem",
  },
  spinnerColor: {
    light: "primary",
    dark: "white",
  },
  spinnerTrackColor: {
    light: "grey-4",
    dark: "grey-7",
  },
});

Idioma (i18n)

Built-in en, es y pt. Por defecto el label sigue el idioma de Quasar ($q.lang.isoName); si Quasar no define idioma, cae en en ("Loading...").

Resolución de locale: boot locale → prefijo de $q.lang.isoName (es*es, pt*pt) → en.

Resolución del label: run({ label }) / show(label) → boot label → boot messages.loadingLabel → catálogo built-in → en.

Claves i18n built-in: loadingLabel (texto por defecto del loader), operationCancelled (DialogLoaderRunCancelledError cuando cancel() aborta la tarea).

Opción Cuándo usarla
framework.lang en quasar.config.ts Recomendado — alinea Dialog Loader con el resto de Quasar
locale: 'es' en boot Fijar un idioma concreto (en | es | pt); ignora cambios de $q.lang
messages: { loadingLabel: '…' } Solo cambiar un texto sin tocar locale
label: '…' en boot Label fijo siempre (ignora i18n)

Ejemplo Quasar config (español en toda la app):

// quasar.config.ts
framework: {
  lang: "es",
},

configureDialogLoader es alias de configureDialogLoaderDefaults.

Opción boot Default built-in Descripción
locale (sigue $q.lang) Fija en | es | pt para textos de la extensión
messages (por locale) Override parcial (loadingLabel, operationCancelled)
label "Loading..." (i18n) Texto fijo cuando no pasas label en show() / run()
variant "glass" Presentación del diálogo
spinner "ios" Indicador de carga (ver spinners)
openDelayMs 120 Retraso antes de mostrar (evita flashes en peticiones muy rápidas)
hideDelayMs 450 Retraso extra al cerrar (alinea con animación de salida)
minVisibleMs 600 Tiempo mínimo visible aunque la operación termine antes
backdropBlur "blur(10px)" Valor CSS backdrop-filter del overlay
zIndex 6000 Debajo de Quasar Notify (~9500) para que toasts sigan visibles
gap "2rem" Espacio label↔spinner
font.family "Arial, sans-serif" CSS font-family del label
font.size "1.35rem" CSS font-size del label
spinnerColor (por variant) Color Quasar del spinner — plano o { light, dark }
spinnerTrackColor (por variant) Color Quasar del track en spinners circulares — plano o { light, dark }

Si omites spinnerColor / spinnerTrackColor, el fallback depende de variant: en primary el spinner es blanco; en glass y minimal usa primary y grey-4. Configura { light, dark } en el boot para adaptar el spinner al tema del host ($q.dark).

Uso rápido

<script setup lang="ts">
import { useDialogLoader } from "@benjaminor-dev/quasar-app-extension-dialog-loader";

const loader = useDialogLoader();

async function onSave() {
  try {
    await loader.run({ task: () => api.save(formData) });
    // notify.success('Guardado');
  } catch (error) {
    // notify.error('Error al guardar');
  }
}
</script>

<template>
  <q-btn label="Guardar" @click="onSave" />
</template>

Sin label explícito — usa i18n según $q.lang (p. ej. "Cargando..." con lang: 'es'):

await loader.run(() => fetchUsers());
// equivalente:
await loader.run({ task: () => fetchUsers() });

Override puntual del texto:

await loader.run({ label: "Guardando...", task: () => api.save(formData) });

Controles opcionales dentro de task:

await loader.run({
  task: async ({ pause, resume, cancel, signal }) => {
    await api.prepare();
    pause(); // oculta overlay; la tarea continúa
    await api.finishInBackground();
    resume(); // vuelve a mostrar el loader
    await fetch("/api/finalize", { signal }); // cancel() aborta este fetch
  },
});
Control Qué hace
pause() Oculta el loader; no cancela la tarea
resume() Reabre el loader tras un pause()
cancel() Aborta signal, cierra el loader y rechaza run() con DialogLoaderRunCancelledError
signal Pásalo a fetch / axios para cancelación cooperativa

pause(), resume() y cancel() son idempotentes y solo afectan a esa operación de run().

import { DialogLoaderRunCancelledError } from "@benjaminor-dev/quasar-app-extension-dialog-loader";

try {
  await loader.run({ task: ({ signal }) => fetch(url, { signal }) });
} catch (error) {
  if (error instanceof DialogLoaderRunCancelledError) return;
  throw error;
}

Control manual (menos habitual):

loader.show("Procesando...");
try {
  await longTask();
} finally {
  loader.hide();
}

TypeScript en tu IDE

No memorices la API: deja que el autocompletado te guíe.

Si algo no aparece en autocompletado, suele ser una prop inexistente o un import incorrecto — revisa el entrypoint principal antes de buscar en el README.

API pública

Punto de entrada: useDialogLoader().

import { useDialogLoader } from "@benjaminor-dev/quasar-app-extension-dialog-loader";

const loader = useDialogLoader();

Métodos

Método Descripción
run(task) Ejecuta la tarea con loader; label del boot. Cierra en finally.
run({ task }) Igual que arriba (forma objeto).
run({ label, task }) Igual con label puntual para esa operación.
show(label?) Incrementa el contador interno y programa apertura del diálogo.
hide() Decrementa el contador; cierra la UI solo cuando no quedan operaciones pendientes.

Estado reactivo (solo lectura)

Ref Descripción
visible Si el diálogo está visible.
label Texto mostrado en el diálogo.

Cuándo usar run vs show / hide

Enfoque Cuándo
run() Peticiones async — preferir run({ task }) o run(task); label solo si hace falta override. Recomendado.
show() / hide() Flujos manuales o varias fases con el mismo label.

Variantes visuales

Configura variant en el boot del host (o built-in).

Valor Presentación
glass Glassmorphism — card semitransparente con blur (default)
primary Card sólida con color --q-primary; texto y spinner blancos
minimal Pill horizontal compacto, menos intrusivo

Indicadores de carga (spinner)

Configura spinner en el boot. Subset curado de spinners Quasar:

Valor Estilo
ios Barras radiales estilo iOS (default)
circular Anillo con track (QCircularProgress)
dots Tres puntos animados
oval Óvalo rotando
tail Cola rotativa
puff Círculo pulsante
rings Anillos concéntricos
bars Barras verticales
hourglass Reloj de arena
infinity Símbolo ∞

Constante exportada: DIALOG_LOADER_SPINNER_TYPES — útil para validar valores en runtime o documentar opciones en tu app.

Anti-parpadeo y operaciones concurrentes

El store mantiene un pendingCount: cada show() / inicio de run() lo incrementa; cada hide() / fin de run() lo decrementa. La UI solo se cierra cuando el contador llega a cero.

Delays configurables en boot:

Varias llamadas concurrentes a run() o show() comparten el mismo diálogo hasta que todas finalizan.

Tipos TypeScript exportados

Desde el entrypoint principal:

import {
  useDialogLoader,
  DialogLoaderRunCancelledError,
  resolveDialogLoaderMessages,
  resolveExtensionLocale,
  type DialogLoaderApi,
  type DialogLoaderRunControls,
  type DialogLoaderRunOptions,
  type DialogLoaderRunTask,
  type DialogLoaderDefaults,
  type DialogLoaderFontDefaults,
  type DialogLoaderMessages,
  type DialogLoaderThemeColor,
  type DialogLoaderThemeColorValue,
  type DialogLoaderThemeMode,
  type DialogLoaderVariant,
  type DialogLoaderSpinnerType,
  type ExtensionLocale,
  type ResolvedDialogLoaderDefaults,
} from "@benjaminor-dev/quasar-app-extension-dialog-loader";
Tipo Uso
DialogLoaderApi Retorno de useDialogLoader()
ExtensionLocale "en" | "es" | "pt" — boot locale
DialogLoaderMessages Forma de boot messages (loadingLabel, operationCancelled)
DialogLoaderRunOptions Argumento objeto de run({ label?, task })
DialogLoaderRunControls { pause, resume, cancel, signal } recibido en task
DialogLoaderRunTask Tipo del callback task
DialogLoaderRunPause Oculta el loader sin cancelar
DialogLoaderRunResume Reabre el loader tras pause()
DialogLoaderRunCancel Cancela la operación
DialogLoaderRunCancelledError Error al cancelar con cancel()
DialogLoaderDefaults Opciones admitidas en configureDialogLoaderDefaults
DialogLoaderFontDefaults { family?, size? } dentro de DialogLoaderDefaults.font
DialogLoaderThemeColorValue Color plano o { light?, dark? } para spinner
DialogLoaderThemeColor { light?, dark? } — parte de DialogLoaderThemeColorValue
DialogLoaderThemeMode "light" | "dark" — resuelto con $q.dark.isActive
DialogLoaderVariant "glass" | "primary" | "minimal"
DialogLoaderSpinnerType Unión de spinners soportados
ResolvedDialogLoaderDefaults Defaults ya mergeados (interno / referencia)

También se exportan BUILTIN_DIALOG_LOADER_DEFAULTS, DIALOG_LOADER_SPINNER_TYPES, resolveDialogLoaderMessages, resolveExtensionLocale, configureDialogLoader (alias) y DIALOG_LOADER_API_KEY (clave de provide del boot para integraciones avanzadas).

Entrypoints públicos

Lo que importas en tu código:

Entrypoint Propósito
@benjaminor-dev/quasar-app-extension-dialog-loader useDialogLoader, configureDialogLoaderDefaults, helpers i18n (resolveDialogLoaderMessages, resolveExtensionLocale), tipos y DIALOG_LOADER_SPINNER_TYPES

Boot (boot/dialog) y estilos (main.css) los registra la extensión en quasar.config al instalar — no hace falta importarlos a mano en una instalación normal.

Troubleshooting

Veo "Loading..." y quiero español (o portugués)

El default sin configurar Quasar es inglés. Elige una línea:

// quasar.config.ts — recomendado (toda la app Quasar)
framework: {
  lang: "es", // o "pt-BR" vía pack Quasar en boot
},
// src/boot/bor/bor-dialog-loader-defaults.ts — fijar solo el locale de Dialog Loader
configureDialogLoaderDefaults({ locale: "es" });

Override de un solo texto:

configureDialogLoaderDefaults({ messages: { loadingLabel: "Cargando..." } });

El label no cambia si cambias idioma durante un run()

Si el usuario cambia el idioma de la app mientras el loader está visible, el texto en pantalla se queda con el de cuando empezó run() / show(). Al cerrar el loader, el label en reposo sigue el nuevo locale. Para fijar un idioma en toda la extensión, usa locale en boot.

Boots o estilos no aparecen en quasar.config

En condiciones normales el install registra el defaults boot en boot: [] y el runner inyecta boot/dialog + CSS al compilar. Si falta el defaults boot:

npx quasar ext invoke @benjaminor-dev/dialog-loader

Error: Pinia no inicializada

useDialogLoader() fuera de setup

Llámalo dentro de setup, otro composable o <script setup>. El boot debe haberse ejecutado (app montada).

El loader parpadea en peticiones rápidas

Ajusta en boot: sube openDelayMs, minVisibleMs o ambos. Valores altos = menos flashes, pero más tiempo visible en operaciones cortas.

El loader no se cierra tras show()

Cada show() requiere un hide() correspondiente (o usa run(), que balancea automáticamente). Varias operaciones concurrentes comparten contador — cierra solo cuando todas terminan.

El diálogo queda detrás de otro overlay

Revisa zIndex en boot (default 6000). Debe ser menor que modales críticos de tu app si quieres que esos modales ganen; mayor que contenido base.

Cambié spinner en boot y no se refleja

Reinicia el dev server. Confirma que editas src/boot/bor/bor-dialog-loader-defaults.ts del host, no solo la plantilla de la extensión.


MIT © Benjamín Olvera R.