Skip to content

Commit

Permalink
fix(ADA-1697): aria-label and seek fix (#42)
Browse files Browse the repository at this point in the history
* fix(ADA-1697): aria-label and seek fix

* chore: fix comments

* upd
  • Loading branch information
semarche-kaltura authored Sep 25, 2024
1 parent 78eba34 commit 8191c8c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 44 deletions.
7 changes: 5 additions & 2 deletions src/components/marker/timeline-marker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {useMemo, useRef, useEffect} from 'preact/hooks';
import {Chapter} from '../../../flow-typed/types/cue-point-option';

const {
redux: {useSelector}
redux: {useSelector, useDispatch},
reducers
} = KalturaPlayer.ui;

const {withText, Text} = KalturaPlayer.ui.preacti18n;

const translates = ({type, startTimeInText}: TimelineMarkerProps) => {
return {
markerAriaLabel: <Text id={"timeline.marker_aria_label"} fields={{ type, position: startTimeInText}} />
markerAriaLabel: <Text id={"timeline.marker_aria_label"} fields={{ type, position: startTimeInText}}>{`${type} at ${startTimeInText}`}</Text>
};
};

Expand All @@ -27,6 +28,7 @@ export const TimelineMarker = withText(translates)(({
markerStartTime,
markerAriaLabel
}: TimelineMarkerProps) => {
const dispatch = useDispatch();
const segment: Chapter = useSelector((state: any) =>
state.seekbar.segments.find((segment: Chapter) => markerStartTime >= segment.startTime && markerStartTime < segment.endTime)
);
Expand All @@ -40,6 +42,7 @@ export const TimelineMarker = withText(translates)(({
useEffect(() => setMarkerRef(markerRef?.current), []);
const renderMarker = useMemo(() => {
const handleFocus = () => {
dispatch(reducers.seekbar.actions.updateVirtualTime(markerStartTime));
const seekBarNode = getSeekBarNode();
if (seekBarNode) {
// change slider role to prevent interrupts reading marker content by screen-readers
Expand Down
83 changes: 47 additions & 36 deletions src/components/marker/timeline-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import {ItemTypes, ThumbnailInfo} from '../../types/timelineTypes';
import {Chapter, CuePointMarker} from '../../../flow-typed/types/cue-point-option';
import {Icon, IconSize} from '@playkit-js/common/dist/icon';

const {withText, Text} = KalturaPlayer.ui.preacti18n;

const {
preacti18n: {withText, Text},
redux: {connect},
reducers: {seekbar},
components: {PLAYER_SIZE}
Expand All @@ -25,9 +24,9 @@ const translates = {

interface TimelinePreviewProps {
toggleNavigationPlugin: (e: OnClickEvent, byKeyboard: boolean, cuePointType: string) => void;
seekTo: (time: number) => void;
seekTo: () => void;
cuePointsData: Array<CuePointMarker>;
thumbnailInfo: () => ThumbnailInfo | ThumbnailInfo[];
getThumbnailInfo: () => ThumbnailInfo | ThumbnailInfo[];
questionTranslate?: string;
reflectionPointTranslate?: string;
hotspotTranslate?: string;
Expand All @@ -40,7 +39,6 @@ interface TimelinePreviewProps {
showNavigationTranslate?: string;
hideNavigationTranslate?: string;
relevantChapter?: Chapter;
virtualTime?: number;
duration?: number;
getSeekBarNode: () => Element | null;
moveOnHover?: boolean;
Expand Down Expand Up @@ -94,17 +92,11 @@ interface TitleProps {
children?: VNode | string;
iconName: string;
shouldDisplayTitle: boolean;
ariaLabel?: string;
}

const Title = ({iconName, children, shouldDisplayTitle = true, ariaLabel = ''}: TitleProps) => {
let titleWrapperProps: any = { className: styles.titleWrapper };
if (ariaLabel) {
titleWrapperProps.ariaLabel = ariaLabel;
}

const Title = ({iconName, children, shouldDisplayTitle = true}: TitleProps) => {
return (
<div {...titleWrapperProps}>
<div className={styles.titleWrapper}>
<Icon size={IconSize.small} name={iconName} />
{shouldDisplayTitle && <span className={styles.title}>{children}</span>}
</div>
Expand All @@ -131,7 +123,6 @@ const mapStateToProps = (state: State, {markerStartTime}: TimelinePreviewProps)
isExtraSmallPlayer: state.shell.playerSize === PLAYER_SIZE.EXTRA_SMALL,
hidePreview: state.shell.playerSize === PLAYER_SIZE.TINY,
relevantChapter,
virtualTime: state.seekbar.virtualTime,
duration: state.engine.duration
};
};
Expand Down Expand Up @@ -164,7 +155,7 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
}

shouldComponentUpdate(nextProps: Readonly<TimelinePreviewProps>, nextState: Readonly<{}>, nextContext: any): boolean {
return this.props.duration !== nextProps.duration || this.props.virtualTime !== nextProps.virtualTime;
return this.props.duration !== nextProps.duration || this.props.relevantChapter !== nextProps.relevantChapter;
}

private _renderHeader(relevantChapter: Chapter | undefined, data: any) {
Expand All @@ -184,25 +175,25 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
if (!this.props.cuePointsData.length && relevantChapter?.title) {
// not a marker - render only chapter
return (
<Title iconName={'chapter'} shouldDisplayTitle >
<Title iconName={'chapter'} shouldDisplayTitle>
{relevantChapter.title}
</Title>
);
}
return (
<Fragment>
{hotspots.length > 0 && (
<Title iconName={'hotspot'} shouldDisplayTitle ariaLabel={this.props.hotspotTitleAriaLabelTranslate!}>
<Title iconName={'hotspot'} shouldDisplayTitle>
{this.props.hotspotTranslate!}
</Title>
)}
{quizQuestions.length > 0 && (
<Title iconName={'quiz'} shouldDisplayTitle >
<Title iconName={'quiz'} shouldDisplayTitle>
{(<span>{`${quizQuestionTitle.type} ${quizQuestionTitle.firstIndex}${quizQuestionTitle.lastIndex}`}</span>) as VNode}
</Title>
)}
{answerOnAir.length > 0 && (
<Title iconName={'answerOnAir'} shouldDisplayTitle >
<Title iconName={'answerOnAir'} shouldDisplayTitle>
{this.props.aoaTranslate!}
</Title>
)}
Expand Down Expand Up @@ -274,15 +265,15 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
};

onThumbnailClick = (e: MouseEvent) => {
this.props.seekTo(this.props.virtualTime!);
this.props.seekTo();
// prevent onMouseDown event on seekbar node
e.preventDefault();
e.stopPropagation();
};

onPreviewHeaderClick = (e: OnClickEvent, byKeyboard: boolean) => {
const relevantQuizQuestion = this.props.cuePointsData.find(cp => cp.type === ItemTypes.QuizQuestion);
relevantQuizQuestion ? relevantQuizQuestion.quizQuestionData?.onClick() : this.props.seekTo(this.props.virtualTime!);
relevantQuizQuestion ? relevantQuizQuestion.quizQuestionData?.onClick() : this.props.seekTo();
this.props.toggleNavigationPlugin(e, byKeyboard, this.props.cuePointsData[0]?.type || ItemTypes.Chapter);
};

Expand Down Expand Up @@ -341,12 +332,18 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
};

_getPreviewHeaderStyle(): any {
const top = !this.props.thumbnailInfo ? '-20px' : null;
const top = !this.props.getThumbnailInfo ? '-20px' : null;
const left = this._getPreviewHeaderLeft() === null ? null : `${this._getPreviewHeaderLeft}px`;
if (top === null && left === null) return null;
if (top === null && left === null) {
return null;
}
const style: any = {};
if (top) style.top = top;
if (left) style.left = left;
if (top) {
style.top = top;
}
if (left) {
style.left = left;
}
return style;
}

Expand All @@ -357,13 +354,34 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
return <div style={getFramePreviewImgStyle(thumbnailInfo)} />;
};

private _getCuePointPreviewHeaderProps = (data: any) => {
const {quizQuestions, hotspots, answerOnAir} = data;

let ariaLabel = '';
if (hotspots.length > 0 && quizQuestions.length === 0 && answerOnAir.length === 0) {
ariaLabel = this.props.hotspotTitleAriaLabelTranslate!;
}

const cuePointPreviewHeaderProps = {
className: styles.header,
'data-testid': 'cuePointPreviewHeader',
style: this._getPreviewHeaderStyle(),
tabIndex: 0,
onFocus: this.handleFocus,
onBlur: this.handleBlur,
...(ariaLabel ? {['aria-label']: ariaLabel} : {})
};
return cuePointPreviewHeaderProps;
};

render() {
if (this.props.hidePreview) {
return null;
}

const {thumbnailInfo, isExtraSmallPlayer, relevantChapter} = this.props;
const {getThumbnailInfo, isExtraSmallPlayer, relevantChapter} = this.props;
const data = this._getData();
const thumbnailInfo = getThumbnailInfo();
const className = [styles.container, this.props.isExtraSmallPlayer ? styles.xsPlayer : ''].join(' ');

return (
Expand All @@ -375,14 +393,7 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
onMouseLeave={() => this.onMouseLeave(relevantChapter)}>
{this._shouldRenderHeader(relevantChapter) ? (
<A11yWrapper onClick={this.onPreviewHeaderClick}>
<div
className={styles.header}
ref={node => (this._previewHeaderElement = node)}
data-testid="cuePointPreviewHeader"
style={this._getPreviewHeaderStyle()}
tabIndex={0}
onFocus={this.handleFocus}
onBlur={this.handleBlur}>
<div {...this._getCuePointPreviewHeaderProps(data)} ref={node => (this._previewHeaderElement = node)}>
<div className={styles.itemsWrapper} data-testid="cuePointPreviewHeaderItems">
{this._renderHeader(relevantChapter, data)}
</div>
Expand All @@ -392,11 +403,11 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
<div
className={styles.imageContainer}
data-testid="cuePointPreviewImageContainer"
style={getFramePreviewImgContainerStyle(thumbnailInfo())}
style={getFramePreviewImgContainerStyle(thumbnailInfo)}
onMouseDown={this.onThumbnailClick}
ref={node => (this._thumbnailContainerElement = node)}>
{isExtraSmallPlayer ? this._renderSmallPlayerHeader(relevantChapter, data) : null}
{this._renderThumbnail(thumbnailInfo())}
{this._renderThumbnail(thumbnailInfo)}
</div>
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions src/timeline-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ class TimelineManager {
return this.navigationPlugin.isVisible();
};

private _seekTo = (time: number) => {
this._player.currentTime = time;
private _seekTo = () => {
this._player.currentTime = this._state.seekbar.virtualTime;
};

private _handleChapter = (chapter: Chapter) => {
Expand All @@ -113,7 +113,7 @@ class TimelineManager {
} else if (!clipTo && seekFrom && duration) {
duration = duration - seekFrom;
}
if (this._player.engineType === EngineType.YOUTUBE){
if (this._player.engineType === EngineType.YOUTUBE) {
return Math.ceil(this._state.engine.duration) === duration;
}
return Math.round(this._state.engine.duration) === duration;
Expand Down Expand Up @@ -184,7 +184,7 @@ class TimelineManager {
toggleNavigationPlugin={this._toggleNavigationPlugin}
seekTo={this._seekTo}
cuePointsData={[]}
thumbnailInfo={() => this._getThumbnailInfo(this._state.seekbar.virtualTime)}
getThumbnailInfo={() => this._getThumbnailInfo(this._state.seekbar.virtualTime)}
getSeekBarNode={this._getSeekBarNode}
/>
)
Expand Down Expand Up @@ -286,7 +286,7 @@ class TimelineManager {
seekTo={this._seekTo}
toggleNavigationPlugin={this._toggleNavigationPlugin}
cuePointsData={timelineMarkerData.cuePointsData}
thumbnailInfo={() => this._getThumbnailInfo(markerStartTime)}
getThumbnailInfo={() => this._getThumbnailInfo(markerStartTime)}
markerStartTime={markerStartTime}
getSeekBarNode={this._getSeekBarNode}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/utils/duration-humanizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const getTimeInText = (seconds: number, uiConfig: any) => {
const durationHumanizer = getDurationHumanizer(uiConfig);
if (durationHumanizer) {
try {
return seconds ? durationHumanizer(seconds * 1000) : '';
return seconds ? durationHumanizer(seconds * 1000) : '0';
} catch (e: any) {
return `${seconds}`;
}
Expand Down

0 comments on commit 8191c8c

Please sign in to comment.