@b9g/crank 0.7.0 → 0.7.1
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 +203 -138
- package/_css.cjs +80 -0
- package/_css.cjs.map +1 -0
- package/_css.d.ts +21 -0
- package/_css.js +76 -0
- package/_css.js.map +1 -0
- package/_utils.cjs +106 -0
- package/_utils.cjs.map +1 -0
- package/_utils.js +99 -0
- package/_utils.js.map +1 -0
- package/async.cjs +0 -1
- package/async.cjs.map +1 -1
- package/async.js +0 -1
- package/async.js.map +1 -1
- package/crank.cjs +37 -133
- package/crank.cjs.map +1 -1
- package/crank.d.ts +255 -3
- package/crank.js +1 -97
- package/crank.js.map +1 -1
- package/dom.cjs +33 -19
- package/dom.cjs.map +1 -1
- package/dom.js +33 -19
- package/dom.js.map +1 -1
- package/html.cjs +5 -3
- package/html.cjs.map +1 -1
- package/html.js +5 -3
- package/html.js.map +1 -1
- package/jsx-runtime.cjs +0 -1
- package/jsx-runtime.cjs.map +1 -1
- package/jsx-runtime.js +0 -1
- package/jsx-runtime.js.map +1 -1
- package/jsx-tag.cjs +0 -1
- package/jsx-tag.cjs.map +1 -1
- package/jsx-tag.js +0 -1
- package/jsx-tag.js.map +1 -1
- package/package.json +2 -2
- package/standalone.cjs +0 -1
- package/standalone.cjs.map +1 -1
- package/standalone.js +0 -1
- package/standalone.js.map +1 -1
- package/umd.js +110 -20
- package/umd.js.map +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="logo.svg" alt="Crank.js Logo" width="200" height="200" />
|
|
4
|
+
|
|
5
|
+
# Crank.js
|
|
6
|
+
|
|
7
|
+
The Just JavaScript Framework
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
1
11
|
## Try Crank
|
|
2
12
|
|
|
3
13
|
The fastest way to try Crank is via the [online playground](https://crank.js.org/playground). In addition, many of the code examples in these guides feature live previews.
|
|
4
14
|
|
|
15
|
+
## Why Crank?
|
|
16
|
+
**Finally, a framework that feels like JavaScript.**
|
|
17
|
+
|
|
18
|
+
While other frameworks invent new paradigms and force you to learn
|
|
19
|
+
framework-specific APIs, Crank embraces the language features you already know.
|
|
20
|
+
No hooks to memorize, no dependency arrays to debug, no cache invalidation to
|
|
21
|
+
manage.
|
|
22
|
+
|
|
23
|
+
## Pure JavaScript, No Compromises
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// Async components just work
|
|
27
|
+
async function UserProfile({userId}) {
|
|
28
|
+
const user = await fetchUser(userId);
|
|
29
|
+
return <div>Hello, {user.name}!</div>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Lifecycle logic with generators feels natural
|
|
33
|
+
function* Timer() {
|
|
34
|
+
let seconds = 0;
|
|
35
|
+
const interval = setInterval(() => this.refresh(() => seconds++), 1000);
|
|
36
|
+
for ({} of this) {
|
|
37
|
+
yield <div>Seconds: {seconds}</div>;
|
|
38
|
+
}
|
|
39
|
+
clearInterval(interval); // Cleanup just works
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Why Developers Choose Crank
|
|
44
|
+
|
|
45
|
+
- **🎯 Intuitive**: Use `async`/`await` for loading states and `function*` for lifecycle—no new APIs to learn
|
|
46
|
+
- **⚡ Fast**: Outperforms React in benchmarks while weighing just 5KB with zero dependencies
|
|
47
|
+
- **🔧 Flexible**: Write components in vanilla JavaScript with template literals, or use JSX
|
|
48
|
+
- **🧹 Clean**: State lives in function scope, lifecycle code goes where it belongs, no mysterious re-render bugs
|
|
49
|
+
- **🌊 Future-proof**: Built on stable JavaScript features, not evolving framework abstractions
|
|
50
|
+
|
|
51
|
+
## The "Just JavaScript" Promise, Delivered
|
|
52
|
+
|
|
53
|
+
Other frameworks claim to be "just JavaScript" but ask you to think in terms of
|
|
54
|
+
effects, dependencies, and framework-specific patterns. Crank actually delivers
|
|
55
|
+
on that promise—your components are literally just functions that use standard
|
|
56
|
+
JavaScript control flow.
|
|
57
|
+
|
|
58
|
+
Ready to write components that feel like the JavaScript you know and love?
|
|
59
|
+
|
|
5
60
|
## Installation
|
|
6
61
|
|
|
7
62
|
The Crank package is available on [NPM](https://npmjs.org/@b9g/crank) through
|
|
@@ -12,37 +67,42 @@ b*ikeshavin*g).
|
|
|
12
67
|
npm i @b9g/crank
|
|
13
68
|
```
|
|
14
69
|
|
|
15
|
-
### Importing Crank with the **
|
|
70
|
+
### Importing Crank with the **automatic** JSX transform.
|
|
16
71
|
|
|
17
72
|
```jsx live
|
|
18
|
-
/** @
|
|
19
|
-
/** @jsxFrag Fragment */
|
|
20
|
-
import {createElement, Fragment} from "@b9g/crank";
|
|
73
|
+
/** @jsxImportSource @b9g/crank */
|
|
21
74
|
import {renderer} from "@b9g/crank/dom";
|
|
22
75
|
|
|
23
76
|
renderer.render(
|
|
24
|
-
<p>This paragraph element is transpiled with the
|
|
77
|
+
<p>This paragraph element is transpiled with the automatic transform.</p>,
|
|
25
78
|
document.body,
|
|
26
79
|
);
|
|
27
80
|
```
|
|
28
81
|
|
|
29
|
-
### Importing Crank with the **
|
|
82
|
+
### Importing Crank with the **classic** JSX transform.
|
|
30
83
|
|
|
31
84
|
```jsx live
|
|
32
|
-
/** @
|
|
85
|
+
/** @jsx createElement */
|
|
86
|
+
/** @jsxFrag Fragment */
|
|
87
|
+
import {createElement, Fragment} from "@b9g/crank";
|
|
33
88
|
import {renderer} from "@b9g/crank/dom";
|
|
34
89
|
|
|
35
90
|
renderer.render(
|
|
36
|
-
<p>This paragraph element is transpiled with the
|
|
91
|
+
<p>This paragraph element is transpiled with the classic transform.</p>,
|
|
37
92
|
document.body,
|
|
38
93
|
);
|
|
39
94
|
```
|
|
40
95
|
|
|
41
|
-
You will likely have to configure your tools to support JSX, especially if you
|
|
96
|
+
You will likely have to configure your tools to support JSX, especially if you
|
|
97
|
+
do not want to use `@jsx` comment pragmas. See below for common tools and
|
|
98
|
+
configurations.
|
|
42
99
|
|
|
43
100
|
### Importing the JSX template tag.
|
|
44
101
|
|
|
45
|
-
Starting in version `0.5`, the Crank package ships a [tagged template
|
|
102
|
+
Starting in version `0.5`, the Crank package ships a [tagged template
|
|
103
|
+
function](/guides/jsx-template-tag) which provides similar syntax and semantics
|
|
104
|
+
as the JSX transform. This allows you to write Crank components in vanilla
|
|
105
|
+
JavaScript.
|
|
46
106
|
|
|
47
107
|
```js live
|
|
48
108
|
import {jsx} from "@b9g/crank/standalone";
|
|
@@ -60,10 +120,6 @@ Crank is also available on CDNs like [unpkg](https://unpkg.com)
|
|
|
60
120
|
|
|
61
121
|
```jsx live
|
|
62
122
|
/** @jsx createElement */
|
|
63
|
-
|
|
64
|
-
// This is an ESM-ready environment!
|
|
65
|
-
// If code previews work, your browser is an ESM-ready environment!
|
|
66
|
-
|
|
67
123
|
import {createElement} from "https://unpkg.com/@b9g/crank/crank?module";
|
|
68
124
|
import {renderer} from "https://unpkg.com/@b9g/crank/dom?module";
|
|
69
125
|
|
|
@@ -75,6 +131,135 @@ renderer.render(
|
|
|
75
131
|
);
|
|
76
132
|
```
|
|
77
133
|
|
|
134
|
+
## Key Examples
|
|
135
|
+
|
|
136
|
+
### A Simple Component
|
|
137
|
+
|
|
138
|
+
```jsx live
|
|
139
|
+
import {renderer} from "@b9g/crank/dom";
|
|
140
|
+
|
|
141
|
+
function Greeting({name = "World"}) {
|
|
142
|
+
return (
|
|
143
|
+
<div>Hello {name}</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
renderer.render(<Greeting />, document.body);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### A Stateful Component
|
|
151
|
+
|
|
152
|
+
```jsx live
|
|
153
|
+
function *Timer(this: Context) {
|
|
154
|
+
let seconds = 0;
|
|
155
|
+
const interval = setInterval(() => this.refresh(() => seconds++), 1000);
|
|
156
|
+
for ({} of this) {
|
|
157
|
+
yield <div>Seconds: {seconds}</div>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
clearInterval(interval);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### An Async Component
|
|
165
|
+
|
|
166
|
+
```jsx live
|
|
167
|
+
import {renderer} from "@b9g/crank/dom";
|
|
168
|
+
async function Definition({word}) {
|
|
169
|
+
// API courtesy https://dictionaryapi.dev
|
|
170
|
+
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
|
|
171
|
+
const data = await res.json();
|
|
172
|
+
if (!Array.isArray(data)) {
|
|
173
|
+
return <p>No definition found for {word}</p>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const {phonetic, meanings} = data[0];
|
|
177
|
+
const {partOfSpeech, definitions} = meanings[0];
|
|
178
|
+
const {definition} = definitions[0];
|
|
179
|
+
return <>
|
|
180
|
+
<p>{word} <code>{phonetic}</code></p>
|
|
181
|
+
<p><b>{partOfSpeech}.</b> {definition}</p>
|
|
182
|
+
</>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await renderer.render(<Definition word="framework" />, document.body);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### A Loading Component
|
|
189
|
+
|
|
190
|
+
```jsx live
|
|
191
|
+
import {Fragment} from "@b9g/crank";
|
|
192
|
+
import {renderer} from "@b9g/crank/dom";
|
|
193
|
+
|
|
194
|
+
async function LoadingIndicator() {
|
|
195
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
196
|
+
return (
|
|
197
|
+
<div style="padding: 20px; text-align: center; background: #f8f9fa; border: 2px dashed #6c757d; border-radius: 8px; color: #6c757d;">
|
|
198
|
+
🐕 Fetching a good boy...
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function RandomDog({throttle = false}) {
|
|
204
|
+
const res = await fetch("https://dog.ceo/api/breeds/image/random");
|
|
205
|
+
const data = await res.json();
|
|
206
|
+
if (throttle) {
|
|
207
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div style="text-align: center; padding: 10px; background: #fff; border: 1px solid #dee2e6; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
212
|
+
<a href={data.message} target="_blank" style="text-decoration: none; color: inherit;">
|
|
213
|
+
<img
|
|
214
|
+
src={data.message}
|
|
215
|
+
alt="A Random Dog"
|
|
216
|
+
width="300"
|
|
217
|
+
style="border-radius: 8px; display: block; margin: 0 auto;"
|
|
218
|
+
/>
|
|
219
|
+
<div style="margin-top: 8px; color: #6c757d; font-size: 14px;">
|
|
220
|
+
Click to view full size
|
|
221
|
+
</div>
|
|
222
|
+
</a>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function *RandomDogLoader({throttle}) {
|
|
228
|
+
// for await can be used to race component trees
|
|
229
|
+
for await ({throttle} of this) {
|
|
230
|
+
yield <LoadingIndicator />;
|
|
231
|
+
yield <RandomDog throttle={throttle} />;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function *RandomDogApp() {
|
|
236
|
+
let throttle = false;
|
|
237
|
+
this.addEventListener("click", (ev) => {
|
|
238
|
+
if (ev.target.tagName === "BUTTON") {
|
|
239
|
+
this.refresh(() => throttle = !throttle);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
for ({} of this) {
|
|
244
|
+
yield (
|
|
245
|
+
<div style="max-width: 400px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
|
246
|
+
<RandomDogLoader throttle={throttle} />
|
|
247
|
+
<div style="text-align: center; margin-top: 20px;">
|
|
248
|
+
<button style="padding: 12px 24px; font-size: 16px; background: #007bff; color: white; border: none; border-radius: 6px; cursor: pointer;">
|
|
249
|
+
Show me another dog!
|
|
250
|
+
</button>
|
|
251
|
+
<div style="margin-top: 10px; font-size: 14px; color: #6c757d;">
|
|
252
|
+
{throttle ? "🐌 Slow mode enabled" : "⚡ Fast mode"}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
renderer.render(<RandomDogApp />, document.body);
|
|
261
|
+
```
|
|
262
|
+
|
|
78
263
|
## Common tool configurations
|
|
79
264
|
The following is an incomplete list of configurations to get started with Crank.
|
|
80
265
|
|
|
@@ -105,16 +290,15 @@ The classic transform is supported as well.
|
|
|
105
290
|
}
|
|
106
291
|
```
|
|
107
292
|
|
|
108
|
-
Crank is written in TypeScript. Refer to [the guide on
|
|
293
|
+
Crank is written in TypeScript. Refer to [the guide on
|
|
294
|
+
TypeScript](https://crank.js.org/guides/working-with-typescript) for more
|
|
295
|
+
information about Crank types.
|
|
109
296
|
|
|
110
297
|
```tsx
|
|
111
298
|
import type {Context} from "@b9g/crank";
|
|
112
299
|
function *Timer(this: Context) {
|
|
113
300
|
let seconds = 0;
|
|
114
|
-
const interval = setInterval(() =>
|
|
115
|
-
seconds++;
|
|
116
|
-
this.refresh();
|
|
117
|
-
}, 1000);
|
|
301
|
+
const interval = setInterval(() => this.refresh(() => seconds++), 1000);
|
|
118
302
|
for ({} of this) {
|
|
119
303
|
yield <div>Seconds: {seconds}</div>;
|
|
120
304
|
}
|
|
@@ -207,122 +391,3 @@ export default defineConfig({
|
|
|
207
391
|
integrations: [crank()],
|
|
208
392
|
});
|
|
209
393
|
```
|
|
210
|
-
|
|
211
|
-
## Key Examples
|
|
212
|
-
|
|
213
|
-
### A Simple Component
|
|
214
|
-
|
|
215
|
-
```jsx live
|
|
216
|
-
import {renderer} from "@b9g/crank/dom";
|
|
217
|
-
|
|
218
|
-
function Greeting({name = "World"}) {
|
|
219
|
-
return (
|
|
220
|
-
<div>Hello {name}</div>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
renderer.render(<Greeting />, document.body);
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### A Stateful Component
|
|
228
|
-
|
|
229
|
-
```jsx live
|
|
230
|
-
import {renderer} from "@b9g/crank/dom";
|
|
231
|
-
|
|
232
|
-
function *Timer() {
|
|
233
|
-
let seconds = 0;
|
|
234
|
-
const interval = setInterval(() => {
|
|
235
|
-
seconds++;
|
|
236
|
-
this.refresh();
|
|
237
|
-
}, 1000);
|
|
238
|
-
try {
|
|
239
|
-
while (true) {
|
|
240
|
-
yield <div>Seconds: {seconds}</div>;
|
|
241
|
-
}
|
|
242
|
-
} finally {
|
|
243
|
-
clearInterval(interval);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
renderer.render(<Timer />, document.body);
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### An Async Component
|
|
251
|
-
|
|
252
|
-
```jsx live
|
|
253
|
-
import {renderer} from "@b9g/crank/dom";
|
|
254
|
-
async function Definition({word}) {
|
|
255
|
-
// API courtesy https://dictionaryapi.dev
|
|
256
|
-
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
|
|
257
|
-
const data = await res.json();
|
|
258
|
-
if (!Array.isArray(data)) {
|
|
259
|
-
return <p>No definition found for {word}</p>;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const {phonetic, meanings} = data[0];
|
|
263
|
-
const {partOfSpeech, definitions} = meanings[0];
|
|
264
|
-
const {definition} = definitions[0];
|
|
265
|
-
return <>
|
|
266
|
-
<p>{word} <code>{phonetic}</code></p>
|
|
267
|
-
<p><b>{partOfSpeech}.</b> {definition}</p>
|
|
268
|
-
</>;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
await renderer.render(<Definition word="framework" />, document.body);
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### A Loading Component
|
|
275
|
-
|
|
276
|
-
```jsx live
|
|
277
|
-
import {Fragment} from "@b9g/crank";
|
|
278
|
-
import {renderer} from "@b9g/crank/dom";
|
|
279
|
-
|
|
280
|
-
async function LoadingIndicator() {
|
|
281
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
282
|
-
return <div>Fetching a good boy...</div>;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function RandomDog({throttle = false}) {
|
|
286
|
-
const res = await fetch("https://dog.ceo/api/breeds/image/random");
|
|
287
|
-
const data = await res.json();
|
|
288
|
-
if (throttle) {
|
|
289
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return (
|
|
293
|
-
<a href={data.message}>
|
|
294
|
-
<img src={data.message} alt="A Random Dog" width="300" />
|
|
295
|
-
</a>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function *RandomDogLoader({throttle}) {
|
|
300
|
-
for await ({throttle} of this) {
|
|
301
|
-
yield <LoadingIndicator />;
|
|
302
|
-
yield <RandomDog throttle={throttle} />;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function *RandomDogApp() {
|
|
307
|
-
let throttle = false;
|
|
308
|
-
this.addEventListener("click", (ev) => {
|
|
309
|
-
if (ev.target.tagName === "BUTTON") {
|
|
310
|
-
throttle = !throttle;
|
|
311
|
-
this.refresh();
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
for ({} of this) {
|
|
316
|
-
yield (
|
|
317
|
-
<Fragment>
|
|
318
|
-
<RandomDogLoader throttle={throttle} />
|
|
319
|
-
<p>
|
|
320
|
-
<button>Show me another dog.</button>
|
|
321
|
-
</p>
|
|
322
|
-
</Fragment>
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
renderer.render(<RandomDogApp />, document.body);
|
|
328
|
-
```
|
package/_css.cjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CSS utility functions for style property transformation.
|
|
5
|
+
*
|
|
6
|
+
* This module handles camelCase to kebab-case conversion and automatic
|
|
7
|
+
* px unit conversion for numeric CSS values, making Crank more React-compatible.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Converts camelCase CSS property names to kebab-case.
|
|
11
|
+
* Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
|
|
12
|
+
*/
|
|
13
|
+
function camelToKebabCase(str) {
|
|
14
|
+
// Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)
|
|
15
|
+
if (/^[A-Z]/.test(str)) {
|
|
16
|
+
return `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;
|
|
17
|
+
}
|
|
18
|
+
// Handle normal camelCase (fontSize -> font-size)
|
|
19
|
+
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* CSS properties that should remain unitless when given numeric values.
|
|
23
|
+
* Based on React's list of unitless properties.
|
|
24
|
+
*/
|
|
25
|
+
const UNITLESS_PROPERTIES = new Set([
|
|
26
|
+
"animation-iteration-count",
|
|
27
|
+
"aspect-ratio",
|
|
28
|
+
"border-image-outset",
|
|
29
|
+
"border-image-slice",
|
|
30
|
+
"border-image-width",
|
|
31
|
+
"box-flex",
|
|
32
|
+
"box-flex-group",
|
|
33
|
+
"box-ordinal-group",
|
|
34
|
+
"column-count",
|
|
35
|
+
"columns",
|
|
36
|
+
"flex",
|
|
37
|
+
"flex-grow",
|
|
38
|
+
"flex-positive",
|
|
39
|
+
"flex-shrink",
|
|
40
|
+
"flex-negative",
|
|
41
|
+
"flex-order",
|
|
42
|
+
"font-weight",
|
|
43
|
+
"grid-area",
|
|
44
|
+
"grid-column",
|
|
45
|
+
"grid-column-end",
|
|
46
|
+
"grid-column-span",
|
|
47
|
+
"grid-column-start",
|
|
48
|
+
"grid-row",
|
|
49
|
+
"grid-row-end",
|
|
50
|
+
"grid-row-span",
|
|
51
|
+
"grid-row-start",
|
|
52
|
+
"line-height",
|
|
53
|
+
"opacity",
|
|
54
|
+
"order",
|
|
55
|
+
"orphans",
|
|
56
|
+
"tab-size",
|
|
57
|
+
"widows",
|
|
58
|
+
"z-index",
|
|
59
|
+
"zoom",
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* Formats CSS property values, automatically adding "px" to numeric values
|
|
63
|
+
* for properties that are not unitless.
|
|
64
|
+
*/
|
|
65
|
+
function formatStyleValue(name, value) {
|
|
66
|
+
if (typeof value === "number") {
|
|
67
|
+
// If the property should remain unitless, keep the number as-is
|
|
68
|
+
if (UNITLESS_PROPERTIES.has(name)) {
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
// Otherwise, append "px" for numeric values
|
|
72
|
+
return `${value}px`;
|
|
73
|
+
}
|
|
74
|
+
return String(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.UNITLESS_PROPERTIES = UNITLESS_PROPERTIES;
|
|
78
|
+
exports.camelToKebabCase = camelToKebabCase;
|
|
79
|
+
exports.formatStyleValue = formatStyleValue;
|
|
80
|
+
//# sourceMappingURL=_css.cjs.map
|
package/_css.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_css.cjs","sources":["../src/_css.ts"],"sourcesContent":["/**\n * CSS utility functions for style property transformation.\n *\n * This module handles camelCase to kebab-case conversion and automatic\n * px unit conversion for numeric CSS values, making Crank more React-compatible.\n */\n\n/**\n * Converts camelCase CSS property names to kebab-case.\n * Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).\n */\nexport function camelToKebabCase(str: string): string {\n\t// Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)\n\tif (/^[A-Z]/.test(str)) {\n\t\treturn `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;\n\t}\n\t// Handle normal camelCase (fontSize -> font-size)\n\treturn str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);\n}\n\n/**\n * CSS properties that should remain unitless when given numeric values.\n * Based on React's list of unitless properties.\n */\nexport const UNITLESS_PROPERTIES = new Set([\n\t\"animation-iteration-count\",\n\t\"aspect-ratio\",\n\t\"border-image-outset\",\n\t\"border-image-slice\",\n\t\"border-image-width\",\n\t\"box-flex\",\n\t\"box-flex-group\",\n\t\"box-ordinal-group\",\n\t\"column-count\",\n\t\"columns\",\n\t\"flex\",\n\t\"flex-grow\",\n\t\"flex-positive\",\n\t\"flex-shrink\",\n\t\"flex-negative\",\n\t\"flex-order\",\n\t\"font-weight\",\n\t\"grid-area\",\n\t\"grid-column\",\n\t\"grid-column-end\",\n\t\"grid-column-span\",\n\t\"grid-column-start\",\n\t\"grid-row\",\n\t\"grid-row-end\",\n\t\"grid-row-span\",\n\t\"grid-row-start\",\n\t\"line-height\",\n\t\"opacity\",\n\t\"order\",\n\t\"orphans\",\n\t\"tab-size\",\n\t\"widows\",\n\t\"z-index\",\n\t\"zoom\",\n]);\n\n/**\n * Formats CSS property values, automatically adding \"px\" to numeric values\n * for properties that are not unitless.\n */\nexport function formatStyleValue(name: string, value: unknown): string {\n\tif (typeof value === \"number\") {\n\t\t// If the property should remain unitless, keep the number as-is\n\t\tif (UNITLESS_PROPERTIES.has(name)) {\n\t\t\treturn String(value);\n\t\t}\n\t\t// Otherwise, append \"px\" for numeric values\n\t\treturn `${value}px`;\n\t}\n\treturn String(value);\n}\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;AAGG;AACG,SAAU,gBAAgB,CAAC,GAAW,EAAA;;AAE3C,IAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACvB,OAAO,CAAA,CAAA,EAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAE,CAAA,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAE;;;AAGlF,IAAA,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAA,CAAE,CAAC;AACnE;AAEA;;;AAGG;AACU,MAAA,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAC1C,2BAA2B;IAC3B,cAAc;IACd,qBAAqB;IACrB,oBAAoB;IACpB,oBAAoB;IACpB,UAAU;IACV,gBAAgB;IAChB,mBAAmB;IACnB,cAAc;IACd,SAAS;IACT,MAAM;IACN,WAAW;IACX,eAAe;IACf,aAAa;IACb,eAAe;IACf,YAAY;IACZ,aAAa;IACb,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,kBAAkB;IAClB,mBAAmB;IACnB,UAAU;IACV,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,SAAS;IACT,OAAO;IACP,SAAS;IACT,UAAU;IACV,QAAQ;IACR,SAAS;IACT,MAAM;AACN,CAAA;AAED;;;AAGG;AACa,SAAA,gBAAgB,CAAC,IAAY,EAAE,KAAc,EAAA;AAC5D,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;;AAE9B,QAAA,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;AAClC,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;;;QAGrB,OAAO,CAAA,EAAG,KAAK,CAAA,EAAA,CAAI;;AAEpB,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB;;;;;;"}
|
package/_css.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS utility functions for style property transformation.
|
|
3
|
+
*
|
|
4
|
+
* This module handles camelCase to kebab-case conversion and automatic
|
|
5
|
+
* px unit conversion for numeric CSS values, making Crank more React-compatible.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Converts camelCase CSS property names to kebab-case.
|
|
9
|
+
* Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
|
|
10
|
+
*/
|
|
11
|
+
export declare function camelToKebabCase(str: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* CSS properties that should remain unitless when given numeric values.
|
|
14
|
+
* Based on React's list of unitless properties.
|
|
15
|
+
*/
|
|
16
|
+
export declare const UNITLESS_PROPERTIES: Set<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Formats CSS property values, automatically adding "px" to numeric values
|
|
19
|
+
* for properties that are not unitless.
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatStyleValue(name: string, value: unknown): string;
|
package/_css.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS utility functions for style property transformation.
|
|
3
|
+
*
|
|
4
|
+
* This module handles camelCase to kebab-case conversion and automatic
|
|
5
|
+
* px unit conversion for numeric CSS values, making Crank more React-compatible.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Converts camelCase CSS property names to kebab-case.
|
|
9
|
+
* Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
|
|
10
|
+
*/
|
|
11
|
+
function camelToKebabCase(str) {
|
|
12
|
+
// Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)
|
|
13
|
+
if (/^[A-Z]/.test(str)) {
|
|
14
|
+
return `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;
|
|
15
|
+
}
|
|
16
|
+
// Handle normal camelCase (fontSize -> font-size)
|
|
17
|
+
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* CSS properties that should remain unitless when given numeric values.
|
|
21
|
+
* Based on React's list of unitless properties.
|
|
22
|
+
*/
|
|
23
|
+
const UNITLESS_PROPERTIES = new Set([
|
|
24
|
+
"animation-iteration-count",
|
|
25
|
+
"aspect-ratio",
|
|
26
|
+
"border-image-outset",
|
|
27
|
+
"border-image-slice",
|
|
28
|
+
"border-image-width",
|
|
29
|
+
"box-flex",
|
|
30
|
+
"box-flex-group",
|
|
31
|
+
"box-ordinal-group",
|
|
32
|
+
"column-count",
|
|
33
|
+
"columns",
|
|
34
|
+
"flex",
|
|
35
|
+
"flex-grow",
|
|
36
|
+
"flex-positive",
|
|
37
|
+
"flex-shrink",
|
|
38
|
+
"flex-negative",
|
|
39
|
+
"flex-order",
|
|
40
|
+
"font-weight",
|
|
41
|
+
"grid-area",
|
|
42
|
+
"grid-column",
|
|
43
|
+
"grid-column-end",
|
|
44
|
+
"grid-column-span",
|
|
45
|
+
"grid-column-start",
|
|
46
|
+
"grid-row",
|
|
47
|
+
"grid-row-end",
|
|
48
|
+
"grid-row-span",
|
|
49
|
+
"grid-row-start",
|
|
50
|
+
"line-height",
|
|
51
|
+
"opacity",
|
|
52
|
+
"order",
|
|
53
|
+
"orphans",
|
|
54
|
+
"tab-size",
|
|
55
|
+
"widows",
|
|
56
|
+
"z-index",
|
|
57
|
+
"zoom",
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Formats CSS property values, automatically adding "px" to numeric values
|
|
61
|
+
* for properties that are not unitless.
|
|
62
|
+
*/
|
|
63
|
+
function formatStyleValue(name, value) {
|
|
64
|
+
if (typeof value === "number") {
|
|
65
|
+
// If the property should remain unitless, keep the number as-is
|
|
66
|
+
if (UNITLESS_PROPERTIES.has(name)) {
|
|
67
|
+
return String(value);
|
|
68
|
+
}
|
|
69
|
+
// Otherwise, append "px" for numeric values
|
|
70
|
+
return `${value}px`;
|
|
71
|
+
}
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { UNITLESS_PROPERTIES, camelToKebabCase, formatStyleValue };
|
|
76
|
+
//# sourceMappingURL=_css.js.map
|
package/_css.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_css.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/_utils.cjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function wrap(value) {
|
|
4
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
5
|
+
}
|
|
6
|
+
function unwrap(arr) {
|
|
7
|
+
return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Ensures a value is an array.
|
|
11
|
+
*
|
|
12
|
+
* This function does the same thing as wrap() above except it handles nulls
|
|
13
|
+
* and iterables, so it is appropriate for wrapping user-provided element
|
|
14
|
+
* children.
|
|
15
|
+
*/
|
|
16
|
+
function arrayify(value) {
|
|
17
|
+
return value == null
|
|
18
|
+
? []
|
|
19
|
+
: Array.isArray(value)
|
|
20
|
+
? value
|
|
21
|
+
: typeof value === "string" ||
|
|
22
|
+
typeof value[Symbol.iterator] !== "function"
|
|
23
|
+
? [value]
|
|
24
|
+
: [...value];
|
|
25
|
+
}
|
|
26
|
+
function isIteratorLike(value) {
|
|
27
|
+
return value != null && typeof value.next === "function";
|
|
28
|
+
}
|
|
29
|
+
function isPromiseLike(value) {
|
|
30
|
+
return value != null && typeof value.then === "function";
|
|
31
|
+
}
|
|
32
|
+
function createRaceRecord(contender) {
|
|
33
|
+
const deferreds = new Set();
|
|
34
|
+
const record = { deferreds, settled: false };
|
|
35
|
+
// This call to `then` happens once for the lifetime of the value.
|
|
36
|
+
Promise.resolve(contender).then((value) => {
|
|
37
|
+
for (const { resolve } of deferreds) {
|
|
38
|
+
resolve(value);
|
|
39
|
+
}
|
|
40
|
+
deferreds.clear();
|
|
41
|
+
record.settled = true;
|
|
42
|
+
}, (err) => {
|
|
43
|
+
for (const { reject } of deferreds) {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
deferreds.clear();
|
|
47
|
+
record.settled = true;
|
|
48
|
+
});
|
|
49
|
+
return record;
|
|
50
|
+
}
|
|
51
|
+
// Promise.race is memory unsafe. This is alternative which is. See:
|
|
52
|
+
// https://github.com/nodejs/node/issues/17469#issuecomment-685235106
|
|
53
|
+
// Keys are the values passed to race.
|
|
54
|
+
// Values are a record of data containing a set of deferreds and whether the
|
|
55
|
+
// value has settled.
|
|
56
|
+
const wm = new WeakMap();
|
|
57
|
+
function safeRace(contenders) {
|
|
58
|
+
let deferred;
|
|
59
|
+
const result = new Promise((resolve, reject) => {
|
|
60
|
+
deferred = { resolve, reject };
|
|
61
|
+
for (const contender of contenders) {
|
|
62
|
+
if (!isPromiseLike(contender)) {
|
|
63
|
+
// If the contender is a not a then-able, attempting to use it as a key
|
|
64
|
+
// in the weakmap would throw an error. Luckily, it is safe to call
|
|
65
|
+
// `Promise.resolve(contender).then` on regular values multiple
|
|
66
|
+
// times because the promise fulfills immediately.
|
|
67
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let record = wm.get(contender);
|
|
71
|
+
if (record === undefined) {
|
|
72
|
+
record = createRaceRecord(contender);
|
|
73
|
+
record.deferreds.add(deferred);
|
|
74
|
+
wm.set(contender, record);
|
|
75
|
+
}
|
|
76
|
+
else if (record.settled) {
|
|
77
|
+
// If the value has settled, it is safe to call
|
|
78
|
+
// `Promise.resolve(contender).then` on it.
|
|
79
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
record.deferreds.add(deferred);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// The finally callback executes when any value settles, preventing any of
|
|
87
|
+
// the unresolved values from retaining a reference to the resolved value.
|
|
88
|
+
return result.finally(() => {
|
|
89
|
+
for (const contender of contenders) {
|
|
90
|
+
if (isPromiseLike(contender)) {
|
|
91
|
+
const record = wm.get(contender);
|
|
92
|
+
if (record) {
|
|
93
|
+
record.deferreds.delete(deferred);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
exports.arrayify = arrayify;
|
|
101
|
+
exports.isIteratorLike = isIteratorLike;
|
|
102
|
+
exports.isPromiseLike = isPromiseLike;
|
|
103
|
+
exports.safeRace = safeRace;
|
|
104
|
+
exports.unwrap = unwrap;
|
|
105
|
+
exports.wrap = wrap;
|
|
106
|
+
//# sourceMappingURL=_utils.cjs.map
|