solid-drift 0.7.1 → 0.9.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.
- package/README.md +236 -1
- package/dist/ai.d.ts +207 -0
- package/dist/ai.js +570 -0
- package/dist/gesture.d.ts +108 -0
- package/dist/gesture.js +207 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +4 -1
- package/dist/motion.d.ts +78 -1
- package/dist/motion.js +81 -49
- package/dist/text.d.ts +27 -0
- package/dist/text.js +84 -0
- package/dist/web3.d.ts +213 -0
- package/dist/web3.js +640 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -345,6 +345,43 @@ const { rotateX, rotateY } = createTilt(() => card, { maxAngle: 12 })
|
|
|
345
345
|
|
|
346
346
|
Returns `{ rotateX, rotateY }`: spring-smoothed tilt in degrees. SSR-safe and reduced-motion safe: both return constant `0` accessors.
|
|
347
347
|
|
|
348
|
+
### `createDrag(ref, options?)`
|
|
349
|
+
|
|
350
|
+
Pointer drag with spring physics, constraints, and momentum. The gesture workhorse: draggable cards, sliders, bottom-sheet handles, sortable rows. While the pointer is down the element tracks it 1:1; on release it glides with inertia and springs into its constraints, stretching elastically past the edges while dragged. Set `touch-action: none` on the draggable element so touch drags do not fight the page scroll. For the physics-toy flavor (exponential friction plus bouncing off walls), see `createFling` instead.
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import { createDrag } from "solid-drift"
|
|
354
|
+
|
|
355
|
+
let card!: HTMLDivElement
|
|
356
|
+
const { x, y, status } = createDrag(() => card, {
|
|
357
|
+
constraints: { left: 0, right: 300, top: 0, bottom: 0 },
|
|
358
|
+
elastic: 0.4,
|
|
359
|
+
})
|
|
360
|
+
<div
|
|
361
|
+
ref={card}
|
|
362
|
+
style={{
|
|
363
|
+
transform: `translate(${x()}px, ${y()}px)`,
|
|
364
|
+
"touch-action": "none",
|
|
365
|
+
cursor: status() === "dragging" ? "grabbing" : "grab",
|
|
366
|
+
}}
|
|
367
|
+
>
|
|
368
|
+
Drag me
|
|
369
|
+
</div>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
| Option | Default | Description |
|
|
373
|
+
| ------------- | -------------------------------- | ------------------------------------------------------------------ |
|
|
374
|
+
| `axis` | `"both"` | `"x"`, `"y"`, or `"both"` |
|
|
375
|
+
| `constraints` | none | Bounds in px relative to the drag origin; release settles inside |
|
|
376
|
+
| `elastic` | `0.35` | Overshoot past constraints while dragging, 0 (hard stop) to 1 |
|
|
377
|
+
| `momentum` | `true` | Glide with inertia after release |
|
|
378
|
+
| `inertia` | `0.2` | Seconds of release velocity projected into the settle target |
|
|
379
|
+
| `spring` | `{ stiffness: 300, damping: 32 }` | Spring physics for the settle after release |
|
|
380
|
+
| `onDragStart` | none | Called when the pointer grabs the element |
|
|
381
|
+
| `onDragEnd` | none | Called on release with `{ x, y, velocityX, velocityY }` (px/s) |
|
|
382
|
+
|
|
383
|
+
Returns `{ x, y, status }`: the drag offset in pixels and `status` (`"idle"`, `"dragging"`, `"settling"`). SSR-safe: everything rests at 0. Under reduced motion the drag still tracks the pointer (direct manipulation is not animation) but release snaps instantly to the constrained target with no glide.
|
|
384
|
+
|
|
348
385
|
### `createTrail(source, options?)`
|
|
349
386
|
|
|
350
387
|
A signal that replays another signal's past: it returns the value the source had `delay` milliseconds ago, interpolated between samples. Chain trails off one source for follower effects (a cursor with a comet tail, cascading highlights), or trail a scroll progress for a delayed echo of the page. The trail catches up and parks exactly on the latest value when the source rests. The follow loop runs only while the trail is behind.
|
|
@@ -723,6 +760,8 @@ onMount(() => kinetic.play());
|
|
|
723
760
|
|
|
724
761
|
Returns `{ play, stop, replay, status }`. `play()` resolves when the last unit arrives. Under reduced motion every unit jumps to its final state, so the text is fully readable.
|
|
725
762
|
|
|
763
|
+
`from` also accepts `variance` (0 to 1, default 0) and `seed`. Each unit jitters `y`, `blur`, `scale`, and `rotate` around the `from` values by up to `variance`, so a headline feels hand-set instead of mechanical. The jitter uses a seeded PRNG, so the same `seed` renders the exact same layout on every run (stable across SSR and replays). `variance: 0` keeps the classic uniform behavior.
|
|
764
|
+
|
|
726
765
|
### `createScenePlayer(scenes)`
|
|
727
766
|
|
|
728
767
|
Scene orchestrator for showreels and launch films: an ordered list of scenes, each with a `duration` and `onEnter`/`onExit` hooks. `scene()` tells your view which scene is live; the hooks trigger each scene's choreography (a `createKineticType`, a camera move, a color shift).
|
|
@@ -803,7 +842,203 @@ const off = beat.onBeat((b) => {
|
|
|
803
842
|
beat.start();
|
|
804
843
|
```
|
|
805
844
|
|
|
806
|
-
Returns `{ beat, bar, phase, onBeat, start, stop, status }`. `onBeat` returns an unsubscribe function. Beats are timing, not motion, so the clock keeps ticking under reduced motion (your callbacks decide what that means visually).
|
|
845
|
+
Returns `{ beat, bar, beatsPerBar, phase, onBeat, start, stop, status }`. `onBeat` returns an unsubscribe function. Beats are timing, not motion, so the clock keeps ticking under reduced motion (your callbacks decide what that means visually).
|
|
846
|
+
|
|
847
|
+
### `createShowreel(scenes)`
|
|
848
|
+
|
|
849
|
+
A guided showreel recipe on top of `createScenePlayer`: scenes carry a `kind` label (`"title"`, `"camera"`, `"color"`, `"cut"`, `"custom"`) so the reel reads like a shot list. It returns the full scene player controls, so `play`, `pause`, `next`, `prev`, and `goTo` all work unchanged.
|
|
850
|
+
|
|
851
|
+
```ts
|
|
852
|
+
const reel = createShowreel([
|
|
853
|
+
{ kind: "title", duration: 1200, onEnter: () => titleCard.play() },
|
|
854
|
+
{ kind: "camera", duration: 2000, onEnter: () => dolly.play() },
|
|
855
|
+
{ kind: "color", duration: 1500, onEnter: () => finale.play() },
|
|
856
|
+
{ kind: "cut", duration: 400, onEnter: () => wipe.play() },
|
|
857
|
+
]);
|
|
858
|
+
await reel.play(); // resolves after the last scene
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
Showreel recipe: combine `createKineticType` (title cards), `createCamera` (dolly moves), `createColorShift` (finale grade), `createTransition` (match cuts), `createBeat` (rhythm), and `createShowreel` (the shot list). Under reduced motion `play()` jumps straight to the last scene.
|
|
862
|
+
|
|
863
|
+
### `createBeatCuts(beat, player, options?)`
|
|
864
|
+
|
|
865
|
+
Beat-synced scene cuts: advances the player every N beats through the beat clock's `onBeat`. The default interval is the beat clock's `beatsPerBar`, so a cut lands on every downbeat. Returns a cleanup function that unsubscribes the cut listener.
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
const beat = createBeat({ bpm: 128, beatsPerBar: 4 });
|
|
869
|
+
const stopCuts = createBeatCuts(beat, player); // cut every bar
|
|
870
|
+
// const stopCuts = createBeatCuts(beat, player, { every: 8 }); // every 2 bars
|
|
871
|
+
beat.start();
|
|
872
|
+
await player.play();
|
|
873
|
+
stopCuts();
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Cuts only fire while the player is running, so pausing the reel pauses the cuts too. Beat timing is not motion, so cuts keep firing under reduced motion (pair with a reduced-motion-safe `onEnter` if the cut itself animates).
|
|
877
|
+
|
|
878
|
+
### `createStreamReveal(ref, options?)`
|
|
879
|
+
|
|
880
|
+
Streaming text for chat and agent UIs: push characters as they arrive and each batch reveals with the kinetic treatment (rise, deblur, settle). Batches flush on a short cadence, or early when the queue grows past `maxBatch`, so fast streams never fall behind.
|
|
881
|
+
|
|
882
|
+
```tsx
|
|
883
|
+
let out!: HTMLDivElement;
|
|
884
|
+
const stream = createStreamReveal(() => out, { batchMs: 120, maxBatch: 24 });
|
|
885
|
+
const res = await fetch("/chat", { method: "POST", body: q });
|
|
886
|
+
const reader = res.body!.getReader();
|
|
887
|
+
const decoder = new TextDecoder();
|
|
888
|
+
for (;;) {
|
|
889
|
+
const { done, value } = await reader.read();
|
|
890
|
+
if (done) break;
|
|
891
|
+
stream.push(decoder.decode(value, { stream: true }));
|
|
892
|
+
}
|
|
893
|
+
await stream.complete(); // flush the tail, then done
|
|
894
|
+
<div ref={out} aria-live="polite" />
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Returns `{ push, complete, reset, status, pending }`. `status()` is `"idle"`, `"streaming"`, or `"done"`; `pending()` counts queued characters. Under reduced motion pushed text appears immediately with no per-unit animation; on the server `push` is a no-op and `status()` is `"done"`.
|
|
898
|
+
|
|
899
|
+
### `createAgentState(options?)`
|
|
900
|
+
|
|
901
|
+
A tiny state machine for agent UIs: `idle`, `thinking`, `streaming`, `tool`, `done`, `error`, with legal-transition gating and enter/exit hooks. Pure signals, no DOM, so it works on the server and in tests.
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
const agent = createAgentState({
|
|
905
|
+
allowed: [
|
|
906
|
+
{ from: "idle", to: "thinking" },
|
|
907
|
+
{ from: "thinking", to: "streaming" },
|
|
908
|
+
{ from: "streaming", to: "tool-call" },
|
|
909
|
+
{ from: "tool-call", to: "streaming" },
|
|
910
|
+
{ from: "streaming", to: "done" },
|
|
911
|
+
],
|
|
912
|
+
onEnter: (s) => console.log("agent:", s),
|
|
913
|
+
});
|
|
914
|
+
agent.set("thinking");
|
|
915
|
+
agent.state(); // "thinking"
|
|
916
|
+
agent.prev(); // "idle"
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
Agent UI recipe: `thinking` pairs with `createWobble` on typing dots, `streaming` drives `createStreamReveal`, `tool-call` overlays a `createTransition`, and `done`/`error` tint a status pill with `createColorShift`. Returns `{ state, prev, set, reset, is }`. Illegal moves are ignored. State is logic, not motion, so it behaves identically under reduced motion.
|
|
920
|
+
|
|
921
|
+
### `parseDriftSpec(input)`
|
|
922
|
+
|
|
923
|
+
Validates a DriftSpec, the JSON motion spec a coding agent can generate: `{ version: 1, scenes: [{ primitive, target?, options?, duration? }] }`. Supported primitives: `kineticType`, `streamReveal`, `camera`, `colorShift`, `transition`, `beat`. Throws a `DriftSpecError` naming the exact path (`scenes[0].options.duration`) on the first problem.
|
|
924
|
+
|
|
925
|
+
```ts
|
|
926
|
+
import { parseDriftSpec } from "solid-drift";
|
|
927
|
+
|
|
928
|
+
const spec = parseDriftSpec({
|
|
929
|
+
version: 1,
|
|
930
|
+
scenes: [
|
|
931
|
+
{
|
|
932
|
+
primitive: "kineticType",
|
|
933
|
+
target: "title",
|
|
934
|
+
options: { duration: 600, stagger: 40, from: { y: 40, variance: 0.5, seed: 7 } },
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
primitive: "colorShift",
|
|
938
|
+
options: {
|
|
939
|
+
stops: [
|
|
940
|
+
{ at: 0, color: "#0a1220" },
|
|
941
|
+
{ at: 1, color: "#d9a441" },
|
|
942
|
+
],
|
|
943
|
+
duration: 1200,
|
|
944
|
+
},
|
|
945
|
+
duration: 1200, // step budget: move on even if the shift is still running
|
|
946
|
+
},
|
|
947
|
+
],
|
|
948
|
+
});
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
Prompt hint for generating specs: "Return ONLY a JSON DriftSpec: `{ version: 1, scenes: [...] }`. Each scene is `{ primitive, target?, options?, duration? }`. `primitive` is one of kineticType, streamReveal, camera, colorShift, transition, beat. `target` is a key into the refs map I provide. `options` match that primitive's options exactly. Keep scenes short; put a `duration` budget on any scene that should not block." Validation is pure and runs anywhere, including the server.
|
|
952
|
+
|
|
953
|
+
### `createSpecPlayer(spec, refs)`
|
|
954
|
+
|
|
955
|
+
Plays a validated DriftSpec: each scene runs its primitive against the matching ref from the `refs` map (`{ title: () => el }`), then the player advances. A scene `duration` acts as a budget, so a long ambient loop never stalls the reel.
|
|
956
|
+
|
|
957
|
+
```ts
|
|
958
|
+
const player = createSpecPlayer(spec, { title: () => titleEl });
|
|
959
|
+
await player.play(); // scenes in order, ends "done"
|
|
960
|
+
player.stop(); // halt mid-reel; the play() promise resolves
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
Returns `{ scene, status, play, stop }`. `scene()` is the live scene index (-1 before the first play). Under reduced motion `play()` applies every scene's final state instantly and ends `"done"`.
|
|
964
|
+
|
|
965
|
+
### `createTxLifecycle(options?)`
|
|
966
|
+
|
|
967
|
+
Transaction lifecycle for onchain UI, with zero wallet dependencies: feed it wagmi/viem-style state through an accessor (the adapter pattern) and it maps that to `idle`, `signing`, `pending`, `confirming`, `success`, `failed`. `progress()` springs between 0, 0.25, 0.5, 0.75, 1 so progress rings glide instead of jumping.
|
|
968
|
+
|
|
969
|
+
```ts
|
|
970
|
+
// Adapter: your wagmi/viem state in, tx state out.
|
|
971
|
+
const [chain] = createSignal({ status: "idle" as const, confirmations: 0 });
|
|
972
|
+
const tx = createTxLifecycle({
|
|
973
|
+
source: () => ({
|
|
974
|
+
status: chain().status, // "idle" | "pending" | "success" | "error"
|
|
975
|
+
confirmations: chain().confirmations,
|
|
976
|
+
}),
|
|
977
|
+
requiredConfirmations: 2,
|
|
978
|
+
onEnter: (s) => console.log("tx:", s),
|
|
979
|
+
});
|
|
980
|
+
tx.set("signing"); // manual: the moment the wallet prompt opens
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
Standard mapping: `set("signing")` when the wallet prompt opens, source `pending` (hash received) maps to `"pending"`, confirmations reaching the threshold promote to `"confirming"`, source `success`/`error` map to `"success"`/`"failed"`. Omit `source` for a fully manual lifecycle. Returns `{ state, set, reset, progress }`. On the server it stays `"idle"` with `progress()` 0; under reduced motion state changes apply instantly and `progress()` jumps to its target.
|
|
984
|
+
|
|
985
|
+
### `createTicker(source, ref, options?)`
|
|
986
|
+
|
|
987
|
+
A price ticker with rolling digits: each digit rolls vertically on change, the whole figure flashes green/red on up/down moves, and rapid source updates batch into one render per frame. `display()` always holds the formatted string, so SSR and tests read the price without DOM.
|
|
988
|
+
|
|
989
|
+
```tsx
|
|
990
|
+
const [price] = createSignal(64218.5);
|
|
991
|
+
const ticker = createTicker(price, () => priceEl, {
|
|
992
|
+
decimals: 2,
|
|
993
|
+
locale: "en-US",
|
|
994
|
+
upColor: "#16a34a",
|
|
995
|
+
downColor: "#dc2626",
|
|
996
|
+
flashMs: 600,
|
|
997
|
+
});
|
|
998
|
+
<div>
|
|
999
|
+
<span ref={priceEl} aria-label={`Price ${ticker.display()}`} />
|
|
1000
|
+
</div>
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
Returns `{ display, direction }`. `direction()` is `"up"`, `"down"`, or `"flat"`. Formatting uses `Intl.NumberFormat` with the given locale. Under reduced motion the text swaps instantly with no rolling digits and no color flash; on the server only `display()` and `direction()` work.
|
|
1004
|
+
|
|
1005
|
+
### `createMintReveal(ref, options?)`
|
|
1006
|
+
|
|
1007
|
+
An NFT mint reveal: an anticipation shake winds up, the card flips on rotateY, `onFlip` fires at the midpoint (edge-on, so the face swap is invisible), and squash-and-stretch sells the landing. `reset()` returns the card to idle.
|
|
1008
|
+
|
|
1009
|
+
```tsx
|
|
1010
|
+
let card!: HTMLDivElement;
|
|
1011
|
+
const reveal = createMintReveal(() => card, {
|
|
1012
|
+
shakeDuration: 500,
|
|
1013
|
+
flipDuration: 700,
|
|
1014
|
+
squash: true,
|
|
1015
|
+
onFlip: () => setFace("revealed"), // swap the artwork mid-flip
|
|
1016
|
+
});
|
|
1017
|
+
<button onClick={() => reveal.play()}>Reveal</button>
|
|
1018
|
+
<div ref={card} style={{ "transform-style": "preserve-3d" }} />
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
Returns `{ play, reset, status }`. `status()` walks `"idle"`, `"anticipating"`, `"flipping"`, `"revealed"`. Under reduced motion (and on the server) `play()` applies the revealed state immediately and still calls `onFlip`.
|
|
1022
|
+
|
|
1023
|
+
### `createConnectButton(ref, options?)`
|
|
1024
|
+
|
|
1025
|
+
Wallet connect button micro-interactions: magnetic pull toward the pointer, a press scale, an animated check overlay for copy-address feedback, and a chain pulse ring. Pointer handling is global (presses that start inside still count if released outside), and everything cleans up on unmount.
|
|
1026
|
+
|
|
1027
|
+
```tsx
|
|
1028
|
+
let btn!: HTMLButtonElement;
|
|
1029
|
+
const connect = createConnectButton(() => btn, { strength: 0.35 });
|
|
1030
|
+
<button
|
|
1031
|
+
ref={btn}
|
|
1032
|
+
onClick={() => {
|
|
1033
|
+
navigator.clipboard.writeText(address);
|
|
1034
|
+
connect.copyTick(); // check overlay pops, then fades
|
|
1035
|
+
}}
|
|
1036
|
+
>
|
|
1037
|
+
{address}
|
|
1038
|
+
</button>
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
Returns `{ copyTick, chainPulse, status }`. `status()` is `"idle"`, `"ticking"` (check visible), or `"pulsing"` (ring expanding). Call `chainPulse()` after a successful connection or network switch. Under reduced motion there is no magnetic pull or scale; `copyTick()` and `chainPulse()` still show their overlays statically.
|
|
807
1042
|
|
|
808
1043
|
### Easings
|
|
809
1044
|
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming-text and agent-state family: motion primitives for
|
|
3
|
+
* conversational interfaces.
|
|
4
|
+
*
|
|
5
|
+
* Token streams arrive in bursts, so `createStreamReveal` batches them
|
|
6
|
+
* into a readable cadence before animating each unit's entrance.
|
|
7
|
+
* `createAgentState` is a pure-signal state machine for agent
|
|
8
|
+
* status, kept free of motion so each state can pair with any
|
|
9
|
+
* primitive. `parseDriftSpec` and `createSpecPlayer` let generated
|
|
10
|
+
* JSON choreography be validated and rendered deterministically.
|
|
11
|
+
*
|
|
12
|
+
* All primitives are signal-native, SSR-safe, dependency-free, and
|
|
13
|
+
* define sensible static behavior under `prefers-reduced-motion`.
|
|
14
|
+
*/
|
|
15
|
+
import { type Accessor } from "solid-js";
|
|
16
|
+
import { type Easing, type EasingName } from "./easing.js";
|
|
17
|
+
import { type KineticTypeFrom } from "./motion.js";
|
|
18
|
+
type MaybeElement = () => Element | null | undefined;
|
|
19
|
+
export type StreamRevealStatus = "idle" | "streaming" | "done";
|
|
20
|
+
export interface StreamRevealOptions {
|
|
21
|
+
/** Split into "chars" or "words". Default "chars". */
|
|
22
|
+
unit?: "chars" | "words";
|
|
23
|
+
/** Flush the batch queue on this cadence, in ms. Default 120. */
|
|
24
|
+
batchMs?: number;
|
|
25
|
+
/** Flush early when the queue grows past this many chars. Default 24. */
|
|
26
|
+
maxBatch?: number;
|
|
27
|
+
/** Entrance duration per unit, in ms. Default 450. */
|
|
28
|
+
duration?: number;
|
|
29
|
+
/** Milliseconds between unit starts. Default 30. */
|
|
30
|
+
stagger?: number;
|
|
31
|
+
/** Starting transform/opacity/blur for each unit. */
|
|
32
|
+
from?: KineticTypeFrom;
|
|
33
|
+
/** Easing for each unit's entrance. Default "easeOutExpo". */
|
|
34
|
+
easing?: Easing | EasingName;
|
|
35
|
+
}
|
|
36
|
+
export interface StreamRevealControls {
|
|
37
|
+
/** Append raw stream tokens; they are batched internally. */
|
|
38
|
+
push: (chunk: string) => void;
|
|
39
|
+
/** Flush remaining units and settle. */
|
|
40
|
+
complete: () => void;
|
|
41
|
+
/** Clear the text and return to idle. */
|
|
42
|
+
reset: () => void;
|
|
43
|
+
/** Reactive status: "idle" | "streaming" | "done". */
|
|
44
|
+
status: Accessor<StreamRevealStatus>;
|
|
45
|
+
/** Chars waiting in the batch queue. */
|
|
46
|
+
pending: Accessor<number>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Smooth irregular token cadence into readable animated text.
|
|
50
|
+
*
|
|
51
|
+
* Stream tokens arrive in bursts, a flood, then silence. `push()`
|
|
52
|
+
* only appends to an internal queue; one rAF task flushes the queue
|
|
53
|
+
* on a steady cadence (or early when it overflows) and drives every
|
|
54
|
+
* unit's entrance, so a 40-token burst reads as a calm typed line
|
|
55
|
+
* instead of a flicker.
|
|
56
|
+
*
|
|
57
|
+
* SSR-safe: `push` is a no-op on the server, `status()` is "done",
|
|
58
|
+
* and the host renders the full text statically. Under reduced motion
|
|
59
|
+
* batching and entrances are skipped: text appears as it is pushed.
|
|
60
|
+
*
|
|
61
|
+
* ```tsx
|
|
62
|
+
* let out!: HTMLDivElement
|
|
63
|
+
* const stream = createStreamReveal(() => out, { unit: "chars" })
|
|
64
|
+
* const reader = response.body.getReader()
|
|
65
|
+
* onMount(async () => {
|
|
66
|
+
* for (;;) {
|
|
67
|
+
* const { done, value } = await reader.read()
|
|
68
|
+
* if (done) break
|
|
69
|
+
* stream.push(decoder.decode(value))
|
|
70
|
+
* }
|
|
71
|
+
* stream.complete()
|
|
72
|
+
* })
|
|
73
|
+
* <div ref={out} aria-live="polite" />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function createStreamReveal(ref: MaybeElement, options?: StreamRevealOptions): StreamRevealControls;
|
|
77
|
+
/** Lifecycle states of a conversational agent turn. */
|
|
78
|
+
export type AgentState = "idle" | "thinking" | "streaming" | "tool-call" | "done" | "error";
|
|
79
|
+
/** One legal state change. */
|
|
80
|
+
export interface AgentStateTransition {
|
|
81
|
+
from: AgentState;
|
|
82
|
+
to: AgentState;
|
|
83
|
+
}
|
|
84
|
+
export interface AgentStateOptions {
|
|
85
|
+
/** Starting state. Default "idle". */
|
|
86
|
+
initial?: AgentState;
|
|
87
|
+
/** Legal transitions. Omit to allow every transition. */
|
|
88
|
+
allowed?: AgentStateTransition[];
|
|
89
|
+
/** Called after entering a state, with the previous state. */
|
|
90
|
+
onEnter?: (state: AgentState, prev: AgentState) => void;
|
|
91
|
+
/** Called before leaving a state, with the next state. */
|
|
92
|
+
onExit?: (state: AgentState, next: AgentState) => void;
|
|
93
|
+
}
|
|
94
|
+
export interface AgentStateControls {
|
|
95
|
+
/** Current state. */
|
|
96
|
+
state: Accessor<AgentState>;
|
|
97
|
+
/** Previous state. */
|
|
98
|
+
prev: Accessor<AgentState>;
|
|
99
|
+
/** Move to the next state. Illegal transitions are ignored. */
|
|
100
|
+
set: (next: AgentState) => void;
|
|
101
|
+
/** Return to the initial state. */
|
|
102
|
+
reset: () => void;
|
|
103
|
+
/** Convenience for class bindings: `agent.is("thinking")`. */
|
|
104
|
+
is: (s: AgentState) => boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A pure-signal state machine for agent UI.
|
|
108
|
+
*
|
|
109
|
+
* Motion is deliberately not built in: pair each state with a recipe
|
|
110
|
+
* instead, so the machine stays transparent, testable, and SSR-safe.
|
|
111
|
+
* A typical pairing is `createWobble` on typing dots while "thinking",
|
|
112
|
+
* `createStreamReveal` while "streaming", `createTransition` for the
|
|
113
|
+
* "tool-call" overlay, and `createColorShift` on the status pill for
|
|
114
|
+
* "done"/"error". Each of those degrades on its own under reduced
|
|
115
|
+
* motion.
|
|
116
|
+
*
|
|
117
|
+
* SSR-safe by construction: signals only, no DOM, no clock.
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* const agent = createAgentState()
|
|
121
|
+
* agent.set("thinking")
|
|
122
|
+
* agent.state() // "thinking"
|
|
123
|
+
* agent.is("streaming") // false
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare function createAgentState(options?: AgentStateOptions): AgentStateControls;
|
|
127
|
+
/** Primitives a drift spec can choreograph. */
|
|
128
|
+
export type DriftSpecPrimitive = "kineticType" | "camera" | "colorShift" | "transition" | "beat" | "streamReveal";
|
|
129
|
+
/** One choreographed step. */
|
|
130
|
+
export interface DriftSpecStep {
|
|
131
|
+
/** Which primitive renders this step. */
|
|
132
|
+
primitive: DriftSpecPrimitive;
|
|
133
|
+
/**
|
|
134
|
+
* Key into the refs record given to `createSpecPlayer`. Required
|
|
135
|
+
* for primitives that render into an element ("kineticType" and
|
|
136
|
+
* "streamReveal").
|
|
137
|
+
*/
|
|
138
|
+
target?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Primitive options. Validated against each primitive's minimal
|
|
141
|
+
* shape. "camera" reads `keyframes`, "colorShift" reads `stops`,
|
|
142
|
+
* "streamReveal" reads `text` here.
|
|
143
|
+
*/
|
|
144
|
+
options?: Record<string, unknown>;
|
|
145
|
+
/** Step budget in milliseconds, for timed players. */
|
|
146
|
+
duration?: number;
|
|
147
|
+
}
|
|
148
|
+
/** A deterministic motion choreography, usually generated as JSON. */
|
|
149
|
+
export interface DriftSpec {
|
|
150
|
+
version: 1;
|
|
151
|
+
scenes: DriftSpecStep[];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Thrown by `parseDriftSpec`. `path` pinpoints the invalid value
|
|
155
|
+
* (for example "scenes[2].options.duration") so a generator can
|
|
156
|
+
* repair the spec without guessing.
|
|
157
|
+
*/
|
|
158
|
+
export declare class DriftSpecError extends Error {
|
|
159
|
+
/** Dot/bracket path to the invalid value, "" for the root. */
|
|
160
|
+
path: string;
|
|
161
|
+
constructor(path: string, message: string);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Validate unknown input against the DriftSpec schema and return the
|
|
165
|
+
* typed spec. Throws `DriftSpecError` with a precise `path` on the
|
|
166
|
+
* first invalid value.
|
|
167
|
+
*
|
|
168
|
+
* Pure validation, no DOM: safe to run on the server.
|
|
169
|
+
*
|
|
170
|
+
* ```ts
|
|
171
|
+
* const spec = parseDriftSpec(JSON.parse(raw))
|
|
172
|
+
* const player = createSpecPlayer(spec, { title: () => titleEl })
|
|
173
|
+
* await player.play()
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function parseDriftSpec(input: unknown): DriftSpec;
|
|
177
|
+
export type SpecPlayerStatus = "idle" | "running" | "done";
|
|
178
|
+
export interface SpecPlayerControls {
|
|
179
|
+
/** Play every scene in order. Resolves after the last scene. */
|
|
180
|
+
play: () => Promise<void>;
|
|
181
|
+
/** Stop mid-spec. The play() promise resolves. */
|
|
182
|
+
stop: () => void;
|
|
183
|
+
/** Reactive status: "idle" | "running" | "done". */
|
|
184
|
+
status: Accessor<SpecPlayerStatus>;
|
|
185
|
+
/** Current scene index, or -1 before the first play(). */
|
|
186
|
+
scene: Accessor<number>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Render a validated DriftSpec: each scene's primitive plays in
|
|
190
|
+
* order against the element refs the host supplies. `duration` on a
|
|
191
|
+
* step caps that step's budget.
|
|
192
|
+
*
|
|
193
|
+
* SSR-safe: `play()` is a no-op on the server. Under reduced motion
|
|
194
|
+
* `play()` jumps straight to the last scene (the clean final frame),
|
|
195
|
+
* matching `createScenePlayer`.
|
|
196
|
+
*
|
|
197
|
+
* ```ts
|
|
198
|
+
* const player = createSpecPlayer(spec, {
|
|
199
|
+
* title: () => titleEl,
|
|
200
|
+
* body: () => bodyEl,
|
|
201
|
+
* })
|
|
202
|
+
* player.scene() // 0, 1, ... as the spec plays
|
|
203
|
+
* await player.play()
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export declare function createSpecPlayer(spec: DriftSpec, refs: Record<string, MaybeElement>): SpecPlayerControls;
|
|
207
|
+
export {};
|