@libs-ui/components-inputs-emoji 0.2.356-42 → 0.2.356-43
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,34 +1,33 @@
|
|
|
1
1
|
# @libs-ui/components-inputs-emoji
|
|
2
2
|
|
|
3
|
-
> Component chọn Emoji (Emoji Picker) tích hợp trong Popover với tính năng tìm kiếm và
|
|
3
|
+
> Component chọn Emoji (Emoji Picker) tích hợp trong Popover với tính năng tìm kiếm, phân loại và lazy loading.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiComponentsEmojiComponent` cung cấp
|
|
7
|
+
`LibsUiComponentsEmojiComponent` cung cấp giao diện chọn emoji trực quan ẩn trong một Popover, hỗ trợ duyệt emoji theo nhóm danh mục, tìm kiếm nhanh theo tên, và tối ưu hiệu năng bằng cơ chế lazy loading khi cuộn danh sách. Component phù hợp tích hợp vào mọi giao diện nhập liệu như chat, bình luận, hoặc soạn thảo văn bản.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
- ✅ Danh sách Emoji đầy đủ, phân loại theo nhóm
|
|
12
|
-
- ✅ Tìm kiếm Emoji nhanh chóng
|
|
13
|
-
- ✅ Tích hợp sẵn Popover
|
|
14
|
-
- ✅ Lazy loading (scroll to load more) tối ưu hiệu năng
|
|
11
|
+
- ✅ Danh sách Emoji đầy đủ, phân loại theo nhóm chuẩn Unicode
|
|
12
|
+
- ✅ Tìm kiếm Emoji nhanh chóng theo tên (có xử lý unicode)
|
|
13
|
+
- ✅ Tích hợp sẵn Popover với animation mượt mà
|
|
14
|
+
- ✅ Lazy loading (scroll to load more) tối ưu hiệu năng với danh sách lớn
|
|
15
15
|
- ✅ Hỗ trợ Custom Trigger qua `ng-content`
|
|
16
|
-
- ✅ Tùy chỉnh linh hoạt kích thước
|
|
16
|
+
- ✅ Tùy chỉnh linh hoạt kích thước, vị trí và z-index của popup
|
|
17
|
+
- ✅ Hỗ trợ điều khiển Popover từ bên ngoài qua `FunctionsControl` getter
|
|
18
|
+
- ✅ Standalone component, tương thích Angular 17+ Signals & OnPush
|
|
17
19
|
|
|
18
20
|
## Khi nào sử dụng
|
|
19
21
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
22
|
+
- Tích hợp tính năng chèn emoji trong các ứng dụng chat, bình luận, social media
|
|
23
|
+
- Cần một bộ chọn emoji gọn nhẹ, tiết kiệm diện tích (ẩn trong popover cho đến khi cần)
|
|
24
|
+
- Cần hiệu năng cao với danh sách emoji lớn (lazy loading tự động)
|
|
25
|
+
- Muốn tùy chỉnh trigger button theo thiết kế riêng thay vì dùng icon mặc định
|
|
23
26
|
|
|
24
27
|
## Cài đặt
|
|
25
28
|
|
|
26
29
|
```bash
|
|
27
|
-
# npm
|
|
28
30
|
npm install @libs-ui/components-inputs-emoji
|
|
29
|
-
|
|
30
|
-
# yarn
|
|
31
|
-
yarn add @libs-ui/components-inputs-emoji
|
|
32
31
|
```
|
|
33
32
|
|
|
34
33
|
## Import
|
|
@@ -44,101 +43,256 @@ import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji
|
|
|
44
43
|
export class YourComponent {}
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
## Ví dụ
|
|
46
|
+
## Ví dụ sử dụng
|
|
47
|
+
|
|
48
|
+
### 1. Cơ bản — Icon mặc định làm trigger
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// component.ts
|
|
52
|
+
import { Component, signal } from '@angular/core';
|
|
53
|
+
import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
|
|
54
|
+
|
|
55
|
+
@Component({
|
|
56
|
+
selector: 'app-chat-input',
|
|
57
|
+
standalone: true,
|
|
58
|
+
imports: [LibsUiComponentsEmojiComponent],
|
|
59
|
+
templateUrl: './chat-input.component.html',
|
|
60
|
+
})
|
|
61
|
+
export class ChatInputComponent {
|
|
62
|
+
protected selectedEmoji = signal('');
|
|
63
|
+
|
|
64
|
+
protected handlerSelectEmoji(emoji: string): void {
|
|
65
|
+
this.selectedEmoji.set(emoji);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- chat-input.component.html -->
|
|
72
|
+
<div class="flex items-center gap-3">
|
|
73
|
+
<libs_ui-components-emoji (outEventEmoji)="handlerSelectEmoji($event)" />
|
|
74
|
+
<span class="text-gray-600">Emoji đã chọn: {{ selectedEmoji() || '(chưa chọn)' }}</span>
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Custom Trigger — Dùng nội dung bất kỳ làm trigger
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// component.ts
|
|
82
|
+
import { Component, signal } from '@angular/core';
|
|
83
|
+
import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
|
|
84
|
+
|
|
85
|
+
@Component({
|
|
86
|
+
selector: 'app-comment-box',
|
|
87
|
+
standalone: true,
|
|
88
|
+
imports: [LibsUiComponentsEmojiComponent],
|
|
89
|
+
templateUrl: './comment-box.component.html',
|
|
90
|
+
})
|
|
91
|
+
export class CommentBoxComponent {
|
|
92
|
+
protected message = signal('');
|
|
93
|
+
|
|
94
|
+
protected handlerInsertEmoji(emoji: string): void {
|
|
95
|
+
this.message.update((current) => `${current}${emoji}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<!-- comment-box.component.html -->
|
|
102
|
+
<div class="flex items-center gap-2 border rounded-lg p-2">
|
|
103
|
+
<input
|
|
104
|
+
class="flex-1 outline-none text-sm"
|
|
105
|
+
[value]="message()"
|
|
106
|
+
placeholder="Nhập bình luận..." />
|
|
107
|
+
|
|
108
|
+
<libs_ui-components-emoji
|
|
109
|
+
[isNgContent]="true"
|
|
110
|
+
(outEventEmoji)="handlerInsertEmoji($event)">
|
|
111
|
+
<button class="px-3 py-1 bg-blue-500 text-white rounded text-sm hover:bg-blue-600">
|
|
112
|
+
😊 Emoji
|
|
113
|
+
</button>
|
|
114
|
+
</libs_ui-components-emoji>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Tùy chỉnh kích thước Popup và vị trí
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// component.ts
|
|
122
|
+
import { Component, signal } from '@angular/core';
|
|
123
|
+
import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
|
|
48
124
|
|
|
49
|
-
|
|
125
|
+
@Component({
|
|
126
|
+
selector: 'app-editor-toolbar',
|
|
127
|
+
standalone: true,
|
|
128
|
+
imports: [LibsUiComponentsEmojiComponent],
|
|
129
|
+
templateUrl: './editor-toolbar.component.html',
|
|
130
|
+
})
|
|
131
|
+
export class EditorToolbarComponent {
|
|
132
|
+
protected selectedEmoji = signal('');
|
|
133
|
+
|
|
134
|
+
protected handlerPickEmoji(emoji: string): void {
|
|
135
|
+
this.selectedEmoji.set(emoji);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
50
139
|
|
|
51
140
|
```html
|
|
52
|
-
|
|
141
|
+
<!-- editor-toolbar.component.html -->
|
|
142
|
+
<libs_ui-components-emoji
|
|
143
|
+
classPopup="w-[400px] h-[350px]"
|
|
144
|
+
modePopoverPosition="center"
|
|
145
|
+
[zIndex]="1500"
|
|
146
|
+
(outEventEmoji)="handlerPickEmoji($event)" />
|
|
53
147
|
```
|
|
54
148
|
|
|
55
|
-
###
|
|
149
|
+
### 4. Điều khiển Popover từ bên ngoài qua ViewChild
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// component.ts
|
|
153
|
+
import { Component, viewChild } from '@angular/core';
|
|
154
|
+
import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
|
|
155
|
+
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';
|
|
156
|
+
|
|
157
|
+
@Component({
|
|
158
|
+
selector: 'app-controlled-emoji',
|
|
159
|
+
standalone: true,
|
|
160
|
+
imports: [LibsUiComponentsEmojiComponent],
|
|
161
|
+
templateUrl: './controlled-emoji.component.html',
|
|
162
|
+
})
|
|
163
|
+
export class ControlledEmojiComponent {
|
|
164
|
+
private readonly emojiRef = viewChild<LibsUiComponentsEmojiComponent>('emojiRef');
|
|
165
|
+
protected selectedEmoji = '';
|
|
166
|
+
|
|
167
|
+
protected handlerSelectEmoji(emoji: string): void {
|
|
168
|
+
this.selectedEmoji = emoji;
|
|
169
|
+
// Đóng popover sau khi chọn
|
|
170
|
+
this.emojiRef()?.FunctionsControl?.removePopoverOverlay();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
openPicker(): void {
|
|
174
|
+
this.emojiRef()?.FunctionsControl?.showPopoverOverlay();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
56
178
|
|
|
57
179
|
```html
|
|
180
|
+
<!-- controlled-emoji.component.html -->
|
|
181
|
+
<button (click)="openPicker()">Mở Emoji Picker</button>
|
|
58
182
|
<libs_ui-components-emoji
|
|
59
|
-
|
|
60
|
-
(outEventEmoji)="
|
|
61
|
-
<button>Chọn Emoji</button>
|
|
62
|
-
</libs_ui-components-emoji>
|
|
183
|
+
#emojiRef
|
|
184
|
+
(outEventEmoji)="handlerSelectEmoji($event)" />
|
|
63
185
|
```
|
|
64
186
|
|
|
65
|
-
##
|
|
187
|
+
## @Input()
|
|
188
|
+
|
|
189
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
190
|
+
|---|---|---|---|---|
|
|
191
|
+
| `[classIconInclude]` | `string` | `'libs-ui-icon-add'` | CSS class cho icon trigger mặc định (khi không dùng ng-content) | `[classIconInclude]="'libs-ui-icon-smile'"` |
|
|
192
|
+
| `[classInclude]` | `string` | `''` | CSS class bổ sung cho container của trigger button | `[classInclude]="'ml-2'"` |
|
|
193
|
+
| `[classPopup]` | `string` | `'w-[290px] h-[245px]'` | CSS class cho container của popup chọn emoji, kiểm soát kích thước | `classPopup="w-[400px] h-[350px]"` |
|
|
194
|
+
| `[configPopover]` | `IPopoverOverlay` | `defaultConfigPopover()` | Cấu hình đầy đủ cho Popover overlay (direction, position, animation,...) | `[configPopover]="myPopoverConfig"` |
|
|
195
|
+
| `[isNgContent]` | `boolean` | `undefined` | Khi `true`, ẩn icon mặc định và dùng nội dung `ng-content` làm trigger | `[isNgContent]="true"` |
|
|
196
|
+
| `[modePopoverPosition]` | `'start' \| 'center' \| 'end'` | `undefined` | Chế độ căn lề ngang của popup so với trigger | `modePopoverPosition="center"` |
|
|
197
|
+
| `[zIndex]` | `number` | `0` | Z-index nền của popup. Content bên trong sẽ tự động được offset thêm `+5` | `[zIndex]="1500"` |
|
|
198
|
+
| `[ignoreShowPopoverText]` | `boolean` | `undefined` | Ẩn tooltip text "Emoji" khi hover trigger | `[ignoreShowPopoverText]="true"` |
|
|
199
|
+
|
|
200
|
+
## @Output()
|
|
66
201
|
|
|
67
|
-
|
|
202
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
203
|
+
|---|---|---|---|---|
|
|
204
|
+
| `(outEventEmoji)` | `string` | Emit ký tự emoji khi người dùng nhấn chọn một emoji trong picker | `handlerSelectEmoji(emoji: string): void { this.selectedEmoji.set(emoji); }` | `(outEventEmoji)="handlerSelectEmoji($event)"` |
|
|
205
|
+
| `(outFunctionsControl)` | `IPopoverFunctionControlEvent` | Emit object chứa các hàm điều khiển Popover (`showPopoverOverlay`, `removePopoverOverlay`) | `handlerFunctionsControl(ctrl: IPopoverFunctionControlEvent): void { this.popoverCtrl = ctrl; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
206
|
+
| `(outFlagMouse)` | `IFlagMouse` | Emit trạng thái chuột (hover) trên vùng popover | `handlerFlagMouse(flag: IFlagMouse): void { this.mouseFlag = flag; }` | `(outFlagMouse)="handlerFlagMouse($event)"` |
|
|
68
207
|
|
|
69
|
-
|
|
208
|
+
## Methods (FunctionsControl)
|
|
70
209
|
|
|
71
|
-
|
|
72
|
-
| ----------------------- | ------------------------------ | ------------------------ | -------------------------------------------- |
|
|
73
|
-
| `[classIconInclude]` | `string` | `'libs-ui-icon-add'` | CSS class cho icon trigger mặc định |
|
|
74
|
-
| `[classInclude]` | `string` | `''` | CSS class bổ sung cho trigger container |
|
|
75
|
-
| `[classPopup]` | `string` | `'w-[290px] h-[245px]'` | CSS class cho container của popup chọn emoji |
|
|
76
|
-
| `[configPopover]` | `IPopoverOverlay` | `defaultConfigPopover()` | Cấu hình overlay cho Popover |
|
|
77
|
-
| `[isNgContent]` | `boolean` | `false` | Sử dụng ng-content làm trigger thay vì icon |
|
|
78
|
-
| `[modePopoverPosition]` | `'start' \| 'center' \| 'end'` | `undefined` | Chế độ căn lề cho Popover |
|
|
79
|
-
| `[zIndex]` | `number` | `0` | Giá trị Z-index nền của popup |
|
|
210
|
+
Truy cập qua `ViewChild` để điều khiển Popover theo chương trình:
|
|
80
211
|
|
|
81
|
-
|
|
212
|
+
```typescript
|
|
213
|
+
private readonly emojiRef = viewChild<LibsUiComponentsEmojiComponent>('emojiRef');
|
|
214
|
+
|
|
215
|
+
// Mở picker
|
|
216
|
+
this.emojiRef()?.FunctionsControl?.showPopoverOverlay();
|
|
217
|
+
|
|
218
|
+
// Đóng picker
|
|
219
|
+
this.emojiRef()?.FunctionsControl?.removePopoverOverlay();
|
|
220
|
+
```
|
|
82
221
|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
| `
|
|
86
|
-
| `(outFunctionsControl)` | `IPopoverFunctionControlEvent` | Emit các functions điều khiển Popover (show/remove) |
|
|
222
|
+
| Getter | Return Type | Mô tả |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `FunctionsControl` | `IPopoverFunctionControlEvent \| undefined` | Trả về object chứa hàm điều khiển Popover. Có giá trị sau khi Popover đã được khởi tạo. |
|
|
87
225
|
|
|
88
226
|
## Types & Interfaces
|
|
89
227
|
|
|
90
228
|
```typescript
|
|
91
|
-
|
|
229
|
+
import { IPopoverOverlay, IPopoverFunctionControlEvent, IFlagMouse } from '@libs-ui/components-popover';
|
|
230
|
+
import { IEmoji, IGroupEmoji } from '@libs-ui/components-inputs-emoji';
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
/** Cấu hình Popover overlay */
|
|
92
235
|
export interface IPopoverOverlay {
|
|
93
|
-
isAddContentToParentDocument?: boolean;
|
|
94
|
-
classInclude?: string;
|
|
95
|
-
widthByParent?: boolean;
|
|
96
|
-
width?: number;
|
|
97
|
-
minWidth?: number;
|
|
98
|
-
maxWidth?: number;
|
|
99
|
-
maxHeight?: number | null;
|
|
100
|
-
zIndex?: number;
|
|
101
236
|
direction?: 'top' | 'right' | 'bottom' | 'left';
|
|
237
|
+
directionDistance?: number;
|
|
102
238
|
position?: {
|
|
103
239
|
mode: 'start' | 'center' | 'end';
|
|
104
240
|
distance: number;
|
|
105
241
|
};
|
|
106
|
-
|
|
242
|
+
zIndex?: number;
|
|
243
|
+
maxWidth?: number;
|
|
244
|
+
maxHeight?: number | null;
|
|
245
|
+
width?: number;
|
|
246
|
+
minWidth?: number;
|
|
247
|
+
widthByParent?: boolean;
|
|
248
|
+
whiteTheme?: boolean;
|
|
249
|
+
ignoreArrow?: boolean;
|
|
250
|
+
classInclude?: string;
|
|
251
|
+
animationConfig?: object;
|
|
107
252
|
}
|
|
108
253
|
|
|
254
|
+
/** Các hàm điều khiển Popover (nhận qua outFunctionsControl hoặc FunctionsControl getter) */
|
|
109
255
|
export interface IPopoverFunctionControlEvent {
|
|
110
256
|
showPopoverOverlay: () => void;
|
|
111
257
|
removePopoverOverlay: () => void;
|
|
112
258
|
}
|
|
259
|
+
|
|
260
|
+
/** Thông tin một Emoji */
|
|
261
|
+
export interface IEmoji {
|
|
262
|
+
/** Ký tự emoji thực tế (vd: '😀') */
|
|
263
|
+
emoji: string;
|
|
264
|
+
/** Mô tả emoji bằng tiếng Anh (vd: 'grinning face') */
|
|
265
|
+
description: string;
|
|
266
|
+
/** Danh mục (vd: 'Smileys & Emotion') */
|
|
267
|
+
category: string;
|
|
268
|
+
display?: boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Thông tin một nhóm Emoji */
|
|
272
|
+
export interface IGroupEmoji {
|
|
273
|
+
/** Tên nhóm (vd: 'Smileys & Emotion') */
|
|
274
|
+
group_name: string;
|
|
275
|
+
/** Danh sách emoji trong nhóm */
|
|
276
|
+
emojis: Array<IEmoji>;
|
|
277
|
+
/** Ký tự đại diện cho nhóm */
|
|
278
|
+
iconGroup?: string;
|
|
279
|
+
}
|
|
113
280
|
```
|
|
114
281
|
|
|
115
|
-
##
|
|
282
|
+
## Lưu ý quan trọng
|
|
116
283
|
|
|
117
|
-
|
|
118
|
-
| --------------- | ------- | ---------------- |
|
|
119
|
-
| Angular | 18+ | Framework |
|
|
120
|
-
| Angular Signals | - | State management |
|
|
121
|
-
| TailwindCSS | 3.x | Styling |
|
|
122
|
-
| OnPush | - | Change Detection |
|
|
284
|
+
⚠️ **Z-index offset**: Z-index của nội dung bên trong popup luôn được offset thêm `+5` so với giá trị `[zIndex]` truyền vào. Khi dùng chung với modal hoặc overlay khác, hãy tính toán `zIndex` sao cho tránh bị che khuất.
|
|
123
285
|
|
|
124
|
-
|
|
286
|
+
⚠️ **isNgContent**: Khi truyền `[isNgContent]="true"`, component sẽ ẩn hoàn toàn icon trigger mặc định và hiển thị phần tử con trong `ng-content`. Nếu `ng-content` rỗng, khu vực trigger sẽ không hiển thị gì.
|
|
125
287
|
|
|
126
|
-
|
|
127
|
-
npx nx serve core-ui
|
|
128
|
-
```
|
|
288
|
+
⚠️ **FunctionsControl timing**: Getter `FunctionsControl` trả về `undefined` cho đến khi sự kiện `(outFunctionsControl)` được emit lần đầu tiên (sau khi Popover khởi tạo). Hãy kiểm tra `?.` trước khi gọi.
|
|
129
289
|
|
|
130
|
-
|
|
290
|
+
⚠️ **configPopover mutable**: `IPopoverOverlay` được xử lý theo dạng mutable object bên trong component. Không nên tái sử dụng cùng một object config cho nhiều instance Emoji Picker.
|
|
131
291
|
|
|
132
|
-
##
|
|
292
|
+
## Demo
|
|
133
293
|
|
|
134
294
|
```bash
|
|
135
|
-
|
|
136
|
-
npx nx test components-inputs-emoji
|
|
137
|
-
|
|
138
|
-
# Coverage
|
|
139
|
-
npx nx test components-inputs-emoji --coverage
|
|
295
|
+
npx nx serve core-ui
|
|
140
296
|
```
|
|
141
297
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
MIT
|
|
298
|
+
Truy cập: http://localhost:4500/inputs/emoji
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, input, output, viewChild, effect, untracked,
|
|
2
|
+
import { signal, input, output, viewChild, effect, untracked, Component, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
import { LibsUiComponentsInputsSearchComponent } from '@libs-ui/components-inputs-search';
|
|
4
4
|
import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
|
|
5
5
|
import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
|