@libs-ui/components-inputs-mention 0.2.356-8 → 0.2.357-0

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/README.md CHANGED
@@ -1,147 +1,444 @@
1
1
  # @libs-ui/components-inputs-mention
2
2
 
3
- > Thêm tính năng mention (gợi ý khi gõ ký tự trigger) vào các input, textarea hoặc contenteditable elements.
3
+ > Angular directive thêm tính năng mention (gợi ý khi gõ ký tự trigger) vào input, textarea hoặc contenteditable elements.
4
4
 
5
5
  ## Giới thiệu
6
6
 
7
- `LibsUiComponentsInputsMentionDirective` là một standalone Angular directive cho phép người dùng tìm kiếm và chọn các mục từ danh sách khi họ gõ một ký tự kích hoạt (ví dụ: `@`, `#`).
7
+ `LibsUiComponentsInputsMentionDirective` là một Angular standalone directive cho phép người dùng tìm kiếm và chọn các mục từ danh sách khi gõ một ký tự kích hoạt (ví dụ: `@`, `#`). Directive hỗ trợ đầy đủ cả input/textarea thông thường lẫn contenteditable elements và các rich text editor như Quill. Danh sách gợi ý được render thông qua dynamic component, tự định vị theo vị trí con trỏ trong văn bản.
8
8
 
9
- ### Tính năng
9
+ ## Tính năng
10
10
 
11
- - Hỗ trợ nhiều ký tự trigger với cấu hình riêng biệt.
12
- - Hoạt động trên `input`, `textarea` và các thành phần `contenteditable`.
13
- - Tích hợp sẵn với các trình soạn thảo như Quill.
14
- - Tùy chỉnh template danh sách gợi ý.
15
- - Hỗ trợ Iframe.
16
- - Angular Signals cho việc quản trạng thái.
17
- - Standalone Directive.
11
+ - Hỗ trợ nhiều ký tự trigger (`@`, `#`, ...) với cấu hình riêng biệt trong cùng một input
12
+ - Hoạt động trên `input`, `textarea` và các phần tử `contenteditable`
13
+ - Tích hợp sẵn với Quill editor (tự động phát hiện `.ql-editor`)
14
+ - Tùy chỉnh template hiển thị từng mục trong danh sách gợi ý qua `TemplateRef`
15
+ - Lọc danh sách theo từ khóa tìm kiếm (tích hợp sẵn hoặc custom filter)
16
+ - Điều hướng bàn phím: mũi tên lên/xuống, Enter để chọn, Escape để đóng
17
+ - Hỗ trợ iframe
18
+ - Expose `FunctionControl` để chèn mention thủ công từ bên ngoài
19
+ - Angular Signals + `ChangeDetectionStrategy.OnPush` cho hiệu năng tốt nhất
20
+ - Tự động dọn dẹp dynamic component khi directive bị destroy
18
21
 
19
22
  ## Khi nào sử dụng
20
23
 
21
- - Khi bạn cần tính năng mention trong khung chat hoặc bình luận.
22
- - Khi bạn muốn hỗ trợ phím tắt hoặc tag nhanh trong các form nhập liệu.
23
- - Khi bạn cần tích hợp mention vào các trình soạn thảo văn bản giàu tính năng (Rich Text Editors).
24
+ - Khi cần tính năng mention trong khung chat hoặc khu vực bình luận
25
+ - Khi muốn hỗ trợ phím tắt gắn thẻ người dùng nhanh trong các form nhập liệu
26
+ - Khi cần tích hợp mention vào các rich text editor (Quill, contenteditable)
27
+ - Khi muốn hỗ trợ nhiều loại trigger khác nhau trong cùng một vùng nhập (`@` cho người dùng, `#` cho tag)
28
+ - Khi cần chèn mention thủ công từ nút bên ngoài (qua `outFunctionControl`)
24
29
 
25
30
  ## Cài đặt
26
31
 
27
32
  ```bash
28
- # npm
29
33
  npm install @libs-ui/components-inputs-mention
30
-
31
- # yarn
32
- yarn add @libs-ui/components-inputs-mention
33
34
  ```
34
35
 
35
36
  ## Import
36
37
 
37
38
  ```typescript
38
39
  import { LibsUiComponentsInputsMentionDirective } from '@libs-ui/components-inputs-mention';
40
+ import { IMentionConfig, IMentionInsert, IMentionFunctionControlEvent } from '@libs-ui/components-inputs-mention';
39
41
 
40
42
  @Component({
41
43
  standalone: true,
42
44
  imports: [LibsUiComponentsInputsMentionDirective],
43
45
  // ...
44
46
  })
45
- export class YourComponent {}
47
+ export class MyComponent {}
46
48
  ```
47
49
 
48
- ## Ví dụ
50
+ ## Ví dụ sử dụng
51
+
52
+ ### 1. Cơ bản — input với trigger `@`
49
53
 
50
- ### Cơ bản
54
+ ```typescript
55
+ import { Component } from '@angular/core';
56
+ import { LibsUiComponentsInputsMentionDirective, IMentionConfig } from '@libs-ui/components-inputs-mention';
57
+
58
+ @Component({
59
+ standalone: true,
60
+ imports: [LibsUiComponentsInputsMentionDirective],
61
+ template: `
62
+ <input
63
+ type="text"
64
+ class="w-full p-2 border rounded"
65
+ placeholder="Gõ @ để mention người dùng..."
66
+ LibsUiComponentsInputsMentionDirective
67
+ [mentionConfig]="basicConfig"
68
+ (outSearchTerm)="handlerSearchTerm($event)"
69
+ (outItemSelected)="handlerItemSelected($event)"
70
+ />
71
+ `,
72
+ })
73
+ export class CommentInputComponent {
74
+ readonly basicConfig: IMentionConfig = {
75
+ items: [
76
+ { id: '1', name: 'Anh Tuấn', username: 'tuan@example.com' },
77
+ { id: '2', name: 'Bảo Ngọc', username: 'ngoc@example.com' },
78
+ { id: '3', name: 'Công Thành', username: 'thanh@example.com' },
79
+ ],
80
+ triggerChar: '@',
81
+ labelKey: 'name',
82
+ mentionFilter: (search, items) => {
83
+ const s = search.toLowerCase();
84
+ return items?.filter((item: any) => item.name.toLowerCase().includes(s));
85
+ },
86
+ mentionSelect: (item: any, triggerChar) => `${triggerChar}${item.name}`,
87
+ };
51
88
 
52
- ```html
53
- <input
54
- type="text"
55
- LibsUiComponentsInputsMentionDirective
56
- [mentionConfig]="{ items: items, triggerChar: '@' }" />
89
+ handlerSearchTerm(term: string | undefined): void {
90
+ // term là chuỗi người dùng đang gõ sau @
91
+ }
92
+
93
+ handlerItemSelected(item: unknown): void {
94
+ // item là object được chọn từ danh sách
95
+ }
96
+ }
57
97
  ```
58
98
 
59
- ### Đa Trigger
99
+ ### 2. Nhiều trigger — `@` cho người dùng và `#` cho tag
60
100
 
61
- ```html
62
- <div
63
- contenteditable="true"
64
- LibsUiComponentsInputsMentionDirective
65
- [mentionConfig]="{
66
- items: people,
101
+ ```typescript
102
+ import { Component } from '@angular/core';
103
+ import { LibsUiComponentsInputsMentionDirective, IMentionConfig } from '@libs-ui/components-inputs-mention';
104
+
105
+ @Component({
106
+ standalone: true,
107
+ imports: [LibsUiComponentsInputsMentionDirective],
108
+ template: `
109
+ <textarea
110
+ class="w-full p-2 border rounded min-h-[100px]"
111
+ placeholder="Gõ @ cho người dùng, # cho tags..."
112
+ LibsUiComponentsInputsMentionDirective
113
+ [mentionConfig]="multiConfig"
114
+ ></textarea>
115
+ `,
116
+ })
117
+ export class MultiTriggerComponent {
118
+ readonly users = [
119
+ { id: '1', name: 'Anh Tuấn', username: 'tuan' },
120
+ { id: '2', name: 'Bảo Ngọc', username: 'ngoc' },
121
+ ];
122
+
123
+ readonly tags = [
124
+ { id: 't1', name: 'angular', username: 'AG' },
125
+ { id: 't2', name: 'typescript', username: 'TS' },
126
+ ];
127
+
128
+ readonly filterByName = (search: string, items?: Array<any>) =>
129
+ items?.filter((item) => item.name.toLowerCase().includes(search.toLowerCase()));
130
+
131
+ readonly multiConfig: IMentionConfig = {
132
+ items: this.users,
67
133
  triggerChar: '@',
134
+ labelKey: 'name',
135
+ mentionFilter: this.filterByName,
136
+ mentionSelect: (item: any, triggerChar) => `${triggerChar}${item.name}`,
68
137
  mention: [
69
- { items: tags, triggerChar: '#' }
70
- ]
71
- }"></div>
138
+ {
139
+ items: this.tags,
140
+ triggerChar: '#',
141
+ labelKey: 'name',
142
+ mentionFilter: this.filterByName,
143
+ mentionSelect: (item: any, triggerChar) => `${triggerChar}${item.name}`,
144
+ },
145
+ ],
146
+ };
147
+ }
72
148
  ```
73
149
 
74
- ## API
150
+ ### 3. ContentEditable với custom template danh sách
75
151
 
76
- ### [LibsUiComponentsInputsMentionDirective]
152
+ ```typescript
153
+ import { Component } from '@angular/core';
154
+ import { LibsUiComponentsInputsMentionDirective, IMentionConfig } from '@libs-ui/components-inputs-mention';
77
155
 
78
- #### Inputs
156
+ @Component({
157
+ standalone: true,
158
+ imports: [LibsUiComponentsInputsMentionDirective],
159
+ template: `
160
+ <div
161
+ contenteditable="true"
162
+ class="w-full p-4 border rounded bg-white min-h-[100px]"
163
+ LibsUiComponentsInputsMentionDirective
164
+ [mentionConfig]="basicConfig"
165
+ [mentionListTemplate]="customTpl"
166
+ >
167
+ Thử gõ @ ở đây...
168
+ </div>
169
+
170
+ <ng-template #customTpl let-item="item">
171
+ <div class="flex items-center gap-3 p-3 hover:bg-blue-50 cursor-pointer">
172
+ <div class="w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center font-bold">
173
+ {{ item.name[0] }}
174
+ </div>
175
+ <div>
176
+ <div class="font-medium text-gray-800 text-sm">{{ item.name }}</div>
177
+ <div class="text-xs text-gray-500">{{ item.username }}</div>
178
+ </div>
179
+ </div>
180
+ </ng-template>
181
+ `,
182
+ })
183
+ export class ContentEditableMentionComponent {
184
+ readonly basicConfig: IMentionConfig = {
185
+ items: [
186
+ { id: '1', name: 'Anh Tuấn', username: 'tuan@example.com' },
187
+ { id: '2', name: 'Bảo Ngọc', username: 'ngoc@example.com' },
188
+ ],
189
+ triggerChar: '@',
190
+ labelKey: 'name',
191
+ mentionFilter: (search, items) =>
192
+ items?.filter((item: any) => item.name.toLowerCase().includes(search.toLowerCase())),
193
+ mentionSelect: (item: any) => `@${item.name}`,
194
+ };
195
+ }
196
+ ```
79
197
 
80
- | Property | Type | Default | Description |
81
- | ----------------------- | -------------------------------- | ----------- | ------------------------------------------------- |
82
- | `[timeDelayInit]` | `number` | `0` | Thời gian trễ trước khi khởi tạo directive logic. |
83
- | `[mentionConfig]` | `IMentionConfig` | `undefined` | Cấu hình chính cho mention. |
84
- | `[mentionListTemplate]` | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined` | Template tùy chỉnh cho danh sách gợi ý. |
198
+ ### 4. Chèn mention thủ công từ nút bên ngoài
85
199
 
86
- #### Outputs
200
+ ```typescript
201
+ import { Component } from '@angular/core';
202
+ import { LibsUiComponentsInputsMentionDirective, IMentionConfig, IMentionFunctionControlEvent } from '@libs-ui/components-inputs-mention';
87
203
 
88
- | Property | Type | Description |
89
- | ---------------------- | -------------------------------------------- | --------------------------------------------------------------- |
90
- | `(outSearchTerm)` | `EventEmitter<string \| undefined>` | Emits từ khóa tìm kiếm hiện tại. |
91
- | `(outItemSelected)` | `EventEmitter<unknown>` | Emits đối tượng mục được chọn. |
92
- | `(outToggle)` | `EventEmitter<boolean>` | Emits `true` khi danh sách hiện, `false` khi ẩn. |
93
- | `(outInsertMention)` | `EventEmitter<IMentionInsert>` | Emits khi một mention được chèn (dành cho các editor phức tạp). |
94
- | `(outFunctionControl)` | `EventEmitter<IMentionFunctionControlEvent>` | Cung cấp phương thức điều khiển thủ công. |
204
+ @Component({
205
+ standalone: true,
206
+ imports: [LibsUiComponentsInputsMentionDirective],
207
+ template: `
208
+ <div
209
+ contenteditable="true"
210
+ class="w-full p-3 border rounded min-h-[80px]"
211
+ LibsUiComponentsInputsMentionDirective
212
+ [mentionConfig]="config"
213
+ (outFunctionControl)="handlerFunctionControl($event)"
214
+ ></div>
215
+
216
+ <button
217
+ class="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
218
+ (click)="handlerAddMention($event)"
219
+ >
220
+ Thêm @mention
221
+ </button>
222
+ `,
223
+ })
224
+ export class ManualMentionComponent {
225
+ private mentionControl: IMentionFunctionControlEvent | undefined;
226
+
227
+ readonly config: IMentionConfig = {
228
+ items: [
229
+ { id: '1', name: 'Anh Tuấn', username: 'tuan' },
230
+ { id: '2', name: 'Bảo Ngọc', username: 'ngoc' },
231
+ ],
232
+ triggerChar: '@',
233
+ labelKey: 'name',
234
+ mentionFilter: (search, items) =>
235
+ items?.filter((item: any) => item.name.toLowerCase().includes(search.toLowerCase())),
236
+ mentionSelect: (item: any) => `@${item.name}`,
237
+ };
238
+
239
+ handlerFunctionControl(event: IMentionFunctionControlEvent): void {
240
+ this.mentionControl = event;
241
+ }
242
+
243
+ handlerAddMention(event: Event): void {
244
+ event.stopPropagation();
245
+ this.mentionControl?.addMention(false);
246
+ }
247
+ }
248
+ ```
249
+
250
+ ### 5. Lắng nghe sự kiện chèn mention (Rich Text Editor)
251
+
252
+ ```typescript
253
+ import { Component } from '@angular/core';
254
+ import { LibsUiComponentsInputsMentionDirective, IMentionConfig, IMentionInsert } from '@libs-ui/components-inputs-mention';
255
+
256
+ @Component({
257
+ standalone: true,
258
+ imports: [LibsUiComponentsInputsMentionDirective],
259
+ template: `
260
+ <div
261
+ contenteditable="true"
262
+ class="w-full p-3 border rounded min-h-[80px]"
263
+ LibsUiComponentsInputsMentionDirective
264
+ [mentionConfig]="config"
265
+ (outInsertMention)="handlerInsertMention($event)"
266
+ (outToggle)="handlerToggle($event)"
267
+ ></div>
268
+ `,
269
+ })
270
+ export class RteIntegrationComponent {
271
+ readonly config: IMentionConfig = {
272
+ items: [{ id: '1', name: 'Anh Tuấn', username: 'tuan@example.com' }],
273
+ triggerChar: '@',
274
+ labelKey: 'name',
275
+ mentionFilter: (search, items) =>
276
+ items?.filter((item: any) => item.name.toLowerCase().includes(search.toLowerCase())),
277
+ mentionSelect: (item: any) => `@${item.name}`,
278
+ };
279
+
280
+ handlerInsertMention(event: IMentionInsert): void {
281
+ event; // { data: { id, feId, value }, lengthKey }
282
+ // Dùng trong Quill: editor.deleteText(pos - event.lengthKey, event.lengthKey)
283
+ // rồi editor.insertEmbed(pos, 'mention', event.data)
284
+ }
285
+
286
+ handlerToggle(isOpen: boolean): void {
287
+ // isOpen = true khi danh sách gợi ý hiện, false khi đóng
288
+ }
289
+ }
290
+ ```
291
+
292
+ ## @Input()
293
+
294
+ | Input | Type | Default | Mô tả | Ví dụ |
295
+ |---|---|---|---|---|
296
+ | `[timeDelayInit]` | `number` | `0` | Thời gian delay (ms) trước khi directive gắn event listeners. Hữu ích khi host element cần render xong trước. | `[timeDelayInit]="500"` |
297
+ | `[mentionConfig]` | `IMentionConfig` | `undefined` | Cấu hình chính cho mention: danh sách items, ký tự trigger, các tùy chọn filter/select. Bắt buộc phải truyền để directive hoạt động. | `[mentionConfig]="myConfig"` |
298
+ | `[mentionListTemplate]` | `TemplateRef<any>` | `undefined` | Template tùy chỉnh cho từng mục trong danh sách gợi ý. Context: `{ item, index }`. Nếu không truyền, dùng template mặc định (avatar + tên + username). | `[mentionListTemplate]="myTpl"` |
299
+
300
+ ## @Output()
301
+
302
+ | Output | Type | Mô tả | Handler TS | Binding HTML |
303
+ |---|---|---|---|---|
304
+ | `(outSearchTerm)` | `string \| undefined` | Emits từ khóa người dùng đang gõ sau ký tự trigger. Dùng để load data bất đồng bộ từ API. | `handlerSearchTerm(term: string \| undefined): void { term; }` | `(outSearchTerm)="handlerSearchTerm($event)"` |
305
+ | `(outItemSelected)` | `unknown` | Emits object mục được chọn từ danh sách (trước khi chèn vào input). | `handlerItemSelected(item: unknown): void { item; }` | `(outItemSelected)="handlerItemSelected($event)"` |
306
+ | `(outToggle)` | `boolean` | Emits `true` khi danh sách gợi ý mở ra, `false` khi đóng lại. | `handlerToggle(isOpen: boolean): void { isOpen; }` | `(outToggle)="handlerToggle($event)"` |
307
+ | `(outInsertMention)` | `IMentionInsert` | Emits khi một mention được chèn thành công. Dùng cho rich text editor cần xử lý chèn node thủ công. | `handlerInsertMention(e: IMentionInsert): void { e.data; e.lengthKey; }` | `(outInsertMention)="handlerInsertMention($event)"` |
308
+ | `(outFunctionControl)` | `IMentionFunctionControlEvent` | Emits object chứa phương thức điều khiển directive từ bên ngoài (ví dụ: chèn mention thủ công). Emits một lần sau `ngAfterViewInit`. | `handlerFunctionControl(ctrl: IMentionFunctionControlEvent): void { this.ctrl = ctrl; }` | `(outFunctionControl)="handlerFunctionControl($event)"` |
95
309
 
96
310
  ## Types & Interfaces
97
311
 
98
312
  ```typescript
313
+ import {
314
+ IMentionConfig,
315
+ IMentionItem,
316
+ IMentionInsert,
317
+ IMentionFunctionControlEvent,
318
+ INodeInsert,
319
+ } from '@libs-ui/components-inputs-mention';
320
+ ```
321
+
322
+ ```typescript
323
+ /**
324
+ * Cấu hình chính truyền vào [mentionConfig].
325
+ * Kế thừa IMentionItem và bổ sung các tùy chọn giao diện + đa trigger.
326
+ */
99
327
  export interface IMentionConfig extends IMentionItem {
328
+ /** Tắt style mặc định của danh sách gợi ý */
100
329
  disableStyle?: boolean;
330
+ /** ID của iframe chứa host element (khi dùng trong iframe) */
101
331
  iframe?: string;
332
+ /** Cấu hình cho các trigger bổ sung (ngoài trigger chính) */
102
333
  mention?: Array<IMentionItem>;
103
334
  }
104
335
 
336
+ /**
337
+ * Cấu hình cho từng trigger riêng lẻ.
338
+ */
105
339
  export interface IMentionItem {
340
+ /** Ký tự kích hoạt danh sách gợi ý. Mặc định: '@' */
106
341
  triggerChar?: string;
342
+ /** Tên field trong object dùng làm nhãn hiển thị. Mặc định: 'email' */
107
343
  labelKey?: string;
344
+ /** Tắt tự động sắp xếp danh sách items theo labelKey */
108
345
  disableSort?: boolean;
346
+ /** Tắt bộ lọc nội bộ (dùng khi tự xử lý filter bằng outSearchTerm + API) */
109
347
  disableSearch?: boolean;
348
+ /** Hiển thị danh sách phía trên con trỏ thay vì bên dưới */
110
349
  dropUp?: boolean;
350
+ /** Cho phép nhập khoảng trắng khi đang search mention */
111
351
  allowSpace?: boolean;
352
+ /** Giới hạn số khoảng trắng tối đa trong search query */
112
353
  limitSpaceSearchQuery?: number;
354
+ /** Bao gồm ký tự trigger trong giá trị emit ra outSearchTerm */
113
355
  returnTrigger?: boolean;
356
+ /** Hàm format text được chèn vào input sau khi chọn item */
114
357
  mentionSelect?: (item: unknown, triggerChar?: string) => string;
358
+ /** Hàm lọc danh sách items theo search string */
115
359
  mentionFilter?: (searchString: string, items?: Array<unknown>) => Array<unknown> | undefined;
360
+ /** Sự kiện DOM trên mention span đã chèn kích hoạt mentionActionByEvent */
116
361
  mentionEventName?: 'click' | 'mouseenter';
362
+ /** Callback được gọi khi sự kiện mentionEventName xảy ra trên span mention */
117
363
  mentionActionByEvent?: (item: unknown, triggerChars?: string) => void;
364
+ /** Danh sách items hiển thị trong danh sách gợi ý. Bắt buộc. */
118
365
  items: Array<Record<string, string | undefined>>;
119
-
366
+ /** z-index của dropdown danh sách gợi ý. Mặc định: 2500 */
120
367
  zIndex?: number;
121
368
  }
122
369
 
370
+ /**
371
+ * Dữ liệu emit qua outInsertMention khi một mention được chèn thành công.
372
+ * Dùng cho rich text editor tích hợp sâu (Quill, ProseMirror).
373
+ */
123
374
  export interface IMentionInsert {
124
375
  data: {
376
+ /** ID định danh duy nhất (từ item.id hoặc uuid tự sinh) */
125
377
  id: string;
378
+ /** Frontend ID dùng để track node trong DOM */
126
379
  feId: string;
380
+ /** Giá trị text được chèn (kết quả của mentionSelect) */
127
381
  value: string;
128
382
  };
383
+ /** Số ký tự cần xóa tính từ vị trí con trỏ để thay bằng mention node */
129
384
  lengthKey: number;
130
385
  }
131
386
 
387
+ /**
388
+ * Object emit qua outFunctionControl để điều khiển directive từ bên ngoài.
389
+ */
132
390
  export interface IMentionFunctionControlEvent {
391
+ /**
392
+ * Chèn ký tự trigger vào vị trí con trỏ hiện tại và mở danh sách gợi ý.
393
+ * @param inputEvent - true nếu gọi từ sự kiện input
394
+ */
133
395
  addMention: (inputEvent: boolean) => void;
134
396
  }
135
397
  ```
136
398
 
137
- ## Công nghệ
399
+ ## Cấu hình mặc định (DEFAULT_CONFIG)
400
+
401
+ Khi không truyền đủ các option, directive áp dụng các giá trị mặc định sau:
402
+
403
+ | Thuộc tính | Giá trị mặc định |
404
+ |---|---|
405
+ | `triggerChar` | `'@'` |
406
+ | `labelKey` | `'email'` |
407
+ | `allowSpace` | `true` |
408
+ | `returnTrigger` | `false` |
409
+ | `limitSpaceSearchQuery` | `3` |
410
+ | `mentionEventName` | `'click'` |
411
+ | `mentionSelect` | `(item) => '@' + item.email` |
412
+ | `mentionFilter` | Lọc theo field `name` và `username` (hỗ trợ tiếng Việt bỏ dấu) |
413
+ | `zIndex` | `2500` |
414
+
415
+ ## Điều hướng bàn phím
416
+
417
+ | Phím | Hành động |
418
+ |---|---|
419
+ | `@`, `#`, ... (trigger char) | Mở danh sách gợi ý |
420
+ | Mũi tên xuống | Chọn item tiếp theo |
421
+ | Mũi tên lên | Chọn item trước đó |
422
+ | `Enter` hoặc `Tab` | Chèn item đang active vào input |
423
+ | `Escape` | Đóng danh sách gợi ý |
424
+ | `Backspace` về trước ký tự trigger | Đóng danh sách gợi ý |
425
+ | `Space` (khi `allowSpace: false`) | Đóng danh sách gợi ý |
138
426
 
139
- | Technology | Version | Purpose |
140
- | --------------- | ------- | ---------------- |
141
- | Angular | 18+ | Framework |
142
- | Angular Signals | - | State management |
143
- | TailwindCSS | 3.x | Styling |
144
- | OnPush | - | Change Detection |
427
+ ## Lưu ý quan trọng
428
+
429
+ ⚠️ **mentionConfig bắt buộc**: Nếu không truyền `[mentionConfig]`, directive sẽ không gắn event listeners và không hoạt động. Kiểm tra `ngAfterViewInit` trong source.
430
+
431
+ ⚠️ **ContentEditable vs Input**: Logic xử lý caret và chèn mention khác nhau giữa `input`/`textarea` và `contenteditable`. Với `input`/`textarea`, directive dùng `selectionStart`. Với `contenteditable`, directive dùng `window.getSelection()` để tính vị trí và chèn HTML node.
432
+
433
+ ⚠️ **outInsertMention chỉ emit với contenteditable**: Output `(outInsertMention)` chỉ được emit khi host element là `contenteditable` (mode editor). Với `input`/`textarea`, mention được chèn trực tiếp vào `value`.
434
+
435
+ ⚠️ **Dependency bắt buộc**: Directive phụ thuộc vào `LibsUiDynamicComponentService` để render danh sách gợi ý. Đảm bảo service này được cung cấp trong ứng dụng.
436
+
437
+ ⚠️ **mentionFilter phải được truyền để lọc hoạt động**: Nếu không truyền `mentionFilter`, directive dùng hàm mặc định lọc theo field `name` và `username`. Nếu data của bạn dùng field khác, hãy truyền hàm `mentionFilter` riêng.
438
+
439
+ ⚠️ **timeDelayInit với lazy-loaded editor**: Khi dùng cùng Quill hoặc editor khởi tạo bất đồng bộ, truyền `[timeDelayInit]="500"` hoặc giá trị phù hợp để đảm bảo editor đã render xong trước khi directive gắn event.
440
+
441
+ ⚠️ **disableSearch để load data từ API**: Khi muốn tự load danh sách từ API dựa trên từ khóa, set `disableSearch: true` trong config và lắng nghe `(outSearchTerm)` để gọi API. Sau khi nhận kết quả, cập nhật `mentionConfig.items` để danh sách hiển thị mới.
145
442
 
146
443
  ## Demo
147
444
 
@@ -149,8 +446,4 @@ export interface IMentionFunctionControlEvent {
149
446
  npx nx serve core-ui
150
447
  ```
151
448
 
152
- Truy cập: `http://localhost:4200/inputs/mention`
153
-
154
- ## License
155
-
156
- MIT
449
+ Truy cập: `http://localhost:4500/inputs/mention`