Skip to content

Commit ec5f730

Browse files
committed
Adds fluent @Keyframes support and 126 missing CSS property methods.
1 parent 622f987 commit ec5f730

34 files changed

Lines changed: 6240 additions & 946 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "purestack-repo",
4-
"version": "1.1.16",
4+
"version": "1.1.17",
55
"packageManager": "yarn@4.18.1",
66
"type": "module",
77
"sideEffects": false,

‎packages/purestack/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "purestack",
3-
"version": "1.1.16",
3+
"version": "1.1.17",
44
"packageManager": "yarn@4.9.2",
55
"module": "./dist/purestack.mjs",
66
"types": "./dist/purestack.d.mts",

‎packages/purestack/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export const version: string = '1.1.16'
1+
export const version: string = '1.1.17'
22

33
export * from '@purestack/ts-ssg'

‎packages/ts-common/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@purestack/ts-common",
3-
"version": "1.1.16",
3+
"version": "1.1.17",
44
"packageManager": "yarn@4.9.2",
55
"module": "./dist/ts-common.mjs",
66
"types": "./dist/ts-common.d.mts",

‎packages/ts-components/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@purestack/ts-components",
3-
"version": "1.1.16",
3+
"version": "1.1.17",
44
"packageManager": "yarn@4.9.2",
55
"module": "./dist/ts-components.mjs",
66
"types": "./dist/ts-components.d.mts",

‎packages/ts-css/README.md‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,35 @@ style.css({ display: 'grid', gap: '1rem' })
9494
style.media('max-width: 720px').css({ display: 'block' })
9595
```
9696

97+
### Keyframe Animations
98+
99+
Use `.keyframes(name)` to create an animation definition and `.select()` to
100+
define its frames. Frame selectors are emitted without the parent selector.
101+
102+
```ts
103+
const style = s()
104+
const frames = style.keyframes('zt-memory-cycle')
105+
frames.select('0%, 4%, 40%, 100%').transform('translateY(0)')
106+
frames.select('12%, 28%').transform('translateY(-8px)')
107+
108+
style.select('.zt-slab-memory')
109+
.animation('zt-memory-cycle 8s ease-in-out infinite')
110+
style.select('.zt-slab-memory')
111+
.media('prefers-reduced-motion: reduce')
112+
.animation('none !important')
113+
```
114+
115+
Frames support `from`, `to`, percentages, and comma-separated percentage lists.
116+
Use existing property methods, `.css()`, or `.set()` for declarations, including
117+
motion-path properties such as `.set('offset-distance', '100%')`.
118+
Repeated names in the same container reuse their block; repeated frame selectors
119+
merge declarations using the normal style builder behavior. Definitions stay
120+
inside their containing media or scope block and participate in revision tracking.
121+
122+
Names are trimmed and must be non-empty; supply a valid CSS animation name and
123+
valid frame selectors. Names are emitted without rewriting and remain global
124+
inside `@scope`. Use distinct names for animations with different definitions.
125+
97126
### CSS Variables via `autoVar`
98127

99128
Values beginning with `$name` or `--name` are automatically converted to `var(...)`.
@@ -189,7 +218,8 @@ const button = s('.button').css({
189218
- `Style` extends `BaseStyle` and stores declarations in a map and nested selectors as children.
190219
- `.select()` creates a child style using the parent selector + the new selector (with automatic spacing unless a combinator or `:` is used).
191220
- `.media()` creates a sibling style under `@media(...)` and targets the current selector inside that media block.
192-
- `.toCSS()` serializes rules in a deterministic order: normal selectors first, then `@media` blocks, preserving creation order via internal IDs.
221+
- `.keyframes()` creates a named animation block whose children are unprefixed frame selectors.
222+
- `.toCSS()` serializes each container in a deterministic order: normal selectors first, then `@media`, `@scope`, and `@keyframes` blocks, preserving creation order within each group via internal IDs.
193223
- `.toPrettyCSS()` runs the output through Prettier (`parser: css`) with default formatting options.
194224

195225
## API Reference
@@ -204,6 +234,7 @@ Creates a `Style` instance. If a selector is provided, it becomes the root selec
204234
- `css(object)` - set many properties at once using `CSSProps`.
205235
- `select(selector)` - create or access a nested selector.
206236
- `media(query)` - create or access a `@media(...)` block scoped to this selector.
237+
- `keyframes(name)` - create or access a named animation; select its frames with `select()`.
207238
- `use(style)` - shallow-merge declarations from another style into this one.
208239
- `toCSS()` - serialize to CSS string.
209240
- `toPrettyCSS(options?)` - serialize and format using Prettier.
@@ -266,6 +297,7 @@ Key `BaseStyle` methods:
266297
- `css({...})`
267298
- `select(selector)`
268299
- `media(query)`
300+
- `keyframes(name)`
269301
- `use(style)`
270302
- `toCSS()`
271303
- `toPrettyCSS(options?)`
@@ -285,6 +317,22 @@ const stops = getGradient('#21a8c3', '#a50d60', 6, 'hsl')
285317

286318
`CSSProps` is a large type union of CSS properties used by `.css()` and property-specific methods on `Style`. It provides autocomplete and helps catch invalid property names or values.
287319

320+
## Maintaining Property Metadata
321+
322+
The generator combines the original `src/meta/cssProps.ts` with the handwritten
323+
additions in `src/meta/cssProps2.ts`. Maintain new properties in `cssProps2.ts`
324+
using the same `$`, `desc`, and optional `values.value` structure. Existing
325+
properties, including vendor-prefixed and older names, remain available.
326+
327+
Regenerate the fluent methods and explicit property types from the repository root:
328+
329+
```bash
330+
yarn tsx packages/ts-css/src/meta/generateStyle.ts
331+
```
332+
333+
This updates `src/style.ts` and `src/cssProps.ts`. Keep property additions in the
334+
metadata so they survive subsequent generator runs.
335+
288336
## Tests
289337

290338
```bash

‎packages/ts-css/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@purestack/ts-css",
3-
"version": "1.1.16",
3+
"version": "1.1.17",
44
"packageManager": "yarn@4.9.2",
55
"module": "./dist/ts-css.mjs",
66
"types": "./dist/ts-css.d.mts",

‎packages/ts-css/src/baseStyle.ts‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ export class BaseStyle<T extends RootStyle> extends RootStyle {
136136
return child
137137
}
138138

139+
/**
140+
* Create or access a named animation block. Select frames with `select()`:
141+
* `style.keyframes('fade-in').select('from').opacity('0')`.
142+
* Names are emitted as supplied and remain global inside `@scope`.
143+
*/
144+
keyframes(name: string) {
145+
const trimmed = name.trim()
146+
if (!trimmed) throw new Error('Keyframes name must be a non-empty string.')
147+
return this.selectWithoutParentKey(`@keyframes ${trimmed}`)
148+
}
149+
139150
toCSS() {
140151
let result = ''
141152
const children = [...this.children.entries()].sort((a, b) => {
@@ -147,11 +158,11 @@ export class BaseStyle<T extends RootStyle> extends RootStyle {
147158
})
148159
const selector = this.selector
149160
if (isGroupingRule(selector)) {
150-
const mediaContent = children
161+
const blockContent = children
151162
.map((x) => `\r\n${x[1].toCSS()}`)
152163
.join('')
153164
.trimEnd()
154-
result = `${selector} {${mediaContent}
165+
result = `${selector} {${blockContent}
155166
}
156167
157168
`
@@ -210,7 +221,11 @@ ${props}
210221
}
211222

212223
function isGroupingRule(selector: string) {
213-
return selector.startsWith('@media') || selector.startsWith('@scope')
224+
return (
225+
selector.startsWith('@media') ||
226+
selector.startsWith('@scope') ||
227+
selector.startsWith('@keyframes')
228+
)
214229
}
215230

216231
function buildScopeRule(rootSelector: string, limitSelector?: string) {

‎packages/ts-css/src/cssProps.ts‎

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,4 +1905,194 @@ export type CSSProps = {
19051905
| (string & {})
19061906
zIndex: number | string
19071907
zoom: 'normal' | (string & {})
1908+
accentColor: 'auto' | (string & {})
1909+
animationComposition: 'replace' | 'add' | 'accumulate' | (string & {})
1910+
appearance: 'none' | 'auto' | (string & {})
1911+
aspectRatio: 'auto' | (string & {})
1912+
backgroundPositionX: 'left' | 'center' | 'right' | (string & {})
1913+
backgroundPositionY: 'top' | 'center' | 'bottom' | (string & {})
1914+
borderBlock: string
1915+
borderBlockColor: string
1916+
borderBlockStyle: string
1917+
borderBlockWidth: string
1918+
borderEndEndRadius: string
1919+
borderEndStartRadius: string
1920+
borderInline: string
1921+
borderInlineColor: string
1922+
borderInlineStyle: string
1923+
borderInlineWidth: string
1924+
borderStartEndRadius: string
1925+
borderStartStartRadius: string
1926+
breakAfter: 'auto' | 'avoid' | 'page' | 'column' | (string & {})
1927+
breakBefore: 'auto' | 'avoid' | 'page' | 'column' | (string & {})
1928+
breakInside: 'auto' | 'avoid' | 'avoid-page' | 'avoid-column' | (string & {})
1929+
colorScheme: 'normal' | 'light' | 'dark' | (string & {})
1930+
columnRuleColor: string
1931+
containIntrinsicBlockSize: string
1932+
containIntrinsicHeight: string
1933+
containIntrinsicInlineSize: string
1934+
containIntrinsicSize: string
1935+
containIntrinsicWidth: string
1936+
container: string
1937+
containerName: string
1938+
containerType:
1939+
| 'normal'
1940+
| 'size'
1941+
| 'inline-size'
1942+
| 'scroll-state'
1943+
| (string & {})
1944+
contentVisibility: 'visible' | 'auto' | 'hidden' | (string & {})
1945+
counterSet: string
1946+
cx: string
1947+
cy: string
1948+
dominantBaseline:
1949+
| 'auto'
1950+
| 'alphabetic'
1951+
| 'middle'
1952+
| 'central'
1953+
| 'hanging'
1954+
| (string & {})
1955+
fontOpticalSizing: 'auto' | 'none' | (string & {})
1956+
fontPalette: 'normal' | 'light' | 'dark' | (string & {})
1957+
fontSynthesisSmallCaps: 'auto' | 'none' | (string & {})
1958+
fontSynthesisStyle: 'auto' | 'none' | (string & {})
1959+
fontSynthesisWeight: 'auto' | 'none' | (string & {})
1960+
fontVariationSettings: string
1961+
hyphenateCharacter: string
1962+
inset: string
1963+
insetBlock: string
1964+
insetBlockEnd: string
1965+
insetBlockStart: string
1966+
insetInline: string
1967+
insetInlineEnd: string
1968+
insetInlineStart: string
1969+
lineClamp: 'none' | (string & {})
1970+
marginBlock: string
1971+
marginInline: string
1972+
mask: string
1973+
maskClip:
1974+
| 'border-box'
1975+
| 'padding-box'
1976+
| 'content-box'
1977+
| 'no-clip'
1978+
| (string & {})
1979+
maskComposite: 'add' | 'subtract' | 'intersect' | 'exclude' | (string & {})
1980+
mathStyle: 'normal' | 'compact' | (string & {})
1981+
offset: string
1982+
offsetAnchor: string
1983+
offsetDistance: string
1984+
offsetPath: string
1985+
offsetPosition: string
1986+
offsetRotate: 'auto' | 'reverse' | (string & {})
1987+
overflowBlock:
1988+
| 'visible'
1989+
| 'hidden'
1990+
| 'clip'
1991+
| 'scroll'
1992+
| 'auto'
1993+
| (string & {})
1994+
overflowInline:
1995+
| 'visible'
1996+
| 'hidden'
1997+
| 'clip'
1998+
| 'scroll'
1999+
| 'auto'
2000+
| (string & {})
2001+
overscrollBehavior: string
2002+
overscrollBehaviorBlock: 'auto' | 'contain' | 'none' | (string & {})
2003+
overscrollBehaviorInline: 'auto' | 'contain' | 'none' | (string & {})
2004+
overscrollBehaviorX: 'auto' | 'contain' | 'none' | (string & {})
2005+
overscrollBehaviorY: 'auto' | 'contain' | 'none' | (string & {})
2006+
paddingBlock: string
2007+
paddingInline: string
2008+
page: string
2009+
placeContent: string
2010+
placeItems: string
2011+
placeSelf: string
2012+
printColorAdjust: 'economy' | 'exact' | (string & {})
2013+
r: string
2014+
rotate: string
2015+
rowGap: string
2016+
rx: string
2017+
ry: string
2018+
scale: string
2019+
scrollMargin: string
2020+
scrollMarginBlock: string
2021+
scrollMarginBlockEnd: string
2022+
scrollMarginBlockStart: string
2023+
scrollMarginBottom: string
2024+
scrollMarginInline: string
2025+
scrollMarginInlineEnd: string
2026+
scrollMarginInlineStart: string
2027+
scrollMarginLeft: string
2028+
scrollMarginRight: string
2029+
scrollMarginTop: string
2030+
scrollPadding: string
2031+
scrollPaddingBlock: string
2032+
scrollPaddingBlockEnd: string
2033+
scrollPaddingBlockStart: string
2034+
scrollPaddingBottom: string
2035+
scrollPaddingInline: string
2036+
scrollPaddingInlineEnd: string
2037+
scrollPaddingInlineStart: string
2038+
scrollPaddingLeft: string
2039+
scrollPaddingRight: string
2040+
scrollPaddingTop: string
2041+
scrollSnapAlign: 'none' | 'start' | 'end' | 'center' | (string & {})
2042+
scrollSnapStop: 'normal' | 'always' | (string & {})
2043+
scrollbarColor: string
2044+
scrollbarGutter: 'auto' | 'stable' | 'stable both-edges' | (string & {})
2045+
scrollbarWidth: 'auto' | 'thin' | 'none' | (string & {})
2046+
shapeRendering:
2047+
| 'auto'
2048+
| 'optimizeSpeed'
2049+
| 'crispEdges'
2050+
| 'geometricPrecision'
2051+
| (string & {})
2052+
textAutospace: string
2053+
textCombineUpright: 'none' | 'all' | (string & {})
2054+
textDecorationSkipInk: 'auto' | 'all' | 'none' | (string & {})
2055+
textDecorationThickness: 'auto' | 'from-font' | (string & {})
2056+
textEmphasis: string
2057+
textEmphasisColor: string
2058+
textEmphasisPosition: string
2059+
textEmphasisStyle: string
2060+
textUnderlineOffset: string
2061+
textUnderlinePosition:
2062+
| 'auto'
2063+
| 'from-font'
2064+
| 'under'
2065+
| 'left'
2066+
| 'right'
2067+
| (string & {})
2068+
textWrap: 'wrap' | 'nowrap' | 'balance' | 'pretty' | (string & {})
2069+
textWrapMode: 'wrap' | 'nowrap' | (string & {})
2070+
textWrapStyle: 'auto' | 'balance' | 'stable' | 'pretty' | (string & {})
2071+
transformBox:
2072+
| 'content-box'
2073+
| 'border-box'
2074+
| 'fill-box'
2075+
| 'stroke-box'
2076+
| 'view-box'
2077+
| (string & {})
2078+
transitionBehavior: 'normal' | 'allow-discrete' | (string & {})
2079+
translate: string
2080+
vectorEffect:
2081+
| 'none'
2082+
| 'non-scaling-stroke'
2083+
| 'non-scaling-size'
2084+
| 'non-rotation'
2085+
| 'fixed-position'
2086+
| (string & {})
2087+
viewTransitionClass: string
2088+
viewTransitionName: string
2089+
whiteSpaceCollapse:
2090+
| 'collapse'
2091+
| 'preserve'
2092+
| 'preserve-breaks'
2093+
| 'preserve-spaces'
2094+
| 'break-spaces'
2095+
| (string & {})
2096+
x: string
2097+
y: string
19082098
} & Record<string & {}, string | number>

0 commit comments

Comments
 (0)