From 22cb57ccecfca54c24c593efc363a30cedf9d841 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:12:19 -0400 Subject: [PATCH] Align modals with refactored markup --- js/historyView.js | 45 +++++------- js/ui/components/EditModal.js | 22 +++++- js/ui/profileModalController.js | 126 ++++++++++++++++++++++++-------- js/ui/watchHistoryController.js | 2 +- 4 files changed, 137 insertions(+), 58 deletions(-) diff --git a/js/historyView.js b/js/historyView.js index 05e6a65c..15647aff 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -345,16 +345,15 @@ export function buildHistoryCard({ metadataPreference, }) { const article = document.createElement("article"); - article.className = "card flex flex-wrap items-stretch gap-6 p-5"; + article.className = "watch-history-card"; article.dataset.pointerKey = item.pointerKey; + article.dataset.historyCard = "true"; if (video?.isPrivate) { - article.dataset.state = "private"; - } else { - delete article.dataset.state; + article.classList.add("watch-history-card--private"); } const primary = document.createElement("div"); - primary.className = "flex min-w-0 flex-1 gap-4"; + primary.className = "watch-history-card__primary"; const playbackData = (() => { if (!video) { @@ -373,8 +372,7 @@ export function buildHistoryCard({ const pointerVideoId = getPointerVideoId(video, item.pointer); const thumbnailLink = document.createElement("a"); - thumbnailLink.className = - "focus-ring block max-w-[14rem] flex-shrink-0 overflow-hidden rounded-lg bg-slate-900 transition hover:opacity-90"; + thumbnailLink.className = "watch-history-card__thumbnail"; thumbnailLink.href = "#"; thumbnailLink.dataset.historyAction = "play"; thumbnailLink.dataset.pointerKey = item.pointerKey; @@ -389,9 +387,8 @@ export function buildHistoryCard({ } const thumbnailInner = document.createElement("div"); - thumbnailInner.className = "aspect-video w-full overflow-hidden rounded-lg bg-slate-900"; + thumbnailInner.className = "watch-history-card__thumbnailInner"; const thumbnailImg = document.createElement("img"); - thumbnailImg.className = "h-full w-full object-cover"; thumbnailImg.alt = video?.title || "Video thumbnail"; const thumbnailSrc = (video && typeof video.thumbnail === "string" && video.thumbnail.trim()) @@ -404,11 +401,10 @@ export function buildHistoryCard({ thumbnailLink.appendChild(thumbnailInner); const details = document.createElement("div"); - details.className = "flex min-w-0 flex-col justify-center gap-2"; + details.className = "watch-history-card__details"; const titleLink = document.createElement("a"); - titleLink.className = - "focus-ring text-left text-lg font-semibold text-white transition hover:text-blue-400"; + titleLink.className = "watch-history-card__title"; titleLink.href = "#"; titleLink.dataset.historyAction = "play"; titleLink.dataset.pointerKey = item.pointerKey; @@ -424,7 +420,7 @@ export function buildHistoryCard({ titleLink.textContent = video?.title || "Untitled video"; const created = document.createElement("p"); - created.className = "text-sm text-gray-400"; + created.className = "watch-history-card__created"; const createdAt = Number.isFinite(video?.created_at) ? video.created_at : null; created.textContent = createdAt ? `Published ${formatRelativeTime(createdAt)}` @@ -437,29 +433,27 @@ export function buildHistoryCard({ primary.appendChild(details); const meta = document.createElement("div"); - meta.className = "flex min-w-[14rem] flex-col justify-between gap-4"; + meta.className = "watch-history-card__meta"; const watched = document.createElement("p"); - watched.className = "text-sm text-gray-500"; + watched.className = "watch-history-card__watched"; watched.textContent = item.watchedAt ? `Watched ${formatRelativeTime(item.watchedAt)}` : "Watched time unavailable"; meta.appendChild(watched); const creator = document.createElement("div"); - creator.className = "flex items-center gap-3"; + creator.className = "watch-history-card__creator"; const creatorAvatarButton = document.createElement("button"); creatorAvatarButton.type = "button"; - creatorAvatarButton.className = - "focus-ring flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-slate-800"; + creatorAvatarButton.className = "watch-history-card__creatorAvatar"; creatorAvatarButton.dataset.historyAction = "channel"; creatorAvatarButton.dataset.pointerKey = item.pointerKey; if (video?.pubkey) { creatorAvatarButton.dataset.author = video.pubkey; } const avatarImg = document.createElement("img"); - avatarImg.className = "h-full w-full object-cover"; const avatarSrc = (profile && typeof profile.picture === "string" && profile.picture.trim()) ? profile.picture.trim() @@ -470,8 +464,7 @@ export function buildHistoryCard({ const creatorNameButton = document.createElement("button"); creatorNameButton.type = "button"; - creatorNameButton.className = - "focus-ring text-left text-sm font-medium text-gray-200 transition hover:text-blue-400"; + creatorNameButton.className = "watch-history-card__creatorName"; creatorNameButton.dataset.historyAction = "channel"; creatorNameButton.dataset.pointerKey = item.pointerKey; if (video?.pubkey) { @@ -491,11 +484,12 @@ export function buildHistoryCard({ meta.appendChild(creator); const actions = document.createElement("div"); - actions.className = "flex flex-wrap items-center gap-3"; + actions.className = "watch-history-card__actions"; const playButton = document.createElement("button"); playButton.type = "button"; - playButton.className = "btn focus-ring"; + playButton.className = + "watch-history-card__action watch-history-card__action--primary"; playButton.dataset.historyAction = "play"; playButton.dataset.pointerKey = item.pointerKey; if (pointerVideoId) { @@ -511,7 +505,7 @@ export function buildHistoryCard({ const channelButton = document.createElement("button"); channelButton.type = "button"; - channelButton.className = "btn-ghost focus-ring"; + channelButton.className = "watch-history-card__action"; channelButton.dataset.historyAction = "channel"; channelButton.dataset.pointerKey = item.pointerKey; if (video?.pubkey) { @@ -521,7 +515,8 @@ export function buildHistoryCard({ const removeButton = document.createElement("button"); removeButton.type = "button"; - removeButton.className = "btn-ghost focus-ring"; + removeButton.className = + "watch-history-card__action watch-history-card__action--danger"; removeButton.dataset.variant = "danger"; removeButton.dataset.historyAction = "remove"; removeButton.dataset.pointerKey = item.pointerKey; diff --git a/js/ui/components/EditModal.js b/js/ui/components/EditModal.js index 2deef5d2..686fa201 100644 --- a/js/ui/components/EditModal.js +++ b/js/ui/components/EditModal.js @@ -43,6 +43,7 @@ export class EditModal { this.root = null; this.overlay = null; + this.panel = null; this.form = null; this.closeButton = null; this.cancelButton = null; @@ -140,7 +141,14 @@ export class EditModal { } cacheElements(context) { - this.overlay = context.querySelector("#editVideoModalOverlay") || null; + this.overlay = + context.querySelector("#editVideoModalOverlay") || + context.querySelector(".bv-modal-backdrop") || + null; + this.panel = + context.querySelector(".bv-modal__panel") || + context.querySelector(".modal-content") || + null; this.form = context.querySelector("#editVideoForm") || null; this.closeButton = context.querySelector("#closeEditVideoModal") || null; this.cancelButton = context.querySelector("#cancelEditVideo") || null; @@ -388,8 +396,19 @@ export class EditModal { if (this.root) { this.root.classList.remove("hidden"); + this.root.setAttribute("aria-hidden", "false"); this.isVisible = true; this.setGlobalModalState("editVideo", true); + + const focusTarget = + this.form?.querySelector("[data-initial-focus]") || + this.panel || + this.root; + window.requestAnimationFrame(() => { + if (typeof focusTarget?.focus === "function") { + focusTarget.focus(); + } + }); } return true; @@ -532,6 +551,7 @@ export class EditModal { close({ emitCancel = false } = {}) { if (this.root) { this.root.classList.add("hidden"); + this.root.setAttribute("aria-hidden", "true"); } this.isVisible = false; this.setGlobalModalState("editVideo", false); diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index b9e2ca79..0879eef9 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -655,6 +655,9 @@ export class ProfileModalController { }; this.profileModal = null; + this.profileModalRoot = null; + this.profileModalPanel = null; + this.profileModalBackdrop = null; this.profileAvatar = null; this.profileName = null; this.profileNpub = null; @@ -743,6 +746,7 @@ export class ProfileModalController { this.boundKeydown = null; this.boundFocusIn = null; this.focusableElements = []; + this.focusTrapContainer = null; this.profileSwitcherSelectionPubkey = null; this.previouslyFocusedElement = null; this.setActivePane(this.getActivePane()); @@ -771,7 +775,12 @@ export class ProfileModalController { } this.modalContainer.appendChild(template.content); + this.profileModalRoot = modalRoot; this.profileModal = modalRoot; + this.profileModalPanel = + modalRoot.querySelector(".bv-modal__panel") || modalRoot; + this.profileModalBackdrop = + modalRoot.querySelector(".bv-modal-backdrop") || null; this.cacheDomReferences(); this.registerEventListeners(); @@ -782,7 +791,12 @@ export class ProfileModalController { } cacheDomReferences() { - this.profileModal = document.getElementById("profileModal") || null; + this.profileModalRoot = document.getElementById("profileModal") || null; + this.profileModalPanel = + this.profileModalRoot?.querySelector(".bv-modal__panel") || null; + this.profileModalBackdrop = + this.profileModalRoot?.querySelector(".bv-modal-backdrop") || null; + this.profileModal = this.profileModalRoot; this.closeButton = document.getElementById("closeProfileModal") || null; this.logoutButton = document.getElementById("profileLogoutBtn") || null; this.channelLink = document.getElementById("profileChannelLink") || null; @@ -1284,7 +1298,6 @@ export class ProfileModalController { normalizedSelection && normalizedPubkey === normalizedSelection; if (isSelected) { button.dataset.state = "active"; - button.classList.add("ring-1", "ring-blue-400/60", "bg-blue-500/10"); button.setAttribute("aria-pressed", "true"); } else { delete button.dataset.state; @@ -3598,16 +3611,21 @@ export class ProfileModalController { } updateFocusTrap() { - if (!(this.profileModal instanceof HTMLElement)) { + const container = + this.profileModalPanel instanceof HTMLElement + ? this.profileModalPanel + : this.profileModal instanceof HTMLElement + ? this.profileModal + : null; + + if (!container) { this.focusableElements = []; return; } const selector = 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'; - const nodes = Array.from( - this.profileModal.querySelectorAll(selector), - ); + const nodes = Array.from(container.querySelectorAll(selector)); this.focusableElements = nodes.filter((node) => { if (!(node instanceof HTMLElement)) { return false; @@ -3625,11 +3643,19 @@ export class ProfileModalController { return true; }); - this.bindFocusTrap(); + this.bindFocusTrap(container); } - bindFocusTrap() { - if (!(this.profileModal instanceof HTMLElement)) { + bindFocusTrap(container) { + const targetContainer = + container || + (this.profileModalPanel instanceof HTMLElement + ? this.profileModalPanel + : this.profileModal instanceof HTMLElement + ? this.profileModal + : null); + + if (!targetContainer) { return; } @@ -3647,8 +3673,10 @@ export class ProfileModalController { if (!this.focusableElements.length) { event.preventDefault(); - if (typeof this.profileModal.focus === "function") { - this.profileModal.focus(); + const fallback = + this.profileModalPanel || this.profileModal || targetContainer; + if (typeof fallback?.focus === "function") { + fallback.focus(); } return; } @@ -3658,7 +3686,7 @@ export class ProfileModalController { const active = document.activeElement; if (event.shiftKey) { - if (active === first || !this.profileModal.contains(active)) { + if (active === first || !targetContainer.contains(active)) { event.preventDefault(); if (typeof last?.focus === "function") { last.focus(); @@ -3678,22 +3706,36 @@ export class ProfileModalController { if (!this.boundFocusIn) { this.boundFocusIn = (event) => { + const modalRoot = this.profileModalRoot || this.profileModal; if ( - !this.profileModal || - this.profileModal.classList.contains("hidden") || - this.profileModal.contains(event.target) + !modalRoot || + modalRoot.classList.contains("hidden") || + modalRoot.contains(event.target) ) { return; } - const target = this.focusableElements[0] || this.profileModal; - if (typeof target?.focus === "function") { - target.focus(); + const fallback = + this.focusableElements[0] || + this.profileModalPanel || + this.profileModal; + if (typeof fallback?.focus === "function") { + fallback.focus(); } }; } - this.profileModal.addEventListener("keydown", this.boundKeydown); + if ( + this.focusTrapContainer && + this.focusTrapContainer !== targetContainer && + this.boundKeydown + ) { + this.focusTrapContainer.removeEventListener("keydown", this.boundKeydown); + } + + targetContainer.addEventListener("keydown", this.boundKeydown); + this.focusTrapContainer = targetContainer; + document.addEventListener("focusin", this.boundFocusIn); } @@ -3707,11 +3749,14 @@ export class ProfileModalController { document.activeElement instanceof HTMLElement ? document.activeElement : null; - if ( - activeElement && - this.profileModal instanceof HTMLElement && - this.profileModal.contains(activeElement) - ) { + const modalRoot = + this.profileModalRoot instanceof HTMLElement + ? this.profileModalRoot + : this.profileModal instanceof HTMLElement + ? this.profileModal + : null; + + if (activeElement && modalRoot && modalRoot.contains(activeElement)) { this.previouslyFocusedElement = null; } else { this.previouslyFocusedElement = activeElement; @@ -3757,16 +3802,26 @@ export class ProfileModalController { } open(pane = "account") { - if (!(this.profileModal instanceof HTMLElement)) { + const modalRoot = + this.profileModalRoot instanceof HTMLElement + ? this.profileModalRoot + : this.profileModal instanceof HTMLElement + ? this.profileModal + : null; + + if (!modalRoot) { return; } - this.profileModal.classList.remove("hidden"); - this.profileModal.setAttribute("aria-hidden", "false"); + modalRoot.classList.remove("hidden"); + modalRoot.setAttribute("aria-hidden", "false"); this.setGlobalModalState("profile", true); this.selectPane(pane); - const focusTarget = this.focusableElements[0] || this.profileModal; + const focusTarget = + this.focusableElements[0] || + this.profileModalPanel || + modalRoot; window.requestAnimationFrame(() => { if (typeof focusTarget?.focus === "function") { focusTarget.focus(); @@ -3779,15 +3834,22 @@ export class ProfileModalController { options && typeof options === "object" ? options : {}; const modalElement = - this.profileModal instanceof HTMLElement ? this.profileModal : null; + this.profileModalRoot instanceof HTMLElement + ? this.profileModalRoot + : this.profileModal instanceof HTMLElement + ? this.profileModal + : null; if (modalElement) { modalElement.classList.add("hidden"); modalElement.setAttribute("aria-hidden", "true"); this.setGlobalModalState("profile", false); - if (this.boundKeydown) { - modalElement.removeEventListener("keydown", this.boundKeydown); + if (this.boundKeydown && this.focusTrapContainer) { + this.focusTrapContainer.removeEventListener( + "keydown", + this.boundKeydown, + ); } } @@ -3795,6 +3857,8 @@ export class ProfileModalController { document.removeEventListener("focusin", this.boundFocusIn); } + this.focusTrapContainer = null; + if ( this.boundProfileHistoryVisibility && typeof document !== "undefined" && diff --git a/js/ui/watchHistoryController.js b/js/ui/watchHistoryController.js index f2d3058d..202dc989 100644 --- a/js/ui/watchHistoryController.js +++ b/js/ui/watchHistoryController.js @@ -10,7 +10,7 @@ function resolveCardElement(trigger) { if (!(trigger instanceof HTMLElement)) { return null; } - const candidate = trigger.closest(".video-card"); + const candidate = trigger.closest("[data-history-card]"); return candidate instanceof HTMLElement ? candidate : null; }