@brandup/ui-richeditor 1.0.39 → 1.0.40

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 CHANGED
@@ -27,12 +27,12 @@
27
27
  "email": "it@brandup.online"
28
28
  },
29
29
  "license": "Apache-2.0",
30
- "version": "1.0.39",
30
+ "version": "1.0.40",
31
31
  "main": "source/index.ts",
32
32
  "types": "source/index.ts",
33
33
  "dependencies": {
34
34
  "@brandup/ui": "^2.0.7",
35
- "@brandup/ui-kit": "^1.0.39"
35
+ "@brandup/ui-kit": "^1.0.40"
36
36
  },
37
37
  "files": [
38
38
  "source",
@@ -274,7 +274,21 @@ export default class RichEditor extends UIElementBound<RichEditorEvents> {
274
274
  if (this.readonly) return;
275
275
 
276
276
  const target = container ?? initiator.parentElement;
277
- if (target) formatToolbar.openEmoji(this, initiator, target);
277
+ if (!target) return;
278
+
279
+ formatToolbar.openEmoji(this, initiator, target);
280
+
281
+ // По кнопке могли нажать, ни разу не заходя в поле, — тогда каретки нет и вставлять символ
282
+ // некуда. Фокусировать вслепую нельзя: focus() сбросил бы уже стоящую каретку, и символ
283
+ // уехал бы не туда. Конец содержимого назначаем сами: фокус ставит каретку в начало,
284
+ // и дописанный к сообщению символ оказался бы перед текстом.
285
+ //
286
+ // Строго после открытия панели: фокус показывает тулбар, а придержать его панель успевает
287
+ // только когда открыта сама.
288
+ if (!this.selection) {
289
+ this.focus();
290
+ caretToEnd(this.editable, this.multiline);
291
+ }
278
292
  }
279
293
 
280
294
  getLength(): number {
@@ -908,6 +922,18 @@ export default class RichEditor extends UIElementBound<RichEditorEvents> {
908
922
 
909
923
  insertFormattedText(this.editable, data, Array.from(this.__pendingFormats), selection);
910
924
 
925
+ // В пустом поле абзацев ещё нет, и каретке некуда встать внутрь — вставка из кода
926
+ // (смайлик, переменная) ложится прямо в редактор. Приводим к модели абзацев тем же
927
+ // способом, что и обработчик input при печати: он на программную вставку не приходит,
928
+ // а без <p> следующий Enter правит текст мимо абзаца.
929
+ if (this.multiline)
930
+ preserveCaret(this.editable, () => {
931
+ const before = this.editable.innerHTML;
932
+ ensureParagraphs(this.editable);
933
+
934
+ return this.editable.innerHTML !== before;
935
+ });
936
+
911
937
  this.__emitChange();
912
938
  formatToolbar.refresh();
913
939
  }
package/source/toolbar.ts CHANGED
@@ -85,6 +85,7 @@ class FormatToolbar {
85
85
  private __actionButtons: Array<[EditorAction, HTMLButtonElement]> = [];
86
86
  private __hostButtons: Array<[ToolbarButton, HTMLButtonElement]> = [];
87
87
  private __active: ToolbarHost | null = null;
88
+ private __suspended: ToolbarHost | null = null; // показ придержан на время панели смайликов
88
89
  private __toolsKey = "";
89
90
  private __inContainer = false;
90
91
  private readonly __reposition = () => this.__schedule("position");
@@ -140,6 +141,9 @@ class FormatToolbar {
140
141
 
141
142
  /** Показать тулбар для редактора (на фокусе): перестроить кнопки, спозиционировать, показать. */
142
143
  attach(host: ToolbarHost) {
144
+ // открыта панель смайликов у собственной кнопки хоста — показ отложен до её закрытия
145
+ if (this.__suspended === host) return;
146
+
143
147
  const actions = host.editorActions ?? [];
144
148
  const buttons = host.toolbarButtons ?? [];
145
149
  if (!host.formatTools.length && !actions.length && !buttons.length) return;
@@ -177,6 +181,10 @@ class FormatToolbar {
177
181
 
178
182
  /** Скрыть тулбар, если он обслуживает этот редактор (на blur/destroy). */
179
183
  detach(host: ToolbarHost) {
184
+ // Придержанный показ снимаем первым делом, до закрытия панели: иначе её onClose поднял бы
185
+ // тулбар над редактором, который как раз уходит (в том числе разрушается).
186
+ if (this.__suspended === host) this.__suspended = null;
187
+
180
188
  // панель смайликов могла быть открыта не для активного редактора (у своей кнопки хоста) —
181
189
  // ссылку на него всё равно отпускаем, иначе уничтоженный редактор держится синглтоном
182
190
  const emojiHost = this.__emojiHost === host;
@@ -191,6 +199,11 @@ class FormatToolbar {
191
199
 
192
200
  if (this.__active !== host) return;
193
201
 
202
+ this.__hide();
203
+ }
204
+
205
+ /** Убрать панель с экрана и отпустить активный редактор (позиционирование, подсветка). */
206
+ private __hide() {
194
207
  this.__active = null;
195
208
  this.__cancelScheduled();
196
209
  if (this.__elem) this.__elem.classList.remove("visible");
@@ -335,7 +348,28 @@ class FormatToolbar {
335
348
  this.__emojiHost = host;
336
349
  this.__emojiInitiator = initiator;
337
350
 
338
- PopupManager.open(this.__ensureEmojiPicker(container), { initiator });
351
+ // Панель у собственной кнопки хоста — самостоятельный слой, и показывать её вместе с тулбаром
352
+ // нельзя: это два всплывающих окна над одним полем. Тулбар придерживаем на всё время работы
353
+ // панели, а показанный убираем с экрана; вернётся он сам при её закрытии, если поле осталось
354
+ // в фокусе. Панель самого тулбара (container === __elem) — его собственный выпадающий слой,
355
+ // прятать её носителя незачем и нечем.
356
+ if (container !== this.__elem) {
357
+ this.__suspended = host;
358
+ if (this.__active === host) this.__hide();
359
+ }
360
+
361
+ PopupManager.open(this.__ensureEmojiPicker(container), { initiator, onClose: () => this.__resume() });
362
+ }
363
+
364
+ /** Панель смайликов закрылась — показываем придержанный тулбар, если редактор ещё в фокусе. */
365
+ private __resume() {
366
+ const host = this.__suspended;
367
+ if (!host) return;
368
+
369
+ this.__suspended = null;
370
+
371
+ // панель могло закрыть и движение мимо поля — тогда показывать нечего
372
+ if (host.editable.ownerDocument.activeElement === host.editable) this.attach(host);
339
373
  }
340
374
 
341
375
  private __toggleEmoji(initiator: HTMLButtonElement, e: MouseEvent) {