From 3f5d070ae96f4b0ff07aca290010df5aaaa9ff2c Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Aug 2025 14:42:08 -0500 Subject: [PATCH 1/6] Stub out basic boundary detection --- .../controllers/dialog_controller.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index b1d9eee4a..d86bc1c08 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -1,5 +1,7 @@ import { Controller } from "@hotwired/stimulus" +const BOTTOM_THRESHOLD = 90 + export default class extends Controller { static targets = [ "dialog" ] static values = { @@ -18,6 +20,7 @@ export default class extends Controller { } else { this.dialogTarget.show() } + this.#orient() this.dialogTarget.setAttribute('aria-hidden', 'false') this.dispatch("show") } @@ -39,4 +42,25 @@ export default class extends Controller { closeOnClickOutside({ target }) { if (!this.element.contains(target)) this.close() } + + #orient() { + console.log("ORIENT") + console.log(`distanceToBottom = ${this.#distanceToBottom}`) + console.log(`maxWidth = ${this.#maxWidth}px`) + + this.dialogTarget.classList.toggle("ANDYSMITH", this.#distanceToBottom < BOTTOM_THRESHOLD) + this.dialogTarget.style.setProperty("--max-width", this.#maxWidth + "px") + } + + get #distanceToBottom() { + return window.innerHeight - this.#boundingClientRect.bottom + } + + get #maxWidth() { + return window.innerWidth - this.#boundingClientRect.left + } + + get #boundingClientRect() { + return this.dialogTarget.getBoundingClientRect() + } } From c444011095c86896c2058fcaaced0fe312a8d5ea Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Aug 2025 14:59:50 -0500 Subject: [PATCH 2/6] Working left/right orientation --- app/assets/stylesheets/popup.css | 7 +++++- .../controllers/dialog_controller.js | 25 ++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index 7ef87d85d..c74350ab2 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -10,13 +10,18 @@ max-inline-size: min(55ch, calc(100dvw - var(--block-space-double))); overflow: auto; position: absolute; - transform: translateX(-50%); + translate: -50% 0; z-index: var(--z-popup); &[open] { display: flex; } + &.orient-left { + inset-inline: auto 0; + translate: 0; + } + &:has(.popup__footer) { --panel-padding: var(--block-space) var(--block-space) 0 var(--block-space); } diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index d86bc1c08..553de7361 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -1,6 +1,6 @@ import { Controller } from "@hotwired/stimulus" -const BOTTOM_THRESHOLD = 90 +const EDGE_THRESHOLD = 90 export default class extends Controller { static targets = [ "dialog" ] @@ -20,7 +20,7 @@ export default class extends Controller { } else { this.dialogTarget.show() } - this.#orient() + this.#orient(this.dialogTarget) this.dialogTarget.setAttribute('aria-hidden', 'false') this.dispatch("show") } @@ -43,23 +43,24 @@ export default class extends Controller { if (!this.element.contains(target)) this.close() } - #orient() { - console.log("ORIENT") - console.log(`distanceToBottom = ${this.#distanceToBottom}`) - console.log(`maxWidth = ${this.#maxWidth}px`) + #orient(element) { + element.classList.toggle("orient-left", this.#distanceToRight < EDGE_THRESHOLD) + element.classList.toggle("orient-right", this.#distanceToLeft < EDGE_THRESHOLD) + element.classList.toggle("orient-top", this.#distanceToBottom < EDGE_THRESHOLD) + } - this.dialogTarget.classList.toggle("ANDYSMITH", this.#distanceToBottom < BOTTOM_THRESHOLD) - this.dialogTarget.style.setProperty("--max-width", this.#maxWidth + "px") + get #distanceToLeft() { + return this.#boundingClientRect.left + } + + get #distanceToRight() { + return window.innerWidth - this.#boundingClientRect.right } get #distanceToBottom() { return window.innerHeight - this.#boundingClientRect.bottom } - get #maxWidth() { - return window.innerWidth - this.#boundingClientRect.left - } - get #boundingClientRect() { return this.dialogTarget.getBoundingClientRect() } From d25f628204c3f5ddd832f4ae7922a36a8e9bb3a0 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Aug 2025 15:44:01 -0500 Subject: [PATCH 3/6] Extract orient into a module --- app/assets/stylesheets/orient.css | 13 +++++++++ app/assets/stylesheets/popup.css | 7 +---- .../controllers/dialog_controller.js | 28 ++----------------- app/javascript/helpers/orientation_helpers.js | 19 +++++++++++++ 4 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 app/assets/stylesheets/orient.css create mode 100644 app/javascript/helpers/orientation_helpers.js diff --git a/app/assets/stylesheets/orient.css b/app/assets/stylesheets/orient.css new file mode 100644 index 000000000..780244fbf --- /dev/null +++ b/app/assets/stylesheets/orient.css @@ -0,0 +1,13 @@ +@layer utilities { + .orient-left { + inset-inline: auto 0; + transform: translateX(0); + background: lime !important; + } + + .orient-right { + inset-inline: 0 auto; + transform: translateX(0); + background: gold !important; + } +} diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index c74350ab2..7ef87d85d 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -10,18 +10,13 @@ max-inline-size: min(55ch, calc(100dvw - var(--block-space-double))); overflow: auto; position: absolute; - translate: -50% 0; + transform: translateX(-50%); z-index: var(--z-popup); &[open] { display: flex; } - &.orient-left { - inset-inline: auto 0; - translate: 0; - } - &:has(.popup__footer) { --panel-padding: var(--block-space) var(--block-space) 0 var(--block-space); } diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index 553de7361..96483b72c 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -1,6 +1,5 @@ import { Controller } from "@hotwired/stimulus" - -const EDGE_THRESHOLD = 90 +import { orient } from "helpers/orientation_helpers" export default class extends Controller { static targets = [ "dialog" ] @@ -19,8 +18,9 @@ export default class extends Controller { this.dialogTarget.showModal() } else { this.dialogTarget.show() + orient(this.dialogTarget) } - this.#orient(this.dialogTarget) + this.dialogTarget.setAttribute('aria-hidden', 'false') this.dispatch("show") } @@ -42,26 +42,4 @@ export default class extends Controller { closeOnClickOutside({ target }) { if (!this.element.contains(target)) this.close() } - - #orient(element) { - element.classList.toggle("orient-left", this.#distanceToRight < EDGE_THRESHOLD) - element.classList.toggle("orient-right", this.#distanceToLeft < EDGE_THRESHOLD) - element.classList.toggle("orient-top", this.#distanceToBottom < EDGE_THRESHOLD) - } - - get #distanceToLeft() { - return this.#boundingClientRect.left - } - - get #distanceToRight() { - return window.innerWidth - this.#boundingClientRect.right - } - - get #distanceToBottom() { - return window.innerHeight - this.#boundingClientRect.bottom - } - - get #boundingClientRect() { - return this.dialogTarget.getBoundingClientRect() - } } diff --git a/app/javascript/helpers/orientation_helpers.js b/app/javascript/helpers/orientation_helpers.js new file mode 100644 index 000000000..6d8e075ec --- /dev/null +++ b/app/javascript/helpers/orientation_helpers.js @@ -0,0 +1,19 @@ +const EDGE_THRESHOLD = 16 + +export function orient(el) { + el.classList.toggle("orient-left", spaceOnRight(el) < EDGE_THRESHOLD) + el.classList.toggle("orient-right", spaceOnLeft(el) < EDGE_THRESHOLD) + el.classList.toggle("orient-top", spaceOnBottom(el) < EDGE_THRESHOLD) +} + +function spaceOnLeft(el) { + return el.getBoundingClientRect().left +} + +function spaceOnRight(el) { + return window.innerWidth - el.getBoundingClientRect().right +} + +function spaceOnBottom(el) { + return window.innerHeight - el.getBoundingClientRect().bottom +} From dfefd16f0f8fd9279a2c0a59801083133d2aee09 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Aug 2025 16:33:18 -0500 Subject: [PATCH 4/6] Place orient classes in popup CSS --- app/assets/stylesheets/orient.css | 13 ------------- app/assets/stylesheets/popup.css | 12 ++++++++++++ 2 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 app/assets/stylesheets/orient.css diff --git a/app/assets/stylesheets/orient.css b/app/assets/stylesheets/orient.css deleted file mode 100644 index 780244fbf..000000000 --- a/app/assets/stylesheets/orient.css +++ /dev/null @@ -1,13 +0,0 @@ -@layer utilities { - .orient-left { - inset-inline: auto 0; - transform: translateX(0); - background: lime !important; - } - - .orient-right { - inset-inline: 0 auto; - transform: translateX(0); - background: gold !important; - } -} diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index 7ef87d85d..e4302fddc 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -17,6 +17,18 @@ display: flex; } + &.orient-left { + inset-inline: auto 0; + transform: translateX(0); + background: lime !important; + } + + &.orient-right { + inset-inline: 0 auto; + transform: translateX(0); + background: gold !important; + } + &:has(.popup__footer) { --panel-padding: var(--block-space) var(--block-space) 0 var(--block-space); } From 78273898d2c9a6279720f357b4a211759951b1e9 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 14 Aug 2025 16:33:56 -0500 Subject: [PATCH 5/6] Remove testing colors --- app/assets/stylesheets/popup.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index e4302fddc..914d3fc9e 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -20,13 +20,11 @@ &.orient-left { inset-inline: auto 0; transform: translateX(0); - background: lime !important; } &.orient-right { inset-inline: 0 auto; transform: translateX(0); - background: gold !important; } &:has(.popup__footer) { From bb53258286288d907fc66c7351c3f099e0f08910 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 18 Aug 2025 11:08:17 -0500 Subject: [PATCH 6/6] Remove orientation on dialog close --- app/javascript/controllers/dialog_controller.js | 1 + app/javascript/helpers/orientation_helpers.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index 96483b72c..9341f4c25 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -37,6 +37,7 @@ export default class extends Controller { this.dialogTarget.close() this.dialogTarget.setAttribute('aria-hidden', 'true') this.dialogTarget.blur() + orient(this.dialogTarget, false) } closeOnClickOutside({ target }) { diff --git a/app/javascript/helpers/orientation_helpers.js b/app/javascript/helpers/orientation_helpers.js index 6d8e075ec..4e3f1a3a5 100644 --- a/app/javascript/helpers/orientation_helpers.js +++ b/app/javascript/helpers/orientation_helpers.js @@ -1,9 +1,15 @@ const EDGE_THRESHOLD = 16 -export function orient(el) { - el.classList.toggle("orient-left", spaceOnRight(el) < EDGE_THRESHOLD) - el.classList.toggle("orient-right", spaceOnLeft(el) < EDGE_THRESHOLD) - el.classList.toggle("orient-top", spaceOnBottom(el) < EDGE_THRESHOLD) +export function orient(el, orient = true) { + const directions = [ + ["orient-left", spaceOnRight], + ["orient-right", spaceOnLeft], + ["orient-top", spaceOnBottom] + ]; + + directions.forEach(([className, fn]) => + el.classList[orient && fn(el) < EDGE_THRESHOLD ? "add" : "remove"](className) + ); } function spaceOnLeft(el) {