@kbach/react 0.6.12 → 0.6.14
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 +27 -0
- package/dist/{chunk-GTDMKS7C.mjs → chunk-7DHBNCL5.mjs} +1 -1
- package/dist/{chunk-WYOKBFSK.mjs → chunk-HCRWLOJE.mjs} +73 -25
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +72 -24
- package/dist/index.mjs +1 -1
- package/dist/jsx-dev-runtime.js +73 -25
- package/dist/jsx-dev-runtime.mjs +2 -2
- package/dist/jsx-runtime.js +73 -25
- package/dist/jsx-runtime.mjs +2 -2
- package/dist/vite-plugin.d.mts +17 -0
- package/dist/vite-plugin.d.ts +17 -0
- package/dist/vite-plugin.js +86 -26
- package/dist/vite-plugin.mjs +86 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -362,6 +362,18 @@ Nested groups need names, or the inner element reacts to whichever `.group` is n
|
|
|
362
362
|
<div className="text-[18px]" />
|
|
363
363
|
```
|
|
364
364
|
|
|
365
|
+
### Arbitrary properties (web only)
|
|
366
|
+
|
|
367
|
+
For the rare CSS property with no named utility, write `[property:value]` directly — no utility prefix:
|
|
368
|
+
|
|
369
|
+
```jsx
|
|
370
|
+
<div className="[mask-type:luminance]" />
|
|
371
|
+
<div className="[-webkit-line-clamp:3]" />
|
|
372
|
+
<div className="[--my-var:10px]" /> {/* CSS custom property */}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Underscores become spaces, same as any other arbitrary value: `[background:url(/a.png)_no-repeat]`. Only the first `:` splits the property from the value, so a value that itself contains a colon (a URL's `http://`) still comes through intact.
|
|
376
|
+
|
|
365
377
|
## Color system
|
|
366
378
|
|
|
367
379
|
12-shade scale — 1 lightest, 12 darkest. Use as `bg-blue-6`, `text-gray-10`, `border-red-4/50`.
|
|
@@ -406,6 +418,15 @@ module.exports = {
|
|
|
406
418
|
sans: 'Inter, sans-serif',
|
|
407
419
|
'sans-bold': 'Inter_700Bold, sans-serif',
|
|
408
420
|
},
|
|
421
|
+
// Custom @keyframes (web only) — reference by name from `animation`,
|
|
422
|
+
// then use that name as animate-{name}. Declaration values are plain
|
|
423
|
+
// CSS strings, same idea as a style object but for a keyframe step.
|
|
424
|
+
keyframes: {
|
|
425
|
+
wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } },
|
|
426
|
+
},
|
|
427
|
+
animation: {
|
|
428
|
+
wiggle: 'wiggle 1s ease-in-out infinite',
|
|
429
|
+
},
|
|
409
430
|
},
|
|
410
431
|
|
|
411
432
|
plugins: [
|
|
@@ -422,6 +443,12 @@ module.exports = {
|
|
|
422
443
|
|
|
423
444
|
Setting `fontFamily.sans` to anything other than `'System'` auto-injects `body { font-family: … }`.
|
|
424
445
|
|
|
446
|
+
```jsx
|
|
447
|
+
<div className="animate-wiggle" />
|
|
448
|
+
{/* Override duration/timing/repeat inline, keeps the same wiggle @keyframes: */}
|
|
449
|
+
<div className="animate-[wiggle_2s_ease-in-out]" />
|
|
450
|
+
```
|
|
451
|
+
|
|
425
452
|
Color aliases:
|
|
426
453
|
|
|
427
454
|
```js
|
|
@@ -540,7 +540,13 @@ var defaultTheme = {
|
|
|
540
540
|
xl: 1280,
|
|
541
541
|
// desktop
|
|
542
542
|
"2xl": 1536
|
|
543
|
-
}
|
|
543
|
+
},
|
|
544
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
545
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
546
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
547
|
+
// alongside them without needing to redeclare the built-ins.
|
|
548
|
+
keyframes: {},
|
|
549
|
+
animation: {}
|
|
544
550
|
};
|
|
545
551
|
|
|
546
552
|
// src/core/platform.ts
|
|
@@ -1240,6 +1246,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1240
1246
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1241
1247
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1242
1248
|
};
|
|
1249
|
+
function keyframeDeclToCSS(decl) {
|
|
1250
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1251
|
+
}
|
|
1252
|
+
function buildKeyframeCSS(name, steps) {
|
|
1253
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1254
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1255
|
+
}
|
|
1256
|
+
var BUILTIN_ANIMATIONS = {
|
|
1257
|
+
none: { animation: "none" },
|
|
1258
|
+
spin: {
|
|
1259
|
+
animation: "kb-spin 1s linear infinite",
|
|
1260
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1261
|
+
},
|
|
1262
|
+
ping: {
|
|
1263
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1264
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1265
|
+
},
|
|
1266
|
+
pulse: {
|
|
1267
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1268
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1269
|
+
},
|
|
1270
|
+
bounce: {
|
|
1271
|
+
animation: "kb-bounce 1s infinite",
|
|
1272
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1276
|
+
const words = animation.trim().split(/\s+/);
|
|
1277
|
+
const name = words[0];
|
|
1278
|
+
if (!name) return { animation };
|
|
1279
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1280
|
+
if (builtin?.__keyframe) {
|
|
1281
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1282
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1283
|
+
}
|
|
1284
|
+
const steps = keyframes?.[name];
|
|
1285
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1286
|
+
}
|
|
1243
1287
|
var RESOLVERS = {
|
|
1244
1288
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1245
1289
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1803,6 +1847,28 @@ var RESOLVERS = {
|
|
|
1803
1847
|
const presets = { none: "none" };
|
|
1804
1848
|
return value in presets ? { content: presets[value] } : null;
|
|
1805
1849
|
},
|
|
1850
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
1851
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
1852
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
1853
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
1854
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
1855
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
1856
|
+
//
|
|
1857
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
1858
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
1859
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
1860
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
1861
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
1862
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
1863
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
1864
|
+
if (negative) return null;
|
|
1865
|
+
const colonIdx = value.indexOf(":");
|
|
1866
|
+
if (colonIdx <= 0) return null;
|
|
1867
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
1868
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
1869
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
1870
|
+
return { [prop]: cssValue };
|
|
1871
|
+
},
|
|
1806
1872
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1807
1873
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1808
1874
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -1996,30 +2062,12 @@ var RESOLVERS = {
|
|
|
1996
2062
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
1997
2063
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
1998
2064
|
// so it never reaches element inline styles.
|
|
1999
|
-
animate: ({ value, isArbitrary }) => {
|
|
2000
|
-
if (!getEffectiveIsWeb()) return null;
|
|
2001
|
-
if (isArbitrary) return
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
animation: "kb-spin 1s linear infinite",
|
|
2006
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
2007
|
-
},
|
|
2008
|
-
ping: {
|
|
2009
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
2010
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
2011
|
-
},
|
|
2012
|
-
pulse: {
|
|
2013
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
2014
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
2015
|
-
},
|
|
2016
|
-
bounce: {
|
|
2017
|
-
animation: "kb-bounce 1s infinite",
|
|
2018
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
2019
|
-
}
|
|
2020
|
-
};
|
|
2021
|
-
const def = defs[value];
|
|
2022
|
-
return def ? def : null;
|
|
2065
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
2066
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2067
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
2068
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
2069
|
+
const custom = theme.animation?.[value];
|
|
2070
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
2023
2071
|
},
|
|
2024
2072
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
2025
2073
|
"line-clamp": ({ value }) => {
|
package/dist/index.d.mts
CHANGED
|
@@ -90,6 +90,23 @@ interface ThemeConfig {
|
|
|
90
90
|
flex: Record<string, number | string>;
|
|
91
91
|
shadow: Record<string, StyleValue>;
|
|
92
92
|
screens: Record<string, string | number>;
|
|
93
|
+
/**
|
|
94
|
+
* Custom @keyframes, web only. Each key is a keyframe name, its value maps
|
|
95
|
+
* percentage/from/to selectors to a plain CSS declaration object (camelCase
|
|
96
|
+
* properties, same shape as an inline style object):
|
|
97
|
+
* keyframes: { wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } } }
|
|
98
|
+
* Referenced from `animation` below, or directly via animate-[wiggle_1s_ease-in-out].
|
|
99
|
+
*/
|
|
100
|
+
keyframes: Record<string, Record<string, StyleValue>>;
|
|
101
|
+
/**
|
|
102
|
+
* Named animation shorthands built on `keyframes` above, referenced via
|
|
103
|
+
* animate-{name} (e.g. animate-wiggle):
|
|
104
|
+
* animation: { wiggle: 'wiggle 1s ease-in-out infinite' }
|
|
105
|
+
* The first word must match a `keyframes` key so its @keyframes rule can be
|
|
106
|
+
* injected alongside the animation — a name with no matching keyframes entry
|
|
107
|
+
* still sets the `animation` CSS property, it just won't animate anything.
|
|
108
|
+
*/
|
|
109
|
+
animation: Record<string, string>;
|
|
93
110
|
[key: string]: unknown;
|
|
94
111
|
}
|
|
95
112
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -90,6 +90,23 @@ interface ThemeConfig {
|
|
|
90
90
|
flex: Record<string, number | string>;
|
|
91
91
|
shadow: Record<string, StyleValue>;
|
|
92
92
|
screens: Record<string, string | number>;
|
|
93
|
+
/**
|
|
94
|
+
* Custom @keyframes, web only. Each key is a keyframe name, its value maps
|
|
95
|
+
* percentage/from/to selectors to a plain CSS declaration object (camelCase
|
|
96
|
+
* properties, same shape as an inline style object):
|
|
97
|
+
* keyframes: { wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } } }
|
|
98
|
+
* Referenced from `animation` below, or directly via animate-[wiggle_1s_ease-in-out].
|
|
99
|
+
*/
|
|
100
|
+
keyframes: Record<string, Record<string, StyleValue>>;
|
|
101
|
+
/**
|
|
102
|
+
* Named animation shorthands built on `keyframes` above, referenced via
|
|
103
|
+
* animate-{name} (e.g. animate-wiggle):
|
|
104
|
+
* animation: { wiggle: 'wiggle 1s ease-in-out infinite' }
|
|
105
|
+
* The first word must match a `keyframes` key so its @keyframes rule can be
|
|
106
|
+
* injected alongside the animation — a name with no matching keyframes entry
|
|
107
|
+
* still sets the `animation` CSS property, it just won't animate anything.
|
|
108
|
+
*/
|
|
109
|
+
animation: Record<string, string>;
|
|
93
110
|
[key: string]: unknown;
|
|
94
111
|
}
|
|
95
112
|
/**
|
package/dist/index.js
CHANGED
|
@@ -625,7 +625,13 @@ var defaultTheme = {
|
|
|
625
625
|
xl: 1280,
|
|
626
626
|
// desktop
|
|
627
627
|
"2xl": 1536
|
|
628
|
-
}
|
|
628
|
+
},
|
|
629
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
630
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
631
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
632
|
+
// alongside them without needing to redeclare the built-ins.
|
|
633
|
+
keyframes: {},
|
|
634
|
+
animation: {}
|
|
629
635
|
};
|
|
630
636
|
|
|
631
637
|
// src/core/reset.ts
|
|
@@ -1401,6 +1407,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1401
1407
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1402
1408
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1403
1409
|
};
|
|
1410
|
+
function keyframeDeclToCSS(decl) {
|
|
1411
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1412
|
+
}
|
|
1413
|
+
function buildKeyframeCSS(name, steps) {
|
|
1414
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1415
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1416
|
+
}
|
|
1417
|
+
var BUILTIN_ANIMATIONS = {
|
|
1418
|
+
none: { animation: "none" },
|
|
1419
|
+
spin: {
|
|
1420
|
+
animation: "kb-spin 1s linear infinite",
|
|
1421
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1422
|
+
},
|
|
1423
|
+
ping: {
|
|
1424
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1425
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1426
|
+
},
|
|
1427
|
+
pulse: {
|
|
1428
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1429
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1430
|
+
},
|
|
1431
|
+
bounce: {
|
|
1432
|
+
animation: "kb-bounce 1s infinite",
|
|
1433
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1437
|
+
const words = animation.trim().split(/\s+/);
|
|
1438
|
+
const name = words[0];
|
|
1439
|
+
if (!name) return { animation };
|
|
1440
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1441
|
+
if (builtin?.__keyframe) {
|
|
1442
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1443
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1444
|
+
}
|
|
1445
|
+
const steps = keyframes?.[name];
|
|
1446
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1447
|
+
}
|
|
1404
1448
|
var RESOLVERS = {
|
|
1405
1449
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1406
1450
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1964,6 +2008,28 @@ var RESOLVERS = {
|
|
|
1964
2008
|
const presets = { none: "none" };
|
|
1965
2009
|
return value in presets ? { content: presets[value] } : null;
|
|
1966
2010
|
},
|
|
2011
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
2012
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
2013
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
2014
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
2015
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
2016
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
2017
|
+
//
|
|
2018
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
2019
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
2020
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
2021
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
2022
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
2023
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
2024
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
2025
|
+
if (negative) return null;
|
|
2026
|
+
const colonIdx = value.indexOf(":");
|
|
2027
|
+
if (colonIdx <= 0) return null;
|
|
2028
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
2029
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
2030
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
2031
|
+
return { [prop]: cssValue };
|
|
2032
|
+
},
|
|
1967
2033
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1968
2034
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1969
2035
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -2157,30 +2223,12 @@ var RESOLVERS = {
|
|
|
2157
2223
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
2158
2224
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
2159
2225
|
// so it never reaches element inline styles.
|
|
2160
|
-
animate: ({ value, isArbitrary }) => {
|
|
2226
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
2161
2227
|
if (!getEffectiveIsWeb()) return null;
|
|
2162
|
-
if (isArbitrary) return
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
animation: "kb-spin 1s linear infinite",
|
|
2167
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
2168
|
-
},
|
|
2169
|
-
ping: {
|
|
2170
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
2171
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
2172
|
-
},
|
|
2173
|
-
pulse: {
|
|
2174
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
2175
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
2176
|
-
},
|
|
2177
|
-
bounce: {
|
|
2178
|
-
animation: "kb-bounce 1s infinite",
|
|
2179
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
2180
|
-
}
|
|
2181
|
-
};
|
|
2182
|
-
const def = defs[value];
|
|
2183
|
-
return def ? def : null;
|
|
2228
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
2229
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
2230
|
+
const custom = theme.animation?.[value];
|
|
2231
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
2184
2232
|
},
|
|
2185
2233
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
2186
2234
|
"line-clamp": ({ value }) => {
|
package/dist/index.mjs
CHANGED
package/dist/jsx-dev-runtime.js
CHANGED
|
@@ -580,7 +580,13 @@ var defaultTheme = {
|
|
|
580
580
|
xl: 1280,
|
|
581
581
|
// desktop
|
|
582
582
|
"2xl": 1536
|
|
583
|
-
}
|
|
583
|
+
},
|
|
584
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
585
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
586
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
587
|
+
// alongside them without needing to redeclare the built-ins.
|
|
588
|
+
keyframes: {},
|
|
589
|
+
animation: {}
|
|
584
590
|
};
|
|
585
591
|
|
|
586
592
|
// src/core/reset.ts
|
|
@@ -1359,6 +1365,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1359
1365
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1360
1366
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1361
1367
|
};
|
|
1368
|
+
function keyframeDeclToCSS(decl) {
|
|
1369
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1370
|
+
}
|
|
1371
|
+
function buildKeyframeCSS(name, steps) {
|
|
1372
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1373
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1374
|
+
}
|
|
1375
|
+
var BUILTIN_ANIMATIONS = {
|
|
1376
|
+
none: { animation: "none" },
|
|
1377
|
+
spin: {
|
|
1378
|
+
animation: "kb-spin 1s linear infinite",
|
|
1379
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1380
|
+
},
|
|
1381
|
+
ping: {
|
|
1382
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1383
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1384
|
+
},
|
|
1385
|
+
pulse: {
|
|
1386
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1387
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1388
|
+
},
|
|
1389
|
+
bounce: {
|
|
1390
|
+
animation: "kb-bounce 1s infinite",
|
|
1391
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1395
|
+
const words = animation.trim().split(/\s+/);
|
|
1396
|
+
const name = words[0];
|
|
1397
|
+
if (!name) return { animation };
|
|
1398
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1399
|
+
if (builtin?.__keyframe) {
|
|
1400
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1401
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1402
|
+
}
|
|
1403
|
+
const steps = keyframes?.[name];
|
|
1404
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1405
|
+
}
|
|
1362
1406
|
var RESOLVERS = {
|
|
1363
1407
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1364
1408
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1922,6 +1966,28 @@ var RESOLVERS = {
|
|
|
1922
1966
|
const presets = { none: "none" };
|
|
1923
1967
|
return value in presets ? { content: presets[value] } : null;
|
|
1924
1968
|
},
|
|
1969
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
1970
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
1971
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
1972
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
1973
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
1974
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
1975
|
+
//
|
|
1976
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
1977
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
1978
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
1979
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
1980
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
1981
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
1982
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
1983
|
+
if (negative) return null;
|
|
1984
|
+
const colonIdx = value.indexOf(":");
|
|
1985
|
+
if (colonIdx <= 0) return null;
|
|
1986
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
1987
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
1988
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
1989
|
+
return { [prop]: cssValue };
|
|
1990
|
+
},
|
|
1925
1991
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1926
1992
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1927
1993
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -2115,30 +2181,12 @@ var RESOLVERS = {
|
|
|
2115
2181
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
2116
2182
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
2117
2183
|
// so it never reaches element inline styles.
|
|
2118
|
-
animate: ({ value, isArbitrary }) => {
|
|
2119
|
-
if (!getEffectiveIsWeb()) return null;
|
|
2120
|
-
if (isArbitrary) return
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
animation: "kb-spin 1s linear infinite",
|
|
2125
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
2126
|
-
},
|
|
2127
|
-
ping: {
|
|
2128
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
2129
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
2130
|
-
},
|
|
2131
|
-
pulse: {
|
|
2132
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
2133
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
2134
|
-
},
|
|
2135
|
-
bounce: {
|
|
2136
|
-
animation: "kb-bounce 1s infinite",
|
|
2137
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
2138
|
-
}
|
|
2139
|
-
};
|
|
2140
|
-
const def = defs[value];
|
|
2141
|
-
return def ? def : null;
|
|
2184
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
2185
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2186
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
2187
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
2188
|
+
const custom = theme.animation?.[value];
|
|
2189
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
2142
2190
|
},
|
|
2143
2191
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
2144
2192
|
"line-clamp": ({ value }) => {
|
package/dist/jsx-dev-runtime.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
Fragment,
|
|
4
4
|
jsx,
|
|
5
5
|
jsxs
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-7DHBNCL5.mjs";
|
|
7
|
+
import "./chunk-HCRWLOJE.mjs";
|
|
8
8
|
|
|
9
9
|
// src/jsx-dev-runtime.tsx
|
|
10
10
|
function jsxDEV(type, props, key, isStaticChildren, source, self) {
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -581,7 +581,13 @@ var defaultTheme = {
|
|
|
581
581
|
xl: 1280,
|
|
582
582
|
// desktop
|
|
583
583
|
"2xl": 1536
|
|
584
|
-
}
|
|
584
|
+
},
|
|
585
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
586
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
587
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
588
|
+
// alongside them without needing to redeclare the built-ins.
|
|
589
|
+
keyframes: {},
|
|
590
|
+
animation: {}
|
|
585
591
|
};
|
|
586
592
|
|
|
587
593
|
// src/core/reset.ts
|
|
@@ -1360,6 +1366,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1360
1366
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1361
1367
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1362
1368
|
};
|
|
1369
|
+
function keyframeDeclToCSS(decl) {
|
|
1370
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1371
|
+
}
|
|
1372
|
+
function buildKeyframeCSS(name, steps) {
|
|
1373
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1374
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1375
|
+
}
|
|
1376
|
+
var BUILTIN_ANIMATIONS = {
|
|
1377
|
+
none: { animation: "none" },
|
|
1378
|
+
spin: {
|
|
1379
|
+
animation: "kb-spin 1s linear infinite",
|
|
1380
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1381
|
+
},
|
|
1382
|
+
ping: {
|
|
1383
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1384
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1385
|
+
},
|
|
1386
|
+
pulse: {
|
|
1387
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1388
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1389
|
+
},
|
|
1390
|
+
bounce: {
|
|
1391
|
+
animation: "kb-bounce 1s infinite",
|
|
1392
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1396
|
+
const words = animation.trim().split(/\s+/);
|
|
1397
|
+
const name = words[0];
|
|
1398
|
+
if (!name) return { animation };
|
|
1399
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1400
|
+
if (builtin?.__keyframe) {
|
|
1401
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1402
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1403
|
+
}
|
|
1404
|
+
const steps = keyframes?.[name];
|
|
1405
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1406
|
+
}
|
|
1363
1407
|
var RESOLVERS = {
|
|
1364
1408
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1365
1409
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1923,6 +1967,28 @@ var RESOLVERS = {
|
|
|
1923
1967
|
const presets = { none: "none" };
|
|
1924
1968
|
return value in presets ? { content: presets[value] } : null;
|
|
1925
1969
|
},
|
|
1970
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
1971
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
1972
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
1973
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
1974
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
1975
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
1976
|
+
//
|
|
1977
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
1978
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
1979
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
1980
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
1981
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
1982
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
1983
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
1984
|
+
if (negative) return null;
|
|
1985
|
+
const colonIdx = value.indexOf(":");
|
|
1986
|
+
if (colonIdx <= 0) return null;
|
|
1987
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
1988
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
1989
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
1990
|
+
return { [prop]: cssValue };
|
|
1991
|
+
},
|
|
1926
1992
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1927
1993
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1928
1994
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -2116,30 +2182,12 @@ var RESOLVERS = {
|
|
|
2116
2182
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
2117
2183
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
2118
2184
|
// so it never reaches element inline styles.
|
|
2119
|
-
animate: ({ value, isArbitrary }) => {
|
|
2120
|
-
if (!getEffectiveIsWeb()) return null;
|
|
2121
|
-
if (isArbitrary) return
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
animation: "kb-spin 1s linear infinite",
|
|
2126
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
2127
|
-
},
|
|
2128
|
-
ping: {
|
|
2129
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
2130
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
2131
|
-
},
|
|
2132
|
-
pulse: {
|
|
2133
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
2134
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
2135
|
-
},
|
|
2136
|
-
bounce: {
|
|
2137
|
-
animation: "kb-bounce 1s infinite",
|
|
2138
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
2139
|
-
}
|
|
2140
|
-
};
|
|
2141
|
-
const def = defs[value];
|
|
2142
|
-
return def ? def : null;
|
|
2185
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
2186
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2187
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
2188
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
2189
|
+
const custom = theme.animation?.[value];
|
|
2190
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
2143
2191
|
},
|
|
2144
2192
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
2145
2193
|
"line-clamp": ({ value }) => {
|
package/dist/jsx-runtime.mjs
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
_invalidateDefaultFontCache,
|
|
5
5
|
jsx,
|
|
6
6
|
jsxs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7DHBNCL5.mjs";
|
|
8
8
|
import {
|
|
9
9
|
registerWebElement
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HCRWLOJE.mjs";
|
|
11
11
|
export {
|
|
12
12
|
Fragment,
|
|
13
13
|
_invalidateDefaultFontCache,
|
package/dist/vite-plugin.d.mts
CHANGED
|
@@ -65,6 +65,23 @@ interface ThemeConfig {
|
|
|
65
65
|
flex: Record<string, number | string>;
|
|
66
66
|
shadow: Record<string, StyleValue>;
|
|
67
67
|
screens: Record<string, string | number>;
|
|
68
|
+
/**
|
|
69
|
+
* Custom @keyframes, web only. Each key is a keyframe name, its value maps
|
|
70
|
+
* percentage/from/to selectors to a plain CSS declaration object (camelCase
|
|
71
|
+
* properties, same shape as an inline style object):
|
|
72
|
+
* keyframes: { wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } } }
|
|
73
|
+
* Referenced from `animation` below, or directly via animate-[wiggle_1s_ease-in-out].
|
|
74
|
+
*/
|
|
75
|
+
keyframes: Record<string, Record<string, StyleValue>>;
|
|
76
|
+
/**
|
|
77
|
+
* Named animation shorthands built on `keyframes` above, referenced via
|
|
78
|
+
* animate-{name} (e.g. animate-wiggle):
|
|
79
|
+
* animation: { wiggle: 'wiggle 1s ease-in-out infinite' }
|
|
80
|
+
* The first word must match a `keyframes` key so its @keyframes rule can be
|
|
81
|
+
* injected alongside the animation — a name with no matching keyframes entry
|
|
82
|
+
* still sets the `animation` CSS property, it just won't animate anything.
|
|
83
|
+
*/
|
|
84
|
+
animation: Record<string, string>;
|
|
68
85
|
[key: string]: unknown;
|
|
69
86
|
}
|
|
70
87
|
/**
|
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -65,6 +65,23 @@ interface ThemeConfig {
|
|
|
65
65
|
flex: Record<string, number | string>;
|
|
66
66
|
shadow: Record<string, StyleValue>;
|
|
67
67
|
screens: Record<string, string | number>;
|
|
68
|
+
/**
|
|
69
|
+
* Custom @keyframes, web only. Each key is a keyframe name, its value maps
|
|
70
|
+
* percentage/from/to selectors to a plain CSS declaration object (camelCase
|
|
71
|
+
* properties, same shape as an inline style object):
|
|
72
|
+
* keyframes: { wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } } }
|
|
73
|
+
* Referenced from `animation` below, or directly via animate-[wiggle_1s_ease-in-out].
|
|
74
|
+
*/
|
|
75
|
+
keyframes: Record<string, Record<string, StyleValue>>;
|
|
76
|
+
/**
|
|
77
|
+
* Named animation shorthands built on `keyframes` above, referenced via
|
|
78
|
+
* animate-{name} (e.g. animate-wiggle):
|
|
79
|
+
* animation: { wiggle: 'wiggle 1s ease-in-out infinite' }
|
|
80
|
+
* The first word must match a `keyframes` key so its @keyframes rule can be
|
|
81
|
+
* injected alongside the animation — a name with no matching keyframes entry
|
|
82
|
+
* still sets the `animation` CSS property, it just won't animate anything.
|
|
83
|
+
*/
|
|
84
|
+
animation: Record<string, string>;
|
|
68
85
|
[key: string]: unknown;
|
|
69
86
|
}
|
|
70
87
|
/**
|
package/dist/vite-plugin.js
CHANGED
|
@@ -640,7 +640,13 @@ var defaultTheme = {
|
|
|
640
640
|
xl: 1280,
|
|
641
641
|
// desktop
|
|
642
642
|
"2xl": 1536
|
|
643
|
-
}
|
|
643
|
+
},
|
|
644
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
645
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
646
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
647
|
+
// alongside them without needing to redeclare the built-ins.
|
|
648
|
+
keyframes: {},
|
|
649
|
+
animation: {}
|
|
644
650
|
};
|
|
645
651
|
|
|
646
652
|
// src/core/utilities.ts
|
|
@@ -1174,6 +1180,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1174
1180
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1175
1181
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1176
1182
|
};
|
|
1183
|
+
function keyframeDeclToCSS(decl) {
|
|
1184
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1185
|
+
}
|
|
1186
|
+
function buildKeyframeCSS(name, steps) {
|
|
1187
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1188
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1189
|
+
}
|
|
1190
|
+
var BUILTIN_ANIMATIONS = {
|
|
1191
|
+
none: { animation: "none" },
|
|
1192
|
+
spin: {
|
|
1193
|
+
animation: "kb-spin 1s linear infinite",
|
|
1194
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1195
|
+
},
|
|
1196
|
+
ping: {
|
|
1197
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1198
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1199
|
+
},
|
|
1200
|
+
pulse: {
|
|
1201
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1202
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1203
|
+
},
|
|
1204
|
+
bounce: {
|
|
1205
|
+
animation: "kb-bounce 1s infinite",
|
|
1206
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1210
|
+
const words = animation.trim().split(/\s+/);
|
|
1211
|
+
const name = words[0];
|
|
1212
|
+
if (!name) return { animation };
|
|
1213
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1214
|
+
if (builtin?.__keyframe) {
|
|
1215
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1216
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1217
|
+
}
|
|
1218
|
+
const steps = keyframes?.[name];
|
|
1219
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1220
|
+
}
|
|
1177
1221
|
var RESOLVERS = {
|
|
1178
1222
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1179
1223
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1737,6 +1781,28 @@ var RESOLVERS = {
|
|
|
1737
1781
|
const presets = { none: "none" };
|
|
1738
1782
|
return value in presets ? { content: presets[value] } : null;
|
|
1739
1783
|
},
|
|
1784
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
1785
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
1786
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
1787
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
1788
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
1789
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
1790
|
+
//
|
|
1791
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
1792
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
1793
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
1794
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
1795
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
1796
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
1797
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
1798
|
+
if (negative) return null;
|
|
1799
|
+
const colonIdx = value.indexOf(":");
|
|
1800
|
+
if (colonIdx <= 0) return null;
|
|
1801
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
1802
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
1803
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
1804
|
+
return { [prop]: cssValue };
|
|
1805
|
+
},
|
|
1740
1806
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1741
1807
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1742
1808
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -1930,30 +1996,12 @@ var RESOLVERS = {
|
|
|
1930
1996
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
1931
1997
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
1932
1998
|
// so it never reaches element inline styles.
|
|
1933
|
-
animate: ({ value, isArbitrary }) => {
|
|
1934
|
-
if (!getEffectiveIsWeb()) return null;
|
|
1935
|
-
if (isArbitrary) return
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
animation: "kb-spin 1s linear infinite",
|
|
1940
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1941
|
-
},
|
|
1942
|
-
ping: {
|
|
1943
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1944
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1945
|
-
},
|
|
1946
|
-
pulse: {
|
|
1947
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1948
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1949
|
-
},
|
|
1950
|
-
bounce: {
|
|
1951
|
-
animation: "kb-bounce 1s infinite",
|
|
1952
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1953
|
-
}
|
|
1954
|
-
};
|
|
1955
|
-
const def = defs[value];
|
|
1956
|
-
return def ? def : null;
|
|
1999
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
2000
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2001
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
2002
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
2003
|
+
const custom = theme.animation?.[value];
|
|
2004
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
1957
2005
|
},
|
|
1958
2006
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
1959
2007
|
"line-clamp": ({ value }) => {
|
|
@@ -3445,9 +3493,21 @@ function kbach(userConfigOrOptions) {
|
|
|
3445
3493
|
},
|
|
3446
3494
|
// When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
|
|
3447
3495
|
// so styles come solely from the static stylesheet — no duplicate injection.
|
|
3496
|
+
//
|
|
3497
|
+
// Anchored to the START of a source line (^\s*, with the m flag) rather than a
|
|
3498
|
+
// bare \bimport\b anywhere in the file — disableRuntimeCSS() sets a globalThis
|
|
3499
|
+
// flag with no way to re-enable it, so a false match here doesn't just misfire
|
|
3500
|
+
// once, it silently kills ALL runtime CSS injection for the rest of that page's
|
|
3501
|
+
// JS lifetime, including on completely unrelated components. A real import
|
|
3502
|
+
// statement is always the first token on its line (give or take whitespace);
|
|
3503
|
+
// this same text appearing as part of a STRING — e.g. a docs site's own code
|
|
3504
|
+
// sample literally showing `import "./kbach.css";` as example text, which is
|
|
3505
|
+
// exactly what happened here — is never positioned that way, since something
|
|
3506
|
+
// always precedes it syntactically (a quote, JSX braces, an array bracket, …).
|
|
3448
3507
|
transform(code, id) {
|
|
3449
3508
|
if (!/\.(tsx?|jsx?|mjs?)$/.test(id)) return;
|
|
3450
|
-
if (
|
|
3509
|
+
if (!/^\s*import\b[^;]*['"][^'"]*kbach\.css['"]/im.test(code)) return;
|
|
3510
|
+
warn(`disableRuntimeCSS() auto-triggered by an import in ${id.replace(/\\/g, "/")}`);
|
|
3451
3511
|
return {
|
|
3452
3512
|
code: `import { disableRuntimeCSS } from '@kbach/react';
|
|
3453
3513
|
disableRuntimeCSS();
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -606,7 +606,13 @@ var defaultTheme = {
|
|
|
606
606
|
xl: 1280,
|
|
607
607
|
// desktop
|
|
608
608
|
"2xl": 1536
|
|
609
|
-
}
|
|
609
|
+
},
|
|
610
|
+
// Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
|
|
611
|
+
// directly in the `animate` resolver, not through this theme table. This exists so
|
|
612
|
+
// user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
|
|
613
|
+
// alongside them without needing to redeclare the built-ins.
|
|
614
|
+
keyframes: {},
|
|
615
|
+
animation: {}
|
|
610
616
|
};
|
|
611
617
|
|
|
612
618
|
// src/core/utilities.ts
|
|
@@ -1140,6 +1146,44 @@ var BORDER_SIDE_PROPS = {
|
|
|
1140
1146
|
"border-b": ["borderBottomWidth", "borderBottomColor"],
|
|
1141
1147
|
"border-l": ["borderLeftWidth", "borderLeftColor"]
|
|
1142
1148
|
};
|
|
1149
|
+
function keyframeDeclToCSS(decl) {
|
|
1150
|
+
return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
|
|
1151
|
+
}
|
|
1152
|
+
function buildKeyframeCSS(name, steps) {
|
|
1153
|
+
const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
|
|
1154
|
+
return `@keyframes ${name} { ${body} }`;
|
|
1155
|
+
}
|
|
1156
|
+
var BUILTIN_ANIMATIONS = {
|
|
1157
|
+
none: { animation: "none" },
|
|
1158
|
+
spin: {
|
|
1159
|
+
animation: "kb-spin 1s linear infinite",
|
|
1160
|
+
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1161
|
+
},
|
|
1162
|
+
ping: {
|
|
1163
|
+
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1164
|
+
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1165
|
+
},
|
|
1166
|
+
pulse: {
|
|
1167
|
+
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1168
|
+
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1169
|
+
},
|
|
1170
|
+
bounce: {
|
|
1171
|
+
animation: "kb-bounce 1s infinite",
|
|
1172
|
+
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
function buildAnimationValue(animation, keyframes) {
|
|
1176
|
+
const words = animation.trim().split(/\s+/);
|
|
1177
|
+
const name = words[0];
|
|
1178
|
+
if (!name) return { animation };
|
|
1179
|
+
const builtin = BUILTIN_ANIMATIONS[name];
|
|
1180
|
+
if (builtin?.__keyframe) {
|
|
1181
|
+
const kbName = builtin.animation.split(/\s+/, 1)[0];
|
|
1182
|
+
return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
|
|
1183
|
+
}
|
|
1184
|
+
const steps = keyframes?.[name];
|
|
1185
|
+
return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
|
|
1186
|
+
}
|
|
1143
1187
|
var RESOLVERS = {
|
|
1144
1188
|
// ── Background ─────────────────────────────────────────────────────────────
|
|
1145
1189
|
bg: ({ value, isArbitrary }, { colors }) => {
|
|
@@ -1703,6 +1747,28 @@ var RESOLVERS = {
|
|
|
1703
1747
|
const presets = { none: "none" };
|
|
1704
1748
|
return value in presets ? { content: presets[value] } : null;
|
|
1705
1749
|
},
|
|
1750
|
+
// ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
|
|
1751
|
+
// e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
|
|
1752
|
+
// as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
|
|
1753
|
+
// presumably a typo) parses, which is why this only fires when the arbitrary value
|
|
1754
|
+
// itself contains a "property:value" pair; anything else (no colon) still resolves
|
|
1755
|
+
// to null exactly as before this resolver existed, rather than guessing a property.
|
|
1756
|
+
//
|
|
1757
|
+
// Only the FIRST colon splits property from value — the value itself may contain
|
|
1758
|
+
// more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
|
|
1759
|
+
// camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
|
|
1760
|
+
// custom property) key, so it can be used as the style object key as-written with no
|
|
1761
|
+
// extra case conversion — it only needs to look like a plausible CSS property name.
|
|
1762
|
+
"": ({ value, isArbitrary, negative }) => {
|
|
1763
|
+
if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
|
|
1764
|
+
if (negative) return null;
|
|
1765
|
+
const colonIdx = value.indexOf(":");
|
|
1766
|
+
if (colonIdx <= 0) return null;
|
|
1767
|
+
const prop = value.slice(0, colonIdx).trim();
|
|
1768
|
+
const cssValue = value.slice(colonIdx + 1).trim();
|
|
1769
|
+
if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
|
|
1770
|
+
return { [prop]: cssValue };
|
|
1771
|
+
},
|
|
1706
1772
|
// ── Outline extended (web-only) ───────────────────────────────────────────
|
|
1707
1773
|
outline: ({ value, isArbitrary }, { colors }) => {
|
|
1708
1774
|
if (!getEffectiveIsWeb()) return null;
|
|
@@ -1896,30 +1962,12 @@ var RESOLVERS = {
|
|
|
1896
1962
|
// Each variant injects a @keyframes rule once via the __keyframe marker.
|
|
1897
1963
|
// resolver.ts detects __keyframe, injects the rule, then strips the marker
|
|
1898
1964
|
// so it never reaches element inline styles.
|
|
1899
|
-
animate: ({ value, isArbitrary }) => {
|
|
1900
|
-
if (!getEffectiveIsWeb()) return null;
|
|
1901
|
-
if (isArbitrary) return
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
animation: "kb-spin 1s linear infinite",
|
|
1906
|
-
__keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
|
|
1907
|
-
},
|
|
1908
|
-
ping: {
|
|
1909
|
-
animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
|
|
1910
|
-
__keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
|
|
1911
|
-
},
|
|
1912
|
-
pulse: {
|
|
1913
|
-
animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
|
|
1914
|
-
__keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
|
|
1915
|
-
},
|
|
1916
|
-
bounce: {
|
|
1917
|
-
animation: "kb-bounce 1s infinite",
|
|
1918
|
-
__keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
|
|
1919
|
-
}
|
|
1920
|
-
};
|
|
1921
|
-
const def = defs[value];
|
|
1922
|
-
return def ? def : null;
|
|
1965
|
+
animate: ({ value, isArbitrary }, theme) => {
|
|
1966
|
+
if (!getEffectiveIsWeb()) return null;
|
|
1967
|
+
if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
|
|
1968
|
+
if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
|
|
1969
|
+
const custom = theme.animation?.[value];
|
|
1970
|
+
return custom ? buildAnimationValue(custom, theme.keyframes) : null;
|
|
1923
1971
|
},
|
|
1924
1972
|
// ── Line-clamp (web-only) ─────────────────────────────────────────────────
|
|
1925
1973
|
"line-clamp": ({ value }) => {
|
|
@@ -3411,9 +3459,21 @@ function kbach(userConfigOrOptions) {
|
|
|
3411
3459
|
},
|
|
3412
3460
|
// When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
|
|
3413
3461
|
// so styles come solely from the static stylesheet — no duplicate injection.
|
|
3462
|
+
//
|
|
3463
|
+
// Anchored to the START of a source line (^\s*, with the m flag) rather than a
|
|
3464
|
+
// bare \bimport\b anywhere in the file — disableRuntimeCSS() sets a globalThis
|
|
3465
|
+
// flag with no way to re-enable it, so a false match here doesn't just misfire
|
|
3466
|
+
// once, it silently kills ALL runtime CSS injection for the rest of that page's
|
|
3467
|
+
// JS lifetime, including on completely unrelated components. A real import
|
|
3468
|
+
// statement is always the first token on its line (give or take whitespace);
|
|
3469
|
+
// this same text appearing as part of a STRING — e.g. a docs site's own code
|
|
3470
|
+
// sample literally showing `import "./kbach.css";` as example text, which is
|
|
3471
|
+
// exactly what happened here — is never positioned that way, since something
|
|
3472
|
+
// always precedes it syntactically (a quote, JSX braces, an array bracket, …).
|
|
3414
3473
|
transform(code, id) {
|
|
3415
3474
|
if (!/\.(tsx?|jsx?|mjs?)$/.test(id)) return;
|
|
3416
|
-
if (
|
|
3475
|
+
if (!/^\s*import\b[^;]*['"][^'"]*kbach\.css['"]/im.test(code)) return;
|
|
3476
|
+
warn(`disableRuntimeCSS() auto-triggered by an import in ${id.replace(/\\/g, "/")}`);
|
|
3417
3477
|
return {
|
|
3418
3478
|
code: `import { disableRuntimeCSS } from '@kbach/react';
|
|
3419
3479
|
disableRuntimeCSS();
|