@@ -8,6 +8,7 @@ import getMaskPaths from './mask-generator'
88
99export default function RoundDiv ( { style, children, ...props } ) {
1010 // welcome to react states hell
11+ const [ position , setPosition ] = useState ( [ 0 , 0 ] )
1112 const [ height , setHeight ] = useState ( 0 )
1213 const [ width , setWidth ] = useState ( 0 )
1314 const [ radius , setRadius ] = useState ( Array ( 4 ) . fill ( 0 ) )
@@ -16,55 +17,67 @@ export default function RoundDiv({style, children, ...props}) {
1617 const [ borderOpacity , setBorderOpacity ] = useState ( Array ( 4 ) . fill ( 1 ) )
1718 const [ borderWidth , setBorderWidth ] = useState ( Array ( 4 ) . fill ( 0 ) )
1819
19- const [ isFlex , setIsFlex ] = useState ( false )
20+ const [ path , setPath ] = useState ( 'Z' )
21+ const [ innerPath , setInnerPath ] = useState ( 'Z' )
22+ const [ maskPaths , setMaskPaths ] = useState ( 'Z' )
2023
2124 const div = useRef ( )
2225
23- useEffect ( ( ) => {
24- // attach shadow root to div
25- if ( ! div . current ?. shadowRoot )
26- div . current ?. attachShadow ( { mode : 'open' } )
27- } , [ ] )
28-
2926 const updateStatesWithArgs = useCallback ( ( ) => updateStates ( {
3027 div,
3128 style,
29+ setPosition,
3230 setHeight,
3331 setWidth,
3432 setRadius,
3533 setBorderColor,
3634 setBorderWidth,
37- setBorderOpacity,
38- setIsFlex
35+ setBorderOpacity
3936 } ) , [ style ] )
4037
41- useEffect ( updateStatesWithArgs , [ div , style , updateStatesWithArgs ] )
38+ useEffect ( updateStatesWithArgs , [ style , updateStatesWithArgs ] )
4239
4340 useEffect ( ( ) => {
4441 attachCSSWatcher ( ( ) => updateStatesWithArgs ( ) )
4542 } , [ updateStatesWithArgs ] )
4643
47- const path = generateSvgSquircle ( height , width , radius )
48- const innerPath = generateSvgSquircle (
49- height - ( borderWidth [ 0 ] + borderWidth [ 2 ] ) ,
50- width - ( borderWidth [ 1 ] + borderWidth [ 3 ] ) ,
51- radius . map ( ( val , i ) =>
52- Math . max ( 0 ,
53- val - Math . max ( borderWidth [ i ] , borderWidth [ i === 0 ? 3 : i - 1 ] )
44+ useEffect ( ( ) => {
45+ setPath ( generateSvgSquircle ( height , width , radius ) )
46+ setInnerPath ( generateSvgSquircle (
47+ height - ( borderWidth [ 0 ] + borderWidth [ 2 ] ) ,
48+ width - ( borderWidth [ 1 ] + borderWidth [ 3 ] ) ,
49+ radius . map ( ( val , i ) =>
50+ Math . max ( 0 ,
51+ val - Math . max ( borderWidth [ i ] , borderWidth [ i === 0 ? 3 : i - 1 ] )
52+ )
5453 )
55- )
56- ) . replace (
57- / ( \d + ( \. \d + ) ? ) , ( \d + ( \. \d + ) ? ) / g,
58- match => match . split ( ',' ) . map ( ( number , i ) =>
59- Number ( number ) + ( i === 0 ? borderWidth [ 3 ] : borderWidth [ 0 ] )
60- ) . join ( ',' )
61- )
54+ ) . replace (
55+ / ( \d + ( \. \d + ) ? ) , ( \d + ( \. \d + ) ? ) / g,
56+ match => match . split ( ',' ) . map ( ( number , i ) =>
57+ Number ( number ) + ( i === 0 ? borderWidth [ 3 ] : borderWidth [ 0 ] )
58+ ) . join ( ',' )
59+ ) )
60+
61+ // prevents unnecessary re-renders:
62+ // single value states (numbers and strings) prevent this out of the box,
63+ // complex states (objects, arrays, etc.) don't, so here it is manually for objects (non-nested)
64+ const lazySetObjectsState = ( setState , newState ) =>
65+ setState ( oldState => {
66+ if ( areEqualObjects ( oldState , newState ) ) return oldState
67+ else return newState
68+ } )
69+
70+ function areEqualObjects ( a , b ) {
71+ if ( Object . keys ( a ) . length !== Object . keys ( b ) . length ) return false
72+ for ( let key in a ) {
73+ if ( a [ key ] !== b [ key ] ) return false
74+ }
75+ return true
76+ }
6277
63- const maskPaths = getMaskPaths ( borderWidth , height , width )
78+ lazySetObjectsState ( setMaskPaths , getMaskPaths ( borderWidth , height , width , radius ) )
79+ } , [ height , width , radius , borderWidth ] )
6480
65- const svgTransform = isFlex
66- ? `translate(${ ( borderWidth [ 1 ] - borderWidth [ 3 ] ) / 2 } px,${ ( borderWidth [ 2 ] - borderWidth [ 0 ] ) / 2 } px)`
67- : `translate(${ ( borderWidth [ 1 ] - borderWidth [ 3 ] ) / 2 } px,-${ borderWidth [ 0 ] } px)`
6881
6982 const divStyle = {
7083 ...style ,
@@ -75,16 +88,17 @@ export default function RoundDiv({style, children, ...props}) {
7588 return < div { ...props } style = { divStyle } ref = { div } >
7689 < ShadowRoot >
7790 < svg viewBox = { `0 0 ${ width } ${ height } ` } style = { {
78- position : 'absolute' ,
91+ position : 'fixed' ,
92+ left : position [ 0 ] ,
93+ top : position [ 1 ] ,
7994 height,
80- width : 1 ,
95+ width,
8196 overflow : 'visible' ,
8297 zIndex : - 1 ,
83- transform : svgTransform
8498 } } xmlnsXlink = "http://www.w3.org/1999/xlink" preserveAspectRatio = { 'xMidYMid slice' } >
8599 < defs >
86100 < clipPath id = "inner" >
87- < path d = { `M0,0V${ height } H${ width } V0Z ` + innerPath } fillRule = { 'evenodd' } />
101+ < path d = { `M0,0V${ height } H${ width } V0H0Z ` + innerPath } fillRule = { 'evenodd' } />
88102 </ clipPath >
89103 </ defs >
90104 { Object . keys ( maskPaths ) . map ( ( key , i ) => {
@@ -100,7 +114,7 @@ export default function RoundDiv({style, children, ...props}) {
100114 fill = { borderColor [ i ] } opacity = { borderOpacity [ i ] } />
101115 } ) }
102116 </ svg >
103- < slot />
117+ < slot style = { { overflow : 'visible' } } />
104118 </ ShadowRoot >
105119 { children }
106120 </ div >
0 commit comments