Dialog Loader para Quasar

🇺🇸 English | 🇪🇸 Versión en español

npm version license Vue Quasar TypeScript Node

Extensão Quasar para diálogo modal de carregamento global em apps Vue 3 + Quasar, com API imperativa baseada em composable.

Índice

Em 30 segundos

Fluxo base recomendado:

  1. Instalar a extensão na sua app Quasar (quasar ext add).
  2. Garantir que o boot do Pinia do host roda antes do boot desta extensão.
  3. Importar useDialogLoader() onde executar pedidos async.
  4. Envolver a tarefa com await loader.run({ task: () => api.save() }) — ou { label, task } para override pontual.

A extensão regista boots e CSS no projeto host automaticamente após quasar ext add.

O que é e o que inclui

Diálogo modal global controlado por useDialogLoader() — não é um componente embebível.

Requisitos e compatibilidade

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

O boot do Pinia do host deve executar-se antes do boot desta extensão.

Instalação na app Quasar

quasar ext add @benjaminor-dev/dialog-loader

Remover:

quasar ext remove @benjaminor-dev/dialog-loader

Após quasar ext add, a extensão regista boots e CSS na sua app automaticamente. Não precisa de passos adicionais numa instalação normal via npm.

O que a extensão adiciona ao host

Recursos instalados ou registados pela extensão:

Recurso Rota npm
Boot do 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 — criado no install; Skip/Overwrite se já existir

Install (quasar ext add / invoke): cria src/boot/bor/bor-dialog-loader-defaults.ts (ou .js) e regista-o em quasar.configboot: [] automaticamente. Não precisa de editar manualmente numa instalação normal. O boot de pacote (boot/dialog) e main.css são injetados em cada dev/build (não aparecem como entradas curtas em quasar.config).

Remove (quasar ext remove): remove o defaults boot e a respetiva entrada em quasar.config.

Ordem mínima de boots:

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

Em quasar.config só verá bor/bor-dialog-loader-defaults; o boot boot/dialog é adicionado pelo runner ao compilar.

O boot monta o diálogo no documento e liga a store Pinia do host. Não precisa importar DialogLoader em App.vue.

Defaults globais (boot)

Evite repetir variant, spinner, gap, font, delays e zIndex em cada chamada. Prioridade do label: run({ label }) / show() → boot label → i18n (locale / $q.lang / en).

Após quasar ext add, reveja src/boot/bor/bor-dialog-loader-defaults.ts — plantilla com todas as opções boot (// recomendado: …) e instruções de idioma no comentário do ficheiro.

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

configureDialogLoaderDefaults({
  // locale: "pt", // descomente para fixar um idioma específico no 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 e pt. Por defeito o label segue o idioma do Quasar ($q.lang.isoName); se o Quasar não definir idioma, cai em en ("Loading...").

Resolução de locale: boot locale → prefixo de $q.lang.isoName (es*es, pt*pt) → en.

Resolução do label: run({ label }) / show(label) → boot label → boot messages.loadingLabel → catálogo built-in → en.

Chaves i18n built-in: loadingLabel (texto padrão do loader), operationCancelled (DialogLoaderRunCancelledError quando cancel() aborta a tarefa).

Opção Quando usar
framework.lang em quasar.config.ts Recomendado — alinha Dialog Loader com o resto do Quasar
locale: 'pt' no boot Fixar um idioma específico (en | es | pt); ignora mudanças de $q.lang
messages: { loadingLabel: '…' } Só alterar um texto sem tocar no locale
label: '…' no boot Label fixo sempre (ignora i18n)

Exemplo Quasar config (português em toda a app):

// quasar.config.ts
framework: {
  lang: "pt-BR",
},

configureDialogLoader é alias de configureDialogLoaderDefaults.

Opção boot Default built-in Descrição
locale (segue $q.lang) Fixa en | es | pt para textos da extensão
messages (por locale) Override parcial (loadingLabel, operationCancelled)
label "Loading..." (i18n) Texto fixo quando não passa label em show() / run()
variant "glass" Apresentação do diálogo
spinner "ios" Indicador de carregamento (ver spinners)
openDelayMs 120 Atraso antes de mostrar (evita flashes em pedidos muito rápidos)
hideDelayMs 450 Atraso extra ao fechar (alinha com animação de saída)
minVisibleMs 600 Tempo mínimo visível ainda que a operação termine antes
backdropBlur "blur(10px)" Valor CSS backdrop-filter do overlay
zIndex 6000 Abaixo do Quasar Notify (~9500) para toasts continuarem visíveis
gap "2rem" Espaço label↔spinner
font.family "Arial, sans-serif" CSS font-family do label
font.size "1.35rem" CSS font-size do label
spinnerColor (por variant) Cor Quasar do spinner — plano ou { light, dark }
spinnerTrackColor (por variant) Cor Quasar do track em spinners circulares — plano ou { light, dark }

Se omitir spinnerColor / spinnerTrackColor, o fallback depende de variant: em primary o spinner é branco; em glass e minimal usa primary e grey-4. Configure { light, dark } no boot para adaptar o spinner ao tema do 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('Erro ao guardar');
  }
}
</script>

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

Sem label explícito — usa i18n conforme $q.lang (p. ex. "Carregando..." com lang: 'pt-BR'):

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

Override pontual do texto:

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

Controlos opcionais dentro de task:

await loader.run({
  task: async ({ pause, resume, cancel, signal }) => {
    await api.prepare();
    pause(); // oculta overlay; a tarefa continua
    await api.finishInBackground();
    resume(); // volta a mostrar o loader
    await fetch("/api/finalize", { signal }); // cancel() aborta este fetch
  },
});
Controlo O que faz
pause() Oculta o loader; não cancela a tarefa
resume() Reabre o loader após um pause()
cancel() Aborta signal, fecha o loader e rejeita run() com DialogLoaderRunCancelledError
signal Passe a fetch / axios para cancelação cooperativa

pause(), resume() e cancel() são idempotentes e só afetam essa operação 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;
}

Controlo manual (menos habitual):

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

TypeScript no seu IDE

Não memorize a API: deixe o autocompletado guiá-lo.

Se algo não aparecer no autocompletado, costuma ser uma prop inexistente ou import incorreto — reveja o entrypoint principal antes de procurar no README.

API pública

Ponto de entrada: useDialogLoader().

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

const loader = useDialogLoader();

Métodos

Método Descrição
run(task) Executa a tarefa com loader; label do boot. Fecha em finally.
run({ task }) Igual ao acima (forma objeto).
run({ label, task }) Igual com label pontual para essa operação.
show(label?) Incrementa o contador interno e agenda abertura do diálogo.
hide() Decrementa o contador; fecha a UI só quando não restam operações pendentes.

Estado reativo (somente leitura)

Ref Descrição
visible Se o diálogo está visível.
label Texto mostrado no diálogo.

Quando usar run vs show / hide

Abordagem Quando
run() Pedidos async — preferir run({ task }) ou run(task); label só se precisar override. Recomendado.
show() / hide() Fluxos manuais ou várias fases com o mesmo label.

Variantes visuais

Configure variant no boot do host (ou built-in).

Valor Apresentação
glass Glassmorphism — card semitransparente com blur (default)
primary Card sólida com cor --q-primary; texto e spinner brancos
minimal Pill horizontal compacto, menos intrusivo

Indicadores de carregamento (spinner)

Configure spinner no boot. Subset curado de spinners Quasar:

Valor Estilo
ios Barras radiais estilo iOS (default)
circular Anel com track (QCircularProgress)
dots Três pontos animados
oval Óvalo a rodar
tail Cauda rotativa
puff Círculo pulsante
rings Anéis concêntricos
bars Barras verticais
hourglass Ampulheta
infinity Símbolo ∞

Constante exportada: DIALOG_LOADER_SPINNER_TYPES — útil para validar valores em runtime.

Anti-piscar e operações concorrentes

A store mantém um pendingCount: cada show() / início de run() incrementa; cada hide() / fim de run() decrementa. A UI só fecha quando o contador chega a zero.

Delays configuráveis no boot:

Várias chamadas concorrentes a run() ou show() partilham o mesmo diálogo até todas terminarem.

Tipos TypeScript exportados

Desde o 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 } recebido em task
DialogLoaderRunTask Tipo do callback task
DialogLoaderRunPause Oculta o loader sem cancelar
DialogLoaderRunResume Reabre o loader após pause()
DialogLoaderRunCancel Cancela a operação
DialogLoaderRunCancelledError Erro ao cancelar com cancel()
DialogLoaderDefaults Opções admitidas em configureDialogLoaderDefaults
DialogLoaderFontDefaults { family?, size? } dentro de DialogLoaderDefaults.font
DialogLoaderThemeColorValue Cor plana ou { light?, dark? } para spinner
DialogLoaderThemeColor { light?, dark? } — parte de DialogLoaderThemeColorValue
DialogLoaderThemeMode "light" | "dark" — resolvido com $q.dark.isActive
DialogLoaderVariant "glass" | "primary" | "minimal"
DialogLoaderSpinnerType União de spinners suportados
ResolvedDialogLoaderDefaults Defaults já mergeados (interno / referência)

Também se exportam BUILTIN_DIALOG_LOADER_DEFAULTS, DIALOG_LOADER_SPINNER_TYPES, resolveDialogLoaderMessages, resolveExtensionLocale, configureDialogLoader (alias) e DIALOG_LOADER_API_KEY (chave de provide do boot para integrações avançadas).

Entrypoints públicos

O que importa no seu código:

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

Boot (boot/dialog) e estilos (main.css) são registados pela extensão em quasar.config ao instalar — não precisa importá-los manualmente.

Troubleshooting

Vejo "Loading..." e quero português (ou espanhol)

O default sem configurar Quasar é inglês. Escolha uma linha:

// quasar.config.ts — recomendado (toda a app Quasar)
framework: {
  lang: "pt-BR",
},
// src/boot/bor/bor-dialog-loader-defaults.ts — fixar só o locale do Dialog Loader
configureDialogLoaderDefaults({ locale: "pt" });

Override de um só texto:

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

O label não muda se o idioma for alterado durante um run()

Se o usuário mudar o idioma da app enquanto o loader está visível, o texto na tela permanece o de quando run() / show() começou. Ao fechar o loader, o label em repouso segue o novo locale. Para fixar um idioma em toda a extensão, use locale no boot.

Boots ou estilos não aparecem em quasar.config

Em condições normais o install regista o defaults boot em boot: [] e o runner injeta boot/dialog + CSS ao compilar. Se faltar o defaults boot:

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

Erro: Pinia não inicializada

useDialogLoader() fora de setup

Chame dentro de setup, outro composable ou <script setup>. O boot deve ter sido executado (app montada).

O loader pisca em pedidos rápidos

Ajuste no boot: suba openDelayMs, minVisibleMs ou ambos.

O loader não fecha após show()

Cada show() requer um hide() correspondente (ou use run(), que equilibra automaticamente).

O diálogo fica atrás de outro overlay

Revise zIndex no boot (default 6000).

Mudei spinner no boot e não se reflete

Reinicie o dev server. Confirme que edita src/boot/bor/bor-dialog-loader-defaults.ts do host.


MIT © Benjamín Olvera R.