|
1 | | -import React, { useEffect, useRef, useState } from 'react'; |
| 1 | +import React, { useEffect, useRef, useState, useCallback } from 'react'; |
2 | 2 | import ReactDOM from 'react-dom'; |
3 | 3 | import { throttle } from '../../utils/utils'; |
4 | 4 |
|
5 | | -export default function ColorPickerPortal({ target, scrollContainerId, throttleDelay, children }) { |
6 | | - |
| 5 | +export default function ColorPickerPortal({ target, scrollContainerId, throttleDelay = 16, children }) { |
7 | 6 | const containerRef = useRef(null); |
8 | | - const [position, setPosition] = useState({ top: 0, left: 0 }); |
| 7 | + const [isPositioned, setIsPositioned] = useState(false); |
| 8 | + const [position, setPosition] = useState(() => { |
| 9 | + return { top: '-9999px', left: '-9999px', visibility: 'hidden' }; |
| 10 | + }); |
9 | 11 |
|
10 | | - useEffect(() => { |
| 12 | + const updatePosition = useCallback(() => { |
11 | 13 | if (!target || !containerRef.current) return; |
12 | | - const updatePosition = () => { |
13 | | - const targetRect = target.getBoundingClientRect(); |
14 | | - const { top: spaceAbove, left } = targetRect; |
15 | | - const portalRectHeight = containerRef.current.clientHeight; |
16 | | - if (spaceAbove < portalRectHeight) { |
17 | | - setPosition({ top: `calc(${spaceAbove}px + 2.375rem)`, left: left + 1 + 'px' }); |
18 | | - return; |
19 | | - } |
20 | | - setPosition({ top: `calc(${spaceAbove - portalRectHeight}px)`, left: left + 1 + 'px' }); |
| 14 | + |
| 15 | + const targetRect = target.getBoundingClientRect(); |
| 16 | + const { top: spaceAbove, left } = targetRect; |
| 17 | + const portalRectHeight = containerRef.current.clientHeight; |
| 18 | + |
| 19 | + const newPosition = { |
| 20 | + left: `${left + 1}px`, |
| 21 | + visibility: 'visible', |
21 | 22 | }; |
22 | | - updatePosition(); |
| 23 | + |
| 24 | + if (spaceAbove < portalRectHeight) { |
| 25 | + newPosition.top = `calc(${spaceAbove}px + 2.375rem)`; |
| 26 | + } else { |
| 27 | + newPosition.top = `calc(${spaceAbove - portalRectHeight}px)`; |
| 28 | + } |
| 29 | + |
| 30 | + setPosition(prev => ({ |
| 31 | + ...prev, |
| 32 | + ...newPosition |
| 33 | + })); |
| 34 | + |
| 35 | + if (!isPositioned) { |
| 36 | + setIsPositioned(true); |
| 37 | + } |
| 38 | + }, [target, isPositioned]); |
| 39 | + |
| 40 | + useEffect(() => { |
| 41 | + if (!target) return; |
| 42 | + |
| 43 | + const initialPosition = () => { |
| 44 | + updatePosition(); |
| 45 | + requestAnimationFrame(updatePosition); |
| 46 | + }; |
| 47 | + const timer = setTimeout(initialPosition, 0); |
| 48 | + |
23 | 49 | const throttledUpdatePosition = throttle(updatePosition, throttleDelay); |
24 | 50 | const scrollContainer = scrollContainerId ? document.getElementById(scrollContainerId) : null; |
25 | 51 |
|
26 | | - scrollContainer && scrollContainer.addEventListener('scroll', throttledUpdatePosition); |
27 | | - window.addEventListener('resize', updatePosition); |
| 52 | + scrollContainer?.addEventListener('scroll', throttledUpdatePosition, { passive: true }); |
| 53 | + window.addEventListener('resize', throttledUpdatePosition, { passive: true }); |
28 | 54 | return () => { |
29 | | - window.removeEventListener('resize', updatePosition); |
30 | | - scrollContainer && scrollContainer.removeEventListener('scroll', throttledUpdatePosition); |
| 55 | + clearTimeout(timer); |
| 56 | + window.removeEventListener('resize', throttledUpdatePosition); |
| 57 | + scrollContainer?.removeEventListener('scroll', throttledUpdatePosition); |
31 | 58 | }; |
32 | | - }, [target, containerRef, scrollContainerId, throttleDelay]); |
| 59 | + }, [target, scrollContainerId, throttleDelay, updatePosition]); |
| 60 | + |
| 61 | + if (!target) return null; |
33 | 62 |
|
34 | 63 | return ReactDOM.createPortal( |
35 | 64 | <div |
36 | 65 | className='dtable-color-picker-portal' |
37 | 66 | style={{ |
38 | | - position: 'absolute', |
39 | | - zIndex: '10', |
| 67 | + position: 'fixed', |
| 68 | + zIndex: '1049', |
40 | 69 | left: position.left, |
41 | 70 | top: position.top, |
42 | 71 | width: '240px', |
43 | | - height: '370px' |
| 72 | + height: '370px', |
| 73 | + visibility: position.visibility, |
| 74 | + opacity: isPositioned ? 1 : 0, |
| 75 | + transition: 'opacity 0.15s ease-in-out', |
44 | 76 | }} |
45 | 77 | ref={containerRef} |
46 | 78 | > |
|
0 commit comments