chaincss 2.1.38 → 2.2.0
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/ROADMAP.md +31 -0
- package/dist/cli/index.js +458 -3
- package/dist/compiler/analyzer.d.ts +12 -0
- package/dist/compiler/css-if-transpiler.d.ts +33 -0
- package/dist/compiler/design-orchestrator.d.ts +119 -0
- package/dist/compiler/intent-engine.d.ts +49 -0
- package/dist/compiler/math-engine.d.ts +89 -0
- package/dist/compiler/scroll-timeline.d.ts +91 -0
- package/dist/compiler/style-graph.d.ts +30 -0
- package/dist/core/compiler.d.ts +12 -0
- package/dist/core/types.d.ts +145 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1765 -9
- package/dist/plugins/vite.js +451 -3
- package/package.json +1 -1
- package/src/compiler/analyzer.ts +62 -0
- package/src/compiler/css-if-transpiler.ts +117 -0
- package/src/compiler/design-orchestrator.ts +322 -0
- package/src/compiler/intent-engine.ts +402 -0
- package/src/compiler/math-engine.ts +511 -0
- package/src/compiler/scroll-timeline.ts +284 -0
- package/src/compiler/style-graph.ts +660 -0
- package/src/core/compiler.ts +40 -0
- package/src/core/types.ts +206 -0
- package/src/index.ts +103 -1
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +0 -1
- package/demo/index.html +0 -16
- package/demo/package.json +0 -20
- package/demo/src/App.tsx +0 -117
- package/demo/src/chaincss-barrel.ts +0 -9
- package/demo/src/main.tsx +0 -8
- package/demo/src/styles.chain.ts +0 -300
- package/demo/vite.config.ts +0 -46
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// src/compiler/scroll-timeline.ts
|
|
2
|
+
/**
|
|
3
|
+
* Scroll-Driven Animations Engine
|
|
4
|
+
*
|
|
5
|
+
* Compiles timeline-based animation descriptions into:
|
|
6
|
+
* 1. Native CSS scroll-timeline / view-timeline (Chromium 115+)
|
|
7
|
+
* 2. @supports fallback with JavaScript polyfill hint
|
|
8
|
+
*
|
|
9
|
+
* API inspiration: GSAP's ScrollTrigger, but compiles to 0kb JS.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
export interface ScrollTimelineConfig {
|
|
17
|
+
/** Name of the timeline */
|
|
18
|
+
name: string;
|
|
19
|
+
/** What drives the timeline */
|
|
20
|
+
source: 'scroll' | 'view';
|
|
21
|
+
/** The scrollable element (default: nearest scrollable ancestor) */
|
|
22
|
+
scroller?: 'nearest' | 'root' | 'self' | string;
|
|
23
|
+
/** Scroll axis */
|
|
24
|
+
axis?: 'block' | 'inline' | 'x' | 'y';
|
|
25
|
+
/** For view timelines: when does the element enter/exit */
|
|
26
|
+
inset?: string | { start: string; end: string };
|
|
27
|
+
/** Timeline range (for view timelines) */
|
|
28
|
+
range?: 'cover' | 'contain' | 'entry' | 'exit' | 'entry-crossing' | 'exit-crossing' | string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface KeyframeStep {
|
|
32
|
+
/** Percentage or keyword (e.g., '0%', 'from', 'to') */
|
|
33
|
+
offset: string;
|
|
34
|
+
/** CSS properties at this keyframe */
|
|
35
|
+
properties: Record<string, string | number>;
|
|
36
|
+
/** Easing for this segment */
|
|
37
|
+
easing?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ScrollAnimation {
|
|
41
|
+
/** Selector to animate */
|
|
42
|
+
selector: string;
|
|
43
|
+
/** Timeline configuration */
|
|
44
|
+
timeline: ScrollTimelineConfig;
|
|
45
|
+
/** Keyframes */
|
|
46
|
+
keyframes: KeyframeStep[];
|
|
47
|
+
/** Animation duration (maps to timeline range) */
|
|
48
|
+
duration?: string;
|
|
49
|
+
/** Fill mode */
|
|
50
|
+
fill?: 'none' | 'forwards' | 'backwards' | 'both';
|
|
51
|
+
/** Iteration count */
|
|
52
|
+
iterations?: number | 'infinite';
|
|
53
|
+
/** Delay before starting */
|
|
54
|
+
delay?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ScrollTimelineResult {
|
|
58
|
+
/** Native CSS for scroll-driven animation */
|
|
59
|
+
css: string;
|
|
60
|
+
/** The @keyframes name generated */
|
|
61
|
+
animationName: string;
|
|
62
|
+
/** The timeline name generated */
|
|
63
|
+
timelineName: string;
|
|
64
|
+
/** Fallback CSS for browsers without scroll-timeline */
|
|
65
|
+
fallback: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Presets — common scroll animations
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
export const SCROLL_PRESETS: Record<string, ScrollAnimation> = {
|
|
73
|
+
fadeIn: {
|
|
74
|
+
selector: '',
|
|
75
|
+
timeline: { name: 'fade-in', source: 'view', range: 'entry' },
|
|
76
|
+
keyframes: [
|
|
77
|
+
{ offset: '0%', properties: { opacity: '0', transform: 'translateY(20px)' } },
|
|
78
|
+
{ offset: '100%', properties: { opacity: '1', transform: 'translateY(0)' } },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
fadeOut: {
|
|
82
|
+
selector: '',
|
|
83
|
+
timeline: { name: 'fade-out', source: 'view', range: 'exit' },
|
|
84
|
+
keyframes: [
|
|
85
|
+
{ offset: '0%', properties: { opacity: '1' } },
|
|
86
|
+
{ offset: '100%', properties: { opacity: '0' } },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
scaleIn: {
|
|
90
|
+
selector: '',
|
|
91
|
+
timeline: { name: 'scale-in', source: 'view', range: 'entry' },
|
|
92
|
+
keyframes: [
|
|
93
|
+
{ offset: '0%', properties: { opacity: '0', transform: 'scale(0.8)' } },
|
|
94
|
+
{ offset: '100%', properties: { opacity: '1', transform: 'scale(1)' } },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
slideLeft: {
|
|
98
|
+
selector: '',
|
|
99
|
+
timeline: { name: 'slide-left', source: 'view', range: 'entry' },
|
|
100
|
+
keyframes: [
|
|
101
|
+
{ offset: '0%', properties: { opacity: '0', transform: 'translateX(-40px)' } },
|
|
102
|
+
{ offset: '100%', properties: { opacity: '1', transform: 'translateX(0)' } },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
slideRight: {
|
|
106
|
+
selector: '',
|
|
107
|
+
timeline: { name: 'slide-right', source: 'view', range: 'entry' },
|
|
108
|
+
keyframes: [
|
|
109
|
+
{ offset: '0%', properties: { opacity: '0', transform: 'translateX(40px)' } },
|
|
110
|
+
{ offset: '100%', properties: { opacity: '1', transform: 'translateX(0)' } },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
parallax: {
|
|
114
|
+
selector: '',
|
|
115
|
+
timeline: { name: 'parallax', source: 'scroll', scroller: 'root' },
|
|
116
|
+
keyframes: [
|
|
117
|
+
{ offset: '0%', properties: { transform: 'translateY(0)' } },
|
|
118
|
+
{ offset: '100%', properties: { transform: 'translateY(-20%)' } },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
stickyReveal: {
|
|
122
|
+
selector: '',
|
|
123
|
+
timeline: { name: 'sticky-reveal', source: 'view', range: 'contain' },
|
|
124
|
+
keyframes: [
|
|
125
|
+
{ offset: '0%', properties: { opacity: '0', clipPath: 'inset(0 0 100% 0)' } },
|
|
126
|
+
{ offset: '50%', properties: { opacity: '1', clipPath: 'inset(0 0 0% 0)' } },
|
|
127
|
+
{ offset: '100%', properties: { opacity: '1', clipPath: 'inset(0 0 0% 0)' } },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Core Compiler
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
let animCounter = 0;
|
|
137
|
+
|
|
138
|
+
function generateName(prefix: string): string {
|
|
139
|
+
return prefix + '-' + (animCounter++).toString(36);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Compile a scroll animation into CSS.
|
|
144
|
+
*
|
|
145
|
+
* Output includes:
|
|
146
|
+
* - @keyframes definition
|
|
147
|
+
* - animation-timeline property
|
|
148
|
+
* - animation-range (for view timelines)
|
|
149
|
+
* - @supports fallback for older browsers
|
|
150
|
+
*/
|
|
151
|
+
export function compileScrollAnimation(animation: ScrollAnimation): ScrollTimelineResult {
|
|
152
|
+
const animName = animation.timeline.name || generateName('scroll-anim');
|
|
153
|
+
const timelineName = '--' + animName + '-tl';
|
|
154
|
+
|
|
155
|
+
let css = '';
|
|
156
|
+
|
|
157
|
+
// 1. Define the scroll timeline
|
|
158
|
+
css += '/* Scroll Timeline: ' + animName + ' */\n';
|
|
159
|
+
|
|
160
|
+
if (animation.timeline.source === 'view') {
|
|
161
|
+
// view-timeline
|
|
162
|
+
const range = animation.timeline.range || 'entry';
|
|
163
|
+
css += animation.selector + ' {\n';
|
|
164
|
+
css += ' view-timeline-name: ' + timelineName + ';\n';
|
|
165
|
+
css += ' view-timeline-axis: ' + (animation.timeline.axis || 'block') + ';\n';
|
|
166
|
+
if (animation.timeline.inset) {
|
|
167
|
+
const inset = typeof animation.timeline.inset === 'string'
|
|
168
|
+
? animation.timeline.inset
|
|
169
|
+
: animation.timeline.inset.start + ' ' + animation.timeline.inset.end;
|
|
170
|
+
css += ' view-timeline-inset: ' + inset + ';\n';
|
|
171
|
+
}
|
|
172
|
+
css += '}\n\n';
|
|
173
|
+
} else {
|
|
174
|
+
// scroll-timeline
|
|
175
|
+
const scroller = animation.timeline.scroller === 'root' ? 'root'
|
|
176
|
+
: animation.timeline.scroller === 'self' ? 'self'
|
|
177
|
+
: animation.timeline.scroller || 'nearest';
|
|
178
|
+
css += animation.selector + ' {\n';
|
|
179
|
+
if (scroller === 'root' || scroller === 'self' || scroller === 'nearest') {
|
|
180
|
+
css += ' scroll-timeline-name: ' + timelineName + ';\n';
|
|
181
|
+
css += ' scroll-timeline-axis: ' + (animation.timeline.axis || 'block') + ';\n';
|
|
182
|
+
}
|
|
183
|
+
css += '}\n\n';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Define @keyframes
|
|
187
|
+
css += '@keyframes ' + animName + ' {\n';
|
|
188
|
+
for (const step of animation.keyframes) {
|
|
189
|
+
css += ' ' + step.offset + ' {\n';
|
|
190
|
+
for (const [prop, value] of Object.entries(step.properties)) {
|
|
191
|
+
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
192
|
+
css += ' ' + kebabProp + ': ' + value + ';\n';
|
|
193
|
+
}
|
|
194
|
+
css += ' }\n';
|
|
195
|
+
}
|
|
196
|
+
css += '}\n\n';
|
|
197
|
+
|
|
198
|
+
// 3. Apply animation to target
|
|
199
|
+
const targetSelector = animation.selector + ' > *' || animation.selector;
|
|
200
|
+
css += '/* Apply animation to children */\n';
|
|
201
|
+
css += targetSelector + ' {\n';
|
|
202
|
+
css += ' animation: ' + animName + ' linear both;\n';
|
|
203
|
+
css += ' animation-timeline: ' + timelineName + ';\n';
|
|
204
|
+
if (animation.timeline.source === 'view') {
|
|
205
|
+
const range = animation.timeline.range || 'entry';
|
|
206
|
+
css += ' animation-range: ' + range + ';\n';
|
|
207
|
+
}
|
|
208
|
+
if (animation.delay) css += ' animation-delay: ' + animation.delay + ';\n';
|
|
209
|
+
css += '}\n\n';
|
|
210
|
+
|
|
211
|
+
// 4. @supports fallback
|
|
212
|
+
css += '/* Fallback for browsers without scroll-timeline */\n';
|
|
213
|
+
css += '@supports not (animation-timeline: scroll()) {\n';
|
|
214
|
+
css += ' ' + targetSelector + ' {\n';
|
|
215
|
+
css += ' /* Use JS polyfill: https://github.com/flackr/scroll-timeline */\n';
|
|
216
|
+
css += ' animation: ' + animName + ' 1s ease both;\n';
|
|
217
|
+
css += ' }\n';
|
|
218
|
+
css += '}\n';
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
css,
|
|
222
|
+
animationName: animName,
|
|
223
|
+
timelineName,
|
|
224
|
+
fallback: '',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Compile multiple scroll animations for a page.
|
|
230
|
+
*/
|
|
231
|
+
export function compileScrollAnimations(animations: ScrollAnimation[]): string {
|
|
232
|
+
let css = '/* ============================================================\n';
|
|
233
|
+
css += ' ChainCSS Scroll-Driven Animations\n';
|
|
234
|
+
css += ' Generated: ' + new Date().toISOString() + '\n';
|
|
235
|
+
css += ' ============================================================ */\n\n';
|
|
236
|
+
|
|
237
|
+
for (const animation of animations) {
|
|
238
|
+
const result = compileScrollAnimation(animation);
|
|
239
|
+
css += result.css;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return css;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create a scroll animation from a preset.
|
|
247
|
+
*/
|
|
248
|
+
export function createScrollAnimation(
|
|
249
|
+
preset: keyof typeof SCROLL_PRESETS,
|
|
250
|
+
selector: string,
|
|
251
|
+
overrides?: Partial<ScrollAnimation>
|
|
252
|
+
): ScrollAnimation {
|
|
253
|
+
const base = SCROLL_PRESETS[preset];
|
|
254
|
+
if (!base) throw new Error('Unknown scroll preset: ' + preset);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
...base,
|
|
258
|
+
selector,
|
|
259
|
+
timeline: { ...base.timeline, ...overrides?.timeline },
|
|
260
|
+
keyframes: overrides?.keyframes || base.keyframes,
|
|
261
|
+
...overrides,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get available scroll animation presets.
|
|
267
|
+
*/
|
|
268
|
+
export function getScrollPresets(): string[] {
|
|
269
|
+
return Object.keys(SCROLL_PRESETS);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// Exports
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
export const scrollTimeline = {
|
|
277
|
+
compile: compileScrollAnimation,
|
|
278
|
+
compileAll: compileScrollAnimations,
|
|
279
|
+
create: createScrollAnimation,
|
|
280
|
+
presets: SCROLL_PRESETS,
|
|
281
|
+
getPresets: getScrollPresets,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export default scrollTimeline;
|