From 158a65c6e80c2509f673ca2630e055b2c5610386 Mon Sep 17 00:00:00 2001 From: Jason Henriquez Date: Tue, 29 Aug 2023 19:21:52 -0500 Subject: [PATCH 01/14] Add title and aria-pressed attributes to filter button --- src/renderer/components/top-nav/top-nav.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue index 291948ec65b7d..b4a604b393fc9 100644 --- a/src/renderer/components/top-nav/top-nav.vue +++ b/src/renderer/components/top-nav/top-nav.vue @@ -90,7 +90,9 @@ Date: Tue, 29 Aug 2023 20:25:38 -0500 Subject: [PATCH 02/14] Add radio button hover and focus styling with accent-color --- src/renderer/components/ft-radio-button/ft-radio-button.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/ft-radio-button/ft-radio-button.css b/src/renderer/components/ft-radio-button/ft-radio-button.css index 7c5aa29e1254f..762f4f41163ee 100644 --- a/src/renderer/components/ft-radio-button/ft-radio-button.css +++ b/src/renderer/components/ft-radio-button/ft-radio-button.css @@ -10,7 +10,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], } .pure-checkbox input[type="checkbox"]:focus + label:before, .pure-radiobutton input[type="checkbox"]:focus + label:before, .pure-checkbox input[type="radio"]:focus + label:before, .pure-radiobutton input[type="radio"]:focus + label:before, .pure-checkbox input[type="checkbox"]:hover + label:before, .pure-radiobutton input[type="checkbox"]:hover + label:before, .pure-checkbox input[type="radio"]:hover + label:before, .pure-radiobutton input[type="radio"]:hover + label:before { - border-color: var(--primary-color); + border: 2px solid var(--accent-color-active); } .pure-checkbox input[type="checkbox"]:active + label:before, .pure-radiobutton input[type="checkbox"]:active + label:before, .pure-checkbox input[type="radio"]:active + label:before, .pure-radiobutton input[type="radio"]:active + label:before { transition-duration: 0s; } @@ -85,6 +85,10 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], animation: borderscale 300ms ease-in; } +.pure-radiobutton input[type="radio"]:focus + label:after{ + background-color: var(--accent-color-active); +} + .pure-checkbox input[type="radio"]:checked + label:after, .pure-radiobutton input[type="radio"]:checked + label:after { transform: scale(1); } .pure-checkbox input[type="radio"] + label:before, .pure-radiobutton input[type="radio"] + label:before, .pure-checkbox input[type="radio"] + label:after, .pure-radiobutton input[type="radio"] + label:after { border-radius: 50%; } From 60b0594a832a21caf0817cf2cac4783b1416042a Mon Sep 17 00:00:00 2001 From: Jason Henriquez Date: Tue, 29 Aug 2023 20:26:28 -0500 Subject: [PATCH 03/14] Programmatically apply focus after clicking to open filter --- src/renderer/components/top-nav/top-nav.js | 9 ++++++++- src/renderer/components/top-nav/top-nav.vue | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js index 347d02ab6fc37..c7075f8c7ab8d 100644 --- a/src/renderer/components/top-nav/top-nav.js +++ b/src/renderer/components/top-nav/top-nav.js @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue' +import { defineComponent, nextTick } from 'vue' import { mapActions } from 'vuex' import FtInput from '../ft-input/ft-input.vue' import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue' @@ -273,6 +273,13 @@ export default defineComponent({ this.showFilters = false }, + toggleSearchFiltersDisplayed: function() { + this.showFilters = !this.showFilters + if (this.showFilters) { + nextTick(() => this.$refs.sortByRadio?.focus()) + } + }, + handleSearchFilterValueChanged: function (filterValueChanged) { this.searchFilterValueChanged = filterValueChanged }, diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue index b4a604b393fc9..2337941125ba9 100644 --- a/src/renderer/components/top-nav/top-nav.vue +++ b/src/renderer/components/top-nav/top-nav.vue @@ -95,8 +95,8 @@ :title="$t('Search Filters.Search Filters')" role="button" tabindex="0" - @click="showFilters = !showFilters" - @keydown.enter.prevent="showFilters = !showFilters" + @click="toggleSearchFiltersDisplayed" + @keydown.enter.prevent="toggleSearchFiltersDisplayed" /> Date: Tue, 29 Aug 2023 21:38:46 -0500 Subject: [PATCH 04/14] Implement filter-button-visible icon styling --- src/renderer/components/top-nav/top-nav.scss | 5 +++++ src/renderer/components/top-nav/top-nav.vue | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/top-nav/top-nav.scss b/src/renderer/components/top-nav/top-nav.scss index 6647409ff283f..7308177ffdbc7 100644 --- a/src/renderer/components/top-nav/top-nav.scss +++ b/src/renderer/components/top-nav/top-nav.scss @@ -96,6 +96,11 @@ box-shadow: 0 0 $effect-distance var(--text-with-main-color); } } + + &.showFilters { + background-color: var(--primary-text-color); + color: var(--card-bg-color); + } } .side { diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue index 2337941125ba9..9c9be81351300 100644 --- a/src/renderer/components/top-nav/top-nav.vue +++ b/src/renderer/components/top-nav/top-nav.vue @@ -89,7 +89,7 @@ /> Date: Wed, 30 Aug 2023 12:52:27 -0500 Subject: [PATCH 05/14] Use more standard means to grab grandchild ref --- src/renderer/components/top-nav/top-nav.js | 2 +- src/renderer/components/top-nav/top-nav.vue | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js index c7075f8c7ab8d..2eeb016a63fa7 100644 --- a/src/renderer/components/top-nav/top-nav.js +++ b/src/renderer/components/top-nav/top-nav.js @@ -276,7 +276,7 @@ export default defineComponent({ toggleSearchFiltersDisplayed: function() { this.showFilters = !this.showFilters if (this.showFilters) { - nextTick(() => this.$refs.sortByRadio?.focus()) + nextTick(() => this.$refs.searchFilters?.$refs.sortByRadio?.$el?.focus()) } }, diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue index 9c9be81351300..143f22541f25e 100644 --- a/src/renderer/components/top-nav/top-nav.vue +++ b/src/renderer/components/top-nav/top-nav.vue @@ -100,6 +100,7 @@ /> Date: Wed, 30 Aug 2023 19:24:02 -0500 Subject: [PATCH 06/14] Make search filters heading not selectable --- src/renderer/components/ft-search-filters/ft-search-filters.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/ft-search-filters/ft-search-filters.css b/src/renderer/components/ft-search-filters/ft-search-filters.css index b9052b3050c85..a124c04d86791 100644 --- a/src/renderer/components/ft-search-filters/ft-search-filters.css +++ b/src/renderer/components/ft-search-filters/ft-search-filters.css @@ -15,6 +15,8 @@ .center { text-align: center; + user-select: none; + -webkit-user-select: none; } .searchRadio { From b136e15216cb5566b7caf9e54e33e2b66da16ce8 Mon Sep 17 00:00:00 2001 From: Jason Henriquez Date: Wed, 30 Aug 2023 19:24:33 -0500 Subject: [PATCH 07/14] Additional icon and radio button styling updates --- .../ft-radio-button/ft-radio-button.css | 14 ++++++------- src/renderer/components/top-nav/top-nav.scss | 21 +++++++++++++------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/renderer/components/ft-radio-button/ft-radio-button.css b/src/renderer/components/ft-radio-button/ft-radio-button.css index 762f4f41163ee..45d58d75a084d 100644 --- a/src/renderer/components/ft-radio-button/ft-radio-button.css +++ b/src/renderer/components/ft-radio-button/ft-radio-button.css @@ -10,7 +10,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], } .pure-checkbox input[type="checkbox"]:focus + label:before, .pure-radiobutton input[type="checkbox"]:focus + label:before, .pure-checkbox input[type="radio"]:focus + label:before, .pure-radiobutton input[type="radio"]:focus + label:before, .pure-checkbox input[type="checkbox"]:hover + label:before, .pure-radiobutton input[type="checkbox"]:hover + label:before, .pure-checkbox input[type="radio"]:hover + label:before, .pure-radiobutton input[type="radio"]:hover + label:before { - border: 2px solid var(--accent-color-active); + border: 2px solid var(--primary-color); } .pure-checkbox input[type="checkbox"]:active + label:before, .pure-radiobutton input[type="checkbox"]:active + label:before, .pure-checkbox input[type="radio"]:active + label:before, .pure-radiobutton input[type="radio"]:active + label:before { transition-duration: 0s; } @@ -29,14 +29,14 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], .pure-checkbox input[type="checkbox"] + label:before, .pure-radiobutton input[type="checkbox"] + label:before, .pure-checkbox input[type="radio"] + label:before, .pure-radiobutton input[type="radio"] + label:before { box-sizing: content-box; content: ''; - color: var(--primary-color); + color: var(--primary-text-color); position: absolute; top: 50%; left: 0; width: 14px; height: 14px; margin-top: -9px; - border: 2px solid var(--primary-color); + border: 2px solid var(--primary-text-color); text-align: center; transition: all 0.4s ease; } @@ -44,7 +44,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], .pure-checkbox input[type="checkbox"] + label:after, .pure-radiobutton input[type="checkbox"] + label:after, .pure-checkbox input[type="radio"] + label:after, .pure-radiobutton input[type="radio"] + label:after { box-sizing: content-box; content: ''; - background-color: var(--primary-color); + background-color: var(--primary-text-color); position: absolute; top: 50%; left: 4px; @@ -86,7 +86,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], } .pure-radiobutton input[type="radio"]:focus + label:after{ - background-color: var(--accent-color-active); + background-color: var(--primary-color); } .pure-checkbox input[type="radio"]:checked + label:after, .pure-radiobutton input[type="radio"]:checked + label:after { transform: scale(1); } @@ -95,14 +95,14 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"], .pure-checkbox input[type="checkbox"]:checked + label:before, .pure-radiobutton input[type="checkbox"]:checked + label:before { animation: borderscale 200ms ease-in; - background: var(--primary-color); + background: var(--primary-text-color); } .pure-checkbox input[type="checkbox"]:checked + label:after, .pure-radiobutton input[type="checkbox"]:checked + label:after { transform: rotate(-45deg) scale(1); } @keyframes borderscale { 50% { - box-shadow: 0 0 0 2px var(--primary-color); + box-shadow: 0 0 0 2px var(--primary-text-color); } } diff --git a/src/renderer/components/top-nav/top-nav.scss b/src/renderer/components/top-nav/top-nav.scss index 7308177ffdbc7..12d686ad98ebc 100644 --- a/src/renderer/components/top-nav/top-nav.scss +++ b/src/renderer/components/top-nav/top-nav.scss @@ -85,22 +85,31 @@ } .navFilterIcon { - $effect-distance: 10px; + $effect-distance: 20px; margin-left: $effect-distance; + &.showFilters { + background-color: var(--primary-text-color); + color: var(--card-bg-color); + box-shadow: 0 0 $effect-distance var(--primary-color); + transform: translateY(1px); + + // @include top-nav-is-colored { + // background-color: var(--primary-text-color); + // color: var(--card-bg-color); + // } + } + &.filterChanged { box-shadow: 0 0 $effect-distance var(--primary-color); + color: var(--primary-color); @include top-nav-is-colored { box-shadow: 0 0 $effect-distance var(--text-with-main-color); + color: var(--text-with-main-color); } } - - &.showFilters { - background-color: var(--primary-text-color); - color: var(--card-bg-color); - } } .side { From fbb633275874e1f0f5709c182fef2689b645d1aa Mon Sep 17 00:00:00 2001 From: Jason Henriquez Date: Thu, 18 Apr 2024 09:36:07 -0500 Subject: [PATCH 08/14] Implement modal focus management with portal-vue For the importance of modal focus management, see: https://accessibility.huit.harvard.edu/technique-accessible-modal-dialogs --- package.json | 1 + src/renderer/App.js | 13 ++- src/renderer/App.vue | 90 +++++++++-------- .../ft-create-playlist-prompt.js | 4 +- .../ft-playlist-add-video-prompt.js | 4 +- .../components/ft-prompt/ft-prompt.js | 17 ++-- .../components/ft-prompt/ft-prompt.vue | 97 ++++++++++--------- .../ft-search-filters/ft-search-filters.js | 2 +- .../ft-search-filters/ft-search-filters.vue | 4 +- src/renderer/main.js | 2 + yarn.lock | 5 + 11 files changed, 131 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index a9ae18cc225a1..3e4437969dd5f 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "lodash.debounce": "^4.0.8", "marked": "^12.0.1", "path-browserify": "^1.0.1", + "portal-vue": "^2.1.7", "process": "^0.11.10", "swiper": "^11.1.1", "video.js": "7.21.5", diff --git a/src/renderer/App.js b/src/renderer/App.js index 009dc2cc46805..853f308a77e34 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -48,6 +48,7 @@ export default defineComponent({ latestBlogUrl: '', updateChangelog: '', changeLogTitle: '', + isPromptOpen: false, lastExternalLinkToBeOpened: '', showExternalLinkOpeningPrompt: false, externalLinkOpeningPromptValues: [ @@ -148,12 +149,6 @@ export default defineComponent({ secColor: 'checkThemeSettings', locale: 'setLocale', - - $route () { - // react to route changes... - // Hide top nav filter panel on page change - this.$refs.topNav?.hideFilters() - } }, created () { this.checkThemeSettings() @@ -300,6 +295,10 @@ export default defineComponent({ this.showBlogBanner = false }, + handlePromptPortalUpdate: function(newVal) { + this.isPromptOpen = newVal + }, + openDownloadsPage: function () { const url = 'https://freetubeapp.io#download' openExternalLink(url) @@ -549,7 +548,7 @@ export default defineComponent({ 'updateMainColor', 'updateSecColor', 'showOutlines', - 'hideOutlines' + 'hideOutlines', ]) } }) diff --git a/src/renderer/App.vue b/src/renderer/App.vue index dbd207735b66c..159f5ce38dfbd 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -8,45 +8,10 @@ isLocaleRightToLeft: isLocaleRightToLeft }" > - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/renderer/components/ft-create-playlist-prompt/ft-create-playlist-prompt.js b/src/renderer/components/ft-create-playlist-prompt/ft-create-playlist-prompt.js index 1beac5852a38a..f2e989dde68ba 100644 --- a/src/renderer/components/ft-create-playlist-prompt/ft-create-playlist-prompt.js +++ b/src/renderer/components/ft-create-playlist-prompt/ft-create-playlist-prompt.js @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue' +import { defineComponent, nextTick } from 'vue' import { mapActions } from 'vuex' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtPrompt from '../ft-prompt/ft-prompt.vue' @@ -34,7 +34,7 @@ export default defineComponent({ mounted: function () { this.playlistName = this.newPlaylistVideoObject.title // Faster to input required playlist name - this.$refs.playlistNameInput.focus() + nextTick(() => this.$refs.playlistNameInput.focus()) }, methods: { createNewPlaylist: function () { diff --git a/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js b/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js index 27c1f376925e1..b66f5333fa732 100644 --- a/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js +++ b/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue' +import { defineComponent, nextTick } from 'vue' import { mapActions } from 'vuex' import debounce from 'lodash.debounce' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' @@ -201,7 +201,7 @@ export default defineComponent({ this.updateQueryDebounce = debounce(this.updateQuery, 500) // User might want to search first if they have many playlists - this.$refs.searchBar.focus() + nextTick(() => this.$refs.searchBar.focus()) }, beforeDestroy() { this.lastActiveElement?.focus() diff --git a/src/renderer/components/ft-prompt/ft-prompt.js b/src/renderer/components/ft-prompt/ft-prompt.js index 45296bbfd8feb..c73e2cdcb68f8 100644 --- a/src/renderer/components/ft-prompt/ft-prompt.js +++ b/src/renderer/components/ft-prompt/ft-prompt.js @@ -56,15 +56,16 @@ export default defineComponent({ }, mounted: function () { this.lastActiveElement = document.activeElement - - document.addEventListener('keydown', this.closeEventFunction, true) - document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true) - this.promptButtons = Array.from( - document.querySelector('.prompt .promptCard .ft-flex-box').childNodes - ).filter((e) => { - return e.id && e.id.startsWith('prompt') + this.$nextTick(() => { + document.addEventListener('keydown', this.closeEventFunction, true) + document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true) + this.promptButtons = Array.from( + document.querySelector('.prompt .promptCard .ft-flex-box').childNodes + ).filter((e) => { + return e.id && e.id.startsWith('prompt') + }) + this.focusItem(0) }) - this.focusItem(0) }, methods: { hide: function() { diff --git a/src/renderer/components/ft-prompt/ft-prompt.vue b/src/renderer/components/ft-prompt/ft-prompt.vue index dfcd84ad71e94..02d2ac0154490 100644 --- a/src/renderer/components/ft-prompt/ft-prompt.vue +++ b/src/renderer/components/ft-prompt/ft-prompt.vue @@ -1,52 +1,55 @@