@libs-ui/pipes-format-text-highlight-by-keyword 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,15 +1,28 @@
1
- # Highlight By Keyword Pipe
1
+ # @libs-ui/pipes-format-text-highlight-by-keyword
2
2
 
3
- Pipe highlight (tô màu) từ khóa trong chuỗi văn bản. Hỗ trợ tìm kiếm không dấu tiếng Việt, không phân biệt hoa/thường và tùy chỉnh CSS class highlight.
3
+ > Pipe tô màu (highlight) từ khóa trong chuỗi văn bản, hỗ trợ tìm kiếm không dấu tiếng Việt và tùy chỉnh CSS class.
4
+
5
+ ## Giới thiệu
6
+
7
+ `LibsUiPipesFormatTextHighlightByKeywordPipe` là một Angular Pipe bọc quanh hàm tiện ích `highlightByKeyword` từ `@libs-ui/utils`. Pipe nhận vào một chuỗi văn bản và một từ khóa, trả về chuỗi HTML với các đoạn khớp từ khóa được bọc trong thẻ `<span>` kèm CSS class tô màu. Pipe hỗ trợ tìm kiếm không phân biệt dấu tiếng Việt (gõ "an" có thể match "An", "ăn", "ân", "ản"...) và không phân biệt hoa/thường.
4
8
 
5
9
  ## Tính năng
6
10
 
7
11
  - ✅ Highlight tất cả các lần xuất hiện của từ khóa trong chuỗi
8
- - ✅ Tìm kiếm không dấu — "an" match "An", "ăn", "ân", "ản"... (tiếng Việt)
12
+ - ✅ Tìm kiếm không dấu — "an" match "An", "ăn", "ân", "ản"... (hỗ trợ tiếng Việt đầy đủ)
9
13
  - ✅ Không phân biệt hoa/thường (case-insensitive)
10
- - ✅ Tùy chỉnh CSS class cho phần highlight
11
- - ✅ Option `ignoreHighlight` để hiệu hóa linh hoạt
12
- - ✅ Xử lý an toàn null/undefined
14
+ - ✅ Tùy chỉnh CSS class cho phần highlight qua tham số `classHightLight`
15
+ - ✅ Tham số `ignoreHighlight` để bật/tắt highlight linh hoạt mà không cần `@if` trong template
16
+ - ✅ Xử lý an toàn khi `value` hoặc `search` là `null`/`undefined`
17
+ - ✅ Standalone pipe, dùng được ngay không cần NgModule
18
+
19
+ ## Khi nào sử dụng
20
+
21
+ - Highlight keyword trong danh sách kết quả tìm kiếm (search result page)
22
+ - Tô màu từ khóa trong dropdown autocomplete khi người dùng gõ
23
+ - Highlight từ khóa trong bảng dữ liệu khi lọc/filter
24
+ - Hiển thị đoạn văn preview của search engine nội bộ với keyword được tô màu
25
+ - Cần tìm kiếm không phân biệt dấu tiếng Việt (gõ "an" → highlight "An", "Ân", "ăn")
13
26
 
14
27
  ## Cài đặt
15
28
 
@@ -29,75 +42,237 @@ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-form
29
42
  export class ExampleComponent {}
30
43
  ```
31
44
 
32
- ## pháp
45
+ ## dụ sử dụng
46
+
47
+ ### 1. Highlight cơ bản
48
+
49
+ ```typescript
50
+ import { Component, signal } from '@angular/core';
51
+ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-format-text-highlight-by-keyword';
52
+
53
+ @Component({
54
+ standalone: true,
55
+ imports: [LibsUiPipesFormatTextHighlightByKeywordPipe],
56
+ template: `
57
+ <!-- ✅ BẮT BUỘC dùng [innerHTML] — pipe trả về HTML string -->
58
+ <p [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe : keyword"></p>
59
+
60
+ <!-- ❌ KHÔNG dùng {{ }} — sẽ hiện HTML string thô, không render -->
61
+ <!-- <p>{{ text | LibsUiPipesFormatTextHighlightByKeywordPipe : keyword }}</p> -->
62
+ `,
63
+ })
64
+ export class SearchResultComponent {
65
+ protected text = signal('Angular is a powerful framework for building web applications');
66
+ protected keyword = signal('angular');
67
+ }
68
+ ```
33
69
 
70
+ **Output HTML được tạo ra:**
34
71
  ```html
35
- value | LibsUiPipesFormatTextHighlightByKeywordPipe : search : ignoreHighlight? : classHightLight?
72
+ <span class="bg-[#19344a] text-white">Angular</span> is a powerful framework for building web applications
73
+ ```
74
+
75
+ ### 2. Tìm kiếm không dấu tiếng Việt
76
+
77
+ ```typescript
78
+ import { Component, signal } from '@angular/core';
79
+ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-format-text-highlight-by-keyword';
80
+
81
+ @Component({
82
+ standalone: true,
83
+ imports: [LibsUiPipesFormatTextHighlightByKeywordPipe],
84
+ template: `
85
+ <!-- Gõ "an" (không dấu) → highlight "An", "ăn", "ân", "ản"... trong text có dấu -->
86
+ <p [innerHTML]="name | LibsUiPipesFormatTextHighlightByKeywordPipe : searchTerm"></p>
87
+ `,
88
+ })
89
+ export class VietnameseSearchComponent {
90
+ protected name = signal('Nguyễn Văn An là kỹ sư phần mềm tài năng tại Hà Nội');
91
+ protected searchTerm = signal('an');
92
+ // "an" (không dấu) sẽ match: "An", "ăn" (tài năng), v.v.
93
+ }
36
94
  ```
37
95
 
38
- > ⚠️ **Quan trọng:** Pipe trả về **HTML string**. Phải dùng `[innerHTML]` để hiển thị highlight. Dùng `{{ }}` sẽ chỉ hiện HTML thô (escaped).
96
+ ### 3. Custom CSS class cho phần highlight
39
97
 
40
- ## Ví dụ
98
+ ```typescript
99
+ import { Component, signal } from '@angular/core';
100
+ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-format-text-highlight-by-keyword';
41
101
 
42
- ### Trong Template
102
+ @Component({
103
+ standalone: true,
104
+ imports: [LibsUiPipesFormatTextHighlightByKeywordPipe],
105
+ template: `
106
+ <!-- Highlight màu vàng thay vì mặc định dark navy -->
107
+ <p
108
+ [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe
109
+ : keyword
110
+ : false
111
+ : 'bg-yellow-300 text-gray-900 font-bold'">
112
+ </p>
113
+ `,
114
+ })
115
+ export class CustomHighlightComponent {
116
+ protected text = signal('Tìm kiếm nhanh với highlight màu tùy chỉnh theo thiết kế');
117
+ protected keyword = signal('highlight');
118
+ }
119
+ ```
43
120
 
44
- ```html
45
- <!-- ✅ Đúng: dùng [innerHTML] -->
46
- <p [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe : keyword"></p>
121
+ ### 4. Toggle highlight theo trạng thái tìm kiếm
47
122
 
48
- <!-- ❌ Sai: dùng {{ }} sẽ hiện text thô -->
49
- <p>{{ text | LibsUiPipesFormatTextHighlightByKeywordPipe : keyword }}</p>
123
+ ```typescript
124
+ import { Component, signal } from '@angular/core';
125
+ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-format-text-highlight-by-keyword';
50
126
 
51
- <!-- Custom CSS class -->
52
- <p
53
- [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe
54
- : keyword
55
- : false
56
- : 'bg-yellow-300 text-gray-900 font-bold'"></p>
127
+ @Component({
128
+ standalone: true,
129
+ imports: [LibsUiPipesFormatTextHighlightByKeywordPipe],
130
+ template: `
131
+ <!-- Chỉ highlight khi isSearching = true — không cần @if trong template -->
132
+ <p
133
+ [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe
134
+ : keyword
135
+ : !isSearching()">
136
+ </p>
137
+ <button (click)="handlerToggleSearch($event)">
138
+ {{ isSearching() ? 'Tắt highlight' : 'Bật highlight' }}
139
+ </button>
140
+ `,
141
+ })
142
+ export class ToggleHighlightComponent {
143
+ protected text = signal('Text sẽ được highlight khi đang trong chế độ tìm kiếm');
144
+ protected keyword = signal('highlight');
145
+ protected isSearching = signal(false);
57
146
 
58
- <!-- Tắt highlight điều kiện -->
59
- <p [innerHTML]="text | LibsUiPipesFormatTextHighlightByKeywordPipe : keyword : !isSearching"></p>
147
+ protected handlerToggleSearch(event: Event): void {
148
+ event.stopPropagation();
149
+ this.isSearching.update(v => !v);
150
+ }
151
+ }
60
152
  ```
61
153
 
62
- ### Dùng trong TypeScript
154
+ ### 5. Dùng trong TypeScript (không qua pipe)
63
155
 
64
- Pipe là thin wrapper của hàm `highlightByKeyword` trong `@libs-ui/utils`. Khi cần gọi trong TypeScript, import và dùng thẳng hàm đó — **không cần inject pipe**:
156
+ Pipe là thin wrapper của hàm `highlightByKeyword` trong `@libs-ui/utils`. Khi cần gọi trong TypeScript, import thẳng hàm đó — không cần inject pipe:
65
157
 
66
158
  ```typescript
67
159
  import { highlightByKeyword } from '@libs-ui/utils';
68
160
 
69
- // Dùng thẳng hàm utils — không cần inject pipe
161
+ // Highlight bản
70
162
  const result = highlightByKeyword('Nguyễn Văn An', 'an');
71
163
  // Output: 'Nguyễn Văn <span class="bg-[#19344a] text-white">An</span>'
72
164
 
73
- // Custom class:
74
- const custom = highlightByKeyword('Hello World', 'hello', false, 'bg-yellow-300 text-gray-900');
165
+ // Custom class
166
+ const customResult = highlightByKeyword(
167
+ 'Hello World từ Việt Nam',
168
+ 'hello',
169
+ false,
170
+ 'bg-yellow-300 text-gray-900 font-bold'
171
+ );
172
+ // Output: '<span class="bg-yellow-300 text-gray-900 font-bold">Hello</span> World từ Việt Nam'
173
+
174
+ // Tắt highlight
175
+ const plain = highlightByKeyword('Text bình thường', 'text', true);
176
+ // Output: 'Text bình thường' (giữ nguyên, không bọc span)
75
177
 
76
- // Trong component class:
77
- class ExampleComponent {
78
- safeHtml = highlightByKeyword(this.rawText, this.searchTerm);
178
+ // Trong component — dùng để gán vào property hiển thị qua [innerHTML]
179
+ @Component({
180
+ standalone: true,
181
+ template: `<p [innerHTML]="highlightedHtml"></p>`,
182
+ })
183
+ export class MyComponent {
184
+ highlightedHtml = highlightByKeyword('Kết quả tìm kiếm phần mềm', 'phần mềm');
79
185
  }
80
186
  ```
81
187
 
82
- ## API
188
+ ### 6. Dùng trực tiếp pipe.transform() (standalone)
83
189
 
84
- | Tham số | Kiểu | Bắt buộc | Default | Mô tả |
85
- | ----------------- | ---------------------- | -------- | --------------------------- | -------------------------------------------------- |
86
- | `value` | `string \| undefined` | ✅ | - | Chuỗi văn bản gốc cần tô màu keyword |
87
- | `search` | `string \| undefined` | ✅ | - | Từ khóa cần highlight trong chuỗi |
88
- | `ignoreHighlight` | `boolean \| undefined` | ❌ | `undefined` | Nếu `true`, bỏ qua highlight và trả về `value` gốc |
89
- | `classHightLight` | `string \| undefined` | ❌ | `'bg-[#19344a] text-white'` | CSS class áp dụng cho `<span>` bao quanh keyword |
190
+ ```typescript
191
+ import { LibsUiPipesFormatTextHighlightByKeywordPipe } from '@libs-ui/pipes-format-text-highlight-by-keyword';
90
192
 
91
- ## Khi nào sử dụng
193
+ const pipe = new LibsUiPipesFormatTextHighlightByKeywordPipe();
194
+
195
+ // Cú pháp: pipe.transform(value, search, ignoreHighlight?, classHightLight?)
196
+ const result1 = pipe.transform('Angular framework', 'angular');
197
+ // → '<span class="bg-[#19344a] text-white">Angular</span> framework'
198
+
199
+ const result2 = pipe.transform('Dữ liệu tiếng Việt', 'du lieu', false, 'bg-blue-200');
200
+ // → tìm "du lieu" không dấu → match "Dữ liệu" → bọc span với bg-blue-200
201
+
202
+ const result3 = pipe.transform(undefined, 'keyword');
203
+ // → '' (CHARACTER_DATA_EMPTY — an toàn với undefined)
204
+
205
+ const result4 = pipe.transform('Text đầy đủ', 'keyword', true);
206
+ // → 'Text đầy đủ' (ignoreHighlight = true → trả về nguyên bản)
207
+ ```
208
+
209
+ ## Transform (Pipe)
210
+
211
+ ### Cú pháp template
212
+
213
+ ```html
214
+ value | LibsUiPipesFormatTextHighlightByKeywordPipe : search : ignoreHighlight? : classHightLight?
215
+ ```
216
+
217
+ ### Bảng tham số
218
+
219
+ | Tham số | Type | Bắt buộc | Default | Mô tả | Ví dụ |
220
+ |---|---|---|---|---|---|
221
+ | `value` | `string \| undefined` | ✅ | — | Chuỗi văn bản gốc cần tô màu keyword | `'Nguyễn Văn An'` |
222
+ | `search` | `string \| undefined` | ✅ | — | Từ khóa cần highlight trong chuỗi | `'an'` |
223
+ | `ignoreHighlight` | `boolean \| undefined` | ❌ | `undefined` | Nếu `true`, bỏ qua highlight và trả về `value` gốc không đổi | `true` / `!isSearching()` |
224
+ | `classHightLight` | `string \| undefined` | ❌ | `'bg-[#19344a] text-white'` | CSS class áp dụng cho `<span>` bao quanh keyword. Có thể dùng Tailwind hoặc custom CSS class | `'bg-yellow-300 text-gray-900 font-bold'` |
225
+
226
+ ### Giá trị trả về
92
227
 
93
- - **Search result:** Highlight keyword trong danh sách kết quả tìm kiếm
94
- - **Autocomplete:** Tô màu từ khóa trong dropdown gợi ý
95
- - **Table filter:** Highlight từ khóa trong bảng dữ liệu khi lọc
96
- - **Tiếng Việt:** Cần tìm kiếm không dấu (gõ "an" match "Ân", "ăn"...)
228
+ Pipe trả về `string` HTML string chứa các thẻ `<span>` bao quanh keyword:
229
+
230
+ ```html
231
+ <!-- dụ output khi search = 'angular' -->
232
+ <span class="bg-[#19344a] text-white">Angular</span> is a powerful framework
233
+ ```
234
+
235
+ > BẮT BUỘC bind qua `[innerHTML]` để Angular render HTML thay vì hiển thị text thô.
236
+
237
+ ## Logic ẩn quan trọng
238
+
239
+ ### Tìm kiếm không dấu (deleteUnicode)
240
+
241
+ Pipe sử dụng hàm `deleteUnicode()` nội bộ để chuyển cả `value` và `search` về dạng không dấu trước khi so sánh vị trí match. Sau đó lấy substring tương ứng trong `value` gốc (có dấu) để highlight đúng ký tự.
242
+
243
+ ```
244
+ search = 'an'
245
+ → searchConvert = 'an'
246
+ → valueConvert = 'nguyen van an la ky su tai nang'
247
+ → tìm vị trí 'an' trong valueConvert
248
+ → highlight đúng substring có dấu tương ứng trong value gốc
249
+ ```
250
+
251
+ Kết quả: "Nguyễn Văn **An** là kỹ sư tài n**ăn**g" (cả "An" và "ăn" trong "năng" đều được highlight).
252
+
253
+ ### Thứ tự xử lý và early return
254
+
255
+ 1. Nếu `value` là falsy → trả về `CHARACTER_DATA_EMPTY` (chuỗi rỗng, an toàn)
256
+ 2. Nếu `search` trống hoặc `ignoreHighlight = true` → trả về `value` gốc nguyên vẹn
257
+ 3. Nếu không có match nào → trả về `value` gốc nguyên vẹn
258
+ 4. Mọi exception được bắt và log — pipe không bao giờ throw lỗi ra ngoài
97
259
 
98
260
  ## Lưu ý quan trọng
99
261
 
100
- - **Luôn dùng `[innerHTML]`** pipe trả về HTML string chứa `<span>` tags
101
- - **XSS:** Chỉ dùng với dữ liệu đã tin cậy. Nếu `value` đến từ user input chưa kiểm tra, hãy sanitize trước khi truyền vào pipe
102
- - **Default class** `'bg-[#19344a] text-white'` (dark navy background). thể override bằng bất kỳ Tailwind hoặc CSS class nào
103
- - **ignoreHighlight** hữu ích để toggle highlight theo state, tránh phải thêm `@if` trong template
262
+ ⚠️ **Bắt buộc dùng `[innerHTML]`**: Pipe trả về HTML string chứa `<span>` tags. Nếu dùng `{{ }}` trong template, Angular sẽ escape HTML và hiển thị text thô như `<span class="...">keyword</span>` thay vì render màu.
263
+
264
+ ⚠️ **Bảo mật XSS**: Chỉ dùng với dữ liệu đã tin cậy (từ API đã sanitize hoặc dữ liệu nội bộ). Nếu `value` đến từ user input chưa kiểm tra, hãy sanitize trước khi truyền vào pipe. Không truyền nội dung HTML động chưa qua `escapeHtml()` / `xssFilter()`.
265
+
266
+ ⚠️ **Tên tham số `classHightLight`**: Lưu ý cách viết — chỉ có 1 chữ "l" trong "Hight**L**ight" (không phải "Highlight"). Đây là tên tham số trong API hiện tại.
267
+
268
+ ⚠️ **Nhiều lần xuất hiện**: Pipe highlight TẤT CẢ các lần xuất hiện của keyword trong chuỗi, không chỉ lần đầu tiên.
269
+
270
+ ⚠️ **Dùng `ignoreHighlight` thay `@if`**: Thay vì dùng `@if` để chọn giữa 2 template (có/không highlight), hãy dùng tham số `ignoreHighlight` — gọn hơn và không tạo/hủy DOM node.
271
+
272
+ ## Demo
273
+
274
+ ```bash
275
+ npx nx serve core-ui
276
+ ```
277
+
278
+ Truy cập: http://localhost:4500/pipes/format-text/highlight-by-keyword
@@ -1 +1 @@
1
- {"version":3,"file":"libs-ui-pipes-format-text-highlight-by-keyword.mjs","sources":["../../../../../../libs-ui/pipes/format-text/highlight-by-keyword/src/highlight-by-keyword.pipe.ts","../../../../../../libs-ui/pipes/format-text/highlight-by-keyword/src/libs-ui-pipes-format-text-highlight-by-keyword.ts"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\nimport { highlightByKeyword } from '@libs-ui/utils';\n\n@Pipe({\n name: 'LibsUiPipesFormatTextHighlightByKeywordPipe',\n standalone: true,\n})\nexport class LibsUiPipesFormatTextHighlightByKeywordPipe implements PipeTransform {\n transform(value: string | undefined, search: string | undefined, ignoreHighlight?: boolean, classHightLight?: string) {\n return highlightByKeyword(value, search, ignoreHighlight, classHightLight);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;MAOa,2CAA2C,CAAA;AACtD,IAAA,SAAS,CAAC,KAAyB,EAAE,MAA0B,EAAE,eAAyB,EAAE,eAAwB,EAAA;QAClH,OAAO,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,CAAC;IAC5E;wGAHW,2CAA2C,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,IAAA,EAAA,CAAA;sGAA3C,2CAA2C,EAAA,YAAA,EAAA,IAAA,EAAA,IAAA,EAAA,6CAAA,EAAA,CAAA;;4FAA3C,2CAA2C,EAAA,UAAA,EAAA,CAAA;kBAJvD,IAAI;AAAC,YAAA,IAAA,EAAA,CAAA;AACJ,oBAAA,IAAI,EAAE,6CAA6C;AACnD,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;ACND;;AAEG;;;;"}
1
+ {"version":3,"file":"libs-ui-pipes-format-text-highlight-by-keyword.mjs","sources":["../../../../../../libs-ui/pipes/format-text/highlight-by-keyword/src/highlight-by-keyword.pipe.ts","../../../../../../libs-ui/pipes/format-text/highlight-by-keyword/src/libs-ui-pipes-format-text-highlight-by-keyword.ts"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\nimport { highlightByKeyword } from '@libs-ui/utils';\n\n@Pipe({\n name: 'LibsUiPipesFormatTextHighlightByKeywordPipe',\n standalone: true,\n})\nexport class LibsUiPipesFormatTextHighlightByKeywordPipe implements PipeTransform {\n transform(value: string | undefined, search: string | undefined, ignoreHighlight?: boolean, classHightLight?: string) {\n return highlightByKeyword(value, search, ignoreHighlight, classHightLight);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;MAOa,2CAA2C,CAAA;AACtD,IAAA,SAAS,CAAC,KAAyB,EAAE,MAA0B,EAAE,eAAyB,EAAE,eAAwB,EAAA;QAClH,OAAO,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;KAC5E;wGAHU,2CAA2C,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,IAAA,EAAA,CAAA,CAAA;sGAA3C,2CAA2C,EAAA,YAAA,EAAA,IAAA,EAAA,IAAA,EAAA,6CAAA,EAAA,CAAA,CAAA;;4FAA3C,2CAA2C,EAAA,UAAA,EAAA,CAAA;kBAJvD,IAAI;AAAC,YAAA,IAAA,EAAA,CAAA;AACJ,oBAAA,IAAI,EAAE,6CAA6C;AACnD,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA,CAAA;;;ACND;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@libs-ui/pipes-format-text-highlight-by-keyword",
3
- "version": "0.2.356-8",
3
+ "version": "0.2.357-0",
4
4
  "peerDependencies": {
5
- "@angular/common": ">=18.0.0",
6
- "@angular/core": ">=18.0.0"
5
+ "@angular/core": ">=18.0.0",
6
+ "@libs-ui/utils": "0.2.357-0"
7
7
  },
8
8
  "sideEffects": false,
9
9
  "module": "fesm2022/libs-ui-pipes-format-text-highlight-by-keyword.mjs",