Fix for className being wiped out

This commit is contained in:
Sam Potts 2019-04-25 12:01:52 +10:00
parent ad68d9484f
commit f0d3e8c3b9

590
src/js/controls.js vendored
View File

@ -172,7 +172,7 @@ const controls = {
// Create a <button> // Create a <button>
createButton(buttonType, attr) { createButton(buttonType, attr) {
const attributes = Object.assign({}, attr); const attributes = extend({}, attr);
let type = toCamelCase(buttonType); let type = toCamelCase(buttonType);
const props = { const props = {
@ -198,8 +198,10 @@ const controls = {
// Set class name // Set class name
if (Object.keys(attributes).includes('class')) { if (Object.keys(attributes).includes('class')) {
if (!attributes.class.includes(this.config.classNames.control)) { if (!attributes.class.split(' ').some(c => c === this.config.classNames.control)) {
attributes.class += ` ${this.config.classNames.control}`; extend(attributes, {
class: `${attributes.class} ${this.config.classNames.control}`,
});
} }
} else { } else {
attributes.class = this.config.classNames.control; attributes.class = this.config.classNames.control;
@ -377,13 +379,13 @@ const controls = {
}, },
// Create time display // Create time display
createTime(type) { createTime(type, attrs) {
const attributes = getAttributesFromSelector(this.config.selectors.display[type]); const attributes = getAttributesFromSelector(this.config.selectors.display[type], attrs);
const container = createElement( const container = createElement(
'div', 'div',
extend(attributes, { extend(attributes, {
class: `${this.config.classNames.display.time} ${attributes.class ? attributes.class : ''}`.trim(), class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),
'aria-label': i18n.get(type, this.config), 'aria-label': i18n.get(type, this.config),
}), }),
'00:00', '00:00',
@ -1258,319 +1260,351 @@ const controls = {
}, },
// Build the default HTML // Build the default HTML
// TODO: Set order based on order in the config.controls array?
create(data) { create(data) {
const {
bindMenuItemShortcuts,
createButton,
createProgress,
createRange,
createTime,
setQualityMenu,
setSpeedMenu,
showMenuPanel,
} = controls;
this.elements.controls = null;
// Larger overlaid play button
if (this.config.controls.includes('play-large')) {
this.elements.container.appendChild(createButton.call(this, 'play-large'));
}
// Create the container // Create the container
const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper)); const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
this.elements.controls = container;
// Restart button // Default item attributes
if (this.config.controls.includes('restart')) { const defaultAttributes = { class: 'plyr__controls__item' };
container.appendChild(controls.createButton.call(this, 'restart'));
}
// Rewind button // Loop through controls in order
if (this.config.controls.includes('rewind')) { dedupe(this.config.controls).forEach(control => {
container.appendChild(controls.createButton.call(this, 'rewind')); // Restart button
} if (control === 'restart') {
container.appendChild(createButton.call(this, 'restart', defaultAttributes));
}
// Play/Pause button // Rewind button
if (this.config.controls.includes('play')) { if (control === 'rewind') {
container.appendChild(controls.createButton.call(this, 'play')); container.appendChild(createButton.call(this, 'rewind', defaultAttributes));
} }
// Fast forward button // Play/Pause button
if (this.config.controls.includes('fast-forward')) { if (control === 'play') {
container.appendChild(controls.createButton.call(this, 'fast-forward')); container.appendChild(createButton.call(this, 'play', defaultAttributes));
} }
// Progress // Fast forward button
if (this.config.controls.includes('progress')) { if (control === 'fast-forward') {
const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); container.appendChild(createButton.call(this, 'fast-forward', defaultAttributes));
}
// Seek range slider // Progress
progress.appendChild( if (control === 'progress') {
controls.createRange.call(this, 'seek', { const progressContainer = createElement('div', {
id: `plyr-seek-${data.id}`, class: `${defaultAttributes.class} plyr__progress__container`,
}), });
);
// Buffer progress const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
progress.appendChild(controls.createProgress.call(this, 'buffer'));
// TODO: Add loop display indicator // Seek range slider
progress.appendChild(
// Seek tooltip createRange.call(this, 'seek', {
if (this.config.tooltips.seek) { id: `plyr-seek-${data.id}`,
const tooltip = createElement( }),
'span',
{
class: this.config.classNames.tooltip,
},
'00:00',
); );
progress.appendChild(tooltip); // Buffer progress
this.elements.display.seekTooltip = tooltip; progress.appendChild(createProgress.call(this, 'buffer'));
// TODO: Add loop display indicator
// Seek tooltip
if (this.config.tooltips.seek) {
const tooltip = createElement(
'span',
{
class: this.config.classNames.tooltip,
},
'00:00',
);
progress.appendChild(tooltip);
this.elements.display.seekTooltip = tooltip;
}
this.elements.progress = progress;
progressContainer.appendChild(this.elements.progress);
container.appendChild(progressContainer);
} }
this.elements.progress = progress; // Media current time display
container.appendChild(this.elements.progress); if (control === 'current-time') {
} container.appendChild(createTime.call(this, 'currentTime', defaultAttributes));
// Media current time display
if (this.config.controls.includes('current-time')) {
container.appendChild(controls.createTime.call(this, 'currentTime'));
}
// Media duration display
if (this.config.controls.includes('duration')) {
container.appendChild(controls.createTime.call(this, 'duration'));
}
// Volume controls
if (this.config.controls.includes('mute') || this.config.controls.includes('volume')) {
const volume = createElement('div', {
class: 'plyr__volume',
});
// Toggle mute button
if (this.config.controls.includes('mute')) {
volume.appendChild(controls.createButton.call(this, 'mute'));
} }
// Volume range control // Media duration display
if (this.config.controls.includes('volume')) { if (control === 'duration') {
// Set the attributes container.appendChild(createTime.call(this, 'duration', defaultAttributes));
const attributes = { }
max: 1,
step: 0.05,
value: this.config.volume,
};
// Create the volume range slider // Volume controls
volume.appendChild( if (control === 'mute' || control === 'volume') {
controls.createRange.call( let { volume } = this.elements;
this,
'volume', // Create the volume container if needed
extend(attributes, { if (!is.element(volume) || !container.contains(volume)) {
id: `plyr-volume-${data.id}`, volume = createElement(
'div',
extend({}, defaultAttributes, {
class: `${defaultAttributes.class} plyr__volume`.trim(),
}), }),
), );
);
this.elements.volume = volume; this.elements.volume = volume;
container.appendChild(volume);
}
// Toggle mute button
if (control === 'mute') {
volume.appendChild(createButton.call(this, 'mute'));
}
// Volume range control
if (control === 'volume') {
// Set the attributes
const attributes = {
max: 1,
step: 0.05,
value: this.config.volume,
};
// Create the volume range slider
volume.appendChild(
createRange.call(
this,
'volume',
extend(attributes, {
id: `plyr-volume-${data.id}`,
}),
),
);
}
} }
container.appendChild(volume); // Toggle captions button
} if (control === 'captions') {
container.appendChild(createButton.call(this, 'captions', defaultAttributes));
}
// Toggle captions button // Settings button / menu
if (this.config.controls.includes('captions')) { if (control === 'settings' && !is.empty(this.config.settings)) {
container.appendChild(controls.createButton.call(this, 'captions')); const control = createElement(
} 'div',
extend({}, defaultAttributes, {
// Settings button / menu class: `${defaultAttributes.class} plyr__menu`.trim(),
if (this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
const control = createElement('div', {
class: 'plyr__menu',
hidden: '',
});
control.appendChild(
controls.createButton.call(this, 'settings', {
'aria-haspopup': true,
'aria-controls': `plyr-settings-${data.id}`,
'aria-expanded': false,
}),
);
const popup = createElement('div', {
class: 'plyr__menu__container',
id: `plyr-settings-${data.id}`,
hidden: '',
});
const inner = createElement('div');
const home = createElement('div', {
id: `plyr-settings-${data.id}-home`,
});
// Create the menu
const menu = createElement('div', {
role: 'menu',
});
home.appendChild(menu);
inner.appendChild(home);
this.elements.settings.panels.home = home;
// Build the menu items
this.config.settings.forEach(type => {
// TODO: bundle this with the createMenuItem helper and bindings
const menuItem = createElement(
'button',
extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
role: 'menuitem',
'aria-haspopup': true,
hidden: '', hidden: '',
}), }),
); );
// Bind menu shortcuts for keyboard users control.appendChild(
controls.bindMenuItemShortcuts.call(this, menuItem, type); createButton.call(this, 'settings', {
'aria-haspopup': true,
// Show menu on click 'aria-controls': `plyr-settings-${data.id}`,
on(menuItem, 'click', () => { 'aria-expanded': false,
controls.showMenuPanel.call(this, type, false);
});
const flex = createElement('span', null, i18n.get(type, this.config));
const value = createElement('span', {
class: this.config.classNames.menu.value,
});
// Speed contains HTML entities
value.innerHTML = data[type];
flex.appendChild(value);
menuItem.appendChild(flex);
menu.appendChild(menuItem);
// Build the panes
const pane = createElement('div', {
id: `plyr-settings-${data.id}-${type}`,
hidden: '',
});
// Back button
const backButton = createElement('button', {
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--back`,
});
// Visible label
backButton.appendChild(
createElement(
'span',
{
'aria-hidden': true,
},
i18n.get(type, this.config),
),
);
// Screen reader label
backButton.appendChild(
createElement(
'span',
{
class: this.config.classNames.hidden,
},
i18n.get('menuBack', this.config),
),
);
// Go back via keyboard
on(
pane,
'keydown',
event => {
// We only care about <-
if (event.which !== 37) {
return;
}
// Prevent seek
event.preventDefault();
event.stopPropagation();
// Show the respective menu
controls.showMenuPanel.call(this, 'home', true);
},
false,
);
// Go back via button click
on(backButton, 'click', () => {
controls.showMenuPanel.call(this, 'home', false);
});
// Add to pane
pane.appendChild(backButton);
// Menu
pane.appendChild(
createElement('div', {
role: 'menu',
}), }),
); );
inner.appendChild(pane); const popup = createElement('div', {
class: 'plyr__menu__container',
this.elements.settings.buttons[type] = menuItem; id: `plyr-settings-${data.id}`,
this.elements.settings.panels[type] = pane; hidden: '',
});
popup.appendChild(inner);
control.appendChild(popup);
container.appendChild(control);
this.elements.settings.popup = popup;
this.elements.settings.menu = control;
}
// Picture in picture button
if (this.config.controls.includes('pip') && support.pip) {
container.appendChild(controls.createButton.call(this, 'pip'));
}
// Airplay button
if (this.config.controls.includes('airplay') && support.airplay) {
container.appendChild(controls.createButton.call(this, 'airplay'));
}
// Download button
if (this.config.controls.includes('download')) {
const attributes = {
element: 'a',
href: this.download,
target: '_blank',
};
const { download } = this.config.urls;
if (!is.url(download) && this.isEmbed) {
extend(attributes, {
icon: `logo-${this.provider}`,
label: this.provider,
}); });
const inner = createElement('div');
const home = createElement('div', {
id: `plyr-settings-${data.id}-home`,
});
// Create the menu
const menu = createElement('div', {
role: 'menu',
});
home.appendChild(menu);
inner.appendChild(home);
this.elements.settings.panels.home = home;
// Build the menu items
this.config.settings.forEach(type => {
// TODO: bundle this with the createMenuItem helper and bindings
const menuItem = createElement(
'button',
extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
role: 'menuitem',
'aria-haspopup': true,
hidden: '',
}),
);
// Bind menu shortcuts for keyboard users
bindMenuItemShortcuts.call(this, menuItem, type);
// Show menu on click
on(menuItem, 'click', () => {
showMenuPanel.call(this, type, false);
});
const flex = createElement('span', null, i18n.get(type, this.config));
const value = createElement('span', {
class: this.config.classNames.menu.value,
});
// Speed contains HTML entities
value.innerHTML = data[type];
flex.appendChild(value);
menuItem.appendChild(flex);
menu.appendChild(menuItem);
// Build the panes
const pane = createElement('div', {
id: `plyr-settings-${data.id}-${type}`,
hidden: '',
});
// Back button
const backButton = createElement('button', {
type: 'button',
class: `${this.config.classNames.control} ${this.config.classNames.control}--back`,
});
// Visible label
backButton.appendChild(
createElement(
'span',
{
'aria-hidden': true,
},
i18n.get(type, this.config),
),
);
// Screen reader label
backButton.appendChild(
createElement(
'span',
{
class: this.config.classNames.hidden,
},
i18n.get('menuBack', this.config),
),
);
// Go back via keyboard
on(
pane,
'keydown',
event => {
// We only care about <-
if (event.which !== 37) {
return;
}
// Prevent seek
event.preventDefault();
event.stopPropagation();
// Show the respective menu
showMenuPanel.call(this, 'home', true);
},
false,
);
// Go back via button click
on(backButton, 'click', () => {
showMenuPanel.call(this, 'home', false);
});
// Add to pane
pane.appendChild(backButton);
// Menu
pane.appendChild(
createElement('div', {
role: 'menu',
}),
);
inner.appendChild(pane);
this.elements.settings.buttons[type] = menuItem;
this.elements.settings.panels[type] = pane;
});
popup.appendChild(inner);
control.appendChild(popup);
container.appendChild(control);
this.elements.settings.popup = popup;
this.elements.settings.menu = control;
} }
container.appendChild(controls.createButton.call(this, 'download', attributes)); // Picture in picture button
} if (control === 'pip' && support.pip) {
container.appendChild(createButton.call(this, 'pip', defaultAttributes));
}
// Toggle fullscreen button // Airplay button
if (this.config.controls.includes('fullscreen')) { if (control === 'airplay' && support.airplay) {
container.appendChild(controls.createButton.call(this, 'fullscreen')); container.appendChild(createButton.call(this, 'airplay', defaultAttributes));
} }
// Larger overlaid play button // Download button
if (this.config.controls.includes('play-large')) { if (control === 'download') {
this.elements.container.appendChild(controls.createButton.call(this, 'play-large')); const attributes = extend({}, defaultAttributes, {
} element: 'a',
href: this.download,
target: '_blank',
});
this.elements.controls = container; const { download } = this.config.urls;
if (!is.url(download) && this.isEmbed) {
extend(attributes, {
icon: `logo-${this.provider}`,
label: this.provider,
});
}
container.appendChild(createButton.call(this, 'download', attributes));
}
// Toggle fullscreen button
if (control === 'fullscreen') {
container.appendChild(createButton.call(this, 'fullscreen', defaultAttributes));
}
});
// Set available quality levels // Set available quality levels
if (this.isHTML5) { if (this.isHTML5) {
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this)); setQualityMenu.call(this, html5.getQualityOptions.call(this));
} }
controls.setSpeedMenu.call(this); setSpeedMenu.call(this);
return container; return container;
}, },