Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions .changeset/animation-groups.md

This file was deleted.

4 changes: 0 additions & 4 deletions .changeset/dedupe-effect-version.md

This file was deleted.

13 changes: 0 additions & 13 deletions .changeset/deprecate-animation-helpers.md

This file was deleted.

37 changes: 0 additions & 37 deletions .changeset/each-intro-flag.md

This file was deleted.

9 changes: 0 additions & 9 deletions .changeset/fix-animation-serialization.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/typecheck-tests.md

This file was deleted.

12 changes: 12 additions & 0 deletions apps/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# docs

## 0.0.14

### Patch Changes

- Updated dependencies [b650fd8]
- Updated dependencies [12654be]
- Updated dependencies [ee8a4d1]
- Updated dependencies [0dd6440]
- Updated dependencies [3c5da0c]
- @effex/dom@1.2.0
- @effex/router@1.2.2

## 0.0.13

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docs",
"version": "0.0.13",
"version": "0.0.14",
"private": true,
"type": "module",
"scripts": {
Expand Down
107 changes: 107 additions & 0 deletions packages/dom/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,112 @@
# @effex/dom

## 1.2.0

### Minor Changes

- b650fd8: Add animation groups — declarative sequencing across multiple animated blocks. Solves the "chained word-by-word intro" case where each word is its own `each` and word N should only start after word N-1 finishes.

```ts
import { $, Animation, collect, each, stagger } from "@effex/dom";

const App = () =>
Effect.gen(function* () {
const [greeting, name, tagline] = yield* Animation.sequence(3);
return $.div(
{},
collect(
each(greetingLetters, {
key: (l) => l.id,
render: (l) => $.span({}, $.of(l.char)),
animate: {
enter: "letter-in",
stagger: stagger(40),
group: greeting,
},
}),
each(nameLetters, {
key: (l) => l.id,
render: (l) => $.span({}, $.of(l.char)),
animate: { enter: "letter-in", stagger: stagger(40), group: name },
}),
each(taglineLetters, {
key: (l) => l.id,
render: (l) => $.span({}, $.of(l.char)),
animate: {
enter: "letter-in",
stagger: stagger(40),
group: tagline,
},
}),
),
);
});
```

New API:
- `Animation.group()` — creates a group with a gate (unresolved) and a completion signal.
- `Animation.sequence(count)` — returns `count` groups wired end-to-end: group 0's gate is open immediately, group N's gate opens when group N-1's registered animations all complete.
- `Animation.parallel(count)` — returns `count` groups with all gates open (useful nested inside `sequence` for concurrent segments in a follow-up release).
- `animate.group: AnimationGroup` — new option on any animated control (`each`, `when`, `match`, ...). The animation registers with the group synchronously, awaits the gate before starting, and signals completion when finished.

Groups finalize when their pending count returns to zero for the first time after having been non-zero. Late registrations that arrive after finalization run immediately (gate is already open) — intended semantics for one-shot intros where post-sequence additions behave like ordinary animations.

- ee8a4d1: Add an `intro?: boolean` flag on `each` — and symmetrically on `when`, `match`, `matchOption`, `matchEither`, and `redraw` — to opt into re-animating SSR/SSG-rendered content during hydration.

Default behaviour stays as-is: hydration attaches handlers to pre-existing DOM without re-running enter animations — the right choice for content lists (feeds, sidebars, todos) that shouldn't jitter into view on every page load. Setting `intro: true` flips that for decorative sequences where the animation _is_ the point:

```ts
each(letters, {
key: (l) => l.id,
render: (l) => $.span({}, $.of(Readable.map(l, (v) => v.char))),
animate: {
enterFrom: "opacity-0 translate-y-4",
enter: "opacity-100 translate-y-0 transition duration-300",
stagger: stagger(40),
},
intro: true,
});
```

The same flag makes sense on single-slot controls too — a hero fade-in for a `when`-gated banner, or an animated card for a `match`-selected state:

```ts
when(isReady, {
onTrue: () => Hero(),
onFalse: () => Placeholder(),
animate: {
enterFrom: "opacity-0",
enter: "opacity-100 transition duration-500",
},
intro: true,
});
```

On the client, `intro` is a no-op — animations already fire normally when there's no pre-existing DOM. It only affects the hydration path.

**FOUC caveat.** Between the SSR paint and hydration applying the `enterFrom` state, there's a brief visual flash of the final state. To eliminate it, hide the container in CSS until hydration completes (e.g. `visibility: hidden` on a class you toggle from your client entry). A first-class FOUC-prevention mechanism is planned for a follow-up.

Respects `prefers-reduced-motion` via `runEnterAnimation`.

### Patch Changes

- 12654be: Deprecate five under-used animation helpers with `@deprecated` JSDoc tags. All continue to work — they'll be removed in a future major.
- `staggerEased` — compose your own: `(index, total) => easingFn(index / (total - 1)) * totalDurationMs`
- `delay` — use `Effect.delay(effect, ms)` directly
- `sequence` — use `Effect.all([...], { concurrency: 1 })` directly
- `parallel` — use `Effect.all([...], { concurrency: "unbounded" })` directly
- `calculateStaggerDelay` — was only ever an internal helper; not re-exported by anything downstream

These wrapped effect combinators without adding real value beyond a slightly shorter name; the framework should be a thin layer over Effect rather than shadow its stdlib.

- 0dd6440: Fix inline animation serialization in `addSlot` / `removeSlot`. Previously each enter/exit animation was awaited via `yield*`, which serialized the `reconcile` sync loop — for a list of `[a, b, c]` added at once, item `b`'s animation would only start after item `a`'s finished. Made `stagger` configs effectively double-counted (each addSlot's stagger delay applied _after_ the previous animation completed rather than concurrently).

Now enter animations are forked into the slot's scope so multiple slots animate concurrently, and exit animations run in a fiber attached to the parent scope so `removeSlot` returns immediately (letting the reconcile loop continue) while the exit still plays before the DOM node is removed. If a slot is removed mid-enter, closing the slot scope interrupts the enter animation before exit starts. This applies to `ClientControlCtx` and both factories in `HydrationControlCtx`.

`@effex/router` will receive an automatic patch via `updateInternalDependencies` since it depends on `@effex/dom` as a workspace dependency.

- 3c5da0c: Fix the `renderToString` return-type inference so provided dependencies (`RendererContext`, `ControlCtx`, `SuspenseBoundaryCtx`, `Scope`) are properly subtracted from the output `R`. Previously the signature used `Deps | R`, which TypeScript can't do set-subtraction from, so those tags leaked into the returned Effect's requirements — callers whose element required `Scope` (from `Signal.make`) or the other provided tags saw them appear in `Effect.runPromise` arguments even though `renderToString` itself provides them internally. Switched to `Exclude<R, Deps>`; the runtime behaviour is unchanged.

## 1.1.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@effex/dom",
"version": "1.1.1",
"version": "1.2.0",
"description": "DOM rendering for Effex - a reactive UI framework built on Effect.ts",
"type": "module",
"license": "MIT",
Expand Down
11 changes: 11 additions & 0 deletions packages/router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# @effex/router

## 1.2.2

### Patch Changes

- Updated dependencies [b650fd8]
- Updated dependencies [12654be]
- Updated dependencies [ee8a4d1]
- Updated dependencies [0dd6440]
- Updated dependencies [3c5da0c]
- @effex/dom@1.2.0

## 1.2.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/router/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@effex/router",
"version": "1.2.1",
"version": "1.2.2",
"description": "Router for Effex applications",
"type": "module",
"license": "MIT",
Expand Down
Loading