|
|
|
|
# ActiveDom
|
|
|
|
|
|
|
|
|
|
`adom` contiene `ActiveDom`: el servicio DOM reactivo de aplicación.
|
|
|
|
|
|
|
|
|
|
## Qué es hoy
|
|
|
|
|
|
|
|
|
|
Ahora mismo `ActiveDom` no es un bus de eventos semánticos ni un reflector de
|
|
|
|
|
`data-event*`.
|
|
|
|
|
|
|
|
|
|
Su responsabilidad actual es más pequeña y más concreta:
|
|
|
|
|
|
|
|
|
|
- exponer el ancho de viewport de forma reactiva
|
|
|
|
|
- resolver el breakpoint actual
|
|
|
|
|
- mantener la definición de breakpoints de la app
|
|
|
|
|
- resolver valores responsive
|
|
|
|
|
- ofrecer helpers de consulta (`isAtLeast`, `matches`)
|
|
|
|
|
- aplicar/remover atributos DOM de forma controlada (`apply`, `remove`)
|
|
|
|
|
|
|
|
|
|
En otras palabras:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
libs/dom -> arts/adom -> App.dom / app.dom
|
|
|
|
|
puro reactivo consumo de app
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Composicion via aapp
|
|
|
|
|
|
|
|
|
|
`createActiveApp(...)` siempre construye `App.dom` (con defaults si no se
|
|
|
|
|
configura) y se lo pasa a `App.frontend` para que comparta la misma instancia.
|
|
|
|
|
Ver `$active-app/README.md`. Construir `createActiveDom()` directamente solo es
|
|
|
|
|
necesario en tests aislados o en consumidores fuera de la composicion estandar.
|
|
|
|
|
|
|
|
|
|
## Qué pertenece a cada capa
|
|
|
|
|
|
|
|
|
|
### `libs/dom`
|
|
|
|
|
|
|
|
|
|
Primitives DOM puras o casi puras:
|
|
|
|
|
|
|
|
|
|
- guards y traversal DOM
|
|
|
|
|
- focus helpers
|
|
|
|
|
- tabbable helpers
|
|
|
|
|
- responsive helpers puros
|
|
|
|
|
|
|
|
|
|
**Capa de implementación.** Componentes y soma NO importan de aquí
|
|
|
|
|
directamente — todo se reexporta a través de `$adom` (ver más abajo).
|
|
|
|
|
Sólo `arts/adom/*` y los tests de `libs/dom` pueden importar
|
|
|
|
|
`$libs/dom` directo.
|
|
|
|
|
|
|
|
|
|
No mantiene estado de aplicación.
|
|
|
|
|
|
|
|
|
|
### `arts/adom`
|
|
|
|
|
|
|
|
|
|
Runtime reactivo de DOM:
|
|
|
|
|
|
|
|
|
|
- `viewport`
|
|
|
|
|
- `breakpoints`
|
|
|
|
|
- `currentBreakpoint`
|
|
|
|
|
- `resolve(...)`
|
|
|
|
|
- `isAtLeast(...)`
|
|
|
|
|
- `matches(...)`
|
|
|
|
|
- `apply(...)` / `remove(...)` como superficie unica de mutacion de atributos
|
|
|
|
|
- `BodyScrollLock` como helper global de body scroll lock, sin bloquear eventos de puntero
|
|
|
|
|
- `DOMContext` como helper scoped para `Document` / `ShadowRoot`
|
|
|
|
|
- `RovingFocusGroup` como helper runtime para navegación compuesta por teclado
|
|
|
|
|
|
|
|
|
|
`ActiveDom` sí mantiene estado reactivo y por eso vive aquí, no en `$libs/dom`.
|
|
|
|
|
|
|
|
|
|
## Posición en App
|
|
|
|
|
|
|
|
|
|
`ActiveDom` vive a nivel de aplicación:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
App.dom;
|
|
|
|
|
app.dom;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
La implementación se consume desde la capa de aplicación y desde artefactos que
|
|
|
|
|
necesitan escribir atributos finales sobre un target DOM, por ejemplo `fend`.
|
|
|
|
|
|
|
|
|
|
## API actual
|
|
|
|
|
|
|
|
|
|
La API pública real de `ActiveDom` hoy es esta:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
export type ActiveDom = {
|
|
|
|
|
breakpoints: Active<Breakpoints>;
|
|
|
|
|
viewport: { readonly width: number };
|
|
|
|
|
currentBreakpoint: Active<Breakpoint>;
|
|
|
|
|
resolve<T>(value: ResponsiveProp<T> | undefined): T | undefined;
|
|
|
|
|
isAtLeast(breakpoint: Breakpoint): boolean;
|
|
|
|
|
matches(breakpoint: Breakpoint): boolean;
|
|
|
|
|
apply(change: StructuralChange): void;
|
|
|
|
|
remove(target: HTMLElement, names: readonly string[]): void;
|
|
|
|
|
dispose(): void;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Creación:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const dom = createActiveDom({
|
|
|
|
|
breakpoints: readableActive(() => ({
|
|
|
|
|
lg: 1100
|
|
|
|
|
}))
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const domWithDefaults = createActiveDom();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Responsive:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const columns = dom.resolve({ base: 1, sm: 2, lg: 3 });
|
|
|
|
|
const tone = dom.resolve({ base: 'compact', md: 'normal', xl: 'wide' });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Mutacion DOM:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
dom.apply({
|
|
|
|
|
target: node,
|
|
|
|
|
attrs: {
|
|
|
|
|
'data-state': 'open',
|
|
|
|
|
'aria-busy': true,
|
|
|
|
|
'data-hidden': false
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dom.remove(node, ['data-state', 'aria-busy']);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Reglas:
|
|
|
|
|
|
|
|
|
|
- los breakpoints se definen en la creación del `dom`
|
|
|
|
|
- `breakpoints` es opcional; si no se pasa, usa `BREAKPOINTS_DEFAULT`
|
|
|
|
|
- no hay herencia de `dom` padre
|
|
|
|
|
- `ActiveDom` es servicio de app, no scope anidado
|
|
|
|
|
- `ActiveDom` registra el listener de `resize` al construirse (por instancia).
|
|
|
|
|
El singleton compartido (`shareViewport: true`) pospone la asignación del
|
|
|
|
|
listener a la primera lectura del viewport — importar `$adom` no aloca
|
|
|
|
|
estado reactivo si nadie consume el viewport.
|
|
|
|
|
- `apply` solo escribe atributos; no interpreta semantica ni eventos
|
|
|
|
|
- `false | null | undefined` remueven atributos
|
|
|
|
|
- `viewport` es **per-instancia por defecto**: cada `ActiveDom` posee su
|
|
|
|
|
propio listener de `resize`, scoped al `targetWindow` (default `window`).
|
|
|
|
|
Esto evita filtraciones entre tests, iframes, popups y entornos happy-dom.
|
|
|
|
|
Llamar `dispose()` desadjunta ese listener.
|
|
|
|
|
|
|
|
|
|
### Compartir el viewport entre instancias
|
|
|
|
|
|
|
|
|
|
Para apps "single window" donde toda la composición vive en el mismo
|
|
|
|
|
documento, opt-in al singleton evita N listeners para el mismo evento:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const dom = createActiveDom({ shareViewport: true });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Bajo esta opción, `dispose()` es no-op para el viewport (el singleton vive
|
|
|
|
|
toda la vida del proceso). Lo que sí se libera es `breakpoints`,
|
|
|
|
|
`currentBreakpoint` y los demás reactivos por-instancia.
|
|
|
|
|
|
|
|
|
|
### Tracking de un window distinto
|
|
|
|
|
|
|
|
|
|
Para iframes, popups o entornos de test:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const iframeDom = createActiveDom({ targetWindow: iframe.contentWindow! });
|
|
|
|
|
const popupDom = createActiveDom({ targetWindow: popup });
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Ignorado cuando `shareViewport: true` (el singleton siempre rastrea el
|
|
|
|
|
top-level `window`).
|
|
|
|
|
|
|
|
|
|
## Qué no es
|
|
|
|
|
|
|
|
|
|
`ActiveDom` no es:
|
|
|
|
|
|
|
|
|
|
- `SemanticEngine`
|
|
|
|
|
- broker de eventos
|
|
|
|
|
- reflector de `data-event*`
|
|
|
|
|
- hub de `MutationObserver`
|
|
|
|
|
- sistema de theme
|
|
|
|
|
- sistema de modal, backdrop o inert
|
|
|
|
|
- reemplazo de `$libs/dom`
|
|
|
|
|
|
|
|
|
|
Además, `uix/adom` puede alojar helpers DOM con estado global real, como
|
|
|
|
|
`BodyScrollLock`, o helpers scoped de runtime como `DOMContext`, cuando ya no
|
|
|
|
|
son primitives puras de `$libs/dom` pero tampoco pertenecen a un componente UI concreto.
|
|
|
|
|
|
|
|
|
|
También caben aquí helpers runtime de foco con estado propio, como
|
|
|
|
|
`RovingFocusGroup`, que reutilizan `$libs/dom` por debajo pero ya no son
|
|
|
|
|
solo utilidades puras.
|
|
|
|
|
|
|
|
|
|
## Pagina De Prueba
|
|
|
|
|
|
|
|
|
|
La pagina manual esta en `/test/adom` y cubre:
|
|
|
|
|
|
|
|
|
|
- viewport y breakpoint actual
|
|
|
|
|
- `resolve()` responsive
|
|
|
|
|
- `apply()` / `remove()`
|
|
|
|
|
- `BodyScrollLock`
|
|
|
|
|
- `DOMContext`
|
|
|
|
|
- `RovingFocusGroup`
|
|
|
|
|
|
|
|
|
|
## Relación con otras piezas
|
|
|
|
|
|
|
|
|
|
### Semántica
|
|
|
|
|
|
|
|
|
|
La semántica pertenece a `Sema` y a `SemanticEngine`, no a `ActiveDom`.
|
|
|
|
|
|
|
|
|
|
Si mañana `ActiveDom` refleja eventos al DOM, será como consumidor de
|
|
|
|
|
`SemanticEngine`, no como autoridad semántica.
|
|
|
|
|
|
|
|
|
|
### Theme
|
|
|
|
|
|
|
|
|
|
El theme no pertenece a `dom`.
|
|
|
|
|
|
|
|
|
|
Va en `app.presentation`, porque es estado de presentación de aplicación, no una
|
|
|
|
|
primitive DOM.
|
|
|
|
|
|
|
|
|
|
### Air y Terra
|
|
|
|
|
|
|
|
|
|
`air` y `terra` no consumen esta capa nueva.
|
|
|
|
|
|
|
|
|
|
Su código actual sirve como referencia histórica para extraer utilidades hacia
|
|
|
|
|
`$libs/dom`, pero no forman parte del runtime nuevo.
|
|
|
|
|
|
|
|
|
|
## Estado del diseño
|
|
|
|
|
|
|
|
|
|
`ActiveDom` está en fase fundacional.
|
|
|
|
|
|
|
|
|
|
Lo que ya está cerrado:
|
|
|
|
|
|
|
|
|
|
- `app.dom`
|
|
|
|
|
- `App.dom`
|
|
|
|
|
- `viewport`
|
|
|
|
|
- `breakpoints`
|
|
|
|
|
- `currentBreakpoint`
|
|
|
|
|
- resolución responsive
|
|
|
|
|
|
|
|
|
|
Lo que queda para fases posteriores, si de verdad hace falta:
|
|
|
|
|
|
|
|
|
|
- reflexión de eventos semánticos al DOM
|
|
|
|
|
- observers compartidos
|
|
|
|
|
- APIs por `Document` o `ShadowRoot`
|
|
|
|
|
- introspección/diagnóstico de runtime más rica
|
|
|
|
|
|
|
|
|
|
La regla importante por ahora es simple:
|
|
|
|
|
|
|
|
|
|
> `ActiveDom` es el servicio reactivo de DOM de la app; `$libs/dom` es su base pura.
|