Skip to content

Commit d6c6466

Browse files
committed
make all work
0 parents  commit d6c6466

File tree

9 files changed

+17486
-0
lines changed

9 files changed

+17486
-0
lines changed

.babelrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"presets": [
3+
"@babel/react",
4+
"@babel/env"
5+
]
6+
}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
dist/
3+
4+
*.tgz

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
src/
2+
tests/
3+
.babelrc
4+
!dist/

package-lock.json

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

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "react-round-div",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"test": "jest",
8+
"build": "babel src --out-dir dist --copy-files"
9+
},
10+
"author": "drinking-code",
11+
"license": "MIT",
12+
"dependencies": {
13+
"react": "^17.0.2"
14+
},
15+
"devDependencies": {
16+
"@babel/cli": "^7.13.14",
17+
"@babel/core": "^7.13.15",
18+
"@babel/preset-env": "^7.13.15",
19+
"@babel/preset-react": "^7.13.13",
20+
"babel-core": "^7.0.0-bridge.0",
21+
"babel-jest": "^26.6.3",
22+
"jest": "^26.6.3",
23+
"react-dom": "^17.0.2",
24+
"regenerator-runtime": "^0.13.7"
25+
}
26+
}

src/getMatchedCSSRules-polyfill.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// polyfill window.getMatchedCSSRules() in FireFox 6+
2+
/* eslint-disable */
3+
if ( typeof window.getMatchedCSSRules !== 'function' ) {
4+
var ELEMENT_RE = /[\w-]+/g,
5+
ID_RE = /#[\w-]+/g,
6+
CLASS_RE = /\.[\w-]+/g,
7+
ATTR_RE = /\[[^\]]+\]/g,
8+
// :not() pseudo-class does not add to specificity, but its content does as if it was outside it
9+
PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g,
10+
PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g;
11+
// convert an array-like object to array
12+
function toArray (list) {
13+
return [].slice.call(list);
14+
}
15+
16+
// handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same
17+
function getSheetRules (stylesheet) {
18+
var sheet_media = stylesheet.media && stylesheet.media.mediaText;
19+
// if this sheet is disabled skip it
20+
if ( stylesheet.disabled ) return [];
21+
// if this sheet's media is specified and doesn't match the viewport then skip it
22+
if ( sheet_media && sheet_media.length && ! window.matchMedia(sheet_media).matches ) return [];
23+
// get the style rules of this sheet
24+
return toArray(stylesheet.cssRules);
25+
}
26+
27+
function _find (string, re) {
28+
var matches = string.match(re);
29+
return re ? re.length : 0;
30+
}
31+
32+
// calculates the specificity of a given `selector`
33+
function calculateScore (selector) {
34+
var score = [0,0,0],
35+
parts = selector.split(' '),
36+
part, match;
37+
//TODO: clean the ':not' part since the last ELEMENT_RE will pick it up
38+
while ( part = parts.shift(), typeof part == 'string' ) {
39+
// find all pseudo-elements
40+
match = _find(part, PSEUDO_ELEMENTS_RE);
41+
score[2] = match;
42+
// and remove them
43+
match && (part = part.replace(PSEUDO_ELEMENTS_RE, ''));
44+
// find all pseudo-classes
45+
match = _find(part, PSEUDO_CLASSES_RE);
46+
score[1] = match;
47+
// and remove them
48+
match && (part = part.replace(PSEUDO_CLASSES_RE, ''));
49+
// find all attributes
50+
match = _find(part, ATTR_RE);
51+
score[1] += match;
52+
// and remove them
53+
match && (part = part.replace(ATTR_RE, ''));
54+
// find all IDs
55+
match = _find(part, ID_RE);
56+
score[0] = match;
57+
// and remove them
58+
match && (part = part.replace(ID_RE, ''));
59+
// find all classes
60+
match = _find(part, CLASS_RE);
61+
score[1] += match;
62+
// and remove them
63+
match && (part = part.replace(CLASS_RE, ''));
64+
// find all elements
65+
score[2] += _find(part, ELEMENT_RE);
66+
}
67+
return parseInt(score.join(''), 10);
68+
}
69+
70+
// returns the heights possible specificity score an element can get from a give rule's selectorText
71+
function getSpecificityScore (element, selector_text) {
72+
var selectors = selector_text.split(','),
73+
selector, score, result = 0;
74+
while ( selector = selectors.shift() ) {
75+
if ( element.mozMatchesSelector(selector) ) {
76+
score = calculateScore(selector);
77+
result = score > result ? score : result;
78+
}
79+
}
80+
return result;
81+
}
82+
83+
function sortBySpecificity (element, rules) {
84+
// comparing function that sorts CSSStyleRules according to specificity of their `selectorText`
85+
function compareSpecificity (a, b) {
86+
return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText);
87+
}
88+
89+
return rules.sort(compareSpecificity);
90+
}
91+
92+
//TODO: not supporting 2nd argument for selecting pseudo elements
93+
//TODO: not supporting 3rd argument for checking author style sheets only
94+
window.getMatchedCSSRules = function (element /*, pseudo, author_only*/) {
95+
var style_sheets, sheet, sheet_media,
96+
rules, rule,
97+
result = [];
98+
// get stylesheets and convert to a regular Array
99+
style_sheets = toArray(window.document.styleSheets);
100+
101+
// assuming the browser hands us stylesheets in order of appearance
102+
// we iterate them from the beginning to follow proper cascade order
103+
while ( sheet = style_sheets.shift() ) {
104+
// get the style rules of this sheet
105+
rules = getSheetRules(sheet);
106+
// loop the rules in order of appearance
107+
while ( rule = rules.shift() ) {
108+
// if this is an @import rule
109+
if ( rule.styleSheet ) {
110+
// insert the imported stylesheet's rules at the beginning of this stylesheet's rules
111+
rules = getSheetRules(rule.styleSheet).concat(rules);
112+
// and skip this rule
113+
continue;
114+
}
115+
// if there's no stylesheet attribute BUT there IS a media attribute it's a media rule
116+
else if ( rule.media ) {
117+
// insert the contained rules of this media rule to the beginning of this stylesheet's rules
118+
rules = getSheetRules(rule).concat(rules);
119+
// and skip it
120+
continue
121+
}
122+
//TODO: for now only polyfilling Gecko
123+
// check if this element matches this rule's selector
124+
if ( element.mozMatchesSelector(rule.selectorText) ) {
125+
// push the rule to the results set
126+
result.push(rule);
127+
}
128+
}
129+
}
130+
// sort according to specificity
131+
return sortBySpecificity(element, result);
132+
};
133+
}

src/main.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React, {useRef, useEffect, useState} from 'react';
2+
import getStyle from "./styles-extractor";
3+
import './getMatchedCSSRules-polyfill'
4+
5+
export default function RoundDiv({clip, style, children, ...props}) {
6+
const [height, setHeight] = useState(0)
7+
const [width, setWidth] = useState(0)
8+
const [offsetX, setOffsetX] = useState(0)
9+
const [offsetY, setOffsetY] = useState(0)
10+
const [radius, setRadius] = useState(0)
11+
const [background, setBackground] = useState('transparent')
12+
13+
const div = useRef()
14+
15+
useEffect(() => {
16+
// attach shadow root to div
17+
if (div.current?.shadowRoot) return
18+
const shadow = div.current?.attachShadow({mode: 'open'})
19+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
20+
svg.style.position = 'fixed';
21+
svg.style.left = '0px';
22+
svg.style.top = '0px';
23+
svg.style.height = '0px';
24+
svg.style.width = '0px';
25+
svg.style.zIndex = '-1';
26+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
27+
svg.appendChild(path)
28+
shadow.appendChild(svg)
29+
const content = document.createElement('slot')
30+
shadow.appendChild(content)
31+
}, [])
32+
33+
useEffect(() => {
34+
const boundingClientRect = div.current?.getBoundingClientRect()
35+
if (boundingClientRect) {
36+
setHeight(boundingClientRect.height)
37+
setWidth(boundingClientRect.width)
38+
setOffsetX(boundingClientRect.left)
39+
setOffsetY(boundingClientRect.top)
40+
}
41+
const divStyle = boundingClientRect ? window?.getComputedStyle(div.current) : null
42+
if (divStyle) {
43+
setRadius(Number(divStyle.borderRadius.replace('px', '')))
44+
setBackground(
45+
style?.background
46+
|| style?.backgroundColor
47+
|| getStyle('background', div.current)?.overwritten[1]?.value
48+
|| 'transparent'
49+
)
50+
}
51+
}, [div, clip, style])
52+
53+
useEffect(() => {
54+
const path = div.current?.shadowRoot?.querySelector('path')
55+
if (!path) return
56+
path.parentNode.style.width = width
57+
path.parentNode.style.height = height
58+
path.parentNode.style.top = offsetY
59+
path.parentNode.style.left = offsetX
60+
path.parentNode.removeAttributeNS('http://www.w3.org/2000/svg', 'viewBox')
61+
path.parentNode.setAttributeNS(
62+
'http://www.w3.org/2000/svg',
63+
'viewBox',
64+
`0 0 ${height} ${width}`
65+
)
66+
const newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
67+
newPath.setAttributeNS(
68+
'http://www.w3.org/2000/svg',
69+
'd',
70+
generateSvgSquircle(height, width, radius, clip)
71+
)
72+
newPath.setAttributeNS('http://www.w3.org/2000/svg', 'fill', background)
73+
// rerender
74+
path.parentNode.innerHTML = newPath.outerHTML
75+
}, [background, height, width, radius, clip, offsetX, offsetY])
76+
77+
const divStyle = {
78+
...style
79+
}
80+
81+
divStyle.background = 'transparent'
82+
divStyle.border = 'none'
83+
84+
return <div {...props} style={divStyle} ref={div}>
85+
{children}
86+
</div>
87+
}
88+
89+
function generateSvgSquircle(height, width, radius, clip) {
90+
const RADIUS_SCALE_FACTOR = 1.25
91+
height = Number(height);
92+
width = Number(width);
93+
radius = clip === false
94+
? Number(radius)
95+
: Math.min(Number(radius), height / 2 / RADIUS_SCALE_FACTOR, width / 2 / RADIUS_SCALE_FACTOR);
96+
if (isNaN(height)) throw new Error(`'height' must be a number`);
97+
if (isNaN(width)) throw new Error(`'width' must be a number`);
98+
if (isNaN(radius)) throw new Error(`'radius' must be a number`);
99+
const point = RADIUS_SCALE_FACTOR * radius,
100+
bezier = radius / 3;
101+
102+
return `M 0,${point} C 0,${bezier}, ${bezier},0, ${point},0
103+
L ${width - point},0 C ${width - bezier},0, ${width},${bezier}, ${width},${point}
104+
L ${width},${height - point} C ${width},${height - bezier}, ${width - bezier},${height}, ${width - point},${height}
105+
L ${point},${height} C ${bezier},${height}, 0,${height - bezier}, 0,${height - point}
106+
Z`;
107+
}

src/styles-extractor.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
function getInherited(element, s, value) {
2+
while (element.parentNode && window.getComputedStyle(element.parentNode)[s] === value) {
3+
element = element.parentNode;
4+
}
5+
6+
if (element) {
7+
return getStyle(s, element).current;
8+
} else {
9+
return null;
10+
}
11+
}
12+
13+
function isImportant(s, style, text) {
14+
return new RegExp(s.replace(/([A-Z])/g, '-$1').toLowerCase() + ':\\s+' + style + '\\s+!important').test(text)
15+
}
16+
17+
function getStyle(key, element) {
18+
const css = window.getMatchedCSSRules(element),
19+
style = window.getComputedStyle(element),
20+
value = style[key],
21+
styles = [],
22+
rules = []
23+
let inherited, currentRule;
24+
25+
if (value) {
26+
27+
if (css) {
28+
for (let i = 0; i < css.length; i++) {
29+
styles.push(css[i]);
30+
}
31+
}
32+
33+
styles.push({
34+
style: element.style,
35+
cssText: 'element.style {' + element.getAttribute('style') + ' }'
36+
});
37+
38+
for (let i = styles.length - 1; i >= 0; i--) {
39+
const def = styles[i],
40+
rule = {
41+
index: rules.length,
42+
style: key,
43+
value: styles[i].style[key],
44+
cssText: def.cssText
45+
};
46+
47+
if (rule.value === 'inherit' && !currentRule) {
48+
// eslint-disable-next-line no-cond-assign
49+
if (inherited = getInherited(element, key, value)) {
50+
rule.inheritedFrom = inherited;
51+
currentRule = rule;
52+
inherited = undefined;
53+
} else {
54+
rules.push(rule);
55+
}
56+
} else if (rule.value === 'inherit' && currentRule && isImportant(key, value, def.cssText)) {
57+
// eslint-disable-next-line no-cond-assign
58+
if (inherited = getInherited(element, key, def)) {
59+
rule.inheritedFrom = inherited;
60+
rules.splice(currentRule.index, 0, currentRule);
61+
currentRule = rule;
62+
inherited = undefined;
63+
} else {
64+
rules.push(rule);
65+
}
66+
} else if (rule.value === value && !currentRule) {
67+
currentRule = rule;
68+
} else if (rule.value === value && currentRule && isImportant(key, value, def.cssText)) {
69+
rules.splice(currentRule.index, 0, currentRule);
70+
currentRule = rule;
71+
} else if (rule.value.length) {
72+
rules.push(rule)
73+
}
74+
}
75+
76+
return {
77+
current: currentRule,
78+
overwritten: rules
79+
};
80+
81+
} else {
82+
return false;
83+
}
84+
}
85+
86+
export default getStyle

tests/round-div.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import RoundDiv from '../src/main.js';
4+
5+
it("renders without crashing", () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<RoundDiv/>, div);
8+
ReactDOM.unmountComponentAtNode(div);
9+
})

0 commit comments

Comments
 (0)