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

@smart-input/commitnotifier

v1.0.5

Published

Commit notification component for tracking and notifying content changes in smart inputs

Downloads

231

Readme

@smart-input/commitnotifier

A commit/submit functionality component for the Open Input editor with message history navigation. Perfect for chat interfaces, comment systems, and any application requiring submission workflows.

Features

  • 💬 Chat-like Submission - Press Enter to commit content
  • 📜 History Navigation - Use arrow keys to navigate through previous submissions
  • 💾 Persistent Storage - Optional localStorage integration for history persistence
  • ⌨️ Keyboard Shortcuts - Intuitive keyboard controls
  • 🔄 State Management - Seamless integration with Open Input state
  • 🎯 Customizable - Flexible callbacks and configuration options

🚀 Live Demo

Try the interactive examples online: https://markgregg.github.io/smart-input/

Installation

npm install @smart-input/commitnotifier @smart-input/core zustand
# or
pnpm add @smart-input/commitnotifier @smart-input/core zustand
# or
yarn add @smart-input/commitnotifier @smart-input/core zustand

Quick Start

Basic Usage

import { SmartInput, Editor } from '@smart-input/core';
import { CommitNotifier } from '@smart-input/commitnotifier';
import '@smart-input/core/style.css';

function ChatApp() {
  const [messages, setMessages] = useState([]);

  const handleCommit = (blocks) => {
    const messageText = blocks
      .map(block => block.text || block.name || '')
      .join('');
    
    setMessages([...messages, { 
      id: Date.now(), 
      text: messageText,
      timestamp: new Date()
    }]);
  };

  return (
    <div>
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id}>{msg.text}</div>
        ))}
      </div>

      <SmartInput>
        <CommitNotifier onCommit={handleCommit} />
        <Editor placeholder="Type a message... (Press Enter to send)" />
      </SmartInput>
    </div>
  );
}

Props

CommitNotifier

| Prop | Type | Required | Description | |------|------|----------|-------------| | onCommit | (blocks: Block[]) => void | Yes | Callback when content is submitted | | storageKey | string | No | LocalStorage key for persisting history | | maxHistorySize | number | No | Maximum history items to store (default: 50) | | clearOnCommit | boolean | No | Clear editor after commit (default: true) | | commitKeys | string[] | No | Keys that trigger commit (default: ['Enter']) | | preventCommitKeys | string[] | No | Modifier keys that prevent commit (default: ['Shift']) |

Examples

Simple Chat

function SimpleChat() {
  const handleCommit = (blocks) => {
    console.log('Message sent:', blocks);
    // Send to server, update UI, etc.
  };

  return (
    <SmartInput>
      <CommitNotifier onCommit={handleCommit} />
      <Editor placeholder="Type a message..." />
    </SmartInput>
  );
}

With Persistent History

function PersistentChat() {
  const handleCommit = (blocks) => {
    const message = blocksToText(blocks);
    sendToServer(message);
  };

  return (
    <SmartInput>
      <CommitNotifier 
        onCommit={handleCommit}
        storageKey="chat-history"
        maxHistorySize={100}
      />
      <Editor placeholder="Type a message..." />
    </SmartInput>
  );
}

Custom Commit Keys

// Commit with Ctrl+Enter instead of Enter
<CommitNotifier 
  onCommit={handleCommit}
  commitKeys={['Enter']}
  preventCommitKeys={[]} // Allow Shift+Enter for new lines
/>

// Or commit with Ctrl+Enter
<CommitNotifier 
  onCommit={handleCommit}
  commitKeys={['Control+Enter', 'Meta+Enter']}
/>

Don't Clear on Commit

// Keep content after submission
<CommitNotifier 
  onCommit={handleCommit}
  clearOnCommit={false}
/>

Complete Chat Interface

import { SmartInput, Editor, Block } from '@smart-input/core';
import { CommitNotifier } from '@smart-input/commitnotifier';
import { useState } from 'react';

interface Message {
  id: string;
  text: string;
  blocks: Block[];
  timestamp: Date;
  user: string;
}

function ChatInterface() {
  const [messages, setMessages] = useState<Message[]>([]);

  const handleCommit = (blocks: Block[]) => {
    if (blocks.length === 0 || blocks.every(b => !b.text)) {
      return; // Don't send empty messages
    }

    const newMessage: Message = {
      id: crypto.randomUUID(),
      text: blocks.map(b => b.text || '').join(''),
      blocks,
      timestamp: new Date(),
      user: 'current-user'
    };

    setMessages([...messages, newMessage]);
    
    // Optional: Send to server
    sendMessage(newMessage);
  };

  return (
    <div className="chat-container">
      <div className="chat-messages">
        {messages.map(message => (
          <div key={message.id} className="message">
            <div className="message-user">{message.user}</div>
            <div className="message-text">{message.text}</div>
            <div className="message-time">
              {message.timestamp.toLocaleTimeString()}
            </div>
          </div>
        ))}
      </div>

      <div className="chat-input">
        <SmartInput>
          <CommitNotifier 
            onCommit={handleCommit}
            storageKey="my-chat-history"
            maxHistorySize={50}
          />
          <Editor 
            placeholder="Type a message... (Press Enter to send, Shift+Enter for new line)"
          />
        </SmartInput>
      </div>
    </div>
  );
}

History Navigation

The CommitNotifier automatically stores submission history and allows navigation:

  • Arrow Up - Navigate to previous submission
  • Arrow Down - Navigate to next submission (or clear to start fresh)

When you navigate through history:

  1. Current content is saved as a draft
  2. Historical content is loaded into the editor
  3. Pressing Arrow Down past the newest item restores your draft

Using the Storage Hook

For advanced history management, use the useBlockStorage hook:

import { useBlockStorage } from '@smart-input/commitnotifier';

function MyComponent() {
  const { 
    blocks,
    setBlocks,
    navigateUp,
    navigateDown,
    clear,
    currentIndex,
    historyLength
  } = useBlockStorage('my-storage-key', { maxSize: 100 });

  return (
    <div>
      <div>History: {currentIndex + 1} / {historyLength}</div>
      <button onClick={navigateUp}>Previous</button>
      <button onClick={navigateDown}>Next</button>
      <button onClick={clear}>Clear History</button>
    </div>
  );
}

Keyboard Shortcuts

| Key | Action | |-----|--------| | Enter | Submit/commit content (default) | | Shift + Enter | Insert new line (prevents commit) | | Arrow Up | Navigate to previous history item | | Arrow Down | Navigate to next history item |

Storage Format

When using storageKey, history is saved to localStorage in this format:

interface StoredHistory {
  items: Block[][];
  currentIndex: number;
  maxSize: number;
}

You can access it directly:

const history = JSON.parse(
  localStorage.getItem('my-storage-key') || '{}'
);
console.log('History items:', history.items);

TypeScript Support

Full TypeScript definitions included:

import type { 
  CommitNotifierProps,
  BlockStorage,
  StorageOptions
} from '@smart-input/commitnotifier';

const props: CommitNotifierProps = {
  onCommit: (blocks) => console.log(blocks),
  storageKey: 'chat',
  maxHistorySize: 50,
  clearOnCommit: true
};

Integration Examples

With Message Formatting

import { Block, BlockType } from '@smart-input/core';

function formatBlocks(blocks: Block[]): string {
  return blocks.map(block => {
    if (block.type === BlockType.Text) {
      return block.text;
    } else if (block.type === BlockType.Styled) {
      return `**${block.text}**`; // Markdown-style bold
    } else if (block.type === BlockType.Image) {
      return `![image](${block.url})`;
    }
    return '';
  }).join('');
}

<CommitNotifier 
  onCommit={(blocks) => {
    const formatted = formatBlocks(blocks);
    sendMessage(formatted);
  }}
/>

With Validation

<CommitNotifier 
  onCommit={(blocks) => {
    const text = blocks.map(b => b.text || '').join('').trim();
    
    if (text.length === 0) {
      alert('Message cannot be empty');
      return;
    }
    
    if (text.length > 500) {
      alert('Message too long (max 500 characters)');
      return;
    }
    
    sendMessage(text);
  }}
/>

With Loading State

function ChatWithLoading() {
  const [isSending, setIsSending] = useState(false);

  const handleCommit = async (blocks: Block[]) => {
    setIsSending(true);
    try {
      await sendMessageToServer(blocks);
    } catch (error) {
      console.error('Failed to send message:', error);
    } finally {
      setIsSending(false);
    }
  };

  return (
    <SmartInput>
      <CommitNotifier onCommit={handleCommit} />
      <Editor 
        placeholder={isSending ? 'Sending...' : 'Type a message...'}
        readOnly={isSending}
      />
    </SmartInput>
  );
}

Best Practices

  1. Always validate content - Check for empty messages before processing
  2. Provide feedback - Show loading states or confirmation messages
  3. Limit history size - Use maxHistorySize to prevent memory issues
  4. Clear sensitive data - Don't persist sensitive messages to localStorage
  5. Handle errors - Wrap async operations in try-catch blocks

Documentation

For more information, see:

Requirements

  • React 18.0.0 or higher
  • @smart-input/core 1.0.0 or higher
  • zustand 5.0.0 or higher

License

MIT © Mark Gregg