diff --git a/src/plugins/visual-search/components/SearchButton.js b/src/plugins/visual-search/components/SearchButton.js index 3ddaf2790..1b87197de 100644 --- a/src/plugins/visual-search/components/SearchButton.js +++ b/src/plugins/visual-search/components/SearchButton.js @@ -2,8 +2,9 @@ import videojs from 'video.js'; export const SearchButton = (onClick) => { const button = videojs.dom.createEl('button', { - className: 'vjs-visual-search-button', - title: 'Search video content' + className: 'vjs-control vjs-button vjs-visual-search-button', + title: 'Search video content', + ariaLabel: 'Search video content' }); const searchIcon = videojs.dom.createEl('span', { diff --git a/src/plugins/visual-search/components/SearchInput.js b/src/plugins/visual-search/components/SearchInput.js index 4f969bb56..69db040ac 100644 --- a/src/plugins/visual-search/components/SearchInput.js +++ b/src/plugins/visual-search/components/SearchInput.js @@ -7,13 +7,17 @@ export const SearchInput = (onSearch, onClose) => { const input = videojs.dom.createEl('input', { className: 'vjs-visual-search-input', - type: 'text' + type: 'text', + ariaLabel: 'Search input', + tabIndex: -1 }); const closeButton = videojs.dom.createEl('button', { - className: 'vjs-visual-search-close', + className: 'vjs-control vjs-button vjs-visual-search-close', type: 'button', - title: 'Close search' + title: 'Close search', + ariaLabel: 'Close search', + tabIndex: -1 }); // Add close icon @@ -44,6 +48,7 @@ export const SearchInput = (onSearch, onClose) => { return { element: form, - input + input, + closeButton }; }; diff --git a/src/plugins/visual-search/components/SearchResults.js b/src/plugins/visual-search/components/SearchResults.js index 6e9396cd1..8247b0094 100644 --- a/src/plugins/visual-search/components/SearchResults.js +++ b/src/plugins/visual-search/components/SearchResults.js @@ -1,51 +1,70 @@ import videojs from 'video.js'; -export const SearchResults = (player) => { +export const SearchResults = player => { const clearMarkers = () => { player.$$('.vjs-visual-search-marker').forEach(el => el.remove()); player.$$('.vjs-visual-search-results-wrapper').forEach(el => el.remove()); - + // Remove the class that indicates search results are displayed player.removeClass('vjs-visual-search-results-active'); }; - - const displayResults = (results) => { + + const displayResults = results => { // Clear existing markers clearMarkers(); - + const total = player.duration(); const seekBar = player.controlBar.progressControl.seekBar; - + // Create wrapper for search results const wrapperEl = videojs.dom.createEl('div', { - className: 'vjs-visual-search-results-wrapper' + className: 'vjs-visual-search-results-wrapper', + role: 'presentation' }); - + // Add markers for each result results.forEach(result => { const { start_time, end_time } = result; + const position = (start_time / total) * 100; + const width = ((end_time - start_time) / total) * 100; + const time = `${Math.floor(start_time / 60)}:${Math.floor(start_time % 60) + .toString() + .padStart(2, '0')}`; + const markerEl = videojs.dom.createEl('div', { - className: 'vjs-visual-search-marker', - style: `left: ${(start_time / total) * 100}%; width: ${((end_time - start_time) / total) * 100}%` + className: 'vjs-control vjs-visual-search-marker', + style: `left: ${position}%; width: ${width}%`, + tabIndex: 0, + role: 'button', + title: `Search result at ${time}`, + ariaLabel: `Search result at ${time}` }); - + wrapperEl.appendChild(markerEl); - + // Add click handler to jump to this time markerEl.addEventListener('click', () => { player.currentTime(start_time); }); + + // Add keyboard support + markerEl.addEventListener('keydown', e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + player.currentTime(start_time); + } + }); }); - + // Add wrapper to seek bar seekBar.el().appendChild(wrapperEl); - + // Add a class to indicate search results are displayed if (results.length > 0) { player.addClass('vjs-visual-search-results-active'); } }; - + return { displayResults, clearMarkers diff --git a/src/plugins/visual-search/visual-search.js b/src/plugins/visual-search/visual-search.js index b2d310992..5ff168bdd 100644 --- a/src/plugins/visual-search/visual-search.js +++ b/src/plugins/visual-search/visual-search.js @@ -74,6 +74,8 @@ const visualSearch = (options, player) => { if (!isSearchActive) { isSearchActive = true; searchContainer.classList.add('vjs-visual-search-active'); + searchInput.input.tabIndex = 0; + searchInput.closeButton.tabIndex = 0; searchInput.input.focus(); } else { const query = searchInput.input.value.trim(); @@ -88,6 +90,8 @@ const visualSearch = (options, player) => { isSearchActive = false; searchContainer.classList.remove('vjs-visual-search-active'); searchInput.input.value = ''; + searchInput.input.tabIndex = -1; + searchInput.closeButton.tabIndex = -1; searchResults.clearMarkers(); } diff --git a/src/plugins/visual-search/visual-search.scss b/src/plugins/visual-search/visual-search.scss index 78b0cab05..52813afb4 100644 --- a/src/plugins/visual-search/visual-search.scss +++ b/src/plugins/visual-search/visual-search.scss @@ -159,6 +159,10 @@ &:hover { opacity: 0.8; } + + &:focus { + box-shadow: inset 0 0 0 0.1em color-mix(in srgb, var(--color-text) 50%, transparent); + } } }