Skip to content

Commit 5d2b8d9

Browse files
authored
Splittext integration 2nd attempt (#275)
* 2nd version + css * format * build * small fix * small fix * small fix * PR fixes * insert plugin into package * fix test * PR fixes * PR fixes * final PR changes
1 parent b73962c commit 5d2b8d9

47 files changed

Lines changed: 1903 additions & 195 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release-splittext.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ jobs:
8080
- name: Build splittext package
8181
run: yarn workspace @wix/splittext build
8282

83+
# @wix/interact is a devDependency used only by the plugin-bridge integration test —
84+
# splittext itself neither builds nor runs against it (note the build step above runs first).
85+
- name: Build motion package (test-only dependency of @wix/interact)
86+
run: yarn workspace @wix/motion build
87+
88+
- name: Build interact package (test-only dependency)
89+
run: yarn workspace @wix/interact build
90+
8391
- name: Test splittext package
8492
run: yarn workspace @wix/splittext test
8593

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
## @wix/splittext
1212

13+
### [0.2.0] - unreleased
14+
15+
#### Added
16+
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)
19+
1320
### [0.1.2] - 2026-07-14
1421

1522
#### Added
@@ -26,6 +33,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2633

2734
## @wix/interact-validate
2835

36+
### [0.2.0] - unreleased
37+
38+
#### Added
39+
40+
- Plugin fields: `$`-prefixed keys on interactions and effects are accepted; every other unknown key is still reported as `SCHEMA_UNRECOGNIZED_KEYS` (#275)
41+
2942
### [0.1.2] - 2026-07-29
3043

3144
#### Added
@@ -63,6 +76,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6376

6477
## @wix/interact
6578

79+
### [2.6.0] - unreleased
80+
81+
#### Added
82+
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)
85+
86+
#### Changed
87+
88+
- `generate(config, options?)`: the second argument now accepts an options bag — `{ useFirstChild?, plugins? }` — exported as the `GenerateOptions` (#275)
89+
6690
### [2.5.5] - 2026-07-29
6791

6892
#### Fixed

apps/demo/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
"dev": "vite",
88
"build": "tsc && vite build",
99
"preview": "vite preview",
10-
"lint": "tsc --noEmit",
11-
"test": "vitest run"
10+
"lint": "tsc --noEmit"
1211
},
1312
"dependencies": {
1413
"@wix/interact": "^2.5.5",
14+
"@wix/splittext": "^0.1.0",
1515
"react": "^18.3.1",
1616
"react-dom": "^18.3.1"
1717
},

apps/demo/src/plugins/splitText.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Demo-side glue for the reusable `@wix/splittext` Interact plugin.
3+
*
4+
* The plugin itself now lives in `@wix/splittext/plugin`, which ships WITHOUT a dependency on
5+
* `@wix/interact` so the two packages stay decoupled. This file is where the demo — the one place
6+
* that depends on BOTH — "resolves the typing":
7+
*
8+
* 1. It binds the package's structurally-typed callbacks to Interact's real `InteractPlugin` /
9+
* `InteractPluginStyleGenerator` contract. The assignments below double as a compile-time
10+
* check that `@wix/splittext/plugin` stays compatible with `@wix/interact`.
11+
* 2. It ties the `$splitText` config field to {@link SplitTextPluginConfig} via declaration
12+
* merging on `InteractPluginConfigMap`, so demo configs get autocomplete + checking.
13+
*/
14+
import type { InteractPlugin, InteractPluginStyleGenerator } from '@wix/interact';
15+
import {
16+
splitTextPlugin as splitTextPluginImpl,
17+
splitTextStyle as splitTextStyleImpl,
18+
type SplitTextPluginConfig,
19+
} from '@wix/splittext/plugin';
20+
21+
export const splitTextPlugin: InteractPlugin = splitTextPluginImpl;
22+
export const splitTextStyle: InteractPluginStyleGenerator = splitTextStyleImpl;
23+
export type { SplitTextPluginConfig };
24+
25+
declare module '@wix/interact' {
26+
interface InteractPluginConfigMap {
27+
splitText: SplitTextPluginConfig;
28+
}
29+
}

apps/demo/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"@/*": ["./src/*"],
99
"@wix/interact/web": ["../../packages/interact/src/web"],
1010
"@wix/interact/react": ["../../packages/interact/src/react"],
11-
"@wix/interact": ["../../packages/interact/src/index"]
11+
"@wix/interact": ["../../packages/interact/src/index"],
12+
"@wix/splittext/plugin": ["../../packages/splittext/dist/types/plugin/index"]
1213
}
1314
},
1415
"include": ["src"],

packages/interact-validate/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ const ExperienceSchema = z.object({
125125
});
126126
```
127127

128-
> `InteractConfigSchema` carries a `.transform()`, so a successful `.parse()` returns the config augmented with an internal `warnings` array (`validateInteractConfig` consumes that for you). `customEffect` and function-valued `offsetEasing` are accepted as opaque functions and not deep-validated.
128+
> `InteractConfigSchema` carries a `.transform()`, so a successful `.parse()` returns the config augmented with an internal `warnings` array (`validateInteractConfig` consumes that for you). `customEffect` and function-valued `offsetEasing` are accepted as opaque functions and not deep-validated. Interactions and effects also accept `$`-prefixed plugin fields (e.g. `$splitText`) — config routed to `Interact.use()` plugins. Their schemas use `.catchall(z.unknown())` + a key check rather than `.strict()`: `$`-prefixed fields are accepted with opaque values, while any non-prefixed unknown key is still rejected as `SCHEMA_UNRECOGNIZED_KEYS`.
129129
130130
## Severity model
131131

packages/interact-validate/src/schema/effects.ts

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from 'zod';
22
import { Keyframe, RangeOffset } from './primitives';
3+
import { withPluginFields } from './plugins';
34

45
export const StateActionType = z.enum(['add', 'remove', 'toggle', 'clear']);
56
export const TimeTriggerType = z.enum(['once', 'repeat', 'alternate', 'state']);
@@ -132,19 +133,19 @@ const EffectBase = {
132133
conditions: z.array(z.string().min(1)).optional(),
133134
};
134135

135-
export const StateEffect = TransitionEffectSourceBase.extend({
136-
...EffectBase,
137-
stateAction: StateActionType.optional(),
138-
})
139-
.strict()
140-
.check(checkExactlyOneTransition);
141-
export const StateEffectRef = TransitionEffectSourceBase.extend({
142-
...EffectBase,
143-
effectId: z.string().min(1),
144-
stateAction: StateActionType.optional(),
145-
})
146-
.strict()
147-
.check(checkAtMostOneTransition);
136+
export const StateEffect = withPluginFields(
137+
TransitionEffectSourceBase.extend({
138+
...EffectBase,
139+
stateAction: StateActionType.optional(),
140+
}),
141+
).check(checkExactlyOneTransition);
142+
export const StateEffectRef = withPluginFields(
143+
TransitionEffectSourceBase.extend({
144+
...EffectBase,
145+
effectId: z.string().min(1),
146+
stateAction: StateActionType.optional(),
147+
}),
148+
).check(checkAtMostOneTransition);
148149

149150
const AnimationEffectBase = {
150151
...EffectBase,
@@ -167,57 +168,57 @@ const pointerMoveEffectFields = {
167168
transitionEasing: z.enum(['linear', 'hardBackOut', 'easeOut', 'elastic', 'bounce']).optional(),
168169
};
169170

170-
export const TimeEffect = EffectSourceBase.extend({
171-
...AnimationEffectBase,
172-
iterations: TimeIterations,
173-
duration: z.number().nonnegative(),
174-
delay: z.number().nonnegative().optional(),
175-
triggerType: TimeTriggerType.optional(),
176-
})
177-
.strict()
178-
.check(checkExactlyOneEffectSource);
179-
export const TimeEffectRef = EffectSourceBase.extend({
180-
...AnimationEffectBase,
181-
iterations: TimeIterations,
182-
effectId: z.string().min(1),
183-
duration: z.number().nonnegative().optional(),
184-
delay: z.number().nonnegative().optional(),
185-
triggerType: TimeTriggerType.optional(),
186-
})
187-
.strict()
188-
.check(checkAtMostOneEffectSource);
189-
190-
export const ViewProgressEffect = EffectSourceBase.extend({
191-
...AnimationEffectBase,
192-
iterations: ScrubIterations,
193-
...viewProgressEffectFields,
194-
})
195-
.strict()
196-
.check(checkExactlyOneEffectSource);
197-
export const ViewProgressEffectRef = EffectSourceBase.extend({
198-
...AnimationEffectBase,
199-
iterations: ScrubIterations,
200-
effectId: z.string().min(1),
201-
...viewProgressEffectFields,
202-
})
203-
.strict()
204-
.check(checkAtMostOneEffectSource);
205-
206-
export const PointerMoveEffect = EffectSourceBase.extend({
207-
...AnimationEffectBase,
208-
iterations: ScrubIterations,
209-
...pointerMoveEffectFields,
210-
})
211-
.strict()
212-
.check(checkExactlyOneEffectSource);
213-
export const PointerMoveEffectRef = EffectSourceBase.extend({
214-
...AnimationEffectBase,
215-
iterations: ScrubIterations,
216-
effectId: z.string().min(1),
217-
...pointerMoveEffectFields,
218-
})
219-
.strict()
220-
.check(checkAtMostOneEffectSource);
171+
export const TimeEffect = withPluginFields(
172+
EffectSourceBase.extend({
173+
...AnimationEffectBase,
174+
iterations: TimeIterations,
175+
duration: z.number().nonnegative(),
176+
delay: z.number().nonnegative().optional(),
177+
triggerType: TimeTriggerType.optional(),
178+
}),
179+
).check(checkExactlyOneEffectSource);
180+
export const TimeEffectRef = withPluginFields(
181+
EffectSourceBase.extend({
182+
...AnimationEffectBase,
183+
iterations: TimeIterations,
184+
effectId: z.string().min(1),
185+
duration: z.number().nonnegative().optional(),
186+
delay: z.number().nonnegative().optional(),
187+
triggerType: TimeTriggerType.optional(),
188+
}),
189+
).check(checkAtMostOneEffectSource);
190+
191+
export const ViewProgressEffect = withPluginFields(
192+
EffectSourceBase.extend({
193+
...AnimationEffectBase,
194+
iterations: ScrubIterations,
195+
...viewProgressEffectFields,
196+
}),
197+
).check(checkExactlyOneEffectSource);
198+
export const ViewProgressEffectRef = withPluginFields(
199+
EffectSourceBase.extend({
200+
...AnimationEffectBase,
201+
iterations: ScrubIterations,
202+
effectId: z.string().min(1),
203+
...viewProgressEffectFields,
204+
}),
205+
).check(checkAtMostOneEffectSource);
206+
207+
export const PointerMoveEffect = withPluginFields(
208+
EffectSourceBase.extend({
209+
...AnimationEffectBase,
210+
iterations: ScrubIterations,
211+
...pointerMoveEffectFields,
212+
}),
213+
).check(checkExactlyOneEffectSource);
214+
export const PointerMoveEffectRef = withPluginFields(
215+
EffectSourceBase.extend({
216+
...AnimationEffectBase,
217+
iterations: ScrubIterations,
218+
effectId: z.string().min(1),
219+
...pointerMoveEffectFields,
220+
}),
221+
).check(checkAtMostOneEffectSource);
221222

222223
export const ScrubEffect = z.union([ViewProgressEffect, PointerMoveEffect]);
223224
export const ScrubEffectRef = z.union([ViewProgressEffectRef, PointerMoveEffectRef]);

0 commit comments

Comments
 (0)