Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import { dropdownEl, inputEl, isDynamicSelect } from '../../utils/dynamic-select-dom.js'
import { centerDropdownPosition, shouldCenterDropdown } from '../centers-dropdown-to-screen-on-mobile/dropdown-mobile-centering.js'
/** @import {DynamicSelect} from '../../utils/dynamic-select-dom' */
/** @type {WeakMap<DynamicSelect, DropdownPositionUpdater>} */
const dataLoaderData = new WeakMap()
const intersectionObserver = new IntersectionObserver(records => {
const selects = new Set(Iterator.from(records)
.filter(record => !record.isIntersecting)
.map(record => record.target)
.filter(isDynamicSelect))
selects.forEach(select => {
select.open = false
dropdownPositionUpdaterOf(select).stopAnchoringToSelect()
})
})
/**
* @param {DynamicSelect} element - target element
* @returns {DropdownPositionUpdater} dataloader of element
*/
export function dropdownPositionUpdaterOf (element) {
let dataLoader = dataLoaderData.get(element)
if (!dataLoader) {
dataLoader = createDropdownPositionUpdaterFor(new WeakRef(element))
dataLoaderData.set(element, dataLoader)
}
return dataLoader
}
/**
* Creates a dataloader for an element
* @param {WeakRef<DynamicSelect>} elementRef - weak reference of element. We do not want to have any strong reference chain pointing to
* globally allocated `dataLoaderData`, effectively creating a memory leak
* @returns {DropdownPositionUpdater} - created dataloader for element
*/
function createDropdownPositionUpdaterFor (elementRef) {
const getElement = () => {
const element = elementRef.deref()
if (!element) {
removeListenersForPotentialPositionChange()
throw Error('element no longer exists')
}
return element
}
const potentialPositionChangeCallback = () => {
const element = getElement()
if (!element.open) {
api.stopAnchoringToSelect()
return
}
updateDropdownPosition(element)
}
const addScrollListener = () => document.defaultView?.addEventListener('scroll', potentialPositionChangeCallback, true)
const removeScrollListener = () => document.defaultView?.removeEventListener('scroll', potentialPositionChangeCallback, true)
const addResizeListener = () => document.defaultView?.addEventListener('resize', potentialPositionChangeCallback)
const removeResizeListener = () => document.defaultView?.removeEventListener('resize', potentialPositionChangeCallback)
const addListenersForPotentialPositionChange = () => {
addScrollListener()
addResizeListener()
}
const removeListenersForPotentialPositionChange = () => {
removeScrollListener()
removeResizeListener()
}
/** @type {DropdownPositionUpdater} */
const api = {
startAnchoringToSelect: () => {
const element = getElement()
updateDropdownPosition(element)
addListenersForPotentialPositionChange()
intersectionObserver.observe(element)
},
stopAnchoringToSelect () {
const element = getElement()
intersectionObserver.unobserve(element)
removeListenersForPotentialPositionChange()
},
}
return api
}
/**
* Update dropdown content based on the content in dynamic select in light DOM
* @param {DynamicSelect} dynamicSelect - web component element reference
*/
export function updateDropdownPosition (dynamicSelect) {
if (shouldCenterDropdown()) {
centerDropdownPosition(dynamicSelect)
return
}
const dropdown = dropdownEl(dynamicSelect)
const input = inputEl(dynamicSelect)
const clientRect = input.getBoundingClientRect()
dropdown.style.minWidth = `${clientRect.width}px`
const viewportRect = getViewportRect()
const isTopDirection = clientRect.bottom + dropdown.clientHeight > viewportRect.height
const isLeftDirection = clientRect.left + dropdown.clientWidth > viewportRect.width
const xPosition = isTopDirection ? clientRect.top : clientRect.bottom
const yPosition = isLeftDirection ? Math.min(viewportRect.width, clientRect.right) : Math.max(0, clientRect.left)
dropdown.classList.toggle('top-direction', isTopDirection)
dropdown.classList.toggle('left-direction', isLeftDirection)
dropdown.style.marginTop = `${xPosition}px`
dropdown.style.marginLeft = `${yPosition}px`
}
/**
* @returns {DOMRectReadOnly} viewport rect
*/
function getViewportRect () {
const { visualViewport } = window
if (!visualViewport) {
return new DOMRectReadOnly(0, 0, window.innerWidth, window.innerHeight)
}
const { offsetLeft, offsetTop, width, height } = visualViewport
return new DOMRectReadOnly(offsetLeft, offsetTop, width, height)
}
/**
* @typedef {object} DropdownPositionUpdater
* @property {() => void} startAnchoringToSelect - starts anchoring to select until select is not visible,
* at that point it closes the dropdown
* @property {() => void} stopAnchoringToSelect - stop anchoring to select
*/
|