diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f76d3f7..992bbaa0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +## [unreleased] + +### Added +- Configuration option in `Button` component to show button title on hover/focus + ## [3.67.0] - 2024-07-03 ### Added @@ -20,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Eco Mode toggle button + ## [3.64.0] - 2024-05-28 ### Added diff --git a/spec/components/fullscreentogglebutton.spec.ts b/spec/components/fullscreentogglebutton.spec.ts index 9e89f4ec5..46fd7ad21 100644 --- a/spec/components/fullscreentogglebutton.spec.ts +++ b/spec/components/fullscreentogglebutton.spec.ts @@ -14,6 +14,14 @@ describe('FullscreenToggleButton', () => { uiInstanceManagerMock = MockHelper.getUiInstanceManagerMock(); fullscreenToggleButton = new FullscreenToggleButton(); + (fullscreenToggleButton).textLabel = { + setText: jest.fn(), + getDomElement: () => MockHelper.generateDOMMock(), + initialize: jest.fn(), + show: jest.fn(), + hide: jest.fn(), + release: jest.fn(), + }; fullscreenToggleButton.initialize(); // Setup DOM Mock diff --git a/spec/components/pictureinpicturetogglebutton.spec.ts b/spec/components/pictureinpicturetogglebutton.spec.ts index edc9daec7..2c5ef5862 100644 --- a/spec/components/pictureinpicturetogglebutton.spec.ts +++ b/spec/components/pictureinpicturetogglebutton.spec.ts @@ -14,6 +14,14 @@ describe('PictureInPictureToggleButton', () => { uiInstanceManagerMock = MockHelper.getUiInstanceManagerMock(); pictureInPictureToggleButton = new PictureInPictureToggleButton(); + (pictureInPictureToggleButton).textLabel = { + setText: jest.fn(), + getDomElement: () => MockHelper.generateDOMMock(), + initialize: jest.fn(), + show: jest.fn(), + hide: jest.fn(), + release: jest.fn(), + }; pictureInPictureToggleButton.initialize(); // Setup DOM Mock diff --git a/src/scss/skin-modern/components/_button.scss b/src/scss/skin-modern/components/_button.scss index 108a574ed..8cdfaca81 100644 --- a/src/scss/skin-modern/components/_button.scss +++ b/src/scss/skin-modern/components/_button.scss @@ -43,3 +43,39 @@ .#{$prefix}-ui-button { @extend %ui-button; } + +.#{$prefix}-ui-labeledbutton { + @extend %ui-button; + + span, + label { + text-align: center; + position: absolute; + color: $color-primary; + transform: translateX(-50%); + white-space: normal; + } + + span.#{$prefix}-hidden, + label.#{$prefix}-hidden { + display: none; + } +} + +.#{$prefix}-ui-controlbar { + .#{$prefix}-ui-labeledbutton { + span, + label { + margin-top: -1.7em; + } + } +} + +.#{$prefix}-ui-titlebar { + .#{$prefix}-ui-labeledbutton { + span, + label { + margin-top: 1.2em; + } + } +} \ No newline at end of file diff --git a/src/ts/components/button.ts b/src/ts/components/button.ts index fe7671ba9..f5d92ceaa 100644 --- a/src/ts/components/button.ts +++ b/src/ts/components/button.ts @@ -2,6 +2,7 @@ import {ComponentConfig, Component} from './component'; import {DOM} from '../dom'; import {EventDispatcher, NoArgs, Event} from '../eventdispatcher'; import { LocalizableText , i18n } from '../localization/i18n'; +import { Label, LabelConfig } from './label'; /** * Configuration interface for a {@link Button} component. @@ -22,12 +23,20 @@ export interface ButtonConfig extends ComponentConfig { * Default: false */ acceptsTouchWithUiHidden?: boolean; + + /** + * Specifies whether the text of the button should be shown when the button is hovered or focused. + * + * Default: false + */ + showTextOnFocus?: boolean; } /** * A simple clickable button. */ export class Button extends Component { + protected textLabel: Label; private buttonEvents = { onClick: new EventDispatcher, NoArgs>(), @@ -41,7 +50,12 @@ export class Button extends Component { role: 'button', tabIndex: 0, acceptsTouchWithUiHidden: false, + showTextOnFocus: false, } as Config, this.config); + + if (this.config.showTextOnFocus) { + this.config.cssClasses = this.config.cssClasses.concat('ui-labeledbutton'); + } } protected toDomElement(): DOM { @@ -61,10 +75,21 @@ export class Button extends Component { buttonElementAttributes['role'] = this.config.role; } + this.textLabel = new Label({ + text: i18n.performLocalization(this.config.text), + for: this.config.id, + hidden: true, + }); + // Create the button element with the text label - let buttonElement = new DOM('button', buttonElementAttributes, this).append(new DOM('span', { - 'class': this.prefixCss('label'), - }).html(i18n.performLocalization(this.config.text))); + let buttonElement = new DOM('button', buttonElementAttributes, this).append(this.textLabel.getDomElement()); + + if (this.config.showTextOnFocus) { + buttonElement.on('focusin', (e) => this.textLabel.show()); + buttonElement.on('mouseenter', (e) => this.textLabel.show()); + buttonElement.on('focusout', (e) => this.textLabel.hide()); + buttonElement.on('mouseleave', (e) => this.textLabel.hide()); + } // Listen for the click event on the button element and trigger the corresponding event on the button component buttonElement.on('click', () => { @@ -79,13 +104,23 @@ export class Button extends Component { * @param text the text to put into the label of the button */ setText(text: LocalizableText): void { - this.getDomElement().find('.' + this.prefixCss('label')).html(i18n.performLocalization(text)); + this.textLabel.setText(text); } protected onClickEvent() { this.buttonEvents.onClick.dispatch(this); } + initialize(): void { + super.initialize(); + this.textLabel.initialize(); + } + + release(): void { + super.release(); + this.textLabel.release(); + } + /** * Gets the event that is fired when the button is clicked. * @returns {Event, NoArgs>} diff --git a/src/ts/uifactory.ts b/src/ts/uifactory.ts index 3e1266a09..f6d5852df 100644 --- a/src/ts/uifactory.ts +++ b/src/ts/uifactory.ts @@ -39,7 +39,7 @@ import { AdSkipButton } from './components/adskipbutton'; import { CloseButton } from './components/closebutton'; import { MetadataLabel, MetadataLabelContent } from './components/metadatalabel'; import { PlayerUtils } from './playerutils'; -import { Label } from './components/label'; +import { Label, LabelConfig } from './components/label'; import { CastUIContainer } from './components/castuicontainer'; import { UIConditionContext, UIManager } from './uimanager'; import { UIConfig } from './uiconfig'; @@ -193,7 +193,10 @@ export namespace UIFactory { new AdClickOverlay(), new PlaybackToggleOverlay(), new Container({ - components: [new AdMessageLabel({ text: i18n.getLocalizer('ads.remainingTime') }), new AdSkipButton()], + components: [ + new AdMessageLabel({ text: i18n.getLocalizer('ads.remainingTime') }), + new AdSkipButton(), + ], cssClass: 'ui-ads-status', }), new ControlBar({ @@ -339,7 +342,10 @@ export namespace UIFactory { ], }), new Container({ - components: [new AdMessageLabel({ text: 'Ad: {remainingTime} secs' }), new AdSkipButton()], + components: [ + new AdMessageLabel({ text: 'Ad: {remainingTime} secs' }), + new AdSkipButton(), + ], cssClass: 'ui-ads-status', }), ],