Skip to content

Commit 5c01d56

Browse files
committed
PR fixes
1 parent 29c8d84 commit 5c01d56

9 files changed

Lines changed: 79 additions & 150 deletions

File tree

packages/interact/docs/api/interact-class.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Interact {
3333
static getController(key: string | undefined): IInteractionController | undefined;
3434
static use(name: string, plugin: InteractPlugin): void;
3535
static getPlugin(name: string): InteractPlugin | undefined;
36-
static hasPlugins(): boolean;
36+
static getPluginNames(): Set<string>;
3737

3838
// Instance methods
3939
init(config: InteractConfig, options?: { useCustomElement?: boolean }): void;
@@ -246,7 +246,7 @@ Registers a plugin under a name. When an interaction or effect carries a `$<name
246246

247247
```typescript
248248
import { Interact } from '@wix/interact';
249-
import { splitTextPlugin } from './splitTextPlugin';
249+
import { splitTextPlugin } from '@wix/splittext/plugin';
250250

251251
Interact.use('splitText', splitTextPlugin);
252252
Interact.create(config); // configs may now use a `$splitText: { ... }` field
@@ -255,15 +255,15 @@ Interact.create(config); // configs may now use a `$splitText: { ... }` field
255255
**Notes:**
256256

257257
- Register plugins **before** `Interact.create()`.
258-
- Registration is global. A `$<name>` field naming an unregistered plugin throws at connect time.
258+
- Registration is global. A `$<name>` field naming an unregistered plugin is ignored.
259259

260260
### `Interact.getPlugin(name)`
261261

262262
Returns the plugin registered under `name`, or `undefined`.
263263

264-
### `Interact.hasPlugins()`
264+
### `Interact.getPluginsNames()`
265265

266-
Returns `true` if any plugin has been registered.
266+
Returns the set of registered plugins names (non-prefixed).
267267

268268
### `Interact.getController(key)`
269269

packages/interact/docs/api/types.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,14 +975,18 @@ type InteractPluginCleanup = () => void; // runs on disconnect/teardown
975975
Augmentable interface for typing plugin fields, keyed by the **unprefixed** plugin name. Empty by default; consumers merge into it:
976976

977977
```typescript
978+
import type { SplitTextPluginConfig } from '@wix/splittext/plugin';
979+
978980
declare module '@wix/interact' {
979981
interface InteractPluginConfigMap {
980-
splitText: { container: string /* + splitText options */ };
982+
splitText: SplitTextPluginConfig;
981983
}
982984
}
983985
// types the `$splitText` field on interactions and effects
984986
```
985987

988+
Prefer the config type exported by the plugin package over re-declaring its shape by hand.
989+
986990
### `PluginFields`
987991

988992
The `$`-prefixed plugin fields allowed on interactions and effects. Augmented plugins keep their value types (as `$<name>`); any other `$`-prefixed field is still allowed with an `unknown` value.

packages/interact/docs/guides/plugins.md

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
`@wix/interact` can route parts of your config to **plugins** — external code registered at runtime. Interact acts purely as a bridge: it knows a plugin's _name_, and when an interaction or effect carries a field named `$<name>` it hands that field's value to the plugin. Interact never knows what the plugin does.
44

5-
This keeps Interact free of any plugin-specific code, and keeps plugins (like [`@wix/splittext`](https://www.npmjs.com/package/@wix/splittext)) free of any Interact-specific code. Neither package depends on the other — the glue lives in your app.
5+
This keeps Interact free of any plugin-specific code, and keeps plugins (like [`@wix/splittext`](https://www.npmjs.com/package/@wix/splittext)) free of any dependency on Interact. A plugin package can still ship its own adapter — `@wix/splittext/plugin` does — by typing it _structurally_ against the contract below rather than importing `@wix/interact`. Your app then only supplies the type glue.
66

77
## How it works
88

@@ -35,7 +35,7 @@ This keeps Interact free of any plugin-specific code, and keeps plugins (like [`
3535

3636
When the `hero` element connects, Interact sees the `$myPlugin` field, looks up the `myPlugin` plugin, and calls it with `{ any: 'value' }`. Plugins run **before** target resolution, so any DOM a plugin creates is visible to the `selector` / `listContainer` queries that follow.
3737

38-
If a `$`-prefixed field names a plugin that was never registered, Interact throws a clear error at connect time.
38+
If a `$`-prefixed field names a plugin that was never registered, Interact ignores it.
3939

4040
> **Why the `$` prefix?** It marks a field as plugin config unambiguously (no clash with real config fields), it's a valid unquoted key in JS/TS and valid JSON, and it lets `@wix/interact-validate` accept plugin fields (via `catchall`) while still flagging genuinely-unknown keys.
4141
@@ -72,22 +72,11 @@ declare module '@wix/interact' {
7272

7373
## Example: `@wix/splittext`
7474

75-
`@wix/splittext` splits an element's text into `<span>` wrappers (`.split-c` for chars, `.split-w` for words, `.split-l` for lines, `.split-s` for sentences). Wire it up as a plugin, then target the generated spans with a normal `selector`:
75+
`@wix/splittext` splits an element's text into `<span>` wrappers (`.split-c` for chars, `.split-w` for words, `.split-l` for lines, `.split-s` for sentences). You don't need to write the adapter — it ships from the `@wix/splittext/plugin` entry point, written against the contract above _structurally_ so `@wix/splittext` keeps no dependency on `@wix/interact`. Register it, then target the generated spans with a normal `selector`:
7676

7777
```ts
78-
// splitTextPlugin.ts — the ONLY module that imports both packages
79-
import { splitText, type SplitTextOptions, type SplitTextResult } from '@wix/splittext';
80-
import type { InteractPlugin } from '@wix/interact';
81-
82-
export type SplitTextPluginConfig = { container: string } & SplitTextOptions;
83-
84-
export const splitTextPlugin: InteractPlugin = (value, { root }) => {
85-
const { container, ...options } = value as SplitTextPluginConfig;
86-
const element = root.querySelector<HTMLElement>(container);
87-
if (!element) return;
88-
const result: SplitTextResult = splitText(element, options);
89-
return () => result.revert(); // Interact reverts the split on teardown
90-
};
78+
// splitTextTypes.ts — the ONLY module that needs both packages, and only for types
79+
import type { SplitTextPluginConfig } from '@wix/splittext/plugin';
9180

9281
declare module '@wix/interact' {
9382
interface InteractPluginConfigMap {
@@ -98,7 +87,7 @@ declare module '@wix/interact' {
9887

9988
```ts
10089
import { Interact } from '@wix/interact';
101-
import { splitTextPlugin } from './splitTextPlugin';
90+
import { splitTextPlugin } from '@wix/splittext/plugin';
10291

10392
Interact.use('splitText', splitTextPlugin);
10493

@@ -171,52 +160,30 @@ type InteractPluginStyleGenerator = (
171160

172161
### SplitText example — hide until split
173162

174-
Pair a runtime marker with a build-time hide rule so the container is hidden on first paint and revealed once split:
163+
`@wix/splittext/plugin` ships this pairing ready-made: `splitTextStyle` is the SSR counterpart to `splitTextPlugin`, and the two agree on a `data-splittext-ready` marker. Opt in per-field with `hideUntilReady`:
175164

176165
```ts
177-
// splitTextPlugin.ts (extends the earlier example)
178-
import type { InteractPlugin, InteractPluginStyleGenerator } from '@wix/interact';
179-
180-
const READY_ATTR = 'data-splittext-ready';
181-
182-
export const splitTextPlugin: InteractPlugin = (value, { root }) => {
183-
const { container, hideUntilReady, ...options } = value as {
184-
container: string;
185-
hideUntilReady?: boolean;
186-
} & SplitTextOptions;
187-
const el = root.querySelector<HTMLElement>(container);
188-
if (!el) return;
189-
const result = splitText(el, options);
190-
if (hideUntilReady) el.setAttribute(READY_ATTR, ''); // reveal (see the SSR rule below)
191-
return () => {
192-
result.revert();
193-
el.removeAttribute(READY_ATTR);
194-
};
195-
};
166+
import { generate } from '@wix/interact';
167+
import { splitTextStyle } from '@wix/splittext/plugin';
196168

197-
// The SSR counterpart — NOT the same callback as splitTextPlugin.
198-
export const splitTextStyle: InteractPluginStyleGenerator = (value, _) => {
199-
const { container, hideUntilReady } = value as { container: string; hideUntilReady?: boolean };
200-
if (!hideUntilReady) return;
201-
return [
169+
const config = {
170+
effects: { 'char-fade-up': { namedEffect: { type: 'FadeIn' }, duration: 400 } },
171+
interactions: [
202172
{
203-
declarations: [{ name: 'visibility', value: 'hidden' }],
204-
selectorSuffix: ` ${container}:not([${READY_ATTR}])`,
173+
key: 'hero',
174+
trigger: 'viewEnter',
175+
$splitText: { container: '.title', type: 'chars', hideUntilReady: true },
176+
sequences: [{ offset: 30, effects: [{ effectId: 'char-fade-up', selector: '.split-c' }] }],
205177
},
206-
];
178+
],
207179
};
208-
```
209-
210-
```ts
211-
import { generate } from '@wix/interact';
212-
import { splitTextStyle } from './splitTextPlugin';
213180

214181
// Embed this CSS in <head> at build/SSR time.
215182
const css = generate(config, true, { splitText: splitTextStyle });
216183
// → `[data-interact-key="hero"] .title:not([data-splittext-ready]) { visibility: hidden; }`
217184
```
218185

219-
On first paint the container is hidden; once the runtime plugin splits it and sets `data-splittext-ready`, the hide rule stops matching and the (individually-hidden) spans take over their entrance animation — no flash of un-split text.
186+
On first paint the container is hidden; once the runtime plugin splits it and sets `data-splittext-ready`, the hide rule stops matching and the (individually-hidden) spans take over their entrance animation — no flash of un-split text. Without `hideUntilReady`, `splitTextStyle` emits nothing.
220187

221188
> Plugin styles are emitted verbatim and unconditionally. If a rule should be scoped to a media query or condition, have the generator build that itself (it receives the interaction/effect `config`).
222189

packages/interact/rules/full-lean.md

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -708,58 +708,46 @@ The target element is what the effect animates. Resolved in priority order:
708708
709709
## Plugins
710710
711-
Interact can route config to external plugins registered with `Interact.use(name, plugin)`. Interact is only a bridge — it matches a `$<name>` config field to a registered plugin name and passes the value in; it never inspects plugin behavior. Neither Interact nor the plugin package depend on each other — the adapter lives in your app.
711+
Interact can route config to external plugins registered with `Interact.use(name, plugin)`. Interact is only a bridge — it matches a `$<name>` config field to a registered plugin name and passes the value in; it never inspects plugin behavior. Neither Interact nor the plugin package depend on each other. A plugin package MAY ship its own adapter typed structurally against the contract (e.g. `@wix/splittext/plugin`) — use it instead of hand-rolling one; your app supplies only the type glue.
712712
713713
- Register before `create()`: `Interact.use('splitText', splitTextPlugin)`.
714714
- Reference with a `$<name>` field on an **interaction** or **effect**: `$splitText: { container: '.title', type: 'chars' }`.
715715
- Plugins run at connect time, **before** target resolution — so DOM they create (e.g. `.split-c` spans) is visible to `selector` queries that follow.
716716
- A plugin may return a cleanup function; Interact runs it on disconnect/teardown.
717-
- A `$<name>` field with no registered plugin throws at connect time.
717+
- A `$<name>` field with no registered plugin is ignored.
718718
- Plugin fields MUST be `$`-prefixed — a non-prefixed unknown key on an interaction/effect is rejected by `@wix/interact-validate`.
719-
- **SSR styling:** for FOUC prevention (e.g. hiding un-split text before an entrance animation), pass a **separate** per-plugin callback as `generate()`'s third arg: `generate(config, true, { splitText: (value, context) => { declarations, selectorSuffix }[] })`. `generate()` does not inspect the value; the callback returns CSS rule(s) data and scopes them under the base selector (`[data-interact-key="<key>"]`). It is NOT the `use()` callback.
719+
- **SSR styling:** for FOUC prevention (e.g. hiding un-split text before an entrance animation), pass a **separate** per-plugin callback as `generate()`'s third arg: `generate(config, true, { myPlugin: (value, context) => { declarations, selectorSuffix }[] })`. `generate()` does not inspect the value; the callback returns CSS rule(s) data and scopes them under the base selector (`[data-interact-key="<key>"]`). It is NOT the `use()` callback. If the plugin package ships a generator (e.g. `splitTextStyle` from `@wix/splittext/plugin`), pass that instead of writing one.
720720
721-
**Example — split text, then stagger the generated char spans:**
721+
**Example — split text, then stagger the generated char spans.** Use the adapter shipped from `@wix/splittext/plugin` (`splitTextPlugin` + its SSR counterpart `splitTextStyle`); do NOT hand-roll it:
722722
723723
```js
724724
import { Interact, generate } from '@wix/interact';
725-
import { splitText } from '@wix/splittext';
725+
import { splitTextPlugin, splitTextStyle } from '@wix/splittext/plugin';
726726
727727
const config = {
728728
effects: { 'char-fade-up': { namedEffect: { type: 'FadeIn' }, duration: 400 } },
729729
interactions: [
730730
{
731731
key: 'hero',
732732
trigger: 'viewEnter',
733-
$splitText: { container: '.title', type: 'chars' },
733+
// `hideUntilReady` opts into the SSR hide rule emitted by splitTextStyle
734+
$splitText: { container: '.title', type: 'chars', hideUntilReady: true },
734735
sequences: [{ offset: 30, effects: [{ effectId: 'char-fade-up', selector: '.split-c' }] }],
735736
},
736737
],
737738
};
738739
739-
const css = Interact.generate(config, /* useFirstChild */ true, {
740-
splitText: (value, _) => {
741-
return [
742-
{
743-
declarations: [{ name: 'visibility', value: 'hidden' }],
744-
selectorSuffix: ` ${value.container ?? ''}:not([data-splittext-ready])`,
745-
},
746-
];
747-
},
748-
});
740+
const css = generate(config, /* useFirstChild */ true, { splitText: splitTextStyle });
749741
// Embed css in HTML — see CSS Generation & FOUC Prevention
750742
751-
// The only glue that imports both packages:
752-
Interact.use('splitText', (value, { root }) => {
753-
const { container, ...options } = value;
754-
const el = root.querySelector(container);
755-
if (!el) return;
756-
const result = splitText(el, options);
757-
return () => result.revert();
758-
});
743+
Interact.use('splitText', splitTextPlugin);
759744
760745
Interact.create(config);
761746
```
762747
748+
- `$splitText` takes `{ container, hideUntilReady?, ...SplitTextOptions }`; `container` is resolved within the element and every match is split.
749+
- Type the field in your app: `declare module '@wix/interact' { interface InteractPluginConfigMap { splitText: SplitTextPluginConfig } }` (type from `@wix/splittext/plugin`).
750+
763751
Default split wrapper classes: `.split-c` (chars), `.split-w` (words), `.split-l` (lines), `.split-s` (sentences).
764752
765753
## Static API

packages/interact/rules/plugins.md

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ Rules for extending `@wix/interact` with external plugins via `Interact.use()` a
77
Interact is a **generic bridge**. It knows a plugin only by the name it was registered under. When an interaction or effect carries a field named `$<name>`, Interact passes that field's value to the matching plugin and (optionally) stores a cleanup. Interact never inspects what the plugin does.
88

99
- `@wix/interact` has **no** plugin-specific code and does **not** depend on any plugin package.
10-
- A plugin package (e.g. `@wix/splittext`) has **no** Interact-specific code and does **not** depend on `@wix/interact`.
11-
- The adapter that maps an Interact plugin call to the plugin's own API lives in **your app** — the only place that imports both.
10+
- A plugin package (e.g. `@wix/splittext`) does **not** depend on `@wix/interact`.
11+
- A plugin package MAY still ship a ready-made adapter (e.g. `@wix/splittext/plugin`), typed _structurally_ against the contract below so it stays assignable to `InteractPlugin` without importing Interact. Prefer the shipped adapter over hand-rolling one.
12+
- Only the **typing** glue lives in your app — the declaration merge on `InteractPluginConfigMap` (see [Config placement](#config-placement)).
1213

1314
## Registration
1415

@@ -22,7 +23,7 @@ Interact.use('<plugin-name>', (value, context) => {
2223
});
2324
```
2425

25-
- `Interact.getPlugin(name)` / `Interact.hasPlugins()` inspect the registry.
26+
- `Interact.getPlugin(name) / Interact.getPluginsNames()` inspect the registry.
2627

2728
## Config placement
2829

@@ -39,7 +40,7 @@ Add a `$<plugin-name>` field on an **interaction** or an **effect**:
3940

4041
## Rules
4142

42-
- **MUST** register the plugin (`Interact.use`) before `Interact.create()`. A `$<name>` field with no registered plugin throws at connect time.
43+
- **MUST** register the plugin (`Interact.use`) before `Interact.create()`. A `$<name>` field with no registered plugin is ignored.
4344
- **MUST** prefix plugin fields with `$` (e.g. `$splitText`). A non-prefixed unknown key on an interaction/effect is rejected by `@wix/interact-validate` (via `catchall` + key check). Only `$`-prefixed fields are treated as opaque, un-inspected plugin config.
4445
- Use a bare, unquoted `$<name>` key — no quotes needed since `$` is a valid identifier start (e.g. `$splitText:`, not `'plugin:splitText':`).
4546
- Plugins run at **connect time, before target resolution** — DOM a plugin creates is visible to the `selector` / `listContainer` queries that follow.
@@ -53,20 +54,21 @@ Runtime plugins mutate the DOM only after JS loads. To style the element _before
5354

5455
```js
5556
const css = generate(config, /* useFirstChild */ true, {
56-
splitText: (value, _context) => {
57-
// value: the opaque `$splitText` value;
57+
myPlugin: (value, _context) => {
58+
// value: the opaque `$myPlugin` value;
5859
// context: { key, scope: 'interaction' | 'effect', config }
5960
// return: { declarations: { name: string; value: number | string }[]; selectorSuffix?: string }[]
6061
return [
6162
{
6263
declarations: [{ name: 'visibility', value: 'hidden' }],
63-
selectorSuffix: ` ${value.container ?? ''}:not([data-splittext-ready])`,
64+
selectorSuffix: ` ${value.container ?? ''}:not([data-myplugin-ready])`,
6465
},
6566
];
6667
},
6768
});
6869
```
6970
71+
- If the plugin package ships its own generator, pass that instead of writing one — e.g. `splitTextStyle` from `@wix/splittext/plugin` (see the example below).
7072
- This is **NOT** the callback registered via `Interact.use()` — it's a build-time styling generator.
7173
- `generate()` does **not** inspect the `$<name>` value (same as `create()`); it just routes it to the generator, which returns partial CSS rule(s) data.
7274
- `declarations` is an array of names and values of CSS properties to set; `selectorSuffix` is used to refine the target of the CSS rule - the resulting selector for the rule is `[data-interact-key=${key}]${selectorSuffix}`
@@ -77,41 +79,41 @@ const css = generate(config, /* useFirstChild */ true, {
7779
7880
Split text into `<span>`s, then target the generated spans with a normal `selector`. Default classes: `.split-c` (chars), `.split-w` (words), `.split-l` (lines), `.split-s` (sentences).
7981
82+
**Do NOT hand-roll this adapter.** `@wix/splittext/plugin` ships both callbacks — `splitTextPlugin` (runtime) and `splitTextStyle` (SSR) — already paired on the `data-splittext-ready` marker:
83+
8084
```js
81-
import { Interact } from '@wix/interact';
82-
import { splitText } from '@wix/splittext';
85+
import { Interact, generate } from '@wix/interact';
86+
import { splitTextPlugin, splitTextStyle } from '@wix/splittext/plugin';
8387
8488
const config = {
8589
effects: { 'char-fade-up': { namedEffect: { type: 'FadeIn' }, duration: 400 } },
8690
interactions: [
8791
{
8892
key: 'hero',
8993
trigger: 'viewEnter',
90-
$splitText: { container: '.title', type: 'chars' },
94+
// `hideUntilReady` opts into the SSR hide rule emitted by splitTextStyle
95+
$splitText: { container: '.title', type: 'chars', hideUntilReady: true },
9196
sequences: [{ offset: 30, effects: [{ effectId: 'char-fade-up', selector: '.split-c' }] }],
9297
},
9398
],
9499
};
95100
96-
Interact.use('splitText', (value, { root }) => {
97-
const { container, ...options } = value;
98-
const el = root.querySelector(container);
99-
if (!el) return;
100-
const result = splitText(el, options);
101-
return () => result.revert();
102-
});
101+
Interact.use('splitText', splitTextPlugin);
103102
104-
const css = Interact.generate(config, /* useFirstChild */ true, {
105-
splitText: (value, _) => {
106-
return [
107-
{
108-
declarations: [{ name: 'visibility', value: 'hidden' }],
109-
selectorSuffix: ` ${value.container ?? ''}:not([data-splittext-ready])`,
110-
},
111-
];
112-
},
113-
});
103+
const css = generate(config, /* useFirstChild */ true, { splitText: splitTextStyle });
114104
// Embed css in HTML — see CSS Generation & FOUC Prevention
115105
116106
Interact.create(config);
117107
```
108+
109+
To type the `$splitText` field, declaration-merge in your app (the only place importing both packages):
110+
111+
```ts
112+
import type { SplitTextPluginConfig } from '@wix/splittext/plugin';
113+
114+
declare module '@wix/interact' {
115+
interface InteractPluginConfigMap {
116+
splitText: SplitTextPluginConfig;
117+
}
118+
}
119+
```

0 commit comments

Comments
 (0)