parent
0766cfe49f
commit
a86ebdcf69
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,195 @@
|
||||
<script lang="ts">
|
||||
import { createACtx } from '../../../../lib/actx/lib/index.ts';
|
||||
import { createLing, type LingNode } from '../../../../lib/ling/lib/index.ts';
|
||||
import { TerraConfig } from '$uix/terra';
|
||||
import { createTerraTranslator, extendTerraTranslationModule } from '$uix/terra/config';
|
||||
import TerraLingPanel from './terra-ling-panel.svelte';
|
||||
|
||||
const ling = createLing(
|
||||
{
|
||||
app: {
|
||||
title: {
|
||||
es: 'Demo reactiva Terra + Ling',
|
||||
en: 'Reactive Terra + Ling demo',
|
||||
},
|
||||
},
|
||||
},
|
||||
'es'
|
||||
);
|
||||
|
||||
const actx = createACtx({ ling });
|
||||
|
||||
const terraModule = {
|
||||
dialog: {
|
||||
close: {
|
||||
es: 'Cerrar',
|
||||
en: 'Close',
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
removeFile: {
|
||||
es: 'Eliminar archivo',
|
||||
en: 'Remove file',
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
nextMonth: {
|
||||
es: 'Mes siguiente',
|
||||
en: 'Next month',
|
||||
},
|
||||
prevMonth: {
|
||||
es: 'Mes anterior',
|
||||
en: 'Previous month',
|
||||
},
|
||||
},
|
||||
} satisfies LingNode;
|
||||
|
||||
extendTerraTranslationModule(actx.ling, terraModule);
|
||||
|
||||
const translator = createTerraTranslator({
|
||||
locale: () => actx.locale,
|
||||
translate: () => actx.ts,
|
||||
translatePath: () => actx.t,
|
||||
runtime: () => actx.ling,
|
||||
});
|
||||
|
||||
let stepperInjected = $state(false);
|
||||
|
||||
function setLocale(locale: 'es' | 'en') {
|
||||
actx.ling.setLocale(locale);
|
||||
}
|
||||
|
||||
function injectStepperModule() {
|
||||
if (stepperInjected) return;
|
||||
|
||||
extendTerraTranslationModule(translator, {
|
||||
stepper: {
|
||||
next: {
|
||||
es: 'Siguiente paso',
|
||||
en: 'Next step',
|
||||
},
|
||||
previous: {
|
||||
es: 'Paso anterior',
|
||||
en: 'Previous step',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stepperInjected = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Terra Translator</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
<header class="hero">
|
||||
<p class="eyebrow">TerraConfig</p>
|
||||
<h1>Bridge reactivo con Ling</h1>
|
||||
<p class="lead">
|
||||
La demo arranca con un módulo `terra` base ya inyectado en Ling, lo expone a través de
|
||||
`actx` y lo consume desde `TerraConfig` mediante `createTerraTranslator(...)`.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" onclick={() => setLocale('es')}>Locale es</button>
|
||||
<button type="button" onclick={() => setLocale('en')}>Locale en</button>
|
||||
<button type="button" onclick={injectStepperModule} disabled={stepperInjected}>
|
||||
{stepperInjected ? 'Stepper ya inyectado' : 'Extender schema con stepper'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TerraConfig {translator}>
|
||||
<TerraLingPanel {stepperInjected} />
|
||||
</TerraConfig>
|
||||
|
||||
<article class="notes">
|
||||
<h2>Qué demuestra</h2>
|
||||
<ul>
|
||||
<li>`actx.locale` sigue siendo reactivo dentro de `terra`</li>
|
||||
<li>`LingString` e IDs `#?terra.*` se resuelven desde el adapter desde el primer render</li>
|
||||
<li>`ling.extend('terra', module)` invalida el panel en caliente con el módulo `stepper`</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
background: #0b1020;
|
||||
color: #e5e7eb;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.hero {
|
||||
margin-bottom: 1.25rem;
|
||||
padding: 1.5rem;
|
||||
border-radius: 18px;
|
||||
border: 1px solid #1f2a44;
|
||||
background: linear-gradient(180deg, rgba(20, 31, 56, 0.96), rgba(10, 15, 30, 0.96));
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 .35rem;
|
||||
font-size: .72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: #7dd3fc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
max-width: 62ch;
|
||||
margin: .65rem 0 0;
|
||||
line-height: 1.55;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: .75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .7rem 1rem;
|
||||
border: 1px solid #24324f;
|
||||
border-radius: 999px;
|
||||
background: #111827;
|
||||
color: #e5e7eb;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: .55;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.notes {
|
||||
margin-top: 1.25rem;
|
||||
padding: 1rem 1.15rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #1f2a44;
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.notes h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.notes ul {
|
||||
margin: .75rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { getTerraConfig } from '$uix/terra/config';
|
||||
|
||||
let { stepperInjected = false }: { stepperInjected?: boolean } = $props();
|
||||
|
||||
const config = getTerraConfig();
|
||||
|
||||
const translator = $derived(config.translator.current);
|
||||
const locale = $derived(translator?.getLocale() ?? 'n/a');
|
||||
const closeLabel = $derived(translator?.translate('#?terra.dialog.close|Close') ?? '(sin translator)');
|
||||
const removeFileLabel = $derived(translator?.translatePath('terra.fileUpload.removeFile') ?? '(sin translator)');
|
||||
const nextMonthLabel = $derived(translator?.translate('#?terra.calendar.nextMonth|Next month') ?? '(sin translator)');
|
||||
const stepperNextLabel = $derived(
|
||||
stepperInjected && translator
|
||||
? translator.translate('#?terra.stepper.next|Next step')
|
||||
: 'Pulsa "Extender schema con stepper"'
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="panel">
|
||||
<div class="row">
|
||||
<span>Locale reactivo</span>
|
||||
<strong>{locale}</strong>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>`ts(#?terra.dialog.close)`</span>
|
||||
<strong>{closeLabel}</strong>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>`t(terra.fileUpload.removeFile)`</span>
|
||||
<strong>{removeFileLabel}</strong>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>`ts(#?terra.calendar.nextMonth)`</span>
|
||||
<strong>{nextMonthLabel}</strong>
|
||||
</div>
|
||||
<div class="row row--hinted">
|
||||
<span>`ts(#?terra.stepper.next)`</span>
|
||||
<strong>{stepperNextLabel}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.panel {
|
||||
display: grid;
|
||||
gap: .75rem;
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #24324f;
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .8rem .9rem;
|
||||
border-radius: 12px;
|
||||
background: #111827;
|
||||
border: 1px solid #1f2a44;
|
||||
}
|
||||
|
||||
.row--hinted strong {
|
||||
color: #7dd3fc;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #94a3b8;
|
||||
font-size: .92rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: .95rem;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,412 @@
|
||||
# Terra Architecture
|
||||
|
||||
Documento de referencia para `src/uix/terra`.
|
||||
|
||||
`terra` es la capa de primitives headless del sistema. Su responsabilidad es resolver comportamiento, accesibilidad, composicion, estado y contrato publico estable. La apariencia vive fuera de `terra`.
|
||||
|
||||
## 1. Proposito
|
||||
|
||||
`terra` existe para dar una base comun sobre la que construir interfaces complejas sin repetir la misma logica de:
|
||||
|
||||
- contexto compartido
|
||||
- control de foco
|
||||
- teclado y puntero
|
||||
- `aria-*`
|
||||
- `data-*`
|
||||
- sincronizacion de estado
|
||||
- integracion con configuracion transversal
|
||||
|
||||
Su objetivo no es ser una capa visual ni de producto. `terra` define primitives reutilizables y predecibles; otras capas deciden el look and feel.
|
||||
|
||||
## 2. Motivacion
|
||||
|
||||
La motivacion de `terra` es evitar tres problemas habituales:
|
||||
|
||||
1. que cada componente invente su propio patron de estado y composicion
|
||||
2. que la accesibilidad y los contratos de estilo se resuelvan ad hoc en cada wrapper
|
||||
3. que la UI quede acoplada al runtime de app o a librerias externas concretas
|
||||
|
||||
`terra` responde a eso con una arquitectura uniforme:
|
||||
|
||||
- wrappers `.svelte` finos
|
||||
- comportamiento real en `*.svelte.ts`
|
||||
- contexto Svelte para estado compartido
|
||||
- contratos `data-*` formalizados y validados
|
||||
- `TerraConfig` para servicios transversales
|
||||
- fronteras explicitas con dependencias externas
|
||||
|
||||
## 3. Principios de diseno
|
||||
|
||||
`terra` sigue estas reglas:
|
||||
|
||||
- la API publica debe ser composable y declarativa
|
||||
- el estado complejo vive fuera del markup, en clases `State`
|
||||
- los hijos leen estado por contexto, no por prop drilling
|
||||
- el wrapper `.svelte` recibe props, crea estado, mezcla props y renderiza
|
||||
- los `data-*` son parte del contrato publico
|
||||
- la accesibilidad base no se delega al consumer
|
||||
- `terra` no se acopla a `actx`; consume contratos propios via `TerraConfig`
|
||||
- la apariencia no pertenece a `terra`
|
||||
|
||||
## 4. Superficie publica
|
||||
|
||||
La API publica de `terra` incluye:
|
||||
|
||||
- exports del barrel publico
|
||||
- props publicas de cada modulo
|
||||
- snippet props expuestas por los componentes
|
||||
- `data-*`
|
||||
- `aria-*` derivadas relevantes
|
||||
- comportamiento observable del primitive
|
||||
|
||||
Cambiar cualquiera de estas piezas es un cambio de contrato.
|
||||
|
||||
## 5. Estructura general
|
||||
|
||||
```text
|
||||
src/uix/terra/
|
||||
├── [componentes]/ primitive o familia concreta
|
||||
│ ├── components/ wrappers Svelte publicos
|
||||
│ ├── types.ts props publicas
|
||||
│ ├── [modulo].svelte.ts estado, contexto y comportamiento
|
||||
│ ├── exports.ts barrel del modulo
|
||||
│ └── index.ts reexport del modulo
|
||||
├── config/ configuracion transversal y servicios de runtime
|
||||
├── layers/ infraestructura UI interna
|
||||
├── dom/ utilitarios DOM y focus
|
||||
├── utils/ helpers puros o casi puros
|
||||
├── external/ fronteras con dependencias externas
|
||||
├── errors.ts
|
||||
└── index.ts barrel publico
|
||||
```
|
||||
|
||||
Regla interna:
|
||||
|
||||
- dentro de `terra` se importa por ruta relativa o por aliases `$terra/...`
|
||||
- nunca se importa internamente desde `$uix/terra`
|
||||
|
||||
## 6. Modelo de componente
|
||||
|
||||
La forma base de `terra` es `Elemento.SubElemento`.
|
||||
|
||||
Ejemplos:
|
||||
|
||||
- `Dialog.Root`
|
||||
- `Dialog.Trigger`
|
||||
- `Dialog.Content`
|
||||
- `Tabs.Root`
|
||||
- `Tabs.Trigger`
|
||||
- `DateField.Root`
|
||||
- `DateField.Segment`
|
||||
|
||||
### Root
|
||||
|
||||
El `Root` crea el estado central del componente y, cuando hace falta, lo pone en contexto.
|
||||
|
||||
### Subcomponentes
|
||||
|
||||
Los subcomponentes no reimplementan la logica del sistema. Leen el estado del `Root` y derivan de el sus props, `aria-*`, `data-*` y comportamiento.
|
||||
|
||||
### Portal
|
||||
|
||||
Cuando un componente usa `Portal`, los contextos se mantienen para que `Content`, `Overlay` y otras partes sigan viendo el estado del `Root` aunque rendericen fuera del arbol DOM inmediato.
|
||||
|
||||
## 7. Wrapper y state
|
||||
|
||||
### Wrapper `.svelte`
|
||||
|
||||
Responsabilidades del wrapper:
|
||||
|
||||
- leer props con `$props()`
|
||||
- crear ids y refs bindables
|
||||
- instanciar el `State`
|
||||
- combinar props externas con props derivadas via `mergeProps`
|
||||
- renderizar DOM o snippets
|
||||
|
||||
El wrapper debe ser pequeno. Si empieza a concentrar demasiada logica, la logica pertenece al `State`.
|
||||
|
||||
### `*.svelte.ts`
|
||||
|
||||
Responsabilidades del `State`:
|
||||
|
||||
- crear o leer contexto
|
||||
- modelar derived state
|
||||
- manejar teclado, puntero y focus
|
||||
- producir `aria-*`, `role` y `data-*`
|
||||
- coordinar subcomponentes
|
||||
- exponer props derivadas para el wrapper
|
||||
|
||||
## 8. Sistema reactivo
|
||||
|
||||
`terra` usa el sistema de `Active` y `State` de `utils/state/` para desacoplar comportamiento y markup sin perder reactividad.
|
||||
|
||||
Primitivos principales:
|
||||
|
||||
- `state(...)`
|
||||
- `readableActive(...)`
|
||||
- `writableActive(...)`
|
||||
- `readonly(...)`
|
||||
|
||||
La idea es simple:
|
||||
|
||||
- los valores complejos no viajan como primitivos sueltos
|
||||
- viajan como cajas reactivas con `.current`
|
||||
- eso permite componer estado entre clases, helpers y wrappers
|
||||
|
||||
## 9. Contrato `data-*`
|
||||
|
||||
`terra` trata los `data-*` como API publica formal.
|
||||
|
||||
La convencion oficial es:
|
||||
|
||||
- root real: `data-{component}`
|
||||
- parte: `data-{component}-{part}`
|
||||
- estado compartido: `data-state`, `data-disabled`, `data-selected`, `data-orientation`, `data-side`, etc.
|
||||
|
||||
Reglas:
|
||||
|
||||
- no usar `data-*-root`
|
||||
- no usar `data-terra-*`
|
||||
- no mezclar sistemas hibridos
|
||||
- si un componente no renderiza un root real, no se inventa un root attr solo por simetria
|
||||
|
||||
Ejemplos correctos:
|
||||
|
||||
- `data-dialog`
|
||||
- `data-dialog-content`
|
||||
- `data-select-item`
|
||||
- `data-tabs-trigger`
|
||||
- `data-state='open'`
|
||||
- `data-disabled`
|
||||
|
||||
## 10. Contratos formales y validacion
|
||||
|
||||
El sistema de contratos de `terra` vive en:
|
||||
|
||||
- `src/uix/terra/utils/contracts.ts`
|
||||
|
||||
Y se proyecta a documentacion generada en:
|
||||
|
||||
- `DATA_ATTRS.md`
|
||||
|
||||
La validacion runtime en desarrollo se hace mediante:
|
||||
|
||||
- `assertDataContract(...)`
|
||||
|
||||
Regla practica:
|
||||
|
||||
- si un `State` genera `data-*` publicos, debe validar contra el contrato
|
||||
- `contracts.ts` es la fuente de verdad del contrato publico
|
||||
- `DATA_ATTRS.md` es documentacion generada, no editada a mano
|
||||
|
||||
## 11. Texto funcional y accesibilidad
|
||||
|
||||
`terra` puede tener texto propio solo cuando forma parte de la semantica funcional del primitive.
|
||||
|
||||
Ejemplos legitimos:
|
||||
|
||||
- `Close`
|
||||
- `Cancel`
|
||||
- `Next month`
|
||||
- `Remove file`
|
||||
- `Dismiss notification`
|
||||
- labels de segmentos de fecha/hora
|
||||
|
||||
No pertenece a `terra`:
|
||||
|
||||
- copy editorial de producto
|
||||
- mensajes de negocio
|
||||
- placeholders de app
|
||||
- contenido opinionated de una UI concreta
|
||||
|
||||
## 12. `Trans` y `translator`
|
||||
|
||||
`terra` expone un componente `Trans` y un servicio `translator` en `TerraConfig`.
|
||||
|
||||
Su objetivo es resolver texto funcional sin acoplar `terra` a un runtime global concreto.
|
||||
|
||||
El contrato de `translator` es propio de `terra`:
|
||||
|
||||
- `getLocale()`
|
||||
- `translate(value)`
|
||||
- `translatePath(path, ...args)`
|
||||
- `extend(...)` opcional
|
||||
|
||||
`terra` no depende de `Ling`, `actx` ni de otra libreria concreta desde los primitives. Si el ecosistema quiere enchufar un runtime de traduccion, lo hace a traves del contrato `translator`.
|
||||
|
||||
## 13. `TerraConfig`
|
||||
|
||||
`TerraConfig` es la puerta de entrada para servicios transversales. No es un saco de defaults historicos; es un contenedor de capacidades de runtime y presentacion.
|
||||
|
||||
Hoy `TerraConfig` admite:
|
||||
|
||||
- `translator`
|
||||
- `dateTimeFormatter`
|
||||
- `numberFormatter`
|
||||
- `currencyFormatter`
|
||||
- `unitFormatter`
|
||||
- `logger`
|
||||
- `dir`
|
||||
- `portalTo`
|
||||
|
||||
### `translator`
|
||||
|
||||
Responsable de:
|
||||
|
||||
- locale activa
|
||||
- traduccion de `TerraTranslationValue`
|
||||
- traduccion por path
|
||||
- extension opcional de modulos de traduccion
|
||||
|
||||
### `dateTimeFormatter`
|
||||
|
||||
Responsable de:
|
||||
|
||||
- politica de orden de fecha
|
||||
- politica de formato horario
|
||||
|
||||
### `numberFormatter`, `currencyFormatter`, `unitFormatter`
|
||||
|
||||
Responsables de la presentacion numerica y de dominio, sin acoplar los primitives a una libreria concreta de formato.
|
||||
|
||||
### `logger`
|
||||
|
||||
Servicio de logging inyectable de `terra`.
|
||||
|
||||
### `dir`
|
||||
|
||||
Direccion visual efectiva para primitives que dependen de LTR o RTL.
|
||||
|
||||
### `portalTo`
|
||||
|
||||
Target por defecto de montaje para primitives que usan portal.
|
||||
|
||||
## 14. Como interactua `TerraConfig` con `terra`
|
||||
|
||||
La integracion sigue este flujo:
|
||||
|
||||
1. `TerraConfig` recibe servicios y valores en su wrapper Svelte
|
||||
2. el wrapper los convierte a `Active<T>` con `readableActive(...)`
|
||||
3. `useTerraConfig(...)` los inserta en contexto
|
||||
4. `getTerraConfig()` expone la configuracion resuelta al resto de `terra`
|
||||
5. cada primitive decide si consume o no cada servicio
|
||||
|
||||
La resolucion sigue prioridad por capas:
|
||||
|
||||
1. prop explicita del componente
|
||||
2. valor disponible en `TerraConfig`
|
||||
3. fallback interno del primitive
|
||||
|
||||
Ademas, `TerraConfig` soporta anidacion. Un `TerraConfig` hijo puede sobrescribir solo una parte del contexto y heredar el resto del padre.
|
||||
|
||||
## 15. Fronteras externas
|
||||
|
||||
`terra` distingue entre:
|
||||
|
||||
- `config/`: configuracion y servicios inyectables
|
||||
- `external/`: fronteras con dependencias externas
|
||||
|
||||
Ejemplo:
|
||||
|
||||
- `external/dates/` existe para desacoplar `terra` de la libreria de fechas concreta
|
||||
|
||||
La idea es que un primitive no importe directamente una dependencia de dominio externa si puede hacerlo a traves de una frontera formal.
|
||||
|
||||
## 16. Layers, DOM y utils
|
||||
|
||||
### `layers/`
|
||||
|
||||
Contiene primitives internas de infraestructura como:
|
||||
|
||||
- portal
|
||||
- floating
|
||||
- presence
|
||||
- dismissible
|
||||
- focus-scope
|
||||
- arrow
|
||||
|
||||
No son necesariamente API publica de consumo directo, pero sostienen muchos primitives.
|
||||
|
||||
### `dom/`
|
||||
|
||||
Contiene logica dependiente del DOM real:
|
||||
|
||||
- focus helpers
|
||||
- navegacion por flechas
|
||||
- roving focus
|
||||
- utilitarios browser
|
||||
|
||||
### `utils/`
|
||||
|
||||
Contiene helpers puros o casi puros:
|
||||
|
||||
- attrs
|
||||
- merge de props
|
||||
- eventos
|
||||
- estado reactivo
|
||||
- datetime helpers
|
||||
- types internos
|
||||
|
||||
Regla:
|
||||
|
||||
- si una pieza depende del DOM real, no pertenece a `utils/`
|
||||
- si es configuracion/runtime inyectable, no pertenece a `utils/`
|
||||
|
||||
## 17. Frontera con `air`
|
||||
|
||||
La frontera entre `terra` y `air` es deliberada.
|
||||
|
||||
`terra` aporta:
|
||||
|
||||
- comportamiento
|
||||
- accesibilidad
|
||||
- contratos `data-*`
|
||||
- contexto y composicion
|
||||
- servicios de runtime
|
||||
|
||||
`air` debe aportar:
|
||||
|
||||
- apariencia
|
||||
- tono visual
|
||||
- decisiones de diseno opinionated
|
||||
- estilos basados en contratos publicos de `terra`
|
||||
|
||||
`air` no debe depender de detalles privados de implementacion de `terra`.
|
||||
|
||||
## 18. Anti-patrones
|
||||
|
||||
Evitar en `terra`:
|
||||
|
||||
- logica compleja dentro del wrapper `.svelte`
|
||||
- props drilling cuando el patron correcto es contexto
|
||||
- `data-*` fuera de contrato
|
||||
- inventar nombres de parts sin revisar anatomias similares
|
||||
- acoplar primitives a `actx`
|
||||
- meter copy de producto dentro del primitive
|
||||
- usar internamente `$uix/terra` para imports
|
||||
- mezclar configuracion inyectable con fronteras externas
|
||||
|
||||
## 19. Regla de estabilidad
|
||||
|
||||
Un primitive de `terra` se considera estable cuando:
|
||||
|
||||
- su API publica esta clara
|
||||
- sus `data-*` estan en `contracts.ts`
|
||||
- valida con `assertDataContract(...)`
|
||||
- el wrapper y el `State` siguen el patron general
|
||||
- su accesibilidad base esta resuelta
|
||||
- no depende de hacks locales o de una demo para sostenerse
|
||||
|
||||
## 20. Resumen
|
||||
|
||||
`terra` no es una coleccion de componentes visuales. Es una base arquitectonica para primitives headless del sistema.
|
||||
|
||||
Su valor esta en combinar:
|
||||
|
||||
- contratos publicos claros
|
||||
- comportamiento reutilizable
|
||||
- accesibilidad integrada
|
||||
- configuracion transversal estable
|
||||
- desacoplo respecto al runtime global y a librerias externas
|
||||
|
||||
Si una nueva pieza no mejora esa base comun, probablemente no pertenece a `terra`.
|
||||
@ -0,0 +1,6 @@
|
||||
import { createTerraAttrs } from '../utils/attrs';
|
||||
|
||||
export const colorPickerAttrs = createTerraAttrs({
|
||||
component: 'color-picker',
|
||||
parts: ['trigger', 'content', 'area', 'area-thumb', 'hue-slider', 'alpha-slider', 'input', 'swatches', 'swatch', 'eyedropper'] as const,
|
||||
});
|
||||
@ -1,61 +1,42 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* # Color Picker Area Thumb
|
||||
*
|
||||
* Indicador visual de la posición actual en el área de color.
|
||||
*
|
||||
* ## Uso
|
||||
* ```svelte
|
||||
* <ColorPickerArea>
|
||||
* <ColorPickerAreaThumb />
|
||||
* </ColorPickerArea>
|
||||
* ```
|
||||
*
|
||||
* ## Propiedades
|
||||
* - `class`: string - Clases CSS adicionales
|
||||
*
|
||||
* ## Comportamiento
|
||||
* - Posicionado con `left` y `top` según la saturación y brillo
|
||||
* - Soporta navegación con flechas del teclado
|
||||
* - Usa Shift+flechas para movimientos de 10 unidades
|
||||
* - Color disponible como `--cp-thumb-color`
|
||||
*
|
||||
* ## Selectores CSS
|
||||
* ```
|
||||
* [data-color-picker-area-thumb] → Indicador visual de posición en el área de color
|
||||
* ```
|
||||
*
|
||||
* ## Estilos
|
||||
* Renderiza un elemento `<div>` con role="slider".
|
||||
*/
|
||||
import { ColorPickerAreaState, ColorPickerRootState } from "../color-picker.svelte";
|
||||
import type { ColorPickerAreaThumbProps } from "../types";
|
||||
import { assertDataContract } from '$terra/utils/contracts';
|
||||
import { colorPickerAttrs } from '../color-picker-attrs';
|
||||
import { ColorPickerAreaState, ColorPickerRootState } from '../color-picker.svelte';
|
||||
import type { ColorPickerAreaThumbProps } from '../types';
|
||||
|
||||
let { class: className }: ColorPickerAreaThumbProps = $props();
|
||||
|
||||
const root = ColorPickerRootState.get();
|
||||
const area = ColorPickerAreaState.create({});
|
||||
|
||||
const attrs = $derived.by(() => {
|
||||
const attrs = {
|
||||
role: 'slider' as const,
|
||||
tabindex: 0,
|
||||
'aria-label': root.translate('#?terra.colorPicker.saturationBrightness|Color saturation and brightness', 'Color saturation and brightness'),
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
'aria-valuenow': Math.round(root.value.current.hsv.s),
|
||||
style: `left: ${area.thumbStyle.current.left}; top: ${area.thumbStyle.current.top}; --cp-thumb-color: ${root.hex.current};`,
|
||||
[colorPickerAttrs['area-thumb']]: '',
|
||||
} as const;
|
||||
|
||||
assertDataContract('ColorPicker', 'area-thumb', attrs);
|
||||
|
||||
return attrs;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={className}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Color saturation and brightness"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={Math.round(root.value.current.hsv.s)}
|
||||
style="
|
||||
left: {area.thumbStyle.current.left};
|
||||
top: {area.thumbStyle.current.top};
|
||||
--cp-thumb-color: {root.hex.current};
|
||||
"
|
||||
{...attrs}
|
||||
onkeydown={(e) => {
|
||||
const { s, v } = root.value.current.hsv;
|
||||
const step = e.shiftKey ? 10 : 1;
|
||||
if (e.key === "ArrowRight") root.setSaturationValue(Math.min(s + step, 100), v);
|
||||
if (e.key === "ArrowLeft") root.setSaturationValue(Math.max(s - step, 0), v);
|
||||
if (e.key === "ArrowUp") root.setSaturationValue(s, Math.min(v + step, 100));
|
||||
if (e.key === "ArrowDown") root.setSaturationValue(s, Math.max(v - step, 0));
|
||||
if (e.key === 'ArrowRight') root.setSaturationValue(Math.min(s + step, 100), v);
|
||||
if (e.key === 'ArrowLeft') root.setSaturationValue(Math.max(s - step, 0), v);
|
||||
if (e.key === 'ArrowUp') root.setSaturationValue(s, Math.min(v + step, 100));
|
||||
if (e.key === 'ArrowDown') root.setSaturationValue(s, Math.max(v - step, 0));
|
||||
}}
|
||||
></div>
|
||||
|
||||
|
||||
@ -1,45 +1,30 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* # Color Picker Content
|
||||
*
|
||||
* Contenedor del panel del selector de color.
|
||||
*
|
||||
* ## Uso
|
||||
* ```svelte
|
||||
* <ColorPickerContent>
|
||||
* <ColorPickerArea>
|
||||
* <ColorPickerAreaThumb />
|
||||
* </ColorPickerArea>
|
||||
* <ColorPickerHueSlider />
|
||||
* <ColorPickerAlphaSlider />
|
||||
* <ColorPickerInput />
|
||||
* </ColorPickerContent>
|
||||
* ```
|
||||
*
|
||||
* ## Propiedades
|
||||
* - `class`: string - Clases CSS adicionales
|
||||
*
|
||||
* ## Selectores CSS
|
||||
* ```
|
||||
* No aplica selectores de datos específicos.
|
||||
* Use el elemento contenedor con role="dialog" para aplicar estilos.
|
||||
* ```
|
||||
*
|
||||
* ## Estilos
|
||||
* Renderiza un elemento `<div>` con role="dialog".
|
||||
*/
|
||||
import type { ColorPickerContentProps } from "../types";
|
||||
import { assertDataContract } from '$terra/utils/contracts';
|
||||
import { colorPickerAttrs } from '../color-picker-attrs';
|
||||
import { ColorPickerRootState } from '../color-picker.svelte';
|
||||
import type { ColorPickerContentProps } from '../types';
|
||||
|
||||
let {
|
||||
class: className,
|
||||
children,
|
||||
}: ColorPickerContentProps & { children?: import("svelte").Snippet } = $props();
|
||||
}: ColorPickerContentProps & { children?: import('svelte').Snippet } = $props();
|
||||
|
||||
const root = ColorPickerRootState.get();
|
||||
|
||||
const attrs = $derived.by(() => {
|
||||
const attrs = {
|
||||
role: 'dialog' as const,
|
||||
'aria-label': root.translate('#?terra.colorPicker.dialogLabel|Color picker', 'Color picker'),
|
||||
[colorPickerAttrs.content]: '',
|
||||
} as const;
|
||||
|
||||
assertDataContract('ColorPicker', 'content', attrs);
|
||||
|
||||
return attrs;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={className}
|
||||
role="dialog"
|
||||
aria-label="Color picker"
|
||||
>
|
||||
<div class={className} {...attrs}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,49 +1,35 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* # Color Picker Eye Dropper
|
||||
*
|
||||
* Botón para abrir la herramienta de selección de color del navegador.
|
||||
*
|
||||
* ## Uso
|
||||
* ```svelte
|
||||
* <ColorPickerEyeDropper>
|
||||
* <EyeDropperIcon />
|
||||
* </ColorPickerEyeDropper>
|
||||
* ```
|
||||
*
|
||||
* ## Propiedades
|
||||
* - `class`: string - Clases CSS adicionales
|
||||
* - `children`: Snippet - Contenido del botón
|
||||
*
|
||||
* ## Comportamiento
|
||||
* - Solo se renderiza si el navegador soporta la API EyeDropper
|
||||
* - Al hacer clic, abre el selector de color del sistema
|
||||
*
|
||||
* ## Selectores CSS
|
||||
* ```
|
||||
* [data-color-picker-eyedropper] → Botón para abrir la herramienta de selección de color
|
||||
* ```
|
||||
*
|
||||
* ## Estilos
|
||||
* Renderiza un elemento `<button>` si es soportado.
|
||||
*/
|
||||
import { ColorPickerEyeDropperState } from "../color-picker.svelte";
|
||||
import type { ColorPickerEyeDropperProps } from "../types";
|
||||
import { assertDataContract } from '$terra/utils/contracts';
|
||||
import { colorPickerAttrs } from '../color-picker-attrs';
|
||||
import { ColorPickerEyeDropperState } from '../color-picker.svelte';
|
||||
import type { ColorPickerEyeDropperProps } from '../types';
|
||||
|
||||
let { class: className, children }: ColorPickerEyeDropperProps & {
|
||||
children?: import("svelte").Snippet;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
const eyedropper = ColorPickerEyeDropperState.create({});
|
||||
|
||||
const attrs = $derived.by(() => {
|
||||
const attrs = {
|
||||
type: 'button' as const,
|
||||
'aria-label': eyedropper.root.translate('#?terra.colorPicker.eyeDropper|Pick color from screen', 'Pick color from screen'),
|
||||
[colorPickerAttrs.eyedropper]: '',
|
||||
} as const;
|
||||
|
||||
assertDataContract('ColorPicker', 'eyedropper', attrs);
|
||||
|
||||
return attrs;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if eyedropper.isSupported}
|
||||
<button
|
||||
type="button"
|
||||
class={className}
|
||||
aria-label="Pick color from screen"
|
||||
{...attrs}
|
||||
onclick={() => eyedropper.open()}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
||||
@ -1,57 +1,37 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* # Color Picker Trigger
|
||||
*
|
||||
* Botón que abre/cierra el panel del selector de color.
|
||||
*
|
||||
* ## Uso
|
||||
* ```svelte
|
||||
* <ColorPicker bind:value={color}>
|
||||
* <ColorPickerTrigger showColor={true}>
|
||||
* <div class="preview" />
|
||||
* </ColorPickerTrigger>
|
||||
* <ColorPickerContent>...</ColorPickerContent>
|
||||
* </ColorPicker>
|
||||
* ```
|
||||
*
|
||||
* ## Propiedades
|
||||
* - `showColor`: boolean - Muestra el color como CSS variable `--cp-trigger-color`
|
||||
*
|
||||
* ## Selectores CSS
|
||||
* ```
|
||||
* [data-disabled] → Presente cuando disabled=true
|
||||
* ```
|
||||
*
|
||||
* ## CSS Variables
|
||||
* ```
|
||||
* --cp-trigger-color → Color actual en formato HEX
|
||||
* ```
|
||||
*
|
||||
* ## Estilos
|
||||
* Renderiza un elemento `<button>`. Usa `data-disabled` cuando está deshabilitado.
|
||||
*/
|
||||
import { ColorPickerRootState } from "../color-picker.svelte";
|
||||
import type { ColorPickerTriggerProps } from "../types";
|
||||
import { assertDataContract } from '$terra/utils/contracts';
|
||||
import { colorPickerAttrs } from '../color-picker-attrs';
|
||||
import { ColorPickerRootState } from '../color-picker.svelte';
|
||||
import type { ColorPickerTriggerProps } from '../types';
|
||||
|
||||
let {
|
||||
showColor = true,
|
||||
children,
|
||||
...restProps
|
||||
}: ColorPickerTriggerProps & {
|
||||
children?: import("svelte").Snippet;
|
||||
children?: import('svelte').Snippet;
|
||||
onclick?: (e: MouseEvent) => void;
|
||||
} = $props();
|
||||
|
||||
const root = ColorPickerRootState.get();
|
||||
|
||||
const attrs = $derived.by(() => {
|
||||
const attrs = {
|
||||
type: 'button' as const,
|
||||
'aria-label': root.translate('#?terra.colorPicker.trigger|Pick a color', 'Pick a color'),
|
||||
'aria-disabled': root.disabled.current,
|
||||
'data-disabled': root.disabled.current ? '' : undefined,
|
||||
style: showColor ? `--cp-trigger-color: ${root.hex.current}` : undefined,
|
||||
[colorPickerAttrs.trigger]: '',
|
||||
} as const;
|
||||
|
||||
assertDataContract('ColorPicker', 'trigger', attrs);
|
||||
|
||||
return attrs;
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Pick a color"
|
||||
aria-disabled={root.disabled.current}
|
||||
data-disabled={root.disabled.current ? "" : undefined}
|
||||
style={showColor ? `--cp-trigger-color: ${root.hex.current}` : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
<button {...restProps} {...attrs}>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import type { TerraCurrencyFormatter } from './types';
|
||||
import { createTerraFormatter, type TerraFormatterSource } from './formatter.svelte.ts';
|
||||
|
||||
export type TerraCurrencyFormatterSource = TerraFormatterSource;
|
||||
|
||||
export function createTerraCurrencyFormatter(
|
||||
source: TerraCurrencyFormatterSource
|
||||
): TerraCurrencyFormatter {
|
||||
return createTerraFormatter(source);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import type { Getter } from '$terra/utils';
|
||||
import type { DateOrder, HourCycle } from '$terra/external/dates';
|
||||
import type { TerraDateTimeFormatter } from './types';
|
||||
|
||||
export type TerraDateTimeFormatterSource = {
|
||||
dateOrder?: Getter<DateOrder | undefined>;
|
||||
timeFormat?: Getter<HourCycle | undefined>;
|
||||
};
|
||||
|
||||
export function createTerraDateTimeFormatter(
|
||||
source: TerraDateTimeFormatterSource
|
||||
): TerraDateTimeFormatter {
|
||||
return {
|
||||
getDateOrder() {
|
||||
return source.dateOrder?.();
|
||||
},
|
||||
getTimeFormat() {
|
||||
return source.timeFormat?.();
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import type { Getter } from '$terra/utils';
|
||||
|
||||
export type TerraFormatter = {
|
||||
format: (...args: unknown[]) => string;
|
||||
};
|
||||
|
||||
export type TerraFormatterSource = {
|
||||
format: Getter<(...args: unknown[]) => string>;
|
||||
};
|
||||
|
||||
export function createTerraFormatter(source: TerraFormatterSource): TerraFormatter {
|
||||
return {
|
||||
format(...args: unknown[]) {
|
||||
return source.format()(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export * from "./exports.ts";
|
||||
export * from './exports.ts';
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
export interface TerraLogger {
|
||||
debug(message: string, context?: Record<string, unknown>): void;
|
||||
info(message: string, context?: Record<string, unknown>): void;
|
||||
warn(message: string, context?: Record<string, unknown>): void;
|
||||
error(message: string, context?: Record<string, unknown>): void;
|
||||
}
|
||||
|
||||
const consoleLogger: TerraLogger = {
|
||||
debug: (msg, ctx) => (ctx ? console.debug(msg, ctx) : console.debug(msg)),
|
||||
info: (msg, ctx) => (ctx ? console.info(msg, ctx) : console.info(msg)),
|
||||
warn: (msg, ctx) => (ctx ? console.warn(msg, ctx) : console.warn(msg)),
|
||||
error: (msg, ctx) => (ctx ? console.error(msg, ctx) : console.error(msg)),
|
||||
};
|
||||
|
||||
let logger: TerraLogger = consoleLogger;
|
||||
|
||||
export function configureTerraLogger(nextLogger: TerraLogger): void {
|
||||
logger = nextLogger;
|
||||
}
|
||||
|
||||
export function getTerraLogger(): TerraLogger {
|
||||
return logger;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import type { TerraNumberFormatter } from './types';
|
||||
import { createTerraFormatter, type TerraFormatterSource } from './formatter.svelte.ts';
|
||||
|
||||
export type TerraNumberFormatterSource = TerraFormatterSource;
|
||||
|
||||
export function createTerraNumberFormatter(
|
||||
source: TerraNumberFormatterSource
|
||||
): TerraNumberFormatter {
|
||||
return createTerraFormatter(source);
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
import { onDestroyEffect, type Getter } from '$terra/utils';
|
||||
import type { TerraTranslationModule, TerraTranslationValue, TerraTranslator } from './types';
|
||||
|
||||
const TRANSLATION_ID_PREFIX = '#?';
|
||||
const TRANSLATION_ID_FALLBACK_SEPARATOR = '|';
|
||||
|
||||
type TerraTranslatorRuntime = {
|
||||
extend?: (...args: any[]) => void;
|
||||
onSchemaChange?: (callback: () => void) => (() => void) | void;
|
||||
};
|
||||
|
||||
export type TerraTranslatorSource = {
|
||||
locale: Getter<string>;
|
||||
translate?: Getter<(value: any) => string>;
|
||||
translatePath?: Getter<(path: string, ...args: unknown[]) => string>;
|
||||
runtime?: Getter<TerraTranslatorRuntime | undefined>;
|
||||
};
|
||||
|
||||
export function createTerraTranslator(source: TerraTranslatorSource): TerraTranslator {
|
||||
let schemaVersion = $state(0);
|
||||
|
||||
const runtime = source.runtime?.();
|
||||
const dispose = runtime?.onSchemaChange?.(() => {
|
||||
schemaVersion += 1;
|
||||
});
|
||||
|
||||
if (dispose) onDestroyEffect(dispose);
|
||||
|
||||
return {
|
||||
getLocale() {
|
||||
schemaVersion;
|
||||
return source.locale();
|
||||
},
|
||||
translate(value: TerraTranslationValue) {
|
||||
source.locale();
|
||||
schemaVersion;
|
||||
const translate = source.translate?.();
|
||||
return translate ? translate(value) : String(value ?? '');
|
||||
},
|
||||
translatePath(path: string, ...args: unknown[]) {
|
||||
source.locale();
|
||||
schemaVersion;
|
||||
const translatePath = source.translatePath?.();
|
||||
return translatePath ? translatePath(path, ...args) : path;
|
||||
},
|
||||
extend(namespace: string, module: TerraTranslationModule) {
|
||||
runtime?.extend?.(namespace, module);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function extendTerraTranslationModule(
|
||||
target: Pick<TerraTranslator, 'extend'> | Pick<TerraTranslatorRuntime, 'extend'>,
|
||||
module: TerraTranslationModule,
|
||||
namespace = 'terra'
|
||||
) {
|
||||
target.extend?.(namespace, module);
|
||||
}
|
||||
|
||||
function getInlineTranslationFallback(value: TerraTranslationValue): string | undefined {
|
||||
if (typeof value !== 'string') return undefined;
|
||||
if (!value.startsWith(TRANSLATION_ID_PREFIX)) return undefined;
|
||||
|
||||
const raw = value.slice(TRANSLATION_ID_PREFIX.length);
|
||||
const separatorIndex = raw.indexOf(TRANSLATION_ID_FALLBACK_SEPARATOR);
|
||||
if (separatorIndex === -1) return undefined;
|
||||
|
||||
return raw.slice(separatorIndex + TRANSLATION_ID_FALLBACK_SEPARATOR.length);
|
||||
}
|
||||
|
||||
export function resolveTerraTranslation(
|
||||
value: TerraTranslationValue,
|
||||
translator: TerraTranslator | undefined,
|
||||
fallback = ''
|
||||
): string {
|
||||
if (translator) return translator.translate(value);
|
||||
|
||||
const inlineFallback = getInlineTranslationFallback(value);
|
||||
if (inlineFallback !== undefined) return inlineFallback;
|
||||
if (typeof value === 'string') return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function resolveTerraTranslationPath(
|
||||
path: string,
|
||||
translator: TerraTranslator | undefined,
|
||||
fallback = path,
|
||||
...args: unknown[]
|
||||
): string {
|
||||
if (!translator) return fallback;
|
||||
return translator.translatePath(path, ...args);
|
||||
}
|
||||
|
||||
|
||||
@ -1,84 +1,53 @@
|
||||
import type { Direction, WithChildren } from '$terra/utils';
|
||||
import type { TerraLogger } from '$terra/utils/logger';
|
||||
import type { DateOrder, HourCycle } from '$terra/adapters/dates';
|
||||
import type { DateOrder, HourCycle } from '$terra/external/dates';
|
||||
import type { PortalTarget } from '../layers/portal/types.ts';
|
||||
import type { TerraFormatter } from './formatter.svelte.ts';
|
||||
|
||||
export type TerraConfigPropsWithoutChildren = {
|
||||
/**
|
||||
* The default portal `to`/target to use for the `Portal` components throughout the app.
|
||||
*/
|
||||
defaultPortalTo?: PortalTarget;
|
||||
|
||||
/**
|
||||
* The default locale to use for the components that support localization.
|
||||
*/
|
||||
defaultLocale?: string;
|
||||
export type TerraLogger = import('./logger').TerraLogger;
|
||||
export type TerraTranslationModule = unknown;
|
||||
export type TerraTranslationValue = unknown;
|
||||
|
||||
/**
|
||||
* The default direction to use for direction-aware components.
|
||||
*
|
||||
* @defaultValue 'ltr'
|
||||
*/
|
||||
defaultDir?: Direction;
|
||||
export type TerraTranslator = {
|
||||
getLocale: () => string;
|
||||
translate: (value: TerraTranslationValue) => string;
|
||||
translatePath: (path: string, ...args: unknown[]) => string;
|
||||
extend?: (...args: any[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The default date part order to use for date-aware components.
|
||||
*/
|
||||
defaultDateOrder?: DateOrder;
|
||||
export type TerraDateTimeFormatter = {
|
||||
getDateOrder: () => DateOrder | undefined;
|
||||
getTimeFormat: () => HourCycle | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* The default time format to use for time-aware components.
|
||||
*/
|
||||
defaultTimeFormat?: HourCycle;
|
||||
export type TerraNumberFormatter = TerraFormatter;
|
||||
export type TerraCurrencyFormatter = TerraFormatter;
|
||||
export type TerraUnitFormatter = TerraFormatter;
|
||||
|
||||
/**
|
||||
* Logger instance to use for terra internal warnings and errors.
|
||||
* If not provided, terra falls back to console.*.
|
||||
* Use `configureTerraLogger()` as an alternative for non-Svelte environments.
|
||||
*/
|
||||
export type TerraConfigPropsWithoutChildren = {
|
||||
portalTo?: PortalTarget;
|
||||
translator?: TerraTranslator;
|
||||
dir?: Direction;
|
||||
dateTimeFormatter?: TerraDateTimeFormatter;
|
||||
numberFormatter?: TerraNumberFormatter;
|
||||
currencyFormatter?: TerraCurrencyFormatter;
|
||||
unitFormatter?: TerraUnitFormatter;
|
||||
logger?: TerraLogger;
|
||||
};
|
||||
|
||||
export type TerraConfigProps = WithChildren<TerraConfigPropsWithoutChildren>;
|
||||
|
||||
export type TerraPresentationProps = {
|
||||
/**
|
||||
* The locale to use for formatting localized components.
|
||||
*
|
||||
* @defaultValue 'en'
|
||||
*/
|
||||
locale?: string;
|
||||
|
||||
/**
|
||||
* The direction to use for direction-aware components.
|
||||
*
|
||||
* @defaultValue 'ltr'
|
||||
*/
|
||||
dir?: Direction;
|
||||
};
|
||||
|
||||
export type TerraDatePresentationProps = TerraPresentationProps & {
|
||||
/**
|
||||
* Preferred order for date segments in date-aware components.
|
||||
*
|
||||
* @defaultValue 'DMY'
|
||||
*/
|
||||
dateOrder?: DateOrder;
|
||||
};
|
||||
|
||||
export type TerraDateTimePresentationProps = TerraDatePresentationProps & {
|
||||
/**
|
||||
* The format to use for displaying time segments.
|
||||
*
|
||||
* @defaultValue the locale's default time format
|
||||
*/
|
||||
hourCycle?: HourCycle;
|
||||
|
||||
/**
|
||||
* Preferred time format for time-aware components.
|
||||
* When provided, it overrides `hourCycle`.
|
||||
*
|
||||
* @defaultValue '24h'
|
||||
*/
|
||||
timeFormat?: HourCycle;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import type { TerraUnitFormatter } from './types';
|
||||
import { createTerraFormatter, type TerraFormatterSource } from './formatter.svelte.ts';
|
||||
|
||||
export type TerraUnitFormatterSource = TerraFormatterSource;
|
||||
|
||||
export function createTerraUnitFormatter(
|
||||
source: TerraUnitFormatterSource
|
||||
): TerraUnitFormatter {
|
||||
return createTerraFormatter(source);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue