@usespaceui/squircle 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Space UI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ <p align="center">
2
+ <a href="https://squircle.spaceui.one" target="_blank">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://squircle.spaceui.one/logo.svg">
5
+ <source media="(prefers-color-scheme: light)" srcset="https://squircle.spaceui.one/logo.svg">
6
+ <img alt="Space UI logo" src="https://squircle.spaceui.one/logo.svg" width="100" />
7
+ </picture>
8
+ </a>
9
+ </p>
10
+
11
+ <h1 align="center">
12
+ @usespaceui/squircle
13
+ </h1>
14
+
15
+ <p align="center">
16
+ Figma • iOS style corner smoothing using CSS Houdini Paint Worklet.
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://squircle.spaceui.one">Preview</a> •
21
+ <a href="https://github.com/usespaceui/squircle">Source Code</a> •
22
+ <a href="https://www.spaceui.one">SpaceUI.one</a>
23
+ </p>
24
+
25
+ <p align="center">
26
+ <a href="https://github.com/usespaceui/squircle/blob/main/LICENSE">
27
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" />
28
+ </a>
29
+ <a href="https://twitter.com/intent/follow?screen_name=usespaceui">
30
+ <img src="https://img.shields.io/twitter/follow/usespaceui.svg?label=Follow%20@usespaceui" alt="Follow @usespaceui" />
31
+ </a>
32
+ </p>
33
+
34
+ <div align="center">
35
+ <a href="https://www.npmjs.com/package/@usespaceui/squircle">
36
+ <img src="https://img.shields.io/npm/v/@usespaceui/squircle?color=%23fa6400&label=version" />
37
+ </a>
38
+ <a href="https://www.npmjs.com/package/@usespaceui/squircle">
39
+ <img src="https://img.shields.io/npm/unpacked-size/%40usespaceui%2Fsquircle?label=install%20size">
40
+ </a>
41
+ <a href="https://www.npmjs.com/package/@usespaceui/squircle">
42
+ <img src="https://img.shields.io/bundlejs/size/%40usespaceui%2Fsquircle?format=min">
43
+ </a>
44
+ <a href="https://www.npmjs.com/package/@usespaceui/squircle">
45
+ <img src="https://img.shields.io/bundlejs/size/%40usespaceui%2Fsquircle">
46
+ </a>
47
+ <a href="https://github.com/usespaceui/squircle">
48
+ <img src="https://img.shields.io/github/repo-size/usespaceui/squircle">
49
+ </a>
50
+ <a href="https://www.npmjs.com/package/@usespaceui/squircle">
51
+ <img src="https://img.shields.io/npm/dm/@usespaceui/squircle" />
52
+ </a>
53
+ <a href="https://github.com/usespaceui/squircle/blob/main/LICENSE">
54
+ <img src="https://img.shields.io/npm/l/@usespaceui/squircle" />
55
+ </a>
56
+ <br><br>
57
+ </div>
58
+
59
+ ---
60
+
61
+ ## ✨ Overview
62
+
63
+ `@usespaceui/squircle` brings iOS-style squircles (mathematically perfect superellipses) to your Tailwind CSS projects. It mimics the continuous curvature corners found in Figma and Apple devices.
64
+ By utilizing the CSS Houdini Paint Worklet API, it provides a native-like performance and seamlessly integrates with Tailwind CSS.
65
+
66
+ ---
67
+
68
+ ## 📦 Installation
69
+
70
+ ```bash
71
+ pnpm add @usespaceui/squircle
72
+ # or
73
+ npm install @usespaceui/squircle
74
+ # or
75
+ yarn add @usespaceui/squircle
76
+ ```
77
+
78
+ Zero dependencies. Add the plugin to your Tailwind configuration or import it directly (Tailwind).
79
+
80
+ ---
81
+
82
+ ## 🚀 Setup
83
+
84
+ Because the plugin relies on a CSS Houdini worklet to draw the shape, you must initialize the script once in your app.
85
+
86
+ ### With React / Next.js
87
+
88
+ Since the worklet interacts with the browser DOM, initialize it inside a Client Component provider.
89
+
90
+ ```tsx
91
+ // components/SquircleProvider.tsx
92
+ 'use client'
93
+ import * as React from 'react'
94
+ import { initSquircle } from '@usespaceui/squircle'
95
+
96
+ export function SquircleProvider({ children }: { children: React.ReactNode }) {
97
+ React.useEffect(() => {
98
+ initSquircle()
99
+ }, [])
100
+
101
+ return <>{children}</>
102
+ }
103
+ ```
104
+
105
+ Wrap your root layout:
106
+
107
+ ```tsx
108
+ import { SquircleProvider } from './SquircleProvider'
109
+
110
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
111
+ return (
112
+ <html>
113
+ <body>
114
+ <SquircleProvider>{children}</SquircleProvider>
115
+ </body>
116
+ </html>
117
+ )
118
+ }
119
+ ```
120
+
121
+ ### With Vanilla JS
122
+
123
+ Initialize the worklet directly in your client-side entry file:
124
+
125
+ ```js
126
+ import { initSquircle } from '@usespaceui/squircle'
127
+
128
+ if (typeof window !== 'undefined') {
129
+ initSquircle()
130
+ }
131
+ ```
132
+
133
+ ## Configure Tailwind CSS 4
134
+
135
+ Add the squircle plugin to your `tailwind.config.ts`.
136
+
137
+ ```css
138
+ @import '@usespaceui/squircle';
139
+ ```
140
+
141
+ ### Configure Tailwind CSS 3
142
+
143
+ Add the squircle plugin to your `tailwind.config.js` or `tailwind.config.ts`.
144
+
145
+ ```ts
146
+ import squirclePlugin from '@usespaceui/squircle'
147
+
148
+ /** @type {import('tailwindcss').Config} */
149
+ export default {
150
+ content: ['./src/**/*.{js,ts,jsx,tsx}'],
151
+ theme: {
152
+ extend: {},
153
+ },
154
+ plugins: [squirclePlugin],
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 🎨 Usage
161
+
162
+ ### 1. Sizing
163
+
164
+ The plugin generates squircle utilities based on your existing Tailwind `borderRadius` theme. If your Tailwind config has `rounded-2xl`, you automatically have `squircle-2xl`.
165
+
166
+ ```html
167
+ <div class="squircle-md p-4 bg-black text-white">Space UI Squircle!</div>
168
+ <div class="squircle-2xl">...</div>
169
+ <div class="squircle-full">...</div>
170
+ <!-- Automatically clamped to pill/circle -->
171
+
172
+ <!-- Arbitrary values are fully supported -->
173
+ <div class="squircle-[40px]">...</div>
174
+ ```
175
+
176
+ ### 2. Smoothing Intensity
177
+
178
+ By default, the plugin applies a **60% smooth transition** between the straight edge and the corner arc, perfectly matching Apple and Figma's native smoothing.
179
+
180
+ You can adjust this intensity using modifiers (`/0`, `/20`, `/40`, `/60`, `/80`, `/100`).
181
+
182
+ ```html
183
+ <!-- Maximum smoothing -->
184
+ <div class="squircle-2xl/100">...</div>
185
+
186
+ <!-- Minimal smoothing (closer to a plain rounded rectangle) -->
187
+ <div class="squircle-2xl/20">...</div>
188
+
189
+ <!-- Arbitrary smoothing -->
190
+ <div class="squircle-2xl/[25]">...</div>
191
+ ```
192
+
193
+ ### 3. Native Borders (The Magic)
194
+
195
+ Use standard Tailwind border utilities. The Houdini worklet reads your native CSS border classes and draws the squircle border perfectly. No workarounds needed.
196
+
197
+ ```html
198
+ <!-- 8px red border -->
199
+ <div class="squircle-2xl border-8 border-red-500 bg-black">...</div>
200
+
201
+ <!-- 1px semi-transparent blue border -->
202
+ <div class="squircle-[30px] border border-blue-500/50 bg-white">...</div>
203
+ ```
204
+
205
+ > **Note on border placement:**
206
+ > By default, the plugin draws the border **outside** the element (like an outline or a ring) to preserve the interior space for your content. If you want the border drawn inward (standard CSS border-box behavior), you can override the custom CSS variable:
207
+ > `<div class="squircle-2xl border-4 border-red-500 [--tw-squircle-outset:0]">...</div>`
208
+
209
+ ---
210
+
211
+ ## 🛠 Advanced: Using with `tailwind-merge`
212
+
213
+ If you use `tailwind-merge` (standard in UI libraries like `shadcn/ui` for their `cn()` utility), you should configure it so that `squircle-*` classes properly override standard `rounded-*` classes when both are applied.
214
+
215
+ Extend your merge configuration to group `squircle` into the `rounded` class group:
216
+
217
+ ```ts
218
+ import { clsx, type ClassValue } from 'clsx'
219
+ import { extendTailwindMerge } from 'tailwind-merge'
220
+
221
+ const customTwMerge = extendTailwindMerge({
222
+ extend: {
223
+ classGroups: {
224
+ rounded: [
225
+ { squircle: [() => true] }, // Merges any squircle-* class into the rounded group
226
+ ],
227
+ },
228
+ },
229
+ })
230
+
231
+ export function cn(...inputs: ClassValue[]) {
232
+ return customTwMerge(clsx(inputs))
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ## 🧰 Utilities Included
239
+
240
+ - `initSquircle()`
241
+ Registers the CSS Houdini Paint Worklet and sets up the custom CSS properties (`--tw-squircle-w` and `--tw-squircle-smooth`). It is safe to call multiple times and fails gracefully in environments where Houdini is not supported.
242
+
243
+ - `plugin(tailwindcss)`
244
+ The default export is a Tailwind CSS plugin that provides the `squircle-*` utility classes. It automatically provides fallback `border-radius` styles for browsers that don't support the Paint API.
245
+
246
+ ---
247
+
248
+ ### Features
249
+
250
+ - 💃 **Native Tailwind Integration:** Use utility classes like `squircle-2xl` just like you would `rounded-2xl`. No framework-specific wrappers required and if the navigator don"'t support squricle the rounded value is applies.
251
+ - ⚡ **Zero-JS Layout Thrashing:** Driven by the native CSS Paint API (Houdini). No `ResizeObserver`, no DOM manipulation, no SVG paths. Repaints happen instantly on the compositor thread.
252
+ - 👌 **Framework Agnostic:** Works in React, Vue, Svelte, Solid, or plain HTML.
253
+
254
+ ---
255
+
256
+ ## 📦 Related Packages
257
+
258
+ | Package | Description |
259
+ | -------------------------------------------------------------- | --------------------------------------- |
260
+ | [`@usespaceui/avatars`](https://github.com/usespaceui/avatars) | Generative deterministic avatars |
261
+ | [`@usespaceui/sounds`](https://github.com/usespaceui/sounds) | UI sound effects and audio interactions |
262
+
263
+ ---
264
+
265
+ ## 🪪 License
266
+
267
+ MIT — Free for commercial and personal use.
268
+
269
+ ---
270
+
271
+ ## 📚 Resources
272
+
273
+ - 🔍 [Explore the squircles & Playground](https://squircle.spaceui.one)
274
+ - 🌍 [Space UI Official Site](https://www.spaceui.one)
275
+
276
+ ---
277
+
278
+ ## 🛠 Maintenance
279
+
280
+ If you find a bug or have a feature request, please open an [issue on GitHub](https://github.com/usespaceui/squircle/issues).
281
+ Engine internals are intentionally not part of the public API.
282
+
283
+ ---
284
+
285
+ <p align="center">
286
+ <a href="https://www.spaceui.one" target="_blank">
287
+ <img src="https://www.spaceui.one/favicon.ico" width="60" style="border-radius: 50%" alt="Space UI Logo" />
288
+ </a>
289
+ <br />
290
+ <b>Maintained by the Space UI Team</b>
291
+ </p>
@@ -0,0 +1,6 @@
1
+ import * as tailwindcss_plugin from 'tailwindcss/plugin';
2
+
3
+ declare function initSquircle(): void;
4
+ declare const _default: tailwindcss_plugin.PluginWithConfig;
5
+
6
+ export { _default as default, initSquircle };
@@ -0,0 +1,6 @@
1
+ import * as tailwindcss_plugin from 'tailwindcss/plugin';
2
+
3
+ declare function initSquircle(): void;
4
+ declare const _default: tailwindcss_plugin.PluginWithConfig;
5
+
6
+ export { _default as default, initSquircle };
package/dist/index.js ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";var o=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var h=(r,t)=>{for(var i in t)o(r,i,{get:t[i],enumerable:!0})},m=(r,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of d(t))!p.call(r,s)&&s!==i&&o(r,s,{get:()=>t[s],enumerable:!(e=l(t,s))||e.enumerable});return r};var w=r=>m(o({},"__esModule",{value:!0}),r);var f={};h(f,{default:()=>S,initSquircle:()=>g});module.exports=w(f);function n(r,t){return{handler:r,config:t}}n.withOptions=function(r,t=()=>({})){function i(e){return{handler:r(e),config:t(e)}}return i.__isOptionsFunction=!0,i};var c=n;var u=`
2
+ class SquirclePainter {
3
+ static get inputProperties() {
4
+ return ['--tw-squircle-w', '--tw-squircle-smooth'];
5
+ }
6
+
7
+ paint(ctx, size, properties) {
8
+ const getProp = (name) => {
9
+ const p = properties.get(name);
10
+ return p ? p.toString().trim() : '';
11
+ };
12
+
13
+ const radiusStr = getProp('--tw-squircle-w');
14
+ let radius = 0;
15
+ if (radiusStr.endsWith('%')) {
16
+ radius = (Math.min(size.width, size.height) * parseFloat(radiusStr)) / 100;
17
+ } else {
18
+ radius = parseFloat(radiusStr) || 0;
19
+ }
20
+ const smoothStr = getProp('--tw-squircle-smooth');
21
+ let smooth = parseFloat(smoothStr);
22
+ if (isNaN(smooth)) smooth = 0.6;
23
+
24
+ radius = Math.min(radius, size.width / 2, size.height / 2);
25
+
26
+ this.drawSquircle(ctx, size.width, size.height, radius, smooth);
27
+ ctx.fillStyle = 'black';
28
+ ctx.fill();
29
+ }
30
+
31
+ drawSquircle(ctx, width, height, radius, smooth) {
32
+ const n = 2 + smooth * 2;
33
+ const steps = 30;
34
+ const step = Math.PI / 2 / steps;
35
+ ctx.beginPath();
36
+ for (let i = 0; i <= steps; i++) {
37
+ const t = Math.PI / 2 - i * step;
38
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
39
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
40
+ const x = width - radius + X;
41
+ const y = radius - Y;
42
+ if (i === 0) ctx.moveTo(x, y);
43
+ else ctx.lineTo(x, y);
44
+ }
45
+ for (let i = 0; i <= steps; i++) {
46
+ const t = i * step;
47
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
48
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
49
+ ctx.lineTo(width - radius + X, height - radius + Y);
50
+ }
51
+ for (let i = 0; i <= steps; i++) {
52
+ const t = Math.PI / 2 - i * step;
53
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
54
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
55
+ ctx.lineTo(radius - X, height - radius + Y);
56
+ }
57
+ for (let i = 0; i <= steps; i++) {
58
+ const t = i * step;
59
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
60
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
61
+ ctx.lineTo(radius - X, radius - Y);
62
+ }
63
+ ctx.closePath();
64
+ }
65
+ }
66
+
67
+ class SquircleBorderPainter extends SquirclePainter {
68
+ static get inputProperties() {
69
+ return [
70
+ '--tw-squircle-w',
71
+ '--tw-squircle-smooth',
72
+ 'border-top-width',
73
+ 'border-top-color'
74
+ ];
75
+ }
76
+
77
+ paint(ctx, size, properties) {
78
+ const getProp = (name) => {
79
+ const p = properties.get(name);
80
+ return p ? p.toString().trim() : '';
81
+ };
82
+
83
+ const radiusStr = getProp('--tw-squircle-w');
84
+ let radius = 0;
85
+ if (radiusStr.endsWith('%')) {
86
+ radius = (Math.min(size.width, size.height) * parseFloat(radiusStr)) / 100;
87
+ } else {
88
+ radius = parseFloat(radiusStr) || 0;
89
+ }
90
+ const smoothStr = getProp('--tw-squircle-smooth');
91
+ let smooth = parseFloat(smoothStr);
92
+ if (isNaN(smooth)) smooth = 0.6;
93
+
94
+ radius = Math.min(radius, size.width / 2, size.height / 2);
95
+
96
+ const bwStr = getProp('border-top-width');
97
+ const bw = parseFloat(bwStr) || 0;
98
+
99
+ if (bw <= 0) return;
100
+
101
+ let color = getProp('border-top-color');
102
+ if (!color) color = 'black';
103
+
104
+ this.drawSquircle(ctx, size.width, size.height, radius, smooth);
105
+
106
+ ctx.lineWidth = bw * 2;
107
+ ctx.strokeStyle = color;
108
+ ctx.stroke();
109
+ }
110
+ }
111
+
112
+ registerPaint('squircle', SquirclePainter);
113
+ registerPaint('squircle-border', SquircleBorderPainter);
114
+ `;function g(){if(typeof window<"u"){if("registerProperty"in CSS)try{CSS.registerProperty({name:"--tw-squircle-w",syntax:"<length> | <percentage>",inherits:!0,initialValue:"0px"}),CSS.registerProperty({name:"--tw-squircle-smooth",syntax:"<number>",inherits:!0,initialValue:"0.6"})}catch{}if("paintWorklet"in CSS){let r=`data:application/javascript;charset=utf8,${encodeURIComponent(u)}`;CSS.paintWorklet.addModule(r).catch(t=>console.error("Squircle worklet failed",t))}}}var S=c(function({matchUtilities:r,theme:t}){r({squircle:(i,{modifier:e})=>{let s=.6;if(e){let a=parseFloat(e);isNaN(a)||(s=a/100)}return{"border-radius":i,"--tw-squircle-w":i,"--tw-squircle-smooth":s.toString(),"@supports (mask-image: paint(squircle))":{"border-radius":"0","mask-image":"paint(squircle)"},'&[class~="border"], &[class*=" border-"], &[class^="border-"]':{"@supports (mask-image: paint(squircle))":{"border-image-source":"paint(squircle-border)","border-image-slice":"0 fill","border-image-outset":"var(--tw-squircle-outset, 1)"}},"a&, input&, a &, input &":{"@supports (mask-image: paint(squircle))":{"border-radius":i,"mask-image":"none","background-image":"paint(squircle)","border-image-source":"none"}}}}},{values:t("borderRadius"),modifiers:{0:"0",20:"20",40:"40",60:"60",80:"80",100:"100"}})});0&&(module.exports={initSquircle});
package/dist/index.mjs ADDED
@@ -0,0 +1,114 @@
1
+ function a(t,r){return{handler:t,config:r}}a.withOptions=function(t,r=()=>({})){function i(e){return{handler:t(e),config:r(e)}}return i.__isOptionsFunction=!0,i};var n=a;var c=`
2
+ class SquirclePainter {
3
+ static get inputProperties() {
4
+ return ['--tw-squircle-w', '--tw-squircle-smooth'];
5
+ }
6
+
7
+ paint(ctx, size, properties) {
8
+ const getProp = (name) => {
9
+ const p = properties.get(name);
10
+ return p ? p.toString().trim() : '';
11
+ };
12
+
13
+ const radiusStr = getProp('--tw-squircle-w');
14
+ let radius = 0;
15
+ if (radiusStr.endsWith('%')) {
16
+ radius = (Math.min(size.width, size.height) * parseFloat(radiusStr)) / 100;
17
+ } else {
18
+ radius = parseFloat(radiusStr) || 0;
19
+ }
20
+ const smoothStr = getProp('--tw-squircle-smooth');
21
+ let smooth = parseFloat(smoothStr);
22
+ if (isNaN(smooth)) smooth = 0.6;
23
+
24
+ radius = Math.min(radius, size.width / 2, size.height / 2);
25
+
26
+ this.drawSquircle(ctx, size.width, size.height, radius, smooth);
27
+ ctx.fillStyle = 'black';
28
+ ctx.fill();
29
+ }
30
+
31
+ drawSquircle(ctx, width, height, radius, smooth) {
32
+ const n = 2 + smooth * 2;
33
+ const steps = 30;
34
+ const step = Math.PI / 2 / steps;
35
+ ctx.beginPath();
36
+ for (let i = 0; i <= steps; i++) {
37
+ const t = Math.PI / 2 - i * step;
38
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
39
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
40
+ const x = width - radius + X;
41
+ const y = radius - Y;
42
+ if (i === 0) ctx.moveTo(x, y);
43
+ else ctx.lineTo(x, y);
44
+ }
45
+ for (let i = 0; i <= steps; i++) {
46
+ const t = i * step;
47
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
48
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
49
+ ctx.lineTo(width - radius + X, height - radius + Y);
50
+ }
51
+ for (let i = 0; i <= steps; i++) {
52
+ const t = Math.PI / 2 - i * step;
53
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
54
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
55
+ ctx.lineTo(radius - X, height - radius + Y);
56
+ }
57
+ for (let i = 0; i <= steps; i++) {
58
+ const t = i * step;
59
+ const X = radius * Math.pow(Math.cos(t), 2 / n);
60
+ const Y = radius * Math.pow(Math.sin(t), 2 / n);
61
+ ctx.lineTo(radius - X, radius - Y);
62
+ }
63
+ ctx.closePath();
64
+ }
65
+ }
66
+
67
+ class SquircleBorderPainter extends SquirclePainter {
68
+ static get inputProperties() {
69
+ return [
70
+ '--tw-squircle-w',
71
+ '--tw-squircle-smooth',
72
+ 'border-top-width',
73
+ 'border-top-color'
74
+ ];
75
+ }
76
+
77
+ paint(ctx, size, properties) {
78
+ const getProp = (name) => {
79
+ const p = properties.get(name);
80
+ return p ? p.toString().trim() : '';
81
+ };
82
+
83
+ const radiusStr = getProp('--tw-squircle-w');
84
+ let radius = 0;
85
+ if (radiusStr.endsWith('%')) {
86
+ radius = (Math.min(size.width, size.height) * parseFloat(radiusStr)) / 100;
87
+ } else {
88
+ radius = parseFloat(radiusStr) || 0;
89
+ }
90
+ const smoothStr = getProp('--tw-squircle-smooth');
91
+ let smooth = parseFloat(smoothStr);
92
+ if (isNaN(smooth)) smooth = 0.6;
93
+
94
+ radius = Math.min(radius, size.width / 2, size.height / 2);
95
+
96
+ const bwStr = getProp('border-top-width');
97
+ const bw = parseFloat(bwStr) || 0;
98
+
99
+ if (bw <= 0) return;
100
+
101
+ let color = getProp('border-top-color');
102
+ if (!color) color = 'black';
103
+
104
+ this.drawSquircle(ctx, size.width, size.height, radius, smooth);
105
+
106
+ ctx.lineWidth = bw * 2;
107
+ ctx.strokeStyle = color;
108
+ ctx.stroke();
109
+ }
110
+ }
111
+
112
+ registerPaint('squircle', SquirclePainter);
113
+ registerPaint('squircle-border', SquircleBorderPainter);
114
+ `;function h(){if(typeof window<"u"){if("registerProperty"in CSS)try{CSS.registerProperty({name:"--tw-squircle-w",syntax:"<length> | <percentage>",inherits:!0,initialValue:"0px"}),CSS.registerProperty({name:"--tw-squircle-smooth",syntax:"<number>",inherits:!0,initialValue:"0.6"})}catch{}if("paintWorklet"in CSS){let t=`data:application/javascript;charset=utf8,${encodeURIComponent(c)}`;CSS.paintWorklet.addModule(t).catch(r=>console.error("Squircle worklet failed",r))}}}var m=n(function({matchUtilities:t,theme:r}){t({squircle:(i,{modifier:e})=>{let s=.6;if(e){let o=parseFloat(e);isNaN(o)||(s=o/100)}return{"border-radius":i,"--tw-squircle-w":i,"--tw-squircle-smooth":s.toString(),"@supports (mask-image: paint(squircle))":{"border-radius":"0","mask-image":"paint(squircle)"},'&[class~="border"], &[class*=" border-"], &[class^="border-"]':{"@supports (mask-image: paint(squircle))":{"border-image-source":"paint(squircle-border)","border-image-slice":"0 fill","border-image-outset":"var(--tw-squircle-outset, 1)"}},"a&, input&, a &, input &":{"@supports (mask-image: paint(squircle))":{"border-radius":i,"mask-image":"none","background-image":"paint(squircle)","border-image-source":"none"}}}}},{values:r("borderRadius"),modifiers:{0:"0",20:"20",40:"40",60:"60",80:"80",100:"100"}})});export{m as default,h as initSquircle};
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@usespaceui/squircle",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "description": "Squircle geometry generator",
8
+ "keywords": [
9
+ "react",
10
+ "squircle",
11
+ "css",
12
+ "houdini",
13
+ "geometry",
14
+ "spaceui",
15
+ "corners",
16
+ "smooth"
17
+ ],
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.mjs"
26
+ },
27
+ "require": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "typecheck": "tsc --noEmit"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/usespaceui/squircle.git"
45
+ },
46
+ "homepage": "https://squircle.spaceui.one",
47
+ "bugs": {
48
+ "url": "https://github.com/usespaceui/squircle/issues"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "^19.2.18",
52
+ "react": "^19.2.8",
53
+ "tailwindcss": "^4.0.0",
54
+ "tsup": "^8.0.2",
55
+ "typescript": "^5.3.3"
56
+ }
57
+ }