{"version":3,"file":"ngxtension-signal-history.mjs","sources":["../../../../libs/ngxtension/signal-history/src/signal-history.ts","../../../../libs/ngxtension/signal-history/src/ngxtension-signal-history.ts"],"sourcesContent":["import {\n\tcomputed,\n\tInjector,\n\trunInInjectionContext,\n\tSignal,\n\tsignal,\n\tWritableSignal,\n} from '@angular/core';\nimport { assertInjector } from 'ngxtension/assert-injector';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\n\ninterface SignalHistoryRecord<T> {\n\tvalue: T;\n\ttimestamp: number;\n}\n\n/**\n * Creates a history record with the current timestamp.\n * @param value The value to store in the history record.\n * @returns A SignalHistoryRecord object.\n */\nfunction createHistoryRecord<T>(value: T): SignalHistoryRecord<T> {\n\treturn { value, timestamp: Date.now() };\n}\n\n/**\n * Enhances a writable signal with undo/redo history functionality.\n *\n * @param source The writable signal to track.\n * @param options Configuration options for the history.\n * @returns An object with `history`, `undo`, `redo`, `canUndo`, and `canRedo` properties.\n */\nexport function signalHistory<T>(\n\tsource: WritableSignal<T>,\n\toptions?: {\n\t\t/**\n\t\t * The maximum number of history records to store.\n\t\t * @default 100\n\t\t */\n\t\tcapacity?: number;\n\n\t\t/**\n\t\t * A function that determines whether a value should be recorded in the history.\n\t\t * By default, all values are recorded.\n\t\t */\n\t\tshouldRecord?: (value: T, oldValue: T) => boolean;\n\n\t\t/**\n\t\t * The injector to use for the effect.\n\t\t * @default undefined\n\t\t */\n\t\tinjector?: Injector;\n\t},\n): {\n\t/**\n\t * The history of changes to the source signal.\n\t */\n\thistory: Signal<SignalHistoryRecord<T>[]>;\n\t/**\n\t * Undo the last change to the source signal.\n\t */\n\tundo: () => void;\n\t/**\n\t * Redo the last undone change to the source signal.\n\t */\n\tredo: () => void;\n\t/**\n\t * Reset the history to the current state.\n\t */\n\treset: () => void;\n\t/**\n\t * Clear the history. This will remove all history records.\n\t */\n\tclear: () => void;\n\t/**\n\t * A signal indicating if undo is possible.\n\t */\n\tcanUndo: Signal<boolean>;\n\t/**\n\t * A signal indicating if redo is possible.\n\t */\n\tcanRedo: Signal<boolean>;\n} {\n\tconst injector = assertInjector(signalHistory, options?.injector);\n\treturn runInInjectionContext(injector, () => {\n\t\tconst capacity = options?.capacity ?? 100; // Default capacity is 100 records\n\n\t\t// Initialize the undo and redo stacks as signals\n\t\tconst undoStack = signal<SignalHistoryRecord<T>[]>([]);\n\t\tconst redoStack = signal<SignalHistoryRecord<T>[]>([]);\n\n\t\t// Initialize with the current value of the source signal.\n\t\t// This ensures that the history always starts with the initial value.\n\t\tconst initialRecord = createHistoryRecord(source());\n\t\tundoStack.set([initialRecord]);\n\n\t\t// Computed signal to provide the history of changes\n\t\tconst history = computed(() => [...undoStack()]);\n\n\t\t// Computed signals to indicate if undo/redo actions are available\n\t\tconst canUndo = computed(() => undoStack().length > 1); // Can undo if there's more than just the initial state\n\t\tconst canRedo = computed(() => redoStack().length > 0); // Can redo if there's more than just the initial state\n\n\t\t// Use explicitEffect to track changes to the source signal\n\t\texplicitEffect(\n\t\t\t[source],\n\t\t\t([value]) => {\n\t\t\t\tconst lastValue = undoStack()[undoStack().length - 1]?.value;\n\n\t\t\t\t// skip if the value is the same as the last value\n\t\t\t\tif (value === lastValue) return;\n\n\t\t\t\t// skip if the value should not be recorded\n\t\t\t\tif (options?.shouldRecord && !options?.shouldRecord(value, lastValue))\n\t\t\t\t\treturn;\n\n\t\t\t\tconst newRecord = createHistoryRecord(value);\n\n\t\t\t\t// Update the undo stack with the new record\n\t\t\t\tundoStack.update((stack) => {\n\t\t\t\t\tconst newStack = [...stack, newRecord];\n\t\t\t\t\treturn capacity ? newStack.slice(-capacity) : newStack; // Apply capacity limit if provided\n\t\t\t\t});\n\n\t\t\t\t// Clear the redo stack when a new change is made,\n\t\t\t\t// because a new change invalidates the redo stack.\n\t\t\t\tredoStack.set([]);\n\t\t\t},\n\t\t\t{ defer: true, injector: options?.injector },\n\t\t);\n\n\t\t/**\n\t\t * Undo the last change to the source signal.\n\t\t */\n\t\tconst undo = () => {\n\t\t\tif (undoStack().length > 1) {\n\t\t\t\t// Prevent undoing the initial state\n\t\t\t\t// Get the last record from the undo stack\n\t\t\t\tconst currentRecord = undoStack()[undoStack().length - 1];\n\n\t\t\t\t// Remove the last record from the undo stack\n\t\t\t\tundoStack.update((stack) => stack.slice(0, -1));\n\n\t\t\t\t// Add the current record to the redo stack\n\t\t\t\tredoStack.update((stack) => [currentRecord, ...stack]);\n\n\t\t\t\tconst previousRecord = undoStack()[undoStack().length - 1];\n\n\t\t\t\t// Set the source signal to the previous value\n\t\t\t\tsource.set(previousRecord.value);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Redo the last undone change to the source signal.\n\t\t */\n\t\tconst redo = () => {\n\t\t\tif (redoStack().length) {\n\t\t\t\t// Get the first record from the redo stack as we want to remove it\n\t\t\t\tconst nextRecord = redoStack()[0];\n\n\t\t\t\t// Remove the first record from the redo stack\n\t\t\t\tredoStack.update((stack) => stack.slice(1));\n\n\t\t\t\t// Add the next record to the undo stack\n\t\t\t\tundoStack.update((stack) => [...stack, nextRecord]);\n\n\t\t\t\t// Set the source signal to the next value\n\t\t\t\tsource.set(nextRecord.value);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Reset the history to the current state.\n\t\t */\n\t\tconst reset = () => {\n\t\t\tconst currentRecord = undoStack()[undoStack().length - 1];\n\t\t\tundoStack.set([currentRecord]);\n\t\t\tredoStack.set([]);\n\t\t};\n\n\t\t/**\n\t\t * Clear the history. This will remove all history records.\n\t\t */\n\t\tconst clear = () => {\n\t\t\tundoStack.set([]);\n\t\t\tredoStack.set([]);\n\t\t};\n\n\t\t// Return the history, undo/redo/reset/clear functions, and canUndo/canRedo signals\n\t\treturn {\n\t\t\thistory,\n\t\t\tundo,\n\t\t\tredo,\n\t\t\treset,\n\t\t\tclear,\n\t\t\tcanUndo,\n\t\t\tcanRedo,\n\t\t};\n\t});\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}