@sequent-org/moodboard 1.4.54 → 1.4.56

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.
@@ -11,6 +11,15 @@ const SVG_ITALIC = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="1
11
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
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
13
 
14
+ const SVG_ALIGN_LEFT = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M4.75 5.73633H19.25"/><path d="M4.75 18.2637H13.25"/><path d="M4.75 14.0879H19.25"/><path d="M4.75 9.91211L13.25 9.91211"/></svg>';
15
+ const SVG_ALIGN_CENTER = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M4.75 5.73633H19.25"/><path d="M7.75 18.2637H16.25"/><path d="M4.75 14.0879H19.25"/><path d="M7.75 9.91211L16.25 9.91211"/></svg>';
16
+ const SVG_ALIGN_RIGHT = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M4.75 5.73633H19.25"/><path d="M10.75 18.2637H19.25"/><path d="M4.75 14.0879H19.25"/><path d="M10.75 9.91211L19.25 9.91211"/></svg>';
17
+ const SVG_ALIGN_JUSTIFY = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M4.75 5.73633H19.25"/><path d="M4.75 9.91211H19.25"/><path d="M4.75 14.0879H19.25"/><path d="M4.75 18.2637H19.25"/></svg>';
18
+
19
+ const SVG_LIST_BULLET = '<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.28516 5.49609H19.2852M8.28516 11H19.2852M8.28516 16.4961H19.2852M4.21484 17.25C4.62906 17.25 4.96484 16.9142 4.96484 16.5C4.96484 16.0858 4.62906 15.75 4.21484 15.75C3.80063 15.75 3.46484 16.0858 3.46484 16.5C3.46484 16.9142 3.80063 17.25 4.21484 17.25ZM4.21484 11.75C4.62906 11.75 4.96484 11.4142 4.96484 11C4.96484 10.5858 4.62906 10.25 4.21484 10.25C3.80063 10.25 3.46484 10.5858 3.46484 11C3.46484 11.4142 3.80063 11.75 4.21484 11.75ZM4.21484 6.24609C4.62906 6.24609 4.96484 5.91031 4.96484 5.49609C4.96484 5.08188 4.62906 4.74609 4.21484 4.74609C3.80063 4.74609 3.46484 5.08188 3.46484 5.49609C3.46484 5.91031 3.80063 6.24609 4.21484 6.24609Z"/></svg>';
20
+ const SVG_LIST_NUMBERED = '<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8.28516 5.49505H19.2852M8.28516 10.999H19.2852M8.28516 16.4951H19.2852M3.30078 5.32812L4.67578 4.64062V9.45312M3.39531 13.2839C3.45867 13.1269 3.55438 12.9853 3.67622 12.8678C3.79808 12.7505 3.9433 12.6602 4.10242 12.6027C4.26156 12.5453 4.43101 12.5221 4.59972 12.5345C4.76844 12.547 4.93262 12.595 5.08156 12.6752C5.23049 12.7555 5.36082 12.8662 5.46407 13.0002C5.56729 13.1343 5.6411 13.2885 5.68067 13.453C5.72022 13.6175 5.72466 13.7885 5.69366 13.9549C5.66266 14.1211 5.59693 14.279 5.50078 14.4182L3.30078 17.3573H5.70703"/></svg>';
21
+ const SVG_LIST_CHECK = '<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8.28516 5.49609H19.2852M8.28516 11H19.2852M8.28516 16.4961H19.2852M2.9 5.2L3.9 6.2L5.7 4.2M2.9 10.7L3.9 11.7L5.7 9.7M2.9 16.2L3.9 17.2L5.7 15.2"/></svg>';
22
+
14
23
  function makeSep() {
15
24
  const d = document.createElement('div');
16
25
  d.style.cssText = 'width:1px;height:18px;background:#e0e0e0;margin:0 6px;flex-shrink:0;';
@@ -27,16 +36,216 @@ function makeToggleBtn(svgStr, title, prop) {
27
36
  return btn;
28
37
  }
29
38
 
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);
39
+ function makeListButtons() {
40
+ const container = document.createElement('div');
41
+ container.className = 'tpp-list-btns';
42
+
43
+ const listOptions = [
44
+ { value: 'bullet', title: 'Маркированный список', svg: SVG_LIST_BULLET },
45
+ { value: 'numbered', title: 'Нумерованный список', svg: SVG_LIST_NUMBERED },
46
+ { value: 'checkbox', title: 'Список с флажками', svg: SVG_LIST_CHECK },
47
+ ];
48
+
49
+ let currentValue = 'none';
50
+ const changeListeners = [];
51
+
52
+ // Кнопка-триггер: всегда показывает иконку bullet-list и открывает модальное окно
53
+ const triggerBtn = document.createElement('button');
54
+ triggerBtn.type = 'button';
55
+ triggerBtn.title = 'Список';
56
+ triggerBtn.className = 'tpp-format-btn tpp-list-trigger';
57
+ triggerBtn.innerHTML = SVG_LIST_BULLET;
58
+
59
+ const modal = document.createElement('div');
60
+ modal.className = 'tpp-list-modal';
61
+ modal.style.display = 'none';
62
+
63
+ listOptions.forEach(({ value, title, svg }) => {
64
+ const optBtn = document.createElement('button');
65
+ optBtn.type = 'button';
66
+ optBtn.title = title;
67
+ optBtn.className = 'tpp-list-modal-option';
68
+ optBtn.dataset.listValue = value;
69
+ optBtn.innerHTML = svg;
70
+ optBtn.addEventListener('click', (e) => {
71
+ e.stopPropagation();
72
+ // Клик по выбранному элементу снимает форматирование (none).
73
+ currentValue = currentValue === value ? 'none' : value;
74
+ closeModal();
75
+ updateActive();
76
+ changeListeners.forEach((cb) => cb({ target: proxy }));
77
+ });
78
+ modal.appendChild(optBtn);
79
+ });
80
+
81
+ container.appendChild(triggerBtn);
82
+ container.appendChild(modal);
83
+
84
+ function openModal() {
85
+ modal.style.display = 'flex';
86
+ updateModalActive();
87
+ setTimeout(() => {
88
+ document.addEventListener('click', closeOnOutside);
89
+ }, 0);
90
+ }
91
+
92
+ function closeModal() {
93
+ modal.style.display = 'none';
94
+ document.removeEventListener('click', closeOnOutside);
95
+ }
96
+
97
+ function closeOnOutside(e) {
98
+ if (!container.contains(e.target)) {
99
+ closeModal();
100
+ }
101
+ }
102
+
103
+ function updateActive() {
104
+ triggerBtn.classList.toggle('is-active', currentValue !== 'none');
105
+ updateModalActive();
106
+ }
107
+
108
+ function updateModalActive() {
109
+ modal.querySelectorAll('.tpp-list-modal-option').forEach((btn) => {
110
+ const isActive = btn.dataset.listValue === currentValue;
111
+ btn.style.color = isActive ? 'rgb(53, 56, 205)' : '';
112
+ });
113
+ }
114
+
115
+ triggerBtn.addEventListener('click', (e) => {
116
+ e.stopPropagation();
117
+ if (modal.style.display === 'none') {
118
+ openModal();
119
+ } else {
120
+ closeModal();
121
+ }
122
+ });
123
+
124
+ const proxy = {
125
+ get value() {
126
+ return currentValue;
127
+ },
128
+ set value(v) {
129
+ currentValue = v || 'none';
130
+ updateActive();
131
+ },
132
+ addEventListener(event, cb) {
133
+ if (event === 'change') changeListeners.push(cb);
134
+ },
135
+ _container: container,
136
+ };
137
+
138
+ updateActive();
139
+ return proxy;
140
+ }
141
+
142
+ function makeAlignButtons() {
143
+ const container = document.createElement('div');
144
+ container.className = 'tpp-align-btns';
145
+
146
+ const alignOptions = [
147
+ { value: 'left', title: 'По левому краю', svg: SVG_ALIGN_LEFT },
148
+ { value: 'center', title: 'По центру', svg: SVG_ALIGN_CENTER },
149
+ { value: 'right', title: 'По правому краю', svg: SVG_ALIGN_RIGHT },
150
+ { value: 'justify', title: 'По ширине', svg: SVG_ALIGN_JUSTIFY },
151
+ ];
152
+
153
+ let currentValue = 'left';
154
+ const changeListeners = [];
155
+
156
+ // Единственная кнопка — отображает текущее выравнивание и открывает модальное окно
157
+ const triggerBtn = document.createElement('button');
158
+ triggerBtn.type = 'button';
159
+ triggerBtn.className = 'tpp-format-btn tpp-align-btn tpp-align-trigger';
160
+ triggerBtn.dataset.alignValue = 'left';
161
+
162
+ // Модальный попап
163
+ const modal = document.createElement('div');
164
+ modal.className = 'tpp-align-modal';
165
+ modal.style.display = 'none';
166
+
167
+ alignOptions.forEach(({ value, title, svg }) => {
168
+ const optBtn = document.createElement('button');
169
+ optBtn.type = 'button';
170
+ optBtn.title = title;
171
+ optBtn.className = 'tpp-align-modal-option';
172
+ optBtn.dataset.alignValue = value;
173
+ optBtn.innerHTML = svg;
174
+ optBtn.addEventListener('click', (e) => {
175
+ e.stopPropagation();
176
+ currentValue = value;
177
+ closeModal();
178
+ updateActive();
179
+ changeListeners.forEach((cb) => cb({ target: proxy }));
180
+ });
181
+ modal.appendChild(optBtn);
182
+ });
183
+
184
+ container.appendChild(triggerBtn);
185
+ container.appendChild(modal);
186
+
187
+ function openModal() {
188
+ modal.style.display = 'flex';
189
+ updateModalActive();
190
+ // Закрытие по клику вне
191
+ setTimeout(() => {
192
+ document.addEventListener('click', closeOnOutside);
193
+ }, 0);
194
+ }
195
+
196
+ function closeModal() {
197
+ modal.style.display = 'none';
198
+ document.removeEventListener('click', closeOnOutside);
199
+ }
200
+
201
+ function closeOnOutside(e) {
202
+ if (!container.contains(e.target)) {
203
+ closeModal();
204
+ }
205
+ }
206
+
207
+ function updateActive() {
208
+ const opt = alignOptions.find((o) => o.value === currentValue);
209
+ if (opt) {
210
+ triggerBtn.innerHTML = opt.svg;
211
+ triggerBtn.title = opt.title;
212
+ triggerBtn.dataset.alignValue = currentValue;
213
+ }
214
+ updateModalActive();
215
+ }
216
+
217
+ function updateModalActive() {
218
+ modal.querySelectorAll('.tpp-align-modal-option').forEach((btn) => {
219
+ const isActive = btn.dataset.alignValue === currentValue;
220
+ btn.style.color = isActive ? 'rgb(53, 56, 205)' : '';
221
+ });
222
+ }
223
+
224
+ triggerBtn.addEventListener('click', (e) => {
225
+ e.stopPropagation();
226
+ if (modal.style.display === 'none') {
227
+ openModal();
228
+ } else {
229
+ closeModal();
230
+ }
38
231
  });
39
- return sel;
232
+
233
+ const proxy = {
234
+ get value() {
235
+ return currentValue;
236
+ },
237
+ set value(v) {
238
+ currentValue = v;
239
+ updateActive();
240
+ },
241
+ addEventListener(event, cb) {
242
+ if (event === 'change') changeListeners.push(cb);
243
+ },
244
+ _container: container,
245
+ };
246
+
247
+ updateActive();
248
+ return proxy;
40
249
  }
41
250
 
42
251
  export function createTextFormatControls(panelInstance, panel) {
@@ -53,21 +262,11 @@ export function createTextFormatControls(panelInstance, panel) {
53
262
 
54
263
  panel.appendChild(makeSep());
55
264
 
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);
265
+ panelInstance.alignControl = makeAlignButtons();
266
+ panel.appendChild(panelInstance.alignControl._container);
267
+
268
+ panelInstance.listControl = makeListButtons();
269
+ panel.appendChild(panelInstance.listControl._container);
71
270
 
72
271
  panel.appendChild(makeSep());
73
272
 
@@ -7,19 +7,80 @@ function hidePresetTicks(buttons) {
7
7
  });
8
8
  }
9
9
 
10
+ function setFontDropdownOpen(panel, isOpen) {
11
+ if (!panel.fontDropdown || !panel.fontSelect) {
12
+ return;
13
+ }
14
+ panel.fontDropdown.classList.toggle('is-open', isOpen);
15
+ panel.fontSelect.classList.toggle('is-active', isOpen);
16
+ panel.fontSelect.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
17
+ }
18
+
10
19
  export function bindTextPropertiesPanelControls(panel) {
11
20
  if (panel._bindingsAttached) {
12
21
  return;
13
22
  }
14
23
 
15
- panel.fontSelect.addEventListener('change', (event) => {
16
- panel._changeFontFamily(event.target.value);
24
+ panel.fontSelect.addEventListener('click', (event) => {
25
+ event.stopPropagation();
26
+ const willOpen = !panel.fontDropdown.classList.contains('is-open');
27
+ setFontDropdownOpen(panel, willOpen);
17
28
  });
18
29
 
30
+ panel.fontSelect.addEventListener('keydown', (event) => {
31
+ if (event.key === 'Enter' || event.key === ' ') {
32
+ event.preventDefault();
33
+ const willOpen = !panel.fontDropdown.classList.contains('is-open');
34
+ setFontDropdownOpen(panel, willOpen);
35
+ } else if (event.key === 'Escape') {
36
+ setFontDropdownOpen(panel, false);
37
+ }
38
+ });
39
+
40
+ panel.fontDropdown.querySelectorAll('.font-dropdown__item').forEach((item) => {
41
+ item.addEventListener('click', (event) => {
42
+ event.stopPropagation();
43
+ const value = item.dataset.value;
44
+ panel.fontSelect.value = value;
45
+ panel._changeFontFamily(value);
46
+ setFontDropdownOpen(panel, false);
47
+ });
48
+ });
49
+
50
+ panel._onFontDocumentClick = (event) => {
51
+ if (!panel._fontSelectWrapper || !event.target) {
52
+ return;
53
+ }
54
+ if (!panel._fontSelectWrapper.contains(event.target)) {
55
+ setFontDropdownOpen(panel, false);
56
+ }
57
+ };
58
+ document.addEventListener('click', panel._onFontDocumentClick);
59
+
19
60
  panel.fontSizeSelect.addEventListener('change', (event) => {
20
61
  panel._changeFontSize(parseInt(event.target.value, 10));
21
62
  });
22
63
 
64
+ if (panel.fontSizeUpBtn) {
65
+ panel.fontSizeUpBtn.addEventListener('click', () => {
66
+ const select = panel.fontSizeSelect;
67
+ if (select.selectedIndex < select.options.length - 1) {
68
+ select.selectedIndex++;
69
+ select.dispatchEvent(new Event('change'));
70
+ }
71
+ });
72
+ }
73
+
74
+ if (panel.fontSizeDownBtn) {
75
+ panel.fontSizeDownBtn.addEventListener('click', () => {
76
+ const select = panel.fontSizeSelect;
77
+ if (select.selectedIndex > 0) {
78
+ select.selectedIndex--;
79
+ select.dispatchEvent(new Event('change'));
80
+ }
81
+ });
82
+ }
83
+
23
84
  panel.currentColorButton.addEventListener('click', (event) => {
24
85
  event.stopPropagation();
25
86
  panel._toggleColorDropdown();
@@ -115,5 +176,10 @@ export function unbindTextPropertiesPanelControls(panel) {
115
176
  panel._onBgDocumentClick = null;
116
177
  }
117
178
 
179
+ if (panel._onFontDocumentClick) {
180
+ document.removeEventListener('click', panel._onFontDocumentClick);
181
+ panel._onFontDocumentClick = null;
182
+ }
183
+
118
184
  panel._bindingsAttached = false;
119
185
  }
@@ -99,54 +99,109 @@ export function updateCurrentBgColorButton(panelInstance, color) {
99
99
  }
100
100
 
101
101
  function createFontControls(panelInstance, panel) {
102
- const fontLabel = document.createElement('span');
103
- fontLabel.textContent = 'Шрифт:';
104
- fontLabel.className = 'tpp-label';
105
- panel.appendChild(fontLabel);
106
-
107
- panelInstance.fontSelect = document.createElement('select');
108
- panelInstance.fontSelect.className = 'font-select';
109
- panelInstance.fontSelect.className = 'font-select';
102
+ const fontWrapper = document.createElement('div');
103
+ fontWrapper.className = 'font-select-wrapper';
104
+
105
+ const trigger = document.createElement('div');
106
+ trigger.className = 'font-select';
107
+ trigger.setAttribute('role', 'combobox');
108
+ trigger.setAttribute('tabindex', '0');
109
+ trigger.setAttribute('aria-haspopup', 'listbox');
110
+ trigger.setAttribute('aria-expanded', 'false');
111
+
112
+ const triggerLabel = document.createElement('span');
113
+ triggerLabel.className = 'font-select__label';
114
+ trigger.appendChild(triggerLabel);
115
+
116
+ const dropdown = document.createElement('div');
117
+ dropdown.className = 'font-dropdown';
118
+ dropdown.setAttribute('role', 'listbox');
119
+
120
+ const optionElements = [];
121
+ const optionRefs = FONT_OPTIONS.map((font) => {
122
+ const item = document.createElement('button');
123
+ item.type = 'button';
124
+ item.className = 'font-dropdown__item';
125
+ item.setAttribute('role', 'option');
126
+ item.dataset.value = font.value;
127
+ item.textContent = font.name;
128
+ item.style.fontFamily = font.value;
129
+ dropdown.appendChild(item);
130
+ optionElements.push(item);
131
+ return { value: font.value, textContent: font.name, element: item };
132
+ });
110
133
 
111
- FONT_OPTIONS.forEach((font) => {
112
- const option = document.createElement('option');
113
- option.value = font.value;
114
- option.textContent = font.name;
115
- option.style.fontFamily = font.value;
116
- panelInstance.fontSelect.appendChild(option);
134
+ fontWrapper.appendChild(trigger);
135
+ fontWrapper.appendChild(dropdown);
136
+ panel.appendChild(fontWrapper);
137
+
138
+ let currentValue = FONT_OPTIONS[0].value;
139
+ const applyValue = (newValue) => {
140
+ currentValue = newValue;
141
+ const match = optionRefs.find((opt) => opt.value === newValue);
142
+ triggerLabel.textContent = match ? match.textContent : newValue;
143
+ triggerLabel.style.fontFamily = newValue;
144
+ optionRefs.forEach((opt) => {
145
+ opt.element.classList.toggle('is-active', opt.value === newValue);
146
+ });
147
+ };
148
+ applyValue(currentValue);
149
+
150
+ Object.defineProperty(trigger, 'value', {
151
+ configurable: true,
152
+ get() {
153
+ return currentValue;
154
+ },
155
+ set(newValue) {
156
+ applyValue(newValue);
157
+ },
158
+ });
159
+ Object.defineProperty(trigger, 'options', {
160
+ configurable: true,
161
+ get() {
162
+ return optionElements;
163
+ },
117
164
  });
118
165
 
119
- panel.appendChild(panelInstance.fontSelect);
166
+ panelInstance.fontSelect = trigger;
167
+ panelInstance.fontDropdown = dropdown;
168
+ panelInstance._fontSelectWrapper = fontWrapper;
120
169
 
121
- const sizeLabel = document.createElement('span');
122
- sizeLabel.textContent = 'Размер:';
123
- sizeLabel.className = 'tpp-label tpp-label--spaced';
124
- panel.appendChild(sizeLabel);
170
+ const fontSizeWrapper = document.createElement('div');
171
+ fontSizeWrapper.className = 'font-size-wrapper';
125
172
 
126
173
  panelInstance.fontSizeSelect = document.createElement('select');
127
174
  panelInstance.fontSizeSelect.className = 'font-size-select';
128
- panelInstance.fontSizeSelect.className = 'font-size-select';
129
175
 
130
176
  FONT_SIZE_OPTIONS.forEach((size) => {
131
177
  const option = document.createElement('option');
132
178
  option.value = size;
133
- option.textContent = `${size}px`;
179
+ option.textContent = String(size);
134
180
  panelInstance.fontSizeSelect.appendChild(option);
135
181
  });
136
182
 
137
- panel.appendChild(panelInstance.fontSizeSelect);
183
+ const stepperContainer = document.createElement('div');
184
+ stepperContainer.className = 'font-size-steppers';
138
185
 
139
- const colorLabel = document.createElement('span');
140
- colorLabel.textContent = 'Цвет:';
141
- colorLabel.className = 'tpp-label tpp-label--spaced';
142
- panel.appendChild(colorLabel);
186
+ panelInstance.fontSizeUpBtn = document.createElement('button');
187
+ panelInstance.fontSizeUpBtn.type = 'button';
188
+ panelInstance.fontSizeUpBtn.className = 'font-size-stepper font-size-stepper--up';
189
+ panelInstance.fontSizeUpBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" style="top: 1px;"><path d="M8.25 6.75L5 3.25L1.75 6.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
143
190
 
144
- createCompactColorSelector(panelInstance, panel);
191
+ panelInstance.fontSizeDownBtn = document.createElement('button');
192
+ panelInstance.fontSizeDownBtn.type = 'button';
193
+ panelInstance.fontSizeDownBtn.className = 'font-size-stepper font-size-stepper--down';
194
+ panelInstance.fontSizeDownBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="bottom: 1px;"><path d="M8.25 3.25L5 6.75L1.75 3.25"></path></svg>';
195
+
196
+ stepperContainer.appendChild(panelInstance.fontSizeUpBtn);
197
+ stepperContainer.appendChild(panelInstance.fontSizeDownBtn);
198
+
199
+ fontSizeWrapper.appendChild(panelInstance.fontSizeSelect);
200
+ fontSizeWrapper.appendChild(stepperContainer);
145
201
 
146
- const bgColorLabel = document.createElement('span');
147
- bgColorLabel.textContent = 'Фон:';
148
- bgColorLabel.className = 'tpp-label tpp-label--spaced';
149
- panel.appendChild(bgColorLabel);
202
+ panel.appendChild(fontSizeWrapper);
203
+
204
+ createCompactColorSelector(panelInstance, panel);
150
205
 
151
206
  createCompactBackgroundSelector(panelInstance, panel);
152
207
 
@@ -49,7 +49,7 @@ export class StyleLoader {
49
49
  }
50
50
 
51
51
  .font-select { min-width: 110px; }
52
- .font-size-select { min-width: 56px; }
52
+ .font-size-select { min-width: 32px; }
53
53
 
54
54
  .text-properties-panel .tpp-label--spaced { margin-left: 6px; }
55
55