@tsdraw/core 0.5.1 → 0.6.2

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
@@ -150,6 +150,12 @@ var DocumentStore = class {
150
150
  loadSnapshot(snapshot) {
151
151
  const pageState = cloneValue(snapshot.page);
152
152
  const normalizedOrder = [...snapshot.order].filter((shapeId) => pageState.shapes[shapeId] != null);
153
+ const orderedSet = new Set(normalizedOrder);
154
+ for (const shapeId of Object.keys(pageState.shapes)) {
155
+ if (!orderedSet.has(shapeId)) {
156
+ normalizedOrder.push(shapeId);
157
+ }
158
+ }
153
159
  this.state = {
154
160
  id: pageState.id,
155
161
  shapes: pageState.shapes,
@@ -865,13 +871,14 @@ var PenDrawingState = class extends StateNode {
865
871
  type: "straight",
866
872
  path: encodePoints([prevEnd, { ...anchorPt, z: pressure }])
867
873
  };
874
+ const withStraightSeg = [...segments, seg];
868
875
  this.editor.updateShapes([
869
876
  {
870
877
  id,
871
878
  type: "draw",
872
879
  props: {
873
- segments: [...segments, seg],
874
- isClosed: this.detectClosure(segments, size, scale)
880
+ segments: withStraightSeg,
881
+ isClosed: this.detectClosure(withStraightSeg, size, scale)
875
882
  }
876
883
  }
877
884
  ]);
@@ -947,7 +954,7 @@ var PenDrawingState = class extends StateNode {
947
954
  type: "draw",
948
955
  props: {
949
956
  segments: updated,
950
- isClosed: this.detectClosure(segments, size, scale)
957
+ isClosed: this.detectClosure(updated, size, scale)
951
958
  }
952
959
  }
953
960
  ]);
@@ -1310,10 +1317,26 @@ function recordsToDocumentSnapshot(records) {
1310
1317
  // src/editor/Editor.ts
1311
1318
  var shapeIdCounter = 0;
1312
1319
  var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
1320
+ var MAX_HISTORY_ENTRIES = 100;
1313
1321
  function createShapeId() {
1314
1322
  shapeIdCounter += 1;
1315
1323
  return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
1316
1324
  }
1325
+ function cloneDocumentSnapshot(snapshot) {
1326
+ if (typeof structuredClone === "function") {
1327
+ return structuredClone(snapshot);
1328
+ }
1329
+ return JSON.parse(JSON.stringify(snapshot));
1330
+ }
1331
+ function areDocumentSnapshotsEqual(left, right) {
1332
+ if (left.records.length !== right.records.length) return false;
1333
+ for (let i = 0; i < left.records.length; i += 1) {
1334
+ if (JSON.stringify(left.records[i]) !== JSON.stringify(right.records[i])) {
1335
+ return false;
1336
+ }
1337
+ }
1338
+ return true;
1339
+ }
1317
1340
  var Editor = class {
1318
1341
  store = new DocumentStore();
1319
1342
  input = new InputManager();
@@ -1329,10 +1352,22 @@ var Editor = class {
1329
1352
  };
1330
1353
  toolStateContext;
1331
1354
  listeners = /* @__PURE__ */ new Set();
1355
+ historyListeners = /* @__PURE__ */ new Set();
1356
+ undoStack = [];
1357
+ redoStack = [];
1358
+ lastDocumentSnapshot;
1359
+ suppressHistoryCapture = false;
1360
+ historyBatchDepth = 0;
1361
+ historyBatchStartSnapshot = null;
1362
+ historyBatchChanged = false;
1332
1363
  // Creates a new editor instance with the given options (with defaults if not provided)
1333
1364
  constructor(opts = {}) {
1334
1365
  this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
1335
- this.store.listen(() => this.emitChange());
1366
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1367
+ this.store.listen(() => {
1368
+ this.captureDocumentHistory();
1369
+ this.emitChange();
1370
+ });
1336
1371
  this.toolStateContext = {
1337
1372
  transition: (id, info) => this.tools.transition(id, info)
1338
1373
  };
@@ -1343,6 +1378,25 @@ var Editor = class {
1343
1378
  this.registerToolDefinition(customTool);
1344
1379
  }
1345
1380
  this.setCurrentTool(opts.initialToolId ?? "pen");
1381
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1382
+ }
1383
+ captureDocumentHistory() {
1384
+ const nextSnapshot = this.getDocumentSnapshot();
1385
+ const previousSnapshot = this.lastDocumentSnapshot;
1386
+ this.lastDocumentSnapshot = nextSnapshot;
1387
+ if (this.suppressHistoryCapture || areDocumentSnapshotsEqual(previousSnapshot, nextSnapshot)) {
1388
+ return;
1389
+ }
1390
+ if (this.historyBatchDepth > 0) {
1391
+ this.historyBatchChanged = true;
1392
+ return;
1393
+ }
1394
+ this.undoStack.push(cloneDocumentSnapshot(previousSnapshot));
1395
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1396
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1397
+ }
1398
+ this.redoStack = [];
1399
+ this.emitHistoryChange();
1346
1400
  }
1347
1401
  registerToolDefinition(toolDefinition) {
1348
1402
  for (const stateConstructor of toolDefinition.stateConstructors) {
@@ -1416,10 +1470,11 @@ var Editor = class {
1416
1470
  this.emitChange();
1417
1471
  }
1418
1472
  setViewport(partial) {
1473
+ const rawZoom = partial.zoom ?? this.viewport.zoom;
1419
1474
  this.viewport = {
1420
1475
  x: partial.x ?? this.viewport.x,
1421
1476
  y: partial.y ?? this.viewport.y,
1422
- zoom: partial.zoom ?? this.viewport.zoom
1477
+ zoom: Math.max(0.1, Math.min(4, rawZoom))
1423
1478
  };
1424
1479
  this.emitChange();
1425
1480
  }
@@ -1437,7 +1492,9 @@ var Editor = class {
1437
1492
  loadDocumentSnapshot(snapshot) {
1438
1493
  const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
1439
1494
  if (!documentSnapshot) return;
1440
- this.store.loadSnapshot(documentSnapshot);
1495
+ this.runWithoutHistoryCapture(() => {
1496
+ this.store.loadSnapshot(documentSnapshot);
1497
+ });
1441
1498
  }
1442
1499
  getSessionStateSnapshot(args) {
1443
1500
  return {
@@ -1475,12 +1532,92 @@ var Editor = class {
1475
1532
  }
1476
1533
  return [];
1477
1534
  }
1535
+ getHistorySnapshot() {
1536
+ return {
1537
+ version: 1,
1538
+ undoStack: this.undoStack.map(cloneDocumentSnapshot),
1539
+ redoStack: this.redoStack.map(cloneDocumentSnapshot)
1540
+ };
1541
+ }
1542
+ loadHistorySnapshot(snapshot) {
1543
+ if (!snapshot || snapshot.version !== 1) return;
1544
+ this.undoStack = snapshot.undoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
1545
+ this.redoStack = snapshot.redoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
1546
+ this.emitHistoryChange();
1547
+ }
1548
+ clearRedoHistory() {
1549
+ if (this.redoStack.length === 0) return;
1550
+ this.redoStack = [];
1551
+ this.emitHistoryChange();
1552
+ }
1553
+ beginHistoryEntry() {
1554
+ if (this.historyBatchDepth === 0) {
1555
+ this.historyBatchStartSnapshot = cloneDocumentSnapshot(this.lastDocumentSnapshot);
1556
+ this.historyBatchChanged = false;
1557
+ }
1558
+ this.historyBatchDepth += 1;
1559
+ }
1560
+ endHistoryEntry() {
1561
+ if (this.historyBatchDepth === 0) return;
1562
+ this.historyBatchDepth -= 1;
1563
+ if (this.historyBatchDepth > 0) return;
1564
+ const startSnapshot = this.historyBatchStartSnapshot;
1565
+ this.historyBatchStartSnapshot = null;
1566
+ if (!startSnapshot) return;
1567
+ const endSnapshot = this.getDocumentSnapshot();
1568
+ this.lastDocumentSnapshot = endSnapshot;
1569
+ const didDocumentChange = this.historyBatchChanged || !areDocumentSnapshotsEqual(startSnapshot, endSnapshot);
1570
+ this.historyBatchChanged = false;
1571
+ if (!didDocumentChange) return;
1572
+ this.undoStack.push(cloneDocumentSnapshot(startSnapshot));
1573
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1574
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1575
+ }
1576
+ this.redoStack = [];
1577
+ this.emitHistoryChange();
1578
+ }
1579
+ canUndo() {
1580
+ return this.undoStack.length > 0;
1581
+ }
1582
+ canRedo() {
1583
+ return this.redoStack.length > 0;
1584
+ }
1585
+ undo() {
1586
+ const previousSnapshot = this.undoStack.pop();
1587
+ if (!previousSnapshot) return false;
1588
+ const currentSnapshot = this.getDocumentSnapshot();
1589
+ this.redoStack.push(cloneDocumentSnapshot(currentSnapshot));
1590
+ if (this.redoStack.length > MAX_HISTORY_ENTRIES) {
1591
+ this.redoStack.splice(0, this.redoStack.length - MAX_HISTORY_ENTRIES);
1592
+ }
1593
+ this.loadDocumentSnapshot(previousSnapshot);
1594
+ this.emitHistoryChange();
1595
+ return true;
1596
+ }
1597
+ redo() {
1598
+ const nextSnapshot = this.redoStack.pop();
1599
+ if (!nextSnapshot) return false;
1600
+ const currentSnapshot = this.getDocumentSnapshot();
1601
+ this.undoStack.push(cloneDocumentSnapshot(currentSnapshot));
1602
+ if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
1603
+ this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
1604
+ }
1605
+ this.loadDocumentSnapshot(nextSnapshot);
1606
+ this.emitHistoryChange();
1607
+ return true;
1608
+ }
1478
1609
  listen(listener) {
1479
1610
  this.listeners.add(listener);
1480
1611
  return () => {
1481
1612
  this.listeners.delete(listener);
1482
1613
  };
1483
1614
  }
1615
+ listenHistory(listener) {
1616
+ this.historyListeners.add(listener);
1617
+ return () => {
1618
+ this.historyListeners.delete(listener);
1619
+ };
1620
+ }
1484
1621
  // Convert screen coords to page coords
1485
1622
  screenToPage(screenX, screenY) {
1486
1623
  return screenToPage(this.viewport, screenX, screenY);
@@ -1497,6 +1634,21 @@ var Editor = class {
1497
1634
  listener();
1498
1635
  }
1499
1636
  }
1637
+ emitHistoryChange() {
1638
+ for (const listener of this.historyListeners) {
1639
+ listener();
1640
+ }
1641
+ }
1642
+ runWithoutHistoryCapture(fn) {
1643
+ const previousValue = this.suppressHistoryCapture;
1644
+ this.suppressHistoryCapture = true;
1645
+ try {
1646
+ fn();
1647
+ } finally {
1648
+ this.suppressHistoryCapture = previousValue;
1649
+ this.lastDocumentSnapshot = this.getDocumentSnapshot();
1650
+ }
1651
+ }
1500
1652
  };
1501
1653
 
1502
1654
  // src/tools/select/selectHelpers.ts