@sequent-org/moodboard 1.4.32 → 1.4.34

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 (137) 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/ai/ChatSessionController.js +14 -8
  58. package/src/services/comments/CommentService.js +344 -0
  59. package/src/tools/object-tools/CommentTool.js +85 -0
  60. package/src/tools/object-tools/DrawingTool.js +110 -10
  61. package/src/tools/object-tools/LaserPointerTool.js +121 -0
  62. package/src/tools/object-tools/SelectTool.js +25 -1
  63. package/src/tools/object-tools/TextTool.js +6 -1
  64. package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
  65. package/src/tools/object-tools/connector/connectorGesture.js +33 -19
  66. package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
  67. package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
  68. package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
  69. package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
  70. package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
  71. package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
  72. package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
  73. package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
  74. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
  75. package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
  76. package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
  77. package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
  78. package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
  79. package/src/ui/CommentPopover.js +6 -0
  80. package/src/ui/CommentsBar.js +91 -0
  81. package/src/ui/ConnectorPropertiesPanel.js +150 -0
  82. package/src/ui/ContextMenu.js +25 -0
  83. package/src/ui/DrawingPropertiesPanel.js +362 -0
  84. package/src/ui/FilePropertiesPanel.js +5 -0
  85. package/src/ui/FramePropertiesPanel.js +5 -0
  86. package/src/ui/HtmlTextLayer.js +246 -66
  87. package/src/ui/NotePropertiesPanel.js +6 -0
  88. package/src/ui/ShapePropertiesPanel.js +307 -0
  89. package/src/ui/TextPropertiesPanel.js +100 -1
  90. package/src/ui/Toolbar.js +25 -2
  91. package/src/ui/Topbar.js +2 -2
  92. package/src/ui/animation/HoverLiftController.js +6 -7
  93. package/src/ui/chat/ChatComposer.js +63 -9
  94. package/src/ui/chat/ChatWindow.js +329 -166
  95. package/src/ui/comments/CommentListPanel.js +213 -0
  96. package/src/ui/comments/CommentPinLayer.js +448 -0
  97. package/src/ui/comments/CommentThreadPopover.js +539 -0
  98. package/src/ui/comments/commentFormat.js +32 -0
  99. package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
  100. package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
  101. package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
  102. package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
  103. package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
  104. package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
  105. package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
  106. package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
  107. package/src/ui/connectors/ConnectorLayer.js +264 -57
  108. package/src/ui/handles/HandlesDomRenderer.js +5 -13
  109. package/src/ui/handles/HandlesEventBridge.js +1 -0
  110. package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
  111. package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
  112. package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
  113. package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
  114. package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
  115. package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
  116. package/src/ui/styles/chat.css +710 -18
  117. package/src/ui/styles/index.css +1 -0
  118. package/src/ui/styles/panels.css +112 -2
  119. package/src/ui/styles/shape-properties-panel.css +250 -0
  120. package/src/ui/styles/toolbar.css +7 -2
  121. package/src/ui/styles/topbar.css +1 -1
  122. package/src/ui/styles/workspace.css +257 -6
  123. package/src/ui/text-properties/TextFormatControls.js +88 -0
  124. package/src/ui/text-properties/TextListRenderer.js +137 -0
  125. package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
  126. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
  127. package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
  128. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
  129. package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
  130. package/src/ui/toolbar/ReactionsPopupController.js +88 -0
  131. package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
  132. package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
  133. package/src/ui/toolbar/ToolbarRenderer.js +9 -1
  134. package/src/ui/toolbar/ToolbarStateController.js +4 -1
  135. package/src/utils/iconLoader.js +17 -16
  136. package/src/utils/markdown.js +14 -0
  137. package/src/utils/richText.js +125 -0
@@ -1,4 +1,21 @@
1
1
  /* Fonts */
2
+ @font-face {
3
+ font-family: 'Inter';
4
+ src: url('../../assets/fonts/inter/inter-latin-400-normal.woff2') format('woff2'),
5
+ url('../../assets/fonts/inter/inter-cyrillic-400-normal.woff2') format('woff2');
6
+ font-weight: 400;
7
+ font-style: normal;
8
+ font-display: swap;
9
+ }
10
+ @font-face {
11
+ font-family: 'Inter';
12
+ src: url('../../assets/fonts/inter/inter-latin-500-normal.woff2') format('woff2'),
13
+ url('../../assets/fonts/inter/inter-cyrillic-500-normal.woff2') format('woff2');
14
+ font-weight: 500;
15
+ font-style: normal;
16
+ font-display: swap;
17
+ }
18
+
2
19
  @font-face {
3
20
  font-family: 'GeistSans';
4
21
  src: url('../../assets/fonts/geist/geist-sans-latin-100-normal.ttf') format('truetype');
@@ -295,8 +312,8 @@
295
312
  word-break: break-word;
296
313
  pointer-events: none;
297
314
  user-select: none;
298
- padding-top: 5px;
299
- padding-bottom: 5px;
315
+ padding-top: 0.3em;
316
+ padding-bottom: 0.3em;
300
317
  box-sizing: border-box;
301
318
  -webkit-font-smoothing: antialiased;
302
319
  -moz-osx-font-smoothing: grayscale;
@@ -304,6 +321,131 @@
304
321
  letter-spacing: normal;
305
322
  }
306
323
 
324
+ /* Markdown-режим текстового объекта: блочный рендер, перенос по словам */
325
+ .mb-text--md {
326
+ white-space: normal;
327
+ overflow-wrap: break-word;
328
+ word-break: break-word;
329
+ }
330
+
331
+ .mb-text--md h1,
332
+ .mb-text--md h2,
333
+ .mb-text--md h3,
334
+ .mb-text--md h4,
335
+ .mb-text--md h5,
336
+ .mb-text--md h6 {
337
+ margin: 0.875em 0 0.375em;
338
+ line-height: 1.25;
339
+ font-weight: 600;
340
+ }
341
+
342
+ .mb-text--md h1 { font-size: 1.4em; }
343
+ .mb-text--md h2 { font-size: 1.2em; }
344
+ .mb-text--md h3 { font-size: 1.1em; }
345
+ .mb-text--md h4,
346
+ .mb-text--md h5,
347
+ .mb-text--md h6 { font-size: 1em; }
348
+
349
+ .mb-text--md p {
350
+ margin: 0 0 0.5em;
351
+ }
352
+
353
+ .mb-text--md p:last-child {
354
+ margin-bottom: 0;
355
+ }
356
+
357
+ .mb-text--md ul,
358
+ .mb-text--md ol {
359
+ margin: 0 0 0.5em;
360
+ padding-left: 1.25em;
361
+ }
362
+
363
+ .mb-text--md li {
364
+ margin: 0.125em 0;
365
+ }
366
+
367
+ .mb-text--md strong,
368
+ .mb-text--md b {
369
+ font-weight: 600;
370
+ }
371
+
372
+ .mb-text--md em,
373
+ .mb-text--md i {
374
+ font-style: italic;
375
+ }
376
+
377
+ .mb-text--md code {
378
+ padding: 0.125em 0.3125em;
379
+ border-radius: 4px;
380
+ background: rgba(0, 0, 0, 0.08);
381
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
382
+ font-size: 0.9em;
383
+ }
384
+
385
+ .mb-text--md pre {
386
+ margin: 0.375em 0;
387
+ padding: 0.625em 0.75em;
388
+ border-radius: 8px;
389
+ background: rgba(0, 0, 0, 0.06);
390
+ overflow-x: auto;
391
+ }
392
+
393
+ .mb-text--md pre code {
394
+ padding: 0;
395
+ border-radius: 0;
396
+ background: transparent;
397
+ font-size: 0.88em;
398
+ line-height: 1.5;
399
+ }
400
+
401
+ .mb-text--md blockquote {
402
+ margin: 0.375em 0;
403
+ padding: 0.25em 0.625em;
404
+ border-left: 3px solid rgba(0, 0, 0, 0.18);
405
+ background: rgba(0, 0, 0, 0.04);
406
+ border-radius: 0 6px 6px 0;
407
+ }
408
+
409
+ .mb-text--md table {
410
+ border-collapse: collapse;
411
+ margin: 0.375em 0;
412
+ font-size: 0.92em;
413
+ }
414
+
415
+ .mb-text--md th,
416
+ .mb-text--md td {
417
+ padding: 0.3125em 0.5em;
418
+ border: 1px solid rgba(0, 0, 0, 0.12);
419
+ }
420
+
421
+ .mb-text--md th {
422
+ background: rgba(0, 0, 0, 0.05);
423
+ font-weight: 600;
424
+ text-align: left;
425
+ }
426
+
427
+ .mb-text--md hr {
428
+ border: 0;
429
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
430
+ margin: 0.625em 0;
431
+ }
432
+
433
+ .mb-text--md img {
434
+ max-width: 100%;
435
+ }
436
+
437
+ /* KaTeX-формулы внутри текстового объекта */
438
+ .mb-text--md .katex-display {
439
+ margin: 0.5em 0;
440
+ overflow-x: auto;
441
+ overflow-y: hidden;
442
+ }
443
+
444
+ .mb-text--md .katex {
445
+ /* Цвет формулы наследуется от цвета текстового объекта */
446
+ color: inherit;
447
+ }
448
+
307
449
  .mb-text--mindmap {
308
450
  display: flex;
309
451
  align-items: center;
@@ -622,10 +764,10 @@
622
764
  display: inline-flex;
623
765
  align-items: center;
624
766
  gap: 8px;
625
- padding: 8px 10px;
767
+ padding: 8px;
626
768
  background: #fff;
627
769
  border: 1px solid #e0e0e0;
628
- border-radius: 10px;
770
+ border-radius: 9999px;
629
771
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
630
772
  z-index: 3000;
631
773
  pointer-events: auto;
@@ -692,10 +834,10 @@
692
834
  display: inline-flex;
693
835
  align-items: center;
694
836
  gap: 8px;
695
- padding: 8px 10px;
837
+ padding: 8px;
696
838
  background: #fff;
697
839
  border: 1px solid #e0e0e0;
698
- border-radius: 10px;
840
+ border-radius: 9999px;
699
841
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
700
842
  z-index: 3000;
701
843
  }
@@ -858,6 +1000,115 @@
858
1000
  }
859
1001
  .moodboard-draw__grid > .moodboard-draw__row { display: contents; }
860
1002
  .moodboard-draw__placeholder { min-width: 30px; min-height: 30px; }
1003
+
1004
+ /* ── Новая панель рисования ─────────────────────────────────────── */
1005
+ .moodboard-toolbar__popup--draw {
1006
+ min-width: 188px;
1007
+ padding: 8px;
1008
+ box-sizing: border-box;
1009
+ }
1010
+ .moodboard-draw__panel {
1011
+ display: flex;
1012
+ flex-direction: column;
1013
+ gap: 10px;
1014
+ }
1015
+ .moodboard-draw__tool-row {
1016
+ display: flex;
1017
+ gap: 4px;
1018
+ justify-content: flex-start;
1019
+ }
1020
+ .moodboard-draw__section {
1021
+ display: flex;
1022
+ flex-direction: column;
1023
+ gap: 6px;
1024
+ }
1025
+ .moodboard-draw__section-header {
1026
+ display: flex;
1027
+ justify-content: space-between;
1028
+ align-items: center;
1029
+ }
1030
+ .moodboard-draw__section-label {
1031
+ font-size: 11px;
1032
+ color: #6b7280;
1033
+ font-weight: 500;
1034
+ }
1035
+ .moodboard-draw__thickness-value {
1036
+ font-size: 11px;
1037
+ color: #374151;
1038
+ font-weight: 600;
1039
+ min-width: 28px;
1040
+ text-align: right;
1041
+ }
1042
+ .moodboard-draw__slider {
1043
+ -webkit-appearance: none;
1044
+ appearance: none;
1045
+ width: 100%;
1046
+ height: 4px;
1047
+ border-radius: 2px;
1048
+ background: #e5e7eb;
1049
+ outline: none;
1050
+ cursor: pointer;
1051
+ }
1052
+ .moodboard-draw__slider::-webkit-slider-thumb {
1053
+ -webkit-appearance: none;
1054
+ appearance: none;
1055
+ width: 14px;
1056
+ height: 14px;
1057
+ border-radius: 50%;
1058
+ background: #374151;
1059
+ cursor: pointer;
1060
+ transition: background 0.15s;
1061
+ }
1062
+ .moodboard-draw__slider::-webkit-slider-thumb:hover { background: #111827; }
1063
+ .moodboard-draw__slider::-moz-range-thumb {
1064
+ width: 14px;
1065
+ height: 14px;
1066
+ border-radius: 50%;
1067
+ background: #374151;
1068
+ cursor: pointer;
1069
+ border: none;
1070
+ }
1071
+ .moodboard-draw__color-grid {
1072
+ display: grid;
1073
+ grid-template-columns: repeat(5, 24px);
1074
+ gap: 4px;
1075
+ }
1076
+ .moodboard-draw__color-btn {
1077
+ width: 24px;
1078
+ height: 24px;
1079
+ border-radius: 50%;
1080
+ border: 2px solid transparent;
1081
+ cursor: pointer;
1082
+ padding: 0;
1083
+ transition: transform 0.12s, border-color 0.12s;
1084
+ position: relative;
1085
+ flex-shrink: 0;
1086
+ box-sizing: border-box;
1087
+ }
1088
+ .moodboard-draw__color-btn:hover {
1089
+ transform: scale(1.15);
1090
+ border-color: #9ca3af;
1091
+ }
1092
+ .moodboard-draw__color-btn--active {
1093
+ border-color: #374151 !important;
1094
+ transform: scale(1.1);
1095
+ }
1096
+ .moodboard-draw__color-btn--custom {
1097
+ background: conic-gradient(
1098
+ #ef4444, #f97316, #facc15, #22c55e, #3b82f6, #8b5cf6, #ec4899, #ef4444
1099
+ );
1100
+ overflow: hidden;
1101
+ }
1102
+ .moodboard-draw__color-input {
1103
+ position: absolute;
1104
+ inset: 0;
1105
+ opacity: 0;
1106
+ width: 100%;
1107
+ height: 100%;
1108
+ cursor: pointer;
1109
+ border: none;
1110
+ padding: 0;
1111
+ }
861
1112
  .moodboard-emoji__section { margin-bottom: 8px; }
862
1113
  .moodboard-emoji__title {
863
1114
  font-size: 12px;
@@ -0,0 +1,88 @@
1
+ import {
2
+ LINE_HEIGHT_DEFAULT,
3
+ LINE_HEIGHT_MAX,
4
+ LINE_HEIGHT_MIN,
5
+ LINE_HEIGHT_STEP,
6
+ } from './TextPropertiesPanelMapper.js';
7
+
8
+ // Статичные SVG-строки (lucide-style). innerHTML допустим — это доверенные константы, не пользовательский ввод.
9
+ const SVG_BOLD = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>';
10
+ const SVG_ITALIC = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>';
11
+ const SVG_UNDERLINE = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/></svg>';
12
+ const SVG_STRIKE = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" y1="12" x2="20" y2="12"/></svg>';
13
+
14
+ function makeSep() {
15
+ const d = document.createElement('div');
16
+ d.style.cssText = 'width:1px;height:18px;background:#e0e0e0;margin:0 6px;flex-shrink:0;';
17
+ return d;
18
+ }
19
+
20
+ function makeToggleBtn(svgStr, title, prop) {
21
+ const btn = document.createElement('button');
22
+ btn.type = 'button';
23
+ btn.title = title;
24
+ btn.className = 'tpp-format-btn';
25
+ btn.dataset.formatProp = prop;
26
+ btn.innerHTML = svgStr;
27
+ return btn;
28
+ }
29
+
30
+ function makeSelect(className, options) {
31
+ const sel = document.createElement('select');
32
+ sel.className = className;
33
+ options.forEach(({ value, label }) => {
34
+ const opt = document.createElement('option');
35
+ opt.value = value;
36
+ opt.textContent = label;
37
+ sel.appendChild(opt);
38
+ });
39
+ return sel;
40
+ }
41
+
42
+ export function createTextFormatControls(panelInstance, panel) {
43
+ panel.appendChild(makeSep());
44
+
45
+ panelInstance.boldBtn = makeToggleBtn(SVG_BOLD, 'Жирный', 'bold');
46
+ panelInstance.italicBtn = makeToggleBtn(SVG_ITALIC, 'Курсив', 'italic');
47
+ panelInstance.underlineBtn = makeToggleBtn(SVG_UNDERLINE, 'Подчёркнутый', 'underline');
48
+ panelInstance.strikethroughBtn = makeToggleBtn(SVG_STRIKE, 'Зачёркнутый', 'strikethrough');
49
+
50
+ [panelInstance.boldBtn, panelInstance.italicBtn, panelInstance.underlineBtn, panelInstance.strikethroughBtn].forEach(
51
+ (btn) => panel.appendChild(btn),
52
+ );
53
+
54
+ panel.appendChild(makeSep());
55
+
56
+ panelInstance.alignControl = makeSelect('tpp-align-select', [
57
+ { value: 'left', label: '≡ Лево' },
58
+ { value: 'center', label: '≡ Центр' },
59
+ { value: 'right', label: '≡ Право' },
60
+ { value: 'justify', label: '≡ По ширине' },
61
+ ]);
62
+ panel.appendChild(panelInstance.alignControl);
63
+
64
+ panelInstance.listControl = makeSelect('tpp-list-select', [
65
+ { value: 'none', label: 'Список: нет' },
66
+ { value: 'bullet', label: '• Маркер' },
67
+ { value: 'numbered', label: '1. Нумерация' },
68
+ { value: 'checkbox', label: '☐ Флажки' },
69
+ ]);
70
+ panel.appendChild(panelInstance.listControl);
71
+
72
+ panel.appendChild(makeSep());
73
+
74
+ const lhLabel = document.createElement('span');
75
+ lhLabel.className = 'tpp-label';
76
+ lhLabel.title = 'Межстрочный интервал';
77
+ lhLabel.textContent = '↕';
78
+ panel.appendChild(lhLabel);
79
+
80
+ panelInstance.lineHeightSlider = document.createElement('input');
81
+ panelInstance.lineHeightSlider.type = 'range';
82
+ panelInstance.lineHeightSlider.className = 'tpp-lh-slider';
83
+ panelInstance.lineHeightSlider.min = String(LINE_HEIGHT_MIN);
84
+ panelInstance.lineHeightSlider.max = String(LINE_HEIGHT_MAX);
85
+ panelInstance.lineHeightSlider.step = String(LINE_HEIGHT_STEP);
86
+ panelInstance.lineHeightSlider.value = String(LINE_HEIGHT_DEFAULT);
87
+ panel.appendChild(panelInstance.lineHeightSlider);
88
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * TextListRenderer — рендер контента текстового объекта как DOM-списка.
3
+ * Вызывается из HtmlTextLayer вместо plain/markdown-пути,
4
+ * когда listType !== 'none'.
5
+ *
6
+ * Безопасность: пользовательский текст добавляется только через textContent,
7
+ * innerHTML не используется.
8
+ */
9
+
10
+ /**
11
+ * Строит DOM-список внутри переданного элемента.
12
+ * @param {HTMLElement} el - контейнер (очищается перед рендером)
13
+ * @param {string} content - строки, разделённые \n
14
+ * @param {'bullet'|'numbered'|'checkbox'} listType
15
+ * @param {boolean[]} listChecked - состояния чекбоксов по индексу строки
16
+ * @param {function(number):void} onToggle - вызывается при клике на чекбокс
17
+ */
18
+ export function renderTextList(el, content, listType, listChecked, onToggle) {
19
+ while (el.firstChild) el.removeChild(el.firstChild);
20
+
21
+ const lines = typeof content === 'string' ? content.split('\n') : [''];
22
+
23
+ if (listType === 'bullet') {
24
+ _renderBullet(el, lines);
25
+ } else if (listType === 'numbered') {
26
+ _renderNumbered(el, lines);
27
+ } else if (listType === 'checkbox') {
28
+ _renderCheckbox(el, lines, listChecked, onToggle);
29
+ }
30
+ }
31
+
32
+ // ─── bullet ──────────────────────────────────────────────────────────────────
33
+
34
+ function _renderBullet(el, lines) {
35
+ const list = document.createElement('ul');
36
+ list.style.cssText = 'list-style:none;margin:0;padding:0;';
37
+
38
+ lines.forEach((lineText) => {
39
+ const item = document.createElement('li');
40
+ item.style.cssText = 'display:flex;align-items:baseline;gap:0.4em;';
41
+
42
+ const marker = document.createElement('span');
43
+ marker.textContent = '•';
44
+ marker.style.cssText = 'flex-shrink:0;user-select:none;';
45
+
46
+ const text = document.createElement('span');
47
+ text.textContent = lineText;
48
+
49
+ item.appendChild(marker);
50
+ item.appendChild(text);
51
+ list.appendChild(item);
52
+ });
53
+
54
+ el.appendChild(list);
55
+ }
56
+
57
+ // ─── numbered ─────────────────────────────────────────────────────────────────
58
+
59
+ function _renderNumbered(el, lines) {
60
+ const list = document.createElement('ol');
61
+ list.style.cssText = 'list-style:none;margin:0;padding:0;';
62
+
63
+ lines.forEach((lineText, idx) => {
64
+ const item = document.createElement('li');
65
+ item.style.cssText = 'display:flex;align-items:baseline;gap:0.4em;';
66
+
67
+ const marker = document.createElement('span');
68
+ marker.textContent = `${idx + 1}.`;
69
+ marker.style.cssText = 'flex-shrink:0;user-select:none;min-width:1.8em;text-align:right;';
70
+
71
+ const text = document.createElement('span');
72
+ text.textContent = lineText;
73
+
74
+ item.appendChild(marker);
75
+ item.appendChild(text);
76
+ list.appendChild(item);
77
+ });
78
+
79
+ el.appendChild(list);
80
+ }
81
+
82
+ // ─── checkbox ─────────────────────────────────────────────────────────────────
83
+
84
+ function _renderCheckbox(el, lines, listChecked, onToggle) {
85
+ const list = document.createElement('ul');
86
+ list.style.cssText = 'list-style:none;margin:0;padding:0;';
87
+
88
+ lines.forEach((lineText, idx) => {
89
+ const checked = Array.isArray(listChecked) ? !!listChecked[idx] : false;
90
+
91
+ const item = document.createElement('li');
92
+ item.style.cssText = 'display:flex;align-items:center;gap:0.5em;';
93
+
94
+ // pointer-events: auto — точечно только на чекбоксе.
95
+ // Родительский .moodboard-html-layer имеет pointer-events: none.
96
+ const box = document.createElement('span');
97
+ box.style.cssText = [
98
+ 'display:inline-flex',
99
+ 'align-items:center',
100
+ 'justify-content:center',
101
+ 'width:1em',
102
+ 'height:1em',
103
+ 'min-width:1em',
104
+ 'border:1.5px solid currentColor',
105
+ 'border-radius:2px',
106
+ 'flex-shrink:0',
107
+ 'cursor:pointer',
108
+ 'user-select:none',
109
+ 'pointer-events:auto', // ключевое включение
110
+ ].join(';') + ';';
111
+
112
+ if (checked) {
113
+ const tick = document.createElement('span');
114
+ tick.textContent = '✓';
115
+ tick.style.cssText = 'font-size:0.75em;line-height:1;pointer-events:none;';
116
+ box.appendChild(tick);
117
+ }
118
+
119
+ box.addEventListener('click', (e) => {
120
+ // stopPropagation: клик не должен уходить в canvas (выделение/драг)
121
+ e.stopPropagation();
122
+ if (typeof onToggle === 'function') onToggle(idx);
123
+ });
124
+
125
+ const text = document.createElement('span');
126
+ text.textContent = lineText;
127
+ if (checked) {
128
+ text.style.cssText = 'text-decoration:line-through;opacity:0.6;';
129
+ }
130
+
131
+ item.appendChild(box);
132
+ item.appendChild(text);
133
+ list.appendChild(item);
134
+ });
135
+
136
+ el.appendChild(list);
137
+ }
@@ -74,6 +74,33 @@ export function bindTextPropertiesPanelControls(panel) {
74
74
  };
75
75
  document.addEventListener('click', panel._onBgDocumentClick);
76
76
 
77
+ if (panel.markdownToggle) {
78
+ panel.markdownToggle.addEventListener('change', (event) => {
79
+ panel._changeMarkdown(event.target.checked);
80
+ });
81
+ }
82
+
83
+ [
84
+ [panel.boldBtn, 'bold'],
85
+ [panel.italicBtn, 'italic'],
86
+ [panel.underlineBtn, 'underline'],
87
+ [panel.strikethroughBtn, 'strikethrough'],
88
+ ].forEach(([btn, prop]) => {
89
+ if (btn) btn.addEventListener('click', () => panel._toggleFormat(prop));
90
+ });
91
+
92
+ if (panel.alignControl) {
93
+ panel.alignControl.addEventListener('change', (e) => panel._changeTextAlign(e.target.value));
94
+ }
95
+
96
+ if (panel.listControl) {
97
+ panel.listControl.addEventListener('change', (e) => panel._changeListType(e.target.value));
98
+ }
99
+
100
+ if (panel.lineHeightSlider) {
101
+ panel.lineHeightSlider.addEventListener('input', (e) => panel._changeLineHeight(parseFloat(e.target.value)));
102
+ }
103
+
77
104
  panel._bindingsAttached = true;
78
105
  }
79
106
 
@@ -15,6 +15,7 @@ export function attachTextPropertiesPanelEventBridge(panel) {
15
15
  onRotateUpdate: () => panel.reposition(),
16
16
  onZoomPercent: () => panel.reposition(),
17
17
  onPanUpdate: () => panel.reposition(),
18
+ onViewportChanged: () => panel.reposition(),
18
19
  onDeleted: ({ objectId }) => {
19
20
  if (panel.currentId && objectId === panel.currentId) {
20
21
  panel.hide();
@@ -22,7 +23,6 @@ export function attachTextPropertiesPanelEventBridge(panel) {
22
23
  },
23
24
  onTextEditStart: () => {
24
25
  panel.isTextEditing = true;
25
- panel.hide();
26
26
  },
27
27
  onTextEditEnd: () => {
28
28
  panel.isTextEditing = false;
@@ -44,6 +44,7 @@ export function attachTextPropertiesPanelEventBridge(panel) {
44
44
  panel.eventBus.on(Events.Tool.RotateUpdate, panel._eventBridgeHandlers.onRotateUpdate);
45
45
  panel.eventBus.on(Events.UI.ZoomPercent, panel._eventBridgeHandlers.onZoomPercent);
46
46
  panel.eventBus.on(Events.Tool.PanUpdate, panel._eventBridgeHandlers.onPanUpdate);
47
+ panel.eventBus.on(Events.Viewport.Changed, panel._eventBridgeHandlers.onViewportChanged);
47
48
  panel.eventBus.on(Events.Object.Deleted, panel._eventBridgeHandlers.onDeleted);
48
49
  panel.eventBus.on(Events.UI.TextEditStart, panel._eventBridgeHandlers.onTextEditStart);
49
50
  panel.eventBus.on(Events.UI.TextEditEnd, panel._eventBridgeHandlers.onTextEditEnd);
@@ -67,6 +68,7 @@ export function detachTextPropertiesPanelEventBridge(panel) {
67
68
  panel.eventBus.off(Events.Tool.RotateUpdate, panel._eventBridgeHandlers.onRotateUpdate);
68
69
  panel.eventBus.off(Events.UI.ZoomPercent, panel._eventBridgeHandlers.onZoomPercent);
69
70
  panel.eventBus.off(Events.Tool.PanUpdate, panel._eventBridgeHandlers.onPanUpdate);
71
+ panel.eventBus.off(Events.Viewport.Changed, panel._eventBridgeHandlers.onViewportChanged);
70
72
  panel.eventBus.off(Events.Object.Deleted, panel._eventBridgeHandlers.onDeleted);
71
73
  panel.eventBus.off(Events.UI.TextEditStart, panel._eventBridgeHandlers.onTextEditStart);
72
74
  panel.eventBus.off(Events.UI.TextEditEnd, panel._eventBridgeHandlers.onTextEditEnd);
@@ -17,6 +17,18 @@ export const FONT_OPTIONS = [
17
17
 
18
18
  export const FONT_SIZE_OPTIONS = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
19
19
 
20
+ // Допустимые значения выравнивания и типов списка (контракт хранения в properties)
21
+ export const TEXT_ALIGN_VALUES = ['left', 'center', 'right', 'justify'];
22
+ export const LIST_TYPE_VALUES = ['none', 'bullet', 'numbered', 'checkbox'];
23
+
24
+ // Границы слайдера межстрочного интервала (множитель). DEFAULT — стартовое
25
+ // положение слайдера, когда у объекта ещё нет явного lineHeight (рендер при этом
26
+ // продолжает использовать адаптивный расчёт до первого изменения пользователем).
27
+ export const LINE_HEIGHT_MIN = 1.0;
28
+ export const LINE_HEIGHT_MAX = 2.5;
29
+ export const LINE_HEIGHT_STEP = 0.1;
30
+ export const LINE_HEIGHT_DEFAULT = 1.3;
31
+
20
32
  export const TEXT_COLOR_PRESETS = [
21
33
  { color: '#000000', name: '#000000' },
22
34
  { color: '#404040', name: '#404040' },
@@ -86,6 +98,14 @@ export function getControlValuesFromProperties(properties) {
86
98
  fontSize: String(properties.fontSize || 18),
87
99
  color: properties.color || '#000000',
88
100
  backgroundColor: properties.backgroundColor !== undefined ? properties.backgroundColor : 'transparent',
101
+ markdown: properties.markdown === true,
102
+ bold: properties.bold === true,
103
+ italic: properties.italic === true,
104
+ underline: properties.underline === true,
105
+ strikethrough: properties.strikethrough === true,
106
+ textAlign: properties.textAlign || 'left',
107
+ lineHeight: typeof properties.lineHeight === 'number' ? properties.lineHeight : null,
108
+ listType: properties.listType || 'none',
89
109
  };
90
110
  }
91
111
 
@@ -95,6 +115,14 @@ export function getFallbackControlValues() {
95
115
  fontSize: '18',
96
116
  color: '#000000',
97
117
  backgroundColor: 'transparent',
118
+ markdown: false,
119
+ bold: false,
120
+ italic: false,
121
+ underline: false,
122
+ strikethrough: false,
123
+ textAlign: 'left',
124
+ lineHeight: null,
125
+ listType: 'none',
98
126
  };
99
127
  }
100
128
 
@@ -122,6 +150,16 @@ export function buildBackgroundColorUpdate(backgroundColor) {
122
150
  };
123
151
  }
124
152
 
153
+ export function buildMarkdownUpdate(markdown) {
154
+ return {
155
+ properties: { markdown },
156
+ };
157
+ }
158
+
159
+ export function buildPropertyUpdate(key, value) {
160
+ return { properties: { [key]: value } };
161
+ }
162
+
125
163
  export function applyTextAppearanceToDom(objectId, properties) {
126
164
  const htmlElement = document.querySelector(`[data-id="${objectId}"]`);
127
165
  if (!htmlElement) {
@@ -144,6 +182,24 @@ export function applyTextAppearanceToDom(objectId, properties) {
144
182
  htmlElement.style.backgroundColor = properties.backgroundColor;
145
183
  }
146
184
  }
185
+ if (properties.bold !== undefined) {
186
+ htmlElement.style.fontWeight = properties.bold ? 'bold' : '';
187
+ }
188
+ if (properties.italic !== undefined) {
189
+ htmlElement.style.fontStyle = properties.italic ? 'italic' : '';
190
+ }
191
+ if (properties.underline !== undefined || properties.strikethrough !== undefined) {
192
+ const parts = [];
193
+ if (properties.underline === true) parts.push('underline');
194
+ if (properties.strikethrough === true) parts.push('line-through');
195
+ htmlElement.style.textDecoration = parts.join(' ');
196
+ }
197
+ if (properties.textAlign !== undefined) {
198
+ htmlElement.style.textAlign = properties.textAlign;
199
+ }
200
+ if (typeof properties.lineHeight === 'number') {
201
+ htmlElement.style.lineHeight = String(properties.lineHeight);
202
+ }
147
203
  }
148
204
 
149
205
  export function syncPixiTextProperties(eventBus, objectId, properties) {