@sequent-org/moodboard 1.4.59 → 1.4.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/PixiEngine.js +19 -7
- package/src/objects/FrameObject.js +16 -3
- package/src/services/text/TextBoxMetrics.js +86 -0
- package/src/tools/manager/ToolManagerLifecycle.js +48 -2
- package/src/tools/object-tools/connector/ConnectorDragController.js +30 -1
- package/src/tools/object-tools/selection/NoteInlineEditorController.js +7 -1
- package/src/tools/object-tools/selection/TextEditorCaretService.js +4 -2
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +14 -0
- package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +7 -0
- package/src/tools/object-tools/selection/TextEditorPositioningService.js +4 -3
- package/src/tools/object-tools/selection/TextEditorSyncService.js +260 -6
- package/src/tools/object-tools/selection/TextInlineEditorController.js +44 -5
- package/src/ui/HtmlTextLayer.js +31 -13
- package/src/ui/animation/HoverLiftController.js +20 -0
- package/src/ui/handles/HandlesDomRenderer.js +30 -3
- package/src/ui/handles/HandlesInteractionController.js +26 -28
- package/src/ui/styles/chat.css +43 -0
- package/src/ui/styles/topbar.css +20 -0
- package/src/ui/styles/workspace.css +35 -2
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +19 -19
|
@@ -216,12 +216,14 @@ export class HandlesInteractionController {
|
|
|
216
216
|
let aspectLockDominantAxis = null;
|
|
217
217
|
let isTextTarget = false;
|
|
218
218
|
let isNoteTarget = false;
|
|
219
|
+
let isVerticalResizeTarget = false;
|
|
219
220
|
{
|
|
220
221
|
const req = { objectId: id, pixiObject: null };
|
|
221
222
|
this.host.eventBus.emit(Events.Tool.GetObjectPixi, req);
|
|
222
223
|
const mbType = req.pixiObject && req.pixiObject._mb && req.pixiObject._mb.type;
|
|
223
224
|
isTextTarget = (mbType === 'text' || mbType === 'simple-text');
|
|
224
225
|
isNoteTarget = (mbType === 'note');
|
|
226
|
+
isVerticalResizeTarget = ['frame', 'text', 'simple-text', 'image'].includes(mbType);
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
const onMove = (ev) => {
|
|
@@ -263,9 +265,9 @@ export class HandlesInteractionController {
|
|
|
263
265
|
newW = rotatedBox.width;
|
|
264
266
|
newH = rotatedBox.height;
|
|
265
267
|
} else {
|
|
266
|
-
if (dir.includes('e')) newW = Math.max(1, startCSS.width + dx);
|
|
268
|
+
if (dir.includes('e') && !isVerticalResizeTarget) newW = Math.max(1, startCSS.width + dx);
|
|
267
269
|
if (dir.includes('s')) newH = Math.max(1, startCSS.height + dy);
|
|
268
|
-
if (dir.includes('w')) {
|
|
270
|
+
if (dir.includes('w') && !isVerticalResizeTarget) {
|
|
269
271
|
newW = Math.max(1, startCSS.width - dx);
|
|
270
272
|
newLeft = startCSS.left + dx;
|
|
271
273
|
}
|
|
@@ -344,7 +346,7 @@ export class HandlesInteractionController {
|
|
|
344
346
|
el.style.width = `${Math.max(1, Math.round(newW))}px`;
|
|
345
347
|
el.style.height = 'auto';
|
|
346
348
|
const measured = Math.max(1, Math.round(el.scrollHeight));
|
|
347
|
-
newH = measured;
|
|
349
|
+
newH = Math.max(newH, measured);
|
|
348
350
|
}
|
|
349
351
|
} catch (_) {}
|
|
350
352
|
}
|
|
@@ -418,35 +420,30 @@ export class HandlesInteractionController {
|
|
|
418
420
|
const mbType = req.pixiObject && req.pixiObject._mb && req.pixiObject._mb.type;
|
|
419
421
|
isFrameTarget = mbType === 'frame';
|
|
420
422
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
oldPosition: { x: startWorld.x, y: startWorld.y },
|
|
426
|
-
newPosition: isFrameTarget ? null : (isEdgeLeftOrTop ? { x: worldX, y: worldY } : { x: startWorld.x, y: startWorld.y }),
|
|
427
|
-
};
|
|
428
|
-
this.host.eventBus.emit(Events.Tool.ResizeEnd, resizeEndData);
|
|
429
|
-
try {
|
|
430
|
-
const req2 = { objectId: id, pixiObject: null };
|
|
431
|
-
this.host.eventBus.emit(Events.Tool.GetObjectPixi, req2);
|
|
432
|
-
const mbType2 = req2.pixiObject && req2.pixiObject._mb && req2.pixiObject._mb.type;
|
|
433
|
-
if (mbType2 === 'text' || mbType2 === 'simple-text') {
|
|
423
|
+
|
|
424
|
+
let finalWorldH = worldH;
|
|
425
|
+
if (isTextTarget) {
|
|
426
|
+
try {
|
|
434
427
|
const textLayer = (typeof window !== 'undefined') ? window.moodboardHtmlTextLayer : null;
|
|
435
428
|
const el = textLayer && textLayer.idToEl ? textLayer.idToEl.get && textLayer.idToEl.get(id) : null;
|
|
436
429
|
if (el) {
|
|
437
430
|
el.style.width = `${Math.max(1, Math.round(endCSS.width))}px`;
|
|
438
431
|
el.style.height = 'auto';
|
|
439
432
|
const measured = Math.max(1, Math.round(el.scrollHeight));
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
object: id,
|
|
443
|
-
size: { width: worldW, height: worldH2 },
|
|
444
|
-
position: isFrameTarget ? null : (isEdgeLeftOrTop ? { x: worldX, y: worldY } : { x: startWorld.x, y: startWorld.y }),
|
|
445
|
-
};
|
|
446
|
-
this.host.eventBus.emit(Events.Tool.ResizeUpdate, fixData);
|
|
433
|
+
const finalCssH = Math.max(measured, endCSS.height);
|
|
434
|
+
finalWorldH = finalCssH / s;
|
|
447
435
|
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
436
|
+
} catch (_) {}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const resizeEndData = {
|
|
440
|
+
object: id,
|
|
441
|
+
oldSize: { width: startWorld.width, height: startWorld.height },
|
|
442
|
+
newSize: { width: worldW, height: finalWorldH },
|
|
443
|
+
oldPosition: { x: startWorld.x, y: startWorld.y },
|
|
444
|
+
newPosition: edgeFinalPositionChanged ? { x: worldX, y: worldY } : { x: startWorld.x, y: startWorld.y },
|
|
445
|
+
};
|
|
446
|
+
this.host.eventBus.emit(Events.Tool.ResizeEnd, resizeEndData);
|
|
450
447
|
}
|
|
451
448
|
};
|
|
452
449
|
|
|
@@ -626,7 +623,7 @@ export class HandlesInteractionController {
|
|
|
626
623
|
el.style.width = `${Math.max(1, Math.round(newW))}px`;
|
|
627
624
|
el.style.height = 'auto';
|
|
628
625
|
const measured = Math.max(1, Math.round(el.scrollHeight));
|
|
629
|
-
newH = measured;
|
|
626
|
+
newH = Math.max(newH, measured);
|
|
630
627
|
}
|
|
631
628
|
} catch (_) {}
|
|
632
629
|
}
|
|
@@ -717,7 +714,7 @@ export class HandlesInteractionController {
|
|
|
717
714
|
} else {
|
|
718
715
|
const edgeFinalPositionChanged = (endCSS.left !== startCSS.left) || (endCSS.top !== startCSS.top);
|
|
719
716
|
let finalWorldH = worldH;
|
|
720
|
-
if (isTextTarget
|
|
717
|
+
if (isTextTarget) {
|
|
721
718
|
try {
|
|
722
719
|
const textLayer = (typeof window !== 'undefined') ? window.moodboardHtmlTextLayer : null;
|
|
723
720
|
const el = textLayer && textLayer.idToEl ? textLayer.idToEl.get && textLayer.idToEl.get(id) : null;
|
|
@@ -725,7 +722,8 @@ export class HandlesInteractionController {
|
|
|
725
722
|
el.style.width = `${Math.max(1, Math.round(endCSS.width))}px`;
|
|
726
723
|
el.style.height = 'auto';
|
|
727
724
|
const measured = Math.max(1, Math.round(el.scrollHeight));
|
|
728
|
-
|
|
725
|
+
const finalCssH = Math.max(measured, endCSS.height);
|
|
726
|
+
finalWorldH = finalCssH / s;
|
|
729
727
|
}
|
|
730
728
|
} catch (_) {}
|
|
731
729
|
}
|
package/src/ui/styles/chat.css
CHANGED
|
@@ -32,6 +32,49 @@
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/* Mobile (≤768px): панель генерации — компактный bottom-sheet во всю ширину,
|
|
36
|
+
приподнят над зум/карта-баром, не наезжает на тулбар и холст. */
|
|
37
|
+
@media (max-width: 768px) {
|
|
38
|
+
.moodboard-chat {
|
|
39
|
+
left: 8px;
|
|
40
|
+
right: 8px;
|
|
41
|
+
bottom: 84px; /* выше зум/карта-бара (16px + 60px touch-кнопки + зазор) */
|
|
42
|
+
transform: none;
|
|
43
|
+
width: auto;
|
|
44
|
+
max-width: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.moodboard-chat__composer {
|
|
48
|
+
min-height: 0;
|
|
49
|
+
padding: 0 6px 6px 8px;
|
|
50
|
+
gap: 6px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* iOS не зумит инпут при font-size ≥ 16px */
|
|
54
|
+
.moodboard-chat__textarea {
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
min-height: 40px;
|
|
57
|
+
height: 40px;
|
|
58
|
+
padding: 12px 0 4px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Пилюли и кнопка отправки переносятся, чтобы не выпадать за край */
|
|
62
|
+
.moodboard-chat__actions-row {
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
gap: 6px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.moodboard-chat__pills {
|
|
68
|
+
gap: 8px 10px;
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* История диалога не должна занимать весь экран на телефоне */
|
|
73
|
+
.moodboard-chat__history {
|
|
74
|
+
max-height: 40vh;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
.moodboard-chat__history {
|
|
36
79
|
max-height: 360px;
|
|
37
80
|
overflow-y: auto;
|
package/src/ui/styles/topbar.css
CHANGED
|
@@ -111,3 +111,23 @@
|
|
|
111
111
|
.moodboard-topbar__button--zoom { width: 32px; height: 32px; font-size: 16px; }
|
|
112
112
|
.moodboard-topbar__zoom-label { min-width: 48px; text-align: center; font-size: 14px; }
|
|
113
113
|
|
|
114
|
+
/* Mobile (≤768px): топбар не должен выходить за край экрана. Сжимаем зазоры,
|
|
115
|
+
ограничиваем ширину вьюпортом и даём прокрутку внутри пилюли, если кнопок много. */
|
|
116
|
+
@media (max-width: 768px) {
|
|
117
|
+
.moodboard-topbar {
|
|
118
|
+
top: 8px;
|
|
119
|
+
left: 8px;
|
|
120
|
+
gap: 2px;
|
|
121
|
+
padding: 6px;
|
|
122
|
+
max-width: calc(100vw - 16px);
|
|
123
|
+
overflow-x: auto;
|
|
124
|
+
scrollbar-width: none; /* Firefox */
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.moodboard-topbar::-webkit-scrollbar { display: none; }
|
|
128
|
+
|
|
129
|
+
.moodboard-topbar__button { flex: 0 0 auto; }
|
|
130
|
+
|
|
131
|
+
.moodboard-topbar__divider { margin: 0 3px; }
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -1575,9 +1575,42 @@
|
|
|
1575
1575
|
|
|
1576
1576
|
/* Responsive */
|
|
1577
1577
|
@media (max-width: 768px) {
|
|
1578
|
+
/* Тулбар прижимаем ближе к краю, чтобы оставить максимум холста.
|
|
1579
|
+
Высоту регулирует ToolbarResponsiveController (схлопывание в «Ещё»). */
|
|
1580
|
+
.moodboard-workspace__toolbar {
|
|
1581
|
+
left: 8px;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1578
1584
|
.moodboard-toolbar {
|
|
1579
|
-
|
|
1580
|
-
|
|
1585
|
+
padding: 6px;
|
|
1586
|
+
gap: 6px;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/* Зум- и карта-бары компактнее и ближе к краю на телефоне */
|
|
1590
|
+
.moodboard-zoombar {
|
|
1591
|
+
right: 72px;
|
|
1592
|
+
bottom: 16px;
|
|
1593
|
+
padding: 6px;
|
|
1594
|
+
gap: 6px;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.moodboard-mapbar {
|
|
1598
|
+
right: 8px;
|
|
1599
|
+
bottom: 16px;
|
|
1600
|
+
padding: 6px;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
.moodboard-zoombar__menu,
|
|
1604
|
+
.moodboard-mapbar__popup {
|
|
1605
|
+
right: 0;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/* Мини-карта не должна перекрывать половину экрана */
|
|
1609
|
+
.moodboard-mapbar__popup {
|
|
1610
|
+
width: 60vw;
|
|
1611
|
+
height: 28vh;
|
|
1612
|
+
min-width: 180px;
|
|
1613
|
+
min-height: 120px;
|
|
1581
1614
|
}
|
|
1582
1615
|
}
|
|
1583
1616
|
|
|
@@ -7,6 +7,16 @@ import {
|
|
|
7
7
|
import { createTextFormatControls } from './TextFormatControls.js';
|
|
8
8
|
import { createTextLockMoreControls } from './TextLockMoreControls.js';
|
|
9
9
|
|
|
10
|
+
const NO_COLOR_ICON_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 23.5C18.3513 23.5 23.5 18.3513 23.5 12C23.5 5.64873 18.3513 0.5 12 0.5C5.64873 0.5 0.5 5.64873 0.5 12C0.5 18.3513 5.64873 23.5 12 23.5Z" stroke="#B2B2B2" stroke-width="1.2"></path><path d="M4.27344 19.7266L19.7148 4.28516" stroke="#B2B2B2" stroke-width="1.2"></path></svg>`;
|
|
11
|
+
|
|
12
|
+
function createNoColorIcon(extraStyle = '') {
|
|
13
|
+
const wrapper = document.createElement('span');
|
|
14
|
+
wrapper.className = 'no-color-icon';
|
|
15
|
+
wrapper.style.cssText = `display:inline-flex;pointer-events:none;width:20px;height:20px;${extraStyle}`;
|
|
16
|
+
wrapper.innerHTML = NO_COLOR_ICON_SVG;
|
|
17
|
+
return wrapper;
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
export function createTextPropertiesPanelRenderer(panelInstance) {
|
|
11
21
|
const panel = document.createElement('div');
|
|
12
22
|
panel.className = 'text-properties-panel';
|
|
@@ -106,20 +116,18 @@ export function updateCurrentBgColorButton(panelInstance, color) {
|
|
|
106
116
|
line.remove();
|
|
107
117
|
}
|
|
108
118
|
|
|
109
|
-
if (!panelInstance.currentBgColorButton.querySelector('
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
img.style.cssText = 'width: 20px; height: 20px; pointer-events: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
|
|
114
|
-
panelInstance.currentBgColorButton.appendChild(img);
|
|
119
|
+
if (!panelInstance.currentBgColorButton.querySelector('.no-color-icon')) {
|
|
120
|
+
panelInstance.currentBgColorButton.appendChild(
|
|
121
|
+
createNoColorIcon('position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);')
|
|
122
|
+
);
|
|
115
123
|
}
|
|
116
124
|
} else {
|
|
117
125
|
panelInstance.currentBgColorButton.style.backgroundColor = color;
|
|
118
126
|
panelInstance.currentBgColorButton.title = `Цвет выделения: ${color}`;
|
|
119
127
|
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
128
|
+
const icon = panelInstance.currentBgColorButton.querySelector('.no-color-icon');
|
|
129
|
+
if (icon) {
|
|
130
|
+
icon.remove();
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
const line = panelInstance.currentBgColorButton.querySelector('div');
|
|
@@ -514,11 +522,7 @@ function createHighlightColorGrid(panelInstance, container) {
|
|
|
514
522
|
position: relative;
|
|
515
523
|
`;
|
|
516
524
|
|
|
517
|
-
|
|
518
|
-
img.src = '/icons/no-color.svg';
|
|
519
|
-
img.className = 'no-color-icon';
|
|
520
|
-
img.style.cssText = 'width: 20px; height: 20px; pointer-events: none;';
|
|
521
|
-
colorButton.appendChild(img);
|
|
525
|
+
colorButton.appendChild(createNoColorIcon());
|
|
522
526
|
} else {
|
|
523
527
|
colorButton.style.cssText = `
|
|
524
528
|
width: 28px;
|
|
@@ -668,11 +672,7 @@ function createBackgroundColorGrid(panelInstance, container) {
|
|
|
668
672
|
position: relative;
|
|
669
673
|
`;
|
|
670
674
|
|
|
671
|
-
|
|
672
|
-
img.src = '/icons/no-color.svg';
|
|
673
|
-
img.className = 'no-color-icon';
|
|
674
|
-
img.style.cssText = 'width: 20px; height: 20px; pointer-events: none;';
|
|
675
|
-
colorButton.appendChild(img);
|
|
675
|
+
colorButton.appendChild(createNoColorIcon());
|
|
676
676
|
} else {
|
|
677
677
|
colorButton.style.cssText = `
|
|
678
678
|
width: 28px;
|