From 764eb8987a0a943d94bb45820c9cccf330ccc2cb Mon Sep 17 00:00:00 2001 From: Adrien Maston Date: Fri, 23 Jan 2026 14:06:17 +0100 Subject: [PATCH] Tweak orientation helper to prevent popups from going off screen --- app/assets/stylesheets/popup.css | 4 +-- app/assets/stylesheets/tooltips.css | 4 +-- .../controllers/dialog_controller.js | 4 +-- .../controllers/tooltip_controller.js | 4 +-- app/javascript/helpers/orientation_helpers.js | 36 +++++++++++-------- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index aa0b8c1da..6e2720637 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -27,13 +27,13 @@ &:where(.popup--align-left), &.orient-left { inset-inline: auto 0; - transform: translateX(0); + transform: translateX(var(--orient-offset, 0px)); } &:where(.popup--align-right), &.orient-right { inset-inline: 0 auto; - transform: translateX(0); + transform: translateX(var(--orient-offset, 0px)); } form { diff --git a/app/assets/stylesheets/tooltips.css b/app/assets/stylesheets/tooltips.css index f6ff54ffc..ab142afdc 100644 --- a/app/assets/stylesheets/tooltips.css +++ b/app/assets/stylesheets/tooltips.css @@ -20,12 +20,12 @@ &.orient-right { inset-inline: 0 auto; - translate: 0 -100%; + translate: var(--orient-offset, 0px) -100%; } &.orient-left { inset-inline: auto 0; - translate: 0 -100%; + translate: var(--orient-offset, 0px) -100%; } } diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index 037c5ec7b..b52eb2ea6 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -26,7 +26,7 @@ export default class extends Controller { this.dialogTarget.showModal() } else { this.dialogTarget.show() - orient(this.dialogTarget) + orient({ target: this.dialogTarget, anchor: this.element }) } this.loadLazyFrames() @@ -46,7 +46,7 @@ export default class extends Controller { this.dialogTarget.close() this.dialogTarget.setAttribute("aria-hidden", "true") this.dialogTarget.blur() - orient(this.dialogTarget, false) + orient({ target: this.dialogTarget, reset: true }) this.dispatch("close") } diff --git a/app/javascript/controllers/tooltip_controller.js b/app/javascript/controllers/tooltip_controller.js index 0b242f157..57958cd15 100644 --- a/app/javascript/controllers/tooltip_controller.js +++ b/app/javascript/controllers/tooltip_controller.js @@ -15,11 +15,11 @@ export default class extends Controller { } mouseEnter(event) { - orient(this.#tooltipElement) + orient({ target: this.#tooltipElement, anchor: this.element }) } mouseOut(event) { - orient(this.#tooltipElement, false) + orient({ target: this.#tooltipElement, reset: true }) } get #tooltipElement() { diff --git a/app/javascript/helpers/orientation_helpers.js b/app/javascript/helpers/orientation_helpers.js index b79d66360..c63da2fca 100644 --- a/app/javascript/helpers/orientation_helpers.js +++ b/app/javascript/helpers/orientation_helpers.js @@ -1,24 +1,30 @@ const EDGE_THRESHOLD = 16 -export function orient(el, orient = true) { - el.classList.remove("orient-left", "orient-right") +export function orient({ target, anchor = null, reset = false }) { + target.classList.remove("orient-left", "orient-right") + target.style.removeProperty("--orient-offset") - if (!orient) return + if (reset) return - const rightSpace = spaceOnRight(el) - const leftSpace = spaceOnLeft(el) + const targetGeometry = geometry(target) + const anchorGeometry = geometry(anchor) - if (rightSpace < EDGE_THRESHOLD && rightSpace < leftSpace) { - el.classList.add("orient-left") - } else if (leftSpace < EDGE_THRESHOLD && leftSpace < rightSpace) { - el.classList.add("orient-right") + if (targetGeometry.spaceOnRight < EDGE_THRESHOLD && targetGeometry.spaceOnRight < targetGeometry.spaceOnLeft) { + const offset = Math.min(0, anchorGeometry.spaceOnLeft + anchorGeometry.width - targetGeometry.width) * -1 + target.classList.add("orient-left") + target.style.setProperty("--orient-offset", `${offset}px`) + } else if (targetGeometry.spaceOnLeft < EDGE_THRESHOLD && targetGeometry.spaceOnLeft < targetGeometry.spaceOnRight) { + const offset = Math.max(0, anchorGeometry.spaceOnLeft + targetGeometry.width - window.innerWidth) * -1 + target.classList.add("orient-right") + target.style.setProperty("--orient-offset", `${offset}px`) } } -function spaceOnLeft(el) { - return el.getBoundingClientRect().left -} - -function spaceOnRight(el) { - return window.innerWidth - el.getBoundingClientRect().right +function geometry(el) { + const rect = el.getBoundingClientRect() + return { + spaceOnLeft: rect.left, + spaceOnRight: window.innerWidth - rect.right, + width: rect.width + } }