@sequent-org/moodboard 1.4.31 → 1.4.33

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 (139) hide show
  1. package/package.json +5 -1
  2. package/src/assets/fonts/inter/inter-cyrillic-400-normal.woff2 +0 -0
  3. package/src/assets/fonts/inter/inter-cyrillic-500-normal.woff2 +0 -0
  4. package/src/assets/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  5. package/src/assets/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  6. package/src/assets/icons/attachments.svg +3 -1
  7. package/src/assets/icons/comments.svg +2 -2
  8. package/src/assets/icons/connector.svg +6 -0
  9. package/src/assets/icons/emoji.svg +6 -1
  10. package/src/assets/icons/frame.svg +4 -1
  11. package/src/assets/icons/image.svg +5 -1
  12. package/src/assets/icons/laser.svg +1 -0
  13. package/src/assets/icons/lasso.svg +5 -0
  14. package/src/assets/icons/mindmap.svg +10 -2
  15. package/src/assets/icons/note.svg +4 -1
  16. package/src/assets/icons/pan.svg +5 -2
  17. package/src/assets/icons/pencil.svg +4 -1
  18. package/src/assets/icons/reactions.svg +5 -0
  19. package/src/assets/icons/redo.svg +3 -2
  20. package/src/assets/icons/select.svg +2 -8
  21. package/src/assets/icons/shapes.svg +5 -1
  22. package/src/assets/icons/text-add.svg +15 -1
  23. package/src/assets/icons/undo.svg +3 -2
  24. package/src/assets/reactions/1f44d.svg +20 -0
  25. package/src/assets/reactions/1f44e.svg +20 -0
  26. package/src/assets/reactions/2705.svg +20 -0
  27. package/src/assets/reactions/274c.svg +19 -0
  28. package/src/assets/reactions/2753.svg +20 -0
  29. package/src/assets/reactions/2764.svg +22 -0
  30. package/src/assets/reactions/2b50.svg +19 -0
  31. package/src/assets/reactions/plus-one.svg +25 -0
  32. package/src/core/PixiEngine.js +23 -0
  33. package/src/core/bootstrap/CoreInitializer.js +43 -0
  34. package/src/core/commands/GroupDeleteCommand.js +13 -1
  35. package/src/core/commands/UpdateShapeStyleCommand.js +121 -0
  36. package/src/core/commands/UpdateTextStyleCommand.js +17 -6
  37. package/src/core/commands/index.js +3 -0
  38. package/src/core/events/Events.js +22 -0
  39. package/src/core/flows/LayerAndViewportFlow.js +1 -0
  40. package/src/core/flows/ObjectLifecycleFlow.js +155 -7
  41. package/src/core/index.js +28 -1
  42. package/src/grid/CrossGridZoomPhases.js +3 -3
  43. package/src/initNoBundler.js +1 -1
  44. package/src/moodboard/DataManager.js +28 -0
  45. package/src/moodboard/MoodBoard.js +27 -0
  46. package/src/moodboard/bootstrap/MoodBoardInitializer.js +69 -1
  47. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +22 -4
  48. package/src/moodboard/integration/MoodBoardEventBindings.js +5 -1
  49. package/src/moodboard/integration/MoodBoardLoadApi.js +10 -1
  50. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +9 -0
  51. package/src/objects/ConnectorObject.js +2 -2
  52. package/src/objects/FrameObject.js +119 -59
  53. package/src/objects/ShapeObject.js +49 -74
  54. package/src/objects/shape/ShapeDrawer.js +210 -0
  55. package/src/services/ConnectorBindingResolver.js +112 -0
  56. package/src/services/ConnectorRouter.js +210 -0
  57. package/src/services/comments/CommentService.js +344 -0
  58. package/src/tools/object-tools/CommentTool.js +85 -0
  59. package/src/tools/object-tools/DrawingTool.js +110 -10
  60. package/src/tools/object-tools/LaserPointerTool.js +121 -0
  61. package/src/tools/object-tools/SelectTool.js +25 -1
  62. package/src/tools/object-tools/TextTool.js +6 -1
  63. package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
  64. package/src/tools/object-tools/connector/connectorGesture.js +33 -19
  65. package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
  66. package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
  67. package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
  68. package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
  69. package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
  70. package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
  71. package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
  72. package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
  73. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
  74. package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
  75. package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
  76. package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
  77. package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
  78. package/src/ui/CommentPopover.js +6 -0
  79. package/src/ui/CommentsBar.js +91 -0
  80. package/src/ui/ConnectorPropertiesPanel.js +150 -0
  81. package/src/ui/ContextMenu.js +25 -0
  82. package/src/ui/DrawingPropertiesPanel.js +362 -0
  83. package/src/ui/FilePropertiesPanel.js +5 -0
  84. package/src/ui/FramePropertiesPanel.js +5 -0
  85. package/src/ui/HtmlTextLayer.js +246 -66
  86. package/src/ui/NotePropertiesPanel.js +6 -0
  87. package/src/ui/ShapePropertiesPanel.js +307 -0
  88. package/src/ui/TextPropertiesPanel.js +100 -1
  89. package/src/ui/Toolbar.js +25 -2
  90. package/src/ui/Topbar.js +2 -2
  91. package/src/ui/animation/HoverLiftController.js +6 -7
  92. package/src/ui/chat/ChatComposer.js +59 -12
  93. package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
  94. package/src/ui/chat/ChatWindow.js +60 -144
  95. package/src/ui/chat/ChatWindowRenderer.js +1 -8
  96. package/src/ui/chat/icons.js +0 -4
  97. package/src/ui/comments/CommentListPanel.js +213 -0
  98. package/src/ui/comments/CommentPinLayer.js +448 -0
  99. package/src/ui/comments/CommentThreadPopover.js +539 -0
  100. package/src/ui/comments/commentFormat.js +32 -0
  101. package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
  102. package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
  103. package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
  104. package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
  105. package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
  106. package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
  107. package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
  108. package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
  109. package/src/ui/connectors/ConnectorLayer.js +264 -57
  110. package/src/ui/handles/HandlesDomRenderer.js +5 -13
  111. package/src/ui/handles/HandlesEventBridge.js +1 -0
  112. package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
  113. package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
  114. package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
  115. package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
  116. package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
  117. package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
  118. package/src/ui/styles/chat.css +682 -28
  119. package/src/ui/styles/index.css +1 -0
  120. package/src/ui/styles/panels.css +112 -2
  121. package/src/ui/styles/shape-properties-panel.css +250 -0
  122. package/src/ui/styles/toolbar.css +7 -2
  123. package/src/ui/styles/topbar.css +1 -1
  124. package/src/ui/styles/workspace.css +257 -6
  125. package/src/ui/text-properties/TextFormatControls.js +88 -0
  126. package/src/ui/text-properties/TextListRenderer.js +137 -0
  127. package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
  128. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
  129. package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
  130. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
  131. package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
  132. package/src/ui/toolbar/ReactionsPopupController.js +88 -0
  133. package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
  134. package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
  135. package/src/ui/toolbar/ToolbarRenderer.js +9 -1
  136. package/src/ui/toolbar/ToolbarStateController.js +4 -1
  137. package/src/utils/iconLoader.js +17 -16
  138. package/src/utils/markdown.js +14 -0
  139. package/src/utils/richText.js +125 -0
@@ -0,0 +1,307 @@
1
+ import { Events } from '../core/events/Events.js';
2
+ import {
3
+ sep,
4
+ buildShapeGroup,
5
+ buildFillGroup,
6
+ buildBorderGroup,
7
+ buildRadiusGroup,
8
+ buildTextGroup,
9
+ } from './shape-properties/ShapePropertiesPanelDom.js';
10
+ import {
11
+ updateControlsFromObject,
12
+ updateBorderStyleBtns,
13
+ setAlign,
14
+ syncSwatches,
15
+ pixiToHex,
16
+ } from './shape-properties/ShapePropertiesPanelSync.js';
17
+ import './styles/shape-properties-panel.css';
18
+
19
+ /**
20
+ * ShapePropertiesPanel — плавающая панель свойств выделенной фигуры.
21
+ * Показывается только при одиночном выделении объекта типа 'shape'.
22
+ * Все изменения эмитятся через Events.Object.StateChanged.
23
+ * Undo/redo обеспечивает UpdateShapeStyleCommand (фаза 2).
24
+ */
25
+ export class ShapePropertiesPanel {
26
+ constructor(eventBus, container, core = null) {
27
+ this.eventBus = eventBus;
28
+ this.container = container;
29
+ this.core = core;
30
+ this.panel = null;
31
+ this.currentId = null;
32
+
33
+ // Единственный открытый поповер (только один одновременно)
34
+ this._openPopoverEl = null;
35
+ this._boundDocClick = this._onDocumentClick.bind(this);
36
+
37
+ this._attachEvents();
38
+ this._createPanel();
39
+ }
40
+
41
+ // ── Публичное API ──────────────────────────────────────────────────────────
42
+
43
+ updateFromSelection() {
44
+ const ids = this.core?.selectTool
45
+ ? Array.from(this.core.selectTool.selectedObjects || [])
46
+ : [];
47
+
48
+ if (!ids || ids.length !== 1) { this.hide(); return; }
49
+
50
+ const id = ids[0];
51
+ if (this.currentId === id && this.panel && this.panel.style.display !== 'none') return;
52
+
53
+ const pixi = this.core?.pixi?.objects?.get ? this.core.pixi.objects.get(id) : null;
54
+ const isShape = !!(pixi && pixi._mb && pixi._mb.type === 'shape');
55
+
56
+ if (isShape) { this.showFor(id); } else { this.hide(); }
57
+ }
58
+
59
+ showFor(objectId) {
60
+ this.currentId = objectId;
61
+ if (this.panel) {
62
+ this.panel.style.display = 'flex';
63
+ this.reposition();
64
+ this._updateControlsFromObject();
65
+ }
66
+ }
67
+
68
+ hide() {
69
+ this.currentId = null;
70
+ if (this.panel) this.panel.style.display = 'none';
71
+ this._closePopover();
72
+ }
73
+
74
+ reposition() {
75
+ if (!this.panel || !this.currentId || this.panel.style.display === 'none') return;
76
+
77
+ const ids = this.core?.selectTool
78
+ ? Array.from(this.core.selectTool.selectedObjects || [])
79
+ : [];
80
+ if (!ids.includes(this.currentId)) { this.hide(); return; }
81
+
82
+ const posData = { objectId: this.currentId, position: null };
83
+ const sizeData = { objectId: this.currentId, size: null };
84
+ this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
85
+ this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
86
+ if (!posData.position || !sizeData.size) return;
87
+
88
+ const worldLayer = this.core?.pixi?.worldLayer;
89
+ const scale = worldLayer?.scale?.x || 1;
90
+ const worldX = worldLayer?.x || 0;
91
+ const worldY = worldLayer?.y || 0;
92
+
93
+ const screenX = posData.position.x * scale + worldX;
94
+ const screenY = posData.position.y * scale + worldY;
95
+ const objectWidth = sizeData.size.width * scale;
96
+ const objectHeight = sizeData.size.height * scale;
97
+
98
+ const panelW = this.panel.offsetWidth || 480;
99
+ const panelH = this.panel.offsetHeight || 80;
100
+ let panelX = screenX + (objectWidth / 2) - (panelW / 2);
101
+ let panelY = screenY - panelH - 16;
102
+
103
+ if (panelY < 0) panelY = screenY + objectHeight + 16;
104
+
105
+ const cw = this.container.offsetWidth || window.innerWidth;
106
+ panelX = Math.max(8, Math.min(panelX, cw - panelW - 8));
107
+ panelY = Math.max(8, panelY);
108
+
109
+ this.panel.style.left = `${Math.round(panelX)}px`;
110
+ this.panel.style.top = `${Math.round(panelY)}px`;
111
+ }
112
+
113
+ destroy() {
114
+ this._closePopover();
115
+ this._cancelRaf();
116
+
117
+ if (this._handlers && this.eventBus?.off) {
118
+ const H = this._handlers;
119
+ this.eventBus.off(Events.Tool.SelectionAdd, H.onSelectionAdd);
120
+ this.eventBus.off(Events.Tool.SelectionRemove, H.onSelectionRemove);
121
+ this.eventBus.off(Events.Tool.SelectionClear, H.onSelectionClear);
122
+ this.eventBus.off(Events.Object.Deleted, H.onDeleted);
123
+ this.eventBus.off(Events.Tool.DragStart, H.onDragStart);
124
+ this.eventBus.off(Events.Tool.DragUpdate, H.onDragUpdate);
125
+ this.eventBus.off(Events.Tool.DragEnd, H.onDragEnd);
126
+ this.eventBus.off(Events.Tool.GroupDragStart, H.onGroupDragStart);
127
+ this.eventBus.off(Events.Tool.GroupDragUpdate, H.onGroupDragUpdate);
128
+ this.eventBus.off(Events.Tool.GroupDragEnd, H.onGroupDragEnd);
129
+ this.eventBus.off(Events.Tool.ResizeUpdate, H.onResizeUpdate);
130
+ this.eventBus.off(Events.Tool.RotateUpdate, H.onRotateUpdate);
131
+ this.eventBus.off(Events.UI.ZoomPercent, H.onZoom);
132
+ this.eventBus.off(Events.Tool.PanUpdate, H.onPan);
133
+ this.eventBus.off(Events.Viewport.Changed, H.onViewportChanged);
134
+ this.eventBus.off(Events.Tool.Activated, H.onActivated);
135
+ this.eventBus.off(Events.Object.StateChanged, H.onStateChanged);
136
+ this.eventBus.off(Events.History.Changed, H.onHistoryChanged);
137
+ this.eventBus.off(Events.Object.TransformUpdated, H.onTransformUpdated);
138
+ this._handlers = null;
139
+ }
140
+ document.removeEventListener('click', this._boundDocClick);
141
+
142
+ if (this.panel?.parentNode) this.panel.parentNode.removeChild(this.panel);
143
+ this.panel = null;
144
+ this.currentId = null;
145
+ }
146
+
147
+ // ── Делегаты для подмодуля Sync ────────────────────────────────────────────
148
+
149
+ _updateControlsFromObject() { updateControlsFromObject(this); }
150
+ _updateBorderStyleBtns(v) { updateBorderStyleBtns(this, v); }
151
+ _setAlign(v) { setAlign(this, v); }
152
+ _syncSwatches(key, hex) { syncSwatches(this[key], hex); }
153
+ _pixiToHex(pixi) { return pixiToHex(pixi); }
154
+
155
+ // ── Подписки ───────────────────────────────────────────────────────────────
156
+
157
+ _attachEvents() {
158
+ const H = this._handlers = {};
159
+
160
+ H.onSelectionAdd = () => this.updateFromSelection();
161
+ H.onSelectionRemove = () => this.updateFromSelection();
162
+ H.onSelectionClear = () => this.hide();
163
+ H.onDeleted = (objectId) => {
164
+ if (this.currentId && objectId === this.currentId) this.hide();
165
+ };
166
+ H.onDragStart = () => this.hide();
167
+ H.onDragUpdate = () => this._repositionThrottled();
168
+ H.onDragEnd = () => this.updateFromSelection();
169
+ H.onGroupDragStart = () => this.hide();
170
+ H.onGroupDragUpdate = () => this._repositionThrottled();
171
+ H.onGroupDragEnd = () => this.updateFromSelection();
172
+ H.onResizeUpdate = () => this._repositionThrottled();
173
+ H.onRotateUpdate = () => this._repositionThrottled();
174
+ H.onZoom = () => { if (this.currentId) this._repositionThrottled(); };
175
+ H.onPan = () => { if (this.currentId) this._repositionThrottled(); };
176
+ H.onViewportChanged = () => { if (this.currentId) this._repositionThrottled(); };
177
+ H.onActivated = ({ tool }) => { if (tool !== 'select') this.hide(); };
178
+ H.onStateChanged = ({ objectId }) => {
179
+ if (this.currentId && objectId === this.currentId &&
180
+ this.panel?.style.display !== 'none') {
181
+ this._updateControlsFromObject();
182
+ }
183
+ };
184
+ H.onHistoryChanged = () => {
185
+ if (this.currentId && this.panel?.style.display !== 'none') {
186
+ this._updateControlsFromObject();
187
+ }
188
+ };
189
+ H.onTransformUpdated = ({ objectId }) => {
190
+ if (this.currentId && objectId === this.currentId &&
191
+ this.panel?.style.display !== 'none') {
192
+ this._repositionThrottled();
193
+ }
194
+ };
195
+
196
+ this.eventBus.on(Events.Tool.SelectionAdd, H.onSelectionAdd);
197
+ this.eventBus.on(Events.Tool.SelectionRemove, H.onSelectionRemove);
198
+ this.eventBus.on(Events.Tool.SelectionClear, H.onSelectionClear);
199
+ this.eventBus.on(Events.Object.Deleted, H.onDeleted);
200
+ this.eventBus.on(Events.Tool.DragStart, H.onDragStart);
201
+ this.eventBus.on(Events.Tool.DragUpdate, H.onDragUpdate);
202
+ this.eventBus.on(Events.Tool.DragEnd, H.onDragEnd);
203
+ this.eventBus.on(Events.Tool.GroupDragStart, H.onGroupDragStart);
204
+ this.eventBus.on(Events.Tool.GroupDragUpdate, H.onGroupDragUpdate);
205
+ this.eventBus.on(Events.Tool.GroupDragEnd, H.onGroupDragEnd);
206
+ this.eventBus.on(Events.Tool.ResizeUpdate, H.onResizeUpdate);
207
+ this.eventBus.on(Events.Tool.RotateUpdate, H.onRotateUpdate);
208
+ this.eventBus.on(Events.UI.ZoomPercent, H.onZoom);
209
+ this.eventBus.on(Events.Tool.PanUpdate, H.onPan);
210
+ this.eventBus.on(Events.Viewport.Changed, H.onViewportChanged);
211
+ this.eventBus.on(Events.Tool.Activated, H.onActivated);
212
+ this.eventBus.on(Events.Object.StateChanged, H.onStateChanged);
213
+ this.eventBus.on(Events.History.Changed, H.onHistoryChanged);
214
+ this.eventBus.on(Events.Object.TransformUpdated, H.onTransformUpdated);
215
+ }
216
+
217
+ // ── Построение DOM ─────────────────────────────────────────────────────────
218
+
219
+ _createPanel() {
220
+ const panel = document.createElement('div');
221
+ panel.className = 'shape-properties-panel';
222
+ panel.id = 'shape-properties-panel';
223
+
224
+ // Строка 1: форма, заливка, рамка, фаска
225
+ const row1 = document.createElement('div');
226
+ row1.className = 'spp-row';
227
+ row1.appendChild(buildShapeGroup(this));
228
+ row1.appendChild(sep());
229
+ const [fillLabel, fillWrap] = buildFillGroup(this);
230
+ row1.appendChild(fillLabel);
231
+ row1.appendChild(fillWrap);
232
+ row1.appendChild(sep());
233
+ row1.appendChild(buildBorderGroup(this));
234
+ row1.appendChild(sep());
235
+ const [rLabel, rGroup] = buildRadiusGroup(this);
236
+ row1.appendChild(rLabel);
237
+ row1.appendChild(rGroup);
238
+ panel.appendChild(row1);
239
+
240
+ // Строка 2: текстовые свойства
241
+ const row2 = document.createElement('div');
242
+ row2.className = 'spp-row';
243
+ buildTextGroup(this).forEach(n => row2.appendChild(n));
244
+ panel.appendChild(row2);
245
+
246
+ this.panel = panel;
247
+ this.container.appendChild(panel);
248
+ }
249
+
250
+ // ── Поповеры ───────────────────────────────────────────────────────────────
251
+
252
+ _togglePopover(popoverEl) {
253
+ if (this._openPopoverEl === popoverEl) {
254
+ this._closePopover();
255
+ } else {
256
+ this._closePopover();
257
+ popoverEl.style.display = 'block';
258
+ this._openPopoverEl = popoverEl;
259
+ setTimeout(() => document.addEventListener('click', this._boundDocClick), 0);
260
+ }
261
+ }
262
+
263
+ _closePopover() {
264
+ if (this._openPopoverEl) {
265
+ this._openPopoverEl.style.display = 'none';
266
+ this._openPopoverEl = null;
267
+ }
268
+ document.removeEventListener('click', this._boundDocClick);
269
+ }
270
+
271
+ _onDocumentClick(e) {
272
+ if (!this._openPopoverEl || !e.target) return;
273
+ if (!this._openPopoverEl.contains(e.target) && !this.panel.contains(e.target)) {
274
+ this._closePopover();
275
+ }
276
+ }
277
+
278
+ // ── Emit helper ────────────────────────────────────────────────────────────
279
+
280
+ _emit(payload) {
281
+ if (!this.currentId) return;
282
+ this.eventBus.emit(Events.Object.StateChanged, {
283
+ objectId: this.currentId,
284
+ ...payload,
285
+ });
286
+ }
287
+
288
+ // ── RAF-позиционирование ───────────────────────────────────────────────────
289
+
290
+ _repositionThrottled() {
291
+ if (this._repositionScheduled) return;
292
+ this._repositionScheduled = true;
293
+ this._repositionRafId = requestAnimationFrame(() => {
294
+ this._repositionScheduled = false;
295
+ this._repositionRafId = null;
296
+ if (this.panel) this.reposition();
297
+ });
298
+ }
299
+
300
+ _cancelRaf() {
301
+ if (this._repositionRafId != null) {
302
+ cancelAnimationFrame(this._repositionRafId);
303
+ this._repositionRafId = null;
304
+ }
305
+ this._repositionScheduled = false;
306
+ }
307
+ }
@@ -12,12 +12,14 @@ import {
12
12
  buildBackgroundColorUpdate,
13
13
  buildFontFamilyUpdate,
14
14
  buildFontSizeUpdate,
15
+ buildMarkdownUpdate,
15
16
  buildTextColorUpdate,
16
17
  getControlValuesFromProperties,
17
18
  getFallbackControlValues,
18
19
  getObjectGeometry,
19
20
  getObjectProperties,
20
21
  getSelectedTextObjectId,
22
+ LINE_HEIGHT_DEFAULT,
21
23
  syncPixiTextProperties,
22
24
  } from './text-properties/TextPropertiesPanelMapper.js';
23
25
  import {
@@ -76,7 +78,6 @@ export class TextPropertiesPanel {
76
78
 
77
79
  updateFromSelection() {
78
80
  if (this.isTextEditing) {
79
- this.hide();
80
81
  return;
81
82
  }
82
83
 
@@ -213,6 +214,80 @@ export class TextPropertiesPanel {
213
214
  this._updateTextAppearance(this.currentId, { backgroundColor });
214
215
  }
215
216
 
217
+ _toggleFormat(prop) {
218
+ if (!this.currentId) {
219
+ return;
220
+ }
221
+
222
+ const properties = getObjectProperties(this.eventBus, this.currentId);
223
+ const newValue = !(properties ? Boolean(properties[prop]) : false);
224
+
225
+ this.eventBus.emit(Events.Object.StateChanged, {
226
+ objectId: this.currentId,
227
+ updates: { properties: { [prop]: newValue } },
228
+ });
229
+ this._updateTextAppearance(this.currentId, { [prop]: newValue });
230
+
231
+ const btnMap = {
232
+ bold: this.boldBtn,
233
+ italic: this.italicBtn,
234
+ underline: this.underlineBtn,
235
+ strikethrough: this.strikethroughBtn,
236
+ };
237
+ if (btnMap[prop]) {
238
+ btnMap[prop].classList.toggle('is-active', newValue);
239
+ }
240
+ }
241
+
242
+ _changeTextAlign(v) {
243
+ if (!this.currentId) {
244
+ return;
245
+ }
246
+
247
+ this.eventBus.emit(Events.Object.StateChanged, {
248
+ objectId: this.currentId,
249
+ updates: { properties: { textAlign: v } },
250
+ });
251
+ this._updateTextAppearance(this.currentId, { textAlign: v });
252
+ }
253
+
254
+ _changeListType(v) {
255
+ if (!this.currentId) {
256
+ return;
257
+ }
258
+
259
+ this.eventBus.emit(Events.Object.StateChanged, {
260
+ objectId: this.currentId,
261
+ updates: { properties: { listType: v } },
262
+ });
263
+ this._updateTextAppearance(this.currentId, { listType: v });
264
+ }
265
+
266
+ _changeLineHeight(n) {
267
+ if (!this.currentId) {
268
+ return;
269
+ }
270
+
271
+ this.eventBus.emit(Events.Object.StateChanged, {
272
+ objectId: this.currentId,
273
+ updates: { properties: { lineHeight: n } },
274
+ });
275
+ this._updateTextAppearance(this.currentId, { lineHeight: n });
276
+ }
277
+
278
+ _changeMarkdown(markdown) {
279
+ if (!this.currentId) {
280
+ return;
281
+ }
282
+
283
+ this.eventBus.emit(Events.Object.StateChanged, {
284
+ objectId: this.currentId,
285
+ updates: buildMarkdownUpdate(markdown),
286
+ });
287
+
288
+ this._updateTextAppearance(this.currentId, { markdown });
289
+ }
290
+
216
291
  _updateTextAppearance(objectId, properties) {
217
292
  applyTextAppearanceToDom(objectId, properties);
218
293
  syncPixiTextProperties(this.eventBus, objectId, properties);
@@ -236,6 +311,19 @@ export class TextPropertiesPanel {
236
311
  this.fontSizeSelect.value = values.fontSize;
237
312
  this._updateCurrentColorButton(values.color);
238
313
  this._updateCurrentBgColorButton(values.backgroundColor);
314
+ if (this.markdownToggle) {
315
+ this.markdownToggle.checked = values.markdown;
316
+ }
317
+
318
+ if (this.boldBtn) this.boldBtn.classList.toggle('is-active', values.bold);
319
+ if (this.italicBtn) this.italicBtn.classList.toggle('is-active', values.italic);
320
+ if (this.underlineBtn) this.underlineBtn.classList.toggle('is-active', values.underline);
321
+ if (this.strikethroughBtn) this.strikethroughBtn.classList.toggle('is-active', values.strikethrough);
322
+ if (this.alignControl) this.alignControl.value = values.textAlign;
323
+ if (this.listControl) this.listControl.value = values.listType;
324
+ if (this.lineHeightSlider) {
325
+ this.lineHeightSlider.value = String(values.lineHeight !== null ? values.lineHeight : LINE_HEIGHT_DEFAULT);
326
+ }
239
327
  }
240
328
 
241
329
  reposition() {
@@ -277,6 +365,17 @@ export class TextPropertiesPanel {
277
365
  return;
278
366
  }
279
367
 
368
+ if (typeof event.target.closest === 'function' && event.target.closest('.moodboard-text-editor')) {
369
+ return;
370
+ }
371
+
372
+ // Клики внутри canvas-контейнера управляются через EventBus (SelectionClear).
373
+ // Без этой проверки тот же mousedown, который вызвал SelectionAdd → showFor,
374
+ // немедленно дотекает до capture-listener и закрывает только что открытую панель.
375
+ if (this.container.contains(event.target)) {
376
+ return;
377
+ }
378
+
280
379
  this.container.getBoundingClientRect();
281
380
  this.hide();
282
381
  }
package/src/ui/Toolbar.js CHANGED
@@ -6,6 +6,7 @@ import { IconLoader } from '../utils/iconLoader.js';
6
6
  import { ToolbarDialogsController } from './toolbar/ToolbarDialogsController.js';
7
7
  import { ToolbarPopupsController } from './toolbar/ToolbarPopupsController.js';
8
8
  import { ToolbarActionRouter } from './toolbar/ToolbarActionRouter.js';
9
+ import { ReactionsPopupController } from './toolbar/ReactionsPopupController.js';
9
10
  import { ToolbarTooltipController } from './toolbar/ToolbarTooltipController.js';
10
11
  import { ToolbarStateController } from './toolbar/ToolbarStateController.js';
11
12
  import { ToolbarRenderer } from './toolbar/ToolbarRenderer.js';
@@ -27,6 +28,7 @@ export class Toolbar {
27
28
 
28
29
  this.dialogsController = new ToolbarDialogsController(this);
29
30
  this.popupsController = new ToolbarPopupsController(this);
31
+ this.reactionsController = new ReactionsPopupController(this);
30
32
  this.actionRouter = new ToolbarActionRouter(this);
31
33
  this.tooltipController = new ToolbarTooltipController(this);
32
34
  this.stateController = new ToolbarStateController(this);
@@ -48,7 +50,6 @@ export class Toolbar {
48
50
 
49
51
  this._toolActivatedHandler = ({ tool }) => {
50
52
  this.setActiveToolbarButton(tool);
51
- // Draw palette must stay open only while draw tool is active.
52
53
  if (tool !== 'draw') {
53
54
  this.closeDrawPopup();
54
55
  }
@@ -135,18 +136,21 @@ export class Toolbar {
135
136
  const isInsideDrawPopup = this.drawPopupEl && this.drawPopupEl.contains(e.target);
136
137
  const isInsideEmojiPopup = this.emojiPopupEl && this.emojiPopupEl.contains(e.target);
137
138
  const isInsideFramePopup = this.framePopupEl && this.framePopupEl.contains(e.target);
139
+ const isInsideReactionsPopup = this.reactionsPopupEl && this.reactionsPopupEl.contains(e.target);
138
140
  const isShapesButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--shapes');
139
141
  const isDrawButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--pencil');
140
142
  const isEmojiButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--emoji');
141
143
  const isFrameButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--frame');
144
+ const isReactionsButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--reactions');
142
145
  const isDrawActive = !!(this.element && this.element.querySelector('.moodboard-toolbar__button--pencil.moodboard-toolbar__button--active'));
143
146
 
144
- if (!isInsideToolbar && !isInsideShapesPopup && !isShapesButton && !isInsideDrawPopup && !isDrawButton && !isInsideEmojiPopup && !isEmojiButton && !isInsideFramePopup && !isFrameButton) {
147
+ if (!isInsideToolbar && !isInsideShapesPopup && !isShapesButton && !isInsideDrawPopup && !isDrawButton && !isInsideEmojiPopup && !isEmojiButton && !isInsideFramePopup && !isFrameButton && !isInsideReactionsPopup && !isReactionsButton) {
145
148
  this.closeShapesPopup();
146
149
  if (!isDrawActive) {
147
150
  this.closeDrawPopup();
148
151
  }
149
152
  this.closeEmojiPopup();
153
+ this.closeReactionsPopup();
150
154
  this.closeFramePopup();
151
155
  }
152
156
  };
@@ -251,6 +255,25 @@ export class Toolbar {
251
255
  return this.popupsController.closeEmojiPopup();
252
256
  }
253
257
 
258
+ /**
259
+ * Всплывающая панель реакций (UI)
260
+ */
261
+ createReactionsPopup() {
262
+ return this.reactionsController.createReactionsPopup();
263
+ }
264
+
265
+ toggleReactionsPopup(anchorButton) {
266
+ return this.reactionsController.toggleReactionsPopup(anchorButton);
267
+ }
268
+
269
+ openReactionsPopup(anchorButton) {
270
+ return this.reactionsController.openReactionsPopup(anchorButton);
271
+ }
272
+
273
+ closeReactionsPopup() {
274
+ return this.reactionsController.closeReactionsPopup();
275
+ }
276
+
254
277
  /**
255
278
  * Показывает диалог подтверждения очистки холста
256
279
  */
package/src/ui/Topbar.js CHANGED
@@ -16,7 +16,7 @@ export class Topbar {
16
16
  this.icons = this.iconLoader.icons;
17
17
  // Палитра кнопки заливки и соответствие цвету фона доски
18
18
  this._palette = [
19
- { id: 1, name: 'default-light', btnHex: '#B3E5FC', board: '#f7fbff' },
19
+ { id: 1, name: 'default-light', btnHex: '#d6e8f7', board: '#f0f6fc' },
20
20
  { id: 2, name: 'mint-light', btnHex: '#E8F5E9', board: '#f8fff7' },
21
21
  { id: 3, name: 'peach-light', btnHex: '#FFF3E0', board: '#fffcf7' },
22
22
  { id: 4, name: 'gray-light', btnHex: '#f5f5f5', board: '#f5f5f5' },
@@ -246,7 +246,7 @@ export class Topbar {
246
246
  const pop = document.createElement('div');
247
247
  pop.className = 'moodboard-topbar__paint-popover';
248
248
  // Пять цветов кнопок-палитры и соответствующие цвета фона доски
249
- // 1: фон #f7fbff, кнопка #B3E5FC
249
+ // 1: фон #f0f6fc, кнопка #d6e8f7
250
250
  // 2: фон #f8fff7, кнопка #E8F5E9
251
251
  // 3: фон #fffcf7, кнопка #FFF3E0
252
252
  // 4: фон #f5f5f5, кнопка #f5f5f5
@@ -108,11 +108,12 @@ export class HoverLiftController {
108
108
  const hasStaticShadow = type === 'image' || type === 'frame';
109
109
  const restAlpha = hasStaticShadow ? IMAGE_REST_ALPHA : 0;
110
110
  const restDistance = hasStaticShadow ? IMAGE_REST_DISTANCE : 8;
111
- // Фрейм — крупный структурный контейнер. Hover-«pop» (scale + подъём)
112
- // на нём бессмысленен и создаёт видимый скачок в переходе hover→resize/drag:
113
- // объект приподнимается на hover, а в момент нажатия мгновенно
114
- // возвращается к базе. Оставляем фрейму только статичную тень, без подъёма.
115
- const liftEnabled = type !== 'frame';
111
+ // Hover-«pop» (scale + подъём) включён в т.ч. для фрейма. Скачок в
112
+ // переходе hover→resize/drag снимается мгновенным snapBack по событиям
113
+ // ResizeStart/DragStart/SelectionAdd (см. _snapBack* ниже): к моменту
114
+ // нажатия объект уже возвращён к базе. Логические габариты фрейма для
115
+ // resize считаются из state, а не из scaled-pixi, поэтому ресайз с
116
+ // координатной математикой hover не пересекается.
116
117
 
117
118
  const shadow = createShadowFilter(restAlpha, restDistance);
118
119
  pixiObject.filters = [...(pixiObject.filters || []), shadow];
@@ -129,8 +130,6 @@ export class HoverLiftController {
129
130
  };
130
131
  this._entries.set(pixiObject, entry);
131
132
 
132
- if (!liftEnabled) return;
133
-
134
133
  const w = objectData?.width ?? objectData?.properties?.width ?? 100;
135
134
  const h = objectData?.height ?? objectData?.properties?.height ?? 100;
136
135
  const preset = getPreset(w, h);