UNPKG

@csedl/stimulus-dropdown

Version:

Dropdown and Tooltip with stimulus and floating-ui

106 lines (91 loc) 3.7 kB
import {arrow, computePosition, flip, offset, shift} from "@floating-ui/dom"; import {debugLog} from "./utils.js"; // Positions a panel relative to its trigger button export function positionPanelByButton(button, logContext) { const toggleId = button.getAttribute('data-panel-id') let panel = document.getElementById(toggleId) positionPanel(button, panel) debugLog('panel positioned', logContext) } // Positions a panel using its event listener export function positionPanelSelf(panel) { let button = document.querySelector(`[data-panel-id="${panel.id}"]`) positionPanel(button, panel) } // Retrieves all currently open panels within a scope element export function getAllOpenPanels(scopeElement) { const buttons = scopeElement.getElementsByClassName('has-open-panel') const elements = [] for (const button of buttons) { if (button.getAttribute('data-controller') === 'csedl-dropdown') { const getOrSetPanelId = button.getAttribute('data-panel-id') const panel = document.getElementById(getOrSetPanelId) elements.push([button, panel]) } } debugLog(`FOUND ${elements.length} OPEN DROPDOWNS`) return (elements) } // Positions all provided panel-button pairs export function positionAllPanels(elements) { for (const el of elements) { positionPanel(el[0], el[1]) } } // Positions a single panel relative to its button export function positionPanel(button, panel) { let arrowElement = panel.querySelector('#arrow') panel.style.removeProperty('height') panel.style.removeProperty('left') panel.style.removeProperty('top') let placementAttr = panel.getAttribute('data-placement') let placement = (placementAttr ? placementAttr : 'bottom') debugLog(`placement: «${placement}»`) computePosition(button, panel, { middleware: [flip(), offset(6), shift({padding: 5}), arrow({element: arrowElement})], placement: placement }).then(({x, y, placement, middlewareData}) => { Object.assign(panel.style, { left: `${x}px`, top: `${y}px`, }); if (arrowElement) { // ARROW const {x: arrowX, y: arrowY} = middlewareData.arrow; const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right', }[placement.split('-')[0]]; Object.assign(arrowElement.style, { left: arrowX != null ? `${arrowX}px` : '', top: arrowY != null ? `${arrowY}px` : '', right: '', bottom: '', [staticSide]: '-4px', }); debugLog(`panel + arrow positioned`) } else { debugLog(`panel positioned`) } adjustToWindowBounds(button, panel) }); } // Adjusts panel to prevent overflow beyond window boundaries function adjustToWindowBounds(button, panel) { if (button.getAttribute('data-constrain-to-window-borders') === 'true') { const rect = panel.getBoundingClientRect(); if (rect.top < 0) { const newTop = parseFloat(panel.style.top) + Math.abs(rect.top) + 2 console.log('constrain top', 'actual top:', panel.style.top, 'absolute top:', rect.top, 'new top:', newTop) panel.style.top = `${newTop}px` } const rect2 = panel.getBoundingClientRect(); if (rect2.bottom > window.innerHeight) { console.log('constrain height') const height = window.innerHeight - rect2.top - 2 panel.style.height = `${height}px` } } }