Switch grid layout to use a proper grid instead of a flex box with dummy cards

This commit is contained in:
HighImKevin committed 2026-10-10 10:15:31 -04:00
1 parent d1161d0280
commit f7b756aad0
5 files changed
+209 -325

No files matched your search

+161 -36
View File
@@ -1358,50 +1358,175 @@
max-width: unset !important;
}
/* Grid (non-scrolling) item layouts */
/* Grid (non-scrolling) item layouts — CSS Grid with JF card.scss column breakpoints.
Shape inferred from data-card-format (see resolveGridShape); default = portrait.
Columns use @container against the scroller width (not viewport), so inset /
max-width sections (e.g. 67vw series episodes) get fewer, correctly sized cards. */
.emby-scroller-container > .emby-scroller {
container-type: inline-size;
container-name: kefin-items;
}
.itemsContainer[data-layout="grid"] {
display: flex !important;
flex-wrap: wrap !important;
display: grid !important;
flex-wrap: unset;
white-space: normal !important;
transform: none !important;
transition: none !important;
grid-template-columns: repeat(3, 1fr);
}
.itemsContainer[data-layout="grid"] > .card {
flex: auto;
box-sizing: border-box;
/* Cards fill tracks; override JF portraitCard/backdropCard % widths and Banner 33vw */
.itemsContainer[data-layout="grid"] > .card,
.itemsContainer[data-layout="grid"] > .homeLibraryButton,
.emby-scroller-container[data-card-format="Banner"] .itemsContainer[data-layout="grid"] > .card {
width: unset !important;
max-width: 100% !important;
min-width: 0;
}
/* Natural widths while counting items-per-row for last-row dummy padding */
.itemsContainer[data-measuring="true"] > .card {
flex: 0 0 auto !important;
}
/* Grid dummies keep aspect via padder but allow .cardBorder to paint */
.itemsContainer[data-layout="grid"] > .card.card-layout-dummy {
visibility: hidden;
position: relative;
pointer-events: none;
}
[data-border-style="wood"] .itemsContainer[data-layout="grid"] > .card.card-layout-dummy,
[data-border="wood"] .itemsContainer[data-layout="grid"] > .card.card-layout-dummy {
visibility: visible;
}
.itemsContainer[data-layout="grid"] > .card.card-layout-dummy .cardPadder {
visibility: hidden;
}
.itemsContainer[data-layout="grid"] > .card.card-layout-dummy .cardBorder {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
box-sizing: border-box;
--card-border-cream: var(--kefin-card-border-color, #e8dab2);
--card-border-fancy: var(--kefin-card-border-color, rgb(231 255 123 / 35%));
align-self: start;
}
.itemsContainer[data-layout="grid"] > .card > .cardBox,
.itemsContainer[data-layout="grid"] > .card .cardScalable {
width: 100% !important;
max-width: none !important;
height: auto;
}
/* Portrait / Square column table (JF .portraitCard / .squareCard) — default on grid */
@container kefin-items (min-width: 31.25em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(3, 1fr);
}
}
@container kefin-items (min-width: 43.75em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(4, 1fr);
}
}
@container kefin-items (min-width: 50em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(5, 1fr);
}
}
@container kefin-items (min-width: 75em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(6, 1fr);
}
}
@container kefin-items (min-width: 87.5em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(7, 1fr);
}
}
@container kefin-items (min-width: 100em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(8, 1fr);
}
}
@container kefin-items (min-width: 120em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(9, 1fr);
}
}
@container kefin-items (min-width: 131.25em) {
.itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(10, 1fr);
}
}
/* Backdrop shape: Backdrop, Thumb, Series Thumb, Logo, Clear Art, Button */
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(1, 1fr);
}
@container kefin-items (min-width: 25em) {
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(2, 1fr);
}
}
@container kefin-items (min-width: 48.125em) {
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(3, 1fr);
}
}
@container kefin-items (min-width: 75em) {
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(4, 1fr);
}
}
@container kefin-items (min-width: 100em) {
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(5, 1fr);
}
}
@container kefin-items (min-width: 156.25em) {
.emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
.emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(6, 1fr);
}
}
/* Banner shape */
.emby-scroller-container[data-card-format="Banner"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(1, 1fr);
}
@container kefin-items (min-width: 50em) {
.emby-scroller-container[data-card-format="Banner"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(2, 1fr);
}
}
@container kefin-items (min-width: 75em) {
.emby-scroller-container[data-card-format="Banner"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(3, 1fr);
}
}
@container kefin-items (min-width: 131.25em) {
.emby-scroller-container[data-card-format="Banner"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(4, 1fr);
}
}
/* TV: portrait/square → 6, backdrop → 4 (fixed, not container-width) */
html.layout-tv .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(6, 1fr);
}
html.layout-tv .emby-scroller-container[data-card-format="Backdrop"] .itemsContainer[data-layout="grid"],
html.layout-tv .emby-scroller-container[data-card-format="Thumb"] .itemsContainer[data-layout="grid"],
html.layout-tv .emby-scroller-container[data-card-format="Series Thumb"] .itemsContainer[data-layout="grid"],
html.layout-tv .emby-scroller-container[data-card-format="Logo"] .itemsContainer[data-layout="grid"],
html.layout-tv .emby-scroller-container[data-card-format="Clear Art"] .itemsContainer[data-layout="grid"],
html.layout-tv .emby-scroller-container[data-card-format="Button"] .itemsContainer[data-layout="grid"] {
grid-template-columns: repeat(4, 1fr);
}
.itemsContainer[data-gapless="true"],
+39 -278
View File
@@ -435,7 +435,7 @@
} else {
sectionEl.style.removeProperty('--wood-vert-img');
}
const cards = sectionEl.querySelectorAll('.card:not(.card-layout-dummy) .cardScalable');
const cards = sectionEl.querySelectorAll('.card .cardScalable');
cards.forEach((scalable) => {
let host = scalable.querySelector(':scope > .cardBorder');
if (!style) {
@@ -479,9 +479,7 @@
function getCardRadius() {
if (cachedCardRadius != null) return cachedCardRadius;
const candidates = document.querySelectorAll(
'.card:not(.card-layout-dummy) .cardScalable, .card:not(.card-layout-dummy) .cardContent, .card:not(.card-layout-dummy)',
);
const candidates = document.querySelectorAll('.card .cardScalable, .card .cardContent, .card');
let sample = null;
for (const el of candidates) {
if (!sectionHasSquareCardBorder(el)) {
@@ -784,6 +782,32 @@
return formats[Math.floor(Math.random() * formats.length)];
}
/**
* Map section cardFormat / data-card-format to JF grid column shape.
* Used for CSS grid column tables (no data-grid-shape attribute).
* @param {string|null|undefined} cardFormat
* @returns {'portrait'|'square'|'backdrop'|'banner'}
*/
function resolveGridShape(cardFormat) {
const f = String(cardFormat || '')
.trim()
.toLowerCase();
if (f === 'banner') return 'banner';
if (f === 'square' || f === 'sqaure' || f === 'disc') return 'square';
if (
f === 'backdrop' ||
f === 'thumb' ||
f === 'series thumb' ||
f === 'logo' ||
f === 'clear art' ||
f === 'button'
) {
return 'backdrop';
}
// Poster, Series Poster, portrait, Random-resolved concrete formats, default
return 'portrait';
}
function sectionHasRandomQuerySort(sectionConfig) {
// Treat as random if any query has an undefined/null/non-existent SortBy without a path
if (String(sectionConfig?.sortBy || '').toLowerCase() === 'random') {
@@ -2022,7 +2046,6 @@
content.setAttribute(attr.name, attr.value);
});
invalidateLastRowPadding(content.querySelector('.itemsContainer'));
invalidateScrollerMetrics(sectionElement.querySelector('.emby-scroller'));
stripCopiedSkeletonSectionClasses(content);
unobserveSkeletonShimmer(sectionElement);
@@ -2202,9 +2225,9 @@
ensureCardBorders: ensureCardBorders,
applyItemsLayoutState: applyItemsLayoutState,
resolveItemsLayout: resolveItemsLayout,
resolveGridShape: resolveGridShape,
resolveSpotlightPresentation: resolveSpotlightPresentation,
resolveUseGaplessCards: resolveUseGaplessCards,
invalidateLastRowPadding: invalidateLastRowPadding,
invalidateCardRadiusCache: invalidateCardRadiusCache,
ensureCardRadiusCssVar: ensureCardRadiusCssVar,
getScrollerPosition: getScrollerPosition,
@@ -6457,241 +6480,12 @@
if (isButtonCardFormat(cardFormat)) {
return Array.from(itemsContainer.querySelectorAll(':scope > .homeLibraryButton'));
}
return Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.card-layout-dummy)'));
return Array.from(itemsContainer.querySelectorAll(':scope > .card'));
}
function getRealLayoutCards(itemsContainer) {
if (!itemsContainer) return [];
return Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.card-layout-dummy)'));
}
function removeLayoutDummies(itemsContainer) {
if (!itemsContainer) return;
itemsContainer.querySelectorAll(':scope > .card-layout-dummy').forEach((el) => el.remove());
}
function invalidateLastRowPadding(itemsContainer) {
if (!itemsContainer) return;
removeLayoutDummies(itemsContainer);
itemsContainer.removeAttribute('data-last-row-padding');
itemsContainer.removeAttribute('data-last-row-padding-layout');
itemsContainer.removeAttribute('data-last-row-padding-width');
itemsContainer.removeAttribute('data-measuring');
}
function canMeasureLastRowPadding(itemsContainer) {
return !!(itemsContainer?.isConnected && itemsContainer.clientWidth >= 1);
}
function getLastRowPaddingCache(itemsContainer, layout) {
if (!itemsContainer || layout !== 'grid') return null;
const cachedPad = itemsContainer.getAttribute('data-last-row-padding');
const cachedLayout = itemsContainer.getAttribute('data-last-row-padding-layout');
const cachedWidth = itemsContainer.getAttribute('data-last-row-padding-width');
if (cachedPad == null || cachedPad === '' || cachedLayout !== layout || !/^\d+$/.test(cachedPad)) {
return null;
}
if (cachedWidth == null || !/^\d+$/.test(cachedWidth)) return null;
return {
padding: parseInt(cachedPad, 10),
width: parseInt(cachedWidth, 10),
};
}
function setLastRowPaddingCache(itemsContainer, layout, padding, width) {
itemsContainer.setAttribute('data-last-row-padding', String(padding));
itemsContainer.setAttribute('data-last-row-padding-layout', layout);
itemsContainer.setAttribute('data-last-row-padding-width', String(width));
}
function countLayoutDummies(itemsContainer) {
if (!itemsContainer) return 0;
return itemsContainer.querySelectorAll(':scope > .card-layout-dummy').length;
}
/** True when cache matches current container width and dummy count already equals padding. */
function lastRowPaddingIsCurrent(itemsContainer, layout) {
const cache = getLastRowPaddingCache(itemsContainer, layout);
if (!cache || !canMeasureLastRowPadding(itemsContainer)) return false;
if (cache.width !== itemsContainer.clientWidth) return false;
return countLayoutDummies(itemsContainer) === cache.padding;
}
function createLayoutDummyCard(templateCard, layout) {
const dummy = document.createElement('div');
dummy.className = templateCard?.className || 'card';
dummy.classList.add('card', 'card-layout-dummy');
dummy.classList.remove('card-hoverable');
dummy.setAttribute('aria-hidden', 'true');
dummy.setAttribute('tabindex', '-1');
// cardBox
const cardBox = document.createElement('div');
cardBox.className = 'cardBox';
dummy.appendChild(cardBox);
// cardScalable
const cardScalable = document.createElement('div');
cardScalable.className = 'cardScalable';
cardBox.appendChild(cardScalable);
if (layout === 'grid') {
const templatePadder = templateCard?.querySelector('.cardPadder');
const padder = document.createElement('div');
padder.className = templatePadder?.className || 'cardPadder cardPadder-portrait lazy-hidden-children';
const cardIcon = document.createElement('span');
cardIcon.className = 'cardImageIcon material-icons';
cardIcon.setAttribute('aria-hidden', 'true');
cardIcon.textContent = 'folder';
padder.appendChild(cardIcon);
cardScalable.appendChild(padder);
const border = createCardBorderHost();
const section = templateCard?.closest('[data-border],[data-border-style]');
const raw =
section?.dataset?.border ||
section?.dataset?.borderStyle ||
templateCard?.querySelector('.cardBorder')?.dataset?.style ||
'';
const style = normalizeCardBorderStyle(raw);
if (style) fillCardBorder(border, style);
cardScalable.appendChild(border);
}
return dummy;
}
/**
* Count how many real cards fit on the first row at natural (non-growing) width.
* @param {HTMLElement} itemsContainer
* @returns {number}
*/
function countItemsPerRow(itemsContainer) {
if (!canMeasureLastRowPadding(itemsContainer)) return 0;
const cards = getRealLayoutCards(itemsContainer);
if (!cards.length) return 0;
itemsContainer.setAttribute('data-measuring', 'true');
void itemsContainer.offsetWidth;
const firstTop = cards[0].offsetTop;
let count = 0;
for (const card of cards) {
if (card.offsetTop !== firstTop) break;
count++;
}
itemsContainer.removeAttribute('data-measuring');
return count;
}
/**
* Pad the last flex row with invisible dummy cards so grow distribution matches full rows.
* Reuses data-last-row-padding when cached for the same layout.
* @param {HTMLElement} itemsContainer
* @param {'grid'} layout
*/
function syncLastRowPadding(itemsContainer, layout) {
if (!itemsContainer || layout !== 'grid') return;
// Already correct for this width — skip remove/recreate.
if (lastRowPaddingIsCurrent(itemsContainer, layout)) return;
const cache = getLastRowPaddingCache(itemsContainer, layout);
const width = canMeasureLastRowPadding(itemsContainer) ? itemsContainer.clientWidth : 0;
const widthMatches = !!(cache && width >= 1 && cache.width === width);
// Disconnected / zero-width: do not measure or cache (retry when laid out).
if (!widthMatches && !canMeasureLastRowPadding(itemsContainer)) return;
let padding;
if (widthMatches) {
padding = cache.padding;
if (countLayoutDummies(itemsContainer) === padding) return;
removeLayoutDummies(itemsContainer);
} else {
// Dummies must be gone before measuring natural items-per-row.
removeLayoutDummies(itemsContainer);
const cards = getRealLayoutCards(itemsContainer);
if (!cards.length) {
invalidateLastRowPadding(itemsContainer);
return;
}
const perRow = countItemsPerRow(itemsContainer);
if (perRow <= 0) return;
const rem = cards.length % perRow;
padding = rem === 0 ? 0 : perRow - rem;
setLastRowPaddingCache(itemsContainer, layout, padding, itemsContainer.clientWidth);
}
if (padding <= 0) return;
if (!itemsContainer.isConnected) return;
const template = getRealLayoutCards(itemsContainer)[0];
if (!template) return;
const frag = document.createDocumentFragment();
for (let i = 0; i < padding; i++) {
frag.appendChild(createLayoutDummyCard(template, layout));
}
itemsContainer.appendChild(frag);
}
let lastRowPaddingResizeTimer = null;
function handleLastRowPaddingWindowResize() {
clearTimeout(lastRowPaddingResizeTimer);
lastRowPaddingResizeTimer = setTimeout(() => {
document.querySelectorAll('.itemsContainer[data-layout="grid"]').forEach((container) => {
const layout = container.getAttribute('data-layout');
if (lastRowPaddingIsCurrent(container, layout)) return;
invalidateLastRowPadding(container);
syncLastRowPadding(container, layout);
});
}, 150);
}
if (!window.__kefinLastRowPaddingResizeBound) {
window.__kefinLastRowPaddingResizeBound = true;
window.addEventListener('resize', handleLastRowPaddingWindowResize);
}
let lastRowPaddingRo = null;
let lastRowPaddingRoTimer = null;
const lastRowPaddingRoPending = new Set();
function ensureLastRowPaddingResizeObserver() {
if (lastRowPaddingRo || typeof ResizeObserver === 'undefined') return lastRowPaddingRo;
lastRowPaddingRo = new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry?.target) lastRowPaddingRoPending.add(entry.target);
});
clearTimeout(lastRowPaddingRoTimer);
lastRowPaddingRoTimer = setTimeout(() => {
const targets = [...lastRowPaddingRoPending];
lastRowPaddingRoPending.clear();
targets.forEach((el) => {
const layout = el.getAttribute?.('data-layout');
if (layout !== 'grid') return;
if (lastRowPaddingIsCurrent(el, layout)) return;
invalidateLastRowPadding(el);
syncLastRowPadding(el, layout);
});
}, 80);
});
return lastRowPaddingRo;
}
function observeLastRowPadding(itemsContainer) {
const ro = ensureLastRowPaddingResizeObserver();
if (!ro || !itemsContainer) return;
try {
ro.observe(itemsContainer);
} catch (_) {
/* ignore */
}
}
function unobserveLastRowPadding(itemsContainer) {
if (!lastRowPaddingRo || !itemsContainer) return;
try {
lastRowPaddingRo.unobserve(itemsContainer);
} catch (_) {
/* ignore */
}
return Array.from(itemsContainer.querySelectorAll(':scope > .card'));
}
/**
@@ -6716,8 +6510,6 @@
itemsContainer.removeAttribute('data-gapless');
}
if (resolved === 'row') {
unobserveLastRowPadding(itemsContainer);
removeLayoutDummies(itemsContainer);
if (scrollButtons) scrollButtons.style.display = '';
} else if (scrollButtons) {
scrollButtons.style.display = 'none';
@@ -6728,33 +6520,6 @@
showAllButton.setAttribute('aria-label', showAllButton.title);
setShowAllIcon(showAllButton, nextLayout);
}
if (resolved === 'grid') {
observeLastRowPadding(itemsContainer);
// Two frames so wrap layout is settled before measuring / padding
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (itemsContainer.getAttribute('data-layout') !== resolved) return;
syncLastRowPadding(itemsContainer, resolved);
});
});
}
}
function syncAttachedGridTilesLayouts(root) {
if (!root?.querySelectorAll) return;
root.querySelectorAll('.itemsContainer[data-layout="grid"]').forEach((itemsContainer) => {
const layout = itemsContainer.getAttribute('data-layout');
const gapless = itemsContainer.getAttribute('data-gapless') === 'true';
const section = itemsContainer.closest('.verticalSection, .emby-scroller-container') || itemsContainer.parentElement;
if (section) applyItemsLayoutState(section, layout, gapless);
else {
observeLastRowPadding(itemsContainer);
if (!lastRowPaddingIsCurrent(itemsContainer, layout)) {
invalidateLastRowPadding(itemsContainer);
syncLastRowPadding(itemsContainer, layout);
}
}
});
}
/** @deprecated Use applyItemsLayoutState */
@@ -7198,7 +6963,7 @@
const metrics = getScrollerMetrics(scroller);
if (metrics.cardPad) return metrics.cardPad;
const sampleCard = scroller.querySelector?.('.itemsContainer > .card:not(.card-layout-dummy):not(.skeleton-card)');
const sampleCard = scroller.querySelector?.('.itemsContainer > .card:not(.skeleton-card)');
const pad = readCardPadding(sampleCard);
metrics.cardPad = pad;
return pad;
@@ -7286,7 +7051,7 @@
*/
function toCenterCard(scroller, card, options = {}) {
if (!scroller || !card || !scroller.contains(card)) return;
if (card.classList.contains('skeleton-card') || card.classList.contains('card-layout-dummy')) return;
if (card.classList.contains('skeleton-card')) return;
const animate = options.animate !== false;
const metrics = getScrollerMetrics(scroller);
@@ -7316,7 +7081,7 @@
(e) => {
const card = e.target?.closest?.('.card');
if (!card || !scroller.contains(card)) return;
if (card.classList.contains('skeleton-card') || card.classList.contains('card-layout-dummy')) return;
if (card.classList.contains('skeleton-card')) return;
pendingCard = card;
if (rafId) return;
rafId = requestAnimationFrame(() => {
@@ -7369,7 +7134,7 @@
return metrics.maxScroll;
}
const cards = scroller.querySelectorAll('.itemsContainer > .card:not(.card-layout-dummy):not(.skeleton-card)');
const cards = scroller.querySelectorAll('.itemsContainer > .card:not(.skeleton-card)');
const fullyVisible = getFullyVisibleCardCount(scroller, cards);
if (fullyVisible === cards.length) {
setMaxScrollPosition(scroller, 0);
@@ -7503,7 +7268,7 @@
function getScrollCards() {
const itemsContainer = scroller.querySelector('.itemsContainer');
if (!itemsContainer) return [];
return Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.card-layout-dummy):not(.skeleton-card)'));
return Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.skeleton-card)'));
}
function getCardScrollLeft(card) {
@@ -9105,7 +8870,7 @@
}
function getPaintedCardIds(sectionElement) {
const cards = sectionElement.querySelectorAll('.itemsContainer > .card[data-id]:not(.card-layout-dummy):not(.skeleton-card)');
const cards = sectionElement.querySelectorAll('.itemsContainer > .card[data-id]:not(.skeleton-card)');
if (cards.length) {
return Array.from(cards)
.map((card) => card.getAttribute('data-id'))
@@ -9282,7 +9047,7 @@
const animate = options.animate === true;
const itemsContainer = scroller.querySelector('.itemsContainer');
if (!itemsContainer) return;
const cards = Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.card-layout-dummy):not(.skeleton-card)'));
const cards = Array.from(itemsContainer.querySelectorAll(':scope > .card:not(.skeleton-card)'));
if (!cards.length) return;
const clampedIndex = Math.max(0, Math.min(index, cards.length - 1));
@@ -9415,7 +9180,7 @@
if (existing) {
seenKept = true;
nextNode = existing.nextElementSibling;
while (nextNode && (nextNode.classList.contains('card-layout-dummy') || nextNode.classList.contains('skeleton-card'))) {
while (nextNode && nextNode.classList.contains('skeleton-card')) {
nextNode = nextNode.nextElementSibling;
}
return;
@@ -9503,7 +9268,6 @@
if (slidee) slidee.style.transition = '';
}
reindexSectionCards(itemsContainer);
invalidateLastRowPadding(itemsContainer);
patchCardsUserData(sectionElement, items);
ensureCardBorders(sectionElement);
if (typeof onComplete === 'function') {
@@ -9530,8 +9294,6 @@
: resolveItemsLayout(sectionConfig);
const gapless = itemsContainer.getAttribute('data-gapless') === 'true' || resolveUseGaplessCards(sectionConfig);
invalidateLastRowPadding(itemsContainer);
const fragment = document.createDocumentFragment();
if (normalizedFormat === 'button' || isButtonCardFormat(cardFormat)) {
items.forEach((item, index) => {
@@ -9645,7 +9407,6 @@
const gapless = oldItemsContainer
? oldItemsContainer.getAttribute('data-gapless') === 'true'
: resolveUseGaplessCards(sectionConfig);
invalidateLastRowPadding(content.querySelector('.itemsContainer'));
attachSectionControlButtons(sectionConfig, content, items);
invalidateScrollerMetrics(sectionElement.querySelector('.emby-scroller'));
stripCopiedSkeletonSectionClasses(content);
+8 -9
View File
@@ -496,7 +496,6 @@
const card = window.cardBuilder?.buildCard?.(item, true, cardFormat);
if (card) {
itemsContainer.appendChild(card);
window.cardBuilder?.invalidateLastRowPadding?.(itemsContainer);
addUnpinButton(card);
addReorderButtons(card);
updateAllReorderChevrons(itemsContainer);
@@ -830,7 +829,8 @@
const pinButton = document.createElement('button');
pinButton.setAttribute('is', 'emby-button');
pinButton.type = 'button';
pinButton.className = 'listItem listItem-button actionSheetMenuItem emby-button';
const isMobile = window.innerWidth < 900;
pinButton.className = `listItem listItem-button actionSheetMenuItem emby-button${isMobile ? ' actionsheet-xlargeFont' : ''}`;
pinButton.setAttribute('data-kefin-pin-action', 'true');
pinButton.innerHTML = `
<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons push_pin" aria-hidden="true"></span>
@@ -886,7 +886,7 @@
function getRealPinnedCards(itemsContainer) {
if (!itemsContainer) return [];
return [...itemsContainer.querySelectorAll(':scope > .card[data-id]:not(.card-layout-dummy)')];
return [...itemsContainer.querySelectorAll(':scope > .card[data-id]')];
}
function updateReorderChevronState(card) {
@@ -937,7 +937,6 @@
const ok = await saveHomeScreen(hs, { refreshHome: false });
if (ok) {
updateRuntimeAfterListPin(sectionId, null, orderedIds);
window.cardBuilder?.invalidateLastRowPadding?.(itemsContainer);
}
return ok;
}
@@ -973,7 +972,7 @@
const card = cardOrOverlay?.classList?.contains('card')
? cardOrOverlay
: cardOrOverlay?.closest?.('.card');
if (!card || card.classList.contains('card-layout-dummy')) return;
if (!card) return;
if (card.querySelector(':scope > .pin-reorder-left')) return;
const sectionEl = card.closest('[data-section-id]');
@@ -1016,7 +1015,7 @@
const card = cardOrOverlay?.classList?.contains('card')
? cardOrOverlay
: cardOrOverlay?.closest?.('.card');
if (!card || card.classList.contains('card-layout-dummy')) return;
if (!card) return;
if (card.querySelector(':scope > .unpin-item-button')) return;
const sectionEl = card.closest('[data-section-id]');
@@ -1049,7 +1048,7 @@
let hs = await loadHomeScreen();
const restoreHomeScreen = cloneHomeScreen(hs);
const realCards = sectionEl
? [...sectionEl.querySelectorAll('.itemsContainer > .card[data-id]:not(.card-layout-dummy)')]
? [...sectionEl.querySelectorAll('.itemsContainer > .card[data-id]')]
: [];
const isLastInSection = realCards.length <= 1;
let listName = null;
@@ -1151,7 +1150,7 @@
padding: 0 !important;
pointer-events: auto !important;
}
[data-section-id^="pinned-list-"] .card[data-id]:not(.card-layout-dummy) {
[data-section-id^="pinned-list-"] .card[data-id] {
position: relative !important;
}
.pin-reorder-left {
@@ -1244,7 +1243,7 @@
};
document.querySelectorAll('.cardOverlayContainer').forEach(processOverlay);
document.querySelectorAll('[data-section-id^="pinned-list-"] .card[data-id]:not(.card-layout-dummy)').forEach(processCard);
document.querySelectorAll('[data-section-id^="pinned-list-"] .card[data-id]').forEach(processCard);
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
-1
View File
@@ -996,7 +996,6 @@
}
if (!isSpotlight && window.cardBuilder?.applyItemsLayoutState) {
window.cardBuilder.invalidateLastRowPadding?.(newContent.querySelector('.itemsContainer'));
window.cardBuilder.applyItemsLayoutState(newContent, preservedLayout || 'row', !!preservedGapless);
}
+1 -1
View File
@@ -575,7 +575,7 @@
function performEpisodeScroll(scrollerContainer, targetCard, targetEpisodeNumber) {
const itemsContainer = scrollerContainer.querySelector('.itemsContainer') || scrollerContainer;
const cards = Array.from(itemsContainer.querySelectorAll(
':scope > .card:not(.card-layout-dummy):not(.skeleton-card)'
':scope > .card:not(.skeleton-card)'
));
const index = cards.indexOf(targetCard);
if (index < 0) {