@edc-motor/ui 0.5.20 → 0.5.22
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 +2 -0
- package/src/lib/splash.ts +20 -6
- package/src/lib/standalone.ts +18 -0
- package/src/lib/swr.ts +73 -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.22",
|
|
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
|
@@ -62,6 +62,8 @@ export {
|
|
|
62
62
|
type SplashRouterLike,
|
|
63
63
|
} from './lib/splash'
|
|
64
64
|
export { readCache, writeCache } from './lib/localCache'
|
|
65
|
+
export { createSwrGet, type SwrGetter } from './lib/swr'
|
|
66
|
+
export { isStandalonePwa } from './lib/standalone'
|
|
65
67
|
export { useToast, type Toast } from './composables/useToast'
|
|
66
68
|
export { useConfirm, type ConfirmOptions } from './composables/useConfirm'
|
|
67
69
|
export { useTheme, type ThemeMode } from './composables/useTheme'
|
package/src/lib/splash.ts
CHANGED
|
@@ -33,6 +33,16 @@ import type { AxiosInstance } from 'axios'
|
|
|
33
33
|
const SPLASH_ID = 'edc-splash'
|
|
34
34
|
const DONE_CLASS = 'edc-splash--done'
|
|
35
35
|
|
|
36
|
+
// Peticiones DE FONDO: con `edcBackground: true` en el config de axios, la
|
|
37
|
+
// petición no cuenta para el velo — es relleno o refresco (catálogos con su
|
|
38
|
+
// propia presentación de carga, revalidaciones SWR, sondeos), no «la página
|
|
39
|
+
// aún no puede pintarse». Así el velo queda solo para las cargas de página.
|
|
40
|
+
declare module 'axios' {
|
|
41
|
+
interface AxiosRequestConfig {
|
|
42
|
+
edcBackground?: boolean
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
export interface WatchSplashOptions {
|
|
37
47
|
/** Cliente(s) axios cuyas peticiones marcan el arranque (createApi). */
|
|
38
48
|
api?: AxiosInstance | AxiosInstance[]
|
|
@@ -114,17 +124,19 @@ export function watchSplash(options: WatchSplashOptions = {}): void {
|
|
|
114
124
|
|
|
115
125
|
for (const api of apis) {
|
|
116
126
|
api.interceptors.request.use((config) => {
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
if (!config.edcBackground) {
|
|
128
|
+
inflight++
|
|
129
|
+
clearTimeout(quietTimer)
|
|
130
|
+
}
|
|
119
131
|
return config
|
|
120
132
|
})
|
|
121
133
|
api.interceptors.response.use(
|
|
122
134
|
(response) => {
|
|
123
|
-
if (--inflight <= 0) armQuiet()
|
|
135
|
+
if (!response.config.edcBackground && --inflight <= 0) armQuiet()
|
|
124
136
|
return response
|
|
125
137
|
},
|
|
126
|
-
(error) => {
|
|
127
|
-
if (--inflight <= 0) armQuiet()
|
|
138
|
+
(error: { config?: { edcBackground?: boolean } }) => {
|
|
139
|
+
if (!error?.config?.edcBackground && --inflight <= 0) armQuiet()
|
|
128
140
|
return Promise.reject(error)
|
|
129
141
|
},
|
|
130
142
|
)
|
|
@@ -150,7 +162,9 @@ export function setupNavigationSplash(
|
|
|
150
162
|
if (typeof document === 'undefined') return
|
|
151
163
|
if (!splashEl()) return
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
// 250 ms: una API razonable responde antes y el velo ni aparece; por
|
|
166
|
+
// debajo saltaba en CADA navegación de producción (feo y alarmante).
|
|
167
|
+
const showDelayMs = options.showDelayMs ?? 250
|
|
154
168
|
const maxWaitMs = options.maxWaitMs ?? 8000
|
|
155
169
|
|
|
156
170
|
let navPending = false
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// ¿Corre la web como PWA INSTALADA (ventana propia, sin pestañas ni barra)?
|
|
2
|
+
// Importa para los PDFs: en la app instalada no hay «pestaña nueva» ni visor
|
|
3
|
+
// con controles — navegar a un PDF inline deja la ventana en blanco sin
|
|
4
|
+
// escape mientras bajan decenas de MB. En standalone se sirve la descarga
|
|
5
|
+
// nativa (Content-Disposition: attachment): el gestor de descargas del
|
|
6
|
+
// sistema da progreso y la app sigue viva.
|
|
7
|
+
export function isStandalonePwa(): boolean {
|
|
8
|
+
if (typeof window === 'undefined') return false
|
|
9
|
+
// display-mode cubre Chrome/Edge/Android; navigator.standalone es el
|
|
10
|
+
// equivalente histórico de iOS Safari.
|
|
11
|
+
return (
|
|
12
|
+
window.matchMedia('(display-mode: standalone)').matches ||
|
|
13
|
+
window.matchMedia('(display-mode: minimal-ui)').matches ||
|
|
14
|
+
window.matchMedia('(display-mode: fullscreen)').matches ||
|
|
15
|
+
('standalone' in window.navigator &&
|
|
16
|
+
Boolean((window.navigator as { standalone?: boolean }).standalone))
|
|
17
|
+
)
|
|
18
|
+
}
|
package/src/lib/swr.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
|
2
|
+
|
|
3
|
+
// Caché SWR en MEMORIA para los GET de contenido de la web pública
|
|
4
|
+
// (stale-while-revalidate): la primera visita a una URL espera a la red
|
|
5
|
+
// (y el velo de navegación puede aparecer); las siguientes se sirven de la
|
|
6
|
+
// memoria AL INSTANTE — sin velo, sin negro — mientras una revalidación DE
|
|
7
|
+
// FONDO (edcBackground: no cuenta para el velo) trae lo fresco y solo
|
|
8
|
+
// re-aplica si algo cambió. La memoria vive lo que la pestaña: nada que
|
|
9
|
+
// invalidar entre sesiones.
|
|
10
|
+
//
|
|
11
|
+
// Uso: const swrGet = createSwrGet(api) // una vez, junto al cliente
|
|
12
|
+
// await swrGet<Payload>('/pages/home', undefined, (data) => { ... })
|
|
13
|
+
// El callback puede llegar DOS veces (caché y luego fresco): debe ser
|
|
14
|
+
// idempotente y, si la vista pudo cambiar mientras tanto (requestId),
|
|
15
|
+
// comprobar dentro que sigue vigente.
|
|
16
|
+
|
|
17
|
+
export interface SwrGetter {
|
|
18
|
+
<T>(
|
|
19
|
+
url: string,
|
|
20
|
+
config: AxiosRequestConfig | undefined,
|
|
21
|
+
onData: (data: T, fresh: boolean) => void,
|
|
22
|
+
): Promise<void>
|
|
23
|
+
/** Vacía la memoria (p. ej. al cerrar sesión, si el contenido depende de ella). */
|
|
24
|
+
clear(): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createSwrGet(api: AxiosInstance): SwrGetter {
|
|
28
|
+
const memory = new Map<string, unknown>()
|
|
29
|
+
|
|
30
|
+
// La clave incluye los params por defecto del cliente (el `?locale` que
|
|
31
|
+
// inyecta el store de locales) además de los de la petición: la misma URL
|
|
32
|
+
// en otro idioma es OTRA entrada.
|
|
33
|
+
function keyFor(url: string, config?: AxiosRequestConfig): string {
|
|
34
|
+
const params: Record<string, unknown> = {
|
|
35
|
+
...(api.defaults.params as Record<string, unknown> | undefined),
|
|
36
|
+
...(config?.params as Record<string, unknown> | undefined),
|
|
37
|
+
}
|
|
38
|
+
const query = Object.keys(params)
|
|
39
|
+
.sort()
|
|
40
|
+
.map((k) => `${k}=${JSON.stringify(params[k])}`)
|
|
41
|
+
.join('&')
|
|
42
|
+
return `${url}?${query}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function run<T>(
|
|
46
|
+
url: string,
|
|
47
|
+
config: AxiosRequestConfig | undefined,
|
|
48
|
+
onData: (data: T, fresh: boolean) => void,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const key = keyFor(url, config)
|
|
51
|
+
if (memory.has(key)) {
|
|
52
|
+
const cached = memory.get(key) as T
|
|
53
|
+
onData(cached, false)
|
|
54
|
+
void api
|
|
55
|
+
.get<T>(url, { ...config, edcBackground: true })
|
|
56
|
+
.then(({ data }) => {
|
|
57
|
+
memory.set(key, data)
|
|
58
|
+
if (JSON.stringify(data) !== JSON.stringify(cached)) onData(data, true)
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {
|
|
61
|
+
// la revalidación es oportunista: si falla, se queda lo cacheado
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
const { data } = await api.get<T>(url, config)
|
|
66
|
+
memory.set(key, data)
|
|
67
|
+
onData(data, true)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const getter = run as SwrGetter
|
|
71
|
+
getter.clear = () => memory.clear()
|
|
72
|
+
return getter
|
|
73
|
+
}
|