fix(direction): el indicador deslizante no se remedia al voltear la direccion

Verificar los tres arreglos que quedaron sin ver en ec533845d destapo la otra
mitad del sliding-indicator.

MeasuredIndicator remide cuando cambian root o active, cuando alguno de los dos
redimensiona, en resize de ventana y en visibilitychange. Voltear la direccion
no redimensiona NADA —los items solo cambian de posicion— y active conserva su
identidad, asi que --indicator-x se quedaba con la coordenada de la disposicion
anterior y la pildora caia sobre otro item.

Arreglado con una opcion `dir` (un getter) que el $effect lee ANTES del return
temprano, para que la dependencia quede registrada en todas las pasadas y no
solo en las que llegan a medir. El consumidor —radio-group, el unico hoy— le
pasa resolvedDir. Getter y no lectura del DOM a proposito: la direccion viene de
la cadena prop/prefs y el atributo es proyeccion, no fuente.

A/B con git stash sobre los dos ficheros, moviendo la direccion por el CAMINO
REAL (el toggle del topbar): sin el arreglo el translate se queda en 177.156px y
el indicador cae 173px fuera del seleccionado; con el arreglo pasa a 4px y dx=0
en las tres posiciones del ciclo auto/ltr/rtl.

Los otros dos arreglos que faltaba ver quedan VERIFICADOS, con un probe de
Playwright y Emulation.setEmulatedMedia porque sus reglas viven en @media
(pointer: coarse) y el navegador embebido no llega ahi: archetypes (checkbox y
switch) da margin-inline-start -22px sobre un slop de 44px —la mitad exacta— con
translate sin componente X, y el barrido de hit-test alcanza 22/21 y 22/22 px,
simetrico en LTR y en RTL; proof-of-human da -23px sobre 46px con translateY
solo, y 23/23 en el barrido.

NOTA DE METODO, anotada en el handoff: el primer probe volteaba con
setAttribute('dir') sobre el elemento y daba "no arreglado". Eso NO cambia
resolvedDir, que sale de la prop/prefs, asi que la dependencia no podia
dispararse — la prueba era invalida, no el arreglo. Cualquier verificacion de
este eje tiene que mover la direccion por el camino real.

Verificado: check 74 = linea base exacta. 73/73 en soma/layers + radio-group.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-dir-prefs
dev 2 months ago
parent ec533845d1
commit dc5e956658

@ -274,10 +274,17 @@ determinable, y hay que medirlo después.
**Verificado en Chrome**: switch (roto→arreglado, medido y visto), carousel, **Verificado en Chrome**: switch (roto→arreglado, medido y visto), carousel,
avatar, cropper, menu-dial, chat-log. avatar, cropper, menu-dial, chat-log.
**NO verificado, y conviene mirarlo**: `archetypes` y `proof-of-human` (sus reglas
viven en `@media (pointer: coarse)`, invisible en escritorio) y **Los tres que faltaban, verificados después** con un probe de Playwright y
`sliding-indicator` (la demo del `radio-group` segmented no llegó a medir — `Emulation.setEmulatedMedia` (`pointer: coarse`), porque el navegador embebido no
`--indicator-w` salía 0). llega a esa media query:
- `archetypes` (checkbox y switch): `margin-inline-start: -22px` con slop de
`44px` — la mitad exacta — y `translate: 0 -50%` sin componente X. Barrido de
hit-test: alcanza 22/21 y 22/22 px, **simétrico en LTR y en RTL**.
- `proof-of-human`: `-23px` sobre `46px`, `translateY(-22)` sin X, hit-test 23/23.
- `sliding-indicator`: el CSS es correcto — con medida fresca cae exacto en RTL.
Pero destapó §6.6.
### 6.3 · `:dir()` es la doctrina; `[dir='rtl']` está PROHIBIDO ### 6.3 · `:dir()` es la doctrina; `[dir='rtl']` está PROHIBIDO
@ -318,6 +325,33 @@ puntuación desplazada en RTL. Es cosmético y sólo en un modo de inspección;
que no pueden demostrar, no. Si se quiere recuperar, **el pin va alrededor de la que no pueden demostrar, no. Si se quiere recuperar, **el pin va alrededor de la
PROSA**, nunca alrededor del canvas que también contiene los ejemplos vivos. PROSA**, nunca alrededor del canvas que también contiene los ejemplos vivos.
### 6.6 · El indicador deslizante no se re-medía al voltear
Verificar §6.2 destapó la otra mitad del `sliding-indicator`. `MeasuredIndicator`
(`src/uix/soma/layers/measured-indicator.svelte.ts`) re-mide cuando cambian `root`
o `active`, cuando alguno redimensiona, en `resize` de ventana y en
`visibilitychange`. **Voltear la dirección no redimensiona nada** —los ítems sólo
cambian de POSICIÓN— y `active` conserva su identidad, así que `--indicator-x`
se quedaba con la coordenada de la disposición anterior.
Arreglado con una opción `dir` (un getter) que el `$effect` lee **antes del return
temprano**, para que la dependencia se registre en todas las pasadas. El consumidor
(`radio-group`) le pasa `resolvedDir`. Getter y no lectura del DOM: la dirección
viene de la cadena prop/prefs, y el atributo es proyección, no fuente.
⚠️ **TRAMPA DE MÉTODO, me costó una pasada entera**: el primer probe volteaba con
`el.setAttribute('dir', 'rtl')` y daba «no arreglado» — pero eso **no cambia
`resolvedDir`**, que sale de la prop/prefs, así que la dependencia no podía
dispararse. Para probar cualquier cosa del eje hay que mover la dirección por el
CAMINO REAL (el toggle del topbar o el control de la demo), nunca tocando el DOM.
A/B con `git stash` sobre los dos ficheros, por la cadena real: sin el arreglo,
`translate` se queda en `177.156px` y el indicador cae 173px fuera del
seleccionado; con él, pasa a `4px` y `dx = 0`. `check` 74 = línea base, 73/73 en
`soma/layers` + `radio-group`.
**Único consumidor hoy**: `radio-group`. Si aparece otro, tiene que pasar `dir`.
### 6.5 · Suelto, para quien siga ### 6.5 · Suelto, para quien siga
- **Hay componentes estampando `dir=""`** (cadena vacía) en vez de omitir el - **Hay componentes estampando `dir=""`** (cadena vacía) en vez de omitir el

@ -360,7 +360,8 @@ export class RadioGroupSelectionIndicatorProvider {
active: () => { active: () => {
const value = this.provider.opts.value.current; const value = this.provider.opts.value.current;
return value ? this.provider.resolveItemEl(value) : null; return value ? this.provider.resolveItemEl(value) : null;
} },
dir: () => this.provider.resolvedDir
}); });
} }

@ -26,6 +26,19 @@ export interface MeasuredIndicatorOpts {
* caller's choice; the layer only measures. * caller's choice; the layer only measures.
*/ */
active: Getter<HTMLElement | null>; active: Getter<HTMLElement | null>;
/**
* The scope's resolved direction. Reading it makes the measure depend on it,
* so flipping RTL/LTR re-measures.
*
* Needed because a flip moves the items without RESIZING anything: neither
* `ResizeObserver` fires nor does `active` change identity, so without this
* the indicator keeps the offset of the previous layout and lands on the
* wrong item (measured: 173px off in a segmented radio-group).
*
* A getter and not a DOM read on purpose — the direction comes from the
* prop/prefs chain; the `dir` attribute is a projection, not a source.
*/
dir?: Getter<unknown>;
} }
/** /**
@ -68,11 +81,14 @@ export class MeasuredIndicator {
}); });
constructor(opts: MeasuredIndicatorOpts) { constructor(opts: MeasuredIndicatorOpts) {
const { dom, root, active } = opts; const { dom, root, active, dir } = opts;
$effect(() => { $effect(() => {
const rootEl = root(); const rootEl = root();
const activeEl = active(); const activeEl = active();
// Read before the early return so the dependency is registered on
// every run, not only on the ones that reach the measure.
dir?.();
if (!rootEl || !activeEl) { if (!rootEl || !activeEl) {
this.rect = null; this.rect = null;
return; return;

Loading…
Cancel
Save

Powered by TurnKey Linux.