@sequent-org/moodboard 1.4.32 → 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 (136) 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 +58 -7
  93. package/src/ui/chat/ChatWindow.js +60 -143
  94. package/src/ui/comments/CommentListPanel.js +213 -0
  95. package/src/ui/comments/CommentPinLayer.js +448 -0
  96. package/src/ui/comments/CommentThreadPopover.js +539 -0
  97. package/src/ui/comments/commentFormat.js +32 -0
  98. package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
  99. package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
  100. package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
  101. package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
  102. package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
  103. package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
  104. package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
  105. package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
  106. package/src/ui/connectors/ConnectorLayer.js +264 -57
  107. package/src/ui/handles/HandlesDomRenderer.js +5 -13
  108. package/src/ui/handles/HandlesEventBridge.js +1 -0
  109. package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
  110. package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
  111. package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
  112. package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
  113. package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
  114. package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
  115. package/src/ui/styles/chat.css +709 -19
  116. package/src/ui/styles/index.css +1 -0
  117. package/src/ui/styles/panels.css +112 -2
  118. package/src/ui/styles/shape-properties-panel.css +250 -0
  119. package/src/ui/styles/toolbar.css +7 -2
  120. package/src/ui/styles/topbar.css +1 -1
  121. package/src/ui/styles/workspace.css +257 -6
  122. package/src/ui/text-properties/TextFormatControls.js +88 -0
  123. package/src/ui/text-properties/TextListRenderer.js +137 -0
  124. package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
  125. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
  126. package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
  127. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
  128. package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
  129. package/src/ui/toolbar/ReactionsPopupController.js +88 -0
  130. package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
  131. package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
  132. package/src/ui/toolbar/ToolbarRenderer.js +9 -1
  133. package/src/ui/toolbar/ToolbarStateController.js +4 -1
  134. package/src/utils/iconLoader.js +17 -16
  135. package/src/utils/markdown.js +14 -0
  136. package/src/utils/richText.js +125 -0
@@ -0,0 +1,223 @@
1
+ import {
2
+ hexToPixiColor,
3
+ buildStyleUpdate,
4
+ } from './ConnectorPropertiesPanelMapper.js';
5
+ import {
6
+ showStrokeDropdown,
7
+ hideStrokeDropdown,
8
+ showLabelColorDropdown,
9
+ hideLabelColorDropdown,
10
+ } from './ConnectorPropertiesPanelRenderer.js';
11
+ import { Events } from '../../core/events/Events.js';
12
+
13
+ export function bindConnectorPropertiesPanelControls(inst) {
14
+ if (inst._bindingsAttached) return;
15
+
16
+ // ── Цвет линии ──────────────────────────────────────────────────────────
17
+
18
+ inst.strokeColorButton.addEventListener('click', (e) => {
19
+ e.stopPropagation();
20
+ if (inst.strokeColorDropdown.style.display === 'none') {
21
+ showStrokeDropdown(inst);
22
+ } else {
23
+ hideStrokeDropdown(inst);
24
+ }
25
+ });
26
+
27
+ inst._strokePresetButtons.forEach(btn => {
28
+ btn.addEventListener('click', () => {
29
+ const pixi = parseInt(btn.dataset.pixiValue, 10);
30
+ inst._emitStyle({ stroke: pixi });
31
+ hideStrokeDropdown(inst);
32
+ });
33
+ });
34
+
35
+ inst.strokeColorInput.addEventListener('change', (e) => {
36
+ const pixi = hexToPixiColor(e.target.value);
37
+ inst._emitStyle({ stroke: pixi });
38
+ hideStrokeDropdown(inst);
39
+ });
40
+
41
+ inst._onStrokeDocumentClick = (e) => {
42
+ if (inst._strokeSelectorContainer && !inst._strokeSelectorContainer.contains(e.target)) {
43
+ hideStrokeDropdown(inst);
44
+ }
45
+ };
46
+ document.addEventListener('click', inst._onStrokeDocumentClick);
47
+
48
+ // ── Ширина ───────────────────────────────────────────────────────────────
49
+
50
+ inst.widthButtons.forEach(btn => {
51
+ btn.addEventListener('click', () => {
52
+ const w = parseInt(btn.dataset.width, 10);
53
+ inst._emitStyle({ width: w });
54
+ });
55
+ });
56
+
57
+ // ── Маршрут ──────────────────────────────────────────────────────────────
58
+
59
+ inst.routeSelect.addEventListener('change', (e) => {
60
+ inst._emitStyle({ route: e.target.value });
61
+ });
62
+
63
+ // ── Пунктир ──────────────────────────────────────────────────────────────
64
+
65
+ inst.dashButton.addEventListener('click', () => {
66
+ const connector = inst._getConnector();
67
+ const currentDash = connector?.properties?.style?.dash ?? false;
68
+ inst._emitStyle({ dash: !currentDash });
69
+ });
70
+
71
+ // ── Наконечник конец ─────────────────────────────────────────────────────
72
+
73
+ inst.headEndSelect.addEventListener('change', (e) => {
74
+ const connector = inst._getConnector();
75
+ const currentHead = connector?.properties?.style?.head ?? { start: 'none', end: 'arrow' };
76
+ inst._emitStyle({ head: { ...currentHead, end: e.target.value } });
77
+ });
78
+
79
+ // ── Наконечник начало ────────────────────────────────────────────────────
80
+
81
+ inst.headStartSelect.addEventListener('change', (e) => {
82
+ const connector = inst._getConnector();
83
+ const currentHead = connector?.properties?.style?.head ?? { start: 'none', end: 'arrow' };
84
+ inst._emitStyle({ head: { ...currentHead, start: e.target.value } });
85
+ });
86
+
87
+ // ── Разворот ─────────────────────────────────────────────────────────────
88
+
89
+ inst._swapBtn.addEventListener('click', () => {
90
+ if (!inst.currentId) return;
91
+ const connector = inst._getConnector();
92
+ if (!connector?.properties) return;
93
+
94
+ const oldStart = connector.properties.start;
95
+ const oldEnd = connector.properties.end;
96
+ const oldHead = connector.properties.style?.head ?? { start: 'none', end: 'arrow' };
97
+
98
+ inst.eventBus.emit(Events.Object.StateChanged, {
99
+ objectId: inst.currentId,
100
+ updates: {
101
+ properties: {
102
+ start: oldEnd,
103
+ end: oldStart,
104
+ style: {
105
+ ...connector.properties.style,
106
+ head: { start: oldHead.end, end: oldHead.start },
107
+ },
108
+ },
109
+ },
110
+ });
111
+ });
112
+
113
+ // ── Кнопка T+: создать / открыть редактор метки ──────────────────────────
114
+
115
+ inst._textBtn.addEventListener('click', () => {
116
+ if (!inst.currentId) return;
117
+ const connector = inst._getConnector();
118
+ if (!connector) return;
119
+
120
+ const existingLabel = connector.properties?.style?.label;
121
+ if (!existingLabel) {
122
+ // Инициализируем метку с пустым текстом
123
+ const strokeColor = connector.properties?.style?.stroke ?? 0x212121;
124
+ inst._emitLabel({ text: '', color: strokeColor, fontSize: 14 });
125
+ }
126
+
127
+ // Открываем редактор после небольшой задержки (StateChanged успевает обработаться)
128
+ requestAnimationFrame(() => {
129
+ inst.core?.connectorLabelLayer?.openEditorForConnector(inst.currentId);
130
+ });
131
+ });
132
+
133
+ // ── Замок ────────────────────────────────────────────────────────────────
134
+
135
+ inst._lockBtn.addEventListener('click', () => {
136
+ if (!inst.currentId) return;
137
+ const connector = inst._getConnector();
138
+ const locked = !(connector?.properties?.locked ?? false);
139
+ inst.eventBus.emit(Events.Object.StateChanged, {
140
+ objectId: inst.currentId,
141
+ updates: { properties: { locked } },
142
+ });
143
+ inst._lockBtn.textContent = locked ? '🔒' : '🔓';
144
+ });
145
+
146
+ // ── Удалить ──────────────────────────────────────────────────────────────
147
+
148
+ inst._delBtn.addEventListener('click', () => {
149
+ if (!inst.currentId) return;
150
+ inst.eventBus.emit(Events.Tool.ObjectsDelete, { objects: [inst.currentId] });
151
+ });
152
+
153
+ // ── Label: цвет (dropdown пресетов) ─────────────────────────────────────
154
+
155
+ if (inst._labelColorBtn) {
156
+ inst._labelColorBtn.addEventListener('click', (e) => {
157
+ e.stopPropagation();
158
+ if (inst._labelColorDropdown?.style.display === 'none') {
159
+ showLabelColorDropdown(inst);
160
+ } else {
161
+ hideLabelColorDropdown(inst);
162
+ }
163
+ });
164
+ }
165
+
166
+ inst._labelPresetButtons?.forEach(btn => {
167
+ btn.addEventListener('click', () => {
168
+ const pixi = parseInt(btn.dataset.pixiValue, 10);
169
+ const connector = inst._getConnector();
170
+ const existing = connector?.properties?.style?.label;
171
+ if (!existing) return;
172
+ inst._emitLabel({ ...existing, color: pixi });
173
+ hideLabelColorDropdown(inst);
174
+ });
175
+ });
176
+
177
+ inst._onLabelDocumentClick = (e) => {
178
+ if (inst._labelColorContainer && !inst._labelColorContainer.contains(e.target)) {
179
+ hideLabelColorDropdown(inst);
180
+ }
181
+ };
182
+ document.addEventListener('click', inst._onLabelDocumentClick);
183
+
184
+ // ── Label: размер шрифта (степпер) ───────────────────────────────────────
185
+
186
+ const FONT_SIZES = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 40, 48];
187
+
188
+ if (inst._labelSizeDown) {
189
+ inst._labelSizeDown.addEventListener('click', () => {
190
+ const connector = inst._getConnector();
191
+ const existing = connector?.properties?.style?.label;
192
+ if (!existing) return;
193
+ const cur = existing.fontSize ?? 14;
194
+ const next = FONT_SIZES.slice().reverse().find(s => s < cur) ?? FONT_SIZES[0];
195
+ inst._emitLabel({ ...existing, fontSize: next });
196
+ });
197
+ }
198
+
199
+ if (inst._labelSizeUp) {
200
+ inst._labelSizeUp.addEventListener('click', () => {
201
+ const connector = inst._getConnector();
202
+ const existing = connector?.properties?.style?.label;
203
+ if (!existing) return;
204
+ const cur = existing.fontSize ?? 14;
205
+ const next = FONT_SIZES.find(s => s > cur) ?? FONT_SIZES[FONT_SIZES.length - 1];
206
+ inst._emitLabel({ ...existing, fontSize: next });
207
+ });
208
+ }
209
+
210
+ inst._bindingsAttached = true;
211
+ }
212
+
213
+ export function unbindConnectorPropertiesPanelControls(inst) {
214
+ if (inst._onStrokeDocumentClick) {
215
+ document.removeEventListener('click', inst._onStrokeDocumentClick);
216
+ inst._onStrokeDocumentClick = null;
217
+ }
218
+ if (inst._onLabelDocumentClick) {
219
+ document.removeEventListener('click', inst._onLabelDocumentClick);
220
+ inst._onLabelDocumentClick = null;
221
+ }
222
+ inst._bindingsAttached = false;
223
+ }
@@ -0,0 +1,114 @@
1
+ import { Events } from '../../core/events/Events.js';
2
+
3
+ export function attachConnectorPropertiesPanelEventBridge(inst) {
4
+ if (inst._eventBridgeAttached) return;
5
+
6
+ inst._eventBridgeHandlers = {
7
+ onSelectionAdd: () => inst.updateFromSelection(),
8
+ onSelectionRemove: () => inst.updateFromSelection(),
9
+ onSelectionClear: () => inst.hide(),
10
+
11
+ onDragStart: () => inst.hide(),
12
+ onDragUpdate: () => inst.reposition(),
13
+ onDragEnd: () => inst.updateFromSelection(),
14
+
15
+ onGroupDragStart: () => inst.hide(),
16
+ onGroupDragUpdate: () => inst.reposition(),
17
+ onGroupDragEnd: () => inst.updateFromSelection(),
18
+
19
+ onResizeUpdate: () => inst.reposition(),
20
+ onRotateUpdate: () => inst.reposition(),
21
+
22
+ onZoomPercent: () => { if (inst.currentId) inst.reposition(); },
23
+ onPanUpdate: () => { if (inst.currentId) inst.reposition(); },
24
+ onViewportChanged: () => { if (inst.currentId) inst.reposition(); },
25
+
26
+ onDeleted: (objectId) => {
27
+ if (inst.currentId && objectId === inst.currentId) inst.hide();
28
+ },
29
+
30
+ onToolActivated: ({ tool }) => {
31
+ if (tool !== 'select') inst.hide();
32
+ },
33
+
34
+ onStateChanged: ({ objectId }) => {
35
+ if (inst.currentId && objectId === inst.currentId
36
+ && inst.panel && inst.panel.style.display !== 'none') {
37
+ inst._updateControlsFromObject();
38
+ }
39
+ },
40
+
41
+ // History.Changed — синхронизация после undo/redo (как у FramePropertiesPanel)
42
+ onHistoryChanged: () => {
43
+ if (inst.currentId && inst.panel && inst.panel.style.display !== 'none') {
44
+ inst._updateControlsFromObject();
45
+ }
46
+ },
47
+ };
48
+
49
+ const h = inst._eventBridgeHandlers;
50
+ const eb = inst.eventBus;
51
+
52
+ eb.on(Events.Tool.SelectionAdd, h.onSelectionAdd);
53
+ eb.on(Events.Tool.SelectionRemove, h.onSelectionRemove);
54
+ eb.on(Events.Tool.SelectionClear, h.onSelectionClear);
55
+
56
+ eb.on(Events.Tool.DragStart, h.onDragStart);
57
+ eb.on(Events.Tool.DragUpdate, h.onDragUpdate);
58
+ eb.on(Events.Tool.DragEnd, h.onDragEnd);
59
+
60
+ eb.on(Events.Tool.GroupDragStart, h.onGroupDragStart);
61
+ eb.on(Events.Tool.GroupDragUpdate, h.onGroupDragUpdate);
62
+ eb.on(Events.Tool.GroupDragEnd, h.onGroupDragEnd);
63
+
64
+ eb.on(Events.Tool.ResizeUpdate, h.onResizeUpdate);
65
+ eb.on(Events.Tool.RotateUpdate, h.onRotateUpdate);
66
+
67
+ eb.on(Events.UI.ZoomPercent, h.onZoomPercent);
68
+ eb.on(Events.Tool.PanUpdate, h.onPanUpdate);
69
+ eb.on(Events.Viewport.Changed, h.onViewportChanged);
70
+
71
+ eb.on(Events.Object.Deleted, h.onDeleted);
72
+ eb.on(Events.Tool.Activated, h.onToolActivated);
73
+ eb.on(Events.Object.StateChanged, h.onStateChanged);
74
+ eb.on(Events.History.Changed, h.onHistoryChanged);
75
+
76
+ inst._eventBridgeAttached = true;
77
+ }
78
+
79
+ export function detachConnectorPropertiesPanelEventBridge(inst) {
80
+ if (!inst._eventBridgeAttached || !inst._eventBridgeHandlers || !inst.eventBus?.off) {
81
+ inst._eventBridgeAttached = false;
82
+ return;
83
+ }
84
+
85
+ const h = inst._eventBridgeHandlers;
86
+ const eb = inst.eventBus;
87
+
88
+ eb.off(Events.Tool.SelectionAdd, h.onSelectionAdd);
89
+ eb.off(Events.Tool.SelectionRemove, h.onSelectionRemove);
90
+ eb.off(Events.Tool.SelectionClear, h.onSelectionClear);
91
+
92
+ eb.off(Events.Tool.DragStart, h.onDragStart);
93
+ eb.off(Events.Tool.DragUpdate, h.onDragUpdate);
94
+ eb.off(Events.Tool.DragEnd, h.onDragEnd);
95
+
96
+ eb.off(Events.Tool.GroupDragStart, h.onGroupDragStart);
97
+ eb.off(Events.Tool.GroupDragUpdate, h.onGroupDragUpdate);
98
+ eb.off(Events.Tool.GroupDragEnd, h.onGroupDragEnd);
99
+
100
+ eb.off(Events.Tool.ResizeUpdate, h.onResizeUpdate);
101
+ eb.off(Events.Tool.RotateUpdate, h.onRotateUpdate);
102
+
103
+ eb.off(Events.UI.ZoomPercent, h.onZoomPercent);
104
+ eb.off(Events.Tool.PanUpdate, h.onPanUpdate);
105
+ eb.off(Events.Viewport.Changed, h.onViewportChanged);
106
+
107
+ eb.off(Events.Object.Deleted, h.onDeleted);
108
+ eb.off(Events.Tool.Activated, h.onToolActivated);
109
+ eb.off(Events.Object.StateChanged, h.onStateChanged);
110
+ eb.off(Events.History.Changed, h.onHistoryChanged);
111
+
112
+ inst._eventBridgeHandlers = null;
113
+ inst._eventBridgeAttached = false;
114
+ }
@@ -0,0 +1,144 @@
1
+ import { Events } from '../../core/events/Events.js';
2
+
3
+ // ── Цветовые пресеты линии ──────────────────────────────────────────────────
4
+
5
+ export const STROKE_COLOR_PRESETS = [
6
+ { color: '#2563EB', value: 0x2563EB, name: 'Синий' },
7
+ { color: '#111827', value: 0x111827, name: 'Чёрный' },
8
+ { color: '#6B7280', value: 0x6B7280, name: 'Серый' },
9
+ { color: '#EF4444', value: 0xEF4444, name: 'Красный' },
10
+ { color: '#F59E0B', value: 0xF59E0B, name: 'Жёлтый' },
11
+ { color: '#10B981', value: 0x10B981, name: 'Зелёный' },
12
+ { color: '#8B5CF6', value: 0x8B5CF6, name: 'Фиолетовый' },
13
+ { color: '#EC4899', value: 0xEC4899, name: 'Розовый' },
14
+ { color: '#06B6D4', value: 0x06B6D4, name: 'Голубой' },
15
+ { color: '#FFFFFF', value: 0xFFFFFF, name: 'Белый' },
16
+ ];
17
+
18
+ // ── Пресеты ширины ───────────────────────────────────────────────────────────
19
+
20
+ export const WIDTH_PRESETS = [1, 2, 4, 8];
21
+
22
+ // ── Варианты маршрута ────────────────────────────────────────────────────────
23
+
24
+ export const ROUTE_OPTIONS = [
25
+ { value: 'straight', label: 'Прямая' },
26
+ { value: 'elbow', label: 'Угловой' },
27
+ { value: 'bezier', label: 'Кривая' },
28
+ ];
29
+
30
+ // ── Варианты наконечника ─────────────────────────────────────────────────────
31
+
32
+ export const HEAD_OPTIONS = [
33
+ { value: 'none', label: '— нет' },
34
+ { value: 'arrow', label: '→ стрелка' },
35
+ { value: 'triangle', label: '▷ треугольник' },
36
+ { value: 'circle', label: '○ круг' },
37
+ { value: 'diamond', label: '◇ ромб' },
38
+ ];
39
+
40
+ // ── Дефолтный style коннектора ───────────────────────────────────────────────
41
+
42
+ export const CONNECTOR_STYLE_DEFAULTS = {
43
+ stroke: 0x2563EB,
44
+ width: 2,
45
+ dash: false,
46
+ route: 'elbow',
47
+ head: { start: 'none', end: 'arrow' },
48
+ };
49
+
50
+ // ── Вспомогательные функции ──────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Конвертирует PIXI-число (0xRRGGBB) в CSS #RRGGBB.
54
+ */
55
+ export function pixiColorToHex(pixi) {
56
+ return '#' + (pixi >>> 0).toString(16).padStart(6, '0').toUpperCase();
57
+ }
58
+
59
+ /**
60
+ * Конвертирует CSS #RRGGBB в PIXI-число.
61
+ */
62
+ export function hexToPixiColor(hex) {
63
+ return parseInt((hex || '#000000').replace('#', ''), 16);
64
+ }
65
+
66
+ /**
67
+ * Возвращает id выделенного коннектора или null.
68
+ */
69
+ export function getSelectedConnectorId(core) {
70
+ const ids = core?.selectTool ? Array.from(core.selectTool.selectedObjects || []) : [];
71
+ if (ids.length !== 1) return null;
72
+ const id = ids[0];
73
+ const obj = (core?.state?.state?.objects ?? []).find(o => o.id === id);
74
+ return (obj?.type === 'connector') ? id : null;
75
+ }
76
+
77
+ /**
78
+ * Возвращает объект-коннектор из state по id.
79
+ */
80
+ export function getConnectorData(core, id) {
81
+ return (core?.state?.state?.objects ?? []).find(o => o.id === id) ?? null;
82
+ }
83
+
84
+ /**
85
+ * Возвращает экранную середину коннектора, опираясь на _lastSegments.
86
+ * Координаты — целые числа (screen-space integer contract).
87
+ * @returns {{ x: number, y: number } | null}
88
+ */
89
+ export function getConnectorMidpointScreen(core, connectorId) {
90
+ const segments = core?.connectorLayer?._lastSegments;
91
+ if (!segments) return null;
92
+ const seg = segments.find(s => s.id === connectorId);
93
+ if (!seg) return null;
94
+
95
+ const worldLayer = core?.pixi?.worldLayer;
96
+ const scale = worldLayer?.scale?.x ?? 1;
97
+ const worldX = worldLayer?.x ?? 0;
98
+ const worldY = worldLayer?.y ?? 0;
99
+
100
+ // Поддерживаем оба формата: { points } и устаревший { start, end }
101
+ let midWorldX, midWorldY;
102
+ if (seg.points && seg.points.length >= 2) {
103
+ // Берём середину ломаной (средняя точка по индексу)
104
+ const pts = seg.points;
105
+ const mid = pts[Math.floor((pts.length - 1) / 2)];
106
+ const mid2 = pts[Math.ceil((pts.length - 1) / 2)];
107
+ midWorldX = (mid.x + mid2.x) / 2;
108
+ midWorldY = (mid.y + mid2.y) / 2;
109
+ } else if (seg.start && seg.end) {
110
+ midWorldX = (seg.start.x + seg.end.x) / 2;
111
+ midWorldY = (seg.start.y + seg.end.y) / 2;
112
+ } else {
113
+ return null;
114
+ }
115
+
116
+ return {
117
+ x: Math.round(midWorldX * scale + worldX),
118
+ y: Math.round(midWorldY * scale + worldY),
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Формирует updates.properties.style для StateChanged-эмита.
124
+ */
125
+ export function buildStyleUpdate(partialStyle) {
126
+ return { properties: { style: partialStyle } };
127
+ }
128
+
129
+ /**
130
+ * Читает текущий style коннектора с fallback на дефолты.
131
+ */
132
+ export function getConnectorStyle(connector) {
133
+ const s = connector?.properties?.style ?? {};
134
+ return {
135
+ stroke: s.stroke ?? CONNECTOR_STYLE_DEFAULTS.stroke,
136
+ width: s.width ?? CONNECTOR_STYLE_DEFAULTS.width,
137
+ dash: s.dash ?? CONNECTOR_STYLE_DEFAULTS.dash,
138
+ route: s.route ?? CONNECTOR_STYLE_DEFAULTS.route,
139
+ head: {
140
+ start: s.head?.start ?? CONNECTOR_STYLE_DEFAULTS.head.start,
141
+ end: s.head?.end ?? CONNECTOR_STYLE_DEFAULTS.head.end,
142
+ },
143
+ };
144
+ }