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

@jmkcoder/components

v1.0.15

Published

Odyssey Web Components Library

Readme

Odyssey Components

A library of web components for modern web applications, featuring the powerful Node Explorer component.

Components

Node Explorer

A hierarchical tree view component with drag-and-drop capabilities, multi-selection support, and keyboard navigation.

Installation

npm install @jmkcoder/components

Usage

In a module environment (with bundlers like Webpack, Rollup, Vite)

// Simple import - automatically registers all components
import '@jmkcoder/components';

In HTML (via CDN or local file)

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="./node_modules/@jmkcoder/components/dist/components.bundle.css">
  <script type="module" src="./node_modules/@jmkcoder/components/dist/index.js"></script>
  <script>
    window.addEventListener('DOMContentLoaded', () => {
      // Register the components
      OdysseyComponents.defineNodeExplorer();
    });
  </script>
  <style>
    odyssey-node-explorer {
      height: 500px;
      display: block;
      border: 1px solid #e0e0e0;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h1>Node Explorer Example</h1>
  <odyssey-node-explorer id="explorer"></odyssey-node-explorer>

  <script>
    window.addEventListener('DOMContentLoaded', () => {
      const explorer = document.getElementById('explorer');
      
      // Set up nodes data
      const nodes = [
        {
          id: 'root',
          label: 'Project Files',
          expanded: true,
          icon: 'folder',
          children: [
            { id: 'src', label: 'src', icon: 'folder' },
            { id: 'docs', label: 'docs', icon: 'folder' },
            { id: 'package.json', label: 'package.json', icon: 'description' }
          ]
        }
      ];
      
      // Set nodes to the explorer
      explorer.setAttribute('nodes', JSON.stringify(nodes));
      
      // Listen for events
      explorer.addEventListener('node-selected', (e) => {
        console.log('Selected:', e.detail.node);
      });
    });
  </script>
</body>
</html>

Check out the complete examples in the examples/ directory:

Node Explorer API

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | nodes | String (JSON) | '[]' | A JSON string of the tree nodes structure | | allow-drag-drop | Boolean | true | Enable or disable drag and drop functionality | | allow-multi-select | Boolean | false | Enable or disable multi-selection of nodes | | theme | String | 'light' | Theme variant ('light', 'dark', 'minimal', 'high-contrast') |

Node Structure

// Basic node structure
{
  id: 'unique-id',       // Required unique identifier
  label: 'Node Label',   // Display text
  expanded: false,       // Whether the node is expanded (if it has children)
  icon: 'folder',        // Optional icon name (using Material Icons)
  children: [],          // Optional array of child nodes
  hasChildren: false,    // Indicate if node has children even if not loaded
  isLazy: false,         // For dynamic loading of children
  isLoading: false       // Indicates loading state
}

Methods

// Get a reference to the node explorer
const explorer = document.getElementById('explorer');

// Node manipulation
explorer.addNode('parent-id', { id: 'new-node', label: 'New Node' });
explorer.removeNode('node-id');
explorer.moveNode('source-id', 'target-id', 'inside'); // 'inside', 'before', or 'after'

// Node state control
explorer.expandNode('node-id');
explorer.collapseNode('node-id');
explorer.selectNode('node-id');

// Get node information
const selectedNode = explorer.getSelectedNode();
const selectedNodes = explorer.getSelectedNodes(); // For multi-select
const node = explorer.findNodeById('node-id');

// Lazy loading
explorer.markNodeAsLazy('node-id', true);
explorer.setNodeChildren('parent-id', childNodes);
explorer.appendNodeChildren('parent-id', moreChildNodes);

Events

// Selection events
explorer.addEventListener('node-selected', (e) => {
  console.log('Selected node:', e.detail.node);
});

explorer.addEventListener('nodes-selected', (e) => {
  console.log('Multiple nodes selected:', e.detail.nodes);
});

// Expansion events
explorer.addEventListener('node-expanded', (e) => {
  console.log('Node expanded:', e.detail.nodeId);
});

explorer.addEventListener('node-collapsed', (e) => {
  console.log('Node collapsed:', e.detail.nodeId);
});

// Lazy loading
explorer.addEventListener('load-children', (e) => {
  const { nodeId, node } = e.detail;
  console.log(`Loading children for node: ${nodeId}`);
  
  // Simulate fetching data
  setTimeout(() => {
    const children = [
      { id: `${nodeId}-child1`, label: 'Child 1' },
      { id: `${nodeId}-child2`, label: 'Child 2' }
    ];
    
    explorer.setNodeChildren(nodeId, children);
  }, 500);
});

// Drag and drop events
explorer.addEventListener('drop', (e) => {
  console.log('Node dropped:', e.detail);
});

// Structure changes
explorer.addEventListener('nodes-changed', (e) => {
  console.log('Node structure changed:', e.detail.nodes);
  // You can persist this new structure to your backend
});

Features

Themes

Four built-in themes: light, dark, minimal, and high-contrast for accessibility.

// Apply a theme
explorer.setAttribute('theme', 'dark');

// Or use the property
explorer.theme = 'high-contrast';

Keyboard Navigation

Full keyboard support for navigation and selection:

  • Arrow keys for navigation
  • Space/Enter for selection
  • Home/End for jumping to first/last node

Drag and Drop

Intuitive drag and drop functionality for reorganizing nodes:

  • Drag nodes to reorder within the same parent
  • Drag nodes to new parents
  • Visual indicators show valid drop targets
// Disable drag and drop
explorer.setAttribute('allow-drag-drop', 'false');

// Or use the property
explorer.allowDragDrop = false;

Multi-Select

Support for selecting multiple nodes using Ctrl/Cmd+click and Shift+click.

// Enable multi-select
explorer.setAttribute('allow-multi-select', 'true');

// Or use the property
explorer.allowMultiSelect = true;

Lazy Loading

Efficient handling of large data sets by loading children on demand:

// Mark a node as lazy-loadable
explorer.markNodeAsLazy('folder-id', true);

// Listen for load events
explorer.addEventListener('load-children', handleLoadChildren);

function handleLoadChildren(event) {
  const { nodeId } = event.detail;
  
  // Fetch data from server
  fetchChildrenFromServer(nodeId).then(children => {
    // Provide the children to the explorer
    explorer.setNodeChildren(nodeId, children);
  });
}

CSS Customization

You can customize the component appearance using CSS custom properties:

odyssey-node-explorer {
  --primary-color: #3b82f6;
  --primary-light: #93c5fd;
  --gray-light: #f3f4f6;
  --gray-medium: #e5e7eb;
  --text-color: #111827;
  --text-light: #6b7280;
  --node-padding: 6px 8px;
  --node-margin: 2px 0;
  --node-border-radius: 6px;
  --node-font-size: 13px;
  --connector-width: 2px;
  --connector-color: #e8e8e8;
  --selection-bg: rgba(59, 130, 246, 0.2);
  --transition-duration: 200ms;
  --transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Dark theme example */
odyssey-node-explorer.dark-theme {
  --primary-color: #60a5fa;
  --primary-light: #2563eb;
  --gray-light: #374151;
  --gray-medium: #1f2937;
  --text-color: #f9fafb;
  --text-light: #d1d5db;
  --connector-color: #4b5563;
  --selection-bg: rgba(96, 165, 250, 0.2);
}

Project Structure

odyssey-components/
├── src/                  # Source code
│   ├── components/       # Web components
│   │   ├── node-explorer/  # Node Explorer component
│   │   │   ├── node-explorer.ts        # Main component file
│   │   │   ├── node-explorer.scss      # Component styles
│   │   │   ├── node-explorer.type.ts   # TypeScript interfaces
│   │   │   └── services/               # Component services
│   │   │       ├── node.service.ts           # Node data handling
│   │   │       ├── node-renderer.service.ts  # DOM rendering
│   │   │       └── drag-drop.service.ts      # Drag & drop handling
│   │   └── odyssey-button/ # Button component
│   ├── utilities/        # Utility functions
│   └── stories/          # Storybook documentation
├── examples/             # Usage examples
└── distlib/              # Built distribution files

Development

To develop and test the components locally:

# Install dependencies
npm install

# Run development server with hot reloading
npm run dev

# Run Storybook for component documentation
npm run storybook

# Build the library
npm run build

# Build Storybook documentation site
npm run build-storybook

Development Workflow

  1. Make changes to component files in src/components/
  2. Test in development server or Storybook
  3. Build the library when ready
  4. Check the examples in the examples/ directory to verify the build

Contribution

Contributions are welcome! Please feel free to submit a Pull Request.

License

ISC

Browser Support

The Node Explorer component supports all modern browsers:

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Acknowledgments

This component uses Material Icons for node icons.