@8btc/whiteboard 0.0.12 → 0.0.14

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.css CHANGED
@@ -372,10 +372,6 @@
372
372
  position: relative;
373
373
  }
374
374
 
375
- .static {
376
- position: static;
377
- }
378
-
379
375
  .top-0 {
380
376
  top: calc(var(--spacing) * 0);
381
377
  }
package/dist/index.d.ts CHANGED
@@ -73,6 +73,14 @@ export declare class CanvasApi extends CanvasCore {
73
73
  * @returns 被删除的节点数据数组
74
74
  */
75
75
  deleteNodes(nodeIds: string[]): INode[];
76
+ /**
77
+ * 将节点移动到最上层
78
+ */
79
+ moveNodesToTop(nodeIds: string[]): void;
80
+ /**
81
+ * 将节点移动到最下层
82
+ */
83
+ moveNodesToBottom(nodeIds: string[]): void;
76
84
  /**
77
85
  * 滚动到内容区域
78
86
  * - 如果提供了 nodeIds,将指定的节点居中显示
@@ -157,6 +165,8 @@ declare class CanvasCore extends CanvasState_2 {
157
165
  /* Excluded from this release type: updateDraftNode */
158
166
  /* Excluded from this release type: finalizeDraftNode */
159
167
  /* Excluded from this release type: selectNode */
168
+ /* Excluded from this release type: deleteNodes */
169
+ /* Excluded from this release type: deleteSelectedNodes */
160
170
  /**
161
171
  * 销毁 canvas
162
172
  */
@@ -367,7 +377,13 @@ declare interface IViewport {
367
377
  scale: number;
368
378
  }
369
379
 
370
- export declare const NODE_NAME_FOR_SELECT = "shapeNameForSelect";
380
+ export declare const NODE_NAMES: {
381
+ nodeRoot: string;
382
+ selectable: string;
383
+ rect: string;
384
+ image: string;
385
+ imageMarker: string;
386
+ };
371
387
 
372
388
  export declare type NodeType = "rectangle" | "image" | "image-marker";
373
389
 
@@ -394,10 +410,12 @@ declare type StateEvents = {
394
410
  "state:redo": ICoreState;
395
411
  "state:reset": ICoreState;
396
412
  "viewport:change": ICoreState["viewport"];
413
+ "viewport:scale:change": number;
397
414
  "transformer:positionChange": TransformerPosition | null;
398
415
  "toolType:change": ICoreState["toolType"];
399
416
  "nodes:created": ICoreState["nodes"];
400
417
  "nodes:deleted": INode[];
418
+ "nodes:selected": string[];
401
419
  };
402
420
 
403
421
  export declare type ToolType = "select" | "hand" | "rectangle" | "image-marker";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
9
9
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
10
10
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
11
11
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
12
- var _core, _stage, _viewport, _handleWheel, _handlePointerDown, _handlePointerMove, _handlePointerUp, _handleDragStart, _handleDragMove, _handleDragEnd, _CanvasStage_instances, setupEventListeners_fn, _core2, _transformer, _handleTransformStart, _handleTransform, _handleTransformEnd, _handleDragStart2, _handleDragMove2, _handleDragEnd2, _CanvasTransformer_instances, setupEventListeners_fn2, _toolTypeChangeHandler, _RectNode_instances, setupEventHandlers_fn, _ImageNode_instances, loadImage_fn, _toolTypeChangeHandler2, setupEventHandlers_fn2, syncImageMarkers_fn, syncImageMarkersToState_fn, _rect, _markerGroup, _circle, _text, _ImageMarkerNode_instances, setupEventHandlers_fn3, _canvasStage, _mainLayer, _canvasTransformer, _draftNode;
12
+ var _core, _stage, _viewport, _handleWheel, _handlePointerDown, _handlePointerMove, _handlePointerUp, _handleDragStart, _handleDragMove, _handleDragEnd, _CanvasStage_instances, setupEventListeners_fn, _core2, _transformer, _handleTransformStart, _handleTransform, _handleTransformEnd, _handleDragStart2, _handleDragMove2, _handleDragEnd2, _CanvasTransformer_instances, setupEventListeners_fn2, _toolTypeChangeHandler, _RectNode_instances, setupEventHandlers_fn, _ImageNode_instances, loadImage_fn, _toolTypeChangeHandler2, setupEventHandlers_fn2, syncImageMarkers_fn, syncImageMarkersToState_fn, _rect, _markerGroup, _circle, _text, _handleViewportChange, _handleNodesSelected, _ImageMarkerNode_instances, updateMarkerScale_fn, setupEventHandlers_fn3, _canvasStage, _mainLayer, _canvasTransformer, _draftNode, _container, _handleKeyDown, _CanvasCore_instances, setupKeyboardEvents_fn;
13
13
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
14
14
  import { useState, useEffect, useRef } from "react";
15
15
  import Konva from "konva";
@@ -509,7 +509,13 @@ class CanvasState {
509
509
  this._emitter.all.clear();
510
510
  }
511
511
  }
512
- const NODE_NAME_FOR_SELECT = "shapeNameForSelect";
512
+ const NODE_NAMES = {
513
+ nodeRoot: "nodeRoot_intrinsic",
514
+ selectable: "selectable_intrinsic",
515
+ rect: "rect_intrinsic",
516
+ image: "image_intrinsic",
517
+ imageMarker: "image_marker_intrinsic"
518
+ };
513
519
  const RECT = {
514
520
  CORNER_RADIUS: 6,
515
521
  MIN_SIZE: 10
@@ -573,7 +579,7 @@ class RectNode extends BaseCanvasNode {
573
579
  width,
574
580
  height,
575
581
  cornerRadius: RECT.CORNER_RADIUS,
576
- name: NODE_NAME_FOR_SELECT,
582
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.rect}`,
577
583
  draggable: true,
578
584
  stroke: "black",
579
585
  strokeWidth: 2
@@ -692,7 +698,7 @@ class ImageNode extends BaseCanvasNode {
692
698
  id: this.node.id,
693
699
  x: this.node.props.x,
694
700
  y: this.node.props.y,
695
- name: NODE_NAME_FOR_SELECT,
701
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.image}`,
696
702
  draggable: true,
697
703
  image: placeholder
698
704
  });
@@ -889,12 +895,23 @@ class ImageMarkerNode extends BaseCanvasNode {
889
895
  __privateAdd(this, _markerGroup);
890
896
  __privateAdd(this, _circle);
891
897
  __privateAdd(this, _text);
898
+ __privateAdd(this, _handleViewportChange);
899
+ __privateAdd(this, _handleNodesSelected);
892
900
  const group = this.getElement();
893
901
  __privateSet(this, _rect, group.findOne(".rect"));
894
902
  __privateSet(this, _markerGroup, group.findOne(".marker-group"));
895
903
  __privateSet(this, _circle, __privateGet(this, _markerGroup).findOne("Circle"));
896
904
  __privateSet(this, _text, __privateGet(this, _markerGroup).findOne("Text"));
897
905
  __privateMethod(this, _ImageMarkerNode_instances, setupEventHandlers_fn3).call(this);
906
+ __privateSet(this, _handleViewportChange, () => {
907
+ __privateMethod(this, _ImageMarkerNode_instances, updateMarkerScale_fn).call(this);
908
+ });
909
+ this.core.on("viewport:scale:change", __privateGet(this, _handleViewportChange));
910
+ __privateSet(this, _handleNodesSelected, (selectedIds) => {
911
+ const isSelected = selectedIds.includes(this.node.id);
912
+ this.setFocusState(isSelected);
913
+ });
914
+ this.core.on("nodes:selected", __privateGet(this, _handleNodesSelected));
898
915
  }
899
916
  createElement() {
900
917
  const width = Math.max(
@@ -907,23 +924,27 @@ class ImageMarkerNode extends BaseCanvasNode {
907
924
  );
908
925
  const group = new Konva.Group({
909
926
  id: this.node.id,
910
- name: `static ${this.node.meta.parent} imageMarker`,
927
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.imageMarker} ${this.node.meta.parent}`,
911
928
  x: this.node.props.x,
912
929
  y: this.node.props.y,
913
930
  width,
914
931
  height
915
932
  });
933
+ const stageScale = this.core.getStageScale();
934
+ const rectStrokeWidth = 2 / stageScale;
935
+ const rectDash = [5 / stageScale, 5 / stageScale];
936
+ const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
916
937
  const rect = new Konva.Rect({
917
938
  name: "rect",
918
939
  x: 0,
919
940
  y: 0,
920
941
  width,
921
942
  height,
922
- stroke: this.node.style.color,
923
- strokeWidth: 2,
924
- dash: [5, 5],
943
+ stroke: "#3B82F6",
944
+ strokeWidth: rectStrokeWidth,
945
+ dash: rectDash,
925
946
  fill: "transparent",
926
- cornerRadius: RECT.CORNER_RADIUS,
947
+ cornerRadius: rectCornerRadius,
927
948
  listening: false
928
949
  });
929
950
  const markerGroup = new Konva.Group({
@@ -932,13 +953,14 @@ class ImageMarkerNode extends BaseCanvasNode {
932
953
  y: height,
933
954
  visible: this.isDraft ? false : true
934
955
  });
935
- const stageScale = this.core.getStageScale();
936
- const radius = 16 / stageScale;
956
+ const radius = 14 / stageScale;
957
+ const strokeWidth = 3 / stageScale;
958
+ const fontSize = 16 / stageScale;
937
959
  const circle = new Konva.Circle({
938
960
  radius,
939
- fill: "red",
940
- stroke: "black",
941
- strokeWidth: 2
961
+ fill: "#3B82F6",
962
+ stroke: "white",
963
+ strokeWidth
942
964
  });
943
965
  const text = new Konva.Text({
944
966
  x: -radius,
@@ -948,7 +970,7 @@ class ImageMarkerNode extends BaseCanvasNode {
948
970
  text: String(this.node.meta.markerNumber || ""),
949
971
  align: "center",
950
972
  verticalAlign: "middle",
951
- fontSize: 16,
973
+ fontSize,
952
974
  fill: "white"
953
975
  });
954
976
  markerGroup.add(circle);
@@ -1011,13 +1033,16 @@ class ImageMarkerNode extends BaseCanvasNode {
1011
1033
  * 销毁
1012
1034
  */
1013
1035
  destroy() {
1036
+ this.core.off("viewport:scale:change", __privateGet(this, _handleViewportChange));
1037
+ this.core.off("nodes:selected", __privateGet(this, _handleNodesSelected));
1014
1038
  this.element.destroy();
1015
1039
  }
1016
1040
  /**
1017
1041
  * 更新焦点状态(hover 或 selected)
1018
1042
  */
1019
1043
  setFocusState(isFocus) {
1020
- const strokeWidth = isFocus ? 4 : 2;
1044
+ const stageScale = this.core.getStageScale();
1045
+ const strokeWidth = isFocus ? 4 / stageScale : 3 / stageScale;
1021
1046
  const scale = isFocus ? 1.2 : 1;
1022
1047
  __privateGet(this, _rect).strokeWidth(strokeWidth);
1023
1048
  __privateGet(this, _circle).strokeWidth(strokeWidth);
@@ -1029,7 +1054,31 @@ _rect = new WeakMap();
1029
1054
  _markerGroup = new WeakMap();
1030
1055
  _circle = new WeakMap();
1031
1056
  _text = new WeakMap();
1057
+ _handleViewportChange = new WeakMap();
1058
+ _handleNodesSelected = new WeakMap();
1032
1059
  _ImageMarkerNode_instances = new WeakSet();
1060
+ /**
1061
+ * 更新标记点的缩放以保持视觉大小不变
1062
+ */
1063
+ updateMarkerScale_fn = function() {
1064
+ const stageScale = this.core.getStageScale();
1065
+ const radius = 14 / stageScale;
1066
+ const strokeWidth = 3 / stageScale;
1067
+ const fontSize = 16 / stageScale;
1068
+ const rectStrokeWidth = 3 / stageScale;
1069
+ const rectDash = [5 / stageScale, 5 / stageScale];
1070
+ const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
1071
+ __privateGet(this, _rect).strokeWidth(rectStrokeWidth);
1072
+ __privateGet(this, _rect).dash(rectDash);
1073
+ __privateGet(this, _rect).cornerRadius(rectCornerRadius);
1074
+ __privateGet(this, _circle).radius(radius);
1075
+ __privateGet(this, _circle).strokeWidth(strokeWidth);
1076
+ __privateGet(this, _text).x(-radius);
1077
+ __privateGet(this, _text).y(-radius);
1078
+ __privateGet(this, _text).width(radius * 2);
1079
+ __privateGet(this, _text).height(radius * 2);
1080
+ __privateGet(this, _text).fontSize(fontSize);
1081
+ };
1033
1082
  /**
1034
1083
  * 设置事件处理器
1035
1084
  */
@@ -1091,8 +1140,7 @@ const createNodeByType = (type, position, style, meta) => {
1091
1140
  ...baseNode,
1092
1141
  style: {
1093
1142
  ...baseNode.style,
1094
- color: "#ff0000",
1095
- line: "dashed"
1143
+ color: "#3B82F6"
1096
1144
  }
1097
1145
  };
1098
1146
  }
@@ -1161,10 +1209,19 @@ class CanvasCore extends CanvasState {
1161
1209
  toolType: "select",
1162
1210
  nodes: []
1163
1211
  });
1212
+ __privateAdd(this, _CanvasCore_instances);
1164
1213
  __privateAdd(this, _canvasStage);
1165
1214
  __privateAdd(this, _mainLayer);
1166
1215
  __privateAdd(this, _canvasTransformer);
1167
1216
  __privateAdd(this, _draftNode, null);
1217
+ __privateAdd(this, _container);
1218
+ __privateAdd(this, _handleKeyDown, (e) => {
1219
+ if (e.key === "Delete" || e.key === "Backspace") {
1220
+ e.preventDefault();
1221
+ this.deleteSelectedNodes();
1222
+ }
1223
+ });
1224
+ __privateSet(this, _container, el);
1168
1225
  __privateSet(this, _canvasStage, new CanvasStage(this, {
1169
1226
  container: el,
1170
1227
  width: el.clientWidth,
@@ -1177,6 +1234,7 @@ class CanvasCore extends CanvasState {
1177
1234
  __privateGet(this, _canvasStage).getStage().add(__privateGet(this, _mainLayer));
1178
1235
  __privateGet(this, _mainLayer).add(__privateGet(this, _canvasTransformer).getTransformer());
1179
1236
  this.updateViewport(this.getState().viewport, false);
1237
+ __privateMethod(this, _CanvasCore_instances, setupKeyboardEvents_fn).call(this);
1180
1238
  }
1181
1239
  /**
1182
1240
  * 获取 CanvasStage 实例
@@ -1272,8 +1330,9 @@ class CanvasCore extends CanvasState {
1272
1330
  */
1273
1331
  updateViewport(viewport, addToHistory = false) {
1274
1332
  __privateGet(this, _canvasStage).setViewport(viewport);
1333
+ const oldViewport = this.getState().viewport;
1275
1334
  const newViewport = {
1276
- ...this.getState().viewport,
1335
+ ...oldViewport,
1277
1336
  ...viewport
1278
1337
  };
1279
1338
  this._updateState(
@@ -1283,6 +1342,9 @@ class CanvasCore extends CanvasState {
1283
1342
  addToHistory
1284
1343
  );
1285
1344
  this.emit("viewport:change", newViewport);
1345
+ if (oldViewport.scale !== newViewport.scale) {
1346
+ this.emit("viewport:scale:change", newViewport.scale);
1347
+ }
1286
1348
  __privateGet(this, _canvasTransformer).emitPositionChange();
1287
1349
  }
1288
1350
  createNodes(nodes) {
@@ -1330,7 +1392,7 @@ class CanvasCore extends CanvasState {
1330
1392
  visible: true
1331
1393
  },
1332
1394
  style: {
1333
- color: "#ff0000",
1395
+ color: "#3B82F6",
1334
1396
  line: "dashed",
1335
1397
  size: "medium",
1336
1398
  opacity: 1
@@ -1478,41 +1540,79 @@ class CanvasCore extends CanvasState {
1478
1540
  */
1479
1541
  selectNode(nodeId, multiSelect = false) {
1480
1542
  const curSelectedNodeIds = this.getState().selectedNodeIds ?? [];
1481
- if (curSelectedNodeIds.length === 0 && !nodeId) {
1543
+ let selectedNodeIds = [];
1544
+ if (nodeId) {
1545
+ if (multiSelect && curSelectedNodeIds.length > 0) {
1546
+ selectedNodeIds = [...curSelectedNodeIds, nodeId];
1547
+ } else {
1548
+ selectedNodeIds = [nodeId];
1549
+ }
1550
+ } else if (curSelectedNodeIds.length === 0) {
1482
1551
  return;
1483
1552
  }
1484
- if (!nodeId) {
1553
+ if (selectedNodeIds.length === 0) {
1485
1554
  __privateGet(this, _canvasTransformer).clearNodes();
1486
- this._updateState(
1487
- {
1488
- selectedNodeIds: []
1489
- },
1490
- false
1491
- );
1492
- return;
1493
- }
1494
- let selectedNodeIds = [];
1495
- if (multiSelect) {
1496
- selectedNodeIds = curSelectedNodeIds.length ? [...curSelectedNodeIds, nodeId] : [nodeId];
1497
1555
  } else {
1498
- selectedNodeIds = [nodeId];
1556
+ const nodes = this.getStage().find(`.${NODE_NAMES.selectable}`).filter((node) => selectedNodeIds.includes(node.id()));
1557
+ __privateGet(this, _canvasTransformer).setNodes(nodes);
1499
1558
  }
1500
- const nodes = this.getStage().find(`.${NODE_NAME_FOR_SELECT}`).filter((node) => {
1501
- const id = node.id();
1502
- return selectedNodeIds.includes(id);
1559
+ this._updateState({ selectedNodeIds }, false);
1560
+ this.emit("nodes:selected", selectedNodeIds);
1561
+ }
1562
+ /**
1563
+ * 删除指定的节点(内部使用)
1564
+ * 如果删除的是 image 节点,会同步删除所有关联的 image-marker
1565
+ * @internal 仅供内部使用,外部请使用 CanvasApi
1566
+ * @param nodeIds - 要删除的节点 ID 数组
1567
+ * @returns 被删除的节点数据数组
1568
+ */
1569
+ deleteNodes(nodeIds) {
1570
+ if (nodeIds.length === 0) return [];
1571
+ const nodes = this.getState().nodes || [];
1572
+ const idsToDelete = new Set(nodeIds);
1573
+ nodeIds.forEach((id) => {
1574
+ const node = nodes.find((n) => n.id === id);
1575
+ if (node?.type === "image") {
1576
+ nodes.forEach((n) => {
1577
+ if (n.type === "image-marker" && n.meta.parent === id) {
1578
+ idsToDelete.add(n.id);
1579
+ }
1580
+ });
1581
+ }
1503
1582
  });
1504
- __privateGet(this, _canvasTransformer).setNodes(nodes);
1583
+ const deletedNodes = nodes.filter((n) => idsToDelete.has(n.id));
1584
+ idsToDelete.forEach((id) => {
1585
+ const shape = this.getStage().findOne(`#${id}`);
1586
+ if (shape) {
1587
+ shape.destroy();
1588
+ }
1589
+ });
1590
+ const newNodes = nodes.filter((n) => !idsToDelete.has(n.id));
1591
+ __privateGet(this, _canvasTransformer).clearNodes();
1505
1592
  this._updateState(
1506
1593
  {
1507
- selectedNodeIds
1594
+ nodes: newNodes,
1595
+ selectedNodeIds: []
1508
1596
  },
1509
- false
1597
+ true
1510
1598
  );
1599
+ this.emit("nodes:deleted", deletedNodes);
1600
+ return deletedNodes;
1601
+ }
1602
+ /**
1603
+ * 删除选中的节点(内部使用)
1604
+ * @internal 仅供内部使用,外部请使用 CanvasApi
1605
+ */
1606
+ deleteSelectedNodes() {
1607
+ const selectedNodeIds = this.getState().selectedNodeIds || [];
1608
+ if (selectedNodeIds.length === 0) return;
1609
+ this.deleteNodes(selectedNodeIds);
1511
1610
  }
1512
1611
  /**
1513
1612
  * 销毁 canvas
1514
1613
  */
1515
1614
  dispose() {
1615
+ __privateGet(this, _container).removeEventListener("keydown", __privateGet(this, _handleKeyDown));
1516
1616
  this.getCanvasTransformer().destroy();
1517
1617
  this.getMainLayer().destroy();
1518
1618
  this.getCanvasStage().destroy();
@@ -1568,6 +1668,16 @@ _canvasStage = new WeakMap();
1568
1668
  _mainLayer = new WeakMap();
1569
1669
  _canvasTransformer = new WeakMap();
1570
1670
  _draftNode = new WeakMap();
1671
+ _container = new WeakMap();
1672
+ _handleKeyDown = new WeakMap();
1673
+ _CanvasCore_instances = new WeakSet();
1674
+ /**
1675
+ * 设置键盘事件监听
1676
+ */
1677
+ setupKeyboardEvents_fn = function() {
1678
+ __privateGet(this, _container).tabIndex = 0;
1679
+ __privateGet(this, _container).addEventListener("keydown", __privateGet(this, _handleKeyDown));
1680
+ };
1571
1681
  class CanvasApi extends CanvasCore {
1572
1682
  /**
1573
1683
  * 获取所有可用的工具类型
@@ -1656,29 +1766,32 @@ class CanvasApi extends CanvasCore {
1656
1766
  * @returns DataURL 格式的图片数据,如果没有选区则返回 null
1657
1767
  */
1658
1768
  exportSelectionAsImage(options) {
1659
- const position = this.getCanvasTransformer().getPosition();
1660
- if (!position) {
1769
+ const selectedNodeIds = this.getState().selectedNodeIds || [];
1770
+ if (selectedNodeIds.length === 0) {
1661
1771
  console.warn("No selection to export");
1662
1772
  return null;
1663
1773
  }
1664
- const stage = this.getStage();
1665
1774
  const padding = options?.padding ?? 0;
1666
- const transformer = this.getCanvasTransformer().getTransformer();
1667
- const transformerVisible = transformer.visible();
1668
- transformer.visible(false);
1669
- try {
1670
- return stage.toDataURL({
1671
- x: position.x - padding,
1672
- y: position.y - padding,
1673
- width: position.width + padding * 2,
1674
- height: position.height + padding * 2,
1675
- pixelRatio: options?.pixelRatio ?? 1,
1676
- mimeType: options?.mimeType ?? "image/png",
1677
- quality: options?.quality ?? 1
1678
- });
1679
- } finally {
1680
- transformer.visible(transformerVisible);
1681
- }
1775
+ const tempGroup = new Konva.Group();
1776
+ selectedNodeIds.forEach((id) => {
1777
+ const shape = this.getStage().findOne(`#${id}`);
1778
+ if (shape) {
1779
+ const clone = shape.clone();
1780
+ tempGroup.add(clone);
1781
+ }
1782
+ });
1783
+ const box = tempGroup.getClientRect();
1784
+ const dataURL = tempGroup.toDataURL({
1785
+ x: box.x - padding,
1786
+ y: box.y - padding,
1787
+ width: box.width + padding * 2,
1788
+ height: box.height + padding * 2,
1789
+ pixelRatio: options?.pixelRatio ?? 2,
1790
+ mimeType: options?.mimeType ?? "image/png",
1791
+ quality: options?.quality ?? 1
1792
+ });
1793
+ tempGroup.destroy();
1794
+ return dataURL;
1682
1795
  }
1683
1796
  /**
1684
1797
  * 导出带有标注的图片节点为图片
@@ -1722,7 +1835,6 @@ class CanvasApi extends CanvasCore {
1722
1835
  */
1723
1836
  deleteSelectedNodes() {
1724
1837
  const selectedNodeIds = this.getState().selectedNodeIds || [];
1725
- if (selectedNodeIds.length === 0) return;
1726
1838
  this.deleteNodes(selectedNodeIds);
1727
1839
  }
1728
1840
  /**
@@ -1732,37 +1844,31 @@ class CanvasApi extends CanvasCore {
1732
1844
  * @returns 被删除的节点数据数组
1733
1845
  */
1734
1846
  deleteNodes(nodeIds) {
1735
- if (nodeIds.length === 0) return [];
1736
- const nodes = this.getState().nodes || [];
1737
- const idsToDelete = new Set(nodeIds);
1847
+ return super.deleteNodes(nodeIds);
1848
+ }
1849
+ /**
1850
+ * 将节点移动到最上层
1851
+ */
1852
+ moveNodesToTop(nodeIds) {
1853
+ if (nodeIds.length === 0) return;
1738
1854
  nodeIds.forEach((id) => {
1739
- const node = nodes.find((n) => n.id === id);
1740
- if (node?.type === "image") {
1741
- nodes.forEach((n) => {
1742
- if (n.type === "image-marker" && n.meta.parent === id) {
1743
- idsToDelete.add(n.id);
1744
- }
1745
- });
1855
+ const shape = this.getStage().findOne(`#${id}`);
1856
+ if (shape) {
1857
+ shape.moveToTop();
1746
1858
  }
1747
1859
  });
1748
- const deletedNodes = nodes.filter((n) => idsToDelete.has(n.id));
1749
- idsToDelete.forEach((id) => {
1860
+ }
1861
+ /**
1862
+ * 将节点移动到最下层
1863
+ */
1864
+ moveNodesToBottom(nodeIds) {
1865
+ if (nodeIds.length === 0) return;
1866
+ nodeIds.forEach((id) => {
1750
1867
  const shape = this.getStage().findOne(`#${id}`);
1751
1868
  if (shape) {
1752
- shape.destroy();
1869
+ shape.moveToBottom();
1753
1870
  }
1754
1871
  });
1755
- const newNodes = nodes.filter((n) => !idsToDelete.has(n.id));
1756
- this.getCanvasTransformer().clearNodes();
1757
- this._updateState(
1758
- {
1759
- nodes: newNodes,
1760
- selectedNodeIds: []
1761
- },
1762
- true
1763
- );
1764
- this.emit("nodes:deleted", deletedNodes);
1765
- return deletedNodes;
1766
1872
  }
1767
1873
  /**
1768
1874
  * 滚动到内容区域
@@ -1790,7 +1896,7 @@ class CanvasApi extends CanvasCore {
1790
1896
  const hasSelection = !hasTargetIds && selectedNodeIds.length > 0;
1791
1897
  const idsToShow = hasTargetIds ? targetNodeIds : hasSelection ? selectedNodeIds : null;
1792
1898
  mainLayer.children.forEach((child) => {
1793
- if (child.visible() && child.getClassName() !== "Transformer" && child.hasName(NODE_NAME_FOR_SELECT)) {
1899
+ if (child.visible() && child.getClassName() !== "Transformer" && child.hasName(NODE_NAMES.selectable)) {
1794
1900
  if (idsToShow) {
1795
1901
  const id = child.id();
1796
1902
  if (!idsToShow.includes(id)) return;
@@ -2108,6 +2214,6 @@ function PureCanvas({ setApi }) {
2108
2214
  }
2109
2215
  export {
2110
2216
  CanvasApi,
2111
- NODE_NAME_FOR_SELECT,
2217
+ NODE_NAMES,
2112
2218
  PureCanvas
2113
2219
  };
package/dist/index.umd.js CHANGED
@@ -13,7 +13,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
13
13
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
14
14
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
15
15
 
16
- var _core, _stage, _viewport, _handleWheel, _handlePointerDown, _handlePointerMove, _handlePointerUp, _handleDragStart, _handleDragMove, _handleDragEnd, _CanvasStage_instances, setupEventListeners_fn, _core2, _transformer, _handleTransformStart, _handleTransform, _handleTransformEnd, _handleDragStart2, _handleDragMove2, _handleDragEnd2, _CanvasTransformer_instances, setupEventListeners_fn2, _toolTypeChangeHandler, _RectNode_instances, setupEventHandlers_fn, _ImageNode_instances, loadImage_fn, _toolTypeChangeHandler2, setupEventHandlers_fn2, syncImageMarkers_fn, syncImageMarkersToState_fn, _rect, _markerGroup, _circle, _text, _ImageMarkerNode_instances, setupEventHandlers_fn3, _canvasStage, _mainLayer, _canvasTransformer, _draftNode;
16
+ var _core, _stage, _viewport, _handleWheel, _handlePointerDown, _handlePointerMove, _handlePointerUp, _handleDragStart, _handleDragMove, _handleDragEnd, _CanvasStage_instances, setupEventListeners_fn, _core2, _transformer, _handleTransformStart, _handleTransform, _handleTransformEnd, _handleDragStart2, _handleDragMove2, _handleDragEnd2, _CanvasTransformer_instances, setupEventListeners_fn2, _toolTypeChangeHandler, _RectNode_instances, setupEventHandlers_fn, _ImageNode_instances, loadImage_fn, _toolTypeChangeHandler2, setupEventHandlers_fn2, syncImageMarkers_fn, syncImageMarkersToState_fn, _rect, _markerGroup, _circle, _text, _handleViewportChange, _handleNodesSelected, _ImageMarkerNode_instances, updateMarkerScale_fn, setupEventHandlers_fn3, _canvasStage, _mainLayer, _canvasTransformer, _draftNode, _container, _handleKeyDown, _CanvasCore_instances, setupKeyboardEvents_fn;
17
17
  var __vite_style__ = document.createElement("style");
18
18
  __vite_style__.textContent = `/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
19
19
  @layer properties {
@@ -389,10 +389,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
389
389
  position: relative;
390
390
  }
391
391
 
392
- .static {
393
- position: static;
394
- }
395
-
396
392
  .top-0 {
397
393
  top: calc(var(--spacing) * 0);
398
394
  }
@@ -1830,7 +1826,13 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
1830
1826
  this._emitter.all.clear();
1831
1827
  }
1832
1828
  }
1833
- const NODE_NAME_FOR_SELECT = "shapeNameForSelect";
1829
+ const NODE_NAMES = {
1830
+ nodeRoot: "nodeRoot_intrinsic",
1831
+ selectable: "selectable_intrinsic",
1832
+ rect: "rect_intrinsic",
1833
+ image: "image_intrinsic",
1834
+ imageMarker: "image_marker_intrinsic"
1835
+ };
1834
1836
  const RECT = {
1835
1837
  CORNER_RADIUS: 6,
1836
1838
  MIN_SIZE: 10
@@ -1894,7 +1896,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
1894
1896
  width,
1895
1897
  height,
1896
1898
  cornerRadius: RECT.CORNER_RADIUS,
1897
- name: NODE_NAME_FOR_SELECT,
1899
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.rect}`,
1898
1900
  draggable: true,
1899
1901
  stroke: "black",
1900
1902
  strokeWidth: 2
@@ -2013,7 +2015,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2013
2015
  id: this.node.id,
2014
2016
  x: this.node.props.x,
2015
2017
  y: this.node.props.y,
2016
- name: NODE_NAME_FOR_SELECT,
2018
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.image}`,
2017
2019
  draggable: true,
2018
2020
  image: placeholder
2019
2021
  });
@@ -2210,12 +2212,23 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2210
2212
  __privateAdd(this, _markerGroup);
2211
2213
  __privateAdd(this, _circle);
2212
2214
  __privateAdd(this, _text);
2215
+ __privateAdd(this, _handleViewportChange);
2216
+ __privateAdd(this, _handleNodesSelected);
2213
2217
  const group = this.getElement();
2214
2218
  __privateSet(this, _rect, group.findOne(".rect"));
2215
2219
  __privateSet(this, _markerGroup, group.findOne(".marker-group"));
2216
2220
  __privateSet(this, _circle, __privateGet(this, _markerGroup).findOne("Circle"));
2217
2221
  __privateSet(this, _text, __privateGet(this, _markerGroup).findOne("Text"));
2218
2222
  __privateMethod(this, _ImageMarkerNode_instances, setupEventHandlers_fn3).call(this);
2223
+ __privateSet(this, _handleViewportChange, () => {
2224
+ __privateMethod(this, _ImageMarkerNode_instances, updateMarkerScale_fn).call(this);
2225
+ });
2226
+ this.core.on("viewport:scale:change", __privateGet(this, _handleViewportChange));
2227
+ __privateSet(this, _handleNodesSelected, (selectedIds) => {
2228
+ const isSelected = selectedIds.includes(this.node.id);
2229
+ this.setFocusState(isSelected);
2230
+ });
2231
+ this.core.on("nodes:selected", __privateGet(this, _handleNodesSelected));
2219
2232
  }
2220
2233
  createElement() {
2221
2234
  const width = Math.max(
@@ -2228,23 +2241,27 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2228
2241
  );
2229
2242
  const group = new Konva.Group({
2230
2243
  id: this.node.id,
2231
- name: `static ${this.node.meta.parent} imageMarker`,
2244
+ name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.imageMarker} ${this.node.meta.parent}`,
2232
2245
  x: this.node.props.x,
2233
2246
  y: this.node.props.y,
2234
2247
  width,
2235
2248
  height
2236
2249
  });
2250
+ const stageScale = this.core.getStageScale();
2251
+ const rectStrokeWidth = 2 / stageScale;
2252
+ const rectDash = [5 / stageScale, 5 / stageScale];
2253
+ const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
2237
2254
  const rect = new Konva.Rect({
2238
2255
  name: "rect",
2239
2256
  x: 0,
2240
2257
  y: 0,
2241
2258
  width,
2242
2259
  height,
2243
- stroke: this.node.style.color,
2244
- strokeWidth: 2,
2245
- dash: [5, 5],
2260
+ stroke: "#3B82F6",
2261
+ strokeWidth: rectStrokeWidth,
2262
+ dash: rectDash,
2246
2263
  fill: "transparent",
2247
- cornerRadius: RECT.CORNER_RADIUS,
2264
+ cornerRadius: rectCornerRadius,
2248
2265
  listening: false
2249
2266
  });
2250
2267
  const markerGroup = new Konva.Group({
@@ -2253,13 +2270,14 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2253
2270
  y: height,
2254
2271
  visible: this.isDraft ? false : true
2255
2272
  });
2256
- const stageScale = this.core.getStageScale();
2257
- const radius = 16 / stageScale;
2273
+ const radius = 14 / stageScale;
2274
+ const strokeWidth = 3 / stageScale;
2275
+ const fontSize = 16 / stageScale;
2258
2276
  const circle = new Konva.Circle({
2259
2277
  radius,
2260
- fill: "red",
2261
- stroke: "black",
2262
- strokeWidth: 2
2278
+ fill: "#3B82F6",
2279
+ stroke: "white",
2280
+ strokeWidth
2263
2281
  });
2264
2282
  const text = new Konva.Text({
2265
2283
  x: -radius,
@@ -2269,7 +2287,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2269
2287
  text: String(this.node.meta.markerNumber || ""),
2270
2288
  align: "center",
2271
2289
  verticalAlign: "middle",
2272
- fontSize: 16,
2290
+ fontSize,
2273
2291
  fill: "white"
2274
2292
  });
2275
2293
  markerGroup.add(circle);
@@ -2332,13 +2350,16 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2332
2350
  * 销毁
2333
2351
  */
2334
2352
  destroy() {
2353
+ this.core.off("viewport:scale:change", __privateGet(this, _handleViewportChange));
2354
+ this.core.off("nodes:selected", __privateGet(this, _handleNodesSelected));
2335
2355
  this.element.destroy();
2336
2356
  }
2337
2357
  /**
2338
2358
  * 更新焦点状态(hover 或 selected)
2339
2359
  */
2340
2360
  setFocusState(isFocus) {
2341
- const strokeWidth = isFocus ? 4 : 2;
2361
+ const stageScale = this.core.getStageScale();
2362
+ const strokeWidth = isFocus ? 4 / stageScale : 3 / stageScale;
2342
2363
  const scale = isFocus ? 1.2 : 1;
2343
2364
  __privateGet(this, _rect).strokeWidth(strokeWidth);
2344
2365
  __privateGet(this, _circle).strokeWidth(strokeWidth);
@@ -2350,7 +2371,31 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2350
2371
  _markerGroup = new WeakMap();
2351
2372
  _circle = new WeakMap();
2352
2373
  _text = new WeakMap();
2374
+ _handleViewportChange = new WeakMap();
2375
+ _handleNodesSelected = new WeakMap();
2353
2376
  _ImageMarkerNode_instances = new WeakSet();
2377
+ /**
2378
+ * 更新标记点的缩放以保持视觉大小不变
2379
+ */
2380
+ updateMarkerScale_fn = function() {
2381
+ const stageScale = this.core.getStageScale();
2382
+ const radius = 14 / stageScale;
2383
+ const strokeWidth = 3 / stageScale;
2384
+ const fontSize = 16 / stageScale;
2385
+ const rectStrokeWidth = 3 / stageScale;
2386
+ const rectDash = [5 / stageScale, 5 / stageScale];
2387
+ const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
2388
+ __privateGet(this, _rect).strokeWidth(rectStrokeWidth);
2389
+ __privateGet(this, _rect).dash(rectDash);
2390
+ __privateGet(this, _rect).cornerRadius(rectCornerRadius);
2391
+ __privateGet(this, _circle).radius(radius);
2392
+ __privateGet(this, _circle).strokeWidth(strokeWidth);
2393
+ __privateGet(this, _text).x(-radius);
2394
+ __privateGet(this, _text).y(-radius);
2395
+ __privateGet(this, _text).width(radius * 2);
2396
+ __privateGet(this, _text).height(radius * 2);
2397
+ __privateGet(this, _text).fontSize(fontSize);
2398
+ };
2354
2399
  /**
2355
2400
  * 设置事件处理器
2356
2401
  */
@@ -2412,8 +2457,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2412
2457
  ...baseNode,
2413
2458
  style: {
2414
2459
  ...baseNode.style,
2415
- color: "#ff0000",
2416
- line: "dashed"
2460
+ color: "#3B82F6"
2417
2461
  }
2418
2462
  };
2419
2463
  }
@@ -2482,10 +2526,19 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2482
2526
  toolType: "select",
2483
2527
  nodes: []
2484
2528
  });
2529
+ __privateAdd(this, _CanvasCore_instances);
2485
2530
  __privateAdd(this, _canvasStage);
2486
2531
  __privateAdd(this, _mainLayer);
2487
2532
  __privateAdd(this, _canvasTransformer);
2488
2533
  __privateAdd(this, _draftNode, null);
2534
+ __privateAdd(this, _container);
2535
+ __privateAdd(this, _handleKeyDown, (e) => {
2536
+ if (e.key === "Delete" || e.key === "Backspace") {
2537
+ e.preventDefault();
2538
+ this.deleteSelectedNodes();
2539
+ }
2540
+ });
2541
+ __privateSet(this, _container, el);
2489
2542
  __privateSet(this, _canvasStage, new CanvasStage(this, {
2490
2543
  container: el,
2491
2544
  width: el.clientWidth,
@@ -2498,6 +2551,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2498
2551
  __privateGet(this, _canvasStage).getStage().add(__privateGet(this, _mainLayer));
2499
2552
  __privateGet(this, _mainLayer).add(__privateGet(this, _canvasTransformer).getTransformer());
2500
2553
  this.updateViewport(this.getState().viewport, false);
2554
+ __privateMethod(this, _CanvasCore_instances, setupKeyboardEvents_fn).call(this);
2501
2555
  }
2502
2556
  /**
2503
2557
  * 获取 CanvasStage 实例
@@ -2593,8 +2647,9 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2593
2647
  */
2594
2648
  updateViewport(viewport, addToHistory = false) {
2595
2649
  __privateGet(this, _canvasStage).setViewport(viewport);
2650
+ const oldViewport = this.getState().viewport;
2596
2651
  const newViewport = {
2597
- ...this.getState().viewport,
2652
+ ...oldViewport,
2598
2653
  ...viewport
2599
2654
  };
2600
2655
  this._updateState(
@@ -2604,6 +2659,9 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2604
2659
  addToHistory
2605
2660
  );
2606
2661
  this.emit("viewport:change", newViewport);
2662
+ if (oldViewport.scale !== newViewport.scale) {
2663
+ this.emit("viewport:scale:change", newViewport.scale);
2664
+ }
2607
2665
  __privateGet(this, _canvasTransformer).emitPositionChange();
2608
2666
  }
2609
2667
  createNodes(nodes) {
@@ -2651,7 +2709,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2651
2709
  visible: true
2652
2710
  },
2653
2711
  style: {
2654
- color: "#ff0000",
2712
+ color: "#3B82F6",
2655
2713
  line: "dashed",
2656
2714
  size: "medium",
2657
2715
  opacity: 1
@@ -2799,41 +2857,79 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2799
2857
  */
2800
2858
  selectNode(nodeId, multiSelect = false) {
2801
2859
  const curSelectedNodeIds = this.getState().selectedNodeIds ?? [];
2802
- if (curSelectedNodeIds.length === 0 && !nodeId) {
2860
+ let selectedNodeIds = [];
2861
+ if (nodeId) {
2862
+ if (multiSelect && curSelectedNodeIds.length > 0) {
2863
+ selectedNodeIds = [...curSelectedNodeIds, nodeId];
2864
+ } else {
2865
+ selectedNodeIds = [nodeId];
2866
+ }
2867
+ } else if (curSelectedNodeIds.length === 0) {
2803
2868
  return;
2804
2869
  }
2805
- if (!nodeId) {
2870
+ if (selectedNodeIds.length === 0) {
2806
2871
  __privateGet(this, _canvasTransformer).clearNodes();
2807
- this._updateState(
2808
- {
2809
- selectedNodeIds: []
2810
- },
2811
- false
2812
- );
2813
- return;
2814
- }
2815
- let selectedNodeIds = [];
2816
- if (multiSelect) {
2817
- selectedNodeIds = curSelectedNodeIds.length ? [...curSelectedNodeIds, nodeId] : [nodeId];
2818
2872
  } else {
2819
- selectedNodeIds = [nodeId];
2873
+ const nodes = this.getStage().find(`.${NODE_NAMES.selectable}`).filter((node) => selectedNodeIds.includes(node.id()));
2874
+ __privateGet(this, _canvasTransformer).setNodes(nodes);
2820
2875
  }
2821
- const nodes = this.getStage().find(`.${NODE_NAME_FOR_SELECT}`).filter((node) => {
2822
- const id = node.id();
2823
- return selectedNodeIds.includes(id);
2876
+ this._updateState({ selectedNodeIds }, false);
2877
+ this.emit("nodes:selected", selectedNodeIds);
2878
+ }
2879
+ /**
2880
+ * 删除指定的节点(内部使用)
2881
+ * 如果删除的是 image 节点,会同步删除所有关联的 image-marker
2882
+ * @internal 仅供内部使用,外部请使用 CanvasApi
2883
+ * @param nodeIds - 要删除的节点 ID 数组
2884
+ * @returns 被删除的节点数据数组
2885
+ */
2886
+ deleteNodes(nodeIds) {
2887
+ if (nodeIds.length === 0) return [];
2888
+ const nodes = this.getState().nodes || [];
2889
+ const idsToDelete = new Set(nodeIds);
2890
+ nodeIds.forEach((id) => {
2891
+ const node = nodes.find((n) => n.id === id);
2892
+ if (node?.type === "image") {
2893
+ nodes.forEach((n) => {
2894
+ if (n.type === "image-marker" && n.meta.parent === id) {
2895
+ idsToDelete.add(n.id);
2896
+ }
2897
+ });
2898
+ }
2824
2899
  });
2825
- __privateGet(this, _canvasTransformer).setNodes(nodes);
2900
+ const deletedNodes = nodes.filter((n) => idsToDelete.has(n.id));
2901
+ idsToDelete.forEach((id) => {
2902
+ const shape = this.getStage().findOne(`#${id}`);
2903
+ if (shape) {
2904
+ shape.destroy();
2905
+ }
2906
+ });
2907
+ const newNodes = nodes.filter((n) => !idsToDelete.has(n.id));
2908
+ __privateGet(this, _canvasTransformer).clearNodes();
2826
2909
  this._updateState(
2827
2910
  {
2828
- selectedNodeIds
2911
+ nodes: newNodes,
2912
+ selectedNodeIds: []
2829
2913
  },
2830
- false
2914
+ true
2831
2915
  );
2916
+ this.emit("nodes:deleted", deletedNodes);
2917
+ return deletedNodes;
2918
+ }
2919
+ /**
2920
+ * 删除选中的节点(内部使用)
2921
+ * @internal 仅供内部使用,外部请使用 CanvasApi
2922
+ */
2923
+ deleteSelectedNodes() {
2924
+ const selectedNodeIds = this.getState().selectedNodeIds || [];
2925
+ if (selectedNodeIds.length === 0) return;
2926
+ this.deleteNodes(selectedNodeIds);
2832
2927
  }
2833
2928
  /**
2834
2929
  * 销毁 canvas
2835
2930
  */
2836
2931
  dispose() {
2932
+ __privateGet(this, _container).removeEventListener("keydown", __privateGet(this, _handleKeyDown));
2837
2933
  this.getCanvasTransformer().destroy();
2838
2934
  this.getMainLayer().destroy();
2839
2935
  this.getCanvasStage().destroy();
@@ -2889,6 +2985,16 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2889
2985
  _mainLayer = new WeakMap();
2890
2986
  _canvasTransformer = new WeakMap();
2891
2987
  _draftNode = new WeakMap();
2988
+ _container = new WeakMap();
2989
+ _handleKeyDown = new WeakMap();
2990
+ _CanvasCore_instances = new WeakSet();
2991
+ /**
2992
+ * 设置键盘事件监听
2993
+ */
2994
+ setupKeyboardEvents_fn = function() {
2995
+ __privateGet(this, _container).tabIndex = 0;
2996
+ __privateGet(this, _container).addEventListener("keydown", __privateGet(this, _handleKeyDown));
2997
+ };
2892
2998
  class CanvasApi extends CanvasCore {
2893
2999
  /**
2894
3000
  * 获取所有可用的工具类型
@@ -2977,29 +3083,32 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
2977
3083
  * @returns DataURL 格式的图片数据,如果没有选区则返回 null
2978
3084
  */
2979
3085
  exportSelectionAsImage(options) {
2980
- const position = this.getCanvasTransformer().getPosition();
2981
- if (!position) {
3086
+ const selectedNodeIds = this.getState().selectedNodeIds || [];
3087
+ if (selectedNodeIds.length === 0) {
2982
3088
  console.warn("No selection to export");
2983
3089
  return null;
2984
3090
  }
2985
- const stage = this.getStage();
2986
3091
  const padding = options?.padding ?? 0;
2987
- const transformer = this.getCanvasTransformer().getTransformer();
2988
- const transformerVisible = transformer.visible();
2989
- transformer.visible(false);
2990
- try {
2991
- return stage.toDataURL({
2992
- x: position.x - padding,
2993
- y: position.y - padding,
2994
- width: position.width + padding * 2,
2995
- height: position.height + padding * 2,
2996
- pixelRatio: options?.pixelRatio ?? 1,
2997
- mimeType: options?.mimeType ?? "image/png",
2998
- quality: options?.quality ?? 1
2999
- });
3000
- } finally {
3001
- transformer.visible(transformerVisible);
3002
- }
3092
+ const tempGroup = new Konva.Group();
3093
+ selectedNodeIds.forEach((id) => {
3094
+ const shape = this.getStage().findOne(`#${id}`);
3095
+ if (shape) {
3096
+ const clone = shape.clone();
3097
+ tempGroup.add(clone);
3098
+ }
3099
+ });
3100
+ const box = tempGroup.getClientRect();
3101
+ const dataURL = tempGroup.toDataURL({
3102
+ x: box.x - padding,
3103
+ y: box.y - padding,
3104
+ width: box.width + padding * 2,
3105
+ height: box.height + padding * 2,
3106
+ pixelRatio: options?.pixelRatio ?? 2,
3107
+ mimeType: options?.mimeType ?? "image/png",
3108
+ quality: options?.quality ?? 1
3109
+ });
3110
+ tempGroup.destroy();
3111
+ return dataURL;
3003
3112
  }
3004
3113
  /**
3005
3114
  * 导出带有标注的图片节点为图片
@@ -3043,7 +3152,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
3043
3152
  */
3044
3153
  deleteSelectedNodes() {
3045
3154
  const selectedNodeIds = this.getState().selectedNodeIds || [];
3046
- if (selectedNodeIds.length === 0) return;
3047
3155
  this.deleteNodes(selectedNodeIds);
3048
3156
  }
3049
3157
  /**
@@ -3053,37 +3161,31 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
3053
3161
  * @returns 被删除的节点数据数组
3054
3162
  */
3055
3163
  deleteNodes(nodeIds) {
3056
- if (nodeIds.length === 0) return [];
3057
- const nodes = this.getState().nodes || [];
3058
- const idsToDelete = new Set(nodeIds);
3164
+ return super.deleteNodes(nodeIds);
3165
+ }
3166
+ /**
3167
+ * 将节点移动到最上层
3168
+ */
3169
+ moveNodesToTop(nodeIds) {
3170
+ if (nodeIds.length === 0) return;
3059
3171
  nodeIds.forEach((id) => {
3060
- const node = nodes.find((n) => n.id === id);
3061
- if (node?.type === "image") {
3062
- nodes.forEach((n) => {
3063
- if (n.type === "image-marker" && n.meta.parent === id) {
3064
- idsToDelete.add(n.id);
3065
- }
3066
- });
3172
+ const shape = this.getStage().findOne(`#${id}`);
3173
+ if (shape) {
3174
+ shape.moveToTop();
3067
3175
  }
3068
3176
  });
3069
- const deletedNodes = nodes.filter((n) => idsToDelete.has(n.id));
3070
- idsToDelete.forEach((id) => {
3177
+ }
3178
+ /**
3179
+ * 将节点移动到最下层
3180
+ */
3181
+ moveNodesToBottom(nodeIds) {
3182
+ if (nodeIds.length === 0) return;
3183
+ nodeIds.forEach((id) => {
3071
3184
  const shape = this.getStage().findOne(`#${id}`);
3072
3185
  if (shape) {
3073
- shape.destroy();
3186
+ shape.moveToBottom();
3074
3187
  }
3075
3188
  });
3076
- const newNodes = nodes.filter((n) => !idsToDelete.has(n.id));
3077
- this.getCanvasTransformer().clearNodes();
3078
- this._updateState(
3079
- {
3080
- nodes: newNodes,
3081
- selectedNodeIds: []
3082
- },
3083
- true
3084
- );
3085
- this.emit("nodes:deleted", deletedNodes);
3086
- return deletedNodes;
3087
3189
  }
3088
3190
  /**
3089
3191
  * 滚动到内容区域
@@ -3111,7 +3213,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
3111
3213
  const hasSelection = !hasTargetIds && selectedNodeIds.length > 0;
3112
3214
  const idsToShow = hasTargetIds ? targetNodeIds : hasSelection ? selectedNodeIds : null;
3113
3215
  mainLayer.children.forEach((child) => {
3114
- if (child.visible() && child.getClassName() !== "Transformer" && child.hasName(NODE_NAME_FOR_SELECT)) {
3216
+ if (child.visible() && child.getClassName() !== "Transformer" && child.hasName(NODE_NAMES.selectable)) {
3115
3217
  if (idsToShow) {
3116
3218
  const id = child.id();
3117
3219
  if (!idsToShow.includes(id)) return;
@@ -3428,7 +3530,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
3428
3530
  ] });
3429
3531
  }
3430
3532
  exports2.CanvasApi = CanvasApi;
3431
- exports2.NODE_NAME_FOR_SELECT = NODE_NAME_FOR_SELECT;
3533
+ exports2.NODE_NAMES = NODE_NAMES;
3432
3534
  exports2.PureCanvas = PureCanvas;
3433
3535
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
3434
3536
  }));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@8btc/whiteboard",
3
3
  "private": false,
4
- "version": "0.0.12",
4
+ "version": "0.0.14",
5
5
  "type": "module",
6
6
  "main": "./dist/index.umd.js",
7
7
  "module": "./dist/index.js",