canvas-drawing-editor 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -128,16 +128,102 @@ function App() {
128
128
  | `show-import` | boolean | true | Show JSON load |
129
129
  | `show-color` | boolean | true | Show color picker |
130
130
  | `show-minimap` | boolean | true | Show navigation minimap |
131
+ | `initial-data` | string | - | Initial JSON data to render (see format below) |
132
+
133
+ ### 📊 Initial Data
134
+
135
+ You can pass JSON data to initialize the canvas content:
136
+
137
+ ```html
138
+ <canvas-drawing-editor
139
+ initial-data='{"objects":[{"id":"abc123","type":"RECTANGLE","x":100,"y":100,"width":200,"height":150,"color":"#3b82f6","lineWidth":2}]}'
140
+ ></canvas-drawing-editor>
141
+ ```
131
142
 
132
143
  ### 📡 Events
133
144
 
145
+ #### `editor-change` Event
146
+
147
+ Fires when canvas content changes. The event detail contains:
148
+
149
+ | Property | Type | Description |
150
+ |----------|------|-------------|
151
+ | `objects` | Array | All drawing objects (raw data) |
152
+ | `data` | Object | Serializable JSON data for storage/restore |
153
+
134
154
  ```javascript
135
- // Listen for canvas changes
136
155
  document.addEventListener('editor-change', (e) => {
137
156
  console.log('Objects:', e.detail.objects);
157
+
158
+ // Use e.detail.data for saving to database
159
+ const jsonData = JSON.stringify(e.detail.data);
160
+ localStorage.setItem('canvas-data', jsonData);
161
+ // or save to server: fetch('/api/save', { method: 'POST', body: jsonData });
162
+ });
163
+ ```
164
+
165
+ #### Object Types & Properties
166
+
167
+ Each object in `e.detail.objects` has the following base properties:
168
+
169
+ | Property | Type | Description |
170
+ |----------|------|-------------|
171
+ | `id` | string | Unique identifier |
172
+ | `type` | string | Object type: `RECTANGLE`, `CIRCLE`, `PATH`, `TEXT`, `IMAGE` |
173
+ | `x` | number | X coordinate |
174
+ | `y` | number | Y coordinate |
175
+ | `color` | string | Stroke/fill color (hex format, e.g., `#3b82f6`) |
176
+ | `lineWidth` | number | Line width in pixels |
177
+
178
+ **Rectangle** (`type: "RECTANGLE"`):
179
+ | Property | Type | Description |
180
+ |----------|------|-------------|
181
+ | `width` | number | Rectangle width |
182
+ | `height` | number | Rectangle height |
183
+
184
+ **Circle** (`type: "CIRCLE"`):
185
+ | Property | Type | Description |
186
+ |----------|------|-------------|
187
+ | `radius` | number | Circle radius |
188
+
189
+ **Path/Pencil** (`type: "PATH"`):
190
+ | Property | Type | Description |
191
+ |----------|------|-------------|
192
+ | `points` | Array<{x, y}> | Array of point coordinates |
193
+
194
+ **Text** (`type: "TEXT"`):
195
+ | Property | Type | Description |
196
+ |----------|------|-------------|
197
+ | `text` | string | Text content |
198
+ | `fontSize` | number | Font size in pixels |
199
+
200
+ **Image** (`type: "IMAGE"`):
201
+ | Property | Type | Description |
202
+ |----------|------|-------------|
203
+ | `width` | number | Image width |
204
+ | `height` | number | Image height |
205
+ | `dataUrl` | string | Base64 encoded image data |
206
+
207
+ #### Example: Saving and Loading
208
+
209
+ ```javascript
210
+ // Save canvas content (use e.detail.data for clean serializable data)
211
+ document.addEventListener('editor-change', (e) => {
212
+ const jsonData = JSON.stringify(e.detail.data);
213
+ localStorage.setItem('my-canvas', jsonData);
214
+ // or save to database
138
215
  });
139
216
 
140
- // Listen for editor close
217
+ // Load canvas content
218
+ const savedData = localStorage.getItem('my-canvas');
219
+ if (savedData) {
220
+ document.querySelector('canvas-drawing-editor').setAttribute('initial-data', savedData);
221
+ }
222
+ ```
223
+
224
+ #### `editor-close` Event
225
+
226
+ ```javascript
141
227
  document.addEventListener('editor-close', () => {
142
228
  console.log('Editor closed');
143
229
  });
@@ -282,16 +368,102 @@ function App() {
282
368
  | `show-import` | boolean | true | 显示 JSON 加载 |
283
369
  | `show-color` | boolean | true | 显示颜色选择器 |
284
370
  | `show-minimap` | boolean | true | 显示导航小地图 |
371
+ | `initial-data` | string | - | 初始化 JSON 数据(格式见下方) |
372
+
373
+ ### 📊 初始化数据
374
+
375
+ 可以通过 `initial-data` 属性传入 JSON 数据来初始化画布内容:
376
+
377
+ ```html
378
+ <canvas-drawing-editor
379
+ initial-data='{"objects":[{"id":"abc123","type":"RECTANGLE","x":100,"y":100,"width":200,"height":150,"color":"#3b82f6","lineWidth":2}]}'
380
+ ></canvas-drawing-editor>
381
+ ```
285
382
 
286
383
  ### 📡 事件监听
287
384
 
385
+ #### `editor-change` 事件
386
+
387
+ 当画布内容变化时触发。事件详情包含:
388
+
389
+ | 属性 | 类型 | 说明 |
390
+ |------|------|------|
391
+ | `objects` | Array | 所有绑图对象(原始数据) |
392
+ | `data` | Object | 可序列化的 JSON 数据,用于存储/回显 |
393
+
288
394
  ```javascript
289
- // 监听画布内容变化
290
395
  document.addEventListener('editor-change', (e) => {
291
396
  console.log('对象列表:', e.detail.objects);
397
+
398
+ // 使用 e.detail.data 保存到数据库
399
+ const jsonData = JSON.stringify(e.detail.data);
400
+ localStorage.setItem('canvas-data', jsonData);
401
+ // 或保存到服务器: fetch('/api/save', { method: 'POST', body: jsonData });
402
+ });
403
+ ```
404
+
405
+ #### 对象类型和属性说明
406
+
407
+ `e.detail.objects` 中每个对象都有以下基础属性:
408
+
409
+ | 属性 | 类型 | 说明 |
410
+ |------|------|------|
411
+ | `id` | string | 唯一标识符 |
412
+ | `type` | string | 对象类型:`RECTANGLE`、`CIRCLE`、`PATH`、`TEXT`、`IMAGE` |
413
+ | `x` | number | X 坐标 |
414
+ | `y` | number | Y 坐标 |
415
+ | `color` | string | 描边/填充颜色(十六进制格式,如 `#3b82f6`) |
416
+ | `lineWidth` | number | 线条宽度(像素) |
417
+
418
+ **矩形** (`type: "RECTANGLE"`):
419
+ | 属性 | 类型 | 说明 |
420
+ |------|------|------|
421
+ | `width` | number | 矩形宽度 |
422
+ | `height` | number | 矩形高度 |
423
+
424
+ **圆形** (`type: "CIRCLE"`):
425
+ | 属性 | 类型 | 说明 |
426
+ |------|------|------|
427
+ | `radius` | number | 圆形半径 |
428
+
429
+ **画笔路径** (`type: "PATH"`):
430
+ | 属性 | 类型 | 说明 |
431
+ |------|------|------|
432
+ | `points` | Array<{x, y}> | 点坐标数组 |
433
+
434
+ **文本** (`type: "TEXT"`):
435
+ | 属性 | 类型 | 说明 |
436
+ |------|------|------|
437
+ | `text` | string | 文本内容 |
438
+ | `fontSize` | number | 字体大小(像素) |
439
+
440
+ **图片** (`type: "IMAGE"`):
441
+ | 属性 | 类型 | 说明 |
442
+ |------|------|------|
443
+ | `width` | number | 图片宽度 |
444
+ | `height` | number | 图片高度 |
445
+ | `dataUrl` | string | Base64 编码的图片数据 |
446
+
447
+ #### 示例:保存和加载画布
448
+
449
+ ```javascript
450
+ // 保存画布内容(使用 e.detail.data 获取可序列化的数据)
451
+ document.addEventListener('editor-change', (e) => {
452
+ const jsonData = JSON.stringify(e.detail.data);
453
+ localStorage.setItem('my-canvas', jsonData);
454
+ // 或保存到数据库
292
455
  });
293
456
 
294
- // 监听编辑器关闭
457
+ // 加载画布内容
458
+ const savedData = localStorage.getItem('my-canvas');
459
+ if (savedData) {
460
+ document.querySelector('canvas-drawing-editor').setAttribute('initial-data', savedData);
461
+ }
462
+ ```
463
+
464
+ #### `editor-close` 事件
465
+
466
+ ```javascript
295
467
  document.addEventListener('editor-close', () => {
296
468
  console.log('编辑器已关闭');
297
469
  });
@@ -12,7 +12,7 @@ const f = {
12
12
  showColor: !0,
13
13
  showMinimap: !0
14
14
  };
15
- class v extends HTMLElement {
15
+ class b extends HTMLElement {
16
16
  constructor() {
17
17
  super(), this.config = { ...f }, this.objects = [], this.selectedId = null, this.tool = "SELECT", this.color = "#000000", this.lineWidth = 3, this.isDragging = !1, this.dragStart = null, this.currentObject = null, this.dragOffset = { x: 0, y: 0 }, this.isTextInputVisible = !1, this.textInputPos = { x: 0, y: 0 }, this.textInputScreenPos = { x: 0, y: 0 }, this.editingTextId = null, this.isResizing = !1, this.resizeHandle = null, this.resizeStartBounds = null, this.resizeOriginalObject = null, this.history = [], this.clipboard = null, this.scale = 1, this.panOffset = { x: 0, y: 0 }, this.isPanning = !1, this.panStart = { x: 0, y: 0 }, this.shadow = this.attachShadow({ mode: "open" }), this.boundHandleResize = this.handleResize.bind(this), this.boundHandleKeyDown = this.handleKeyDown.bind(this), this.boundHandleWheel = this.handleWheel.bind(this);
18
18
  }
@@ -30,12 +30,13 @@ class v extends HTMLElement {
30
30
  "show-export",
31
31
  "show-import",
32
32
  "show-color",
33
- "show-minimap"
33
+ "show-minimap",
34
+ "initial-data"
34
35
  ];
35
36
  }
36
37
  // 生命周期:连接到 DOM
37
38
  connectedCallback() {
38
- this.parseAttributes(), this.render(), this.setupEventListeners(), this.initCanvas();
39
+ this.parseAttributes(), this.render(), this.setupEventListeners(), this.initCanvas(), this.loadInitialData();
39
40
  }
40
41
  // 生命周期:从 DOM 断开
41
42
  disconnectedCallback() {
@@ -43,7 +44,13 @@ class v extends HTMLElement {
43
44
  }
44
45
  // 生命周期:属性变化
45
46
  attributeChangedCallback(t, i, s) {
46
- i !== s && (this.parseAttributes(), this.container && this.updateUI());
47
+ if (i !== s) {
48
+ if (t === "initial-data" && s && this.canvas) {
49
+ this.loadInitialData();
50
+ return;
51
+ }
52
+ this.parseAttributes(), this.container && this.updateUI();
53
+ }
47
54
  }
48
55
  // 解析 HTML 属性
49
56
  parseAttributes() {
@@ -66,6 +73,24 @@ class v extends HTMLElement {
66
73
  generateId() {
67
74
  return Math.random().toString(36).substr(2, 9);
68
75
  }
76
+ // 加载初始数据
77
+ loadInitialData() {
78
+ const t = this.getAttribute("initial-data");
79
+ if (t)
80
+ try {
81
+ const i = JSON.parse(t);
82
+ i.objects && Array.isArray(i.objects) && (this.objects = i.objects, this.selectedId = null, this.objects.forEach((s) => {
83
+ if (s.type === "IMAGE" && s.dataUrl) {
84
+ const e = new Image();
85
+ e.onload = () => {
86
+ s.imageElement = e, this.renderCanvas(), this.renderMinimap();
87
+ }, e.src = s.dataUrl;
88
+ }
89
+ }), this.renderCanvas(), this.renderMinimap(), this.updateUI());
90
+ } catch (i) {
91
+ console.error("Failed to parse initial-data:", i);
92
+ }
93
+ }
69
94
  // 设置事件监听
70
95
  setupEventListeners() {
71
96
  window.addEventListener("resize", this.boundHandleResize), window.addEventListener("keydown", this.boundHandleKeyDown);
@@ -94,8 +119,8 @@ class v extends HTMLElement {
94
119
  s = t.clientX, e = t.clientY;
95
120
  else
96
121
  return { x: 0, y: 0 };
97
- const n = (s - i.left - this.panOffset.x) / this.scale, a = (e - i.top - this.panOffset.y) / this.scale;
98
- return { x: n, y: a };
122
+ const n = (s - i.left - this.panOffset.x) / this.scale, o = (e - i.top - this.panOffset.y) / this.scale;
123
+ return { x: n, y: o };
99
124
  }
100
125
  // 获取屏幕坐标(不考虑缩放和平移)
101
126
  getScreenPos(t) {
@@ -128,23 +153,23 @@ class v extends HTMLElement {
128
153
  case "PATH": {
129
154
  const i = t;
130
155
  if (i.points.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
131
- const s = Math.min(...i.points.map((o) => o.x)), e = Math.max(...i.points.map((o) => o.x)), n = Math.min(...i.points.map((o) => o.y)), a = Math.max(...i.points.map((o) => o.y));
132
- return { x: s, y: n, width: e - s, height: a - n };
156
+ const s = Math.min(...i.points.map((a) => a.x)), e = Math.max(...i.points.map((a) => a.x)), n = Math.min(...i.points.map((a) => a.y)), o = Math.max(...i.points.map((a) => a.y));
157
+ return { x: s, y: n, width: e - s, height: o - n };
133
158
  }
134
159
  }
135
160
  return { x: 0, y: 0, width: 0, height: 0 };
136
161
  }
137
162
  // 检查调整大小手柄
138
163
  getResizeHandleAtPoint(t, i, s) {
139
- const e = this.getObjectBounds(t), n = 8, a = [
164
+ const e = this.getObjectBounds(t), n = 8, o = [
140
165
  { name: "nw", x: e.x, y: e.y },
141
166
  { name: "ne", x: e.x + e.width, y: e.y },
142
167
  { name: "sw", x: e.x, y: e.y + e.height },
143
168
  { name: "se", x: e.x + e.width, y: e.y + e.height }
144
169
  ];
145
- for (const o of a)
146
- if (Math.abs(i - o.x) <= n && Math.abs(s - o.y) <= n)
147
- return o.name;
170
+ for (const a of o)
171
+ if (Math.abs(i - a.x) <= n && Math.abs(s - a.y) <= n)
172
+ return a.name;
148
173
  return null;
149
174
  }
150
175
  // 碰撞检测
@@ -169,8 +194,8 @@ class v extends HTMLElement {
169
194
  case "PATH": {
170
195
  const e = t;
171
196
  if (e.points.length === 0) return !1;
172
- const n = Math.min(...e.points.map((r) => r.x)), a = Math.max(...e.points.map((r) => r.x)), o = Math.min(...e.points.map((r) => r.y)), l = Math.max(...e.points.map((r) => r.y));
173
- return i >= n && i <= a && s >= o && s <= l;
197
+ const n = Math.min(...e.points.map((r) => r.x)), o = Math.max(...e.points.map((r) => r.x)), a = Math.min(...e.points.map((r) => r.y)), l = Math.max(...e.points.map((r) => r.y));
198
+ return i >= n && i <= o && s >= a && s <= l;
174
199
  }
175
200
  }
176
201
  return !1;
@@ -214,10 +239,23 @@ class v extends HTMLElement {
214
239
  }
215
240
  // 派发变化事件
216
241
  dispatchChangeEvent() {
242
+ const t = {
243
+ objects: this.objects.map((i) => {
244
+ if (i.type === "IMAGE") {
245
+ const { imageElement: s, ...e } = i;
246
+ return e;
247
+ }
248
+ return { ...i };
249
+ })
250
+ };
217
251
  this.dispatchEvent(new CustomEvent("editor-change", {
218
252
  bubbles: !0,
219
253
  composed: !0,
220
- detail: { objects: this.objects }
254
+ detail: {
255
+ objects: this.objects,
256
+ data: t
257
+ // 可直接用于存储/回显的 JSON 数据
258
+ }
221
259
  }));
222
260
  }
223
261
  // 键盘事件处理
@@ -266,13 +304,13 @@ class v extends HTMLElement {
266
304
  // 滚轮缩放
267
305
  handleWheel(t) {
268
306
  t.preventDefault();
269
- const i = this.canvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = t.deltaY > 0 ? 0.9 : 1.1, a = this.scale * n;
270
- this.zoomAtPoint(a, s, e);
307
+ const i = this.canvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = t.deltaY > 0 ? 0.9 : 1.1, o = this.scale * n;
308
+ this.zoomAtPoint(o, s, e);
271
309
  }
272
310
  // 以指定点为中心缩放
273
311
  zoomAtPoint(t, i, s) {
274
- const e = Math.min(Math.max(t, 0.2), 5), n = (i - this.panOffset.x) / this.scale, a = (s - this.panOffset.y) / this.scale, o = i - n * e, l = s - a * e;
275
- this.scale = e, this.panOffset = { x: o, y: l }, this.renderCanvas(), this.renderMinimap(), this.updateZoomDisplay();
312
+ const e = Math.min(Math.max(t, 0.2), 5), n = (i - this.panOffset.x) / this.scale, o = (s - this.panOffset.y) / this.scale, a = i - n * e, l = s - o * e;
313
+ this.scale = e, this.panOffset = { x: a, y: l }, this.renderCanvas(), this.renderMinimap(), this.updateZoomDisplay();
276
314
  }
277
315
  // 放大
278
316
  zoomIn() {
@@ -344,16 +382,16 @@ class v extends HTMLElement {
344
382
  const { x: i, y: s } = this.getMousePos(t), e = this.getScreenPos(t);
345
383
  if (this.dragStart = { x: i, y: s }, this.isDragging = !0, this.isTextInputVisible && this.tool !== "TEXT" && this.submitText(), this.tool === "SELECT") {
346
384
  if (this.selectedId) {
347
- const a = this.objects.find((o) => o.id === this.selectedId);
348
- if (a) {
349
- const o = this.getResizeHandleAtPoint(a, i, s);
350
- if (o) {
351
- this.saveHistory(), this.isResizing = !0, this.resizeHandle = o, this.resizeStartBounds = this.getObjectBounds(a), this.resizeOriginalObject = JSON.parse(JSON.stringify(a));
385
+ const o = this.objects.find((a) => a.id === this.selectedId);
386
+ if (o) {
387
+ const a = this.getResizeHandleAtPoint(o, i, s);
388
+ if (a) {
389
+ this.saveHistory(), this.isResizing = !0, this.resizeHandle = a, this.resizeStartBounds = this.getObjectBounds(o), this.resizeOriginalObject = JSON.parse(JSON.stringify(o));
352
390
  return;
353
391
  }
354
392
  }
355
393
  }
356
- const n = [...this.objects].reverse().find((a) => this.isHit(a, i, s));
394
+ const n = [...this.objects].reverse().find((o) => this.isHit(o, i, s));
357
395
  n ? (this.selectedId = n.id, this.dragOffset = { x: i - n.x, y: s - n.y }, this.saveHistory(), this.updateUI()) : (this.selectedId = null, this.isPanning = !0, this.panStart = e, this.updateUI());
358
396
  } else if (this.tool === "TEXT")
359
397
  this.textInputPos = { x: i, y: s }, this.showTextInput(e.x, e.y), this.isDragging = !1;
@@ -367,8 +405,8 @@ class v extends HTMLElement {
367
405
  // 画布鼠标移动
368
406
  handleCanvasPointerMove(t) {
369
407
  if (this.isPanning) {
370
- const e = this.getScreenPos(t), n = e.x - this.panStart.x, a = e.y - this.panStart.y;
371
- this.panOffset = { x: this.panOffset.x + n, y: this.panOffset.y + a }, this.panStart = e, this.renderCanvas(), this.renderMinimap();
408
+ const e = this.getScreenPos(t), n = e.x - this.panStart.x, o = e.y - this.panStart.y;
409
+ this.panOffset = { x: this.panOffset.x + n, y: this.panOffset.y + o }, this.panStart = e, this.renderCanvas(), this.renderMinimap();
372
410
  return;
373
411
  }
374
412
  if (!this.isDragging || !this.dragStart) return;
@@ -376,27 +414,27 @@ class v extends HTMLElement {
376
414
  if (this.isResizing && this.selectedId && this.resizeHandle && this.resizeStartBounds && this.resizeOriginalObject) {
377
415
  const e = this.objects.find((d) => d.id === this.selectedId);
378
416
  if (!e) return;
379
- const n = i - this.dragStart.x, a = s - this.dragStart.y;
380
- let o = this.resizeStartBounds.x, l = this.resizeStartBounds.y, r = this.resizeStartBounds.width, h = this.resizeStartBounds.height;
381
- switch (this.resizeHandle.includes("e") && (r = this.resizeStartBounds.width + n), this.resizeHandle.includes("w") && (o = this.resizeStartBounds.x + n, r = this.resizeStartBounds.width - n), this.resizeHandle.includes("s") && (h = this.resizeStartBounds.height + a), this.resizeHandle.includes("n") && (l = this.resizeStartBounds.y + a, h = this.resizeStartBounds.height - a), r = Math.max(10, r), h = Math.max(10, h), e.type) {
417
+ const n = i - this.dragStart.x, o = s - this.dragStart.y;
418
+ let a = this.resizeStartBounds.x, l = this.resizeStartBounds.y, r = this.resizeStartBounds.width, h = this.resizeStartBounds.height;
419
+ switch (this.resizeHandle.includes("e") && (r = this.resizeStartBounds.width + n), this.resizeHandle.includes("w") && (a = this.resizeStartBounds.x + n, r = this.resizeStartBounds.width - n), this.resizeHandle.includes("s") && (h = this.resizeStartBounds.height + o), this.resizeHandle.includes("n") && (l = this.resizeStartBounds.y + o, h = this.resizeStartBounds.height - o), r = Math.max(10, r), h = Math.max(10, h), e.type) {
382
420
  case "RECTANGLE":
383
421
  case "IMAGE":
384
- e.x = o, e.y = l, e.width = r, e.height = h;
422
+ e.x = a, e.y = l, e.width = r, e.height = h;
385
423
  break;
386
424
  case "CIRCLE": {
387
425
  const d = Math.max(r, h) / 2;
388
- e.x = o + d, e.y = l + d, e.radius = d;
426
+ e.x = a + d, e.y = l + d, e.radius = d;
389
427
  break;
390
428
  }
391
429
  case "TEXT": {
392
430
  const d = this.resizeOriginalObject, c = r / this.resizeStartBounds.width;
393
- e.x = o, e.y = l + h, e.fontSize = Math.max(8, Math.round(d.fontSize * c));
431
+ e.x = a, e.y = l + h, e.fontSize = Math.max(8, Math.round(d.fontSize * c));
394
432
  break;
395
433
  }
396
434
  case "PATH": {
397
435
  const d = this.resizeOriginalObject, c = r / this.resizeStartBounds.width, p = h / this.resizeStartBounds.height;
398
436
  e.points = d.points.map((u) => ({
399
- x: o + (u.x - this.resizeStartBounds.x) * c,
437
+ x: a + (u.x - this.resizeStartBounds.x) * c,
400
438
  y: l + (u.y - this.resizeStartBounds.y) * p
401
439
  }));
402
440
  break;
@@ -409,8 +447,8 @@ class v extends HTMLElement {
409
447
  const e = this.objects.find((n) => n.id === this.selectedId);
410
448
  if (e) {
411
449
  if (e.type === "PATH") {
412
- const n = e, a = i - this.dragStart.x, o = s - this.dragStart.y;
413
- n.points = n.points.map((l) => ({ x: l.x + a, y: l.y + o })), this.dragStart = { x: i, y: s };
450
+ const n = e, o = i - this.dragStart.x, a = s - this.dragStart.y;
451
+ n.points = n.points.map((l) => ({ x: l.x + o, y: l.y + a })), this.dragStart = { x: i, y: s };
414
452
  } else
415
453
  e.x = i - this.dragOffset.x, e.y = s - this.dragOffset.y;
416
454
  this.renderCanvas(), this.renderMinimap();
@@ -436,8 +474,8 @@ class v extends HTMLElement {
436
474
  if (e && e.type === "TEXT") {
437
475
  const n = e;
438
476
  this.editingTextId = n.id, this.textInputPos = { x: n.x, y: n.y };
439
- const a = n.x * this.scale + this.panOffset.x, o = n.y * this.scale + this.panOffset.y;
440
- this.showTextInput(a, o, n.text), this.setTool("SELECT");
477
+ const o = n.x * this.scale + this.panOffset.x, a = n.y * this.scale + this.panOffset.y;
478
+ this.showTextInput(o, a, n.text), this.setTool("SELECT");
441
479
  }
442
480
  }
443
481
  // 渲染画布
@@ -499,8 +537,8 @@ class v extends HTMLElement {
499
537
  { x: s.x + s.width, y: s.y },
500
538
  { x: s.x, y: s.y + s.height },
501
539
  { x: s.x + s.width, y: s.y + s.height }
502
- ].forEach((a) => {
503
- t.beginPath(), t.rect(a.x - e / 2, a.y - e / 2, e, e), t.fill(), t.stroke();
540
+ ].forEach((o) => {
541
+ t.beginPath(), t.rect(o.x - e / 2, o.y - e / 2, e, e), t.fill(), t.stroke();
504
542
  }), t.strokeStyle = "#3b82f6", t.lineWidth = 1, t.setLineDash([5, 5]), t.strokeRect(s.x, s.y, s.width, s.height), t.setLineDash([]);
505
543
  }
506
544
  // 渲染小地图
@@ -508,8 +546,8 @@ class v extends HTMLElement {
508
546
  if (!this.minimapCtx || !this.config.showMinimap) return;
509
547
  const t = this.minimapCanvas, i = this.canvas;
510
548
  this.minimapCtx.clearRect(0, 0, t.width, t.height);
511
- const s = t.width / i.width, e = t.height / i.height, n = Math.min(s, e) * 0.92, a = i.width * n, o = i.height * n, l = (t.width - a) / 2, r = (t.height - o) / 2;
512
- this.minimapCtx.fillStyle = "#ffffff", this.minimapCtx.fillRect(l, r, a, o), this.minimapCtx.save(), this.minimapCtx.translate(l, r), this.minimapCtx.scale(n, n), this.minimapCtx.translate(this.panOffset.x, this.panOffset.y), this.minimapCtx.scale(this.scale, this.scale), (this.currentObject ? [...this.objects, this.currentObject] : this.objects).forEach((d) => {
549
+ const s = t.width / i.width, e = t.height / i.height, n = Math.min(s, e) * 0.92, o = i.width * n, a = i.height * n, l = (t.width - o) / 2, r = (t.height - a) / 2;
550
+ this.minimapCtx.fillStyle = "#ffffff", this.minimapCtx.fillRect(l, r, o, a), this.minimapCtx.save(), this.minimapCtx.translate(l, r), this.minimapCtx.scale(n, n), this.minimapCtx.translate(this.panOffset.x, this.panOffset.y), this.minimapCtx.scale(this.scale, this.scale), (this.currentObject ? [...this.objects, this.currentObject] : this.objects).forEach((d) => {
513
551
  switch (this.minimapCtx.fillStyle = d.color, this.minimapCtx.strokeStyle = d.color, this.minimapCtx.lineWidth = Math.max(d.lineWidth, 1), this.minimapCtx.setLineDash([]), d.type) {
514
552
  case "RECTANGLE": {
515
553
  const c = d;
@@ -537,11 +575,11 @@ class v extends HTMLElement {
537
575
  break;
538
576
  }
539
577
  }
540
- }), this.minimapCtx.restore(), this.minimapCtx.strokeStyle = "#94a3b8", this.minimapCtx.lineWidth = 1, this.minimapCtx.strokeRect(l, r, a, o);
578
+ }), this.minimapCtx.restore(), this.minimapCtx.strokeStyle = "#94a3b8", this.minimapCtx.lineWidth = 1, this.minimapCtx.strokeRect(l, r, o, a);
541
579
  }
542
580
  // 小地图点击定位
543
581
  handleMinimapClick(t) {
544
- const i = this.minimapCanvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = this.minimapCanvas.width / this.canvas.width, a = this.minimapCanvas.height / this.canvas.height, o = Math.min(n, a) * 0.92, l = this.canvas.width * o, r = this.canvas.height * o, h = (this.minimapCanvas.width - l) / 2, d = (this.minimapCanvas.height - r) / 2, c = s - h, p = e - d, u = (c / o - this.panOffset.x) / this.scale, m = (p / o - this.panOffset.y) / this.scale, g = this.canvas.width / 2, x = this.canvas.height / 2;
582
+ const i = this.minimapCanvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = this.minimapCanvas.width / this.canvas.width, o = this.minimapCanvas.height / this.canvas.height, a = Math.min(n, o) * 0.92, l = this.canvas.width * a, r = this.canvas.height * a, h = (this.minimapCanvas.width - l) / 2, d = (this.minimapCanvas.height - r) / 2, c = s - h, p = e - d, u = (c / a - this.panOffset.x) / this.scale, m = (p / a - this.panOffset.y) / this.scale, g = this.canvas.width / 2, x = this.canvas.height / 2;
545
583
  this.panOffset = {
546
584
  x: g / this.scale - u,
547
585
  y: x / this.scale - m
@@ -554,11 +592,11 @@ class v extends HTMLElement {
554
592
  const s = i.files[0], e = new FileReader();
555
593
  e.onload = (n) => {
556
594
  var l;
557
- const a = (l = n.target) == null ? void 0 : l.result, o = new Image();
558
- o.onload = () => {
595
+ const o = (l = n.target) == null ? void 0 : l.result, a = new Image();
596
+ a.onload = () => {
559
597
  this.saveHistory();
560
598
  const r = 300;
561
- let h = o.width, d = o.height;
599
+ let h = a.width, d = a.height;
562
600
  if (h > r || d > r) {
563
601
  const p = Math.min(r / h, r / d);
564
602
  h *= p, d *= p;
@@ -572,19 +610,19 @@ class v extends HTMLElement {
572
610
  height: d,
573
611
  color: "#000000",
574
612
  lineWidth: 1,
575
- dataUrl: a,
576
- imageElement: o
613
+ dataUrl: o,
614
+ imageElement: a
577
615
  };
578
616
  this.objects.push(c), this.selectedId = c.id, this.setTool("SELECT"), this.renderCanvas(), this.renderMinimap(), this.updateUI(), this.dispatchChangeEvent();
579
- }, o.src = a;
617
+ }, a.src = o;
580
618
  }, e.readAsDataURL(s), i.value = "";
581
619
  }
582
620
  // 保存 JSON
583
621
  saveJson() {
584
622
  const t = {
585
623
  version: "1.0",
586
- objects: this.objects.map((a) => {
587
- const { imageElement: o, ...l } = a;
624
+ objects: this.objects.map((o) => {
625
+ const { imageElement: a, ...l } = o;
588
626
  return l;
589
627
  })
590
628
  }, i = JSON.stringify(t, null, 2), s = new Blob([i], { type: "application/json" }), e = URL.createObjectURL(s), n = document.createElement("a");
@@ -596,10 +634,10 @@ class v extends HTMLElement {
596
634
  if (!i.files || i.files.length === 0) return;
597
635
  const s = i.files[0], e = new FileReader();
598
636
  e.onload = (n) => {
599
- var a;
637
+ var o;
600
638
  try {
601
- const o = JSON.parse((a = n.target) == null ? void 0 : a.result);
602
- o.objects && Array.isArray(o.objects) && (this.saveHistory(), this.objects = o.objects, this.selectedId = null, this.objects.forEach((l) => {
639
+ const a = JSON.parse((o = n.target) == null ? void 0 : o.result);
640
+ a.objects && Array.isArray(a.objects) && (this.saveHistory(), this.objects = a.objects, this.selectedId = null, this.objects.forEach((l) => {
603
641
  if (l.type === "IMAGE" && l.dataUrl) {
604
642
  const r = new Image();
605
643
  r.onload = () => {
@@ -607,8 +645,8 @@ class v extends HTMLElement {
607
645
  }, r.src = l.dataUrl;
608
646
  }
609
647
  }), this.renderCanvas(), this.renderMinimap(), this.updateUI(), this.dispatchChangeEvent());
610
- } catch (o) {
611
- console.error("Failed to load JSON:", o);
648
+ } catch (a) {
649
+ console.error("Failed to load JSON:", a);
612
650
  }
613
651
  }, e.readAsText(s), i.value = "";
614
652
  }
@@ -628,7 +666,7 @@ class v extends HTMLElement {
628
666
  if (this.selectedId) {
629
667
  const e = this.objects.find((n) => n.id === this.selectedId);
630
668
  if (e) {
631
- const a = {
669
+ const o = {
632
670
  RECTANGLE: "矩形",
633
671
  CIRCLE: "圆形",
634
672
  PATH: "画笔",
@@ -636,15 +674,15 @@ class v extends HTMLElement {
636
674
  IMAGE: "图片"
637
675
  }[e.type] || e.type;
638
676
  t.innerHTML = `
639
- <span class="selection-label">已选择: ${a}</span>
677
+ <span class="selection-label">已选择: ${o}</span>
640
678
  <button class="delete-btn" title="删除">
641
679
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
642
680
  <path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
643
681
  </svg>
644
682
  </button>
645
683
  `, t.classList.add("visible");
646
- const o = t.querySelector(".delete-btn");
647
- o && o.addEventListener("click", () => this.deleteSelected());
684
+ const a = t.querySelector(".delete-btn");
685
+ a && a.addEventListener("click", () => this.deleteSelected());
648
686
  }
649
687
  } else
650
688
  t.classList.remove("visible"), t.innerHTML = "";
@@ -762,6 +800,7 @@ class v extends HTMLElement {
762
800
 
763
801
  <!-- 文本输入 -->
764
802
  <div class="text-input-container" style="display: none;">
803
+ <div class="text-input-hint">按 Enter 确认,Esc 取消</div>
765
804
  <input type="text" class="text-input" placeholder="输入文本..." />
766
805
  </div>
767
806
 
@@ -794,10 +833,10 @@ class v extends HTMLElement {
794
833
  });
795
834
  const s = this.shadow.querySelector(".image-input");
796
835
  s && s.addEventListener("change", (h) => this.handleImageUpload(h));
797
- const e = this.shadow.querySelector(".zoom-in-btn"), n = this.shadow.querySelector(".zoom-out-btn"), a = this.shadow.querySelector(".zoom-text");
798
- e && e.addEventListener("click", () => this.zoomIn()), n && n.addEventListener("click", () => this.zoomOut()), a && a.addEventListener("click", () => this.resetZoom());
799
- const o = this.shadow.querySelector(".save-json-btn"), l = this.shadow.querySelector(".load-json-input"), r = this.shadow.querySelector(".export-png-btn");
800
- o && o.addEventListener("click", () => this.saveJson()), l && l.addEventListener("change", (h) => this.loadJson(h)), r && r.addEventListener("click", () => this.exportPng()), this.minimapCanvas && this.minimapCanvas.addEventListener("click", (h) => this.handleMinimapClick(h)), this.textInput && (this.textInput.addEventListener("keydown", (h) => {
836
+ const e = this.shadow.querySelector(".zoom-in-btn"), n = this.shadow.querySelector(".zoom-out-btn"), o = this.shadow.querySelector(".zoom-text");
837
+ e && e.addEventListener("click", () => this.zoomIn()), n && n.addEventListener("click", () => this.zoomOut()), o && o.addEventListener("click", () => this.resetZoom());
838
+ const a = this.shadow.querySelector(".save-json-btn"), l = this.shadow.querySelector(".load-json-input"), r = this.shadow.querySelector(".export-png-btn");
839
+ a && a.addEventListener("click", () => this.saveJson()), l && l.addEventListener("change", (h) => this.loadJson(h)), r && r.addEventListener("click", () => this.exportPng()), this.minimapCanvas && this.minimapCanvas.addEventListener("click", (h) => this.handleMinimapClick(h)), this.textInput && (this.textInput.addEventListener("keydown", (h) => {
801
840
  h.key === "Enter" ? (h.preventDefault(), this.submitText()) : h.key === "Escape" && this.hideTextInput();
802
841
  }), this.textInput.addEventListener("blur", () => {
803
842
  this.isTextInputVisible && this.submitText();
@@ -1108,6 +1147,19 @@ class v extends HTMLElement {
1108
1147
  .text-input-container {
1109
1148
  position: absolute;
1110
1149
  z-index: 20;
1150
+ display: flex;
1151
+ flex-direction: column;
1152
+ align-items: flex-start;
1153
+ }
1154
+
1155
+ .text-input-hint {
1156
+ background: rgba(0, 0, 0, 0.75);
1157
+ color: #fff;
1158
+ font-size: 12px;
1159
+ padding: 4px 8px;
1160
+ border-radius: 4px;
1161
+ margin-bottom: 4px;
1162
+ white-space: nowrap;
1111
1163
  }
1112
1164
 
1113
1165
  .text-input {
@@ -1145,8 +1197,8 @@ class v extends HTMLElement {
1145
1197
  `;
1146
1198
  }
1147
1199
  }
1148
- typeof window < "u" && !customElements.get("canvas-drawing-editor") && customElements.define("canvas-drawing-editor", v);
1200
+ typeof window < "u" && !customElements.get("canvas-drawing-editor") && customElements.define("canvas-drawing-editor", b);
1149
1201
  export {
1150
- v as CanvasDrawingEditor
1202
+ b as CanvasDrawingEditor
1151
1203
  };
1152
1204
  //# sourceMappingURL=canvas-drawing-editor.es.js.map