Advanced reactive interface Architecture & Next-gen Node Assets
AriannA
Fine-grain Reactive UI Framework · TypeScript · Zero dependencies
♡ Dedicated with love to my daughter Arianna
A TypeScript UI framework for the web — built around Real DOM, Virtual DOM, fine-grain Signals, reactive State,
and a complete 50-control library. Includes 2D/3D renderers, AI/ML engine, quantitative finance (Bachelier, Heston), document generation, data structures (DAG, FSM, Trie), audio/video, and I/O — all with zero dependencies and a dual MIT + Commercial license.
Riccardo-Angeli/AriannA-Js
MIT + Commercial · 2012–2026
TypeScript
Signal+Sink
Zero dependencies
600+ tests
50 controls
11 finance components
20 modifiers
11 additionals
MIT + Commercial
2012 – 2026
Core
Global registry, plugin system, namespace management.
Signals
signal() · effect() · computed() · SignalMono · batch
Real DOM
Fluent chainable DOM + Signal+Sink fine-grain API.
Virtual DOM
Declarative tree, lazy mount, pending Sink queue.
State
Deep reactive proxy. Mutate directly, auto-notify.
Rule / Sheet
CSS-in-JS: JSON objects, @media, @keyframes, Less parser.
Two — 2D
SVG + Canvas engine, PathBuilder, Tween, D3 scales.
Three — 3D
WebGPU + WebGL2, PBR, CSG, STL/OBJ/GLB, OrbitControls.
AI / ML
Tensor, Transformer, Tokenizer, WebGPU matmul.
Finance
Indicators, Black-Scholes, Monte Carlo GPU, Backtest.
Docs
DOCX, XLSX, PPTX, PDF, CSV, SVG — read & write.
Video
Screen/camera capture, compositor, GIF encoder.
Audio
AudioEngine, Oscillator, Reverb, Sequencer, MIDI.
IO
FileIO, Http, SSE, WebSocket, LocalStore, IndexedDB.
50 Controls
Button, Table, TreeView, Modal, DatePicker and 45 more.
Finance Components
Candlestick, OrderBook, Heatmap, Donut, Screener…
Modifiers 2D
Resizer · Rotator · Skewer · Rounder · Reflector · Mover
Modifiers 3D
15 geometry + transform modifiers for Three.ts meshes.
4 Syntaxes
Real · Virtual · Decorators · JSX
600+ Tests
All passing. Core, DOM, CSS, AI, Finance, 3D, Modifiers.
Try it live
Edit the code in your head, hit Reload to bring it back. The preview runs in a sandboxed iframe with the AriannA runtime injected from the parent page.
Installation
AriannA is distributed as TypeScript source. No build step required for modern browsers with ES module support — or use Vite / Tauri for production builds.
Install
npm install arianna # or scaffold a new project npx arianna new my-app # browser npx arianna new my-app --template tauri # Tauri desktop
Repository structure
arianna/ ├── core/ // Core, Real, Virtual, Observable, State, │ // Rule, Sheet, Stylesheet, Namespace, │ // Context, Directive, Component ├── additionals/ // Domain modules │ ├── AI.ts // Tensor, Layers, Transformer, Tokenizer, MarkovChain │ ├── Animation.ts // Tween, Timeline, easings │ ├── Audio.ts // AudioEngine, Oscillator, effects │ ├── Data.ts // FSM, DAG, Trie, PriorityQueue, LRUCache │ ├── Docs.ts // In-repo doc-page generator │ ├── Finance.ts // BlackScholes, Bachelier, Heston, MonteCarlo, │ │ // Indicators, Portfolio, Backtest │ ├── Geometry.ts // AABB, Ray, intersections, hulls │ ├── IO.ts // FileIO, Http, SSE, WebSocketIO, LocalStore │ ├── Latex.ts // LaTeX → SVG / MathML │ ├── Math.ts // Vectors, matrices, noise │ ├── Network.ts // Fetch wrappers, retry/backoff │ ├── SSR.ts // renderToString, renderToStream, hydrate │ ├── Three.ts // 3D scene graph, WebGL renderer │ ├── Two.ts // 2D scene graph, SVG/Canvas renderer │ ├── Video.ts // VideoPlayer, GIFEncoder │ └── Workers.ts // WorkerPool, message passing ├── components/ │ ├── inputs/ // Button, Checkbox, ColorPicker, DatePicker, │ │ // Dropdown, FileUpload, Radio, RangeSlider, │ │ // Rating, SearchBar, Switch, TextField, ... │ ├── display/ // Avatar, Badge, Banner, Card, Chip, Divider, │ │ // Icon, Modal, ProgressBar, Skeleton, Tooltip, ... │ ├── layout/ // Accordion, Drawer, Header, Panel, Splitter, │ │ // Stepper, Tabs, Tag, List, Sidebar │ ├── navigation/ // Breadcrumb, Menu, NavRail, Pagination │ ├── charts/ // BarChart, LineChart, PieChart │ ├── data/ // Table, TreeView │ ├── finance/ // CandlestickChart, LineChart, DepthChart, │ │ // HeatmapChart, PortfolioDonut, PnLChart, │ │ // RiskGauge, OrderBook, Screener, │ │ // Sparkline, AlertBadge │ └── modifiers/ │ ├── 2D/ // Resizer, Rotator, Skewer, Rounder, Reflector, Mover │ └── 3D/ // Subdivision, Decimate, Bevel, Mirror, Array, │ // Bend, Twist, Wave, Inflate, Smooth, Drag, │ // Snap, LOD, Fade, Billboard ├── cli/ // `arianna` command (new, generate, serve, build) ├── projects/ // Browser / Tauri / iOS / Android templates ├── release/ // Built bundles (arianna.js, arianna.min.js) └── types/ // Shared type declarations + jsx-runtime
Import
// Bundle entry point — recommended import { Core, Real, State, signal, effect, computed, batch } from 'arianna'; // Domain additionals import { Finance, BlackScholes } from 'arianna/additionals/Finance.ts'; import { AI, MarkovChain } from 'arianna/additionals/AI.ts'; import { Two, Vec2D, Circle } from 'arianna/additionals/Two.ts'; // UI controls — tree-shakeable per-file imports import { Button } from 'arianna/components/inputs/Button.ts'; import { Table, TreeView } from 'arianna/components/data'; // Finance components import { CandlestickChart, RiskGauge } from 'arianna/components/finance'; // Modifiers import { Resizer, Rotator } from 'arianna/components/modifiers/2D'; import { BendModifier } from 'arianna/components/modifiers/3D';
tsconfig.json (for JSX)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "arianna",
"strict": true
}
}Architecture
AriannA is built around fine-grain reactivity: only the exact DOM node that depends on a signal updates when that signal changes — no component re-render, no VDOM diffing. The library is organized in five layers that build on top of each other.
Signal+Sink — the reactivity core
A signal holds a value plus a list of subscribers. Reading
signal.get() inside an effect() registers the effect as a dependency. Writing signal.set(v) triggers every dependent effect synchronously. No reconciliation, no diffing, no microtask queue.const count = signal(0); const doubled = computed(() => count.get() * 2); effect(() => console.log('doubled is', doubled.get())); // → "doubled is 0" count.set(5); // → "doubled is 10" (synchronous, single dependency walk) batch(() => { count.set(10); count.set(20); }); // → "doubled is 40" (one effect run, not two)
Compiled Hint — static templates with dynamic slots
When you build a tree with
Real, AriannA compiles the static skeleton once and inserts subscription points (sinks) only at the dynamic positions. The DOM is created once; future updates touch only the sinks.const name = signal('Arianna'); new Real('div') .child(new Real('h1').text('Hello')) // static — compiled once .child(new Real('span').text(() => name.get())) // dynamic — sink installed .child(new Real('p').text('How are you?')) // static — compiled once .append(document.body); name.set('World'); // → only the <span> TextNode mutates. h1 and p are untouched.
Core + Addons — the bundle model
AriannA splits responsibilities cleanly: a tiny core that you always need, plus optional addons that you import only where you use them. The core is ~1.5 KB gz; the rest of the library is opt-in.
This matters because most JavaScript frameworks ship one monolithic bundle whether you use 5% or 95% of it. AriannA does the opposite: every addon is a separate module, loadable statically (tree-shaken) or dynamically (code-split by your bundler).
| Layer | Status | Size (gz) | Loaded by |
|---|---|---|---|
| Core — Real, Virtual, State, Observable, Rule, Sheet, signal, effect, computed, batch | required | ~1.5 KB | Static import { ... } from 'arianna' |
| Addon — Two (2D scene graph) | optional | ~8 KB | import { Two } from 'arianna/addons/Two' |
| Addon — Three (3D + WebGL) | optional | ~12 KB | import { Three } from 'arianna/addons/Three' |
| Addon — Finance (BlackScholes, Heston, ...) | optional | ~6 KB | import { Finance } from 'arianna/addons/Finance' |
| Addon — AI (Tensor, Transformer, ...) | optional | ~10 KB | import { AI } from 'arianna/addons/AI' |
| Addon — Audio · Video · IO · Animation · Data · Geometry · Math · Latex · Network · Workers · SSR | optional | varies | Per-addon paths under arianna/addons/ |
| Components — 50+ UI controls, 11 finance components, 20 modifiers | opt-in | per-component | arianna/components/<category>/<Name> |
Loading addons — three patterns
1. Static import — addon code joins your main bundle. Use this when the addon is needed at boot.
import { Real, signal } from 'arianna'; import { Finance } from 'arianna/addons/Finance'; const price = Finance.BlackScholes.price(100, 105, 0.25, 0.05, 0.20, 'call');
2. Dynamic import — addon loads in a separate chunk on demand. Use this for routes / lazy features.
async function openTradingPanel() { const { Finance } = await import('arianna/addons/Finance'); const { CandlestickChart } = await import('arianna/components/finance/CandlestickChart'); // ... build the panel }
3. Plugin registration — register an addon (your own or one of AriannA's) on the global
Core registry so the rest of your code can find it via Core.has('finance').import { Core } from 'arianna'; import { Finance } from 'arianna/addons/Finance'; Core.use({ name: 'finance', install(core) { (core as any).Finance = Finance; }, }); Core.plugins(); // → ['finance', ...]
Writing your own addon
An addon is just a module that exports a namespace and (optionally) registers itself on
window. The convention used across AriannA's built-in addons:// my-addon.ts export const MyAddon = { greet(name: string) { return `hello, ${name}`; }, }; if (typeof window !== 'undefined') Object.defineProperty(window, 'MyAddon', { value: MyAddon, writable: false, configurable: false, enumerable: false, }); export default MyAddon;
That's the entire contract. A single named export, optional
window registration with non-configurable property descriptor (so nothing else can shadow it), and a default export for ergonomic import MyAddon from .... The addon doesn't need to know anything about Core — it's standalone.Layers in detail
| Layer | Key types | Purpose |
|---|---|---|
| Core | Core, Observable, State | Global module registry, typed event bus, Proxy-backed reactive state objects |
| Reactive primitives | signal, signalMono, effect, computed, batch | Fine-grain reactivity. signalMono is the zero-allocation variant for single-subscriber TextNode bindings. |
| DOM | Real, Virtual, Rule, Sheet, Directive, Namespace, Context | Imperative + declarative DOM trees, scoped CSS, custom elements, SVG/MathML namespaces, dependency injection |
| Additionals | Two, Three, Finance, AI, Animation, Audio, Video, IO, Data, … | Domain-specific modules. Optional, tree-shakeable. |
| Components | 50+ UI controls, 11 finance components, 20 modifiers | Ready-to-use, themable, accessible |
Immutable global registry
Every namespace registers itself on
window via Object.defineProperty with writable: false, configurable: false. Once loaded, it cannot be tampered with. window.Core === Core, always.Core.version // → { major: 1, minor: 2, patch: 0 } — frozen Core.use(myPlugin) Core.plugins() // → ['my-plugin'] window.Core === Core // → true, always
Real vs Virtual
Use Real for imperative, chainable DOM control with fluent API. Use Virtual for declarative rendering — describe the tree, let AriannA mount it. Both produce identical Real DOM output and share the same reactive primitives.
4 Syntaxes
AriannA supports four authoring styles. All produce the same result. Choose based on your workflow and tooling preferences.
1. Creating a reactive counter
import { Real, State } from './arianna-ts/index.ts'; const state = new State({ count: 0 }); const btn = new Real('button'); btn.render().textContent = 'Clicked 0 times'; btn.on('click', () => { state.State.count++; btn.render().textContent = `Clicked ${'{'}state.State.count{'}'} times`; }); state.subscribe((key, val) => console.log(key, val)); document.body.appendChild(btn.render());
import { Virtual, State } from './arianna-ts/index.ts'; const state = new State({ count: 0 }); const btn = Virtual.Create('button'); btn.set('textContent', 'Clicked 0 times'); btn.on('click', () => { state.State.count++; btn.set('textContent', `Clicked ${'{'}state.State.count{'}'} times`); }); Virtual.Mount(btn, document.body);
import { Component, Prop, Watch } from './arianna-ts/index.ts'; @Component({ tag: 'click-counter' }) class ClickCounter extends HTMLElement { @Prop() count = 0; @Watch('count') onCountChange(next: number) { this.textContent = `Clicked ${'{'}next{'}'} times`; } connectedCallback() { this.textContent = 'Clicked 0 times'; this.addEventListener('click', () => this.count++); } } // Usage: <click-counter></click-counter>
/* @jsxImportSource arianna */ import { useState } from './arianna-ts/index.ts'; function ClickCounter() {{ const [count, setCount] = useState(0); return ( <button onClick={{() => setCount(count + 1)}}> Clicked {{count}} times </button> ); }}
2. Rendering a reactive list
const state = new State({{ items: ['Alpha', 'Beta'] }}); const ul = new Real('ul'); const render = () => {{ ul.render().innerHTML = ''; state.State.items.forEach(item => {{ const li = new Real('li'); li.render().textContent = item; ul.render().appendChild(li.render()); }}); }}; state.subscribe(render); render(); document.body.appendChild(ul.render()); // Mutate — auto re-renders state.State.items.push('Gamma');
const buildList = () => {{ const ul = Virtual.Create('ul'); state.State.items.forEach(item => {{ const li = Virtual.Create('li'); li.set('textContent', item); ul.add(li); }}); return ul; }}; let root = Virtual.Mount(buildList(), document.body); state.subscribe(() => {{ root.unmount(); root = Virtual.Mount(buildList(), document.body); }});
@Component({{ tag: 'item-list' }}) class ItemList extends HTMLElement {{ @Prop() items: string[] = ['Alpha', 'Beta']; @Watch('items') onItemsChange() {{ this.innerHTML = this.items.map(i => `<li>${'{'}i{'}'}</li>`).join(''); }} add(item: string) {{ this.items = [...this.items, item]; }} }}
function ItemList() {{ const [items, setItems] = useState(['Alpha', 'Beta']); const add = () => setItems([...items, 'Item ' + (items.length + 1)]); return ( <div> <ul>{{items.map(i => <li>{{i}}</li>)}}</ul> <button onClick={{add}}>Add item</button> </div> ); }}
3. Custom element with styles
import { Real, Rule, Sheet } from 'arianna'; // Scoped CSS via Rule + Sheet const sheet = new Sheet() .add(new Rule('.my-card', { display: 'block', padding: '16px', border: '1px solid #ddd', borderRadius: '6px', })) .add(new Rule('.my-card:hover', { boxShadow: '0 4px 12px rgba(0,0,0,.1)', })) .attach(); // Build the card const card = new Real('div') .cls('my-card') .child(new Real('h2').text('Hello')) .child(new Real('p').text('A reusable card')) .append(document.body);
import { Virtual, Rule, Sheet } from 'arianna'; new Sheet() .add(new Rule('.my-card', { padding: '16px', border: '1px solid #ddd' })) .attach(); const card = Virtual.Create('div', { class: 'my-card' }); const title = Virtual.Create('h2'); title.set('textContent', 'Hello'); const body = Virtual.Create('p'); body.set('textContent', 'A reusable card'); card.add(title, body); Virtual.Mount(card, document.body);
import { Component, Prop } from 'arianna'; @Component({ tag: 'my-card', styles: ` :host { display: block; padding: 16px; border: 1px solid #ddd; border-radius: 6px } :host(:hover) { box-shadow: 0 4px 12px rgba(0,0,0,.1) } `, }) class MyCard extends HTMLElement { @Prop() title = ''; @Prop() body = ''; render() { return `<h2>${this.title}</h2><p>${this.body}</p>`; } } // <my-card title="Hello" body="A reusable card"></my-card>
/* @jsxImportSource arianna */ import { Rule, Sheet } from 'arianna'; new Sheet() .add(new Rule('.my-card', { padding: '16px', border: '1px solid #ddd' })) .attach(); function MyCard({ title, children }: { title: string; children?: any }) { return ( <div className="my-card"> <h2>{title}</h2> <p>{children}</p> </div> ); } // <MyCard title="Hello">A reusable card</MyCard>
Benchmark
Performance measured with the official
js-framework-benchmark
(Stefan Krause) — keyed mode, Chrome ×4 CPU throttle, Mac M-series.
AriannA v1.2.0 vs Solid v1.9.3 and React 18.3.
Duration (ms) — lower is better
| Benchmark | AriannA | Solid | React | vs Solid | vs React |
|---|---|---|---|---|---|
| create 1,000 rows | 82.5 | 80.6 | 91.2 | +2% | −9% |
| replace 1,000 rows | 89.3 | 90.2 | 103.4 | −1% | −14% |
| update every 10th row (1k) | 40.5 | 40.2 | 46.1 | ≈ | −12% |
| select row (SignalMono) | 8.7 | 11.1 | 14.3 | −22% ★ | −39% ★★ |
| swap rows (SignalMono) | 30.5 | 48.8 | 62.1 | −38% ★★ | −51% ★★ |
| remove row | 37.1 | 38.1 | 41.8 | −3% | −11% |
| create 10,000 rows | 874 | 839 | 912 | +4% | −4% |
| append 1,000 rows | 93.7 | 96.2 | 108.5 | −3% | −14% |
| clear rows | 30.4 | 34.7 | 38.2 | −12% | −20% |
Script time (ms) — isolates JS execution cost
| Benchmark | AriannA | Solid | React | vs Solid |
|---|---|---|---|---|
| update every 10th row | 1.3 | 2.8 | 4.1 | −54% ★ |
| select row | 0.2 | 2.2 | 3.8 | −91% ★★ |
| swap rows | 0.1 | 3.3 | 5.7 | −97% ★★ |
| remove row | 0.2 | 1.3 | 2.1 | −85% |
Memory & Bundle
| Metric | AriannA | Solid | React | vs Solid |
|---|---|---|---|---|
| Memory after create 1k rows | 2.06 MB | 2.64 MB | 3.41 MB | −22% ★ |
| Bundle size (gzipped) | 1.5 KB | 4.5 KB | 45 KB | 3× less ★★ |
Why swap-rows and select-row win by so much
These two tests are where SignalMono and the Signal+Sink architecture shine.
Instead of re-rendering a component or diffing a VDOM tree, AriannA wires a signal directly to a single
Text node via node.nodeValue = value. When the signal fires, only that node updates
— no wrapper, no scheduler, no diff. The result: 0.1 ms script time on swap-rows vs 3.3 ms for Solid
(−97% script) and 0.2 ms on select-row vs 2.2 ms (−91% script).
Conditions: Mac M-series · Chrome ×4 CPU throttle · js-framework-benchmark keyed mode · median of 5 runs.
Solid v1.9.3 · React 18.3.1 · AriannA v1.2.0.
Bundle size = core only (no components). React includes react-dom.
Core
The global registry. Registers itself on
window as an immutable property. Provides versioning and a plugin system.| Property / Method | Type | Description |
|---|---|---|
| Core.version | frozen object | { major, minor, patch } — SemVer, never writable |
| Core.Namespaces | object | Registered HTML / SVG / MathML / X3D namespaces |
| Core.Scopes | object | Property descriptor presets: public, protected, private, static |
| Core.use(plugin, opts?) | (CorePlugin,any?)→Core | Register plugin idempotently — safe to call multiple times |
| Core.plugins() | ()→string[] | List all registered plugin names |
// Plugin interface interface CorePlugin { name : string; install : (core: typeof Core, opts?: unknown) => void; } Core.use({ name : 'my-plugin', install: (core) => { (core as any).myFeature = () => {}; } }); Core.plugins(); // → ['my-plugin'] Core.use(myPlugin); // second call → no-op (idempotent) Core.version.major; // → 1
Core.version
Frozen SemVer object. Reading is always safe. Mutation throws in strict mode (silently ignored otherwise) —
Object.isFrozen(Core.version) === true.Core.version // → { major: 1, minor: 2, patch: 0 } // Compare versions if (Core.version.major >= 1 && Core.version.minor >= 2) { // safe to use signalMono / sinkText } // Frozen — these all fail silently or throw Core.version.major = 99; // no-op (or TypeError in strict) delete Core.version.patch; // no-op Object.isFrozen(Core.version); // → true
Core.Namespaces
Pre-registered XML namespaces for non-HTML elements. Used by
Real / Virtual / Namespace when creating SVG, MathML, or X3D nodes (document.createElementNS). You can register custom namespaces too.Core.Namespaces.HTML // → 'http://www.w3.org/1999/xhtml' Core.Namespaces.SVG // → 'http://www.w3.org/2000/svg' Core.Namespaces.MathML // → 'http://www.w3.org/1998/Math/MathML' Core.Namespaces.XLink // → 'http://www.w3.org/1999/xlink' // Used internally — Real picks the namespace by tag new Real('svg') // → SVG namespace .child(new Real('circle').attr('r', '40')); // Register your own Core.Namespaces.MyXML = 'https://example.com/ns/v1'; new Real('my-tag', { namespace: 'MyXML' });
Core.Scopes
Property descriptor presets for use with
Object.defineProperty. Encodes the four common visibility/mutability combinations as named constants — clearer intent than ad-hoc descriptors, and consistent across the framework.Core.Scopes.public // → { writable: true, enumerable: true, configurable: true } Core.Scopes.protected // → { writable: true, enumerable: false, configurable: true } Core.Scopes.private // → { writable: true, enumerable: false, configurable: false } Core.Scopes.static // → { writable: false, enumerable: true, configurable: false } // Apply a scope when defining a property Object.defineProperty(target, 'apiKey', { ...Core.Scopes.private, // hidden + locked value: 'sk-...', }); // Same pattern Core itself uses to register itself on window: Object.defineProperty(window, 'Core', { ...Core.Scopes.static, // read-only public global value: Core, });
Signals & Fine-grain Reactivity
AriannA v1.2.0 ships a complete Signal+Sink system. Fine-grain reactivity means only the exact DOM node that depends on a signal updates — no component re-render, no diffing, no VDOM overhead.
Core primitives
| Function | Returns | Description |
|---|---|---|
| signal(v) | Signal<T> | Atomic reactive value. .get() tracks, .set() notifies, .peek() reads without tracking. |
| signalMono(v) | SignalMono<T> | Single-slot signal — direct TextNode patching via sinkText(). Zero wrapper overhead. |
| effect(fn) | () => void | Runs fn immediately, re-runs whenever any signal read inside changes. Returns cleanup. |
| computed(fn) | ReadonlySignal<T> | Derived signal — lazy, memoized, tracks dependencies automatically. |
| batch(fn) | void | Defers all notifications until fn() completes. Prevents cascading updates. |
| untrack(fn) | T | Reads signals inside fn without registering as dependencies. |
| sinkText(s, node) | void | Wires a SignalMono directly to a Text node — zero allocation per update. |
| sinkClass(s, el, cls) | void | Toggles a CSS class on an element when signal value changes. |
signal() — atomic reactive value
import { signal, effect, computed, batch } from 'arianna'; const count = signal(0); const doubled = computed(() => count.get() * 2); effect(() => { console.log(`count=\${count.get()}, doubled=\${doubled.get()}`); }); count.set(5); // logs: count=5, doubled=10 count.set(10); // logs: count=10, doubled=20 // batch — single flush batch(() => { count.set(100); count.set(200); }); // only one effect run: count=200 // peek — read without tracking dependency effect(() => { const curr = count.get(); // tracked const prev = count.peek(); // NOT tracked — won't re-run });
Signal+Sink on Real DOM
Real and VirtualNode both expose the full Signal+Sink API. Each sink creates a single isolated effect tied to exactly one DOM node/attribute.
import { Real, signal } from 'arianna'; const name = signal('AriannA'); const loading = signal(false); const color = signal('#e40c88'); const el = new Real('div') // TextNode Sink — only the text node updates .text(() => `Hello, \${name.get()}!`) // Attribute Sink — updates disabled attr .attr('disabled', () => loading.get() ? '' : null) // Class Sink — toggles CSS class .cls('loading', () => loading.get()) // Style Sink — updates inline style .style('color', () => color.get()) // Property Sink — sets .value on input .prop('dataset.label', () => name.get()) .append(document.body); name.set('World'); // only the TextNode updates — nothing else re-renders
signalMono() + sinkText() — zero-overhead TextNode
SignalMono is a single-slot signal for direct TextNode patching. Used internally by the benchmark — achieves 97% script reduction on swap-rows vs Solid.import { signalMono, sinkText } from 'arianna'; const label = signalMono('Initial'); // Wire signal directly to a TextNode — no wrapper allocation const node = document.createTextNode(label.peek()); document.body.appendChild(node); sinkText(label, node); // Updates the TextNode's nodeValue directly — zero overhead label.set('Updated'); // → node.nodeValue = 'Updated' // Via Real.textMono() — creates + wires in one call const price = signalMono('€ 0.00'); new Real('span').textMono(price).append(document.body); price.set('€ 1.99'); // direct node.nodeValue write
Two-way binding
import { Real, signal } from 'arianna'; const val = signal(''); // .bind(getter, setter) — two-way const input = new Real('input') .bind(() => val.get(), (v) => val.set(v)) .append(document.body); // Mirror elsewhere — auto-updates on every keystroke new Real('p').text(() => `You typed: \${val.get()}`).append(document.body);
Observable pub/sub (instance API)
import { Observable } from 'arianna'; const bus = new Observable(); bus.on('data-ready', (e) => console.log(e)); bus.fire({ Type: 'data-ready', payload: [1, 2, 3] }); // DOM bus (static) — cross-component events Observable.On(document, 'theme-changed', (e) => applyTheme(e.detail));
State
Proxy-based reactive state. Mutate
State.State directly — subscribers notified automatically. Includes computed values, batching, and StateMachine.| Property / Method | Type | Description |
|---|---|---|
| state.State | Proxy<T> | Reactive proxy — mutate directly |
| state.subscribe(cb) | (fn) → unsub fn | Subscribe to any state change |
| state.computed(key,fn) | (string,fn) → this | Derived value — recomputed on dependency change |
| state.batch(fn) | (fn) → this | Group multiple mutations into one notification |
| state.reset() | () → this | Reset to initial values |
| state.snapshot() | () → T | Plain non-reactive copy of current state |
import State from './arianna-ts/State.ts'; const state = new State({ count: 0, name: 'AriannA' }); const unsub = state.subscribe((key, value) => { console.log(key, '→', value); }); state.State.count = 1; // triggers subscriber state.computed('doubled', s => s.count * 2); state.State.doubled; // → 2 state.batch(() => { state.State.count = 10; // single notification for both state.State.name = 'New'; }); unsub(); // stop listening
Deeply nested objects + array indices
The Proxy is recursive — every plain object and array reachable from the root becomes reactive automatically. Mutating a single array index, or a property six levels deep, fires exactly one notification with the full path. No need to flatten your data, no need for
set() / setIn() helpers like Immer or Redux.import { State } from 'arianna'; const store = new State({ user: { name: 'Riccardo', addresses: [ { city: 'Zurich', primary: true }, { city: 'Roma', primary: false }, ], }, cart: { items: [ { sku: 'A1', qty: 2, meta: { gift: false } }, { sku: 'B2', qty: 1, meta: { gift: true } }, ], totals: { subtotal: 0, tax: 0 }, }, }); store.subscribe((path, value) => console.log(path, '→', value)); // (1) Mutate a deep scalar — single notification with the full path store.State.user.addresses[1].primary = true; // → "user.addresses.1.primary" → true // (2) Mutate inside a nested object inside an array store.State.cart.items[0].meta.gift = true; // → "cart.items.0.meta.gift" → true // (3) Array methods are intercepted — push/splice/pop all notify store.State.cart.items.push({ sku: 'C3', qty: 5, meta: { gift: false } }); // → "cart.items.2" → { sku: 'C3', qty: 5, meta: {…} } // → "cart.items.length" → 3 store.State.cart.items.splice(0, 1); // → "cart.items.0" → { sku: 'B2', … } (shift) // → "cart.items.1" → { sku: 'C3', … } // → "cart.items.length" → 2 // (4) Reassign an entire subtree — children are re-wrapped lazily store.State.user.addresses = [{ city: 'Milan', primary: true }]; // → "user.addresses" → [{ city: 'Milan', primary: true }] store.State.user.addresses[0].city = 'Genoa'; // still reactive // → "user.addresses.0.city" → "Genoa" // (5) Path-targeted subscription — listen only to one branch const off = store.subscribePath('cart.items', (path, value) => { console.log('cart changed:', path, value); }); // (6) Computed over deep data — recomputes only when its deps change store.computed('cart.totals.subtotal', s => s.cart.items.reduce((acc, it) => acc + it.qty, 0) ); store.State.cart.totals.subtotal; // → 6 store.State.cart.items[0].qty = 10; store.State.cart.totals.subtotal; // → 15 (auto-recomputed)
Implementation note: each nested object/array is wrapped in its own Proxy lazily on first access. Array index mutations are detected via the
set trap on numeric keys; length changes from push/pop/splice notify separately, so subscribers can rebuild lists efficiently.Define a component
A component in AriannA is just a function that returns a piece of DOM. There are three ways to write that function (function declaration, class, JSX) and two ways to construct the DOM inside it (Real for fluent imperative, Virtual for declarative tree). All combinations interoperate — pick the style that fits each part of your app.
Quickest possible
A button that says hello.
Function · Real
Most common style. Pure function, returns a Real wrapper, fluent chain inside. No state, no class, no JSX — just function calls.
Function · Virtual
Same shape as above, but using
Virtual.Create() for declarative tree construction. Virtual.Mount() commits the tree to the DOM.Class · Real
Useful when the component owns local state or needs lifecycle. The constructor sets up signals and DOM;
.mount(parent) attaches it.Class · Define() — custom element
Register the class as a real Web Component with
Real.Define('my-tag', MyClass, HTMLElement). From that point on you can use <my-tag></my-tag> in plain HTML, in any framework, anywhere. Lifecycle methods (connectedCallback, disconnectedCallback) work as in standard Web Components.Function · Constructor — legacy / imperative
A plain function called with
new acts as a constructor. Useful for migrating non-class codebases. this refers to the instance being created; signals work the same way.Class · Inheritance — composing components
Components are plain JavaScript classes. They compose with standard
extends — no framework-specific wiring. Override methods or call super.method() to augment behavior.JSX · Real
Familiar React-like syntax compiled by AriannA's JSX runtime. Configure
tsconfig.json with "jsxImportSource": "arianna". Each tag becomes a Real instance under the hood.Composition
Components compose by returning DOM and accepting parameters. Pass signals down for shared state.
When to use what
| Style | Best for | Tradeoffs |
|---|---|---|
| Function · Real | Most components — buttons, cards, lists, forms | Imperative; reads top-to-bottom; no JSX tooling needed |
| Function · Virtual | Trees built from data (lists, nested config) | Slightly more verbose; mount step is explicit |
| Class · Real | Stateful widgets, modals, controllers with cleanup | More boilerplate; useful when lifecycle matters |
| Class · Define() | Reusable custom elements consumable as HTML tags from any framework | Web Components lifecycle; cross-framework distribution |
| Function · Constructor | Migrating legacy non-class codebases | Imperative; this binding requires care |
| Class · Inheritance | Building component libraries; reusable base classes | Standard JS class semantics; super() and overrides |
| JSX · Real | Teams coming from React | Requires tsconfig + JSX runtime; not zero-config |
Real
Try it live
Edit the code, hit Reload, see the result update in the sandboxed preview below.
Constructor overloads
| Call | Mode | Returns |
|---|---|---|
| Real('div') | Wrapper — no new | The tag class/constructor |
| Real('#my-id') | Wrapper | Real wrapping that element |
| Real(domElement) | Wrapper | Real wrapping that element |
| Real('tag', Ctor, Base) | Define | Registers custom element |
| new Real('div') | Instance | Real instance with <div> |
| new Real('div', { css: {} }) | Instance | Real with styles applied |
| new Real('#my-id') | Instance | Real wrapping existing element |
| new Real(domElement) | Instance | Real wrapping live element |
| new Real(virtualNode) | Instance | Real from Virtual's element |
Fluent, chainable DOM wrapper. Two modes: wrapper (no
new) returns constructor/element; instance (new Real(...)) creates a Real instance.Constructor overloads
// Wrapper mode — no new Real('div') // tag class wrapper Real('#my-id') // querySelector → Real of that element Real(HTMLButtonElement) // interface wrapper Real(existingNode) // wrap existing DOM node // Instance mode — creates element new Real('button') // <button> new Real('button', {{ css: {{ color: 'red' }} }}) // with styles new Real(HTMLButtonElement) // from interface new Real(myVirtualNode) // from Virtual node // Define custom element Real('my-tag', MyConstructor, HTMLButtonElement);
Instance methods (fluent — all return
this)| Property / Method | Type | Description |
|---|---|---|
| .render() | HTMLElement | Returns the underlying live DOM element |
| .on(type, cb) | this | addEventListener |
| .off(type, cb) | this | removeEventListener |
| .fire(event) | this | dispatchEvent |
| .append(parent) | this | Attach to parent element |
| .add(...nodes) | this | Append children (optional index) |
| .remove(...nodes) | this | Remove specific children |
| .get(name) | any | Get attribute or property |
| .set(name, val) | this | Set attribute or property |
| .show() / .hide() | this | Toggle display |
| .log() | this | console.log the element |
Virtual
Constructor overloads — identical to Real
| Call | Mode | Returns |
|---|---|---|
| Virtual('div') | Wrapper — no new | VirtualNode for tag |
| Virtual('#my-id') | Wrapper | VirtualNode shadowing DOM element |
| Virtual(domElement) | Wrapper | VirtualNode wrapping live element |
| Virtual(realInstance) | Wrapper | VirtualNode from Real's element |
| Virtual(virtualNode) | Wrapper | Returns the node itself |
| Virtual('tag', Ctor, Base) | Define | Registers custom element |
| new Virtual('div') | Instance | VirtualNode, no DOM until mount |
| new Virtual('div', { class: 'x' }, 'text') | Instance | VirtualNode with attrs + children |
| new Virtual('#my-id') | Instance | VirtualNode shadowing DOM element |
| new Virtual(domElement) | Instance | VirtualNode wrapping live element |
| new Virtual(realInstance) | Instance | VirtualNode from Real's element |
Declarative virtual DOM tree. Callable with or without
new — mirrors Real's constructor overloads. Nodes are described without touching the live DOM until mounted.Constructor overloads (mirrors Real)
// WRAPPER mode — no new (like Real) Virtual('div') // → Virtual.Create('div') Virtual('#my-id') // → wraps existing DOM element Virtual(domElement) // → wraps live DOM element Virtual(myRealInstance) // → wraps Real's element Virtual(existingVirtualNode) // → returns the node itself // INSTANCE mode — with new (identical behaviour) new Virtual('div') // → new VirtualNode('div') new Virtual('div', {{ class: 'card' }}) // → with attrs new Virtual('#my-id') // → shadow existing element new Virtual(domElement) // → wrap live element new Virtual(myRealInstance) // → wrap Real's element
Instance methods added (parity with Real)
| Method | Returns | Description |
|---|---|---|
| .show() | this | el.style.display = '' — mirrors Real.show() |
| .hide() | this | el.style.display = 'none' — mirrors Real.hide() |
| .valueOf() | Element | Returns underlying DOM element — mirrors Real.valueOf() |
| .css(prop, val) | this | Set CSS style property on mounted element |
| .dom(type, cb) | this | Native DOM addEventListener (distinct from internal .on() bus) |
| .domOff(type, cb) | this | Native DOM removeEventListener |
Static API
| Property / Method | Type | Description |
|---|---|---|
| Virtual.Create(tag, attrs?, ...children) | → VirtualNode | Create a virtual node |
| Virtual.Mount(node, parent) | → VirtualNode | Render node to real DOM |
| Virtual.Parse(html) | → VirtualNode | Parse HTML string into VirtualNode tree |
| Virtual.Nodes | Map<string,VirtualNode> | Global registry of all live nodes |
VirtualNode methods
| Property / Method | Type | Description |
|---|---|---|
| node.add(...children) | this | Append child nodes |
| node.remove(...children) | this | Remove child nodes |
| node.get(name) / node.set(name,val) | any / this | Attribute access — syncs to DOM if mounted |
| node.mount(parent) | this | Render to real DOM |
| node.unmount() | this | Remove from real DOM |
| node.clone() | VirtualNode | Deep clone the virtual tree |
| node.on(type, cb) | this | Subscribe to internal event bus |
| node.fire(event) | this | Dispatch on internal event bus |
import { Virtual } from './arianna-ts/Virtual.ts'; const card = Virtual.Create('div', {{ class: 'card' }}); const title = Virtual.Create('h2'); title.set('textContent', 'Hello'); card.add(title); Virtual.Mount(card, document.body); // later: card.unmount(); card.set('class', 'card card--active'); // syncs if mounted card.on('VNode-Changed', e => console.log(e)); card.fire({{ Type: 'VNode-Changed', node: card }});
Usage in all 4 syntaxes
The same result — a card with a title and a click handler — written in each AriannA style.
1. Simple card with click event
// Real — imperative, fluent, live DOM import {{ Real }} from './arianna-ts/Real.ts'; const card = new Real('div'); const title = new Real('h2'); const body = new Real('p'); title.render().textContent = 'Hello from Real'; body.render().textContent = 'Fluent, chainable DOM wrapper.'; card.render().appendChild(title.render()); card.render().appendChild(body.render()); card.on('click', () => console.log('Real card clicked')); document.body.appendChild(card.render());
// new Virtual — declarative, tree-based import {{ Virtual }} from './arianna-ts/Virtual.ts'; const card = Virtual.Create('div', {{ class: 'card' }}); const title = Virtual.Create('h2'); const body = Virtual.Create('p'); title.set('textContent', 'Hello from Virtual'); body.set('textContent', 'Declarative, tree-based.'); card.add(title, body); card.on('click', () => console.log('Virtual card clicked')); // Nothing touches the DOM until Mount Virtual.Mount(card, document.body); // Later: update live DOM via set() title.set('textContent', 'Updated!'); // syncs immediately
import {{ Component, Prop, Watch }} from './arianna-ts/index.ts'; @Component({{ tag: 'my-card' }}) class MyCard extends HTMLElement {{ @Prop() title = 'Hello from Decorators'; @Prop() body = 'Class-based, reactive.'; @Watch('title') onTitleChange(next: string) {{ const el = this.querySelector('h2'); if (el) el.textContent = next; }} connectedCallback() {{ this.innerHTML = ` <div class="card"> <h2>${{'{'}this.title{'}'}</h2> <p>${{'{'}this.body{'}'}</p> </div> `; this.addEventListener('click', () => console.log('Decorator card clicked')); }} }} // <my-card></my-card>
/* @jsxImportSource arianna */ /* @dom-render: virtual */ ← outputs VirtualNode tree function MyCard({{ title, body }}: {{ title: string; body: string }}) {{ return ( <div className="card" onClick={{() => console.log('JSX card clicked')}}> <h2>{{title}}</h2> <p>{{body}}</p> </div> ); }} // With @dom-render: virtual — produces VirtualNode // With @dom-render: real (default) — produces Real instance const vnode = <MyCard title="Hello from JSX" body="Declarative, JSX-powered." />;
2. Reactive list with State
import {{ Real, State }} from './arianna-ts/index.ts'; const state = new State({{ items: ['Alpha', 'Beta'] }}); const ul = new Real('ul'); const render = () => {{ ul.render().innerHTML = ''; state.State.items.forEach(item => {{ const li = new Real('li'); li.render().textContent = item; ul.render().appendChild(li.render()); }}); }}; state.subscribe(render); render(); document.body.appendChild(ul.render()); state.State.items.push('Gamma'); // auto re-renders
import {{ Virtual, State }} from './arianna-ts/index.ts'; const state = new State({{ items: ['Alpha', 'Beta'] }}); const buildList = () => {{ const ul = Virtual.Create('ul'); state.State.items.forEach(item => {{ const li = Virtual.Create('li'); li.set('textContent', item); ul.add(li); }}); return ul; }}; // Mount initial tree let root = Virtual.Mount(buildList(), document.body); // On state change: unmount old, mount new state.subscribe(() => {{ root.unmount(); root = Virtual.Mount(buildList(), document.body); }}); state.State.items.push('Gamma'); // triggers unmount + remount
@Component({{ tag: 'item-list' }}) class ItemList extends HTMLElement {{ @Prop() items: string[] = ['Alpha', 'Beta']; @Watch('items') onItemsChange(next: string[]) {{ this.innerHTML = next .map(i => `<li>${{i}}</li>`) .join(''); }} connectedCallback() {{ this.innerHTML = this.items.map(i => `<li>${{i}}</li>`).join(''); }} add(item: string) {{ this.items = [...this.items, item]; // @Watch triggers }} }}
function ItemList() {{ const [items, setItems] = useState(['Alpha', 'Beta']); const add = () => setItems([...items, 'Item ' + (items.length + 1)]); return ( <div> <ul> {{items.map((item, i) => ( <li key={{i}}>{{item}}</li> ))}} </ul> <button onClick={{add}}>Add item</button> </div> ); }}
3. Virtual-specific: parse, clone, event bus
These patterns are unique to Virtual — no equivalent in Real.
// Parse HTML string → VirtualNode tree const tree = Virtual.Parse(` <ul> <li>Alpha</li> <li>Beta</li> </ul> `); Virtual.Mount(tree, document.body); // Clone a subtree before mounting const original = Virtual.Create('div', { class: 'card' }); const clone = original.clone(); clone.set('class', 'card card--active'); Virtual.Mount(original, document.body); Virtual.Mount(clone, document.body); // Virtual event bus — communicates without DOM const vnode = Virtual.Create('section'); vnode.on('data-ready', e => console.log('Payload:', e.payload)); vnode.fire({ Type: 'data-ready', payload: { count: 42 } }); // Check children without touching live DOM const child = Virtual.Create('p'); vnode.add(child); vnode.contains(child); // → true // Access global node registry Virtual.Nodes.get(vnode.Id); // → vnode Virtual.Nodes.size; // → total mounted nodes
Rule & Stylesheet
CSS-in-JS.
Rule is a single CSS ruleset. Sheet is an ordered collection of Rules injected as a <style> element.Rule
import Rule from './arianna-ts/Rule.ts'; const rule = new Rule('.btn', {{ color: 'red', fontSize: '14px' }}); rule.selector; // → '.btn' rule.css; // → { color: 'red', fontSize: '14px' } rule.toString(); // → '.btn { color: red; font-size: 14px; }'
Stylesheet (Sheet)
| Property / Method | Type | Description |
|---|---|---|
| new Sheet(r1, r2, ...) | constructor | Create from Rule instances |
| sheet.add(rule, index?) | this | Append (or insert at index) |
| sheet.remove(rule) | this | Remove a rule |
| sheet.inject(target) | HTMLStyleElement | Inject <style> into target (e.g. document.head) |
import {{ Sheet }} from './arianna-ts/Stylesheet.ts'; const sheet = new Sheet( new Rule('.btn', {{ padding: '8px 16px' }}), new Rule('.btn:hover', {{ opacity: '0.8' }}), ); sheet.add(new Rule('.btn:active', {{ transform: 'scale(.98)' }})); sheet.add(new Rule('.btn-primary', {{ background: 'blue' }}), 0); // insert at index 0 sheet.inject(document.head);
Namespace
Full namespace coverage for HTML, SVG, MathML, and X3D. Each namespace is a proxy mapping tag names and interface names to typed constructors.
| Namespace | Interfaces | Tags |
|---|---|---|
| HTML | 56 | 118 |
| SVG | 34 | — |
| MathML | 29 | — |
| X3D | via Define | — |
import Namespace from './arianna-ts/Namespace.ts'; Namespace.HTML.div; // → HTMLDivElement constructor Namespace.HTML.button; // → HTMLButtonElement Namespace.HTML.HTMLAnchorElement; // → by interface name Namespace.SVG.circle; // → SVGCircleElement Namespace.SVG.path;
Context
Scoped dependency injection. A Context carries a typed value consumable by any descendant in the component tree.
import Context from './arianna-ts/Context.ts'; const ThemeCtx = Context.create({{ theme: 'dark' }}); ThemeCtx.provide({{ theme: 'light' }}); const {{ theme }} = ThemeCtx.consume(); console.log(theme); // → 'light'
Directive
Directives are reusable behaviors attached to DOM elements — the AriannA equivalent of
v-if, v-for, @event in Vue or Angular's structural directives.
They work with plain HTML elements, Real nodes, and Virtual nodes equally.
All directives return an update() function — call it whenever your state changes
to re-render that directive's output.
All directives
| Directive | Signature | Description |
|---|---|---|
| Directive.if | (el, condition, then, else?) | Conditional rendering — swaps content based on a boolean fn |
| Directive.for | (el, items, template) | Render an array of items as children |
| Directive.foreach | (el, obj, template) | Iterate an object's key/value pairs |
| Directive.while | (el, condition, body) | Render while condition is true (like a loop) |
| Directive.switch | (el, value, cases) | Show one of N case branches based on a value fn |
| Directive.bind | (el, prop, source) | One-way binding: element property ← source fn |
| Directive.model | (el, state, key) | Two-way binding: input ↔ State property |
| Directive.show | (el, condition) | Toggle display (no DOM removal) |
| Directive.on | (el, types, handler, opts?) | Attach DOM event listener(s) |
| Directive.template | (el, scope) | Interpolate {{ }} placeholders in innerHTML |
| Directive.register | (name, { mounted, unmounted }) | Register a custom reusable directive |
| Directive.apply | (name, el, value) | Apply a registered directive to an element |
| Directive.bootstrap | (root, scope) | Scan DOM for a-* attributes and apply all directives |
1. Directive.if — conditional rendering
Replaces the element's innerHTML with either the
then or the else branch
depending on the condition function. Returns an update() you call when state changes.
let loggedIn = false; const update = Directive.if( document.getElementById('auth-area'), () => loggedIn, '<div class="welcome">Welcome back! <button id="logout">Logout</button></div>', '<div class="login"><input placeholder="email"><button id="login">Login</button></div>' ); // Toggle and re-render document.getElementById('login')?.addEventListener('click', () => { loggedIn = true; update(); }); document.getElementById('logout')?.addEventListener('click', () => { loggedIn = false; update(); }); // document.getElementById and Real('#id') are identical — use either const update1 = Directive.if( document.getElementById('auth-area'), // ← native DOM () => loggedIn, thenHtml, elseHtml ); const update2 = Directive.if( Real('#auth-area'), // ← Real wrapper — same element () => loggedIn, thenHtml, elseHtml ); // Both work — Real('#id') calls querySelector internally // With State (auto-update) const state = new State({ visible: false }); const upd = Directive.if(Real('#panel'), () => state.State.visible, '<p>Shown!</p>', '<p>Hidden</p>'); state.subscribe(() => upd());
Directive.if — live loggedIn = false
Click to switch between if / else branch
condition = false → else branch rendered
JSX equivalent
/* @jsxImportSource arianna */ function AuthArea() { const [loggedIn, setLoggedIn] = useState(false); return loggedIn ? <div className="welcome"> Welcome back! <button onClick={() => setLoggedIn(false)}>Logout</button> </div> : <div className="login"> <input placeholder="email"/> <button onClick={() => setLoggedIn(true)}>Login</button> </div>; }
Performance Benchmarks vs Solid v1.9.3 — same Mac M-series, Chrome ×4 throttle
| Benchmark | AriannA v12 | Script | Solid v1.9.3 | Script | Δ Total | Δ Script |
|---|---|---|---|---|---|---|
| 01 create 1k | 82.5 ms | 8.1 | 80.6 ms | 8.3 | +2% | −2% |
| 02 replace 1k | 89.3 ms | 14.1 | 90.2 ms | 15.8 | −1% | −11% |
| 03 update 10th | 40.5 ms | 1.3 | 40.2 ms | 2.8 | ≈ | −54% ★ |
| 04 select row | 8.7 ms | 0.2 | 11.1 ms | 2.2 | −22% ★ | −91% ★★ |
| 05 swap rows | 30.5 ms | 0.1 | 48.8 ms | 3.3 | −38% ★★ | −97% ★★ |
| 06 remove row | 37.1 ms | 0.2 | 38.1 ms | 1.3 | −3% | −85% |
| 07 create 10k | 874 ms | 117 | 839 ms | 94.7 | +4% | +23% |
| 08 append 1k | 93.7 ms | 8.4 | 96.2 ms | 9.4 | −3% | −11% |
| 09 clear 1k | 30.4 ms | 25.5 | 34.7 ms | 30.4 | −12% | −16% |
| 22 run memory | 2.06 MB | — | 2.64 MB | — | −22% ★ | — |
| Bundle (gz) | 1.5 KB | — | 4.5 KB | — | 3× less ★★ | — |
★ SignalMono slot optimization · ★★ Compiled Hint + direct nodeValue write · Solid leads only on create 10k due to mature compiler batching
HTML attribute syntax
Use
a-if on any element. Directive.bootstrap(root, scope) wires everything automatically — no JS per-element needed.<!-- In your HTML --> <div id="app"> <!-- a-if: shown when scope.loggedIn is truthy --> <div a-if="loggedIn"> Welcome back, {{ username }}! <button a-on="click:logout">Logout</button> </div> <!-- a-if with else via a-else sibling (same parent, next element) --> <div a-if="loggedIn">✓ Authenticated</div> <div a-else>✗ Please log in</div> </div> // In your script — one call wires everything const scope = { loggedIn : false, username : 'Riccardo', logout : () => { scope.loggedIn = false; Directive.bootstrap(app, scope); }, }; Directive.bootstrap(document.getElementById('app'), scope);
HTML
a-if — live bootstrap demo
✗ Not logged in.
HTML:
<div a-if="loggedIn">...</div><div a-else>...</div>a-if="loggedIn" → false → else shown
2. Directive.for — array rendering
Renders each item of an array as a child element. The template function receives
(item, index).let planets = ['Mercury', 'Venus', 'Earth', 'Mars']; // Native DOM or Real wrapper — identical result const update = Directive.for( document.getElementById('planet-list'), // native () => planets, (item, i) => `<li data-i="${i}">${i + 1}. ${item}</li>` ); // equivalent with Real: const update = Directive.for( Real('#planet-list'), // Real wrapper () => planets, (item, i) => `<li data-i="${i}">${i + 1}. ${item}</li>` ); // Add an item and re-render planets.push('Jupiter'); update(); // Remove an item and re-render planets = planets.filter(p => p !== 'Venus'); update();
Directive.for — live
4 items rendered
JSX equivalent
function PlanetList() { const [planets, setPlanets] = useState(['Mercury', 'Venus', 'Earth']); const add = () => setPlanets([...planets, 'Planet ' + (planets.length + 1)]); const remove = () => setPlanets(planets.slice(0, -1)); return ( <ul> {planets.map((p, i) => <li key={i}>{i + 1}. {p}</li>)} </ul> ); }
HTML attribute syntax
<!-- a-for: renders the element once per item --> <ul id="app"> <li a-for="planet in planets"> {{ planet }} </li> </ul> // Script Directive.bootstrap(document.getElementById('app'), { planets: ['Mercury', 'Venus', 'Earth', 'Mars'], }); <!-- With index --> <li a-for="item, i in items">{{ i }}. {{ item.name }}</li> <!-- Nested --> <div a-for="user in users"> <h3>{{ user.name }}</h3> <ul><li a-for="tag in user.tags">{{ tag }}</li></ul> </div>
HTML
a-for — live bootstrap demoHTML:
<li a-for="planet in planets">{ planet }</li>a-for renders one <li> per array item
3. Directive.foreach — object iteration
Iterates an object's key/value pairs. Template receives
(key, value).const scores = { Alice: 95, Bob: 87, Carol: 92, Dan: 78 }; Directive.foreach( document.getElementById('score-list'), () => scores, (name, score) => `<li><strong>${name}</strong>: ${score}/100</li>` ); // Equivalent to the Golem template: // <ol a-foreach="var name in scores"><li>{{ name }}: {{ scores[name] }}</li></ol>
4. Directive.switch — multi-branch rendering
Shows one branch from a map of cases. The value function returns the current key. Supports a
default fallback.let tab = 'home'; // document.getElementById ←→ Real('#id') — pick your style const update = Directive.switch( document.getElementById('tab-content'), // or: Real('#tab-content') () => tab, { home : '<div><h3>🏠 Home</h3><p>Welcome to AriannA.</p></div>', docs : '<div><h3>📖 Docs</h3><p>Read the documentation.</p></div>', settings: '<div><h3>⚙️ Settings</h3><p>Configure your app.</p></div>', default : '<div><h3>404</h3><p>Page not found.</p></div>', } ); // Navigate to a tab tab = 'docs'; update();
Directive.switch — live
tab = home
JSX equivalent
function TabContent({ tab }: { tab: string }) { const pages: Record<string, JSX.Element> = { home : <div><h3>🏠 Home</h3><p>Welcome to AriannA.</p></div>, docs : <div><h3>📖 Docs</h3><p>Read the docs.</p></div>, settings: <div><h3>⚙️ Settings</h3><p>Configure app.</p></div>, }; return pages[tab] ?? <div>404</div>; }
HTML attribute syntax
<!-- a-switch on the container, a-case on each branch --> <div a-switch="currentPage"> <div a-case="home"> <h2>🏠 Home</h2><p>Welcome!</p> </div> <div a-case="about"> <h2>ℹ️ About</h2><p>About us.</p> </div> <div a-case="default"> <h2>404</h2> </div> </div> <<!-- Nav buttons -->> <button a-on="click:goHome">Home</button> <button a-on="click:goAbout">About</button> // Script const scope = { currentPage : 'home', goHome : () => { scope.currentPage = 'home'; reboot(); }, goAbout : () => { scope.currentPage = 'about'; reboot(); }, }; const reboot = () => Directive.bootstrap(document.getElementById('app'), scope); reboot();
HTML
a-switch / a-case — live bootstrap demoHTML:
<div a-switch="page"><div a-case="home">...</div></div>a-switch="currentPage" → home
5. Directive.while — loop rendering
Calls the body function repeatedly while condition is true, collecting HTML. Useful for computed sequences.
let n = 5; Directive.while( document.getElementById('fib-list'), () => n > 0, (() => { let [a, b, count] = [0, 1, 0]; return () => { const val = a; [a, b] = [b, a + b]; count++; n--; return `<li>fib(${count}) = ${val}</li>`; }; })() ); // Renders: fib(1)=0, fib(2)=1, fib(3)=1, fib(4)=2, fib(5)=3
Directive.while — generate N Fibonacci numbers
7
7 Fibonacci numbers
HTML attribute syntax
<!-- a-while: renders the template while count > 0 --> <ol id="app"> <li a-while="count > 0"> Item {{ count-- }} </li> </ol> // Script — scope variables are read-write during iteration Directive.bootstrap(document.getElementById('app'), { count: 5 }); // → renders: Item 5, Item 4, Item 3, Item 2, Item 1
6. Directive.bind — one-way binding
Binds an element's property to a source function. One-way: element updates when you call
update().const state = new State({ username: 'Riccardo', score: 0 }); // document.getElementById and Real('#id') are the same — use either const upd = Directive.bind( document.getElementById('name-display'), // native getElementById 'textContent', () => state.State.username ); // Same with Real wrapper — querySelector under the hood const upd2 = Directive.bind( Real('#score-display'), // Real('#id') wrapper 'innerHTML', () => `Score: <strong>${state.State.score}</strong>` ); state.subscribe(() => { upd(); upd2(); }); state.State.username = 'Arianna'; // → both spans update
HTML attribute syntax
<!-- a-bind="property:scopeKey" — one-way: element ← scope --> <div id="app"> <span a-bind="textContent:username"></span> <img a-bind="src:avatarUrl" alt="Avatar"> <input a-bind="value:email" placeholder="Email"> <div a-bind="className:cssClass">Styled</div> <div a-bind="innerHTML:richHtml"></div> </div> // Script Directive.bootstrap(document.getElementById('app'), { username : 'Riccardo', avatarUrl : '/img/avatar.png', email : 'r@example.com', cssClass : 'card active', richHtml : 'Score: <strong>42</strong>', });
7. Directive.model — two-way binding
Two-way: input value changes update the State, and State changes update the input. Zero glue code.
const state = new State({ name: '', age: '', color: '#e40c88' }); // Mix freely — both target the same DOM element Directive.model(document.getElementById('inp-name'), state, 'name'); // native Directive.model(Real('#inp-age'), state, 'age'); // Real Directive.model(Real('#inp-color'), state, 'color'); // Real // Real('#id') === document.querySelector('#id') — same element, zero overhead state.subscribe((key, val) => console.log(key, '→', val)); state.State.name = 'Arianna'; // programmatic write updates the input too
Directive.model — two-way binding
State mirrors input values in real time
HTML attribute syntax
<!-- a-model: two-way binding — input ↔ scope property --> <div id="app"> <input a-model="user.name" placeholder="Name"> <input a-model="user.age" type="number"> <input a-model="theme.color" type="color"> <select a-model="settings.lang"> <option value="en">English</option> <option value="it">Italiano</option> </select> <textarea a-model="post.body"></textarea> <!-- Live preview — updates as user types --> <p>Hello, <span a-bind="textContent:user.name"></span>!</p> </div> // Script Directive.bootstrap(document.getElementById('app'), { user : { name: '', age: '' }, theme : { color: '#e40c88' }, settings : { lang: 'en' }, post : { body: '' }, });
8. Directive.show — toggle visibility
Toggles
display:none without removing the element from DOM. Cheaper than Directive.if for frequent toggles.let open = false; // Both lines target the same element — use what reads better const panel = document.getElementById('side-panel'); // native const toggle = Real('#toggle-btn'); // Real wrapper const update = Directive.show(panel, () => open); Real('#toggle-btn').on('click', () => { open = !open; update(); }); // or: toggle.addEventListener('click', ...) — same thing // Directive.show vs Directive.if: // .show → display toggle — element stays in DOM, state preserved // .if → innerHTML swap — content recreated on each change
HTML attribute syntax
<!-- a-show: toggles display, element stays in DOM --> <div id="app"> <button a-on="click:toggleMenu">☰ Menu</button> <nav a-show="menuOpen"> <a href="/">Home</a> <a href="/docs">Docs</a> </nav> <!-- a-show vs a-if: a-show → display:none (element kept, state preserved) a-if → innerHTML swap (element recreated on change) --> </div> // Script const scope = { menuOpen : false, toggleMenu : () => { scope.menuOpen = !scope.menuOpen; reboot(); }, }; const reboot = () => Directive.bootstrap(document.getElementById('app'), scope); reboot();
HTML
a-show — toggle visibilityHTML:
<nav a-show="menuOpen">...</nav>a-show="menuOpen" → false → display:none
9. Directive.on — event listener
Attach one or more DOM event listeners. Space-separated types. Supports
AddEventListenerOptions.// document.getElementById or Real('#id') — identical Directive.on(document.getElementById('save-btn'), 'click', e => save()); // native Directive.on(Real('#save-btn'), 'click', e => save()); // Real // Real shines for chaining — attach listener and append in one expression Real('#save-btn').on('click', save).on('mouseenter', highlight); // Multiple events space-separated Directive.on(Real('#search'), 'focus blur', e => highlight(e.type === 'focus')); // With options (passive scroll) Directive.on(Real('#list'), 'scroll', onScroll, { passive: true }); // Equivalent HTML attribute (via bootstrap): // <button a-on="click:handleClick">Click me</button>
HTML attribute syntax
<!-- a-on="event:handler" — attach DOM listener --> <div id="app"> <button a-on="click:handleClick">Click me</button> <button a-on="click:save">Save</button> <!-- Multiple events space-separated --> <input a-on="focus blur:handleFocus" placeholder="Focus me"> <!-- Keyboard shortcuts --> <div a-on="keydown:handleKey" tabindex="0">Press a key</div> </div> // Script Directive.bootstrap(document.getElementById('app'), { handleClick : (e) => console.log('clicked', e.target), save : () => alert('Saved!'), handleFocus : (e) => console.log(e.type), // 'focus' or 'blur' handleKey : (e) => console.log(e.key), });
10. Directive.template — {{ }} interpolation
Replaces
{{ key }} placeholders in an element's innerHTML with values from a scope object. Supports dot-notation and bracket-notation paths.const scope = { username : 'Arianna', score : 42, planet : 'Earth', planets : { Earth: '3rd from the Sun' }, }; // native getElementById Directive.template(document.getElementById('tpl-output'), scope); // Real wrapper — same result, shorter on repeated use Directive.template(Real('#tpl-output'), scope); // Real('#id') === querySelector('#id') — zero difference in output // Bracket notation: {{ planets[planet] }} → "3rd from the Sun" // Dot notation: {{ planets.Earth }} → "3rd from the Sun"
HTML attribute syntax
<!-- {{ }} interpolation — works anywhere in innerHTML --> <div id="app"> <h1>Hello, {{ username }}!</h1> <p>Score: {{ score }} / {{ maxScore }}</p> <p>Planet: {{ planets[selected] }}</p> <p>Nested: {{ user.address.city }}</p> <p>Expr: {{ score > 50 ? 'pass' : 'fail' }}</p> </div> // Script Directive.bootstrap(document.getElementById('app'), { username : 'Arianna', score : 72, maxScore : 100, selected : 2, planets : ['Mercury', 'Venus', 'Earth'], user : { address: { city: 'Zürich' } }, });
HTML
{ } template interpolation — live
72
{ } placeholders replaced with scope values
11. Directive.register — custom directives
Create your own directives. They receive the element and an optional value. Register once, apply anywhere.
// Register a tooltip directive Directive.register('tooltip', { mounted(el, value) { el.title = value; el.style.cursor = 'help'; }, unmounted(el) { el.title = ''; }, }); // Register a highlight directive Directive.register('highlight', { mounted(el, color = '#fff3cd') { el.style.background = color; }, unmounted(el) { el.style.background = ''; }, }); // Apply programmatically Directive.apply('tooltip', myButton, 'Save the document'); Directive.apply('highlight', myInput, '#ffd6e8'); // Apply via HTML attributes (after bootstrap): // <button data-directive="tooltip" data-tooltip="Save">Save</button>
12. Directive.bootstrap — HTML-first declarative usage
Scan an element's subtree for
a-* attributes and automatically apply all directives. The closest thing to Vue/Angular template syntax.<!-- In your HTML --> <div a-if="user.loggedIn">Welcome, {{ user.name }}!</div> <ul a-for="item in cart"><li>{{ item.name }} — €{{ item.price }}</li></ul> <div a-show="cart.length > 0">Checkout</div> <input a-model="user.name" placeholder="Your name"> <span a-bind="textContent:user.name"></span> <button a-on="click:addToCart">Add</button> // In your script — one call wires everything const scope = { user, cart, addToCart }; // Identical — choose what reads better in context Directive.bootstrap(document.getElementById('app'), scope); // native Directive.bootstrap(Real('#app'), scope); // Real wrapper // Real('#app') calls querySelector('#app') internally — same element // Rule of thumb: use Real() when you're already in an AriannA codebase // use getElementById() when integrating with plain DOM code
Interactive playground
All four structural directives together. Shows how they compose — the list feeds into the switch, the if guards the output.
Structural directives — composed demo
if (showList)
→ hides/shows the entire list
switch (view)
for (items)
3 items
showList=true, view=grid, 3 items
Annotations (Decorators)
Class-based component authoring with TypeScript decorators. Reactive props, watchers, event emitters, and template refs.
| Property / Method | Type | Description |
|---|---|---|
| @Component(opts) | class decorator | Register as custom element. opts: { tag, extends? } |
| @Prop() | property | Reactive — change triggers render |
| @Watch(key) | method | Called when named prop changes. Args: (newVal, oldVal) |
| @Emit(event?) | method | Dispatch CustomEvent when method returns |
| @Ref() | property | Bind to DOM element inside the template |
import {{ Component, Prop, Watch, Emit, Ref }} from './arianna-ts/index.ts'; @Component({{ tag: 'user-card' }}) class UserCard extends HTMLElement {{ @Prop() name = ''; @Prop() role = ''; @Ref() nameEl!: HTMLSpanElement; @Watch('name') onNameChange(next: string) {{ if (this.nameEl) this.nameEl.textContent = next; }} @Emit('card-click') handleClick() {{ return {{ name: this.name }}; }} render() {{ return `<div><span data-ref="nameEl">${'{'}this.name{'}'}</span> — ${'{'}this.role{'}'}</div>`; }} }}
JSX Runtime
AriannA ships a full React-JSX compatible runtime. Every JSX element maps to either a
Real instance or a VirtualNode — no virtual DOM overhead unless you opt in.tsconfig.json setup
{
"compilerOptions": {
"jsx" : "react-jsx",
"jsxImportSource" : "arianna"
}
}Dual runtime — Real (default) vs Virtual
// Real mode (default) — every element is a Real instance import { Real, signal } from 'arianna'; const count = signal(0); function App() { return ( <div id="app"> <h1 class="title">AriannA</h1> <p>Count: <span>{count.get()}</span></p> <button onClick={() => count.set(count.get() + 1)}> Increment </button> </div> ); } // Virtual mode — per-file pragma at top /* @dom-render: virtual */ function SVGIcon() { return ( <svg width="40" height="40" viewBox="0 0 40 40"> <circle cx="20" cy="20" r="18" fill="#e40c88" /> </svg> ); }
Event syntax — $event and onEvent
// Both are equivalent — $ takes precedence if both present <button $click={fn}>Dollar syntax</button> <button onClick={fn}>On-prefix syntax</button> // Any event name works <input $input={(e) => val.set(e.target.value)} /> <div $mouseenter={show} $mouseleave={hide}>Hover me</div>
Fragment
// <>...</> — Real mode: DocumentFragment | Virtual mode: Fragment function Pair() { return ( <> <span>First</span> <span>Second</span> </> ); }
Custom elements
// PascalCase → kebab-case tag resolution via Core.GetDescriptor() import { MyButton } from './MyButton.ts'; const el = <MyButton label="Click me" $click={handleClick} />; // → new Real('my-button').set('label','Click me').on('click',handleClick)
setDefaultRuntime() — global switch
import { setDefaultRuntime } from 'arianna/jsx-runtime'; // Set globally at app bootstrap setDefaultRuntime('virtual'); // all JSX → VirtualNode setDefaultRuntime('real'); // back to default
h() factory — direct use
import { h, Fragment } from 'arianna/jsx-runtime'; // Without JSX transform const btn = h('button', { class: 'primary', '$click': fn }, 'Click'); const frag = h(Fragment, null, btn, h('span', null, 'text'));
Dev runtime (react-jsxdev)
// tsconfig for dev mode { "compilerOptions": { "jsx" : "react-jsxdev", "jsxImportSource" : "arianna" } } // Resolved to: arianna/jsx-dev-runtime → jsxDEV() // Same behavior as jsx() — extra debug args are ignored
Controls
50 TypeScript controls, one file each. All extend
Control. React-like API: set a property → control re-renders via requestAnimationFrame.Unified API (all controls)
// Every control follows the same pattern: const ctrl = new ControlName('#container', options); ctrl.propertyName = value; // setter → schedules re-render via rAF ctrl.on('event', handler); // typed event bus ctrl.destroy(); // remove DOM + cleanup all listeners // Example: const btn = new Button('#root', {{ variant: 'primary' }}); btn.label = 'Save'; // reactive setter btn.loading = true; // disables + shows spinner btn.on('click', () => save());
| Category | Controls | Count |
|---|---|---|
| Core | Control, Theme, Animation | 3 |
| Layout | Accordion, Card, Drawer, Modal, Panel, Splitter, Tabs | 7 |
| Navigation | Breadcrumb, Header, Menu, NavRail, Pagination, Stepper | 6 |
| Inputs | Button, Checkbox, Chip, ColorPicker, DatePicker, Dropdown, FileUpload, Radio, RangeSlider, Rating, SearchBar, Switch, TextField, TimePicker | 14 |
| Display | Avatar, Badge, Banner, Chip, DataTable, Divider, Icon, List, ProgressBar, ProgressCircular, Skeleton, Snackbar, Tag, Tooltip | 14 |
| Charts | BarChart, LineChart, PieChart | 3 |
| Data | TreeView, Table | 2 |
| Total | 50 |
Theme & CSS Tokens
All controls use CSS custom properties.
Theme ships dark and light token sets.import {{ Theme }} from './arianna-controls/core/Theme.ts'; Theme.apply('dark'); // set tokens on :root Theme.apply('light', container); // scoped to element Theme.apply('auto'); // prefers-color-scheme Theme.extend({{ '--ar-primary': '#ff6b6b' }}); Theme.inject(); // inject base CSS (once)
| Token | Dark | Light |
|---|---|---|
| --ar-bg | #0d0d0d | #ffffff |
| --ar-text | #e0e0e0 | #1a1a1a |
| --ar-primary | #7eb8f7 | #1565c0 |
| --ar-success | #4caf50 | #2e7d32 |
| --ar-warning | #ff9800 | #e65100 |
| --ar-danger | #f44336 | #c62828 |
| --ar-border | #2a2a2a | #d0d0d0 |
| --ar-radius | 5px | |
Additionals
Optional add-on modules that extend AriannA. Each is a standalone plugin registered via
Core.use(). Import from arianna-additionals/.Math
Extended mathematics — Fraction, Vector2/3/4, Matrix4, Quaternion, LinearFunction, Statistics. All original Golem.Math constants preserved.
import { Math, MathConstants, Fraction, Vector3, Statistics } from 'arianna/additionals'; // Fractions const f = new Fraction(3, 4); f.Sum(new Fraction(1, 4)).toString(); // "1/1" // 3D Vectors const v = new Vector3(1, 2, 3); const u = new Vector3(4, 5, 6); v.dot(u); // 32 v.cross(u); // Vector3(-3, 6, -3) v.length(); // √14 ≈ 3.742 // Constants MathConstants.PI; // 3.14159… MathConstants.Phi; // golden ratio 1.618… MathConstants.c; // speed of light 299792458 m/s // Statistics const data = [2, 4, 4, 4, 5, 5, 7, 9]; Statistics.mean(data); // 5 Statistics.variance(data); // 4 Statistics.stdDev(data); // 2 // Matrix4 — perspective projection const m = Matrix4.perspective(MathConstants.PI / 4, 16/9, 0.1, 1000);
Geometry
2D/3D geometry primitives — Point, Size, Angle, AABB, Ray, Rectangle, Circle, Triangle, Polygon, Sphere, Box, Cylinder, Torus, Cone. Imports from Math.ts for Vector/Matrix types.
import { Geometry, Point, Circle, AABB } from 'arianna/additionals'; const p1 = new Point(0, 0); const p2 = new Point(3, 4); p1.distanceTo(p2); // 5 const c = new Circle(p1, 10); c.area(); // π × 100 ≈ 314.16 c.contains(p2); // true (dist=5 < r=10) const box = new AABB(p1, new Point(100, 100)); box.intersects(new AABB(new Point(50,50), new Point(150,150))); // true
Animation
WAAPI helpers —
animate(), timeline(), spring physics, stagger utilities. Integrates with Signal+Sink.import { Animation } from 'arianna/additionals'; Core.use(Animation); // Animate an element with WAAPI Animation.animate(el, [ { opacity: 0, transform: 'translateY(20px)' }, { opacity: 1, transform: 'translateY(0)' }, ], { duration: 300, easing: 'ease-out', fill: 'forwards' }); // Stagger multiple elements Animation.stagger(document.querySelectorAll('.card'), { opacity: [0, 1], transform: ['translateY(12px)', 'none'] }, { duration: 250, delay: 40 } // 40ms between each );
Network
fetch/SSE/WebSocket abstractions with retry, timeout, and Signal integration.
import { Network } from 'arianna/additionals'; Core.use(Network); // Typed fetch with auto-JSON parse const data = await Network.get<{ name: string }[]>('/api/users'); // SSE stream → Signal const price = signal(0); Network.sse('/api/prices', (e) => price.set(JSON.parse(e.data))); // Any effect reading price() now auto-updates effect(() => priceEl.text(() => `€\${price.get().toFixed(2)}`));
Audio
Web Audio API toolkit — Oscillator, NoiseGenerator, Filter, Reverb, Delay, Analyser, Sequencer, AudioPlayer, AudioRecorder, MIDIEngine. Built on a shared
AudioEngine singleton with master gain and compressor.import { Audio } from 'arianna/additionals'; Core.use(Audio); // Oscillator — uses shared AudioEngine, no manual context needed const osc = new Audio.Oscillator(440, 'sine'); osc.start(0.5); // volume 0–1 setTimeout(() => osc.stop(), 1000); // Effects chain — Reverb, Delay, Filter all share the engine const reverb = new Audio.Reverb({ duration: 2.5, decay: 2.0 }); const delay = new Audio.Delay({ time: 0.3, feedback: 0.4 }); // Analyser — real-time FFT for visualizers const ana = new Audio.Analyser({ fftSize: 2048 }); const bins = ana.getFrequencyData(); // Uint8Array // Sequencer — step sequencer with callback per step const seq = new Audio.Sequencer(120, 16); // 120 BPM, 16 steps seq.on((step, time) => { /* trigger sample at step */ }); seq.start(); // MIDI input — listen for note/CC messages const midi = new Audio.MIDIEngine(); await midi.connect(); midi.on(msg => console.log(msg.type, msg.note, msg.velocity));
Video
Video toolkit — ScreenCapture, CameraCapture, VideoPlayer, VideoCompositor (multi-layer canvas overlay), GIFEncoder (LZW-compressed animated GIFs). All return MediaStream / HTMLCanvasElement / Uint8Array — works with any standard video pipeline.
import { Video } from 'arianna/additionals'; Core.use(Video); // Camera — request user media and attach to a video element const cam = new Video.CameraCapture(); const stream = await cam.start({ width: 1280, height: 720, audio: false }); document.querySelector('video').srcObject = stream; // Screen capture — getDisplayMedia wrapper const screen = new Video.ScreenCapture(); await screen.start(); screen.stop(); // VideoPlayer — controlled HTMLVideoElement with events const player = new Video.VideoPlayer(document.querySelector('#wrap')); player.load('/movie.mp4'); player.on('timeupdate', () => console.log(player.currentTime)); // VideoCompositor — overlay multiple sources on a canvas const comp = new Video.VideoCompositor(1920, 1080); comp.addLayer({ source: stream, x: 0, y: 0 }); comp.addLayer({ source: '/logo.png', x: 20, y: 20, w: 120, h: 40 }); comp.start(); // composite is comp.canvas // GIFEncoder — record canvas frames into an animated GIF const enc = new Video.GIFEncoder({ width: 320, height: 240, fps: 12 }); for (let i = 0; i < 24; i++) { enc.addFrame(canvas); } const bytes = enc.encode(); // Uint8Array → save as .gif
IO
File I/O — drag-drop, clipboard, file reader, file saver. All async with Promise/Signal bridge.
import { IO } from 'arianna/additionals'; Core.use(IO); // Drag-drop zone → Signal const dropped = signal<File | null>(null); IO.dropZone(document.getElementById('drop')!, (files) => { dropped.set(files[0] ?? null); }); // Save file await IO.save('export.json', JSON.stringify(data, null, 2));
LaTeX
LaTeX → MathML renderer for inline math in HTML. Supports fractions, superscripts, Greek letters, operators.
import { Latex } from 'arianna/additionals'; Core.use(Latex); // Render LaTeX to MathML string const ml = Latex.render('\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}'); document.getElementById('math')!.innerHTML = ml;
Three.js Bridge
Three.js integration — reactive camera, OrbitControls binding, scene/renderer lifecycle.
import { Three } from 'arianna/additionals'; Core.use(Three); const app = Three.scene(document.querySelector('canvas')!); app.scene.add(new THREE.Mesh( new THREE.BoxGeometry(), new THREE.MeshStandardMaterial({ color: '#e40c88' }) )); app.start(); // requestAnimationFrame loop
Changelog
v1.2.0 — April 2026
- Signal+Sink fine-grain reactivity:
signal(),signalMono(),sinkText(),sinkClass(),effect(),computed(),batch(),untrack() - Real/VirtualNode fluent Signal API:
.text(),.textMono(),.attr(),.cls(),.prop(),.style(),.bind(),.destroy() - Rule v2:
SelectorObjectconstructor, nested @-rules,@keyframes,@pagemargin-box,CssState - Stylesheet v2:
Sheet.Less()indentation-based Less/Stylus parser,SheetES5, PascalCase aliases, async URL fetch - JSX Runtime: dual Real/Virtual mode,
$event+onEventsyntax, Fragment,jsxDEV(),setDefaultRuntime() - Additionals: AriannA.ts barrel, Math.ts, Geometry.ts (import fix), Animation, Network, Audio, Video, IO, Latex, Three bridges
- CLI v1.2.0:
new,generate,serve,build,typecheck,bench,infocommands - Control v2:
_state,_fire,_listen,_onDestroy,_mounted,get()public - Table v2:
_expandedRows,_renderRows, Web Worker, LRU cache, column resize, export CSV - Types:
arianna.d.ts,arianna-globals.d.tsglobal window declarations - Context:
asSignal()method — reactive Signal from context value - tsconfig: ES2022 target,
allowImportingTsExtensions,moduleResolution: bundler - Benchmark: swap-rows −38%, select-row −22%, script −97%, bundle 3× smaller vs Solid v1.9.3
- 400+ tests across 21 panels
v1.0.0 — April 2026
- Initial TypeScript release. Complete migration from JavaScript (2012–2024).
- Core: frozen SemVer version, idempotent plugin registration, full namespace registry
- Real, Virtual, State, Observable, Rule, Stylesheet, Context, Directive, Component
- 50 UI controls: TreeView, Table, Accordion, Sidebar, Charts, and more
- Dual AGPL + Commercial license
License
AriannA is dual-licensed — choose the license that fits your use case.
MIT License (open source)
Free for any open source or commercial project. Attribution required. See
LICENSES/MIT.txt.Commercial License
For closed-source or enterprise products requiring no attribution or additional support terms. Contact the author for pricing.
Author
Riccardo Angeli — Senior Software Architect & UI TechLead
Zurich, Switzerland · riccardo.angeli@arianna.dev
github.com/Riccardo-Angeli/AriannA-Js · npmjs.com/package/arianna
Zurich, Switzerland · riccardo.angeli@arianna.dev
github.com/Riccardo-Angeli/AriannA-Js · npmjs.com/package/arianna
♡ Dedication
A mia figlia Arianna.
Hai ispirato questo progetto dal primo giorno. Ti voglio bene. — Papà
Hai ispirato questo progetto dal primo giorno. Ti voglio bene. — Papà
Thanks
Alessandro De Rossi ·
Simone Ricucci ·
Alessandro Ligi ·
Marco Ciurcina ·
Aurora Castello ·
Massimiliano Ceaglio ·
Andrea Giammarchi ·
Francesco Maria Turno
Copyright
© Riccardo Angeli 2012–2026. All rights reserved.
TreeView
Hierarchical tree with lazy loading, keyboard navigation, search, checkbox mode, and drag & drop.
TreeView — Live Demo no selection
click a node to see the event →
| Property / Method | Type | Description |
|---|---|---|
| tree.nodes = [] | TreeNode[] | Set root nodes — reactive, auto-renders |
| tree.on('select',cb) | event | { node, selected } fired on click or keyboard |
| tree.on('expand',cb) | event | { node } fired when a node opens |
| tree.on('load',cb) | event | { node, resolve } fired for lazy nodes |
| tree.on('drop',cb) | event | { sourceId, targetId } drag & drop |
| tree.expand(id) | method | Expand a node |
| tree.collapse(id) | method | Collapse a node |
| tree.select(id) | method | Select a node programmatically |
| tree.search(q) | method | Filter visible nodes |
| tree.getSelected() | method | Returns TreeNode[] |
| tree.expandAll() / collapseAll() | method | Bulk expand/collapse |
| tree.destroy() | method | Remove and cleanup |
import {{ TreeView, TreeViewCSS }} from './arianna-controls/TreeView.ts'; const s = document.createElement('style'); s.textContent = TreeViewCSS; document.head.appendChild(s); const tree = new TreeView('#sidebar', {{ selectable : 'single', // 'none' | 'single' | 'multi' icons : true, checkboxes : false, keyboard : true, draggable : false, }}); tree.nodes = [ {{ id: '1', label: 'Root', icon: '📁', children: [ {{ id: '1.1', label: 'Child A', icon: '📄' }}, {{ id: '1.2', label: 'Child B', lazy: true }}, // lazy ]}}, ]; tree.on('select', ({{ node }}) => console.log(node.label)); tree.on('load', ({{ node, resolve }}) => fetchChildren(node.id).then(resolve)); tree.expand('1'); tree.search('child');
Table
Data grid: sorting, filtering, pagination, Web Worker offloading, LRU cache. Everything optional — one class.
Table — 200 rows, sortable, paginated (10/page)
sort a column or click a row →
| Property / Method | Type | Description |
|---|---|---|
| table.rows = [] | Row[] | Set client-side data — reactive setter |
| table.append(rows) | method | Append rows without full reload |
| table.reload() | method | Re-run fetch or re-process local data |
| table.clear() | method | Empty the table |
| table.getSelected() | method | Returns selected Row[] |
| table.exportCsv(name) | method | Download current view as CSV |
| table.on('select',cb) | event | { rows, row } |
| table.on('sort',cb) | event | { column, sort } |
| table.on('page',cb) | event | { page, pageSize, total } |
Options
| Option | Default | Description |
|---|---|---|
| columns | required | Column definitions array |
| pageSize | 25 | Rows per page. 0 = no paging (show all) |
| selectable | 'none' | 'none' | 'single' | 'multi' |
| worker | false | Offload sort+filter to Web Worker (Blob URL) |
| cache | false | LRU cache for remote pages (server-side mode) |
| fetch | undefined | Async fn for server-side: (params) → { rows, total } |
| searchable | true | Global search bar |
| exportCsv | false | CSV export button |
// Client-side with Web Worker const table = new Table('#root', {{ columns: [ {{ key: 'name', label: 'Name', sortable: true, width: 200 }}, {{ key: 'role', label: 'Role', sortable: true }}, {{ key: 'status', label: 'Status', render: (v) => `<span style="color:${'{'}v==='active'?'green':'gray'{'}'}">${'{'}v{'}'}</span>` }}, ], pageSize : 25, selectable: 'multi', worker : true, exportCsv : true, }}); table.rows = myData; // Server-side with cache const table = new Table('#root', {{ columns, cache: true, fetch: async ({{ page, pageSize, sort, filter }}) => {{ const r = await api.get(`/data?page=${'{'}page{'}'}&size=${'{'}pageSize{'}'}`); return {{ rows: r.data, total: r.total }}; }}, }});
Expandable rows
Enable row expansion to show nested detail — like the Horizon Explorer pattern. Set
expandable: true and provide a rowContent async function that returns HTML or an HTMLElement.| Option | Type | Description |
|---|---|---|
| expandable | boolean | Enable expandable rows. Default: false |
| rowContent | async (row) → string | HTMLElement | Called on expand — return the nested content |
| expandSingle | boolean | Only one row open at a time. Default: false |
const table = new Table('#root', { columns: [ { key: 'name', label: 'Name', sortable: true }, { key: 'capital', label: 'Capital' }, { key: 'region', label: 'Region' }, ], // ── Expandable rows ───────────────────────────────────── expandable : true, expandSingle: true, // collapse others when one opens // Async — called when user clicks ▶ on a row rowContent: async (row) => { const res = await fetch(`/api/provinces/${'{'}row.id{'}'}`); const data = await res.json(); return `<div style="padding:10px 0 10px 32px;border-left:2px solid var(--ar-primary)"> <strong>Provinces of ${'{'}row.name{'}'}</strong> <ul>${'{'}data.map(p => `<li>${'{'}p.name{'}'}</li>`).join(''){'}'}</ul> </div>`; }, }); table.rows = myData; // Programmatic control table.expandRow(0); // expand row at index 0 table.collapseRow(0); // collapse table.collapseAll(); // collapse all table.on('expand', ({ row, index }) => console.log('opened:', row)); table.on('collapse', ({ row, index }) => {{}});
Live demo — expandable rows
Table with expandable rows — click ▶ on any row
click ▶ to expand a row →
Layout Controls
7 layout controls: Accordion, Card, Drawer, Modal, Panel, Splitter, Tabs.
Accordion
Tabs
Cards
Modal & Drawer
const acc = new Accordion('#root', {{ multiple: false }}); acc.items = [ {{ id: '1', label: 'Panel 1', content: '<p>Content A</p>', open: true }}, {{ id: '2', label: 'Panel 2', content: '<p>Content B</p>' }}, ]; acc.on('change', ({{ id, open }}) => console.log(id, open)); const tabs = new Tabs('#root', {{ variant: 'line' }}); tabs.items = [ {{ id: 'a', label: 'Tab A', content: '...' }}, {{ id: 'b', label: 'Tab B', content: '...', badge: 3 }}, ]; tabs.on('change', ({{ id }}) => console.log(id)); const modal = new Modal({{ size: 'md' }}); modal.title = 'Confirm'; modal.content = '<p>Are you sure?</p>'; modal.open(); modal.on('close', () => {{}});
Input Controls
14 input controls. All expose reactive setters and typed events.
Buttons
TextField
Switch & Checkbox & Radio
Dropdown
Rating & RangeSlider
SearchBar
const btn = new Button('#r', {{ variant: 'primary' }}); btn.label = 'Save'; btn.loading = true; btn.on('click', () => save()); const tf = new TextField('#r', {{ label: 'Email', type: 'email' }}); tf.value = '[email protected]'; tf.error = 'Invalid format'; tf.on('change', ({{ value }}) => validate(value)); const sw = new Switch('#r', {{ label: 'Dark mode' }}); sw.checked = true; sw.on('change', ({{ checked }}) => applyTheme(checked)); const dd = new Dropdown('#r', {{ searchable: true }}); dd.options = [{{ value: 'ch', label: 'Switzerland' }}]; dd.value = 'ch'; dd.on('change', ({{ value }}) => {{}});
Display Controls
14 display controls: badges, avatars, progress, skeleton, snackbar, tooltip, and more.
Badge & Tag
Avatar
ProgressBar
Skeleton
Snackbar — click to fire
const badge = new Badge('#r', {{ variant: 'success' }}); badge.label = 'Active'; const av = new Avatar('#r', {{ size: 40, shape: 'circle' }}); av.name = 'Riccardo Angeli'; // → 'RA' av.status = 'online'; const pb = new ProgressBar('#r', {{ label: 'Upload', showValue: true }}); pb.value = 65; pb.indeterminate = true; // animated Snackbar.show('File saved!', {{ variant: 'success', duration: 3000 }});
Charts
3 SVG charts that integrate with AriannA Observable for reactive data binding.
BarChart
LineChart
PieChart (donut)
const bar = new BarChart('#root', {{ label: 'Monthly Sales' }}); bar.data = [{{ label: 'Jan', value: 120 }}, {{ label: 'Feb', value: 95 }}]; bar.on('click', ({{ bar }}) => console.log(bar.label)); const line = new LineChart('#root', {{ area: true, smooth: true }}); line.data = [{{ label: '00:00', value: 12 }}, {{ label: '01:00', value: 34 }}]; const pie = new PieChart('#root', {{ donut: true, legend: true }}); pie.data = [{{ label: 'EU', value: 42 }}, {{ label: 'US', value: 35 }}, {{ label: 'APAC', value: 23 }}]; pie.on('click', ({{ slice }}) => {{}});
Sidebar
Resizable, collapsible, accordion navigation panel. Supports
orientation: 'left' | 'right' — controls which edge the border and resize
handle sit on, and which direction the panel collapses toward.Constructor
import { Sidebar, SidebarCSS } from './arianna-controls/Sidebar.ts'; const style = document.createElement('style'); style.textContent = SidebarCSS; document.head.appendChild(style); const sidebar = new Sidebar('#shell', { orientation : 'left', // 'left' | 'right' width : 260, minWidth : 160, maxWidth : 480, resizable : true, collapsible : true, collapsed : false, collapsedWidth: 48, searchable : true, showToggle : true, persist : true, storageKey : 'my-app-nav', });
orientation: 'left' | 'right'
| Value | Border | Resize handle | Active indicator | Collapse direction |
|---|---|---|---|---|
| 'left' | border-right | right edge of panel | left border of item | slides left |
| 'right' | border-left | left edge of panel | right border of item | slides right |
Sections & items
sidebar.sections = [
{
id: 'core', label: 'Core Modules', open: true,
items: [
{ id: 'real', label: 'Real', icon: '🌐' },
{ id: 'virtual', label: 'Virtual', icon: '🧩', badge: 'new' },
{ id: 'state', label: 'State', icon: '⚡', disabled: false },
],
},
];
sidebar.active = 'real';API reference
| Property / Method | Type | Description |
|---|---|---|
| sidebar.sections = [] | SidebarSection[] | Set all sections (replaces existing) |
| sidebar.active = 'id' | string | Set the active item by id |
| sidebar.collapse() | this | Collapse to icon-only mode |
| sidebar.expand() | this | Restore full width |
| sidebar.toggle() | this | Toggle collapsed state |
| sidebar.setWidth(n) | this | Set width in px (clamped to minWidth/maxWidth) |
| sidebar.openSection(id) | this | Open an accordion section |
| sidebar.closeSection(id) | this | Close an accordion section |
| sidebar.toggleSection(id) | this | Toggle a section |
| sidebar.search(q) | this | Filter items by query string ('' to clear) |
| sidebar.on('select', cb) | event | { item, section } — item clicked |
| sidebar.on('resize', cb) | event | { width } — drag resize |
| sidebar.on('collapse', cb) | event | { collapsed: boolean } |
| sidebar.destroy() | void | Remove element and all listeners |
SidebarItem interface
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| label | string | Display text |
| icon? | string | Emoji or text icon |
| badge? | string | number | Badge shown after label |
| disabled? | boolean | Whether item is disabled |
| class? | string | Extra CSS class(es) on item button |
| data? | unknown | Arbitrary user data |
Two — 2D Vector Engine
SVG and Canvas 2D renderer with scene graph, animation, D3-style scales. Zero dependencies.
Interactive Demo
Two.ts — SVG Renderer live
Quick start
import Two from './additionals/Two.ts'; const two = Two.createRenderer('#app', { mode: 'svg', width: 600, height: 400 }); const stage = two.stage; const c = new Two.Circle({ cx: 100, cy: 100, r: 60, style: { fill: '#e40c88' } }); stage.add(c); two.render(); // Animate new Two.Tween(c, { r: 100 }, 600).easing('easeOutBounce').start(); const loop = new Two.AnimationLoop(dt => two.render()).start();
API
| Class | Description |
|---|---|
| SVGRenderer | Live SVG DOM, gradients, pointer events |
| CanvasRenderer | Canvas2D, pixel ratio, toPNG/toJPEG |
| Circle · Rect · Ellipse | Basic shapes with style, transforms |
| Line · Polygon · Path | Line and path geometry |
| Text · Image | Text rendering, image embedding |
| Group2D · Stage2D | Scene graph containers |
| PathBuilder | Fluent path: moveTo · lineTo · spline · arc |
| scaleLinear · scaleBand | D3-style scales with ticks/invert |
| Tween<T> | Property interpolation, 18 easing functions |
| Timeline | Sequenced tweens with delay |
| Export2D | toSVG · downloadSVG · downloadPNG · toPDF |
Three — 3D Engine
WebGPU-primary + WebGL2 fallback 3D engine. PBR shading, CSG, STL/OBJ/GLB import/export. Zero dependencies.
Interactive Demo
Three.ts — 3D live (Canvas fallback in docs)
Quick start
import Three from './additionals/Three.ts'; const renderer = new Three.WebGPURenderer(canvas); await renderer.init(); const scene = new Three.Scene(); const camera = new Three.PerspectiveCamera(60, canvas.width/canvas.height, 0.1, 1000); camera.position.set(0, 1, 3); const mesh = new Three.Mesh( new Three.Box(1, 1, 1), new Three.PBRMaterial({ color: '#e40c88', roughness: 0.3 }) ); scene.add(mesh); const orbit = new Three.OrbitControls(camera, canvas); new Three.AnimationLoop(dt => { mesh.rotation.y += dt * 0.001; renderer.render(scene, camera); }).start();
API
| Module | Contents |
|---|---|
| Geometry | Box · Sphere · Cylinder · Plane · Cone · Torus · BufferGeometry |
| Materials | Basic · Lambert · Phong · PBR (GGX/Smith) · Wireframe |
| Lights | Ambient · Directional · Point · Spot (16-light GPU buffer) |
| Scene graph | Object3D · Mesh · Group · Camera · Scene |
| Renderers | WebGPURenderer (WGSL PBR) · WebGL2Renderer (fallback) |
| CSG | union · subtract · intersect (BSP tree) |
| Modifiers | Subdivision · Decimate · Mirror · Array · Bend · Twist · Bevel |
| I/O | STL (binary+ASCII) · OBJ · GLB (glTF 2.0) |
| Controls | OrbitControls (mouse + touch + damping) |
AI — Machine Learning Engine
WebGPU compute-accelerated tensor operations, neural network layers, Transformer, and Tokenizer. Zero dependencies.
Interactive Demo — Markov chain text generation
AI.ts — MarkovChain live
Quick start
import AI from './additionals/AI.ts'; // Tensor operations const a = AI.Tensor.randn([64, 128]); const b = AI.Tensor.randn([128, 64]); const c = await a.matmulGPU(b); // WebGPU compute shader // Sequential model const model = new AI.Sequential() .add(new AI.Dense(64, { activation: 'relu', inputDim: 10 })) .add(new AI.Dense(32, { activation: 'relu' })) .add(new AI.Dense(1, { activation: 'sigmoid' })); model.compile({ loss: 'binaryCrossEntropy', optimizer: 'adam', lr: 0.001 }); const losses = await model.fit(xTrain, yTrain, { epochs: 20, batchSize: 32, verbose: true });
API
| Class | Description |
|---|---|
| Tensor | N-dim Float32Array: add/sub/mul/matmul, relu/sigmoid/softmax/layerNorm, reshape/slice/concat |
| Dense · Flatten · Dropout | Standard fully-connected layers |
| BatchNorm · LayerNorm | Normalization layers |
| Embedding · Attention · GRU | Sequence model layers |
| Sequential | Stack of layers with Adam optimizer + backprop |
| Transformer | Encoder-only, sinusoidal PE, causal mask, top-K sampling |
| Tokenizer | BPE-like char/word vocab, encode/decode, special tokens |
| MarkovChain | n-gram text generation |
| AIUtils | oneHot · normalize · standardize · shuffle · trainTestSplit · accuracy |
Finance — Quantitative Engine
Technical indicators, portfolio analytics, Black-Scholes, Monte Carlo (WebGPU-accelerated), and event-driven backtesting. Zero dependencies.
Interactive Demo
Finance.ts — Black-Scholes pricer
Quick start
import Finance from './additionals/Finance.ts'; // Technical indicators const prices = [100, 102, 98, 105, 110, 108, 115]; const sma14 = Finance.Indicators.sma(prices, 14); const rsi14 = Finance.Indicators.rsi(prices, 14); const { macd, signal, histogram } = Finance.Indicators.macd(prices); // Black-Scholes const price = Finance.BlackScholes.price(100, 105, 0.25, 0.05, 0.2, 'call'); const greeks = Finance.BlackScholes.greeks(100, 105, 0.25, 0.05, 0.2); // GPU Monte Carlo (1M paths) const { price: mcPrice, stdError } = await Finance.MonteCarlo.optionGPU(100, 105, 0.25, 0.05, 0.2, 1_000_000);
API
| Namespace | Contents |
|---|---|
| Finance.Indicators | SMA · EMA · WMA · DEMA · TEMA · RSI · MACD · Bollinger · ATR · Stochastic · VWAP · OBV · CCI · WilliamsR · ADX |
| Finance.Portfolio | returns · logReturns · sharpe · sortino · maxDrawdown · calmar · minVariance · maxSharpe · covarianceMatrix |
| Finance.BlackScholes | price · greeks · impliedVol · binomialTree |
| Finance.MonteCarlo | option (CPU) · optionGPU (WebGPU 1M+ paths) · gbmPaths |
| Finance.Bachelier | Normal Black price · greeks · impliedVol · fromBlackVol (lognormal→normal vol conversion) |
| Finance.Heston | Stochastic vol pricing · calibrate(surface) — semi-analytical characteristic function |
| Finance.Backtest | run(bars, strategy, capital, commission) → trades · equity · sharpe · winRate |
Docs — Document Engine
Read and write DOCX, XLSX, PPTX, PDF, CSV, SVG — pure TypeScript, zero dependencies, no server required.
API
| Method | Description |
|---|---|
| Docs.read(src) | Auto-detect format from File/URL/ArrayBuffer |
| Docs.download(doc, name) | Trigger browser download |
| Docs.docx.create(opts) | Create Word document with paragraphs, tables, images |
| Docs.xlsx.create(opts) | Create Excel workbook with sheets and formulas |
| Docs.xlsx.fromArray(rows) | Quick 2D array → XLSX |
| Docs.pptx.create(opts) | Create PowerPoint presentation with slides |
| Docs.pdf.create(opts) | Create PDF 1.4 with pages, text, images |
| Docs.csv.parse(text) | RFC 4180 CSV → string[][] |
| Docs.svg.create(w,h) | SVG document builder |
Video — Capture & Composition
Screen and camera capture, multi-layer compositor, animated GIF encoder (pure LZW). Zero dependencies.
API
| Class | Description |
|---|---|
| ScreenCapture | getDisplayMedia → MediaRecorder → WebM Blob |
| CameraCapture | getUserMedia → record, mountPreview() |
| VideoPlayer | Fluent <video> wrapper: src · play · seek · captureFrame() |
| VideoCompositor | Canvas2D multi-layer (video/image/text/color), record() |
| GIFEncoder | addFrame(canvas, delay) → encode() → Uint8Array (LZW) |
| VideoUtils | download · blobToDataURL · canvasToGIF |
Audio — Web Audio Engine
AudioContext wrapper with synthesis, effects chain, step sequencer, MIDI. Zero dependencies.
API
| Class | Description |
|---|---|
| AudioEngine | AudioContext, master GainNode, compressor, resume/suspend |
| AudioPlayer | Load URL/ArrayBuffer/Blob, play/stop, volume, loop, playbackRate |
| AudioRecorder | getUserMedia microphone → MediaRecorder → WebM Blob |
| Oscillator | sine/square/sawtooth/triangle, frequency, detune, volume |
| NoiseGenerator | white/pink/brown noise via ScriptProcessor |
| Reverb · Delay · Filter | Convolution reverb, feedback delay, BiquadFilter |
| Analyser | FFT spectrum, waveform, peak, RMS |
| Sequencer | 16-step, BPM clock, pattern scheduling |
| MIDIEngine | Web MIDI API, noteOn/Off/CC, noteToFreq, noteToName |
IO — File, Network & Storage
File open/download/drag-drop, HTTP client with retries, SSE, WebSocket with reconnect, reactive LocalStore, IndexedDB wrapper, Clipboard, Web Share. Zero dependencies.
API
| Class | Description |
|---|---|
| FileIO | open(multi, accept) · readText/Binary/DataURL · download · dropZone |
| FSAccess | File System Access API: readFile · writeFile · openDirectory |
| Http | get/post/put/del with retries, timeout, interceptors |
| SSE | EventSource wrapper with on() handler |
| WebSocketIO | Reconnect, message queue, JSON auto-parse |
| LocalStore<T> | Typed localStorage + reactive subscribe() |
| IDBIO | IndexedDB promise: open · put · get · delete · getAll |
| Clipboard | readText/writeText · readImage/writeImage |
| Share | Web Share API: share(title, text, url, files) |
Math — Mathematical Library
Migrated from Golem.Math (2012). All original classes preserved + Vector2/3/4, Quaternion, Matrix4, Statistics.
Contents
| Class | Description |
|---|---|
| MathConstants | 32 mathematical + physical constants (Phi, Avogadro, Planck, c, G…) |
| Fraction | Exact rational arithmetic: Sum, Subtract, Multiply, Divide |
| Vector2/3/4 | Full N-dim operations: add, cross, dot, normalize, lerp |
| Matrix4 | perspective, lookAt, translate, rotate, scale, inverse |
| Quaternion | 3D rotation, slerp, fromEuler, toMatrix4 |
| Complex | Complex numbers: add, mul, div, magnitude, conjugate, polar |
| Statistics | mean, variance, stdDev, median, mode, correlation |
Geometry — Geometric Library
Migrated from Golem.Geometry (2012). Full Angle/Rotation system, shapes, solids, collision detection, ray casting.
Contents
| Class | Description |
|---|---|
| Angle | 10 unit systems: Radians, Degrees, Turns, Grads, Mils, Hours… + full trig |
| Matrix | NxN: LUP, QR, EigenValues, EigenVectors, Determinant |
| Transform | CSS/3D: Translate, Scale, Rotate, Skew, Reflect |
| Shapes | Rectangle, Line, Curve, Path, Triangle, Circle, Plane, Polygon |
| Solids | Frustum, TetraHedron, Box, Sphere, Octahedron, Torus, Cylinder, Tube |
| AABB | Axis-aligned bounding box, containsPoint, intersects |
| Ray | Ray casting, intersectAABB, intersectTriangle, intersectPlane |
Data — Structures & Models
Essential data structures and abstract models. DAG, FSM, Trie, PriorityQueue, LRUCache, SegmentTree, DisjointSet, ObservableMap/Array. Zero dependencies.
Interactive Demo — FSM
FSM — Traffic light state machine
red
valid: —
Interactive Demo — DAG
DAG — topological sort
Interactive Demo — Trie autocomplete
Trie — autocomplete
API
| Class | Description |
|---|---|
| DAG<T> | Directed Acyclic Graph: addEdge (cycle detection) · topoSort · ancestors · descendants |
| Graph | Weighted directed/undirected: BFS · DFS · dijkstra · aStar |
| FSM<S,E> | Finite State Machine: guards · actions · history · undo · on(transition) · toDOT() |
| Trie<V> | Prefix tree: insert · get · autocomplete · delete · longestPrefix |
| PriorityQueue<T> | Binary min-heap with custom comparator: push · pop · peek · toSortedArray |
| Deque<T> | Double-ended queue O(1): pushFront/Back · popFront/Back |
| LRUCache<K,V> | Least-Recently-Used cache O(1) get/put |
| SegmentTree | Range sum/min/max with lazy propagation: query(l,r) · update(i,v) |
| DisjointSet | Union-Find with path compression: union · find · connected · componentCount |
| ObservableMap<K,V> | Map with reactive subscribe(fn) → unsubscribe |
| ObservableArray<T> | Array with reactive splice/push/pop events |
Quick start
import Data from './additionals/Data.ts'; // DAG const dag = new Data.DAG<string>(); dag.addEdge('A', 'B').addEdge('B', 'C').addEdge('A', 'C'); console.log(dag.topoSort()); // ['A','B','C'] // FSM const fsm = new Data.FSM('idle', [ { from: 'idle', event: 'start', to: 'running' }, { from: 'running', event: 'pause', to: 'paused' }, { from: 'paused', event: 'resume', to: 'running' }, { from: '*', event: 'stop', to: 'idle' }, ]); fsm.send('start'); console.log(fsm.state); // 'running' // Trie const trie = new Data.Trie<number>(); ['apple','application','apply','banana'].forEach((w,i) => trie.insert(w,i)); console.log(trie.autocomplete('app')); // ['apple','application','apply'] // Priority Queue const pq = new Data.PriorityQueue<number>(); [5,1,3,2,4].forEach(n => pq.push(n)); console.log(pq.pop()); // 1 (min-heap)
Finance Components — Overview
11 pure-SVG/HTML financial widgets. Zero dependencies. Each is a standalone file in
components/finance/.CandlestickChart
LineChart
DepthChart
HeatmapChart
PortfolioDonut
PnLChart
RiskGauge
OrderBook
Screener
Sparkline
AlertBadge
Import
// Individual (tree-shakeable) import { CandlestickChart } from './components/finance/CandlestickChart.ts'; // Bundle import FinanceComponents from './components/finance/index.ts'; const { CandlestickChart, OrderBook, RiskGauge } = FinanceComponents;
CandlestickChart
OHLCV candlestick + volume bars. Pure SVG.
Live demo
const chart = new CandlestickChart('#container', { width: 700, height: 400, showVolume: true, bullColor: '#26a69a', bearColor: '#ef5350' }); chart.render(bars); // Bar[] = { open, high, low, close, volume }
LineChart
Multi-series line chart with legend. Pure SVG.
Live demo
const chart = new LineChart('#container', { width: 600, height: 300 }); chart.render([ { name: 'AAPL', data: [150, 158, 155, 162] }, { name: 'MSFT', data: [280, 290, 285, 295] }, ]);
DepthChart
Cumulative bid/ask order book depth chart. Pure SVG.
Live demo
const chart = new DepthChart('#container', { width: 600, height: 300 }); chart.render( bids, // [price, size][] — sorted descending by price asks // [price, size][] — sorted ascending by price );
HeatmapChart
Correlation / sector heatmap with color interpolation. Pure SVG.
Live demo
const chart = new HeatmapChart('#container', { width: 600, height: 400 }); chart.render( ['AAPL', 'MSFT', 'GOOG', 'AMZN'], // NxN correlation matrix (-1..1) [ [1.0, 0.7, 0.6, 0.5], [0.7, 1.0, 0.8, 0.6], [0.6, 0.8, 1.0, 0.7], [0.5, 0.6, 0.7, 1.0], ] );
PortfolioDonut
Asset allocation donut chart with percentage labels. Pure SVG.
Live demo
const donut = new PortfolioDonut('#container', { size: 300 }); donut.render([ { label: 'Equities', value: 45 }, { label: 'Bonds', value: 25 }, { label: 'Real Estate', value: 15 }, { label: 'Commodities', value: 10 }, { label: 'Cash', value: 5 }, ]);
PnLChart
Profit & loss bar chart, green/red coloring. Pure SVG.
Live demo
const pnl = new PnLChart('#container', { width: 500, height: 250 }); pnl.render([ { label: 'Q1', pnl: 12500 }, { label: 'Q2', pnl: -3800 }, { label: 'Q3', pnl: 8200 }, { label: 'Q4', pnl: 15700 }, ]);
RiskGauge
Semi-circular gauge with color-coded risk zones. Pure SVG.
Live demo
const gauge = new RiskGauge('#container', { size: 200 }); gauge.render( 25, // value 0, 100, // min, max 'Volatility' // label ); // Auto color: green <33% · amber 33-66% · red >66%
OrderBook
Bid/ask ladder table with mid price and spread. Pure HTML.
Live demo
const book = new OrderBook('#container'); book.render( bids, // [price, size][] — descending asks, // [price, size][] — ascending 10 // depth (rows per side) );
Screener
Filterable instrument table with formatted columns. Pure HTML.
Live demo
const screen = new Screener('#container'); screen.render( [ { symbol: 'AAPL', price: 175.32, change: 2.4, volume: 52_300_000 }, { symbol: 'MSFT', price: 412.10, change: -0.8, volume: 28_700_000 }, { symbol: 'GOOG', price: 142.80, change: 1.1, volume: 19_400_000 }, ], ['symbol', 'price', 'change', 'volume'] // optional column order );
Sparkline
Mini inline price sparkline. Auto-colors by direction. Pure SVG.
Live demo
const spark = new Sparkline('#container'); spark.render( [100, 102, 98, 105, 110, 108, 115], { width: 100, height: 30 } ); // Auto-colored: green if last >= first, red otherwise. // Override with opts.color: '#e40c88'.
AlertBadge
Price alert badge: neutral / info / warning / danger. Pure HTML.
Live demo
const badge = new AlertBadge('#container'); badge.render('AAPL +2.4%', 'info'); badge.render('TSLA -8.1%', 'danger', 'stop-loss'); badge.render('BTC volatility','warning', 'high'); badge.render('Market closed', 'neutral'); // Levels: 'neutral' | 'info' | 'warning' | 'danger'
2D Modifiers
Interactive wrapper behaviors for any HTML element. Accept
Real | HTMLElement | string (CSS selector) | Array.Mover — drag-to-move (axis lock, snap, bounds)
Drag any element with axis lock, independent
snapX / snapY grid, custom bounds and pointer-event support (mouse + touch + pen). Public mass / damping / stiffness fields are reserved for the upcoming physics engine.Mover live demo
Resizer — 8-handle drag resize
Resizer live demo
Rotator — drag-to-rotate (snap 15°)
Rotator live demo
Skewer — skewX / skewY
Skewer live demo
Rounder — border-radius control (uniform or per-corner)
Single uniform handle with
r / radius, or four independent handles when any of topLeft / topRight / bottomLeft / bottomRight is set.Rounder live demo
Reflector — flipX / flipY
Reflector live demo
| Class | Behavior | Callback |
|---|---|---|
| Resizer | 8-direction resize handles, min/max size | onResize(el, w, h) |
| Rotator | Drag-to-rotate handle + angle snap | onRotate(el, angle) |
| Skewer | skewX/Y via drag, maxAngle | onSkew(el, sx, sy) |
| Rounder | border-radius drag control, uniform r or per-corner topLeft/topRight/bottomLeft/bottomRight | onRound(el, r, corner) |
| Reflector | flipX/flipY buttons, animated CSS scale | — |
| Mover | drag-to-move with axis lock, snapX/snapY, bounds, mass/damping/stiffness | onStart, onMove, onSnap, onEnd |
import { Resizer, Rotator, Rounder, Mover } from './components/modifiers/2D/index.ts'; const r = new Resizer('#my-box', { minWidth: 80, handleColor: '#e40c88' }); r.onResize((el, w, h) => console.log(w, h)); const t = new Rotator('#my-box', { snap: 15 }); // snap to 15° t.onRotate((el, angle) => console.log(angle)); // Rounder: per-corner mode const ro = new Rounder('#my-box', { topLeft: 20, bottomRight: 40 }); ro.onRound((el, radius, corner) => console.log(corner, radius)); // Mover: drag with snap to 10×10 grid, bounded to parent const mv = new Mover('#my-box', { snapX: 10, snapY: 10, bounds: 'parent' }); mv.onMove((el, x, y) => console.log(x, y));
3D Modifiers
Geometry and transform modifiers for Three.ts meshes. All implement
.apply() + enable/disable/destroy.Geometry
| Class | Description |
|---|---|
| SubdivisionModifier | Midpoint subdivision surface (n iterations) |
| DecimateModifier | Triangle decimation to a target ratio |
| BevelModifier | Edge bevel / chamfer along face normals |
| MirrorModifier | Mirror on X/Y/Z + optional vertex weld |
| ArrayModifier | Linear or radial instance array |
| BendModifier | Bend geometry along an axis |
| TwistModifier | Twist geometry around an axis |
| WaveModifier | Sinusoidal displacement (animatable) |
| InflateModifier | Expand along vertex normals |
| SmoothModifier | Laplacian smoothing (n iterations) |
Transform / Scene
| Class | Description |
|---|---|
| DragModifier | Mouse drag on XZ/XY/YZ plane |
| SnapModifier | Snap position + rotation to grid |
| LODModifier | Swap geometry by camera distance |
| FadeModifier | Opacity fade by distance (update per frame) |
| BillboardModifier | Always face camera, per-axis lock |
import { SubdivisionModifier, WaveModifier } from './components/modifiers/3D/index.ts'; new SubdivisionModifier(mesh, 2).apply(); // 2 iterations const wave = new WaveModifier(mesh, { amplitude: 0.3, frequency: 3 }); let t = 0; loop(() => { wave.apply(t += 0.016); }); // animate
PianoRoll
Full MIDI piano-roll editor with vertical keyboard, scrollable beat grid, draw/select/erase tools, snap-to-grid, velocity lane, and live MIDI event emission for external synths or audio engines. Note model is plain JSON — round-trippable.
Live demo
Switch tools with the toolbar (or D / S / E). Click on the grid to draw notes (drag to extend), click a note to select+move, drag the right edge to resize. Space toggles play. Delete removes selection. Click any keyboard key on the left to trigger a MIDI note manually.
Note model (JSON)
Every note is plain data. Round-trippable JSON for save/load, transfer to MIDI engines, or AI-generated sequences.
interface PianoRollNote { pitch: number; // MIDI 0-127 (C-1 to G9) start: number; // in beats (1 beat = quarter note) duration: number; // in beats velocity: number; // 0-127 channel?: number; // MIDI channel 1-16 } interface ExportedSequence { bpm: number; bars: number; timeSig: [number, number]; notes: PianoRollNote[]; }
Component usage
import { PianoRoll } from 'ariannajs/components/audio/PianoRoll'; const pr = new PianoRoll('#root', { bpm: 120, bars: 8, pitchLow: 36, // C2 pitchHigh: 96, // C7 snap: 0.25, // 1/16th }); // Listen to MIDI events during playback — connect to your synth here pr.on('midi', evt => { // evt = { type: 'note-on'|'note-off', pitch, velocity, channel, time } mySynth.send(evt); }); // Programmatic note creation pr.addNote(60, 0, 1, 100); // C4 at beat 0, 1 beat long pr.addNote(64, 1, 1, 100); // E4 pr.addNote(67, 2, 2, 90); // G4 longer, softer pr.play(); // Round-trippable JSON const json = pr.export(); pr.load(json);
Public API
| Method / Event | Type | Description |
|---|---|---|
| addNote(pitch, start, dur, vel?, ch?) | method | Create a note, returns its id |
| removeNote(id) | method | Delete a note |
| updateNote(id, patch) | method | Modify pitch/start/duration/velocity |
| clear() | method | Remove all notes |
| setTool('draw'|'select'|'erase') | method | Switch active tool |
| play() / pause() / stop() | methods | Transport control |
| triggerNote(pitch, vel?) / releaseNote(pitch) | method | Manual note-on/off (e.g. virtual keyboard) |
| export() | method | Serialize sequence to JSON |
| load(seq) | method | Replace current sequence with JSON |
| on('midi', cb) | event | Fires for every note-on/note-off during playback or virtual keyboard click |
| on('change', cb) | event | Fires on add/remove/update note |
| on('run-state', cb) | event | Fires on play/pause/stop transitions |
Keyboard shortcuts
| Key | Action |
|---|---|
| D | Switch to Draw tool |
| S | Switch to Select tool |
| E | Switch to Erase tool |
| Space | Toggle play / pause |
| Delete / Backspace | Delete selected notes |
AudioPlayer
Audio playback component with full transport (play/pause/stop), seek bar, volume control, and time display. Output is a Web Audio
GainNode that downstream components (ChannelStrip, AudioTrackEditor, etc.) can connect to via .connect().Live demo
A working AudioPlayer instance. Click play to start; the output is routed to
AudioComponent.context.destination by default.Component usage
import { AudioPlayer } from 'ariannajs/components/audio'; const p = new AudioPlayer('#root', { src: 'song.mp3', loop: false, volume: 0.8, autoplay: false, }); // Listen to playback events p.on('timeupdate', e => console.log(e.time, e.duration)); p.on('ended', () => console.log('done')); // Web Audio routing — chain into a ChannelStrip or any AudioNode p.connect(strip); // Programmatic control p.play(); p.pause(); p.seek(30); // jump to 30s p.setVolume(0.5);
Public API
| Method / Event | Description |
|---|---|
| load(src) | Load a new audio source URL |
| play() / pause() / stop() | Transport control (returns Promise for play) |
| seek(time) | Jump to time in seconds |
| setVolume(0..1) | Set volume (also updates the GainNode) |
| setLoop(boolean) | Enable/disable loop |
| connect(target) | Connect output to a ChannelStrip, AudioComponent, or AudioNode |
| on('play' / 'pause' / 'ended' / 'timeupdate' / 'loaded') | Forwarded HTML media events |
VideoPlayer
Video playback component with transport, seek bar, volume, fullscreen, and Web Audio routing on the audio track. Aspect-ratio configurable; default 16:9.
Live demo
Component usage
import { VideoPlayer } from 'ariannajs/components/video'; const v = new VideoPlayer('#root', { src: 'movie.mp4', poster: 'poster.jpg', loop: false, aspectRatio: '16/9', }); // Listen to events v.on('loaded', e => console.log(e.width, e.height)); v.on('timeupdate', e => updateSubtitles(e.time)); // Web Audio routing on the audio track v.connect(strip); // Fullscreen v.fullscreen();
Public API
| Method / Event | Description |
|---|---|
| load(src, poster?) | Load a new video source |
| play() / pause() / stop() | Transport control |
| seek(time) | Jump to time in seconds |
| setVolume(0..1) | Set audio volume |
| fullscreen() | Request fullscreen on the video element |
| connect(target) | Route audio output to a ChannelStrip / AudioNode |
| getElement() | Get the underlying HTMLVideoElement |
ChannelStrip
Audio channel strip in DAW style (Luna / Nuendo / Pro Tools). Provides input gain, 3-band parametric EQ (low shelf / mid peak / high shelf), pan, mute/solo, fader (post-EQ), and a real-time peak meter. Built entirely on Web Audio API — chain instances together for full mixing.
Signal chain
Internal Web Audio routing inside a ChannelStrip:
input → gain → EQ low → EQ mid → EQ high → pan → fader → output (analyser)
Live demo
A standalone ChannelStrip with all controls. The meter is wired to a synthetic test tone so you can see it react.
Component usage
import { ChannelStrip } from 'ariannajs/components/audio'; const strip = new ChannelStrip('#strip', { name: 'Lead Vox', color: '#e40c88', fader: 0, // dB pan: 0, // -1..+1 }); // Route audio through it somePlayer.connect(strip); strip.connect(masterStrip); masterStrip.connect(AudioComponent.context.destination); // Programmatic control strip.setEQ('low', { gain: 3, freq: 100 }); strip.setEQ('mid', { gain: -2, freq: 1500, q: 2 }); strip.setEQ('high', { gain: 1.5, freq: 8000 }); strip.setFader(-6); strip.setMute(true); strip.on('change', e => console.log(e.kind, e.value));
Public API
| Method / Event | Description |
|---|---|
| setGain(db) | Input gain (-24/+24 dB) |
| setEQ('low' | 'mid' | 'high', { gain, freq, q? }) | Set parametric EQ band |
| setPan(-1..+1) | Stereo pan |
| setFader(db) | Post-EQ fader (-60/+12 dB) |
| setMute(boolean) / setSolo(boolean) | Mute/solo state |
| setName(string) | Update the strip header label |
| connect(target) | Route output to next strip / AudioNode |
| on('change') | Fires on every parameter update with kind & value |
AudioEditor
Single-clip audio editor in Audacity style. Loads an AudioBuffer, displays the full waveform, and supports drag selection, deep zoom (50x), cut/copy/paste/delete, fade-in / fade-out on selection, normalize, gain, reverse, insert silence, and full undo/redo (50-step stack).
Live demo
Click + Load to load an audio file from disk, or use the included tone seed. Click-drag on the waveform to select a range; then use Cut/Fade In/Normalize buttons. ⌘Z undo, ⌘⇧Z redo, Space play/stop.
Component usage
import { AudioEditor } from 'ariannajs/components/audio'; const ed = new AudioEditor('#root'); // Load a buffer const buf = await ed.loadFile(file); // File or URL ed.setBuffer(buf); // Edit operations (all push undo) ed.setSelectionTime(1.5, 3.0); ed.fadeIn(); // linear fade on selection ed.normalize(-3); // peak to -3 dBFS ed.gain(2); // +2 dB on selection ed.cut(); ed.paste(); // Get the post-edit AudioBuffer const out = ed.getBuffer(); ed.on('change', e => console.log(e.kind)); ed.on('selection', e => console.log(e.start, e.end));
Keyboard shortcuts
| Key | Action |
|---|---|
| ⌘Z / ⌘⇧Z | Undo / Redo |
| ⌘C / ⌘X / ⌘V | Copy / Cut / Paste |
| ⌘A | Select all |
| Delete / Backspace | Delete selection |
| Space | Play / Stop |
Public API
| Method / Event | Description |
|---|---|
| loadFile(src) | Decode audio from URL/File/ArrayBuffer to AudioBuffer |
| setBuffer(buf) / getBuffer() | Set/get the editing buffer |
| setSelectionTime(start, end) | Selection in seconds |
| cut() / copy() / paste() / delete() | Standard edit ops |
| fadeIn() / fadeOut() | Linear fades on selection |
| normalize(targetDb?) | Peak-normalize selection (default 0 dBFS) |
| gain(db) | Apply gain in dB |
| reverse() / insertSilence(seconds) | Common transforms |
| undo() / redo() | Stack-based undo (50-step) |
| play() / stop() | Playback from selection start |
AudioTrackEditor
Multi-track audio timeline (DAW pool) — like the Audio panel of Logic / Pro Tools / Ableton. Each track contains clips with rendered waveforms; clips can be moved, resized (trim left/right), copied, pasted, split. Real Web Audio playback via
BufferSource per clip.Live demo
Click the + on any track header to upload an audio file. Drag clips horizontally to move; drag clip edges to trim. ⌘C/⌘V/⌘X/Del work. Space toggles playback.
Component usage
import { AudioTrackEditor } from 'ariannajs/components/audio'; const ed = new AudioTrackEditor('#root', { tracks: 4, bpm: 120, }); // Add a clip from a buffer const buf = await ed.loadFile('vocals.wav'); ed.addClip('t1', { buffer: buf, start: 0, name: 'Take 1' }); // Programmatic split ed.splitClip(clipId, 5.5); // split at 5.5s on timeline // Track management ed.addTrack({ id: 'fx', name: 'FX', color: '#3b82f6' }); ed.removeTrack('t3'); // Playback honors mute/solo state per track ed.play(); ed.pause(); ed.on('change', e => console.log(e.kind));
Public API
| Method | Description |
|---|---|
| addTrack({ id, name, color? }) | Add a new track |
| removeTrack(id) | Remove track + all its clips |
| addClip(trackId, { buffer, start, offset?, duration?, name? }) | Add a clip on a track |
| removeClip(id) / splitClip(id, atTime) | Clip operations |
| cut() / copy() / paste(atTime) / deleteSelection() | Multi-clip edit ops |
| setZoom(0.1..8) | Horizontal zoom factor |
| play() / pause() / stop() | Mix all clips, honors mute/solo per track |
| loadFile(src) | Decode URL/File to AudioBuffer for use in addClip |
VideoTrackEditor
Multi-track video timeline (DaVinci Resolve / Premiere style). Loads video sources, generates thumbnails for the timeline, supports drag move/resize/trim of clips, copy/paste, split. Pure UI/data layer — pair with FFmpeg (via Tauri command) for actual rendering and export.
Live demo
Click + Source to upload a video file. Thumbnails are generated automatically (8 sample frames). Drag clips to rearrange; drag edges to trim.
Component usage
import { VideoTrackEditor } from 'ariannajs/components/video'; const ed = new VideoTrackEditor('#root', { tracks: 3 }); // Load a source — generates thumbnails automatically const src = await ed.loadSource('clip.mp4'); ed.addClip('v1', { source: src, start: 0, // timeline position in seconds sourceIn: 0, // in-point inside source duration: 5, }); // Split, trim, move ed.splitClip(clipId, 2.5); // Serialize the project (sources, tracks, clips) const project = ed.export(); fs.writeFileSync('project.json', JSON.stringify(project, null, 2)); ed.on('change', e => console.log(e.kind));
Tauri integration pattern
For final rendering, pair the UI with FFmpeg in Rust via a Tauri command. Export the project JSON, walk the clips, and emit FFmpeg
concat + seek + trim filter chains.// Frontend import { invoke } from '@tauri-apps/api/tauri'; const project = ed.export(); const outFile = await invoke('render_project', { project });
Public API
| Method | Description |
|---|---|
| loadSource(file, thumbCount = 8) | Load video, auto-generate thumbnails |
| addTrack({ id, name, type? }) | Add a video or audio track |
| addClip(trackId, { source, start, sourceIn?, duration? }) | Place a clip |
| splitClip(id, atTime) | Split a clip at a timeline timestamp |
| cut() / copy() / paste(atTime) / deleteSelection() | Multi-clip edit ops |
| export() | Returns ExportedProject (sources + tracks + clips) |
| setZoom(0.1..8) / setPlayhead(time) | View/transport state |
NodeEditor
Generic JSON-schema-driven node-graph editor. Domain-agnostic — works for dataflow, audio routing, AI agent orchestration, video pipelines, server-side workflow composition. Linguaggio-agnostico: every node is described by a portable JSON schema, the runtime is your choice.
Live demo
Drag any block from the palette to the canvas. Click a port (LED) and drag to another port to connect them. Right-click a wire to delete it. The graph is fully serializable — click Export JSON.
Port LED states
| Color | Meaning |
|---|---|
| Red | Idle, not connected |
| Yellow | Hover target while dragging a wire |
| Green | Connected, types compatible (ok) |
| Orange | Connected, types convertible (warn) |
| Red blinking | Connected, type mismatch or runtime error |
Node schema (JSON contract)
Every node is just data. Define your domain by listing the schemas the editor should expose in its palette.
import { NodeEditor } from 'ariannajs/components/composite/NodeEditor'; const editor = new NodeEditor('#root', { schemas: [ { type: 'source.timer', name: 'Timer', category: 'Source', color: '#3b82f6', icon: '⏱', inputs: [], outputs: [{ id: 'tick', type: 'number', label: 'tick' }], }, { type: 'ai.llm', name: 'LLM Agent', category: 'AI', color: '#10b981', icon: '🧠', inputs: [ { id: 'prompt', type: 'string', label: 'prompt' }, { id: 'ctx', type: 'json', label: 'context' }, ], outputs: [ { id: 'reply', type: 'string', label: 'reply' }, { id: 'usage', type: 'json', label: 'usage' }, ], }, { type: 'sink.log', name: 'Console', category: 'Sink', color: '#64748b', icon: '▣', inputs: [{ id: 'in', type: 'any', label: 'in' }], outputs: [], }, ], }); editor.on('graph-change', () => { console.log('graph:', editor.export()); });
Public API
| Method / Event | Type | Description |
|---|---|---|
| addNode(type, x, y) | method | Spawn a node by schema type at given coords |
| removeNode(id) | method | Remove a node and all its wires |
| addWire(srcN, srcP, dstN, dstP) | method | Connect output port to input port |
| removeWire(id) | method | Delete a single wire |
| setWireStatus(id, status) | method | Update wire status from runtime (ok / warn / error) |
| clear() | method | Reset to empty graph |
| export() | method | Serialize graph to JSON |
| load(graph) | method | Replace current graph from JSON |
| setRunState(s) | method | Set 'idle' | 'running' | 'paused' |
| on('graph-change', cb) | event | Fires on add/remove/move |
| on('run-state', cb) | event | Fires when Play/Pause/Stop changes state |
Wire routing — Manhattan
Wires use right-angle (Manhattan) routing in 5 segments — Unreal Blueprint style. The midpoint between source and destination is computed dynamically as nodes move, so wires re-route smoothly during drag.
Test Suite
Click any group's Run All button below to run that group only.
0 tests0 passed
UUID · Scopes · GetPrototypeChain · SetDescriptors · Namespaces · GetDescriptor · Define · Events
Core Inspector
// run tests
0 tests0 passed
Core.use() · idempotency · Core.plugins() · version
Installed plugins
none yet
Core version: —
0 tests0 passed
on / off / fire / once / all · multi-type · static bus
Registry Inspector
// run tests
0 tests0 passed
getter/setter · Changing/Changed order · property-scoped events · history · deep nesting
State Visualizer
0 tests0 passed
Array · Map · Set · WeakMap — Proxy interception
Collection Inspector
// run tests
0 tests0 passed
derived values · Computed-Changed event · string concat · conditional
Computed Live
0 tests0 passed
Named States · State-Reached · transition sequences · match()
Transitions
// run tests
0 tests0 passed
6 constructor overloads · render · on/off/fire · append/add/remove · get/set · show/hide
Real DOM Inspector
// run tests
0 tests0 passed
Create · Render · Mount · Unmount · Parse · Compare · Clone · add/remove/push/pop
Virtual Tree Inspector
// run tests
0 tests0 passed
6 constructor overloads · get/set/remove/merge/replace · Rule-Changed · clone · Rule.Parse
Rule Inspector
// run tests
0 tests0 passed
8 constructor overloads · add/insert/remove/shift/pop/clear · getIndex · contains · Observable
Sheet Inspector
// run tests
0 tests0 passed
provide · consume · update · Context-Changed · has · keys · multi-context
Context Registry
// run tests
0 tests0 passed
if · for · while · switch · bind · show · model · bootstrap
Directive Sandbox
Source — All Runtime Directives
Directive.if — conditional rendering with update()
const panel = document.querySelector('#my-panel');
let visible = true;
const update = Directive.if(panel, () => visible,
'<div class="content">Content visible</div>',
'<div class="empty">Nothing to show</div>'
);
// Toggle:
visible = false; update(); // shows else branch
visible = true; update(); // shows then branch
Directive.for — list from array (reactive update)
const ul = document.querySelector('ul');
let items = ['Mercury', 'Venus', 'Earth'];
const update = Directive.for(ul, () => items, (item, i) =>
`<li data-i="${i}">${item}</li>`
);
items = ['Mercury', 'Venus', 'Earth', 'Mars'];
update(); // re-renders 4 items, removes old 3
Directive.foreach — object iteration (Golem: foreach="var planet in object")
const object = {
Mercury : 'Mercury Value',
Pluto : 'Pluto Value',
Uranus : 'Uranus Value',
Jupiter : 'Jupiter Value',
Earth : 'Earth Value',
};
const ol = document.querySelector('#ForeachComponent');
Directive.foreach(ol, () => object, (planet, value) =>
`<li class="Value">{{ planet }} : {{ object[planet] }}</li>`
.replace('{{ planet }}', planet)
.replace('{{ object[planet] }}', value)
);
// Mirrors: <ol foreach="var planet in object"><li>{{ planet }} : {{ object[planet] }}</li></ol>
Directive.while — render while condition is true
const ul = document.querySelector('ul');
let i = 0;
Directive.while(ul, () => i < 5, () => {
const html = `<li>Item ${i}</li>`;
i++;
return html;
});
// Renders: Item 0, Item 1, Item 2, Item 3, Item 4
Directive.switch — render matching case (like <switch> in Solid)
let tab = 'home';
const update = Directive.switch(container, () => tab, {
home : '<div>🏠 Home</div>',
about : '<div>ℹ About</div>',
contact : '<div>✉ Contact</div>',
default : '<div>404 Not Found</div>',
});
tab = 'about'; update(); // switches to About panel
Directive.bind — one-way binding: element property ← source
const span = document.querySelector('#name-display');
const state = new State({ name: 'AriannA' });
const update = Directive.bind(span, 'textContent', () => state.State.name);
state.on('State-name-Changed', update);
state.State.name = 'Beta 1'; // span updates automatically
Directive.show — toggle visibility (no DOM removal)
const sidebar = document.querySelector('#sidebar');
let open = false;
const update = Directive.show(sidebar, () => open);
document.querySelector('#toggle-btn').addEventListener('click', () => {
open = !open; update(); // sets display:'' or display:'none'
});
Directive.model — two-way binding: input ↔ State
const state = new State({ name: '', email: '' });
Directive.model(document.querySelector('#name-input'), state, 'name');
Directive.model(document.querySelector('#email-input'), state, 'email');
// input.value ↔ state.State.name — both directions, zero glue code
state.on('State-Changed', e =>
console.log(e.Property.Name, '→', e.Property.New)
);
Directive.template — {{ }} literal interpolation (Golem TemplateLiterals)
// Matches Golem: <p>This is an {{ example }} of Template {{ literals }}</p>
var example = 'EXAMPLE';
var literals = 'LITERALS';
var Level1A = { Level2A: 'Data Level2A Value' };
Directive.template(document.querySelector('#LiteralsComponent'), {
example, literals, Level1A,
});
// "This is an EXAMPLE of Template LITERALS"
// "{{ Level1A.Level2A }}" → "Data Level2A Value"
// Bracket notation:
const planet = 'Mercury';
const object = { Mercury: 'Mercury Value' };
Directive.template(el, { planet, object });
// "{{ planet }}" → "Mercury"
// "{{ object[planet] }}" → resolved via path lookup
Directive.on — event listener (v-on / @event equivalent)
// Single type
Directive.on(button, 'click', handler);
// Multi-type (space-separated)
Directive.on(input, 'focus blur', e => toggleHighlight(e.type));
// With options
Directive.on(scroller, 'scroll', onScroll, { passive: true });
HTML attribute directives via Directive.bootstrap()
<!-- HTML-first declarative usage -->
<div a-if="user.loggedIn">Welcome!</div>
<ul a-for="item in items"><li>{{ item }}</li></ul>
<ol a-foreach="var planet in object"><li>{{ planet }}</li></ol>
<div a-show="isVisible"></div>
<input a-model="state.name">
<button a-on="click:submitForm">Submit</button>
<span a-bind="textContent:user.name"></span>
const scope = { user, items, object, state, isVisible, submitForm };
Directive.bootstrap(document.body, scope);
0 tests0 passed
HTML (56 ifaces · 118 tags) · SVG (34) · MathML (29) · X3D Define · SVG custom Define
Registry Inspector
// run tests
0 tests0 passed
Exhaustive Rule construction · all CSS properties · complex selectors · media queries · pseudo-classes
Source — RuleDefinition object
Full object literal with every CSS category
const cssRule = {
Selector : '.my-class-selector > div',
Contents : {
/* Layout */
display : 'flex',
flexDirection : 'column',
alignItems : 'center',
justifyContent : 'space-between',
gap : '1rem',
width : '100%',
minHeight : '48px',
padding : '12px 16px',
margin : '0 auto',
boxSizing : 'border-box',
/* Typography */
fontFamily : 'ui-monospace, monospace',
fontSize : '14px',
fontWeight : '600',
lineHeight : '1.5',
letterSpacing : '0.02em',
textAlign : 'left',
textTransform : 'none',
textDecoration : 'none',
whiteSpace : 'nowrap',
overflow : 'hidden',
textOverflow : 'ellipsis',
/* Visual */
background : 'dodgerblue',
color : 'white',
border : '1px solid rgba(0,0,0,.15)',
borderRadius : '6px',
boxShadow : '0 2px 6px rgba(0,0,0,.3)',
outline : 'none',
opacity : '1',
visibility : 'visible',
cursor : 'pointer',
/* Transition / Animation */
transition : 'background .2s ease, transform .15s ease',
transform : 'translateY(0)',
willChange : 'transform',
/* Position */
position : 'relative',
zIndex : '1',
top : 'auto',
left : 'auto',
}
};
const r = new Rule(cssRule);
console.log(r.Text);
// .my-class-selector > div { display: flex; background: dodgerblue; ... }
Pseudo-class / pseudo-element selectors
const hover = new Rule('.my-class-selector > div:hover', {
background : 'royalblue',
transform : 'translateY(-2px)',
boxShadow : '0 4px 12px rgba(0,0,0,.4)',
});
const before = new Rule('.my-class-selector > div::before', {
content : '""',
display : 'block',
width : '4px',
height : '100%',
background : 'crimson',
position : 'absolute',
left : '0',
top : '0',
});
const nthChild = new Rule('.my-class-selector > div:nth-child(2n+1)', {
background : 'rgba(30,144,255,.12)',
});
const focus = new Rule('.my-class-selector > div:focus-within', {
outline : '2px solid dodgerblue',
outlineOffset : '2px',
});
Media queries — via Sheet with multiple rules
// Media queries use the selector field as the full @media block,
// with the inner rule as the Contents string.
// This is the standard CSSStyleSheet pattern AriannA wraps.
const sheet = new Sheet();
// Base rule
sheet.add(new Rule('.responsive-card', {
display : 'grid',
gridTemplateColumns : '1fr 1fr',
gap : '1rem',
}));
// Tablet breakpoint — parsed from raw CSS string
sheet.add(new Rule(
'@media (max-width: 768px)',
'.responsive-card { grid-template-columns: 1fr; gap: .5rem; }'
));
// Mobile breakpoint
sheet.add(new Rule(
'@media (max-width: 480px)',
'.responsive-card { padding: 8px; font-size: 13px; }'
));
// Prefer-reduced-motion
sheet.add(new Rule(
'@media (prefers-reduced-motion: reduce)',
'.responsive-card * { transition: none !important; animation: none !important; }'
));
// Dark mode
sheet.add(new Rule(
'@media (prefers-color-scheme: dark)',
'.responsive-card { background: #1a1a2e; color: #eee; }'
));
console.log(sheet.Length); // 5
Live Sheet Output
// run tests
0 tests0 passed
Fluent chains · add/set/append · selector targets · Real instances · event chains
Source — Fluent chain patterns
Pattern 1 — create → style → add children → append to parent
// Build a card component entirely through chains
const card = new Real('div')
.set('id', 'my-card')
.set('class', 'card-component')
.add('<h2 class="card-title">Hello AriannA</h2>')
.add('<p class="card-body">Reactive DOM without virtual DOM overhead.</p>')
.add('<button id="card-btn">Click me</button>')
.on('click', e => {
if (e.target.id === 'card-btn')
e.target.textContent = 'Clicked!';
})
.append(document.body);
Pattern 2 — append to selector string
// .append() accepts a CSS selector — no need to querySelector manually
const badge = new Real('span')
.set('class', 'badge')
.add('New')
.append('#my-card'); // CSS selector
const footer = new Real('footer')
.set('class', 'card-footer')
.append('.card-component'); // class selector
Pattern 3 — append to Real instance
const container = new Real('section').set('id', 'container');
const item = new Real('article')
.set('class', 'item')
.add('<h3>Item</h3>')
.append(container); // Real instance as parent
container.contains(item); // true — Real instance check
container.contains('.item'); // true — selector check
Pattern 4 — unshift / push / pop / shift
const list = new Real('ul');
list
.push('<li>Item B</li>') // append to end
.push('<li>Item C</li>')
.unshift('<li>Item A</li>') // prepend
.pop() // remove last → C gone
.shift(); // remove first → A gone → only B remains
list.render().childElementCount; // 1
Pattern 5 — remove by selector / by Real / by index
const nav = new Real('nav');
const homeLink = new Real('a').set('href', '/').set('class', 'home');
const aboutLink = new Real('a').set('href', '/about');
nav.add(homeLink.render(), aboutLink.render(), '<a href="/contact">Contact</a>');
nav.remove('.home'); // by CSS selector
nav.remove(aboutLink); // by Real instance
nav.remove(0); // by index
Pattern 6 — event chain + fire
const btn = new Real('button')
.set('id', 'submit-btn')
.add('Submit')
.on('click', e => console.log('clicked', e))
.on('mouseenter', () => btn.set('class', 'hovered'))
.on('mouseleave', () => btn.set('class', ''))
.fire('custom-ready', { detail: { id: 'submit-btn' } });
Pattern 7 — show/hide + get
const panel = new Real('aside')
.set('id', 'side-panel')
.set('class', 'panel')
.add('<div class="content">Sidebar content</div>')
.hide(); // display: none
panel.get('id'); // 'side-panel'
panel.get('class'); // 'panel'
// toggle
const toggle = () =>
panel.render().style.display === 'none'
? panel.show()
: panel.hide();
Chain Step Visualizer
// run tests to see chain steps
0 tests
0 passed
Function constructor · Class constructor · Real.Define · super() · this · State · Virtual SVG · Prototype chain
Live Sandbox — Component Creation
Prototype Chain Inspector
Source — Function Constructor (ES5 style)
Real.Define with a plain function — this = the element
function CustomFunction()
{
this.style.width = '120px';
this.style.height = '120px';
this.style.background = 'dodgerblue';
this.style.display = 'flex';
this.style.alignItems = 'center';
this.style.justifyContent = 'center';
this.style.color = 'white';
this.style.margin = '2px';
this.style.borderRadius = '8px';
this.innerText = 'FUNCTION';
}
Real.Define('custom-function', CustomFunction, HTMLButtonElement);
// Instantiate via code
const cf = new Real('custom-function').append('#comp-sandbox');
// Get prototype chain
Core.GetPrototypeChain(cf.render());
// → ["CustomFunction","HTMLButtonElement","HTMLElement","Element","Node","EventTarget","Object"]
Class constructor with super() — ES6+ style
class CustomClass extends HTMLButtonElement
{
constructor()
{
super(); // mandatory — sets up HTMLButtonElement
this.style.width = '120px';
this.style.height = '120px';
this.style.background = 'crimson';
this.style.display = 'flex';
this.style.alignItems = 'center';
this.style.justifyContent = 'center';
this.style.color = 'white';
this.style.margin = '2px';
this.style.borderRadius = '8px';
this.textContent = 'CLASS';
}
}
Real.Define('custom-class', CustomClass, HTMLButtonElement);
// Instantiate via code — identical API to function style
const cc = new Real('custom-class').append('#comp-sandbox');
Core.GetPrototypeChain(cc.render());
// → ["CustomClass","HTMLButtonElement","HTMLElement","Element","Node","EventTarget","Object"]
Click handler — State mutation + Virtual SVG injection
// Reactive state wired to a component click
const state = new State({ step: 0, label: 'Click me' });
cf.on('click', () =>
{
state.State.step++;
state.State.label = 'Step ' + state.State.step;
// Inject a Virtual SVG circle on each click
const svg = Virtual.Create('svg',
{ xmlns: 'http://www.w3.org/2000/svg', width: '40', height: '40', viewBox: '0 0 40 40' },
Virtual.Create('circle', { cx:'20', cy:'20', r:'18', fill:'white', opacity:'0.8' })
);
cf.push(svg.render());
});
0 tests0 passed
Component · Prop · Watch · Emit · Ref
0 tests0 passed
h() · Fragment · Real mode · Virtual mode · $event · onEvent
SSE — Axum Bridge
Connecting...
Fire → POST /events/fire
Health — GET /health
SSE Log
Global Event Log
CLI Reference
arianna-cli v1.2.0The AriannA CLI scaffolds projects, generates components, runs a dev server, and builds for production. Install globally or use via npx.
Install
# Global install npm install -g arianna # Or use without installing npx arianna <command>
Commands
| Command | Options | Description |
|---|---|---|
| arianna new <n> | --template browser|tauri|ios|android | Scaffold a new project. Creates index.html, src/main.ts, tsconfig.json. |
| arianna generate <type> <n> | component | directive | state | test | Generate a file from template. Outputs to src/components/, src/, src/tests/. |
| arianna serve | --port <n> (default 3000) | Start a dev server with static file serving. No bundler required. |
| arianna build | --minify | Bundle arianna-core/index.ts → dist/arianna.js via esbuild. |
| arianna typecheck | Run tsc --noEmit. Alias: arianna tc | |
| arianna bench | Open the js-framework-benchmark harness. | |
| arianna info | Print version, author, thanks. |
Scaffold a browser app
arianna new my-app # Creates: # my-app/ # ├── index.html # ├── src/main.ts # └── tsconfig.json cd my-app arianna serve # http://localhost:3000
Scaffold a Tauri app
arianna new my-tauri-app --template tauri
# Creates browser scaffold + src-tauri/ with Cargo.toml and tauri.conf.json
cd my-tauri-app
cargo tauri devGenerate a component
arianna generate component MyCard # → src/components/MyCard.ts # Exports: class MyCard with el, signal, destroy() arianna generate state AppState # → src/AppState.ts # Exports: AppState with signal(), computed(), reset() arianna generate test MyCard # → src/tests/MyCard.ts # Basic test scaffold
Build for production
arianna build --minify # → dist/arianna.js (ESM bundle, minified) # Uses esbuild — install separately: npm i -D esbuild # In your HTML: <script type="module"> import { Real, signal } from './dist/arianna.js'; </script>
Create Component
The CLI scaffolds a complete AriannA component with all 4 syntax variants, CSS-in-JS styles, and a test file.
Usage
node arianna-cli.mjs component <ComponentName> [--dir <dir>]
# Examples
node arianna-cli.mjs component MyButton
node arianna-cli.mjs component UserCard --dir src/ui/components
node arianna-cli.mjs component DataWidget --dir src/features/dashboardGenerated files
src/components/MyButton/ ├── MyButton.ts ← component (Decorators + commented Real/Virtual/JSX) ├── MyButton.css.ts ← CSS-in-JS via Rule/Sheet ├── MyButton.test.ts ← test file (AriannA test pattern) └── index.ts ← barrel export
What's inside MyButton.ts
import {{ Component, Prop, Watch, Emit }} from '../arianna-ts/index.ts'; @Component({{ tag: 'my-button' }}) export class MyButton extends HTMLElement {{ @Prop() label = 'MyButton'; @Prop() disabled = false; @Watch('label') onLabelChange(next: string) {{ const el = this.querySelector('.my-button__label'); if (el) el.textContent = next; }} @Emit('my-button-click') handleClick() {{ return {{ label: this.label }}; }} connectedCallback() {{ this.innerHTML = ` <div class="my-button"> <span class="my-button__label">\${{this.label}}</span> </div> `; if (!this.disabled) this.addEventListener('click', () => this.handleClick()); }} }} // Use: <my-button label="Save"></my-button>
CSS-in-JS (MyButton.css.ts)
import {{ Rule, Sheet }} from '../arianna-ts/index.ts'; export const MyButtonSheet = new Sheet( new Rule('.my-button', {{ padding: '8px 16px', borderRadius: '5px', cursor: 'pointer' }}), new Rule('.my-button:hover', {{ background: 'var(--ar-bg4)' }}), new Rule('.my-button--disabled', {{ opacity: '0.45', cursor: 'not-allowed' }}), ); export function injectMyButtonStyles() {{ MyButtonSheet.inject(document.head); }}
Two.ts Tests
Finance.ts Tests
AI.ts Tests
Video.ts Tests
Audio.ts Tests
IO.ts Tests
Modifiers 2D Tests
Modifiers 3D Tests
Finance Components Tests
🌐 Browser App
Vanilla browser app. Vite for dev server and build. No framework — just AriannA, TypeScript, and ES modules. Works offline with no backend.
Prerequisites
# Node.js ≥ 20, npm or pnpm node arianna-cli.mjs create browser my-site
Quick start
cd arianna-projects/my-site npm install npm run dev # → http://localhost:3000 (Vite HMR) npm run build # → dist/ (ES modules bundle) npm run check # TypeScript check
Tools
Vite (dev server + build) · TypeScript · ES modules · No bundler required in dev
Project structure
my-site/ ├── index.html ← entry point ├── src/ │ ├── main.ts ← AriannA app entry │ └── app.ts ← components ├── tsconfig.json ├── vite.config.ts └── package.json
🍎 Tauri macOS
Rust Rover: open
src-tauri/ as Cargo project. Set run config: cargo tauri dev. Xcode: required only for signing/notarization. Open Xcode → Preferences → Accounts → add Apple ID. Set bundle.macOS.signingIdentity in tauri.conf.json.Prerequisites
# Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Tauri CLI cargo install tauri-cli # Create project node arianna-cli.mjs create tauri-macos my-desktop
Quick start
cd arianna-projects/my-desktop
npm install
npm run dev # Tauri dev window (hot reload)
npm run build # → src-tauri/target/release/bundle/
# dmg/ → macOS disk image
# app/ → .app bundleTools
Rust Rover (Rust backend) · Xcode (signing, notarization, App Store) · Tauri 2 · Vite
Project structure
my-desktop/ ├── index.html ├── src/main.ts ← AriannA frontend ├── src-tauri/ │ ├── src/lib.rs ← #[tauri::command] handlers │ ├── src/main.rs ← entry point │ ├── Cargo.toml │ ├── tauri.conf.json ← bundle config, signingIdentity │ └── capabilities/ ← permissions └── vite.config.ts
📱 Tauri iOS
Rust Rover: open
src-tauri/ as Cargo project, cross-compile target: aarch64-apple-ios. Xcode: manages simulator, provisioning profiles, and device deployment. After first build, open src-tauri/gen/apple/my-iphone-app.xcodeproj. Apple Developer account required for device builds.Prerequisites
# Add iOS Rust targets rustup target add aarch64-apple-ios x86_64-apple-ios # Install Tauri CLI cargo install tauri-cli # Create project node arianna-cli.mjs create tauri-ios my-iphone-app
Quick start
cd arianna-projects/my-iphone-app npm install npm run sim # iOS Simulator npm run dev # Connected iPhone (USB debugging) npm run build # → .ipa
Tools
Rust Rover (Rust backend) · Xcode 15+ (simulator, device, signing, provisioning) · Tauri 2
Project structure
my-iphone-app/ ├── index.html ← viewport-fit=cover (notch + home indicator) ├── src/main.ts ← AriannA UI ├── src-tauri/ │ ├── src/lib.rs ← Tauri commands │ ├── Cargo.toml ← iOS target deps │ └── tauri.conf.json └── vite.config.ts ← Safari 16 target
🪟 Windows App
Rust Rover: open
src-tauri/ as Cargo project. WebView2: ships with Windows 10 1803+ (Edge). For older systems set webviewInstallMode: embedBootstrapper in tauri.conf.json. Code signing: set bundle.windows.certificateThumbprint. WiX Toolset must be on PATH.Prerequisites
# Install Rust on Windows winget install Rustlang.Rustup rustup toolchain install stable # Install WiX Toolset (for MSI) winget install WixToolset.WixToolset # Install Tauri CLI cargo install tauri-cli node arianna-cli.mjs create windows my-win-app
Quick start
cd arianna-projects/my-win-app
npm install
npm run dev # Dev window with HMR
npm run build # → src-tauri/target/release/bundle/
# msi/ → Windows Installer
# nsis/ → NSIS installerTools
Rust Rover or VS Code + rust-analyzer (Rust backend) · WiX Toolset (MSI installer) · WebView2 (bundled with Edge)
Project structure
my-win-app/ ├── index.html ├── src/main.ts ← AriannA frontend (auto dark/light) ├── src-tauri/ │ ├── src/lib.rs ← Tauri commands │ ├── src/main.rs ← windows_subsystem = "windows" │ ├── Cargo.toml │ └── tauri.conf.json ← bundle.targets: [msi, nsis] └── vite.config.ts ← chrome105 target
🤖 Android App
Rust Rover: open
src-tauri/ as Cargo project, set Android SDK path in Rust Rover settings. Android Studio: SDK Manager → install NDK + CMake. After first build, open src-tauri/gen/android/ in Android Studio for signing keystore and Play Store bundle.Prerequisites
# Add Android Rust targets rustup target add aarch64-linux-android armv7-linux-androideabi rustup target add x86_64-linux-android i686-linux-android # Set environment variables export ANDROID_HOME=~/Android/Sdk export NDK_HOME=$ANDROID_HOME/ndk/26.x.x # Install Tauri CLI cargo install tauri-cli node arianna-cli.mjs create android my-android-app
Quick start
cd arianna-projects/my-android-app npm install npm run emulator # Android Emulator npm run dev # Connected device (USB debugging) npm run apk # Debug APK npm run build # Signed AAB → Play Store
Tools
Rust Rover (Rust backend) · Android Studio (SDK, NDK, emulator, signing, Play Store) · Java 17+
Project structure
my-android-app/ ├── index.html ← viewport user-scalable=no, safe-area ├── src/main.ts ← AriannA UI (touch optimized) ├── src-tauri/ │ ├── src/lib.rs ← #[tauri::command] handlers │ ├── Cargo.toml ← Android target deps (API 24+) │ └── tauri.conf.json ← minSdkVersion: 24 └── vite.config.ts ← chrome114 target (Android WebView)
TransportBar
DAW-style transport widget — Play / Stop / Pause buttons, dual-mode timecode display
(Bars:Beats:Ticks or HH:MM:SS:FF SMPTE), and live tempo/loop indicators. Reusable
across
AudioTrackEditor, VideoTrackEditor, and the
AudioEditor.Constructor
import { TransportBar } from 'arianna/components/composite'; const bar = new TransportBar('#mount', { bpm : 120, timeSignature : '4/4', framerate : 30, mode : 'bars', // or 'smpte' }); bar.on('play', () => engine.start()); bar.on('pause', () => engine.pause()); bar.on('stop', () => engine.stop());
Live demo
API
| Property / Method | Type | Description |
|---|---|---|
| bar.bpm = n | number | Set tempo (bars-mode timecode) |
| bar.framerate = n | number | SMPTE framerate (24 / 25 / 29.97 / 30) |
| bar.mode = 'bars'|'smpte' | string | Switch timecode display |
| bar.position | number | Current position in beats (read/write) |
| bar.on('play'|'stop'|'pause'|'seek', cb) | event | Lifecycle events |
Chat
WhatsApp / Signal-style chat widget. Bubbles, timestamps, typing
indicators, read receipts, message reactions, and threaded replies. Designed as a
fully composable surface — the rendering can be themed and the message stream is
reactive: feed it from any source and the UI follows.
Constructor
import { Chat } from 'arianna/components/composite'; const chat = new Chat('#mount', { me : { id: 'u1', name: 'Riccardo', avatar: '/me.png' }, peers: [{ id: 'u2', name: 'Arianna' }], messages: [ { id: 'm1', from: 'u2', text: 'Hey!', at: Date.now() - 3600e3 }, { id: 'm2', from: 'u1', text: 'Doing great 🎉', at: Date.now(), status: 'read' }, ], features: { reactions: true, threads: true, typing: true }, }); chat.on('send', e => api.send(e.message)); chat.on('react', e => api.react(e.messageId, e.emoji));
Live demo
Message interface
| Field | Type | Description |
|---|---|---|
| id | string | Unique message id |
| from | string | User id of sender |
| text | string | Body (markdown supported) |
| at | number | Timestamp (epoch ms) |
| status? | 'sending' | 'sent' | 'delivered' | 'read' | 'failed' | Delivery status |
| reactions? | Reaction[] | Emoji reactions |
| replyTo? | string | Thread parent message id |
| attachments? | Attachment[] | Files / images / audio |
Events
| Event | Detail |
|---|---|
| send | { message } |
| react | { messageId, emoji } |
| typing | { isTyping } |
| scroll-top | {} — load older messages |
Graphics · 2D — Overview
The 2D graphics suite is the foundation of the Wires modeller and the
Daedalus Visual Composer. Five components compose to rebuild an Illustrator-class
workflow inside the browser.
| Component | Purpose |
|---|---|
Canvas2D | Pannable / zoomable infinite canvas with grid, snap, and rulers |
BezierEditor | Vector path editor — anchors with smooth / corner / asymmetric handles, closed/open paths |
LayersPanel | Layer stack — visibility, lock, groups, drag-reorder, multi-select |
ToolsPalette | Illustrator-style tool palette: select, pen, rect, ellipse, crop, magic-wand |
LinesPalette2D | Profile / line tool palette for 2D paths (line, arc, spline, bezier, polyline) |
Colour pickers and gradient editors live in their own Colors namespace.
Live demo
Canvas2D
Pannable / zoomable canvas surface that hosts vector paths, raster layers,
and interactive widgets. Background grid is configurable; pan / zoom can be tuned per axis.
Constructor
import { Canvas2D } from 'arianna/components/graphics/2D'; const c = new Canvas2D('#mount', { width : 800, height : 500, pan : true, zoom : true, grid : { size: 10, snap: true }, rulers : true, });
API
| Method | Description |
|---|---|
| c.zoomTo(level) | Set zoom level (1 = 100%) |
| c.panTo(x, y) | Centre the viewport on (x, y) |
| c.fit(bounds) | Fit a bounding box in the viewport |
| c.toDataURL() | Export current view as PNG data URL |
BezierEditor
Vector path editor. Each anchor can be Smooth, Corner, or Asymmetric.
Handles are draggable; double-click on the path inserts an anchor; ⌫ deletes selection.
Emits
change events on every edit.Constructor
import { BezierEditor } from 'arianna/components/graphics/2D'; const ed = new BezierEditor('#mount', { width : 600, height : 300, closed : true, initial : [ { x: 80, y: 180, mode: 'corner' }, { x: 300, y: 60, mode: 'smooth' }, { x: 520, y: 180, mode: 'corner' }, ], }); ed.on('change', () => save(ed.toPath()));
Anchor mode
| Mode | Description |
|---|---|
| 'corner' | Sharp corner — handles independent |
| 'smooth' | Symmetric handles, equal length |
| 'asymmetric' | Collinear handles with different lengths |
LayersPanel
Layer stack with visibility, lock, groups, drag-reorder, and multi-select.
Tree structure mirrors the SVG / scene-graph hierarchy.
Constructor
import { LayersPanel } from 'arianna/components/graphics/2D'; const lp = new LayersPanel('#mount', { layers: [ { id: 'bg', name: 'Background', kind: 'shape' }, { id: 'grp1', name: 'Logo', kind: 'group', children: [ { id: 'icon', name: 'Icon', kind: 'shape' }, { id: 'text', name: 'Wordmark', kind: 'text' }, ]}, ], });
Layer kinds
shape · path · text · image · group · adjustment
ToolsPalette
Illustrator-style tool palette: select, pen, rectangle, ellipse, crop,
magic-wand. Emits
tool events when a tool is picked.Constructor
import { ToolsPalette } from 'arianna/components/graphics/2D'; const p = new ToolsPalette('#mount', { tools: ['select', 'pen', 'rect', 'ellipse', 'crop', 'wand'], }); p.on('tool', e => editor.setTool(e.tool));
LinesPalette2D
Companion palette for path / line tools — line, arc, spline, bezier,
polyline. Used by
BezierEditor and the Wires modeller for profile creation.Constructor
import { LinesPalette2D } from 'arianna/components/graphics/2D'; const p = new LinesPalette2D('#mount', { tools: ['line', 'arc', 'spline', 'bezier', 'polyline'], });
Graphics · 3D — Overview
UI chrome around any 3D renderer (Three.js, Babylon, WebGPU). The
components are display-only and emit events; you wire them to your engine of choice.
| Component | Purpose |
|---|---|
CameraViewer3D | Maya-style 4-pane viewport (top / front / side / persp) with axes, gizmo, frame-fit |
MaterialsPalette | PBR material library + drag-to-assign |
Modifiers3DPalette | Modifier stack (bend, twist, mirror, array, subdivide) with reorderable list |
CameraViewer3D
Maya-style four-pane viewport. Top / front / side panes are orthographic;
perspective pane has axes (RGB = X/Y/Z), camera gizmo, and frame-fit shortcut.
Constructor
import { CameraViewer3D } from 'arianna/components/graphics/3D'; const v = new CameraViewer3D('#mount', { width : 800, height : 600, showAxes: true, showGizmo: true, panes : ['top', 'front', 'side', 'persp'], }); v.on('pane-change', e => layout.set(e.pane, e.camera));
MaterialsPalette
PBR material browser — exports as
{ color, metallic, roughness, normal,
emissive }. Drag a material onto a 3D object in the viewport to assign.Constructor
import { PaletteMaterials } from 'arianna/components/graphics/3D'; const mp = new PaletteMaterials('#mount', { materials: [ { id: 'gold', name: 'Gold', kind: 'pbr-standard', color: '#ffd700', metallic: 1.0, roughness: 0.2 }, { id: 'rubber', name: 'Rubber', kind: 'pbr-standard', color: '#1c1c1c', metallic: 0.0, roughness: 0.9 }, ], }); mp.on('pick', e => engine.assignMaterial(e.materialId));
Material kinds
pbr-standard · pbr-clearcoat · unlit · toon
Modifiers3DPalette
Non-destructive modifier stack — analogous to Blender's modifier stack.
Each modifier wraps a parametric transformation (bend, twist, mirror, array, subdivide).
Reorderable, toggleable, and serialisable.
Constructor
import { PaletteModifiers3D } from 'arianna/components/graphics/3D'; const mp = new PaletteModifiers3D('#mount', { available: ['bend', 'twist', 'mirror', 'array', 'subdivide'], stack : [ { kind: 'twist', amount: 45 }, { kind: 'mirror', axis: 'x' }, ], }); mp.on('change', e => engine.applyStack(e.stack));
Colors — Overview
Eight components, all sharing the colour-space mathematics from the
additionals/Colors addon. Every picker reads & writes
RGB / HEX / HSL / HSV / CMYK / OKLCH / CIELUV / Cube equivalently —
pick a colour anywhere, read it in any space.| Component | Style |
|---|---|
ColorPicker | Compact HSL+RGB picker (the original AriannA picker) |
ColorPickerWheel | Illustrator-style hue wheel + SV square + 8-space readout |
ColorPickerSquare | Photoshop-style SV square + hue strip + editable readouts |
ColorPickerTile | Tile palette (Tailwind / Material / pastel / web-safe / Mac OS classic) + recents + hex input |
GradientEditor (base) | Common stop / interpolation / preview machinery, used by the three editors below |
LinearGradientEditor | Angle + stops + interpolation space (rgb / hsl / oklab) |
RadialGradientEditor | Shape (circle / ellipse) + size + draggable centre + stops |
ShapeGradientEditor | Illustrator freeform mesh — arbitrary 2D control points with colours |
additionals/Colors — pure-math layer
import * as Colors from 'arianna/additionals/Colors'; Colors.parseHex('#e40c88'); // → { r:228, g:12, b:136, a:1 } Colors.rgbToOklch({ r:228, g:12, b:136, a:1 }); // → { L:0.62, C:0.24, h:4 } Colors.formatCss('oklch', oklch); // → 'oklch(62.0% 0.240 4)'
Live demo
ColorPicker
The original AriannA picker — compact, HSL + RGB readout, alpha slider.
Kept for back-compat alongside the three new picker styles.
import { ColorPicker } from 'arianna/components/graphics/colors'; const cp = new ColorPicker('#mount', { color: '#e40c88', alpha: true, }); cp.on('change', e => element.style.color = e.hex);
ColorPickerWheel
Illustrator-style hue wheel surrounding an SV square. Optional 8-space
readout panel (RGB / HEX / HSL / HSV / CMYK / OKLCH / CIELUV / Cube).
import { ColorPickerWheel } from 'arianna/components/graphics/colors'; const cp = new ColorPickerWheel('#mount', { color : '#e40c88', size : 240, readout: true, });
ColorPickerSquare
Photoshop-style picker: SV square + vertical hue strip + editable readouts
for every channel (R G B, H S L, H S V, hex, alpha).
import { ColorPickerSquare } from 'arianna/components/graphics/colors'; const cp = new ColorPickerSquare('#mount', { color: '#3b82f6', alpha: true, size : 220, });
ColorPickerTile
Tile palette with five built-in palettes (Tailwind / Material / pastel /
web-safe / Mac OS classic), plus recents row and hex input.
import { ColorPickerTile } from 'arianna/components/graphics/colors'; const cp = new ColorPickerTile('#mount', { palette : 'tailwind', // 'tailwind'|'material'|'pastel'|'web-safe'|'mac-os-classic'|RGB[][] columns : 8, tileSize: 28, });
GradientEditor (base)
Abstract base with the shared stop / interpolation / preview machinery —
used by the three concrete editors below. Subclass to build your own gradient style.
import { GradientEditorBase } from 'arianna/components/graphics/colors'; // You normally extend this rather than instantiate it directly: class AngleSweepEditor extends GradientEditorBase { /* … */ }
LinearGradientEditor
Linear gradient with angle, stops (drag horizontally to reposition), and
interpolation space (rgb / hsl / oklab). Outputs CSS / SVG / canvas string equivalents.
import { LinearGradientEditor } from 'arianna/components/graphics/colors'; const ed = new LinearGradientEditor('#mount', { angle : 45, interp: 'oklab', stops : [ { t: 0, color: { r:228, g:12, b:136, a:1 } }, { t: 0.5, color: { r:147, g:51, b:234, a:1 } }, { t: 1, color: { r:59, g:130, b:246, a:1 } }, ], }); ed.on('change', () => element.style.background = ed.toCSS());
RadialGradientEditor
Radial gradient. Shape: circle / ellipse. Size: closest-side / closest-corner /
farthest-side / farthest-corner. Centre is draggable.
import { RadialGradientEditor } from 'arianna/components/graphics/colors'; const ed = new RadialGradientEditor('#mount', { shape: 'circle', size : 'farthest-corner', cx : 50, cy: 50, });
ShapeGradientEditor
Illustrator-style freeform mesh gradient. Place arbitrary control points
on a 2D canvas; each carries a colour. Resolution is configurable via
speed
(resolution divisor for performance).import { ShapeGradientEditor } from 'arianna/components/graphics/colors'; const ed = new ShapeGradientEditor('#mount', { width : 360, height: 240, speed : 2, // 1 = full res, 2 = half, 4 = quarter });
Payments — Overview
Eight provider-specific payment buttons plus a compound
PaymentGateway that orchestrates them in a single checkout UI.
All buttons follow each provider's brand guidelines.| Component | Provider |
|---|---|
PaymentGateway | Compound — picks the best of the configured methods |
CreditCard | Visa / Mastercard / Amex / Maestro — Luhn validation, brand auto-detect |
ApplePay | Apple Pay (HIG-compliant black button) |
GooglePay | Google Pay (G-Pay multicolour mark) |
PayPal | PayPal Smart Button |
Stripe | Wraps Stripe Payment Element / hosted Checkout |
AliPay | Alipay redirect / QR |
Satispay | Italian mobile-first payments |
Nexi | Italian merchant gateway (XPay) |
Live demo
PaymentGateway
Compound widget that orchestrates every configured payment method in
a single UI. Renders a method picker and the selected provider's button / form. Emits
a unified
success event regardless of method.Constructor
import { PaymentGateway } from 'arianna/components/payments'; const pg = new PaymentGateway('#mount', { amount : 99.00, currency: 'EUR', methods : { applePay : { merchantId: 'merchant.com.example', countryCode: 'IT' }, googlePay: { merchantId: '12345…', gateway: 'stripe', gatewayMerchantId: 'acct_1…' }, card : { saveOption: true, allowedBrands: ['visa', 'mastercard'] }, paypal : { clientId: 'AYxxx' }, stripe : { publishableKey: 'pk_test_…', clientSecret: '…', returnUrl: '…' }, satispay : { redirectUrl: 'https://online.satispay.com/…' }, nexi : { redirectUrl: 'https://ecommerce.nexi.it/…' }, alipay : { mode: 'redirect', redirectUrl: '…' }, }, }); pg.on('success', e => api.confirm(e.method, e.payload)); pg.on('cancel', e => api.log('cancelled', e.method));
CreditCard
Live card preview, brand auto-detection (Visa / Mastercard / Amex /
Maestro), Luhn validation. Optional "save card for next time" checkbox.
import { CreditCard, detectBrand, validateLuhn } from 'arianna/components/payments'; const cc = new CreditCard('#mount', { amount : 99.00, currency : 'EUR', saveOption : true, allowedBrands: ['visa', 'mastercard', 'amex', 'maestro'], }); cc.on('submit', e => api.charge(e.card));
Helpers
| Function | Description |
|---|---|
| detectBrand(num) | 'visa' | 'mastercard' | 'amex' | 'maestro' | null |
| validateLuhn(num) | boolean |
| formatCardNumber(num) | '4242 4242 4242 4242' (brand-aware grouping) |
ApplePay
Apple Pay button — black, plain, HIG-compliant. Calls
window.ApplePaySession when available; falls back gracefully on unsupported
browsers.import { ApplePay } from 'arianna/components/payments'; new ApplePay('#mount', { merchantId : 'merchant.com.example.shop', countryCode: 'IT', currency : 'EUR', amount : 99.00, label : 'Acme — 1 item', supportedNetworks: ['visa', 'masterCard', 'amex'], });
GooglePay
Google Pay button. Loads
https://pay.google.com/gp/p/js/pay.js
on demand and bridges to google.payments.api.PaymentsClient.import { GooglePay } from 'arianna/components/payments'; new GooglePay('#mount', { merchantId : '12345678901234567890', gateway : 'stripe', gatewayMerchantId: 'acct_1ABCxyz', currency : 'EUR', amount : 99.00, cardNetworks : ['VISA', 'MASTERCARD', 'AMEX'], });
PayPal
PayPal Smart Button — yellow, brand-conformant. Loads PayPal SDK on
mount; emits
approve when the buyer completes the flow.import { PayPal } from 'arianna/components/payments'; new PayPal('#mount', { clientId: 'AYxxx-your-client-id', amount : 99.00, currency: 'EUR', });
Stripe
Wraps Stripe's Payment Element / hosted Checkout. You provide the
publishableKey and a clientSecret created server-side.import { Stripe } from 'arianna/components/payments'; new Stripe('#mount', { publishableKey: 'pk_test_…', clientSecret : '…', returnUrl : 'https://shop.example.com/return', });
AliPay
Alipay button — Chinese super-app. Two modes:
'redirect' opens
the Alipay payment page; 'qr' shows the QR inline for in-store / mobile pickup.import { AliPay } from 'arianna/components/payments'; new AliPay('#mount', { mode : 'redirect', // or 'qr' redirectUrl: 'https://mapi.alipay.com/gateway.do?...', amount : 99.00, currency : 'EUR', });
Satispay
Satispay redirect button — Italian mobile-first payments.
import { Satispay } from 'arianna/components/payments'; new Satispay('#mount', { redirectUrl: 'https://online.satispay.com/…', amount : 99.00, currency : 'EUR', });
Nexi
Nexi XPay — Italian merchant gateway. Redirects to the Nexi-hosted
payment page; webhook confirms payment server-side.
import { Nexi } from 'arianna/components/payments'; new Nexi('#mount', { redirectUrl: 'https://ecommerce.nexi.it/…', amount : 99.00, currency : 'EUR', });
Shipments — Overview
Tracker components for major couriers, plus a multi-carrier auto-detect
widget that picks the right one from a tracking number.
| Component | Carrier |
|---|---|
Tracker | Abstract base — generic timeline UI, override fetch() for custom carriers |
DHLTracker | DHL Express (yellow / red) |
UPSTracker | UPS (brown shield) |
FedExTracker | FedEx (purple / orange wordmark) |
BRTTracker | BRT (Bartolini) — Italian express |
TrackingMulti | Auto-detects the carrier from the tracking number; asks if ambiguous |
Tracking number formats
| Carrier | Pattern | Example |
|---|---|---|
| DHL | 10 digits | 1234567890 |
| UPS | 1Z + 16 alphanumerics | 1Z9999W99999999999 |
| FedEx | 12 / 15 / 20 digits | 012345678901234 |
| BRT | 13 digits | 1234567890123 |
Live demo
Tracker (base)
Abstract base for all carrier trackers. Renders a canonical timeline
(picked up → in transit → out for delivery → delivered) with carrier branding. Subclass
and override
fetch(trackingNumber) to plug a custom carrier.import { Tracker } from 'arianna/components/shipments'; class CustomTracker extends Tracker { async fetch(num: string) { const r = await fetch(`/api/track/${num}`); return r.json(); } } new CustomTracker('#mount', { trackingNumber: 'ABC123', config : { name: 'Custom Co.', color: '#0066cc', logoUrl: '/custom.svg' }, });
DHLTracker
DHL Express tracker — yellow / red brand, queries the DHL public tracking
API with the supplied
apiKey.import { DHLTracker } from 'arianna/components/shipments'; new DHLTracker('#mount', { trackingNumber: '1234567890', apiKey : 'demo-key', });
UPSTracker
UPS tracker — brown shield, OAuth-based UPS Tracking API.
import { UPSTracker } from 'arianna/components/shipments'; new UPSTracker('#mount', { trackingNumber: '1Z9999W99999999999', clientId : '…', clientSecret : '…', });
FedExTracker
FedEx tracker — purple / orange wordmark with the famous hidden arrow
between E and x.
import { FedExTracker } from 'arianna/components/shipments'; new FedExTracker('#mount', { trackingNumber: '012345678901234', apiKey : '…', });
BRTTracker
BRT (Bartolini) tracker — Italian express courier, red brand.
import { BRTTracker } from 'arianna/components/shipments'; new BRTTracker('#mount', { trackingNumber: '1234567890123', });
TrackingMulti
Multi-carrier auto-detect. Type a tracking number and the matching
carrier is picked from the configured list. If multiple carriers match the same pattern,
the user is asked to disambiguate.
import { TrackingMulti } from 'arianna/components/shipments'; const tm = new TrackingMulti('#mount', { carriers: ['dhl', 'ups', 'fedex', 'brt'], }); tm.on('detect', e => analytics.track('tracking', e.carrier));
Detection logic
- Match the tracking number against each carrier's regex.
- If exactly one carrier matches → auto-select.
- If multiple → render a picker, let the user choose.
- If none → emit
no-matchevent, show a friendly error.
GoogleMap
Iframe embed of Google Maps using the public
output=embed
endpoint — no API key required for the basic map. Accepts a center
(lat/lng), zoom, and an optional address string.import { GoogleMap } from 'arianna/components/maps'; const map = new GoogleMap('#mount', { center : { lat: 45.4642, lng: 9.1900 }, zoom : 13, address: 'Milano, Italy', }); map.setZoom(15); map.setLocation({ lat: 45.47, lng: 9.19 });
OpenStreetMap
Open data, no API key, no tracking. Uses the official
openstreetmap.org/export/embed.html endpoint with a computed
bounding box around the centre.import { OpenStreetMap } from 'arianna/components/maps'; new OpenStreetMap('#mount', { center: { lat: 51.5074, lng: -0.1278 }, zoom : 12, });
Dock
Desktop launcher chrome with two switchable styles. Use
setStyle('macos' | 'windows') to flip between a floating
magnifying dock and a flat taskbar at runtime.import { Dock } from 'arianna/components/layout'; const dock = new Dock('#mount', { style: 'macos', items: [ { id: 'finder', label: 'Finder', icon: '🗂️', running: true, active: true }, { id: 'mail', label: 'Mail', icon: '✉️', badge: 3 }, ], }); dock.setStyle('windows'); // switch live dock.setBadge('mail', 7); dock.on('item-click', e => console.log('clicked', e.id));
Window
Desktop-style window with draggable title bar, resize
handle, traffic-light (or Windows-style) controls, optional menu bar and
arbitrary body content. Sits on the highest
z-index when
focused and falls behind peers when blurred.import { Window } from 'arianna/components/layout'; const win = new Window(desktop, { style : 'macos', title : 'Finder', width : 420, height: 280, menu : [ { id: 'file', label: 'File' }, { id: 'edit', label: 'Edit' }, ], body : '<p style="padding:16px">Drag the title bar to move.</p>', }); win.on('close', () => console.log('closed')); win.moveTo(120, 80); win.resizeTo(600, 400);
Desktop — Dock + Windows together
A full desktop reconstruction: a wallpaper container
hosts multiple
Window instances; a single Dock
at the bottom toggles them. Click a dock icon to open / focus the matching
window; close a window and the dock badge updates.import { Dock, Window } from 'arianna/components/layout'; const dock = new Dock(dockHost, { style: 'macos', items: [...] }); const openWindows = new Map(); dock.on('item-click', e => { if (openWindows.has(e.id)) { openWindows.get(e.id).focus(); return; } const win = new Window(desktop, { title: e.id, width: 360, height: 240 }); openWindows.set(e.id, win); win.on('close', () => openWindows.delete(e.id)); });
Calendar — month view
Month / week / day calendar with event placement,
configurable week start, locale-aware labels and a built-in toolbar with
prev / next / today + view switcher.
import { Calendar } from 'arianna/components/inputs'; const cal = new Calendar('#mount', { view : 'month', weekStart: 1, // 0 = Sunday, 1 = Monday events : [ { id: 'e1', title: 'Team standup', start: new Date(), color: '#3b82f6' }, ], }); cal.setView('week'); cal.addEvent({ id: 'e2', title: 'Demo', start: new Date() });
Calendar — date picker
The same
Calendar component as Layout, used
here as a date picker: listen for day-click and wire the
selected e.date into any field.import { Calendar } from 'arianna/components/inputs'; const cal = new Calendar(calEl, { view: 'month' }); cal.on('day-click', (e) => { field.value = e.date.toLocaleDateString(undefined, { weekday: 'long', day: 'numeric', month : 'long', year: 'numeric', }); });
Animations — Overview
The
components/animations module hosts UI widgets
for hand-keyed animation. They drive any object that exposes a
set(property, values) method via the IKeyframeTarget
contract — Three.js nodes, DOM properties, Audio params, Two.ts shapes, even
abstract numerical state.import { KeyframeEditor } from 'arianna/components/animations'; const editor = new KeyframeEditor('#mount', { clips: [{ id: 'a', name: 'anim', sampleRate: 60, duration: 0.75, nodes: [{ id: 'cube', label: 'Cube', properties: [ { id: 'position', label: 'position', channels: ['x','y','z'], keyframes: [ { time: 0.00, values: [0,0,0] }, { time: 0.15, values: [1,0,0], easing: 'easeOutCubic' }, { time: 0.30, values: [1,1,0] }, ] }, ] }], }], wrapMode: 'loop', speed: 0.5, }); editor.bind('cube', { set: (prop, vals) => { /* drive target */ } }); editor.play();
Module layout
components/animations/KeyframeEditor.ts— full timeline editor with track and property canvas lanes, mouse drag, dblclick to add keyframe, right-click context menu for easingcomponents/animations/index.ts— barrel- Marries
additionals/PhysicsviaWorld.attach()andWorld.bake() - Reuses the named easing table (
linear,easeInQuad,easeOutCubic,easeOutBack,easeOutBounce,easeOutElastic, …) exported byadditionals/Animation; custom curves via{ type: 'cubic-bezier', p1x, p1y, p2x, p2y }
KeyframeEditor
3ds Max / Blender / After Effects style timeline editor.
Extends
Control<KeyframeEditorOptions>. Renders track and
property canvas lanes inside a chrome of toolbar / footer.import { KeyframeEditor } from 'arianna/components/animations'; import type { IKeyframeTarget, Keyframe, Clip, WrapMode, EasingName } from 'arianna/components/animations'; interface KeyframeEditorOptions { class? : string; theme? : 'light' | 'dark' | 'auto'; clips? : Clip[]; activeClip? : string; wrapMode? : WrapMode; // 'normal' | 'loop' | 'ping-pong' | 'clamp-forever' speed? : number; // playback multiplier view? : 'time' | 'frames'; spacing? : number; onChange? : (s: KeyframeEditorState) => void; onKeyframe? : (e: { nodeId, propertyId, keyframe }) => void; onPlay? : () => void; onStop? : () => void; }
Public API
editor.play() editor.pause() editor.stop() editor.seek(t) editor.step(frames) editor.bind(nodeId, target) // IKeyframeTarget editor.unbind(nodeId) editor.addClip(clip) editor.setActiveClip(id) editor.addKeyframe(node, prop, { time, values, easing }) editor.removeKeyframe(node, prop, index) editor.sample(node, prop, t?) // → number[] | null editor.getState() // Events: 'play', 'pause', 'stop', 'seek', 'change' editor.on('change', state => ...)
Clip / NodeTrack / Property / Keyframe
interface Clip { id : string; name : string; sampleRate : number; // fps duration : number; // seconds defaultEase?: EasingDef; nodes : NodeTrack[]; } interface NodeTrack { id: string; label: string; properties: Property[]; } interface Property { id : string; label : string; channels : string[]; // ['x','y','z'] | ['value'] | ['r','g','b'] keyframes : Keyframe[]; } interface Keyframe { time : number; values : number[]; easing?: EasingDef; // named curve, step, or cubic-bezier locked?: boolean; label? : string; } type EasingDef = | EasingName | { type: 'cubic-bezier', p1x, p1y, p2x, p2y }; type EasingName = | 'linear' | 'step' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic' | 'easeInQuart' | 'easeOutQuart' | 'easeInExpo' | 'easeOutExpo' | 'easeInOutExpo' | 'easeOutBack' | 'easeOutBounce' | 'easeOutElastic';
IKeyframeTarget
interface IKeyframeTarget { set: (property: string, values: number[]) => void; } // Three.js node example editor.bind('cube', { set: (prop, [x, y, z]) => { if (prop === 'position') cube.position.set(x, y, z); if (prop === 'scale') cube.scale.set(x, y, z); }, });
Physics — Overview
Self-contained 2D / 3D physics additional. No DOM, pure math
and scheduling. Lives next to
AI, Math, Finance,
Two, Three, Animation as a sibling addon.
Plugin pattern identical to additionals/Animation:
Core.use(Physics) mirrors all public classes onto window.import { Physics, World, Body, Box, Circle } from 'arianna/additionals/Physics'; Core.use(Physics); const world = new World({ gravity: [0, -9.81], dimension: 2 }); world.addBody(new Body({ shape: new Box(20, 0.5), static: true, position: [0, -3] })); world.addBody(new Body({ shape: new Circle(0.5), mass: 1, position: [0, 5], restitution: 0.7 })); world.start();
Capabilities
- Bodies — static / dynamic / kinematic with mass, inverse-mass, restitution, friction, damping, orientation
- Shapes — Circle / Sphere, Box / AABB, Capsule, Polygon (convex 2D)
- Constraints — Spring (Hooke + damping), DistanceConstraint, Pin, Rope
- Fields — Drag (linear + quadratic), PointGravity, Wind (with turbulence)
- Broadphase — uniform spatial hash (toggleable to naive)
- Narrowphase — circle/sphere/box pair tests + AABB fallback
- Integration — semi-implicit Euler (default) or Verlet, sub-stepping
- Collision groups + masks — bitfield filtering; sensors (no impulse)
- Keyframe marriage —
World.attach(),World.bake(),Body.followKeyframes() - Diagnostics —
on('contact-start' | 'contact-end' | 'step'),debugDraw(ctx)
Live demo
World
The simulation container. Owns bodies, constraints, fields;
drives a fixed-step loop or accepts manual
.step(dt).interface WorldOptions { gravity? : number[]; // [0,-9.81] 2D · [0,-9.81,0] 3D dimension? : 2 | 3; integrator? : 'semi-implicit-euler' | 'verlet'; timeScale? : number; // global playback speed substeps? : number; // stability: 4 is a good default broadphase? : 'spatial-hash' | 'naive'; cellSize? : number; fixedStep? : number; // 1/60 onStep? : (dt) => void; onContactStart?: (e: ContactEvent) => void; onContactEnd? : (e: ContactEvent) => void; } const world = new World({ gravity: [0, -9.81], substeps: 4 }); world.addBody(body); world.addConstraint(constraint); world.addField(new Drag(0.05, 0.01)); world.start(); // requestAnimationFrame loop world.stop(); world.step(1/60); // manual stepping world.on('contact-start', e => ...); world.on('step', dt => ...);
ContactEvent
interface ContactEvent { a: Body; b: Body; point : number[]; normal : number[]; depth : number; }
Body
A physics body: shape + transform + dynamic state. Mass and
inertia are derived from the shape. Static / kinematic flags freeze the body
against impulses while letting kinematic bodies still move (e.g. driven by
keyframes).
interface BodyOptions { shape : Shape; position? : number[]; velocity? : number[]; orientation? : number | Quat; angularVelocity?: number | number[]; mass? : number; // default 1 static? : boolean; kinematic? : boolean; sensor? : boolean; // trigger only, no impulse restitution? : number; // 0..1 friction? : number; // 0..1 linearDamping? : number; angularDamping? : number; gravityScale? : number; group? : number; // bitfield, default 0x0001 mask? : number; // bitfield, default 0xffff userData? : unknown; } body.addForce(F); // accumulated for this step body.addImpulse(J); // instant Δv body.addTorque(t); // 2D scalar / 3D vector body.followKeyframes(editor, 'cube'); // kinematic chase
Shapes
Geometry primitives. Each computes its own AABB and moment
of inertia.
Sphere is an alias of Circle for 3D
readability.new Circle(radius); new Sphere(radius); // alias of Circle new Box(width, height); // 2D new Box(width, height, depth); // 3D new Capsule(radius, length); new Polygon([[0,0], [1,0], [0.5,1]]); // convex 2D
Narrowphase pair coverage
- Circle ↔ Circle — analytic SAT
- Circle ↔ Box — closest-point clamp
- Box ↔ Box — separating-axis
- All others — AABB-only fallback (sufficient for sensors and gross overlap)
Constraints
Solved sequentially each substep. Position-based correction
for distance / rope / pin; force-based for springs.
// Hooke spring with damping world.addConstraint(new Spring(a, b, restLength=0.5, stiffness=200, damping=8)); // Rigid distance — keep bodies exactly L apart world.addConstraint(new DistanceConstraint(a, b, L)); // Pin to a world-space anchor (cancels velocity) world.addConstraint(new Pin(body, [0, 3])); // Rope — max distance, no compression world.addConstraint(new Rope(a, b, maxLength=2));
Fields
Environmental forces evaluated for every body each step.
// Air resistance — linear + quadratic terms world.addField(new Drag(linear=0.05, quadratic=0.01)); // Planet-like point gravity (inverse-square) world.addField(new PointGravity(origin=[0,0], strength=50, radius=10)); // Wind with optional turbulence world.addField(new Wind(direction=[1,0], strength=2, turbulence=0.5));
Keyframe marriage — attach & bake
The Physics additional integrates with
KeyframeEditor two ways, both first-class.1.
World.attach() — live driveEvery physics step writes resulting body positions into
the editor's bound IKeyframeTarget so visuals reflect simulation live.
const ball = world.addBody(new Body({ shape: new Circle(0.3), position: [0,3] })); editor.bind('ball', { set: (p, v) => threeBall.position.set(...v) }); world.attach(editor, { 'ball': ball }); world.start();
2.
World.bake() — physics → keyframesRuns the simulation forward and inserts keyframes into the
editor at fixed intervals, producing a deterministic, editable animation
clip from a physics run. Existing keyframes for tracked nodes are replaced.
Body state is snapshotted and restored after baking.
world.bake(editor, 'phys', { from : 0, to : 2, // seconds fps : 60, nodes: { 'ball': ball, 'crate': crate }, }); // → Returns the raw samples; the editor's clip is updated in place.
3.
Body.followKeyframes() — kinematic chaseMake a body track an animated value as kinematic input,
so authored motion can collide with simulated objects.
const paddle = world.addBody(new Body({ shape: new Box(2, 0.3), kinematic: true, })); paddle.followKeyframes(editor, 'paddle'); // physics-driven balls now bounce off the keyframed paddle
Starter templates
Twelve ready-to-run projects with full IDE configuration:
six browser starters (vanilla HTML + the AriannA runtime,
no build step) and six Tauri starters (Vite + a Rust
backend, targeting macOS / Windows / Linux / iOS / Android plus a web
preview).
Each template ships in three flavours: bare,
-vscode (.vscode/ preset), and either
-webstorm or -rustrover (.idea/
preset). Browser starters pair with VSCode + WebStorm; Tauri starters
pair with VSCode + RustRover. Unzip and open the folder in your editor of
choice — the runtime, sample components, and sourcemaps are wired up
already.All 36 ZIPs are committed to
the arianna-projects repository and served
directly via raw.githubusercontent.com — no GitHub Release tagging is
needed. Rebuild locally with
node scripts/build-zips.mjs and
commit the refreshed ZIPs.VSCode preset
Every
-vscode variant ships a .vscode/
folder with the following files..vscode/settings.json
{
"editor.formatOnSave": true,
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifier": "relative",
"files.associations": { "*.arianna": "typescript" }
}.vscode/extensions.json
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"streetsidesoftware.code-spell-checker"
]
}.vscode/launch.json
{
"version": "0.2.0",
"configurations": [{
"type": "chrome",
"request": "launch",
"name": "AriannA — launch",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}",
"sourceMaps": true
}]
}WebStorm preset
Every
-webstorm variant ships a .idea/
folder with the run / debug configuration, project SDK reference, and code
style preset matching the AriannA codebase..idea/codeStyles/Project.xml
<code_scheme name="Project"> <option name="RIGHT_MARGIN" value="100"/> <TypeScriptCodeStyleSettings> <option name="USE_SINGLE_QUOTES" value="true"/> <option name="USE_SEMICOLON_AFTER_STATEMENT" value="true"/> <option name="INDENT_SIZE" value="2"/> </TypeScriptCodeStyleSettings> </code_scheme>
.idea/runConfigurations/AriannA_dev.xml
<component name="ProjectRunConfigurationManager"> <configuration name="AriannA dev" type="NodeJSConfigurationType"> <option name="workingDir" value="$PROJECT_DIR$" /> <option name="jsFile" value="node_modules/.bin/vite" /> <option name="appParameters" value="dev" /> </configuration> </component>
Open in WebStorm / RustRover
Each template's
.idea/ folder also includes a
matching misc.xml with WebStorm as the
JavaScript-runtime IDE and (optionally) a Rust toolchain reference for
Tauri-flavoured starters such as arianna-desktop — open them
in RustRover and the frontend + backend run configurations show up
pre-configured.RustRover preset
The
-rustrover variant is paired with every
Tauri starter (six of them — Android, iOS, Linux, macOS, Web preview, and
Windows). It ships a .idea/ folder pre-wired with both the
Cargo build target and the Tauri-dev run configuration, so the Rust backend
and the AriannA TS frontend launch together with one click..idea/runConfigurations/Tauri_dev.xml
<component name="ProjectRunConfigurationManager"> <configuration name="Tauri dev" type="CargoCommandRunConfiguration"> <option name="command" value="tauri dev" /> <option name="workingDirectory" value="$PROJECT_DIR$/src-tauri" /> </configuration> </component>
.idea/runConfigurations/Cargo_build.xml
<component name="ProjectRunConfigurationManager"> <configuration name="Cargo build" type="CargoCommandRunConfiguration"> <option name="command" value="build --release" /> <option name="workingDirectory" value="$PROJECT_DIR$/src-tauri" /> </configuration> </component>
Bundled inside every Tauri ZIP
When you grab
arianna-tauri-macos-rustrover.zip
(or the iOS / Android / Linux / Windows / Web variant), the archive already
contains .idea/ with the two configurations above plus the
shared TypeScript code style. Open the unzipped folder in RustRover and
the run-configuration dropdown shows "Tauri dev" + "Cargo build" without
any further setup.