@wix/interact 2.0.4 → 2.1.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.
@@ -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.0.4",
3
+ "version": "2.1.0",
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.1",
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
  {
@@ -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: Array<Effect | EffectRef>**
218
- - REQUIRED. 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.
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