@anu3ev/fabric-image-editor 0.1.58 → 0.1.60

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.
Files changed (2) hide show
  1. package/dist/main.js +100 -70
  2. package/package.json +9 -2
package/dist/main.js CHANGED
@@ -143,7 +143,7 @@ class z {
143
143
  */
144
144
  handleCopyEvent(e) {
145
145
  const { ctrlKey: t, metaKey: s, code: n } = e;
146
- !t && !s || n !== "KeyC" || (e.preventDefault(), this.editor.clipboardManager.copy());
146
+ this._shouldIgnoreKeyboardEvent(e) || !t && !s || n !== "KeyC" || (e.preventDefault(), this.editor.clipboardManager.copy());
147
147
  }
148
148
  /**
149
149
  * Обработчик вставки объекта или изображения из буфера обмена.
@@ -162,7 +162,7 @@ class z {
162
162
  handleUndoRedoEvent(e) {
163
163
  return I(this, null, function* () {
164
164
  const { ctrlKey: t, metaKey: s, code: n, repeat: i } = e;
165
- !t && !s || i || !/Mac/i.test(navigator.userAgent) && this.isUndoRedoKeyPressed || (n === "KeyZ" ? (e.preventDefault(), this.isUndoRedoKeyPressed = !0, yield this.editor.historyManager.undo()) : n === "KeyY" && (e.preventDefault(), this.isUndoRedoKeyPressed = !0, yield this.editor.historyManager.redo()));
165
+ this._shouldIgnoreKeyboardEvent(e) || !t && !s || i || !/Mac/i.test(navigator.userAgent) && this.isUndoRedoKeyPressed || (n === "KeyZ" ? (e.preventDefault(), this.isUndoRedoKeyPressed = !0, yield this.editor.historyManager.undo()) : n === "KeyY" && (e.preventDefault(), this.isUndoRedoKeyPressed = !0, yield this.editor.historyManager.redo()));
166
166
  });
167
167
  }
168
168
  /**
@@ -170,8 +170,8 @@ class z {
170
170
  * @param event — объект события
171
171
  * @param event.code — код клавиши
172
172
  */
173
- handleUndoRedoKeyUp({ code: e }) {
174
- ["KeyZ", "KeyY"].includes(e) && (this.isUndoRedoKeyPressed = !1);
173
+ handleUndoRedoKeyUp(e) {
174
+ this._shouldIgnoreKeyboardEvent(e) || ["KeyZ", "KeyY"].includes(e.code) && (this.isUndoRedoKeyPressed = !1);
175
175
  }
176
176
  /**
177
177
  * Обработчик для выделения всех объектов (Ctrl+A).
@@ -181,16 +181,17 @@ class z {
181
181
  * @param event.code — код клавиши
182
182
  */
183
183
  handleSelectAllEvent(e) {
184
+ if (this._shouldIgnoreKeyboardEvent(e)) return;
184
185
  const { ctrlKey: t, metaKey: s, code: n } = e;
185
186
  !t && !s || n !== "KeyA" || (e.preventDefault(), this.editor.selectionManager.selectAll());
186
187
  }
187
188
  /**
188
- * Обработчик для удаления объектов (Delete).
189
+ * Обработчик для удаления объектов (Delete или Backspace).
189
190
  * @param event — объект события
190
191
  * @param event.code — код клавиши
191
192
  */
192
193
  handleDeleteObjectsEvent(e) {
193
- e.code === "Delete" && (e.preventDefault(), this.editor.deletionManager.deleteSelectedObjects());
194
+ this._shouldIgnoreKeyboardEvent(e) || e.code !== "Delete" && e.code !== "Backspace" || (e.preventDefault(), this.editor.deletionManager.deleteSelectedObjects());
194
195
  }
195
196
  /**
196
197
  * Обработчик для нажатия пробела.
@@ -199,7 +200,7 @@ class z {
199
200
  * @param event.code — код клавиши
200
201
  */
201
202
  handleSpaceKeyDown(e) {
202
- if (e.code !== "Space") return;
203
+ if (e.code !== "Space" || this._shouldIgnoreKeyboardEvent(e)) return;
203
204
  const { canvas: t, editor: s, isSpacePressed: n, isDragging: i } = this;
204
205
  n || i || (this.isSpacePressed = !0, e.preventDefault(), t.set({
205
206
  selection: !1,
@@ -219,7 +220,7 @@ class z {
219
220
  * @param event.code — код клавиши
220
221
  */
221
222
  handleSpaceKeyUp(e) {
222
- e.code === "Space" && (this.isSpacePressed = !1, this.isDragging && this.handleCanvasDragEnd(), this.canvas.set({
223
+ e.code !== "Space" || this._shouldIgnoreKeyboardEvent(e) || (this.isSpacePressed = !1, this.isDragging && this.handleCanvasDragEnd(), this.canvas.set({
223
224
  defaultCursor: "default",
224
225
  selection: !0
225
226
  }), this.canvas.setCursor("default"), this.editor.canvasManager.getObjects().forEach((t) => {
@@ -285,6 +286,35 @@ class z {
285
286
  const t = e == null ? void 0 : e.target;
286
287
  t && this.editor.transformManager.resetObject(t);
287
288
  }
289
+ /**
290
+ * Проверяет, должно ли событие клавиатуры быть проигнорировано
291
+ * Возвращает true если фокус находится в поле ввода или элементе из списка игнорируемых селекторов
292
+ * @param event - Событие клавиатуры
293
+ * @returns true если событие должно быть проигнорировано
294
+ */
295
+ _shouldIgnoreKeyboardEvent(e) {
296
+ const t = e.target;
297
+ if (!t) return !1;
298
+ const s = ["input", "textarea", "select"], n = t.tagName.toLowerCase();
299
+ if (s.includes(n) || t.contentEditable === "true") return !0;
300
+ const { keyboardIgnoreSelectors: i } = this.options;
301
+ if (i != null && i.length)
302
+ for (const a of i)
303
+ try {
304
+ if (t.matches && t.matches(a) || t.closest && t.closest(a))
305
+ return !0;
306
+ } catch (o) {
307
+ this.editor.errorManager.emitWarning({
308
+ origin: "Listeners",
309
+ method: "_shouldIgnoreKeyboardEvent",
310
+ code: "INVALID_SELECTOR",
311
+ // eslint-disable-next-line max-len
312
+ message: `Invalid keyboard ignore selector: "${a}". Error: ${o.message}`,
313
+ data: o
314
+ });
315
+ }
316
+ return !1;
317
+ }
288
318
  /**
289
319
  * Метод для удаления всех слушателей
290
320
  */
@@ -378,17 +408,17 @@ class Te {
378
408
  this.worker.terminate();
379
409
  }
380
410
  }
381
- const N = 12, ke = 2, Q = 8, J = 20, xe = 100, $ = 20, K = 8, Be = 100, q = 32, ee = 1, Ze = "#2B2D33", te = "#3D8BF4", se = "#FFFFFF";
411
+ const N = 12, ke = 2, Q = 8, $ = 20, xe = 100, J = 20, K = 8, Be = 100, q = 32, ee = 1, Ze = "#2B2D33", te = "#3D8BF4", se = "#FFFFFF";
382
412
  function W(c, e, t, s, n) {
383
413
  const i = N, a = ke;
384
414
  c.save(), c.translate(e, t), c.rotate(R.degreesToRadians(n.angle)), c.fillStyle = se, c.strokeStyle = te, c.lineWidth = ee, c.beginPath(), c.roundRect(-12 / 2, -12 / 2, i, i, a), c.fill(), c.stroke(), c.restore();
385
415
  }
386
416
  function he(c, e, t, s, n) {
387
- const i = Q, a = J, o = xe;
417
+ const i = Q, a = $, o = xe;
388
418
  c.save(), c.translate(e, t), c.rotate(R.degreesToRadians(n.angle)), c.fillStyle = se, c.strokeStyle = te, c.lineWidth = ee, c.beginPath(), c.roundRect(-8 / 2, -20 / 2, i, a, o), c.fill(), c.stroke(), c.restore();
389
419
  }
390
420
  function ge(c, e, t, s, n) {
391
- const i = $, a = K, o = Be;
421
+ const i = J, a = K, o = Be;
392
422
  c.save(), c.translate(e, t), c.rotate(R.degreesToRadians(n.angle)), c.fillStyle = se, c.strokeStyle = te, c.lineWidth = ee, c.beginPath(), c.roundRect(-20 / 2, -8 / 2, i, a, o), c.fill(), c.stroke(), c.restore();
393
423
  }
394
424
  const Ue = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE4Ljc1IDQuMzc1djMuNzVhLjYyNS42MjUgMCAwIDEtLjYyNS42MjVoLTMuNzVhLjYyNS42MjUgMCAwIDEgMC0xLjI1aDIuMTRsLTIuMDc3LTEuOTAzLS4wMi0uMDE5YTYuMjUgNi4yNSAwIDEgMC0uMTMgOC45NjcuNjI2LjYyNiAwIDAgMSAuODYuOTA5QTcuNDU2IDcuNDU2IDAgMCAxIDEwIDE3LjVoLS4xMDNhNy41IDcuNSAwIDEgMSA1LjM5Ni0xMi44MTJMMTcuNSA2LjcwM1Y0LjM3NWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBaIi8+PC9zdmc+", ue = new Image();
@@ -431,28 +461,28 @@ const Re = {
431
461
  ml: {
432
462
  render: he,
433
463
  sizeX: Q,
434
- sizeY: J,
464
+ sizeY: $,
435
465
  offsetX: 0,
436
466
  offsetY: 0
437
467
  },
438
468
  mr: {
439
469
  render: he,
440
470
  sizeX: Q,
441
- sizeY: J,
471
+ sizeY: $,
442
472
  offsetX: 0,
443
473
  offsetY: 0
444
474
  },
445
475
  // Середина горизонталей
446
476
  mt: {
447
477
  render: ge,
448
- sizeX: $,
478
+ sizeX: J,
449
479
  sizeY: K,
450
480
  offsetX: 0,
451
481
  offsetY: 0
452
482
  },
453
483
  mb: {
454
484
  render: ge,
455
- sizeX: $,
485
+ sizeX: J,
456
486
  sizeY: K,
457
487
  offsetX: 0,
458
488
  offsetY: 0
@@ -561,7 +591,9 @@ const Ye = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMj
561
591
  },
562
592
  handlers: {
563
593
  copyPaste: (c) => I(void 0, null, function* () {
564
- yield c.clipboardManager.copy(), yield c.clipboardManager.paste();
594
+ yield c.clipboardManager.copy(), yield c.clipboardManager.paste(), c.clipboardManager.clipboard && c.canvas.fire("editor:object-duplicated", {
595
+ object: c.clipboardManager.clipboard
596
+ });
565
597
  }),
566
598
  delete: (c) => {
567
599
  c.deletionManager.deleteSelectedObjects();
@@ -700,7 +732,7 @@ class Qe {
700
732
  this.el.removeEventListener("mouseover", this._onBtnOver), this.el.removeEventListener("mouseout", this._onBtnOut), this.canvas.off("mouse:down", this._onMouseDown), this.canvas.off("object:moving", this._onObjectMoving), this.canvas.off("object:scaling", this._onObjectScaling), this.canvas.off("object:rotating", this._onObjectRotating), this.canvas.off("mouse:up", this._onMouseUp), this.canvas.off("object:modified", this._onObjectModified), this.canvas.off("selection:created", this._onSelectionChange), this.canvas.off("selection:updated", this._onSelectionChange), this.canvas.off("after:render", this._onSelectionChange), this.canvas.off("selection:cleared", this._onSelectionClear), this.el.remove();
701
733
  }
702
734
  }
703
- class Je {
735
+ class $e {
704
736
  constructor({ editor: e }) {
705
737
  this.editor = e, this.canvas = e.canvas, this._historySuspendCount = 0, this.baseState = null, this.patches = [], this.currentIndex = 0, this.maxHistoryLength = e.options.maxHistoryLength, this.totalChangesCount = 0, this.baseStateChangesCount = 0, this._createDiffPatcher();
706
738
  }
@@ -903,7 +935,7 @@ class Je {
903
935
  });
904
936
  }
905
937
  }
906
- const $e = 0.1, Ke = 2, qe = 0.1, et = 90, x = 16, B = 16, O = 4096, T = 4096;
938
+ const Je = 0.1, Ke = 2, qe = 0.1, et = 90, x = 16, B = 16, O = 4096, T = 4096;
907
939
  class D {
908
940
  constructor({ editor: e }) {
909
941
  this.editor = e, this.options = e.options, this._createdBlobUrls = [], this.acceptContentTypes = this.editor.options.acceptContentTypes, this.acceptFormats = this.getAllowedFormatsFromContentTypes();
@@ -1728,7 +1760,7 @@ class tt {
1728
1760
  }
1729
1761
  class st {
1730
1762
  constructor({ editor: e }) {
1731
- this.editor = e, this.options = e.options, this.minZoom = this.options.minZoom || $e, this.maxZoom = this.options.maxZoom || Ke, this.defaultZoom = this.options.defaultScale, this.maxZoomFactor = this.options.maxZoomFactor;
1763
+ this.editor = e, this.options = e.options, this.minZoom = this.options.minZoom || Je, this.maxZoom = this.options.maxZoom || Ke, this.defaultZoom = this.options.defaultScale, this.maxZoomFactor = this.options.maxZoomFactor;
1732
1764
  }
1733
1765
  /**
1734
1766
  * Метод рассчитывает и применяет зум по умолчанию для монтажной области редактора.
@@ -2283,59 +2315,56 @@ class at {
2283
2315
  * @fires editor:object-copied
2284
2316
  */
2285
2317
  copy() {
2286
- const { canvas: e, errorManager: t } = this.editor, s = e.getActiveObject();
2287
- if (!s) return;
2288
- if (typeof ClipboardItem == "undefined" || !navigator.clipboard) {
2289
- t.emitWarning({
2290
- origin: "ClipboardManager",
2291
- method: "copy",
2292
- code: "CLIPBOARD_NOT_SUPPORTED",
2293
- // eslint-disable-next-line max-len
2294
- message: "ClipboardManager. navigator.clipboard не поддерживается в этом браузере или отсутствует соединение по HTTPS-протоколу."
2295
- }), this._cloneAndFire(e, s);
2296
- return;
2297
- }
2298
- if (s.type !== "image") {
2299
- const g = `application/image-editor:${JSON.stringify(s.toObject(["format"]))}`;
2300
- navigator.clipboard.writeText(g).catch((u) => {
2318
+ return I(this, null, function* () {
2319
+ const { canvas: e, errorManager: t } = this.editor, s = e.getActiveObject();
2320
+ if (!s) return;
2321
+ try {
2322
+ const g = yield s.clone(["format"]);
2323
+ this.clipboard = g, e.fire("editor:object-copied", { object: g });
2324
+ } catch (g) {
2325
+ t.emitError({
2326
+ origin: "ClipboardManager",
2327
+ method: "copy",
2328
+ code: "CLONE_FAILED",
2329
+ message: "Ошибка клонирования объекта",
2330
+ data: g
2331
+ });
2332
+ return;
2333
+ }
2334
+ if (typeof ClipboardItem == "undefined" || !navigator.clipboard) {
2301
2335
  t.emitWarning({
2302
2336
  origin: "ClipboardManager",
2303
2337
  method: "copy",
2304
- code: "CLIPBOARD_WRITE_TEXT_FAILED",
2305
- message: `Ошибка записи текстового объекта в буфер обмена: ${u.message}`,
2306
- data: u
2338
+ code: "CLIPBOARD_NOT_SUPPORTED",
2339
+ // eslint-disable-next-line max-len
2340
+ message: "ClipboardManager. navigator.clipboard не поддерживается в этом браузере или отсутствует соединение по HTTPS-протоколу."
2341
+ });
2342
+ return;
2343
+ }
2344
+ if (s.type !== "image") {
2345
+ const g = `application/image-editor:${JSON.stringify(s.toObject(["format"]))}`;
2346
+ navigator.clipboard.writeText(g).catch((u) => {
2347
+ t.emitWarning({
2348
+ origin: "ClipboardManager",
2349
+ method: "copy",
2350
+ code: "CLIPBOARD_WRITE_TEXT_FAILED",
2351
+ message: `Ошибка записи текстового объекта в буфер обмена: ${u.message}`,
2352
+ data: u
2353
+ });
2354
+ });
2355
+ return;
2356
+ }
2357
+ const i = s.toCanvasElement().toDataURL(), a = i.slice(5).split(";")[0], o = i.split(",")[1], d = atob(o), r = new Uint8Array(d.length);
2358
+ for (let g = 0; g < d.length; g += 1)
2359
+ r[g] = d.charCodeAt(g);
2360
+ const l = new Blob([r.buffer], { type: a }), h = new ClipboardItem({ [a]: l });
2361
+ navigator.clipboard.write([h]).catch((g) => {
2362
+ t.emitWarning({
2363
+ origin: "ClipboardManager",
2364
+ method: "copy",
2365
+ code: "CLIPBOARD_WRITE_IMAGE_FAILED",
2366
+ message: `Ошибка записи изображения в буфер обмена: ${g.message}`
2307
2367
  });
2308
- }), this._cloneAndFire(e, s);
2309
- return;
2310
- }
2311
- const i = s.toCanvasElement().toDataURL(), a = i.slice(5).split(";")[0], o = i.split(",")[1], d = atob(o), r = new Uint8Array(d.length);
2312
- for (let g = 0; g < d.length; g += 1)
2313
- r[g] = d.charCodeAt(g);
2314
- const l = new Blob([r.buffer], { type: a }), h = new ClipboardItem({ [a]: l });
2315
- navigator.clipboard.write([h]).catch((g) => {
2316
- t.emitWarning({
2317
- origin: "ClipboardManager",
2318
- method: "copy",
2319
- code: "CLIPBOARD_WRITE_IMAGE_FAILED",
2320
- message: `Ошибка записи изображения в буфер обмена: ${g.message}`
2321
- });
2322
- }), this._cloneAndFire(e, s);
2323
- }
2324
- /**
2325
- * Клонирует объект и вызывает событие 'editor:object-copied'.
2326
- * @param canvas - экземпляр canvas
2327
- * @param object - активный объект
2328
- */
2329
- _cloneAndFire(e, t) {
2330
- t.clone(["format"]).then((s) => {
2331
- this.clipboard = s, e.fire("editor:object-copied", { object: s });
2332
- }).catch((s) => {
2333
- this.editor.errorManager.emitError({
2334
- origin: "ClipboardManager",
2335
- method: "_cloneAndFire",
2336
- code: "CLONE_FAILED",
2337
- message: "Ошибка клонирования объекта",
2338
- data: s
2339
2368
  });
2340
2369
  });
2341
2370
  }
@@ -2720,7 +2749,7 @@ class ne {
2720
2749
  scaleType: r,
2721
2750
  _onReadyCallback: l
2722
2751
  } = this.options;
2723
- if (He.apply(), this.canvas = new Ce(this.containerId, this.options), this.moduleLoader = new Ee(), this.workerManager = new Te(), this.errorManager = new V({ editor: this }), this.historyManager = new Je({ editor: this }), this.toolbar = new Qe({ editor: this }), this.transformManager = new st({ editor: this }), this.canvasManager = new tt({ editor: this }), this.imageManager = new D({ editor: this }), this.layerManager = new P({ editor: this }), this.shapeManager = new it({ editor: this }), this.interactionBlocker = new nt({ editor: this }), this.clipboardManager = new at({ editor: this }), this.objectLockManager = new F({ editor: this }), this.groupingManager = new ot({ editor: this }), this.selectionManager = new rt({ editor: this }), this.deletionManager = new ct({ editor: this }), this._createMontageArea(), this._createClippingArea(), this.listeners = new z({ editor: this, options: this.options }), this.canvasManager.setEditorContainerWidth(e), this.canvasManager.setEditorContainerHeight(t), this.canvasManager.setCanvasWrapperWidth(s), this.canvasManager.setCanvasWrapperHeight(n), this.canvasManager.setCanvasCSSWidth(i), this.canvasManager.setCanvasCSSHeight(a), o != null && o.source) {
2752
+ if (He.apply(), this.canvas = new Ce(this.containerId, this.options), this.moduleLoader = new Ee(), this.workerManager = new Te(), this.errorManager = new V({ editor: this }), this.historyManager = new $e({ editor: this }), this.toolbar = new Qe({ editor: this }), this.transformManager = new st({ editor: this }), this.canvasManager = new tt({ editor: this }), this.imageManager = new D({ editor: this }), this.layerManager = new P({ editor: this }), this.shapeManager = new it({ editor: this }), this.interactionBlocker = new nt({ editor: this }), this.clipboardManager = new at({ editor: this }), this.objectLockManager = new F({ editor: this }), this.groupingManager = new ot({ editor: this }), this.selectionManager = new rt({ editor: this }), this.deletionManager = new ct({ editor: this }), this._createMontageArea(), this._createClippingArea(), this.listeners = new z({ editor: this, options: this.options }), this.canvasManager.setEditorContainerWidth(e), this.canvasManager.setEditorContainerHeight(t), this.canvasManager.setCanvasWrapperWidth(s), this.canvasManager.setCanvasWrapperHeight(n), this.canvasManager.setCanvasCSSWidth(i), this.canvasManager.setCanvasCSSHeight(a), o != null && o.source) {
2724
2753
  const {
2725
2754
  source: h,
2726
2755
  scale: g = `image-${r}`,
@@ -2885,7 +2914,8 @@ const lt = {
2885
2914
  undoRedoByHotKeys: !0,
2886
2915
  selectAllByHotkey: !0,
2887
2916
  deleteObjectsByHotkey: !0,
2888
- resetObjectFitByDoubleClick: !0
2917
+ resetObjectFitByDoubleClick: !0,
2918
+ keyboardIgnoreSelectors: []
2889
2919
  };
2890
2920
  function bt(c, e = {}) {
2891
2921
  const t = y(y({}, lt), e), s = document.getElementById(c);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anu3ev/fabric-image-editor",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
4
4
  "description": "JavaScript image editor built on FabricJS, allowing you to create instances with an integrated montage area and providing an API to modify and manage state.",
5
5
  "module": "dist/main.js",
6
6
  "files": [
@@ -14,12 +14,15 @@
14
14
  "build": "vite build --config vite.config.prod.js",
15
15
  "build:docs": "vite build --config vite.config.docs.js",
16
16
  "preview": "vite preview",
17
- "lint": "eslint \"src/**/*.{js,ts}\""
17
+ "lint": "eslint \"src/**/*.{js,ts}\"",
18
+ "test": "jest --coverage",
19
+ "test:watch": "jest --watch"
18
20
  },
19
21
  "devDependencies": {
20
22
  "@babel/core": "^7.26.7",
21
23
  "@babel/preset-env": "^7.26.7",
22
24
  "@eslint/js": "^9.16.0",
25
+ "@types/jest": "^30.0.0",
23
26
  "@typescript-eslint/eslint-plugin": "^8.36.0",
24
27
  "@typescript-eslint/parser": "^8.36.0",
25
28
  "eslint": "^8.57.1",
@@ -31,7 +34,11 @@
31
34
  "eslint-plugin-sort-keys-fix": "^1.1.2",
32
35
  "eslint-plugin-vue": "^9.32.0",
33
36
  "globals": "^15.13.0",
37
+ "jest": "^29.7.0",
38
+ "jest-environment-jsdom": "^29.7.0",
34
39
  "prettier": "^3.4.2",
40
+ "ts-jest": "^29.4.1",
41
+ "ts-node": "^10.9.2",
35
42
  "vite": "^6.3.5",
36
43
  "vite-bundle-analyzer": "^0.21.0",
37
44
  "vite-plugin-babel": "^1.3.0",