Skip to content

Commit 17370e0

Browse files
committed
Merge branch 'master' of github.com:wix/interact into splittext_integration_2nd_attempt
2 parents c4c4736 + b73962c commit 17370e0

54 files changed

Lines changed: 576 additions & 111 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
name: Fix entrance FOUC
3+
overview: Prevent delayed entrance flashes without changing IntersectionObserver geometry by making the transient neutral styles author-important, using real zero delays, and giving entrance effects backwards fill by default. Align validation, documentation, and Interactor guidance with the runtime behavior.
4+
todos:
5+
- id: important-initial-css
6+
content: Emit author-important transient FOUC declarations and preserve true zero animation delays
7+
status: completed
8+
- id: entrance-fill-defaults
9+
content: Default every entrance preset descriptor to backwards fill while preserving explicit overrides
10+
status: completed
11+
- id: validation-guidance
12+
content: Add backwards-fill validation guidance and align Interact docs, rules, and Interactor skill
13+
status: completed
14+
- id: regression-tests
15+
content: Cover CSS serialization, timing, all entrance presets, validator behavior, and run targeted verification
16+
status: completed
17+
isProject: false
18+
---
19+
20+
# Fix Delayed Entrance FOUC
21+
22+
## Implementation
23+
24+
- Extend the internal declaration shape in [`packages/interact/src/types/css.ts`](packages/interact/src/types/css.ts) with an `important` flag and serialize it in [`packages/interact/src/core/cssUtils.ts`](packages/interact/src/core/cssUtils.ts). Mark every [`DEFAULT_INITIAL`](packages/interact/src/core/css.ts) declaration important so `visibility: hidden` and neutral transform properties override pre-trigger animation values, while retaining the existing `:not([data-interact-enter])` lifecycle.
25+
- In [`packages/motion/src/api/cssAnimations.ts`](packages/motion/src/api/cssAnimations.ts), replace the synthetic `delay || 1` fallback with `delay ?? 0`. Do not add the proposed `0.1ms` initial override, because it would alter computed sequence timing and could move a playing animation back from its active phase into its delay phase.
26+
- Add a small `getEntranceFill()` helper in [`packages/motion-presets/src/utils.ts`](packages/motion-presets/src/utils.ts), returning `options.fill ?? 'backwards'`. Apply it after `...options` to every animation descriptor in all 19 modules under [`packages/motion-presets/src/library/entrance/`](packages/motion-presets/src/library/entrance/) so both CSS and WAAPI paths default to backwards fill while explicit `none`, `forwards`, or `both` values remain authoritative.
27+
28+
## Validation and guidance
29+
30+
- Add an informational `RECOMMENDED_FILL_BACKWARDS` semantic check in [`packages/interact-validate/src/semantic/recommendedPatterns.ts`](packages/interact-validate/src/semantic/recommendedPatterns.ts), wire it through [`collectSemanticWarnings.ts`](packages/interact-validate/src/semantic/collectSemanticWarnings.ts) and [`errors.ts`](packages/interact-validate/src/errors.ts), and recommend explicit `backwards` or `both` for effective `viewEnter`/`once` animation effects, including nested sequence effects. Keep this advisory so preset defaults remain a safety net rather than a schema requirement.
31+
- Update the authoritative entrance/FOUC guidance and generated CSS examples in [`packages/interact/rules/viewenter.md`](packages/interact/rules/viewenter.md), [`packages/interact/rules/full-lean.md`](packages/interact/rules/full-lean.md), [`packages/interact/docs/api/functions.md`](packages/interact/docs/api/functions.md), and [`packages/interact/docs/examples/entrance-animations.md`](packages/interact/docs/examples/entrance-animations.md): explain the important neutral pre-trigger rule, use `fill: 'backwards'` for `once`, and reserve `both` for effects that must retain their final keyframe.
32+
- Update the repository Interactor source in [`skills/interactor/SKILL.md`](skills/interactor/SKILL.md) plus its `config-schema`, `triggers`, `integration-recipes`, and `validate` references. Make explicit backwards fill an invariant in generated `viewEnter`/`once` configs and update the relevant eval expectation; do not manually edit external installed skill snapshots.
33+
34+
## Regression coverage
35+
36+
- Update [`packages/interact/test/css.spec.ts`](packages/interact/test/css.spec.ts) and [`cssUtils.spec.ts`](packages/interact/test/cssUtils.spec.ts) to verify important initial declarations are emitted only for eligible same-element `viewEnter`/`once` effects and disappear from the post-start selector.
37+
- Update [`packages/motion/test/motion.spec.ts`](packages/motion/test/motion.spec.ts) to assert omitted/zero delay produces `0ms`, while positive delays remain unchanged.
38+
- Add centralized entrance-preset tests covering all 19 exports: every generated descriptor defaults to `backwards`, and an explicit fill value is preserved. Retain existing per-preset behavior tests and add coverage for the currently untested `ExpandIn` export through this matrix.
39+
- Add validator tests for direct effects, referenced registry effects, sequence effects, implicit `once`, accepted `backwards`/`both`, and informational severity/category overrides.
40+
41+
## Verification
42+
43+
- Run `nvm use`, then targeted tests and type checks for `@wix/motion`, `@wix/motion-presets`, `@wix/interact`, and `@wix/interact-validate`; finish with repository lint/format checks for touched files.

CHANGELOG.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1414

1515
#### Added
1616

17-
- `@wix/splittext/plugin` entry point: `splitTextPlugin` (runtime adapter for `Interact.use('splitText', …)`) and its build-time counterpart `splitTextStyle` for `generate()`'s `plugins` option (#275)
18-
- `SplitTextPluginConfig` type — `{ container, hideUntilReady?, ...SplitTextOptions }` — for declaration-merging `$splitText` into `InteractPluginConfigMap` (#275)
19-
- `hideUntilReady` opts into SSR FOUC prevention: `splitTextStyle` hides the container until the runtime split sets `data-splittext-ready` (#275)
17+
- `@wix/splittext/plugin` entry points: `splitTextPlugin` for `Interact.use()`, and `splitTextStyle` for `generate()` (#275)
18+
- `hideUntilReady`: `splitTextStyle` hides the container until the runtime split sets `data-splittext-ready` (#275)
2019

2120
### [0.1.2] - 2026-07-14
2221

@@ -38,7 +37,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3837

3938
#### Added
4039

41-
- Plugin fields: `$`-prefixed keys on interactions and effects are accepted with opaque values (validate never inspects plugin config), while every other unknown key is still reported as `SCHEMA_UNRECOGNIZED_KEYS` so typos are still caught (#275)
40+
- Plugin fields: `$`-prefixed keys on interactions and effects are accepted; every other unknown key is still reported as `SCHEMA_UNRECOGNIZED_KEYS` (#275)
41+
42+
### [0.1.2] - 2026-07-29
43+
44+
#### Added
45+
46+
- `RECOMMENDED_FILL_BACKWARDS` semantic check nudging `viewEnter` + `once` keyframe/named effects without FOUC hiding rules to set `fill: 'backwards'` or `'both'` (#277)
47+
48+
#### Changed
49+
50+
- README rule catalog updated for `RECOMMENDED_FILL_BACKWARDS` (#277)
4251

4352
### [0.1.1] - 2026-07-14
4453

@@ -71,14 +80,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
7180

7281
#### Added
7382

74-
- Generic plugin bridge: `Interact.use(name, plugin)` registers a plugin, and a `$<name>` field on an interaction or effect (e.g. `$splitText`) routes its value to it at connect time, before target resolution. Plugin values are opaque to Interact; a returned cleanup runs on disconnect/teardown (#275)
75-
- `generate()` accepts a `plugins` option — a map of plugin name → build-time style generator — so plugins can emit SSR CSS (e.g. FOUC prevention for un-split text) without Interact inspecting their config (#275)
76-
- Plugin types: `InteractPlugin`, `InteractPluginContext`, `InteractPluginCleanup`, `InteractPluginConfigMap` (augment to type `$<name>` fields), `InteractPluginStyleContext`, `InteractPluginStyleGenerator`, `InteractPluginStyles`, and `PluginFields` (#275)
77-
- Agent rules (`rules/plugins.md`) and docs (`docs/guides/plugins.md`) for registering plugins and their SSR styling (#275)
83+
- Generic plugin bridge: `Interact.use(name, plugin)` registers a plugin, and a `$<name>` field on an interaction or effect (#275)
84+
- `generate()` accepts a `plugins` option — a map of plugin name → build-time style generator (#275)
7885

7986
#### Changed
8087

81-
- `generate(config, options?)`: the second argument now accepts an options bag — `{ useFirstChild?, plugins? }` — exported as the `GenerateOptions` type. Passing a bare boolean still works and is treated as `useFirstChild`, so `generate(config, true)` is unchanged (#275)
88+
- `generate(config, options?)`: the second argument now accepts an options bag — `{ useFirstChild?, plugins? }` — exported as the `GenerateOptions` (#275)
89+
90+
### [2.5.5] - 2026-07-29
91+
92+
#### Fixed
93+
94+
- Entrance FOUC prevention: `generate()` initial rules now emit `!important` on transform neutralization declarations so they override inline styles until the animation starts (#277)
95+
96+
#### Changed
97+
98+
- `viewEnter` rules and docs recommend `fill: 'backwards'` (or `'both'`) for entrance animations; CSS rule declarations support an optional `important` flag (#277)
8299

83100
### [2.5.4] - 2026-07-16
84101

@@ -282,6 +299,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
282299

283300
## @wix/motion
284301

302+
### [2.1.8] - 2026-07-29
303+
304+
#### Added
305+
306+
- `getWebAnimation()` accepts SVG elements (and any `Element`) as keyframe targets, not only `HTMLElement` (#280)
307+
308+
#### Changed
309+
310+
- `getCSSAnimation()` delay serialization: use `0ms` when `delay` is `0` instead of previously defaulting to `1ms` (#277)
311+
285312
### [2.1.7] - 2026-05-29
286313

287314
#### Added
@@ -360,6 +387,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
360387

361388
## @wix/motion-presets
362389

390+
### [1.0.4] - 2026-07-29
391+
392+
#### Changed
393+
394+
- Entrance presets default to `fill: 'backwards'` via `getEntranceFill()` unless overridden (#277)
395+
363396
### [1.0.3] - 2026-05-29
364397

365398
#### Changed

apps/demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"test": "vitest run"
1212
},
1313
"dependencies": {
14-
"@wix/interact": "^2.5.4",
14+
"@wix/interact": "^2.5.5",
1515
"@wix/splittext": "^0.1.0",
1616
"react": "^18.3.1",
1717
"react-dom": "^18.3.1"

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"test": "echo 'No tests yet'"
1212
},
1313
"dependencies": {
14-
"@wix/interact": "^2.5.4",
14+
"@wix/interact": "^2.5.5",
1515
"react": "^18.3.1",
1616
"react-dom": "^18.3.1",
1717
"react-markdown": "^9.0.1",

apps/playground/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
"lint": "tsc --noEmit"
1111
},
1212
"dependencies": {
13-
"@wix/interact": "^2.5.4",
14-
"@wix/motion-presets": "^1.0.3"
13+
"@wix/interact": "^2.5.5",
14+
"@wix/motion-presets": "^1.0.4"
1515
},
1616
"devDependencies": {
1717
"typescript": "^5.9.3",

packages/interact-validate/README.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -194,25 +194,26 @@ The single source of truth for every code the validator emits. The agent-facing
194194

195195
These encode statically-detectable authoring pitfalls from the trigger rule files. Each belongs to a [rule category](#severity-model), so set the category to `'off'` to silence it or `'error'` to make it fail `valid`.
196196

197-
| Code | Trigger | Rule category |
198-
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------ |
199-
| `UNUSED_EFFECT` | A `config.effects` entry is never referenced. | `UNUSED_DEFINITION` |
200-
| `UNUSED_SEQUENCE` | A `config.sequences` entry is never referenced. | `UNUSED_DEFINITION` |
201-
| `UNUSED_CONDITION` | A `config.conditions` entry is never referenced. | `UNUSED_DEFINITION` |
202-
| `DUPLICATE_KEYFRAME_NAME` | A `keyframeEffect.name` is reused across effects. | `UNIQUE_DEFINITION_IDS` |
203-
| `SAME_ELEMENT_RETRIGGER` | `viewEnter` with a non-`once` `triggerType` on the same source+target element. | `SAME_ELEMENT_RETRIGGER` |
204-
| `HIT_AREA_SHIFT` | `hover`/`pointerMove` `keyframeEffect` with a `translate`/`scale`/`matrix` transform on the same source+target element. | `HIT_AREA_SHIFT` |
205-
| `SCROLL_PRESET_MISSING_RANGE` | A `*Scroll` `namedEffect` on `viewProgress` omits `range`. | `SCROLL_RANGE` |
206-
| `SCROLL_PRESET_BAD_RANGE` | A scroll preset `range` is not `'in'`/`'out'`/`'continuous'`. | `SCROLL_RANGE` |
207-
| `ANIMATION_END_SELF_REFERENCE` | An `animationEnd` interaction waits on an effect it also produces (never starts). | `ANIMATION_END_GRAPH` |
208-
| `LIST_ITEM_SELECTOR_WITHOUT_CONTAINER` | `listItemSelector` present without `listContainer` (inert). | `ELEMENT_SELECTION` |
209-
| `REDUNDANT_SELECTOR_WITH_LIST_ITEM` | `selector` ignored when `listContainer` + `listItemSelector` are both present. | `ELEMENT_SELECTION` |
210-
| `EMPTY_STYLE_PROPERTIES` | A state effect's `transition.styleProperties` / `transitionProperties` is `[]` (toggles nothing). | `STATE_EFFECT` |
211-
| `STATE_REMOVE_WITHOUT_EFFECT_ID` | `stateAction: 'remove'` with no `effectId` to pair with a matching `'add'`. | `STATE_EFFECT` |
212-
| `RECOMMENDED_FILL_BOTH` | A scrubbed (`viewProgress`/`pointerMove`) or toggling (`alternate`/`repeat`/`state`) effect omits `fill: 'both'`. | `RECOMMENDED_FILL` |
213-
| `POINTER_AXIS_IGNORED` | `pointerMove` `params.axis` set on a `namedEffect`/`customEffect` (axis only applies to `keyframeEffect`). | `POINTER_AXIS` |
214-
| `KEYFRAME_PROP_NOT_CAMEL_CASE` | A `keyframeEffect` property name is kebab-case (not WAAPI camelCase). | `KEYFRAME_STYLE` |
215-
| `INVALID_INSET` | `viewEnter` `params.inset` is not 1–4 CSS lengths/percentages. | `VIEW_INSET` |
197+
| Code | Trigger | Rule category |
198+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
199+
| `UNUSED_EFFECT` | A `config.effects` entry is never referenced. | `UNUSED_DEFINITION` |
200+
| `UNUSED_SEQUENCE` | A `config.sequences` entry is never referenced. | `UNUSED_DEFINITION` |
201+
| `UNUSED_CONDITION` | A `config.conditions` entry is never referenced. | `UNUSED_DEFINITION` |
202+
| `DUPLICATE_KEYFRAME_NAME` | A `keyframeEffect.name` is reused across effects. | `UNIQUE_DEFINITION_IDS` |
203+
| `SAME_ELEMENT_RETRIGGER` | `viewEnter` with a non-`once` `triggerType` on the same source+target element. | `SAME_ELEMENT_RETRIGGER` |
204+
| `HIT_AREA_SHIFT` | `hover`/`pointerMove` `keyframeEffect` with a `translate`/`scale`/`matrix` transform on the same source+target element. | `HIT_AREA_SHIFT` |
205+
| `SCROLL_PRESET_MISSING_RANGE` | A `*Scroll` `namedEffect` on `viewProgress` omits `range`. | `SCROLL_RANGE` |
206+
| `SCROLL_PRESET_BAD_RANGE` | A scroll preset `range` is not `'in'`/`'out'`/`'continuous'`. | `SCROLL_RANGE` |
207+
| `ANIMATION_END_SELF_REFERENCE` | An `animationEnd` interaction waits on an effect it also produces (never starts). | `ANIMATION_END_GRAPH` |
208+
| `LIST_ITEM_SELECTOR_WITHOUT_CONTAINER` | `listItemSelector` present without `listContainer` (inert). | `ELEMENT_SELECTION` |
209+
| `REDUNDANT_SELECTOR_WITH_LIST_ITEM` | `selector` ignored when `listContainer` + `listItemSelector` are both present. | `ELEMENT_SELECTION` |
210+
| `EMPTY_STYLE_PROPERTIES` | A state effect's `transition.styleProperties` / `transitionProperties` is `[]` (toggles nothing). | `STATE_EFFECT` |
211+
| `STATE_REMOVE_WITHOUT_EFFECT_ID` | `stateAction: 'remove'` with no `effectId` to pair with a matching `'add'`. | `STATE_EFFECT` |
212+
| `RECOMMENDED_FILL_BOTH` | A scrubbed (`viewProgress`/`pointerMove`) or toggling (`alternate`/`repeat`/`state`) effect omits `fill: 'both'`. | `RECOMMENDED_FILL` |
213+
| `RECOMMENDED_FILL_BACKWARDS` | A `viewEnter` + `once` named/keyframe effect targeting another element or using a same-element delay omits `fill: 'backwards'` or `'both'`. | `RECOMMENDED_FILL` |
214+
| `POINTER_AXIS_IGNORED` | `pointerMove` `params.axis` set on a `namedEffect`/`customEffect` (axis only applies to `keyframeEffect`). | `POINTER_AXIS` |
215+
| `KEYFRAME_PROP_NOT_CAMEL_CASE` | A `keyframeEffect` property name is kebab-case (not WAAPI camelCase). | `KEYFRAME_STYLE` |
216+
| `INVALID_INSET` | `viewEnter` `params.inset` is not 1–4 CSS lengths/percentages. | `VIEW_INSET` |
216217

217218
## Usage recipes
218219

packages/interact-validate/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@wix/interact-validate",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "Schema + referential + semantic validation for @wix/interact's InteractConfig.",
55
"license": "MIT",
66
"main": "dist/cjs/index.js",
@@ -49,11 +49,11 @@
4949
"zod": "^4.0.0"
5050
},
5151
"peerDependencies": {
52-
"@wix/interact": "^2.5.4"
52+
"@wix/interact": "^2.5.5"
5353
},
5454
"devDependencies": {
5555
"@vitest/coverage-v8": "^4.0.14",
56-
"@wix/interact": "^2.5.4",
56+
"@wix/interact": "^2.5.5",
5757
"rimraf": "^6.0.1",
5858
"typescript": "^5.9.3",
5959
"vite": "^7.2.2",

packages/interact-validate/src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const RULE_CODE_MAP: Record<string, string> = {
2929
EMPTY_STYLE_PROPERTIES: 'STATE_EFFECT',
3030
STATE_REMOVE_WITHOUT_EFFECT_ID: 'STATE_EFFECT',
3131
RECOMMENDED_FILL_BOTH: 'RECOMMENDED_FILL',
32+
RECOMMENDED_FILL_BACKWARDS: 'RECOMMENDED_FILL',
3233
POINTER_AXIS_IGNORED: 'POINTER_AXIS',
3334
KEYFRAME_PROP_NOT_CAMEL_CASE: 'KEYFRAME_STYLE',
3435
INVALID_INSET: 'VIEW_INSET',

packages/interact-validate/src/semantic/fouc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { RETRIGGER_TYPES } from '../types';
1111
const DISCRETE_TRIGGERS = ['hover', 'click', 'interest', 'activate'];
1212
const HIT_AREA_TRANSFORM = /(translate|scale|matrix)/;
1313

14-
function targetsSameElementAsSource(owner: AnyInteraction, effect: AnyEffect): boolean {
14+
export function targetsSameElementAsSource(owner: AnyInteraction, effect: AnyEffect): boolean {
1515
if (effect.key !== undefined && effect.key !== owner.key) return false;
1616
const refiners = ['selector', 'listContainer', 'listItemSelector'] as const;
1717
for (const field of refiners) {

0 commit comments

Comments
 (0)