@wix/interact 2.0.4 → 2.1.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/dist/cjs/index.js +1 -1
- package/dist/cjs/react.js +1 -1
- package/dist/cjs/web.js +1 -1
- package/dist/es/index.js +1 -1
- package/dist/es/react.js +2 -2
- package/dist/es/web.js +2 -2
- package/dist/{index-BZL18ynN.mjs → index-BtEG0cjF.mjs} +603 -562
- package/dist/index-BtEG0cjF.mjs.map +1 -0
- package/dist/index-ErMKtmX2.js +18 -0
- package/dist/index-ErMKtmX2.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/core/Interact.d.ts +3 -0
- package/dist/types/core/Interact.d.ts.map +1 -1
- package/dist/types/core/remove.d.ts.map +1 -1
- package/dist/types/handlers/eventTrigger.d.ts.map +1 -1
- package/docs/api/types.md +114 -0
- package/docs/examples/README.md +10 -1
- package/docs/examples/list-patterns.md +148 -0
- package/docs/guides/README.md +5 -0
- package/docs/guides/sequences.md +421 -0
- package/package.json +2 -2
- package/rules/click.md +112 -1
- package/rules/full-lean.md +67 -3
- package/rules/hover.md +74 -0
- package/rules/integration.md +70 -1
- package/rules/viewenter.md +93 -163
- package/dist/index-BZL18ynN.mjs.map +0 -1
- package/dist/index-IaOsZpFD.js +0 -18
- package/dist/index-IaOsZpFD.js.map +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# Sequences & Staggering
|
|
2
|
+
|
|
3
|
+
Sequences let you coordinate multiple effects as a single timeline with staggered delay offsets. Built on the `@wix/motion` `Sequence` class, they provide easing-driven timing distribution across effects — ideal for list entrances, multi-element orchestrations, and any pattern requiring coordinated animation timing.
|
|
4
|
+
|
|
5
|
+
## What is a Sequence?
|
|
6
|
+
|
|
7
|
+
A Sequence groups multiple effects and applies calculated delay offsets to each one, so they play back in a staggered pattern. The stagger timing is shaped by an easing function, producing natural-feeling cascades rather than uniform delays.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
offset[i] = easing(i / last) * last * offsetMs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For example, with 5 effects and `offset: 200`:
|
|
14
|
+
|
|
15
|
+
| Easing | Computed delays | Feel |
|
|
16
|
+
| --------- | --------------------- | ------------------------ |
|
|
17
|
+
| `linear` | 0, 200, 400, 600, 800 | Even spacing |
|
|
18
|
+
| `quadIn` | 0, 50, 200, 450, 800 | Slow start, then rapid |
|
|
19
|
+
| `sineOut` | 0, 306, 565, 739, 800 | Fast start, then gradual |
|
|
20
|
+
|
|
21
|
+
## Config Structure
|
|
22
|
+
|
|
23
|
+
Sequences can be defined at two levels:
|
|
24
|
+
|
|
25
|
+
### Reusable Sequences (`InteractConfig.sequences`)
|
|
26
|
+
|
|
27
|
+
Define named sequences in the top-level `sequences` map, then reference them by ID from any interaction:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const config: InteractConfig = {
|
|
31
|
+
sequences: {
|
|
32
|
+
'card-stagger': {
|
|
33
|
+
offset: 150,
|
|
34
|
+
offsetEasing: 'quadIn',
|
|
35
|
+
effects: [{ effectId: 'fade-up' }],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
interactions: [
|
|
39
|
+
{
|
|
40
|
+
key: 'cards',
|
|
41
|
+
trigger: 'viewEnter',
|
|
42
|
+
listContainer: '.card-grid',
|
|
43
|
+
sequences: [{ sequenceId: 'card-stagger' }],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
effects: {
|
|
47
|
+
'fade-up': {
|
|
48
|
+
duration: 600,
|
|
49
|
+
easing: 'ease-out',
|
|
50
|
+
keyframeEffect: {
|
|
51
|
+
name: 'fade-up',
|
|
52
|
+
keyframes: [
|
|
53
|
+
{ opacity: 0, transform: 'translateY(20px)' },
|
|
54
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Inline Sequences (`Interaction.sequences`)
|
|
63
|
+
|
|
64
|
+
Define sequences directly on an interaction:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const config: InteractConfig = {
|
|
68
|
+
interactions: [
|
|
69
|
+
{
|
|
70
|
+
key: 'items',
|
|
71
|
+
trigger: 'viewEnter',
|
|
72
|
+
listContainer: '.item-list',
|
|
73
|
+
sequences: [
|
|
74
|
+
{
|
|
75
|
+
offset: 100,
|
|
76
|
+
offsetEasing: 'sineOut',
|
|
77
|
+
effects: [
|
|
78
|
+
{
|
|
79
|
+
duration: 500,
|
|
80
|
+
keyframeEffect: {
|
|
81
|
+
name: 'slide-in',
|
|
82
|
+
keyframes: [
|
|
83
|
+
{ opacity: 0, transform: 'translateX(-30px)' },
|
|
84
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
effects: {},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Combining Effects and Sequences
|
|
98
|
+
|
|
99
|
+
An interaction can have both `effects` and `sequences`:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
{
|
|
103
|
+
key: 'hero',
|
|
104
|
+
trigger: 'viewEnter',
|
|
105
|
+
effects: [{ effectId: 'background-fade' }],
|
|
106
|
+
sequences: [{
|
|
107
|
+
offset: 200,
|
|
108
|
+
effects: [
|
|
109
|
+
{ key: 'hero-title', effectId: 'slide-down' },
|
|
110
|
+
{ key: 'hero-subtitle', effectId: 'fade-in' },
|
|
111
|
+
],
|
|
112
|
+
}],
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Types Reference
|
|
117
|
+
|
|
118
|
+
### `SequenceOptionsConfig`
|
|
119
|
+
|
|
120
|
+
Shared options for sequence timing and identity:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
type SequenceOptionsConfig = {
|
|
124
|
+
delay?: number; // Base delay (ms). Default: 0
|
|
125
|
+
offset?: number; // Stagger interval (ms). Default: 0
|
|
126
|
+
offsetEasing?: string | ((p: number) => number); // Easing for offset distribution
|
|
127
|
+
sequenceId?: string; // ID for reusable sequence reference
|
|
128
|
+
conditions?: string[]; // Media query condition IDs
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `SequenceConfig`
|
|
133
|
+
|
|
134
|
+
Inline sequence definition (extends `SequenceOptionsConfig`):
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
type SequenceConfig = SequenceOptionsConfig & {
|
|
138
|
+
effects: (Effect | EffectRef)[];
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `SequenceConfigRef`
|
|
143
|
+
|
|
144
|
+
Reference to a reusable sequence with optional overrides:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
type SequenceConfigRef = {
|
|
148
|
+
sequenceId: string;
|
|
149
|
+
delay?: number;
|
|
150
|
+
offset?: number;
|
|
151
|
+
offsetEasing?: string | ((p: number) => number);
|
|
152
|
+
conditions?: string[];
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Overrides in the ref merge on top of the referenced sequence's values.
|
|
157
|
+
|
|
158
|
+
## Cross-Element Sequences
|
|
159
|
+
|
|
160
|
+
Effects within a sequence can target different elements using the `key` property:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
{
|
|
164
|
+
key: 'trigger-element',
|
|
165
|
+
trigger: 'click',
|
|
166
|
+
params: { type: 'alternate' },
|
|
167
|
+
sequences: [{
|
|
168
|
+
offset: 150,
|
|
169
|
+
offsetEasing: 'sineOut',
|
|
170
|
+
effects: [
|
|
171
|
+
{ key: 'heading', effectId: 'slide-down' },
|
|
172
|
+
{ key: 'body-text', effectId: 'fade-in' },
|
|
173
|
+
{ key: 'hero-image', effectId: 'scale-in' },
|
|
174
|
+
],
|
|
175
|
+
}],
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Cross-element sequences are resolved at add-time. When a sequence effect targets a `key` different from the source interaction, Interact waits for both elements to be registered before creating the Sequence. The effects are processed via `addEffectsForTarget()` when the target controller connects.
|
|
180
|
+
|
|
181
|
+
## Sequences with `listContainer`
|
|
182
|
+
|
|
183
|
+
The most common use case: staggering list item animations.
|
|
184
|
+
|
|
185
|
+
### Initial Load
|
|
186
|
+
|
|
187
|
+
When the source element connects, all existing list items are gathered and a Sequence is created with one `AnimationGroup` per item:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
{
|
|
191
|
+
key: 'product-grid',
|
|
192
|
+
trigger: 'viewEnter',
|
|
193
|
+
listContainer: '.products',
|
|
194
|
+
params: { type: 'once' },
|
|
195
|
+
sequences: [{
|
|
196
|
+
offset: 80,
|
|
197
|
+
offsetEasing: 'quadIn',
|
|
198
|
+
effects: [{
|
|
199
|
+
key: 'product-grid',
|
|
200
|
+
listContainer: '.products',
|
|
201
|
+
effectId: 'card-entrance',
|
|
202
|
+
}],
|
|
203
|
+
}],
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Dynamic Additions (`addListItems`)
|
|
208
|
+
|
|
209
|
+
When new items are appended to the DOM (detected by MutationObserver), `addListItems` is called. For sequences, this calls `Interact.addToSequence()` with `IndexedGroup` entries at the correct indices, triggering automatic offset recalculation across the entire Sequence.
|
|
210
|
+
|
|
211
|
+
Each `addListItems` invocation uses a unique cache key to manage its Sequence independently.
|
|
212
|
+
|
|
213
|
+
### Dynamic Removals (`removeListItems`)
|
|
214
|
+
|
|
215
|
+
When items are removed from the DOM, `removeListItems` automatically calls `Interact.removeFromSequences(elements)`. This:
|
|
216
|
+
|
|
217
|
+
1. Looks up associated Sequences via the `elementSequenceMap` WeakMap (O(1) per element)
|
|
218
|
+
2. Calls `sequence.removeGroups()` to cancel and remove the matching groups
|
|
219
|
+
3. Recalculates offsets for remaining groups
|
|
220
|
+
4. Cleans up the element from the map
|
|
221
|
+
|
|
222
|
+
No manual cleanup is needed — MutationObserver handles it automatically.
|
|
223
|
+
|
|
224
|
+
## Conditions on Sequences
|
|
225
|
+
|
|
226
|
+
### Sequence-Level Conditions
|
|
227
|
+
|
|
228
|
+
Gate an entire sequence with media query conditions:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
{
|
|
232
|
+
key: 'hero',
|
|
233
|
+
trigger: 'viewEnter',
|
|
234
|
+
sequences: [{
|
|
235
|
+
conditions: ['desktop-only'],
|
|
236
|
+
offset: 200,
|
|
237
|
+
effects: [
|
|
238
|
+
{ key: 'hero-title', effectId: 'slide-down' },
|
|
239
|
+
{ key: 'hero-body', effectId: 'fade-in' },
|
|
240
|
+
],
|
|
241
|
+
}],
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
When the condition doesn't match, the entire sequence is skipped. Interact sets up `matchMedia` listeners so the sequence is added/removed dynamically when the condition changes.
|
|
246
|
+
|
|
247
|
+
### Effect-Level Conditions
|
|
248
|
+
|
|
249
|
+
Individual effects within a sequence can have their own conditions:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
sequences: [
|
|
253
|
+
{
|
|
254
|
+
offset: 150,
|
|
255
|
+
effects: [
|
|
256
|
+
{ effectId: 'base-entrance' },
|
|
257
|
+
{ effectId: 'fancy-entrance', conditions: ['desktop-only'] },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
When an effect-level condition doesn't match, that effect is excluded from the Sequence's animation groups, and offsets are calculated for the remaining effects only.
|
|
264
|
+
|
|
265
|
+
## Sequence Caching
|
|
266
|
+
|
|
267
|
+
Interact caches Sequences to avoid recreating them:
|
|
268
|
+
|
|
269
|
+
- **`Interact.sequenceCache`** (`Map<string, Sequence>`) — maps cache keys to Sequence instances
|
|
270
|
+
- **`Interact.elementSequenceMap`** (`WeakMap<HTMLElement, Set<Sequence>>`) — maps elements to their Sequences for efficient removal
|
|
271
|
+
|
|
272
|
+
Cache keys are derived from the interaction key, sequence index, and context. Cleanup happens automatically:
|
|
273
|
+
|
|
274
|
+
- `Interact.destroy()` clears both `sequenceCache` and `elementSequenceMap`
|
|
275
|
+
- `clearInteractionStateForKey()` removes cache entries by key prefix
|
|
276
|
+
- Element removal cleans up `elementSequenceMap` entries via the WeakMap
|
|
277
|
+
|
|
278
|
+
## Examples
|
|
279
|
+
|
|
280
|
+
### Staggered Card Grid Entrance
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const config: InteractConfig = {
|
|
284
|
+
interactions: [
|
|
285
|
+
{
|
|
286
|
+
key: 'cards',
|
|
287
|
+
trigger: 'viewEnter',
|
|
288
|
+
listContainer: '.card-grid',
|
|
289
|
+
params: { type: 'once', threshold: 0.1 },
|
|
290
|
+
sequences: [
|
|
291
|
+
{
|
|
292
|
+
offset: 80,
|
|
293
|
+
offsetEasing: 'quadIn',
|
|
294
|
+
effects: [
|
|
295
|
+
{
|
|
296
|
+
key: 'cards',
|
|
297
|
+
listContainer: '.card-grid',
|
|
298
|
+
effectId: 'card-entrance',
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
effects: {
|
|
306
|
+
'card-entrance': {
|
|
307
|
+
duration: 600,
|
|
308
|
+
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
309
|
+
keyframeEffect: {
|
|
310
|
+
name: 'card-entrance',
|
|
311
|
+
keyframes: [
|
|
312
|
+
{ opacity: 0, transform: 'translateY(40px) scale(0.95)' },
|
|
313
|
+
{ opacity: 1, transform: 'translateY(0) scale(1)' },
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Click-Triggered Multi-Element Orchestration
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const config: InteractConfig = {
|
|
325
|
+
interactions: [
|
|
326
|
+
{
|
|
327
|
+
key: 'reveal-btn',
|
|
328
|
+
trigger: 'click',
|
|
329
|
+
params: { type: 'alternate' },
|
|
330
|
+
sequences: [
|
|
331
|
+
{
|
|
332
|
+
offset: 150,
|
|
333
|
+
offsetEasing: 'sineOut',
|
|
334
|
+
effects: [
|
|
335
|
+
{ key: 'section-heading', effectId: 'slide-down' },
|
|
336
|
+
{ key: 'section-body', effectId: 'fade-in' },
|
|
337
|
+
{ key: 'section-image', effectId: 'scale-in' },
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
effects: {
|
|
344
|
+
'slide-down': {
|
|
345
|
+
duration: 400,
|
|
346
|
+
keyframeEffect: {
|
|
347
|
+
name: 'slide-down',
|
|
348
|
+
keyframes: [
|
|
349
|
+
{ transform: 'translateY(-20px)', opacity: 0 },
|
|
350
|
+
{ transform: 'translateY(0)', opacity: 1 },
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
'fade-in': {
|
|
355
|
+
duration: 500,
|
|
356
|
+
keyframeEffect: {
|
|
357
|
+
name: 'fade-in',
|
|
358
|
+
keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
'scale-in': {
|
|
362
|
+
duration: 600,
|
|
363
|
+
keyframeEffect: {
|
|
364
|
+
name: 'scale-in',
|
|
365
|
+
keyframes: [
|
|
366
|
+
{ transform: 'scale(0.9)', opacity: 0 },
|
|
367
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Sequence with Media-Query Conditions
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
const config: InteractConfig = {
|
|
379
|
+
conditions: {
|
|
380
|
+
'desktop-only': { type: 'media', predicate: '(min-width: 1024px)' },
|
|
381
|
+
'no-reduced-motion': { type: 'media', predicate: '(prefers-reduced-motion: no-preference)' },
|
|
382
|
+
},
|
|
383
|
+
interactions: [
|
|
384
|
+
{
|
|
385
|
+
key: 'features',
|
|
386
|
+
trigger: 'viewEnter',
|
|
387
|
+
listContainer: '.feature-list',
|
|
388
|
+
conditions: ['no-reduced-motion'],
|
|
389
|
+
sequences: [
|
|
390
|
+
{
|
|
391
|
+
conditions: ['desktop-only'],
|
|
392
|
+
offset: 120,
|
|
393
|
+
offsetEasing: 'quadIn',
|
|
394
|
+
effects: [{ effectId: 'feature-entrance' }],
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
effects: {
|
|
400
|
+
'feature-entrance': {
|
|
401
|
+
duration: 700,
|
|
402
|
+
easing: 'ease-out',
|
|
403
|
+
keyframeEffect: {
|
|
404
|
+
name: 'feature-entrance',
|
|
405
|
+
keyframes: [
|
|
406
|
+
{ opacity: 0, transform: 'translateY(30px)' },
|
|
407
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## See Also
|
|
416
|
+
|
|
417
|
+
- [Lists and Dynamic Content](./lists-and-dynamic-content.md) — `listContainer`, `addListItems`, mutation tracking
|
|
418
|
+
- [Conditions and Media Queries](./conditions-and-media-queries.md) — conditional interactions
|
|
419
|
+
- [Effects and Animations](./effects-and-animations.md) — effect types and properties
|
|
420
|
+
- [Interact Class API](../api/interact-class.md) — `getSequence()`, `addToSequence()`, `removeFromSequences()`
|
|
421
|
+
- [Type Definitions](../api/types.md) — `SequenceConfig`, `SequenceConfigRef`, `SequenceOptionsConfig`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/interact",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A powerful, declarative interaction library for creating engaging web apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"url": "https://github.com/wix/interact/issues"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@wix/motion": "^2.0
|
|
58
|
+
"@wix/motion": "^2.1.0",
|
|
59
59
|
"fastdom": "^1.0.12",
|
|
60
60
|
"fizban": "^0.7.2",
|
|
61
61
|
"kuliso": "^0.4.13"
|
package/rules/click.md
CHANGED
|
@@ -416,11 +416,122 @@ These rules help generate click-based interactions using the `@wix/interact` lib
|
|
|
416
416
|
|
|
417
417
|
---
|
|
418
418
|
|
|
419
|
+
## Rule 5: Click with Sequence (Staggered Multi-Element Orchestration)
|
|
420
|
+
|
|
421
|
+
**Use Case**: Click-triggered coordinated animations across multiple elements with staggered timing (e.g., page section reveals, multi-element toggles, orchestrated content entrances)
|
|
422
|
+
|
|
423
|
+
**When to Apply**:
|
|
424
|
+
|
|
425
|
+
- When a click should animate multiple elements with staggered timing
|
|
426
|
+
- For orchestrated content reveals (heading, body, image in sequence)
|
|
427
|
+
- When you want easing-controlled stagger instead of manual delays
|
|
428
|
+
- For toggle-able multi-element sequences
|
|
429
|
+
|
|
430
|
+
**Pattern**:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
{
|
|
434
|
+
key: '[SOURCE_KEY]',
|
|
435
|
+
trigger: 'click',
|
|
436
|
+
params: {
|
|
437
|
+
type: 'alternate'
|
|
438
|
+
},
|
|
439
|
+
sequences: [
|
|
440
|
+
{
|
|
441
|
+
offset: [OFFSET_MS],
|
|
442
|
+
offsetEasing: '[OFFSET_EASING]',
|
|
443
|
+
effects: [
|
|
444
|
+
{ effectId: '[EFFECT_ID_1]', key: '[TARGET_KEY_1]' },
|
|
445
|
+
{ effectId: '[EFFECT_ID_2]', key: '[TARGET_KEY_2]' },
|
|
446
|
+
{ effectId: '[EFFECT_ID_3]', key: '[TARGET_KEY_3]' }
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Variables**:
|
|
454
|
+
|
|
455
|
+
- `[OFFSET_MS]`: Stagger offset in ms between consecutive effects (typically 100-200ms)
|
|
456
|
+
- `[OFFSET_EASING]`: Easing for stagger distribution — `'linear'`, `'quadIn'`, `'sineOut'`, etc.
|
|
457
|
+
- `[EFFECT_ID_N]`: Effect id from the effects registry for each element
|
|
458
|
+
- `[TARGET_KEY_N]`: Element key for each target
|
|
459
|
+
- Other variables same as Rule 1
|
|
460
|
+
|
|
461
|
+
**Example - Orchestrated Content Reveal**:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
{
|
|
465
|
+
key: 'reveal-button',
|
|
466
|
+
trigger: 'click',
|
|
467
|
+
params: {
|
|
468
|
+
type: 'alternate'
|
|
469
|
+
},
|
|
470
|
+
sequences: [
|
|
471
|
+
{
|
|
472
|
+
offset: 150,
|
|
473
|
+
offsetEasing: 'sineOut',
|
|
474
|
+
effects: [
|
|
475
|
+
{ effectId: 'heading-entrance', key: 'content-heading' },
|
|
476
|
+
{ effectId: 'body-entrance', key: 'content-body' },
|
|
477
|
+
{ effectId: 'image-entrance', key: 'content-image' }
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
effects: {
|
|
486
|
+
'heading-entrance': {
|
|
487
|
+
key: 'content-heading',
|
|
488
|
+
duration: 600,
|
|
489
|
+
easing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
490
|
+
keyframeEffect: {
|
|
491
|
+
name: 'heading-in',
|
|
492
|
+
keyframes: [
|
|
493
|
+
{ transform: 'translateX(-40px)', opacity: 0 },
|
|
494
|
+
{ transform: 'translateX(0)', opacity: 1 }
|
|
495
|
+
]
|
|
496
|
+
},
|
|
497
|
+
fill: 'both'
|
|
498
|
+
},
|
|
499
|
+
'body-entrance': {
|
|
500
|
+
key: 'content-body',
|
|
501
|
+
duration: 500,
|
|
502
|
+
easing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
503
|
+
keyframeEffect: {
|
|
504
|
+
name: 'body-in',
|
|
505
|
+
keyframes: [
|
|
506
|
+
{ transform: 'translateY(20px)', opacity: 0 },
|
|
507
|
+
{ transform: 'translateY(0)', opacity: 1 }
|
|
508
|
+
]
|
|
509
|
+
},
|
|
510
|
+
fill: 'both'
|
|
511
|
+
},
|
|
512
|
+
'image-entrance': {
|
|
513
|
+
key: 'content-image',
|
|
514
|
+
duration: 700,
|
|
515
|
+
easing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
516
|
+
keyframeEffect: {
|
|
517
|
+
name: 'image-in',
|
|
518
|
+
keyframes: [
|
|
519
|
+
{ transform: 'scale(0.8) rotate(-5deg)', opacity: 0 },
|
|
520
|
+
{ transform: 'scale(1) rotate(0deg)', opacity: 1 }
|
|
521
|
+
]
|
|
522
|
+
},
|
|
523
|
+
fill: 'both'
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
419
530
|
## Advanced Patterns and Combinations
|
|
420
531
|
|
|
421
532
|
### Multi-Target Click Effects
|
|
422
533
|
|
|
423
|
-
When one click should animate multiple elements:
|
|
534
|
+
When one click should animate multiple elements (without stagger, use `effects`; with stagger, prefer `sequences` above):
|
|
424
535
|
|
|
425
536
|
```typescript
|
|
426
537
|
{
|
package/rules/full-lean.md
CHANGED
|
@@ -116,7 +116,7 @@ This configuration declares what user/system triggers occur on which source elem
|
|
|
116
116
|
|
|
117
117
|
### Global rules
|
|
118
118
|
|
|
119
|
-
- **Required/Optional**: You MUST provide an `interactions` array. You SHOULD provide an `effects` registry when you want to reference reusable effects by id. `conditions` are OPTIONAL.
|
|
119
|
+
- **Required/Optional**: You MUST provide an `interactions` array. You SHOULD provide an `effects` registry when you want to reference reusable effects by id. `conditions` and `sequences` are OPTIONAL.
|
|
120
120
|
- **Cross-references**: All cross-references (by id) MUST point to existing entries (e.g., an `EffectRef.effectId` MUST exist in `effects`).
|
|
121
121
|
- **Element keys**: All element keys (`key` fields) refer to the element path string (e.g., the value used in `data-interact-key`) and MUST be stable for the lifetime of the configuration.
|
|
122
122
|
- **List context**: Where both a list container and list item selector are provided, they MUST describe the same list context across an interaction and its effects. Mismatched list contexts will be ignored by the system.
|
|
@@ -130,6 +130,11 @@ This configuration declares what user/system triggers occur on which source elem
|
|
|
130
130
|
- **Key (string)**: The effect id. MUST be unique across the registry.
|
|
131
131
|
- **Value (Effect)**: A full effect definition. See Effect rules below.
|
|
132
132
|
|
|
133
|
+
- **sequences?: Record<string, SequenceConfig>**
|
|
134
|
+
- **Purpose**: A registry of reusable sequence definitions that can be referenced from interactions via `SequenceConfigRef`.
|
|
135
|
+
- **Key (string)**: The sequence id. MUST be unique across the registry.
|
|
136
|
+
- **Value (SequenceConfig)**: A full sequence definition. See Sequences section below.
|
|
137
|
+
|
|
133
138
|
- **conditions?: Record<string, Condition>**
|
|
134
139
|
- **Purpose**: Named predicates that gate interactions/effects by runtime context.
|
|
135
140
|
- **Key (string)**: The condition id. MUST be unique across the registry.
|
|
@@ -214,8 +219,67 @@ This configuration declares what user/system triggers occur on which source elem
|
|
|
214
219
|
- OPTIONAL. Additional CSS selector to refine element selection:
|
|
215
220
|
- Without `listContainer`: Uses `querySelectorAll` to match all elements within the root element as separate items.
|
|
216
221
|
- With `listContainer`: Uses `querySelectorAll` within the container to find matching elements as list items. For dynamically added list items, uses `querySelector` within each item to find a single matching element.
|
|
217
|
-
- **effects
|
|
218
|
-
-
|
|
222
|
+
- **effects?: Array<Effect | EffectRef>**
|
|
223
|
+
- The effects to apply when the trigger fires. Ordering is significant: the first array entry is applied first. The system may reverse internal storage to preserve this application order.
|
|
224
|
+
- At least one of `effects` or `sequences` MUST be provided.
|
|
225
|
+
- **sequences?: Array<SequenceConfig | SequenceConfigRef>**
|
|
226
|
+
- OPTIONAL. Sequences to play when the trigger fires. Each sequence coordinates multiple effects with staggered timing. See Sequences section below.
|
|
227
|
+
|
|
228
|
+
### Sequences (coordinated multi-effect stagger)
|
|
229
|
+
|
|
230
|
+
Sequences let you group multiple effects into a single coordinated timeline with staggered delays. Instead of manually setting `delay` on each effect, you define `offset` (ms between items) and `offsetEasing` (how that offset is distributed).
|
|
231
|
+
|
|
232
|
+
**Prefer sequences over manual delay stagger** for any multi-element entrance or orchestration pattern.
|
|
233
|
+
|
|
234
|
+
- **SequenceConfig** type:
|
|
235
|
+
- `effects: (Effect | EffectRef)[]` — REQUIRED. The effects in this sequence, applied in array order.
|
|
236
|
+
- `delay?: number` — Base delay (ms) before the entire sequence starts. Default `0`.
|
|
237
|
+
- `offset?: number` — Stagger offset (ms) between consecutive effects. Default `0`.
|
|
238
|
+
- `offsetEasing?: string | ((p: number) => number)` — Easing function for stagger distribution. Named easings: `'linear'`, `'quadIn'`, `'quadOut'`, `'sineOut'`, `'cubicIn'`, `'cubicOut'`, `'cubicInOut'`. Also accepts `'cubic-bezier(...)'` strings or a JS function `(p: number) => number`. Default `'linear'`.
|
|
239
|
+
- `sequenceId?: string` — Id for caching and referencing. Auto-generated if omitted.
|
|
240
|
+
- `conditions?: string[]` — Condition ids that MUST all pass for this sequence to be active.
|
|
241
|
+
|
|
242
|
+
- **SequenceConfigRef** type (referencing a reusable sequence):
|
|
243
|
+
- `sequenceId: string` — REQUIRED. MUST match a key in `InteractConfig.sequences`.
|
|
244
|
+
- `delay?`, `offset?`, `offsetEasing?`, `conditions?` — OPTIONAL overrides merged on top of the referenced sequence.
|
|
245
|
+
|
|
246
|
+
- Effects within a sequence follow the same rules as standalone effects. Each effect can:
|
|
247
|
+
- Target a different element via `key` (cross-element sequences).
|
|
248
|
+
- Use `listContainer` to target list children (each child becomes a separate effect in the sequence).
|
|
249
|
+
- Reference the effects registry via `effectId`.
|
|
250
|
+
|
|
251
|
+
- A sequence is treated as a single animation unit by the trigger handler—it plays, reverses, and alternates as one.
|
|
252
|
+
|
|
253
|
+
**Example — viewEnter staggered list using `listContainer`**:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
{
|
|
257
|
+
interactions: [
|
|
258
|
+
{
|
|
259
|
+
key: 'card-grid',
|
|
260
|
+
trigger: 'viewEnter',
|
|
261
|
+
params: { type: 'once', threshold: 0.3 },
|
|
262
|
+
sequences: [
|
|
263
|
+
{
|
|
264
|
+
offset: 100,
|
|
265
|
+
offsetEasing: 'quadIn',
|
|
266
|
+
effects: [
|
|
267
|
+
{
|
|
268
|
+
effectId: 'card-entrance',
|
|
269
|
+
listContainer: '.card-grid',
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
effects: {
|
|
277
|
+
'card-entrance': {
|
|
278
|
+
// ...
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
```
|
|
219
283
|
|
|
220
284
|
### Working with elements
|
|
221
285
|
|