drawui-core 0.3.6 → 0.4.1

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
@@ -18,7 +18,7 @@ interface ShapeStyle {
18
18
  /** 描边色透明度(0-1),缺省为 1;与 strokeColor 叠加使用 */
19
19
  strokeOpacity?: number;
20
20
  }
21
- type ShapeType = 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'cylinder' | 'cloud' | 'heart' | 'block-arrow-right' | 'block-arrow-left' | 'block-arrow-up' | 'block-arrow-down' | 'block-arrow-double' | 'right-triangle' | 'cross' | 'speech-rect' | 'speech-round' | 'cube' | 'sector' | 'delay' | 'predefined-process' | 'document' | 'stadium' | 'class-catalog' | 'chevron' | 'brace-left' | 'brace-right' | 'envelope' | 'checkbox' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie' | 'uml-class' | 'uml-interface' | 'mind-map' | 'line' | 'arrow' | 'text' | 'draw';
21
+ type ShapeType = 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'cylinder' | 'cloud' | 'heart' | 'block-arrow-right' | 'block-arrow-left' | 'block-arrow-up' | 'block-arrow-down' | 'block-arrow-double' | 'right-triangle' | 'cross' | 'speech-rect' | 'speech-round' | 'cube' | 'sector' | 'delay' | 'predefined-process' | 'document' | 'stadium' | 'class-catalog' | 'chevron' | 'brace-left' | 'brace-right' | 'envelope' | 'checkbox' | 'sticky-note' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie' | 'uml-class' | 'uml-interface' | 'mind-map' | 'table' | 'icon' | 'line' | 'arrow' | 'text' | 'draw';
22
22
  interface BaseShape {
23
23
  id: string;
24
24
  type: ShapeType;
@@ -123,6 +123,32 @@ interface StadiumShape extends BaseShape {
123
123
  width: number;
124
124
  height: number;
125
125
  }
126
+ /**
127
+ * 便签纸:矩形纸面 + 右下角折角(dog-ear)。
128
+ *
129
+ * 折角的观感来自两部分:**纸面缺掉一角** + **折回来的一块纸背**(同色系略深),
130
+ * 轮廓与折角三角定义在 `shapePaths.ts`,折角色板与文字避让在 `stickyNote.ts`。
131
+ * 折角大小按 `STICKY_FOLD_RATIO` 随尺寸自动缩放,因此不需要额外的尺寸字段。
132
+ */
133
+ interface StickyNoteShape extends BaseShape {
134
+ type: 'sticky-note';
135
+ width: number;
136
+ height: number;
137
+ }
138
+ /**
139
+ * 矢量图标:把内置图标库(`icons.ts`)里的一个图标放到画布上。
140
+ *
141
+ * 尺寸模型与矩形一致(以 (x,y) 为中心 + width/height),图标在 24×24 网格里作画后等比缩放;
142
+ * 颜色:线稿图层跟随 `style.fillColor`(样式面板里的「填充」就是图标颜色),
143
+ * 少数带固有配色的图层(如数字徽标的圆底)保持自己的颜色。
144
+ */
145
+ interface IconShape extends BaseShape {
146
+ type: 'icon';
147
+ /** 图标库里的 id,见 `ICONS` / `getIcon` */
148
+ iconId: string;
149
+ width: number;
150
+ height: number;
151
+ }
126
152
  /**
127
153
  * 「矩形区域 + 归一化轮廓」类图形(对话框 / 立方体 / 流程图符号等)。
128
154
  * 尺寸模型与矩形完全一致(以 (x,y) 为中心 + width/height),差异只在轮廓,
@@ -133,7 +159,7 @@ interface PathShape extends BaseShape {
133
159
  width: number;
134
160
  height: number;
135
161
  }
136
- type UmlVisibility = '+' | '-' | '#' | '~';
162
+ type UmlVisibility = '+' | '-' | '#' | '~' | '';
137
163
  interface UmlAttribute {
138
164
  id: string;
139
165
  visibility: UmlVisibility;
@@ -178,6 +204,11 @@ interface LineShape extends BaseShape {
178
204
  connectionType?: ConnectionType;
179
205
  /** 用户手工调节过的中间折点(世界坐标),见 ArrowShape.userPoints */
180
206
  userPoints?: Vec2[];
207
+ /** userPoints 的参照端点(见 ArrowShape.userPointsRef) */
208
+ userPointsRef?: {
209
+ start: Vec2;
210
+ end: Vec2;
211
+ };
181
212
  }
182
213
  type ConnectionType = 'straight' | 'polyline' | 'orthogonal' | 'curve';
183
214
  /** 生成点位创建上下文:from 为源点(生成点位),to 为目标落点 */
@@ -252,6 +283,16 @@ interface ArrowShape extends BaseShape {
252
283
  * 两端依旧跟随绑定(拖动图形后两端自动跟上,中间保持用户摆好的位置)。
253
284
  */
254
285
  userPoints?: Vec2[];
286
+ /**
287
+ * `userPoints` 的参照端点:写下这些折点那一刻,两端实际停在哪里(世界坐标)。
288
+ * 图形移动时按它把折点一起平移(见 connectorEdit.followEndpoints)——
289
+ * 否则端点移了几像素、折点没动,走线会在端点旁多出一个 2~3px 的小台阶
290
+ * (肉眼就是「一条线变成两条贴着的线」)。
291
+ */
292
+ userPointsRef?: {
293
+ start: Vec2;
294
+ end: Vec2;
295
+ };
255
296
  }
256
297
  interface TextShape extends BaseShape {
257
298
  type: 'text';
@@ -289,8 +330,38 @@ interface MindMapShape extends BaseShape {
289
330
  height: number;
290
331
  root: MindMapNode;
291
332
  }
292
- type Shape = RectangleShape | RoundedRectangleShape | CircleShape | EllipseShape | DiamondShape | TriangleShape | HexagonShape | ParallelogramShape | TrapezoidShape | PentagonShape | OctagonShape | StarShape | CylinderShape | CloudShape | HeartShape | BlockArrowShape | StadiumShape | PathShape | UmlClassShape | UmlInterfaceShape | LineShape | ArrowShape | TextShape | DrawShape | MindMapShape;
293
- type ToolType = 'select' | 'hand' | 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'cylinder' | 'cloud' | 'heart' | 'block-arrow-right' | 'block-arrow-left' | 'block-arrow-up' | 'block-arrow-down' | 'block-arrow-double' | 'right-triangle' | 'cross' | 'speech-rect' | 'speech-round' | 'cube' | 'sector' | 'delay' | 'predefined-process' | 'document' | 'stadium' | 'class-catalog' | 'chevron' | 'brace-left' | 'brace-right' | 'envelope' | 'checkbox' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie' | 'uml-class' | 'uml-interface' | 'line' | 'arrow' | 'text' | 'draw' | 'pen' | 'highlighter' | 'eraser' | 'mind-map';
333
+ /**
334
+ * 表格单元格:合并模型见 `table.ts` —— 只有合并区域的**左上角锚点**携带
335
+ * `rowSpan/colSpan`,区域内其余格子是被覆盖格(数据保持空 `{text:''}`,
336
+ * 渲染/命中时跳过,点击映射回锚点)。
337
+ */
338
+ interface TableCell {
339
+ text: string;
340
+ textColor?: string;
341
+ fontSize?: number;
342
+ bold?: boolean;
343
+ /** 单元格底色(缺省透明,透出表格 style.fillColor) */
344
+ fillColor?: string;
345
+ align?: 'left' | 'center' | 'right';
346
+ rowSpan?: number;
347
+ colSpan?: number;
348
+ }
349
+ /**
350
+ * 表格:(x,y) 为中心 + width/height(与矩形一致),但宽高恒等于
351
+ * colWidths/rowHeights 之和(见 table.ts),行列结构由纯函数操作。
352
+ */
353
+ interface TableShape extends BaseShape {
354
+ type: 'table';
355
+ /** 恒等于 colWidths / rowHeights 之和,写回时同步 */
356
+ width: number;
357
+ height: number;
358
+ colWidths: number[];
359
+ rowHeights: number[];
360
+ /** cells[row][col] */
361
+ cells: TableCell[][];
362
+ }
363
+ type Shape = RectangleShape | RoundedRectangleShape | CircleShape | EllipseShape | DiamondShape | TriangleShape | HexagonShape | ParallelogramShape | TrapezoidShape | PentagonShape | OctagonShape | StarShape | CylinderShape | CloudShape | HeartShape | BlockArrowShape | StadiumShape | StickyNoteShape | IconShape | PathShape | UmlClassShape | UmlInterfaceShape | LineShape | ArrowShape | TextShape | DrawShape | MindMapShape | TableShape;
364
+ type ToolType = 'select' | 'hand' | 'rectangle' | 'rounded-rectangle' | 'circle' | 'ellipse' | 'diamond' | 'triangle' | 'hexagon' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'octagon' | 'star' | 'cylinder' | 'cloud' | 'heart' | 'block-arrow-right' | 'block-arrow-left' | 'block-arrow-up' | 'block-arrow-down' | 'block-arrow-double' | 'right-triangle' | 'cross' | 'speech-rect' | 'speech-round' | 'cube' | 'sector' | 'delay' | 'predefined-process' | 'document' | 'stadium' | 'class-catalog' | 'chevron' | 'brace-left' | 'brace-right' | 'envelope' | 'checkbox' | 'sticky-note' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie' | 'uml-class' | 'uml-interface' | 'line' | 'arrow' | 'text' | 'draw' | 'pen' | 'highlighter' | 'eraser' | 'mind-map' | 'table' | 'icon';
294
365
  interface CameraState {
295
366
  x: number;
296
367
  y: number;
@@ -456,6 +527,8 @@ declare const zhCN: {
456
527
  readonly 'tool.class-catalog': "类目";
457
528
  readonly 'tool.envelope': "信封";
458
529
  readonly 'tool.checkbox': "复选框";
530
+ readonly 'tool.sticky-note': "便签";
531
+ readonly 'shape.sticky-note': "便签";
459
532
  readonly 'tool.triangular-pyramid': "三棱锥";
460
533
  readonly 'tool.quadrangular-pyramid': "四棱锥";
461
534
  readonly 'tool.cuboid': "长方体";
@@ -572,6 +645,10 @@ declare const zhCN: {
572
645
  readonly 'toolbar.export.png': "PNG 图片";
573
646
  readonly 'toolbar.export.svg': "SVG 矢量图";
574
647
  readonly 'toolbar.export.pdf': "PDF 文档";
648
+ readonly 'toolbar.export.data': "导出数据";
649
+ readonly 'toolbar.import.data': "导入数据";
650
+ readonly 'import.success': "导入成功,已恢复 {n} 个图形";
651
+ readonly 'import.fail': "导入失败,文件格式不正确";
575
652
  readonly 'shape.mind-map': "思维导图";
576
653
  readonly 'mindmap.defaultRoot': "中心主题";
577
654
  readonly 'mindmap.defaultBranch': "分支主题";
@@ -591,6 +668,46 @@ declare const zhCN: {
591
668
  readonly 'mindmap.expand': "展开";
592
669
  readonly 'mindmap.close': "关闭编辑";
593
670
  readonly 'mindmap.more': "更多设置";
671
+ readonly 'tool.table': "表格";
672
+ readonly 'toolbar.table': "表格";
673
+ readonly 'shape.table': "表格";
674
+ readonly 'table.insertRow': "插入行";
675
+ readonly 'table.deleteRow': "删除行";
676
+ readonly 'table.insertCol': "插入列";
677
+ readonly 'table.deleteCol': "删除列";
678
+ readonly 'table.merge': "合并单元格";
679
+ readonly 'table.split': "拆分单元格";
680
+ readonly 'table.insertRowAbove': "在上方插入行";
681
+ readonly 'table.insertRowBelow': "在下方插入行";
682
+ readonly 'table.insertColLeft': "在左侧插入列";
683
+ readonly 'table.insertColRight': "在右侧插入列";
684
+ readonly 'table.pickHint': "拖选行列数";
685
+ readonly 'toolbar.icon': "图标";
686
+ readonly 'icon.search': "搜索图标";
687
+ readonly 'icon.empty': "没有匹配的图标";
688
+ readonly 'icon.recent': "最近使用";
689
+ readonly 'iconCat.status': "状态标记";
690
+ readonly 'iconCat.number': "数字徽标";
691
+ readonly 'iconCat.tech': "云与技术";
692
+ readonly 'iconCat.chart': "数据图表";
693
+ readonly 'iconCat.office': "办公文档";
694
+ readonly 'iconCat.media': "媒体协作";
695
+ readonly 'iconCat.arrow': "箭头方向";
696
+ readonly 'iconCat.misc': "常用";
697
+ readonly 'iconCat.emoji': "表情";
698
+ readonly 'iconCat.device': "电脑设备";
699
+ readonly 'iconCat.weather': "天气";
700
+ readonly 'iconCat.transport': "交通";
701
+ readonly 'iconCat.nature': "自然";
702
+ readonly 'iconCat.building': "建筑";
703
+ readonly 'iconCat.medical': "医疗";
704
+ readonly 'iconCat.shopping': "购物";
705
+ readonly 'iconCat.science': "科学";
706
+ readonly 'iconCat.animal': "动物";
707
+ readonly 'iconCat.time': "时间";
708
+ readonly 'iconCat.edit': "编辑";
709
+ readonly 'iconCat.food': "食物";
710
+ readonly 'iconCat.communication': "通讯";
594
711
  };
595
712
  type MessageKey = keyof typeof zhCN;
596
713
  type LanguageListener = () => void;
@@ -681,6 +798,8 @@ declare class Editor {
681
798
  private mindMapEditingShapeId;
682
799
  private mindMapSelectedNode;
683
800
  private mindMapHoverNode;
801
+ private tableSelection;
802
+ private tableEditing;
684
803
  private renderScheduled;
685
804
  private pointerIsDown;
686
805
  private notifyScheduled;
@@ -691,6 +810,13 @@ declare class Editor {
691
810
  * 只影响之后创建的箭头;改选中箭头的样式走 updateArrowStyle。
692
811
  */
693
812
  private arrowStyle;
813
+ /**
814
+ * 新建便签的纸面色(工具栏「便签」面板里选的)。
815
+ * 同样「只影响之后创建的便签」;改选中便签的颜色走 updateSelectedShapesStyle。
816
+ */
817
+ private stickyNoteColor;
818
+ /** 连续插入图标时的错开计数(避免每次都叠在同一处) */
819
+ private iconInsertSeq;
694
820
  constructor(canvas: HTMLCanvasElement, options?: EditorOptions);
695
821
  private setupEventListeners;
696
822
  /** 窗口失焦(切页签/alt-tab):按下状态与一切瞬态预览都作废,避免"卡住" */
@@ -726,6 +852,15 @@ declare class Editor {
726
852
  private screenToPage;
727
853
  private addShape;
728
854
  private updateShape;
855
+ /**
856
+ * 表格尺寸变化时保持「左上角」不动:行高变化向下伸缩、列宽变化向右伸缩,
857
+ * 避免整表在画布上向两边跳。返回新的中心坐标(已按旋转角换算)。
858
+ */
859
+ private tableTopLeftAnchored;
860
+ /** 写回表格数据(cells/rowHeights/colWidths),并同步尺寸与中心(左上角不动) */
861
+ private writeTable;
862
+ /** 行高不够显示单元格文字时自动增高(只增不减) */
863
+ private autoGrowTableRows;
729
864
  private updateBoundArrows;
730
865
  private getBindingPosition;
731
866
  private deleteShape;
@@ -746,6 +881,11 @@ declare class Editor {
746
881
  * 交给工具层做线段手柄的命中与拖动 —— 用的是渲染器同一套计算,保证「看到哪就能拖哪」。
747
882
  */
748
883
  private getConnectorRoute;
884
+ /**
885
+ * 忽略手工折点的「自动走线」。拖线段松手时用它判断用户是不是又拖回了原来的线
886
+ * (等价 → 清掉 userPoints,连线恢复成完全自动)。
887
+ */
888
+ private getAutoConnectorRoute;
749
889
  /**
750
890
  * 清掉所有「瞬态」的交互可视化状态:连线预览、目标高亮、对齐参考线、框选、悬停锚点。
751
891
  * 这些状态只在一次交互过程中有效 —— 只要交互结束(或事件丢失)就必须清空,
@@ -798,6 +938,20 @@ declare class Editor {
798
938
  getArrowStyle(): ArrowStyle;
799
939
  /** 修改「新建箭头」的默认样式:只影响之后画出来的箭头 */
800
940
  setArrowStyle(updates: Partial<ArrowStyle>): void;
941
+ /** 读取「新建便签」的纸面色(工具栏「便签」面板用) */
942
+ getStickyNoteColor(): string;
943
+ /**
944
+ * 往画布插入一个内置图标(工具栏「图标」面板用)。
945
+ *
946
+ * - 落在**视口中心**;连续插入会依次错开一点,避免全叠在一起;
947
+ * - 插入后自动选中并切回选择工具,方便立刻拖动 / 缩放;
948
+ * - 计入撤销历史;图标 id 不存在时返回 undefined(不做任何事)。
949
+ */
950
+ insertIcon(iconId: string, size?: number): string | undefined;
951
+ /** 视口中心(世界坐标):插入类操作(图标)的默认落点 */
952
+ private viewportCenter;
953
+ /** 修改「新建便签」的纸面色:只影响之后创建的便签;非法色值会被忽略 */
954
+ setStickyNoteColor(color: string): void;
801
955
  /** 批量更新选中箭头的虚线样式 */
802
956
  updateArrowDashed(dashed: boolean): void;
803
957
  /** 更新 UML 类图的类名、属性、方法,自动重算尺寸 */
@@ -870,6 +1024,107 @@ declare class Editor {
870
1024
  deleteMindMapNode(shapeId: string, nodeId: string): void;
871
1025
  /** 折叠/展开指定节点 */
872
1026
  toggleMindMapCollapse(shapeId: string, nodeId: string): void;
1027
+ /** 获取选中的表格(恰好选中一个时) */
1028
+ getSelectedTable(): TableShape | null;
1029
+ private getTableById;
1030
+ /** 读取表格某格(只读引用,勿修改) */
1031
+ getTableCell(shapeId: string, row: number, col: number): TableCell | null;
1032
+ /** 页面坐标下的表格单元格命中(被覆盖格映射回锚点) */
1033
+ getTableAtPoint(point: Vec2): {
1034
+ shapeId: string;
1035
+ row: number;
1036
+ col: number;
1037
+ } | null;
1038
+ /** 屏幕坐标(画布左上角为原点)下的表格单元格命中 */
1039
+ getTableAtScreenPoint(screenPoint: Vec2): {
1040
+ shapeId: string;
1041
+ row: number;
1042
+ col: number;
1043
+ } | null;
1044
+ /**
1045
+ * 拖拽框选用:把页面坐标解析成某表格内的行列;**超出表格时 clamp 到边缘行列**
1046
+ * (拖到表格外 = 选中整行/整列)。指针不在该表格附近时也照样给出边缘格,不再返回 null。
1047
+ */
1048
+ getTableCellAtPointClamped(shapeId: string, point: Vec2): {
1049
+ row: number;
1050
+ col: number;
1051
+ } | null;
1052
+ /** 指针是否落在表格左上角的「拖动把手」上(只有它能移动整表) */
1053
+ isTableMoveHandleHit(shapeId: string, point: Vec2): boolean;
1054
+ /** 表格某格(缺省整表)在屏幕上的矩形(左上角 + 尺寸),用于就地编辑定位 */
1055
+ getTableScreenRect(shapeId: string, row?: number, col?: number): {
1056
+ x: number;
1057
+ y: number;
1058
+ width: number;
1059
+ height: number;
1060
+ } | null;
1061
+ /** 当前单元格选区 */
1062
+ getTableSelection(): {
1063
+ shapeId: string;
1064
+ anchor: {
1065
+ row: number;
1066
+ col: number;
1067
+ };
1068
+ focus: {
1069
+ row: number;
1070
+ col: number;
1071
+ };
1072
+ } | null;
1073
+ /** 选中表格的一个单元格(extend=true 时从原锚点扩展选区到该格) */
1074
+ selectTableCell(shapeId: string, row: number, col: number, extend?: boolean): void;
1075
+ /** 当前正在编辑的单元格(null = 未在编辑) */
1076
+ getTableEditing(): {
1077
+ shapeId: string;
1078
+ row: number;
1079
+ col: number;
1080
+ } | null;
1081
+ /** 进入单元格编辑态(双击单元格触发) */
1082
+ beginTableEdit(shapeId: string, row: number, col: number): void;
1083
+ /** 退出单元格编辑态 */
1084
+ exitTableEdit(): void;
1085
+ /** 更新单元格内容/样式。commit=false 时只应用不写历史(就地编辑逐键调用)。 */
1086
+ updateTableCell(shapeId: string, row: number, col: number, patch: Partial<TableCell>, commit?: boolean): void;
1087
+ /** 往视口中心插入一张 rows×cols 的空表(工具栏「表格」面板用) */
1088
+ insertTable(rows?: number, cols?: number): string | undefined;
1089
+ /** 在指定行(缺省选区行)下方插入一行 */
1090
+ insertTableRow(shapeId: string, at?: number): void;
1091
+ /** 在指定列(缺省选区列)右侧插入一列 */
1092
+ insertTableColumn(shapeId: string, at?: number): void;
1093
+ /** 删除指定行(缺省选区行);仅剩一行时不删 */
1094
+ deleteTableRow(shapeId: string, at?: number): void;
1095
+ /** 删除指定列(缺省选区列);仅剩一列时不删 */
1096
+ deleteTableColumn(shapeId: string, at?: number): void;
1097
+ /**
1098
+ * 当前选区覆盖的行列范围(clamp 后);无选区或不属于该表时返回 null。
1099
+ * ⚠️ 结果会**扩张到完整包住所有相交的合并区**(对齐飞书:选区永远吸附整块),
1100
+ * 这样高亮、合并、拆分、删除行列看到的是同一个范围。
1101
+ */
1102
+ private getTableSelRange;
1103
+ /**
1104
+ * 相对当前选区插入一行:above = 选区首行之上,below = 选区末行之下。
1105
+ * 插入后选区落到新行(沿用原列范围),便于连续向上/向下插入。
1106
+ */
1107
+ insertTableRowRelative(shapeId: string, position: 'above' | 'below'): void;
1108
+ /** 相对当前选区插入一列:left = 选区首列之左,right = 选区末列之右。插入后选区落到新列 */
1109
+ insertTableColumnRelative(shapeId: string, position: 'left' | 'right'): void;
1110
+ /**
1111
+ * 删除选区覆盖的全部行(一次历史记录);至少保留一行,全选时不做任何事。
1112
+ * 删除后选区收敛到删除位置所在行(同列范围)。
1113
+ */
1114
+ deleteTableRowsInSelection(shapeId: string): void;
1115
+ /** 删除选区覆盖的全部列(一次历史记录);至少保留一列 */
1116
+ deleteTableColsInSelection(shapeId: string): void;
1117
+ /** 行列删除后把选区/编辑格收敛到剩余范围内 */
1118
+ private clampTableSelectionAfterDelete;
1119
+ /** 合并当前选区(单格时无操作)。范围含已合并区时按扩张后的完整范围合并 */
1120
+ mergeTableCells(shapeId?: string): void;
1121
+ /**
1122
+ * 拆分当前选区里的**所有**合并区(飞书式:选中合并区的任意部分即可整体还原)。
1123
+ * 选区里没有合并区时无操作。
1124
+ */
1125
+ splitTableCells(shapeId?: string): void;
1126
+ /** 拆分指定格所在的合并区域(保留旧入口:用于「就点这一格」的调用方) */
1127
+ splitTableCell(shapeId?: string): void;
873
1128
  /** 给指定 UML 类追加一条属性 */
874
1129
  addUmlAttribute(shapeId: string): void;
875
1130
  /** 给指定 UML(类图或接口)追加一条方法 */
@@ -919,6 +1174,18 @@ declare class Editor {
919
1174
  getCamera(): Camera;
920
1175
  /** 指针是否处于按下状态(拖拽/画线/框选中) */
921
1176
  isPointerDown(): boolean;
1177
+ /**
1178
+ * 该图形画在画布上的文字**是否没显示全**(被省略号截断)。
1179
+ * UI 用它在「文字显示不全」时才弹出全文提示块 —— 显示得下就不打扰用户。
1180
+ */
1181
+ isShapeTextTruncated(shapeId: string): boolean;
1182
+ /** UML 的某一行文字是否没显示全(提示块显示条件;字号/加粗按该行实际绘制用的值取) */
1183
+ isUmlRowTextTruncated(row: {
1184
+ shape: UmlClassShape | UmlInterfaceShape;
1185
+ section: 'className' | 'attribute' | 'method';
1186
+ rowIndex: number;
1187
+ text: string;
1188
+ }): boolean;
922
1189
  /** 当前画布内容的包围盒(世界坐标,含留白),导出时用于确定画布尺寸与视口 */
923
1190
  getContentBounds(padding?: number): {
924
1191
  x: number;
@@ -975,8 +1242,15 @@ declare class Editor {
975
1242
  saveToStorage(): boolean;
976
1243
  loadFromStorage(): boolean;
977
1244
  clearStorage(): void;
1245
+ /**
1246
+ * 导出完整文档数据为 JSON 字符串(图形 + 相机 + 版本号),
1247
+ * 供「导出数据 → 下次导入」保存/恢复继续绘图。
1248
+ */
978
1249
  exportJSON(): string;
1250
+ /** 从 JSON 字符串恢复完整文档数据;兼容旧版只含 shapes 的格式,返回是否成功 */
979
1251
  importJSON(json: string): boolean;
1252
+ /** 导出完整数据并触发浏览器下载 .json 文件 */
1253
+ downloadJSON(fileName?: string): void;
980
1254
  getEditingShapeId(): string | null;
981
1255
  canEditShape(shape: Shape): boolean;
982
1256
  enterEditing(shapeId: string): void;
@@ -1051,8 +1325,12 @@ declare function pickSegmentHandle(route: Vec2[], point: Vec2, handleRadius?: nu
1051
1325
  *
1052
1326
  * 传入的 route 应当始终是「按下时的那条走线」,每次移动都从它重新算 —— 因为拖首/末段
1053
1327
  * 会插入折点、段序号会变,从原始走线重算才能得到稳定(不漂移)的结果。
1328
+ *
1329
+ * `extraLevels`:额外的磁吸线(除相邻段之外)。拖动时把「按下那一刻本段所在的水平线」
1330
+ * 和「自动走线的同名水平线」传进来 —— 于是「往回拖到原来那条线」会精准落在线上,
1331
+ * 松手即可判定为「没改过」并合并回自动走线(飞书手感)。
1054
1332
  */
1055
- declare function offsetConnectorSegment(route: Vec2[], index: number, delta: number): Vec2[];
1333
+ declare function offsetConnectorSegment(route: Vec2[], index: number, delta: number, extraLevels?: number[]): Vec2[];
1056
1334
  /**
1057
1335
  * 去掉连续重复点与共线冗余点,精简冗余拐点。
1058
1336
  * (唯一实现:Renderer.collapseLinePoints 与交互层写回前都调它 —— 别再各写一份)
@@ -1060,6 +1338,24 @@ declare function offsetConnectorSegment(route: Vec2[], index: number, delta: num
1060
1338
  declare function collapseRoute(pts: Vec2[]): Vec2[];
1061
1339
  /** 走线 → 可写回 shape 的中间折点(去掉两端) */
1062
1340
  declare function routeToUserPoints(route: Vec2[]): Vec2[];
1341
+ /** 两条走线是否几何等价(点数相同、逐点重合)——「拖回原来的线」=没改过 */
1342
+ declare function sameRoute(a: Vec2[], b: Vec2[], tol?: number): boolean;
1343
+ /**
1344
+ * 端点旁的出/入桩拐角:距端点不超过一个桩长(≈24px),且与端点同轴(只差一个轴向坐标)。
1345
+ * 自动走线的第一个 / 最后一个中间折点就是它 —— 每次渲染都会由桩重新算出来。
1346
+ */
1347
+ declare function isStubCorner(end: Vec2, p: Vec2, lead?: number, tol?: number): boolean;
1348
+ /**
1349
+ * 丢掉「其实属于自动走线」的手工折点 —— 两端出/入桩的拐角。
1350
+ *
1351
+ * 拖线段时是把当时的整条走线(含自动生成的 24px 桩拐角)写成手工折点的;
1352
+ * 这些拐角一旦被冻进 `userPoints`,之后再移动两端图形时,桩会按新位置重新生成,
1353
+ * 与冻结点错开 → 端点旁多出一次折返(视觉上就是「端点上长了个小钩子」/「变成两条线」)。
1354
+ * 它们每次都会被自动走线重新生成,所以直接丢掉是等价的(画布与导出都靠桩补回来)。
1355
+ *
1356
+ * @param autoRoute 忽略手工折点后的自动走线(世界坐标)
1357
+ */
1358
+ declare function pruneStubBends(userPoints: Vec2[], autoRoute: Vec2[], lead?: number, tol?: number): Vec2[];
1063
1359
 
1064
1360
  interface SnapGuide {
1065
1361
  type: 'horizontal' | 'vertical';
@@ -1075,6 +1371,22 @@ declare class Renderer {
1075
1371
  private showGrid;
1076
1372
  /** 文本排版缓存:避免每帧对每个字符做 measureText */
1077
1373
  private textLayoutCache;
1374
+ /** 当前表格单元格选区(由 Editor 每帧写入),用于在画布上画选区高亮 */
1375
+ private tableSelection;
1376
+ /** 写入表格单元格选区(null = 无) */
1377
+ setTableSelection(sel: {
1378
+ shapeId: string;
1379
+ anchor: {
1380
+ row: number;
1381
+ col: number;
1382
+ };
1383
+ focus: {
1384
+ row: number;
1385
+ col: number;
1386
+ };
1387
+ } | null): void;
1388
+ /** 当前悬停的图形 id(由每帧 render 写入),仅用于表格拖动把手等提示性绘制 */
1389
+ private hoverShapeId;
1078
1390
  private static readonly TEXT_CACHE_LIMIT;
1079
1391
  constructor(canvas: HTMLCanvasElement, dpr?: number);
1080
1392
  resize(width: number, height: number): void;
@@ -1110,6 +1422,8 @@ declare class Renderer {
1110
1422
  */
1111
1423
  private drawGrid;
1112
1424
  private drawShape;
1425
+ /** 表格左上角的六点拖动把手(尺寸恒定屏幕像素) */
1426
+ private drawTableMoveHandle;
1113
1427
  private applyStyle;
1114
1428
  private drawRectangle;
1115
1429
  private drawRoundedRectangle;
@@ -1132,7 +1446,24 @@ declare class Renderer {
1132
1446
  private drawPathShape;
1133
1447
  /** 把归一化路径命令展开到 canvas 当前路径 */
1134
1448
  private tracePath;
1449
+ /**
1450
+ * 矢量图标:把 24×24 网格里的图层等比缩放到图形的 width/height 后绘制。
1451
+ *
1452
+ * - 颜色:线稿图层的 `'ink'` 取图形自身的 fillColor(样式面板改「填充」即换图标颜色),
1453
+ * 写死 hex 的图层(数字徽标的圆底)保持自己的配色;
1454
+ * - 线宽用「网格单位」而不是屏幕单位,所以图标放大时线条跟着变粗,像字体图标那样;
1455
+ * - 用 `Path2D` 直接吃 SVG path 字符串,与 SVG 导出共用同一份路径数据(含圆弧,无需自己解析)。
1456
+ */
1457
+ private drawIcon;
1135
1458
  private drawUmlClass;
1459
+ /**
1460
+ * 表格:整表背景 + 单元格底色 + 内部网格线(被合并区域跨过的边界段断开)
1461
+ * + 外边框(跟随图形样式)+ 单元格文字(超出省略)。
1462
+ */
1463
+ private drawTable;
1464
+ /** 画一条带缺口的网格线(gaps 为沿线区间,区间内不画) */
1465
+ private drawSegmentsWithGaps;
1466
+ private strokeGridSegment;
1136
1467
  /**
1137
1468
  * UML 接口:标题显示为 `<<${className}>>`,仅方法段,无属性段,只有一条分隔线。
1138
1469
  */
@@ -1153,21 +1484,35 @@ declare class Renderer {
1153
1484
  /**
1154
1485
  * 把连线两端解析成世界坐标折点 + 停靠法向(绑定落点、auto 重选、未绑定端点的轮廓吸附)。
1155
1486
  * 绘制与 getConnectorRoute 共用这一份,避免两处规则漂移。
1487
+ *
1488
+ * fromBox/toBox:两端**绑定图形**的包围盒(已转成与 points 相同的本地坐标,
1489
+ * 即相对 shape.x/y)。供正交路由做「总线避开两端图形内部」的避让(见 docking.orthogonalRoute)。
1490
+ * 只取绑定端:未绑定(悬空)端的画布/导出本来就不完全对齐,本轮避让不扩大战场。
1156
1491
  */
1157
1492
  private resolveConnectorEnds;
1493
+ /** 绑定图形的包围盒,转成连线本地点坐标系(相对 shape.x/y,供 orthogonalRoute 避让用) */
1494
+ private localDockBox;
1158
1495
  /**
1159
1496
  * 用户手工调过折点的连线:走线 = 起点 → 用户折点… → 终点(两端仍跟随绑定/端点)。
1160
1497
  *
1161
1498
  * ⚠️ 两端要先做和「两端点」时同样的近似对齐:用户是照着**对齐后**的那条线拖的手柄,
1162
1499
  * 折点坐标是相对对齐后的轴线算出来的;若这里用未对齐的原始端点接上,就会在两端各
1163
1500
  * 留下几像素的斜边(画布与导出还会各画各的)。对齐后手工折点与端点严格同轴。
1501
+ *
1502
+ * 折点还要先「跟着两端移动」+「拉回端点轴线」(resolveUserPoints):图形移动后若折点留在
1503
+ * 原地,端点旁会多出 2~3px 的小台阶 —— 肉眼就是「一条线变成两条贴着的线」。
1164
1504
  */
1165
1505
  private withUserPoints;
1166
1506
  /**
1167
1507
  * 连线当前的真实走线(**世界坐标**折点)。
1168
1508
  * 手柄命中、手柄拖动都用它 —— 与绘制同一套计算,所以「看到手柄在哪,点它就一定命中」。
1509
+ *
1510
+ * `options.ignoreUserPoints`:把手工艺折点当作不存在,算出「纯自动」的那条走线。
1511
+ * 拖线段松手时用它判断用户是不是又拖回了原来的线(等价 → 清掉手工状态)。
1169
1512
  */
1170
- getConnectorRoute(shape: Shape, shapeMap: Map<string, Shape>): Vec2[];
1513
+ getConnectorRoute(shape: Shape, shapeMap: Map<string, Shape>, options?: {
1514
+ ignoreUserPoints?: boolean;
1515
+ }): Vec2[];
1171
1516
  /**
1172
1517
  * 选中连线时:在每段(够长的)线段中点画一个可拖动的小手柄(飞书式「拖线段调位置」)。
1173
1518
  * 拖动交互在 SelectTool 的 'dragging-segment' 状态里。
@@ -1223,27 +1568,12 @@ declare class Renderer {
1223
1568
  private drawSharpPolyline;
1224
1569
  /** 绘制对齐参考线(飞书风格:浅蓝色虚线) */
1225
1570
  private drawSnapGuides;
1226
- /** 折线:与orthogonal使用相同的H-V-H路由,差异仅在渲染(sharp vs rounded)。dock 法向在首/末段加一段垂直出线桩,垂直脱离图形边缘。 */
1227
- private generatePolylinePoints;
1228
1571
  /**
1229
- * 把折线末段掰到期望轴向('H' 水平收尾 / 'V' 垂直收尾)。
1230
- * 保证箭头始终沿接入边法向插入(或沿出线方向收尾)。
1572
+ * 折线:与 orthogonal 使用相同的 H-V-H 路由,差异仅在渲染(sharp vs rounded)。
1573
+ * 实现已抽到 connectorRoute.polylineRoutePoints —— 与**导出**共用同一份,
1574
+ * 从构造上保证「画布画出来的 == 导出的」。
1231
1575
  */
1232
- private ensureFinalAxis;
1233
- /** 去除连续重合点与共线点,精简冗余拐点(唯一实现见 connectorEdit.collapseRoute) */
1234
- private collapseLinePoints;
1235
- /**
1236
- * 计算单段直角路由:最简单的正交折线(H-V-H / V-H-V / L 拐角)
1237
- * —— **刻意不做避障**,允许从其它图形上穿过去。
1238
- *
1239
- * 为什么不做避障(2026-09-22 决定,对齐飞书):绕行会让路径在拖动时反复改形
1240
- * (3 段 ↔ 6 段拐点来回跳),观感就是「绕来绕去」;而「挡路/重叠时直接穿过去、
1241
- * 端点不挪窝、永远横平竖直」更可预期也更好读。实测绕行只在约 6% 的布局下触发,
1242
- * 但它带来的「画布 ≠ 导出」与拖拽跳变是所有场景都在承受的。
1243
- *
1244
- * 规则与 export.dockRoutePoints 完全一致 → 画布所见即导出所得。
1245
- */
1246
- private computeOrthogonalRoute;
1576
+ private generatePolylinePoints;
1247
1577
  /**
1248
1578
  * 曲线:用三次贝塞尔拼接出平滑曲线(离散化采样)。
1249
1579
  * 端点若停靠在图形上(dock.fromN/toN),控制点沿法向伸出,使曲线垂直脱离 / 垂直接入
@@ -1253,12 +1583,23 @@ declare class Renderer {
1253
1583
  /**
1254
1584
  * 获取文本的显示行(换行 + 截断),结果按 shape/文本/尺寸缓存,
1255
1585
  * 避免每帧渲染都对文本逐字符 measureText。
1586
+ * cacheKeyExtra:同一图形内不同字体(如表格单元格加粗/字号)要分开缓存时传入。
1256
1587
  */
1257
1588
  private getDisplayLines;
1258
1589
  private drawShapeText;
1259
1590
  private getTextBounds;
1260
1591
  private wrapLine;
1261
1592
  private truncateLine;
1593
+ /**
1594
+ * 画在画布上的文字是否被截断(有内容没显示全)。
1595
+ * 与 drawShapeText / drawText 用**同一套** bounds 与换行规则,所以判定结果
1596
+ * 和用户看到的一致:UI 上用它在「文字显示不全」时才弹出全文提示。
1597
+ */
1598
+ isShapeTextTruncated(shape: Shape): boolean;
1599
+ /** 换行后的行数是否超过可见行数(超过 = 画的时候会截断并加省略号) */
1600
+ private isTextTruncated;
1601
+ /** UML 的某一行是否被省略号截断(与 drawUmlClass / drawUmlInterface 的 ellipsize 同规则) */
1602
+ isUmlRowTextTruncated(shape: Shape, text: string, fontSize: number, bold?: boolean): boolean;
1262
1603
  /** 连接点句柄 → 外法向(页面坐标单位向量;规则见 docking.ts) */
1263
1604
  private static readonly HANDLE_NORMALS;
1264
1605
  /**
@@ -1341,6 +1682,112 @@ declare const Vec: {
1341
1682
  equals(a: Vec2, b: Vec2): boolean;
1342
1683
  };
1343
1684
 
1685
+ /**
1686
+ * 便签纸专属常量与派生逻辑。
1687
+ *
1688
+ * 只放「一个来源、多处消费」的东西:色板、折角比例、折角配色、文字避让。
1689
+ * 几何轮廓本身在 `shapePaths.ts`(canvas 与 SVG 导出共用),这里不重复定义路径。
1690
+ */
1691
+ /**
1692
+ * 便签纸色板:12 色(3 列 × 4 行,按色相排布)。
1693
+ * 这是**自有一套**的便签配色(比飞书那套更饱和一点,区分度更高),
1694
+ * 工具栏「便签」面板与样式面板的便签填充色板共用这一份,保证「面板里选什么、后面就是什么」。
1695
+ */
1696
+ declare const STICKY_NOTE_COLORS: string[];
1697
+ /** 新建便签的默认纸面色 */
1698
+ declare const STICKY_NOTE_DEFAULT_COLOR: string;
1699
+ /**
1700
+ * 折角在归一化坐标(0..1)下占的比例:横向占 `STICKY_FOLD_RATIO × width`、
1701
+ * 纵向占 `STICKY_FOLD_RATIO × height`。几何与文字避让共用这一个系数,避免两边对不上。
1702
+ */
1703
+ declare const STICKY_FOLD_RATIO = 0.2;
1704
+ /**
1705
+ * 便签文字的可用区域缩放:常规内边距 0.85 再让掉半个折角,
1706
+ * 保证文字块右下角落在折角斜边之内(不再是 0.85,否则会压到折角上)。
1707
+ */
1708
+ declare const STICKY_TEXT_SCALE: number;
1709
+
1710
+ /**
1711
+ * 内置矢量图标库。
1712
+ *
1713
+ * 设计约定:
1714
+ * - 每个图标都在 **24×24 的网格**里作画(和工具栏图标同一套坐标习惯),
1715
+ * 渲染时按图形的 width/height 等比缩放,所以放到多大都不糊、导出 SVG 是纯矢量;
1716
+ * - 默认是**线稿风格**(`fill: 'none'` + 描边),这套风格是我们自己的标识,与飞书的实心多彩图标明显不同;
1717
+ * - 颜色用「角色」表达:`'ink'` = 跟随图形自身的颜色(样式面板里改「填充」即换色),
1718
+ * 写具体 hex 的图层则是图标固有配色(如数字徽标的圆底);
1719
+ * - 本文件是**手绘线稿图标**(`status/number/emoji/device/tech/chart/office/media/arrow/misc`),
1720
+ * 另有 `icons-vendor.ts` 中的图标来自 **Lucide Icons(ISC 协议)**,仅把其真实 SVG 路径
1721
+ * 归一化到 24×24 网格并套用同一套渲染,未改动图形语义——用以补充更多分类(天气/交通等)。
1722
+ */
1723
+ /** 图标分类(labelKey 到 i18n 取多语言文案) */
1724
+ declare const ICON_CATEGORIES: {
1725
+ id: IconCategoryId;
1726
+ labelKey: MessageKey;
1727
+ }[];
1728
+ type IconCategoryId = 'status' | 'number' | 'emoji' | 'device' | 'tech' | 'chart' | 'office' | 'media' | 'arrow' | 'misc' | 'weather' | 'transport' | 'nature' | 'building' | 'medical' | 'shopping' | 'science' | 'animal' | 'time' | 'edit' | 'food' | 'communication';
1729
+ interface IconLayer {
1730
+ /** 24×24 网格内的路径(语法同 SVG path,统一大写指令) */
1731
+ d: string;
1732
+ /** 填充:`'ink'`(跟随图形颜色,默认)/ `'none'`(线稿)/ 具体 hex */
1733
+ fill?: string;
1734
+ /** 描边:`'ink'`(默认)/ `'none'` / 具体 hex */
1735
+ stroke?: string;
1736
+ /** 线宽(24 网格单位),缺省 1.8;线稿图标建议 1.7~1.9 */
1737
+ w?: number;
1738
+ }
1739
+ interface IconDef {
1740
+ id: string;
1741
+ cat: IconCategoryId;
1742
+ /** 中文名(同时作为搜索词) */
1743
+ zh: string;
1744
+ /** 英文名(同时作为搜索词) */
1745
+ en: string;
1746
+ /** 额外搜索词(中英混排,空格分隔) */
1747
+ kw?: string;
1748
+ layers: IconLayer[];
1749
+ /**
1750
+ * 这个图标自己的默认「墨色」,优先于所属分类的主色(见 iconInk)。
1751
+ * 全彩图标(表情贴纸)把它设成主色调(脸的黄),作为宿主的兜底色 ——
1752
+ * 它的图层都写死了 hex,颜色是图形语义,不跟着样式面板的「填充」走。
1753
+ */
1754
+ ink?: string;
1755
+ /** 圆底色(数字徽标用):画一个整圆垫底 */
1756
+ disc?: string;
1757
+ /** 中心文字(数字徽标用) */
1758
+ label?: {
1759
+ text: string;
1760
+ color: string;
1761
+ size?: number;
1762
+ };
1763
+ }
1764
+ declare const ICONS: IconDef[];
1765
+ declare function getIcon(id: string): IconDef | undefined;
1766
+ /** 存在该 id 的图标(写入图形/读文档时校验,避免引用到不存在的图标) */
1767
+ declare function hasIcon(id: string): boolean;
1768
+ declare function iconsByCategory(cat: IconCategoryId): IconDef[];
1769
+ /**
1770
+ * 搜索图标:忽略大小写,匹配中/英文名与关键词。
1771
+ * 空查询返回全部;多个词按「全部命中」处理(先按名称命中排序,名称命中优先)。
1772
+ */
1773
+ declare function searchIcons(query: string): IconDef[];
1774
+ /** 图标图形的默认边长(页面单位) */
1775
+ declare const ICON_DEFAULT_SIZE = 48;
1776
+ /** 图标作画网格边长(所有图标路径都在 0..ICON_GRID 的坐标里) */
1777
+ declare const ICON_GRID = 24;
1778
+ /** 图标「墨色」的默认值:线稿图层与单色图标跟随图形颜色(style.fillColor) */
1779
+ declare const ICON_DEFAULT_INK = "#3b82f6";
1780
+ /**
1781
+ * 每个分类的默认墨色。
1782
+ *
1783
+ * 我们的图标是单色线稿(不是飞书那种实心多彩贴纸),全用一个蓝会显得很单调;
1784
+ * 这里给每个分类配一个主色 —— 面板预览与「插入到画布」都用它,预览与落笔所见即所得;
1785
+ * 插入后仍可在样式面板里改色(换肤不受影响)。
1786
+ */
1787
+ declare const ICON_CATEGORY_INK: Record<IconCategoryId, string>;
1788
+ /** 取某个图标的默认墨色:图标自带 ink 优先(全彩贴纸),否则用分类主色,最后退回全局默认 */
1789
+ declare function iconInk(icon: IconDef): string;
1790
+
1344
1791
  interface ToolContext {
1345
1792
  addShape(shape: Shape): void;
1346
1793
  updateShape(id: string, updates: Partial<Shape>): void;
@@ -1378,6 +1825,12 @@ interface ToolContext {
1378
1825
  setHoverConnector(id: string | null): void;
1379
1826
  /** 某条连线当前的真实走线(世界坐标折点):线段手柄的命中/拖动用它,与画布绘制同一套计算 */
1380
1827
  getConnectorRoute(shapeId: string): Vec2[];
1828
+ /**
1829
+ * 忽略手工折点后的「自动走线」(世界坐标折点)。
1830
+ * 用于拖线段松手时判断「是不是又拖回原来的线了」—— 等价则把手工艺状态清掉,
1831
+ * 让连线恢复成完全自动(之后再移动图形仍是一条干净的线)。
1832
+ */
1833
+ getAutoConnectorRoute(shapeId: string): Vec2[];
1381
1834
  /** 新建连线的线条样式(颜色/粗细),与最终创建的连线保持一致 */
1382
1835
  getLineStyle(): ShapeStyle;
1383
1836
  /** 拖动到空白处:创建一条浮动线条并弹出"选择新图形"菜单 */
@@ -1399,6 +1852,8 @@ interface ToolContext {
1399
1852
  updateConnectionType(connectionType: ConnectionType): void;
1400
1853
  /** 新建箭头的默认样式(工具栏「箭头」下拉里选的) */
1401
1854
  getArrowStyle(): ArrowStyle;
1855
+ /** 新建便签的纸面色(工具栏「便签」面板里选的) */
1856
+ getStickyNoteColor(): string;
1402
1857
  /** 光标语义命中:思维导图节点框或折叠标记内才命中(节点间空档不命中) */
1403
1858
  getMindMapCursorHitAtPoint(point: Vec2): {
1404
1859
  shapeId: string;
@@ -1406,6 +1861,8 @@ interface ToolContext {
1406
1861
  } | null;
1407
1862
  /** 选中思维导图节点(不进入编辑态) */
1408
1863
  selectMindMapNode(shapeId: string, nodeId: string): void;
1864
+ /** 选中表格单元格(extend = 从原锚点扩展)—— 表格内拖拽框选走这里 */
1865
+ selectTableCell(shapeId: string, row: number, col: number, extend?: boolean): void;
1409
1866
  }
1410
1867
  declare abstract class Tool {
1411
1868
  abstract type: ToolType;
@@ -1435,6 +1892,8 @@ declare abstract class Tool {
1435
1892
  declare class SelectTool extends Tool {
1436
1893
  type: ToolType;
1437
1894
  private state;
1895
+ /** 正在框选的表格 id(表格内拖拽框选单元格,拖到表外自动扩成整行/整列) */
1896
+ private cellSelectShapeId;
1438
1897
  private translatingState;
1439
1898
  private resizingState;
1440
1899
  private generatingState;
@@ -1465,6 +1924,14 @@ declare class SelectTool extends Tool {
1465
1924
  /** 把生成点位打包成创建上下文(from 为源点,to 为目标落点) */
1466
1925
  private genContext;
1467
1926
  onPointerDown(point: Vec2, event: PointerEvent): void;
1927
+ /**
1928
+ * 命中「表格左上角拖动把手」的表格。把手画在表格包围盒之外、普通 hitTest 命中不到,
1929
+ * 所以这里单独判定;落点上若有别的图形则不抢(避免盖住别人的点击)。
1930
+ */
1931
+ private findTableMoveHandleAt;
1932
+ /** 记录「移动选中图形」的起始状态(按住拖动把手/图形本体都用这一份) */
1933
+ private beginTranslating;
1934
+ private captureTranslatingStart;
1468
1935
  onPointerMove(point: Vec2, event: PointerEvent): void;
1469
1936
  onPointerUp(point: Vec2, _event: PointerEvent): void;
1470
1937
  /**
@@ -1483,6 +1950,14 @@ declare class SelectTool extends Tool {
1483
1950
  * 这样在选中连线上任意位置按下都能拖到最近那一段,也就不会误触发「拖整条线 / 从锚点拉新线」。
1484
1951
  */
1485
1952
  private hitConnectorSegmentHandle;
1953
+ /**
1954
+ * 拖线段时的磁吸上下文:
1955
+ * - extraLevels =「按下那一刻本段所在的那条线」+「自动走线里同轴的各条线」。
1956
+ * 前者是橡皮筋(往回拖到原位就精准落回去),后者是「拖到自动走线那条线」——
1957
+ * 落在自动走线的线上时,松手就会被判定为「没改过」而合并回自动走线(飞书手感)。
1958
+ * - autoRoute = 忽略手工折点的自动走线,松手时用来做等价判断。
1959
+ */
1960
+ private segmentSnapContext;
1486
1961
  /** 拖线段时的光标:水平段能上下拖 → 上下箭头;垂直段能左右拖 → 左右箭头 */
1487
1962
  private segmentCursor;
1488
1963
  /** 拖动线段:把这一段沿垂直方向平移(相邻段自然伸缩),两端端点永不移动 */
@@ -1493,8 +1968,17 @@ declare class SelectTool extends Tool {
1493
1968
  * - 自由连线(无绑定)整条走线就是它自己的 `points`。
1494
1969
  * 先把走线里的重复点/共线冗余点去掉:**如果去掉后就是一条直线(≤2 点),把 userPoints 清空**,
1495
1970
  * 交还给自动路由 —— 这样「往基线拖、把浅台阶抹平」之后,连线是真的恢复原样,而不是留着一堆零高度折点。
1971
+ *
1972
+ * 另外还要丢掉「其实属于自动走线的折点」(两端出/入桩的拐角,见 pruneStubBends):
1973
+ * 它们是自动生成的,冻进 userPoints 后会让之后移动图形时端点旁长出小钩子。
1496
1974
  */
1497
1975
  private writeConnectorRoute;
1976
+ /**
1977
+ * 拖线段松手:如果又拖回了「自动走线」那条线上(逐点等价),就把手工状态清掉 ——
1978
+ * 连线恢复成完全自动,之后拖动两端图形仍是一条干净的线(不会残留成两条线 / 小钩子)。
1979
+ * 这就是飞书的「拖回一条线松手即合并」。
1980
+ */
1981
+ private finishSegmentDrag;
1498
1982
  hasTransientState(): boolean;
1499
1983
  /**
1500
1984
  * 取消一切瞬态交互(拖拽/生成/框选/旋转/连线端点拖拽)并清掉画布上的瞬态预览。
@@ -1576,7 +2060,13 @@ declare class SelectTool extends Tool {
1576
2060
  private findShapeNearPoint;
1577
2061
  private pointToRectDistance;
1578
2062
  private static readonly SNAP_THRESHOLD;
2063
+ /**
2064
+ * 连接点吸附半径:**靠近即可**,不再要求精准按在点位上(用户反馈:差一点就「跑走」)。
2065
+ * 连接点优先于精确锚点 —— 这是飞书式的主交互。
2066
+ */
1579
2067
  private static readonly HANDLE_SNAP_DISTANCE;
2068
+ /** 贴边判定:离轮廓这么近视为「贴着这条边松手」→ 吸附到该边中点的连接点 */
2069
+ private static readonly ON_EDGE_SNAP;
1580
2070
  private static readonly ANCHOR_EPS;
1581
2071
  /**
1582
2072
  * 拖拽箭头端点时计算辅助线(与 ArrowTool 规则一致):
@@ -1585,8 +2075,14 @@ declare class SelectTool extends Tool {
1585
2075
  */
1586
2076
  private computeSnapGuidesForArrow;
1587
2077
  /**
1588
- * 在 point 附近找可吸附的图形,并返回 binding(精确或非精确)。
1589
- * 优先级:最近的 handle 吸附(非精确) > 最近的边缘点(精确 normalizedAnchor)
2078
+ * 在 point 附近找可吸附的图形,并返回 binding(连接点或精确锚点)。
2079
+ * 优先级(对齐飞书「靠近连接点即可」):
2080
+ * 1. 任意图形的连接点(四边中点)40px 内 → 直接吸附该连接点(不受下方候选窗限制);
2081
+ * 2. 贴着轮廓松手(≤12px)→ 吸附到**这条边中点的连接点**,不落在松手处 ——
2082
+ * 沿某条边方向接近并松手(如贴着顶边从右往左),预期就是那个点位,
2083
+ * 而不是松手位置的精确锚点(用户反馈:松手后点位「跑走」);
2084
+ * 3. 离轮廓 12~32px 的悬空带 → 精确锚点(保留「连到边上指定位置」的能力);
2085
+ * 4. 指针落在图形内部 → 按落点相对目标中心的方向选最近的边。
1590
2086
  */
1591
2087
  private findBindingNearPoint;
1592
2088
  private getPageBounds;
@@ -1767,4 +2263,169 @@ declare function countMindMapNodes(root: MindMapNode): number;
1767
2263
  */
1768
2264
  declare function layoutMindMap(root: MindMapNode): MindMapLayoutResult;
1769
2265
 
1770
- export { type ArrowBinding, type ArrowHeadType, type ArrowShape, type ArrowStyle, ArrowTool, Camera, type CameraState, type CircleShape, type ConnectionType, DEFAULT_HIGHLIGHTER_COLOR, type DiamondShape, type DrawShape, type DrawuiTheme, type DrawuiThemeSelection, Editor, type EditorData, type EditorOptions, type EditorState, type EllipseShape, type ExportFormat, type ExportOptions, type FreehandMode, FreehandTool, type GenerationContext, HandTool, type HexagonShape, LANGUAGES, type Language, type LineShape, LineTool, MINDMAP_COLORS, MINDMAP_FONT_SIZE, MINDMAP_MAX_WIDTH, MINDMAP_MIN_WIDTH, MINDMAP_NODE_HEIGHT, MIN_HANDLE_SEGMENT, MIN_JOG, Mat2d, type MessageKey, type MindMapLayoutResult, type MindMapNode, type MindMapNodePos, type MindMapShape, type OctagonShape, type ParallelogramShape, type PentagonShape, type Rect, type RectangleShape, Renderer, type RoundedRectangleShape, type SegmentHandle, SelectTool, type Shape, type ShapeStyle, ShapeTool, type ShapeType, type SnapGuide, type StarShape, THEME, type TextShape, type ToolType, type TrapezoidShape, type TriangleShape, UML_FONT_SIZE, UML_HEADER_HEIGHT, UML_LINE_HEIGHT, UML_MIN_WIDTH, UML_PADDING_X, UML_PADDING_Y, type UiEvent, type UmlAttribute, type UmlClassShape, type UmlInterfaceShape, type UmlMethod, type UmlVisibility, Vec, type Vec2 as Vec2Type, calcUmlClassSize, cloneMindMapNode, collapseRoute, connectorSegmentHandles, countMindMapNodes, downloadBlob, exportToBlob, findMindMapNode, getContentBounds, getLanguage, getMindMapNodeDepth, getMindMapParent, getTheme, hexToRgba, layoutMindMap, mindMapNodeColor, mindMapNodeSize, offsetConnectorSegment, pickSegmentHandle, pointToSegmentDistance, renderSVGString, renderToCanvas, routeToUserPoints, setLanguage, setTheme, shadeColor, subscribeLanguage, subscribeTheme, t };
2266
+ /**
2267
+ * 表格的纯数据操作(唯一实现):行列增删、合并/拆分、命中定位。
2268
+ *
2269
+ * 合并模型:**每个合并区域只有左上角格子是「锚点」**,锚点带 `rowSpan/colSpan`;
2270
+ * 区域内其余格子是被覆盖格(渲染/命中时跳过,点击时映射回锚点)。
2271
+ * 所有操作都返回新的 cells/rowHeights 数组(不可变),由 Editor 负责写回与历史。
2272
+ */
2273
+ declare const TABLE_FONT_SIZE = 14;
2274
+ /** 单元格文字与边框的内边距 */
2275
+ declare const TABLE_CELL_PADDING_X = 10;
2276
+ /** 单元格文字与上下边框的内边距(多行时用) */
2277
+ declare const TABLE_CELL_PADDING_Y = 4;
2278
+ /** 单元格多行文字的行高倍率(与图形文字换行保持一致) */
2279
+ declare const TABLE_LINE_HEIGHT_RATIO = 1.3;
2280
+ /** 行高随内容自动增高的上限行数(再多就靠裁剪+省略号) */
2281
+ declare const TABLE_MAX_AUTO_LINES = 10;
2282
+ declare const TABLE_MIN_COL_WIDTH = 40;
2283
+ declare const TABLE_MIN_ROW_HEIGHT = 24;
2284
+ declare const TABLE_DEFAULT_COL_WIDTH = 140;
2285
+ declare const TABLE_DEFAULT_ROW_HEIGHT = 36;
2286
+ /** 默认首行(表头)底色与网格线颜色 */
2287
+ declare const TABLE_HEADER_FILL = "#f1f3f5";
2288
+ /** 把手尺寸(屏幕像素,需除以 zoom 换算成本地尺寸):竖排的六点抓手 */
2289
+ declare const TABLE_MOVE_HANDLE_WIDTH = 18;
2290
+ declare const TABLE_MOVE_HANDLE_HEIGHT = 22;
2291
+ /** 把手与表格左边框的间距(屏幕像素)。
2292
+ * ⚠️ 必须贴在表格**左侧外侧、顶部对齐**:
2293
+ * - 画在表格上方会被「单元格悬浮工具条」盖住(它正好在表格上方那一带);
2294
+ * - 画在表格内侧会压住单元格第一行的文字。 */
2295
+ declare const TABLE_MOVE_HANDLE_GAP = 4;
2296
+ /** 页面坐标 → 表格本地坐标(原点 = 表格左上角;已抵消旋转) */
2297
+ declare function tableLocalPoint(t: TableShape, point: Vec2): Vec2;
2298
+ /** 本地坐标 → 页面坐标(tableLocalPoint 的逆运算) */
2299
+ declare function tablePagePoint(t: TableShape, local: Vec2): Vec2;
2300
+ /** 拖动把手的本地矩形(贴表格左边框外侧、顶部对齐,尺寸恒为屏幕像素) */
2301
+ declare function tableMoveHandleLocalRect(t: TableShape, zoom: number): {
2302
+ x: number;
2303
+ y: number;
2304
+ width: number;
2305
+ height: number;
2306
+ };
2307
+ /** 指针是否落在拖动把手上(point 为页面坐标;tol 为额外容差,屏幕像素) */
2308
+ declare function hitTableMoveHandle(t: TableShape, point: Vec2, zoom: number, tol?: number): boolean;
2309
+ /** 指针是否落在表格内部(表格内拖拽 = 框选单元格,不移动整表) */
2310
+ declare function hitTableBody(t: TableShape, point: Vec2, tol?: number): boolean;
2311
+ /**
2312
+ * 本地坐标 → 表格行列;**超出表格时 clamp 到边缘行列**(拖到表外 = 选中整行/整列)。
2313
+ * 本地坐标恰好落在边界线时归到右侧/下方的格子。
2314
+ */
2315
+ declare function tableCellAtLocalClamped(t: TableShape, local: Vec2): {
2316
+ row: number;
2317
+ col: number;
2318
+ };
2319
+ /** 选区(anchor→focus)在本地坐标下的合并矩形(用于画选区高亮) */
2320
+ declare function tableRangeLocalRect(t: TableShape, r1: number, c1: number, r2: number, c2: number): {
2321
+ x: number;
2322
+ y: number;
2323
+ width: number;
2324
+ height: number;
2325
+ };
2326
+ declare function tableTotalWidth(colWidths: number[]): number;
2327
+ declare function tableTotalHeight(rowHeights: number[]): number;
2328
+ /**
2329
+ * 单元格内多行文字第一行的「基线中心」相对单元格顶部的偏移(画布/导出共用,保证一致)。
2330
+ * - 文字整体放得下:垂直居中;
2331
+ * - 放不下(行数超出可视行数被截断):顶部对齐并留出 TABLE_CELL_PADDING_Y 内边距,
2332
+ * 这样读者能看到文字开头而不是中间被裁的片段。
2333
+ */
2334
+ declare function tableCellTextStartOffset(lineCount: number, lineHeight: number, cellHeight: number): number;
2335
+ /** 建一张空表;header=true 时首行为表头(加粗 + 浅灰底) */
2336
+ declare function createTableCells(rows: number, cols: number, header?: boolean): TableCell[][];
2337
+ /**
2338
+ * 某格文字在给定宽度下需要几行(逐字符贪心折行 + 显式 \n),
2339
+ * 与 Renderer.getDisplayLines / export.wrapText 的换行规则一致。
2340
+ */
2341
+ declare function tableCellLineCount(text: string, fontSize: number, bold: boolean, maxWidth: number): number;
2342
+ /**
2343
+ * 按内容把行高撑到够显示(只增不减,上限 TABLE_MAX_AUTO_LINES 行)。
2344
+ * 无需变化时返回 null;否则返回新的 rowHeights。
2345
+ * 合并单元格按其跨越行的总高度判断,缺口补在跨区最后一行。
2346
+ */
2347
+ declare function tableGrowRowHeights(shape: TableShape): number[] | null;
2348
+ declare function cloneTableCells(cells: TableCell[][]): TableCell[][];
2349
+ declare function cloneTable(t: TableShape): TableShape;
2350
+ /** 覆盖矩阵:covered[r][c]=true 表示该格被某个合并锚点覆盖(不渲染/不命中) */
2351
+ declare function tableCoveredMap(cells: TableCell[][]): boolean[][];
2352
+ /** 先拆掉与目标矩形相交的所有已有合并区域,再合并成一个大格 */
2353
+ declare function tableMerge(cells: TableCell[][], r1: number, c1: number, r2: number, c2: number): TableCell[][];
2354
+ /** 拆分某格所在的合并区域(无合并时原样返回) */
2355
+ declare function tableSplit(cells: TableCell[][], r: number, c: number): TableCell[][];
2356
+ /** 若 (r,c) 属于某个合并区(含被覆盖格),返回该合并区的锚点行列;否则返回自身 */
2357
+ declare function tableAnchorAt(t: TableShape, r: number, c: number): {
2358
+ row: number;
2359
+ col: number;
2360
+ };
2361
+ /**
2362
+ * 把选区扩张到「完整包住所有相交的合并区」(飞书式:选区永远吸附到整块)。
2363
+ * 迭代到稳定,因为扩张后可能又碰到新的合并区。
2364
+ */
2365
+ declare function tableExpandRangeToMerges(t: TableShape, r1: number, c1: number, r2: number, c2: number): {
2366
+ r1: number;
2367
+ c1: number;
2368
+ r2: number;
2369
+ c2: number;
2370
+ };
2371
+ /** 选区里是否含合并区(「拆分单元格」按钮的启用条件) */
2372
+ declare function tableRangeHasMerge(t: TableShape, r1: number, c1: number, r2: number, c2: number): boolean;
2373
+ /** 拆分选区里**所有**合并区(无合并时返回原 cells 引用) */
2374
+ declare function tableSplitRange(t: TableShape, r1: number, c1: number, r2: number, c2: number): TableCell[][];
2375
+ /**
2376
+ * 在 at 行之前插入一个空行;穿过该行的合并区域自动加一行跨度。
2377
+ * 新行的样式与行高沿用相邻行(上邻行;若上邻行是表头行则改用其下一行,
2378
+ * 避免在表头下方插入时把表头样式复制成两行)。
2379
+ */
2380
+ declare function tableInsertRow(cells: TableCell[][], rowHeights: number[], at: number): {
2381
+ cells: TableCell[][];
2382
+ rowHeights: number[];
2383
+ };
2384
+ /**
2385
+ * 在 at 列之前插入一个空列;穿过该列的合并区域自动加一列跨度。
2386
+ * 新列的样式与列宽沿用左邻列(在首列前插入则用原首列)。
2387
+ */
2388
+ declare function tableInsertColumn(cells: TableCell[][], colWidths: number[], at: number): {
2389
+ cells: TableCell[][];
2390
+ colWidths: number[];
2391
+ };
2392
+ /** 删除第 at 行(仅剩一行时返回 null 表示不可删);合并区域自动收缩/换锚点 */
2393
+ declare function tableDeleteRow(cells: TableCell[][], rowHeights: number[], at: number): {
2394
+ cells: TableCell[][];
2395
+ rowHeights: number[];
2396
+ } | null;
2397
+ /** 删除第 at 列(仅剩一列时返回 null 表示不可删);合并区域自动收缩/换锚点 */
2398
+ declare function tableDeleteColumn(cells: TableCell[][], colWidths: number[], at: number): {
2399
+ cells: TableCell[][];
2400
+ colWidths: number[];
2401
+ } | null;
2402
+ /** 表格内某格(含合并跨度)相对表格左上角的矩形 */
2403
+ declare function tableCellLocalRect(t: TableShape, row: number, col: number): {
2404
+ ox: number;
2405
+ oy: number;
2406
+ width: number;
2407
+ height: number;
2408
+ };
2409
+ /**
2410
+ * 表格内命中:返回该点的 (row, col)。
2411
+ * 点落在合并区域的被覆盖格上时映射回锚点;不在表格内返回 null。
2412
+ * lx/ly 为相对表格左上角的坐标。
2413
+ */
2414
+ declare function tableRowAtPoint(t: TableShape, lx: number, ly: number): {
2415
+ row: number;
2416
+ col: number;
2417
+ } | null;
2418
+
2419
+ /**
2420
+ * UML 行文字的唯一来源(画布 / SVG 导出 / 尺寸计算 / 就地基共用)。
2421
+ *
2422
+ * 约定:**预设值是要给的**(新建的类/接口自带 `+ field: string`、`+ method(param): void`),
2423
+ * 但整行就是一段普通文字,用户可以像改 input 一样把它删光(连 `+` 一起删)。
2424
+ * 删空的行在画布上什么都不画(`''`),不会自己长回预设文字。
2425
+ */
2426
+ /** 属性行:`+ name: type`;可见性被删掉就只剩 `name: type`;整行删空返回 '' */
2427
+ declare function umlAttrRowText(a: UmlAttribute): string;
2428
+ /** 方法行:`+ name(params): returnType`;同上 */
2429
+ declare function umlMethodRowText(m: UmlMethod): string;
2430
+
2431
+ export { type ArrowBinding, type ArrowHeadType, type ArrowShape, type ArrowStyle, ArrowTool, Camera, type CameraState, type CircleShape, type ConnectionType, DEFAULT_HIGHLIGHTER_COLOR, type DiamondShape, type DrawShape, type DrawuiTheme, type DrawuiThemeSelection, Editor, type EditorData, type EditorOptions, type EditorState, type EllipseShape, type ExportFormat, type ExportOptions, type FreehandMode, FreehandTool, type GenerationContext, HandTool, type HexagonShape, ICONS, ICON_CATEGORIES, ICON_CATEGORY_INK, ICON_DEFAULT_INK, ICON_DEFAULT_SIZE, ICON_GRID, type IconCategoryId, type IconDef, type IconLayer, type IconShape, LANGUAGES, type Language, type LineShape, LineTool, MINDMAP_COLORS, MINDMAP_FONT_SIZE, MINDMAP_MAX_WIDTH, MINDMAP_MIN_WIDTH, MINDMAP_NODE_HEIGHT, MIN_HANDLE_SEGMENT, MIN_JOG, Mat2d, type MessageKey, type MindMapLayoutResult, type MindMapNode, type MindMapNodePos, type MindMapShape, type OctagonShape, type ParallelogramShape, type PentagonShape, type Rect, type RectangleShape, Renderer, type RoundedRectangleShape, STICKY_FOLD_RATIO, STICKY_NOTE_COLORS, STICKY_NOTE_DEFAULT_COLOR, STICKY_TEXT_SCALE, type SegmentHandle, SelectTool, type Shape, type ShapeStyle, ShapeTool, type ShapeType, type SnapGuide, type StarShape, type StickyNoteShape, TABLE_CELL_PADDING_X, TABLE_CELL_PADDING_Y, TABLE_DEFAULT_COL_WIDTH, TABLE_DEFAULT_ROW_HEIGHT, TABLE_FONT_SIZE, TABLE_HEADER_FILL, TABLE_LINE_HEIGHT_RATIO, TABLE_MAX_AUTO_LINES, TABLE_MIN_COL_WIDTH, TABLE_MIN_ROW_HEIGHT, TABLE_MOVE_HANDLE_GAP, TABLE_MOVE_HANDLE_HEIGHT, TABLE_MOVE_HANDLE_WIDTH, THEME, type TableCell, type TableShape, type TextShape, type ToolType, type TrapezoidShape, type TriangleShape, UML_FONT_SIZE, UML_HEADER_HEIGHT, UML_LINE_HEIGHT, UML_MIN_WIDTH, UML_PADDING_X, UML_PADDING_Y, type UiEvent, type UmlAttribute, type UmlClassShape, type UmlInterfaceShape, type UmlMethod, type UmlVisibility, Vec, type Vec2 as Vec2Type, calcUmlClassSize, cloneMindMapNode, cloneTable, cloneTableCells, collapseRoute, connectorSegmentHandles, countMindMapNodes, createTableCells, downloadBlob, exportToBlob, findMindMapNode, getContentBounds, getIcon, getLanguage, getMindMapNodeDepth, getMindMapParent, getTheme, hasIcon, hexToRgba, hitTableBody, hitTableMoveHandle, iconInk, iconsByCategory, isStubCorner, layoutMindMap, mindMapNodeColor, mindMapNodeSize, offsetConnectorSegment, pickSegmentHandle, pointToSegmentDistance, pruneStubBends, renderSVGString, renderToCanvas, routeToUserPoints, sameRoute, searchIcons, setLanguage, setTheme, shadeColor, subscribeLanguage, subscribeTheme, t, tableAnchorAt, tableCellAtLocalClamped, tableCellLineCount, tableCellLocalRect, tableCellTextStartOffset, tableCoveredMap, tableDeleteColumn, tableDeleteRow, tableExpandRangeToMerges, tableGrowRowHeights, tableInsertColumn, tableInsertRow, tableLocalPoint, tableMerge, tableMoveHandleLocalRect, tablePagePoint, tableRangeHasMerge, tableRangeLocalRect, tableRowAtPoint, tableSplit, tableSplitRange, tableTotalHeight, tableTotalWidth, umlAttrRowText, umlMethodRowText };