Dialog Loader para Quasar
🇺🇸 English | 🇧🇷 Versão em português
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
- Qué es y qué incluye
- Requisitos y compatibilidad
- Instalación en app Quasar
- Qué agrega la extensión al host
- Defaults globales (boot)
- Idioma (i18n)
- Uso rápido
- TypeScript en tu IDE
- API pública
- Variantes visuales
- Indicadores de carga (spinner)
- Anti-parpadeo y operaciones concurrentes
- Tipos TypeScript exportados
- Entrypoints públicos
- Troubleshooting
En 30 segundos
Flujo base recomendado:
- Instalar la extensión en tu app Quasar (
quasar ext add). - Asegurar que el boot de Pinia del host corre antes que el boot de esta extensión.
- Importar
useDialogLoader()donde ejecutes peticiones async. - 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.
run({ task })orun(task)— caso principal; usa el label del boot si no pasaslabel.run({ label, task })— override puntual del texto sin tocar el boot.show()/hide()— control manual con contador interno de operaciones pendientes.- Tres variantes de presentación:
glass,primary,minimal. - Spinners configurables (subset curado de Quasar; default
ios). - Anti-parpadeo: delays de apertura/cierre y tiempo mínimo visible.
- Defaults globales en boot del host (
gap,font, variant, spinner…); label por llamada opcional. - i18n built-in
en,es,pt— sigue$q.langde Quasar; overrides en boot (locale,messages). - Controles en
task:pause,resume,cancelysignal(AbortSignal).
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.config → boot: [] 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.
useDialogLoader()devuelveDialogLoaderApi— métodos y refs aparecen al escribirloader..- En
configureDialogLoaderDefaults({ ... }), Ctrl+Space (Windows/Linux) o Cmd+Space (macOS) listavariant,spinner,spinnerColor,spinnerTrackColor,gap,font,openDelayMs, etc. variantacepta"glass" \| "primary" \| "minimal";spinneracepta los valores deDIALOG_LOADER_SPINNER_TYPES(p. ej."ios","dots","circular").run()— overloads:run(task)orun({ task, label? }); entask, desestructura{ pause, resume, cancel, signal }con tipos inferidos.- En boot,
localeacepta"en" \| "es" \| "pt";messagesaceptaDialogLoaderMessagesparcial (loadingLabel,operationCancelled). - Pasa el cursor sobre un método: el tooltip muestra parámetros y JSDoc.
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:
openDelayMs— no muestra el diálogo si todas las operaciones terminan antes (peticiones muy rápidas).minVisibleMs— evita un flash de cierre inmediato tras abrir.hideDelayMs— alinea el cierre con la animación fade del overlay.
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
- Verifica un boot de Pinia en el host (
app.use(pinia)o equivalente). - En
quasar.config, el boot de Pinia debe ir antes que el bootdialogde esta extensión.
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.