Revert "feat(media-player): VolumeFloat — el volumen en panel, sin mover la botonera"

This reverts commit 26ddbaa1c9.
alpha-0.1-dir-prefs
dev 2 months ago
parent 26ddbaa1c9
commit c798f7465a

@ -162,38 +162,6 @@ esas propiedades. **Consecuencia**: el VTT posicionado, el de karaoke y el
multi-hablante no se verán como los autoró quien los escribió. Es una decisión,
no un olvido.
## Volumen flotante — `<MediaPlayer.VolumeFloat>` (2026-08-04)
**Opcional.** Componer `<MuteButton>` + `<VolumeSlider>` uno al lado del otro
sigue siendo la forma en línea; ésta es la otra, y la elige el desarrollador.
**Por qué un float y no un despliegue en la propia fila.** Se probó lo segundo —
el patrón de YouTube, con el rail a `inline-size: 0` creciendo al hover— y se
descartó **con el usuario delante**: un slider que se expande dentro de una fila
flex **empuja todo lo que va detrás** (tiempo, scrubber, subtítulos, PiP,
pantalla completa) y en una fila apretada desborda. YouTube se lo permite porque
su barra ocupa la ventana entera y tiene holgura; la nuestra no. El panel va
fuera de flujo, así que **abrirlo no mueve nada** — verificado comparando la
posición de los diez controles con el panel abierto y cerrado.
**Composición**, toda de piezas del catálogo, sin nada reinventado:
| pieza | papel |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Popover` con `openOnHover` | el flotado y el gesto. Ya existía; sólo se acortó el retardo de apertura (120ms — esto es un control, no un tooltip) y se alargó el de cierre (400ms, para poder viajar hasta él) |
| `MuteButton` como trigger vía `child` | **sigue siendo el botón de verdad**: conserva `aria-pressed`, el atajo `m` y el glifo por nivel |
| `VolumeSlider orientation="vertical"` | el eje que el morfo declaraba desde el principio y nadie había cableado |
**El gesto**: hover abre, **clic silencia** — y cierra, que es el remate natural
de esa acción. En táctil no hay hover, así que el toque hace las dos cosas; es
además la única vía por la que un usuario táctil llega al slider.
⚠️ **Los tokens `--_mp-*` NO cruzan el portal.** El contenido se porta fuera del
player, y esos tokens se declaran en `[data-media-player]`. Por eso el tinte del
slider de volumen lleva fallback al rol que envuelve
(`var(--_mp-fg, var(--color-content-primary))`): sin él, el control sale sin
color dentro del panel.
## Audit exceptions
- **R-1.2 exception:** el estado disabled lo dibujan los primitivos compuestos —

@ -24,7 +24,6 @@ import Title from './media-player-title.svelte';
import Time from './media-player-time.svelte';
import TimeSlider from './media-player-time-slider.svelte';
import VolumeSlider from './media-player-volume-slider.svelte';
import VolumeFloat from './media-player-volume-float.svelte';
import DefaultControls from './media-player-default-controls.svelte';
import Artwork from './media-player-artwork.svelte';
import Artist from './media-player-artist.svelte';
@ -50,7 +49,6 @@ type MediaPlayerNamespace = typeof MediaPlayerComponent & {
Time: typeof Time;
TimeSlider: typeof TimeSlider;
VolumeSlider: typeof VolumeSlider;
VolumeFloat: typeof VolumeFloat;
DefaultControls: typeof DefaultControls;
Artwork: typeof Artwork;
Artist: typeof Artist;
@ -77,7 +75,6 @@ MediaPlayer.Title = Title;
MediaPlayer.Time = Time;
MediaPlayer.TimeSlider = TimeSlider;
MediaPlayer.VolumeSlider = VolumeSlider;
MediaPlayer.VolumeFloat = VolumeFloat;
MediaPlayer.DefaultControls = DefaultControls;
MediaPlayer.Artwork = Artwork;
MediaPlayer.Artist = Artist;
@ -107,7 +104,6 @@ export type {
MediaPlayerTimeProps as TimeProps,
MediaPlayerTimeSliderProps as TimeSliderProps,
MediaPlayerVolumeSliderProps as VolumeSliderProps,
MediaPlayerVolumeFloatProps as VolumeFloatProps,
MediaPlayerDefaultControlsProps as DefaultControlsProps,
MediaPlayerArtworkProps as ArtworkProps,
MediaPlayerArtistProps as ArtistProps,

@ -1,48 +0,0 @@
<script lang="ts">
/**
* `<MediaPlayer.VolumeFloat>` — the volume as a floating panel instead of an
* always-visible rail. Optional: composing `<MuteButton>` + `<VolumeSlider>`
* side by side is still the inline shape, and this is the other one.
*
* Why a float and not an in-row reveal: a slider that expands inside the
* control row PUSHES everything after it — time, scrubber, captions, PiP,
* fullscreen all shift on hover, and a tight row overflows. The panel is
* positioned out of flow, so opening it moves nothing.
*
* The speaker stays the real `<MuteButton>`: it keeps `aria-pressed`, the `m`
* hotkey and the loudness glyph. Hover opens the panel, click mutes — and
* closes it, which is the right end to that gesture. On touch there is no
* hover, so the tap does both: it is also the only way a touch user reaches
* the slider at all.
*
* Composed from the catalogue's own `Popover` (its `openOnHover` already
* exists); nothing about floating is reinvented here.
*/
import { Popover } from '$uix/eidos/components/popover';
import MuteButton from './media-player-mute-button.svelte';
import VolumeSlider from './media-player-volume-slider.svelte';
import type { MediaPlayerVolumeFloatProps } from './types';
let {
openDelay = 120,
closeDelay = 400,
side = 'top',
...rest
}: MediaPlayerVolumeFloatProps = $props();
</script>
<Popover {...rest}>
<Popover.Trigger openOnHover {openDelay} {closeDelay}>
{#snippet child({ props })}
<MuteButton {...props} />
{/snippet}
</Popover.Trigger>
<Popover.Portal>
<!-- The panel hugs the control. Popover's own sizes are made for prose and
forms; a vertical rail needs its hit area and nothing more, so the
width is overridden on the space scale rather than inherited. -->
<Popover.Content {side} size="sm" width="calc(var(--space-8) * 1.5)">
<VolumeSlider orientation="vertical" />
</Popover.Content>
</Popover.Portal>
</Popover>

@ -397,25 +397,7 @@
align-items: center;
margin-inline-end: var(--space-2);
}
/* The fallback is load-bearing, not defensive noise: in the floating volume the
slider is PORTALLED out of the player, and `--_mp-*` are declared on
`[data-media-player]`, so they do not inherit across the portal. Falling back
to the same role the token wraps keeps the control tinted in both places. */
[data-media-player-volume-slider] [data-slider] {
--slider-range-bg: var(--_mp-fg, var(--color-content-primary));
--slider-thumb-bg: var(--_mp-fg, var(--color-content-primary));
}
/* ── Volume, vertical (the floating panel) ────────────────────────────────
The Slider draws the whole vertical geometry itself — rail, range and thumb
all flip axis in its own recipe — so the player owes it only a LENGTH and the
removal of the inline width it wears inside a control row. */
[data-media-player-volume-slider][data-orientation='vertical'] {
inline-size: auto;
margin-inline-end: 0;
justify-content: center;
}
[data-media-player-volume-slider][data-orientation='vertical'] [data-slider] {
--slider-min-block-size: calc(var(--space-8) * 3);
flex: none;
--slider-range-bg: var(--_mp-fg);
--slider-thumb-bg: var(--_mp-fg);
}

@ -25,10 +25,6 @@ import type {
LiveIndicatorProps
} from '$soma/components/media-player';
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type {
PopoverProps,
ContentProps as PopoverContentProps
} from '$uix/eidos/components/popover';
/** The player's control-chrome size. Scales the buttons, time and spacing. */
export type MediaPlayerSize = Extract<Size, 'sm' | 'md' | 'lg' | 'xl'>;
@ -52,20 +48,6 @@ export type MediaPlayerTitleProps = TitleProps;
export type MediaPlayerTimeProps = TimeProps;
export type MediaPlayerTimeSliderProps = TimeSliderProps;
export type MediaPlayerVolumeSliderProps = VolumeSliderProps;
/**
* The floating volume. An eidos-only composition (no morfo part of its own —
* same footing as `DefaultControls` and `AudioLayout`): it arranges the canon
* `Popover`, `MuteButton` and `VolumeSlider` and adds no contract.
*/
export type MediaPlayerVolumeFloatProps = PopoverProps & {
/** ms of hover before the panel opens. Short: this is a control, not a tip. @default 120 */
openDelay?: number;
/** ms before it closes on leave — long enough to travel into it. @default 400 */
closeDelay?: number;
/** Which side of the speaker the panel sits on. @default 'top' */
side?: PopoverContentProps['side'];
};
export type MediaPlayerDefaultControlsProps = { id?: string };
// ── Audio-only parts (F5, PLAN-audio-player-v2.md) ───────────────────────────

@ -50,9 +50,6 @@
let ariaLabel = $state('');
let captionsTrack = $state(true);
let size = $state<'sm' | 'md' | 'lg' | 'xl'>('md');
// Volume shape: the always-visible rail, or the floating panel. Opt-in either
// way — the point of the control is to A/B them on the same bar.
let volume = $state<'inline' | 'float'>('inline');
// ── Audio mode (F5): the <AudioPlayer> competing root + its 4 shapes ────
let variant = $state<'card' | 'row' | 'bar' | 'inline'>('card');
@ -215,12 +212,8 @@
<MediaPlayer.PlayButton />
<MediaPlayer.SeekButton direction="backward" />
<MediaPlayer.SeekButton direction="forward" />
{#if volume === 'float'}
<MediaPlayer.VolumeFloat />
{:else}
<MediaPlayer.MuteButton />
<MediaPlayer.VolumeSlider />
{/if}
<MediaPlayer.MuteButton />
<MediaPlayer.VolumeSlider />
<MediaPlayer.Time type="current" />
<MediaPlayer.TimeSlider />
<MediaPlayer.Time type="duration" />
@ -354,20 +347,6 @@
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label
>volume <span data-uix-control-hint>rail or floating panel</span></span
>
<span data-uix-chips role="radiogroup">
{#each ['inline', 'float'] as v}
<button
data-uix-chip
data-active={volume === v}
onclick={() => (volume = v as typeof volume)}>{v}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>layout</span>
<span data-uix-chips role="radiogroup">

Loading…
Cancel
Save

Powered by TurnKey Linux.