|
67 | 67 | // Private |
68 | 68 | let _rootEl: HTMLElement; |
69 | 69 | let _popoverEl: HTMLElement; |
| 70 | + const _needsPositionPolyfill = typeof document !== "undefined" && !("anchorName" in document.documentElement.style); |
| 71 | + let _positionRafId: number | null = null; |
70 | 72 |
|
71 | 73 | // Reactive |
72 | 74 | let _targetEl: HTMLElement; |
|
119 | 121 | showDeprecationWarnings(); |
120 | 122 | addGlobalCloseListener(); |
121 | 123 | window.addEventListener("resize", updateAutoPosition); |
122 | | - mountAnchorPositioningPolyfill(); |
123 | 124 | }); |
124 | 125 |
|
125 | 126 | onDestroy(() => { |
| 127 | + stopManualPositioning(); |
126 | 128 | window.removeEventListener("resize", updateAutoPosition); |
127 | 129 | // true was passed when the listener was added, so it's necesary to be passed here as well |
128 | 130 | window.removeEventListener("popstate", handleUrlChange, true); |
129 | 131 | }); |
130 | 132 |
|
131 | 133 | // Functions |
132 | 134 |
|
133 | | - function mountAnchorPositioningPolyfill() { |
134 | | - if (window.ANCHOR_POSITIONING_POLYFILL) { |
135 | | - const oldRoots = window.ANCHOR_POSITIONING_POLYFILL?.roots || []; |
136 | | - window.ANCHOR_POSITIONING_POLYFILL({ |
137 | | - roots: [...oldRoots, _popoverEl.shadowRoot], |
138 | | - }); |
| 135 | + function updatePopoverPosition() { |
| 136 | + if (!_isOpen || !_targetEl || !_popoverEl) return; |
| 137 | +
|
| 138 | + const targetRect = _targetEl.getBoundingClientRect(); |
| 139 | + const xOffset = hoffset ? parseFloat(hoffset) : 0; |
| 140 | + const yOffset = voffset ? parseFloat(voffset) : 3; |
| 141 | +
|
| 142 | + // Recalculate auto position based on current viewport space |
| 143 | + if (position === "auto") { |
| 144 | + const popoverRect = _popoverEl.getBoundingClientRect(); |
| 145 | + const spaceAbove = targetRect.top; |
| 146 | + const spaceBelow = window.innerHeight - targetRect.bottom; |
| 147 | +
|
| 148 | + _autoPosition = |
| 149 | + spaceBelow < popoverRect.height && spaceAbove > spaceBelow |
| 150 | + ? "above" |
| 151 | + : "below"; |
| 152 | + } |
| 153 | +
|
| 154 | + const isAbove = position === "above" || (position === "auto" && _autoPosition === "above"); |
| 155 | +
|
| 156 | + if (isAbove) { |
| 157 | + _popoverEl.style.top = `${targetRect.top - yOffset}px`; |
| 158 | + _popoverEl.style.left = `${targetRect.left + xOffset}px`; |
| 159 | + _popoverEl.style.transform = "translateY(-100%)"; |
| 160 | + } else { |
| 161 | + _popoverEl.style.top = `${targetRect.bottom + yOffset}px`; |
| 162 | + _popoverEl.style.left = `${targetRect.left + xOffset}px`; |
| 163 | + _popoverEl.style.transform = ""; |
| 164 | + } |
| 165 | + } |
| 166 | +
|
| 167 | + function startManualPositioning() { |
| 168 | + if (!_needsPositionPolyfill) return; |
| 169 | + const loop = () => { |
| 170 | + updatePopoverPosition(); |
| 171 | + _positionRafId = requestAnimationFrame(loop); |
| 172 | + }; |
| 173 | + _positionRafId = requestAnimationFrame(loop); |
| 174 | + } |
| 175 | +
|
| 176 | + function stopManualPositioning() { |
| 177 | + if (_positionRafId !== null) { |
| 178 | + cancelAnimationFrame(_positionRafId); |
| 179 | + _positionRafId = null; |
139 | 180 | } |
140 | 181 | } |
141 | 182 |
|
|
215 | 256 | if (_isOpen) { |
216 | 257 | dispatch(_rootEl, "_open"); |
217 | 258 | requestAnimationFrame(updateAutoPosition); // same vs await tick(), make sure popover element is fully rendered before we measure its dimension |
| 259 | + startManualPositioning(); |
218 | 260 | } else { |
| 261 | + stopManualPositioning(); |
219 | 262 | _targetEl.focus(); |
220 | 263 | dispatch(_rootEl, "_close"); |
221 | 264 | } |
|
359 | 402 | filter: var(--goa-popover-shadow, none); |
360 | 403 | border: var(--goa-popover-border, none); |
361 | 404 | margin: 0; |
362 | | -
|
363 | | - position-anchor: --goa-popover-target; |
364 | | - inset-block-start: anchor(bottom); |
365 | | - inset-inline-start: anchor(left); |
366 | | - --popover-translate-x: var(--offset-left, 0); |
367 | | - --popover-translate-y: var(--offset-top, 3px); |
368 | | - translate: var(--popover-translate-x) var(--popover-translate-y); |
| 405 | + inset: auto; |
369 | 406 | } |
370 | 407 |
|
371 | | - .popover-content.position-above { |
372 | | - inset-block-start: anchor(top); |
373 | | - --popover-translate-y: calc(-100% - var(--offset-bottom, 3px)); |
374 | | - position-try-fallbacks: none; |
375 | | - } |
| 408 | + @supports (anchor-name: --a) { |
| 409 | + .popover-content { |
| 410 | + position-anchor: --goa-popover-target; |
| 411 | + inset-block-start: anchor(bottom); |
| 412 | + inset-inline-start: anchor(left); |
| 413 | + --popover-translate-x: var(--offset-left, 0); |
| 414 | + --popover-translate-y: var(--offset-top, 3px); |
| 415 | + translate: var(--popover-translate-x) var(--popover-translate-y); |
| 416 | + } |
| 417 | +
|
| 418 | + .popover-content.position-above { |
| 419 | + inset-block-start: anchor(top); |
| 420 | + --popover-translate-y: calc(-100% - var(--offset-bottom, 3px)); |
| 421 | + position-try-fallbacks: none; |
| 422 | + } |
376 | 423 |
|
377 | | - .popover-content.position-below { |
378 | | - inset-block-start: anchor(bottom); |
379 | | - --popover-translate-y: var(--offset-top, 3px); |
| 424 | + .popover-content.position-below { |
| 425 | + inset-block-start: anchor(bottom); |
| 426 | + --popover-translate-y: var(--offset-top, 3px); |
| 427 | + } |
380 | 428 | } |
381 | 429 |
|
382 | 430 | :global(::slotted(ul)) { |
|
0 commit comments