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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@web-loom/ui-core

v0.5.2

Published

Framework-agnostic headless UI behaviors for modern web applications.

Downloads

140

Readme

@web-loom/ui-core

Framework-agnostic headless UI behaviors for modern web applications.

Overview

@web-loom/ui-core provides atomic UI interaction behaviors that work across different frameworks (React, Vue, Angular) or with vanilla JavaScript. All behaviors are built on top of @web-loom/store-core for reactive state management and are completely headless - providing logic without assumptions about styling or DOM structure.

Installation

npm install @web-loom/ui-core

Features

  • Framework-agnostic: Works with React, Vue, Angular, or vanilla JS
  • Lightweight: Each behavior is <2KB gzipped with tree-shaking support
  • Type-safe: Full TypeScript support with comprehensive type definitions
  • Headless: Pure logic, no styling or DOM assumptions
  • Composable: Behaviors can be combined to create complex patterns
  • Batteries included: Framework adapters for React, Vue, and Angular
  • Reactive: Built on observable patterns for automatic UI updates

Available Behaviors

📚 Detailed Documentation: See the docs folder for comprehensive guides on each behavior, including advanced usage, accessibility guidelines, and framework-specific examples.

Dialog Behavior

Manages modal dialog state with open/close/toggle actions.

import { createDialogBehavior } from '@web-loom/ui-core';

const dialog = createDialogBehavior({
  id: 'settings-dialog',
  onOpen: (content) => console.log('Dialog opened with:', content),
  onClose: () => console.log('Dialog closed'),
});

// Open the dialog
dialog.actions.open({ title: 'Settings', tab: 'general' });

// Subscribe to changes
const unsubscribe = dialog.subscribe((state) => {
  console.log('Dialog state:', state);
});

// Clean up
unsubscribe();
dialog.destroy();

Disclosure Behavior

Controls expandable/collapsible content sections (accordions, dropdowns).

import { createDisclosureBehavior } from '@web-loom/ui-core';

const disclosure = createDisclosureBehavior({
  id: 'faq-item',
  initialExpanded: false,
  onToggle: (isExpanded) => console.log('Expanded:', isExpanded),
});

disclosure.actions.toggle();
console.log(disclosure.getState().isExpanded); // true

Form Behavior

Manages form state with validation, dirty tracking, and submission handling.

import { createFormBehavior } from '@web-loom/ui-core';

const form = createFormBehavior({
  initialValues: { email: '', password: '' },
  validateOnChange: true,
  validateOnBlur: true,
  onSubmit: async (values) => {
    console.log('Submitting:', values);
  },
});

// Set field value
form.actions.setFieldValue('email', '[email protected]');

// Set field error
form.actions.setFieldError('email', 'Invalid email format');

// Submit form
await form.actions.submitForm();

List Selection Behavior

Manages single or multi-select list interactions with keyboard navigation support.

import { createListSelection } from '@web-loom/ui-core';

const selection = createListSelection({
  items: ['item-1', 'item-2', 'item-3'],
  mode: 'multi',
  onSelectionChange: (selected) => console.log('Selected:', selected),
});

// Select items
selection.actions.select('item-1');
selection.actions.toggleSelection('item-2');

// Check selection
console.log(selection.getState().selectedItems); // ['item-1', 'item-2']

Roving Focus Behavior

Implements keyboard navigation for composite widgets (toolbars, menus, grids).

import { createRovingFocus } from '@web-loom/ui-core';

const rovingFocus = createRovingFocus({
  items: ['button-1', 'button-2', 'button-3'],
  orientation: 'horizontal',
  loop: true,
  onFocusChange: (focusedId) => console.log('Focused:', focusedId),
});

// Move focus
rovingFocus.actions.moveNext();
rovingFocus.actions.movePrevious();
rovingFocus.actions.moveFirst();
rovingFocus.actions.moveLast();

Keyboard Shortcuts Behavior

Manages keyboard shortcut registration and execution with platform normalization.

import { createKeyboardShortcuts } from '@web-loom/ui-core';

const shortcuts = createKeyboardShortcuts({
  scope: 'global',
  onShortcutExecuted: (key) => console.log(`Executed: ${key}`),
});

// Register shortcuts
shortcuts.actions.registerShortcut({
  key: 'Ctrl+K',
  handler: () => console.log('Command palette opened'),
  description: 'Open command palette',
  preventDefault: true,
});

// Platform-specific keys are normalized (Cmd on macOS, Ctrl on Windows/Linux)
shortcuts.actions.registerShortcut({
  key: 'Cmd+Shift+P',
  handler: () => console.log('Command palette opened'),
  description: 'Open command palette',
  preventDefault: true,
});

// Clean up
shortcuts.destroy();

Undo/Redo Stack Behavior

Maintains an immutable history of states with undo/redo operations.

import { createUndoRedoStack } from '@web-loom/ui-core';

interface EditorState {
  content: string;
  cursor: number;
}

const undoRedo = createUndoRedoStack<EditorState>({
  initialState: { content: '', cursor: 0 },
  maxLength: 100,
  onStateChange: (state) => console.log('State changed:', state),
});

// Push new states
undoRedo.actions.pushState({ content: 'Hello', cursor: 5 });
undoRedo.actions.pushState({ content: 'Hello World', cursor: 11 });

// Undo
undoRedo.actions.undo();
console.log(undoRedo.getState().present); // { content: 'Hello', cursor: 5 }

// Redo
undoRedo.actions.redo();
console.log(undoRedo.getState().present); // { content: 'Hello World', cursor: 11 }

Drag-and-Drop Behavior

Manages drag-and-drop interaction state with validation and callbacks.

import { createDragDropBehavior } from '@web-loom/ui-core';

const dragDrop = createDragDropBehavior({
  onDragStart: (itemId, data) => console.log('Drag started:', itemId),
  onDragEnd: (itemId) => console.log('Drag ended:', itemId),
  onDrop: (draggedItem, dropTarget, data) => {
    console.log('Dropped', draggedItem, 'on', dropTarget);
  },
  validateDrop: (draggedItem, dropTarget) => {
    return dropTarget !== 'restricted-zone';
  },
});

// Register drop zones
dragDrop.actions.registerDropZone('zone-1');
dragDrop.actions.registerDropZone('zone-2');

// Start dragging
dragDrop.actions.startDrag('item-1', { type: 'card', priority: 'high' });

// Perform drop
dragDrop.actions.drop('zone-1');

Framework Adapters

React

import { useDialogBehavior } from '@web-loom/ui-core/react';

function MyComponent() {
  const dialog = useDialogBehavior({
    onOpen: (content) => console.log('Opened:', content),
  });

  return (
    <div>
      <button onClick={() => dialog.open({ title: 'Hello' })}>
        Open Dialog
      </button>
      {dialog.state.isOpen && (
        <div>Dialog is open with content: {dialog.state.content.title}</div>
      )}
    </div>
  );
}

Vue

import { useDialogBehavior } from '@web-loom/ui-core/vue';

export default {
  setup() {
    const dialog = useDialogBehavior({
      onOpen: (content) => console.log('Opened:', content),
    });

    return { dialog };
  },
};
<template>
  <div>
    <button @click="dialog.open({ title: 'Hello' })">Open Dialog</button>
    <div v-if="dialog.state.isOpen">
      Dialog is open with content: {{ dialog.state.content.title }}
    </div>
  </div>
</template>

Angular

import { Component } from '@angular/core';
import { DialogBehaviorService } from '@web-loom/ui-core/angular';

@Component({
  selector: 'app-my-component',
  template: `
    <button (click)="dialog.actions.open({ title: 'Hello' })">Open Dialog</button>
    <div *ngIf="(dialog.state$ | async)?.isOpen">
      Dialog is open
    </div>
  `,
  providers: [DialogBehaviorService],
})
export class MyComponent {
  constructor(public dialog: DialogBehaviorService) {}
}

Vanilla JavaScript

import { createDialogBehavior } from '@web-loom/ui-core';

const dialog = createDialogBehavior();

// Subscribe to state changes and update DOM
dialog.subscribe((state) => {
  const dialogEl = document.getElementById('my-dialog');
  dialogEl.style.display = state.isOpen ? 'block' : 'none';
});

// Attach to button
document.getElementById('open-btn').addEventListener('click', () => {
  dialog.actions.open({ title: 'Hello' });
});

API Reference

Common Behavior Interface

All behaviors follow a consistent interface:

interface Behavior<State, Actions> {
  getState: () => State;
  subscribe: (listener: (state: State) => void) => () => void;
  actions: Actions;
  destroy: () => void;
}
  • getState(): Returns current state snapshot
  • subscribe(listener): Subscribe to state changes, returns unsubscribe function
  • actions: Object containing all available actions for the behavior
  • destroy(): Cleanup method to remove all subscriptions

Tree-Shaking

The package is built with ESM support and proper sideEffects: false configuration. Import only what you need:

// Only imports the dialog behavior
import { createDialogBehavior } from '@web-loom/ui-core';

// Or use direct imports for maximum tree-shaking
import { createDialogBehavior } from '@web-loom/ui-core/behaviors/dialog';

TypeScript

Full TypeScript support with exported types for all behaviors:

import type {
  DialogState,
  DialogActions,
  DialogBehavior
} from '@web-loom/ui-core';

Detailed Documentation

For comprehensive guides including advanced usage, accessibility guidelines, and real-world examples, see the documentation for each behavior:

New Behaviors

Behavior Enhancements

Each documentation file includes:

  • Complete API reference with TypeScript interfaces
  • Advanced usage examples and patterns
  • Framework integration guides (React, Vue, Angular)
  • Accessibility guidelines and ARIA recommendations
  • Performance considerations and best practices
  • Common use cases and real-world examples

Dependencies

  • @web-loom/store-core: Reactive state management (included)
  • @web-loom/event-bus-core: Event system for cross-component communication (included)

License

MIT