Skip to content

Compositions

Four compositions expose the objects the components build. Use them for the things a template cannot express: exporting an image, querying the graph, driving an ad-hoc transition.

ts
import {
    useRiplContext,
    useRiplElement,
    useRiplRenderer,
    useRiplScene,
} from '@ripl/vue';

const context = useRiplContext();
const scene = useRiplScene();
const renderer = useRiplRenderer();
const element = useRiplElement();

Each returns a ShallowRef. Because the components construct during setup() rather than on mount, these already hold their value in a descendant's own setup(), so there is nothing to watch:

vue
<script setup lang="ts">
import {
    useRiplScene,
} from '@ripl/vue';

const scene = useRiplScene();

// Already resolved.
console.log(scene.value?.width, scene.value?.height);
</script>

They are undefined in two cases: outside the corresponding provider, and during server rendering. Both are ordinary, so guard with ?. rather than asserting.

useRiplContext

The rendering context, which is the drawing surface. Useful for exporting:

vue
<script setup lang="ts">
import {
    useRiplContext,
} from '@ripl/vue';

const context = useRiplContext();

function download() {
    const url = context.value?.export().toURL();

    if (url) {
        window.open(url);
    }
}
</script>

useRiplScene

The scene, when a <ripl-scene> encloses the caller. Gives you the graph query API:

ts
const scene = useRiplScene();

const active = scene.value?.queryAll('.segment.active');
const byId = scene.value?.getElementById('total');

useRiplRenderer

The renderer, when a <ripl-renderer> encloses the caller. Use it to drive a transition that no prop change describes:

ts
const renderer = useRiplRenderer();
const element = useRiplElement();

function pulse() {
    if (!renderer.value || !element.value) {
        return;
    }

    return renderer.value.transition(element.value, {
        duration: 300,
        loop: 'alternate',
        state: {
            opacity: 0.4,
        },
    });
}

renderer.transition returns a cancelable promise, so it can be awaited or aborted.

useRiplElement

The nearest enclosing element, group or scene. Called from a component nested inside a <ripl-group>, it returns that group, which lets you write a component that contributes to whatever group it is dropped into:

vue
<script setup lang="ts">
import {
    useRiplElement,
} from '@ripl/vue';

import {
    isGroup,
} from '@ripl/web';

const parent = useRiplElement();

const bounds = computed(() => isGroup(parent.value)
    ? parent.value.getBoundingBox()
    : undefined);
</script>

Template refs

A ref on any Ripl component resolves to the object that component wraps, not to a Vue component instance, so a ref is the third way to reach one alongside the compositions and <ripl-context>'s ready event:

vue
<template>
    <ripl-context ref="context">
        <ripl-scene ref="scene">
            <ripl-renderer ref="renderer">
                <ripl-circle ref="circle" :cx="50" :cy="50" :radius="20" />
            </ripl-renderer>
        </ripl-scene>
    </ripl-context>
</template>

<script setup lang="ts">
import {
    useTemplateRef,
} from 'vue';

const context = useTemplateRef('context');
const circle = useTemplateRef('circle');

function download() {
    // `context` is the Context; `circle` is the Circle.
    window.open(context.value?.export().toURL());
}
</script>

Each ref is typed as the object it holds, so circle.value.radius and scene.value.queryAll(...) are checked as you would expect. A ref is set on mount, so read it from onMounted onwards rather than during setup(). Reach for the compositions when you need the object earlier than that.

Use a composition rather than a ref if you need object identity: a ref hands back a transparent stand-in for the object, so every property, method and instanceof check works, but === against a handle obtained another way does not hold.

Holding Ripl objects

Ripl instances are deeply mutable and carry Sets, Maps and caches, so they should never go into ref() or reactive(). The compositions already return raw, markRaw'd instances inside a shallowRef. If you store one yourself, use shallowRef or a plain variable.