@pixerate/editor 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ export { ExtractedMention, escapeHtml, extractMentionsFromDoc, getEditorMarkdown
6
6
  import { Editor, EditorOptions, Content, Extensions } from '@tiptap/core';
7
7
  export { Content, Editor, EditorOptions, Extensions } from '@tiptap/core';
8
8
  export { ASTNode, BinaryOpNode, CellCoordinate, CellData, CellDep, CellFormat, CellRange, CellRefNode, ColumnBinding, ColumnType, DataSourceBinder, DependencyGraph, EvaluationContext, FormulaEvaluator, FormulaParser, FormulaToken, FormulaTokenType, FunctionCallNode, LiteralNode, PropertyRefNode, RangeNode, RowType, SpreadsheetColumn, SpreadsheetController, SpreadsheetControllerOptions, SpreadsheetDataSource, SpreadsheetDataSourceField, SpreadsheetDocument, SpreadsheetRow, UnaryOpNode, colNameToIndex, extractCellDependencies, indexToColName, parseCellReference, parseFormula, parseRange, tokenizeFormula } from './spreadsheet/index.cjs';
9
+ export { Command, HistoryManager, HistoryManagerOptions, HistoryState, MergedExecutionOptions, SnapshotCommandOptions, createHistoryManager } from './history/index.cjs';
9
10
  export { MarkdownOptions } from 'tiptap-markdown';
10
11
  import '@tiptap/pm/view';
11
12
  import '@tiptap/pm/model';
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { ExtractedMention, escapeHtml, extractMentionsFromDoc, getEditorMarkdown
6
6
  import { Editor, EditorOptions, Content, Extensions } from '@tiptap/core';
7
7
  export { Content, Editor, EditorOptions, Extensions } from '@tiptap/core';
8
8
  export { ASTNode, BinaryOpNode, CellCoordinate, CellData, CellDep, CellFormat, CellRange, CellRefNode, ColumnBinding, ColumnType, DataSourceBinder, DependencyGraph, EvaluationContext, FormulaEvaluator, FormulaParser, FormulaToken, FormulaTokenType, FunctionCallNode, LiteralNode, PropertyRefNode, RangeNode, RowType, SpreadsheetColumn, SpreadsheetController, SpreadsheetControllerOptions, SpreadsheetDataSource, SpreadsheetDataSourceField, SpreadsheetDocument, SpreadsheetRow, UnaryOpNode, colNameToIndex, extractCellDependencies, indexToColName, parseCellReference, parseFormula, parseRange, tokenizeFormula } from './spreadsheet/index.js';
9
+ export { Command, HistoryManager, HistoryManagerOptions, HistoryState, MergedExecutionOptions, SnapshotCommandOptions, createHistoryManager } from './history/index.js';
9
10
  export { MarkdownOptions } from 'tiptap-markdown';
10
11
  import '@tiptap/pm/view';
11
12
  import '@tiptap/pm/model';
package/dist/index.js CHANGED
@@ -3834,6 +3834,234 @@ var DirtyTracker = class {
3834
3834
  function createDirtyTracker(initialBaseline, options) {
3835
3835
  return new DirtyTracker(initialBaseline, options);
3836
3836
  }
3837
+
3838
+ // src/history/HistoryManager.ts
3839
+ var HistoryManager = class {
3840
+ undoStack = [];
3841
+ redoStack = [];
3842
+ maxDepth;
3843
+ isApplying = false;
3844
+ batchDepth = 0;
3845
+ currentBatch = null;
3846
+ listeners = /* @__PURE__ */ new Set();
3847
+ onStateChange;
3848
+ cachedState;
3849
+ constructor(options = {}) {
3850
+ this.maxDepth = options.maxDepth ?? 50;
3851
+ this.onStateChange = options.onStateChange;
3852
+ this.cachedState = this.computeState();
3853
+ }
3854
+ computeState() {
3855
+ const canUndo = this.undoStack.length > 0;
3856
+ const canRedo = this.redoStack.length > 0;
3857
+ return {
3858
+ canUndo,
3859
+ canRedo,
3860
+ undoName: canUndo ? this.undoStack[this.undoStack.length - 1].name : "",
3861
+ redoName: canRedo ? this.redoStack[this.redoStack.length - 1].name : "",
3862
+ undoCount: this.undoStack.length,
3863
+ redoCount: this.redoStack.length
3864
+ };
3865
+ }
3866
+ /**
3867
+ * Returns a snapshot of the current history state.
3868
+ * Cached to maintain referential equality across renders for useSyncExternalStore.
3869
+ */
3870
+ getState() {
3871
+ return this.cachedState;
3872
+ }
3873
+ notify() {
3874
+ this.cachedState = this.computeState();
3875
+ const state = this.cachedState;
3876
+ this.onStateChange?.(state);
3877
+ this.listeners.forEach((listener) => {
3878
+ try {
3879
+ listener(state);
3880
+ } catch (err) {
3881
+ console.error("[HistoryManager] Error in state listener:", err);
3882
+ }
3883
+ });
3884
+ }
3885
+ /**
3886
+ * Executes a command and pushes it onto the undo stack.
3887
+ * Clears the redo stack.
3888
+ */
3889
+ execute(command) {
3890
+ if (this.isApplying) {
3891
+ command.execute();
3892
+ return;
3893
+ }
3894
+ if (this.batchDepth > 0 && this.currentBatch) {
3895
+ command.execute();
3896
+ this.currentBatch.push({
3897
+ ...command,
3898
+ timestamp: command.timestamp ?? Date.now()
3899
+ });
3900
+ return;
3901
+ }
3902
+ command.execute();
3903
+ this.undoStack.push({
3904
+ ...command,
3905
+ timestamp: command.timestamp ?? Date.now()
3906
+ });
3907
+ if (this.undoStack.length > this.maxDepth) {
3908
+ this.undoStack.shift();
3909
+ }
3910
+ this.redoStack = [];
3911
+ this.notify();
3912
+ }
3913
+ /**
3914
+ * Executes a command, coalescing it with the previous command if they share
3915
+ * the same `tag` and occurred within `windowMs` (default: 300ms).
3916
+ * Useful for high-frequency interactions like slider scrubs or node drags.
3917
+ */
3918
+ executeMerged(command, options = {}) {
3919
+ const windowMs = options.windowMs ?? 300;
3920
+ const now = Date.now();
3921
+ if (command.tag && this.undoStack.length > 0 && this.batchDepth === 0 && !this.isApplying) {
3922
+ const top = this.undoStack[this.undoStack.length - 1];
3923
+ const elapsed = top.timestamp ? now - top.timestamp : Infinity;
3924
+ if (top.tag === command.tag && elapsed <= windowMs) {
3925
+ command.execute();
3926
+ const initialUndo = top.undo;
3927
+ this.undoStack[this.undoStack.length - 1] = {
3928
+ name: command.name,
3929
+ tag: command.tag,
3930
+ timestamp: now,
3931
+ execute: command.execute,
3932
+ undo: initialUndo,
3933
+ redo: command.redo ?? command.execute
3934
+ };
3935
+ this.redoStack = [];
3936
+ this.notify();
3937
+ return;
3938
+ }
3939
+ }
3940
+ this.execute({ ...command, timestamp: now });
3941
+ }
3942
+ /**
3943
+ * Batches multiple commands executed within `fn` into a single atomic undo entry.
3944
+ */
3945
+ batch(name, fn) {
3946
+ if (this.isApplying) {
3947
+ fn();
3948
+ return;
3949
+ }
3950
+ if (this.batchDepth > 0) {
3951
+ fn();
3952
+ return;
3953
+ }
3954
+ this.batchDepth++;
3955
+ this.currentBatch = [];
3956
+ try {
3957
+ fn();
3958
+ } finally {
3959
+ this.batchDepth--;
3960
+ const batchCommands = this.currentBatch;
3961
+ this.currentBatch = null;
3962
+ if (batchCommands && batchCommands.length > 0) {
3963
+ const compoundCommand = {
3964
+ name,
3965
+ timestamp: Date.now(),
3966
+ execute: () => {
3967
+ for (let i = 0; i < batchCommands.length; i++) {
3968
+ (batchCommands[i].redo ?? batchCommands[i].execute)();
3969
+ }
3970
+ },
3971
+ undo: () => {
3972
+ for (let i = batchCommands.length - 1; i >= 0; i--) {
3973
+ batchCommands[i].undo();
3974
+ }
3975
+ },
3976
+ redo: () => {
3977
+ for (let i = 0; i < batchCommands.length; i++) {
3978
+ (batchCommands[i].redo ?? batchCommands[i].execute)();
3979
+ }
3980
+ }
3981
+ };
3982
+ this.undoStack.push(compoundCommand);
3983
+ if (this.undoStack.length > this.maxDepth) {
3984
+ this.undoStack.shift();
3985
+ }
3986
+ this.redoStack = [];
3987
+ this.notify();
3988
+ }
3989
+ }
3990
+ }
3991
+ /**
3992
+ * Reverts the most recent command on the undo stack.
3993
+ * Returns true if a command was undone, false otherwise.
3994
+ */
3995
+ undo() {
3996
+ if (this.undoStack.length === 0 || this.isApplying || this.batchDepth > 0) {
3997
+ return false;
3998
+ }
3999
+ this.isApplying = true;
4000
+ try {
4001
+ const command = this.undoStack.pop();
4002
+ command.undo();
4003
+ this.redoStack.push(command);
4004
+ return true;
4005
+ } finally {
4006
+ this.isApplying = false;
4007
+ this.notify();
4008
+ }
4009
+ }
4010
+ /**
4011
+ * Re-applies the most recent undone command on the redo stack.
4012
+ * Returns true if a command was redone, false otherwise.
4013
+ */
4014
+ redo() {
4015
+ if (this.redoStack.length === 0 || this.isApplying || this.batchDepth > 0) {
4016
+ return false;
4017
+ }
4018
+ this.isApplying = true;
4019
+ try {
4020
+ const command = this.redoStack.pop();
4021
+ (command.redo ?? command.execute)();
4022
+ this.undoStack.push(command);
4023
+ return true;
4024
+ } finally {
4025
+ this.isApplying = false;
4026
+ this.notify();
4027
+ }
4028
+ }
4029
+ /**
4030
+ * Clears both undo and redo stacks.
4031
+ */
4032
+ clear() {
4033
+ this.undoStack = [];
4034
+ this.redoStack = [];
4035
+ this.notify();
4036
+ }
4037
+ /**
4038
+ * Subscribes to history state changes.
4039
+ * The listener is immediately invoked with the current state.
4040
+ * Returns an unsubscribe function.
4041
+ */
4042
+ subscribe(listener) {
4043
+ this.listeners.add(listener);
4044
+ listener(this.getState());
4045
+ return () => {
4046
+ this.listeners.delete(listener);
4047
+ };
4048
+ }
4049
+ /**
4050
+ * Utility helper to create a Command from before/after state snapshots.
4051
+ */
4052
+ static createSnapshotCommand(options) {
4053
+ return {
4054
+ name: options.name,
4055
+ tag: options.tag,
4056
+ execute: () => options.apply(options.after),
4057
+ undo: () => options.apply(options.before),
4058
+ redo: () => options.apply(options.after)
4059
+ };
4060
+ }
4061
+ };
4062
+ function createHistoryManager(options = {}) {
4063
+ return new HistoryManager(options);
4064
+ }
3837
4065
  export {
3838
4066
  ColorHighlighter,
3839
4067
  DataSourceBinder,
@@ -3844,6 +4072,7 @@ export {
3844
4072
  FormulaEvaluator,
3845
4073
  FormulaParser,
3846
4074
  GradientText,
4075
+ HistoryManager,
3847
4076
  INSTRUCTION_REGEX,
3848
4077
  Image2 as Image,
3849
4078
  LoadingNode,
@@ -3858,6 +4087,7 @@ export {
3858
4087
  colNameToIndex,
3859
4088
  createDirtyTracker,
3860
4089
  createEditor,
4090
+ createHistoryManager,
3861
4091
  createMentionExtension,
3862
4092
  createRichTextPreset,
3863
4093
  defaultIsEqual,