npm package: @opncanvas/history
Description: Undo/redo system for design schemas using command pattern
Latest version: 0.1.0

# @opncanvas/history

Undo/redo system for design schemas using the command pattern. Works with any state management or framework.

## Installation

```bash
npm install @opncanvas/history @opncanvas/schema @opncanvas/engine
```

## Usage with React

```tsx
import { useHistory } from '@opncanvas/history'
import { updateNodeProperty } from '@opncanvas/engine'
import { initialFormSchema } from '@opncanvas/templates'

function Editor() {
  const { schema, setSchema, undo, redo, canUndo, canRedo } = useHistory(initialFormSchema)

  const handleLabelChange = (nodeId: string, newLabel: string) => {
    setSchema(
      updateNodeProperty(nodeId, 'label', newLabel),
      'Update button label' // Optional description
    )
  }

  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>Undo (Ctrl+Z)</button>
      <button onClick={redo} disabled={!canRedo}>Redo (Ctrl+Y)</button>
      {/* Your editor UI */}
    </div>
  )
}
```

## Features

- **Keyboard Shortcuts**: Automatic Ctrl+Z (undo) and Ctrl+Y/Ctrl+Shift+Z (redo)
- **Command Descriptions**: Track what each change does
- **Configurable History Size**: Limit memory usage
- **Batch Commands**: Group multiple changes into one undo step
- **Framework Agnostic**: Use the `HistoryManager` class directly without React

## API

### `useHistory(initialSchema, maxHistorySize?)`

React hook that returns:

- `schema` - Current schema state
- `setSchema(updater, description?)` - Apply a change with history tracking
- `undo()` - Undo last change
- `redo()` - Redo last undone change
- `canUndo` - Whether undo is available
- `canRedo` - Whether redo is available
- `clear()` - Clear all history
- `undoSize` - Number of undo steps
- `redoSize` - Number of redo steps
- `undoDescription` - Description of last undo
- `redoDescription` - Description of last redo

### `HistoryManager`

Class for managing history without React:

```typescript
import { HistoryManager, createCommand } from '@opncanvas/history'
import { updateNodeProperty } from '@opncanvas/engine'

const history = new HistoryManager(50) // Max 50 undo steps

let schema = initialSchema

// Execute a command
const command = createCommand(
  updateNodeProperty('button_1', 'label', 'New Label'),
  'Update button label'
)
schema = history.execute(command, schema)

// Undo
const undoneSchema = history.undo(schema)
if (undoneSchema) schema = undoneSchema

// Redo
const redoneSchema = history.redo(schema)
if (redoneSchema) schema = redoneSchema
```

### `createCommand(updater, description?)`

Create a command from an updater function.

### `createBatchCommand(commands, description?)`

Group multiple commands into one:

```typescript
import { createBatchCommand, createCommand } from '@opncanvas/history'
import { updateNodeProperty } from '@opncanvas/engine'

const batch = createBatchCommand([
  createCommand(updateNodeProperty('button_1', 'label', 'Submit')),
  createCommand(updateNodeProperty('button_1', 'color', '#000'))
], 'Style button')
```

## Examples

### Custom Keyboard Shortcuts

```tsx
import { useHistory } from '@opncanvas/history'
import { useEffect } from 'react'

function Editor() {
  const history = useHistory(initialSchema)

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.ctrlKey && e.key === 'u') {
        e.preventDefault()
        history.undo()
      }
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [history.undo])

  return <div>...</div>
}
```

### History UI

```tsx
function HistoryPanel() {
  const { undoSize, redoSize, undoDescription, canUndo, canRedo, undo, redo } = useHistory(initialSchema)

  return (
    <div>
      <div>Undo Stack: {undoSize}</div>
      <div>Redo Stack: {redoSize}</div>
      <button onClick={undo} disabled={!canUndo}>
        Undo {undoDescription ? `(${undoDescription})` : ''}
      </button>
      <button onClick={redo} disabled={!canRedo}>
        Redo {redoDescription ? `(${redoDescription})` : ''}
      </button>
    </div>
  )
}
```

## License

MIT