@edc-motor/ui 0.5.18 → 0.5.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/splash.ts +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edc-motor/ui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "EdC Motor — componentes públicos Vue 3 + tokens SCSS para webs de juegos de mesa (paquete fuente: lo compila el consumidor con Vite)",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export { default as LocaleSelector } from './components/LocaleSelector.vue'
|
|
|
53
53
|
export { default as AppBreadcrumbs, type Crumb } from './components/AppBreadcrumbs.vue'
|
|
54
54
|
export { default as PreviewGrid, type PreviewGridItem } from './components/PreviewGrid.vue'
|
|
55
55
|
export { createApi, type CreateApiOptions } from './lib/createApi'
|
|
56
|
+
export { watchSplash, dismissSplash, type WatchSplashOptions } from './lib/splash'
|
|
56
57
|
export { useToast, type Toast } from './composables/useToast'
|
|
57
58
|
export { useConfirm, type ConfirmOptions } from './composables/useConfirm'
|
|
58
59
|
export { useTheme, type ThemeMode } from './composables/useTheme'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios'
|
|
2
|
+
|
|
3
|
+
// Splash de arranque. La SPA sirve un cascarón instantáneo y pide TODO lo
|
|
4
|
+
// real (settings, menús, sesión, contenido de la vista) a la API después de
|
|
5
|
+
// montar: hasta que responde, cada componente pinta su estado por defecto.
|
|
6
|
+
// En local (API a ~1 ms) ese fotograma provisional no llega a verse; en
|
|
7
|
+
// producción sí. El remedio: el index.html estático trae un velo a pantalla
|
|
8
|
+
// completa (#edc-splash, HTML autosuficiente pintado desde el fotograma
|
|
9
|
+
// cero, antes incluso de descargar el bundle) y este módulo lo retira
|
|
10
|
+
// cuando el arranque termina de verdad.
|
|
11
|
+
//
|
|
12
|
+
// ¿Y cuándo termina «de verdad»? En vez de instrumentar cada vista, se
|
|
13
|
+
// observa el cliente axios: se cuentan las peticiones en vuelo y el splash
|
|
14
|
+
// cae en el PRIMER REPOSO DE RED (cero peticiones durante `quietMs`). Eso
|
|
15
|
+
// cubre solo la cascada inicial —locales → settings → datos de la primera
|
|
16
|
+
// vista, encadenados por microtareas que siempre ganan al temporizador— y
|
|
17
|
+
// no exige tocar las vistas. Un tope (`maxWaitMs`) garantiza que una API
|
|
18
|
+
// caída nunca deja el velo puesto.
|
|
19
|
+
//
|
|
20
|
+
// Uso (main.ts, ANTES de app.mount() — si se llama después, las peticiones
|
|
21
|
+
// del onMounted ya habrían salido sin contar):
|
|
22
|
+
//
|
|
23
|
+
// watchSplash({ api })
|
|
24
|
+
// app.mount('#app')
|
|
25
|
+
|
|
26
|
+
const SPLASH_ID = 'edc-splash'
|
|
27
|
+
|
|
28
|
+
export interface WatchSplashOptions {
|
|
29
|
+
/** Cliente(s) axios cuyas peticiones marcan el arranque (createApi). */
|
|
30
|
+
api?: AxiosInstance | AxiosInstance[]
|
|
31
|
+
/** Reposo de red que da el arranque por terminado (ms). */
|
|
32
|
+
quietMs?: number
|
|
33
|
+
/** Tope duro: el splash cae aunque la red siga ocupada o caída (ms). */
|
|
34
|
+
maxWaitMs?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Retira el splash con su fundido (idempotente; sin splash, no hace nada). */
|
|
38
|
+
export function dismissSplash(): void {
|
|
39
|
+
const el = document.getElementById(SPLASH_ID)
|
|
40
|
+
if (!el || el.classList.contains('edc-splash--done')) return
|
|
41
|
+
el.classList.add('edc-splash--done')
|
|
42
|
+
const remove = () => el.remove()
|
|
43
|
+
el.addEventListener('transitionend', remove, { once: true })
|
|
44
|
+
// Por si no hay transición (prefers-reduced-motion, CSS recortado).
|
|
45
|
+
setTimeout(remove, 600)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Observa el arranque y retira el splash en el primer reposo de red. */
|
|
49
|
+
export function watchSplash(options: WatchSplashOptions = {}): void {
|
|
50
|
+
if (typeof document === 'undefined') return
|
|
51
|
+
if (!document.getElementById(SPLASH_ID)) return
|
|
52
|
+
|
|
53
|
+
const quietMs = options.quietMs ?? 200
|
|
54
|
+
const maxWaitMs = options.maxWaitMs ?? 8000
|
|
55
|
+
const apis = Array.isArray(options.api) ? options.api : options.api ? [options.api] : []
|
|
56
|
+
|
|
57
|
+
let inflight = 0
|
|
58
|
+
let quietTimer: ReturnType<typeof setTimeout> | undefined
|
|
59
|
+
|
|
60
|
+
const done = () => {
|
|
61
|
+
clearTimeout(quietTimer)
|
|
62
|
+
// Doble rAF: el render definitivo llega a pintarse BAJO el velo antes
|
|
63
|
+
// del fundido (sin esto el fundido podría destapar un frame a medias).
|
|
64
|
+
requestAnimationFrame(() => requestAnimationFrame(dismissSplash))
|
|
65
|
+
}
|
|
66
|
+
const armQuiet = () => {
|
|
67
|
+
clearTimeout(quietTimer)
|
|
68
|
+
quietTimer = setTimeout(done, quietMs)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const api of apis) {
|
|
72
|
+
api.interceptors.request.use((config) => {
|
|
73
|
+
inflight++
|
|
74
|
+
clearTimeout(quietTimer)
|
|
75
|
+
return config
|
|
76
|
+
})
|
|
77
|
+
api.interceptors.response.use(
|
|
78
|
+
(response) => {
|
|
79
|
+
if (--inflight <= 0) armQuiet()
|
|
80
|
+
return response
|
|
81
|
+
},
|
|
82
|
+
(error) => {
|
|
83
|
+
if (--inflight <= 0) armQuiet()
|
|
84
|
+
return Promise.reject(error)
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Armado inicial: si el arranque no llega a pedir nada (o no se pasó
|
|
90
|
+
// `api`), el splash cae solo tras el primer reposo.
|
|
91
|
+
armQuiet()
|
|
92
|
+
setTimeout(dismissSplash, maxWaitMs)
|
|
93
|
+
}
|