fix(packages): make tooltips visual-only and auto-forward media button labels (#1174)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
rahim
2026-04-02 14:20:01 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 341b405a21
commit 86cf3e8977
52 changed files with 242 additions and 485 deletions
+18 -2
View File
@@ -1,12 +1,19 @@
import type { InferComponentState, InferMediaState, MediaUIComponent, StateAttrMap } from '@videojs/core';
import type {
ButtonState,
InferComponentState,
InferMediaState,
MediaButtonComponent,
StateAttrMap,
} from '@videojs/core';
import { applyElementProps, applyStateDataAttrs, createButton, logMissingFeature } from '@videojs/core/dom';
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
import type { State } from '@videojs/store';
import type { PlayerController } from '../player/player-controller';
import { MediaElement } from './media-element';
/** Abstract base for HTML custom elements that render a media-control button. */
export abstract class MediaButtonElement<Core extends MediaUIComponent> extends MediaElement {
export abstract class MediaButtonElement<Core extends MediaButtonComponent> extends MediaElement {
static override properties: PropertyDeclarationMap = {
label: { type: String },
disabled: { type: Boolean },
@@ -21,6 +28,10 @@ export abstract class MediaButtonElement<Core extends MediaUIComponent> extends
protected abstract activate(state: InferMediaState<Core>): void;
get $state(): State<ButtonState> {
return this.core.state;
}
#disconnect: AbortController | null = null;
override connectedCallback(): void {
@@ -46,6 +57,11 @@ export abstract class MediaButtonElement<Core extends MediaUIComponent> extends
this.#disconnect = null;
}
/** Returns the button's current label derived from media state. */
getLabel(): string | undefined {
return this.core.state.current.label || undefined;
}
protected override willUpdate(changed: PropertyValues): void {
super.willUpdate(changed);
this.core.setProps?.(this);
@@ -1,4 +1,4 @@
import { TooltipCore, TooltipCSSVars, TooltipDataAttrs, type TooltipInput } from '@videojs/core';
import { type ButtonState, TooltipCore, TooltipCSSVars, TooltipDataAttrs, type TooltipInput } from '@videojs/core';
import {
applyElementProps,
applyStateDataAttrs,
@@ -13,12 +13,22 @@ import {
} from '@videojs/core/dom';
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
import { ContextConsumer } from '@videojs/element/context';
import type { State } from '@videojs/store';
import { SnapshotController } from '@videojs/store/html';
import { applyStyles, supportsAnchorPositioning, tryHidePopover, tryShowPopover } from '@videojs/utils/dom';
import { MediaElement } from '../media-element';
import { tooltipGroupContext } from './context';
type TriggerElement = HTMLElement & {
getLabel(): string | undefined;
$state: State<ButtonState>;
};
function isLabelTrigger(el: HTMLElement): el is TriggerElement {
return '$state' in el;
}
export class TooltipElement extends MediaElement {
static readonly tagName = 'media-tooltip';
@@ -150,9 +160,8 @@ export class TooltipElement extends MediaElement {
tryHidePopover(this);
}
// Apply trigger ARIA and anchor-name to the discovered trigger.
// Apply anchor-name to the discovered trigger for CSS positioning.
if (this.#currentTrigger) {
applyElementProps(this.#currentTrigger, this.#core.getTriggerAttrs(state, this.id));
applyStyles(this.#currentTrigger, getAnchorNameStyle(this.id));
}
@@ -205,15 +214,22 @@ export class TooltipElement extends MediaElement {
if (triggerEl && this.#tooltip) {
this.#triggerAbort = new AbortController();
applyElementProps(triggerEl, this.#tooltip.triggerProps, { signal: this.#triggerAbort.signal });
if (isLabelTrigger(triggerEl)) {
this.#syncContent(triggerEl);
triggerEl.$state.subscribe(() => this.#syncContent(triggerEl), {
signal: this.#triggerAbort.signal,
});
}
}
}
#syncContent(triggerEl: TriggerElement): void {
this.textContent = triggerEl.getLabel() ?? '';
}
#cleanupTrigger(): void {
if (this.#currentTrigger) {
// Remove ARIA attributes and anchor-name style from the old trigger.
applyElementProps(this.#currentTrigger, {
'aria-describedby': undefined,
});
this.#currentTrigger.style.removeProperty('anchor-name');
}