Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(MWPW-146367):Added accessibility player controls (NON MPC) #3053

Merged
merged 42 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ae0fa67
updated feature with accessibility code
sharath-kannan Oct 15, 2024
b9ff89a
video accessiblity added for carousels
sharath-kannan Oct 16, 2024
d0b659f
added opt-out functionality
sharath-kannan Oct 16, 2024
66b50a8
fixed linting errors
sharath-kannan Oct 17, 2024
853df47
fixed unit test cases
sharath-kannan Oct 17, 2024
48517ac
fixed adobe tv issue
sharath-kannan Oct 18, 2024
e39ce89
hover and focus added
sharath-kannan Oct 18, 2024
1bb2332
controls positioned for rtl
sharath-kannan Oct 24, 2024
417e3f6
Merge branch 'adobecom:stage' into mwpw-146367
sharath-kannan Oct 24, 2024
1b7a379
hide-controls hash params added
sharath-kannan Oct 24, 2024
ae4d004
how to block controls position bug fix
sharath-kannan Oct 24, 2024
34db839
dark mode|bug fixes
sharath-kannan Oct 29, 2024
b233253
code enhancement
sharath-kannan Oct 29, 2024
0ac85c9
pause-play bug mouse click bug fix
sharath-kannan Oct 30, 2024
45f7091
marquee dark mode|positioning fix
sharath-kannan Oct 30, 2024
287ecb1
code enhancement
sharath-kannan Nov 4, 2024
51e2de3
handled marquee backward compatiblity
sharath-kannan Nov 4, 2024
90b734f
Added placeholder for labels|indexed video aria-labels
sharath-kannan Nov 5, 2024
a54bbc3
aria-label added for hover play videos
sharath-kannan Nov 5, 2024
2707f51
async awaited decorateVideo in video.js and other linting errors
sharath-kannan Nov 6, 2024
4444c4c
video indexes added
sharath-kannan Nov 7, 2024
f268e20
random video index and unit test cases updated
sharath-kannan Nov 9, 2024
e5f53cd
daa-ll is synced along with aria-label
sharath-kannan Nov 11, 2024
43a13b5
code enhancement
sharath-kannan Nov 11, 2024
afa16ea
Merge branch 'adobecom:stage' into mwpw-146367
sharath-kannan Nov 11, 2024
cdd5eac
nala test fix|code coverege
sharath-kannan Nov 12, 2024
1bc65be
nala test bug fix
sharath-kannan Nov 12, 2024
f0de063
nala test fix
sharath-kannan Nov 12, 2024
7e47a2c
right-left positioning is done for screens > 600px and a img fix
sharath-kannan Nov 12, 2024
6522411
getFedsconfig moved to feds file|url fetched from fedRoot function
sharath-kannan Nov 13, 2024
185bf61
linting fix
sharath-kannan Nov 13, 2024
3608371
Merge branch 'adobecom:stage' into mwpw-146367
sharath-kannan Nov 14, 2024
f9dac71
icons adapted to the figma
sharath-kannan Nov 14, 2024
db007bd
carousel and how-to fix with other minor fixes
sharath-kannan Nov 15, 2024
0248ad9
playpause wrapper adjusted for window
sharath-kannan Nov 15, 2024
0d964fa
icon offset bug fix
sharath-kannan Nov 18, 2024
1f6f617
Merge branch 'adobecom:stage' into mwpw-146367
sharath-kannan Nov 20, 2024
958f9aa
indentation of string literal
sharath-kannan Nov 27, 2024
518b4a0
figma match
sharath-kannan Dec 2, 2024
80a6f69
figma focus match
sharath-kannan Dec 2, 2024
224a542
Merge branch 'adobecom:stage' into mwpw-146367
sharath-kannan Dec 2, 2024
c32e97f
Merge branch 'stage' into mwpw-146367
sharath-kannan Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/blocks/adobetv/adobetv.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import url('../../styles/iframe.css');
@import url('../video/video.css');
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved

a[href*='.mp4'].hide-video {
visibility: hidden !important;
Expand Down
5 changes: 5 additions & 0 deletions libs/blocks/aside/aside.css
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
}

.aside.rounded-corners .foreground .image img,
.aside.rounded-corners .foreground .image:not(:has(.video-container)) .pause-play-wrapper,
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
.aside.rounded-corners .foreground .image video {
border-radius: 16px;
}
Expand Down Expand Up @@ -1074,6 +1075,10 @@
max-width: 1396px;
}

.aside.split .video-container .pause-play-wrapper img.accessibility-control {
width: auto;
}

overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
.aside.split .desktop-wide img,
.aside.split .desktop-wide video {
aspect-ratio: var(--aspect-ratio-wide);
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/aside/aside.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function decorateLayout(el) {
}
const foregroundImage = foreground.querySelector(':scope > div:not(.text) img')?.closest('div');
const bgImage = el.querySelector(':scope > div:not(.text):not(.foreground) img')?.closest('div');
const foregroundMedia = foreground.querySelector(':scope > div:not(.text) video, :scope > div:not(.text) a:is([href*=".mp4"], [href*="tv.adobe.com"])')?.closest('div');
const foregroundMedia = foreground.querySelector(':scope > div:not(.text) .video-container,:scope > div:not(.text) video, :scope > div:not(.text) a:is([href*=".mp4"], [href*="tv.adobe.com"])')?.closest('div:not(.video-container)');
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
const bgMedia = el.querySelector(':scope > div:not(.text):not(.foreground) video, :scope > div:not(.text):not(.foreground) a:is([href*=".mp4"], [href*="tv.adobe.com"])')?.closest('div');
const image = foregroundImage ?? bgImage;
const asideMedia = foregroundMedia ?? bgMedia ?? image;
Expand Down
10 changes: 10 additions & 0 deletions libs/blocks/brick/brick.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
margin: 0;
}

.brick .foreground div > .video-container {
margin: 0;
}

.brick .foreground div > * {
margin-top: var(--spacing-xs);
}
Expand Down Expand Up @@ -342,6 +346,12 @@
position: absolute;
}

.brick.split.row .foreground .brick-media .video-container img,
.brick.split.row .foreground .brick-media .video-container video {
width: 100%;
height: 100%;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

.brick .foreground .brick-media video,
.brick.split.row .foreground .brick-media video {
object-fit: fill;
Expand Down
6 changes: 3 additions & 3 deletions libs/blocks/carousel/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function moveSlides(event, carouselElements, jumpToIndex) {
referenceSlide.classList.remove('reference-slide');
referenceSlide.style.order = null;
activeSlide.classList.remove('active');
activeSlide.querySelectorAll('a').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', -1); });
activeSlide.querySelectorAll('a,video').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', -1); });
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
activeSlideIndicator.classList.remove('active');
activeSlideIndicator.setAttribute('tabindex', -1);

Expand Down Expand Up @@ -218,10 +218,10 @@ function moveSlides(event, carouselElements, jumpToIndex) {
if (index < show) {
tabIndex = 0;
}
slide.querySelectorAll('a').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', tabIndex); });
slide.querySelectorAll('a,:not(.video-container,.pause-play-wrapper) > video').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', tabIndex); });
});
} else {
activeSlide.querySelectorAll('a').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', 0); });
activeSlide.querySelectorAll('a,:not(.video-container,.pause-play-wrapper) > video').forEach((focusableElement) => { focusableElement.setAttribute('tabindex', 0); });
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}
activeSlideIndicator.classList.add('active');
activeSlideIndicator.setAttribute('tabindex', 0);
Expand Down
1 change: 1 addition & 0 deletions libs/blocks/figure/figure.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
.figure .figure {
margin: 0 auto;
max-width: 800px;
width: fit-content;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

.figure-list {
Expand Down
8 changes: 5 additions & 3 deletions libs/blocks/figure/figure.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { applyHoverPlay, decorateAnchorVideo } from '../../utils/decorate.js';
import { applyHoverPlay, decorateAnchorVideo, applyAccessibiltyEvents } from '../../utils/decorate.js';
import { createTag } from '../../utils/utils.js';

function buildCaption(pEl) {
Expand All @@ -21,6 +21,7 @@ function decorateVideo(clone, figEl) {
if (anchorTag && !anchorTag.hash) anchorTag.hash = '#autoplay';
if (anchorTag) decorateAnchorVideo({ src: anchorTag.href, anchorTag });
if (videoTag) {
const videoContainer = clone.querySelector('.video-container') || clone.querySelector('.pause-play-wrapper') || clone.querySelector('video');
videoTag.removeAttribute('data-mouseevent');
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
if (videoTag.dataset?.videoSource) {
videoTag.appendChild(
Expand All @@ -31,7 +32,8 @@ function decorateVideo(clone, figEl) {
);
}
applyHoverPlay(videoTag);
figEl.prepend(videoTag);
applyAccessibiltyEvents(videoTag);
figEl.prepend(videoContainer);
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -68,7 +70,7 @@ export function buildFigure(blockEl) {
const link = clone.querySelector('a');
if (link) {
const img = figEl.querySelector('picture') || figEl.querySelector('video');
if (img) {
if (img && !link.classList.contains('pause-play-wrapper')) {
// wrap picture or video in A tag
link.textContent = '';
link.append(img);
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/hero-marquee/hero-marquee.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export default async function init(el) {
foreground.classList.add('foreground', `cols-${fRows.length}`);
let copy = fRows[0];
const anyTag = foreground.querySelector('p, h1, h2, h3, h4, h5, h6');
const asset = foreground.querySelector('div > picture, div > video, div > a[href*=".mp4"], div > a.image-link');
const asset = foreground.querySelector('div > picture,div > .video-container,div > .pause-play-wrapper, div > video, div > a[href*=".mp4"], div > a.image-link');
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
const allRows = foreground.querySelectorAll('div > div');
copy = anyTag.closest('div');
copy.classList.add('copy');
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/marquee/marquee.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function decorateSplit(el, foreground, media) {

let mediaCreditInner;
const txtContent = media?.lastChild?.textContent?.trim();
if (txtContent?.match(/^http.*\.mp4/) || media?.lastChild?.tagName === 'VIDEO') return;
if (txtContent?.match(/^http.*\.mp4/) || media.querySelector('video')) return;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
if (txtContent) {
mediaCreditInner = createTag('p', { class: 'body-s' }, txtContent);
} else if (media.lastElementChild?.tagName !== 'PICTURE') {
Expand Down
95 changes: 95 additions & 0 deletions libs/blocks/video/video.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,98 @@ video {
max-width: 100%;
height: auto;
}

overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved

.pause-play-wrapper img {
min-height:40px !important;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

.video-container{
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
position: relative;
height:100%;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
width: fit-content;
margin: auto;
}

.aside .video-container,.marquee .video-container,.quiz-marquee .video-container {
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
width: auto;
}

.brick-media .video-container {
width: 100%;
height: 100%;
border-radius: inherit;
}

overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved

.pause-play-wrapper {
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
width: fit-content;
margin: auto;
}

.video-container .pause-play-wrapper {
width: 40px;
height: 40px;
display: flex;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
position: absolute;
right: 2%;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
bottom: 2%;
margin: 0;
background:#242424;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
border-radius: 50%;
z-index: 2;
cursor: pointer;
}

.video-container .pause-play-wrapper:hover {
background:#0000;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

.video-container .pause-play-wrapper:focus-visible {
background:#0000;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
border: 3px solid var(--color-white);
outline: var(--color-accent-focus-ring) solid 2px;
}

.editorial-card .video-container,
.editorial-card:not(:has(.video-container)) .pause-play-wrapper,
.hero-marquee:not(:has(.video-container)) .pause-play-wrapper,
.marquee.split:not(:has(.video-container)) .pause-play-wrapper {
width: auto;
height: 100%;
}

.brick .brick-media:not(:has(.video-container)) .pause-play-wrapper {
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
border-radius: 0;
border-top-right-radius: inherit;
border-bottom-right-radius: inherit;
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
width: auto;
height: 100%;
margin: 0;
}

.hero-marquee .background .video-container {
position: inherit;
}

.video-container .pause-play-wrapper img.accessibility-control {
width: auto;
}

.video-container .pause-play-wrapper img.hidden{
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
display: none;
}

.marquee .background .video-container {
display: contents;
}

@media (min-width: 900px) {
.media:not(.media-reverse-mobile, .media-reversed) .pause-play-wrapper,
.aside:not(.split-left) .pause-play-wrapper,
.hero-marquee.asset-left .pause-play-wrapper {
left: 2%;
}
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}
90 changes: 84 additions & 6 deletions libs/utils/decorate.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,73 @@
return `${globalAttrs} controls`;
}

export function syncPausePlayIcon(video) {
const playIcon = video.nextElementSibling?.querySelector('.play-icon');
const pauseIcon = video.nextElementSibling?.querySelector('.pause-icon');
if (video.paused || video.ended) {
playIcon?.classList.remove('hidden');
pauseIcon?.classList.add('hidden');
} else {
pauseIcon?.classList.remove('hidden');
playIcon?.classList.add('hidden');
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 240 in libs/utils/decorate.js

View check run for this annotation

Codecov / codecov/patch

libs/utils/decorate.js#L238-L240

Added lines #L238 - L240 were not covered by tests
}

export function addAccessibilityControl(videoString, videoAttributes, tabIndex = 0) {
if (!videoAttributes.includes('controls')) {
if (videoAttributes.includes('hoverplay')) {
return `<a class='pause-play-wrapper' tabindex=${tabIndex} alt='play/pause motion' aria-label='play/pause motion' >${videoString}
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
</a>`;
}
return `<div class='video-container'>${videoString}
<a class='pause-play-wrapper' role='button' tabindex=${tabIndex} alt='play/pause motion' aria-label='play/pause motion'>
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
<img class='accessibility-control pause-icon ${videoAttributes.includes('autoplay') ? '' : 'hidden'}' alt='pause icon' src='https://main--federal--adobecom.hlx.page/federal/assets/svgs/accessibility-pause.svg'/>
<img class='accessibility-control play-icon ${videoAttributes.includes('autoplay') ? 'hidden' : ''}' alt='play icon' src='https://main--federal--adobecom.hlx.page/federal/assets/svgs/accessibility-play.svg'/>
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
</a>
</div>`;
}
return videoString;
}
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved

export function handlePause(event) {
event.stopPropagation();
if (event.code !== 'Enter' && event.code !== 'Space' && !['focus', 'click', 'blur'].includes(event.type)) {
return;
}
event.preventDefault();
const video = event.target.parentElement.parentElement.querySelector('video');
if (event.type === 'blur') {
video.pause();
} else if (video.paused || video.ended || event.type === 'focus') {
video.play();
} else {
video.pause();
}
syncPausePlayIcon(video);
}

Check warning on line 274 in libs/utils/decorate.js

View check run for this annotation

Codecov / codecov/patch

libs/utils/decorate.js#L260-L274

Added lines #L260 - L274 were not covered by tests

export function applyHoverPlay(video) {
if (!video) return;
if (video.hasAttribute('data-hoverplay') && !video.hasAttribute('data-mouseevent')) {
video.addEventListener('mouseenter', () => { video.play(); });
video.addEventListener('mouseleave', () => { video.pause(); });
video.setAttribute('data-mouseevent', true);
if (video.hasAttribute('data-hoverplay')) {
video.parentElement.addEventListener('focus', handlePause);
video.parentElement.addEventListener('blur', handlePause);
if (!video.hasAttribute('data-mouseevent')) {
video.addEventListener('mouseenter', () => { video.play(); });
video.addEventListener('mouseleave', () => { video.pause(); });
video.addEventListener('ended', () => { syncPausePlayIcon(video); });
video.setAttribute('data-mouseevent', true);
}
}
}

export function applyAccessibiltyEvents(videoEl) {
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
const pausePlayWrapper = videoEl.parentElement.querySelector('.pause-play-wrapper') || videoEl.closest('.pause-play-wrapper');
if (pausePlayWrapper?.querySelector('.accessibility-control')) {
pausePlayWrapper?.addEventListener('click', handlePause);
pausePlayWrapper?.addEventListener('keydown', handlePause);
overmyheadandbody marked this conversation as resolved.
Show resolved Hide resolved
}
if (videoEl.hasAttribute('autoplay')) {
videoEl.addEventListener('ended', () => { syncPausePlayIcon(videoEl); });
}
}

Expand Down Expand Up @@ -269,7 +330,7 @@
const isHaveLoopAttr = video.getAttributeNames().includes('loop');
const { playedOnce = false } = video.dataset;
const isPlaying = video.currentTime > 0 && !video.paused && !video.ended
&& video.readyState > video.HAVE_CURRENT_DATA;
&& video.readyState > video.HAVE_CURRENT_DATA;

if (intersectionRatio <= 0.8) {
video.pause();
Expand Down Expand Up @@ -325,11 +386,27 @@
}
}

export function isAccessible(anchorTag) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have a general naming issue with the methods in this file. The file itself tailors to multiple types of content, including videos. isAccessible is relevant just for videos, but it could very well suggest it can apply to any other blocks. I'd say we need to better separate video logic as such through some better namings.

const section = anchorTag.closest('div[class="section"]');
if (section) {
const [block] = Array.from(section.children).filter((element) => element.contains(anchorTag));
return !block.classList.contains('hide-controls');
}
return true;
}

export function decorateAnchorVideo({ src = '', anchorTag }) {
if (!src.length || !(anchorTag instanceof HTMLElement)) return;
if (anchorTag.closest('.marquee, .aside, .hero-marquee, .quiz-marquee') && !anchorTag.hash) anchorTag.hash = '#autoplay';
const accessibilityEnabled = isAccessible(anchorTag);
const { dataset, parentElement } = anchorTag;
const video = `<video ${getVideoAttrs(anchorTag.hash, dataset)} data-video-source=${src}></video>`;
const attrs = getVideoAttrs(anchorTag.hash, dataset);
const tabIndex = anchorTag.tabIndex || 0;
const videoIndex = (tabIndex === -1) ? 'tabindex=-1' : '';
let video = `<video ${attrs} data-video-source=${src} ${videoIndex}></video>`;
if (accessibilityEnabled) {
video = addAccessibilityControl(video, attrs, tabIndex);
}
anchorTag.insertAdjacentHTML('afterend', video);
const videoEl = parentElement.querySelector('video');
createIntersectionObserver({
Expand All @@ -339,6 +416,7 @@
videoEl?.appendChild(createTag('source', { src, type: 'video/mp4' }));
},
});
if (accessibilityEnabled) applyAccessibiltyEvents(videoEl);
applyHoverPlay(videoEl);
applyInViewPortPlay(videoEl);
anchorTag.remove();
Expand Down
Loading