Skip to content

Commit

Permalink
fix: simplify captions listbox, make uniformer
Browse files Browse the repository at this point in the history
  • Loading branch information
luwes committed Jul 20, 2023
1 parent 77b7bf4 commit e3b8208
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 112 deletions.
163 changes: 77 additions & 86 deletions src/js/experimental/media-captions-listbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,48 @@ import { MediaChromeListbox, createOption } from './media-chrome-listbox.js';
import './media-chrome-option.js';
import { globalThis, document } from '../utils/server-safe-globals.js';
import { MediaUIAttributes, MediaUIEvents } from '../constants.js';
import { parseTextTracksStr, formatTextTrackObj } from '../utils/captions.js';
import { toggleSubsCaps } from '../utils/captions.js';
import { parseTextTracksStr, stringifyTextTrackList, formatTextTrackObj, toggleSubsCaps } from '../utils/captions.js';

const ccIcon = /*html*/`
<svg aria-hidden="true" viewBox="0 0 26 24">
<path d="M22.83 5.68a2.58 2.58 0 0 0-2.3-2.5c-3.62-.24-11.44-.24-15.06 0a2.58 2.58 0 0 0-2.3 2.5c-.23 4.21-.23 8.43 0 12.64a2.58 2.58 0 0 0 2.3 2.5c3.62.24 11.44.24 15.06 0a2.58 2.58 0 0 0 2.3-2.5c.23-4.21.23-8.43 0-12.64Zm-11.39 9.45a3.07 3.07 0 0 1-1.91.57 3.06 3.06 0 0 1-2.34-1 3.75 3.75 0 0 1-.92-2.67 3.92 3.92 0 0 1 .92-2.77 3.18 3.18 0 0 1 2.43-1 2.94 2.94 0 0 1 2.13.78c.364.359.62.813.74 1.31l-1.43.35a1.49 1.49 0 0 0-1.51-1.17 1.61 1.61 0 0 0-1.29.58 2.79 2.79 0 0 0-.5 1.89 3 3 0 0 0 .49 1.93 1.61 1.61 0 0 0 1.27.58 1.48 1.48 0 0 0 1-.37 2.1 2.1 0 0 0 .59-1.14l1.4.44a3.23 3.23 0 0 1-1.07 1.69Zm7.22 0a3.07 3.07 0 0 1-1.91.57 3.06 3.06 0 0 1-2.34-1 3.75 3.75 0 0 1-.92-2.67 3.88 3.88 0 0 1 .93-2.77 3.14 3.14 0 0 1 2.42-1 3 3 0 0 1 2.16.82 2.8 2.8 0 0 1 .73 1.31l-1.43.35a1.49 1.49 0 0 0-1.51-1.21 1.61 1.61 0 0 0-1.29.58A2.79 2.79 0 0 0 15 12a3 3 0 0 0 .49 1.93 1.61 1.61 0 0 0 1.27.58 1.44 1.44 0 0 0 1-.37 2.1 2.1 0 0 0 .6-1.15l1.4.44a3.17 3.17 0 0 1-1.1 1.7Z"/>
</svg>`;


const slotTemplate = document.createElement('template');
slotTemplate.innerHTML = /*html*/`
<slot hidden name="captions-indicator">${ccIcon}</slot>
`;

const compareTracks = (a, b) => {
return a.label === b.label && a.language === b.language;
}
/**
* @param {any} el Should be HTMLElement but issues with globalThis shim
* @param {string} attrName
* @returns {Array<Object>} An array of TextTrack-like objects.
*/
const getSubtitlesListAttr = (el, attrName) => {
const attrVal = el.getAttribute(attrName);
return attrVal ? parseTextTracksStr(attrVal) : [];
};

/**
*
* @param {any} el Should be HTMLElement but issues with globalThis shim
* @param {string} attrName
* @param {Array<Object>} list An array of TextTrack-like objects
*/
const setSubtitlesListAttr = (el, attrName, list) => {
// null, undefined, and empty arrays are treated as "no value" here
if (!list?.length) {
el.removeAttribute(attrName);
return;
}

// don't set if the new value is the same as existing
const newValStr = stringifyTextTrackList(list);
const oldVal = el.getAttribute(attrName);
if (oldVal === newValStr) return;

el.setAttribute(attrName, newValStr);
};

/**
* @attr {string} mediasubtitleslist - (read-only) A list of all subtitles and captions.
Expand All @@ -45,34 +70,21 @@ class MediaCaptionsListbox extends MediaChromeListbox {
#captionsIndicator;
/** @type {Element} */
#selectedIndicator;
#subs = [];
#offOption;
#prevState;

constructor() {
super({ slotTemplate });

this.#selectedIndicator = this.getSlottedIndicator('selected-indicator');
this.#captionsIndicator = this.getSlottedIndicator('captions-indicator');

const offOption = createOption('Off', 'off');
offOption.prepend(this.#selectedIndicator.cloneNode(true));
this.#offOption = offOption;
}

attributeChangedCallback(attrName, oldValue, newValue) {
if (attrName === MediaUIAttributes.MEDIA_SUBTITLES_LIST && oldValue !== newValue) {

this.#subs = this.#perTypeUpdate(newValue, this.#subs);

this.#render();

} else if (attrName === MediaUIAttributes.MEDIA_SUBTITLES_SHOWING && oldValue !== newValue) {
const selectedTrack = parseTextTracksStr(newValue ?? '')[0];

this.#subs.forEach(track => {
track.selected = track.language === selectedTrack.language && track.label === selectedTrack.label;
});
this.#render();
this.value = newValue;

} else if (attrName === 'aria-multiselectable') {
// diallow aria-multiselectable
Expand All @@ -84,93 +96,72 @@ class MediaCaptionsListbox extends MediaChromeListbox {
}

connectedCallback() {
this.#render();

this.addEventListener('change', this.#onChange);

super.connectedCallback();
this.addEventListener('change', this.#onChange);
}

disconnectedCallback() {
this.removeEventListener('change', this.#onChange);

super.disconnectedCallback();
this.removeEventListener('change', this.#onChange);
}

#perTypeUpdate(newValue, oldItems) {
const newItems = newValue ? parseTextTracksStr(newValue ?? '') : [];

const removedTracks = [];
const newTracks = [];
/**
* @type {Array<object>} An array of TextTrack-like objects.
* Objects must have the properties: kind, language, and label.
*/
get mediaSubtitlesList() {
return getSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST);
}

// find all the items that are no longer available
oldItems.forEach(track => {
if (!newItems.some(newTrack => compareTracks(newTrack, track))) {
removedTracks.push(track);
}
});
// find all the new items
newItems.forEach(track => {
if (!oldItems.some(newTrack => compareTracks(newTrack, track))) {
newTracks.push(track);
}
});
set mediaSubtitlesList(list) {
setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST, list);
}

// remove the removed tracks from the DOM
removedTracks.forEach(track => track.el.remove());
/**
* @type {Array<object>} An array of TextTrack-like objects.
* Objects must have the properties: kind, language, and label.
*/
get mediaSubtitlesShowing() {
return getSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING);
}

// filter out the removed tracks and include the new ones
return oldItems.filter(track => !removedTracks.includes(track)).concat(newTracks);
set mediaSubtitlesShowing(list) {
setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING, list);
}

#render() {
const container = this.shadowRoot.querySelector('slot');
if (!container.contains(this.#offOption)) {
container.append(this.#offOption);
}

if (!this.hasAttribute(MediaUIAttributes.MEDIA_SUBTITLES_SHOWING)) {
this.#offOption.setAttribute('aria-selected', 'true');
this.#offOption.setAttribute('tabindex', '0');
} else {
this.#offOption.setAttribute('aria-selected', 'false');
this.#offOption.setAttribute('tabindex', '-1');
}

this.#renderTracks(this.#subs);
}
if (this.#prevState === JSON.stringify(this.mediaSubtitlesList)) return;
this.#prevState = JSON.stringify(this.mediaSubtitlesList);

#renderTracks(tracks) {
const container = this.shadowRoot.querySelector('slot');
container.textContent = '';

tracks.forEach(track => {
let option = track.el;
let alreadyInDom = true;
const type = track.kind ?? 'subs';
const isOff = !this.value;

if (!option) {
alreadyInDom = false;
const option = createOption('Off', 'off', isOff);
option.prepend(this.#selectedIndicator.cloneNode(true));
container.append(option);

option = createOption(track.label, formatTextTrackObj(track));
option.prepend(this.#selectedIndicator.cloneNode(true));
const subtitlesList = this.mediaSubtitlesList;

// add CC icon for captions
if (type === 'captions') {
option.append(this.#captionsIndicator.cloneNode(true));
}
}
for (const subs of subtitlesList) {

if (track.selected) {
option.setAttribute('aria-selected', 'true');
} else {
option.setAttribute('aria-selected', 'false');
}
/** @type {HTMLOptionElement} */
const option = createOption(
subs.label,
formatTextTrackObj(subs),
this.value == formatTextTrackObj(subs),
);
option.prepend(this.#selectedIndicator.cloneNode(true));

if (!alreadyInDom) {
container.append(option);
track.el = option;
// add CC icon for captions
const type = subs.kind ?? 'subs';
if (type === 'captions') {
option.append(this.#captionsIndicator.cloneNode(true));
}
});

container.append(option);
}
}

#onChange() {
Expand Down
3 changes: 1 addition & 2 deletions src/js/experimental/media-playback-rate-listbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,11 @@ class MediaPlaybackRateListbox extends MediaChromeListbox {

connectedCallback() {
super.connectedCallback();

this.addEventListener('change', this.#onChange);
}

disconnectedCallback() {
super.disconnectedCallback();

this.removeEventListener('change', this.#onChange);
}

Expand All @@ -93,6 +91,7 @@ class MediaPlaybackRateListbox extends MediaChromeListbox {
container.textContent = '';

for (const rate of this.rates) {

/** @type {HTMLOptionElement} */
const option = createOption(`${rate}x`, rate, this.mediaPlaybackRate == rate);
option.prepend(this.#selectedIndicator.cloneNode(true));
Expand Down
30 changes: 6 additions & 24 deletions src/js/experimental/media-rendition-listbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,13 @@ class MediaRenditionListbox extends MediaChromeListbox {

/** @type {Element} */
#selectedIndicator;
#autoOption;
#renditionList = [];
#prevState;

constructor() {
super();

this.#selectedIndicator = this.getSlottedIndicator('selected-indicator');

const autoOption = createOption('Auto', 'auto');
autoOption.prepend(this.#selectedIndicator.cloneNode(true));
this.#autoOption = autoOption;
}

attributeChangedCallback(attrName, oldValue, newValue) {
Expand All @@ -50,24 +45,20 @@ class MediaRenditionListbox extends MediaChromeListbox {
}

connectedCallback() {
this.addEventListener('change', this.#onChange);

super.connectedCallback();
this.addEventListener('change', this.#onChange);
}

disconnectedCallback() {
this.removeEventListener('change', this.#onChange);

super.disconnectedCallback();
this.removeEventListener('change', this.#onChange);
}

get mediaRenditionList() {
return this.#renditionList;
}

set mediaRenditionList(list) {
this.removeAttribute(MediaUIAttributes.MEDIA_RENDITION_LIST);

this.#renditionList = list;
this.#render();
}
Expand All @@ -77,8 +68,6 @@ class MediaRenditionListbox extends MediaChromeListbox {
}

set mediaRenditionSelected(rendition) {
this.removeAttribute(MediaUIAttributes.MEDIA_RENDITION_SELECTED);

this.value = rendition?.id;
}

Expand All @@ -92,18 +81,11 @@ class MediaRenditionListbox extends MediaChromeListbox {
const container = this.shadowRoot.querySelector('slot');
container.textContent = '';

if (!container.contains(this.#autoOption)) {
container.append(this.#autoOption);
}

let isAuto = !this.mediaRenditionSelected;
if (isAuto) {
this.#autoOption.setAttribute('aria-selected', 'true');
this.#autoOption.setAttribute('tabindex', '0');
} else {
this.#autoOption.setAttribute('aria-selected', 'false');
this.#autoOption.setAttribute('tabindex', '-1');
}

const option = createOption('Auto', 'auto', isAuto);
option.prepend(this.#selectedIndicator.cloneNode(true));
container.append(option);

for (const rendition of renditionList) {

Expand Down

0 comments on commit e3b8208

Please sign in to comment.