Documentation / @ripl/vue
@ripl/vue ​
Declarative Vue 3 components for Ripl. Describe a scene graph as a template, bind props to element state, and let
v-ifandv-fordrive the graph.
Features ​
- Every built-in element as a component —
<ripl-arc>,<ripl-circle>,<ripl-ellipse>,<ripl-image>,<ripl-line>,<ripl-path>,<ripl-polygon>,<ripl-polyline>,<ripl-rect>,<ripl-text>and<ripl-group>, each typed with its own state properties. - Three levels of engine, all optional — a context alone paints; add
<ripl-scene>for a hoisted graph and z-ordering; add<ripl-renderer>for an animation loop and transitions. <ripl-transition>— enter, update and leave phases with per-element staggering, following Vue's own enter-from / leave-to model.- Pointer events as Vue listeners —
@click,@mouseenter,@dragand the rest, subscribed only when you bind them so hit testing stays accurate. - Compositions for the imperative escape hatch —
useRiplContext,useRiplScene,useRiplRendereranduseRiplElement. - Strict TypeScript, tree-shakable, SSR-safe.
Installation ​
# npm
npm install @ripl/vue
# yarn
yarn add @ripl/vue
# pnpm
pnpm add @ripl/vuevue (3.5 or later) is a peer dependency you already have. @ripl/core, @ripl/web, @ripl/dom and @ripl/utilities arrive as dependencies of this package; you never install them yourself.
This package targets
@ripl/web, i.e. Canvas 2D. To render through another backend, build the context yourself and pass it in via thecontextprop on<ripl-context>.
Quick start ​
Register the components globally:
import {
createRipl,
} from '@ripl/vue';
import {
createApp,
} from 'vue';
import App from './app.vue';
createApp(App).use(createRipl()).mount('#app');Then describe a scene. Give <ripl-context> a size, since the canvas fills it:
<template>
<ripl-context style="width: 400px; height: 300px">
<ripl-scene>
<ripl-renderer>
<ripl-transition
:enter="{ duration: 400, state: { opacity: 0, radius: 0 } }"
:update="{ duration: 250 }"
:leave="{ duration: 200, state: { opacity: 0 } }"
>
<ripl-circle
v-for="item in items"
:key="item.id"
:cx="item.x"
:cy="item.y"
:radius="item.radius"
fill="#1e6978"
@click="select(item)"
/>
</ripl-transition>
</ripl-renderer>
</ripl-scene>
</ripl-context>
</template>Components can equally be imported one at a time, in which case the plugin is unnecessary.
The three tiers ​
Each level adds capability, and every element picks up the highest one above it:
| Template | What you get |
|---|---|
<ripl-context> | Elements paint directly. Pointer events and hit testing work. |
+ <ripl-scene> | A hoisted, flat instruction stream: z-ordering, group clipping, efficient large graphs. |
+ <ripl-renderer> | An animation loop, and <ripl-transition>. |
Transitions ​
enter is the state an element animates from; leave is the state it animates to; update is how a prop change animates. Each takes an options object or a factory called per element, which is what makes staggering work:
<template>
<ripl-transition
:enter="(element, index, length) => ({
duration: 400,
delay: (index / length) * 200,
state: { opacity: 0 },
})"
>
<ripl-rect v-for="bar in bars" :key="bar.id" v-bind="bar" />
</ripl-transition>
</template>An enter phase can reference a property the template never binds: the target is read off the element before the enter state is applied, so fading in from { opacity: 0 } recovers a target of 1 from the element's inherited or default state.
loop repeats a phase: true restarts it, 'alternate' plays it back and forth. A looping phase never completes, so its onComplete never fires and the renderer cannot idle while one runs; it is cancelled when its element leaves, and ignored on the leave phase, which has to finish in order to destroy the element.
Compositions ​
import {
useRiplContext,
useRiplElement,
useRiplRenderer,
useRiplScene,
} from '@ripl/vue';
const context = useRiplContext();
const scene = useRiplScene();
const renderer = useRiplRenderer();
const element = useRiplElement();Providers construct during setup(), so these already resolve in a descendant's own setup() with no watching required. They are undefined outside a provider, and during server rendering.
A template ref on any of the components resolves to the Ripl object it wraps, typed as that object:
<template>
<ripl-context ref="context">
<ripl-circle ref="circle" :cx="50" :cy="50" :radius="20" />
</ripl-context>
</template>Notes ​
- A prop you do not bind is never written, so Ripl's own defaults and a group's cascading state survive. Changing a bound prop back to
undefinedlikewise leaves the last value in place. - Props are compared by identity, so an inline
:data="{ ... }"or:line-dash="[4, 2]"re-applies on every parent render. Hoist those to acomputed.classis normalised first, so every binding form is stable.
Extending ​
@ripl/vue exports the pieces it is built from, so a sibling adapter can wrap a different kind of Ripl object without re-implementing the machinery. @ripl/vue-3d and @ripl/vue-charts are built this way.
| Export | Use |
|---|---|
defineRiplElement, elementFactory | Wrap anything that extends Element as a component. |
useElementProps | Construct an object from bound props and keep it in sync with them. |
useForwardedEvents | Forward a bus's own $events to Vue listeners, subscribing only to bound ones. |
useExposedInstance | Make a template ref resolve to the Ripl object rather than a Vue proxy. |
registerComponents | Register components on an app, skipping names already taken. |
RIPL_CONTEXT, RIPL_SCENE, RIPL_RENDERER, RIPL_PARENT, RIPL_ELEMENT, RIPL_TREE, RIPL_TRANSITION | The injection keys the components provide. |
readBoundProps, collectChangedProps, partitionProps, applyState, applyFields | The prop pipeline. |
Two contracts a sibling adapter depends on:
- This package owns the
@ripl/webimport, and with it the platform factory:requestAnimationFrame,devicePixelRatio,getDefaultStateandmeasureText. A sibling adapter inherits that through its dependency on@ripl/vueand should not add an@ripl/webimport of its own, because a bare side-effect import inside asideEffects: falsepackage can be tree-shaken away, whereas the value imports here cannot. - The injection keys are registry symbols (
Symbol.for), so two copies of this module, which the standalone IIFE builds produce, still resolve to the same key.
Plugins compose in any order: createRipl3D() and createRiplCharts() install the core components themselves, and registering a name twice is a no-op.
Documentation ​
Full documentation lives at ripl.run.
License ​
MIT
Classes ​
| Class | Description |
|---|---|
| RiplTransitionScope | The live transition phases in scope for a subtree of elements, resolved lazily so that reactive prop changes on the transition component take effect without re-registering its descendants. |
| RiplTree | Coordinates one context's declarative tree: which group each element belongs to, which paint tier is in effect, and when to repaint. |
Interfaces ​
| Interface | Description |
|---|---|
| RiplComponent | A declarative component wrapping a Ripl object. |
| RiplContextProps | Props accepted by RiplContext. |
| RiplDragPayload | The payload carried by a drag event: the current position plus the gesture's origin and delta. |
| RiplElementListeners | Every listener an element or group accepts. |
| RiplElementOptionProps | Construction options every element accepts, which become plain fields rather than animatable state. |
| RiplElementPropsOptions | How a component builds its element from props and writes later changes back onto it. |
| RiplNodeDefinition | Describes one element to wrap as a component. |
| RiplPointerListeners | Pointer and drag listeners, shared by elements, groups and the context. |
| RiplPointerPayload | The payload carried by a pointer event: the pointer position in logical (CSS) pixels. |
| RiplPropPartition | The two halves a changed prop batch splits into, and what each half needs applying. |
| RiplRendererProps | Props accepted by RiplRenderer. |
| RiplSceneProps | Props accepted by RiplScene. |
| RiplShapeProps | Painting options accepted by every path-backed shape. |
| RiplTickPayload | The payload carried by the renderer's tick event. |
| RiplTransitionPhaseOptions | Options for a single transition phase. Mirrors Ripl's RendererTransitionOptions, except that state is optional: the update phase derives its target from whichever props changed. |
| RiplTransitionPhases | The set of phases a transition component contributes to its subtree. |
| RiplTransitionProps | Props accepted by RiplTransition. |
| RiplUpdatedPayload | The payload carried by an updated event: the state property that changed and its new value. |
Type Aliases ​
| Type Alias | Description |
|---|---|
| RiplElementProps | The full prop surface of an element component: the element's own state, the shared construction options and paint flags, and its event listeners. |
| RiplElementState | Resolves an element's state interface, falling back to the shared base state. |
| RiplFieldWriters | Per-field write overrides, for fields an element exposes through a method rather than a setter. |
| RiplListener | A forwarded Ripl event listener. Receives the event's payload directly, with the underlying Event — carrying target, timestamp and stopPropagation — as a second argument. |
| RiplTransitionPhase | A transition phase: static options, or a factory invoked per element for staggering. |
| RiplTransitionPhaseName | The three phases a transition scope can describe. |
| RiplWritable | An untyped view of an element, used to write state through its accessors by key. |
Variables ​
| Variable | Description |
|---|---|
| ANY_PROP | A prop Ripl itself types, declared here only so Vue extracts it from attrs. Runtime validation would duplicate — and inevitably drift from — the element state interfaces. |
| BASE_STATE_KEYS | Every inheritable visual state property shared by all elements. |
| BOOLEAN_PROP | A boolean prop. default: undefined is load-bearing: without an explicit default Vue casts an absent boolean prop to false, which would override Ripl's own defaults rather than leave them alone. Declaring the default keeps valueless-attribute casting (<ripl-rect clip>) while letting an omitted prop stay omitted. |
| CONSTRUCTION_ONLY_KEYS | Options an element only reads when it is constructed, so they cannot be synced on a prop change. |
| CONTEXT_EVENTS | Every event the context component forwards to its Vue listeners. |
| ELEMENT_EVENTS | Every event an element or group forwards to its Vue listeners. |
| ELEMENT_OPTION_KEYS | Construction options that become plain fields on the element rather than animatable state. |
| ELEMENT_STATE_KEYS | The state properties specific to each built-in element, keyed by element type. |
| NUMBER_PROP | A numeric prop, left undefined when absent so it cannot override a Ripl default. |
| RENDERER_EVENTS | Every event the renderer component forwards to its Vue listeners. |
| RIPL_CONTEXT | Injection key for the rendering context the subtree draws to. |
| RIPL_ELEMENT | Injection key for the nearest enclosing element or group. |
| RIPL_PARENT | Injection key for the group new elements attach themselves to. |
| RIPL_RENDERER | Injection key for the renderer driving the subtree, if one was declared. |
| RIPL_SCENE | Injection key for the scene the subtree belongs to, if one was declared. |
| RIPL_TRANSITION | Injection key for the transition phases applied to descendant elements. |
| RIPL_TREE | Injection key for the RiplTree owned by the enclosing context component. |
| RiplArc | An arc or annular segment, the building block of pie, donut and gauge shapes. |
| RiplCircle | A circle rendered at a center point with a given radius. |
| RiplContext | Creates a Ripl rendering context and provides it to its subtree, mounting the canvas into its own root element. |
| RiplEllipse | An ellipse, optionally drawn as a partial sweep between two angles. |
| RiplGroup | Groups its children, cascading its own state to them and transforming them as a unit. |
| RiplImage | A bitmap drawn from any canvas image source. |
| RiplLine | A straight line between two points. |
| RiplPath | A shape drawn by a custom path renderer within a bounding box. |
| RiplPolygon | A regular polygon with a given number of sides. |
| RiplPolyline | A multi-segment line through a list of points. |
| RiplRect | A rectangle, optionally with rounded corners. |
| RiplRenderer | Drives the enclosing scene with a requestAnimationFrame loop, and makes transitions available to its subtree. |
| RiplScene | Creates a scene bound to the enclosing context and parents its subtree to it. |
| RiplText | A run of text, optionally laid out along an SVG path. |
| RiplTransition | Animates the descendants it wraps as they enter, update and leave, mirroring Vue's own enter-from / leave-to model. |
| SHAPE_FIELD_KEYS | Plain Shape2D fields that change how a shape paints but emit no update event. |
| SHAPE_FIELDS | Plain fields that change how a shape paints. They are also the adapter's only boolean props, so this doubles as the set needing Vue's valueless-attribute casting. |
Functions ​
| Function | Description |
|---|---|
| applyFields | Writes the plain fields, which emit nothing, so a repaint has to be requested separately. |
| applyState | Writes state values through the element's accessors, which mark it dirty and emit updated. |
| collectChangedProps | Reads the bound props, folding any that differ from applied into a changed batch. |
| createProps | Builds a Vue runtime props declaration from a list of prop names. |
| createRipl | Creates the Vue plugin that registers every Ripl component globally, so templates can use <ripl-circle> and <RiplCircle> without importing them. |
| createRiplTree | Creates a RiplTree, the per-context coordinator for a declarative Ripl graph. |
| defineRiplElement | Builds a declarative component for a Ripl element. |
| elementFactory | Adapts a typed element factory to RiplNodeDefinition's untyped create hook. |
| partitionProps | Partitions changed props into animatable state and plain fields. |
| readBoundProps | Reads the props that were actually bound; an unbound prop must not overwrite a Ripl default. |
| registerComponents | Registers components on an app, skipping any name already taken. |
| resolveClassNames | Splits any of Vue's class binding forms into individual class names. |
| useElementProps | Constructs an element from its bound props and keeps it in sync with them. |
| useExposedInstance | Resolves a template ref on this component to the Ripl object it wraps rather than to Vue's component proxy, so <ripl-context ref="context"> hands back the Context. |
| useForwardedEvents | Forwards an event bus's events to Vue listeners, subscribing only to those a listener is actually bound to. |
| useRiplContext | Returns the rendering context provided by the nearest context component. |
| useRiplElement | Returns the nearest enclosing element, group or scene. |
| useRiplRenderer | Returns the renderer provided by the nearest renderer component. |
| useRiplScene | Returns the scene provided by the nearest scene component. |