@tetrax/squircle 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/core.d.mts +9 -10
- package/dist/core.d.ts +9 -10
- package/dist/core.js +71 -4
- package/dist/core.mjs +71 -4
- package/dist/react/index.d.mts +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +43 -10
- package/dist/react/index.mjs +43 -10
- package/dist/react-native/index.d.ts +1 -0
- package/dist/react-native/index.js +28 -4
- package/dist/react-native/index.mjs +28 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ function App() {
|
|
|
34
34
|
| `rounded-squircle-{0-96}` | Corner radius in px |
|
|
35
35
|
| `corner-smoothing-{0-100}` | Smoothing (default: 100 = full squircle, G1 only) |
|
|
36
36
|
| `squircle-g2` | Enable G2 curvature-continuous mode (ignores manual smoothing percent) |
|
|
37
|
+
| `squircle-z` | Enable Z-Squircle (Zomato) style mode (ignores manual smoothing percent) |
|
|
37
38
|
| `squircle-border` | Enable squircle border overlay (1px) |
|
|
38
39
|
| `squircle-border-{n}` | Border width (e.g. `squircle-border-2`, `squircle-border-1.5`) |
|
|
39
40
|
| `border-{color}` | Border color — standard Tailwind (e.g. `border-gray-200`) |
|
|
@@ -55,6 +56,16 @@ All other Tailwind classes (`bg-*`, `p-*`, `text-*`, etc.) pass through normally
|
|
|
55
56
|
G2 Smooth
|
|
56
57
|
</Squircle>
|
|
57
58
|
|
|
59
|
+
// Z-Squircle (Zomato/ZSquircleView) style squircle
|
|
60
|
+
<Squircle className="rounded-squircle-24 squircle-z bg-amber-500 p-4 text-white">
|
|
61
|
+
Zomato Style
|
|
62
|
+
</Squircle>
|
|
63
|
+
|
|
64
|
+
// Alternatively with prop:
|
|
65
|
+
<Squircle cornerRadius={24} zSquircle className="bg-amber-500 p-4 text-white">
|
|
66
|
+
Zomato Style
|
|
67
|
+
</Squircle>
|
|
68
|
+
|
|
58
69
|
// With border (standard Tailwind)
|
|
59
70
|
<Squircle className="rounded-squircle-24 squircle-border border-gray-200 bg-white p-6">
|
|
60
71
|
Card with border
|
|
@@ -89,6 +100,7 @@ All other Tailwind classes (`bg-*`, `p-*`, `text-*`, etc.) pass through normally
|
|
|
89
100
|
| `cornerRadius` | `number` | from `rounded-squircle-*` | Override corner radius |
|
|
90
101
|
| `cornerSmoothing` | `number` | `1` | Override smoothing (0–1, Standard G1 only) |
|
|
91
102
|
| `g2Continuous` | `boolean` | `false` | Enable pure G2 curvature continuity |
|
|
103
|
+
| `zSquircle` | `boolean` | `false` | Enable Z-Squircle (Zomato) style mode |
|
|
92
104
|
| `preserveSmoothing` | `boolean` | `false` | Preserve smoothing at large radii |
|
|
93
105
|
| `as` | `ComponentType \| string` | `"div"` | HTML tag or component to render |
|
|
94
106
|
|
|
@@ -128,11 +140,11 @@ This works **directly on void elements** (like `<input>`, `<img>`, etc.) where a
|
|
|
128
140
|
Hello from standard div!
|
|
129
141
|
</div>
|
|
130
142
|
|
|
131
|
-
<!-- Squirckled input tag (void element) — border renders perfectly! -->
|
|
143
|
+
<!-- Squirckled input tag with squircle-z (void element) — border renders perfectly! -->
|
|
132
144
|
<input
|
|
133
145
|
type="password"
|
|
134
146
|
placeholder="Thick border: yellow-400/20"
|
|
135
|
-
class="rounded-squircle-9 squircle-border-2 border-yellow-400/20 bg-[#1B1B1B] text-white px-4 py-3 outline-none"
|
|
147
|
+
class="rounded-squircle-9 squircle-border-2 squircle-z border-yellow-400/20 bg-[#1B1B1B] text-white px-4 py-3 outline-none"
|
|
136
148
|
/>
|
|
137
149
|
```
|
|
138
150
|
|
|
@@ -167,6 +179,7 @@ function Card() {
|
|
|
167
179
|
| `borderColor` | `string` | `"transparent"` | Border color |
|
|
168
180
|
| `borderWidth` | `number` | `1` | Border width in dp |
|
|
169
181
|
| `backgroundColor` | `string` | — | Background fill |
|
|
182
|
+
| `zSquircle` | `boolean` | `false` | Enable Z-Squircle (Zomato) style mode |
|
|
170
183
|
| `style` | `ViewStyle` | — | Additional styles |
|
|
171
184
|
|
|
172
185
|
> React Native requires `react-native-svg` as a peer dependency.
|
|
@@ -216,6 +229,9 @@ drawSquircleRect(ctx, 10, 150, 200, 100, {
|
|
|
216
229
|
tl: 8, tr: 32, br: 8, bl: 32
|
|
217
230
|
});
|
|
218
231
|
|
|
232
|
+
// Z-Squircle (Zomato) style on Canvas (radius, smoothing, zSquircle)
|
|
233
|
+
drawSquircleRect(ctx, 10, 290, 200, 100, 24, 1, true);
|
|
234
|
+
|
|
219
235
|
ctx.fillStyle = "#3b82f6";
|
|
220
236
|
ctx.fill();
|
|
221
237
|
```
|
|
@@ -234,6 +250,7 @@ ctx.fill();
|
|
|
234
250
|
| `cornerSmoothing` | `number` | `1` | 0 = rounded rect, 1 = full squircle |
|
|
235
251
|
| `preserveSmoothing` | `boolean` | `false` | Preserve smoothing at large radii |
|
|
236
252
|
| `g2Continuous` | `boolean` | `false` | Enable G2 curvature-continuous mode |
|
|
253
|
+
| `zSquircle` | `boolean` | `false` | Enable Z-Squircle (Zomato) style mode |
|
|
237
254
|
|
|
238
255
|
## How It Works
|
|
239
256
|
|
package/dist/core.d.mts
CHANGED
|
@@ -9,25 +9,23 @@ interface SquirclePathParams {
|
|
|
9
9
|
cornerSmoothing?: number;
|
|
10
10
|
preserveSmoothing?: boolean;
|
|
11
11
|
g2Continuous?: boolean;
|
|
12
|
+
zSquircle?: boolean;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Generate an SVG path string for a squircle rectangle.
|
|
15
|
-
* Direct port of figma-squircle's getSvgPath(), with optional pure G2 Bézier mode
|
|
16
|
+
* Direct port of figma-squircle's getSvgPath(), with optional pure G2 Bézier mode
|
|
17
|
+
* or ZSquircleView style single-Bézier mode.
|
|
16
18
|
*/
|
|
17
|
-
declare function getSvgPath({ cornerRadius, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, cornerSmoothing, width, height, preserveSmoothing, g2Continuous, }: SquirclePathParams): string;
|
|
19
|
+
declare function getSvgPath({ cornerRadius, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, cornerSmoothing, width, height, preserveSmoothing, g2Continuous, zSquircle, }: SquirclePathParams): string;
|
|
18
20
|
/**
|
|
19
21
|
* Compute an SVG path string from width/height/radius/smoothing.
|
|
20
22
|
* Convenience wrapper around getSvgPath().
|
|
21
23
|
*/
|
|
22
|
-
declare function computeSquirclePath(width: number, height: number, cornerRadius: number, cornerSmoothing?: number, preserveSmoothing?: boolean, g2Continuous?: boolean): string;
|
|
24
|
+
declare function computeSquirclePath(width: number, height: number, cornerRadius: number, cornerSmoothing?: number, preserveSmoothing?: boolean, g2Continuous?: boolean, zSquircle?: boolean): string;
|
|
23
25
|
/**
|
|
24
26
|
* Render a squircle rectangle directly onto a Canvas 2D context using
|
|
25
|
-
* G2 curvature-continuous Bézier curves (Pavel Klavík's optimal coefficients)
|
|
26
|
-
*
|
|
27
|
-
* FIX #5: Rewrote to use the same *relative delta* G2 coefficients as the
|
|
28
|
-
* SVG path drawer, so Canvas and SVG paths are numerically identical.
|
|
29
|
-
* Previous version used accumulated offsets (0.804, 0.912, 0.961) which
|
|
30
|
-
* did not match the SVG path coefficients (0.185, 0.293, 0.342).
|
|
27
|
+
* G2 curvature-continuous Bézier curves (Pavel Klavík's optimal coefficients)
|
|
28
|
+
* or ZSquircleView style single-Bézier curves.
|
|
31
29
|
*
|
|
32
30
|
* @example
|
|
33
31
|
* const ctx = canvas.getContext('2d');
|
|
@@ -41,7 +39,7 @@ declare function drawSquircleRect(ctx: CanvasRenderingContext2D | any, x: number
|
|
|
41
39
|
tr?: number;
|
|
42
40
|
br?: number;
|
|
43
41
|
bl?: number;
|
|
44
|
-
}, smoothing?: number): void;
|
|
42
|
+
}, smoothing?: number, zSquircle?: boolean): void;
|
|
45
43
|
|
|
46
44
|
interface SquircleClassConfig {
|
|
47
45
|
cornerRadius: number | null;
|
|
@@ -50,6 +48,7 @@ interface SquircleClassConfig {
|
|
|
50
48
|
borderColor: string | null;
|
|
51
49
|
borderWidth: number;
|
|
52
50
|
g2Continuous: boolean;
|
|
51
|
+
zSquircle: boolean;
|
|
53
52
|
restClasses: string;
|
|
54
53
|
}
|
|
55
54
|
/**
|
package/dist/core.d.ts
CHANGED
|
@@ -9,25 +9,23 @@ interface SquirclePathParams {
|
|
|
9
9
|
cornerSmoothing?: number;
|
|
10
10
|
preserveSmoothing?: boolean;
|
|
11
11
|
g2Continuous?: boolean;
|
|
12
|
+
zSquircle?: boolean;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Generate an SVG path string for a squircle rectangle.
|
|
15
|
-
* Direct port of figma-squircle's getSvgPath(), with optional pure G2 Bézier mode
|
|
16
|
+
* Direct port of figma-squircle's getSvgPath(), with optional pure G2 Bézier mode
|
|
17
|
+
* or ZSquircleView style single-Bézier mode.
|
|
16
18
|
*/
|
|
17
|
-
declare function getSvgPath({ cornerRadius, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, cornerSmoothing, width, height, preserveSmoothing, g2Continuous, }: SquirclePathParams): string;
|
|
19
|
+
declare function getSvgPath({ cornerRadius, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, cornerSmoothing, width, height, preserveSmoothing, g2Continuous, zSquircle, }: SquirclePathParams): string;
|
|
18
20
|
/**
|
|
19
21
|
* Compute an SVG path string from width/height/radius/smoothing.
|
|
20
22
|
* Convenience wrapper around getSvgPath().
|
|
21
23
|
*/
|
|
22
|
-
declare function computeSquirclePath(width: number, height: number, cornerRadius: number, cornerSmoothing?: number, preserveSmoothing?: boolean, g2Continuous?: boolean): string;
|
|
24
|
+
declare function computeSquirclePath(width: number, height: number, cornerRadius: number, cornerSmoothing?: number, preserveSmoothing?: boolean, g2Continuous?: boolean, zSquircle?: boolean): string;
|
|
23
25
|
/**
|
|
24
26
|
* Render a squircle rectangle directly onto a Canvas 2D context using
|
|
25
|
-
* G2 curvature-continuous Bézier curves (Pavel Klavík's optimal coefficients)
|
|
26
|
-
*
|
|
27
|
-
* FIX #5: Rewrote to use the same *relative delta* G2 coefficients as the
|
|
28
|
-
* SVG path drawer, so Canvas and SVG paths are numerically identical.
|
|
29
|
-
* Previous version used accumulated offsets (0.804, 0.912, 0.961) which
|
|
30
|
-
* did not match the SVG path coefficients (0.185, 0.293, 0.342).
|
|
27
|
+
* G2 curvature-continuous Bézier curves (Pavel Klavík's optimal coefficients)
|
|
28
|
+
* or ZSquircleView style single-Bézier curves.
|
|
31
29
|
*
|
|
32
30
|
* @example
|
|
33
31
|
* const ctx = canvas.getContext('2d');
|
|
@@ -41,7 +39,7 @@ declare function drawSquircleRect(ctx: CanvasRenderingContext2D | any, x: number
|
|
|
41
39
|
tr?: number;
|
|
42
40
|
br?: number;
|
|
43
41
|
bl?: number;
|
|
44
|
-
}, smoothing?: number): void;
|
|
42
|
+
}, smoothing?: number, zSquircle?: boolean): void;
|
|
45
43
|
|
|
46
44
|
interface SquircleClassConfig {
|
|
47
45
|
cornerRadius: number | null;
|
|
@@ -50,6 +48,7 @@ interface SquircleClassConfig {
|
|
|
50
48
|
borderColor: string | null;
|
|
51
49
|
borderWidth: number;
|
|
52
50
|
g2Continuous: boolean;
|
|
51
|
+
zSquircle: boolean;
|
|
53
52
|
restClasses: string;
|
|
54
53
|
}
|
|
55
54
|
/**
|
package/dist/core.js
CHANGED
|
@@ -203,6 +203,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
203
203
|
Z
|
|
204
204
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
205
205
|
}
|
|
206
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
207
|
+
const minSide = Math.min(width, height);
|
|
208
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
209
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
210
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
211
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
212
|
+
return `
|
|
213
|
+
M ${rTL} 0
|
|
214
|
+
L ${width - rTR} 0
|
|
215
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
216
|
+
L ${width} ${height - rBR}
|
|
217
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
218
|
+
L ${rBL} ${height}
|
|
219
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
220
|
+
L 0 ${rTL}
|
|
221
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
222
|
+
Z
|
|
223
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
224
|
+
}
|
|
206
225
|
function getSvgPath({
|
|
207
226
|
cornerRadius = 0,
|
|
208
227
|
topLeftCornerRadius,
|
|
@@ -213,12 +232,16 @@ function getSvgPath({
|
|
|
213
232
|
width,
|
|
214
233
|
height,
|
|
215
234
|
preserveSmoothing = false,
|
|
216
|
-
g2Continuous = false
|
|
235
|
+
g2Continuous = false,
|
|
236
|
+
zSquircle = false
|
|
217
237
|
}) {
|
|
218
238
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
219
239
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
220
240
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
221
241
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
242
|
+
if (zSquircle) {
|
|
243
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
244
|
+
}
|
|
222
245
|
if (g2Continuous) {
|
|
223
246
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
224
247
|
topLeftCornerRadius: tl,
|
|
@@ -272,11 +295,11 @@ function getSvgPath({
|
|
|
272
295
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
273
296
|
});
|
|
274
297
|
}
|
|
275
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
298
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
276
299
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
277
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
300
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
278
301
|
}
|
|
279
|
-
function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1) {
|
|
302
|
+
function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1, zSquircle = false) {
|
|
280
303
|
let tlR = 0, trR = 0, brR = 0, blR = 0;
|
|
281
304
|
if (typeof cornerRadius === "number") {
|
|
282
305
|
tlR = trR = brR = blR = cornerRadius;
|
|
@@ -286,6 +309,44 @@ function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1)
|
|
|
286
309
|
brR = cornerRadius.br ?? 0;
|
|
287
310
|
blR = cornerRadius.bl ?? 0;
|
|
288
311
|
}
|
|
312
|
+
if (zSquircle) {
|
|
313
|
+
const minSide = Math.min(width, height);
|
|
314
|
+
const rTL2 = tlR > 0 ? Math.min(minSide / 2, 2.5 * (tlR + 4)) : 0;
|
|
315
|
+
const rTR2 = trR > 0 ? Math.min(minSide / 2, 2.5 * (trR + 4)) : 0;
|
|
316
|
+
const rBR2 = brR > 0 ? Math.min(minSide / 2, 2.5 * (brR + 4)) : 0;
|
|
317
|
+
const rBL2 = blR > 0 ? Math.min(minSide / 2, 2.5 * (blR + 4)) : 0;
|
|
318
|
+
const left2 = x;
|
|
319
|
+
const top2 = y;
|
|
320
|
+
const right2 = x + width;
|
|
321
|
+
const bottom2 = y + height;
|
|
322
|
+
ctx.moveTo(left2 + rTL2, top2);
|
|
323
|
+
ctx.lineTo(right2 - rTR2, top2);
|
|
324
|
+
if (rTR2 > 0) {
|
|
325
|
+
ctx.bezierCurveTo(right2, top2, right2, top2, right2, top2 + rTR2);
|
|
326
|
+
} else {
|
|
327
|
+
ctx.lineTo(right2, top2);
|
|
328
|
+
}
|
|
329
|
+
ctx.lineTo(right2, bottom2 - rBR2);
|
|
330
|
+
if (rBR2 > 0) {
|
|
331
|
+
ctx.bezierCurveTo(right2, bottom2, right2, bottom2, right2 - rBR2, bottom2);
|
|
332
|
+
} else {
|
|
333
|
+
ctx.lineTo(right2, bottom2);
|
|
334
|
+
}
|
|
335
|
+
ctx.lineTo(left2 + rBL2, bottom2);
|
|
336
|
+
if (rBL2 > 0) {
|
|
337
|
+
ctx.bezierCurveTo(left2, bottom2, left2, bottom2, left2, bottom2 - rBL2);
|
|
338
|
+
} else {
|
|
339
|
+
ctx.lineTo(left2, bottom2);
|
|
340
|
+
}
|
|
341
|
+
ctx.lineTo(left2, top2 + rTL2);
|
|
342
|
+
if (rTL2 > 0) {
|
|
343
|
+
ctx.bezierCurveTo(left2, top2, left2, top2, left2 + rTL2, top2);
|
|
344
|
+
} else {
|
|
345
|
+
ctx.lineTo(left2, top2);
|
|
346
|
+
}
|
|
347
|
+
ctx.closePath();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
289
350
|
const { topLeft, topRight, bottomLeft, bottomRight } = distributeAndNormalize({
|
|
290
351
|
topLeftCornerRadius: tlR,
|
|
291
352
|
topRightCornerRadius: trR,
|
|
@@ -554,6 +615,7 @@ function parseSquircleClasses(className = "") {
|
|
|
554
615
|
let borderColor = null;
|
|
555
616
|
let borderWidth = null;
|
|
556
617
|
let g2Continuous = false;
|
|
618
|
+
let zSquircle = false;
|
|
557
619
|
const rest = [];
|
|
558
620
|
for (const cls of classes) {
|
|
559
621
|
let matched = false;
|
|
@@ -577,6 +639,10 @@ function parseSquircleClasses(className = "") {
|
|
|
577
639
|
g2Continuous = true;
|
|
578
640
|
matched = true;
|
|
579
641
|
}
|
|
642
|
+
if (!matched && (cls === "squircle-z" || cls === "squircle-zsquircle")) {
|
|
643
|
+
zSquircle = true;
|
|
644
|
+
matched = true;
|
|
645
|
+
}
|
|
580
646
|
if (!matched) {
|
|
581
647
|
m = cls.match(SQUIRCLE_BORDER_WIDTH_RE);
|
|
582
648
|
if (m) {
|
|
@@ -617,6 +683,7 @@ function parseSquircleClasses(className = "") {
|
|
|
617
683
|
borderColor,
|
|
618
684
|
borderWidth: borderWidth ?? 1,
|
|
619
685
|
g2Continuous,
|
|
686
|
+
zSquircle,
|
|
620
687
|
restClasses: rest.join(" ")
|
|
621
688
|
};
|
|
622
689
|
}
|
package/dist/core.mjs
CHANGED
|
@@ -174,6 +174,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
174
174
|
Z
|
|
175
175
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
176
176
|
}
|
|
177
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
178
|
+
const minSide = Math.min(width, height);
|
|
179
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
180
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
181
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
182
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
183
|
+
return `
|
|
184
|
+
M ${rTL} 0
|
|
185
|
+
L ${width - rTR} 0
|
|
186
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
187
|
+
L ${width} ${height - rBR}
|
|
188
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
189
|
+
L ${rBL} ${height}
|
|
190
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
191
|
+
L 0 ${rTL}
|
|
192
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
193
|
+
Z
|
|
194
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
195
|
+
}
|
|
177
196
|
function getSvgPath({
|
|
178
197
|
cornerRadius = 0,
|
|
179
198
|
topLeftCornerRadius,
|
|
@@ -184,12 +203,16 @@ function getSvgPath({
|
|
|
184
203
|
width,
|
|
185
204
|
height,
|
|
186
205
|
preserveSmoothing = false,
|
|
187
|
-
g2Continuous = false
|
|
206
|
+
g2Continuous = false,
|
|
207
|
+
zSquircle = false
|
|
188
208
|
}) {
|
|
189
209
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
190
210
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
191
211
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
192
212
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
213
|
+
if (zSquircle) {
|
|
214
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
215
|
+
}
|
|
193
216
|
if (g2Continuous) {
|
|
194
217
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
195
218
|
topLeftCornerRadius: tl,
|
|
@@ -243,11 +266,11 @@ function getSvgPath({
|
|
|
243
266
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
244
267
|
});
|
|
245
268
|
}
|
|
246
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
269
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
247
270
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
248
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
271
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
249
272
|
}
|
|
250
|
-
function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1) {
|
|
273
|
+
function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1, zSquircle = false) {
|
|
251
274
|
let tlR = 0, trR = 0, brR = 0, blR = 0;
|
|
252
275
|
if (typeof cornerRadius === "number") {
|
|
253
276
|
tlR = trR = brR = blR = cornerRadius;
|
|
@@ -257,6 +280,44 @@ function drawSquircleRect(ctx, x, y, width, height, cornerRadius, smoothing = 1)
|
|
|
257
280
|
brR = cornerRadius.br ?? 0;
|
|
258
281
|
blR = cornerRadius.bl ?? 0;
|
|
259
282
|
}
|
|
283
|
+
if (zSquircle) {
|
|
284
|
+
const minSide = Math.min(width, height);
|
|
285
|
+
const rTL2 = tlR > 0 ? Math.min(minSide / 2, 2.5 * (tlR + 4)) : 0;
|
|
286
|
+
const rTR2 = trR > 0 ? Math.min(minSide / 2, 2.5 * (trR + 4)) : 0;
|
|
287
|
+
const rBR2 = brR > 0 ? Math.min(minSide / 2, 2.5 * (brR + 4)) : 0;
|
|
288
|
+
const rBL2 = blR > 0 ? Math.min(minSide / 2, 2.5 * (blR + 4)) : 0;
|
|
289
|
+
const left2 = x;
|
|
290
|
+
const top2 = y;
|
|
291
|
+
const right2 = x + width;
|
|
292
|
+
const bottom2 = y + height;
|
|
293
|
+
ctx.moveTo(left2 + rTL2, top2);
|
|
294
|
+
ctx.lineTo(right2 - rTR2, top2);
|
|
295
|
+
if (rTR2 > 0) {
|
|
296
|
+
ctx.bezierCurveTo(right2, top2, right2, top2, right2, top2 + rTR2);
|
|
297
|
+
} else {
|
|
298
|
+
ctx.lineTo(right2, top2);
|
|
299
|
+
}
|
|
300
|
+
ctx.lineTo(right2, bottom2 - rBR2);
|
|
301
|
+
if (rBR2 > 0) {
|
|
302
|
+
ctx.bezierCurveTo(right2, bottom2, right2, bottom2, right2 - rBR2, bottom2);
|
|
303
|
+
} else {
|
|
304
|
+
ctx.lineTo(right2, bottom2);
|
|
305
|
+
}
|
|
306
|
+
ctx.lineTo(left2 + rBL2, bottom2);
|
|
307
|
+
if (rBL2 > 0) {
|
|
308
|
+
ctx.bezierCurveTo(left2, bottom2, left2, bottom2, left2, bottom2 - rBL2);
|
|
309
|
+
} else {
|
|
310
|
+
ctx.lineTo(left2, bottom2);
|
|
311
|
+
}
|
|
312
|
+
ctx.lineTo(left2, top2 + rTL2);
|
|
313
|
+
if (rTL2 > 0) {
|
|
314
|
+
ctx.bezierCurveTo(left2, top2, left2, top2, left2 + rTL2, top2);
|
|
315
|
+
} else {
|
|
316
|
+
ctx.lineTo(left2, top2);
|
|
317
|
+
}
|
|
318
|
+
ctx.closePath();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
260
321
|
const { topLeft, topRight, bottomLeft, bottomRight } = distributeAndNormalize({
|
|
261
322
|
topLeftCornerRadius: tlR,
|
|
262
323
|
topRightCornerRadius: trR,
|
|
@@ -525,6 +586,7 @@ function parseSquircleClasses(className = "") {
|
|
|
525
586
|
let borderColor = null;
|
|
526
587
|
let borderWidth = null;
|
|
527
588
|
let g2Continuous = false;
|
|
589
|
+
let zSquircle = false;
|
|
528
590
|
const rest = [];
|
|
529
591
|
for (const cls of classes) {
|
|
530
592
|
let matched = false;
|
|
@@ -548,6 +610,10 @@ function parseSquircleClasses(className = "") {
|
|
|
548
610
|
g2Continuous = true;
|
|
549
611
|
matched = true;
|
|
550
612
|
}
|
|
613
|
+
if (!matched && (cls === "squircle-z" || cls === "squircle-zsquircle")) {
|
|
614
|
+
zSquircle = true;
|
|
615
|
+
matched = true;
|
|
616
|
+
}
|
|
551
617
|
if (!matched) {
|
|
552
618
|
m = cls.match(SQUIRCLE_BORDER_WIDTH_RE);
|
|
553
619
|
if (m) {
|
|
@@ -588,6 +654,7 @@ function parseSquircleClasses(className = "") {
|
|
|
588
654
|
borderColor,
|
|
589
655
|
borderWidth: borderWidth ?? 1,
|
|
590
656
|
g2Continuous,
|
|
657
|
+
zSquircle,
|
|
591
658
|
restClasses: rest.join(" ")
|
|
592
659
|
};
|
|
593
660
|
}
|
package/dist/react/index.d.mts
CHANGED
|
@@ -7,6 +7,7 @@ interface SquircleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
7
7
|
preserveSmoothing?: boolean;
|
|
8
8
|
as?: ComponentType | string;
|
|
9
9
|
g2Continuous?: boolean;
|
|
10
|
+
zSquircle?: boolean;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Drop-in squircle wrapper. Uses Figma's algorithm (2 Bézier curves + 1 arc per corner).
|
package/dist/react/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface SquircleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
7
7
|
preserveSmoothing?: boolean;
|
|
8
8
|
as?: ComponentType | string;
|
|
9
9
|
g2Continuous?: boolean;
|
|
10
|
+
zSquircle?: boolean;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Drop-in squircle wrapper. Uses Figma's algorithm (2 Bézier curves + 1 arc per corner).
|
package/dist/react/index.js
CHANGED
|
@@ -215,6 +215,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
215
215
|
Z
|
|
216
216
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
217
217
|
}
|
|
218
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
219
|
+
const minSide = Math.min(width, height);
|
|
220
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
221
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
222
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
223
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
224
|
+
return `
|
|
225
|
+
M ${rTL} 0
|
|
226
|
+
L ${width - rTR} 0
|
|
227
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
228
|
+
L ${width} ${height - rBR}
|
|
229
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
230
|
+
L ${rBL} ${height}
|
|
231
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
232
|
+
L 0 ${rTL}
|
|
233
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
234
|
+
Z
|
|
235
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
236
|
+
}
|
|
218
237
|
function getSvgPath({
|
|
219
238
|
cornerRadius = 0,
|
|
220
239
|
topLeftCornerRadius,
|
|
@@ -225,12 +244,16 @@ function getSvgPath({
|
|
|
225
244
|
width,
|
|
226
245
|
height,
|
|
227
246
|
preserveSmoothing = false,
|
|
228
|
-
g2Continuous = false
|
|
247
|
+
g2Continuous = false,
|
|
248
|
+
zSquircle = false
|
|
229
249
|
}) {
|
|
230
250
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
231
251
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
232
252
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
233
253
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
254
|
+
if (zSquircle) {
|
|
255
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
256
|
+
}
|
|
234
257
|
if (g2Continuous) {
|
|
235
258
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
236
259
|
topLeftCornerRadius: tl,
|
|
@@ -284,9 +307,9 @@ function getSvgPath({
|
|
|
284
307
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
285
308
|
});
|
|
286
309
|
}
|
|
287
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
310
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
288
311
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
289
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
312
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
// src/core/class-parser.ts
|
|
@@ -429,6 +452,7 @@ function parseSquircleClasses(className = "") {
|
|
|
429
452
|
let borderColor = null;
|
|
430
453
|
let borderWidth = null;
|
|
431
454
|
let g2Continuous = false;
|
|
455
|
+
let zSquircle = false;
|
|
432
456
|
const rest = [];
|
|
433
457
|
for (const cls of classes) {
|
|
434
458
|
let matched = false;
|
|
@@ -452,6 +476,10 @@ function parseSquircleClasses(className = "") {
|
|
|
452
476
|
g2Continuous = true;
|
|
453
477
|
matched = true;
|
|
454
478
|
}
|
|
479
|
+
if (!matched && (cls === "squircle-z" || cls === "squircle-zsquircle")) {
|
|
480
|
+
zSquircle = true;
|
|
481
|
+
matched = true;
|
|
482
|
+
}
|
|
455
483
|
if (!matched) {
|
|
456
484
|
m = cls.match(SQUIRCLE_BORDER_WIDTH_RE);
|
|
457
485
|
if (m) {
|
|
@@ -492,6 +520,7 @@ function parseSquircleClasses(className = "") {
|
|
|
492
520
|
borderColor,
|
|
493
521
|
borderWidth: borderWidth ?? 1,
|
|
494
522
|
g2Continuous,
|
|
523
|
+
zSquircle,
|
|
495
524
|
restClasses: rest.join(" ")
|
|
496
525
|
};
|
|
497
526
|
}
|
|
@@ -505,14 +534,15 @@ function SquircleSvgBorder({
|
|
|
505
534
|
cornerSmoothing,
|
|
506
535
|
borderColor,
|
|
507
536
|
borderWidth,
|
|
508
|
-
g2Continuous
|
|
537
|
+
g2Continuous,
|
|
538
|
+
zSquircle
|
|
509
539
|
}) {
|
|
510
540
|
if (width <= 0 || height <= 0 || cornerRadius <= 0 || borderWidth <= 0) return null;
|
|
511
541
|
const inset = borderWidth / 2;
|
|
512
542
|
const innerW = Math.max(1, width - inset * 2);
|
|
513
543
|
const innerH = Math.max(1, height - inset * 2);
|
|
514
544
|
const innerR = Math.max(0, cornerRadius - inset);
|
|
515
|
-
const pathData = computeSquirclePath(innerW, innerH, innerR, cornerSmoothing, false, g2Continuous);
|
|
545
|
+
const pathData = computeSquirclePath(innerW, innerH, innerR, cornerSmoothing, false, g2Continuous, zSquircle);
|
|
516
546
|
if (!pathData) return null;
|
|
517
547
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
518
548
|
"svg",
|
|
@@ -550,6 +580,7 @@ function Squircle(props) {
|
|
|
550
580
|
preserveSmoothing: preserveSmoothingProp = false,
|
|
551
581
|
as: Tag = "div",
|
|
552
582
|
g2Continuous = false,
|
|
583
|
+
zSquircle = false,
|
|
553
584
|
style,
|
|
554
585
|
...rest
|
|
555
586
|
} = props;
|
|
@@ -564,6 +595,7 @@ function Squircle(props) {
|
|
|
564
595
|
const cornerRadius = cornerRadiusProp ?? parsed.cornerRadius ?? 0;
|
|
565
596
|
const cornerSmoothing = "cornerSmoothing" in props ? cornerSmoothingProp : parsed.cornerSmoothing ?? 1;
|
|
566
597
|
const preserveSmoothing = "preserveSmoothing" in props ? !!preserveSmoothingProp : false;
|
|
598
|
+
const isZSquircle = "zSquircle" in props ? !!zSquircle : parsed.zSquircle;
|
|
567
599
|
const setRef = import_react.default.useCallback((node) => {
|
|
568
600
|
outerRef.current = node;
|
|
569
601
|
setEl(node);
|
|
@@ -578,9 +610,9 @@ function Squircle(props) {
|
|
|
578
610
|
}, [el]);
|
|
579
611
|
const clipPath = (0, import_react.useMemo)(() => {
|
|
580
612
|
if (!mounted || !cornerRadius || size.width === 0 || size.height === 0) return "none";
|
|
581
|
-
const pathData = computeSquirclePath(size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous);
|
|
613
|
+
const pathData = computeSquirclePath(size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, isZSquircle);
|
|
582
614
|
return pathData ? `path('${pathData}')` : "none";
|
|
583
|
-
}, [mounted, size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous]);
|
|
615
|
+
}, [mounted, size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, isZSquircle]);
|
|
584
616
|
const squircleReady = mounted && cornerRadius > 0 && size.width > 0 && size.height > 0;
|
|
585
617
|
const showSvgBorder = squircleReady && parsed.squircleBorder;
|
|
586
618
|
const squircleStyle = {
|
|
@@ -615,7 +647,8 @@ function Squircle(props) {
|
|
|
615
647
|
cornerSmoothing,
|
|
616
648
|
borderColor: parsed.borderColor || "currentColor",
|
|
617
649
|
borderWidth: parsed.borderWidth,
|
|
618
|
-
g2Continuous
|
|
650
|
+
g2Continuous,
|
|
651
|
+
zSquircle: isZSquircle
|
|
619
652
|
}
|
|
620
653
|
)
|
|
621
654
|
]
|
|
@@ -685,7 +718,7 @@ function initGlobalSquircleObserver() {
|
|
|
685
718
|
const width = el.offsetWidth || 0;
|
|
686
719
|
const height = el.offsetHeight || 0;
|
|
687
720
|
if (width === 0 || height === 0) return;
|
|
688
|
-
const pathData = computeSquirclePath(width, height, config.cornerRadius || 0, config.cornerSmoothing, false, config.g2Continuous);
|
|
721
|
+
const pathData = computeSquirclePath(width, height, config.cornerRadius || 0, config.cornerSmoothing, false, config.g2Continuous, config.zSquircle);
|
|
689
722
|
const clipPath = pathData ? `path('${pathData}')` : "none";
|
|
690
723
|
el.style.clipPath = clipPath;
|
|
691
724
|
el.style.WebkitClipPath = clipPath;
|
|
@@ -711,7 +744,7 @@ function initGlobalSquircleObserver() {
|
|
|
711
744
|
const innerW = Math.max(1, width - inset * 2);
|
|
712
745
|
const innerH = Math.max(1, height - inset * 2);
|
|
713
746
|
const innerR = Math.max(0, (config.cornerRadius || 0) - inset);
|
|
714
|
-
const borderPathData = computeSquirclePath(innerW, innerH, innerR, config.cornerSmoothing, false, config.g2Continuous);
|
|
747
|
+
const borderPathData = computeSquirclePath(innerW, innerH, innerR, config.cornerSmoothing, false, config.g2Continuous, config.zSquircle);
|
|
715
748
|
svgEl.setAttribute("width", String(width));
|
|
716
749
|
svgEl.setAttribute("height", String(height));
|
|
717
750
|
svgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
package/dist/react/index.mjs
CHANGED
|
@@ -179,6 +179,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
179
179
|
Z
|
|
180
180
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
181
181
|
}
|
|
182
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
183
|
+
const minSide = Math.min(width, height);
|
|
184
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
185
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
186
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
187
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
188
|
+
return `
|
|
189
|
+
M ${rTL} 0
|
|
190
|
+
L ${width - rTR} 0
|
|
191
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
192
|
+
L ${width} ${height - rBR}
|
|
193
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
194
|
+
L ${rBL} ${height}
|
|
195
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
196
|
+
L 0 ${rTL}
|
|
197
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
198
|
+
Z
|
|
199
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
200
|
+
}
|
|
182
201
|
function getSvgPath({
|
|
183
202
|
cornerRadius = 0,
|
|
184
203
|
topLeftCornerRadius,
|
|
@@ -189,12 +208,16 @@ function getSvgPath({
|
|
|
189
208
|
width,
|
|
190
209
|
height,
|
|
191
210
|
preserveSmoothing = false,
|
|
192
|
-
g2Continuous = false
|
|
211
|
+
g2Continuous = false,
|
|
212
|
+
zSquircle = false
|
|
193
213
|
}) {
|
|
194
214
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
195
215
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
196
216
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
197
217
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
218
|
+
if (zSquircle) {
|
|
219
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
220
|
+
}
|
|
198
221
|
if (g2Continuous) {
|
|
199
222
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
200
223
|
topLeftCornerRadius: tl,
|
|
@@ -248,9 +271,9 @@ function getSvgPath({
|
|
|
248
271
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
249
272
|
});
|
|
250
273
|
}
|
|
251
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
274
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
252
275
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
253
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
276
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
254
277
|
}
|
|
255
278
|
|
|
256
279
|
// src/core/class-parser.ts
|
|
@@ -393,6 +416,7 @@ function parseSquircleClasses(className = "") {
|
|
|
393
416
|
let borderColor = null;
|
|
394
417
|
let borderWidth = null;
|
|
395
418
|
let g2Continuous = false;
|
|
419
|
+
let zSquircle = false;
|
|
396
420
|
const rest = [];
|
|
397
421
|
for (const cls of classes) {
|
|
398
422
|
let matched = false;
|
|
@@ -416,6 +440,10 @@ function parseSquircleClasses(className = "") {
|
|
|
416
440
|
g2Continuous = true;
|
|
417
441
|
matched = true;
|
|
418
442
|
}
|
|
443
|
+
if (!matched && (cls === "squircle-z" || cls === "squircle-zsquircle")) {
|
|
444
|
+
zSquircle = true;
|
|
445
|
+
matched = true;
|
|
446
|
+
}
|
|
419
447
|
if (!matched) {
|
|
420
448
|
m = cls.match(SQUIRCLE_BORDER_WIDTH_RE);
|
|
421
449
|
if (m) {
|
|
@@ -456,6 +484,7 @@ function parseSquircleClasses(className = "") {
|
|
|
456
484
|
borderColor,
|
|
457
485
|
borderWidth: borderWidth ?? 1,
|
|
458
486
|
g2Continuous,
|
|
487
|
+
zSquircle,
|
|
459
488
|
restClasses: rest.join(" ")
|
|
460
489
|
};
|
|
461
490
|
}
|
|
@@ -469,14 +498,15 @@ function SquircleSvgBorder({
|
|
|
469
498
|
cornerSmoothing,
|
|
470
499
|
borderColor,
|
|
471
500
|
borderWidth,
|
|
472
|
-
g2Continuous
|
|
501
|
+
g2Continuous,
|
|
502
|
+
zSquircle
|
|
473
503
|
}) {
|
|
474
504
|
if (width <= 0 || height <= 0 || cornerRadius <= 0 || borderWidth <= 0) return null;
|
|
475
505
|
const inset = borderWidth / 2;
|
|
476
506
|
const innerW = Math.max(1, width - inset * 2);
|
|
477
507
|
const innerH = Math.max(1, height - inset * 2);
|
|
478
508
|
const innerR = Math.max(0, cornerRadius - inset);
|
|
479
|
-
const pathData = computeSquirclePath(innerW, innerH, innerR, cornerSmoothing, false, g2Continuous);
|
|
509
|
+
const pathData = computeSquirclePath(innerW, innerH, innerR, cornerSmoothing, false, g2Continuous, zSquircle);
|
|
480
510
|
if (!pathData) return null;
|
|
481
511
|
return /* @__PURE__ */ jsx(
|
|
482
512
|
"svg",
|
|
@@ -514,6 +544,7 @@ function Squircle(props) {
|
|
|
514
544
|
preserveSmoothing: preserveSmoothingProp = false,
|
|
515
545
|
as: Tag = "div",
|
|
516
546
|
g2Continuous = false,
|
|
547
|
+
zSquircle = false,
|
|
517
548
|
style,
|
|
518
549
|
...rest
|
|
519
550
|
} = props;
|
|
@@ -528,6 +559,7 @@ function Squircle(props) {
|
|
|
528
559
|
const cornerRadius = cornerRadiusProp ?? parsed.cornerRadius ?? 0;
|
|
529
560
|
const cornerSmoothing = "cornerSmoothing" in props ? cornerSmoothingProp : parsed.cornerSmoothing ?? 1;
|
|
530
561
|
const preserveSmoothing = "preserveSmoothing" in props ? !!preserveSmoothingProp : false;
|
|
562
|
+
const isZSquircle = "zSquircle" in props ? !!zSquircle : parsed.zSquircle;
|
|
531
563
|
const setRef = React.useCallback((node) => {
|
|
532
564
|
outerRef.current = node;
|
|
533
565
|
setEl(node);
|
|
@@ -542,9 +574,9 @@ function Squircle(props) {
|
|
|
542
574
|
}, [el]);
|
|
543
575
|
const clipPath = useMemo(() => {
|
|
544
576
|
if (!mounted || !cornerRadius || size.width === 0 || size.height === 0) return "none";
|
|
545
|
-
const pathData = computeSquirclePath(size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous);
|
|
577
|
+
const pathData = computeSquirclePath(size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, isZSquircle);
|
|
546
578
|
return pathData ? `path('${pathData}')` : "none";
|
|
547
|
-
}, [mounted, size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous]);
|
|
579
|
+
}, [mounted, size.width, size.height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, isZSquircle]);
|
|
548
580
|
const squircleReady = mounted && cornerRadius > 0 && size.width > 0 && size.height > 0;
|
|
549
581
|
const showSvgBorder = squircleReady && parsed.squircleBorder;
|
|
550
582
|
const squircleStyle = {
|
|
@@ -579,7 +611,8 @@ function Squircle(props) {
|
|
|
579
611
|
cornerSmoothing,
|
|
580
612
|
borderColor: parsed.borderColor || "currentColor",
|
|
581
613
|
borderWidth: parsed.borderWidth,
|
|
582
|
-
g2Continuous
|
|
614
|
+
g2Continuous,
|
|
615
|
+
zSquircle: isZSquircle
|
|
583
616
|
}
|
|
584
617
|
)
|
|
585
618
|
]
|
|
@@ -649,7 +682,7 @@ function initGlobalSquircleObserver() {
|
|
|
649
682
|
const width = el.offsetWidth || 0;
|
|
650
683
|
const height = el.offsetHeight || 0;
|
|
651
684
|
if (width === 0 || height === 0) return;
|
|
652
|
-
const pathData = computeSquirclePath(width, height, config.cornerRadius || 0, config.cornerSmoothing, false, config.g2Continuous);
|
|
685
|
+
const pathData = computeSquirclePath(width, height, config.cornerRadius || 0, config.cornerSmoothing, false, config.g2Continuous, config.zSquircle);
|
|
653
686
|
const clipPath = pathData ? `path('${pathData}')` : "none";
|
|
654
687
|
el.style.clipPath = clipPath;
|
|
655
688
|
el.style.WebkitClipPath = clipPath;
|
|
@@ -675,7 +708,7 @@ function initGlobalSquircleObserver() {
|
|
|
675
708
|
const innerW = Math.max(1, width - inset * 2);
|
|
676
709
|
const innerH = Math.max(1, height - inset * 2);
|
|
677
710
|
const innerR = Math.max(0, (config.cornerRadius || 0) - inset);
|
|
678
|
-
const borderPathData = computeSquirclePath(innerW, innerH, innerR, config.cornerSmoothing, false, config.g2Continuous);
|
|
711
|
+
const borderPathData = computeSquirclePath(innerW, innerH, innerR, config.cornerSmoothing, false, config.g2Continuous, config.zSquircle);
|
|
679
712
|
svgEl.setAttribute("width", String(width));
|
|
680
713
|
svgEl.setAttribute("height", String(height));
|
|
681
714
|
svgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
@@ -214,6 +214,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
214
214
|
Z
|
|
215
215
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
216
216
|
}
|
|
217
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
218
|
+
const minSide = Math.min(width, height);
|
|
219
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
220
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
221
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
222
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
223
|
+
return `
|
|
224
|
+
M ${rTL} 0
|
|
225
|
+
L ${width - rTR} 0
|
|
226
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
227
|
+
L ${width} ${height - rBR}
|
|
228
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
229
|
+
L ${rBL} ${height}
|
|
230
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
231
|
+
L 0 ${rTL}
|
|
232
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
233
|
+
Z
|
|
234
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
235
|
+
}
|
|
217
236
|
function getSvgPath({
|
|
218
237
|
cornerRadius = 0,
|
|
219
238
|
topLeftCornerRadius,
|
|
@@ -224,12 +243,16 @@ function getSvgPath({
|
|
|
224
243
|
width,
|
|
225
244
|
height,
|
|
226
245
|
preserveSmoothing = false,
|
|
227
|
-
g2Continuous = false
|
|
246
|
+
g2Continuous = false,
|
|
247
|
+
zSquircle = false
|
|
228
248
|
}) {
|
|
229
249
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
230
250
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
231
251
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
232
252
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
253
|
+
if (zSquircle) {
|
|
254
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
255
|
+
}
|
|
233
256
|
if (g2Continuous) {
|
|
234
257
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
235
258
|
topLeftCornerRadius: tl,
|
|
@@ -283,9 +306,9 @@ function getSvgPath({
|
|
|
283
306
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
284
307
|
});
|
|
285
308
|
}
|
|
286
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
309
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
287
310
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
288
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
311
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
289
312
|
}
|
|
290
313
|
|
|
291
314
|
// src/react-native/index.tsx
|
|
@@ -298,6 +321,7 @@ function SquircleNative({
|
|
|
298
321
|
borderColor = "transparent",
|
|
299
322
|
borderWidth = 1,
|
|
300
323
|
backgroundColor,
|
|
324
|
+
zSquircle = false,
|
|
301
325
|
style,
|
|
302
326
|
...rest
|
|
303
327
|
}) {
|
|
@@ -309,7 +333,7 @@ function SquircleNative({
|
|
|
309
333
|
const { width, height } = size;
|
|
310
334
|
let squirclePath = "";
|
|
311
335
|
if (width > 0 && height > 0 && cornerRadius > 0) {
|
|
312
|
-
squirclePath = computeSquirclePath(width, height, cornerRadius, cornerSmoothing);
|
|
336
|
+
squirclePath = computeSquirclePath(width, height, cornerRadius, cornerSmoothing, false, false, zSquircle);
|
|
313
337
|
}
|
|
314
338
|
const clipId = `sq-${Math.random().toString(36).slice(2, 9)}`;
|
|
315
339
|
const hasClip = squirclePath.length > 0;
|
|
@@ -179,6 +179,25 @@ function getPureBezierSVGPath(width, height, tl, tr, br, bl) {
|
|
|
179
179
|
Z
|
|
180
180
|
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
181
181
|
}
|
|
182
|
+
function getZSquircleSVGPath(width, height, tl, tr, br, bl) {
|
|
183
|
+
const minSide = Math.min(width, height);
|
|
184
|
+
const rTL = tl > 0 ? Math.min(minSide / 2, 2.5 * (tl + 4)) : 0;
|
|
185
|
+
const rTR = tr > 0 ? Math.min(minSide / 2, 2.5 * (tr + 4)) : 0;
|
|
186
|
+
const rBR = br > 0 ? Math.min(minSide / 2, 2.5 * (br + 4)) : 0;
|
|
187
|
+
const rBL = bl > 0 ? Math.min(minSide / 2, 2.5 * (bl + 4)) : 0;
|
|
188
|
+
return `
|
|
189
|
+
M ${rTL} 0
|
|
190
|
+
L ${width - rTR} 0
|
|
191
|
+
${rTR > 0 ? `C ${width} 0 ${width} 0 ${width} ${rTR}` : `L ${width} 0`}
|
|
192
|
+
L ${width} ${height - rBR}
|
|
193
|
+
${rBR > 0 ? `C ${width} ${height} ${width} ${height} ${width - rBR} ${height}` : `L ${width} ${height}`}
|
|
194
|
+
L ${rBL} ${height}
|
|
195
|
+
${rBL > 0 ? `C 0 ${height} 0 ${height} 0 ${height - rBL}` : `L 0 ${height}`}
|
|
196
|
+
L 0 ${rTL}
|
|
197
|
+
${rTL > 0 ? `C 0 0 0 0 ${rTL} 0` : `L 0 0`}
|
|
198
|
+
Z
|
|
199
|
+
`.replace(/[\t\s\n]+/g, " ").trim();
|
|
200
|
+
}
|
|
182
201
|
function getSvgPath({
|
|
183
202
|
cornerRadius = 0,
|
|
184
203
|
topLeftCornerRadius,
|
|
@@ -189,12 +208,16 @@ function getSvgPath({
|
|
|
189
208
|
width,
|
|
190
209
|
height,
|
|
191
210
|
preserveSmoothing = false,
|
|
192
|
-
g2Continuous = false
|
|
211
|
+
g2Continuous = false,
|
|
212
|
+
zSquircle = false
|
|
193
213
|
}) {
|
|
194
214
|
const tl = topLeftCornerRadius ?? cornerRadius;
|
|
195
215
|
const tr = topRightCornerRadius ?? cornerRadius;
|
|
196
216
|
const bl = bottomLeftCornerRadius ?? cornerRadius;
|
|
197
217
|
const br = bottomRightCornerRadius ?? cornerRadius;
|
|
218
|
+
if (zSquircle) {
|
|
219
|
+
return getZSquircleSVGPath(width, height, tl, tr, br, bl);
|
|
220
|
+
}
|
|
198
221
|
if (g2Continuous) {
|
|
199
222
|
const { topLeft: topLeft2, topRight: topRight2, bottomLeft: bottomLeft2, bottomRight: bottomRight2 } = distributeAndNormalize({
|
|
200
223
|
topLeftCornerRadius: tl,
|
|
@@ -248,9 +271,9 @@ function getSvgPath({
|
|
|
248
271
|
bottomLeftPathParams: getPathParamsForCorner({ cornerSmoothing, preserveSmoothing, cornerRadius: bottomLeft.radius, roundingAndSmoothingBudget: bottomLeft.roundingAndSmoothingBudget })
|
|
249
272
|
});
|
|
250
273
|
}
|
|
251
|
-
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false) {
|
|
274
|
+
function computeSquirclePath(width, height, cornerRadius, cornerSmoothing = 1, preserveSmoothing = false, g2Continuous = false, zSquircle = false) {
|
|
252
275
|
if (width <= 0 || height <= 0 || cornerRadius <= 0) return "";
|
|
253
|
-
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous });
|
|
276
|
+
return getSvgPath({ width, height, cornerRadius, cornerSmoothing, preserveSmoothing, g2Continuous, zSquircle });
|
|
254
277
|
}
|
|
255
278
|
|
|
256
279
|
// src/react-native/index.tsx
|
|
@@ -263,6 +286,7 @@ function SquircleNative({
|
|
|
263
286
|
borderColor = "transparent",
|
|
264
287
|
borderWidth = 1,
|
|
265
288
|
backgroundColor,
|
|
289
|
+
zSquircle = false,
|
|
266
290
|
style,
|
|
267
291
|
...rest
|
|
268
292
|
}) {
|
|
@@ -274,7 +298,7 @@ function SquircleNative({
|
|
|
274
298
|
const { width, height } = size;
|
|
275
299
|
let squirclePath = "";
|
|
276
300
|
if (width > 0 && height > 0 && cornerRadius > 0) {
|
|
277
|
-
squirclePath = computeSquirclePath(width, height, cornerRadius, cornerSmoothing);
|
|
301
|
+
squirclePath = computeSquirclePath(width, height, cornerRadius, cornerSmoothing, false, false, zSquircle);
|
|
278
302
|
}
|
|
279
303
|
const clipId = `sq-${Math.random().toString(36).slice(2, 9)}`;
|
|
280
304
|
const hasClip = squirclePath.length > 0;
|