Plugins

Plugins can hook into compilation (onHook) and, more powerfully, register their own v-xxx directives through the same registry that v-model/v-show/v-text use internally - there is no special privilege for built-ins.

Creating a Plugin

// my-plugin.js
export const MyPlugin = {
    install(api, options) {
        // api contains: app, reactive, ref, computed, watchEffect,
        //               onHook, directive, compile, Scope, evalExp

        // Register a new v-xxx directive
        api.directive('focus', (el, exp, ctx) => {
            setTimeout(() => el.focus(), 0);
        });

        // Directives can take an argument and modifiers too, same shape as
        // Vue's: v-tooltip:top.instant="text" -> arg: 'top', modifiers: { instant: true }
        api.directive('tooltip', (el, exp, { scope, evalExp, arg, modifiers }) => {
            const position = arg || 'bottom';
            const delay = modifiers.instant ? 0 : 300;
            // ...
        });

        // Or hook into the compile pass directly
        api.onHook('beforeCompile', (el, scope, cs) => {
            // ...
        });
    }
};

A directive handler receives (el, expression, ctx), where ctx is { scope, cs, evalExp, watchEffect, ref, reactive, arg, modifiers } - everything needed to set up a reactive effect or listener, plus arg (the string after :, or undefined) and modifiers (an object with each dot-suffix as a truthy key, e.g. .foo.bar -> { foo: true, bar: true }). Register cleanup through cs (cs.addEffect(...) / cs.addListener(...)) so it's automatically torn down when the element is removed.

API stability (v1): app, directive, onHook, reactive, ref, computed, watchEffect are the supported plugin surface and follow semver - build against these. compile, Scope, evalExp are also passed through for advanced cases (e.g. compiling a dynamically-created subtree), but they mirror the compiler's internals directly and can change without a major version bump.

Using Plugins

import { createApp } from '@wrium/wrium';
import { MyPlugin } from './my-plugin.js';

createApp(() => ({ /* ... */ }))
    .use(MyPlugin, { debug: true })
    .mount('#app');

Using an unregistered v-xxx directive (forgetting to install its plugin) reports an error through onHook('onError', ...) and logs to the console - it never fails silently.

Optional Plugins

v-html

Sets innerHTML directly. Left out of core because it's an XSS risk if bound to untrusted content; install it explicitly when you need it:

import { createApp, ref } from '@wrium/wrium';
import { HtmlPlugin } from '@wrium/wrium/plugins/html.js';

createApp(() => ({ htmlContent: ref('<b>Bold</b>') }))
    .use(HtmlPlugin)
    .mount('#app');
<div v-html="htmlContent"></div>

v-password-strength

Flags weak/common passwords as you type. Blocks an exact/substring match against a common-password list, sequential runs (abc, 123), and repeated characters; scores character-class variety for the rest. v-password-strength="someRef" must point at a ref() - it writes { label, valid, reasons } into it on every input.

import { createApp, ref } from '@wrium/wrium';
import { PasswordStrengthPlugin } from '@wrium/wrium/plugins/password-strength.js';

createApp(() => ({ password: ref(''), strength: ref(null) }))
    .use(PasswordStrengthPlugin, { minLength: 10, minScore: 'good' }) // label thresholds: weak < fair < good < strong
    .mount('#app');
<input type="password" v-model="password" v-password-strength="strength" />
<p>Strength: {{ strength?.label }}</p>
<button :disabled="!strength?.valid">Submit</button>

assessPassword(password, options) is also exported directly as a pure function, for validating the same way outside a template (e.g. before a submit that also hits a server).

v-draggable

Makes an element draggable with the mouse, touch, or pen, via a single Pointer Events code path. v-draggable="position" must point at a ref({ x, y }): dragging writes the new coordinates into it, and setting position.value from application code moves the element too, since one watchEffect drives the on-screen position either way. Movement is automatically clamped so the element can't leave its offset parent (which is given position: absolute automatically if it has no position set).

import { createApp, ref } from '@wrium/wrium';
import { DraggablePlugin } from '@wrium/wrium/plugins/draggable.js';

createApp(() => ({ position: ref({ x: 0, y: 0 }) }))
    .use(DraggablePlugin)
    .mount('#app');
<div class="box" style="position: relative">
    <div v-draggable="position">Drag me</div>
</div>
<p>{{ position.x }}, {{ position.y }}</p>

Continue with Examples.

Wrium v1.0.0 · MIT License