npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

exodeui-react-native

v6.9.10

Published

React Native runtime for ExodeUI animations

Readme

exodeui-react-native

The official React Native Runtime for the ExodeUI Animation Engine. Render complex, high-performance interactive animations natively using Shopify's React Native Skia.


🌟 Features & Specialties

ExodeUI isn't just another animation player. It brings game-engine capabilities to your React Native application:

  • 120 FPS Skia Rendering: Hardware-accelerated graphics via React Native Skia for butter-smooth performance.
  • True 3D Transforms: Full 3D rotation (X, Y, Z), depth translation, and perspective projection in a 2D canvas.
  • Physics Engine & Constraints: Built-in 2D rigid body physics (Matter.js) with Inverse Kinematics (IK), Follow Path, and Spring constraints.
  • Interactive State Machines: Complex state logic (Hover, Pressed, Toggled) designed in the editor, driven at runtime.
  • Rich UI Components: Buttons, Toggle Switches, Sliders, Dropdowns, ListViews, and dynamic Line/Bar/Pie Charts — all rendered inside the canvas.
  • Advanced Masking & Blending: Boolean operations, nested clipping masks, and blend modes.
  • Liquid Morphing & Path Distortion: Real-time noise-based path distortions and fluid shape morphing.

Installation

npm install exodeui-react-native @shopify/react-native-skia
# or
yarn add exodeui-react-native @shopify/react-native-skia

Setup

1. Install React Native Skia

Configure @shopify/react-native-skia following their installation guide.

2. Configure Metro Bundler

Wrap your metro.config.js with the withExode utility. It wires up everything for you: .exode files are routed through the ExodeUI metro-transformer (which parses the artboard, glyph-subsets its fonts Rive-style, and bundles extracted images as assets), and ttf/otf/woff/woff2 files are registered as bundleable assets:

// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withExode } = require('exodeui-react-native/withExode');

const defaultConfig = getDefaultConfig(__dirname);
const config = {
  // Your other metro config here
};

module.exports = withExode(mergeConfig(defaultConfig, config));

Then restart the packager with a clean cache:

npx react-native start --reset-cache

Basic Usage

import { ExodeUIView } from 'exodeui-react-native';
import splashAnimation from './assets/splash.exode';

function App() {
  return (
    <ExodeUIView
      artboard={splashAnimation}
      style={{ width: '100%', height: 400 }}
      autoPlay={true}
    />
  );
}

You can also load from a remote URL:

<ExodeUIView
  src="https://example.com/animations/splash.json"
  style={{ flex: 1 }}
/>

<ExodeUIView /> Props

Core Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | artboard | object | — | Direct artboard data object (e.g. imported from a .exode file). | | src | string | — | Remote URL to fetch artboard JSON from. | | style | ViewStyle | — | Styles applied to the container View. | | autoPlay | boolean | true | Whether the animation loop runs automatically. | | fit | 'Contain' \| 'Cover' \| 'Fill' | 'Contain' | How the artboard scales inside the view. | | alignment | 'Center' \| 'TopLeft' \| 'TopRight' \| 'BottomLeft' \| 'BottomRight' | 'Center' | Alignment of the artboard within the view. | | interactive | boolean | true | Whether touch/pointer events are forwarded to the engine. | | revision | number | 0 | Increment this to force a full artboard reload. | | currentTime | number | — | Manually seek the animation to a specific time (in seconds). |

Input Binding Props

Pass reactive values directly as props — no onReady needed:

| Prop | Type | Description | |------|------|-------------| | inputs | Record<string, any> | Map of input names to values. Updated reactively. | | scrollY | number | Shorthand for binding vertical scroll position. | | scrollX | number | Shorthand for binding horizontal scroll position. | | mouseX | number | Pointer X position in canvas coordinates. | | mouseY | number | Pointer Y position in canvas coordinates. | | isMouseDown | boolean | Whether a pointer is currently pressed. |

Event Listener Props

| Prop | Type | Description | |------|------|-------------| | onReady | (engine: ExodeUIEngine) => void | Called once the engine has loaded the artboard and is ready. | | onButtonClick | (name: string, objectId: string) => void | Fired when a Button component is tapped. | | onToggle | (name: string, checked: boolean) => void | Fired when a Toggle Switch is flipped. | | onInputChange | (name: string, text: string) => void | Fired when text inside an InputBox changes. | | onInputFocus | (name: string) => void | Fired when an InputBox receives focus. | | onInputBlur | (name: string) => void | Fired when an InputBox loses focus. | | onGraphPointClick | (event: GraphPointClickEvent) => void | Fired when a point on a Line/Bar chart is tapped. | | onTrigger | (triggerName: string, animationName: string) => void | Fired when a state machine trigger activates. | | onInputUpdate | (nameOrId: string, value: any) => void | Fired whenever any input value changes internally. | | onComponentChange | (event: ComponentEvent) => void | Fired on any generic component state change. | | onLogicUpdate | (values: Record<string, any>) => void | Fires every frame with the current state of all logic node outputs. | | onLog | (msg: string) => void | Receives internal engine log messages (useful for debugging). |


ExodeUIEngine (via onReady)

The engine object gives you full imperative control over the artboard at runtime.

State Variables (Inputs)

State Variables (referred to internally as "Inputs") allow you to drive the logic of your state machines from your React Native code. ExodeUI supports several types of state variables:

  • Boolean: Simple true / false switches.
  • Number: Float or integer values (e.g. 0 to 100).
  • Trigger: Momentary pulses that auto-reset (e.g. for "Jump" or "Click").
  • Array: Arrays of numbers or strings (e.g. for charts or list data).
  • Random Number / Random Color: Engine-managed variables that dynamically evaluate based on seeds and time.

Setting State Variables

// Set a boolean state variable (e.g. "IsMenuOpen")
engine.updateInput('IsMenuOpen', true);

// Set a number state variable (e.g. "Speed")
engine.updateInput('Speed', 42);

// Fire a trigger (momentary pulse)
engine.updateInput('Jump', true); // Trigger types auto-pulse

// Set an Array
engine.setInputNumberArray('ChartData', [10, 20, 30, 40]);

Reading State Variables

You can also read the current value of any state variable at runtime using engine.getInputValue(). This works for standard variables you set, as well as dynamic ones like Random Number and Random Color evaluated internally by the engine:

// Read a boolean, number, array, or dynamically generated variable
const isMenuOpen = engine.getInputValue('IsMenuOpen');
const randomVal = engine.getInputValue('MyRandomNumber'); // Read engine-managed random value
console.log(`Menu is open: ${isMenuOpen}, Random: ${randomVal}`);

Data & Component Updates

// Update a LineGraph or BarChart dataset
engine.updateGraphData('RevenueChart', [120, 340, 210, 480, 390]);

// Update options for a Dropdown or ListView
engine.updateObjectOptions('CategoryDropdown', ['Electronics', 'Clothing', 'Food']);

// Modify a constraint at runtime (e.g. spring stiffness)
engine.updateConstraint('SpringArm', 0, { stiffness: 300, damping: 20 });

Layout & Playback

// Change fit/alignment dynamically
engine.setLayout('Cover', 'TopLeft');

// Seek to a specific time
engine.seek(2.5); // 2.5 seconds

// Reset all states to artboard defaults
engine.reset();

Reading State

// Get currently active state IDs for a layer
const activeStates = engine.getActiveStateIds('NavigationLayer');
console.log(activeStates); // e.g. ['state_home']

Direct Listeners (alternative to props)

engine.setButtonClickCallback((name, id) => { ... });
engine.setToggleCallback((name, checked) => { ... });
engine.setInputChangeCallback((name, text) => { ... });
engine.setInputFocusCallback((name) => { ... });
engine.setInputBlurCallback((name) => { ... });
engine.setGraphPointClickCallback((event) => { ... });
engine.setTriggerCallback((triggerName, animationName) => { ... });
engine.setInputUpdateCallback((nameOrId, value) => { ... });
engine.setComponentChangeCallback((event) => { ... });
engine.setOnLogCallback((msg) => { ... });
engine.setLogicUpdateCallback((values) => { ... });

ref Access

Use a ref to access the engine imperatively without onReady:

const viewRef = useRef(null);

// Later:
const engine = viewRef.current?.getEngine();
engine?.updateInput('IsVisible', true);

// In JSX:
<ExodeUIView ref={viewRef} artboard={data} style={{ flex: 1 }} />

Complete Examples

1. Button Click → Navigate

<ExodeUIView
  artboard={myArtboard}
  style={{ flex: 1 }}
  onButtonClick={(name, id) => {
    if (name === 'btn_start') navigation.navigate('Home');
  }}
/>

2. Toggle Switch → Update App State

<ExodeUIView
  artboard={settingsArtboard}
  style={{ height: 300, width: '100%' }}
  onToggle={(name, checked) => {
    if (name === 'DarkModeToggle') setDarkMode(checked);
  }}
/>

3. Real-time Scroll Binding

const scrollY = useSharedValue(0);

<Animated.ScrollView
  onScroll={(e) => { scrollY.value = e.nativeEvent.contentOffset.y; }}
>
  <ExodeUIView
    artboard={headerArtboard}
    style={{ height: 200, width: '100%' }}
    scrollY={scrollY.value}
  />
</Animated.ScrollView>

4. Live Data Chart

<ExodeUIView
  artboard={dashboardArtboard}
  style={{ flex: 1 }}
  onReady={(engine) => {
    // Push live sensor data every second
    setInterval(() => {
      engine.updateGraphData('HeartRate', getLiveSensorData());
    }, 1000);
  }}
/>

5. State Machine — Driving an Interactive Game UI

<ExodeUIView
  artboard={gameHUD}
  style={{ flex: 1 }}
  inputs={{ Health: playerHealth, Score: score, IsGameOver: isGameOver }}
  onTrigger={(triggerName, animation) => {
    if (triggerName === 'LevelComplete') showLevelModal();
  }}
/>

6. Listening to Graph Point Taps

<ExodeUIView
  artboard={chartArtboard}
  style={{ height: 300, width: '100%' }}
  onGraphPointClick={({ pointIndex, value, label }) => {
    Alert.alert(`Point ${label}`, `Value: ${value}`);
  }}
/>

7. InputBox (Text Field inside Canvas)

<ExodeUIView
  artboard={loginArtboard}
  style={{ flex: 1 }}
  onInputChange={(name, text) => {
    if (name === 'EmailField') setEmail(text);
    if (name === 'PasswordField') setPassword(text);
  }}
  onButtonClick={(name) => {
    if (name === 'LoginButton') handleLogin();
  }}
/>

Fonts & Typography (Rive-Level)

Rive subsets font glyphs directly into the .riv binary so playback needs zero external HTTP requests and ships only the glyphs that are actually used. ExodeUI for React Native does the exact same thing at build time: the metro-transformer glyph-subsets every font an artboard uses and bundles the result as a tiny asset. At runtime, text renders 100% offline — no Google Fonts <link> injection, no network TLS handshake.

How it works

  1. Build time (metro-transformer) — wired automatically by withExode. It accepts both plain-JSON and ZIP .exode exports. For every font family the artboard uses, the transformer:
    • collects the exact text it renders (static strings, segments, component labels, and keyframed animated strings),
    • adds a small curated safe glyph set (ASCII, common European accents, typographic punctuation, currency) so dynamic numbers/punctuation never tofu, plus Latin Extended / Greek / Cyrillic when the artboard has dynamic text inputs or charts,
    • subsets the font to just those glyphs (harfbuzz, the same engine used by production subsetting tools) and emits it as a require()d asset. A static artboard typically ships ~30–70% smaller fonts; artboards using large multi-language fonts (thousands of glyphs) see far bigger savings.
    • Also extracts large embedded images and zip assets/ entries into bundled assets so they don't bloat the JS bundle.
  2. Runtime (offline registry) — require('./myArtboard.exode') returns the parsed artboard object. The subsetted fonts are registered through the __bundledFonts map and resolved with zero HTTP requests.

Build artifact: the transformer writes subset fonts and extracted images to a .exode-assets/ folder next to each .exode file. It's regenerated on demand (and gitignored), so you can delete it any time and Metro rebuilds it. Font families with no local source are skipped (a warning is logged) and fall back to the platform default font at runtime.

Providing font sources

The transformer needs a font file for each family. Sources are resolved in this order:

  1. Embedded in the .exode file — fonts/ zip entries or data:font/*;base64,… URIs in the document JSON (the truly self-contained, Rive-style case).

  2. Inline __fontMap in the artboard JSON.

  3. Sidecar map — a myArtboard.exode.fonts.json file next to the artboard. This is the recommended workflow for Google-Fonts-by-name artboards like Inter/Oswald/etc. — a plain JSON file that maps family → font path:

    {
      "Inter": "./assets/fonts/Inter.ttf",
      "Playfair Display": "./assets/fonts/PlayfairDisplay.ttf",
      "Inter-Bold": "./assets/fonts/Inter-Bold.ttf"
    }

    Paths are relative to the .exode file.

  4. Runtime fonts prop / registerFont — for fonts that can't be resolved at build time, register them in JS (also fully offline):

    <ExodeUIView
      artboard={splash}
      style={{ flex: 1 }}
      fonts={{ Inter: require('./assets/fonts/Inter.ttf') }}
    />

    or globally:

    import { registerFont, preloadFonts } from 'exodeui-react-native';
    registerFont('Inter', require('./assets/fonts/Inter.ttf'));
    preloadFonts(['Inter']);

If a family has no local source at all, the transformer logs a warning and the runtime falls back to the platform default font while the optional Google Fonts network fetch (ON by default, so text always loads) resolves the family in the background. Once a local source exists — sidecar, embedded, or the fonts prop — that family renders fully offline with zero HTTP requests.

Subset overrides

Control the glyph set per family via an inline __fontSubset field:

{
  "name": "myArtboard",
  "__fontSubset": {
    "Inter": {
      "text": "µΩ→",                          // extra characters to always keep
      "ranges": [[0x0370, 0x03ff]],           // extra unicode ranges (greek, …)
      "dynamic": true                          // include the dynamic-text ranges
    }
  },
  "objects": [ … ]
}

Google Fonts network fallback

Families that have no local font source fall back to the Google Fonts network fetch by default (this is the historical behavior, so text always loads). Provide local font files (sidecar / embedded / fonts prop) to make that family render fully offline. For strict offline rendering:

<ExodeUIView artboard={data} allowNetworkFonts={false} />   // per-view

// or globally:
import { configureFonts } from 'exodeui-react-native';
configureFonts({ networkFallback: false });

TypeScript Types

import type { ExodeUIEngine, ExodeUIViewProps } from 'exodeui-react-native';

// GraphPointClickEvent shape
type GraphPointClickEvent = {
  objectId: string;
  componentName: string;
  datasetIndex: number;
  pointIndex: number;
  value: number;
  label: string;
};

License

MIT