diff --git a/.babelrc b/.babelrc index 90d355d..f9e2a23 100644 --- a/.babelrc +++ b/.babelrc @@ -1,8 +1,7 @@ { "presets": [ "@babel/react", - "@babel/env", - "minify" + "@babel/env" ], "plugins": [ "@babel/plugin-proposal-class-properties" diff --git a/README.md b/README.md index 2445258..9334147 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ const App = () => { export default App; ``` -`react-round-div` will clip the `border-radius` of it is too large to prevent artifacts from appearing. You can turn this off by passing `clip={false}`. ## Caveats -This package is still in the starting blocks, so for now only single colored backgrounds and solid, uniform borders are supported. -There is support to come for gradients and image backgrounds, gradient borders, and perhaps proper `backdrop-filter` support. +This package is still in the starting blocks, so for now borders are only supported in the `solid` style and some transitions on the div may not work properly. + +Moreover, children inside `RoundDiv` get cut off when are placed (partly) outside the div due to `clip-path` being used to make the smooth corners. There will probably an option in later versions to use a pseudo-element for the shape, so that children can be rendered outside. diff --git a/img/compare.svg b/img/compare.svg index 6833db0..a187b5f 100644 --- a/img/compare.svg +++ b/img/compare.svg @@ -8,7 +8,7 @@ not hunky-dory default html corners - + very much hunky-dory smooth corners via svg diff --git a/package-lock.json b/package-lock.json index de579d3..43c5601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-round-div", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-round-div", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "prop-types": "^15.7.2", @@ -27,16 +27,15 @@ } }, "node_modules/@babel/cli": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz", - "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.14.5.tgz", + "integrity": "sha512-poegjhRvXHWO0EAsnYajwYZuqcz7gyfxwfaecUESxDujrqOivf3zrjFbub8IJkrqEaz3fvJWh001EzxBub54fg==", "dev": true, "dependencies": { "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.19", "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" @@ -45,8 +44,11 @@ "babel": "bin/babel.js", "babel-external-helpers": "bin/babel-external-helpers.js" }, + "engines": { + "node": ">=6.9.0" + }, "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.2", "chokidar": "^3.4.0" }, "peerDependencies": { @@ -2183,16 +2185,16 @@ } }, "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "version": "2.1.8-no-fsevents.2", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", + "integrity": "sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==", "dev": true, "optional": true, "dependencies": { "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", - "glob-parent": "^3.1.0", + "glob-parent": "^5.1.2", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", @@ -3128,14 +3130,14 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", - "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001208", + "caniuse-lite": "^1.0.30001219", "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.712", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", "node-releases": "^1.1.71" }, @@ -3217,10 +3219,14 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", - "dev": true + "version": "1.0.30001237", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz", + "integrity": "sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } }, "node_modules/capture-exit": { "version": "2.0.0", @@ -3335,19 +3341,6 @@ "node": ">=8" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/chokidar/node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3828,9 +3821,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.717", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz", - "integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==", + "version": "1.3.752", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", + "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, "node_modules/emittery": { @@ -4414,27 +4407,16 @@ } }, "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "optional": true, "dependencies": { - "is-extglob": "^2.1.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, "node_modules/globals": { @@ -7741,13 +7723,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true, - "optional": true - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9755,9 +9730,9 @@ } }, "node_modules/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true, "engines": { "node": ">=8.3.0" @@ -9837,18 +9812,17 @@ }, "dependencies": { "@babel/cli": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz", - "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.14.5.tgz", + "integrity": "sha512-poegjhRvXHWO0EAsnYajwYZuqcz7gyfxwfaecUESxDujrqOivf3zrjFbub8IJkrqEaz3fvJWh001EzxBub54fg==", "dev": true, "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.2", "chokidar": "^3.4.0", "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.19", "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" @@ -11561,16 +11535,16 @@ } }, "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "version": "2.1.8-no-fsevents.2", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", + "integrity": "sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==", "dev": true, "optional": true, "requires": { "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", - "glob-parent": "^3.1.0", + "glob-parent": "^5.1.2", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", @@ -12385,14 +12359,14 @@ "dev": true }, "browserslist": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", - "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001208", + "caniuse-lite": "^1.0.30001219", "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.712", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", "node-releases": "^1.1.71" } @@ -12452,9 +12426,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", + "version": "1.0.30001237", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz", + "integrity": "sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==", "dev": true }, "capture-exit": { @@ -12544,16 +12518,6 @@ "to-regex-range": "^5.0.1" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^4.0.1" - } - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -12943,9 +12907,9 @@ } }, "electron-to-chromium": { - "version": "1.3.717", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz", - "integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==", + "version": "1.3.752", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", + "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, "emittery": { @@ -13399,26 +13363,13 @@ } }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "optional": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "globals": { @@ -15939,13 +15890,6 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true, - "optional": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -17557,9 +17501,9 @@ } }, "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 359557d..8297208 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-round-div", - "version": "1.0.0", + "version": "1.1.0", "description": "Make your rounded corners look phenomenal with g2 continuity.", "main": "dist/main.js", "scripts": { diff --git a/src/css-utils.js b/src/css-utils.js index 7d1e742..94d9c7c 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -1,75 +1,63 @@ -import CSS_COLOR_NAMES from "./html-colors"; -import toPx from "./css-length-converter"; +import CSS_COLOR_NAMES from "./external/bobspace:html-colors"; +import toPx from "./external/heygrady:units:length"; -function _getAttributeFromString(string, method, ...data) { - if (!string) return false - string = string.split(' ') - for (let i in string) { - const res = method(string, Number(i), ...data) - if (res) return res - } -} - -function _getColor(border, i) { - const val = border[i] +/** @returns {string} */ +function convertPlainColor(val) { + if (!val) return '#000' + val = val?.toLowerCase() // color is a hex code - if (val.toLowerCase().match(/#([0-9a-f]{3}){1,2}/)) return val + if (val?.match(/#([0-9a-f]{3}){1,2}/)) return val // color is a function (rgb, rgba, hsl, hsla) - if (val.startsWith('rgb') || val.startsWith('hsl')) { - let color = val; - if (!val.endsWith(')')) - for (let j = 1; !border[i + j - 1].endsWith(')'); j++) { - color += border[i + j] - } - if (color[3] === 'a') - color = color.replace('a', '').replace(/,[^),]+\)/, ')') - return color - } + else if (val?.match(/^(rgb|hsl)a?\(([^,]{1,3},? *){3}(\d*\.?\d+)?\)/)) + return val + .replace('a', '') + .replace(/\((([\d%]{1,3}, *){2}([\d%]{1,3}))(, *\d*\.?\d+)?\)/, '($1)') // color is a html color name - if ( - CSS_COLOR_NAMES.map(color => color.toLowerCase()) - .includes(val.toLowerCase()) - ) return val - return false + else if (CSS_COLOR_NAMES.map(color => color.toLowerCase()) + .includes(val.toLowerCase())) + return val + else if (val === 'currentcolor') { + return 'currentcolor' + } else return '#000' } -function _getOpacity(border, i) { - let val = border[i] - if (val.startsWith('rgba') || val.startsWith('hsla')) { - if (!val.endsWith(')')) - for (let j = 1; !border[i + j - 1].endsWith(')'); j++) { - val += border[i + j] - } - return val.replace(/(rgb|hsl)a?\(([^,)]+,){3}/, '').replace(/\)$/, '') - } - if (border.length - 1 === i) - return 1 +/** @returns {number} */ +function convertColorOpacity(val) { + if (val?.startsWith('rgba') || val?.startsWith('hsla')) { + return Number(val.match(/(\d*\.?\d+)?\)$/)[1]) + } else return 1 } -const htmlLengthNotSvgError = new Error(' Border lengths must be either "thin", "medium", "thick", or in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.') - -function unitCheck(length) { - if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw htmlLengthNotSvgError - return length +const htmlLengthNotSvgErrorTemplate = (a, b) => ` ${a} must be ${b ? `either ${b}, or` : ''} in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.` +const htmlBorderLengthNotSvgError = + new Error(htmlLengthNotSvgErrorTemplate('border lengths', '"thin", "medium", "thick"')) +const htmlBorderRadiusNotSvgError = + new Error(htmlLengthNotSvgErrorTemplate('border radii')) + +function toNumber(length, element, err) { + if (!length) return false + if (typeof length === 'number' || !length.match(/\D+/)) + return Number(length); + else if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) + if (err) throw err + else return false + else if (length?.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) + return toPx(element, length) } -function _getWidth(border, i, element) { - const val = border[i] - // width is 0 - if (val === '0') return 0 +/** @returns {number} */ +function convertBorderWidth(val, element) { + if (!val) return 0 // width is a word - if (val.toLowerCase() === 'thin') return 1 - if (val.toLowerCase() === 'medium') return 3 - if (val.toLowerCase() === 'thick') return 5 - unitCheck(val) + if (val?.toLowerCase() === 'thin') + return 1 + else if (val?.toLowerCase() === 'medium') + return 3 + else if (val?.toLowerCase() === 'thick') + return 5 // width is - if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) - return toPx(element, val) - return false + else + return toNumber(val, element, htmlBorderLengthNotSvgError) || 0 } -const getWidth = s => _getAttributeFromString(s, _getWidth), - getColor = s => _getAttributeFromString(s, _getColor), - getOpacity = s => _getAttributeFromString(s, _getOpacity) - -export {getWidth, getColor, unitCheck, getOpacity} +export {convertPlainColor, convertColorOpacity, convertBorderWidth, toNumber, htmlBorderRadiusNotSvgError} diff --git a/src/react-shadow-dom.js b/src/external/apearce:eact-shadow-dom.js similarity index 100% rename from src/react-shadow-dom.js rename to src/external/apearce:eact-shadow-dom.js diff --git a/src/external/bobspace:html-colors.js b/src/external/bobspace:html-colors.js new file mode 100644 index 0000000..3d69e4b --- /dev/null +++ b/src/external/bobspace:html-colors.js @@ -0,0 +1,8 @@ +// CSS Color Names +// Compiled by @bobspace. +// +// A javascript array containing all of the color names listed in the CSS Spec. +// The full list can be found here: https://www.w3schools.com/cssref/css_colors.asp +// Use it as you please, 'cuz you can't, like, own a color, man. + +const CSS_COLOR_NAMES=["AliceBlue","AntiqueWhite","Aqua","Aquamarine","Azure","Beige","Bisque","Black","BlanchedAlmond","Blue","BlueViolet","Brown","BurlyWood","CadetBlue","Chartreuse","Chocolate","Coral","CornflowerBlue","Cornsilk","Crimson","Cyan","DarkBlue","DarkCyan","DarkGoldenRod","DarkGray","DarkGrey","DarkGreen","DarkKhaki","DarkMagenta","DarkOliveGreen","DarkOrange","DarkOrchid","DarkRed","DarkSalmon","DarkSeaGreen","DarkSlateBlue","DarkSlateGray","DarkSlateGrey","DarkTurquoise","DarkViolet","DeepPink","DeepSkyBlue","DimGray","DimGrey","DodgerBlue","FireBrick","FloralWhite","ForestGreen","Fuchsia","Gainsboro","GhostWhite","Gold","GoldenRod","Gray","Grey","Green","GreenYellow","HoneyDew","HotPink","IndianRed","Indigo","Ivory","Khaki","Lavender","LavenderBlush","LawnGreen","LemonChiffon","LightBlue","LightCoral","LightCyan","LightGoldenRodYellow","LightGray","LightGrey","LightGreen","LightPink","LightSalmon","LightSeaGreen","LightSkyBlue","LightSlateGray","LightSlateGrey","LightSteelBlue","LightYellow","Lime","LimeGreen","Linen","Magenta","Maroon","MediumAquaMarine","MediumBlue","MediumOrchid","MediumPurple","MediumSeaGreen","MediumSlateBlue","MediumSpringGreen","MediumTurquoise","MediumVioletRed","MidnightBlue","MintCream","MistyRose","Moccasin","NavajoWhite","Navy","OldLace","Olive","OliveDrab","Orange","OrangeRed","Orchid","PaleGoldenRod","PaleGreen","PaleTurquoise","PaleVioletRed","PapayaWhip","PeachPuff","Peru","Pink","Plum","PowderBlue","Purple","RebeccaPurple","Red","RosyBrown","RoyalBlue","SaddleBrown","Salmon","SandyBrown","SeaGreen","SeaShell","Sienna","Silver","SkyBlue","SlateBlue","SlateGray","SlateGrey","Snow","SpringGreen","SteelBlue","Tan","Teal","Thistle","Tomato","Turquoise","Violet","Wheat","White","WhiteSmoke","Yellow","YellowGreen"];export default CSS_COLOR_NAMES diff --git a/src/getMatchedCSSRules-polyfill.js b/src/external/getMatchedCSSRules-polyfill.js similarity index 100% rename from src/getMatchedCSSRules-polyfill.js rename to src/external/getMatchedCSSRules-polyfill.js diff --git a/src/css-length-converter.js b/src/external/heygrady:units:length.js similarity index 100% rename from src/css-length-converter.js rename to src/external/heygrady:units:length.js diff --git a/src/styles-extractor.js b/src/external/styles-extractor.js similarity index 100% rename from src/styles-extractor.js rename to src/external/styles-extractor.js diff --git a/src/generator.js b/src/generator.js new file mode 100644 index 0000000..1331f5b --- /dev/null +++ b/src/generator.js @@ -0,0 +1,111 @@ +/** + * @param {number} height + * @param {number} width + * @param {number | Array} radius + * + * @returns {string} SVG path data + * */ +export default function generateSvgSquircle(height, width, radius) { + /* from right to left top left corner upper (right half) */ + const ratios = [1.528665037, 1.0884928889, 0.8684068148, 0.07491140741, 0.6314939259, 0.1690595556, 0.3728238519]; + const roundToNthPlace = 1; + + if (typeof radius === 'number') + radius = Array(4).fill(radius) + else if (radius.length === 2) + radius.push(radius[0]) + if (radius.length === 3) + radius.push(radius[1]) + + height = Number(height); + width = Number(width); + + const _rawRadius = [...radius].map(n => Number(n)) + const max = radius.length - 1 + const next = i => i === max ? 0 : i + 1 + const prev = i => i === 0 ? max : i - 1 + radius = _rawRadius.map((radius, i) => + Math.min( + radius, + Math.min( + height - _rawRadius[i % 2 === 0 ? prev(i) : next(i)], + height / 2 + ), + Math.min( + width - _rawRadius[i % 2 === 0 ? next(i) : prev(i)], + width / 2 + ) + ) + ) + + const [a0x, a1x, a2x, a3y, a3x, b1y, b1x] = Array(7) + .fill(Array(4).fill(0)) + .map((a, i) => a.map((b, j) => radius[j] * ratios[i])), + [b0y, b0x] = [a3y, a3x] + + if (isNaN(height)) throw new Error(`'height' must be a number`); + if (isNaN(width)) throw new Error(`'width' must be a number`); + if (radius.includes(NaN)) throw new Error(`'radius' must be a number or an array containing 2 to 4 numbers`); + + const a0xF = x => Math.min(x / 2, a0x[0]), + a0xw = a0xF(width), + a0xh = a0xF(height) + + /*function mapRange(number, in_min, in_max, out_min, out_max) { + return (number - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + }*/ + + // const maxRadius = Math.max(...radius); + + const yOffsetF = (x) => 0, + hyOffset = yOffsetF(height) || 0, + wyOffset = yOffsetF(width) || 0 + + const startPoint = `${a0xw},${wyOffset}` + + return `M${startPoint} + ${width / 2 < a0x[1] + ? '' + : `L${width - a0xw},0` + } + + C${width - a1x[1]},0,${width - a2x[1]},0,${width - a3x[1]},${a3y[1]} + C${width - b1x[1]},${b1y[1]},${width - b1y[1]},${b1x[1]},${width - b0y[1]},${b0x[1]} + C${width},${a2x[1]},${width},${a1x[1]}, + + ${width - hyOffset},${a0xh} + ${height / 2 < a0x[2] + ? '' + : `L${width},${height - a0xh}` + } + + C${width},${height - a1x[2]},${width},${height - a2x[2]},${width - a3y[2]},${height - a3x[2]} + C${width - b1y[2]},${height - b1x[2]},${width - b1x[2]},${height - b1y[2]},${width - b0x[2]},${height - b0y[2]} + C${width - a2x[2]},${height},${width - a1x[2]},${height}, + + ${width - a0xw},${height - wyOffset} + ${width / 2 < a0x[3] + ? '' + : `L${a0xw},${height}` + } + + C${a1x[3]},${height},${a2x[3]},${height},${a3x[3]},${height - a3y[3]} + C${b1x[3]},${height - b1y[3]},${b1y[3]},${height - b1x[3]},${b0y[3]},${height - b0x[3]} + C0,${height - a2x[3]},0,${height - a1x[3]}, + + ${hyOffset},${height - a0xh} + ${height / 2 < a0x[0] + ? '' + : `L0,${a0xh}` + } + + C0,${a1x[0]},0,${a2x[0]},${a3y[0]},${a3x[0]} + C${b1y[0]},${b1x[0]},${b1x[0]},${b1y[0]},${b0x[0]},${b0y[0]} + C${a2x[0]},0,${a1x[0]},0,${startPoint} + Z` + .replace(/[\n ]/g, '') + .replace(/NaN/g, '0') + .replace(/\d+\.\d+/g, match => + Math.round(Number(match) * (10 ** roundToNthPlace)) / (10 ** roundToNthPlace) + ) +} diff --git a/src/html-colors.js b/src/html-colors.js deleted file mode 100644 index 7ff5a77..0000000 --- a/src/html-colors.js +++ /dev/null @@ -1,159 +0,0 @@ -// CSS Color Names -// Compiled by @bobspace. -// -// A javascript array containing all of the color names listed in the CSS Spec. -// The full list can be found here: https://www.w3schools.com/cssref/css_colors.asp -// Use it as you please, 'cuz you can't, like, own a color, man. - -const CSS_COLOR_NAMES = [ - "AliceBlue", - "AntiqueWhite", - "Aqua", - "Aquamarine", - "Azure", - "Beige", - "Bisque", - "Black", - "BlanchedAlmond", - "Blue", - "BlueViolet", - "Brown", - "BurlyWood", - "CadetBlue", - "Chartreuse", - "Chocolate", - "Coral", - "CornflowerBlue", - "Cornsilk", - "Crimson", - "Cyan", - "DarkBlue", - "DarkCyan", - "DarkGoldenRod", - "DarkGray", - "DarkGrey", - "DarkGreen", - "DarkKhaki", - "DarkMagenta", - "DarkOliveGreen", - "DarkOrange", - "DarkOrchid", - "DarkRed", - "DarkSalmon", - "DarkSeaGreen", - "DarkSlateBlue", - "DarkSlateGray", - "DarkSlateGrey", - "DarkTurquoise", - "DarkViolet", - "DeepPink", - "DeepSkyBlue", - "DimGray", - "DimGrey", - "DodgerBlue", - "FireBrick", - "FloralWhite", - "ForestGreen", - "Fuchsia", - "Gainsboro", - "GhostWhite", - "Gold", - "GoldenRod", - "Gray", - "Grey", - "Green", - "GreenYellow", - "HoneyDew", - "HotPink", - "IndianRed", - "Indigo", - "Ivory", - "Khaki", - "Lavender", - "LavenderBlush", - "LawnGreen", - "LemonChiffon", - "LightBlue", - "LightCoral", - "LightCyan", - "LightGoldenRodYellow", - "LightGray", - "LightGrey", - "LightGreen", - "LightPink", - "LightSalmon", - "LightSeaGreen", - "LightSkyBlue", - "LightSlateGray", - "LightSlateGrey", - "LightSteelBlue", - "LightYellow", - "Lime", - "LimeGreen", - "Linen", - "Magenta", - "Maroon", - "MediumAquaMarine", - "MediumBlue", - "MediumOrchid", - "MediumPurple", - "MediumSeaGreen", - "MediumSlateBlue", - "MediumSpringGreen", - "MediumTurquoise", - "MediumVioletRed", - "MidnightBlue", - "MintCream", - "MistyRose", - "Moccasin", - "NavajoWhite", - "Navy", - "OldLace", - "Olive", - "OliveDrab", - "Orange", - "OrangeRed", - "Orchid", - "PaleGoldenRod", - "PaleGreen", - "PaleTurquoise", - "PaleVioletRed", - "PapayaWhip", - "PeachPuff", - "Peru", - "Pink", - "Plum", - "PowderBlue", - "Purple", - "RebeccaPurple", - "Red", - "RosyBrown", - "RoyalBlue", - "SaddleBrown", - "Salmon", - "SandyBrown", - "SeaGreen", - "SeaShell", - "Sienna", - "Silver", - "SkyBlue", - "SlateBlue", - "SlateGray", - "SlateGrey", - "Snow", - "SpringGreen", - "SteelBlue", - "Tan", - "Teal", - "Thistle", - "Tomato", - "Turquoise", - "Violet", - "Wheat", - "White", - "WhiteSmoke", - "Yellow", - "YellowGreen", -]; - -export default CSS_COLOR_NAMES diff --git a/src/main.js b/src/main.js index 7f7682d..3722e79 100644 --- a/src/main.js +++ b/src/main.js @@ -1,92 +1,121 @@ -import React, {useRef, useEffect, useState} from 'react'; -import './getMatchedCSSRules-polyfill' -import updateStates from "./updateStates"; -import ShadowRoot from "./react-shadow-dom"; +import React, {useRef, useEffect, useState, useCallback} from 'react' +import generateSvgSquircle from './generator' +import './external/getMatchedCSSRules-polyfill' +import updateStates from "./updateStates" +import ShadowRoot from "./external/apearce:eact-shadow-dom" +import attachCSSWatcher from './styleSheetWatcher' +import getMaskPaths from './mask-generator' -export default function RoundDiv({clip, style, children, ...props}) { +export default function RoundDiv({style, children, ...props}) { + // welcome to react states hell + const [position, setPosition] = useState([0, 0]) const [height, setHeight] = useState(0) const [width, setWidth] = useState(0) - const [radius, setRadius] = useState(0) - const [background, setBackground] = useState('transparent') - const [backgroundOpacity, setBackgroundOpacity] = useState(0) - const [borderColor, setBorderColor] = useState('transparent') - const [borderWidth, setBorderWidth] = useState(1) - const [borderOpacity, setBorderOpacity] = useState(1) + const [radius, setRadius] = useState(Array(4).fill(0)) - const div = useRef() + const [borderColor, setBorderColor] = useState(Array(4).fill('transparent')) + const [borderOpacity, setBorderOpacity] = useState(Array(4).fill(1)) + const [borderWidth, setBorderWidth] = useState(Array(4).fill(0)) - useEffect(() => { - // attach shadow root to div - if (!div.current?.shadowRoot) - div.current?.attachShadow({mode: 'open'}) - }, []) + const [path, setPath] = useState('Z') + const [innerPath, setInnerPath] = useState('Z') + const [maskPaths, setMaskPaths] = useState('Z') - useEffect(() => updateStates({ + const div = useRef() + + const updateStatesWithArgs = useCallback(() => updateStates({ div, style, + setPosition, setHeight, setWidth, setRadius, - setBackground, - setBackgroundOpacity, setBorderColor, setBorderWidth, setBorderOpacity - }), [div, clip, style]) + }), [style]) + + useEffect(updateStatesWithArgs, [style, updateStatesWithArgs]) + + useEffect(() => { + attachCSSWatcher(() => updateStatesWithArgs()) + }, [updateStatesWithArgs]) + + useEffect(() => { + setPath(generateSvgSquircle(height, width, radius)) + setInnerPath(generateSvgSquircle( + height - (borderWidth[0] + borderWidth[2]), + width - (borderWidth[1] + borderWidth[3]), + radius.map((val, i) => + Math.max(0, + val - Math.max(borderWidth[i], borderWidth[i === 0 ? 3 : i - 1]) + ) + ) + ).replace( + /(\d+(\.\d+)?),(\d+(\.\d+)?)/g, + match => match.split(',').map((number, i) => + Number(number) + (i === 0 ? borderWidth[3] : borderWidth[0]) + ).join(',') + )) + + // prevents unnecessary re-renders: + // single value states (numbers and strings) prevent this out of the box, + // complex states (objects, arrays, etc.) don't, so here it is manually for objects (non-nested) + const lazySetObjectsState = (setState, newState) => + setState(oldState => { + if (areEqualObjects(oldState, newState)) return oldState + else return newState + }) + + function areEqualObjects(a, b) { + if (Object.keys(a).length !== Object.keys(b).length) return false + for (let key in a) { + if (a[key] !== b[key]) return false + } + return true + } + + lazySetObjectsState(setMaskPaths, getMaskPaths(borderWidth, height, width, radius)) + }, [height, width, radius, borderWidth]) + const divStyle = { - ...style + ...style, + clipPath: `path("${path}")`, + borderColor: 'transparent' } - divStyle.background = 'transparent' - divStyle.borderWidth = divStyle.borderWidth || '0' - divStyle.borderColor = 'transparent' - return
- + zIndex: -1, + }} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}> - - - - + + - - + {Object.keys(maskPaths).map((key, i) => { + if (borderColor[i] === borderColor[i - 1]) return '' + + let path = maskPaths[key] + while (borderColor[i] === borderColor[i + 1]) { + path += maskPaths[Object.keys(maskPaths)[i + 1]] + i++ + } + + return + })} - + {children}
} - -function generateSvgSquircle(height, width, radius, clip) { - const RADIUS_SCALE_FACTOR = 1.25 - height = Number(height); - width = Number(width); - radius = clip === false - ? Number(radius) - : Math.min(Number(radius), height / 2 / RADIUS_SCALE_FACTOR, width / 2 / RADIUS_SCALE_FACTOR); - if (isNaN(height)) throw new Error(`'height' must be a number`); - if (isNaN(width)) throw new Error(`'width' must be a number`); - if (isNaN(radius)) throw new Error(`'radius' must be a number`); - const point = RADIUS_SCALE_FACTOR * radius, - bezier = radius / 3; - - return `M 0,${point} C 0,${bezier}, ${bezier},0, ${point},0 - L ${width - point},0 C ${width - bezier},0, ${width},${bezier}, ${width},${point} - L ${width},${height - point} C ${width},${height - bezier}, ${width - bezier},${height}, ${width - point},${height} - L ${point},${height} C ${bezier},${height}, 0,${height - bezier}, 0,${height - point} - Z`; -} diff --git a/src/mask-generator.js b/src/mask-generator.js new file mode 100644 index 0000000..c2d9806 --- /dev/null +++ b/src/mask-generator.js @@ -0,0 +1,70 @@ +export default function getMaskPaths(borderWidth, height, width, radius) { + // console.log(borderWidth, height, width, radius) + const allSides = ['top', 'right', 'bottom', 'left'] + const max = allSides.length - 1 + const next = i => i === max ? 0 : i + 1 + const prev = i => i === 0 ? max : i - 1 + + /** + * @constant + * @type {Array} + * */ + const allRatios = allSides.map((side, i) => + ((i % 2 === 0 ? height : width) + - Math.max(borderWidth[next(next(i))], radius[prev(i)], radius[prev(prev(i))]) + ) + / borderWidth[i] + ) + + /** + * @param {string} posY + * @param {string} posX + * @param {boolean} [inside] + * @param {string} [borderPos] needed if inside=true + * */ + const makePoint = (posY, posX, inside, borderPos) => { + return makeValue(posX, inside, borderPos) + ',' + makeValue(posY, inside, borderPos) + } + + /** + * @param {string} pos + * @param {boolean} [inside] + * @param {string} [borderPos] needed if inside=true + * */ + const makeValue = (pos, inside, borderPos) => { + let v = -borderWidth[allSides.indexOf(pos)] + + if (inside) { + const i = allSides.indexOf(borderPos) + v *= -Math.min(allRatios[prev(i)], allRatios[i], allRatios[next(i)]) + } + + if (pos === 'right') + v = -v + Number(width) + else if (pos === 'bottom') + v = -v + Number(height) + + return v + } + + const makeMaskPath = (side) => { + const i = allSides.indexOf(side) + const isH = i % 2 === 0 // is "top" or "bottom" + const T = isH ? 'H' : 'V' + const nextSide = allSides[next(i)] + const prevSide = allSides[prev(i)] + const prevIfH = isH ? prevSide : side + const prevIfV = !isH ? prevSide : side + const nextIfH = isH ? nextSide : side + const nextIfV = !isH ? nextSide : side + + return ('M' + makePoint(prevIfV, prevIfH, true, side) + + T + makeValue(nextSide, true, side) + + 'L' + makePoint(nextIfV, nextIfH) + + T + makeValue(prevSide) + 'Z').replace(/NaN/g, '0') + } + + return Object.fromEntries( + allSides.map(side => [side, makeMaskPath(side)]) + ) +} diff --git a/src/styleSheetWatcher.js b/src/styleSheetWatcher.js new file mode 100644 index 0000000..0c5bfd1 --- /dev/null +++ b/src/styleSheetWatcher.js @@ -0,0 +1,32 @@ +const CSSChangeEvent = new CustomEvent('css-change'); + +export default function attachCSSWatcher(callback) { + CSSWatcher.addEventListener('css-change', () => callback()) +} + +const CSSWatcher = new EventTarget() + +;(function watchCSS() { + let CSS = getCSSText() + setInterval(() => { + const newCSS = getCSSText() + if (CSS === newCSS) return + CSS = newCSS + CSSWatcher.dispatchEvent(CSSChangeEvent) + }, 30) + window.addEventListener('resize', () => { + CSS = getCSSText() + CSSWatcher.dispatchEvent(CSSChangeEvent) + }) +})() + +function getCSSText() { + let CSS = '' + for (let i = 0; i < document.styleSheets.length; i++) { + const sheet = document.styleSheets[i] + for (let j = 0; j < sheet.rules.length; j++) { + CSS += sheet.rules[j].cssText + } + } + return CSS +} diff --git a/src/updateStates.js b/src/updateStates.js index 3bed3f9..59df963 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,58 +1,100 @@ -import {getColor, getOpacity, getWidth, unitCheck} from "./css-utils"; -import getStyle from "./styles-extractor"; +import { + convertPlainColor, + convertColorOpacity, + convertBorderWidth, + toNumber, + htmlBorderRadiusNotSvgError +} from "./css-utils"; +import getStyle from "./external/styles-extractor"; +import ReactDOM from 'react-dom' + +// prevents unnecessary re-renders: +// single value states (numbers and strings) prevent this out of the box, +// complex states (objects, arrays, etc.) don't, so here it is manually for arrays (non-nested) +const lazySetArrayState = (setState, newState) => + setState(oldState => { + if (oldState.every((val, i) => val === newState[i])) return oldState + else return newState + }) export default function updateStates(args) { - const { - div, - style, - setHeight, - setWidth, - setRadius, - setBackground, - setBackgroundOpacity, - setBorderColor, - setBorderWidth, - setBorderOpacity - } = args + const {div, setPosition, setHeight, setWidth} = args const boundingClientRect = div.current?.getBoundingClientRect() + let height, width; if (boundingClientRect) { - setHeight(boundingClientRect.height) - setWidth(boundingClientRect.width) + lazySetArrayState(setPosition, [boundingClientRect.x, boundingClientRect.y]) + height = boundingClientRect.height + width = boundingClientRect.width + setHeight(height) + setWidth(width) } - const divStyle = boundingClientRect ? window?.getComputedStyle(div.current) : null - if (divStyle) { - setRadius(Number( - (divStyle.borderRadius || divStyle.borderTopLeftRadius) - .replace('px', ''))) - setBackground(getColor( - style?.background - || style?.backgroundColor - || (getStyle('background', div.current)?.overwritten || [])[1]?.value - || (getStyle('background-color', div.current)?.overwritten || [])[1]?.value - ) || 'transparent') - setBackgroundOpacity(getOpacity( - style?.background - || style?.backgroundColor - || (getStyle('background', div.current)?.overwritten || [])[1]?.value - || (getStyle('background-color', div.current)?.overwritten || [])[1]?.value - ) || 1) - setBorderColor(getColor( - style?.border - || style?.borderColor - || (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value - || (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value - ) || 'transparent') - setBorderWidth(getWidth( - style?.border - || style?.borderWidth - || unitCheck((getStyle('borderWidth', div.current)?.overwritten || [])[0]?.value) - || unitCheck((getStyle('borderTopWidth', div.current)?.overwritten || [])[0]?.value), - div.current) || 1) - setBorderOpacity(getOpacity( - style?.border - || style?.borderColor - || (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value - || (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value - ) || 1) + + function camelise(str) { + return str?.replace(/^\w|[A-Z]|\b\w|\s+/g, function (match, index) { + if (+match === 0) return ""; + return index === 0 ? match.toLowerCase() : match.toUpperCase(); + }).replace(/-/g, ''); } + + const getNthStyle = (key, n) => { + const returnNthOverwrittenOrCurrent = r => + !r ? false : + r?.overwritten.length > 0 + ? r.overwritten[n ?? 0].value + : r.current?.value + + const normal = getStyle(key, div.current) + const camelised = getStyle(camelise(key), div.current) + + return returnNthOverwrittenOrCurrent(normal) || returnNthOverwrittenOrCurrent(camelised) + }; + + const getBorderStyles = (key, n) => [ + getNthStyle('border-top-' + key, n), + getNthStyle('border-right-' + key, n), + getNthStyle('border-bottom-' + key, n), + getNthStyle('border-left-' + key, n), + ] + + const getBorderRadii = (n) => [ + getNthStyle('border-top-right-radius', n), + getNthStyle('border-top-left-radius', n), + getNthStyle('border-bottom-right-radius', n), + getNthStyle('border-bottom-left-radius', n), + ] + + const states = args + const lazySetRadius = newState => lazySetArrayState(states.setRadius, newState), + lazySetBorderColor = newState => lazySetArrayState(states.setBorderColor, newState), + lazySetBorderOpacity = newState => lazySetArrayState(states.setBorderOpacity, newState), + lazySetBorderWidth = newState => lazySetArrayState(states.setBorderWidth, newState) + + const divStyle = div.current ? window?.getComputedStyle(div.current) : null + if (!divStyle) return + ReactDOM.unstable_batchedUpdates(() => { + lazySetRadius( + getBorderRadii(1) + .map(s => Math.min( + toNumber(s, div.current, htmlBorderRadiusNotSvgError), + height / 2, + width / 2 + )) + ) + + // get color + lazySetBorderColor( + getBorderStyles('color', 1) + .map(s => convertPlainColor(s)) + ) + // get alpha value of color + lazySetBorderOpacity( + getBorderStyles('color', 1) + .map(s => convertColorOpacity(s)) + ) + + lazySetBorderWidth( + getBorderStyles('width', 0) + .map(s => convertBorderWidth(s, div.current)) + ) + }) }