@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 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 **classic** JSX transform.
70
+ ### Importing Crank with the **automatic** JSX transform.
16
71
 
17
72
  ```jsx live
18
- /** @jsx createElement */
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 classic transform.</p>,
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 **automatic** JSX transform.
82
+ ### Importing Crank with the **classic** JSX transform.
30
83
 
31
84
  ```jsx live
32
- /** @jsxImportSource @b9g/crank */
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 automatic transform.</p>,
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 do not want to use `@jsx` comment pragmas. See below for common tools and configurations.
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 function](/guides/jsx-template-tag) which provides similar syntax and semantics as the JSX transform. This allows you to write Crank components in vanilla JavaScript.
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 TypeScript](/guides/working-with-typescript) for more information about Crank types.
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