@sequent-org/moodboard 1.4.30 → 1.4.32

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 (61) hide show
  1. package/package.json +3 -1
  2. package/src/core/PixiEngine.js +34 -5
  3. package/src/core/bootstrap/CoreInitializer.js +4 -0
  4. package/src/core/commands/CreateConnectorCommand.js +25 -0
  5. package/src/core/commands/GroupMoveCommand.js +2 -2
  6. package/src/core/commands/MoveObjectCommand.js +1 -1
  7. package/src/core/commands/UpdateConnectorCommand.js +38 -0
  8. package/src/core/events/Events.js +1 -0
  9. package/src/mindmap/MindmapCompoundContract.js +1 -0
  10. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +14 -0
  11. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +18 -0
  12. package/src/objects/ConnectorObject.js +85 -0
  13. package/src/objects/DrawingObject.js +47 -0
  14. package/src/objects/MindmapObject.js +21 -3
  15. package/src/objects/NoteObject.js +16 -8
  16. package/src/objects/ObjectFactory.js +3 -1
  17. package/src/objects/ShapeObject.js +1 -1
  18. package/src/services/ConnectorBindingResolver.js +204 -0
  19. package/src/services/ai/AiClient.js +30 -2
  20. package/src/services/ai/ChatSessionController.js +1 -0
  21. package/src/tools/ToolManager.js +3 -0
  22. package/src/tools/manager/PointerGestureController.js +206 -0
  23. package/src/tools/manager/ToolEventRouter.js +10 -0
  24. package/src/tools/manager/ToolManagerGuards.js +3 -1
  25. package/src/tools/manager/ToolManagerLifecycle.js +70 -58
  26. package/src/tools/object-tools/ConnectorTool.js +147 -0
  27. package/src/tools/object-tools/PlacementTool.js +2 -2
  28. package/src/tools/object-tools/connector/ConnectorDragController.js +296 -0
  29. package/src/tools/object-tools/connector/connectorGesture.js +108 -0
  30. package/src/tools/object-tools/placement/GhostController.js +4 -4
  31. package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -2
  32. package/src/tools/object-tools/placement/PlacementInputRouter.js +5 -5
  33. package/src/tools/object-tools/selection/MindmapInlineEditorController.js +11 -2
  34. package/src/tools/object-tools/selection/SelectInputRouter.js +33 -4
  35. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +12 -0
  36. package/src/tools/object-tools/selection/SelectToolSetup.js +3 -0
  37. package/src/tools/object-tools/selection/TextEditorDomFactory.js +1 -2
  38. package/src/tools/object-tools/selection/TextEditorSyncService.js +4 -4
  39. package/src/tools/object-tools/selection/TextInlineEditorController.js +21 -3
  40. package/src/tools/object-tools/selection/TransformInteractionController.js +4 -6
  41. package/src/ui/HtmlTextLayer.js +212 -5
  42. package/src/ui/animation/HoverLiftController.js +395 -0
  43. package/src/ui/chat/ChatComposer.js +1 -10
  44. package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
  45. package/src/ui/chat/ChatWindow.js +167 -36
  46. package/src/ui/chat/ChatWindowRenderer.js +1 -8
  47. package/src/ui/chat/icons.js +17 -5
  48. package/src/ui/connectors/ConnectionAnchorsLayer.js +231 -0
  49. package/src/ui/connectors/ConnectorLayer.js +251 -0
  50. package/src/ui/handles/HandlesDomRenderer.js +11 -7
  51. package/src/ui/handles/HandlesInteractionController.js +65 -34
  52. package/src/ui/handles/HandlesPositioningService.js +41 -6
  53. package/src/ui/mindmap/MindmapCollapseGraph.js +169 -0
  54. package/src/ui/mindmap/MindmapCollapseLayer.js +380 -0
  55. package/src/ui/mindmap/MindmapConnectionLayer.js +50 -25
  56. package/src/ui/mindmap/MindmapHtmlTextLayer.js +223 -2
  57. package/src/ui/mindmap/MindmapLayoutConfig.js +12 -0
  58. package/src/ui/styles/chat.css +2 -37
  59. package/src/ui/styles/toolbar.css +6 -0
  60. package/src/ui/styles/workspace.css +83 -21
  61. package/src/ui/toolbar/ToolbarPopupsController.js +1 -1
@@ -1,11 +1,21 @@
1
1
  import { Events } from '../../core/events/Events.js';
2
+ import gsap from 'gsap';
2
3
  import * as PIXI from 'pixi.js';
3
4
  import { MindmapTextOverlayAdapter } from './MindmapTextOverlayAdapter.js';
4
- import { MINDMAP_LAYOUT } from './MindmapLayoutConfig.js';
5
+ import { MINDMAP_LAYOUT, MINDMAP_AUTOFIT } from './MindmapLayoutConfig.js';
5
6
 
6
7
  const MINDMAP_PLACEHOLDER = 'Напишите что-нибудь';
7
8
  const MINDMAP_MAX_LINE_CHARS = MINDMAP_LAYOUT.maxLineChars;
8
9
 
10
+ // Hover-lift канон «маленькие» — идентичен HtmlTextLayer.js
11
+ const MM_HOVER_TY = -2;
12
+ const MM_HOVER_SC = 1.06;
13
+ const MM_HOVER_DUR = 0.22;
14
+ const MM_BACK_DUR = 0.18;
15
+ const mmPrefersReducedMotion =
16
+ typeof window !== 'undefined' &&
17
+ window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
18
+
9
19
  function normalizeMindmapLineLength(value, maxLineChars = MINDMAP_MAX_LINE_CHARS) {
10
20
  const text = (typeof value === 'string')
11
21
  ? value.replace(/\r/g, '').replace(/\n/g, '')
@@ -32,6 +42,12 @@ export class MindmapHtmlTextLayer {
32
42
  this.idToCleanup = new Map();
33
43
  this.idToContentEl = new Map();
34
44
  this.overlayAdapter = new MindmapTextOverlayAdapter();
45
+ // hover-lift state: objectId → { ty: 0, sc: 1 }
46
+ this._hoverStates = new Map();
47
+ this._hoveredId = null;
48
+ this._selectedIds = new Set();
49
+ this._pixiHoverHandlers = new Map();
50
+ this._transformActive = false;
35
51
  }
36
52
 
37
53
  attach() {
@@ -50,6 +66,7 @@ export class MindmapHtmlTextLayer {
50
66
  if (!this.overlayAdapter.supportsObject(objectData)) return;
51
67
  this._ensureTextEl(objectId, objectData);
52
68
  this.updateOne(objectId);
69
+ this._scheduleAutoFit(objectId);
53
70
  });
54
71
 
55
72
  this.eventBus.on(Events.Object.Deleted, ({ objectId }) => {
@@ -74,6 +91,7 @@ export class MindmapHtmlTextLayer {
74
91
  const containerEl = this.idToEl.get(objectId);
75
92
  if (contentEl && containerEl && typeof content === 'string') {
76
93
  this._applyContentValue(containerEl, contentEl, content);
94
+ this._autoFitNodeWidth(objectId);
77
95
  }
78
96
  });
79
97
 
@@ -91,10 +109,14 @@ export class MindmapHtmlTextLayer {
91
109
  el.style.color = updates.color || updates.properties?.textColor;
92
110
  }
93
111
  this.updateOne(objectId);
112
+ this._autoFitNodeWidth(objectId);
94
113
  });
95
114
 
96
115
  this.eventBus.on(Events.UI.ZoomPercent, () => this.updateAll());
97
116
  this.eventBus.on(Events.Tool.PanUpdate, () => this.updateAll());
117
+ this.eventBus.on(Events.UI.TextEditEnd, ({ objectId }) => {
118
+ if (objectId && this.idToEl.has(objectId)) this._scheduleAutoFit(objectId);
119
+ });
98
120
  this.eventBus.on(Events.Tool.DragUpdate, ({ object }) => this.updateOne(object));
99
121
  this.eventBus.on(Events.Tool.ResizeUpdate, ({ object }) => this.updateOne(object));
100
122
  this.eventBus.on(Events.Tool.RotateUpdate, ({ object }) => this.updateOne(object));
@@ -108,11 +130,61 @@ export class MindmapHtmlTextLayer {
108
130
  (Array.isArray(objects) ? objects : []).forEach((id) => this.updateOne(id));
109
131
  });
110
132
 
133
+ // Блокировка hover во время drag/resize/rotate (канон HtmlTextLayer)
134
+ this._onTransformStart = () => {
135
+ this._transformActive = true;
136
+ if (this._hoveredId) {
137
+ const id = this._hoveredId;
138
+ this._hoveredId = null;
139
+ this._animHoverOut(id);
140
+ }
141
+ };
142
+ this._onTransformEnd = () => { this._transformActive = false; };
143
+ this.eventBus.on(Events.Tool.DragStart, this._onTransformStart);
144
+ this.eventBus.on(Events.Tool.GroupDragStart, this._onTransformStart);
145
+ this.eventBus.on(Events.Tool.DragEnd, this._onTransformEnd);
146
+ this.eventBus.on(Events.Tool.GroupDragEnd, this._onTransformEnd);
147
+ this.eventBus.on(Events.Tool.ResizeStart, this._onTransformStart);
148
+ this.eventBus.on(Events.Tool.GroupResizeStart, this._onTransformStart);
149
+ this.eventBus.on(Events.Tool.ResizeEnd, this._onTransformEnd);
150
+ this.eventBus.on(Events.Tool.GroupResizeEnd, this._onTransformEnd);
151
+ this.eventBus.on(Events.Tool.RotateStart, this._onTransformStart);
152
+ this.eventBus.on(Events.Tool.GroupRotateStart, this._onTransformStart);
153
+ this.eventBus.on(Events.Tool.RotateEnd, this._onTransformEnd);
154
+ this.eventBus.on(Events.Tool.GroupRotateEnd, this._onTransformEnd);
155
+
156
+ // Выделение: не показываем hover у выделенных узлов
157
+ this._onSelectionAdd = (data) => {
158
+ const id = data?.object ?? data?.objectId ?? data?.id ?? data;
159
+ if (id) {
160
+ this._selectedIds.add(String(id));
161
+ if (this._hoveredId === String(id)) {
162
+ this._hoveredId = null;
163
+ this._animHoverOut(String(id));
164
+ }
165
+ }
166
+ };
167
+ this._onSelectionRemove = (data) => {
168
+ const id = data?.object ?? data?.objectId ?? data?.id ?? data;
169
+ if (id) this._selectedIds.delete(String(id));
170
+ };
171
+ this._onSelectionClear = () => { this._selectedIds.clear(); };
172
+ this.eventBus.on(Events.Tool.SelectionAdd, this._onSelectionAdd);
173
+ this.eventBus.on(Events.Tool.SelectionRemove, this._onSelectionRemove);
174
+ this.eventBus.on(Events.Tool.SelectionClear, this._onSelectionClear);
175
+
111
176
  this.rebuildFromState();
112
177
  this.updateAll();
113
178
  }
114
179
 
115
180
  destroy() {
181
+ for (const [id, state] of this._hoverStates) {
182
+ gsap.killTweensOf(state);
183
+ this._detachPixiHover(id);
184
+ }
185
+ this._hoverStates.clear();
186
+ this._pixiHoverHandlers.clear();
187
+ this._selectedIds.clear();
116
188
  if (this.layer) this.layer.remove();
117
189
  this.layer = null;
118
190
  this.idToEl.clear();
@@ -128,6 +200,7 @@ export class MindmapHtmlTextLayer {
128
200
  this._ensureTextEl(objectData.id, objectData);
129
201
  });
130
202
  this.updateAll();
203
+ this._scheduleAutoFitAll();
131
204
  }
132
205
 
133
206
  _ensureTextEl(objectId, objectData) {
@@ -183,6 +256,7 @@ export class MindmapHtmlTextLayer {
183
256
  });
184
257
  this.idToCleanup.set(objectId, cleanup);
185
258
 
259
+ this._hoverStates.set(objectId, { ty: 0, sc: 1 });
186
260
  el.appendChild(contentEl);
187
261
  this.layer.appendChild(el);
188
262
  this.idToEl.set(objectId, el);
@@ -194,6 +268,10 @@ export class MindmapHtmlTextLayer {
194
268
  const cleanup = this.idToCleanup.get(objectId);
195
269
  if (typeof cleanup === 'function') cleanup();
196
270
  if (el) el.remove();
271
+ this._detachPixiHover(objectId);
272
+ const state = this._hoverStates.get(objectId);
273
+ if (state) gsap.killTweensOf(state);
274
+ this._hoverStates.delete(objectId);
197
275
  this.idToEl.delete(objectId);
198
276
  this.idToCleanup.delete(objectId);
199
277
  this.idToContentEl.delete(objectId);
@@ -268,7 +346,78 @@ export class MindmapHtmlTextLayer {
268
346
  }
269
347
 
270
348
  el.style.transformOrigin = 'center center';
271
- el.style.transform = angle ? `rotate(${angle}deg)` : '';
349
+ const hover = this._hoverStates.get(objectId);
350
+ const hoverTy = hover?.ty ?? 0;
351
+ const hoverSc = hover?.sc ?? 1;
352
+ const hoverPart = (Math.abs(hoverTy) > 0.001 || Math.abs(hoverSc - 1) > 0.001)
353
+ ? `translate3d(0, ${hoverTy}px, 0) scale(${hoverSc})`
354
+ : '';
355
+ const rotatePart = angle ? `rotate(${angle}deg)` : '';
356
+ el.style.transform = [hoverPart, rotatePart].filter(Boolean).join(' ');
357
+ this._ensurePixiHover(objectId);
358
+ }
359
+
360
+ /** Лениво вешает pointerover/pointerout на PIXI-капсулу узла */
361
+ _ensurePixiHover(objectId) {
362
+ if (this._pixiHoverHandlers.has(objectId)) return;
363
+ const cap = this.core?.pixi?.objects?.get ? this.core.pixi.objects.get(objectId) : null;
364
+ if (!cap || typeof cap.on !== 'function') return;
365
+ const onOver = () => this._onPointerOver(objectId);
366
+ const onOut = () => this._onPointerOut(objectId);
367
+ cap.on('pointerover', onOver);
368
+ cap.on('pointerout', onOut);
369
+ this._pixiHoverHandlers.set(objectId, { cap, onOver, onOut });
370
+ }
371
+
372
+ _detachPixiHover(objectId) {
373
+ const h = this._pixiHoverHandlers.get(objectId);
374
+ if (!h) return;
375
+ try { h.cap.off('pointerover', h.onOver); } catch (_) {}
376
+ try { h.cap.off('pointerout', h.onOut); } catch (_) {}
377
+ this._pixiHoverHandlers.delete(objectId);
378
+ }
379
+
380
+ _onPointerOver(objectId) {
381
+ if (mmPrefersReducedMotion) return;
382
+ if (this._transformActive) return;
383
+ if (this._selectedIds.has(objectId) || this._selectedIds.has(String(objectId))) return;
384
+ if (this._hoveredId === objectId) return;
385
+ this._hoveredId = objectId;
386
+ this._animHoverIn(objectId);
387
+ }
388
+
389
+ _onPointerOut(objectId) {
390
+ if (this._hoveredId === objectId) this._hoveredId = null;
391
+ this._animHoverOut(objectId);
392
+ }
393
+
394
+ _animHoverIn(objectId) {
395
+ if (mmPrefersReducedMotion) return;
396
+ const state = this._hoverStates.get(objectId);
397
+ if (!state) return;
398
+ gsap.killTweensOf(state);
399
+ gsap.to(state, {
400
+ ty: MM_HOVER_TY,
401
+ sc: MM_HOVER_SC,
402
+ duration: MM_HOVER_DUR,
403
+ ease: 'hoverLiftSpring',
404
+ onUpdate: () => this.updateOne(objectId),
405
+ onComplete: () => this.updateOne(objectId),
406
+ });
407
+ }
408
+
409
+ _animHoverOut(objectId) {
410
+ const state = this._hoverStates.get(objectId);
411
+ if (!state) return;
412
+ gsap.killTweensOf(state);
413
+ gsap.to(state, {
414
+ ty: 0,
415
+ sc: 1,
416
+ duration: MM_BACK_DUR,
417
+ ease: 'power2.out',
418
+ onUpdate: () => this.updateOne(objectId),
419
+ onComplete: () => this.updateOne(objectId),
420
+ });
272
421
  }
273
422
 
274
423
  _applyContentValue(containerEl, contentEl, rawContent) {
@@ -282,4 +431,76 @@ export class MindmapHtmlTextLayer {
282
431
  contentEl.textContent = isPlaceholder ? MINDMAP_PLACEHOLDER : actual;
283
432
  contentEl.classList.toggle('is-placeholder', isPlaceholder);
284
433
  }
434
+
435
+ _scheduleAutoFit(objectId) {
436
+ const doFit = () => this._autoFitNodeWidth(objectId);
437
+ if (typeof document !== 'undefined' && document.fonts?.ready) {
438
+ document.fonts.ready.then(doFit);
439
+ } else {
440
+ doFit();
441
+ }
442
+ }
443
+
444
+ _scheduleAutoFitAll() {
445
+ const doFit = () => { for (const id of this.idToEl.keys()) this._autoFitNodeWidth(id); };
446
+ if (typeof document !== 'undefined' && document.fonts?.ready) {
447
+ document.fonts.ready.then(doFit);
448
+ } else {
449
+ doFit();
450
+ }
451
+ }
452
+
453
+ _autoFitNodeWidth(objectId) {
454
+ const el = this.idToEl.get(objectId);
455
+ const contentEl = this.idToContentEl.get(objectId);
456
+ if (!el || !contentEl || !this.core) return;
457
+ // Skip while the static element is hidden during inline editing.
458
+ if (el.style.visibility === 'hidden') return;
459
+ try {
460
+ const objectData = (this.core.state.state.objects || []).find(o => o.id === objectId);
461
+ if (!objectData || !objectData.position) return;
462
+
463
+ const world = this.core.pixi.worldLayer || this.core.pixi.app.stage;
464
+ const res = (this.core?.pixi?.app?.renderer?.resolution) || 1;
465
+ const worldScale = world?.scale?.x || 1;
466
+
467
+ // Measure rendered text width via scrollWidth of the <span> (forces layout reflow).
468
+ const scrollWidthCss = contentEl.scrollWidth;
469
+ if (scrollWidthCss <= 0) return;
470
+
471
+ const paddingX = Math.max(0, Math.round(
472
+ objectData.properties?.paddingX ?? MINDMAP_LAYOUT.paddingX
473
+ ));
474
+ // css → world: same formula as _autoFitTextHeight in HtmlTextLayer.js
475
+ const contentWorldW = (scrollWidthCss * res) / worldScale;
476
+ const rawWorldW = contentWorldW + 2 * paddingX;
477
+
478
+ const level = objectData.properties?.mindmap?.level ?? 0;
479
+ const isRoot = level === 0;
480
+ const minW = isRoot ? MINDMAP_AUTOFIT.ROOT_MIN_WIDTH : MINDMAP_AUTOFIT.CHILD_MIN_WIDTH;
481
+ const maxW = isRoot ? MINDMAP_AUTOFIT.ROOT_MAX_WIDTH : MINDMAP_AUTOFIT.CHILD_MAX_WIDTH;
482
+ const newWorldW = Math.max(minW, Math.min(maxW, Math.round(rawWorldW)));
483
+
484
+ // Measure natural height at the fitted width to handle multi-line wrapping.
485
+ const cssFitW = Math.max(1, Math.round(newWorldW * worldScale / res));
486
+ const prevW = el.style.width;
487
+ const prevH = el.style.height;
488
+ el.style.width = `${cssFitW}px`;
489
+ el.style.height = 'auto';
490
+ const scrollHeightCss = Math.max(1, Math.round(el.scrollHeight));
491
+ el.style.width = prevW;
492
+ el.style.height = prevH;
493
+ const newWorldH = Math.max(1, Math.round((scrollHeightCss * res) / worldScale));
494
+
495
+ const currentW = Math.round(objectData.width || objectData.properties?.width || MINDMAP_LAYOUT.width);
496
+ const currentH = Math.round(objectData.height || objectData.properties?.height || MINDMAP_LAYOUT.height);
497
+ if (newWorldW === currentW && newWorldH === currentH) return;
498
+
499
+ this.core.eventBus.emit(Events.Tool.ResizeUpdate, {
500
+ object: objectId,
501
+ size: { width: newWorldW, height: newWorldH },
502
+ position: objectData.position,
503
+ });
504
+ } catch (_) {}
505
+ }
285
506
  }
@@ -27,3 +27,15 @@ export const MINDMAP_LAYOUT = Object.freeze({
27
27
  maxLineChars: MINDMAP_BASE_LAYOUT.maxLineChars,
28
28
  });
29
29
 
30
+ // Auto-fit width bounds (world units).
31
+ // ROOT: current fixed width ~179 → min 100 keeps pill shape for short words;
32
+ // max 440 ≈ 50-char line in Roboto 14px (~375px) + 40px padding.
33
+ // CHILD: slightly narrower pill baseline; same maxLineChars as root but
34
+ // typically smaller padding, so max is a bit tighter.
35
+ export const MINDMAP_AUTOFIT = Object.freeze({
36
+ ROOT_MIN_WIDTH: 100,
37
+ ROOT_MAX_WIDTH: 440,
38
+ CHILD_MIN_WIDTH: 80,
39
+ CHILD_MAX_WIDTH: 360,
40
+ });
41
+
@@ -289,14 +289,6 @@
289
289
  color: #374151;
290
290
  }
291
291
 
292
- .moodboard-chat__input-icon-btn--enhance-prompt[data-empty="true"] {
293
- color: lab(5.0601 0 0 / 0.3216);
294
- }
295
-
296
- .moodboard-chat__input-icon-btn--enhance-prompt[data-empty="true"]:hover {
297
- color: lab(5.0601 0 0 / 0.3216);
298
- }
299
-
300
292
  .moodboard-chat__actions-row {
301
293
  display: flex;
302
294
  align-items: center;
@@ -424,7 +416,7 @@
424
416
  }
425
417
 
426
418
  .moodboard-chat__send[data-state="streaming"] {
427
- background: #EF4444;
419
+ background: #111827;
428
420
  color: #ffffff;
429
421
  }
430
422
 
@@ -724,6 +716,7 @@
724
716
  background-size: 200% 100%;
725
717
  animation: moodboard-pending-shimmer 5.76s linear infinite;
726
718
  border-radius: 12px;
719
+ box-shadow: 8px 8px 24px rgba(0, 0, 0, 0.12);
727
720
  overflow: hidden;
728
721
  pointer-events: none;
729
722
  z-index: 10;
@@ -983,31 +976,3 @@
983
976
  .moodboard-chat__extended-clear:hover {
984
977
  color: #374151;
985
978
  }
986
-
987
- .moodboard-chat__extended-enhance {
988
- background: transparent;
989
- border: none;
990
- color: #6B7280;
991
- font-size: 13px;
992
- font-weight: 500;
993
- cursor: pointer;
994
- padding: 0;
995
- display: inline-flex;
996
- align-items: center;
997
- gap: 6px;
998
- transition: color 120ms ease;
999
- }
1000
-
1001
- .moodboard-chat__extended-enhance:hover {
1002
- color: #374151;
1003
- }
1004
-
1005
- .moodboard-chat__extended-enhance-icon {
1006
- display: inline-flex;
1007
- color: inherit;
1008
- }
1009
-
1010
- .moodboard-chat__extended-enhance-icon svg {
1011
- width: 16px;
1012
- height: 16px;
1013
- }
@@ -78,3 +78,9 @@
78
78
  /* Divider inside toolbar */
79
79
  .moodboard-toolbar__divider { width: 100%; height: 1px; background: #e0e0e0; margin: 6px 0; }
80
80
 
81
+ @media (pointer: coarse) {
82
+ .moodboard-toolbar__button {
83
+ width: 44px;
84
+ height: 44px;
85
+ }
86
+ }
@@ -307,8 +307,8 @@
307
307
  .mb-text--mindmap {
308
308
  display: flex;
309
309
  align-items: center;
310
- justify-content: flex-start;
311
- text-align: left;
310
+ justify-content: center;
311
+ text-align: center;
312
312
  pointer-events: none;
313
313
  cursor: default;
314
314
  color: #212121;
@@ -340,6 +340,7 @@
340
340
  position: absolute;
341
341
  inset: 0;
342
342
  pointer-events: none;
343
+ touch-action: none;
343
344
  z-index: 12;
344
345
  }
345
346
 
@@ -357,23 +358,18 @@
357
358
  position: absolute;
358
359
  width: 12px;
359
360
  height: 12px;
360
- background: #80D8FF;
361
+ background: #ffffff;
361
362
  border: 2px solid #80D8FF;
362
363
  border-radius: 50%;
363
364
  box-sizing: border-box;
364
365
  pointer-events: auto;
365
366
  z-index: 10;
367
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.18);
368
+ transition: background-color 120ms ease, border-color 120ms ease;
366
369
  }
367
370
 
368
371
  .mb-handle-inner {
369
- position: absolute;
370
- top: 1px;
371
- left: 1px;
372
- width: 6px;
373
- height: 6px;
374
- background: #fff;
375
- border-radius: 50%;
376
- pointer-events: none;
372
+ display: none;
377
373
  }
378
374
 
379
375
  .mb-edge {
@@ -576,6 +572,10 @@
576
572
  display: block;
577
573
  width: 100%;
578
574
  height: 100%;
575
+ touch-action: none;
576
+ user-select: none;
577
+ -webkit-user-select: none;
578
+ -webkit-tap-highlight-color: transparent;
579
579
  }
580
580
 
581
581
  .moodboard-workspace {
@@ -741,6 +741,7 @@
741
741
  position: absolute;
742
742
  inset: 0;
743
743
  overflow: hidden;
744
+ touch-action: none;
744
745
  }
745
746
 
746
747
  /* Toolbar Styles */
@@ -777,9 +778,9 @@
777
778
  /* Frame popup (grid 2x2 + header row) */
778
779
  .moodboard-root, :root {
779
780
  /* CSS variables for frame style */
780
- --frame-border-color: #EEEEEE;
781
- --frame-border-width: 2; /* px */
782
- --frame-corner-radius: 3; /* px */
781
+ --frame-border-color: #C7CDD6;
782
+ --frame-border-width: 1; /* px */
783
+ --frame-corner-radius: 6; /* px */
783
784
  }
784
785
  .frame-popup {
785
786
  display: grid;
@@ -1002,27 +1003,27 @@
1002
1003
  /* Иконки фигур — рисуем CSSом */
1003
1004
  .moodboard-shapes__icon { display: inline-block; }
1004
1005
  .shape-square {
1005
- width: 15px; height: 15px; background: #1d4ed8; border-radius: 2px;
1006
+ width: 15px; height: 15px; background: #ffffff; border-radius: 2px; border: 1.5px solid #94a3b8;
1006
1007
  }
1007
1008
  .shape-rounded-square {
1008
- width: 15px; height: 15px; background: #1d4ed8; border-radius: 4px;
1009
+ width: 15px; height: 15px; background: #ffffff; border-radius: 4px; border: 1.5px solid #94a3b8;
1009
1010
  }
1010
1011
  .shape-circle {
1011
- width: 15px; height: 15px; background: #1d4ed8; border-radius: 50%;
1012
+ width: 15px; height: 15px; background: #ffffff; border-radius: 50%; border: 1.5px solid #94a3b8;
1012
1013
  }
1013
1014
  .shape-triangle {
1014
1015
  width: 0; height: 0;
1015
1016
  border-left: 8px solid transparent;
1016
1017
  border-right: 8px solid transparent;
1017
- border-bottom: 14px solid #1d4ed8;
1018
+ border-bottom: 14px solid #94a3b8;
1018
1019
  }
1019
1020
  .shape-diamond {
1020
- width: 11px; height: 11px; background: #1d4ed8; transform: rotate(45deg);
1021
+ width: 11px; height: 11px; background: #ffffff; border: 1.5px solid #94a3b8; transform: rotate(45deg);
1021
1022
  }
1022
1023
  .shape-parallelogram {
1023
- width: 17px; height: 11px; background: #1d4ed8; transform: skewX(-20deg);
1024
+ width: 17px; height: 11px; background: #ffffff; border: 1.5px solid #94a3b8; transform: skewX(-20deg);
1024
1025
  }
1025
- .shape-arrow { font-size: 16px; color: #1d4ed8; line-height: 1; }
1026
+ .shape-arrow { font-size: 16px; color: #94a3b8; line-height: 1; }
1026
1027
 
1027
1028
 
1028
1029
 
@@ -1300,4 +1301,65 @@
1300
1301
  line-height: inherit;
1301
1302
  }
1302
1303
 
1304
+ @media (pointer: coarse) {
1305
+ .mb-handle::before,
1306
+ .mb-edge::before {
1307
+ content: '';
1308
+ position: absolute;
1309
+ inset: -16px;
1310
+ pointer-events: auto;
1311
+ }
1312
+
1313
+ .mb-rotate-handle::before {
1314
+ content: '';
1315
+ position: absolute;
1316
+ inset: -12px;
1317
+ pointer-events: auto;
1318
+ }
1319
+
1320
+ .mb-rotate-handle {
1321
+ transform: translate(-16px, 16px) !important;
1322
+ }
1323
+
1324
+ .mb-edge[data-edge="top"], .mb-edge[data-edge="bottom"] {
1325
+ left: 50% !important;
1326
+ transform: translateX(-50%) !important;
1327
+ width: max(32px, calc(var(--box-w) - 44px)) !important;
1328
+ z-index: 11 !important;
1329
+ }
1330
+ .mb-edge[data-edge="left"], .mb-edge[data-edge="right"] {
1331
+ top: 50% !important;
1332
+ transform: translateY(-50%) !important;
1333
+ height: max(32px, calc(var(--box-h) - 44px)) !important;
1334
+ z-index: 11 !important;
1335
+ }
1336
+
1337
+ .mb-handle[data-dir="nw"]::before, .mb-handle[data-dir="sw"]::before {
1338
+ right: max(-16px, calc(22px - var(--box-w) / 2));
1339
+ }
1340
+ .mb-handle[data-dir="ne"]::before, .mb-handle[data-dir="se"]::before {
1341
+ left: max(-16px, calc(22px - var(--box-w) / 2));
1342
+ }
1343
+ .mb-handle[data-dir="nw"]::before, .mb-handle[data-dir="ne"]::before {
1344
+ bottom: max(-16px, calc(22px - var(--box-h) / 2));
1345
+ }
1346
+ .mb-handle[data-dir="sw"]::before, .mb-handle[data-dir="se"]::before {
1347
+ top: max(-16px, calc(22px - var(--box-h) / 2));
1348
+ }
1349
+
1350
+ .moodboard-zoombar__button {
1351
+ width: 44px;
1352
+ height: 44px;
1353
+ }
1354
+
1355
+ .moodboard-mapbar__button {
1356
+ width: 44px;
1357
+ height: 44px;
1358
+ }
1359
+
1360
+ .mb-mindmap-side-btn {
1361
+ width: 44px;
1362
+ height: 44px;
1363
+ }
1364
+ }
1303
1365
 
@@ -138,7 +138,7 @@ export class ToolbarPopupsController {
138
138
  } else {
139
139
  icon.className = `moodboard-shapes__icon shape-${s.id}`;
140
140
  if (s.id === 'arrow') {
141
- icon.innerHTML = '<svg width="18" height="12" viewBox="0 0 18 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect x="0" y="5" width="12" height="2" rx="1" fill="#1d4ed8"/><path d="M12 0 L18 6 L12 12 Z" fill="#1d4ed8"/></svg>';
141
+ icon.innerHTML = '<svg width="18" height="12" viewBox="0 0 18 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect x="0" y="5" width="12" height="2" rx="1" fill="#94a3b8"/><path d="M12 0 L18 6 L12 12 Z" fill="#94a3b8"/></svg>';
142
142
  }
143
143
  }
144
144
  btn.appendChild(icon);