Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .github/workflows/bundle-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on:

jobs:
check-bundle-size:
name: Verify Bundle Size <5KB
name: Verify Bundle Size <6KB
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -63,25 +63,26 @@ jobs:
UMD_KB=$(echo "scale=2; $UMD_SIZE / 1024" | bc)
CJS_KB=$(echo "scale=2; $CJS_SIZE / 1024" | bc)

TARGET="5.00 KB"
TARGET="6.00 KB"

ESM_STATUS="✅"
UMD_STATUS="✅"
CJS_STATUS="✅"

if (( $(echo "$ESM_KB >= 5.0" | bc -l) )); then ESM_STATUS="❌"; fi
if (( $(echo "$UMD_KB >= 5.0" | bc -l) )); then UMD_STATUS="❌"; fi
if (( $(echo "$CJS_KB >= 5.0" | bc -l) )); then CJS_STATUS="❌"; fi
if (( $(echo "$ESM_KB >= 6.0" | bc -l) )); then ESM_STATUS="❌"; fi
if (( $(echo "$UMD_KB >= 6.0" | bc -l) )); then UMD_STATUS="❌"; fi
if (( $(echo "$CJS_KB >= 6.0" | bc -l) )); then CJS_STATUS="❌"; fi

echo "| ESM | ${ESM_KB} KB | < ${TARGET} | ${ESM_STATUS} |" >> $GITHUB_STEP_SUMMARY
echo "| UMD | ${UMD_KB} KB | < ${TARGET} | ${UMD_STATUS} |" >> $GITHUB_STEP_SUMMARY
echo "| CJS | ${CJS_KB} KB | < ${TARGET} | ${CJS_STATUS} |" >> $GITHUB_STEP_SUMMARY

echo "" >> $GITHUB_STEP_SUMMARY
echo "**Success Criteria SC-002**: All bundles must be <5KB gzipped" >> $GITHUB_STEP_SUMMARY
echo "**Success Criteria SC-004**: All bundles must be <6KB gzipped (with border support)" >> $GITHUB_STEP_SUMMARY

- name: Comment on PR
if: github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
Expand All @@ -102,7 +103,7 @@ jobs:
const umdSize = getSize('cornerkit.js');
const cjsSize = getSize('cornerkit.cjs');

const target = 5.0;
const target = 6.0;
const allPassed = parseFloat(esmSize) < target &&
parseFloat(umdSize) < target &&
parseFloat(cjsSize) < target;
Expand All @@ -117,7 +118,7 @@ jobs:
| UMD | ${umdSize} KB | < ${target} KB | ${parseFloat(umdSize) < target ? '✅' : '❌'} |
| CJS | ${cjsSize} KB | < ${target} KB | ${parseFloat(cjsSize) < target ? '✅' : '❌'} |

**Success Criteria SC-002**: ${allPassed ? '✅ All bundles meet the <5KB gzipped target' : '❌ One or more bundles exceed the 5KB gzipped target'}`;
**Success Criteria SC-004**: ${allPassed ? '✅ All bundles meet the <6KB gzipped target (with border support)' : '❌ One or more bundles exceed the 6KB gzipped target'}`;

github.rest.issues.createComment({
issue_number: context.issue.number,
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,7 @@ docs/
specs/
.claude/
CLAUDE.md
**/CLAUDE.md
CLAUDE.md.backup
.specify/
.vite/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For detailed changelog information, see the package-specific changelogs:
- [v1.0.0](packages/react/CHANGELOG.md#100---2025-11-22) - Initial release with Squircle component and useSquircle hook

### @cornerkit/core
- [v1.2.0](packages/core/CHANGELOG.md#120---2025-12-07) - SVG-based borders with dashed/dotted/gradient support
- [v1.1.0](packages/core/CHANGELOG.md#110---2025-11-18) - Border support
- [v1.0.2](packages/core/CHANGELOG.md#102---2025-11-16) - Safari clip-path detection fix + Demo website
- [v1.0.0](packages/core/CHANGELOG.md#100---2025-11-12) - Initial release
Expand Down
113 changes: 56 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> Bring iOS-style squircle corners to your web applications

[![npm version](https://img.shields.io/npm/v/@cornerkit/core)](https://www.npmjs.com/package/@cornerkit/core)
[![Bundle Size](https://img.shields.io/badge/bundle%20size-4.58%20KB-success)](https://bundlephobia.com/package/@cornerkit/core)
[![Bundle Size](https://img.shields.io/badge/bundle%20size-5.50%20KB-success)](https://bundlephobia.com/package/@cornerkit/core)
[![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](package.json)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3%2B-blue)](https://www.typescriptlang.org/)
[![Security: A+](https://img.shields.io/badge/security-A%2B-success)](SECURITY.md)
Expand All @@ -12,7 +12,7 @@

**[Live Demo](https://bejarcode.github.io/cornerKit/)** - Interactive playground with 36+ UI examples

CornerKit is a lightweight JavaScript library that brings the smooth, continuous curve corners (squircles) from iOS design to the web. At just **4.58 KB gzipped** with **zero runtime dependencies**, it delivers pixel-perfect rounded corners that look better than standard CSS `border-radius`.
CornerKit is a lightweight JavaScript library that brings the smooth, continuous curve corners (squircles) from iOS design to the web. At just **5.50 KB gzipped** with **zero runtime dependencies**, it delivers pixel-perfect rounded corners that look better than standard CSS `border-radius`.

## Live Demo

Expand Down Expand Up @@ -45,8 +45,10 @@ const ck = new CornerKit();
ck.apply('.card', {
radius: 24, // Corner size in pixels
smoothing: 0.6, // iOS standard smoothness (0-1)
borderWidth: 2, // Optional: border width in pixels
borderColor: '#000' // Optional: border color
border: { // Optional: SVG-based border (v1.2.0+)
width: 2,
color: '#000'
}
});
```

Expand All @@ -71,7 +73,7 @@ ck.apply('.card', {
## Why CornerKit?

### Exceptionally Tiny
- **4.58 KB gzipped** (ESM) - 8% under 5KB budget
- **5.50 KB gzipped** (ESM) - includes SVG border rendering
- **Zero runtime dependencies**
- Tree-shakeable ES modules
- Smaller than most icon libraries
Expand All @@ -89,9 +91,9 @@ ck.apply('.card', {
- Automatic capability detection

### Production Ready
- **313/313 unit tests passing** (100%)
- **46/47 integration tests passing** (97.9%)
- **97.9% code coverage**
- **412/412 unit tests passing** (100%)
- **66/67 integration tests passing** (98.5%)
- **84.9% code coverage**
- Memory leak prevention with WeakMap registry
- A+ security rating (zero vulnerabilities)

Expand Down Expand Up @@ -241,21 +243,44 @@ const info = ck.inspect('#button');
console.log(info.config); // { radius: 32, smoothing: 0.6 }
```

## Border Support
## Border Support (v1.2.0+)

CornerKit supports squircle borders using a dual pseudo-element rendering technique that creates smooth borders matching the corner curves.
CornerKit v1.2.0 introduces SVG-based border rendering that eliminates anti-aliasing fringe on dark backgrounds and supports **solid**, **dashed**, **dotted**, and **gradient** styles.

### Basic Border Usage

```javascript
const ck = new CornerKit();

// Apply squircle with border
// Solid border
ck.apply('.card', {
radius: 24,
smoothing: 0.6,
borderWidth: 2, // Border width in pixels
borderColor: '#e5e7eb' // Border color (any CSS color)
border: { width: 2, color: '#e5e7eb' }
});

// Dashed border
ck.apply('.upload-zone', {
radius: 20,
border: { width: 2, color: '#6b7280', style: 'dashed' }
});

// Dotted border
ck.apply('.badge', {
radius: 12,
border: { width: 3, color: '#10b981', style: 'dotted' }
});

// Gradient border
ck.apply('.featured', {
radius: 24,
border: {
width: 3,
gradient: [
{ offset: '0%', color: '#3b82f6' },
{ offset: '100%', color: '#8b5cf6' }
]
}
});
```

Expand All @@ -265,70 +290,44 @@ ck.apply('.card', {
<div
data-squircle
data-squircle-radius="24"
data-squircle-smoothing="0.6"
data-squircle-border-width="2"
data-squircle-border-color="#e5e7eb"
data-squircle-border-style="dashed"
>
Card with squircle border
Card with dashed squircle border
</div>
```

### Compatible Elements

Borders work seamlessly on elements with `overflow: visible` (the default for most elements):
### Migration from v1.1

✅ **Fully compatible:**
- `<div>` containers
- `<button>` elements
- `<a>` links
- `<span>` inline elements
- `<section>`, `<article>`, `<header>`, etc.
Legacy `borderWidth` and `borderColor` props still work:

### Limitations

Borders use CSS pseudo-elements (`::before` and `::after`) that extend beyond the element's bounds to create the border effect. This requires `overflow: visible`.
```javascript
// Old API (v1.1) - still works
ck.apply('.card', { borderWidth: 2, borderColor: '#e5e7eb' });

⚠️ **Not compatible with:**
- `<textarea>` - Has browser-enforced `overflow: auto` for scrolling
- `<select>` - Similar overflow restrictions
- Scrollable containers with `overflow: scroll` or `overflow: auto`
// New API (v1.2) - recommended
ck.apply('.card', { border: { width: 2, color: '#e5e7eb' } });
```

### Manual Wrapper Pattern for Form Elements
### CSS Framework Compatibility

For form elements like `<textarea>`, wrap the element in a container and apply the border to the wrapper:
Works with CSS frameworks that use `!important` (like Tailwind CSS):

```html
<!-- Wrapper gets the squircle border -->
<div
data-squircle
data-squircle-radius="16"
data-squircle-smoothing="0.85"
data-squircle-border-width="2"
data-squircle-border-color="#d1d5db"
class="inline-block w-full"
>
<!-- Textarea has no border/radius to avoid conflicts -->
<textarea
class="w-full px-4 py-3 bg-white border-0 focus:outline-none focus:ring-0"
rows="3"
></textarea>
<!-- Works correctly with Tailwind's important mode -->
<div class="bg-blue-50 p-4" data-squircle data-squircle-border-width="2">
Content
</div>
```

**Key points:**
- Apply squircle to the **wrapper div**, not the textarea
- Remove conflicting styles from the textarea (border, border-radius, focus rings)
- Wrapper should use `display: inline-block` or `block` and match desired width

**Future improvement:** Phase 3 framework packages (React, Vue, Svelte) will handle wrapper injection automatically for form elements.

## Performance Benchmarks

All metrics verified by automated tests on 2020 MacBook Pro (M1):

| Metric | Target | Actual | Performance |
|--------|--------|--------|-------------|
| Bundle size (ESM) | <5KB | 4.58 KB | 8% under budget |
| Bundle size (ESM) | <6KB | 5.50 KB | 8% under budget |
| Single element render | <10ms | 7.3ms | 27% faster |
| Initialization | <100ms | 42ms | 58% faster |
| 100 elements batch | <500ms | 403ms | 19% faster |
Expand Down Expand Up @@ -408,13 +407,13 @@ Working examples with interactive demos:
```html
<!-- ES Module -->
<script type="module">
import CornerKit from 'https://cdn.jsdelivr.net/npm/@cornerkit/core@1.1.0/dist/cornerkit.esm.js';
import CornerKit from 'https://cdn.jsdelivr.net/npm/@cornerkit/core@1.2.0/dist/cornerkit.esm.js';
const ck = new CornerKit();
ck.apply('.card', { radius: 24, smoothing: 0.6 });
</script>

<!-- UMD (Global) -->
<script src="https://cdn.jsdelivr.net/npm/@cornerkit/core@1.1.0/dist/cornerkit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@cornerkit/core@1.2.0/dist/cornerkit.js"></script>
<script>
const ck = new CornerKit();
ck.apply('.card', { radius: 24, smoothing: 0.6 });
Expand Down
54 changes: 53 additions & 1 deletion packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2025-12-07

### Added
- **SVG-based border rendering** - Complete rewrite of border system using layered SVG paths
- Eliminates anti-aliasing fringe on dark backgrounds (SC-001)
- SVG contains background fill path + border stroke path
- SVG positioned with `z-index: -1` and parent uses `isolation: isolate`
- **New border styles** - Support for solid, dashed, and dotted borders
- Solid: Default style with clean edges
- Dashed: 8px dash / 4px gap pattern (`border.style: 'dashed'`)
- Dotted: Round dots using inset path rendering (`border.style: 'dotted'`)
- **Custom dash patterns** - Configure via `border.dashArray` (e.g., `'12 6'`)
- **Gradient borders** - Linear gradients with configurable color stops
- `border.gradient: [{ offset: 0, color: '#3b82f6' }, { offset: 1, color: '#8b5cf6' }]`
- Default direction: top-left to bottom-right
- **Border data attributes** - Declarative HTML configuration
- `data-squircle-border-width="2"`
- `data-squircle-border-color="#3b82f6"`
- `data-squircle-border-style="dashed"`
- **Border validation** - Width clamped to 1-8px range, invalid colors fall back to transparent
- **Background capture** - Preserves background-image and box-shadow during border rendering

### Changed
- **New nested border API** - Configuration uses `border: { width, color, style, dashArray, gradient }`
- **Backward compatible** - Legacy `borderWidth` and `borderColor` still work
- **Bundle size** - Increased to ~5.8 KB gzipped (under 6KB target per SC-004)
- **CSS framework compatibility** - Uses `!important` for critical styles to prevent Tailwind conflicts

### Technical Details
- Dotted borders use inset path rendering (no clip-path) to avoid artifacts through gaps
- Background fill extends with stroke to cover anti-aliased edges in gaps
- ResizeObserver updates borders on element resize within 16ms frame timing
- 412 unit tests + 66 integration tests passing
- Works consistently across Chrome 90+, Firefox 90+, Safari 14+, Edge 90+

### Migration Guide
```javascript
// Old API (still works)
ck.apply(element, { borderWidth: 2, borderColor: '#3b82f6' })

// New API (recommended)
ck.apply(element, {
border: {
width: 2,
color: '#3b82f6',
style: 'solid' // or 'dashed', 'dotted'
}
})
```

## [1.1.0] - 2025-11-18

### Added
Expand Down Expand Up @@ -187,6 +237,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Working vanilla JavaScript example with interactive demo
- CHANGELOG.md following Keep a Changelog format

[Unreleased]: https://github.com/bejarcode/cornerkit/compare/v1.0.2...HEAD
[Unreleased]: https://github.com/bejarcode/cornerkit/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/bejarcode/cornerkit/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/bejarcode/cornerkit/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/bejarcode/cornerkit/compare/v1.0.0...v1.0.2
[1.0.0]: https://github.com/bejarcode/cornerkit/releases/tag/v1.0.0
Loading
Loading