master
parent
133699153c
commit
740de6584e
@ -1,7 +1,11 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npx vitest run src/glob/tests/unit.test.ts)"
|
||||
"Bash(npx vitest run src/glob/tests/unit.test.ts)",
|
||||
"Bash(npx tsc --noEmit)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"g:\\dev\\TypeScript\\v\\src\\adapters\\svelte"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>VICE — Catalog Editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="/src/apps/svelte/main.ts"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,173 @@
|
||||
import type { GlobInstance, GlobConfig } from '../../glob/glob_types.ts';
|
||||
import type { GlobLocale } from '../../glob/comn_types.ts';
|
||||
import type { LingNode } from '../../ling/types.ts';
|
||||
import type { CurrDisplay } from '../../glob/curr_types.ts';
|
||||
import type { DatiOptions } from '../../glob/dati_types.ts';
|
||||
import type { UnitDisplay, UnitCategoryInstance } from '../../glob/unit_types.ts';
|
||||
import { createGlob } from '../../glob/glob_engine';
|
||||
|
||||
|
||||
// ── Tipos ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ReactiveNumr {
|
||||
f: (n: number, opts?: { minDecimals?: number; maxDecimals?: number }) => string;
|
||||
}
|
||||
|
||||
export interface ReactiveCurr {
|
||||
f : (amount: number, display?: CurrDisplay) => string;
|
||||
convert : GlobInstance['curr']['convert'];
|
||||
convertTo: GlobInstance['curr']['convertTo'];
|
||||
get : GlobInstance['curr']['get'];
|
||||
}
|
||||
|
||||
export interface ReactiveDati {
|
||||
f : (date: Date, opts?: DatiOptions) => string;
|
||||
setTimeFormat: GlobInstance['dati']['setTimeFormat'];
|
||||
getTimeFormat: GlobInstance['dati']['getTimeFormat'];
|
||||
setDateOrder : GlobInstance['dati']['setDateOrder'];
|
||||
getDateOrder : GlobInstance['dati']['getDateOrder'];
|
||||
}
|
||||
|
||||
export interface ReactiveUnitCategory {
|
||||
f : (value: number, display?: UnitDisplay) => string;
|
||||
fTo : (value: number, targetUnit?: string, display?: UnitDisplay) => string;
|
||||
convert: UnitCategoryInstance['convert'];
|
||||
}
|
||||
|
||||
export interface ReactiveUnit {
|
||||
weight : ReactiveUnitCategory;
|
||||
length : ReactiveUnitCategory;
|
||||
temp : ReactiveUnitCategory;
|
||||
volume : ReactiveUnitCategory;
|
||||
area : ReactiveUnitCategory;
|
||||
setSystem: GlobInstance['unit']['setSystem'];
|
||||
getSystem: GlobInstance['unit']['getSystem'];
|
||||
}
|
||||
|
||||
|
||||
// ── Estado reactivo ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crea un estado reactivo de Svelte 5 (runes) a partir de una `GlobInstance`.
|
||||
*
|
||||
* Usa `$state` internamente. Debe importarse desde un archivo `.svelte`,
|
||||
* `.svelte.ts` o `.svelte.js` para que las runes funcionen.
|
||||
*
|
||||
* Los getters leen `_locale` / `_currency` para establecer dependencias
|
||||
* reactivas: cuando el locale o la moneda cambian, cualquier contexto
|
||||
* reactivo que lea esas propiedades se re-evalúa automáticamente.
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <script>
|
||||
* import { createGlobState } from '@/svelte/glob.svelte';
|
||||
* import { glob } from '$lib/glob';
|
||||
*
|
||||
* const g = createGlobState(glob);
|
||||
* </script>
|
||||
*
|
||||
* <p>{g.t('hello')}</p>
|
||||
* <p>{g.numr.f(1_234.5)}</p>
|
||||
* <p>{g.curr.f(99.99, 'symbol')}</p>
|
||||
*
|
||||
* <select bind:value={g.locale}>
|
||||
* <option value="es">Español</option>
|
||||
* <option value="en">English</option>
|
||||
* </select>
|
||||
* ```
|
||||
*/
|
||||
function isGlobInstance<S extends LingNode>(
|
||||
input: GlobInstance<S> | GlobConfig<S>
|
||||
): input is GlobInstance<S> {
|
||||
return 'onLocaleChange' in input;
|
||||
}
|
||||
|
||||
export function createGlobState<S extends LingNode>(input: GlobInstance<S> | GlobConfig<S>) {
|
||||
|
||||
const glob = isGlobInstance(input) ? input : createGlob(input);
|
||||
|
||||
let _locale = $state<GlobLocale>(glob.getLocale());
|
||||
let _currency = $state<string>(glob.curr.getCurrency());
|
||||
|
||||
// Sincroniza cuando glob cambia el locale desde fuera (ej. otro módulo)
|
||||
const unsubLocale = glob.onLocaleChange((loc: GlobLocale) => { _locale = loc; });
|
||||
|
||||
// Helper reutilizable para categorías de unidades
|
||||
const reactiveUnitCat = (c: UnitCategoryInstance, locale: GlobLocale): ReactiveUnitCategory => ({
|
||||
f : (value, display) => c.f(value, display, locale),
|
||||
fTo : (value, target, display) => c.fTo(value, target, display, locale),
|
||||
convert: c.convert.bind(c),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
// ── Locale ────────────────────────────────────────────────────────────
|
||||
// El setter llama a glob.setLocale(), que a su vez dispara onLocaleChange,
|
||||
// que actualiza _locale y reactiva todos los getters dependientes.
|
||||
get locale() { return _locale; },
|
||||
set locale(v: GlobLocale) { glob.setLocale(v); },
|
||||
|
||||
// ── Currency ──────────────────────────────────────────────────────────
|
||||
get currency() { return _currency; },
|
||||
set currency(v: string) {
|
||||
glob.curr.setCurrency(v);
|
||||
_currency = glob.curr.getCurrency(); // normaliza a mayúsculas
|
||||
},
|
||||
|
||||
// ── Traducciones ──────────────────────────────────────────────────────
|
||||
// Leer _locale dentro del getter establece la dependencia reactiva.
|
||||
// glob.t usa ling.getLocale() internamente, así que siempre lee el locale actual.
|
||||
get t() { _locale; return glob.t; },
|
||||
get ts() { _locale; return glob.ts; },
|
||||
|
||||
// ── Numr ──────────────────────────────────────────────────────────────
|
||||
get numr(): ReactiveNumr {
|
||||
const l = _locale;
|
||||
return {
|
||||
f: (n, opts) => glob.numr.f(n, opts, l),
|
||||
};
|
||||
},
|
||||
|
||||
// ── Curr ──────────────────────────────────────────────────────────────
|
||||
// Depende de locale y currency — ambas lecturas crean dependencias.
|
||||
get curr(): ReactiveCurr {
|
||||
const l = _locale;
|
||||
void _currency; // dependencia reactiva: se re-evalúa al cambiar moneda
|
||||
return {
|
||||
f : (amount, display) => glob.curr.f(amount, display, l),
|
||||
convert : glob.curr.convert.bind(glob.curr),
|
||||
convertTo: glob.curr.convertTo.bind(glob.curr),
|
||||
get : glob.curr.get.bind(glob.curr),
|
||||
};
|
||||
},
|
||||
|
||||
// ── Dati ──────────────────────────────────────────────────────────────
|
||||
get dati(): ReactiveDati {
|
||||
const l = _locale;
|
||||
return {
|
||||
f : (date, opts) => glob.dati.f(date, opts, l),
|
||||
setTimeFormat: glob.dati.setTimeFormat.bind(glob.dati),
|
||||
getTimeFormat: glob.dati.getTimeFormat.bind(glob.dati),
|
||||
setDateOrder : glob.dati.setDateOrder.bind(glob.dati),
|
||||
getDateOrder : glob.dati.getDateOrder.bind(glob.dati),
|
||||
};
|
||||
},
|
||||
|
||||
// ── Unit ──────────────────────────────────────────────────────────────
|
||||
get unit(): ReactiveUnit {
|
||||
const l = _locale;
|
||||
return {
|
||||
weight : reactiveUnitCat(glob.unit.weight, l),
|
||||
length : reactiveUnitCat(glob.unit.length, l),
|
||||
temp : reactiveUnitCat(glob.unit.temp, l),
|
||||
volume : reactiveUnitCat(glob.unit.volume, l),
|
||||
area : reactiveUnitCat(glob.unit.area, l),
|
||||
setSystem: glob.unit.setSystem.bind(glob.unit),
|
||||
getSystem: glob.unit.getSystem.bind(glob.unit),
|
||||
};
|
||||
},
|
||||
|
||||
/** Limpia la suscripción interna con glob. Útil si desmontas el contexto raíz. */
|
||||
destroy: () => unsubLocale(),
|
||||
};
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './glob.svelte.ts';
|
||||
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import './app.css';
|
||||
import GlobDemo from './components/GlobDemo.svelte';
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
||||
<GlobDemo />
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<style global>
|
||||
main { font-family: sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
|
||||
</style>
|
||||
@ -0,0 +1,15 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
|
||||
/* Smooth scroll para navegación */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
|
||||
@layer components {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
|
||||
import { mount } from 'svelte';
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!
|
||||
});
|
||||
|
||||
|
||||
|
||||
export default app;
|
||||
@ -0,0 +1,13 @@
|
||||
export * from './comn_types';
|
||||
export * from './curr_types';
|
||||
export * from './dati_types';
|
||||
export * from './glob_types';
|
||||
export * from './numr_types';
|
||||
export * from './unit_types';
|
||||
|
||||
export * from './comn_engine';
|
||||
export * from './curr_engine';
|
||||
export * from './dati_engine';
|
||||
export * from './glob_engine';
|
||||
export * from './numr_engine';
|
||||
export * from './unit_engine';
|
||||
Loading…
Reference in new issue