From 29e61682612e42c8aa9e3621ab730c9d62c613fb Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 30 Mar 2026 14:18:35 +0200 Subject: [PATCH] Add terra date domain adapter --- src/uix/terra/README.md | 183 +++++++++++++++++- src/uix/terra/adapters/dates/index.ts | 2 + src/uix/terra/base/config/prop-resolvers.ts | 2 +- src/uix/terra/base/config/types.ts | 2 +- src/uix/terra/calendar/calendar.svelte.ts | 4 +- .../terra/calendar/components/calendar.svelte | 4 +- src/uix/terra/calendar/types.ts | 4 +- .../date-field/components/date-field.svelte | 2 +- src/uix/terra/date-field/date-field.svelte.ts | 6 +- src/uix/terra/date-field/types.ts | 2 +- .../date-picker/components/date-picker.svelte | 4 +- .../terra/date-picker/date-picker.svelte.ts | 4 +- src/uix/terra/date-picker/types.ts | 4 +- .../components/date-range-field.svelte | 6 +- .../date-range-field.svelte.ts | 6 +- src/uix/terra/date-range-field/types.ts | 4 +- .../components/date-range-picker.svelte | 4 +- .../date-range-picker.svelte.ts | 4 +- src/uix/terra/date-range-picker/types.ts | 4 +- .../components/range-calendar.svelte | 4 +- .../range-calendar/range-calendar.svelte.ts | 2 +- src/uix/terra/range-calendar/types.ts | 4 +- .../time-field/components/time-field.svelte | 2 +- src/uix/terra/time-field/time-field.svelte.ts | 2 +- src/uix/terra/time-field/types.ts | 2 +- .../components/time-range-field.svelte | 2 +- .../time-range-field.svelte.ts | 6 +- src/uix/terra/time-range-field/types.ts | 2 +- .../utils/datetime/calendar-helpers.svelte.ts | 2 +- src/uix/terra/utils/datetime/formatter.ts | 2 +- src/uix/terra/utils/datetime/helpers.ts | 4 +- src/uix/terra/utils/datetime/time-helpers.ts | 6 +- 32 files changed, 230 insertions(+), 61 deletions(-) create mode 100644 src/uix/terra/adapters/dates/index.ts diff --git a/src/uix/terra/README.md b/src/uix/terra/README.md index 00dc6626a..f46b337de 100644 --- a/src/uix/terra/README.md +++ b/src/uix/terra/README.md @@ -21,7 +21,7 @@ La idea es que, cuando creemos un componente nuevo, no inventemos otro patron di - el estado complejo vive fuera del markup, normalmente en clases `State` - los componentes hijos (`Input`, `Trigger`, `Content`, `Segment`, etc.) leen estado mediante contexto, no por prop drilling - los defaults globales viven en `TerraConfig`, pero los componentes no deben acoplarse a `actx` -- la logica de dominio compartida debe ir a `glob` o a `utils`, no enterrada en un componente concreto +- la logica de dominio compartida debe vivir fuera del componente; si `terra` necesita un dominio externo, debe entrar por un adapter claro - los wrappers `.svelte` deben ser pequenos: recibir props, crear estado, mezclar atributos y renderizar ## 2. Estructura base de la libreria @@ -211,7 +211,7 @@ Ejemplos comunes: - `DateFieldRootState` - `DateFieldInputState` -- `DateFieldLabelState` +- `DateFieldLabe$state` - `DateFieldSegmentState` - `TimeFieldRootState` @@ -500,7 +500,7 @@ export class MyComponentRootState { Si hay subpiezas con comportamiento, crea clases como: - `MyComponentInputState` -- `MyComponentLabelState` +- `MyComponentLabe$state` - `MyComponentItemState` Cada una hace `MyComponentRootContext.get()` y deriva lo que necesita. @@ -523,7 +523,7 @@ Ejemplo: ```ts export { default as Root } from './components/mi-componente.svelte'; -export { default as Item } from './components/mi-componente-item.svelte'; +export { default as Item } from './components/mi-componente-virtual-list-item.svelte'; export type { MyComponentRootProps as RootProps, @@ -559,6 +559,21 @@ Esto es especialmente importante para: - componentes con teclado complejo - componentes configurables por `TerraConfig` +## 13. Frontera de dominio: adapters + +Cuando `terra` depende de una libreria de dominio externa (por ejemplo fechas), no debe importar sus archivos directamente desde cada componente. La regla es: + +- el dominio vive fuera de `terra` +- `terra` define un adapter local y estable +- los componentes importan del adapter, no de la libreria externa + +Ejemplo actual: + +- `src/uix/terra/adapters/dates/index.ts` + +Ese adapter actua como frontera para toda la familia date/time. Si la libreria compartida cambia de estructura, el impacto se concentra ahi y no se propaga a veinte componentes. + + ## 13. Convencion especial para date/time La familia date/time tiene una regla extra: @@ -566,7 +581,7 @@ La familia date/time tiene una regla extra: - no debe acoplarse a `actx` - debe aceptar props explicitas - debe heredar defaults desde `TerraConfig` -- debe apoyarse en `glob/date` para tipos y helpers de dominio +- debe apoyarse en un adapter de dominio de `terra`, no en imports dispersos a la libreria externa Hoy esto afecta especialmente a: @@ -579,7 +594,7 @@ Hoy esto afecta especialmente a: Para estos componentes, si necesitas formato/locale: -- usa `glob/lib` para tipos y helpers (`DatiDateOrder`, `DatiTimeFormat`, `getDefaultDate`, `getDefaultTime`, etc.) +- usa `src/uix/terra/adapters/dates` como frontera unica hacia la libreria de fechas - usa `resolveDateOrderProp`, `resolveHourCycleProp`, `resolveLocaleProp` - no importes `actx` dentro del modulo @@ -623,7 +638,7 @@ Antes de dar por bueno un componente, revisar: - hay `mergeProps` en los wrappers - la accesibilidad base (`role`, `aria-*`, `data-*`) sale del estado, no del usuario final - si soporta defaults globales, usa `TerraConfig` -- si es date/time, usa `glob/date` +- si es date/time, importa desde `src/uix/terra/adapters/dates` - tiene demo en `src/routes/test/` ## 17. Ejemplo minimo de lectura del sistema @@ -737,7 +752,7 @@ export class XRootState { Patron habitual: - `XInputState` -- `XLabelState` +- `XLabe$state` - `XSegmentState` - `XHiddenInputState` @@ -973,3 +988,155 @@ Si quieres una version ultra resumida para trabajar rapido, piensa en `terra` as - `exports.ts` define la API `Elemento.SubElemento` - `src/routes/test/` valida que el componente existe de verdad + + +## 21. Estado actual de la arquitectura + +A fecha de hoy, `terra` ya no debe entenderse como una migracion desde `uisv` o `acel`, sino como la base estable del framework. + +### 21.1 Capas actuales + +- `terra` = primitives headless, comportamiento, accesibilidad, contexto y defaults compartidos +- `air` = capa visual/opinionated, todavia en fase temprana +- `glob` = logica y formatos de dominio (fechas, numeros, currency, units, locale) +- `routes/test` = banco de pruebas real de la libreria + +### 21.2 Que ya no forma parte de `terra` + +No debemos volver a introducir en `terra` estas ideas: + +- managers globales tipo `uiux.toast` +- managers globales tipo `uiux.modal` +- componentes puramente visuales sin valor como primitive +- aliases legacy tipo `Acel*`, `Bits*`, `boxWith`, `simpleBox`, `derived` + +### 21.3 Que si pertenece a `terra` + +Pertenece a `terra` todo lo que represente comportamiento reusable y headless: + +- overlays como `Dialog`, `AlertDialog`, `Popover`, `Drawer` +- inputs complejos como `DateField`, `TimeField`, `Select`, `Combobox` +- composicion de formularios y validacion accesible +- configuracion de defaults via `TerraConfig` +- virtualizacion, focus management, dismiss layers, portal, floating, etc. + +## 22. Drawer y futuros overlays + +`Drawer` ya existe como primitive real en `terra`. + +La decision de arquitectura actual es: + +- `Drawer` si vive en `terra` +- `Sheet` no necesita ser otra primitive base distinta por ahora + +### 22.1 Como pensar `Sheet` + +`Sheet` probablemente sera una abstraccion de producto o de capa visual sobre `Drawer`, no una nueva infraestructura. + +Regla recomendada: + +- si cambia el comportamiento base, evaluar primitive nueva +- si cambia sobre todo la presentacion o la semantica UX, resolverlo en `air` + +## 23. Analisis de `Form` + +`Form` es uno de los siguientes primitives importantes para `terra`. + +### 23.1 Referencias externas recomendadas + +Para inspiracion de diseƱo: + +- `React Aria` como referencia principal de `Form` +- `Ark UI` como referencia principal de `Field` +- `Radix` como referencia general de primitives compuestos + +La lectura actual es: + +- `React Aria` resuelve muy bien validacion, submit, errores de servidor y accesibilidad +- `Ark UI` resuelve muy bien el contexto de `Field` y la composicion alrededor del control +- `Radix` sigue siendo una buena referencia general, pero su `Form` no es el mejor modelo para composicion interna completa del sistema + +### 23.2 Objetivo de `Form` en `terra` + +`Form` no debe convertirse en una form library cerrada. Debe ser una primitive headless para: + +- contexto de formulario +- submit y reset +- estado `pending` +- invalidacion global o por campo +- errores de servidor +- foco al primer invalido si aplica +- composicion correcta con `Field`, `Select`, `Combobox`, `Checkbox`, `RadioGroup`, `DateField`, etc. + +### 23.3 Superficie inicial recomendada + +Primera version razonable: + +- `Form.Root` +- `Form.Field` +- `Form.Label` +- `Form.Description` +- `Form.Error` +- `Form.Submit` +- `Form.Reset` + +Y quizas a nivel de estado: + +- `pending` +- `disabled` +- `readonly` +- `required` +- `invalid` +- `touched` +- `dirty` + +### 23.4 Relacion entre `Form` y `Field` + +`Field` no debe desaparecer. Debe crecer y coordinarse con `Form`. + +Modelo recomendado: + +- `Field` = primitive de campo y de asociacion accesible entre label/control/help/error +- `Form` = primitive de formulario, submit, reset, contexto y errores globales + +Es decir: + +- `Field` organiza el campo +- `Form` organiza el formulario + +### 23.5 Inputs especializados futuros + +No conviene convertir `Field` en un monstruo con modos especiales. Mejor construir una familia de inputs sobre `Field` + `Form` + `glob`. + +Buenos candidatos futuros: + +- `Input` textual base +- `NumberField` +- `CurrencyField` +- `PhoneField` +- `PatternField` +- `EmailField` +- `UrlField` +- `SearchField` + +Ventaja del framework: + +- `glob` ya ofrece una base fuerte para formatos y parseo +- eso puede hacer que nuestros campos localizados superen a muchos sistemas existentes + +## 24. FloatingButton + +Otro primitive pendiente que si encaja en `terra` es `FloatingButton`. + +Encaja porque: + +- no es solo visual +- tiene semantica de posicionamiento y accion flotante +- puede requerir accesibilidad, layering, portal o posicion fija reusable + +Aun asi, la skin final probablemente vivira mejor en `air`. + +Regla practica: + +- comportamiento y contrato -> `terra` +- variaciones visuales y motion -> `air` diff --git a/src/uix/terra/adapters/dates/index.ts b/src/uix/terra/adapters/dates/index.ts new file mode 100644 index 000000000..fdae4f675 --- /dev/null +++ b/src/uix/terra/adapters/dates/index.ts @@ -0,0 +1,2 @@ +export * from '$lib/util/dates'; +export { getPlaceholder } from '$lib/util/dates/format/placeholders'; diff --git a/src/uix/terra/base/config/prop-resolvers.ts b/src/uix/terra/base/config/prop-resolvers.ts index 9a4dfc8c9..59b816e85 100644 --- a/src/uix/terra/base/config/prop-resolvers.ts +++ b/src/uix/terra/base/config/prop-resolvers.ts @@ -1,5 +1,5 @@ import { readableActive, type Active, type Getter } from '../../utils'; -import { DEFAULT_DATE_ORDER, DEFAULT_TIME_FORMAT, timeFormatToHourCycle, type HourCycle } from '$lib/util/dates'; +import { DEFAULT_DATE_ORDER, DEFAULT_TIME_FORMAT, timeFormatToHourCycle, type HourCycle } from '$terra/adapters/dates'; import { type TerraConfigState, getTerraConfig } from './terra-config.ts'; /** diff --git a/src/uix/terra/base/config/types.ts b/src/uix/terra/base/config/types.ts index f76ed8170..56bcba1e9 100644 --- a/src/uix/terra/base/config/types.ts +++ b/src/uix/terra/base/config/types.ts @@ -1,5 +1,5 @@ import type { WithChildren } from '../../utils'; -import type { DateOrder, HourCycle } from '$lib/util/dates'; +import type { DateOrder, HourCycle } from '$terra/adapters/dates'; import type { PortalTarget } from '../portal/types.ts'; export type TerraConfigPropsWithoutChildren = { diff --git a/src/uix/terra/calendar/calendar.svelte.ts b/src/uix/terra/calendar/calendar.svelte.ts index 169a779cb..06ded01fe 100644 --- a/src/uix/terra/calendar/calendar.svelte.ts +++ b/src/uix/terra/calendar/calendar.svelte.ts @@ -5,7 +5,7 @@ import { isSameDay, isSameMonth, isToday, -} from "$lib/util/dates"; +} from "$terra/adapters/dates"; import { DEV } from "esm-env"; import { onMount, untrack } from "svelte"; import { @@ -33,7 +33,7 @@ import { getDateValueType, isBefore, dateValueToDate as toDate, -} from "$lib/util/dates"; +} from "$terra/adapters/dates"; import { type Announcer, getAnnouncer } from "../utils/datetime/announcer"; import { type Formatter, createFormatter } from "../utils/datetime/formatter"; import { diff --git a/src/uix/terra/calendar/components/calendar.svelte b/src/uix/terra/calendar/components/calendar.svelte index d8e3aac05..5130f142b 100644 --- a/src/uix/terra/calendar/components/calendar.svelte +++ b/src/uix/terra/calendar/components/calendar.svelte @@ -1,12 +1,12 @@