momoi-history
v0.1.0
Published
Framework-agnostic undo/redo with Command pattern, merging, grouping, and event hooks
Maintainers
Readme
momoi-history
フレームワーク非依存のUndo/Redoライブラリ。Commandパターンで履歴を管理する。
特徴
- Commandパターン — execute/undoを持つコマンドオブジェクトでスタック管理
- コマンドマージ — ドラッグ中の連続移動などを1つのundo操作に統合
- グループ化 — 複数コマンドを1回のundoでまとめて戻す
- イベント通知 — onChange で状態変化を監視(React/Zustand等との連携に)
- 例外安全 — group() 内で例外が発生したら実行済みコマンドを自動undo
- フレームワーク非依存 — React, Vue, Zustand, Redux, 素のJS、何でもOK
- TypeScript — 完全な型定義付き
- 軽量 — ESM 4.5KB / CJS 5.5KB、依存パッケージなし
インストール
npm install momoi-historyクイックスタート
import { CommandHistory, Command } from 'momoi-history'
const history = new CommandHistory()
let value = 0
// コマンドを定義して実行
const cmd: Command = {
label: '値を10に変更',
execute() { value = 10 },
undo() { value = 0 }
}
history.execute(cmd) // value → 10
history.undo() // value → 0
history.redo() // value → 10API リファレンス
CommandHistory
メインクラス。undo/redoスタックを管理する。
const history = new CommandHistory(options?)| オプション | 型 | デフォルト | 説明 |
|-----------|------|-----------|------|
| maxSize | number | 100 | undoスタックの最大サイズ |
プロパティ
| プロパティ | 型 | 説明 |
|-----------|------|------|
| canUndo | boolean | undo可能か |
| canRedo | boolean | redo可能か |
| undoCount | number | undoスタックのコマンド数 |
| redoCount | number | redoスタックのコマンド数 |
| undoLabel | string \| null | 次にundoされるコマンドのラベル |
| redoLabel | string \| null | 次にredoされるコマンドのラベル |
| maxSize | number | スタック最大サイズ(get/set) |
メソッド
| メソッド | 説明 |
|---------|------|
| execute(command) | コマンドを実行しスタックに追加。redoスタックはクリア |
| undo() | 最後のコマンドを取り消す |
| redo() | 最後に取り消したコマンドをやり直す |
| clear() | 全履歴をクリア |
| group(label, fn) | fn内の全executeを1つのundo操作にまとめる |
| beginGroup(label) | グループを手動で開始 |
| endGroup() | グループを手動で終了 |
| onChange(listener) | 変更リスナーを登録。Disposerを返す |
Command インターフェース
ユーザーが実装するコマンド。
interface Command {
readonly label: string
execute(): void
undo(): void
mergeWith?(previous: Command): Command | null
}GroupCommand
複数コマンドを1つにまとめるCommand実装。通常は group() 経由で自動生成される。
const group = new GroupCommand('一括変更', [cmd1, cmd2, cmd3])
history.execute(group)ChangeEvent
onChange リスナーに渡されるイベント。
interface ChangeEvent {
type: 'execute' | 'undo' | 'redo' | 'clear' | 'group'
command?: Command
canUndo: boolean
canRedo: boolean
}使用例
コマンドマージ(ドラッグ操作の統合)
function createMoveCommand(obj: Point, newX: number, newY: number): Command {
const oldX = obj.x, oldY = obj.y
return {
label: 'move',
execute() { obj.x = newX; obj.y = newY },
undo() { obj.x = oldX; obj.y = oldY },
mergeWith(prev) {
if (prev.label !== 'move') return null
// 前のコマンドのundoを引き継ぎ、新しい値だけ更新
return createMoveCommand(obj, newX, newY)
}
}
}Zustand との連携
import { create } from 'zustand'
import { CommandHistory, Command } from 'momoi-history'
const commandHistory = new CommandHistory()
const useHistoryStore = create((set) => ({
canUndo: false,
canRedo: false,
execute: (cmd: Command) => {
commandHistory.execute(cmd)
set({ canUndo: commandHistory.canUndo, canRedo: commandHistory.canRedo })
},
undo: () => { commandHistory.undo(); set({ canUndo: commandHistory.canUndo, canRedo: commandHistory.canRedo }) },
redo: () => { commandHistory.redo(); set({ canUndo: commandHistory.canUndo, canRedo: commandHistory.canRedo }) },
}))onChange でリアクティブに同期
const history = new CommandHistory()
// Zustand/React の状態と自動同期
history.onChange((e) => {
updateUI(e.canUndo, e.canRedo, e.type)
})Built with Claude Code by Anthropic.
