From e2efc781fd86da00477423473337188bf84731d9 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 17 Jun 2021 00:55:51 +0200 Subject: [PATCH 01/10] rework generator --- package-lock.json | 178 +++++++++++++++----------------------------- package.json | 2 +- src/generator.js | 77 +++++++++++++++++++ src/main.js | 33 ++------ src/updateStates.js | 2 +- 5 files changed, 147 insertions(+), 145 deletions(-) create mode 100644 src/generator.js diff --git a/package-lock.json b/package-lock.json index de579d3..0c939d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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/generator.js b/src/generator.js new file mode 100644 index 0000000..e4efcac --- /dev/null +++ b/src/generator.js @@ -0,0 +1,77 @@ +export default function generateSvgSquircle(height, width, radius, clip) { + /* 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]; + + height = Number(height); + width = Number(width); + + radius = clip === false + ? Number(radius) + : Math.min(Number(radius), height / 2, width / 2); + + const [a0x, a1x, a2x, a3y, a3x, b1y, b1x] = Array(7).fill(0).map((a, i) => radius * 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 (isNaN(radius)) throw new Error(`'radius' must be a number`); + + const a0xF = x => Math.min(x / 2, a0x), + 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 yOffsetF = (x) => + Math.max(0, Math.min( + mapRange(radius, (x / 2) * .90, x / 2, 0, 1), + 1 + )) * 1.7, + hyOffset = yOffsetF(height), + wyOffset = yOffsetF(width) + + const startPoint = `${a0xw},${wyOffset}` + + return `M${startPoint} + ${width / 2 < a0x + ? '' + : `L${width - a0xw},0` + } + + C${width - a1x},0,${width - a2x},0,${width - a3x},${a3y} + C${width - b1x},${b1y},${width - b1y},${b1x},${width - b0y},${b0x} + C${width},${a2x},${width},${a1x}, + + ${width - hyOffset},${a0xh} + ${height / 2 < a0x + ? '' + : `L${width},${height - a0xh}` + } + + C${width},${height - a1x},${width},${height - a2x},${width - a3y},${height - a3x} + C${width - b1y},${height - b1x},${width - b1x},${height - b1y},${width - b0x},${height - b0y} + C${width - a2x},${height},${width - a1x},${height}, + + ${width - a0xw},${height - wyOffset} + ${width / 2 < a0x + ? '' + : `L${a0xw},${height}` + } + + C${a1x},${height},${a2x},${height},${a3x},${height - a3y} + C${b1x},${height - b1y},${b1y},${height - b1x},${b0y},${height - b0x} + C0,${height - a2x},0,${height - a1x}, + + ${hyOffset},${height - a0xh} + ${height / 2 < a0x + ? '' + : `L0,${a0xh}` + } + + C0,${a1x},0,${a2x},${a3y},${a3x} + C${b1y},${b1x},${b1x},${b1y},${b0x},${b0y} + C${a2x},0,${a1x},0,${startPoint} + Z`.replace(/\n */g, ''); +} diff --git a/src/main.js b/src/main.js index 7f7682d..e513c95 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,8 @@ -import React, {useRef, useEffect, useState} from 'react'; +import React, {useRef, useEffect, useState} from 'react' +import generateSvgSquircle from './generator' import './getMatchedCSSRules-polyfill' -import updateStates from "./updateStates"; -import ShadowRoot from "./react-shadow-dom"; +import updateStates from "./updateStates" +import ShadowRoot from "./react-shadow-dom" export default function RoundDiv({clip, style, children, ...props}) { const [height, setHeight] = useState(0) @@ -10,7 +11,7 @@ export default function RoundDiv({clip, style, children, ...props}) { const [background, setBackground] = useState('transparent') const [backgroundOpacity, setBackgroundOpacity] = useState(0) const [borderColor, setBorderColor] = useState('transparent') - const [borderWidth, setBorderWidth] = useState(1) + const [borderWidth, setBorderWidth] = useState(0) const [borderOpacity, setBorderOpacity] = useState(1) const div = useRef() @@ -44,7 +45,7 @@ export default function RoundDiv({clip, style, children, ...props}) { 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/updateStates.js b/src/updateStates.js index 3bed3f9..825a3b8 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -47,7 +47,7 @@ export default function updateStates(args) { || style?.borderWidth || unitCheck((getStyle('borderWidth', div.current)?.overwritten || [])[0]?.value) || unitCheck((getStyle('borderTopWidth', div.current)?.overwritten || [])[0]?.value), - div.current) || 1) + div.current) || 0) setBorderOpacity(getOpacity( style?.border || style?.borderColor From b146e57b93fc769eab41ea8f045ce2fa171269c5 Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 18 Jun 2021 00:16:59 +0200 Subject: [PATCH 02/10] fix positioning issues, add css watcher --- img/compare.svg | 2 +- src/generator.js | 12 ++++++------ src/main.js | 20 ++++++++++++++------ src/style-sheet-watcher.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 src/style-sheet-watcher.js 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/src/generator.js b/src/generator.js index e4efcac..fea6abf 100644 --- a/src/generator.js +++ b/src/generator.js @@ -29,13 +29,13 @@ export default function generateSvgSquircle(height, width, radius, clip) { mapRange(radius, (x / 2) * .90, x / 2, 0, 1), 1 )) * 1.7, - hyOffset = yOffsetF(height), - wyOffset = yOffsetF(width) + hyOffset = clip !== false ? yOffsetF(height) : 0, + wyOffset = clip !== false ? yOffsetF(width) : 0 const startPoint = `${a0xw},${wyOffset}` return `M${startPoint} - ${width / 2 < a0x + ${width / 2 < a0x && clip !== false ? '' : `L${width - a0xw},0` } @@ -45,7 +45,7 @@ export default function generateSvgSquircle(height, width, radius, clip) { C${width},${a2x},${width},${a1x}, ${width - hyOffset},${a0xh} - ${height / 2 < a0x + ${height / 2 < a0x && clip !== false ? '' : `L${width},${height - a0xh}` } @@ -55,7 +55,7 @@ export default function generateSvgSquircle(height, width, radius, clip) { C${width - a2x},${height},${width - a1x},${height}, ${width - a0xw},${height - wyOffset} - ${width / 2 < a0x + ${width / 2 < a0x && clip !== false ? '' : `L${a0xw},${height}` } @@ -65,7 +65,7 @@ export default function generateSvgSquircle(height, width, radius, clip) { C0,${height - a2x},0,${height - a1x}, ${hyOffset},${height - a0xh} - ${height / 2 < a0x + ${height / 2 < a0x && clip !== false ? '' : `L0,${a0xh}` } diff --git a/src/main.js b/src/main.js index e513c95..5891123 100644 --- a/src/main.js +++ b/src/main.js @@ -1,8 +1,9 @@ -import React, {useRef, useEffect, useState} from 'react' +import React, {useRef, useEffect, useState, useCallback} from 'react' import generateSvgSquircle from './generator' import './getMatchedCSSRules-polyfill' import updateStates from "./updateStates" import ShadowRoot from "./react-shadow-dom" +import attachCSSWatcher from './style-sheet-watcher' export default function RoundDiv({clip, style, children, ...props}) { const [height, setHeight] = useState(0) @@ -22,7 +23,7 @@ export default function RoundDiv({clip, style, children, ...props}) { div.current?.attachShadow({mode: 'open'}) }, []) - useEffect(() => updateStates({ + const updateStatesWithArgs = useCallback(() => updateStates({ div, style, setHeight, @@ -33,7 +34,13 @@ export default function RoundDiv({clip, style, children, ...props}) { setBorderColor, setBorderWidth, setBorderOpacity - }), [div, clip, style]) + }), [style]) + + useEffect(updateStatesWithArgs, [div, clip, style, updateStatesWithArgs]) + + useEffect(() => { + attachCSSWatcher(() => updateStatesWithArgs()) + }, [updateStatesWithArgs]) const divStyle = { ...style @@ -45,13 +52,14 @@ export default function RoundDiv({clip, style, children, ...props}) { return
+ + }} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}> callback()) +} + +const CSSWatcher = new EventTarget() + +;(function watchCSS() { + let CSS = getCSSText() + setInterval(() => { + const newCSS = getCSSText() + if (CSS === newCSS) return + CSS = newCSS + CSSWatcher.dispatchEvent(CSSChangeEvent) + }, 1000) +})() + +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 +} From 4c8c0744fe1f25d89508ef6c1294edbe40c0fc87 Mon Sep 17 00:00:00 2001 From: johannes Date: Sat, 19 Jun 2021 21:18:04 +0200 Subject: [PATCH 03/10] basic support for background images --- src/css-utils.js | 58 +++++++++++++++++++++++----- src/main.js | 56 ++++++++++++++++++++++++--- src/style-sheet-watcher.js | 3 -- src/updateStates.js | 78 ++++++++++++++++++++++++++------------ 4 files changed, 152 insertions(+), 43 deletions(-) diff --git a/src/css-utils.js b/src/css-utils.js index 7d1e742..ce2cfdb 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -1,10 +1,12 @@ import CSS_COLOR_NAMES from "./html-colors"; import toPx from "./css-length-converter"; +import {element} from 'prop-types' function _getAttributeFromString(string, method, ...data) { if (!string) return false string = string.split(' ') for (let i in string) { + if (!string.hasOwnProperty(i)) continue const res = method(string, Number(i), ...data) if (res) return res } @@ -26,13 +28,43 @@ function _getColor(border, i) { return color } // color is a html color name - if ( - CSS_COLOR_NAMES.map(color => color.toLowerCase()) - .includes(val.toLowerCase()) - ) return val + if (CSS_COLOR_NAMES.map(color => color.toLowerCase()) + .includes(val.toLowerCase())) + return val + if (val === 'currentcolor') { + return 'currentcolor' + } return false } +function _getImage(border, i) { + const val = border[i] + + if (val.startsWith('url')) { + let url = val; + for (let j = 1; border[i + j]; j++) { + url += border[i + j] + } + url = /url\(("[^"]+"|'[^']+'|[^)]+)\)/g.exec(url) + url = url ? url[1] : false + url = url?.startsWith('"') || url?.startsWith("'") + ? url.substr(1, url.length - 2) + : url + return url + } +} + +function _getImageSize(border, i, element) { + const val = border[i] + + if (['cover', 'contain'].includes(val) || val.endsWith('%')) { + return val + } + unitCheck(val, htmlBackgroundSizeNotSvgError) + if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) + return toPx(element, val) +} + function _getOpacity(border, i) { let val = border[i] if (val.startsWith('rgba') || val.startsWith('hsla')) { @@ -46,10 +78,14 @@ function _getOpacity(border, i) { 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.') +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 htmlBackgroundSizeNotSvgError = + new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"')) -function unitCheck(length) { - if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw htmlLengthNotSvgError +function unitCheck(length, err) { + if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw err return length } @@ -61,15 +97,17 @@ function _getWidth(border, i, element) { if (val.toLowerCase() === 'thin') return 1 if (val.toLowerCase() === 'medium') return 3 if (val.toLowerCase() === 'thick') return 5 - unitCheck(val) + unitCheck(val, htmlBorderLengthNotSvgError) // 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 } -const getWidth = s => _getAttributeFromString(s, _getWidth), +const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el), + getImage = s => _getAttributeFromString(s, _getImage), + getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el), getColor = s => _getAttributeFromString(s, _getColor), getOpacity = s => _getAttributeFromString(s, _getOpacity) -export {getWidth, getColor, unitCheck, getOpacity} +export {getWidth, getImage, getImageSize, getColor, getOpacity} diff --git a/src/main.js b/src/main.js index 5891123..c828c70 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,8 @@ export default function RoundDiv({clip, style, children, ...props}) { const [width, setWidth] = useState(0) const [radius, setRadius] = useState(0) const [background, setBackground] = useState('transparent') + const [backgroundImage, setBackgroundImage] = useState('none') + const [backgroundImageSize, setBackgroundImageSize] = useState(null) const [backgroundOpacity, setBackgroundOpacity] = useState(0) const [borderColor, setBorderColor] = useState('transparent') const [borderWidth, setBorderWidth] = useState(0) @@ -30,6 +32,8 @@ export default function RoundDiv({clip, style, children, ...props}) { setWidth, setRadius, setBackground, + setBackgroundImage, + setBackgroundImageSize, setBackgroundOpacity, setBorderColor, setBorderWidth, @@ -47,12 +51,46 @@ export default function RoundDiv({clip, style, children, ...props}) { } divStyle.background = 'transparent' - divStyle.borderWidth = divStyle.borderWidth || '0' + divStyle.borderWidth = '0' divStyle.borderColor = 'transparent' + const [backgroundImageAspectRatio, setBackgroundImageAspectRatio] = useState(1) + const [backgroundImageHeight, setBackgroundImageHeight] = useState(0) + const [backgroundImageWidth, setBackgroundImageWidth] = useState(0) + useEffect(() => { + const img = new Image() + img.onload = () => { + setBackgroundImageAspectRatio(img.naturalWidth / img.naturalHeight) + setBackgroundImageHeight(img.naturalHeight) + setBackgroundImageWidth(img.naturalWidth) + } + img.src = backgroundImage + }, [backgroundImage, setBackgroundImageAspectRatio]) + + const fullHeight = height + borderWidth * 2, + fullWidth = width + borderWidth * 2 + + console.log(backgroundImageSize) + + const lengthCalculator = (isWidth) => { + if ((!isWidth && backgroundImageAspectRatio > 1) + || (isWidth && backgroundImageAspectRatio < 1) + || !backgroundImageSize + ) return undefined + + if (typeof backgroundImageSize === 'number') + return backgroundImageSize + + if (['cover', 'contain'].includes(backgroundImageSize)) + return fullHeight + + if (backgroundImageSize.endsWith('%')) + return (isWidth ? fullWidth : fullHeight) + * (Number(backgroundImageSize.replace('%', '')) / 100) + } + return
- + + + + - + diff --git a/src/style-sheet-watcher.js b/src/style-sheet-watcher.js index fda6eab..000d112 100644 --- a/src/style-sheet-watcher.js +++ b/src/style-sheet-watcher.js @@ -1,9 +1,6 @@ -let watchers = 0 const CSSChangeEvent = new CustomEvent('css-change'); export default function attachCSSWatcher(callback) { - watchers++ - console.log(watchers) CSSWatcher.addEventListener('css-change', () => callback()) } diff --git a/src/updateStates.js b/src/updateStates.js index 825a3b8..e379dcd 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,4 +1,4 @@ -import {getColor, getOpacity, getWidth, unitCheck} from "./css-utils"; +import {getColor, getImage, getImageSize, getOpacity, getWidth} from "./css-utils"; import getStyle from "./styles-extractor"; export default function updateStates(args) { @@ -9,6 +9,8 @@ export default function updateStates(args) { setWidth, setRadius, setBackground, + setBackgroundImage, + setBackgroundImageSize, setBackgroundOpacity, setBorderColor, setBorderWidth, @@ -19,40 +21,66 @@ export default function updateStates(args) { setHeight(boundingClientRect.height) setWidth(boundingClientRect.width) } - const divStyle = boundingClientRect ? window?.getComputedStyle(div.current) : null + + 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) => + (getStyle(camelise(key), div.current)?.overwritten || [])[n]?.value, + + getNthStyleAttrOrAlt = (...args) => { + args = Array.from(args) + let n = args.pop() + if (typeof n !== 'number') { + args.push(n) + n = 0 + } + + let a, b, c, d + if (args.length === 2) + [a, b, c, d] = [args[0], args[1], args[0], args[1]] + else if (args.length === 3) + [a, b, c, d] = [args[0], args[1], args[1], args[2]] + + return style ? style[camelise(a)] || style[camelise(b)] + : getNthStyle(c, n) || getNthStyle(d, n) + }, + getNthStyleAttr = (a, n) => + style ? style[camelise(a)] : getNthStyle(a, n) + + const divStyle = div.current ? window?.getComputedStyle(div.current) : null if (divStyle) { setRadius(Number( (divStyle.borderRadius || divStyle.borderTopLeftRadius) - .replace('px', ''))) + .replace('px', '')) + ) setBackground(getColor( - style?.background - || style?.backgroundColor - || (getStyle('background', div.current)?.overwritten || [])[1]?.value - || (getStyle('background-color', div.current)?.overwritten || [])[1]?.value + getNthStyleAttrOrAlt('background', 'background-color', 1) ) || 'transparent') + setBackgroundImage(getImage( + getNthStyleAttrOrAlt('background', 'background-image', 1) + ) || 'none') + setBackgroundImageSize(getImageSize( + getNthStyleAttr('background-size', 1) + ) || null) setBackgroundOpacity(getOpacity( - style?.background - || style?.backgroundColor - || (getStyle('background', div.current)?.overwritten || [])[1]?.value - || (getStyle('background-color', div.current)?.overwritten || [])[1]?.value + getNthStyleAttrOrAlt('background', 'background-color', 1) ) || 1) + setBorderColor(getColor( - style?.border - || style?.borderColor - || (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value - || (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value + getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) ) || '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) || 0) setBorderOpacity(getOpacity( - style?.border - || style?.borderColor - || (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value - || (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value + getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) ) || 1) + + setBorderWidth(getWidth( + getNthStyleAttrOrAlt('border', 'border-width', 'border-top-width', 0), + div.current + ) || 0) } } From 5a63e4e83aae4501a4d610146c00931e32a4aebe Mon Sep 17 00:00:00 2001 From: johannes Date: Sun, 27 Jun 2021 15:23:30 +0200 Subject: [PATCH 04/10] complete background image size support, basic repeat support --- .babelrc | 3 +- src/css-utils.js | 80 ++++++++++++++++++++++++++++++++------------- src/main.js | 76 ++++++++++++++++++++++++++++-------------- src/updateStates.js | 39 +++++++++------------- 4 files changed, 125 insertions(+), 73 deletions(-) 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/src/css-utils.js b/src/css-utils.js index ce2cfdb..b6d2a6e 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -1,6 +1,5 @@ import CSS_COLOR_NAMES from "./html-colors"; import toPx from "./css-length-converter"; -import {element} from 'prop-types' function _getAttributeFromString(string, method, ...data) { if (!string) return false @@ -12,16 +11,16 @@ function _getAttributeFromString(string, method, ...data) { } } -function _getColor(border, i) { - const val = border[i] +function _getColor(b, i) { + const val = b[i] // color is a hex code if (val.toLowerCase().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] + for (let j = 1; !b[i + j - 1].endsWith(')'); j++) { + color += b[i + j] } if (color[3] === 'a') color = color.replace('a', '').replace(/,[^),]+\)/, ')') @@ -37,13 +36,13 @@ function _getColor(border, i) { return false } -function _getImage(border, i) { - const val = border[i] +function _getImage(b, i) { + const val = b[i] if (val.startsWith('url')) { let url = val; - for (let j = 1; border[i + j]; j++) { - url += border[i + j] + for (let j = 1; b[i + j]; j++) { + url += b[i + j] } url = /url\(("[^"]+"|'[^']+'|[^)]+)\)/g.exec(url) url = url ? url[1] : false @@ -54,30 +53,56 @@ function _getImage(border, i) { } } -function _getImageSize(border, i, element) { - const val = border[i] +function _getImageSize(b, i, element) { + // "val" is always what is defined in backgrund-size ([i]th argument) + const val = b[i] - if (['cover', 'contain'].includes(val) || val.endsWith('%')) { - return val + if (['cover', 'contain'].includes(val)) { + return [val, null] } - unitCheck(val, htmlBackgroundSizeNotSvgError) - if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) - return toPx(element, val) + + const returnIfCSSNumeric = (val, throwErr) => { + if (val?.endsWith('%')) + return val + else if (val?.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) { + unitCheck(val, throwErr ? htmlBackgroundSizeNotSvgError : undefined) + return toPx(element, val) + } else + return null + } + + const convertedVal = returnIfCSSNumeric(val, true) // has null as fallback already + // "background-size: 50% 50%" is different to "background-size: 50%" + return [convertedVal, returnIfCSSNumeric(b[i + 1])] } -function _getOpacity(border, i) { - let val = border[i] +function _getOpacity(b, i) { + let val = b[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] + for (let j = 1; !b[i + j - 1].endsWith(')'); j++) { + val += b[i + j] } return val.replace(/(rgb|hsl)a?\(([^,)]+,){3}/, '').replace(/\)$/, '') } - if (border.length - 1 === i) + if (b.length - 1 === i) return 1 } +function _getRepeat(b, i) { + let val = b[i] + if (val === 'repeat-x') + return ['repeat', 'no-repeat'] + else if (val === 'repeat-y') + return ['no-repeat', 'repeat'] + else if (['repeat', 'space', 'round', 'no-repeat'].includes(val)) { + if (['repeat', 'space', 'round', 'no-repeat'].includes(b[i + 1] || '')) + return [val, b[i + 1]] + else + return [val, val] + } +} + 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"')) @@ -85,7 +110,9 @@ const htmlBackgroundSizeNotSvgError = new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"')) function unitCheck(length, err) { - if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw err + if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) + if (err) throw err + else return false return length } @@ -104,10 +131,17 @@ function _getWidth(border, i, element) { return false } +/** @returns {number} */ const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el), + /** @returns {string} */ getImage = s => _getAttributeFromString(s, _getImage), + /** @returns {Array} */ getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el), + /** @returns {string} */ getColor = s => _getAttributeFromString(s, _getColor), + /** @returns {Array} */ + getRepeat = s => _getAttributeFromString(s, _getRepeat), + /** @returns {number} */ getOpacity = s => _getAttributeFromString(s, _getOpacity) -export {getWidth, getImage, getImageSize, getColor, getOpacity} +export {getWidth, getImage, getImageSize, getColor, getRepeat, getOpacity} diff --git a/src/main.js b/src/main.js index c828c70..4fda3b1 100644 --- a/src/main.js +++ b/src/main.js @@ -6,13 +6,18 @@ import ShadowRoot from "./react-shadow-dom" import attachCSSWatcher from './style-sheet-watcher' export default function RoundDiv({clip, style, children, ...props}) { + // welcome to react states hell const [height, setHeight] = useState(0) const [width, setWidth] = useState(0) const [radius, setRadius] = useState(0) + const [background, setBackground] = useState('transparent') const [backgroundImage, setBackgroundImage] = useState('none') - const [backgroundImageSize, setBackgroundImageSize] = useState(null) + // todo: background size two values (from css) + const [backgroundImageSize, setBackgroundImageSize] = useState([null, null]) const [backgroundOpacity, setBackgroundOpacity] = useState(0) + const [backgroundRepeat, setBackgroundRepeat] = useState(['repeat', 'repeat']) + const [borderColor, setBorderColor] = useState('transparent') const [borderWidth, setBorderWidth] = useState(0) const [borderOpacity, setBorderOpacity] = useState(1) @@ -35,6 +40,7 @@ export default function RoundDiv({clip, style, children, ...props}) { setBackgroundImage, setBackgroundImageSize, setBackgroundOpacity, + setBackgroundRepeat, setBorderColor, setBorderWidth, setBorderOpacity @@ -55,14 +61,14 @@ export default function RoundDiv({clip, style, children, ...props}) { divStyle.borderColor = 'transparent' const [backgroundImageAspectRatio, setBackgroundImageAspectRatio] = useState(1) - const [backgroundImageHeight, setBackgroundImageHeight] = useState(0) - const [backgroundImageWidth, setBackgroundImageWidth] = useState(0) + // const [backgroundImageHeight, setBackgroundImageHeight] = useState(0) + // const [backgroundImageWidth, setBackgroundImageWidth] = useState(0) useEffect(() => { const img = new Image() img.onload = () => { setBackgroundImageAspectRatio(img.naturalWidth / img.naturalHeight) - setBackgroundImageHeight(img.naturalHeight) - setBackgroundImageWidth(img.naturalWidth) + // setBackgroundImageHeight(img.naturalHeight) + // setBackgroundImageWidth(img.naturalWidth) } img.src = backgroundImage }, [backgroundImage, setBackgroundImageAspectRatio]) @@ -70,25 +76,41 @@ export default function RoundDiv({clip, style, children, ...props}) { const fullHeight = height + borderWidth * 2, fullWidth = width + borderWidth * 2 - console.log(backgroundImageSize) - const lengthCalculator = (isWidth) => { - if ((!isWidth && backgroundImageAspectRatio > 1) - || (isWidth && backgroundImageAspectRatio < 1) - || !backgroundImageSize - ) return undefined + let n = isWidth ? 0 : 1 + + if (backgroundImageSize[0] === 'contain') + if (backgroundImageAspectRatio > 1) + return isWidth ? width : (height / backgroundImageAspectRatio) + else + return isWidth ? (width * backgroundImageAspectRatio) : height - if (typeof backgroundImageSize === 'number') - return backgroundImageSize + if (['cover', 'contain'].includes(backgroundImageSize[0])) + return isWidth ? width : height - if (['cover', 'contain'].includes(backgroundImageSize)) - return fullHeight + if (backgroundImageSize[n] === null && !!backgroundImageSize[0]) + return lengthCalculator(true) * + (backgroundImageAspectRatio < 1 + ? 1 / backgroundImageAspectRatio + : backgroundImageAspectRatio + ) - if (backgroundImageSize.endsWith('%')) - return (isWidth ? fullWidth : fullHeight) - * (Number(backgroundImageSize.replace('%', '')) / 100) + if (!backgroundImageSize[n]) + return undefined + + if (backgroundImageSize[n]?.endsWith('%')) + return width * (Number(backgroundImageSize[n].replace('%', '')) / 100) + + if (typeof backgroundImageSize[n] === 'number') + return backgroundImageSize[n] } + const imageHeight = lengthCalculator(false), + imageWidth = lengthCalculator(true), + preserveImageAspectRatio = ( + ['cover', 'contain'].includes(backgroundImageSize[0]) + ) + return
- + {/* todo: support for "repeat: space" and "repeat: round" */} - + width={backgroundRepeat[0] === 'no-repeat' ? fullWidth : imageWidth} + height={backgroundRepeat[1] === 'no-repeat' ? fullHeight : imageHeight} + x={borderWidth} y={borderWidth}> + diff --git a/src/updateStates.js b/src/updateStates.js index e379dcd..3320b00 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,21 +1,8 @@ -import {getColor, getImage, getImageSize, getOpacity, getWidth} from "./css-utils"; +import {getColor, getImage, getImageSize, getOpacity, getRepeat, getWidth} from "./css-utils"; import getStyle from "./styles-extractor"; export default function updateStates(args) { - const { - div, - style, - setHeight, - setWidth, - setRadius, - setBackground, - setBackgroundImage, - setBackgroundImageSize, - setBackgroundOpacity, - setBorderColor, - setBorderWidth, - setBorderOpacity - } = args + const {div, style, setHeight, setWidth} = args const boundingClientRect = div.current?.getBoundingClientRect() if (boundingClientRect) { setHeight(boundingClientRect.height) @@ -54,31 +41,35 @@ export default function updateStates(args) { const divStyle = div.current ? window?.getComputedStyle(div.current) : null if (divStyle) { - setRadius(Number( + let states = args + states.setRadius(Number( (divStyle.borderRadius || divStyle.borderTopLeftRadius) .replace('px', '')) ) - setBackground(getColor( + states.setBackground(getColor( getNthStyleAttrOrAlt('background', 'background-color', 1) ) || 'transparent') - setBackgroundImage(getImage( + states.setBackgroundImage(getImage( getNthStyleAttrOrAlt('background', 'background-image', 1) ) || 'none') - setBackgroundImageSize(getImageSize( + states.setBackgroundImageSize(getImageSize( getNthStyleAttr('background-size', 1) - ) || null) - setBackgroundOpacity(getOpacity( + ) || [null, null]) + states.setBackgroundOpacity(getOpacity( getNthStyleAttrOrAlt('background', 'background-color', 1) ) || 1) + states.setBackgroundRepeat(getRepeat( + getNthStyleAttrOrAlt('background', 'background-repeat', 1) + ) || ['repeat', 'repeat']) - setBorderColor(getColor( + states.setBorderColor(getColor( getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) ) || 'transparent') - setBorderOpacity(getOpacity( + states.setBorderOpacity(getOpacity( getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) ) || 1) - setBorderWidth(getWidth( + states.setBorderWidth(getWidth( getNthStyleAttrOrAlt('border', 'border-width', 'border-top-width', 0), div.current ) || 0) From 640d1b414e4904cd51d5c0e6d0bf0e74af2943a0 Mon Sep 17 00:00:00 2001 From: johannes Date: Fri, 9 Jul 2021 18:09:30 +0200 Subject: [PATCH 05/10] add getting background position --- src/css-utils.js | 51 ++++++++++++++++++++++++++++++++++++++++----- src/main.js | 2 ++ src/updateStates.js | 5 ++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/css-utils.js b/src/css-utils.js index b6d2a6e..3a7b4c3 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -11,6 +11,10 @@ function _getAttributeFromString(string, method, ...data) { } } +// eslint-disable-next-line no-extend-native +String.prototype.matchesValidCSSLength = () => + this.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/) + function _getColor(b, i) { const val = b[i] // color is a hex code @@ -61,17 +65,50 @@ function _getImageSize(b, i, element) { return [val, null] } + return __getTwoNumerics(b, i, element, htmlBackgroundSizeNotSvgError) +} + +function _getPosition(b, i, element) { + // "val" is always what is defined in backgrund-size ([i]th argument) + const val = b[i] + const allWords = ['top', 'bottom', 'left', 'right', 'center'] + + if (b.length === 1 && allWords.includes(val)) { + if (val === 'center') + return ['center', 'center'] + else if (['left', 'right'].includes(val)) + return [val, 0] + else if (['top', 'bottom'].includes(val)) + return [0, val] + } + + const a = [0, 0] + + if (allWords.includes(val)) { + if (b[i + 1].matchesValidCSSLength()) { + + } + } + + /*if (['cover', 'contain'].includes(val)) { + return [val, null] + }*/ + + return __getTwoNumerics(b, i, element, htmlBackgroundPositionNotSvgError) +} + +function __getTwoNumerics(b, i, element, err) { const returnIfCSSNumeric = (val, throwErr) => { if (val?.endsWith('%')) return val - else if (val?.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) { - unitCheck(val, throwErr ? htmlBackgroundSizeNotSvgError : undefined) + else if (val?.matchesValidCSSLength()) { + unitCheck(val, throwErr ? err : undefined) return toPx(element, val) } else return null } - const convertedVal = returnIfCSSNumeric(val, true) // has null as fallback already + const convertedVal = returnIfCSSNumeric(b[i], true) // has null as fallback already // "background-size: 50% 50%" is different to "background-size: 50%" return [convertedVal, returnIfCSSNumeric(b[i + 1])] } @@ -108,6 +145,8 @@ const htmlBorderLengthNotSvgError = new Error(htmlLengthNotSvgErrorTemplate('border lengths', '"thin", "medium", "thick"')) const htmlBackgroundSizeNotSvgError = new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"')) +const htmlBackgroundPositionNotSvgError = + new Error(htmlLengthNotSvgErrorTemplate('background position', '"top", "bottom", "left", "right", "center"')) function unitCheck(length, err) { if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) @@ -126,7 +165,7 @@ function _getWidth(border, i, element) { if (val.toLowerCase() === 'thick') return 5 unitCheck(val, htmlBorderLengthNotSvgError) // width is - if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) + if (val.matchesValidCSSLength()) return toPx(element, val) return false } @@ -137,6 +176,8 @@ const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el), getImage = s => _getAttributeFromString(s, _getImage), /** @returns {Array} */ getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el), + /** @returns {Array} */ + getPosition = (s, el) => _getAttributeFromString(s, _getPosition, el), /** @returns {string} */ getColor = s => _getAttributeFromString(s, _getColor), /** @returns {Array} */ @@ -144,4 +185,4 @@ const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el), /** @returns {number} */ getOpacity = s => _getAttributeFromString(s, _getOpacity) -export {getWidth, getImage, getImageSize, getColor, getRepeat, getOpacity} +export {getWidth, getImage, getImageSize, getPosition, getColor, getRepeat, getOpacity} diff --git a/src/main.js b/src/main.js index 4fda3b1..63d05d2 100644 --- a/src/main.js +++ b/src/main.js @@ -15,6 +15,7 @@ export default function RoundDiv({clip, style, children, ...props}) { const [backgroundImage, setBackgroundImage] = useState('none') // todo: background size two values (from css) const [backgroundImageSize, setBackgroundImageSize] = useState([null, null]) + const [backgroundPosition, setBackgroundPosition] = useState([0, 0]) const [backgroundOpacity, setBackgroundOpacity] = useState(0) const [backgroundRepeat, setBackgroundRepeat] = useState(['repeat', 'repeat']) @@ -39,6 +40,7 @@ export default function RoundDiv({clip, style, children, ...props}) { setBackground, setBackgroundImage, setBackgroundImageSize, + setBackgroundPosition, setBackgroundOpacity, setBackgroundRepeat, setBorderColor, diff --git a/src/updateStates.js b/src/updateStates.js index 3320b00..b283b6c 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,4 +1,4 @@ -import {getColor, getImage, getImageSize, getOpacity, getRepeat, getWidth} from "./css-utils"; +import {getColor, getImage, getImageSize, getPosition, getOpacity, getRepeat, getWidth} from "./css-utils"; import getStyle from "./styles-extractor"; export default function updateStates(args) { @@ -55,6 +55,9 @@ export default function updateStates(args) { states.setBackgroundImageSize(getImageSize( getNthStyleAttr('background-size', 1) ) || [null, null]) + states.setBackgroundImageSize(getPosition( + getNthStyleAttr('background-size', 1) + ) || [null, null]) states.setBackgroundOpacity(getOpacity( getNthStyleAttrOrAlt('background', 'background-color', 1) ) || 1) From cd884e7d5597d2acf06c5fa15d90f1d08408cbc0 Mon Sep 17 00:00:00 2001 From: johannes Date: Sun, 18 Jul 2021 20:55:39 +0200 Subject: [PATCH 06/10] use clip-path add different sided borders --- src/css-utils.js | 199 ++++++++------------------------------------ src/main.js | 166 +++++++++++++++--------------------- src/updateStates.js | 85 ++++++++----------- 3 files changed, 141 insertions(+), 309 deletions(-) diff --git a/src/css-utils.js b/src/css-utils.js index 3a7b4c3..90ae3a8 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -1,188 +1,61 @@ import CSS_COLOR_NAMES from "./html-colors"; import toPx from "./css-length-converter"; -function _getAttributeFromString(string, method, ...data) { - if (!string) return false - string = string.split(' ') - for (let i in string) { - if (!string.hasOwnProperty(i)) continue - const res = method(string, Number(i), ...data) - if (res) return res - } -} - -// eslint-disable-next-line no-extend-native -String.prototype.matchesValidCSSLength = () => - this.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/) - -function _getColor(b, i) { - const val = b[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; !b[i + j - 1].endsWith(')'); j++) { - color += b[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()) + else if (CSS_COLOR_NAMES.map(color => color.toLowerCase()) .includes(val.toLowerCase())) return val - if (val === 'currentcolor') { + else if (val === 'currentcolor') { return 'currentcolor' - } - return false -} - -function _getImage(b, i) { - const val = b[i] - - if (val.startsWith('url')) { - let url = val; - for (let j = 1; b[i + j]; j++) { - url += b[i + j] - } - url = /url\(("[^"]+"|'[^']+'|[^)]+)\)/g.exec(url) - url = url ? url[1] : false - url = url?.startsWith('"') || url?.startsWith("'") - ? url.substr(1, url.length - 2) - : url - return url - } -} - -function _getImageSize(b, i, element) { - // "val" is always what is defined in backgrund-size ([i]th argument) - const val = b[i] - - if (['cover', 'contain'].includes(val)) { - return [val, null] - } - - return __getTwoNumerics(b, i, element, htmlBackgroundSizeNotSvgError) -} - -function _getPosition(b, i, element) { - // "val" is always what is defined in backgrund-size ([i]th argument) - const val = b[i] - const allWords = ['top', 'bottom', 'left', 'right', 'center'] - - if (b.length === 1 && allWords.includes(val)) { - if (val === 'center') - return ['center', 'center'] - else if (['left', 'right'].includes(val)) - return [val, 0] - else if (['top', 'bottom'].includes(val)) - return [0, val] - } - - const a = [0, 0] - - if (allWords.includes(val)) { - if (b[i + 1].matchesValidCSSLength()) { - - } - } - - /*if (['cover', 'contain'].includes(val)) { - return [val, null] - }*/ - - return __getTwoNumerics(b, i, element, htmlBackgroundPositionNotSvgError) + } else return '#000' } -function __getTwoNumerics(b, i, element, err) { - const returnIfCSSNumeric = (val, throwErr) => { - if (val?.endsWith('%')) - return val - else if (val?.matchesValidCSSLength()) { - unitCheck(val, throwErr ? err : undefined) - return toPx(element, val) - } else - return null - } - - const convertedVal = returnIfCSSNumeric(b[i], true) // has null as fallback already - // "background-size: 50% 50%" is different to "background-size: 50%" - return [convertedVal, returnIfCSSNumeric(b[i + 1])] -} - -function _getOpacity(b, i) { - let val = b[i] - if (val.startsWith('rgba') || val.startsWith('hsla')) { - if (!val.endsWith(')')) - for (let j = 1; !b[i + j - 1].endsWith(')'); j++) { - val += b[i + j] - } - return val.replace(/(rgb|hsl)a?\(([^,)]+,){3}/, '').replace(/\)$/, '') - } - if (b.length - 1 === i) - return 1 -} - -function _getRepeat(b, i) { - let val = b[i] - if (val === 'repeat-x') - return ['repeat', 'no-repeat'] - else if (val === 'repeat-y') - return ['no-repeat', 'repeat'] - else if (['repeat', 'space', 'round', 'no-repeat'].includes(val)) { - if (['repeat', 'space', 'round', 'no-repeat'].includes(b[i + 1] || '')) - return [val, b[i + 1]] - else - return [val, val] - } +/** @returns {number} */ +function convertColorOpacity(val) { + if (val?.startsWith('rgba') || val?.startsWith('hsla')) { + return Number(val.match(/(\d*\.?\d+)?\)$/)[1]) + } else return 1 } 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 htmlBackgroundSizeNotSvgError = - new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"')) -const htmlBackgroundPositionNotSvgError = - new Error(htmlLengthNotSvgErrorTemplate('background position', '"top", "bottom", "left", "right", "center"')) -function unitCheck(length, err) { - if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) +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 - return length + 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, htmlBorderLengthNotSvgError) + if (val?.toLowerCase() === 'thin') + return 1 + else if (val?.toLowerCase() === 'medium') + return 3 + else if (val?.toLowerCase() === 'thick') + return 5 // width is - if (val.matchesValidCSSLength()) - return toPx(element, val) - return false + else + return toNumber(val, element, htmlBorderLengthNotSvgError) || 0 } -/** @returns {number} */ -const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el), - /** @returns {string} */ - getImage = s => _getAttributeFromString(s, _getImage), - /** @returns {Array} */ - getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el), - /** @returns {Array} */ - getPosition = (s, el) => _getAttributeFromString(s, _getPosition, el), - /** @returns {string} */ - getColor = s => _getAttributeFromString(s, _getColor), - /** @returns {Array} */ - getRepeat = s => _getAttributeFromString(s, _getRepeat), - /** @returns {number} */ - getOpacity = s => _getAttributeFromString(s, _getOpacity) - -export {getWidth, getImage, getImageSize, getPosition, getColor, getRepeat, getOpacity} +export {convertPlainColor, convertColorOpacity, convertBorderWidth} diff --git a/src/main.js b/src/main.js index 63d05d2..df7e21d 100644 --- a/src/main.js +++ b/src/main.js @@ -11,17 +11,11 @@ export default function RoundDiv({clip, style, children, ...props}) { const [width, setWidth] = useState(0) const [radius, setRadius] = useState(0) - const [background, setBackground] = useState('transparent') - const [backgroundImage, setBackgroundImage] = useState('none') - // todo: background size two values (from css) - const [backgroundImageSize, setBackgroundImageSize] = useState([null, null]) - const [backgroundPosition, setBackgroundPosition] = useState([0, 0]) - const [backgroundOpacity, setBackgroundOpacity] = useState(0) - const [backgroundRepeat, setBackgroundRepeat] = useState(['repeat', 'repeat']) - - const [borderColor, setBorderColor] = useState('transparent') - const [borderWidth, setBorderWidth] = useState(0) - const [borderOpacity, setBorderOpacity] = useState(1) + const [borderColor, setBorderColor] = useState(Array(4).fill('transparent')) + const [borderOpacity, setBorderOpacity] = useState(Array(4).fill(1)) + const [borderWidth, setBorderWidth] = useState(Array(4).fill(0)) + + const [isFlex, setIsFlex] = useState(false) const div = useRef() @@ -37,15 +31,10 @@ export default function RoundDiv({clip, style, children, ...props}) { setHeight, setWidth, setRadius, - setBackground, - setBackgroundImage, - setBackgroundImageSize, - setBackgroundPosition, - setBackgroundOpacity, - setBackgroundRepeat, setBorderColor, setBorderWidth, - setBorderOpacity + setBorderOpacity, + setIsFlex }), [style]) useEffect(updateStatesWithArgs, [div, clip, style, updateStatesWithArgs]) @@ -54,64 +43,59 @@ export default function RoundDiv({clip, style, children, ...props}) { attachCSSWatcher(() => updateStatesWithArgs()) }, [updateStatesWithArgs]) - const divStyle = { - ...style + const path = generateSvgSquircle(height, width, radius, clip) + const maxBorderWidth = Math.max(...borderWidth) + const diffBorderMaxWidthLeftWidth = maxBorderWidth - borderWidth[3] + const diffBorderMaxWidthTopWidth = maxBorderWidth - borderWidth[0] + // const verticalBorderWidthDelta = maxWidth - borderWidth[2]; + // const horizontalBorderWidthDelta = maxWidth - borderWidth[1]; + const borderPath = generateSvgSquircle( + height + diffBorderMaxWidthTopWidth + (maxBorderWidth - borderWidth[2]), + width + diffBorderMaxWidthLeftWidth + (maxBorderWidth - borderWidth[1]), + radius, + clip) + + const maskPointsA = { + to: -borderWidth[0], + ti: borderWidth[0] * 2, + ro: width + borderWidth[1], + ri: width - borderWidth[1] * 2, + bo: height + borderWidth[2], + bi: height - borderWidth[2] * 2, + lo: -borderWidth[3], + li: borderWidth[3] * 2, } - divStyle.background = 'transparent' - divStyle.borderWidth = '0' - divStyle.borderColor = 'transparent' + const maskPoints = { + tlo: maskPointsA.lo + ',' + maskPointsA.to, + tli: maskPointsA.li + ',' + maskPointsA.ti, + tro: maskPointsA.ro + ',' + maskPointsA.to, + tri: maskPointsA.ri + ',' + maskPointsA.ti, + blo: maskPointsA.lo + ',' + maskPointsA.bo, + bli: maskPointsA.li + ',' + maskPointsA.bi, + bro: maskPointsA.ro + ',' + maskPointsA.bo, + bri: maskPointsA.ri + ',' + maskPointsA.bi, + } - const [backgroundImageAspectRatio, setBackgroundImageAspectRatio] = useState(1) - // const [backgroundImageHeight, setBackgroundImageHeight] = useState(0) - // const [backgroundImageWidth, setBackgroundImageWidth] = useState(0) - useEffect(() => { - const img = new Image() - img.onload = () => { - setBackgroundImageAspectRatio(img.naturalWidth / img.naturalHeight) - // setBackgroundImageHeight(img.naturalHeight) - // setBackgroundImageWidth(img.naturalWidth) - } - img.src = backgroundImage - }, [backgroundImage, setBackgroundImageAspectRatio]) - - const fullHeight = height + borderWidth * 2, - fullWidth = width + borderWidth * 2 - - const lengthCalculator = (isWidth) => { - let n = isWidth ? 0 : 1 - - if (backgroundImageSize[0] === 'contain') - if (backgroundImageAspectRatio > 1) - return isWidth ? width : (height / backgroundImageAspectRatio) - else - return isWidth ? (width * backgroundImageAspectRatio) : height - - if (['cover', 'contain'].includes(backgroundImageSize[0])) - return isWidth ? width : height - - if (backgroundImageSize[n] === null && !!backgroundImageSize[0]) - return lengthCalculator(true) * - (backgroundImageAspectRatio < 1 - ? 1 / backgroundImageAspectRatio - : backgroundImageAspectRatio - ) - - if (!backgroundImageSize[n]) - return undefined - - if (backgroundImageSize[n]?.endsWith('%')) - return width * (Number(backgroundImageSize[n].replace('%', '')) / 100) - - if (typeof backgroundImageSize[n] === 'number') - return backgroundImageSize[n] + const maskPaths = { + top: `M${maskPoints.tli}H${maskPointsA.ri}L${maskPoints.tro}H${maskPointsA.lo}Z`, + right: `M${maskPoints.tri}V${maskPointsA.bi}L${maskPoints.bro}V${maskPointsA.to}Z`, + bottom: `M${maskPoints.bri}H${maskPointsA.li}L${maskPoints.blo}H${maskPointsA.ro}Z`, + left: `M${maskPoints.bli}V${maskPointsA.ti}L${maskPoints.tlo}V${maskPointsA.bo}Z`, } - const imageHeight = lengthCalculator(false), - imageWidth = lengthCalculator(true), - preserveImageAspectRatio = ( - ['cover', 'contain'].includes(backgroundImageSize[0]) - ) + // console.log(borderWidth, borderColor, borderOpacity) + // console.log(isFlex) + + const svgTransform = isFlex + ? `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,${(borderWidth[2] - borderWidth[0]) / 2}px)` + : `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,-${borderWidth[0]}px)` + + const divStyle = { + ...style, + clipPath: `path("${path}")`, + borderColor: 'transparent' + } return
@@ -120,35 +104,23 @@ export default function RoundDiv({clip, style, children, ...props}) { height, width: 1, overflow: 'visible', - zIndex: -1 + zIndex: -1, + transform: svgTransform }} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}> - - {/* todo: support for "repeat: space" and "repeat: round" */} - - - - - - - + {Object.keys(maskPaths).map(key => ( + + + + ))} + - - + {Object.keys(maskPaths).map((key, i) => ( + + ))} diff --git a/src/updateStates.js b/src/updateStates.js index b283b6c..aaa16a4 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,8 +1,8 @@ -import {getColor, getImage, getImageSize, getPosition, getOpacity, getRepeat, getWidth} from "./css-utils"; +import {convertPlainColor, convertColorOpacity, convertBorderWidth} from "./css-utils"; import getStyle from "./styles-extractor"; export default function updateStates(args) { - const {div, style, setHeight, setWidth} = args + const {div, setHeight, setWidth} = args const boundingClientRect = div.current?.getBoundingClientRect() if (boundingClientRect) { setHeight(boundingClientRect.height) @@ -16,28 +16,25 @@ export default function updateStates(args) { }).replace(/-/g, ''); } - const getNthStyle = (key, n) => - (getStyle(camelise(key), div.current)?.overwritten || [])[n]?.value, + const getNthStyle = (key, n) => { + const returnNthOverwrittenOrCurrent = r => + !r ? false : + r?.overwritten.length > 1 + ? r.overwritten[n ?? 0].value + : r.current?.value - getNthStyleAttrOrAlt = (...args) => { - args = Array.from(args) - let n = args.pop() - if (typeof n !== 'number') { - args.push(n) - n = 0 - } + const normal = getStyle(key, div.current); + const camelised = getStyle(camelise(key), div.current) - let a, b, c, d - if (args.length === 2) - [a, b, c, d] = [args[0], args[1], args[0], args[1]] - else if (args.length === 3) - [a, b, c, d] = [args[0], args[1], args[1], args[2]] + return returnNthOverwrittenOrCurrent(normal) || returnNthOverwrittenOrCurrent(camelised) + }; - return style ? style[camelise(a)] || style[camelise(b)] - : getNthStyle(c, n) || getNthStyle(d, n) - }, - getNthStyleAttr = (a, n) => - style ? style[camelise(a)] : getNthStyle(a, n) + 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 divStyle = div.current ? window?.getComputedStyle(div.current) : null if (divStyle) { @@ -46,35 +43,25 @@ export default function updateStates(args) { (divStyle.borderRadius || divStyle.borderTopLeftRadius) .replace('px', '')) ) - states.setBackground(getColor( - getNthStyleAttrOrAlt('background', 'background-color', 1) - ) || 'transparent') - states.setBackgroundImage(getImage( - getNthStyleAttrOrAlt('background', 'background-image', 1) - ) || 'none') - states.setBackgroundImageSize(getImageSize( - getNthStyleAttr('background-size', 1) - ) || [null, null]) - states.setBackgroundImageSize(getPosition( - getNthStyleAttr('background-size', 1) - ) || [null, null]) - states.setBackgroundOpacity(getOpacity( - getNthStyleAttrOrAlt('background', 'background-color', 1) - ) || 1) - states.setBackgroundRepeat(getRepeat( - getNthStyleAttrOrAlt('background', 'background-repeat', 1) - ) || ['repeat', 'repeat']) - states.setBorderColor(getColor( - getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) - ) || 'transparent') - states.setBorderOpacity(getOpacity( - getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1) - ) || 1) + // get color + states.setBorderColor( + getBorderStyles('color', 1) + .map(s => convertPlainColor(s)) + ) + // get alpha value of color + states.setBorderOpacity( + getBorderStyles('color', 1) + .map(s => convertColorOpacity(s)) + ) - states.setBorderWidth(getWidth( - getNthStyleAttrOrAlt('border', 'border-width', 'border-top-width', 0), - div.current - ) || 0) + states.setBorderWidth( + getBorderStyles('width', 0) + .map(s => convertBorderWidth(s, div.current)) + ) + + states.setIsFlex( + getNthStyle('display', 0)?.endsWith('flex') || false + ) } } From 14cbba6d1ee52024a753c83b4796d634b300a63c Mon Sep 17 00:00:00 2001 From: johannes Date: Sat, 7 Aug 2021 21:31:11 +0200 Subject: [PATCH 07/10] masks for borders --- src/css-utils.js | 8 +++-- src/generator.js | 72 +++++++++++++++++++++++++++---------------- src/main.js | 54 ++++++++++---------------------- src/mask-generator.js | 33 ++++++++++++++++++++ src/updateStates.js | 29 ++++++++++++----- 5 files changed, 120 insertions(+), 76 deletions(-) create mode 100644 src/mask-generator.js diff --git a/src/css-utils.js b/src/css-utils.js index 90ae3a8..94d9c7c 100644 --- a/src/css-utils.js +++ b/src/css-utils.js @@ -1,5 +1,5 @@ -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"; /** @returns {string} */ function convertPlainColor(val) { @@ -31,6 +31,8 @@ function convertColorOpacity(val) { 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 @@ -58,4 +60,4 @@ function convertBorderWidth(val, element) { return toNumber(val, element, htmlBorderLengthNotSvgError) || 0 } -export {convertPlainColor, convertColorOpacity, convertBorderWidth} +export {convertPlainColor, convertColorOpacity, convertBorderWidth, toNumber, htmlBorderRadiusNotSvgError} diff --git a/src/generator.js b/src/generator.js index fea6abf..081d7a7 100644 --- a/src/generator.js +++ b/src/generator.js @@ -1,22 +1,36 @@ -export default function generateSvgSquircle(height, width, radius, clip) { +/** + * @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]; + 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); - radius = clip === false - ? Number(radius) - : Math.min(Number(radius), height / 2, width / 2); + radius = radius.map(radius => Math.min(Number(radius), height / 2, width / 2)) - const [a0x, a1x, a2x, a3y, a3x, b1y, b1x] = Array(7).fill(0).map((a, i) => radius * ratios[i]), + 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 (isNaN(radius)) throw new Error(`'radius' 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), + const a0xF = x => Math.min(x / 2, a0x[0]), a0xw = a0xF(width), a0xh = a0xF(height) @@ -24,54 +38,58 @@ export default function generateSvgSquircle(height, width, radius, clip) { return (number - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } + const maxRadius = Math.max(...radius); + const yOffsetF = (x) => Math.max(0, Math.min( - mapRange(radius, (x / 2) * .90, x / 2, 0, 1), + mapRange(maxRadius, (x / 2) * .90, x / 2, 0, 1), 1 - )) * 1.7, - hyOffset = clip !== false ? yOffsetF(height) : 0, - wyOffset = clip !== false ? yOffsetF(width) : 0 + )) * 200 / maxRadius, + hyOffset = yOffsetF(height), + wyOffset = yOffsetF(width) + + console.log(hyOffset, wyOffset) const startPoint = `${a0xw},${wyOffset}` return `M${startPoint} - ${width / 2 < a0x && clip !== false + ${width / 2 < a0x[1] ? '' : `L${width - a0xw},0` } - C${width - a1x},0,${width - a2x},0,${width - a3x},${a3y} - C${width - b1x},${b1y},${width - b1y},${b1x},${width - b0y},${b0x} - C${width},${a2x},${width},${a1x}, + 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 && clip !== false + ${height / 2 < a0x[2] ? '' : `L${width},${height - a0xh}` } - C${width},${height - a1x},${width},${height - a2x},${width - a3y},${height - a3x} - C${width - b1y},${height - b1x},${width - b1x},${height - b1y},${width - b0x},${height - b0y} - C${width - a2x},${height},${width - a1x},${height}, + 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 && clip !== false + ${width / 2 < a0x[3] ? '' : `L${a0xw},${height}` } - C${a1x},${height},${a2x},${height},${a3x},${height - a3y} - C${b1x},${height - b1y},${b1y},${height - b1x},${b0y},${height - b0x} - C0,${height - a2x},0,${height - a1x}, + 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 && clip !== false + ${height / 2 < a0x[0] ? '' : `L0,${a0xh}` } - C0,${a1x},0,${a2x},${a3y},${a3x} - C${b1y},${b1x},${b1x},${b1y},${b0x},${b0y} - C${a2x},0,${a1x},0,${startPoint} + 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, ''); } diff --git a/src/main.js b/src/main.js index df7e21d..d1b2e2e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,15 +1,16 @@ import React, {useRef, useEffect, useState, useCallback} from 'react' import generateSvgSquircle from './generator' -import './getMatchedCSSRules-polyfill' +import './external/getMatchedCSSRules-polyfill' import updateStates from "./updateStates" -import ShadowRoot from "./react-shadow-dom" -import attachCSSWatcher from './style-sheet-watcher' +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 [height, setHeight] = useState(0) const [width, setWidth] = useState(0) - const [radius, setRadius] = useState(0) + const [radius, setRadius] = useState(Array(4).fill(0)) const [borderColor, setBorderColor] = useState(Array(4).fill('transparent')) const [borderOpacity, setBorderOpacity] = useState(Array(4).fill(1)) @@ -37,13 +38,13 @@ export default function RoundDiv({clip, style, children, ...props}) { setIsFlex }), [style]) - useEffect(updateStatesWithArgs, [div, clip, style, updateStatesWithArgs]) + useEffect(updateStatesWithArgs, [div, style, updateStatesWithArgs]) useEffect(() => { attachCSSWatcher(() => updateStatesWithArgs()) }, [updateStatesWithArgs]) - const path = generateSvgSquircle(height, width, radius, clip) + const path = generateSvgSquircle(height, width, radius) const maxBorderWidth = Math.max(...borderWidth) const diffBorderMaxWidthLeftWidth = maxBorderWidth - borderWidth[3] const diffBorderMaxWidthTopWidth = maxBorderWidth - borderWidth[0] @@ -52,37 +53,14 @@ export default function RoundDiv({clip, style, children, ...props}) { const borderPath = generateSvgSquircle( height + diffBorderMaxWidthTopWidth + (maxBorderWidth - borderWidth[2]), width + diffBorderMaxWidthLeftWidth + (maxBorderWidth - borderWidth[1]), - radius, - clip) - - const maskPointsA = { - to: -borderWidth[0], - ti: borderWidth[0] * 2, - ro: width + borderWidth[1], - ri: width - borderWidth[1] * 2, - bo: height + borderWidth[2], - bi: height - borderWidth[2] * 2, - lo: -borderWidth[3], - li: borderWidth[3] * 2, - } - - const maskPoints = { - tlo: maskPointsA.lo + ',' + maskPointsA.to, - tli: maskPointsA.li + ',' + maskPointsA.ti, - tro: maskPointsA.ro + ',' + maskPointsA.to, - tri: maskPointsA.ri + ',' + maskPointsA.ti, - blo: maskPointsA.lo + ',' + maskPointsA.bo, - bli: maskPointsA.li + ',' + maskPointsA.bi, - bro: maskPointsA.ro + ',' + maskPointsA.bo, - bri: maskPointsA.ri + ',' + maskPointsA.bi, - } - - const maskPaths = { - top: `M${maskPoints.tli}H${maskPointsA.ri}L${maskPoints.tro}H${maskPointsA.lo}Z`, - right: `M${maskPoints.tri}V${maskPointsA.bi}L${maskPoints.bro}V${maskPointsA.to}Z`, - bottom: `M${maskPoints.bri}H${maskPointsA.li}L${maskPoints.blo}H${maskPointsA.ro}Z`, - left: `M${maskPoints.bli}V${maskPointsA.ti}L${maskPoints.tlo}V${maskPointsA.bo}Z`, - } + [ + radius[0] + maxBorderWidth - Math.min(borderWidth[3], borderWidth[0]), + radius[1] + maxBorderWidth - Math.min(borderWidth[0], borderWidth[1]), + radius[2] + maxBorderWidth - Math.min(borderWidth[1], borderWidth[2]), + radius[3] + maxBorderWidth - Math.min(borderWidth[2], borderWidth[3]) + ]) + + const maskPaths = getMaskPaths(borderWidth, height, width) // console.log(borderWidth, borderColor, borderOpacity) // console.log(isFlex) diff --git a/src/mask-generator.js b/src/mask-generator.js new file mode 100644 index 0000000..43aabb7 --- /dev/null +++ b/src/mask-generator.js @@ -0,0 +1,33 @@ +export default function getMaskPaths(borderWidth, height, width) { + const n = 10; + // todo: make adaptive + + const maskPointsA = { + to: -borderWidth[0], + ti: borderWidth[0] * n, + ro: width + borderWidth[1], + ri: width - borderWidth[1] * n, + bo: height + borderWidth[2], + bi: height - borderWidth[2] * n, + lo: -borderWidth[3], + li: borderWidth[3] * n, + } + + const maskPoints = { + tlo: maskPointsA.lo + ',' + maskPointsA.to, + tli: maskPointsA.li + ',' + maskPointsA.ti, + tro: maskPointsA.ro + ',' + maskPointsA.to, + tri: maskPointsA.ri + ',' + maskPointsA.ti, + blo: maskPointsA.lo + ',' + maskPointsA.bo, + bli: maskPointsA.li + ',' + maskPointsA.bi, + bro: maskPointsA.ro + ',' + maskPointsA.bo, + bri: maskPointsA.ri + ',' + maskPointsA.bi, + } + + return { + top: `M${maskPoints.tli}H${maskPointsA.ri}L${maskPoints.tro}H${maskPointsA.lo}Z`, + right: `M${maskPoints.tri}V${maskPointsA.bi}L${maskPoints.bro}V${maskPointsA.to}Z`, + bottom: `M${maskPoints.bri}H${maskPointsA.li}L${maskPoints.blo}H${maskPointsA.ro}Z`, + left: `M${maskPoints.bli}V${maskPointsA.ti}L${maskPoints.tlo}V${maskPointsA.bo}Z`, + } +} diff --git a/src/updateStates.js b/src/updateStates.js index aaa16a4..b9241d0 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -1,5 +1,11 @@ -import {convertPlainColor, convertColorOpacity, convertBorderWidth} from "./css-utils"; -import getStyle from "./styles-extractor"; +import { + convertPlainColor, + convertColorOpacity, + convertBorderWidth, + toNumber, + htmlBorderRadiusNotSvgError +} from "./css-utils"; +import getStyle from "./external/styles-extractor"; export default function updateStates(args) { const {div, setHeight, setWidth} = args @@ -19,9 +25,9 @@ export default function updateStates(args) { const getNthStyle = (key, n) => { const returnNthOverwrittenOrCurrent = r => !r ? false : - r?.overwritten.length > 1 - ? r.overwritten[n ?? 0].value - : r.current?.value + r?.overwritten.length > 1 + ? r.overwritten[n ?? 0].value + : r.current?.value const normal = getStyle(key, div.current); const camelised = getStyle(camelise(key), div.current) @@ -36,12 +42,19 @@ export default function updateStates(args) { 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 divStyle = div.current ? window?.getComputedStyle(div.current) : null if (divStyle) { let states = args - states.setRadius(Number( - (divStyle.borderRadius || divStyle.borderTopLeftRadius) - .replace('px', '')) + states.setRadius( + getBorderRadii(1) + .map(s => toNumber(s, div.current, htmlBorderRadiusNotSvgError)) ) // get color From f2b7048d99fa0115999cf4a4fe1b489858793bc9 Mon Sep 17 00:00:00 2001 From: johannes Date: Sun, 8 Aug 2021 01:15:50 +0200 Subject: [PATCH 08/10] finish dynamic border sizing and sort files --- package-lock.json | 4 +- .../apearce:eact-shadow-dom.js} | 0 src/external/bobspace:html-colors.js | 8 + .../getMatchedCSSRules-polyfill.js | 0 .../heygrady:units:length.js} | 0 src/{ => external}/styles-extractor.js | 0 src/generator.js | 8 +- src/html-colors.js | 159 ------------------ src/main.js | 58 +++---- src/mask-generator.js | 86 +++++++--- ...-sheet-watcher.js => styleSheetWatcher.js} | 2 +- 11 files changed, 103 insertions(+), 222 deletions(-) rename src/{react-shadow-dom.js => external/apearce:eact-shadow-dom.js} (100%) create mode 100644 src/external/bobspace:html-colors.js rename src/{ => external}/getMatchedCSSRules-polyfill.js (100%) rename src/{css-length-converter.js => external/heygrady:units:length.js} (100%) rename src/{ => external}/styles-extractor.js (100%) delete mode 100644 src/html-colors.js rename src/{style-sheet-watcher.js => styleSheetWatcher.js} (98%) diff --git a/package-lock.json b/package-lock.json index 0c939d9..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", 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 index 081d7a7..da07ae8 100644 --- a/src/generator.js +++ b/src/generator.js @@ -45,10 +45,8 @@ export default function generateSvgSquircle(height, width, radius) { mapRange(maxRadius, (x / 2) * .90, x / 2, 0, 1), 1 )) * 200 / maxRadius, - hyOffset = yOffsetF(height), - wyOffset = yOffsetF(width) - - console.log(hyOffset, wyOffset) + hyOffset = yOffsetF(height) || 0, + wyOffset = yOffsetF(width) || 0 const startPoint = `${a0xw},${wyOffset}` @@ -91,5 +89,5 @@ export default function generateSvgSquircle(height, width, radius) { 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, ''); + Z`.replace(/\n */g, '').replace(/NaN/g, '0'); } 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 d1b2e2e..5f695c8 100644 --- a/src/main.js +++ b/src/main.js @@ -45,26 +45,23 @@ export default function RoundDiv({style, children, ...props}) { }, [updateStatesWithArgs]) const path = generateSvgSquircle(height, width, radius) - const maxBorderWidth = Math.max(...borderWidth) - const diffBorderMaxWidthLeftWidth = maxBorderWidth - borderWidth[3] - const diffBorderMaxWidthTopWidth = maxBorderWidth - borderWidth[0] - // const verticalBorderWidthDelta = maxWidth - borderWidth[2]; - // const horizontalBorderWidthDelta = maxWidth - borderWidth[1]; - const borderPath = generateSvgSquircle( - height + diffBorderMaxWidthTopWidth + (maxBorderWidth - borderWidth[2]), - width + diffBorderMaxWidthLeftWidth + (maxBorderWidth - borderWidth[1]), - [ - radius[0] + maxBorderWidth - Math.min(borderWidth[3], borderWidth[0]), - radius[1] + maxBorderWidth - Math.min(borderWidth[0], borderWidth[1]), - radius[2] + maxBorderWidth - Math.min(borderWidth[1], borderWidth[2]), - radius[3] + maxBorderWidth - Math.min(borderWidth[2], borderWidth[3]) - ]) + const innerPath = 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(',') + ) const maskPaths = getMaskPaths(borderWidth, height, width) - // console.log(borderWidth, borderColor, borderOpacity) - // console.log(isFlex) - const svgTransform = isFlex ? `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,${(borderWidth[2] - borderWidth[0]) / 2}px)` : `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,-${borderWidth[0]}px)` @@ -86,19 +83,22 @@ export default function RoundDiv({style, children, ...props}) { transform: svgTransform }} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}> - {Object.keys(maskPaths).map(key => ( - - - - ))} - + + + - {Object.keys(maskPaths).map((key, i) => ( - - ))} + {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 + })} diff --git a/src/mask-generator.js b/src/mask-generator.js index 43aabb7..6e93572 100644 --- a/src/mask-generator.js +++ b/src/mask-generator.js @@ -1,33 +1,67 @@ export default function getMaskPaths(borderWidth, height, width) { - const n = 10; - // todo: make adaptive - - const maskPointsA = { - to: -borderWidth[0], - ti: borderWidth[0] * n, - ro: width + borderWidth[1], - ri: width - borderWidth[1] * n, - bo: height + borderWidth[2], - bi: height - borderWidth[2] * n, - lo: -borderWidth[3], - li: borderWidth[3] * n, + 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) - borderWidth[next(next(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) } - const maskPoints = { - tlo: maskPointsA.lo + ',' + maskPointsA.to, - tli: maskPointsA.li + ',' + maskPointsA.ti, - tro: maskPointsA.ro + ',' + maskPointsA.to, - tri: maskPointsA.ri + ',' + maskPointsA.ti, - blo: maskPointsA.lo + ',' + maskPointsA.bo, - bli: maskPointsA.li + ',' + maskPointsA.bi, - bro: maskPointsA.ro + ',' + maskPointsA.bo, - bri: maskPointsA.ri + ',' + maskPointsA.bi, + /** + * @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 } - return { - top: `M${maskPoints.tli}H${maskPointsA.ri}L${maskPoints.tro}H${maskPointsA.lo}Z`, - right: `M${maskPoints.tri}V${maskPointsA.bi}L${maskPoints.bro}V${maskPointsA.to}Z`, - bottom: `M${maskPoints.bri}H${maskPointsA.li}L${maskPoints.blo}H${maskPointsA.ro}Z`, - left: `M${maskPoints.bli}V${maskPointsA.ti}L${maskPoints.tlo}V${maskPointsA.bo}Z`, + 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' } + + return Object.fromEntries( + allSides.map(side => [side, makeMaskPath(side)]) + ) } diff --git a/src/style-sheet-watcher.js b/src/styleSheetWatcher.js similarity index 98% rename from src/style-sheet-watcher.js rename to src/styleSheetWatcher.js index 000d112..cac9b75 100644 --- a/src/style-sheet-watcher.js +++ b/src/styleSheetWatcher.js @@ -13,7 +13,7 @@ const CSSWatcher = new EventTarget() if (CSS === newCSS) return CSS = newCSS CSSWatcher.dispatchEvent(CSSChangeEvent) - }, 1000) + }, 100) })() function getCSSText() { From 519bca8b75869536976b01daa53516a71b9f5da1 Mon Sep 17 00:00:00 2001 From: johannes Date: Sun, 8 Aug 2021 18:16:26 +0200 Subject: [PATCH 09/10] improvements --- src/generator.js | 30 ++++++++++----- src/main.js | 80 +++++++++++++++++++++++----------------- src/mask-generator.js | 10 +++-- src/styleSheetWatcher.js | 11 +++++- src/updateStates.js | 52 ++++++++++++++++++-------- 5 files changed, 119 insertions(+), 64 deletions(-) diff --git a/src/generator.js b/src/generator.js index da07ae8..a19af25 100644 --- a/src/generator.js +++ b/src/generator.js @@ -19,7 +19,23 @@ export default function generateSvgSquircle(height, width, radius) { height = Number(height); width = Number(width); - radius = radius.map(radius => Math.min(Number(radius), height / 2, width / 2)) + 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)) @@ -34,17 +50,13 @@ export default function generateSvgSquircle(height, width, radius) { a0xw = a0xF(width), a0xh = a0xF(height) - function mapRange(number, in_min, in_max, out_min, out_max) { + /*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 maxRadius = Math.max(...radius); - const yOffsetF = (x) => - Math.max(0, Math.min( - mapRange(maxRadius, (x / 2) * .90, x / 2, 0, 1), - 1 - )) * 200 / maxRadius, + const yOffsetF = (x) => 0, hyOffset = yOffsetF(height) || 0, wyOffset = yOffsetF(width) || 0 diff --git a/src/main.js b/src/main.js index 5f695c8..443a7cf 100644 --- a/src/main.js +++ b/src/main.js @@ -8,6 +8,7 @@ import getMaskPaths from './mask-generator' 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(Array(4).fill(0)) @@ -16,55 +17,67 @@ export default function RoundDiv({style, children, ...props}) { const [borderOpacity, setBorderOpacity] = useState(Array(4).fill(1)) const [borderWidth, setBorderWidth] = useState(Array(4).fill(0)) - const [isFlex, setIsFlex] = useState(false) + const [path, setPath] = useState('Z') + const [innerPath, setInnerPath] = useState('Z') + const [maskPaths, setMaskPaths] = useState('Z') const div = useRef() - useEffect(() => { - // attach shadow root to div - if (!div.current?.shadowRoot) - div.current?.attachShadow({mode: 'open'}) - }, []) - const updateStatesWithArgs = useCallback(() => updateStates({ div, style, + setPosition, setHeight, setWidth, setRadius, setBorderColor, setBorderWidth, - setBorderOpacity, - setIsFlex + setBorderOpacity }), [style]) - useEffect(updateStatesWithArgs, [div, style, updateStatesWithArgs]) + useEffect(updateStatesWithArgs, [style, updateStatesWithArgs]) useEffect(() => { attachCSSWatcher(() => updateStatesWithArgs()) }, [updateStatesWithArgs]) - const path = generateSvgSquircle(height, width, radius) - const innerPath = 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]) + 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(',') - ) + ).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 + } - const maskPaths = getMaskPaths(borderWidth, height, width) + lazySetObjectsState(setMaskPaths, getMaskPaths(borderWidth, height, width, radius)) + }, [height, width, radius, borderWidth]) - const svgTransform = isFlex - ? `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,${(borderWidth[2] - borderWidth[0]) / 2}px)` - : `translate(${(borderWidth[1] - borderWidth[3]) / 2}px,-${borderWidth[0]}px)` const divStyle = { ...style, @@ -75,16 +88,17 @@ export default function RoundDiv({style, children, ...props}) { return
- + {Object.keys(maskPaths).map((key, i) => { @@ -100,7 +114,7 @@ export default function RoundDiv({style, children, ...props}) { fill={borderColor[i]} opacity={borderOpacity[i]}/> })} - + {children}
diff --git a/src/mask-generator.js b/src/mask-generator.js index 6e93572..3e0994f 100644 --- a/src/mask-generator.js +++ b/src/mask-generator.js @@ -1,4 +1,4 @@ -export default function getMaskPaths(borderWidth, height, width) { +export default function getMaskPaths(borderWidth, height, width, radius) { const allSides = ['top', 'right', 'bottom', 'left'] const max = allSides.length - 1 const next = i => i === max ? 0 : i + 1 @@ -9,7 +9,9 @@ export default function getMaskPaths(borderWidth, height, width) { * @type {Array} * */ const allRatios = allSides.map((side, i) => - ((i % 2 === 0 ? height : width) - borderWidth[next(next(i))]) + ((i % 2 === 0 ? height : width) + - Math.max(borderWidth[next(next(i))], radius[prev(i)], radius[prev(prev(i))]) + ) / borderWidth[i] ) @@ -55,10 +57,10 @@ export default function getMaskPaths(borderWidth, height, width) { const nextIfH = isH ? nextSide : side const nextIfV = !isH ? nextSide : side - return 'M' + makePoint(prevIfV, prevIfH, true, side) + + return ('M' + makePoint(prevIfV, prevIfH, true, side) + T + makeValue(nextSide, true, side) + 'L' + makePoint(nextIfV, nextIfH) + - T + makeValue(prevSide) + 'Z' + T + makeValue(prevSide) + 'Z').replace(/NaN/g, '0') } return Object.fromEntries( diff --git a/src/styleSheetWatcher.js b/src/styleSheetWatcher.js index cac9b75..11d6aae 100644 --- a/src/styleSheetWatcher.js +++ b/src/styleSheetWatcher.js @@ -1,7 +1,10 @@ const CSSChangeEvent = new CustomEvent('css-change'); export default function attachCSSWatcher(callback) { - CSSWatcher.addEventListener('css-change', () => callback()) + // attach later, after loading is done + setTimeout(() => + CSSWatcher.addEventListener('css-change', () => callback()) + , 20) } const CSSWatcher = new EventTarget() @@ -13,7 +16,11 @@ const CSSWatcher = new EventTarget() if (CSS === newCSS) return CSS = newCSS CSSWatcher.dispatchEvent(CSSChangeEvent) - }, 100) + }, 30) + window.addEventListener('resize', () => { + CSS = getCSSText() + CSSWatcher.dispatchEvent(CSSChangeEvent) + }) })() function getCSSText() { diff --git a/src/updateStates.js b/src/updateStates.js index b9241d0..51974f0 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -6,13 +6,27 @@ import { 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, setHeight, setWidth} = 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) } function camelise(str) { @@ -29,7 +43,7 @@ export default function updateStates(args) { ? r.overwritten[n ?? 0].value : r.current?.value - const normal = getStyle(key, div.current); + const normal = getStyle(key, div.current) const camelised = getStyle(camelise(key), div.current) return returnNthOverwrittenOrCurrent(normal) || returnNthOverwrittenOrCurrent(camelised) @@ -49,32 +63,38 @@ export default function updateStates(args) { 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) { - let states = args - states.setRadius( + if (!divStyle) return + ReactDOM.unstable_batchedUpdates(() => { + lazySetRadius( getBorderRadii(1) - .map(s => toNumber(s, div.current, htmlBorderRadiusNotSvgError)) + .map(s => Math.min( + toNumber(s, div.current, htmlBorderRadiusNotSvgError), + height / 2, + width / 2 + )) ) // get color - states.setBorderColor( + lazySetBorderColor( getBorderStyles('color', 1) .map(s => convertPlainColor(s)) ) // get alpha value of color - states.setBorderOpacity( + lazySetBorderOpacity( getBorderStyles('color', 1) .map(s => convertColorOpacity(s)) ) - states.setBorderWidth( + lazySetBorderWidth( getBorderStyles('width', 0) .map(s => convertBorderWidth(s, div.current)) ) - - states.setIsFlex( - getNthStyle('display', 0)?.endsWith('flex') || false - ) - } + }) } From 8a4fd23f404339c41d8a039142d28b65ff3e5443 Mon Sep 17 00:00:00 2001 From: johannes Date: Sun, 8 Aug 2021 18:52:27 +0200 Subject: [PATCH 10/10] fix borders in firefox, update readme --- README.md | 6 +++--- src/generator.js | 8 +++++++- src/main.js | 2 +- src/mask-generator.js | 1 + src/styleSheetWatcher.js | 5 +---- src/updateStates.js | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) 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/src/generator.js b/src/generator.js index a19af25..1331f5b 100644 --- a/src/generator.js +++ b/src/generator.js @@ -8,6 +8,7 @@ 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) @@ -101,5 +102,10 @@ export default function generateSvgSquircle(height, width, radius) { 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'); + 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/main.js b/src/main.js index 443a7cf..3722e79 100644 --- a/src/main.js +++ b/src/main.js @@ -98,7 +98,7 @@ export default function RoundDiv({style, children, ...props}) { }} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}> - + {Object.keys(maskPaths).map((key, i) => { diff --git a/src/mask-generator.js b/src/mask-generator.js index 3e0994f..c2d9806 100644 --- a/src/mask-generator.js +++ b/src/mask-generator.js @@ -1,4 +1,5 @@ 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 diff --git a/src/styleSheetWatcher.js b/src/styleSheetWatcher.js index 11d6aae..0c5bfd1 100644 --- a/src/styleSheetWatcher.js +++ b/src/styleSheetWatcher.js @@ -1,10 +1,7 @@ const CSSChangeEvent = new CustomEvent('css-change'); export default function attachCSSWatcher(callback) { - // attach later, after loading is done - setTimeout(() => - CSSWatcher.addEventListener('css-change', () => callback()) - , 20) + CSSWatcher.addEventListener('css-change', () => callback()) } const CSSWatcher = new EventTarget() diff --git a/src/updateStates.js b/src/updateStates.js index 51974f0..59df963 100644 --- a/src/updateStates.js +++ b/src/updateStates.js @@ -39,7 +39,7 @@ export default function updateStates(args) { const getNthStyle = (key, n) => { const returnNthOverwrittenOrCurrent = r => !r ? false : - r?.overwritten.length > 1 + r?.overwritten.length > 0 ? r.overwritten[n ?? 0].value : r.current?.value