mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
Fix absolutely positioned popovers
This commit is contained in:
@@ -520,7 +520,6 @@
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
z-index: 2;
|
||||
padding: 8px 16px;
|
||||
letter-spacing: 2px;
|
||||
word-spacing: 2px;
|
||||
}
|
||||
@@ -1930,6 +1929,7 @@
|
||||
top: initial !important;
|
||||
transform: initial !important;
|
||||
align-items: center;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.spotlight-section-title.headerTabs.sectionTabs,
|
||||
@@ -1941,7 +1941,6 @@
|
||||
justify-content: start;
|
||||
width: auto;
|
||||
font-size: revert !important;
|
||||
height: 40px !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1968,7 +1967,8 @@
|
||||
|
||||
.spotlight-section:not(.hide) .spotlight-nav-buttons-container {
|
||||
display: flex !important;
|
||||
padding: 0 0.5em !important;
|
||||
padding: 0 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.spotlight-section:not(.hide) .spotlight-nav-buttons-container button {
|
||||
padding: 0.5em;
|
||||
|
||||
@@ -1701,6 +1701,7 @@
|
||||
max-height: min(60vh, 420px);
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
min-width: initial !important;
|
||||
}
|
||||
.kefin-multi-query-modal [data-name="kefin-modal-content"] {
|
||||
overflow-y: auto !important;
|
||||
@@ -1813,8 +1814,7 @@
|
||||
dialogClassName: 'kefin-multi-query-modal',
|
||||
onOpen: (modal) => {
|
||||
if (modal.backdrop) modal.backdrop.style.background = 'transparent';
|
||||
if (modal.dialogContainer) modal.dialogContainer.style.pointerEvents = 'none';
|
||||
if (modal.dialog) modal.dialog.style.pointerEvents = 'auto';
|
||||
// Document-absolute shell via ModalSystem.positionNearAnchor.
|
||||
if (modal.dialogContent) {
|
||||
modal.dialogContent.style.padding = '0';
|
||||
modal.dialogContent.style.overflowY = 'auto';
|
||||
|
||||
@@ -1066,18 +1066,15 @@
|
||||
const maxLeft = window.innerWidth - menu.offsetWidth - margin;
|
||||
left = Math.min(Math.max(left, margin), maxLeft);
|
||||
|
||||
menu.style.top = `${top}px`;
|
||||
menu.style.left = `${left}px`;
|
||||
const container = menu.closest?.('.dialogContainer');
|
||||
if (typeof window.ModalSystem?.positionAnchoredPopoverShell === 'function') {
|
||||
window.ModalSystem.positionAnchoredPopoverShell(container, menu, left, top);
|
||||
}
|
||||
}
|
||||
|
||||
function styleAnchoredPopoverDialog(modal, anchorBtn) {
|
||||
if (!modal?.dialog || !anchorBtn) return;
|
||||
modal.dialog.classList.remove('centeredDialog', 'formDialog', 'smoothScrollY');
|
||||
modal.dialog.style.position = 'fixed';
|
||||
modal.dialog.style.margin = '0';
|
||||
modal.dialog.style.maxHeight = 'none';
|
||||
modal.dialog.style.minWidth = '8rem';
|
||||
modal.dialog.style.width = 'auto';
|
||||
if (typeof window.ModalSystem?.applyOpenAnimation === 'function') {
|
||||
window.ModalSystem.applyOpenAnimation(modal.dialog);
|
||||
} else {
|
||||
@@ -1091,11 +1088,12 @@
|
||||
if (modal.backdrop) {
|
||||
modal.backdrop.style.background = 'transparent';
|
||||
}
|
||||
if (modal.dialogContainer) {
|
||||
modal.dialogContainer.style.pointerEvents = 'none';
|
||||
if (typeof window.ModalSystem?.prepareAnchoredPopoverDialog === 'function') {
|
||||
window.ModalSystem.prepareAnchoredPopoverDialog(modal.dialog);
|
||||
}
|
||||
if (typeof window.ModalSystem?.prepareAnchoredDialogContainer === 'function') {
|
||||
window.ModalSystem.prepareAnchoredDialogContainer(modal.dialogContainer);
|
||||
}
|
||||
modal.dialog.style.pointerEvents = 'auto';
|
||||
modal.dialog.style.overflow = 'visible';
|
||||
requestAnimationFrame(() => positionFormatMenu(modal.dialog, anchorBtn));
|
||||
}
|
||||
|
||||
@@ -1192,6 +1190,13 @@
|
||||
WARN('ModalSystem unavailable for option menu');
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
window.ModalSystem.isOpen?.(OPTION_MENU_MODAL_ID)
|
||||
&& activePopover?.formatMenuAnchor === anchorBtn
|
||||
) {
|
||||
closeActiveFormatMenu();
|
||||
return null;
|
||||
}
|
||||
closeActiveFormatMenu();
|
||||
if (!opts?.preserveCardTitleMenu) {
|
||||
closeCardTitleMenuOnly();
|
||||
@@ -1398,6 +1403,13 @@
|
||||
WARN('ModalSystem unavailable for card title options');
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
window.ModalSystem.isOpen?.(CARD_TITLE_MENU_MODAL_ID)
|
||||
&& activePopover?.cardTitleMenuAnchor === anchorBtn
|
||||
) {
|
||||
closeCardTitleMenu();
|
||||
return null;
|
||||
}
|
||||
closeCardTitleMenu();
|
||||
|
||||
const cfg = entry.effectiveConfig;
|
||||
@@ -2139,7 +2151,7 @@
|
||||
}
|
||||
|
||||
function startPopoverTracking() {
|
||||
// Placement is fixed for the life of the open popover.
|
||||
// Placement is locked after first place — no scroll/resize chasing (0c11842).
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@@ -2167,6 +2179,12 @@
|
||||
}
|
||||
|
||||
function finalizeConfigurePopoverClose() {
|
||||
if (window.ModalSystem?.isOpen?.(OPTION_MENU_MODAL_ID)) {
|
||||
window.ModalSystem.close(OPTION_MENU_MODAL_ID);
|
||||
}
|
||||
if (window.ModalSystem?.isOpen?.(CARD_TITLE_MENU_MODAL_ID)) {
|
||||
window.ModalSystem.close(CARD_TITLE_MENU_MODAL_ID);
|
||||
}
|
||||
if (!activePopover) return;
|
||||
const { entry, stopTracking, mountElement, sectionElement, outsideHandler } = activePopover;
|
||||
stopTracking?.();
|
||||
@@ -2333,16 +2351,18 @@
|
||||
}
|
||||
|
||||
popover.classList.toggle('is-below', placeBelow);
|
||||
popover.style.position = 'fixed';
|
||||
popover.style.left = `${leftViewport}px`;
|
||||
popover.style.bottom = 'auto';
|
||||
popover.style.right = 'auto';
|
||||
popover.style.margin = '0';
|
||||
let topViewport;
|
||||
if (placeBelow) {
|
||||
popover.style.top = `${buttonRect.bottom + gap}px`;
|
||||
topViewport = buttonRect.bottom + gap;
|
||||
} else {
|
||||
const height = getPopoverPlacementHeight(popover, popover.classList.contains('is-expanded'));
|
||||
popover.style.top = `${Math.max(margin, buttonRect.top - gap - height)}px`;
|
||||
topViewport = Math.max(margin, buttonRect.top - gap - height);
|
||||
}
|
||||
const container = activePopover?.modal?.dialogContainer;
|
||||
if (typeof window.ModalSystem?.positionAnchoredPopoverShell === 'function') {
|
||||
window.ModalSystem.positionAnchoredPopoverShell(container, popover, leftViewport, topViewport);
|
||||
} else if (typeof window.ModalSystem?.prepareAnchoredPopoverDialog === 'function') {
|
||||
window.ModalSystem.prepareAnchoredPopoverDialog(popover);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2507,14 +2527,14 @@
|
||||
},
|
||||
onOpen: (modalInstance) => {
|
||||
if (modalInstance.backdrop) modalInstance.backdrop.style.background = 'transparent';
|
||||
if (modalInstance.dialogContainer) modalInstance.dialogContainer.style.pointerEvents = 'none';
|
||||
if (typeof window.ModalSystem?.prepareAnchoredDialogContainer === 'function') {
|
||||
window.ModalSystem.prepareAnchoredDialogContainer(modalInstance.dialogContainer);
|
||||
}
|
||||
if (typeof window.ModalSystem?.prepareAnchoredPopoverDialog === 'function') {
|
||||
window.ModalSystem.prepareAnchoredPopoverDialog(modalInstance.dialog);
|
||||
}
|
||||
if (modalInstance.dialog) {
|
||||
modalInstance.dialog.classList.remove('centeredDialog', 'formDialog', 'smoothScrollY', 'dialog-fixedSize');
|
||||
modalInstance.dialog.style.pointerEvents = 'auto';
|
||||
modalInstance.dialog.style.maxHeight = 'none';
|
||||
modalInstance.dialog.style.minHeight = '0';
|
||||
modalInstance.dialog.style.height = 'auto';
|
||||
modalInstance.dialog.style.width = 'auto';
|
||||
}
|
||||
if (modalInstance.dialogContent) {
|
||||
modalInstance.dialogContent.style.padding = '0';
|
||||
@@ -2526,6 +2546,12 @@
|
||||
});
|
||||
|
||||
const popover = modal.dialog;
|
||||
if (typeof window.ModalSystem?.prepareAnchoredDialogContainer === 'function') {
|
||||
window.ModalSystem.prepareAnchoredDialogContainer(modal.dialogContainer);
|
||||
}
|
||||
if (typeof window.ModalSystem?.prepareAnchoredPopoverDialog === 'function') {
|
||||
window.ModalSystem.prepareAnchoredPopoverDialog(modal.dialog);
|
||||
}
|
||||
// Queries / expand state use the dialog root (has kefin-section-configure-popover)
|
||||
|
||||
const openEditorBtn = popover.querySelector('.kefin-section-open-editor-btn');
|
||||
@@ -2863,10 +2889,15 @@
|
||||
function createConfigureButton(sectionConfig, sectionElement) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'section-configure-button material-icons settings paper-icon-button-light';
|
||||
button.setAttribute('is', 'paper-icon-button-light');
|
||||
button.className = 'section-configure-button paper-icon-button-light';
|
||||
if (isTvLayout()) button.classList.add('show-focus');
|
||||
button.title = 'Configure';
|
||||
button.setAttribute('aria-label', 'Configure');
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'material-icons settings';
|
||||
icon.setAttribute('aria-hidden', 'true');
|
||||
button.appendChild(icon);
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -2944,7 +2975,7 @@
|
||||
overflow: visible;
|
||||
}
|
||||
.kefin-section-configure-popover {
|
||||
position: fixed;
|
||||
position: relative;
|
||||
z-index: 12000;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
@@ -2953,6 +2984,7 @@
|
||||
min-width: 14rem;
|
||||
padding: 0.5em;
|
||||
overflow: visible;
|
||||
inset: auto;
|
||||
}
|
||||
.kefin-section-configure-popover [data-name="kefin-modal-content"] {
|
||||
padding: 0 !important;
|
||||
|
||||
+103
-10
@@ -107,12 +107,103 @@ window.ModalSystem = (function() {
|
||||
);
|
||||
}
|
||||
|
||||
let anchoredPopoverStylesInjected = false;
|
||||
|
||||
function ensureAnchoredPopoverStyles() {
|
||||
if (anchoredPopoverStylesInjected) return;
|
||||
anchoredPopoverStylesInjected = true;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'kefinTweaks-anchoredPopover-styles';
|
||||
style.textContent = `
|
||||
.dialogContainer.kefin-modal-anchored-container {
|
||||
contain: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.dialogContainer.kefin-modal-anchored-container > .dialog.kefin-anchored-popover-dialog {
|
||||
position: relative !important;
|
||||
inset: auto !important;
|
||||
top: auto !important;
|
||||
right: auto !important;
|
||||
bottom: auto !important;
|
||||
left: auto !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position a dialog near an anchor (fixed coords, viewport-clamped).
|
||||
* Document-absolute shell: override Jellyfin fixed full-screen dialogContainer.
|
||||
* @param {HTMLElement|null|undefined} dialogContainer
|
||||
*/
|
||||
function prepareAnchoredDialogContainer(dialogContainer) {
|
||||
if (!dialogContainer) return;
|
||||
ensureAnchoredPopoverStyles();
|
||||
dialogContainer.classList.add('kefin-modal-anchored-container');
|
||||
dialogContainer.setAttribute('data-kefin-anchored', 'true');
|
||||
dialogContainer.style.position = 'absolute';
|
||||
dialogContainer.style.right = 'auto';
|
||||
dialogContainer.style.bottom = 'auto';
|
||||
dialogContainer.style.inset = 'auto';
|
||||
dialogContainer.style.width = 'auto';
|
||||
dialogContainer.style.height = 'auto';
|
||||
dialogContainer.style.overflow = 'visible';
|
||||
dialogContainer.style.contain = 'none';
|
||||
dialogContainer.style.pointerEvents = 'none';
|
||||
if (!dialogContainer.style.zIndex) {
|
||||
dialogContainer.style.zIndex = '1100';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner dialog is in-flow inside the anchored container (not viewport-fixed / inset fill).
|
||||
* @param {HTMLElement|null|undefined} dialog
|
||||
*/
|
||||
function prepareAnchoredPopoverDialog(dialog) {
|
||||
if (!dialog) return;
|
||||
ensureAnchoredPopoverStyles();
|
||||
dialog.classList.add('kefin-anchored-popover-dialog');
|
||||
dialog.classList.remove('centeredDialog', 'formDialog', 'smoothScrollY', 'dialog-fixedSize');
|
||||
dialog.style.position = 'relative';
|
||||
dialog.style.inset = 'auto';
|
||||
dialog.style.top = 'auto';
|
||||
dialog.style.right = 'auto';
|
||||
dialog.style.bottom = 'auto';
|
||||
dialog.style.left = 'auto';
|
||||
dialog.style.margin = '0';
|
||||
dialog.style.width = 'auto';
|
||||
dialog.style.height = 'auto';
|
||||
dialog.style.maxHeight = 'none';
|
||||
dialog.style.pointerEvents = 'auto';
|
||||
dialog.style.overflow = 'visible';
|
||||
}
|
||||
|
||||
/**
|
||||
* Place anchored popover shell in document space; dialog lays out inside the container.
|
||||
* @param {HTMLElement|null|undefined} dialogContainer
|
||||
* @param {HTMLElement|null|undefined} dialog
|
||||
* @param {number} leftViewport
|
||||
* @param {number} topViewport
|
||||
*/
|
||||
function positionAnchoredPopoverShell(dialogContainer, dialog, leftViewport, topViewport) {
|
||||
prepareAnchoredDialogContainer(dialogContainer);
|
||||
prepareAnchoredPopoverDialog(dialog);
|
||||
if (!dialogContainer) return;
|
||||
const scrollX = window.scrollX || window.pageXOffset || 0;
|
||||
const scrollY = window.scrollY || window.pageYOffset || 0;
|
||||
dialogContainer.style.left = `${leftViewport + scrollX}px`;
|
||||
dialogContainer.style.top = `${topViewport + scrollY}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position a dialog near an anchor (document-absolute shell + scroll offsets).
|
||||
* @param {HTMLElement} dialog
|
||||
* @param {HTMLElement} anchor
|
||||
* @param {{ dialogContainer?: HTMLElement|null }} [options]
|
||||
*/
|
||||
function positionNearAnchor(dialog, anchor) {
|
||||
function positionNearAnchor(dialog, anchor, options = {}) {
|
||||
if (!dialog || !anchor) return;
|
||||
const margin = 8;
|
||||
const gap = 8;
|
||||
@@ -138,12 +229,10 @@ window.ModalSystem = (function() {
|
||||
}
|
||||
top = Math.min(Math.max(top, margin), window.innerHeight - Math.min(dialogHeight, window.innerHeight - margin * 2) - margin);
|
||||
|
||||
dialog.style.position = 'fixed';
|
||||
dialog.style.left = `${left}px`;
|
||||
dialog.style.top = `${top}px`;
|
||||
dialog.style.margin = '0';
|
||||
dialog.style.right = 'auto';
|
||||
dialog.style.bottom = 'auto';
|
||||
const dialogContainer = options.dialogContainer
|
||||
|| dialog.closest?.('.dialogContainer')
|
||||
|| null;
|
||||
positionAnchoredPopoverShell(dialogContainer, dialog, left, top);
|
||||
}
|
||||
|
||||
function focusElement(el) {
|
||||
@@ -562,8 +651,9 @@ window.ModalSystem = (function() {
|
||||
if (anchor instanceof HTMLElement) {
|
||||
dialog.classList.remove('centeredDialog', 'formDialog', 'smoothScrollY', 'dialog-fixedSize');
|
||||
dialog.style.maxHeight = 'none';
|
||||
prepareAnchoredDialogContainer(dialogContainer);
|
||||
requestAnimationFrame(() => {
|
||||
positionNearAnchor(dialog, anchor);
|
||||
positionNearAnchor(dialog, anchor, { dialogContainer });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -622,7 +712,7 @@ window.ModalSystem = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dialog from DOM
|
||||
// Remove dialog container from DOM (dialog stays a child for anchored popovers)
|
||||
if (modal.dialogContainer && modal.dialogContainer.parentNode) {
|
||||
modal.dialogContainer.parentNode.removeChild(modal.dialogContainer);
|
||||
}
|
||||
@@ -710,6 +800,9 @@ window.ModalSystem = (function() {
|
||||
closeAll: closeAllModals,
|
||||
applyOpenAnimation,
|
||||
attachFocusTrap,
|
||||
prepareAnchoredDialogContainer,
|
||||
prepareAnchoredPopoverDialog,
|
||||
positionAnchoredPopoverShell,
|
||||
positionNearAnchor,
|
||||
getFocusableElements,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user