{"_id":"@busyexplore/zotonic-data-history1","name":"@busyexplore/zotonic-data-history1","dist-tags":{"beta":"0.0.0","latest":"0.0.0"},"versions":{"0.0.0":{"name":"@busyexplore/zotonic-data-history1","private":false,"version":"0.0.0","type":"module","main":"dist/zotonic-data-history.umd.js","module":"dist/zotonic-data-history.es.js","scripts":{"dev":"set NODE_OPTIONS=--openssl-legacy-provider && vite","build":"vite build","lint":"eslint .","preview":"vite preview","deploy":"npm version prerelease --preid=beta --no-git-tag-version && npm publish --tag beta"},"dependencies":{"react":"^16.8 || ^17 || ^18","react-dom":"^16.8 || ^17 || ^18"},"peerDependencies":{"react":"^16.8 || ^17 || ^18","react-dom":"^16.8 || ^17 || ^18"},"devDependencies":{"@eslint/js":"^9.29.0","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitejs/plugin-react":"^4.6.0","eslint":"^9.29.0","eslint-plugin-react-hooks":"^5.2.0","eslint-plugin-react-refresh":"^0.4.20","globals":"^16.2.0","vite":"^6.3.5"},"_id":"@busyexplore/zotonic-data-history1@0.0.0","gitHead":"a577b1a9b32e2ebd0a6f26df59972282d0a7c9a6","description":"A lightweight, generic React hook for managing **undo/redo history**.","_nodeVersion":"20.9.0","_npmVersion":"10.1.0","dist":{"integrity":"sha512-yDpCEwxKYjnQaNj6sGav5cfMen4PRt84XCd5Af1+F777f5g9TAjcBMdUfnnMRjE12tKYCzqMHQPKAxO3Oo05Cg==","shasum":"9ae9ada688c0b9e54b5f152d347c50a75e0e71ae","tarball":"https://registry.npmjs.org/@busyexplore/zotonic-data-history1/-/zotonic-data-history1-0.0.0.tgz","fileCount":4,"unpackedSize":10957,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIErl+niyF+z72y2J1oHbIV8YKIORDVCXCdlZH/7xb+76AiEAvQsLnAdYJoQEik6ZvrEvIbcVMs/TWVToIdy219LK/mw="}]},"_npmUser":{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"},"directories":{},"maintainers":[{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/zotonic-data-history1_0.0.0_1786720440478_0.18353692909870323"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-14T15:14:00.252Z","0.0.0":"2026-08-14T15:14:00.601Z","modified":"2026-08-14T15:14:00.895Z"},"maintainers":[{"name":"busyexplore","email":"suprise.mlimi97@gmail.com"}],"description":"A lightweight, generic React hook for managing **undo/redo history**.","readme":"# @busyexplore/zotonic-data-history\n\nA lightweight, generic React hook for managing **undo/redo history**.\n\nHistory entries are keyed by a `type`, allowing different kinds of data or commands to share the same history stack while still supporting type-specific event listeners.\n\n## Features\n\n* Generic undo/redo history management\n* Configurable maximum history size\n* Separate **past** and **future** stacks\n* Automatically clears the redo stack when a new action is pushed\n* Type-specific undo/redo event callbacks\n* Notification when old history entries are evicted\n* Subscription methods return unsubscribe functions\n* Exposes `canUndo`, `canRedo`, and `stackSize`\n\n## Installation\n\nInstall the package using npm:\n\n```bash\nnpm install @busyexplore/zotonic-data-history\n```\n\nOr with Yarn:\n\n```bash\nyarn add @busyexplore/zotonic-data-history\n```\n\n## Requirements\n\nThe hook requires React and uses:\n\n* `useRef`\n* `useState`\n* `useCallback`\n\n## Usage\n\n```jsx\nimport useReactZotonicDataHistory from '@busyexplore/zotonic-data-history';\n\nfunction Editor() {\n  const history = useReactZotonicDataHistory(100);\n\n  const handleChange = (item) => {\n    history.push('text', 'update', item);\n  };\n\n  const handleUndo = () => {\n    history.undo();\n  };\n\n  const handleRedo = () => {\n    history.redo();\n  };\n\n  return (\n    <div>\n      <button onClick={handleUndo} disabled={!history.canUndo}>\n        Undo\n      </button>\n\n      <button onClick={handleRedo} disabled={!history.canRedo}>\n        Redo\n      </button>\n    </div>\n  );\n}\n```\n\n## API\n\n### `useReactZotonicDataHistory(stackSize)`\n\nCreates a history manager.\n\n```js\nconst history = useReactZotonicDataHistory(100);\n```\n\n### Parameters\n\n| Parameter   | Type     | Default | Description                                             |\n| ----------- | -------- | ------: | ------------------------------------------------------- |\n| `stackSize` | `number` |   `100` | Maximum number of entries retained in the undo history. |\n\n### `canUndo`\n\n```js\nhistory.canUndo\n```\n\nReturns `true` when at least one action is available to undo.\n\n### `canRedo`\n\n```js\nhistory.canRedo\n```\n\nReturns `true` when at least one action is available to redo.\n\n### `stackSize`\n\n```js\nhistory.stackSize\n```\n\nReturns the configured maximum history size.\n\n### `push(type, command, item)`\n\nAdds a new entry to the history.\n\n```js\nhistory.push('text', 'update', {\n  id: 1,\n  value: 'Hello',\n});\n```\n\nEach entry has the following structure:\n\n```js\n{\n  type,\n  command,\n  item\n}\n```\n\nAdding a new entry **clears the entire redo stack**.\n\nIf the history exceeds `stackSize`, the oldest entries are removed and reported through `onDeleteOldHistory`.\n\n### `undo()`\n\nMoves the most recent history entry from the undo stack to the redo stack.\n\n```js\nhistory.undo();\n```\n\nIf there is nothing to undo, the method does nothing.\n\nWhen an entry is undone, registered `onUndo` callbacks receive:\n\n```js\n(type, command, item)\n```\n\n### `redo()`\n\nMoves the most recent history entry from the redo stack back to the undo stack.\n\n```js\nhistory.redo();\n```\n\nIf there is nothing to redo, the method does nothing.\n\nWhen an entry is redone, registered `onRedo` callbacks receive:\n\n```js\n(type, command, item)\n```\n\n## Event Listeners\n\nThe hook supports three event subscriptions.\n\nEach subscription returns an **unsubscribe function**.\n\n### `onUndo(callback)`\n\nCalled whenever an entry is undone.\n\n```js\nconst unsubscribe = history.onUndo((type, command, item) => {\n  console.log('Undo:', type, command, item);\n});\n\n// Later:\nunsubscribe();\n```\n\n### `onRedo(callback)`\n\nCalled whenever an entry is redone.\n\n```js\nconst unsubscribe = history.onRedo((type, command, item) => {\n  console.log('Redo:', type, command, item);\n});\n```\n\n### `onDeleteOldHistory(callback)`\n\nCalled when an entry is removed because the configured history limit has been exceeded.\n\n```js\nconst unsubscribe = history.onDeleteOldHistory(\n  (type, command, item) => {\n    console.log('History entry evicted:', type, command, item);\n  }\n);\n```\n\nThis is useful when history entries hold resources that need cleanup.\n\n## Example: Applying Commands\n\nA common pattern is to store enough information in `item` for listeners to apply the corresponding operation.\n\n```jsx\nconst history = useReactZotonicDataHistory(50);\n\nhistory.onUndo((type, command, item) => {\n  if (type === 'shape' && command === 'create') {\n    removeShape(item.id);\n  }\n});\n\nhistory.onRedo((type, command, item) => {\n  if (type === 'shape' && command === 'create') {\n    createShape(item);\n  }\n});\n\n// Record an operation\nhistory.push('shape', 'create', {\n  id: 'shape-1',\n  x: 100,\n  y: 200,\n});\n```\n\nThe hook itself does **not** modify application data. It only tracks history and emits events.\n\nYour application decides what each `type`, `command`, and `item` means.\n\n## History Lifecycle\n\nThe history behaves as follows:\n\n```text\npush(A)\n  │\n  ▼\npast:   [A]\nfuture: []\n\npush(B)\n  │\n  ▼\npast:   [A, B]\nfuture: []\n\nundo()\n  │\n  ▼\npast:   [A]\nfuture: [B]\n\nredo()\n  │\n  ▼\npast:   [A, B]\nfuture: []\n\nundo()\n  │\n  ▼\npast:   [A]\nfuture: [B]\n\npush(C)\n  │\n  ▼\npast:   [A, C]\nfuture: []\n```\n\nA new action after an undo discards the redo history.\n\n## History Limit\n\nThe maximum history size can be configured:\n\n```js\nconst history = useReactZotonicDataHistory(20);\n```\n\nWhen the 21st entry is added, the oldest entry is removed:\n\n```text\nBefore:\n\n[A, B, C, ..., T]\n\nPush U:\n\n[A, B, C, ..., T, U]\n ↑\n removed\n\nAfter:\n\n[B, C, D, ..., T, U]\n```\n\nThe removed entry triggers `onDeleteOldHistory`.\n\n## Type-Based History\n\nThe `type` field allows multiple kinds of operations to share one history stack.\n\nFor example:\n\n```js\nhistory.push('text', 'insert', textData);\nhistory.push('shape', 'create', shapeData);\nhistory.push('image', 'delete', imageData);\n```\n\nListeners can then respond based on the type:\n\n```js\nhistory.onUndo((type, command, item) => {\n  switch (type) {\n    case 'text':\n      // Undo text operation\n      break;\n\n    case 'shape':\n      // Undo shape operation\n      break;\n\n    case 'image':\n      // Undo image operation\n      break;\n  }\n});\n```\n\n## Complete Return Value\n\n`useReactZotonicDataHistory()` returns:\n\n```js\n{\n  canUndo,\n  canRedo,\n  stackSize,\n  push,\n  undo,\n  redo,\n  onUndo,\n  onRedo,\n  onDeleteOldHistory,\n}\n```\n\n## Important Notes\n\n### The hook does not store application state\n\n`useReactZotonicDataHistory` stores history entries, but it does not automatically reverse or reapply your application's changes.\n\nFor example:\n\n```js\nhistory.push('text', 'update', item);\n```\n\ndoes not itself update or restore the text.\n\nYour `onUndo` and `onRedo` handlers are responsible for performing those operations.\n\n### Event callbacks receive the original entry data\n\nCallbacks receive:\n\n```js\n(type, command, item)\n```\n\nThis allows the application to interpret the history entry however it needs.\n\n### Subscribers should be cleaned up\n\nBecause subscriptions return unsubscribe functions, components should remove subscriptions when appropriate:\n\n```jsx\nuseEffect(() => {\n  const unsubscribe = history.onUndo(handleUndo);\n\n  return unsubscribe;\n}, [history, handleUndo]);\n```\n\n<!-- ## License\n\nAdd the project's applicable license here. -->\n","readmeFilename":"README.md","_rev":"1-2b2789fe64ba531102056b97c7a29c6f"}