Skip to content

Commit 03882bf

Browse files
committed
fix bug with positioning inside shadow dom. improve minified size
1 parent 4c487b8 commit 03882bf

12 files changed

Lines changed: 2874 additions & 15470 deletions

.babelrc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
{
22
"presets": [
3-
"@babel/react",
4-
"@babel/env"
3+
"@babel/react"
54
],
65
"plugins": [
7-
"@babel/plugin-proposal-class-properties"
6+
"babel-plugin-remove-template-literals-whitespace"
87
],
98
"comments": false
109
}

package-lock.json

Lines changed: 2673 additions & 15258 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
{
22
"name": "react-round-div",
3-
"version": "1.2.2",
3+
"version": "1.3.0",
44
"description": "Make your rounded corners look phenomenal with g2 continuity.",
55
"main": "dist/main.js",
66
"scripts": {
7-
"test": "jest",
87
"build": "babel src --out-dir dist --copy-files"
98
},
109
"author": "drinking-code",
@@ -34,13 +33,7 @@
3433
"devDependencies": {
3534
"@babel/cli": "^7.13.14",
3635
"@babel/core": "^7.13.15",
37-
"@babel/plugin-proposal-class-properties": "^7.13.0",
38-
"@babel/preset-env": "^7.13.15",
3936
"@babel/preset-react": "^7.13.13",
40-
"babel-core": "^7.0.0-bridge.0",
41-
"babel-jest": "^26.6.3",
42-
"babel-preset-minify": "^0.5.1",
43-
"jest": "^26.6.3",
44-
"regenerator-runtime": "^0.13.7"
37+
"babel-plugin-remove-template-literals-whitespace": "^1.0.4"
4538
}
4639
}

src/external/bobspace:html-colors.js

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.js

Lines changed: 67 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import React, {useRef, useEffect, useState, useCallback} from 'react'
1+
import React, {useRef, useEffect, useState} from 'react'
22

33
const ShadowRoot = typeof window !== 'undefined' ? require('react-shadow-root').default : () => 0
44

55
import generateSvgSquircle from './generator'
66
import getMaskPaths from './mask-generator'
7-
import attachCSSWatcher from './styleSheetWatcher'
7+
import {attachCSSWatcher, detachCSSWatcher} from './styleSheetWatcher'
88
import updateStates from './updateStates'
99

1010
import {lazySetObjectsState} from './utils/react-utils'
@@ -16,7 +16,8 @@ import {camelise} from './utils/string-manipulation'
1616
* */
1717
export default function RoundDiv({style, children, dontConvertShadow, ...props}) {
1818
// welcome to react states hell
19-
const [position, setPosition] = useState([0, 0])
19+
// const [position, setPosition] = useState([0, 0])
20+
const [padding, setPadding] = useState(Array(4).fill(0))
2021
const [height, setHeight] = useState(0)
2122
const [width, setWidth] = useState(0)
2223
const [radius, setRadius] = useState(Array(4).fill(0))
@@ -33,11 +34,12 @@ export default function RoundDiv({style, children, dontConvertShadow, ...props})
3334

3435
const div = useRef()
3536

36-
const updateStatesWithArgs = useCallback(() => {
37+
const updateStatesWithArgs = () => {
3738
updateStates({
3839
div,
3940
style,
40-
setPosition,
41+
// setPosition,
42+
setPadding,
4143
setHeight,
4244
setWidth,
4345
setRadius,
@@ -47,18 +49,20 @@ export default function RoundDiv({style, children, dontConvertShadow, ...props})
4749
setBorderOpacity,
4850
setShadows
4951
})
50-
}, [style])
51-
52-
useEffect(updateStatesWithArgs, [style, updateStatesWithArgs])
52+
}
5353

5454
useEffect(() => {
55-
attachCSSWatcher(() => updateStatesWithArgs()) // todo: make this a react hook
56-
}, [updateStatesWithArgs])
55+
const watcherId = attachCSSWatcher(updateStatesWithArgs, div.current) // todo: make this a react hook
56+
updateStatesWithArgs()
57+
return () => {
58+
detachCSSWatcher(watcherId)
59+
}
60+
}, [])
5761

5862
const svgRef = useRef();
5963

6064
useEffect(() => {
61-
setPath(generateSvgSquircle(height, width, radius))
65+
lazySetObjectsState(setMaskPaths, getMaskPaths(borderWidth, height, width, radius))
6266
setInnerPath(generateSvgSquircle(
6367
height - (borderWidth[0] + borderWidth[2]),
6468
width - (borderWidth[1] + borderWidth[3]),
@@ -73,18 +77,17 @@ export default function RoundDiv({style, children, dontConvertShadow, ...props})
7377
Number(number) + (i === 0 ? borderWidth[3] : borderWidth[0])
7478
).join(',')
7579
))
80+
setPath(generateSvgSquircle(height, width, radius))
81+
}, [height, width, radius, borderWidth])
7682

77-
lazySetObjectsState(setMaskPaths, getMaskPaths(borderWidth, height, width, radius))
78-
79-
// patch for webkit's svg bug
80-
if (svgRef.current)
83+
// patch for webkit's svg bug
84+
if (svgRef.current)
85+
setTimeout(() => {
86+
svgRef.current.style.position = ''
8187
setTimeout(() => {
82-
svgRef.current.style.position = ''
83-
setTimeout(() => {
84-
svgRef.current.style.position = 'fixed'
85-
}, 0)
88+
svgRef.current.style.position = 'fixed'
8689
}, 0)
87-
}, [height, width, radius, borderWidth])
90+
}, 0)
8891

8992
const pathIsEmpty = (path.startsWith('Z') || path === '')
9093
const divStyle = {
@@ -100,52 +103,60 @@ export default function RoundDiv({style, children, dontConvertShadow, ...props})
100103
filter: dontConvertShadow ? '' : shadows[0].map(shadowData => `drop-shadow(${shadowData})`).join(' '),
101104
})
102105
}
106+
103107
const shapeComponentStyles = {
104108
height,
105109
width,
106110
position: 'fixed',
107-
left: Math.round(position[0]),
108-
top: Math.round(position[1]),
111+
left: 0,
112+
top: 0,
113+
transform: `translate(-${padding[3]}px, -${padding[0]}px)`,
109114
zIndex: -1,
110115
}
111116

112117
return <div {...props} style={divStyle} ref={div}>
113118
{pathIsEmpty ? null : <ShadowRoot>
114119
<div style={{
115-
...shapeComponentStyles,
116-
clipPath: `path("${path}")`,
117-
// inset shadow only
118-
boxShadow: shadows[1].join(','),
119-
...(Object.fromEntries(Object.keys(background).map(key => {
120-
return [camelise(key === 'null' ? 'background' : ('background-' + key)), background[key]]
121-
}))),
122-
}}/>
123-
<svg viewBox={`0 0 ${width} ${height}`} style={shapeComponentStyles} preserveAspectRatio={'xMidYMid slice'}
124-
ref={svgRef}>
125-
<defs>
126-
<clipPath id="inner" clipPathUnits="userSpaceOnUse">
127-
<path d={`M0,0V${height}H${width}V0Z` + innerPath} fillRule={'evenodd'}/>
128-
</clipPath>
129-
<clipPath id="outer" clipPathUnits="userSpaceOnUse">
130-
<path d={path} fillRule={'evenodd'}/>
131-
</clipPath>
132-
</defs>
133-
<g clipPath={'url(#outer)'}>
134-
{Object.keys(maskPaths).map((key, i) => {
135-
if (borderColor[i] === borderColor[i - 1]) return ''
136-
137-
let path = maskPaths[key]
138-
while (borderColor[i] === borderColor[i + 1]) {
139-
path += maskPaths[Object.keys(maskPaths)[i + 1]]
140-
i++
141-
}
142-
143-
return <path d={path} clipPath={'url(#inner)'} key={key}
144-
fill={borderColor[i]} opacity={borderOpacity[i]}/>
145-
})}
146-
</g>
147-
</svg>
148-
<slot style={{overflow: 'visible'}}/>
120+
transform: 'scale(1)'
121+
}}>
122+
<div style={{
123+
...shapeComponentStyles,
124+
clipPath: `path("${path}")`,
125+
// inset shadow only
126+
boxShadow: shadows[1].join(','),
127+
borderRadius: radius.map(n => (n - 1) + 'px').join(' '),
128+
...(Object.fromEntries(Object.keys(background).map(key => {
129+
return [camelise(key === 'null' ? 'background' : ('background-' + key)), background[key]]
130+
}))),
131+
}}/>
132+
<svg viewBox={`0 0 ${width} ${height}`} style={shapeComponentStyles}
133+
preserveAspectRatio={'xMidYMid slice'}
134+
ref={svgRef}>
135+
<defs>
136+
<clipPath id="inner" clipPathUnits="userSpaceOnUse">
137+
<path d={`M0,0V${height}H${width}V0Z` + innerPath} fillRule={'evenodd'}/>
138+
</clipPath>
139+
<clipPath id="outer" clipPathUnits="userSpaceOnUse">
140+
<path d={path} fillRule={'evenodd'}/>
141+
</clipPath>
142+
</defs>
143+
<g clipPath={'url(#outer)'}>
144+
{Object.keys(maskPaths).map((key, i) => {
145+
if (borderColor[i] === borderColor[i - 1]) return ''
146+
147+
let path = maskPaths[key]
148+
while (borderColor[i] === borderColor[i + 1]) {
149+
path += maskPaths[Object.keys(maskPaths)[i + 1]]
150+
i++
151+
}
152+
153+
return <path d={path} clipPath={'url(#inner)'} key={key}
154+
fill={borderColor[i]} opacity={borderOpacity[i]}/>
155+
})}
156+
</g>
157+
</svg>
158+
<slot style={{overflow: 'visible'}}/>
159+
</div>
149160
</ShadowRoot>}
150161
{children}
151162
</div>

src/styleSheetWatcher.js

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
1-
const CSSChangeEvent = typeof CustomEvent !== 'undefined' ? new CustomEvent('css-change') : 'css-change';
1+
import {iterateAllCssRules} from './utils/css-utils'
2+
import {areEqualObjects} from './utils/assert'
23

3-
export default function attachCSSWatcher(callback) {
4-
CSSWatcher.addEventListener('css-change', () => callback())
5-
}
4+
let id = 0
5+
const listeners = []
66

7-
const CSSWatcher = new EventTarget()
7+
export function attachCSSWatcher(callback, element) {
8+
listeners[id] = watchCSS(callback, element)
9+
return id++
10+
}
811

9-
if (typeof document !== 'undefined') {
10-
if (document.readyState === 'complete')
11-
CSSWatcher.dispatchEvent(CSSChangeEvent)
12-
else
13-
window.addEventListener('load', () =>
14-
CSSWatcher.dispatchEvent(CSSChangeEvent)
15-
)
12+
export function detachCSSWatcher(id) {
13+
listeners[id]()
14+
delete listeners[id]
1615
}
1716

18-
;(function watchCSS() {
17+
function watchCSS(callback, element) {
1918
let CSS = getCSSText()
20-
setInterval(() => {
19+
let style = {...element.style}
20+
const interval = setInterval(() => {
2121
const newCSS = getCSSText()
22-
if (CSS === newCSS) return
22+
const newStyle = {...element.style}
23+
if (CSS === newCSS && areEqualObjects(style, newStyle)) return
2324
CSS = newCSS
24-
CSSWatcher.dispatchEvent(CSSChangeEvent)
25-
}, 30)
25+
style = newStyle
26+
callback()
27+
}, 50)
28+
29+
let timeout
30+
const forceUpdate = () => {
31+
clearTimeout(timeout)
32+
timeout = setTimeout(() => {
33+
CSS = getCSSText()
34+
style = {...element.style}
35+
callback()
36+
}, 0)
37+
}
2638

27-
if (typeof window === 'undefined') return
28-
window.addEventListener('resize', () => {
29-
CSS = getCSSText()
30-
CSSWatcher.dispatchEvent(CSSChangeEvent)
31-
})
32-
})()
39+
window.addEventListener('resize', forceUpdate)
40+
window.addEventListener('scroll', forceUpdate, {passive: true})
41+
42+
return () => {
43+
clearInterval(interval)
44+
window.removeEventListener('resize', forceUpdate)
45+
window.removeEventListener('scroll', forceUpdate, {passive: true})
46+
}
47+
}
3348

3449
function getCSSText() {
35-
if (typeof document === 'undefined') return ''
3650
let CSS = ''
37-
for (let i = 0; i < document.styleSheets.length; i++) {
38-
const sheet = document.styleSheets[i]
39-
for (let j = 0; j < sheet.rules.length; j++) {
40-
CSS += sheet.rules[j].cssText
41-
}
42-
}
51+
iterateAllCssRules(rule =>
52+
CSS += rule.cssText
53+
)
4354
return CSS
4455
}

src/updateStates.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import ReactDOM from 'react-dom'
1010
import {lazySetArrayState, lazySetObjectsState} from './utils/react-utils'
1111

1212
export default function updateStates(args) {
13-
const {div, setPosition, setHeight, setWidth} = args
13+
const {div, /*setPosition,*/ setHeight, setWidth} = args
1414
const boundingClientRect = div.current?.getBoundingClientRect()
1515
let height, width;
1616
if (boundingClientRect) {
17-
lazySetArrayState(setPosition, [boundingClientRect.x, boundingClientRect.y])
17+
// lazySetArrayState(setPosition, [boundingClientRect.x, boundingClientRect.y])
1818
height = boundingClientRect.height
1919
width = boundingClientRect.width
2020
setHeight(height)
@@ -26,19 +26,11 @@ export default function updateStates(args) {
2626
return styles[n]?.declaration?.value
2727
}
2828

29-
const getBorderStyles = (key, n) => [
30-
getNthStyle('border-top-' + key, n, 'border'),
31-
getNthStyle('border-right-' + key, n, 'border'),
32-
getNthStyle('border-bottom-' + key, n, 'border'),
33-
getNthStyle('border-left-' + key, n, 'border'),
34-
]
29+
const getBorderStyles = (key, n) => ['top', 'right', 'bottom', 'left']
30+
.map(s => getNthStyle('border-' + s + '-' + key, n, 'border'))
3531

36-
const getBorderRadii = (n) => [
37-
getNthStyle('border-top-right-radius', n, 'border-radius'),
38-
getNthStyle('border-top-left-radius', n, 'border-radius'),
39-
getNthStyle('border-bottom-right-radius', n, 'border-radius'),
40-
getNthStyle('border-bottom-left-radius', n, 'border-radius'),
41-
]
32+
const getBorderRadii = (n) => ['top-right', 'top-left', 'bottom-right', 'bottom-left']
33+
.map(s => getNthStyle('border-' + s + '-radius', n, 'border-radius'))
4234

4335
const states = args
4436
const lazySetRadius = newState => lazySetArrayState(states.setRadius, newState),
@@ -51,6 +43,12 @@ export default function updateStates(args) {
5143
const divStyle = div.current ? window?.getComputedStyle(div.current) : null
5244
if (!divStyle) return
5345
ReactDOM.unstable_batchedUpdates(() => {
46+
states.setPadding(
47+
['top', 'right', 'bottom', 'left']
48+
.map(s => getNthStyle('padding-' + s, 0, 'padding'))
49+
.map(n => toNumber(n, div.current))
50+
)
51+
5452
lazySetRadius(
5553
getBorderRadii(0)
5654
.map(s => Math.min(

src/utils/assert.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function areEqualObjects(a, b) {
2+
if (Object.keys(a).length !== Object.keys(b).length) return false
3+
for (let key in a) {
4+
if (a[key] !== b[key]) return false
5+
}
6+
return true
7+
}

0 commit comments

Comments
 (0)