diff --git a/css/builder.css b/css/builder.css index 9b5ef3fe4236a95de03deb9484d157dec5fe9d06..f6d0260066fa702abc2fed43fe7e9ec0a0d049d4 100644 --- a/css/builder.css +++ b/css/builder.css @@ -12,7 +12,7 @@ --lp-btn-bg-color: #0550E6; --lp-btn-fg-color: #fff; - --lp-controls-sticky-top: 170px; + --lp-controls-sticky-top: 70px; } .lp-builder { @@ -111,15 +111,13 @@ border: 1px dotted #ccc; font-size: small; } -.lpb-controls__wrapper { - position: absolute; -} .lpb-controls { - position: sticky; + position: absolute; z-index: 80; - top: var(--lp-controls-sticky-top); + top: 0; margin: 0 auto; - transform: translateY(calc(-100% - 15px)); + left: 50%; + transform: translate(-50%, calc(-100% - 15px)); display: flex; padding: 2px 2px 2px 15px; opacity: 0; @@ -134,6 +132,15 @@ letter-spacing: 2px; font-weight: bold; } + &.is-fixed { + position: fixed; + top: var(--lp-controls-sticky-top); + left: var(--lp-controls-sticky-left, 50%); + transform: translate(-50%, 0); + } + &.is-scrolled-past { + opacity: 0 !important; + } } .lpb-controls:hover, .lpb-controls:focus-within { diff --git a/js/builder.js b/js/builder.js index 8e01e2b5c20a5630698ad188551e2239677f7a37..9a9c0ac089f437e00379e0a1e29f3c2cc67e5f53 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,6 +1,9 @@ (($, Drupal, drupalSettings, debounce, Sortable, once) => { const idAttr = 'data-lpb-id'; + // The current active component, or NULL. + let activeComponent = null; + /** * Helper function to select all elements matching a selector. * @@ -72,6 +75,50 @@ }); } + /** + * Sets position for the active component's controls. + */ + const setControlsPosition = debounce(() => { + if (!activeComponent) { + return; + } + const controls = select('.lpb-controls', activeComponent); + const componentRect = activeComponent.getBoundingClientRect(); + const controlsRect = controls.getBoundingClientRect(); + const controlsLeft = componentRect.left + componentRect.width / 2; + activeComponent.style.setProperty( + '--lp-controls-sticky-left', + `${controlsLeft}px`, + ); + if (!controls.getAttribute('data-offset-y')) { + controls.setAttribute( + 'data-offset-y', + componentRect.top - controlsRect.top, + ); + controls.setAttribute( + 'data-threshold-y', + parseInt( + getComputedStyle(controls).getPropertyValue( + '--lp-controls-sticky-top', + ), + 10, + ), + ); + } + const offsetY = parseInt(controls.getAttribute('data-offset-y'), 10); + const top = componentRect.top - offsetY; + if (top < controls.getAttribute('data-threshold-y')) { + controls.classList.add('is-fixed'); + } else { + controls.classList.remove('is-fixed'); + } + if (controlsRect.bottom + offsetY > componentRect.bottom) { + controls.classList.add('is-scrolled-past'); + } else { + controls.classList.remove('is-scrolled-past'); + } + }, 25); + /** * Removes focus data attributes from all components. */ @@ -85,6 +132,7 @@ selectAll('.is-navigating').forEach((element) => element.classList.remove('is-navigating'), ); + activeComponent = null; setUiElementVisibility(); } @@ -94,11 +142,13 @@ * @param {HTMLElement} element The component to focus. */ function selectComponent(element) { + activeComponent = element; select('.lpb-hover-label')?.remove(); // Add the data-active attribute to the element. element.setAttribute('data-active', 'true'); element.setAttribute('data-active-within', 'true'); setUiElementVisibility(); + setControlsPosition(); // Add the data-active-within attribute to all parent elements. let parent = element.parentNode.closest('.js-lpb-component'); while (parent) { @@ -134,7 +184,7 @@ // Add hover to the element. if (element && !element.getAttribute('data-active')) { element.setAttribute('data-hover', 'true'); - const hoverLabel = select('.lpb-controls-label')?.textContent; + const hoverLabel = select('.lpb-controls-label', element)?.textContent; if (hoverLabel) { const hoverLabelElement = select('.lpb-hover-label') || @@ -861,6 +911,11 @@ */ listen(window, 'scroll', () => { document.querySelector('.lpb-hover-label')?.remove(); + setControlsPosition(); + }); + + listen(window, 'resize', () => { + setControlsPosition(); }); /**