@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 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
  }
@@ -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).
@@ -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).
@@ -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}`);
@@ -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}`);
@@ -10,6 +10,7 @@ export interface SquircleNativeProps {
10
10
  borderColor?: string;
11
11
  borderWidth?: number;
12
12
  backgroundColor?: string;
13
+ zSquircle?: boolean;
13
14
  style?: ViewStyle;
14
15
  [key: string]: any;
15
16
  }
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tetrax/squircle",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Figma-accurate squircles for React & React Native with Tailwind CSS classes",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",