@sequent-org/moodboard 1.4.55 → 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.
- package/package.json +1 -1
- package/src/initNoBundler.js +1 -1
- package/src/tools/object-tools/TextTool.js +9 -3
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +61 -0
- package/src/ui/HtmlTextLayer.js +6 -0
- package/src/ui/TextPropertiesPanel.js +5 -1
- package/src/ui/styles/panels.css +235 -16
- package/src/ui/styles/workspace.css +1 -1
- package/src/ui/text-properties/TextFormatControls.js +223 -24
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +68 -2
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +86 -31
- package/src/utils/styleLoader.js +1 -1
package/package.json
CHANGED
package/src/initNoBundler.js
CHANGED
|
@@ -356,7 +356,7 @@ export function forceInjectPanelStyles() {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
.font-select { min-width: 110px !important; }
|
|
359
|
-
.font-size-select { min-width:
|
|
359
|
+
.font-size-select { min-width: 32px !important; }
|
|
360
360
|
|
|
361
361
|
.current-color-button, .current-bgcolor-button, .fpp-color-button {
|
|
362
362
|
width: 28px !important;
|
|
@@ -108,7 +108,6 @@ export class TextTool extends BaseTool {
|
|
|
108
108
|
startEditingNew(textData, x, y) {
|
|
109
109
|
this.isEditing = true;
|
|
110
110
|
this.editingObject = textData;
|
|
111
|
-
|
|
112
111
|
this.createTextInput(x, y, '');
|
|
113
112
|
this.emit('text:edit:start', { object: textData });
|
|
114
113
|
}
|
|
@@ -166,7 +165,7 @@ export class TextTool extends BaseTool {
|
|
|
166
165
|
});
|
|
167
166
|
|
|
168
167
|
// Обработчики событий input
|
|
169
|
-
this.textInput.addEventListener('blur', () => this.
|
|
168
|
+
this.textInput.addEventListener('blur', (e) => this.handleBlur(e));
|
|
170
169
|
this.textInput.addEventListener('keydown', (e) => this.handleInputKeys(e));
|
|
171
170
|
this.textInput.addEventListener('input', (e) => this.handleTextChange(e));
|
|
172
171
|
|
|
@@ -183,6 +182,13 @@ export class TextTool extends BaseTool {
|
|
|
183
182
|
this.adjustInputSize();
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Обработка blur
|
|
187
|
+
*/
|
|
188
|
+
handleBlur(event) {
|
|
189
|
+
this.finishEditing();
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
/**
|
|
187
193
|
* Обработка клавиш в input
|
|
188
194
|
*/
|
|
@@ -328,7 +334,7 @@ export class TextTool extends BaseTool {
|
|
|
328
334
|
// TODO: Реализовать поиск текстового объекта по координатам
|
|
329
335
|
return null; // Временная заглушка
|
|
330
336
|
}
|
|
331
|
-
|
|
337
|
+
|
|
332
338
|
/**
|
|
333
339
|
* Обработка клавиш во время редактирования (глобальные)
|
|
334
340
|
*/
|
|
@@ -164,6 +164,22 @@ export function createTextEditorFinalize(controller, {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// Классы DOM-элементов, клики по которым не должны закрывать пустой текстовый редактор.
|
|
168
|
+
const UI_BLOCK_SELECTOR = [
|
|
169
|
+
'.moodboard-toolbar',
|
|
170
|
+
'.text-properties-layer',
|
|
171
|
+
'.moodboard-topbar',
|
|
172
|
+
'.moodboard-zoom-panel',
|
|
173
|
+
'.moodboard-ui-layer',
|
|
174
|
+
].join(', ');
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Возвращает true, если элемент находится внутри UI-панелей (тулбар, панель свойств текста и т.п.).
|
|
178
|
+
*/
|
|
179
|
+
function _isInsideToolbarUI(target) {
|
|
180
|
+
return !!(target && typeof target.closest === 'function' && target.closest(UI_BLOCK_SELECTOR));
|
|
181
|
+
}
|
|
182
|
+
|
|
167
183
|
export function bindTextEditorInteractions(controller, {
|
|
168
184
|
textarea,
|
|
169
185
|
isNewCreation,
|
|
@@ -192,6 +208,46 @@ export function bindTextEditorInteractions(controller, {
|
|
|
192
208
|
}, 0);
|
|
193
209
|
};
|
|
194
210
|
|
|
211
|
+
// Перехватываем mousedown на тулбаре/панелях в capture-фазе, чтобы:
|
|
212
|
+
// 1. Предотвратить потерю фокуса textarea (e.preventDefault()).
|
|
213
|
+
// 2. Заблокировать последующий click до ToolbarActionRouter (одноразовый capture-обработчик).
|
|
214
|
+
// Работает только пока поле ввода пусто и объект только что создан — если текст уже есть,
|
|
215
|
+
// клик по другому инструменту отрабатывает штатно (commit + переключение).
|
|
216
|
+
let _pendingClickBlocker = null;
|
|
217
|
+
const mousedownCaptureHandler = (e) => {
|
|
218
|
+
if (!controller?.textEditor?.active) return;
|
|
219
|
+
if (e.target === textarea) return;
|
|
220
|
+
if (!_isInsideToolbarUI(e.target)) return;
|
|
221
|
+
|
|
222
|
+
const value = (textarea.value || '').trim();
|
|
223
|
+
if (!isNewCreation || value.length > 0) return;
|
|
224
|
+
|
|
225
|
+
// Пустое новое поле + клик по UI: не даём сместить фокус с textarea.
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
|
|
228
|
+
// Блокируем следующий click-события, чтобы ToolbarActionRouter не переключил инструмент.
|
|
229
|
+
if (_pendingClickBlocker) {
|
|
230
|
+
document.removeEventListener('click', _pendingClickBlocker, true);
|
|
231
|
+
}
|
|
232
|
+
_pendingClickBlocker = (ce) => {
|
|
233
|
+
if (_isInsideToolbarUI(ce.target)) {
|
|
234
|
+
ce.stopPropagation();
|
|
235
|
+
ce.preventDefault();
|
|
236
|
+
}
|
|
237
|
+
document.removeEventListener('click', _pendingClickBlocker, true);
|
|
238
|
+
_pendingClickBlocker = null;
|
|
239
|
+
};
|
|
240
|
+
document.addEventListener('click', _pendingClickBlocker, true);
|
|
241
|
+
|
|
242
|
+
// Страховочный возврат фокуса на случай, если браузер всё-таки убрал фокус.
|
|
243
|
+
setTimeout(() => {
|
|
244
|
+
if (controller?.textEditor?.active && textarea) {
|
|
245
|
+
try { textarea.focus(); } catch (_) {}
|
|
246
|
+
}
|
|
247
|
+
}, 0);
|
|
248
|
+
};
|
|
249
|
+
document.addEventListener('mousedown', mousedownCaptureHandler, true);
|
|
250
|
+
|
|
195
251
|
const keydownHandler = (e) => {
|
|
196
252
|
const isList = listType && listType !== 'none';
|
|
197
253
|
if (e.key === 'Enter') {
|
|
@@ -244,6 +300,11 @@ export function bindTextEditorInteractions(controller, {
|
|
|
244
300
|
textarea.removeEventListener('blur', blurHandler);
|
|
245
301
|
textarea.removeEventListener('keydown', keydownHandler);
|
|
246
302
|
textarea.removeEventListener('input', inputHandler);
|
|
303
|
+
document.removeEventListener('mousedown', mousedownCaptureHandler, true);
|
|
304
|
+
if (_pendingClickBlocker) {
|
|
305
|
+
document.removeEventListener('click', _pendingClickBlocker, true);
|
|
306
|
+
_pendingClickBlocker = null;
|
|
307
|
+
}
|
|
247
308
|
};
|
|
248
309
|
|
|
249
310
|
if (controller.textEditor) {
|
package/src/ui/HtmlTextLayer.js
CHANGED
|
@@ -173,6 +173,12 @@ export class HtmlTextLayer {
|
|
|
173
173
|
el.style.overflowWrap = isMarkdown ? 'break-word' : '';
|
|
174
174
|
if (!isMarkdown) el.style.padding = '0';
|
|
175
175
|
}
|
|
176
|
+
// После коммита текста высота .mb-text осталась от пустого редактора (схлопнута),
|
|
177
|
+
// а ResizeUpdate при завершении редактирования отрабатывает раньше синхронизации
|
|
178
|
+
// контента. Без пере-подгонки DOM-бокс не оборачивает глифы, и рамка выделения,
|
|
179
|
+
// строящаяся по getBoundingClientRect этого блока, оказывается выше текста.
|
|
180
|
+
this._autoFitTextHeight(objectId);
|
|
181
|
+
this.updateOne(objectId);
|
|
176
182
|
console.log(`🔍 HtmlTextLayer: содержимое обновлено для ${objectId}:`, content);
|
|
177
183
|
} else {
|
|
178
184
|
console.warn(`❌ HtmlTextLayer: не удалось обновить содержимое для ${objectId}:`, { el: !!el, content });
|
|
@@ -349,7 +349,11 @@ export class TextPropertiesPanel {
|
|
|
349
349
|
const panelY = screenY - this.panel.offsetHeight - 20;
|
|
350
350
|
|
|
351
351
|
const containerRect = this.container.getBoundingClientRect();
|
|
352
|
-
const
|
|
352
|
+
const toolbarEl = document.querySelector('.moodboard-toolbar');
|
|
353
|
+
const toolbarRight = toolbarEl
|
|
354
|
+
? toolbarEl.getBoundingClientRect().right - containerRect.left + 10
|
|
355
|
+
: 10;
|
|
356
|
+
const finalX = Math.max(toolbarRight, Math.min(panelX, containerRect.width - this.panel.offsetWidth - 10));
|
|
353
357
|
const finalY = Math.max(10, panelY);
|
|
354
358
|
|
|
355
359
|
this.panel.style.left = `${Math.round(finalX)}px`;
|
package/src/ui/styles/panels.css
CHANGED
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
flex-direction: row;
|
|
196
196
|
align-items: center;
|
|
197
197
|
gap: 8px;
|
|
198
|
-
padding:
|
|
198
|
+
padding: 0 22px;
|
|
199
199
|
background-color: #ffffff;
|
|
200
200
|
border: 1px solid #e0e0e0;
|
|
201
201
|
border-radius: 9999px;
|
|
@@ -206,6 +206,10 @@
|
|
|
206
206
|
height: 36px;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
.text-properties-panel * {
|
|
210
|
+
max-height: 36px;
|
|
211
|
+
}
|
|
212
|
+
|
|
209
213
|
/* Frame properties toolbar — mirror image toolbar look */
|
|
210
214
|
.frame-properties-panel {
|
|
211
215
|
position: absolute;
|
|
@@ -604,18 +608,151 @@
|
|
|
604
608
|
|
|
605
609
|
.text-properties-panel .tpp-label--spaced { margin-left: 6px; }
|
|
606
610
|
|
|
607
|
-
.text-properties-panel .font-select
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
+
.text-properties-panel .font-select-wrapper {
|
|
612
|
+
position: relative;
|
|
613
|
+
display: inline-block;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.text-properties-panel .font-select {
|
|
617
|
+
box-sizing: border-box;
|
|
618
|
+
min-width: 90px;
|
|
619
|
+
width: 90px;
|
|
620
|
+
height: 38px;
|
|
611
621
|
padding: 4px 8px;
|
|
612
622
|
font-size: 13px;
|
|
613
623
|
background-color: #fff;
|
|
624
|
+
border: none;
|
|
625
|
+
border-radius: 4px;
|
|
626
|
+
cursor: pointer;
|
|
627
|
+
display: flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
user-select: none;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.text-properties-panel .font-select:focus { outline: none; box-shadow: none; }
|
|
633
|
+
|
|
634
|
+
.text-properties-panel .font-select__label {
|
|
635
|
+
flex: 1;
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
text-overflow: ellipsis;
|
|
638
|
+
white-space: nowrap;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.text-properties-panel .font-dropdown {
|
|
642
|
+
position: absolute;
|
|
643
|
+
top: calc(100% + 4px);
|
|
644
|
+
left: 0;
|
|
645
|
+
min-width: 180px;
|
|
646
|
+
max-height: 280px;
|
|
647
|
+
overflow-y: auto;
|
|
648
|
+
background: #fff;
|
|
649
|
+
border: 1px solid #E5E7EB;
|
|
650
|
+
border-radius: 8px;
|
|
651
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
652
|
+
padding: 4px;
|
|
653
|
+
display: none;
|
|
654
|
+
z-index: 10000;
|
|
655
|
+
scrollbar-width: thin;
|
|
656
|
+
scrollbar-color: transparent transparent;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.text-properties-panel .font-dropdown.is-open { display: block; }
|
|
660
|
+
|
|
661
|
+
.text-properties-panel .font-dropdown:hover {
|
|
662
|
+
scrollbar-color: rgba(170, 170, 170, 0.7) transparent;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.text-properties-panel .font-dropdown::-webkit-scrollbar { width: 9px; }
|
|
666
|
+
.text-properties-panel .font-dropdown::-webkit-scrollbar-button {
|
|
667
|
+
display: none;
|
|
668
|
+
height: 0;
|
|
669
|
+
width: 0;
|
|
670
|
+
}
|
|
671
|
+
.text-properties-panel .font-dropdown::-webkit-scrollbar-track { background: transparent; }
|
|
672
|
+
.text-properties-panel .font-dropdown::-webkit-scrollbar-thumb {
|
|
673
|
+
background: transparent;
|
|
674
|
+
border-radius: 4px;
|
|
675
|
+
}
|
|
676
|
+
.text-properties-panel .font-dropdown:hover::-webkit-scrollbar-thumb {
|
|
677
|
+
background: rgba(170, 170, 170, 0.7);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.text-properties-panel .font-dropdown__item {
|
|
681
|
+
display: block;
|
|
682
|
+
width: 100%;
|
|
683
|
+
padding: 6px 10px;
|
|
684
|
+
border: none;
|
|
685
|
+
background: transparent;
|
|
686
|
+
text-align: left;
|
|
687
|
+
font-size: 14px;
|
|
688
|
+
color: #111;
|
|
689
|
+
cursor: pointer;
|
|
690
|
+
border-radius: 4px;
|
|
691
|
+
white-space: nowrap;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.text-properties-panel .font-dropdown__item:hover {
|
|
695
|
+
background: #f3f4f6;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.text-properties-panel .font-dropdown__item.is-active {
|
|
699
|
+
background: #2563eb;
|
|
700
|
+
color: #fff;
|
|
701
|
+
}
|
|
702
|
+
.text-properties-panel .font-size-wrapper {
|
|
703
|
+
display: flex;
|
|
704
|
+
align-items: center;
|
|
705
|
+
position: relative;
|
|
706
|
+
border: none;
|
|
707
|
+
border-radius: 4px;
|
|
708
|
+
background-color: #fff;
|
|
709
|
+
height: 38px;
|
|
710
|
+
box-sizing: border-box;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.text-properties-panel .font-size-select {
|
|
714
|
+
outline: none;
|
|
715
|
+
height: 100%;
|
|
716
|
+
width: 32px;
|
|
717
|
+
border: none;
|
|
718
|
+
background: transparent;
|
|
719
|
+
appearance: none;
|
|
720
|
+
-webkit-appearance: none;
|
|
721
|
+
-moz-appearance: none;
|
|
722
|
+
padding: 0 4px 0 8px;
|
|
723
|
+
font-size: 13px;
|
|
614
724
|
cursor: pointer;
|
|
615
725
|
}
|
|
616
726
|
|
|
617
|
-
.text-properties-panel .font-
|
|
618
|
-
|
|
727
|
+
.text-properties-panel .font-size-steppers {
|
|
728
|
+
display: flex;
|
|
729
|
+
flex-direction: column;
|
|
730
|
+
height: 100%;
|
|
731
|
+
width: 20px;
|
|
732
|
+
border-left: none;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.text-properties-panel .font-size-stepper {
|
|
736
|
+
flex: 1;
|
|
737
|
+
display: flex;
|
|
738
|
+
align-items: center;
|
|
739
|
+
justify-content: center;
|
|
740
|
+
background: transparent;
|
|
741
|
+
border: none;
|
|
742
|
+
padding: 0;
|
|
743
|
+
cursor: pointer;
|
|
744
|
+
color: #666;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.text-properties-panel .font-size-stepper:hover {
|
|
748
|
+
background: #f5f5f5;
|
|
749
|
+
color: #333;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.text-properties-panel .font-size-stepper--down {
|
|
753
|
+
border-bottom-right-radius: 4px;
|
|
754
|
+
margin-top: -2px;
|
|
755
|
+
}
|
|
619
756
|
|
|
620
757
|
.text-properties-panel .current-color-button,
|
|
621
758
|
.text-properties-panel .current-bgcolor-button {
|
|
@@ -654,19 +791,101 @@
|
|
|
654
791
|
color: #6155F5;
|
|
655
792
|
}
|
|
656
793
|
|
|
657
|
-
.text-properties-panel .tpp-
|
|
658
|
-
|
|
659
|
-
|
|
794
|
+
.text-properties-panel .tpp-list-btns {
|
|
795
|
+
display: flex;
|
|
796
|
+
gap: 2px;
|
|
797
|
+
align-items: center;
|
|
798
|
+
position: relative;
|
|
799
|
+
}
|
|
800
|
+
.text-properties-panel .tpp-list-btns svg {
|
|
801
|
+
width: 22px;
|
|
802
|
+
height: 22px;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.text-properties-panel .tpp-list-modal {
|
|
806
|
+
max-height: none;
|
|
807
|
+
position: absolute;
|
|
808
|
+
top: calc(100% + 4px);
|
|
809
|
+
left: 0;
|
|
810
|
+
flex-direction: column;
|
|
811
|
+
gap: 2px;
|
|
812
|
+
background: #ffffff;
|
|
813
|
+
border: 1px solid #E5E7EB;
|
|
814
|
+
border-radius: 6px;
|
|
815
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
816
|
+
padding: 4px;
|
|
817
|
+
z-index: 4000;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.text-properties-panel .tpp-list-modal-option {
|
|
821
|
+
display: flex;
|
|
822
|
+
align-items: center;
|
|
823
|
+
justify-content: center;
|
|
824
|
+
width: 28px;
|
|
825
|
+
height: 28px;
|
|
826
|
+
padding: 0;
|
|
827
|
+
border: none;
|
|
828
|
+
background: transparent;
|
|
660
829
|
border-radius: 4px;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
830
|
+
cursor: pointer;
|
|
831
|
+
color: #374151;
|
|
832
|
+
transition: background 0.1s;
|
|
833
|
+
flex-shrink: 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.text-properties-panel .tpp-list-modal-option:hover {
|
|
837
|
+
background: #f3f4f6;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.text-properties-panel .tpp-align-btns {
|
|
841
|
+
display: flex;
|
|
842
|
+
gap: 2px;
|
|
843
|
+
align-items: center;
|
|
844
|
+
position: relative;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.text-properties-panel .tpp-align-btns img,
|
|
848
|
+
.text-properties-panel .tpp-align-btns svg,
|
|
849
|
+
.text-properties-panel .tpp-align-modal img,
|
|
850
|
+
.text-properties-panel .tpp-align-modal svg {
|
|
851
|
+
width: 24px;
|
|
852
|
+
height: 24px;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.text-properties-panel .tpp-align-modal {
|
|
856
|
+
max-height: none;
|
|
857
|
+
position: absolute;
|
|
858
|
+
top: calc(100% + 4px);
|
|
859
|
+
left: 0;
|
|
860
|
+
flex-direction: column;
|
|
861
|
+
gap: 2px;
|
|
862
|
+
background: #ffffff;
|
|
863
|
+
border: 1px solid #E5E7EB;
|
|
864
|
+
border-radius: 6px;
|
|
865
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
866
|
+
padding: 4px;
|
|
867
|
+
z-index: 4000;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.text-properties-panel .tpp-align-modal-option {
|
|
871
|
+
display: flex;
|
|
872
|
+
align-items: center;
|
|
873
|
+
justify-content: center;
|
|
874
|
+
width: 28px;
|
|
664
875
|
height: 28px;
|
|
876
|
+
padding: 0;
|
|
877
|
+
border: none;
|
|
878
|
+
background: transparent;
|
|
879
|
+
border-radius: 4px;
|
|
665
880
|
cursor: pointer;
|
|
666
|
-
|
|
881
|
+
color: #374151;
|
|
882
|
+
transition: background 0.1s;
|
|
883
|
+
flex-shrink: 0;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.text-properties-panel .tpp-align-modal-option:hover {
|
|
887
|
+
background: #f3f4f6;
|
|
667
888
|
}
|
|
668
|
-
.text-properties-panel .tpp-align-select { min-width: 86px; }
|
|
669
|
-
.text-properties-panel .tpp-list-select { min-width: 110px; }
|
|
670
889
|
|
|
671
890
|
.text-properties-panel .tpp-lh-slider {
|
|
672
891
|
width: 68px;
|
|
@@ -684,7 +684,7 @@
|
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
.text-properties-panel .font-select { min-width: 110px; }
|
|
687
|
-
.text-properties-panel .font-size-select { min-width:
|
|
687
|
+
.text-properties-panel .font-size-select { min-width: 32px; }
|
|
688
688
|
|
|
689
689
|
.text-properties-panel .current-color-button,
|
|
690
690
|
.text-properties-panel .current-bgcolor-button {
|
|
@@ -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
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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 =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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('
|
|
16
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
166
|
+
panelInstance.fontSelect = trigger;
|
|
167
|
+
panelInstance.fontDropdown = dropdown;
|
|
168
|
+
panelInstance._fontSelectWrapper = fontWrapper;
|
|
120
169
|
|
|
121
|
-
const
|
|
122
|
-
|
|
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 =
|
|
179
|
+
option.textContent = String(size);
|
|
134
180
|
panelInstance.fontSizeSelect.appendChild(option);
|
|
135
181
|
});
|
|
136
182
|
|
|
137
|
-
|
|
183
|
+
const stepperContainer = document.createElement('div');
|
|
184
|
+
stepperContainer.className = 'font-size-steppers';
|
|
138
185
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
panel.appendChild(bgColorLabel);
|
|
202
|
+
panel.appendChild(fontSizeWrapper);
|
|
203
|
+
|
|
204
|
+
createCompactColorSelector(panelInstance, panel);
|
|
150
205
|
|
|
151
206
|
createCompactBackgroundSelector(panelInstance, panel);
|
|
152
207
|
|
package/src/utils/styleLoader.js
CHANGED