@sequent-org/moodboard 1.4.21 → 1.4.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.4.21",
3
+ "version": "1.4.23",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -3,6 +3,9 @@ import { Events } from '../../../core/events/Events.js';
3
3
  export function onMouseDown(event) {
4
4
  // Если активен текстовый редактор, закрываем его при клике вне
5
5
  if (this.textEditor.active) {
6
+ // Помечаем, что закрытие инициировано кликом вне редактора.
7
+ // Это используется в blur-handler, чтобы избежать двойного finalize в одном цикле.
8
+ this.textEditor._closingByOutside = true;
6
9
  const activeEditorType = this.textEditor.objectType;
7
10
  if (this.textEditor.objectType === 'file') {
8
11
  this._closeFileNameEditor(true);
@@ -120,7 +120,11 @@ export function createTextEditorFinalize(controller, {
120
120
  updateGlobalTextEditorHandlesLayer();
121
121
 
122
122
  if (!commitValue) {
123
- if (isNewCreation && objectId) {
123
+ // Safety against race conditions: when a newly-created editor is blurred by
124
+ // tool-switch/outside click, do not auto-delete the object. Explicit cancel
125
+ // (Esc -> commit === false) still removes empty creation.
126
+ const shouldDeleteEmptyNewCreation = isNewCreation && objectId && commit === false;
127
+ if (shouldDeleteEmptyNewCreation) {
124
128
  controller.eventBus.emit(Events.Tool.ObjectsDelete, { objects: [objectId] });
125
129
  }
126
130
  return;
@@ -168,12 +172,21 @@ export function bindTextEditorInteractions(controller, {
168
172
  finalize,
169
173
  }) {
170
174
  const blurHandler = () => {
171
- const value = (textarea.value || '').trim();
172
- if (isNewCreation && value.length === 0) {
173
- finalize(false);
174
- return;
175
- }
176
- finalize(true);
175
+ const editorObjectId = controller?.textEditor?.objectId || null;
176
+ setTimeout(() => {
177
+ // Если редактор уже закрыт/переключен другим обработчиком (outside click),
178
+ // blur не должен дублировать finalize.
179
+ if (!controller?.textEditor?.active) return;
180
+ if ((controller?.textEditor?.objectId || null) !== editorObjectId) return;
181
+ if (controller?.textEditor?._closingByOutside) return;
182
+
183
+ const value = (textarea.value || '').trim();
184
+ if (isNewCreation && value.length === 0) {
185
+ finalize(false);
186
+ return;
187
+ }
188
+ finalize(true);
189
+ }, 0);
177
190
  };
178
191
 
179
192
  const keydownHandler = (e) => {
@@ -216,6 +229,7 @@ export function closeTextEditorFromState(controller, commit) {
216
229
  const properties = controller.textEditor.properties;
217
230
  const isNewCreation = controller.textEditor.isNewCreation;
218
231
  const initialContent = controller.textEditor.initialContent ?? '';
232
+ const shouldDeleteEmptyNewCreation = !commitValue && !!isNewCreation && !!objectId;
219
233
 
220
234
  if (objectId) {
221
235
  if (typeof window !== 'undefined' && window.moodboardHtmlTextLayer) {
@@ -240,7 +254,12 @@ export function closeTextEditorFromState(controller, commit) {
240
254
 
241
255
  textarea.remove();
242
256
  controller.textEditor = { active: false, objectId: null, textarea: null, world: null, objectType: 'text' };
243
- if (!commitValue) return;
257
+ if (!commitValue) {
258
+ if (shouldDeleteEmptyNewCreation) {
259
+ controller.eventBus.emit(Events.Tool.ObjectsDelete, { objects: [objectId] });
260
+ }
261
+ return;
262
+ }
244
263
  if (objectId == null) {
245
264
  controller.eventBus.emit(Events.UI.ToolbarAction, {
246
265
  type: objectType,
@@ -58,8 +58,22 @@ export function openTextEditor(object, create = false) {
58
58
  return;
59
59
  }
60
60
 
61
- // Закрываем предыдущий редактор, если он открыт
62
- if (this.textEditor.active) this._closeTextEditor(true);
61
+ // Закрываем предыдущий редактор, если он открыт.
62
+ // Защита от повторного открытия того же объекта в один цикл событий:
63
+ // не пересоздаём textarea/обёртку, если уже редактируем этот объект.
64
+ if (this.textEditor.active) {
65
+ const sameEditorObject = !!(
66
+ objectId &&
67
+ this.textEditor.objectId &&
68
+ this.textEditor.objectId === objectId &&
69
+ this.textEditor.objectType === objectType
70
+ );
71
+ if (sameEditorObject && this.textEditor.textarea) {
72
+ try { this.textEditor.textarea.focus(); } catch (_) {}
73
+ return;
74
+ }
75
+ this._closeTextEditor(true);
76
+ }
63
77
 
64
78
  // Если это редактирование существующего объекта, получаем его данные
65
79
  if (!create && objectId) {