@libs-ui/components-inputs-quill2x 0.2.357-16 → 0.2.357-18

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.
@@ -683,23 +683,63 @@ const getStylesConvertInline = (customStyles) => {
683
683
 
684
684
  /* eslint-disable @typescript-eslint/no-explicit-any */
685
685
  const Embed = Quill2x.import('blots/embed');
686
+ // Đặt trên WRAPPER riêng vì Embed constructor của quill 2 sẽ bọc children vào contentNode — style
687
+ // để trên node ngoài sẽ không còn ôm trực tiếp các piece sau khi bị bọc.
688
+ const CHIP_WRAPPER_STYLE = 'display:inline-flex;align-items:center;gap:4px;padding:0 6px;height:20px;max-width:100%;overflow:hidden;border-radius:4px;background:#f1f3f5;vertical-align:middle;white-space:nowrap;user-select:none';
689
+ // Dựng nội dung chip từ attr `chip` (JSON mảng pieces) — textContent thuần, KHÔNG innerHTML (an toàn XSS).
690
+ // JSON hỏng → fallback hiển thị text `value` như blot thường.
691
+ const buildChipContent = (node, chip, value) => {
692
+ let pieces;
693
+ try {
694
+ pieces = JSON.parse(chip);
695
+ }
696
+ catch {
697
+ node.innerText += value;
698
+ return;
699
+ }
700
+ if (!Array.isArray(pieces) || !pieces.length) {
701
+ node.innerText += value;
702
+ return;
703
+ }
704
+ const wrapper = document.createElement('span');
705
+ wrapper.setAttribute('style', CHIP_WRAPPER_STYLE);
706
+ pieces.forEach((piece) => {
707
+ const span = document.createElement('span');
708
+ if (piece.className) {
709
+ span.className = piece.className;
710
+ }
711
+ if (piece.text !== undefined) {
712
+ span.textContent = piece.text;
713
+ }
714
+ wrapper.appendChild(span);
715
+ });
716
+ node.appendChild(wrapper);
717
+ };
686
718
  class QuillMention2xBlot extends Embed {
687
719
  static blotName = 'mention';
688
720
  static tagName = 'span';
689
721
  static className = 'libs-ui-quill-mention';
690
722
  static create(data) {
691
723
  const node = super.create();
692
- node.innerText += data.value;
693
724
  node.setAttribute('id', data.id);
694
725
  node.setAttribute('feId', data.feId);
695
726
  node.setAttribute('value', data.value);
727
+ if (data.chip) {
728
+ // Chip giàu (icon + nhãn) — pieces do consumer cấp qua IMentionItem.mentionInsertData.
729
+ node.setAttribute('chip', data.chip);
730
+ buildChipContent(node, data.chip, data.value);
731
+ return node;
732
+ }
733
+ node.innerText += data.value;
696
734
  return node;
697
735
  }
698
736
  static value(domNode) {
737
+ const chip = domNode.getAttribute('chip');
699
738
  return {
700
739
  id: domNode.getAttribute('id'),
701
740
  feId: domNode.getAttribute('feId'),
702
741
  value: domNode.getAttribute('value'),
742
+ ...(chip ? { chip } : {}),
703
743
  };
704
744
  }
705
745
  attach() {
@@ -1638,7 +1678,12 @@ class LibsUiComponentsInputsQuill2xComponent {
1638
1678
  handler: (range) => this.noPreventEmbedDeletion(range),
1639
1679
  },
1640
1680
  enter: {
1641
- key: 13,
1681
+ // Quill 2 match binding theo event.key (string) — key số 13 (keyCode kiểu Quill 1) không bao giờ
1682
+ // match ⇒ Enter khi mention đang mở vẫn chèn newline (insertMention từng phải bù thủ công).
1683
+ key: 'Enter',
1684
+ // shiftKey: null ⇒ match cả Enter lẫn Shift+Enter. Mention directive KHÔNG loại trừ shiftKey khi
1685
+ // chèn mention, nên thiếu dòng này thì Shift+Enter vẫn rơi vào default handler và chèn newline dư.
1686
+ shiftKey: null,
1642
1687
  handler: () => !this.showMention(),
1643
1688
  },
1644
1689
  },
@@ -2192,20 +2237,24 @@ class LibsUiComponentsInputsQuill2xComponent {
2192
2237
  instance?.outClose.subscribe(() => this.dynamicComponentService.delete(this.addEditLinkComponentRef()));
2193
2238
  this.dynamicComponentService.addToBody(this.addEditLinkComponentRef());
2194
2239
  }
2240
+ // Chèn blot mention thay cho chuỗi '<triggerChar><search>' người dùng vừa gõ.
2241
+ // Caret đang đứng NGAY SAU chuỗi đó (lengthKey ký tự, tính cả trigger char) → vị trí chèn = caret - lengthKey.
2242
+ // Công thức cũ trừ thêm 1 (heuristic '@') làm cửa sổ xóa lệch trái 1 ký tự: sót trigger char trong nội dung,
2243
+ // nuốt nhầm ký tự đứng trước; mention ở đầu document còn ra index -1 → insertEmbed throw IndexSizeError.
2195
2244
  insertMention(event) {
2196
- const range = this.quill?.getSelection();
2245
+ // Click chuột chọn item trên list (append body) có thể làm editor mất selection → getSelection(true)
2246
+ // focus lại editor và trả về range đã lưu trước khi blur.
2247
+ const range = this.quill?.getSelection() ?? this.quill?.getSelection(true);
2197
2248
  if (!range) {
2198
2249
  return;
2199
2250
  }
2200
- const indexSubtract = this.quill?.getText(range.index - 1, 1)?.includes('@') ? 0 : 1;
2201
- const cursorPos = range.index - indexSubtract;
2202
- const startPosInsert = cursorPos - event.lengthKey;
2203
- this.quill?.deleteText(startPosInsert, event.lengthKey, Quill2x.sources.SILENT);
2251
+ // Kẹp CẢ vị trí lẫn độ dài: nếu lengthKey > range.index (caret chưa đủ tự phía trước, vd range khôi
2252
+ // phục về 0) chỉ kẹp vị trí thì cửa sổ xóa trượt phải và nuốt mất ký tự đứng sau caret.
2253
+ const startPosInsert = Math.max(range.index - event.lengthKey, 0);
2254
+ const lengthDelete = Math.min(event.lengthKey, range.index);
2255
+ this.quill?.deleteText(startPosInsert, lengthDelete, Quill2x.sources.SILENT);
2204
2256
  this.quill?.insertEmbed(startPosInsert, 'mention', event.data, Quill2x.sources.SILENT);
2205
2257
  this.insertText(' ', startPosInsert + 1);
2206
- if (indexSubtract) {
2207
- this.quill?.deleteText(startPosInsert + 3, 1, Quill2x.sources.SILENT);
2208
- }
2209
2258
  this.quill?.setSelection(startPosInsert + 2, 0, Quill2x.sources.SILENT);
2210
2259
  }
2211
2260
  updateValueByRootQuillHtml() {