diff --git a/packages/history-utility/docs/modules.md b/packages/history-utility/docs/modules.md
index e521edc..227817e 100644
--- a/packages/history-utility/docs/modules.md
+++ b/packages/history-utility/docs/modules.md
@@ -8,6 +8,7 @@
- [History](modules.md#history)
- [HistoryNode](modules.md#historynode)
+- [SkipSubscribeOrCallback](modules.md#skipsubscribeorcallback)
### Functions
@@ -27,15 +28,16 @@
#### Type declaration
-| Name | Type | Description |
-| :------ | :----------------------------------------------- | :-------------------------------------------------------------- |
-| `index` | `number` | the history index of the current snapshot |
-| `nodes` | [`HistoryNode`](modules.md#historynode)\<`T`\>[] | the nodes of the history for each change |
-| `wip?` | `Snapshot`\<`T`\> | field for holding sandbox changes; used to avoid infinite loops |
+| Name | Type | Description |
+| :------------ | :----------------------------------------------- | :-------------------------------------------------------------- |
+| `index` | `number` | the history index of the current snapshot |
+| `nodes` | [`HistoryNode`](modules.md#historynode)\<`T`\>[] | the nodes of the history for each change |
+| `unsubscribe` | () => `void` | a function to stop the internal subscription process |
+| `wip?` | `Snapshot`\<`T`\> | field for holding sandbox changes; used to avoid infinite loops |
#### Defined in
-[packages/history-utility/src/history-utility.ts:26](https://github.com/valtiojs/valtio-history/blob/86c1430/packages/history-utility/src/history-utility.ts#L26)
+[packages/history-utility/src/history-utility.ts:26](https://github.com/valtiojs/valtio-history/blob/c756d61/packages/history-utility/src/history-utility.ts#L26)
---
@@ -59,13 +61,27 @@
#### Defined in
-[packages/history-utility/src/history-utility.ts:10](https://github.com/valtiojs/valtio-history/blob/86c1430/packages/history-utility/src/history-utility.ts#L10)
+[packages/history-utility/src/history-utility.ts:10](https://github.com/valtiojs/valtio-history/blob/c756d61/packages/history-utility/src/history-utility.ts#L10)
+
+---
+
+### SkipSubscribeOrCallback
+
+Ƭ **SkipSubscribeOrCallback**: `boolean` \| `SubscribeCallback`
+
+A field to either enable/disable the internal subscribe functionality.
+Optionally a callback function can be provided to hook into the
+internal subscribe handler.
+
+#### Defined in
+
+[packages/history-utility/src/history-utility.ts:52](https://github.com/valtiojs/valtio-history/blob/c756d61/packages/history-utility/src/history-utility.ts#L52)
## Functions
### proxyWithHistory
-▸ **proxyWithHistory**\<`V`\>(`initialValue`, `skipSubscribe?`): `Object`
+▸ **proxyWithHistory**\<`V`\>(`initialValue`, `skipSubscribeOrCallback?`): `Object`
This creates a new proxy with history support (ProxyHistoryObject).
It includes following main properties:
@@ -97,10 +113,10 @@ Notes:
#### Parameters
-| Name | Type | Default value | Description |
-| :-------------- | :-------- | :------------ | :---------------------------------------------------------------- |
-| `initialValue` | `V` | `undefined` | any object to track |
-| `skipSubscribe` | `boolean` | `false` | determines if the internal subscribe behaviour should be skipped. |
+| Name | Type | Default value | Description |
+| :------------------------ | :-------------------------------------------------------------- | :------------ | :----------------------------------------------------------------------------------------------------------------- |
+| `initialValue` | `V` | `undefined` | any object to track |
+| `skipSubscribeOrCallback` | [`SkipSubscribeOrCallback`](modules.md#skipsubscribeorcallback) | `false` | determines if the internal subscribe behaviour should be skipped. Optionally, a callback function can be provided. |
#### Returns
@@ -136,4 +152,4 @@ const state = proxyWithHistory({
#### Defined in
-[packages/history-utility/src/history-utility.ts:94](https://github.com/valtiojs/valtio-history/blob/86c1430/packages/history-utility/src/history-utility.ts#L94)
+[packages/history-utility/src/history-utility.ts:108](https://github.com/valtiojs/valtio-history/blob/c756d61/packages/history-utility/src/history-utility.ts#L108)
diff --git a/packages/history-utility/src/__tests__/history-utility.vanilla.spec.ts b/packages/history-utility/src/__tests__/history-utility.vanilla.spec.ts
index 909526c..693d4e9 100644
--- a/packages/history-utility/src/__tests__/history-utility.vanilla.spec.ts
+++ b/packages/history-utility/src/__tests__/history-utility.vanilla.spec.ts
@@ -1,4 +1,4 @@
-import { describe, expect, it } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { HistoryNode, proxyWithHistory } from '../history-utility';
@@ -7,6 +7,10 @@ const mapNumbers = (node: HistoryNode<{ count: number }>) =>
describe('proxyWithHistory: vanilla', () => {
describe('basic', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
it('should provide basic history functionality', async () => {
const state = proxyWithHistory({ count: 0 });
await Promise.resolve();
@@ -25,6 +29,27 @@ describe('proxyWithHistory: vanilla', () => {
expect(state.value.count).toEqual(1);
});
+ it('should call subscribe callback when provided', async () => {
+ const callback = vi.fn();
+ const state = proxyWithHistory({ count: 0 }, callback);
+ await Promise.resolve();
+ expect(state.value.count).toEqual(0);
+
+ state.value.count += 1;
+ await Promise.resolve();
+ expect(state.value.count).toEqual(1);
+
+ state.undo();
+ await Promise.resolve();
+ expect(state.value.count).toEqual(0);
+
+ state.redo();
+ await Promise.resolve();
+ expect(state.value.count).toEqual(1);
+
+ expect(callback).toHaveBeenCalledTimes(3);
+ });
+
it('should provide basic sequential undo functionality', async () => {
const state = proxyWithHistory({ count: 0 });
await Promise.resolve();
diff --git a/packages/history-utility/src/history-utility.ts b/packages/history-utility/src/history-utility.ts
index 9288ba5..8479391 100644
--- a/packages/history-utility/src/history-utility.ts
+++ b/packages/history-utility/src/history-utility.ts
@@ -36,8 +36,21 @@ export type History = {
* the history index of the current snapshot
*/
index: number;
+ /**
+ * a function to stop the internal subscription process
+ */
+ unsubscribe: ReturnType;
};
+type SubscribeOps = Parameters[1]>[0];
+type SubscribeCallback = (ops: SubscribeOps, historySaved: boolean) => void;
+/**
+ * A field to either enable/disable the internal subscribe functionality.
+ * Optionally a callback function can be provided to hook into the
+ * internal subscribe handler.
+ */
+export type SkipSubscribeOrCallback = boolean | SubscribeCallback;
+
const isObject = (value: unknown): value is object =>
!!value && typeof value === 'object';
@@ -82,7 +95,8 @@ const deepClone = (value: T): T => {
* - Suspense/promise is not supported.
*
* @param initialValue - any object to track
- * @param skipSubscribe - determines if the internal subscribe behaviour should be skipped.
+ * @param skipSubscribeOrCallback - determines if the internal subscribe behaviour should be skipped. Optionally,
+ * a callback function can be provided.
* @returns proxyObject
*
* @example
@@ -91,7 +105,10 @@ const deepClone = (value: T): T => {
* count: 1,
* })
*/
-export function proxyWithHistory(initialValue: V, skipSubscribe = false) {
+export function proxyWithHistory(
+ initialValue: V,
+ skipSubscribeOrCallback: SkipSubscribeOrCallback = false
+) {
const proxyObject = proxy({
/**
* any value to be tracked (does not have to be an object)
@@ -107,6 +124,7 @@ export function proxyWithHistory(initialValue: V, skipSubscribe = false) {
wip: undefined, // to avoid infinite loop
nodes: [],
index: -1,
+ unsubscribe: () => {},
}),
/**
* get the date when a node was entered into history.
@@ -196,14 +214,15 @@ export function proxyWithHistory(initialValue: V, skipSubscribe = false) {
*/
subscribe: () =>
subscribe(proxyObject, (ops) => {
- if (
- ops.every(
- (op) =>
- op[1][0] === 'value' &&
- (op[0] !== 'set' || op[2] !== proxyObject.history.wip)
- )
- ) {
- proxyObject.saveHistory();
+ const shouldSaveHistory = ops.every(
+ (op) =>
+ op[1][0] === 'value' &&
+ (op[0] !== 'set' || op[2] !== proxyObject.history.wip)
+ );
+
+ if (shouldSaveHistory) proxyObject.saveHistory();
+ if (typeof skipSubscribeOrCallback === 'function') {
+ skipSubscribeOrCallback(ops, shouldSaveHistory);
}
}),
@@ -283,8 +302,8 @@ export function proxyWithHistory(initialValue: V, skipSubscribe = false) {
proxyObject.saveHistory();
- if (!skipSubscribe) {
- proxyObject.subscribe();
+ if (skipSubscribeOrCallback !== true) {
+ proxyObject.history.unsubscribe = proxyObject.subscribe();
}
return proxyObject;