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
65 changes: 57 additions & 8 deletions packages/docs/.vitepress/reference/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -992,13 +992,7 @@ export const referenceCatalog = [
packages: [motionPackage],
status: 'stable',
symbols: [
motionSymbol('Motion', '/reference/items/Motion/js-api#motion'),
motionSymbol(
'MotionScrollTimeline',
'/reference/items/Motion/js-api#motionscrolltimeline',
),
motionSymbol('MotionSequence', '/reference/items/Motion/js-api#motionsequence'),
motionSymbol('MotionView', '/reference/items/Motion/js-api#motionview'),
motionSymbol('Motion', '/reference/items/Motion/js-api'),
motionSymbol(
'provideMotion',
'/reference/items/Motion/js-api#providing-the-motion-dependency',
Expand All @@ -1016,7 +1010,62 @@ export const referenceCatalog = [
),
],
capabilities: ['enter animations', 'spring physics', 'imperative playback'],
related: ['action', 'transition', 'scroll-reveal'],
related: [
'motion-scroll-timeline',
'motion-sequence',
'motion-view',
'action',
'transition',
'scroll-reveal',
],
},
{
id: 'motion-scroll-timeline',
title: 'MotionScrollTimeline',
summary: 'Drive the Motion children of an element with its scroll progress.',
kind: 'component',
path: '/reference/items/MotionScrollTimeline/',
family: 'animation',
primaryTask: 'animation',
tags: ['motion', 'animation', 'scroll', 'timeline'],
surfaces: ['js'],
packages: [motionPackage],
status: 'stable',
symbols: [motionSymbol('MotionScrollTimeline', '/reference/items/MotionScrollTimeline/js-api')],
capabilities: ['scroll-driven animations', 'hardware-accelerated scrubbing'],
related: ['motion', 'motion-sequence', 'scroll-animation'],
},
{
id: 'motion-sequence',
title: 'MotionSequence',
summary: 'Compose Motion children into a single staggered animation timeline.',
kind: 'component',
path: '/reference/items/MotionSequence/',
family: 'animation',
primaryTask: 'animation',
tags: ['motion', 'animation', 'sequence', 'stagger'],
surfaces: ['js'],
packages: [motionPackage],
status: 'stable',
symbols: [motionSymbol('MotionSequence', '/reference/items/MotionSequence/js-api')],
capabilities: ['staggered choreography', 'single-timeline playback'],
related: ['motion', 'motion-scroll-timeline', 'action'],
},
{
id: 'motion-view',
title: 'MotionView',
summary: 'Play DOM updates as view transitions declared with Motion keyframes.',
kind: 'component',
path: '/reference/items/MotionView/',
family: 'animation',
primaryTask: 'animation',
tags: ['motion', 'animation', 'view-transition', 'spring'],
surfaces: ['js'],
packages: [motionPackage],
status: 'stable',
symbols: [motionSymbol('MotionView', '/reference/items/MotionView/js-api')],
capabilities: ['animated DOM updates', 'spring view transitions', 'ambient wiring'],
related: ['motion', 'view-transition', 'dialog', 'data-bind'],
},
{
id: 'panel',
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/.vitepress/reference/public-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,23 +931,23 @@ export const publicContractSymbols = [
kind: 'type',
package: 'npm:@studiometa/ui-motion',
importPath: '@studiometa/ui-motion',
href: '/reference/items/Motion/js-api',
href: '/reference/items/MotionScrollTimeline/js-api',
status: 'stable',
},
{
name: 'MotionSequenceProps',
kind: 'type',
package: 'npm:@studiometa/ui-motion',
importPath: '@studiometa/ui-motion',
href: '/reference/items/Motion/js-api',
href: '/reference/items/MotionSequence/js-api',
status: 'stable',
},
{
name: 'MotionViewProps',
kind: 'type',
package: 'npm:@studiometa/ui-motion',
importPath: '@studiometa/ui-motion',
href: '/reference/items/Motion/js-api',
href: '/reference/items/MotionView/js-api',
status: 'stable',
},
] satisfies ReferenceSymbol[];
2 changes: 1 addition & 1 deletion packages/docs/reference/items/DataBind/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Use `data-bind:if` when the element must not exist in the DOM — a form control

### Wrapping the DOM change with the `dom-update` event

Before `data-bind:if` inserts or removes the template content, the component emits the bubbling `dom-update` protocol event — the shared announcement components use before an imminent DOM change. Its `detail` carries the new logical state as `isPresent` and a `wrap(runner)` function: a listener can call `wrap()` to substitute what runs the DOM change. The runner is either a function receiving an `apply()` callback that performs the actual insertion or removal, or a duck-typed transitioner exposing an `update(mutate)` method — like `MotionView` from `@studiometa/ui-motion` — whose `update()` receives the callback.
Before `data-bind:if` inserts or removes the template content, the component emits the bubbling `dom-update` protocol event — the shared announcement components use before an imminent DOM change. Its `detail` carries the new logical state as `isPresent` and a `wrap(runner)` function: a listener can call `wrap()` to substitute what runs the DOM change. The runner is either a function receiving an `apply()` callback that performs the actual insertion or removal, or a duck-typed transitioner exposing an `update(mutate)` method — like [`MotionView`](/reference/items/MotionView/) from `@studiometa/ui-motion` — whose `update()` receives the callback.

```ts
interface DomUpdateTransitioner {
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/reference/items/Dialog/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Both lifecycle events expose `event.detail.waitUntil(x)`, modeled on the Service
`waitUntil()` accepts either form:

- a **promise** (any thenable), awaited as is
- a **transitioner**: a duck-typed object with `enter()` and `leave()` methods, e.g. `MotionView` from `@studiometa/ui-motion` — the dialog follows its lifecycle by awaiting `enter()` on `open` and `leave()` on `close`
- a **transitioner**: a duck-typed object with `enter()` and `leave()` methods, e.g. [`MotionView`](/reference/items/MotionView/) from `@studiometa/ui-motion` — the dialog follows its lifecycle by awaiting `enter()` on `open` and `leave()` on `close`

For example, a [`Motion`](/reference/items/Motion/) box can spring in and out with the dialog through two [`Action`](/reference/items/Action/) attributes:

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/reference/items/Fetch/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,15 @@ Before applying the fetched content, `Fetch` dispatches the bubbling [`dom-updat
`wrap()` accepts a `DomUpdateRunner`, which is either form:

- a **function** with the signature `(apply: () => void) => void | Promise<unknown>`: it receives an `apply` function that injects the fetched content into the DOM, and its return value is awaited before the [`fetch-update-after` event](#fetch-update-after) is emitted
- a **transitioner**: any duck-typed object with an `update(mutate)` method (the `DomUpdateTransitioner` interface), e.g. `MotionView` from `@studiometa/ui-motion` — its `update()` method receives the apply function and its return value is awaited the same way
- a **transitioner**: any duck-typed object with an `update(mutate)` method (the `DomUpdateTransitioner` interface), e.g. [`MotionView`](/reference/items/MotionView/) from `@studiometa/ui-motion` — its `update()` method receives the apply function and its return value is awaited the same way

The protocol enforces three rules:

- **Synchronous registration only**: `wrap` must be called synchronously while the event dispatches — later calls warn and are ignored.
- **Last call wins**: a single runner is kept, the last `wrap` call during dispatch replaces any previous one.
- **The content is never lost**: if the runner throws or rejects, the error is logged with a warning and the content is applied directly when it has not been applied yet. The `fetch-update-after` event is always emitted.

With the upcoming ambient `MotionView` from `@studiometa/ui-motion`, the common case is pure nesting: a `MotionView` wrapping the updated content picks up the bubbling event by itself, with no attributes to write. When the transitioner lives elsewhere in the tree, an [Action](/reference/items/Action/) is the explicit escape hatch to route the event to it:
With the [ambient `MotionView`](/reference/items/MotionView/js-api#ambient-wiring) from `@studiometa/ui-motion`, the common case is pure nesting: a `MotionView` wrapping the updated content picks up the bubbling event by itself, with no attributes to write. When the transitioner lives elsewhere in the tree, an [Action](/reference/items/Action/) is the explicit escape hatch to route the event to it:

```html
<div data-component="Action" data-on:dom-update="MotionView(#list)->event.detail.wrap(target)">
Expand Down
63 changes: 0 additions & 63 deletions packages/docs/reference/items/Motion/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,66 +129,3 @@ A single element carries `Action`, `Motion` and [`TimerProgress`](/reference/ite
:::

</llm-only>

## Staggered sequence

[`MotionSequence`](./js-api#motionsequence) composes its `Motion` children into one timeline: the `stagger` option spreads the first three items, the finale positions itself with `data-option-at="+0.2"`, and one `Action` target plays or reverses the entire choreography — a single animation under the hood.

<llm-exclude>
<PreviewPlayground
:html="() => import('./stories/sequence/app.twig')"
:script="() => import('./stories/sequence/app.js?raw')"
/>
</llm-exclude>
<llm-only>

:::code-group

<<< ./stories/sequence/app.twig
<<< ./stories/sequence/app.js

:::

</llm-only>

## Animated state change with `MotionView`

[`MotionView`](./js-api#motionview) wraps DOM updates in Motion's `animateView()`, as a drop-in alternative to [`ViewTransition`](/reference/items/ViewTransition/): toggling swaps the `enterTo`/`leaveTo` classes inside a view transition, and the spring `transition` with the `layout` morph animates the card between its two states — no `::view-transition-*` CSS needed. Where view transitions are unavailable, the classes still swap, only without animation.

<llm-exclude>
<PreviewPlayground
:html="() => import('./stories/view/app.twig')"
:script="() => import('./stories/view/app.js?raw')"
/>
</llm-exclude>
<llm-only>

:::code-group

<<< ./stories/view/app.twig
<<< ./stories/view/app.js

:::

</llm-only>

## Scroll-driven timeline

[`MotionScrollTimeline`](./js-api#motionscrolltimeline) separates the timeline from the animations, like the `ScrollAnimation` family: the tall section defines the scroll range, and every `Motion` inside it is driven by that progress through Motion's `scroll()` — hardware-accelerated where the browser supports `ScrollTimeline`. Keyframe arrays give each child a multi-step track across the same range, and registering the timeline is enough: it mounts its `Motion` children itself.

<llm-exclude>
<PreviewPlayground
:html="() => import('./stories/with-scroll-timeline/app.twig')"
:script="() => import('./stories/with-scroll-timeline/app.js?raw')"
/>
</llm-exclude>
<llm-only>

:::code-group

<<< ./stories/with-scroll-timeline/app.twig
<<< ./stories/with-scroll-timeline/app.js

:::

</llm-only>
2 changes: 2 additions & 0 deletions packages/docs/reference/items/Motion/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The component is a thin, headless playback surface exposing imperative methods t

The `motion` peer dependency is resolved with a lazy `import()` the first time an animation is built, so it stays out of your main bundle until needed. See [providing the Motion dependency](./js-api#providing-the-motion-dependency) to inject a specific build such as `motion/mini`.

`Motion` is the playback primitive of `@studiometa/ui-motion`, which also ships [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) to drive a group of `Motion` children with the scroll, [`MotionSequence`](/reference/items/MotionSequence/) to compose them into one staggered timeline, and [`MotionView`](/reference/items/MotionView/) to play DOM updates as view transitions.

## Usage

Option values are parsed as JSON, so object keys must be quoted:
Expand Down
Loading
Loading