@tsdraw/core 0.5.1 → 0.6.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.cjs CHANGED
@@ -1310,10 +1310,26 @@ function recordsToDocumentSnapshot(records) {
1310
1310
  // src/editor/Editor.ts
1311
1311
  var shapeIdCounter = 0;
1312
1312
  var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
1313
+ var MAX_HISTORY_ENTRIES = 100;
1313
1314
  function createShapeId() {
1314
1315
  shapeIdCounter += 1;
1315
1316
  return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
1316
1317
  }
1318
+ function cloneDocumentSnapshot(snapshot) {
1319
+ if (typeof structuredClone === "function") {
1320
+ return structuredClone(snapshot);
1321
+ }
1322
+ return JSON.parse(JSON.stringify(snapshot));
1323
+ }
1324
+ function areDocumentSnapshotsEqual(left, right) {
1325
+ if (left.records.length !== right.records.length) return false;
1326
+ for (let i = 0; i < left.records.length; i += 1) {
1327
+ if (JSON.stringify(left.records[i]) !== JSON.stringify(right.records[i])) {
1328
+ return false;
1329
+ }
1330
+ }
1331
+ return true;
1332
+ }
1317
1333
  var Editor = class {
1318
1334
  store = new DocumentStore();
1319
1335
  input = new InputManager();
@@ -1329,10 +1345,22 @@ var Editor = class {
1329
1345
  };
1330
1346
  toolStateContext;
1331
1347
  listeners = /* @__PURE__ */ new Set();
1348
+ historyListeners = /* @__PURE__ */ new Set();
1349
+ undoStack = [];
1350
+ redoStack = [];
1351
+ lastDocumentSnapshot;
1352
+ suppressHistoryCapture = false;
1353
+ historyBatchDepth = 0;
1354
+ historyBatchStartSnapshot = null;
1355
+ historyBatchChanged = false;
1332
1356
  // Creates a new editor instance with the given options (with defaults if not provided)
1333
1357
  constructor(opts = {}) {
1334
1358
  this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
1335
- this.store.listen(() => this.emitChange());
1359
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1360
+ this.store.listen(() => {
1361
+ this.captureDocumentHistory();
1362
+ this.emitChange();
1363
+ });
1336
1364
  this.toolStateContext = {
1337
1365
  transition: (id, info) => this.tools.transition(id, info)
1338
1366
  };
@@ -1343,6 +1371,25 @@ var Editor = class {
1343
1371
  this.registerToolDefinition(customTool);
1344
1372
  }
1345
1373
  this.setCurrentTool(opts.initialToolId ?? "pen");
1374
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1375
+ }
1376
+ captureDocumentHistory() {
1377
+ const nextSnapshot = this.getDocumentSnapshot();
1378
+ const previousSnapshot = this.lastDocumentSnapshot;
1379
+ this.lastDocumentSnapshot = nextSnapshot;
1380
+ if (this.suppressHistoryCapture || areDocumentSnapshotsEqual(previousSnapshot, nextSnapshot)) {
1381
+ return;
1382
+ }
1383
+ if (this.historyBatchDepth > 0) {
1384
+ this.historyBatchChanged = true;
1385
+ return;
1386
+ }
1387
+ this.undoStack.push(cloneDocumentSnapshot(previousSnapshot));
1388
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1389
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1390
+ }
1391
+ this.redoStack = [];
1392
+ this.emitHistoryChange();
1346
1393
  }
1347
1394
  registerToolDefinition(toolDefinition) {
1348
1395
  for (const stateConstructor of toolDefinition.stateConstructors) {
@@ -1437,7 +1484,9 @@ var Editor = class {
1437
1484
  loadDocumentSnapshot(snapshot) {
1438
1485
  const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
1439
1486
  if (!documentSnapshot) return;
1440
- this.store.loadSnapshot(documentSnapshot);
1487
+ this.runWithoutHistoryCapture(() => {
1488
+ this.store.loadSnapshot(documentSnapshot);
1489
+ });
1441
1490
  }
1442
1491
  getSessionStateSnapshot(args) {
1443
1492
  return {
@@ -1475,12 +1524,92 @@ var Editor = class {
1475
1524
  }
1476
1525
  return [];
1477
1526
  }
1527
+ getHistorySnapshot() {
1528
+ return {
1529
+ version: 1,
1530
+ undoStack: this.undoStack.map(cloneDocumentSnapshot),
1531
+ redoStack: this.redoStack.map(cloneDocumentSnapshot)
1532
+ };
1533
+ }
1534
+ loadHistorySnapshot(snapshot) {
1535
+ if (!snapshot || snapshot.version !== 1) return;
1536
+ this.undoStack = snapshot.undoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
1537
+ this.redoStack = snapshot.redoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
1538
+ this.emitHistoryChange();
1539
+ }
1540
+ clearRedoHistory() {
1541
+ if (this.redoStack.length === 0) return;
1542
+ this.redoStack = [];
1543
+ this.emitHistoryChange();
1544
+ }
1545
+ beginHistoryEntry() {
1546
+ if (this.historyBatchDepth === 0) {
1547
+ this.historyBatchStartSnapshot = cloneDocumentSnapshot(this.lastDocumentSnapshot);
1548
+ this.historyBatchChanged = false;
1549
+ }
1550
+ this.historyBatchDepth += 1;
1551
+ }
1552
+ endHistoryEntry() {
1553
+ if (this.historyBatchDepth === 0) return;
1554
+ this.historyBatchDepth -= 1;
1555
+ if (this.historyBatchDepth > 0) return;
1556
+ const startSnapshot = this.historyBatchStartSnapshot;
1557
+ this.historyBatchStartSnapshot = null;
1558
+ if (!startSnapshot) return;
1559
+ const endSnapshot = this.getDocumentSnapshot();
1560
+ this.lastDocumentSnapshot = endSnapshot;
1561
+ const didDocumentChange = this.historyBatchChanged || !areDocumentSnapshotsEqual(startSnapshot, endSnapshot);
1562
+ this.historyBatchChanged = false;
1563
+ if (!didDocumentChange) return;
1564
+ this.undoStack.push(cloneDocumentSnapshot(startSnapshot));
1565
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1566
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1567
+ }
1568
+ this.redoStack = [];
1569
+ this.emitHistoryChange();
1570
+ }
1571
+ canUndo() {
1572
+ return this.undoStack.length > 0;
1573
+ }
1574
+ canRedo() {
1575
+ return this.redoStack.length > 0;
1576
+ }
1577
+ undo() {
1578
+ const previousSnapshot = this.undoStack.pop();
1579
+ if (!previousSnapshot) return false;
1580
+ const currentSnapshot = this.getDocumentSnapshot();
1581
+ this.redoStack.push(cloneDocumentSnapshot(currentSnapshot));
1582
+ if (this.redoStack.length > MAX_HISTORY_ENTRIES) {
1583
+ this.redoStack.splice(0, this.redoStack.length - MAX_HISTORY_ENTRIES);
1584
+ }
1585
+ this.loadDocumentSnapshot(previousSnapshot);
1586
+ this.emitHistoryChange();
1587
+ return true;
1588
+ }
1589
+ redo() {
1590
+ const nextSnapshot = this.redoStack.pop();
1591
+ if (!nextSnapshot) return false;
1592
+ const currentSnapshot = this.getDocumentSnapshot();
1593
+ this.undoStack.push(cloneDocumentSnapshot(currentSnapshot));
1594
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1595
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1596
+ }
1597
+ this.loadDocumentSnapshot(nextSnapshot);
1598
+ this.emitHistoryChange();
1599
+ return true;
1600
+ }
1478
1601
  listen(listener) {
1479
1602
  this.listeners.add(listener);
1480
1603
  return () => {
1481
1604
  this.listeners.delete(listener);
1482
1605
  };
1483
1606
  }
1607
+ listenHistory(listener) {
1608
+ this.historyListeners.add(listener);
1609
+ return () => {
1610
+ this.historyListeners.delete(listener);
1611
+ };
1612
+ }
1484
1613
  // Convert screen coords to page coords
1485
1614
  screenToPage(screenX, screenY) {
1486
1615
  return screenToPage(this.viewport, screenX, screenY);
@@ -1497,6 +1626,21 @@ var Editor = class {
1497
1626
  listener();
1498
1627
  }
1499
1628
  }
1629
+ emitHistoryChange() {
1630
+ for (const listener of this.historyListeners) {
1631
+ listener();
1632
+ }
1633
+ }
1634
+ runWithoutHistoryCapture(fn) {
1635
+ const previousValue = this.suppressHistoryCapture;
1636
+ this.suppressHistoryCapture = true;
1637
+ try {
1638
+ fn();
1639
+ } finally {
1640
+ this.suppressHistoryCapture = previousValue;
1641
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1642
+ }
1643
+ }
1500
1644
  };
1501
1645
 
1502
1646
  // src/tools/select/selectHelpers.ts