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

@io-gui/core

v2.0.0-alpha.3

Published

A lightweight library that provides core features for Io-Gui components (nodes and elements).

Readme

@io-gui/core

A lightweight (~25KB gzipped) core reactive library for Io-Gui framework.

Core Classes

ReactiveNode

Reactive Object with all Io-Gui features. Use for data models, state containers, and business logic.

IoElement

Reactive HTMLElement with ReactiveNode features plus virtual DOM rendering and CSS style management.

Both share identical APIs: reactive properties, bindings, event dispatch, change handlers, and lifecycle methods.


Registration

Every subclass must be registered before instantiation using the @Register decorator:

@Register
class MyNode extends ReactiveNode {
  @ReactiveProperty({type: String, value: ''})
  declare label: string
}

Registration triggers ProtoChain initialization which aggregates property definitions, listeners, handlers, and styles from the entire prototype chain.


Reactive Properties

Declare with @ReactiveProperty decorator or static get ReactiveProperties():

static get ReactiveProperties() {
  return {
    count: Number,                         // Type only → defaults to 0
    label: '',                             // Value only → infers type
    enabled: {type: Boolean, value: true}, // Full definition
    data: {type: Object, init: null},      // Object type with empty init
    size: {type: Array, init: [0, 0]},     // Array with initial values
    items: {type: NodeArray, init: 'this'} // NodeArray requires 'this' init
  }
}

Property Definition Options

| Option | Description | |--------|-------------| | value | Initial value | | type | Constructor (String, Number, Boolean, Array, Object, ReactiveNode, etc.) | | binding | Binding object for two-way sync | | reflect | If true, syncs to HTML attribute (IoElement only) | | init | Constructor arguments. Use null for empty init, 'this' for NodeArray |

Type Defaults

  • String''
  • Number0
  • Booleanfalse
  • Object{} (when init: null)
  • Array[] (when init: null)

Non-Reactive Properties

Use @Property decorator or static get Properties() for properties that don't need change tracking:

@Property(Object)
declare $: Record<string, HTMLElement> // Common pattern for element refs

Change Detection & Handlers

When a reactive property changes:

  1. [propName]Changed(change) handler is invoked (if defined)
  2. [propName]-changed event is dispatched with {property, value, oldValue}
  3. changed() handler is invoked
  4. io-object-mutation event is dispatched for the node
labelChanged(change: Change) {
  console.log(change.oldValue, '→', change.value)
}
changed() {
  // Called after any property change
}

Reactivity Modes

Control dispatch timing via reactivity property:

| Mode | Behavior | |------|----------| | 'immediate' | Synchronous dispatch (default) | | 'throttled' | Once per animation frame, first value wins | | 'debounced' | Once per animation frame, last value wins |


Two-Way Binding

Create bindings with this.bind('propertyName'):

const binding = sourceNode.bind('value')
targetNode.prop = binding  // Target syncs to source
// Changes propagate bidirectionally

Binding Edge Cases

  • Multiple targets: One binding can sync to many target properties
  • Binding collision: If target already has a binding, the old one is removed
  • NaN handling: NaN === NaN is false; bindings handle this correctly
  • Type mismatch: Debug mode warns if bound properties have incompatible types
  • Re-assignment: Assigning the same binding object is a no-op

Object Mutation Observation

ReactiveNode-typed Properties

Automatically observed. Mutations trigger [propName]Mutated() handlers.

@ReactiveProperty({type: MyReactiveNode, init: null})
declare data: MyReactiveNode

dataMutated(event: CustomEvent) {
  // Called when data or its descendants mutate
}

Non-ReactiveNode Objects

Observed but require manual mutation dispatch:

this.plainObject.value = 42
this.dispatchMutation(this.plainObject)

NodeArray

A Proxy-wrapped Array that auto-dispatches mutations on all mutating operations (push, splice, etc.). Items must be ReactiveNode instances.

@ReactiveProperty({type: NodeArray, init: 'this'})
declare items: NodeArray<MyReactiveNode>

itemsMutated() {
  // Called on any array modification
}

Edge cases:

  • fill() and copyWithin() are unsupported (log warning)
  • Setting length to extend array logs warning
  • Item listeners are automatically managed on add/remove

Event System

Listener Definition

Subclass Listeners replace parent handlers for the same event name (last wins). Only one proto listener is registered per event at runtime.

static get Listeners() {
  return {
    'click': 'onClick',                      // Method name
    'pointermove': ['onMove', {passive: true}], // With options
  }
}

Inline Event Listeners

const element = new MyElement({
  '@click': this.onClick,
  '@custom-event': (e) => console.log(e.detail)
})

Dispatch Events

this.dispatch('my-event', {data: 42}, true) // type, detail, bubbles

Event Propagation for ReactiveNodes

Non-DOM ReactiveNodes bubble events through _parents array. Add/remove parents with addParent()/removeParent().


Virtual DOM (IoElement)

Render children with this.render():

changed() {
  this.render([
    div({class: 'container'}, [
      span({id: 'label'}, this.label),
      MyComponent.vConstructor({value: this.bind('value')}),
    ])
  ])
}

vDOM Edge Cases

  • null children are filtered out (useful for conditional rendering)
  • Element reuse: matching tag names update props; mismatched tags replace element
  • this.$ map stores elements with id prop: this.$.label
  • Native element events require EventDispatcher attachment (automatic)
  • Text content optimized via single TextNode (no layout thrashing)

Styling (IoElement)

static get Style() {
  return /* css */`
    :host {
      display: flex;
    }
    :host[disabled] {
      opacity: 0.5;
    }
  `
}

Rules:

  • All selectors must start with :host
  • Styles are aggregated up prototype chain
  • Injected to document <head> at registration
  • Custom mixins via --mixin-name: { ... } and @apply --mixin-name

ThemeSingleton

Global CSS variables manager. Properties map to --io_propertyName variables:

ThemeSingleton.themeID = 'dark'
ThemeSingleton.spacing = 4
ThemeSingleton.bgColor = new Color(0.2, 0.2, 0.2, 1)

Themes persist to localStorage. Register custom themes with registerTheme(id, vars).


Storage

Persistent reactive values:

const myValue = Storage({key: 'my-key', value: 'default', storage: 'local'})
element.prop = myValue // Binding syncs to storage

// Storage types: 'local' (localStorage), 'hash' (URL hash), 'none'

Privacy: Storage.permit() / Storage.unpermit() controls localStorage access.


Lifecycle

| Method | When Called | |--------|-------------| | init() | Before property initialization | | ready() | After construction, before first dispatch | | changed() | After any reactive property change | | connectedCallback() | IoElement attached to DOM | | disconnectedCallback() | IoElement removed from DOM | | dispose() | Manual cleanup (removes listeners, bindings) |


Throttle/Debounce

this.throttle(this.expensiveOperation) // Once per frame, first call wins
this.debounce(this.expensiveOperation) // Once per frame, last call wins

Frame-based queue with automatic cleanup on disposal.


IoGl

WebGL-rendered element base class. Properties auto-map to shader uniforms:

@Register
class MyShader extends IoGl {
  @ReactiveProperty({type: Number, value: 0.5})
  declare intensity: number // → uniform float uIntensity

  static get Frag() {
    return `void main() { gl_FragColor = vec4(uIntensity); }`
  }
}

Theme variables available as io_* uniforms. Shared WebGL context across all instances.


IoOverlaySingleton

Document-level container for floating UI (menus, modals, tooltips). Manages focus restoration and pointer blocking.

IoOverlaySingleton.appendChild(myMenu)
IoOverlaySingleton.expanded = true

Utilities

Focus Navigation

io-focus-to event system for keyboard navigation between focusable elements.

Nudge

Viewport-aware element positioning:

nudge(floatingElement, anchorElement, 'down') // 'up'|'down'|'left'|'right'|'over'

Debug Mode

Runtime type checking, warning logs, and validation in development. Debug blocks stripped in production builds:

debug: {
  if (typeof value !== 'string') {
    console.warn('Expected string')
  }
}

Common Pitfalls

  1. Forgetting @Register: Classes must be registered before use
  2. Arrow function handlers: Won't auto-bind; use regular methods for on* handlers
  3. Direct object mutation: Must call dispatchMutation() for non-ReactiveNode objects
  4. NodeArray init: Must use init: 'this' to get owner reference
  5. Disposed nodes: Operations on disposed nodes are no-ops
  6. Binding loops: Circular bindings are prevented; same-binding assignment exits early
  7. Property type mismatch: Debug warnings for assigning wrong types
  8. CSS selector leakage: Non :host prefixed selectors leak to global scope