# @wix/interact Integration Rules

Rules for integrating `@wix/interact` into a webpage — binding animations and effects to user-driven triggers via declarative configuration.

## Table of Contents

- [Entry Points](#entry-points)
  - [Web (Custom Elements)](#web-custom-elements)
  - [React](#react)
  - [Vanilla JS](#vanilla-js)
- [Named Effects & registerEffects](#named-effects--registereffects)
- [Configuration Schema](#configuration-schema)
  - [InteractConfig](#interactconfig)
  - [Interaction](#interaction)
  - [Element Selection](#element-selection)
- [Triggers](#triggers)
- [Sequences](#sequences)
- [CSS Generation & FOUC Prevention](#css-generation--fouc-prevention)
- [Static API](#static-api)

---

## Entry Points

Install with your package manager:

```bash
npm install @wix/interact @wix/motion-presets
```

### Web (Custom Elements)

```typescript
import { Interact, generate } from '@wix/interact/web';
import { FadeIn } from '@wix/motion-presets';

Interact.registerEffects({ FadeIn });
const css = generate(config, true); // useFirstChild = true for web
// Embed css in HTML — see CSS Generation & FOUC Prevention
Interact.create(config);
```

The `config` object contains `interactions` (trigger-effect bindings), and optionally `effects`, `sequences`, and `conditions`. See [Configuration Schema](#configuration-schema) for full details.

Wrap target elements with `<interact-element>`:

```html
<interact-element data-interact-key="hero">
  <section class="hero">...</section>
</interact-element>
```

**Rules:**

- MUST set `data-interact-key` to a unique string within the page.
- MUST contain at least one child element (the library targets `.firstElementChild` by default).

### React

- Call `generate(config, false)` during SSR/build and embed the CSS in document `<head>` (`<style>` or a linked `.css` file).
- Wrap the `Interact.create()` call in a `useEffect` hook to prevent it from running on server-side.
- Store the returned instance, and call its `.destroy()` method on the effect's cleanup function.

```typescript
import { useEffect } from 'react';
import { Interact, generate } from '@wix/interact/react';

const css = generate(config, false); // useFirstChild = false for React
// Embed css in <head> during SSR/build

useEffect(() => {
  const instance = Interact.create(config);

  return () => {
    instance.destroy();
  };
}, [config]);
```

Replace target elements with `<Interaction>`:

```tsx
import { Interaction } from '@wix/interact/react';

<Interaction tagName="div" interactKey="hero" className="hero">
  ...
</Interaction>;
```

**Rules:**

- MUST set `tagName` to a valid HTML tag string for the element being replaced.
- MUST set `interactKey` to a unique string within the page.

### Vanilla JS

```typescript
import { Interact, add, generate } from '@wix/interact';
import { FadeIn } from '@wix/motion-presets';

Interact.registerEffects({ FadeIn });
const css = generate(config, false); // useFirstChild = false for vanilla
// Embed css in HTML — see CSS Generation & FOUC Prevention
Interact.create(config);
add(element, 'hero');
```

**Rules:**

- Call `add(element, key)` after elements exist in the DOM.
- Call `remove(key)` to unregister all interactions for a key.

---

## Named Effects & registerEffects

To use `namedEffect` presets from `@wix/motion-presets`, register them before calling `generate()` or `Interact.create()`. For full effect type syntax (`keyframeEffect`, `customEffect`, `StateEffect`, `ScrubEffect`), see `full-lean.md`.

**Install:**

```bash
> npm install @wix/motion-presets
```

**Import and register:**

```typescript
import { Interact } from '@wix/interact/web';
import * as presets from '@wix/motion-presets';

Interact.registerEffects(presets);
```

Or register selectively:

```typescript
import { FadeIn, ParallaxScroll } from '@wix/motion-presets';
Interact.registerEffects({ FadeIn, ParallaxScroll });
```

Then use in effects:

```typescript
{ namedEffect: { type: 'FadeIn' }, duration: 800, easing: 'ease-out' }
```

For full effect type syntax (`keyframeEffect`, `namedEffect`, `customEffect`, `transition`/`transitionProperties`), see [full-lean.md](./full-lean.md) and the trigger-specific rule files.

---

## Configuration Schema

### InteractConfig

```typescript
type InteractConfig = {
  interactions: Interaction[];
  effects?: Record<string, Effect>;
  sequences?: Record<string, SequenceConfig>;
  conditions?: Record<string, Condition>;
};
```

| Field          | Description                                                             |
| :------------- | :---------------------------------------------------------------------- |
| `interactions` | Required. Array of interaction definitions binding triggers to effects. |
| `effects?`     | Reusable effects referenced by `effectId` from interactions.            |
| `sequences?`   | Reusable sequence definitions, referenced by `sequenceId`.              |
| `conditions?`  | Named conditions (media/container/selector queries), referenced by ID.  |

Each call to `Interact.create(config)` creates a new `Interact` instance. A single config can define multiple interactions.

### Interaction

```typescript
{
  key: string;                     // REQUIRED — matches data-interact-key / interactKey
  trigger: TriggerType;            // REQUIRED — trigger type
  params?: TriggerParams;          // trigger-specific parameters
  selector?: string;               // CSS selector to refine target within the element
  listContainer?: string;          // CSS selector for a list container
  listItemSelector?: string;       // optional — CSS selector to filter which children of listContainer are selected
  conditions?: string[];           // array of condition IDs; all must pass
  effects?: Effect[];              // effects to apply
  sequences?: SequenceConfig[];    // sequences to apply
}
```

At least one of `effects` or `sequences` MUST be provided.

**Multiple effects per interaction:** A single interaction can contain multiple effects in its `effects` array. All effects share the same trigger — they fire together when the trigger activates. Use this to animate different targets from the same trigger event instead of duplicating interactions.

### Element Selection

**Most common**: Omit `selector`/`listContainer`/`listItemSelector` entirely — the element with the matching key is used as both source and target. Use `selector` to target a child element within the keyed element. Use `listContainer` for staggered sequences across list items.

`listItemSelector` is **optional** — only use it when you need to **filter** which children of `listContainer` participate (e.g. select only `.active` items). When omitted, all immediate children of the `listContainer` are selected.

#### Source element resolution (Interaction level)

The source element is what the trigger attaches to. Resolved in priority order:

1. **`listContainer` + `listItemSelector`** — matches only the elements matching `listItemSelector` within the the `listContainer`.
2. **`listContainer` only** — trigger attaches to all immediate children of the container (common case).
3. **`listContainer` + `selector`** — matches via `querySelector` within each immediate child of the container.
4. **`selector` only** — matches via `querySelectorAll` within the root element.
5. **Fallback** — first child of `<interact-element>` (web) or the root element (react/vanilla).

#### Target element resolution (Effect level)

The target element is what the effect animates. Resolved in priority order:

1. **`Effect.key`** — the root with matching `data-interact-key`.
2. **Registry Effect's `key`** — if the effect is an `EffectRef`, the `key` from the referenced registry entry is used.
3. **Fallback to `Interaction.key`** — the source element acts as the target's root.
4. After resolving the target's root, `selector`, `listContainer`, and `listItemSelector` on the effect further refine which child elements within that target are animated (same priority order as source resolution).

---

## Triggers

| Trigger        | Description                            | Trigger `params`                                                                        | Rules                                |
| :------------- | :------------------------------------- | :-------------------------------------------------------------------------------------- | :----------------------------------- |
| `hover`        | Mouse enter/leave                      | No params. Set `triggerType` on TimeEffect or `stateAction` on StateEffect.             | [hover.md](./hover.md)               |
| `click`        | Mouse click                            | Same as `hover`                                                                         | [click.md](./click.md)               |
| `interest`     | Accessible hover (hover + focus)       | Same as `hover`                                                                         | [hover.md](./hover.md)               |
| `activate`     | Accessible click (click + Enter/Space) | Same as `click`                                                                         | [click.md](./click.md)               |
| `viewEnter`    | Element enters viewport                | `threshold?`; `inset?`. Set `triggerType` on TimeEffect or sequence config.             | [viewenter.md](./viewenter.md)       |
| `viewProgress` | Scroll-driven (ViewTimeline)           | No trigger params. Configure `rangeStart`/`rangeEnd` on the **effect**, not on `params` | [viewprogress.md](./viewprogress.md) |
| `pointerMove`  | Mouse movement                         | `hitArea?`: `'self'` \| `'root'`; `axis?`: `'x'` \| `'y'`                               | [pointermove.md](./pointermove.md)   |
| `animationEnd` | Chain after another effect             | `effectId`: ID of the preceding effect                                                  | —                                    |

For `hover`/`click` (and their accessible variants `interest`/`activate`): set `triggerType` on the effect for keyframe/named/custom effects (TimeEffect), or `stateAction` on the effect for transitions (StateEffect). Do not mix both on the same effect.

---

## Sequences

Sequences coordinate multiple effects with staggered timing.

```typescript
{
  offset: number,           // ms between consecutive items
  offsetEasing: string,     // Any valid easing string for stagger distribution curve
  delay: number,            // ms base delay before the sequence starts
  effects: [
    /* ... effect definitions */,
  ],
}
```

Define reusable sequences in `InteractConfig.sequences` and reference by `sequenceId`:

```typescript
{
  sequences: {
    'stagger-fade': {
      /* ... sequence definition */
    },
  },
  interactions: [
    {
      key: `'[SOURCE_KEY]'`,
      trigger: `'[TRIGGER]'`,
      params: `[TRIGGER_PARAMS]`,
      sequences: [{ sequenceId: 'stagger-fade' }],
    },
  ],
}
```

- **MUST** use a **string** `offsetEasing` (named key, `cubic-bezier(...)` or `linear(...)`) on any
  sequence that needs generated CSS. `generate()` compiles the stagger into a `calc()` delay driven by
  `--motion-<sequenceId>-index` custom properties, which a `(p: number) => number` function cannot
  express — such a sequence is omitted from the generated CSS entirely and loses FOUC prevention.
  `@wix/interact-validate` flags it as `FUNCTION_OFFSET_EASING` (warning, rule category `OFFSET_EASING`).
- **Rule**: `sequenceId` names those custom properties, so it must be identical on both sides.
  Interact handles this for you — omitted ids default to `seq-<interactionIndex>-<sequenceIndex>`, derived
  from the config position — but a hand-written id must be stable across CSS generation and runtime.

---

## CSS Generation & FOUC Prevention

`generate(config, options?)` produces complete CSS for **all** interactions in the config — `@keyframes`, animation/transition custom properties, `view-timeline` declarations, state-selector rules, coordinated-list aggregation, and FOUC-prevention initial rules.

**Static site policy:** For static or pre-rendered HTML (agent-generated pages,
SSG, static export), prefer calling `generate()` for the complete config at
build/generation time and embedding the CSS in the shipped HTML. If some
interactions depend on runtime-only data, split them into a separate config:
pre-generate the static config and generate/inject the runtime config in the
browser before its `Interact.create()` call. If splitting is impractical,
generating the complete CSS at runtime is an acceptable fallback. Call
`registerEffects()` before each `generate()` when using `namedEffect`.

The `useFirstChild` option tells Interact whether to render `:first-child` selectors for custom-element (`web`) integration: `true` for **web**, `false` for **react** and **vanilla**. Pass it either as a bare boolean (`generate(config, true)`) or in the options bag (`generate(config, { useFirstChild: true })`) — the bag is also where `plugins` goes.

```javascript
import { generate } from '@wix/interact/web';
import { FadeIn } from '@wix/motion-presets';

Interact.registerEffects({ FadeIn });
const css = generate(config, true); // true for web; false for react/vanilla
```

**Embed the CSS** in the HTML output using one of:

```html
<!-- Option 1: inline in <head> (preferred) -->
<head>
  <style>
    …generated css…
  </style>
</head>

<!-- Option 2: linked stylesheet in <head> -->
<head>
  <link rel="stylesheet" href="interact.css" />
</head>

<!-- Option 3: render-blocking at start of <body> (prevents FOUC) -->
<body>
  <style blocking="render">
    …generated css…
  </style>
  <!-- or -->
  <link rel="stylesheet" href="interact.css" blocking="render" />
  …
</body>
```

### FOUC Prevention (viewEnter + once)

**Problem:** Elements with entrance animations (e.g. `FadeIn` on `viewEnter`) are initially visible in their final state. Before the animation framework applies the starting keyframe, the content flashes visibly — a flash of un-animated content (FOUC).

**Solution:** Call `generate(config, useFirstChild)` at build/generation time and embed the resulting CSS before first paint. The generated initial rules hide entrance-animated elements until the animation starts.

See [viewenter.md](./viewenter.md) for full details.

**Rules:**

- For static/pre-rendered sites, prefer generating all possible CSS at
  build/generation time. Split runtime-dependent interactions into a separate
  config where practical, or fall back to generating the full config at runtime.
- Inject runtime-generated CSS before the corresponding `Interact.create()` call
  and before revealing initially hidden content to minimize FOUC.
- `generate()` processes all interactions, not just `viewEnter`.
- FOUC initial rules apply only to `viewEnter` + `triggerType: 'once'` (or no `triggerType`, which defaults to `'once'`) where source and target are the same element.

---

## Static API

Each `Interact.create(config)` call returns an instance. Keep a reference if you need to add/remove elements dynamically (vanilla JS) or to destroy a specific instance. Call `Interact.destroy()` to tear down all instances at once (e.g. on page navigation).

| Method / Property                   | Description                                                                                                                                                                        |
| :---------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `generate(config, options?)`        | Produce complete CSS for all interactions. Call at build/generation time; embed in HTML. `options` = `{ useFirstChild?, plugins? }`, or a bare boolean for legacy `useFirstChild`. |
| `Interact.create(config)`           | Initialize with a config. Returns the instance. Multiple configs create separate instances.                                                                                        |
| `Interact.registerEffects(presets)` | Register named effect presets before `generate()` and `create`. Required for `namedEffect`.                                                                                        |
| `Interact.destroy()`                | Tear down all instances.                                                                                                                                                           |
| `Interact.forceReducedMotion`       | `boolean` — force reduced-motion behavior regardless of OS setting. Default: `false`.                                                                                              |
| `Interact.allowA11yTriggers`        | `boolean` — enable accessibility triggers (`interest`, `activate`). Default: `true`.                                                                                               |
| `Interact.setup(options)`           | Configure global defaults for scroll/pointer/viewEnter trigger params. Call before `create`.                                                                                       |
