Interactiv
A TypeScript framework for building embedded interactive applications with event management, state management, navigation, and screensaver functionality. The package is designed to maximise compatibility with older versions of Node/Chromium, specifically to work with BrightSign media players.
The package also emphsises support for touchscreen input, but can also be used for cursor/poiner device inputs.
test change
Installation
Local Development with npm link
Since this is a local package, you can install it in your projects using npm link:
npm link
npm link interactiv
Features
- Event Management: Unified pointer interactions (mouse & touch)
- State Management: Global and external state management with subscriptions
- Navigation: Page and view-based navigation system
- Screensaver: Built-in screensaver functionality
- Settings Manager: Hidden settings page with corner touch activation
- Animation Bus: Animation coordination system
- App Builder: Component-based application architecture
Basic Usage
import {
createOrchestrator,
AppBuilder,
Page,
View,
Component,
html,
css,
} from "interactiv";
import "interactiv/animations.css";
const orchestrator = createOrchestrator();
const app = new AppBuilder(orchestrator);
const homePage = new Page("home-page", orchestrator, false);
const homeView = new View(
"home-view",
orchestrator,
false,
html`<div class="home"><h1>Welcome</h1></div>`,
css`.home { padding: 2rem; }`
);
homePage.addView(homeView);
app.addPage(homePage);
app.attachToDom();
app.navigateToView("home-view");
orchestrator.navigateToView("home-view", {
type: "fade",
duration: 300
});
Core Modules
Event Orchestrator
Create and manage event buses for communication between components:
import { createOrchestrator } from "interactiv";
const orchestrator = createOrchestrator();
const eventBus = orchestrator.registerEventBus("my-bus");
eventBus.on("my-event", (event) => {
console.log(event.detail);
});
eventBus.emit("my-event", { data: "Hello!" });
State Management
Manage global application state with subscriptions:
import {
setGlobalState,
getGlobalState,
subscribeToGlobalState,
} from "interactiv";
setGlobalState("user.name", "John");
const userName = getGlobalState("user.name");
subscribeToGlobalState("user.name", (value) => {
console.log("Name changed:", value);
});
Navigation Manager
Important: NavigationManager is a singleton service that should not be instantiated directly. It is automatically created by AppBuilder, and you access navigation methods through your AppBuilder instance.
Navigate between pages and views:
const app = new AppBuilder(orchestrator);
app.addPage(homePage);
app.navigateToPage("home-page");
app.navigateToView("home-view", {
type: "fade",
duration: 300,
easing: "ease-in-out"
});
const currentPageId = app.getCurrentPageId();
const currentViewId = app.getCurrentViewId();
const isTransitioning = app.isTransitioning();
❌ Incorrect Usage:
const navManager = new NavigationManager(orchestrator);
The NavigationManager uses shared global state and event bus namespaces. Creating multiple instances would cause:
- Conflicting global state management
- Event bus namespace collisions
- Ambiguous navigation routing
For this reason, only one instance exists per application, managed by AppBuilder.
Navigating from Within Components
All components have access to the orchestrator via this.orchestrator, making navigation simple and consistent:
import { Component } from "interactiv";
class MyComponent extends Component {
constructor(id: string, orchestrator: EventOrchestrator) {
super(id, orchestrator);
this.setupNavigation();
}
private setupNavigation(): void {
this.point(".nav-button", () => {
this.orchestrator.navigateToView("target-view", {
type: "slide",
direction: "left",
duration: 300
});
});
this.point(".page-button", () => {
this.orchestrator.navigateToPage("target-page", {
type: "fade",
duration: 400
});
});
}
protected defineTemplate(): void {
this.template = html`
<div>
<button class="nav-button">Go to View</button>
<button class="page-button">Go to Page</button>
</div>
`;
}
protected defineStyles(): void {
this.styles = css`
button {
padding: 1rem;
margin: 0.5rem;
}
`;
}
}
Key Benefits:
- Simple and direct API - no need to access event bus manually
- Consistent mental model: orchestrator coordinates everything
- Less boilerplate code
- Type-safe navigation methods
Event Manager
Handle pointer interactions (mouse & touch) in components:
import { EventManager } from "interactiv";
const eventManager = new EventManager(shadowRoot, "my-component");
eventManager.point(".button", (data) => {
console.log("Clicked at:", data.x, data.y);
});
eventManager.hover(".item", {
enter: (data) => console.log("Hover enter"),
leave: (data) => console.log("Hover leave"),
});
eventManager.drag(".draggable", {
start: (data) => console.log("Drag start"),
move: (data) => console.log("Dragging"),
end: (data) => console.log("Drag end"),
});
Screensaver Manager
The screensaver manager monitors user activity and triggers actions after a configurable timeout period. It supports two modes:
Standard Screensaver Mode
Navigate to a dedicated screensaver page after inactivity. User activity exits the screensaver and returns to the previous or starting page.
const screensaverPage = new Page("screensaver-page", orchestrator, false);
const screensaverView = new View("screensaver-view", orchestrator, false,
html`<div class="screensaver">...</div>`,
css`.screensaver { }`
);
screensaverPage.addView(screensaverView);
app.addScreensaver(screensaverPage, {
timeoutSeconds: 30,
defaultViewId: "screensaver-view",
exitBehavior: "return",
transitionConfig: { type: "fade", duration: 500 },
});
Return-to-Home Mode
Instead of showing a screensaver, navigate back to a specified home page/view after inactivity. No screensaver page is needed — the app simply redirects to the home screen. The screensaver never enters an "active" state, so user activity just resets the inactivity timer.
app.addScreensaver(null, {
timeoutSeconds: 60,
screensaverViewBehavior: "returnHome",
startingPageId: "home-page",
defaultViewId: "home-view",
transitionConfig: { type: "fade", duration: 500 },
});
Configuration Options
timeoutSeconds | number | Seconds of inactivity before activation |
page | Page | Screensaver page (required unless using returnHome) |
screensaverViewBehavior | "default" | "specific" | "return" | "returnHome" | How to handle the view on activation |
defaultViewId | string | Default view to show (or home view for returnHome) |
exitBehavior | "reset" | "return" | Where to go when exiting the screensaver |
startingPageId | string | Page to navigate to on exit/reset (or home page for returnHome) |
startingViewId | string | View within the starting page on exit |
transitionConfig | TransitionConfig | Transition animation configuration |
activateCallback | () => void | Called when screensaver activates or returns home |
deactivateCallback | () => void | Called when screensaver deactivates |
blockerCallback | () => boolean | Return true to prevent activation |
rebootTimeout | number | null | Minutes before triggering a reboot callback |
rebootCallback | () => void | Called when reboot timeout elapses |
Settings Manager
Create hidden settings pages with corner touch activation. See SETTINGS_MANAGER.md for detailed documentation.
Template Helpers
Use the html and css tagged template literals for better IDE support:
import { html, css } from "interactiv";
const template = html`
<div class="container">
<h1>Title</h1>
</div>
`;
const styles = css`
.container {
padding: 1rem;
}
`;
TypeScript Support
This package includes full TypeScript definitions. Import types as needed:
import type {
PageProps,
ViewProps,
ComponentProps,
PointerEventData,
DragCallbacks,
HoverCallbacks,
SwipeCallbacks,
StateSubscription,
} from "interactiv";
Development
Building the Package
npm run build
This compiles TypeScript to JavaScript and copies the CSS file to the dist folder.
Linting and Formatting
npm run lint
npm run format
npm run check
License
ISC
Contributing
Contributions are welcome! Please ensure all code passes linting and formatting checks before submitting.