@tsdraw/core 0.5.0 → 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 +165 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +165 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -255,105 +255,26 @@ function zoomViewport(viewport, factor, centerX, centerY) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// src/utils/colors.ts
|
|
258
|
+
var DARK_COLORS = {
|
|
259
|
+
black: "#f0f0f0",
|
|
260
|
+
grey: "#aeb8c2",
|
|
261
|
+
"light-violet": "#cf6ef5",
|
|
262
|
+
violet: "#a83ce0",
|
|
263
|
+
blue: "#5b7dff",
|
|
264
|
+
"light-blue": "#4fb3ff",
|
|
265
|
+
yellow: "#f4b13a",
|
|
266
|
+
orange: "#ef7a24",
|
|
267
|
+
green: "#1fb27a",
|
|
268
|
+
"light-green": "#4ecb66",
|
|
269
|
+
"light-red": "#ff6f78",
|
|
270
|
+
red: "#f24343",
|
|
271
|
+
white: "#ffffff"
|
|
272
|
+
};
|
|
258
273
|
function resolveThemeColor(colorStyle, theme) {
|
|
259
|
-
const
|
|
260
|
-
if (!
|
|
261
|
-
if (theme === "light") return
|
|
262
|
-
return
|
|
263
|
-
}
|
|
264
|
-
function invertAndHueRotate180(color) {
|
|
265
|
-
const rgb = parseHexColor(color);
|
|
266
|
-
if (!rgb) return color;
|
|
267
|
-
const inverted = {
|
|
268
|
-
r: 255 - rgb.r,
|
|
269
|
-
g: 255 - rgb.g,
|
|
270
|
-
b: 255 - rgb.b
|
|
271
|
-
};
|
|
272
|
-
const hsl = rgbToHsl(inverted.r, inverted.g, inverted.b);
|
|
273
|
-
const rotated = hslToRgb((hsl.h + 180) % 360, hsl.s, hsl.l);
|
|
274
|
-
return rgbToHex(rotated.r, rotated.g, rotated.b);
|
|
275
|
-
}
|
|
276
|
-
function parseHexColor(color) {
|
|
277
|
-
const normalized = color.trim().toLowerCase();
|
|
278
|
-
if (!normalized.startsWith("#")) return null;
|
|
279
|
-
if (normalized.length === 4) {
|
|
280
|
-
return {
|
|
281
|
-
r: parseInt(normalized[1] + normalized[1], 16),
|
|
282
|
-
g: parseInt(normalized[2] + normalized[2], 16),
|
|
283
|
-
b: parseInt(normalized[3] + normalized[3], 16)
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
if (normalized.length !== 7) return null;
|
|
287
|
-
return {
|
|
288
|
-
r: parseInt(normalized.slice(1, 3), 16),
|
|
289
|
-
g: parseInt(normalized.slice(3, 5), 16),
|
|
290
|
-
b: parseInt(normalized.slice(5, 7), 16)
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
function rgbToHex(r, g, b) {
|
|
294
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
295
|
-
}
|
|
296
|
-
function toHex(value) {
|
|
297
|
-
return Math.round(Math.max(0, Math.min(255, value))).toString(16).padStart(2, "0");
|
|
298
|
-
}
|
|
299
|
-
function rgbToHsl(r, g, b) {
|
|
300
|
-
const red = r / 255;
|
|
301
|
-
const green = g / 255;
|
|
302
|
-
const blue = b / 255;
|
|
303
|
-
const maxChannel = Math.max(red, green, blue);
|
|
304
|
-
const minChannel = Math.min(red, green, blue);
|
|
305
|
-
const delta = maxChannel - minChannel;
|
|
306
|
-
const lightness = (maxChannel + minChannel) / 2;
|
|
307
|
-
if (delta === 0) {
|
|
308
|
-
return { h: 0, s: 0, l: lightness };
|
|
309
|
-
}
|
|
310
|
-
const saturation = lightness > 0.5 ? delta / (2 - maxChannel - minChannel) : delta / (maxChannel + minChannel);
|
|
311
|
-
let hue = 0;
|
|
312
|
-
if (maxChannel === red) {
|
|
313
|
-
hue = ((green - blue) / delta + (green < blue ? 6 : 0)) * 60;
|
|
314
|
-
} else if (maxChannel === green) {
|
|
315
|
-
hue = ((blue - red) / delta + 2) * 60;
|
|
316
|
-
} else {
|
|
317
|
-
hue = ((red - green) / delta + 4) * 60;
|
|
318
|
-
}
|
|
319
|
-
return { h: hue, s: saturation, l: lightness };
|
|
320
|
-
}
|
|
321
|
-
function hslToRgb(h, s, l) {
|
|
322
|
-
if (s === 0) {
|
|
323
|
-
const channel = l * 255;
|
|
324
|
-
return { r: channel, g: channel, b: channel };
|
|
325
|
-
}
|
|
326
|
-
const chroma = (1 - Math.abs(2 * l - 1)) * s;
|
|
327
|
-
const hueSegment = h / 60;
|
|
328
|
-
const x = chroma * (1 - Math.abs(hueSegment % 2 - 1));
|
|
329
|
-
let red = 0;
|
|
330
|
-
let green = 0;
|
|
331
|
-
let blue = 0;
|
|
332
|
-
if (hueSegment >= 0 && hueSegment < 1) {
|
|
333
|
-
red = chroma;
|
|
334
|
-
green = x;
|
|
335
|
-
} else if (hueSegment < 2) {
|
|
336
|
-
red = x;
|
|
337
|
-
green = chroma;
|
|
338
|
-
} else if (hueSegment < 3) {
|
|
339
|
-
green = chroma;
|
|
340
|
-
blue = x;
|
|
341
|
-
} else if (hueSegment < 4) {
|
|
342
|
-
green = x;
|
|
343
|
-
blue = chroma;
|
|
344
|
-
} else if (hueSegment < 5) {
|
|
345
|
-
red = x;
|
|
346
|
-
blue = chroma;
|
|
347
|
-
} else {
|
|
348
|
-
red = chroma;
|
|
349
|
-
blue = x;
|
|
350
|
-
}
|
|
351
|
-
const match = l - chroma / 2;
|
|
352
|
-
return {
|
|
353
|
-
r: (red + match) * 255,
|
|
354
|
-
g: (green + match) * 255,
|
|
355
|
-
b: (blue + match) * 255
|
|
356
|
-
};
|
|
274
|
+
const lightThemeColor = DEFAULT_COLORS[colorStyle];
|
|
275
|
+
if (!lightThemeColor) return colorStyle;
|
|
276
|
+
if (theme === "light") return lightThemeColor;
|
|
277
|
+
return DARK_COLORS[colorStyle] ?? lightThemeColor;
|
|
357
278
|
}
|
|
358
279
|
var CanvasRenderer = class {
|
|
359
280
|
theme = "light";
|
|
@@ -1389,10 +1310,26 @@ function recordsToDocumentSnapshot(records) {
|
|
|
1389
1310
|
// src/editor/Editor.ts
|
|
1390
1311
|
var shapeIdCounter = 0;
|
|
1391
1312
|
var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
|
|
1313
|
+
var MAX_HISTORY_ENTRIES = 100;
|
|
1392
1314
|
function createShapeId() {
|
|
1393
1315
|
shapeIdCounter += 1;
|
|
1394
1316
|
return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
|
|
1395
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
|
+
}
|
|
1396
1333
|
var Editor = class {
|
|
1397
1334
|
store = new DocumentStore();
|
|
1398
1335
|
input = new InputManager();
|
|
@@ -1408,10 +1345,22 @@ var Editor = class {
|
|
|
1408
1345
|
};
|
|
1409
1346
|
toolStateContext;
|
|
1410
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;
|
|
1411
1356
|
// Creates a new editor instance with the given options (with defaults if not provided)
|
|
1412
1357
|
constructor(opts = {}) {
|
|
1413
1358
|
this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
|
|
1414
|
-
this.
|
|
1359
|
+
this.lastDocumentSnapshot = this.getDocumentSnapshot();
|
|
1360
|
+
this.store.listen(() => {
|
|
1361
|
+
this.captureDocumentHistory();
|
|
1362
|
+
this.emitChange();
|
|
1363
|
+
});
|
|
1415
1364
|
this.toolStateContext = {
|
|
1416
1365
|
transition: (id, info) => this.tools.transition(id, info)
|
|
1417
1366
|
};
|
|
@@ -1422,6 +1371,25 @@ var Editor = class {
|
|
|
1422
1371
|
this.registerToolDefinition(customTool);
|
|
1423
1372
|
}
|
|
1424
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();
|
|
1425
1393
|
}
|
|
1426
1394
|
registerToolDefinition(toolDefinition) {
|
|
1427
1395
|
for (const stateConstructor of toolDefinition.stateConstructors) {
|
|
@@ -1516,7 +1484,9 @@ var Editor = class {
|
|
|
1516
1484
|
loadDocumentSnapshot(snapshot) {
|
|
1517
1485
|
const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
|
|
1518
1486
|
if (!documentSnapshot) return;
|
|
1519
|
-
this.
|
|
1487
|
+
this.runWithoutHistoryCapture(() => {
|
|
1488
|
+
this.store.loadSnapshot(documentSnapshot);
|
|
1489
|
+
});
|
|
1520
1490
|
}
|
|
1521
1491
|
getSessionStateSnapshot(args) {
|
|
1522
1492
|
return {
|
|
@@ -1554,12 +1524,92 @@ var Editor = class {
|
|
|
1554
1524
|
}
|
|
1555
1525
|
return [];
|
|
1556
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
|
+
}
|
|
1557
1601
|
listen(listener) {
|
|
1558
1602
|
this.listeners.add(listener);
|
|
1559
1603
|
return () => {
|
|
1560
1604
|
this.listeners.delete(listener);
|
|
1561
1605
|
};
|
|
1562
1606
|
}
|
|
1607
|
+
listenHistory(listener) {
|
|
1608
|
+
this.historyListeners.add(listener);
|
|
1609
|
+
return () => {
|
|
1610
|
+
this.historyListeners.delete(listener);
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1563
1613
|
// Convert screen coords to page coords
|
|
1564
1614
|
screenToPage(screenX, screenY) {
|
|
1565
1615
|
return screenToPage(this.viewport, screenX, screenY);
|
|
@@ -1576,6 +1626,21 @@ var Editor = class {
|
|
|
1576
1626
|
listener();
|
|
1577
1627
|
}
|
|
1578
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
|
+
}
|
|
1579
1644
|
};
|
|
1580
1645
|
|
|
1581
1646
|
// src/tools/select/selectHelpers.ts
|