@wix/interact 2.1.3 → 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.
Files changed (84) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/cjs/react.js +1 -1
  3. package/dist/cjs/web.js +1 -1
  4. package/dist/cjs/web.js.map +1 -1
  5. package/dist/es/index.js +1 -1
  6. package/dist/es/react.js +2 -2
  7. package/dist/es/web.js +15 -15
  8. package/dist/es/web.js.map +1 -1
  9. package/dist/index-ByLXasWO.mjs +2832 -0
  10. package/dist/index-ByLXasWO.mjs.map +1 -0
  11. package/dist/index-CzRuJxn8.js +18 -0
  12. package/dist/index-CzRuJxn8.js.map +1 -0
  13. package/dist/tsconfig.build.tsbuildinfo +1 -1
  14. package/dist/types/core/Interact.d.ts +2 -2
  15. package/dist/types/core/InteractionController.d.ts +2 -2
  16. package/dist/types/core/InteractionController.d.ts.map +1 -1
  17. package/dist/types/core/add.d.ts.map +1 -1
  18. package/dist/types/core/css.d.ts.map +1 -1
  19. package/dist/types/handlers/effectHandlers.d.ts +4 -4
  20. package/dist/types/handlers/effectHandlers.d.ts.map +1 -1
  21. package/dist/types/handlers/eventTrigger.d.ts +2 -2
  22. package/dist/types/handlers/eventTrigger.d.ts.map +1 -1
  23. package/dist/types/handlers/index.d.ts.map +1 -1
  24. package/dist/types/handlers/viewEnter.d.ts.map +1 -1
  25. package/dist/types/index.d.ts +1 -1
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/react/index.d.ts +1 -1
  28. package/dist/types/react/index.d.ts.map +1 -1
  29. package/dist/types/types/config.d.ts +47 -0
  30. package/dist/types/types/config.d.ts.map +1 -0
  31. package/dist/types/types/controller.d.ts +34 -0
  32. package/dist/types/types/controller.d.ts.map +1 -0
  33. package/dist/types/types/effects.d.ts +75 -0
  34. package/dist/types/types/effects.d.ts.map +1 -0
  35. package/dist/types/types/external.d.ts +6 -0
  36. package/dist/types/types/external.d.ts.map +1 -0
  37. package/dist/types/types/global.d.ts +11 -0
  38. package/dist/types/types/global.d.ts.map +1 -0
  39. package/dist/types/types/handlers.d.ts +41 -0
  40. package/dist/types/types/handlers.d.ts.map +1 -0
  41. package/dist/types/types/index.d.ts +8 -0
  42. package/dist/types/types/index.d.ts.map +1 -0
  43. package/dist/types/types/internal.d.ts +36 -0
  44. package/dist/types/types/internal.d.ts.map +1 -0
  45. package/dist/types/types/triggers.d.ts +28 -0
  46. package/dist/types/types/triggers.d.ts.map +1 -0
  47. package/dist/types/web/InteractElement.d.ts +2 -2
  48. package/dist/types/web/InteractElement.d.ts.map +1 -1
  49. package/dist/types/web/index.d.ts +1 -1
  50. package/dist/types/web/index.d.ts.map +1 -1
  51. package/docs/api/README.md +2 -3
  52. package/docs/api/functions.md +4 -4
  53. package/docs/api/interact-class.md +2 -3
  54. package/docs/api/interact-element.md +2 -2
  55. package/docs/api/interaction-controller.md +4 -4
  56. package/docs/api/types.md +38 -69
  57. package/docs/examples/README.md +1 -1
  58. package/docs/examples/click-interactions.md +0 -7
  59. package/docs/examples/entrance-animations.md +28 -27
  60. package/docs/examples/list-patterns.md +17 -16
  61. package/docs/guides/conditions-and-media-queries.md +2 -3
  62. package/docs/guides/configuration-structure.md +5 -7
  63. package/docs/guides/effects-and-animations.md +2 -4
  64. package/docs/guides/getting-started.md +0 -1
  65. package/docs/guides/lists-and-dynamic-content.md +10 -9
  66. package/docs/guides/sequences.md +3 -4
  67. package/docs/guides/state-management.md +0 -2
  68. package/docs/guides/understanding-triggers.md +9 -13
  69. package/package.json +2 -2
  70. package/rules/click.md +96 -560
  71. package/rules/full-lean.md +536 -360
  72. package/rules/hover.md +107 -530
  73. package/rules/integration.md +212 -261
  74. package/rules/pointermove.md +154 -1407
  75. package/rules/viewenter.md +128 -863
  76. package/rules/viewprogress.md +88 -322
  77. package/dist/index-BtEG0cjF.mjs +0 -2791
  78. package/dist/index-BtEG0cjF.mjs.map +0 -1
  79. package/dist/index-ErMKtmX2.js +0 -18
  80. package/dist/index-ErMKtmX2.js.map +0 -1
  81. package/dist/types/types.d.ts +0 -256
  82. package/dist/types/types.d.ts.map +0 -1
  83. package/rules/MASTER-CLEANUP-PLAN.md +0 -286
  84. package/rules/scroll-list.md +0 -748
@@ -1,149 +1,179 @@
1
- ### Basic usage (quick start)
1
+ # @wix/interact Rules
2
+
3
+ Declarative configuration-driven interaction library. Binds animations to triggers via JSON config.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Common Pitfalls](#common-pitfalls)
8
+ - [Quick Start](#quick-start)
9
+ - [Element Binding](#element-binding)
10
+ - [Config Structure](#config-structure)
11
+ - [Interactions](#interactions)
12
+ - [Triggers](#triggers)
13
+ - [hover / click](#hover--click)
14
+ - [viewEnter](#viewenter)
15
+ - [viewProgress](#viewprogress)
16
+ - [pointerMove](#pointermove)
17
+ - [animationEnd](#animationend)
18
+ - [Effects](#effects)
19
+ - [Time-based Effect](#time-based-effect)
20
+ - [Scroll / Pointer-driven Effect](#scroll--pointer-driven-effect)
21
+ - [State Effect](#stateeffect-css-style-toggle)
22
+ - [Animation Payloads](#animation-payloads)
23
+ - [Sequences](#sequences)
24
+ - [Conditions](#conditions)
25
+ - [FOUC Prevention](#fouc-prevention)
26
+ - [Element Resolution](#element-resolution)
27
+ - [Static API](#static-api)
28
+
29
+ ---
30
+
31
+ ## Common Pitfalls
32
+
33
+ Each item here is CRITICAL — ignoring any of them will break animations.
34
+
35
+ - **CRITICAL — `overflow: hidden` breaks `viewProgress`**: Replace with `overflow: clip` on all ancestors between source and scroll container. In Tailwind, replace `overflow-hidden` with `overflow-clip`.
36
+ - **CRITICAL**: When using `viewEnter` trigger and source (trigger) and target (effect) elements are the **same element**, use ONLY `type: 'once'`. For all other types (`'repeat'`, `'alternate'`, `'state'`) MUST use **separate** source and target elements — animating the observed element itself can cause it to leave/re-enter the viewport, leading to rapid re-triggers or the animation never firing.
37
+ - **CRITICAL - Hit-area shift**: When a hover effect changes the size or position of the hovered element (e.g., `transform: scale(…)`), MUST use a separate source and target elements. Otherwise the hit-area shifts, causing rapid enter/leave.
38
+ events and flickering. Use `selector` to target a child element, or set the effect's `key` to a different element.
39
+ - **CRITICAL**: For `pointerMove` trigger MUST AVOID using the same element as both source and target with `hitArea: 'self'` and effects that change size or position (e.g. `transform: translate(…)`, `scale(…)`). The transform shifts the hit area, causing jittery re-entry cycles. Instead, use `selector` to target a child element for the animation.
40
+ - **CRITICAL — Do NOT guess preset options**: If you don't know the expected type/structure for a `namedEffect` param, omit it — rely on defaults rather than guessing.
41
+ - **Reduced motion**: Use conditions to provide gentler alternatives (shorter durations, fewer transforms, no perpetual motion) for users who prefer reduced motion. You can also set `Interact.forceReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches` to force a global reduced-motion behavior programmatically.
42
+ - **Perspective**: Prefer `transform: perspective(...)` inside keyframes. Use the CSS `perspective` property only when multiple children share the same `perspective-origin`.
43
+
44
+ ---
45
+
46
+ ## Quick Start
47
+
48
+ ```bash
49
+ npm install @wix/interact @wix/motion-presets
50
+ ```
2
51
 
3
- - Import the runtime and create it with a config defining the interactions.
4
- - Call `Interact.create(config)` once to initialize.
5
- - Create the full configuration up‑front and pass it in a single `create` call to avoid unintended overrides; subsequent calls replace the previous config.
52
+ Create the full config up-front and pass it in a single `create` call. Subsequent calls create new `Interact` instances. When creating multiple instances, each manages its own set of interactions independently — use separate instances for isolated component scopes or lazy-loaded sections.
6
53
 
7
- **For web (Custom Elements):**
54
+ **Web (Custom Elements):**
8
55
 
9
56
  ```ts
10
57
  import { Interact } from '@wix/interact/web';
11
- import type { InteractConfig } from '@wix/interact';
12
-
13
- const config: InteractConfig = {
14
- // config-props
15
- };
16
-
17
- Interact.create(config);
58
+ const instance = Interact.create(config);
18
59
  ```
19
60
 
20
- **For React:**
61
+ The `config` object is an `InteractConfig` containing `interactions` (required), and optionally shared `effects`, `sequences`, and `conditions`.
62
+
63
+ **React:**
21
64
 
22
65
  ```ts
23
66
  import { Interact } from '@wix/interact/react';
24
- import type { InteractConfig } from '@wix/interact';
67
+ const instance = Interact.create(config);
68
+ ```
25
69
 
26
- const config: InteractConfig = {
27
- // config-props
28
- };
70
+ **Vanilla JS:**
29
71
 
30
- Interact.create(config);
72
+ ```ts
73
+ import { Interact } from '@wix/interact';
74
+ const instance = Interact.create(config);
75
+ instance.add(element, 'hero'); // bind after element exists in DOM
76
+ instance.remove('hero'); // unregister
31
77
  ```
32
78
 
33
- ### Using `namedEffect` presets (`registerEffects`)
79
+ **CDN (no build tools):**
80
+
81
+ ```html
82
+ <script type="module">
83
+ import { Interact } from 'https://esm.sh/@wix/interact';
84
+ Interact.create(config);
85
+ </script>
86
+ ```
34
87
 
35
- Before using `namedEffect`, you must register the presets with the `Interact` instance. Without this, `namedEffect` types will not resolve.
88
+ **Registering presets** MUST be called before calling `Interact.create()` with usage of `namedEffect`:
36
89
 
37
90
  ```ts
38
- import { Interact } from '@wix/interact/web'; // or /react
39
91
  import * as presets from '@wix/motion-presets';
40
-
41
92
  Interact.registerEffects(presets);
42
- Interact.create(config);
43
93
  ```
44
94
 
45
- Or register only what you need:
95
+ Or selectively:
46
96
 
47
97
  ```ts
48
98
  import { FadeIn, ParallaxScroll } from '@wix/motion-presets';
49
99
  Interact.registerEffects({ FadeIn, ParallaxScroll });
50
100
  ```
51
101
 
52
- - Without Node/build tools: add a `<script type="module">` and import from the CDN.
102
+ ---
53
103
 
54
- ```html
55
- <script type="module">
56
- import { Interact } from 'https://esm.sh/@wix/interact';
104
+ ## Element Binding
57
105
 
58
- const config = {
59
- // config-props
60
- };
106
+ **CRITICAL:** Do NOT add observers/event listeners manually. The runtime binds triggers and effects via element keys.
61
107
 
62
- Interact.create(config);
63
- </script>
108
+ ### Web: `<interact-element>`
109
+
110
+ - MUST set `data-interact-key` to a unique value.
111
+ - MUST contain at least one child element (the library targets `.firstElementChild`).
112
+ - If an effect targets a different element, that element also needs its own `<interact-element>`.
113
+
114
+ ```html
115
+ <interact-element data-interact-key="hero">
116
+ <section class="hero">...</section>
117
+ </interact-element>
64
118
  ```
65
119
 
66
- ### Preventing FOUC for entrance animations
120
+ ### React: `<Interaction>` component
67
121
 
68
- - Use `generate(config)` to create critical CSS that hides elements until their `viewEnter` entrance animation plays.
69
- - Add `data-interact-initial="true"` to the `<interact-element>` that should have its first child hidden initially.
70
- - Only use `data-interact-initial="true"` for `<interact-element>` with `viewEnter` trigger and `type: 'once'`, where the source and target elements are the same.
71
- - Do NOT use for `hover` or `click` interactions.
122
+ - MUST set `tagName` to the replaced element's HTML tag.
123
+ - MUST set `interactKey` to a unique string.
72
124
 
73
- **Usage:**
125
+ ```tsx
126
+ import { Interaction } from '@wix/interact/react';
74
127
 
75
- ```javascript
76
- import { generate } from '@wix/interact/web';
128
+ <Interaction tagName="section" interactKey="hero" className="hero">
129
+ ...
130
+ </Interaction>;
131
+ ```
77
132
 
78
- const config = {
79
- /*...*/
80
- };
133
+ ---
81
134
 
82
- // Generate CSS at build time or on server
83
- const css = generate(config);
135
+ ## Config Structure
84
136
 
85
- // Include in your HTML template
86
- const html = `
87
- <!DOCTYPE html>
88
- <html>
89
- <head>
90
- <style>${css}</style>
91
- </head>
92
- <body>
93
- <interact-element data-interact-key="hero" data-interact-initial="true">
94
- <section class="hero">
95
- <h1>Welcome to Our Site</h1>
96
- <p>This content fades in smoothly without flash</p>
97
- </section>
98
- </interact-element>
99
- <script type="module" src="./main.js"></script>
100
- </body>
101
- </html>
102
- `;
137
+ ```ts
138
+ type InteractConfig = {
139
+ interactions: Interaction[]; // REQUIRED
140
+ effects?: Record<string, Effect>; // reusable effects referenced by effectId
141
+ sequences?: Record<string, SequenceConfig>; // reusable sequences by sequenceId
142
+ conditions?: Record<string, Condition>; // named conditions; keys are condition ids
143
+ };
103
144
  ```
104
145
 
105
- ### General guidelines (avoiding common pitfalls)
146
+ All cross-references (by id) MUST point to existing entries. Element keys MUST be stable for the config's lifetime.
106
147
 
107
- - Missing required fields or invalid references SHOULD be treated as no-ops for the offending interaction/effect while leaving the rest of the config functional.
108
- - Params with incorrect types or shapes (especially for `namedEffect` preset options) can produce console errors. If you do not know the expected type/structure for a param, omit it and rely on defaults rather than guessing.
109
- - Using `overflow: hidden` or `overflow: auto` can break viewProgress animations. Prefer `overflow: clip` for clipping semantics while preserving normal ViewTimeline.
110
- - When animating with perspective, prefer `transform: perspective(...)` inside keyframes/presets. Reserve the static CSS `perspective` property for the specific case where multiple children of the same container must share the same viewpoint (`perspective-origin`).
111
- - Stacking contexts and `viewProgress` (ViewTimeline): Creating a new stacking context on the target or any of its ancestors can prevent or freeze ViewTimeline sampling in some engines and setups. Avoid stacking‑context‑creating styles on the observed subtree (target and ancestors), including `transform`, `filter`, `perspective`, `opacity < 1`, `mix-blend-mode`, `isolation: isolate`, aggressive `will-change`, and `contain: paint/layout/size`. If needed for visuals, wrap the content and apply these styles to an inner child so the element that owns the timeline remains “flat”. Also avoid turning the scroll container into a stacking context; if you need clipping, prefer `overflow: clip` and avoid `transform` on the container. Typical symptoms are `viewProgress` not running, jumping 0→1, or never reaching anchors—remove or relocate the offending styles.
148
+ ---
112
149
 
113
- ### InteractConfig – Rules for authoring interactions (AI-agent oriented)
150
+ ## Interactions
114
151
 
115
- This configuration declares what user/system triggers occur on which source element(s), and which visual effects should be applied to which target element(s). It is composed of three top-level sections: `effects`, `conditions`, and `interactions`.
152
+ Each interaction maps a source element + trigger to one or more effects.
116
153
 
117
- ### Global rules
154
+ **Multiple effects per interaction:** A single interaction can contain multiple effects in its `effects` array. All effects in the same interaction share the same trigger — they all fire together when the trigger activates. Use this to apply different animations to different targets from the same trigger event, rather than creating separate interactions with duplicate trigger configs.
118
155
 
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
- - **Cross-references**: All cross-references (by id) MUST point to existing entries (e.g., an `EffectRef.effectId` MUST exist in `effects`).
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
- - **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.
123
- - **Conditions**: Conditions act as guards. If any condition on an interaction or effect evaluates to false, the corresponding trigger/effect WILL NOT be applied.
124
- - **Element binding**: Do NOT add observers/listeners manually. For web, wrap the DOM subtree with `<interact-element>` and set `data-interact-key` to the element key. For React, use the `<Interaction>` component with `interactKey` prop. Use the same key in your config (`Interaction.key`/`Effect.key`). The runtime binds triggers/effects via this attribute.
156
+ ```ts
157
+ {
158
+ key: string; // REQUIRED matches data-interact-key / interactKey - the root element
159
+ trigger: TriggerType; // REQUIRED
160
+ params?: TriggerParams; // trigger-specific options
161
+ effects?: (Effect | EffectRef)[]; // possible to add multiple effects for same trigger
162
+ sequences?: (SequenceConfig | SequenceConfigRef)[]; // possible to add multiple sequences for same trigger
163
+ conditions?: string[]; // ids referencing the top-level conditions map; all must pass
164
+ selector?: string; // optional - CSS selector to refine source element selection within the root element
165
+ listContainer?: string; // optional — CSS selector for list container
166
+ listItemSelector?: string; // optional — CSS selector to filter which children of listContainer are observed as sources
167
+ }
168
+ ```
125
169
 
126
- ### Structure
170
+ At least one of `effects` or `sequences` MUST be provided.
127
171
 
128
- - **effects: Record<string, Effect>**
129
- - **Purpose**: A registry of reusable, named effect definitions that can be referenced from interactions via `EffectRef`.
130
- - **Key (string)**: The effect id. MUST be unique across the registry.
131
- - **Value (Effect)**: A full effect definition. See Effect rules below.
172
+ For most use cases, `key` alone is sufficient for both source and target resolution. The `selector`, `listContainer`, and `listItemSelector` fields are only needed for advanced patterns (lists, delegated triggers, child targeting). See [Element Resolution](#element-resolution) for details.
132
173
 
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.
174
+ ---
137
175
 
138
- - **conditions?: Record<string, Condition>**
139
- - **Purpose**: Named predicates that gate interactions/effects by runtime context.
140
- - **Key (string)**: The condition id. MUST be unique across the registry.
141
- - **Value (Condition)**:
142
- - **type**: `'media' | 'container' | 'selector'`
143
- - `'media'`: The predicate MUST be a valid CSS media query expression without the outer `@media` keyword (e.g., `'(min-width: 768px)'`).
144
- - `'container'`: The predicate SHOULD be a valid CSS container query condition string relative to the relevant container context.
145
- - `'selector'`: The predicate is a CSS selector pattern. If it contains `&`, the `&` is replaced with the base element selector; otherwise the predicate is appended to the base selector. Used for conditional styling (e.g., `:nth-of-type(odd)`, `.active`).
146
- - **predicate?**: OPTIONAL textual predicate for the given type. If omitted, the condition is treated as always-true (i.e., a no-op guard).
176
+ ## Triggers
147
177
 
148
178
  - **interactions: Interaction[]**
149
179
  - **Purpose**: Declarative mapping from a source element and trigger to one or more target effects.
@@ -157,38 +187,16 @@ This configuration declares what user/system triggers occur on which source elem
157
187
  - **trigger: TriggerType**
158
188
  - REQUIRED. One of:
159
189
  - `'hover' | 'click' | 'activate' | 'interest'`: Pointer interactions (`activate` = click with keyboard Space/Enter; `interest` = hover with focus).
160
- - `'viewEnter' | 'pageVisible' | 'viewProgress'`: Viewport visibility/progress triggers.
190
+ - `'viewEnter' | 'viewProgress'`: Viewport visibility/progress triggers.
161
191
  - `'animationEnd'`: Fires when a specific effect completes on the source element.
162
192
  - `'pointerMove'`: Continuous pointer motion over an area.
163
193
  - **params?: TriggerParams**
164
194
  - OPTIONAL. Parameter object that MUST match the trigger:
165
- - hover/click/activate/interest: `StateParams | PointerTriggerParams` (activate uses same params as click; interest uses same params as hover).
166
- - `StateParams.method`: `'add' | 'remove' | 'toggle' | 'clear'`
167
- - `PointerTriggerParams.type?`: `'once' | 'repeat' | 'alternate' | 'state'`
168
- - Usage:
169
- - When the effect is a `TransitionEffect`, use `StateParams.method` to control the state toggle invoked on interaction:
170
- - `'toggle'` (default): Hover — adds on enter and removes on leave. Click — toggles on each click.
171
- - `'add'`: Apply the state on the event; hover leave will NOT auto‑remove.
172
- - `'remove'`: Remove the state on the event.
173
- - `'clear'`: Clear/reset the effect’s state for the element (or list item when list context is used).
174
- - With lists (`listContainer`/`listItemSelector`), the state is set on the matching item only.
175
- - When the effect is a time animation (`namedEffect`/`keyframeEffect`), use `PointerTriggerParams.type`:
176
- - `'alternate'` (default): Hover — play on enter, reverse on leave. Click — alternate play/reverse on successive clicks.
177
- - `'repeat'`: Restart from progress 0 on each event; on hover leave the animation is canceled.
178
- - `'once'`: Play once and remove the listener (hover attaches only the enter listener; no leave).
179
- - `'state'`: Hover — play on enter if idle/paused, pause on leave if running. Click — toggle play/pause on successive clicks until finished.
180
- - viewEnter/pageVisible/viewProgress: `ViewEnterParams`
181
- - `type?`: `'once' | 'repeat' | 'alternate' | 'state'`
195
+ - hover/click/activate/interest: No params needed. Behavior is configured on the effect itself.
196
+ - viewEnter: `ViewEnterParams`
182
197
  - `threshold?`: number in [0,1] describing intersection threshold
183
198
  - `inset?`: string CSS-style inset for rootMargin/observer geometry
184
- - Usage:
185
- - `'once'`: Play on first visibility and unobserve the element.
186
- - `'repeat'`: Play each time the element re‑enters visibility according to `threshold`/`inset`.
187
- - `'alternate'`: Triggers on re‑entries; if you need alternating direction, set it on the effect (e.g., `alternate: true`) rather than relying on the trigger.
188
- - `'state'`: Play on entry, pause on exit (for looping/continuous animations).
189
- - `threshold`: Passed to `IntersectionObserver.threshold` — typical values are 0.1–0.6 for entrances.
190
- - `inset`: Applied as vertical `rootMargin` (`top/bottom`), e.g., `'-100px'` to trigger earlier/later; left/right remain 0.
191
- - Note: For `viewProgress`, `threshold` and `inset` are ignored; progress is driven by ViewTimeline/scroll scenes. Control the range via `ScrubEffect.rangeStart/rangeEnd` and `namedEffect.range`.
199
+ - viewProgress: No trigger params. Progress is driven by ViewTimeline/scroll scenes. Control the range via `ScrubEffect.rangeStart/rangeEnd` and `namedEffect.range`.
192
200
  - animationEnd: `AnimationEndParams`
193
201
  - `effectId`: string of the effect to wait for completion
194
202
  - Usage: Fire when the specified effect (by `effectId`) on the source element finishes, useful for chaining sequences.
@@ -213,292 +221,460 @@ This configuration declares what user/system triggers occur on which source elem
213
221
  };
214
222
  ```
215
223
 
216
- - **conditions?: string[]**
217
- - OPTIONAL. Array of condition ids that MUST all pass for this trigger to be active.
218
- - **selector?: string**
219
- - OPTIONAL. Additional CSS selector to refine element selection:
220
- - Without `listContainer`: Uses `querySelectorAll` to match all elements within the root element as separate items.
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.
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.
224
+ ### hover / click
225
+
226
+ For `TimeEffect` (keyframe/named/custom effects), set `triggerType` on the effect. For `StateEffect` (transitions), set `stateAction` on the effect. Do NOT mix `triggerType` and `stateAction` on the same effect.
227
+
228
+ **`triggerType`** on `TimeEffect`:
229
+
230
+ | Type | hover behavior | click behavior |
231
+ | :---------------------- | :-------------------------------------- | :------------------------------- |
232
+ | `'alternate'` (default) | Play on enter, reverse on leave | Alternate play/reverse per click |
233
+ | `'repeat'` | Play on enter, stop and rewind on leave | Restart per click |
234
+ | `'once'` | Play once on first enter only | Play once on first click only |
235
+ | `'state'` | Play on enter, pause on leave | Toggle play/pause per click |
236
+
237
+ **`stateAction`** — on `StateEffect`:
238
+
239
+ | Action | hover behavior | click behavior |
240
+ | :------------------- | :---------------------------------------------- | :--------------------------- |
241
+ | `'toggle'` (default) | Add style state on enter, remove on leave | Toggle style state per click |
242
+ | `'add'` | Add style state on enter; leave does NOT remove | Add style state on click |
243
+ | `'remove'` | Remove style state on enter | Remove style state on click |
244
+ | `'clear'` | Clear/reset all style states on enter | Clear/reset all style states |
245
+
246
+ ### viewEnter
247
+
248
+ ```ts
249
+ params: {
250
+ threshold?: number; // 0–1, IntersectionObserver threshold
251
+ inset?: string; // like view-timeline-inset, e.g. '-100px' or '-50px 0px'
252
+ }
253
+ // Playback behavior is set on each effect:
254
+ effect.triggerType: 'once' | 'repeat' | 'alternate' | 'state'; // default: 'once'
255
+ ```
256
+
257
+ **CRITICAL:** When source and target are the **same element**, MUST use `triggerType: 'once'`. For `'repeat'` / `'alternate'` / `'state'`, ALWAYS use **separate** source and target elements — animating the observed element can cause it to leave/re-enter the viewport, causing rapid re-triggers.
258
+
259
+ ### viewProgress
260
+
261
+ Scroll-driven animations using native `ViewTimeline`, with polyfill where not supported. Progress is driven by scroll position. Control the range via `rangeStart`/`rangeEnd` on the effect (see [Scroll / Pointer-driven Effect](#scroll--pointer-driven-effect)).
262
+
263
+ `viewProgress` has no trigger params. Range configuration (`rangeStart`/`rangeEnd`) is on the effect, not on the trigger.
264
+
265
+ **CRITICAL:** Replace ALL `overflow: hidden` with `overflow: clip` on every element between the trigger source and the scroll container. `overflow: hidden` creates a new scroll context that breaks ViewTimeline. In Tailwind replace `overflow-hidden` with `overflow-clip`.
266
+
267
+ ### pointerMove
268
+
269
+ ```ts
270
+ params: {
271
+ hitArea?: 'self' | 'root'; // 'self' = source element bounds, 'root' = viewport
272
+ axis?: 'x' | 'y'; // restricts tracking to a single axis (for keyframeEffect)
273
+ }
274
+ ```
275
+
276
+ **Rules:**
277
+
278
+ - Source element MUST NOT have `pointer-events: none`.
279
+ - MUST NOT use the same element as both source and target with size or position effects — use `selector` to target a child or set a different `key`.
280
+ - Use a `(hover: hover)` media condition to disable on touch-only devices. On touch-only devices prefer `viewEnter` or `viewProgress` fallbacks.
281
+ - For 2D effects, use `namedEffect` mouse presets or `customEffect`. `keyframeEffect` only supports a single axis.
282
+ - For independent 2-axis control with keyframes, use two separate interactions (one `axis: 'x'`, one `axis: 'y'`) with `composite: 'add'` or `'accumulate'` on the second effect.
283
+
284
+ **`centeredToTarget`** — set `true` to remap the `0–1` progress range so that `0.5` progress corresponds to the center of the target element. Use when source and target are different elements, or when `hitArea: 'root'` is used, so that the pointer resting over the target center produces 50% progress regardless of position in viewport.
285
+
286
+ **Progress object** (for `customEffect`):
287
+
288
+ ```ts
289
+ { x: number; y: number; v?: { x: number; y: number }; active?: boolean }
290
+ // x, y: 0–1 normalized position within hit area
291
+ // v: velocity vector (unbounded, typically -1 to 1 range at moderate speed; 0 = stationary)
292
+ // active: whether pointer is within the active hit area
293
+ ```
294
+
295
+ ### animationEnd
296
+
297
+ ```ts
298
+ params: {
299
+ effectId: string;
300
+ } // the effect to wait for
301
+ ```
302
+
303
+ Fires when the specified effect completes on the source element. Useful for chaining sequences.
304
+
305
+ ---
306
+
307
+ ## Effects
308
+
309
+ Each effect applies a visual change to a target element. An effect is either inline or referenced by `effectId` from the top-level `effects` registry (`EffectRef`). An `EffectRef` inherits all properties from the registry entry, and can override any of them (e.g. `key`, `duration`, `easing`, `fill`, etc.) — not just the target. See [Element Resolution](#element-resolution) for how the target is determined.
310
+
311
+ ### Common fields
312
+
313
+ ```ts
314
+ {
315
+ key?: string; // target element key; omit to target the source
316
+ effectId?: string; // reference to effects registry (EffectRef)
317
+ conditions?: string[]; // ids referencing the top-level conditions map; all must pass
318
+ selector?: string; // optional — CSS selector to refine target element
319
+ listContainer?: string; // optional — CSS selector for list container
320
+ listItemSelector?: string; // optional — filter which children of listContainer are selected
321
+ composite?: 'replace' | 'add' | 'accumulate';
322
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
323
+ }
324
+ ```
325
+
326
+ **`fill` guidance:**
327
+
328
+ - `'both'` — use for scroll-driven (`viewProgress`), pointer-driven (`pointerMove`), and toggling effects (`hover`/`click` with `alternate`, `repeat`, or `state` type).
329
+ - `'backwards'` — use for entrance animations with `type: 'once'` when the element's own CSS already matches the final keyframe (applies the initial keyframe during any `delay`).
330
+
331
+ **`composite`** — same as CSS's `animation-composition`. Controls how this effect combines with others on the same property (transforms & filters):
332
+
333
+ - `'replace'` (default): fully replaces prior values.
334
+ - `'add'`: concatenates transform/filter functions after any existing ones (e.g. existing `translateX(10px)` + added `translateY(20px)` → both apply).
335
+ - `'accumulate'`: merges arguments of matching functions (e.g. `translateX(10px)` + `translateX(20px)` → `translateX(30px)`); non-matching functions concatenate like `'add'`.
336
+
337
+ **`easing` guidance:** from `@wix/motion` (in addition to standard CSS easings):
338
+
339
+ `'linear'`, `'ease'`, `'ease-in'`, `'ease-out'`, `'ease-in-out'`, `'sineIn'`, `'sineOut'`, `'sineInOut'`, `'quadIn'`, `'quadOut'`, `'quadInOut'`, `'cubicIn'`, `'cubicOut'`, `'cubicInOut'`, `'quartIn'`, `'quartOut'`, `'quartInOut'`, `'quintIn'`, `'quintOut'`, `'quintInOut'`, `'expoIn'`, `'expoOut'`, `'expoInOut'`, `'circIn'`, `'circOut'`, `'circInOut'`, `'backIn'`, `'backOut'`, `'backInOut'`, or any `'cubic-bezier(...)'` / `'linear(...)'` string.
340
+
341
+ ### Time-based Effect
342
+
343
+ Used with `hover`, `click`, `viewEnter`, `animationEnd` triggers.
344
+
345
+ ```ts
346
+ {
347
+ duration: number; // REQUIRED (ms)
348
+ easing?: string; // CSS easing or named easing (see below)
349
+ delay?: number; // ms
350
+ iterations?: number; // >=1 or Infinity; 0 is treated as Infinity
351
+ alternate?: boolean;
352
+ reversed?: boolean;
353
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
354
+ composite?: 'replace' | 'add' | 'accumulate';
355
+ // + exactly one animation payload (see below)
356
+ }
357
+ ```
358
+
359
+ ### Scroll / Pointer-driven Effect
360
+
361
+ Used with `viewProgress` and `pointerMove` triggers.
362
+
363
+ ```ts
364
+ {
365
+ rangeStart?: RangeOffset; // REQUIRED for viewProgress
366
+ rangeEnd?: RangeOffset; // REQUIRED for viewProgress
367
+ easing?: string; // CSS easing or named easing (see above)
368
+ iterations?: number; // NOT Infinity
369
+ alternate?: boolean;
370
+ reversed?: boolean;
371
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
372
+ composite?: 'replace' | 'add' | 'accumulate';
373
+ centeredToTarget?: boolean;
374
+ transitionDuration?: number; // ms, smoothing on progress jumps (primarily for pointerMove)
375
+ transitionDelay?: number; // ms (primarily for pointerMove)
376
+ transitionEasing?: 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce';
377
+ // + exactly one animation payload (see below)
378
+ }
379
+ ```
380
+
381
+ **RangeOffset** — works like CSS's `animation-range`:
227
382
 
228
- ### Sequences (coordinated multi-effect stagger)
383
+ ```ts
384
+ {
385
+ name?: 'entry' | 'exit' | 'contain' | 'cover' | 'entry-crossing' | 'exit-crossing';
386
+ offset?: { value: number; unit: 'percentage' | 'px' | 'vh' | 'vw' }
387
+ }
388
+ ```
229
389
 
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).
390
+ | Range name | Meaning |
391
+ | :--------------- | :------------------------------------------------------------- |
392
+ | `entry` | Element entering viewport |
393
+ | `exit` | Element exiting viewport |
394
+ | `contain` | After `entry` range and before `exit` range |
395
+ | `cover` | Full range from `entry` through `contain` and `exit` |
396
+ | `entry-crossing` | From element's leading edge entering to trailing edge entering |
397
+ | `exit-crossing` | From element's leading edge exiting to trailing edge exiting |
231
398
 
232
- **Prefer sequences over manual delay stagger** for any multi-element entrance or orchestration pattern.
399
+ **Sticky container pattern** for scroll-driven animations inside a stuck `position: sticky` container:
233
400
 
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.
401
+ - Tall wrapper: height defines scroll distance (e.g. `300vh` for ~2 viewport-heights of scroll travel).
402
+ - Sticky child (`key`) with `position: sticky; top: 0; height: 100vh`: stays fixed while the wrapper scrolls. This is the ViewTimeline source.
403
+ - Use `rangeStart/rangeEnd` with `name: 'contain'` to animate only during the stuck phase.
241
404
 
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.
405
+ ### StateEffect (CSS style toggle)
245
406
 
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`.
407
+ Used with `hover` / `click` triggers. Set `stateAction` on the effect to control state behavior.
250
408
 
251
- - A sequence is treated as a single animation unit by the trigger handler—it plays, reverses, and alternates as one.
409
+ **StateEffect** (CSS transition-style state toggles):
252
410
 
253
- **Example viewEnter staggered list using `listContainer`**:
411
+ - `key?`: string (target override; see TARGET CASCADE)
412
+ - `effectId?`: string (when used as a reference identity)
413
+ - One of:
414
+ - `transition?`: `{ duration?: number; delay?: number; easing?: string; styleProperties: { name: string; value: string }[] }`
415
+ - Applies a single transition options block to all listed style properties.
416
+ - `transitionProperties?`: `Array<{ name: string; value: string; duration?: number; delay?: number; easing?: string }>`
417
+ - Allows per-property transition options. If both `transition` and `transitionProperties` are provided, the system SHOULD apply both with per-property entries taking precedence for overlapping properties.
254
418
 
255
- ```typescript
419
+ ```ts
420
+ // Shared timing for all properties:
421
+ {
422
+ transition: {
423
+ duration?: number; delay?: number; easing?: string;
424
+ styleProperties: [{ name: string; value: string }]
425
+ }
426
+ }
427
+
428
+ // Per-property timing:
429
+ {
430
+ transitionProperties: [
431
+ { name: string; value: string; duration?: number; delay?: number; easing?: string }
432
+ ]
433
+ }
434
+ ```
435
+
436
+ CSS property names use **camelCase** (e.g. `'backgroundColor'`, `'borderRadius'`).
437
+
438
+ ### Animation Payloads
439
+
440
+ Exactly one MUST be provided per time-based or scroll/pointer-driven effect:
441
+
442
+ 1. **`namedEffect`** (preferred) — pre-built presets from `@wix/motion-presets`. GPU-friendly and tuned.
443
+
444
+ ```ts
445
+ namedEffect: {
446
+ type: '[PRESET_NAME]',
447
+ // ...optional [PRESET_OPTIONS] as additional properties
448
+ }
449
+ ```
450
+
451
+ - `[PRESET_NAME]` — one of the registered preset names (see table below).
452
+ - `[PRESET_OPTIONS]` — optional preset-specific properties spread as additional keys on the object. **CRITICAL:** Do NOT guess option names/types. Omit unknown options and rely on defaults.
453
+
454
+ Available presets:
455
+
456
+ | Category | Presets |
457
+ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
458
+ | Entrance | `FadeIn`, `GlideIn`, `SlideIn`, `FloatIn`, `RevealIn`, `ExpandIn`, `BlurIn`, `FlipIn`, `ArcIn`, `ShuttersIn`, `CurveIn`, `DropIn`, `FoldIn`, `ShapeIn`, `TiltIn`, `WinkIn`, `SpinIn`, `TurnIn`, `BounceIn` |
459
+ | Ongoing | `Pulse`, `Spin`, `Breathe`, `Bounce`, `Wiggle`, `Flash`, `Flip`, `Fold`, `Jello`, `Poke`, `Rubber`, `Swing`, `Cross` |
460
+ | Scroll | `FadeScroll`, `RevealScroll`, `ParallaxScroll`, `MoveScroll`, `SlideScroll`, `GrowScroll`, `ShrinkScroll`, `TiltScroll`, `PanScroll`, `BlurScroll`, `FlipScroll`, `SpinScroll`, `ArcScroll`, `ShapeScroll`, `ShuttersScroll`, `SkewPanScroll`, `Spin3dScroll`, `StretchScroll`, `TurnScroll` |
461
+ | Mouse | `TrackMouse`, `Tilt3DMouse`, `Track3DMouse`, `SwivelMouse`, `AiryMouse`, `ScaleMouse`, `BlurMouse`, `SkewMouse`, `BlobMouse` |
462
+ - **CRITICAL** — Scroll presets (`*Scroll`) used with `viewProgress` MUST include `range` in options: `'in'` (ends at idle state), `'out'` (starts from idle state), or `'continuous'` (passes through idle). Prefer `'continuous'`.
463
+ - Mouse presets are preferred over `keyframeEffect` for `pointerMove` 2D effects.
464
+
465
+ 2. **`keyframeEffect`** — custom keyframe animations.
466
+
467
+ ```ts
468
+ keyframeEffect: { name: '[EFFECT_NAME]', keyframes: [KEYFRAMES] }
469
+ ```
470
+
471
+ - `[EFFECT_NAME]` — unique string identifier for this effect.
472
+ - `[KEYFRAMES]` — array of keyframe objects using standard WAAPI format (e.g. `[{ opacity: '0' }, { opacity: '1' }]`). Property names in camelCase.
473
+
474
+ 3. **`customEffect`** — imperative update callback. Use only when CSS-based effects cannot express the desired behavior (e.g., animating SVG attributes, canvas, text content).
475
+
476
+ ```ts
477
+ customEffect: [CUSTOM_EFFECT_CALLBACK];
478
+ ```
479
+
480
+ - `[CUSTOM_EFFECT_CALLBACK]` — function with signature `(element: Element, progress: number | ProgressObject) => void`. Called on each animation frame.
481
+
482
+ ---
483
+
484
+ ## Sequences
485
+
486
+ Coordinate multiple effects with staggered timing. Prefer sequences over manual delay stagger.
487
+
488
+ ### Sequence As type
489
+
490
+ ```ts
491
+ {
492
+ effects: (Effect | EffectRef)[]; // REQUIRED
493
+ delay?: number; // ms before sequence starts
494
+ offset?: number; // ms between each child's animation start
495
+ offsetEasing?: string; // easing curve for staggering offsets
496
+ sequenceId?: string; // for caching/referencing
497
+ conditions?: string[]; // ids referencing the top-level conditions map
498
+ }
499
+ ```
500
+
501
+ ### Template
502
+
503
+ ```ts
256
504
  {
257
505
  interactions: [
258
506
  {
259
- key: 'card-grid',
260
- trigger: 'viewEnter',
261
- params: { type: 'once', threshold: 0.3 },
507
+ key: '[SOURCE_KEY]',
508
+ trigger: '[TRIGGER]',
509
+ params: [TRIGGER_PARAMS],
262
510
  sequences: [
263
511
  {
264
- offset: 100,
265
- offsetEasing: 'quadIn',
512
+ offset: [OFFSET_MS], // optional
513
+ offsetEasing: '[OFFSET_EASING]', // optional
514
+ delay: [DELAY_MS], // optional
266
515
  effects: [
516
+ // if used `listContainer` each item in the list is a target of a child effect
267
517
  {
268
- effectId: 'card-entrance',
269
- listContainer: '.card-grid',
518
+ effectId: '[EFFECT_ID]',
519
+ listContainer: '[LIST_CONTAINER_SELECTOR]',
270
520
  },
521
+ // if multiple effects are given each generated effect is added to the sequence
271
522
  ],
272
523
  },
273
524
  ],
274
525
  },
275
526
  ],
276
527
  effects: {
277
- 'card-entrance': {
278
- // ...
528
+ '[EFFECT_ID]': {
529
+ // effect definition (namedEffect, keyframeEffect, or customEffect)
279
530
  },
280
531
  },
281
532
  }
282
533
  ```
283
534
 
284
- ### Working with elements
535
+ ### Variables
285
536
 
286
- #### Web: `<interact-element>` custom element
537
+ - `[SOURCE_KEY]` — identifier matching the element's key (`data-interact-key` for /vanilla, `interactKey` for React).
538
+ - `[TRIGGER]` — any trigger for time-based animation effects (e.g., `'viewEnter'`, `'activate'`, `'interest'`).
539
+ - `[TRIGGER_PARAMS]` — trigger-specific parameters (e.g., `{ type: 'once', threshold: 0.3 }`).
540
+ - `[OFFSET_MS]` — ms between each child's animation start.
541
+ - `[OFFSET_EASING]` — CSS easing string or named easing from `@wix/motion`.
542
+ - `[DELAY_MS]` — optional. Base delay (ms) before the entire sequence starts.
543
+ - `[EFFECT_ID]` — string key referencing an entry in the top-level `effects` map.
544
+ - `[LIST_CONTAINER_SELECTOR]` — optional. CSS selector for the container whose children will be staggered.
287
545
 
288
- - Wrap the interactive DOM subtree with the custom element and set `data-interact-key` to a stable key. Reference that same key from your config via `Interaction.key` (and optionally `Effect.key`). No observers/listeners or manual DOM querying are needed—the runtime binds triggers and effects via this attribute.
289
- - If an effect targets an element that is not the interaction's source, you MUST also wrap that target element's subtree with its own `<interact-element>` and set `data-interact-key` to the target's key (the value used in `Effect.key` or the referenced registry Effect's `key`). This is required so the runtime can locate and apply effects to non-source targets.
290
- - MUST have a `data-interact-key` attribute with a value that is unique within the scope.
291
- - MUST contain at least one child element.
546
+ Reusable sequences can be defined in `InteractConfig.sequences` and referenced by `sequenceId`.
292
547
 
293
- ```html
294
- <interact-element data-interact-key="my-button">
295
- <button id="my-button">Click me</button>
296
- </interact-element>
548
+ ---
549
+
550
+ ## Conditions
551
+
552
+ Named conditions that gate interactions, effects, or sequences.
553
+
554
+ | Type | Predicate |
555
+ | :--------- | :------------------------------------------------------------------------ |
556
+ | `media` | CSS media query condition without `@media` (e.g., `'(min-width: 768px)'`) |
557
+ | `selector` | CSS selector; `&` is replaced with the base element selector |
558
+
559
+ Attach via `conditions: ['[CONDITION_ID]']` on interactions, effects, or sequences. On an interaction, conditions gate the entire trigger; on an effect, only that specific effect is skipped. All listed conditions must pass.
560
+
561
+ ### Examples
562
+
563
+ ```ts
564
+ conditions: {
565
+ 'desktop': { type: 'media', predicate: '(min-width: 768px)' },
566
+ 'hover-device': { type: 'media', predicate: '(hover: hover)' },
567
+ 'reduced-motion': { type: 'media', predicate: '(prefers-reduced-motion: reduce)' },
568
+ 'odd-items': { type: 'selector', predicate: ':nth-of-type(odd)' },
569
+ }
297
570
  ```
298
571
 
572
+ ---
573
+
574
+ ## FOUC Prevention
575
+
576
+ **Problem:** Elements with entrance animations (e.g. `viewEnter` + `type: 'once'` with `FadeIn`) start in their final visible state. Before the animation framework initializes and applies the starting keyframe (e.g. `opacity: 0`), the element is briefly visible at full opacity — causing a flash of unstyled/un-animated content (FOUC).
577
+
578
+ **Solution:** Two things are required — both MUST be present:
579
+
580
+ 1. **Generate critical CSS** using `generate(config)` — produces CSS rules that hide entrance-animated elements from the moment the page renders.
581
+ 2. **Mark elements with `initial`** — tells the runtime which elements have critical CSS applied so it can coordinate with the generated styles.
582
+
583
+ ### Step 1: Generate CSS
584
+
585
+ Call `generate(config)` server-side or at build time and inject the result into the `<head>` (preferred), or insert to beginning of `<body>`, so it loads before the page content is painted:
586
+
299
587
  ```ts
300
- import type { InteractConfig } from '@wix/interact';
588
+ import { generate } from '@wix/interact/web';
589
+ const css = generate(config);
590
+ ```
301
591
 
302
- const config: InteractConfig = {
303
- interactions: [
304
- {
305
- key: 'my-button', // matches data-interact-key
306
- trigger: 'hover',
307
- effects: [
308
- {
309
- // key omitted -> targets the source element ('my-button')
310
- // effect props go here (e.g., transition | keyframeEffect | namedEffect | customEffect)
311
- },
312
- ],
313
- },
314
- ],
315
- };
592
+ **Append to `<head>` or beginning of `<body>`:**
593
+
594
+ ```html
595
+ <style>
596
+ ${css}
597
+ </style>
316
598
  ```
317
599
 
318
- For a different target element:
600
+ ### Step 2: Mark elements
601
+
602
+ **Web (Custom Elements):**
319
603
 
320
604
  ```html
321
- <interact-element data-interact-key="my-button">
322
- <button id="my-button">Click me</button>
605
+ <interact-element data-interact-key="hero" data-interact-initial="true">
606
+ <section class="hero">...</section>
323
607
  </interact-element>
608
+ ```
324
609
 
325
- <interact-element data-interact-key="my-badge">
326
- <span id="my-badge">Badge</span>
327
- </interact-element>
610
+ **React:**
611
+
612
+ ```tsx
613
+ <Interaction tagName="section" interactKey="hero" initial={true} className="hero">
614
+ ...
615
+ </Interaction>
328
616
  ```
329
617
 
330
- ```ts
331
- const config: InteractConfig = {
332
- interactions: [
333
- {
334
- key: 'my-button',
335
- trigger: 'click',
336
- effects: [
337
- {
338
- key: 'my-badge', // target is different than source
339
- // effect props (e.g., transition | keyframeEffect | namedEffect)
340
- },
341
- ],
342
- },
343
- ],
344
- };
618
+ **Vanilla:**
619
+
620
+ ```html
621
+ <section data-interact-key="hero" data-interact-initial="true" class="hero">...</section>
345
622
  ```
346
623
 
347
- #### React: `<Interaction>` component
624
+ ### Rules
348
625
 
349
- - MUST replace the element itself with the `<Interaction/>` component.
350
- - MUST set the `tagName` prop with the tag of the replaced element.
351
- - MUST set the `interactKey` prop to a unique string within the scope.
626
+ - `generate()` should be called server-side or at build time. Can also be called on client-side if page content is initially hidden (e.g. behind a loader/splash screen).
627
+ - **Both** `generate(config)` CSS **and** `initial` on the element are required. Using only one has no effect.
628
+ - `initial` is only valid for `viewEnter` + `type: 'once'` where source and target are the same element.
629
+ - For `repeat`/`alternate`/`state`, do NOT use `initial`. Instead, manually apply the initial keyframe as inline styles on the target element and use `fill: 'both'`.
352
630
 
353
- ```tsx
354
- import { Interaction } from '@wix/interact/react';
631
+ ---
355
632
 
356
- function MyComponent() {
357
- return (
358
- <Interaction tagName="button" interactKey="my-button" className="btn">
359
- Click me
360
- </Interaction>
361
- );
362
- }
363
- ```
633
+ ## Element Resolution
364
634
 
365
- For a different target element:
635
+ For simple use cases, `key` on the interaction matches the element, and the same element is both trigger source and animation target. The fields below are only needed for advanced patterns (lists, delegated triggers, child targeting).
366
636
 
367
- ```tsx
368
- import { Interaction } from '@wix/interact/react';
637
+ ### Source element resolution (Interaction level)
369
638
 
370
- function MyComponent() {
371
- return (
372
- <>
373
- <Interaction tagName="button" interactKey="my-button" className="btn">
374
- Click me
375
- </Interaction>
376
-
377
- <Interaction tagName="span" interactKey="my-badge" className="badge">
378
- Badge
379
- </Interaction>
380
- </>
381
- );
382
- }
383
- ```
639
+ The source element is what the trigger attaches to. Resolved in priority order:
640
+
641
+ 1. **`listContainer` + `listItemSelector`** — trigger attaches to each element matching `listItemSelector` within the `listContainer`. Use `listItemSelector` only when you need to **filter** which children participate (e.g. select only `.active` items). If all immediate children should participate, omit `listItemSelector`.
642
+ 2. **`listContainer` only** — trigger attaches to each immediate child of the container. This is the common case for lists.
643
+ 3. **`listContainer` + `selector`** — trigger attaches to the element found via `querySelector` within each immediate child of the container.
644
+ 4. **`selector` only** — trigger attaches to all elements matching `querySelectorAll` within the root `<interact-element>`.
645
+ 5. **Fallback** — first child of `<interact-element>` (web) or the root element (react/vanilla).
646
+
647
+ ### Target element resolution (Effect level)
648
+
649
+ The target element is what the effect animates. Resolved in priority order:
650
+
651
+ 1. **`Effect.key`** — the `<interact-element>` with matching `data-interact-key`.
652
+ 2. **Registry Effect's `key`** — if the effect is an `EffectRef`, the `key` from the referenced registry entry is used.
653
+ 3. **Fallback to `Interaction.key`** — the same `key` is used for the source will be used for the target.
654
+ 4. After resolving the root target, `selector`, `listContainer`, and `listItemSelector` on the effect further refine which child elements within that target are animated (same priority order as source resolution).
655
+
656
+ ---
657
+
658
+ ## Static API
659
+
660
+ | Method / Property | Description |
661
+ | :---------------------------------- | :------------------------------------------------------------------------------------------------------------ |
662
+ | `Interact.create(config)` | Initialize with a config. Returns the instance. Store the instance to manage its lifecycle. |
663
+ | `Interact.registerEffects(presets)` | Register named effect presets. MUST be called before `create`. |
664
+ | `Interact.destroy()` | Tear down all instances. Call on unmount or route change to prevent memory leaks. |
665
+ | `Interact.forceReducedMotion` | `boolean` (default: `false`) — force reduced-motion behavior regardless of OS setting. |
666
+ | `Interact.allowA11yTriggers` | `boolean` (default: `false`) — enable accessibility trigger variants (`interest`, `activate`). |
667
+ | `Interact.setup(options)` | Configure global options for scroll, pointer, and viewEnter systems. Call before `create`. See options below. |
668
+
669
+ **`Interact.setup(options)`** — optional configuration object:
670
+
671
+ | Option | Type | Description |
672
+ | :--------------------- | :----------------------------- | :-------------------------------------------------------------------- |
673
+ | `scrollOptionsGetter` | `() => Partial<scrollConfig>` | Function returning defaults for scroll-driven animation configuration |
674
+ | `pointerOptionsGetter` | `() => Partial<PointerConfig>` | Function returning defaults for pointer-move animation configuration |
675
+ | `viewEnter` | `Partial<ViewEnterParams>` | Defaults for all viewEnter triggers (`threshold`,`inset`) |
676
+ | `allowA11yTriggers` | `boolean` | Enable accessibility trigger variants (use `interest` and `activate`) |
677
+
678
+ Use `setup()` when you need to override default observer thresholds or provide global configuration that applies to all interactions of a given trigger type.
384
679
 
385
- The config remains the same for both integrations—only the HTML/JSX setup differs.
386
-
387
- ### Effect rules (applies to both registry-defined Effect and inline Effect within interactions)
388
-
389
- - Common fields (`EffectBase`):
390
- - **key?: string**
391
- - OPTIONAL. Target element path. If omitted, resolution follows TARGET CASCADE:
392
- 1. `Effect.key` (if provided)
393
- 2. If Effect is an `EffectRef`: lookup registry by `effectId` and use that registry Effect’s `key`
394
- 3. Fallback to the `Interaction.key` (i.e., source acts as target)
395
- - **listContainer?: string, listItemSelector?: string**
396
- - OPTIONAL. If provided, MUST match the interaction's list context when both exist.
397
- - **conditions?: string[]**
398
- - OPTIONAL. All conditions MUST pass for the effect to run (in addition to interaction conditions).
399
- - **selector?: string**
400
- - OPTIONAL. Additional CSS selector to refine target element selection:
401
- - Without `listContainer`: Uses `querySelectorAll` to match all elements within the target root as separate items.
402
- - 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.
403
- - **effectId?: string**
404
- - For `EffectRef` this field is REQUIRED and MUST reference an entry in `effects`.
405
-
406
- - Composition and fill usage
407
- - **composite** (similar to CSS `animation-composition` / WAAPI `composite`):
408
- - Controls how this effect combines with other effects targeting the same element/property.
409
- - `'replace'` (default): this effect fully replaces prior values for overlapping properties.
410
- - `'add'`: this effect adds to the underlying value where the property supports additive composition (e.g., transforms, filters, opacity).
411
- - `'accumulate'`: values build up across iterations/repeats where supported.
412
- - Note: If a property is not additive, the runtime will treat `'add'`/`'accumulate'` like `'replace'`.
413
- - **fill** (like CSS `animation-fill-mode`):
414
- - `'none'`: styles are only applied while the effect is actively running/in-range.
415
- - `'forwards'`: the effect’s end state is retained after completion (or last sampled value for scrub).
416
- - `'backwards'`: the start state applies before the effect begins (or before `rangeStart` for scrub/during `delay` for time effects).
417
- - `'both'`: combines `'backwards'` and `'forwards'`.
418
- - For scroll-driven animations (`viewProgress`), prefer `fill: 'both'` to preserve start/end states around the active range and avoid flicker on rapid scroll.
419
-
420
- - Types of `Effect` (exactly one MUST be provided via discriminated fields):
421
- 1. **TimeEffect** (discrete animation over time)
422
- - `duration`: number (REQUIRED)
423
- - `easing?`: string (CSS/WAAPI easing)
424
- - `iterations?`: number (>=1 or Infinity)
425
- - `alternate?`: boolean (direction alternation)
426
- - `fill?`: `'none' | 'forwards' | 'backwards' | 'both'`
427
- - `composite?`: `'replace' | 'add' | 'accumulate'`
428
- - `reversed?`: boolean
429
- - `delay?`: number (ms)
430
- - One of:
431
- - `keyframeEffect`: `{ name: string; keyframes: Keyframe[] }`
432
- - `namedEffect`: `NamedEffect` (from `@wix/motion-presets`)
433
- - `customEffect`: `(element: Element, progress: any) => void`
434
-
435
- 2. **ScrubEffect** (animation driven by scroll/progress)
436
- - `easing?`: string
437
- - `iterations?`: number (NOT Infinity)
438
- - `alternate?`: boolean
439
- - `fill?`: `'none' | 'forwards' | 'backwards' | 'both'`
440
- - `composite?`: `'replace' | 'add' | 'accumulate'`
441
- - `reversed?`: boolean
442
- - `rangeStart`: `RangeOffset`
443
- - `rangeEnd`: `RangeOffset`
444
- - `centeredToTarget?`: boolean // If `true` centers the coordinate range at the target element, otherwise uses source element
445
- - `transitionDuration?`: number (ms for smoothing on progress jumps)
446
- - `transitionDelay?`: number
447
- - `transitionEasing?`: `ScrubTransitionEasing`
448
- - One of `keyframeEffect | namedEffect | customEffect` (see above)
449
- - For mouse-effects driven by the `pointerMove` trigger, avoid `keyframeEffect` unless using `params: { axis: 'x' | 'y' }` to map a single pointer axis to linear 0–1 progress. For 2D effects, use `namedEffect` mouse presets or `customEffect` instead.
450
- - For scroll `namedEffect` presets (e.g., `*Scroll`) used with a `viewProgress` trigger, include `range: 'in' | 'out' | 'continuous'` in the `namedEffect` options; prefer `'continuous'` for simplicity.
451
- - RangeOffset (used by `rangeStart`/`rangeEnd`):
452
- - Type: `{ name: 'entry' | 'exit' | 'contain' | 'cover' | 'entry-crossing' | 'exit-crossing'; offset: LengthPercentage }`
453
- - name?: Optional logical anchor derived from ViewTimeline concepts.
454
- - 'entry': Leading edge of the target crosses into the view/container.
455
- - 'exit': Trailing edge of the target crosses out of the view/container.
456
- - 'contain': Interval where the target is fully within the view/container.
457
- - 'cover': Interval where the view/container is fully covered by the target.
458
- - 'entry-crossing': The moment the target's center crosses the entry boundary.
459
- - 'exit-crossing': The moment the target's center crosses the exit boundary.
460
- - If omitted, the runtime chooses a context-appropriate anchor; specify explicitly for deterministic behavior.
461
- - offset: A `LengthPercentage` that shifts the anchor boundary.
462
- - Explicit format: `{ value: number; unit: 'percentage' | 'px' | 'em' | 'rem' | 'vh' | 'vw' | 'vmin' | 'vmax' }`
463
- - Percentages are interpreted along the relevant scroll axis relative to the observation area (e.g., viewport or container size).
464
- - Positive values move the anchor "forward" along the scroll direction; negative values move it "backward".
465
- - Examples:
466
- - Start when the element is 20% inside the viewport: `rangeStart: { name: 'entry', offset: { value: 20, unit: 'percentage' } }`
467
- - End when the element is leaving: `rangeEnd: { name: 'exit', offset: { value: 0, unit: 'percentage' } }`
468
-
469
- 3. **TransitionEffect** (CSS transition-style state toggles)
470
- - `key?`: string (target override; see TARGET CASCADE)
471
- - `effectId?`: string (when used as a reference identity)
472
- - One of:
473
- - `transition?`: `{ duration?: number; delay?: number; easing?: string; styleProperties: { name: string; value: string }[] }`
474
- - Applies a single transition options block to all listed style properties.
475
- - `transitionProperties?`: `Array<{ name: string; value: string; duration?: number; delay?: number; easing?: string }>`
476
- - Allows per-property transition options. If both `transition` and `transitionProperties` are provided, the system SHOULD apply both with per-property entries taking precedence for overlapping properties.
477
-
478
- ### Authoring rules for animation payloads (`namedEffect`, `keyframeEffect`, `customEffect`):
479
-
480
- - **namedEffect (Preferred)**: Use first for best performance. These are pre-built presets from `@wix/motion-presets` that are GPU-friendly and tuned.
481
- - Structure: `namedEffect: { type: '<PresetName>', /* optional preset options like direction (bottom|top|left|right), etc. do not use those without having proper documentation of which options exist and of what types. */ }`
482
- - Short list of common preset names:
483
- - Entrance: `FadeIn`, `BounceIn`, `SlideIn`, `FlipIn`, `ArcIn`
484
- - Ongoing: `Pulse`, `Spin`, `Wiggle`, `Bounce`
485
- - Scroll: `ParallaxScroll`, `FadeScroll`, `RevealScroll`, `TiltScroll` — for `viewProgress`, `namedEffect` options MUST include `range: 'in' | 'out' | 'continuous'`; prefer `'continuous'`
486
- - Mouse: `TrackMouse`, `Tilt3DMouse`, `ScaleMouse`, `BlurMouse` — for `pointerMove`; prefer over `keyframeEffect` for 2D pointer effects
487
- - **keyframeEffect (Default for custom animations)**: Prefer this when you need a custom-made animation.
488
- - Structure: `keyframeEffect: { name: string; keyframes: Keyframe[] }` (keyframes use standard CSS/WAAPI properties).
489
- - When used with `pointerMove`, requires `params: { axis: 'x' | 'y' }` to select which pointer coordinate maps to linear progress. Without `axis`, pointer progress is two-dimensional and cannot drive keyframe animations. For 2D pointer effects, use `namedEffect` or `customEffect`.
490
- - **customEffect (Last resort)**: Use only when you must perform DOM manipulation or produce randomized/non-deterministic visuals that cannot be expressed as keyframes or presets.
491
- - Structure: `customEffect: (element: Element, progress: any) => void`
492
-
493
- ### Target resolution and list context
494
-
495
- - When applying an effect, the system resolves the final target as:
496
- `Effect.key -> registry Effect.key (for EffectRef) -> Interaction.key`.
497
- - If a `listContainer` is present on the interaction, the selector resolution may be widened to include list items (optionally filtered by `listItemSelector`), and then further refined by any provided `selector`.
498
-
499
- ### Reduced motion
500
-
501
- - The runtime MAY force reduced motion globally. Authors SHOULD keep effects resilient to reduced motion by avoiding reliance on specific durations or continuous motion.
502
- - Use `conditions` to provide responsive and accessible behavior:
503
- - Define media conditions such as `'(prefers-reduced-motion: reduce)'` and breakpoint queries, and attach them to interactions/effects to disable, simplify, or swap animations when appropriate.
504
- - Provide alternative reduced‑motion variants (e.g., shorter durations, fewer transforms, no perpetual motion/parallax/3D), and select them via `conditions` or effect references so that users who prefer reduced motion get a gentler experience.
680
+ Each `Interact.create()` call returns an instance. Store instances and call `instance.destroy()` when no longer needed (e.g. on component unmount) to prevent stale listeners and memory leaks.