mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
Make Discover More indicator a focusable button for TV display mode
This commit is contained in:
+14
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
.spotlight-section {
|
||||
position: relative;
|
||||
width: calc(100% - (2 * max(env(safe-area-inset-left), 3.3%)));
|
||||
width: calc(100% - (1 * max(env(safe-area-inset-left), 3.3%)));
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
@@ -4327,10 +4327,23 @@ html.sleekfin-main-ui .emby-scroller-container[data-section-id="liveTv-channels"
|
||||
background-color: var(--theme-primary-color, #00a4dc) !important;
|
||||
background: var(--theme-primary-color, #00a4dc) !important;
|
||||
color: #fff !important;
|
||||
opacity: 1;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.layout-tv .spotlight-section-title-container .show-all-button.show-focus:focus .material-icons,
|
||||
.layout-tv .spotlight-section-title-container .section-refresh-button.show-focus:focus .material-icons,
|
||||
.layout-tv .spotlight-section-title-container .section-configure-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitleContainer .show-all-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitleContainer .section-refresh-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitleContainer .section-configure-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitle-wrapper .show-all-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitle-wrapper .section-refresh-button.show-focus:focus .material-icons,
|
||||
.layout-tv .sectionTitle-wrapper .section-configure-button.show-focus:focus .material-icons {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.layout-tv .metadata-container.show-focus:focus,
|
||||
.layout-tv button.metadata-container.emby-button.show-focus:focus,
|
||||
.layout-tv button.metadata-container.emby-button.show-focus:focus:not(.skip),
|
||||
|
||||
+23
-4
@@ -2413,6 +2413,7 @@
|
||||
enhanceOnVisible = false,
|
||||
enhanceRootMargin = '30% 0px 30% 0px',
|
||||
showStaleDataBeforeRefresh = true,
|
||||
insertBefore = null,
|
||||
} = options;
|
||||
|
||||
const renderProgressiveSectionsStartTime = performance.now();
|
||||
@@ -2587,7 +2588,11 @@
|
||||
const batch = sectionNodes.slice(i, i + BATCH_SIZE);
|
||||
const batchFragment = document.createDocumentFragment();
|
||||
batch.forEach((node) => batchFragment.appendChild(node));
|
||||
container.appendChild(batchFragment);
|
||||
if (insertBefore && insertBefore.parentNode === container) {
|
||||
container.insertBefore(batchFragment, insertBefore);
|
||||
} else {
|
||||
container.appendChild(batchFragment);
|
||||
}
|
||||
batch.forEach((sectionEl) => {
|
||||
if (sectionEl?.nodeType === 1) registerLazyImagesInSection(sectionEl);
|
||||
});
|
||||
@@ -6435,8 +6440,12 @@
|
||||
|
||||
function setShowAllIcon(button, layout) {
|
||||
if (!button) return;
|
||||
const iconName = ITEMS_LAYOUT_ICONS[layout] || 'view_array';
|
||||
const icon = button.querySelector('.material-icons') || button;
|
||||
icon.classList.remove('view_array', 'grid_view', 'grid_on');
|
||||
icon.classList.add(iconName);
|
||||
// Keep glyph classes off the paper-icon button itself
|
||||
button.classList.remove('view_array', 'grid_view', 'grid_on');
|
||||
button.classList.add(ITEMS_LAYOUT_ICONS[layout] || 'view_array');
|
||||
}
|
||||
|
||||
function isButtonCardFormat(cardFormat) {
|
||||
@@ -6791,6 +6800,13 @@
|
||||
return ITEMS_LAYOUT_NEXT_TITLE[resolved] || ITEMS_LAYOUT_NEXT_TITLE.row;
|
||||
}
|
||||
|
||||
function getShowAllControlIcon(sectionElement) {
|
||||
const itemsContainer = sectionElement?.querySelector('.itemsContainer');
|
||||
const currentLayout = itemsContainer?.getAttribute('data-layout') || 'row';
|
||||
const resolved = currentLayout === 'grid' ? currentLayout : 'row';
|
||||
return ITEMS_LAYOUT_ICONS[nextItemsLayout(resolved)] || 'grid_view';
|
||||
}
|
||||
|
||||
function handleShowAllLayoutToggle(sectionElement) {
|
||||
const verticalSection = sectionElement.closest('.emby-scroller-container') || sectionElement;
|
||||
const itemsContainer = verticalSection.querySelector('.itemsContainer');
|
||||
@@ -6841,6 +6857,7 @@
|
||||
definitions.push({
|
||||
id: 'showAll',
|
||||
icon: 'grid_view',
|
||||
getIcon: () => getShowAllControlIcon(sectionElement),
|
||||
getLabel: () => getShowAllControlLabel(sectionElement),
|
||||
label: 'Grid layout',
|
||||
className: 'show-all-button',
|
||||
@@ -6874,9 +6891,10 @@
|
||||
|
||||
visibleDefinitions.forEach((def) => {
|
||||
const label = typeof def.getLabel === 'function' ? def.getLabel() : def.label;
|
||||
const icon = typeof def.getIcon === 'function' ? def.getIcon() : def.icon;
|
||||
const button = createPaperIconButton({
|
||||
className: def.className || '',
|
||||
icon: def.icon || '',
|
||||
icon: icon || '',
|
||||
title: label,
|
||||
showFocus: isTvLayout(),
|
||||
});
|
||||
@@ -6922,13 +6940,14 @@
|
||||
: 'kefinTweaks-popover section-controls-more-popover';
|
||||
visibleDefinitions.forEach((def) => {
|
||||
const label = typeof def.getLabel === 'function' ? def.getLabel() : def.label;
|
||||
const icon = typeof def.getIcon === 'function' ? def.getIcon() : def.icon;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'kefinTweaks-popover-item detailsGroupItem section-controls-more-item';
|
||||
item.setAttribute('role', 'button');
|
||||
item.setAttribute('tabindex', '0');
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.className = `material-icons section-controls-more-item-icon ${def.icon}`;
|
||||
iconSpan.className = `material-icons section-controls-more-item-icon ${icon || ''}`.trim();
|
||||
iconSpan.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const labelSpan = document.createElement('span');
|
||||
|
||||
+50
-31
@@ -27,36 +27,36 @@
|
||||
opacity: 1;
|
||||
} */
|
||||
|
||||
#indexPage[data-discovery-ready] div#discovery-loading-indicator:not(.show)::before {
|
||||
#discovery-loading-indicator {
|
||||
order: 999999999 !important;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#indexPage[data-discovery-ready] #discovery-loading-indicator:not(.show)::before {
|
||||
content: "Discover More";
|
||||
}
|
||||
|
||||
#indexPage[data-discovery-exhausted] div#discovery-loading-indicator:not(.show)::before {
|
||||
#indexPage[data-discovery-exhausted] #discovery-loading-indicator:not(.show)::before {
|
||||
content: "No More Content to Discover";
|
||||
}
|
||||
|
||||
div#discovery-loading-indicator:not(.show)::after {
|
||||
#discovery-loading-indicator:not(.show)::after {
|
||||
content: "\E313";
|
||||
font-family: "Material Icons";
|
||||
position: absolute;
|
||||
top: calc(50% + 0.4em);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
font-size: 1.5em;
|
||||
color: #ffffff;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#indexPage[data-discovery-exhausted] div#discovery-loading-indicator:not(.show)::after {
|
||||
#indexPage[data-discovery-exhausted] #discovery-loading-indicator:not(.show)::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
[data-loading-discovery="true"] div#discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] ~ #discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] + #discovery-loading-indicator,
|
||||
.sections[data-loading-discovery="true"] ~ #discovery-loading-indicator,
|
||||
.sections[data-loading-discovery="true"] + #discovery-loading-indicator {
|
||||
[data-loading-discovery="true"] #discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] #discovery-loading-indicator,
|
||||
.sections[data-loading-discovery="true"] #discovery-loading-indicator {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
display: flex !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Hide Discover More / loading affordance outside Home + Discover categories */
|
||||
@@ -73,11 +74,6 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
.libraryPage
|
||||
.homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"])
|
||||
#discovery-loading-indicator,
|
||||
#indexPage .homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"]) ~ #discovery-loading-indicator,
|
||||
.homePage .homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"]) ~ #discovery-loading-indicator,
|
||||
.libraryPage
|
||||
.homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"])
|
||||
~ #discovery-loading-indicator,
|
||||
#indexPage .homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"]) .discovery-loading-indicator,
|
||||
.homePage .homeSectionsContainer[data-category]:not([data-category="none"]):not([data-category="discovery"]) .discovery-loading-indicator,
|
||||
.libraryPage
|
||||
@@ -96,13 +92,14 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
#indexPage[data-discovery-ready] #discovery-loading-indicator:not(.show) .spinner {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* #indexPage[data-discovery-ready]:not([data-infinite-scroll='true']) div#discovery-loading-indicator {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
#indexPage:not([data-discovery-ready]) div#discovery-loading-indicator:not(.show)::after {
|
||||
#indexPage:not([data-discovery-ready]) #discovery-loading-indicator:not(.show)::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
@@ -131,13 +128,19 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.discovery-loading-indicator,
|
||||
.load-more-discovery-btn {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.discovery-loading-indicator {
|
||||
display: none;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font: inherit;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
@@ -156,14 +159,26 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* TV: real button + emby-button for spatial nav; keep Discover More chrome unstyled */
|
||||
.layout-tv button#discovery-loading-indicator.emby-button,
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable {
|
||||
outline: none;
|
||||
min-height: 3em;
|
||||
padding: 0.5em 1em;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable.show-focus:focus {
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable.show-focus:focus,
|
||||
.layout-tv button#discovery-loading-indicator.emby-button.show-focus:focus {
|
||||
background-color: var(--theme-primary-color, #00a4dc) !important;
|
||||
background: var(--theme-primary-color, #00a4dc) !important;
|
||||
color: #fff !important;
|
||||
@@ -173,7 +188,9 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
}
|
||||
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable.show-focus:focus::before,
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable.show-focus:focus::after {
|
||||
.layout-tv #discovery-loading-indicator.discovery-load-more-focusable.show-focus:focus::after,
|
||||
.layout-tv button#discovery-loading-indicator.emby-button.show-focus:focus::before,
|
||||
.layout-tv button#discovery-loading-indicator.emby-button.show-focus:focus::after {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
@@ -210,10 +227,8 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
}
|
||||
|
||||
.sections[data-loading-discovery="true"] .discovery-loading-indicator,
|
||||
.sections[data-loading-discovery="true"] ~ #discovery-loading-indicator,
|
||||
.sections[data-loading-discovery="true"] + #discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] ~ #discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] + #discovery-loading-indicator {
|
||||
.sections[data-loading-discovery="true"] #discovery-loading-indicator,
|
||||
.homeSectionsContainer[data-loading-discovery="true"] #discovery-loading-indicator {
|
||||
display: flex !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
@@ -297,7 +312,7 @@ div#discovery-loading-indicator:not(.show)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.homePage[data-discovery-ready] div#discovery-loading-indicator:not(.show) {
|
||||
.homePage[data-discovery-ready] #discovery-loading-indicator:not(.show) {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
@@ -674,6 +689,10 @@ html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-se
|
||||
background-position: bottom center;
|
||||
}
|
||||
|
||||
html[data-active-page="home"] body[data-seasonal-background="true"] .backdropContainer .backdropImage {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) and (min-width: 900px) {
|
||||
#kefin-home-category-rail .kefin-home-category-rail.kefin-home-chrome {
|
||||
pointer-events: auto;
|
||||
|
||||
+98
-34
@@ -134,7 +134,7 @@
|
||||
pendingJellyfinSectionObservers: []
|
||||
};
|
||||
|
||||
// Create loading indicator as next sibling of the sections container (outside flex column)
|
||||
// Create loading indicator as last child of the sections container (keeps height during load; TV Down focus)
|
||||
function createDiscoveryLoadingIndicator(container) {
|
||||
// Prefer the provided container, but fall back to the standard sections container
|
||||
if (!container) {
|
||||
@@ -142,44 +142,47 @@
|
||||
|| document.querySelector('.libraryPage:not(.hide) .homeSectionsContainer');
|
||||
}
|
||||
|
||||
let loadingDiv = document.querySelector('.homePage:not(.hide) #discovery-loading-indicator')
|
||||
let loadingEl = document.querySelector('.homePage:not(.hide) #discovery-loading-indicator')
|
||||
|| document.querySelector('.libraryPage:not(.hide) #discovery-loading-indicator');
|
||||
|
||||
if (loadingDiv) {
|
||||
if (container && loadingDiv.previousElementSibling !== container) {
|
||||
// Place as next sibling of the sections container
|
||||
if (container.nextSibling) {
|
||||
container.parentNode.insertBefore(loadingDiv, container.nextSibling);
|
||||
} else if (container.parentNode) {
|
||||
container.parentNode.appendChild(loadingDiv);
|
||||
}
|
||||
}
|
||||
bindDiscoveryLoadMoreControl(loadingDiv);
|
||||
syncDiscoveryLoadMoreFocus(loadingDiv);
|
||||
return loadingDiv;
|
||||
// Migrate legacy div indicator to a real button (Jellyfin TV spatial nav targets buttons)
|
||||
if (loadingEl && loadingEl.tagName !== 'BUTTON') {
|
||||
const replacement = document.createElement('button');
|
||||
replacement.type = 'button';
|
||||
replacement.id = loadingEl.id;
|
||||
replacement.className = loadingEl.className;
|
||||
replacement.innerHTML = loadingEl.innerHTML || '<div class="spinner"></div>';
|
||||
loadingEl.replaceWith(replacement);
|
||||
loadingEl = replacement;
|
||||
}
|
||||
|
||||
if (!container || !container.parentNode) {
|
||||
if (loadingEl) {
|
||||
if (container) {
|
||||
container.appendChild(loadingEl);
|
||||
}
|
||||
bindDiscoveryLoadMoreControl(loadingEl);
|
||||
syncDiscoveryLoadMoreFocus(loadingEl);
|
||||
return loadingEl;
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
WARN('Discovery loading indicator: sections container not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
loadingDiv = document.createElement('div');
|
||||
loadingDiv.className = 'discovery-loading-indicator';
|
||||
loadingDiv.id = 'discovery-loading-indicator';
|
||||
loadingDiv.innerHTML = `
|
||||
loadingEl = document.createElement('button');
|
||||
loadingEl.type = 'button';
|
||||
loadingEl.className = 'discovery-loading-indicator';
|
||||
loadingEl.id = 'discovery-loading-indicator';
|
||||
loadingEl.innerHTML = `
|
||||
<div class="spinner"></div>
|
||||
`;
|
||||
|
||||
if (container.nextSibling) {
|
||||
container.parentNode.insertBefore(loadingDiv, container.nextSibling);
|
||||
} else {
|
||||
container.parentNode.appendChild(loadingDiv);
|
||||
}
|
||||
LOG('Created discovery loading indicator as sibling of sections container');
|
||||
bindDiscoveryLoadMoreControl(loadingDiv);
|
||||
syncDiscoveryLoadMoreFocus(loadingDiv);
|
||||
return loadingDiv;
|
||||
container.appendChild(loadingEl);
|
||||
LOG('Created discovery loading indicator as last child of sections container');
|
||||
bindDiscoveryLoadMoreControl(loadingEl);
|
||||
syncDiscoveryLoadMoreFocus(loadingEl);
|
||||
return loadingEl;
|
||||
}
|
||||
|
||||
function removeDiscoveryLoadingIndicator() {
|
||||
@@ -208,14 +211,17 @@
|
||||
const interactive = isDiscoveryLoadMoreInteractive();
|
||||
|
||||
if (tv && interactive) {
|
||||
el.setAttribute('role', 'button');
|
||||
el.disabled = false;
|
||||
el.removeAttribute('disabled');
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.setAttribute('aria-label', 'Discover More');
|
||||
// emby-button: Jellyfin TV spatial nav treats plain controls as Tab-only otherwise
|
||||
el.classList.add('emby-button');
|
||||
el.classList.add('show-focus');
|
||||
el.classList.add('discovery-load-more-focusable');
|
||||
} else {
|
||||
el.setAttribute('tabindex', '-1');
|
||||
el.removeAttribute('role');
|
||||
el.classList.remove('emby-button');
|
||||
el.classList.remove('show-focus');
|
||||
el.classList.remove('discovery-load-more-focusable');
|
||||
if (state.discoverySectionsRemain === false || getDiscoveryHomePageEl()?.dataset?.discoveryExhausted === 'true') {
|
||||
@@ -1570,7 +1576,7 @@
|
||||
|
||||
// Chevron mode: nudge scroll so newly appended sections start entering the viewport
|
||||
if (state.discoveryMode === 'chevron') {
|
||||
window.scrollBy({ top: 100, behavior: 'smooth' });
|
||||
window.scrollBy({ top: 200, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1599,14 +1605,15 @@
|
||||
const config = await window.KefinHomeScreen.getConfig();
|
||||
const revealSectionsSequentially = config.DISCOVERY_SETTINGS?.fadeInSections === true;
|
||||
|
||||
// Sibling spinner stays outside the flex column — no insertBefore parking
|
||||
createDiscoveryLoadingIndicator(container);
|
||||
// Spinner stays mounted as last child; sections insert before it (cardBuilder)
|
||||
const indicator = createDiscoveryLoadingIndicator(container);
|
||||
|
||||
await window.cardBuilder.renderProgressiveSections(container, bufferedSections, {
|
||||
revealSectionsSequentially,
|
||||
enhanceOnVisible: true,
|
||||
enhanceRootMargin: DISCOVERY_ENHANCE_ROOT_MARGIN,
|
||||
showStaleDataBeforeRefresh: config.HOME_SETTINGS?.showStaleDataBeforeRefresh === true
|
||||
showStaleDataBeforeRefresh: config.HOME_SETTINGS?.showStaleDataBeforeRefresh === true,
|
||||
insertBefore: indicator || undefined,
|
||||
});
|
||||
|
||||
createDiscoveryLoadingIndicator(container);
|
||||
@@ -1624,6 +1631,22 @@
|
||||
ensureDiscoveryBuffer();
|
||||
}
|
||||
|
||||
// TV chevron: hand focus to first card of the first newly added section
|
||||
if (
|
||||
document.documentElement.classList.contains('layout-tv')
|
||||
&& state.discoveryMode === 'chevron'
|
||||
) {
|
||||
const firstId = bufferedSections[0]?.config?.id;
|
||||
if (firstId != null) {
|
||||
const sectionIdAttr =
|
||||
typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
|
||||
? CSS.escape(String(firstId))
|
||||
: String(firstId).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
const sectionEl = container.querySelector(`[data-section-id="${sectionIdAttr}"]`);
|
||||
focusFirstCardInDiscoverySection(sectionEl);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
ERR('Error rendering discovery group:', e);
|
||||
state.discoveryBuffer = null;
|
||||
@@ -2540,6 +2563,47 @@
|
||||
return rect.width > 0 && rect.height > 0;
|
||||
}
|
||||
|
||||
function getFirstFocusableInDiscoverySection(sectionEl) {
|
||||
if (!sectionEl) return null;
|
||||
const card = Array.from(
|
||||
sectionEl.querySelectorAll(
|
||||
'.card.itemAction:not([tabindex="-1"]), button.card:not([tabindex="-1"]), a.card:not([tabindex="-1"])',
|
||||
),
|
||||
).find((node) => isVisibleFocusTarget(node));
|
||||
if (card) return card;
|
||||
|
||||
// Spotlight / non-card sections: first visible focusable
|
||||
return Array.from(
|
||||
sectionEl.querySelectorAll(
|
||||
'button:not([disabled]):not([tabindex="-1"]), a[href]:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])',
|
||||
),
|
||||
).find((node) => isVisibleFocusTarget(node)) || null;
|
||||
}
|
||||
|
||||
function focusFirstCardInDiscoverySection(sectionEl) {
|
||||
const tryFocus = () => {
|
||||
const target = getFirstFocusableInDiscoverySection(sectionEl);
|
||||
if (!target || typeof target.focus !== 'function') return false;
|
||||
try {
|
||||
target.focus({ preventScroll: true });
|
||||
} catch (_) {
|
||||
target.focus();
|
||||
}
|
||||
try {
|
||||
target.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (tryFocus()) return;
|
||||
// Skeleton may not have cards yet — one frame retry
|
||||
requestAnimationFrame(() => {
|
||||
tryFocus();
|
||||
});
|
||||
}
|
||||
|
||||
/** Live spotlight target after slide change (inactive/drilled nodes go stale). */
|
||||
function getActiveSpotlightReturnFocus() {
|
||||
const activeSlide = document.querySelector(
|
||||
|
||||
Reference in New Issue
Block a user