@sequent-org/moodboard 1.4.0 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,21 @@
1
1
  import { Events } from '../../core/events/Events.js';
2
2
  import { logMindmapCompoundDebug } from '../../mindmap/MindmapCompoundContract.js';
3
3
 
4
+ function getSelectTool(board) {
5
+ const toolManager = board?.coreMoodboard?.toolManager;
6
+ if (!toolManager) return null;
7
+ return toolManager?.tools?.get?.('select')
8
+ || toolManager?.registry?.get?.('select')
9
+ || null;
10
+ }
11
+
12
+ function hasOpenTextEditorInDom(board) {
13
+ const doc = board?.workspaceElement?.ownerDocument
14
+ || (typeof document !== 'undefined' ? document : null);
15
+ if (!doc || typeof doc.querySelector !== 'function') return false;
16
+ return Boolean(doc.querySelector('.moodboard-text-input'));
17
+ }
18
+
4
19
  export function bindToolbarEvents(board) {
5
20
  board.coreMoodboard.eventBus.on(Events.UI.ToolbarAction, (action) => {
6
21
  if (action?.type === 'mindmap') {
@@ -13,8 +28,49 @@ export function bindToolbarEvents(board) {
13
28
  const createdObject = board.actionHandler.handleToolbarAction(action);
14
29
  if (action?.type === 'mindmap' && createdObject?.id) {
15
30
  const content = String(createdObject?.properties?.content || '');
16
- if (content.trim().length === 0) {
31
+ const createdMeta = createdObject?.properties?.mindmap || {};
32
+ const isRootMindmap = createdMeta?.role === 'root';
33
+ const mindmapObjects = (board?.coreMoodboard?.state?.state?.objects || [])
34
+ .filter((obj) => obj?.type === 'mindmap');
35
+ const rootCount = mindmapObjects.filter((obj) => (obj?.properties?.mindmap?.role || null) === 'root').length;
36
+ const shouldAutoOpenForRoot = !isRootMindmap || rootCount <= 1;
37
+ const selectTool = getSelectTool(board);
38
+ const hasActiveEditor = Boolean(selectTool?.textEditor?.active);
39
+ const hasEditorDom = hasOpenTextEditorInDom(board);
40
+ const shouldBlockAutoOpen = hasActiveEditor && hasEditorDom;
41
+ if (content.trim().length === 0 && !shouldBlockAutoOpen && shouldAutoOpenForRoot) {
42
+ const doc = board?.workspaceElement?.ownerDocument
43
+ || (typeof document !== 'undefined' ? document : null);
44
+ const closeSeqAtSchedule = Number(selectTool?._mindmapEditorCloseSeq || 0);
45
+ let cancelledByPointer = false;
46
+ const cancelOnPointerDown = () => {
47
+ cancelledByPointer = true;
48
+ };
49
+ const cancelOnEscape = (event) => {
50
+ if (event?.key === 'Escape') {
51
+ cancelledByPointer = true;
52
+ }
53
+ };
54
+ if (doc && typeof doc.addEventListener === 'function') {
55
+ doc.addEventListener('pointerdown', cancelOnPointerDown, true);
56
+ doc.addEventListener('keydown', cancelOnEscape, true);
57
+ }
17
58
  setTimeout(() => {
59
+ if (doc && typeof doc.removeEventListener === 'function') {
60
+ doc.removeEventListener('pointerdown', cancelOnPointerDown, true);
61
+ doc.removeEventListener('keydown', cancelOnEscape, true);
62
+ }
63
+ if (cancelledByPointer) return;
64
+ const latestSelectTool = getSelectTool(board);
65
+ const latestCloseSeq = Number(latestSelectTool?._mindmapEditorCloseSeq || 0);
66
+ if (latestCloseSeq !== closeSeqAtSchedule) return;
67
+
68
+ const nextSelectTool = getSelectTool(board);
69
+ const nextHasActiveEditor = Boolean(nextSelectTool?.textEditor?.active);
70
+ const nextHasEditorDom = hasOpenTextEditorInDom(board);
71
+ if (nextHasActiveEditor || nextHasEditorDom) {
72
+ return;
73
+ }
18
74
  board.coreMoodboard.eventBus.emit(Events.Keyboard.ToolSelect, { tool: 'select' });
19
75
  board.coreMoodboard.eventBus.emit(Events.Tool.ObjectEdit, {
20
76
  object: {
@@ -25,7 +81,7 @@ export function bindToolbarEvents(board) {
25
81
  },
26
82
  create: true,
27
83
  });
28
- }, 20);
84
+ }, 0);
29
85
  }
30
86
  }
31
87
  });
@@ -175,7 +175,12 @@ export function openMindmapEditor(object, create = false) {
175
175
 
176
176
  this.eventBus.emit(Events.UI.TextEditStart, { objectId: objectId || null });
177
177
  if (objectId && typeof this.setSelection === 'function') {
178
- this.setSelection([objectId]);
178
+ this._selectionSyncFromEditor = true;
179
+ try {
180
+ this.setSelection([objectId]);
181
+ } finally {
182
+ this._selectionSyncFromEditor = false;
183
+ }
179
184
  }
180
185
  updateGlobalTextEditorHandlesLayer();
181
186
 
@@ -496,6 +501,36 @@ export function openMindmapEditor(object, create = false) {
496
501
 
497
502
  hideStaticTextDuringEditing(this, objectId);
498
503
 
504
+ const ownerDoc = view?.ownerDocument || (typeof document !== 'undefined' ? document : null);
505
+ const onOutsideDocumentPointerDown = (event) => {
506
+ if (!this.textEditor?.active || this.textEditor.objectType !== 'mindmap') return;
507
+ const target = event?.target;
508
+ if (!target) return;
509
+ if (wrapper.contains(target)) return;
510
+ const sideButton = (typeof target.closest === 'function')
511
+ ? target.closest('.mb-mindmap-side-btn')
512
+ : null;
513
+ if (sideButton?.dataset?.side === 'bottom') return;
514
+ finalize(true);
515
+ if (typeof this.clearSelection === 'function') {
516
+ this.clearSelection();
517
+ }
518
+ };
519
+ const onEscapeDocumentKeyDown = (event) => {
520
+ if (!this.textEditor?.active || this.textEditor.objectType !== 'mindmap') return;
521
+ if (event?.key !== 'Escape') return;
522
+ if (typeof event.preventDefault === 'function') event.preventDefault();
523
+ if (typeof event.stopPropagation === 'function') event.stopPropagation();
524
+ finalize(false);
525
+ if (typeof this.clearSelection === 'function') {
526
+ this.clearSelection();
527
+ }
528
+ };
529
+ if (ownerDoc && typeof ownerDoc.addEventListener === 'function') {
530
+ ownerDoc.addEventListener('pointerdown', onOutsideDocumentPointerDown, true);
531
+ ownerDoc.addEventListener('keydown', onEscapeDocumentKeyDown, true);
532
+ }
533
+
499
534
  const syncEditorBoundsToObject = () => {
500
535
  if (!objectId || !wrapper || !view || !view.parentElement || !world) return;
501
536
  const staticEl = (typeof window !== 'undefined')
@@ -561,8 +596,14 @@ export function openMindmapEditor(object, create = false) {
561
596
  const finalize = (commit) => {
562
597
  if (finalized) return;
563
598
  finalized = true;
599
+ this._mindmapEditorLastClosedAt = Date.now();
600
+ this._mindmapEditorCloseSeq = Number(this._mindmapEditorCloseSeq || 0) + 1;
564
601
 
565
602
  unregisterEditorListeners(this.eventBus, editorListeners);
603
+ if (ownerDoc && typeof ownerDoc.removeEventListener === 'function') {
604
+ ownerDoc.removeEventListener('pointerdown', onOutsideDocumentPointerDown, true);
605
+ ownerDoc.removeEventListener('keydown', onEscapeDocumentKeyDown, true);
606
+ }
566
607
 
567
608
  textarea.removeEventListener('blur', onBlur);
568
609
  textarea.removeEventListener('keydown', onKeyDown);
@@ -250,7 +250,20 @@ export function onContextMenu(event) {
250
250
  export function onKeyDown(event) {
251
251
  // Проверяем, не активен ли текстовый редактор (редактирование названия файла или текста)
252
252
  if (this.textEditor && this.textEditor.active) {
253
- return; // Не обрабатываем клавиши во время редактирования
253
+ if (event.key === 'Escape') {
254
+ if (this.textEditor.objectType === 'file') {
255
+ this._closeFileNameEditor(false);
256
+ } else {
257
+ this._closeTextEditor(false);
258
+ }
259
+ if (this.textEditor.objectType === 'mindmap') {
260
+ this.clearSelection();
261
+ }
262
+ if (event?.originalEvent?.preventDefault) {
263
+ event.originalEvent.preventDefault();
264
+ }
265
+ }
266
+ return; // Не обрабатываем остальные клавиши во время редактирования
254
267
  }
255
268
 
256
269
  switch (event.key) {
@@ -48,6 +48,13 @@ export function hasSelection() {
48
48
  }
49
49
 
50
50
  export function setSelection(objectIds) {
51
+ if (this.textEditor?.active && !this._selectionSyncFromEditor) {
52
+ if (this.textEditor.objectType === 'file' && typeof this._closeFileNameEditor === 'function') {
53
+ this._closeFileNameEditor(true);
54
+ } else if (typeof this._closeTextEditor === 'function') {
55
+ this._closeTextEditor(true);
56
+ }
57
+ }
51
58
  const prev = this.selection.toArray();
52
59
  this.selection.clear();
53
60
  this.selection.addMany(objectIds);
@@ -109,17 +109,14 @@ export class MindmapConnectionLayer {
109
109
  this.graphics = null;
110
110
  this.subscriptions = [];
111
111
  this._lastSegments = [];
112
+ this._eventsAttached = false;
112
113
  }
113
114
 
114
115
  attach() {
115
116
  if (!this.core?.pixi) return;
116
- if (this.graphics) return;
117
- this.graphics = new PIXI.Graphics();
118
- this.graphics.name = 'mindmap-connection-layer';
119
- this.graphics.zIndex = 2;
120
- const world = this.core.pixi.worldLayer || this.core.pixi.app?.stage;
121
- world?.addChild?.(this.graphics);
122
- this._attachEvents();
117
+ if (!this._eventsAttached) {
118
+ this._attachEvents();
119
+ }
123
120
  this.updateAll();
124
121
  }
125
122
 
@@ -135,6 +132,7 @@ export class MindmapConnectionLayer {
135
132
  }
136
133
 
137
134
  _attachEvents() {
135
+ if (this._eventsAttached) return;
138
136
  const bindings = [
139
137
  [Events.Object.Created, () => this.updateAll()],
140
138
  [Events.Object.Deleted, () => this.updateAll()],
@@ -156,6 +154,7 @@ export class MindmapConnectionLayer {
156
154
  this.eventBus.on(event, handler);
157
155
  this.subscriptions.push([event, handler]);
158
156
  });
157
+ this._eventsAttached = true;
159
158
  }
160
159
 
161
160
  _detachEvents() {
@@ -165,10 +164,10 @@ export class MindmapConnectionLayer {
165
164
  }
166
165
  this.subscriptions.forEach(([event, handler]) => this.eventBus.off(event, handler));
167
166
  this.subscriptions = [];
167
+ this._eventsAttached = false;
168
168
  }
169
169
 
170
170
  updateAll() {
171
- if (!this.graphics) return;
172
171
  const objects = asArray(this.core?.state?.state?.objects);
173
172
  const mindmaps = objects.filter(isMindmap);
174
173
  const byId = new Map(mindmaps.map((obj) => [obj.id, obj]));
@@ -184,6 +183,22 @@ export class MindmapConnectionLayer {
184
183
  return meta.role === 'child' && typeof meta.parentId === 'string' && meta.parentId.length > 0;
185
184
  });
186
185
 
186
+ if (children.length === 0) {
187
+ if (this.graphics) {
188
+ this.graphics.clear();
189
+ }
190
+ this._lastSegments = [];
191
+ return;
192
+ }
193
+
194
+ if (!this.graphics) {
195
+ this.graphics = new PIXI.Graphics();
196
+ this.graphics.name = 'mindmap-connection-layer';
197
+ this.graphics.zIndex = 2;
198
+ const world = this.core?.pixi?.worldLayer || this.core?.pixi?.app?.stage;
199
+ world?.addChild?.(this.graphics);
200
+ }
201
+
187
202
  const g = this.graphics;
188
203
  g.clear();
189
204
  this._lastSegments = [];