diff --git a/src/uix/eidos/THEMING.md b/src/uix/eidos/THEMING.md index 175f8de74..3c2b07b45 100644 --- a/src/uix/eidos/THEMING.md +++ b/src/uix/eidos/THEMING.md @@ -1358,6 +1358,44 @@ Añade al recipe del componente si: - Es específico (un slider drag, una calendar swap). - No reacciona a una señal de sema, sino a un `data-state` transition. +### Superficies arrastrables: el lift de "pickup" + +Cuando el usuario **agarra y arrastra** una superficie, ésta debe **subir hacia +él** (profundidad: "lo he cogido"). Es un cue transversal — NO específico de un +componente — así que el factor de escala es un **token global de motion**, no un +literal por recipe: + +| Token | Valor | Uso | +|---|---|---| +| `--motion-scale-lift` | `1.02` | escala de pickup al arrastrar (el único `>1` de la familia `--motion-scale-*`) | + +**Patrón (cualquier draggable lo aplica igual)** — gateado por el data-attr de +arrastre que su morfo declara (`data-dragging`, `data-grabbed`, …), emparejado +con una elevación de sombra, y suprimido bajo reduced-motion: + +```css +/* float-panel, un thumb de slider arrastrándose, un item sortable, un drawer… */ +[data-x][data-dragging] { + scale: var(--motion-scale-lift); /* sube hacia el usuario */ + box-shadow: var(--…-shadow-active); /* + eleva la sombra */ +} +@media (prefers-reduced-motion: reduce) { + [data-x][data-dragging] { scale: 1; transition: none; } +} +``` + +Reglas: +- **`scale` (no `transform`)** para componer con la posición, que viaja por + `translate` (propiedades distintas) → el lift no pelea con el arrastre 1:1. +- El cambio de `scale` se **transiciona** (lift al agarrar / settle al soltar); + durante el move queda **estático** (compuesto, sin coste por frame). +- `will-change: translate, scale` mientras dura el gesto. +- Un theme retematiza el lift en `STATIC_MOTION.scale.lift` (`primitives/static.ts`) + — todos los draggables lo heredan. No redefinir el 1.02 por componente. + +Hoy lo consume `float-panel` (`[data-float-panel-content][data-dragging]`); un +slider/sortable que añada arrastre debe leer el MISMO token, no inventar el suyo. + --- ## 15. Comparación con librerías de referencia diff --git a/src/uix/eidos/components/float-panel/float-panel.css b/src/uix/eidos/components/float-panel/float-panel.css index 1c6b36011..f60504e2d 100644 --- a/src/uix/eidos/components/float-panel/float-panel.css +++ b/src/uix/eidos/components/float-panel/float-panel.css @@ -26,10 +26,13 @@ font-size: var(--float-panel-font-size); line-height: var(--float-panel-line-height); box-shadow: var(--float-panel-shadow); - /* Only the elevation lift (→ topmost) is eased here. Position (`translate`) - is never transitioned so the drag stays 1:1; presence is the preset's job. */ - transition: box-shadow var(--float-panel-transition-duration) - var(--float-panel-transition-ease); + /* The elevation lift (→ topmost) AND the drag pickup-scale are eased here. + Position (`translate`) is never transitioned so the drag stays 1:1; presence + (scale on `[data-state]`) is the motion preset's job — distinct from this + steady-state `scale` transition, which only fires on the grab/settle. */ + transition: + box-shadow var(--float-panel-transition-duration) var(--float-panel-transition-ease), + scale var(--float-panel-transition-duration) var(--float-panel-transition-ease); } /* The focused/frontmost panel lifts. */ @@ -51,6 +54,15 @@ box-shadow: var(--float-panel-shadow-active); } +/* Dragging also lifts the surface TOWARD the user (a subtle scale) — the depth + "pickup" cue paired with the elevation shadow. Move only: resize changes the + real size, so scaling it too would read wrong. `scale` composes with the drag + `translate` (distinct properties), is static during the move (composited, no + per-frame cost), and eases in on grab / out on settle via the base transition. */ +[data-float-panel-content][data-dragging] { + scale: var(--motion-scale-lift); +} + [data-float-panel-content]:focus-visible { outline: none; box-shadow: @@ -323,4 +335,17 @@ /* Reduced-motion for the open/close is handled by the motion system itself (the `scale-fade` preset degrades to opacity-only under `[data-motion='reduce']` - + `@media (prefers-reduced-motion)`), so no recipe-level override is needed. */ + + `@media (prefers-reduced-motion)`), so no recipe-level override is needed. + The drag pickup-scale IS a recipe transition, so suppress it here — the shadow + lift alone still conveys the grab. */ +[data-motion='reduce'] [data-float-panel-content][data-dragging], +[data-float-panel-content][data-dragging][data-motion='reduce'] { + scale: 1; + transition: none; +} +@media (prefers-reduced-motion: reduce) { + [data-float-panel-content][data-dragging] { + scale: 1; + transition: none; + } +} diff --git a/src/uix/eidos/eidos-motion.md b/src/uix/eidos/eidos-motion.md index 896c03e9c..4334d143d 100644 --- a/src/uix/eidos/eidos-motion.md +++ b/src/uix/eidos/eidos-motion.md @@ -437,7 +437,7 @@ Tokens existentes (`STATIC_MOTION`, generados, leídos por css y js): | `--duration-{k}` | `instant 0` · `fast 120` · `normal 180` · `moderate 240` · `slow 320` ms | | `--ease-{k}` | `default` · `out` · `in` · `spring` · `alert` · `symmetric` | | `--motion-distance-{k}` | `xs 2` · `sm 4` · `md 8` · `lg 16` px | -| `--motion-scale-{k}` | `enter 0.985` · `press 0.97` (fade-through usa `0.92` — F6 lo tokeniza) | +| `--motion-scale-{k}` | `enter 0.985` · `press 0.97` · `through 0.92` (fade-through) · `lift 1.02` (pickup de drag — el único `>1`, ver §Draggable surfaces en THEMING) | **Vars de override (runtime, NO tokens de theme)** — un componente las pone en su elemento; el preset las lee con fallback al token: diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index c5ed2b3b5..26f5d1579 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -229,6 +229,7 @@ --motion-scale-enter: 0.985; --motion-scale-press: 0.97; --motion-scale-through: 0.92; + --motion-scale-lift: 1.02; --motion-stagger: 20ms; --icon-size-xxs: calc(12px * var(--scaling)); --icon-size-xs: calc(14px * var(--scaling)); diff --git a/src/uix/eidos/lib/config-types.ts b/src/uix/eidos/lib/config-types.ts index b3fe9fe48..e564b5cb2 100644 --- a/src/uix/eidos/lib/config-types.ts +++ b/src/uix/eidos/lib/config-types.ts @@ -509,7 +509,7 @@ export const EASE_KEYS = [ 'emphasized' ] as const; export const MOTION_DISTANCE_KEYS = ['xs', 'sm', 'md', 'lg', 'xl'] as const; -export const MOTION_SCALE_KEYS = ['enter', 'press', 'through'] as const; +export const MOTION_SCALE_KEYS = ['enter', 'press', 'through', 'lift'] as const; export const ICON_STROKE_WIDTH_KEYS = ['sm', 'md', 'lg'] as const; export type DurationKey = (typeof DURATION_KEYS)[number]; diff --git a/src/uix/eidos/lib/primitives/static.ts b/src/uix/eidos/lib/primitives/static.ts index 0e72d2145..0badae3b5 100644 --- a/src/uix/eidos/lib/primitives/static.ts +++ b/src/uix/eidos/lib/primitives/static.ts @@ -157,7 +157,12 @@ export const STATIC_MOTION = { enter: '0.985', press: '0.97', // Fade-through (Material) incoming scale. - through: '0.92' + through: '0.92', + // Pickup LIFT — a grabbed surface scales slightly TOWARD the viewer (depth + // "picked up" cue). Cross-component: any draggable surface (float-panel, + // a slider thumb, a sortable item…) applies it on its grab/drag state, + // paired with an elevation shadow. >1 (the only grow in the scale family). + lift: '1.02' }, stagger: '20ms', // Expressive set (Carbon's productive/expressive split): emphasized easing diff --git a/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts b/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts index a26c84eb3..990925b71 100644 --- a/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts +++ b/src/uix/soma/components/float-panel/float-panel-provider.svelte.ts @@ -1019,7 +1019,7 @@ export class FloatPanelContentProvider { el.style.top = '0px'; el.style.pointerEvents = 'auto'; el.style.zIndex = String(p.zIndex); - el.style.willChange = active ? 'translate' : ''; + el.style.willChange = active ? 'translate, scale' : ''; el.style.userSelect = active ? 'none' : ''; // Geometry: only when no gesture is driving the element directly. // Round to whole pixels — `translate` is a transform and doesn't snap to