drawui-core 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts 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' | 'sticky-note' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie' | 'uml-class' | 'uml-interface' | 'mind-map' | 'table' | 'icon' | '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' | 'image';
22
22
  interface BaseShape {
23
23
  id: string;
24
24
  type: ShapeType;
@@ -167,6 +167,23 @@ interface IconShape extends BaseShape {
167
167
  * 尺寸模型与矩形完全一致(以 (x,y) 为中心 + width/height),差异只在轮廓,
168
168
  * 轮廓定义见 `shapePaths.ts`,canvas 与 SVG 导出共用。
169
169
  */
170
+ /**
171
+ * 图片图形:`src` 是图片地址 —— 可以是第三方上传接口返回的远端 URL,
172
+ * 也可以是本地上传的临时地址(`blob:`/`data:`,此时 `temporary: true`,刷新即失效)。
173
+ */
174
+ interface ImageShape extends BaseShape {
175
+ type: 'image';
176
+ width: number;
177
+ height: number;
178
+ src: string;
179
+ /** src 是本地临时地址(blob:/data:),没有配置上传接口时的兜底,刷新后失效 */
180
+ temporary?: boolean;
181
+ /** 原始像素宽高:用于等比缩放与「恢复到原始比例」 */
182
+ naturalWidth?: number;
183
+ naturalHeight?: number;
184
+ /** 圆角半径(世界单位) */
185
+ borderRadius?: number;
186
+ }
170
187
  interface PathShape extends BaseShape {
171
188
  type: 'block-arrow-double' | 'right-triangle' | 'cross' | 'speech-rect' | 'speech-round' | 'cube' | 'sector' | 'delay' | 'predefined-process' | 'document' | 'class-catalog' | 'chevron' | 'brace-left' | 'brace-right' | 'envelope' | 'checkbox' | 'triangular-pyramid' | 'quadrangular-pyramid' | 'cuboid' | 'sphere' | 'ellipsoid' | 'pie' | 'hollow-pie';
172
189
  width: number;
@@ -373,8 +390,8 @@ interface TableShape extends BaseShape {
373
390
  /** cells[row][col] */
374
391
  cells: TableCell[][];
375
392
  }
376
- 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;
377
- 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';
393
+ type Shape = RectangleShape | RoundedRectangleShape | CircleShape | EllipseShape | DiamondShape | TriangleShape | HexagonShape | ParallelogramShape | TrapezoidShape | PentagonShape | OctagonShape | StarShape | CylinderShape | CloudShape | HeartShape | BlockArrowShape | StadiumShape | StickyNoteShape | IconShape | ImageShape | PathShape | UmlClassShape | UmlInterfaceShape | LineShape | ArrowShape | TextShape | DrawShape | MindMapShape | TableShape;
394
+ 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' | 'image';
378
395
  interface CameraState {
379
396
  x: number;
380
397
  y: number;
@@ -392,6 +409,53 @@ interface EditorData {
392
409
  camera: CameraState;
393
410
  }
394
411
 
412
+ /**
413
+ * 图片上传通道。
414
+ *
415
+ * 两种模式(由接入方决定):
416
+ * A. 配了上传接口 `ImageUploadConfig`(url 必填)→ 由**接入方自己的服务器**接收文件,
417
+ * 返回图片地址,我们拿到地址后只存 URL(刷新不丢)。
418
+ * B. 没配 → 退回**本地临时二进制流**(`URL.createObjectURL(file)`,标记 temporary),
419
+ * 图片只在当前页面内存里有效,刷新即失效。
420
+ *
421
+ * 也支持直接给一个函数(`ImageUploader`):需要签名、分片、转存等自定义逻辑时用它,
422
+ * 优先级高于 URL 配置。
423
+ */
424
+
425
+ /** 上传通道内置的图片体积上限:30 MB。超过此体积禁止上传,由接入方通过 `maxSizeBytes` 覆盖。 */
426
+ declare const DEFAULT_IMAGE_MAX_SIZE_BYTES: number;
427
+ /** 上传接口配置(走内置 fetch 实现) */
428
+ interface ImageUploadConfig {
429
+ /** 上传地址,例如 https://api.example.com/upload */
430
+ url: string;
431
+ /** 请求方法,默认 POST */
432
+ method?: 'POST' | 'PUT';
433
+ /** 文件字段名,默认 'file' */
434
+ fieldName?: string;
435
+ /** 额外请求头(如 Authorization) */
436
+ headers?: Record<string, string>;
437
+ /** 额外表单字段 */
438
+ fields?: Record<string, string>;
439
+ /** 是否带 cookie,默认 'same-origin' */
440
+ withCredentials?: boolean;
441
+ /** 从响应 JSON 里取 url 的路径,如 'data.url';不填则自动探测 */
442
+ responseUrlPath?: string;
443
+ /** 超时(毫秒),默认 60000 */
444
+ timeoutMs?: number;
445
+ /** 单张图片体积上限(字节)。超过此大小直接抛出 `image.tooLarge`,不会真的发请求。默认 30 MB。 */
446
+ maxSizeBytes?: number;
447
+ }
448
+ /** 自定义上传函数:返回图片可访问的 URL */
449
+ type ImageUploader = (file: File) => Promise<string>;
450
+ type ImageUploadOption = ImageUploadConfig | ImageUploader;
451
+ declare function isUploaderFunction(v: unknown): v is ImageUploader;
452
+ /** 从响应体文本里解析出图片地址(导出给接入方复用时也用得上) */
453
+ declare function parseUploadResponse(body: string, responseUrlPath?: string): string;
454
+ /** 走配置里的 URL 上传文件,返回图片地址 */
455
+ declare function uploadImageViaConfig(file: File, cfg: ImageUploadConfig): Promise<string>;
456
+ /** 从一批图形里找出「本地临时图片」的地址(刷新后会失效,界面可据此提示) */
457
+ declare function collectTemporaryImageSrcs(shapes: Shape[]): string[];
458
+
395
459
  interface Mat2d {
396
460
  a: number;
397
461
  b: number;
@@ -706,16 +770,23 @@ declare const zhCN: {
706
770
  readonly 'ctx.sendToBack': "置于底层";
707
771
  readonly 'ctx.copyStyle': "复制样式";
708
772
  readonly 'ctx.pasteStyle': "粘贴样式";
709
- readonly 'ctx.flipH': "水平翻转";
710
- readonly 'ctx.flipV': "垂直翻转";
773
+ readonly 'ctx.rotate90': "90°";
774
+ readonly 'ctx.rotate180': "180°";
775
+ readonly 'ctx.rotate270': "270°";
711
776
  readonly 'ctx.rotate': "旋转";
712
777
  readonly 'ctx.group': "成组";
713
778
  readonly 'ctx.ungroup': "解组";
714
779
  readonly 'ctx.lock': "锁定";
715
780
  readonly 'ctx.unlock': "解锁";
716
- readonly 'ctx.unlockGroup': "解锁本组";
717
781
  readonly 'ctx.delete': "删除";
718
782
  readonly 'toolbar.icon': "图标";
783
+ readonly 'toolbar.image': "图片";
784
+ readonly 'tool.image': "图片";
785
+ readonly 'image.uploadFailed': "图片上传失败";
786
+ readonly 'image.insertFailed': "图片插入失败";
787
+ readonly 'image.tooLarge': "图片超过 30 MB,无法上传";
788
+ readonly 'image.temporaryInserted': "已插入临时图片(未配置上传接口,刷新后会丢失)";
789
+ readonly 'image.naturalSize': "原图尺寸";
719
790
  readonly 'icon.search': "搜索图标";
720
791
  readonly 'icon.empty': "没有匹配的图标";
721
792
  readonly 'icon.recent': "最近使用";
@@ -775,7 +846,7 @@ declare function exportToBlob(shapes: Shape[], format: ExportFormat, opts?: Expo
775
846
  /** 浏览器端触发下载 */
776
847
  declare function downloadBlob(blob: Blob, fileName: string): void;
777
848
 
778
- type Listener = () => void;
849
+ type Listener$1 = () => void;
779
850
  type NotificationListener = (message: string) => void;
780
851
  /** 核心 → React 的 UI 指令事件 */
781
852
  type UiEvent = {
@@ -785,6 +856,11 @@ type UiEvent = {
785
856
  context: GenerationContext;
786
857
  existingArrowId?: string;
787
858
  defaultShapeType: ShapeType;
859
+ }
860
+ /** 图片上传/加载失败(接入方可据此弹 toast) */
861
+ | {
862
+ kind: 'imageError';
863
+ message: string;
788
864
  };
789
865
  /** Editor 构造选项:主题色 / 语言 / 初始数据 / 本地持久化开关 */
790
866
  interface EditorOptions {
@@ -796,6 +872,14 @@ interface EditorOptions {
796
872
  shapes?: Shape[];
797
873
  /** 是否启用 localStorage 持久化(默认 true;提供 shapes 时自动关闭) */
798
874
  persist?: boolean;
875
+ /**
876
+ * 图片上传通道(「图片」按钮 / 粘贴 / 拖入文件时用):
877
+ * - 传 `{ url }` 配置:文件会 POST 到接入方自己的服务器,服务器返回图片地址 → 只存 URL,刷新不丢
878
+ * - 传函数:完全自定义上传(签名、分片、转存等)
879
+ * - 不传:退回**本地临时二进制流**(`URL.createObjectURL`),图形带 `temporary: true`,
880
+ * 图片只活在这个页面里,刷新即失效
881
+ */
882
+ imageUpload?: ImageUploadOption;
799
883
  }
800
884
  declare class Editor {
801
885
  private canvas;
@@ -848,6 +932,16 @@ declare class Editor {
848
932
  * 同样「只影响之后创建的便签」;改选中便签的颜色走 updateSelectedShapesStyle。
849
933
  */
850
934
  private stickyNoteColor;
935
+ /** 图片上传通道(null = 本地临时 blob 兜底) */
936
+ private imageUploadOption;
937
+ /** 图片加载完成 → 重绘的取消订阅函数 */
938
+ private removeImageReadyListener;
939
+ /** 本编辑器创建的临时图片地址(blob:),图形删掉后释放 */
940
+ private ownImageUrls;
941
+ /** 插入图片的错开步进,避免多张图完全重叠 */
942
+ private imageInsertSeq;
943
+ /** Ctrl+V 时若系统剪贴板里是图片,paste 事件已处理 → 跳过一次内部粘贴 */
944
+ private imagePasteHandled;
851
945
  /** 连续插入图标时的错开计数(避免每次都叠在同一处) */
852
946
  private iconInsertSeq;
853
947
  private clipboardShapes;
@@ -1027,7 +1121,12 @@ declare class Editor {
1027
1121
  * 文字/连线/表格/UML/思维导图/便签不支持翻转(isFlippableShapeType),会被跳过。
1028
1122
  */
1029
1123
  flipSelected(axis: 'horizontal' | 'vertical'): boolean;
1030
- /** 选中图形旋转 90°(clockwise=false 为逆时针),一步撤销 */
1124
+ /**
1125
+ * 按角度旋转选中图形(**顺时针为正**,单位:度)。每个图形绕自身中心转,
1126
+ * 与拖拽旋转手柄一致;整体一步撤销。锁定图形不参与。
1127
+ */
1128
+ rotateSelected(degrees: number): boolean;
1129
+ /** 选中图形旋转 90°(clockwise=false 为逆时针);保留旧签名,内部走 rotateSelected */
1031
1130
  rotateSelected90(clockwise?: boolean): boolean;
1032
1131
  /** 锁定/解锁选中图形;锁定后自动取消选中(锁定图形不可选中) */
1033
1132
  setSelectedShapesLocked(locked: boolean): boolean;
@@ -1064,6 +1163,45 @@ declare class Editor {
1064
1163
  * - 计入撤销历史;图标 id 不存在时返回 undefined(不做任何事)。
1065
1164
  */
1066
1165
  insertIcon(iconId: string, size?: number): string | undefined;
1166
+ /** 运行时配置图片上传通道(null = 退回本地临时 blob)。常见于接入方拿到用户配置后再注入 */
1167
+ setImageUpload(option: ImageUploadOption | null): void;
1168
+ getImageUpload(): ImageUploadOption | null;
1169
+ /** 是否配置了上传通道:false 表示插入的图片是本地临时 blob,刷新即失效 */
1170
+ hasImageUploader(): boolean;
1171
+ /** 画布上所有「本地临时图片」的地址(接入方保存前可据此提示「刷新会丢」) */
1172
+ getTemporaryImageSrcs(): string[];
1173
+ hasTemporaryImages(): boolean;
1174
+ /**
1175
+ * 插入一张图片图形(同步)。尺寸按原始像素等比缩放到长边不超过 `IMAGE_MAX_SIDE`;
1176
+ * 拿不到原始尺寸时用 320×240 兜底。返回新图形 id。
1177
+ */
1178
+ insertImageShape(opts: {
1179
+ src: string;
1180
+ naturalWidth?: number;
1181
+ naturalHeight?: number;
1182
+ temporary?: boolean;
1183
+ at?: Vec2;
1184
+ }): string;
1185
+ /**
1186
+ * 上传(或本地兜底)后插入一张图片:
1187
+ * - 配了上传通道 → 调接入方接口拿远端 URL(`temporary` 不置位,刷新不丢)
1188
+ * - 没配 → `URL.createObjectURL(file)` 本地临时地址(`temporary: true`,刷新即失效)
1189
+ * 上传失败会抛出(错误信息可直接展示给用户);返回新图形 id。
1190
+ *
1191
+ * 单张图片超过 30 MB 会被直接拒绝;`ImageUploadConfig` 可通过 `maxSizeBytes` 自定义上限。
1192
+ */
1193
+ insertImageFile(file: File, at?: Vec2): Promise<string>;
1194
+ /** 批量插入(工具栏多选 / 拖入多个文件 / 粘贴多张图) */
1195
+ insertImageFiles(files: FileList | File[], at?: Vec2): Promise<string[]>;
1196
+ /** 直接用图片地址插入(已有素材库 / 贴图片链接) */
1197
+ insertImageFromUrl(src: string, at?: Vec2): Promise<string>;
1198
+ /** 释放已经没有任何图形引用的临时图片地址(删除/清空/替换数据后调用) */
1199
+ private releaseUnusedImageUrls;
1200
+ /**
1201
+ * 系统剪贴板粘贴:剪贴板里有图片就直接插图(优先级高于内部图形剪贴板)。
1202
+ * 截图后 Ctrl+V 贴进画布走的就是这里。
1203
+ */
1204
+ private handlePasteEvent;
1067
1205
  /** 视口中心(世界坐标):插入类操作(图标)的默认落点 */
1068
1206
  private viewportCenter;
1069
1207
  /** 修改「新建便签」的纸面色:只影响之后创建的便签;非法色值会被忽略 */
@@ -1309,6 +1447,14 @@ declare class Editor {
1309
1447
  width: number;
1310
1448
  height: number;
1311
1449
  };
1450
+ /** 当前可视区对应的世界坐标范围(用于画板范围、小地图等) */
1451
+ getViewportBounds(): Rect;
1452
+ /**
1453
+ * 画板「应覆盖的范围」(世界坐标):内容包围盒 ∪ 当前可视区,再各加 padding。
1454
+ * 一次性的纯计算,不做任何状态保留;宿主若要「删了图形后延迟缩回」的语义,
1455
+ * 用 `BoardExtentKeeper`(见 core 的 boardExtent.ts)包一层。
1456
+ */
1457
+ getBoardExtent(padding?: number): Rect;
1312
1458
  /**
1313
1459
  * 导出当前画布为图片 Blob。
1314
1460
  * @param format png | svg | pdf
@@ -1339,7 +1485,7 @@ declare class Editor {
1339
1485
  render(): void;
1340
1486
  /** 合帧渲染:同一帧内多次调用只会触发一次实际重绘 */
1341
1487
  private scheduleRender;
1342
- subscribe(listener: Listener): () => boolean;
1488
+ subscribe(listener: Listener$1): () => boolean;
1343
1489
  subscribeNotifications(listener: NotificationListener): () => boolean;
1344
1490
  private notify;
1345
1491
  private notifyListeners;
@@ -1570,6 +1716,12 @@ declare class Renderer {
1570
1716
  * - 线宽用「网格单位」而不是屏幕单位,所以图标放大时线条跟着变粗,像字体图标那样;
1571
1717
  * - 用 `Path2D` 直接吃 SVG path 字符串,与 SVG 导出共用同一份路径数据(含圆弧,无需自己解析)。
1572
1718
  */
1719
+ /**
1720
+ * 图片:命中缓存就画,没命中先画占位块(斜线 + 灰底/失败时红底)。
1721
+ * `peekImage()` 会顺手启动异步加载,加载完成后 imageStore 通知 Editor 重绘,
1722
+ * 所以这里不需要任何 await —— 画布永远是同步画一帧。
1723
+ */
1724
+ private drawImageShape;
1573
1725
  private drawIcon;
1574
1726
  private drawUmlClass;
1575
1727
  /**
@@ -1775,6 +1927,14 @@ declare class Renderer {
1775
1927
  * 独立于选中态 —— 锁定的图形通常选不中,得靠这枚锁告诉用户「它为什么点不动」。
1776
1928
  */
1777
1929
  private drawLockBadge;
1930
+ /**
1931
+ * 锁定组的统一标记:整组只画**一把锁**,挂在组包围盒顶部居中,
1932
+ * 而不是组里每个成员各挂一把(一堆锁没法看)。只要组处于锁定状态就画,
1933
+ * 与选中无关(锁定的组本来也选不中,这枚锁就是唯一的视觉提示)。
1934
+ */
1935
+ private drawGroupLockBadges;
1936
+ /** 在指定位置(徽标左上角,世界坐标)画一枚挂锁徽标 */
1937
+ private drawLockBadgeAt;
1778
1938
  /**
1779
1939
  * 组外框:选中集恰好是一个完整的组(且不止一个成员)时,给整组描一圈虚线,
1780
1940
  * 提示「这些图形是绑在一起的」。整组锁定的成员画不到(成员不可选),不需要外框。
@@ -1828,8 +1988,12 @@ declare const STICKY_NOTE_DEFAULT_COLOR: string;
1828
1988
  */
1829
1989
  declare const STICKY_FOLD_RATIO = 0.2;
1830
1990
  /**
1831
- * 便签文字的可用区域缩放:常规内边距 0.85 再让掉半个折角,
1832
- * 保证文字块右下角落在折角斜边之内(不再是 0.85,否则会压到折角上)。
1991
+ * 便签文字的可用区域缩放:常规内边距 0.85 再让掉半个折角 = 0.75。
1992
+ *
1993
+ * 为什么这样就够、不需要再把文字整体往左上挪:文字块是**居中**摆放的,
1994
+ * 可用区 0.75 倍时文字块右下角最多落在 (0.875w, 0.875h);而折角三角区是
1995
+ * `Δx/0.2w + Δy/0.2h > 1` 的那一块,把 0.875 代进去是 0.375+0.375 = 0.75 < 1,
1996
+ * **已经在折角斜边之外**。所以居中排版天然不会压到折角。
1833
1997
  */
1834
1998
  declare const STICKY_TEXT_SCALE: number;
1835
1999
 
@@ -1914,6 +2078,76 @@ declare const ICON_CATEGORY_INK: Record<IconCategoryId, string>;
1914
2078
  /** 取某个图标的默认墨色:图标自带 ink 优先(全彩贴纸),否则用分类主色,最后退回全局默认 */
1915
2079
  declare function iconInk(icon: IconDef): string;
1916
2080
 
2081
+ /**
2082
+ * 画板范围(Board Extent)—— 宿主用来决定「画板这块 DOM / 可滚区域该多大」。
2083
+ *
2084
+ * 语义(与产品约定一致):
2085
+ * - **长出去就立刻长**:内容跑到更远处时,画板范围马上跟着扩(这合理,不需要等待)。
2086
+ * - **缩回来要谨慎**:内容被删掉后空出来的那块,不能马上收 ——
2087
+ * · 鼠标没动过 → 先不变(否则指针底下的东西会突然跳/滚动位置变了)
2088
+ * · 鼠标动过、但要让出的那块还在可视区里 → 还是不变(用户正看着它)
2089
+ * · 鼠标动过、且要让出的那块已经完全在可视区之外 → 这时才把这块空间收回
2090
+ *
2091
+ * 本文件不碰 DOM,纯几何 + 状态机,宿主(React / 业务容器)拿到结果自己去设尺寸。
2092
+ */
2093
+
2094
+ declare function isEmptyRect(r: Rect | null | undefined): boolean;
2095
+ /** 两个矩形求并(任一为空则返回另一个的副本) */
2096
+ declare function unionRect(a: Rect | null, b: Rect | null): Rect | null;
2097
+ /** 两个矩形是否相交(边界接触不算相交) */
2098
+ declare function rectsIntersect(a: Rect | null, b: Rect | null): boolean;
2099
+ /** 向内收缩 r,得到「target 之外、r 之内的部分」是否有内容(用于判断「要让出的那块」是否与可视区重叠) */
2100
+ declare function subtractUnion(a: Rect, b: Rect): Rect[];
2101
+ interface BoardExtentUpdate {
2102
+ /** 内容包围盒(世界坐标);没有内容传 null */
2103
+ content: Rect | null;
2104
+ /** 当前可视区(世界坐标) */
2105
+ viewport: Rect;
2106
+ /** 自上次 update 以来指针是否移动过(宿主提供;决定能不能缩回) */
2107
+ pointerMoved: boolean;
2108
+ /** 内容外的留白 */
2109
+ padding?: number;
2110
+ /**
2111
+ * 画板范围是否必须覆盖当前可视区:
2112
+ * - true(默认):相机式画布 —— 画布必须盖住整屏,否则滚动出画面
2113
+ * - false:可滚动画板 —— 范围只跟内容走,空出来的远处空间才有机会被「收回」
2114
+ */
2115
+ includeViewport?: boolean;
2116
+ /** 内容为空时,画板至少保留这么大(默认 = 一个视口大小) */
2117
+ minSize?: {
2118
+ width: number;
2119
+ height: number;
2120
+ };
2121
+ /** 内容为空时画板回到的位置(默认世界原点)——可滚动画板的「默认画板位置」 */
2122
+ anchor?: {
2123
+ x: number;
2124
+ y: number;
2125
+ };
2126
+ }
2127
+ interface BoardExtentResult {
2128
+ /** 本次应该采用的画板范围 */
2129
+ extent: Rect;
2130
+ /** 相比上次是否有变化(宿主据此决定要不要改 DOM 尺寸 / 滚动位置) */
2131
+ changed: boolean;
2132
+ /** 本次是「长大」还是「缩回」还是没变 */
2133
+ action: 'grow' | 'shrink' | 'hold' | 'init';
2134
+ /** hold 时说明原因(便于调试 / 埋点) */
2135
+ holdReason?: 'pointer-still' | 'still-visible';
2136
+ }
2137
+ /**
2138
+ * 画板范围状态机:调用 `update()` 传入当前内容/可视区/指针状态,拿回应该采用的范围。
2139
+ * 一次会话里保持同一个实例即可。
2140
+ */
2141
+ declare class BoardExtentKeeper {
2142
+ private current;
2143
+ /** 重置(例如加载了新文档 / 宿主容器尺寸变了) */
2144
+ reset(extent?: Rect | null): void;
2145
+ get(): Rect | null;
2146
+ /** 本次「理想范围」:内容 + 留白;内容为空时退化成「默认画板」那一块 */
2147
+ private desiredOf;
2148
+ update(input: BoardExtentUpdate): BoardExtentResult;
2149
+ }
2150
+
1917
2151
  interface ToolContext {
1918
2152
  addShape(shape: Shape): void;
1919
2153
  updateShape(id: string, updates: Partial<Shape>): void;
@@ -2564,4 +2798,45 @@ declare function umlAttrRowText(a: UmlAttribute): string;
2564
2798
  /** 方法行:`+ name(params): returnType`;同上 */
2565
2799
  declare function umlMethodRowText(m: UmlMethod): string;
2566
2800
 
2567
- 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, isFlippableShapeType, 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 };
2801
+ /**
2802
+ * 图片资源仓库:`src` → 已加载的 HTMLImageElement 缓存 + 异步加载 + 就绪回调。
2803
+ *
2804
+ * 设计要点(都是踩过的坑):
2805
+ * - Renderer 是**同步一次性绘制**的,所以绘制时只做 `peekImage()`:命中就画,没命中先画占位;
2806
+ * 加载完成后通过监听者通知(Editor 会把它接到 requestRender 上)自动重绘补齐。
2807
+ * - 远端 http(s) 图片默认加 `crossOrigin='anonymous'`:不加的话画布会被污染(tainted),
2808
+ * 之后 `toDataURL()`/导出会直接抛错。
2809
+ * - 加载失败要**记住失败**,否则每帧都会重新 new Image 去打一个坏地址(疯狂重试)。
2810
+ * - 本地兜底的 `blob:` 地址由我们创建,删除图形时通过 `releaseObjectUrl()` 释放。
2811
+ */
2812
+
2813
+ interface LoadedImage {
2814
+ img: HTMLImageElement;
2815
+ width: number;
2816
+ height: number;
2817
+ src: string;
2818
+ }
2819
+ type Listener = () => void;
2820
+ /** 图片加载完成/失败时通知(Editor 接到 requestRender)。返回取消订阅函数 */
2821
+ declare function addImageReadyListener(cb: Listener): () => void;
2822
+ /** 配置远端图的 crossOrigin(传 null 表示不加) */
2823
+ declare function setImageCrossOrigin(value: string | null): void;
2824
+ /** 同步取已加载的图片(未加载/失败返回 null,并顺带触发加载) */
2825
+ declare function peekImage(src: string): LoadedImage | null;
2826
+ /** 该地址是否已确认加载失败 */
2827
+ declare function isImageFailed(src: string): boolean;
2828
+ /** 该地址是否正在加载 */
2829
+ declare function isImageLoading(src: string): boolean;
2830
+ /** 加载图片(命中缓存直接返回)。失败返回 null,并记录失败状态 */
2831
+ declare function loadImage(src: string): Promise<LoadedImage | null>;
2832
+ /** 让一批图形里的图片全部就绪(导出前调用,否则导出的图会是空的) */
2833
+ declare function ensureImagesLoaded(shapes: Shape[]): Promise<void>;
2834
+ /**
2835
+ * 取图片的 dataURL(导出 SVG 内嵌用):
2836
+ * 已加载的图片画到离屏 canvas 再 toDataURL;跨域被拒等失败时返回 null(调用方退回原始地址)。
2837
+ */
2838
+ declare function imageDataUrl(src: string): string | null;
2839
+ /** 清空缓存(测试/卸载时用;会释放自己创建的 blob 地址) */
2840
+ declare function clearImageCache(): void;
2841
+
2842
+ export { type ArrowBinding, type ArrowHeadType, type ArrowShape, type ArrowStyle, ArrowTool, BoardExtentKeeper, type BoardExtentResult, type BoardExtentUpdate, Camera, type CameraState, type CircleShape, type ConnectionType, DEFAULT_HIGHLIGHTER_COLOR, DEFAULT_IMAGE_MAX_SIZE_BYTES, 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, type ImageShape, type ImageUploadConfig, type ImageUploadOption, type ImageUploader, 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, addImageReadyListener, calcUmlClassSize, clearImageCache, cloneMindMapNode, cloneTable, cloneTableCells, collapseRoute, collectTemporaryImageSrcs, connectorSegmentHandles, countMindMapNodes, createTableCells, downloadBlob, ensureImagesLoaded, exportToBlob, findMindMapNode, getContentBounds, getIcon, getLanguage, getMindMapNodeDepth, getMindMapParent, getTheme, hasIcon, hexToRgba, hitTableBody, hitTableMoveHandle, iconInk, iconsByCategory, imageDataUrl, isEmptyRect, isFlippableShapeType, isImageFailed, isImageLoading, isStubCorner, isUploaderFunction, layoutMindMap, loadImage, mindMapNodeColor, mindMapNodeSize, offsetConnectorSegment, parseUploadResponse, peekImage, pickSegmentHandle, pointToSegmentDistance, pruneStubBends, rectsIntersect, renderSVGString, renderToCanvas, routeToUserPoints, sameRoute, searchIcons, setImageCrossOrigin, setLanguage, setTheme, shadeColor, subscribeLanguage, subscribeTheme, subtractUnion, 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, unionRect, uploadImageViaConfig };