diff --git a/css/frontend_editing.css b/css/frontend_editing.css index f27f8add82c9734b58feebd51ea24fec2da7a6e3..ce063cdfc0c7d504eda1fa62dc5b0caf73cbf34a 100644 --- a/css/frontend_editing.css +++ b/css/frontend_editing.css @@ -154,6 +154,8 @@ body:not(.frontend-editing--hidden) .frontend-editing:has(.add-paragraphs.hover- .common-actions-container.place-fixed { position: fixed; + top: auto; + bottom: 10px; } .title-edit-container, diff --git a/js/editing_actions.js b/js/editing_actions.js index 1f746c9195dab12158f473c70bfdbacd24f7c074..cd1b10d368bb7b75a438aa081c6f1c19cb99acbc 100644 --- a/js/editing_actions.js +++ b/js/editing_actions.js @@ -1,38 +1,98 @@ -/** - * @file - * Keeps editing actions in viewport. - */ (function (Drupal) { + // Prevents the need to query the DOM multiple times + const actionsContainerMap = new Map(); - const floatingLinks = function () { - const clientHeight = document.documentElement.clientHeight; - document.querySelectorAll(".frontend-editing").forEach((element) => { - const { top, bottom, left } = element.getBoundingClientRect() - const actions = element.querySelector('.common-actions-container') + const getActionsContainer = (element) => { + if (!actionsContainerMap.has(element)) { + const actions = element.querySelector('.common-actions-container'); + actionsContainerMap.set(element, actions); + } + return actionsContainerMap.get(element); + }; + + const updatePositionReset = (element) => { + const actions = getActionsContainer(element); + + actions.removeAttribute('style'); + actions.classList.remove('place-bottom'); + actions.classList.remove('place-fixed'); + } + + const updatePositionBottom = (element, rect) => { + const { bottom } = rect; + + // Apply bottom position if element bottom is inside the viewport + if (bottom <= window.innerHeight && bottom >= 0) { + const actions = getActionsContainer(element); + + updatePositionReset(element); + actions.classList.add('place-bottom'); + } + } - // Reset - actions.removeAttribute('style'); - actions.classList.remove('place-bottom'); - actions.classList.remove('place-fixed'); + const updatePositionFixed = (element, rect) => { + const { left, bottom, top } = rect; - // Top intersecting sections - if (top < 0 && bottom > 0 && bottom < clientHeight) { - actions.classList.add('place-bottom'); + // Apply fixed position if element top and bottom are outside the viewport + if (top <= 0 && bottom >= window.innerHeight) { + const actions = getActionsContainer(element); + + updatePositionReset(element); + actions.classList.add('place-fixed'); + actions.style.left = left + 'px'; + } + } + + // Observer for the top of the element + const topObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const element = entry.target; + const rect = element.getBoundingClientRect(); + + if (entry.isIntersecting) { + // Top left viewport + updatePositionFixed(element, rect); + } else { + // Top entered viewport + updatePositionReset(element); } + }); + }, { + root: null, + threshold: 0, + rootMargin: '0px 0px -100% 0px' + }); + + // Observer for the bottom of the element + const bottomObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const element = entry.target; + const rect = element.getBoundingClientRect(); - // Top + bottom intersecting sections - if (top < 0 && bottom > clientHeight) { - actions.classList.add('place-fixed'); - actions.style.top = clientHeight - actions.offsetHeight - 10 + 'px' - actions.style.left = left + 'px' + if (entry.isIntersecting) { + // Bottom left viewport + updatePositionFixed(element, rect); + } else { + // Bottom entered viewport + updatePositionBottom(element, rect); } - }) - } + }); + }, { + root: null, + threshold: 0, + rootMargin: '-100% 0px 0px 0px' + }); Drupal.behaviors.editingActionsPosition = { - attach(context, settings) { - window.addEventListener('scroll', Drupal.debounce(floatingLinks, 250, false)); + attach(context) { + context.querySelectorAll('.frontend-editing').forEach((element) => { + // Only observe if element is larger than the viewport + if (element.offsetHeight > window.innerHeight) { + topObserver.observe(element); + bottomObserver.observe(element); + } + }); } - } + }; })(Drupal);