@libs-ui/components-scroll-overlay 0.2.356-9 → 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,153 +1,386 @@
|
|
|
1
1
|
# @libs-ui/components-scroll-overlay
|
|
2
2
|
|
|
3
|
-
> Directive tạo thanh cuộn tùy chỉnh (custom scrollbar) thay thế thanh cuộn mặc định của trình duyệt.
|
|
3
|
+
> Directive tạo thanh cuộn tùy chỉnh (custom scrollbar) dạng overlay, thay thế thanh cuộn mặc định của trình duyệt với giao diện đồng nhất trên mọi hệ điều hành.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiComponentsScrollOverlayDirective` là một standalone
|
|
7
|
+
`LibsUiComponentsScrollOverlayDirective` là một Angular standalone directive giúp thay thế thanh cuộn mặc định của trình duyệt bằng một thanh cuộn tùy chỉnh hiển thị dạng overlay — không chiếm diện tích layout. Directive tự động bọc element host trong một `div` container, inject CSS global một lần duy nhất, và quản lý toàn bộ tương tác kéo thả, hover, resize qua RxJS và requestAnimationFrame throttling. Mỗi instance độc lập màu sắc thông qua CSS custom properties.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
11
|
+
- Thanh cuộn tùy chỉnh thay thế native scrollbar, giao diện đồng nhất trên Windows, macOS, Linux
|
|
12
|
+
- Hỗ trợ cả scroll dọc (Y) và scroll ngang (X) đồng thời
|
|
13
|
+
- Tự động ẩn/hiện khi hover vào container, giữ hiện khi đang kéo thả
|
|
14
|
+
- Kéo thả (drag & drop) thumb với xử lý mouse chính xác
|
|
15
|
+
- Click vào track để nhảy đến vị trí scroll tương ứng
|
|
16
|
+
- Tự động tính lại kích thước thumb khi nội dung thay đổi (interval poll mỗi 1s khi hover + dimension cache)
|
|
17
|
+
- Resize cửa sổ tự động cập nhật scrollbar
|
|
18
|
+
- Scroll handler throttle qua `requestAnimationFrame` — tránh layout thrashing
|
|
19
|
+
- Màu sắc per-instance qua CSS variables, không ghi đè lẫn nhau
|
|
20
|
+
- Phát 5 loại output event: `outScroll`, `outScrollX`, `outScrollY`, `outScrollTop`, `outScrollBottom`
|
|
21
|
+
- Hỗ trợ truyền element riêng để tính `scrollWidth`/`scrollHeight` (hữu ích khi content element khác scroll element)
|
|
18
22
|
|
|
19
23
|
## Khi nào sử dụng
|
|
20
24
|
|
|
21
|
-
- Khi cần giao diện thanh cuộn đồng bộ trên các trình duyệt
|
|
22
|
-
- Khi cần thanh cuộn nhỏ gọn, overlay lên nội dung mà không chiếm diện tích layout
|
|
23
|
-
- Khi cần tùy biến màu sắc thanh cuộn theo theme của ứng dụng
|
|
25
|
+
- Khi cần giao diện thanh cuộn đồng bộ trên các trình duyệt và hệ điều hành khác nhau
|
|
26
|
+
- Khi cần thanh cuộn nhỏ gọn, overlay lên nội dung mà không chiếm diện tích layout
|
|
27
|
+
- Khi cần tùy biến màu sắc thanh cuộn theo theme của ứng dụng
|
|
28
|
+
- Khi cần lắng nghe sự kiện scroll lên đầu hoặc xuống cuối để trigger lazy loading
|
|
29
|
+
- Khi muốn ẩn hoàn toàn scrollbar nhưng vẫn cho phép scroll bằng mouse wheel hoặc touch
|
|
24
30
|
|
|
25
31
|
## Cài đặt
|
|
26
32
|
|
|
27
33
|
```bash
|
|
28
|
-
# npm
|
|
29
34
|
npm install @libs-ui/components-scroll-overlay
|
|
30
|
-
|
|
31
|
-
# yarn
|
|
32
|
-
yarn add @libs-ui/components-scroll-overlay
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
## Import
|
|
36
38
|
|
|
37
39
|
```typescript
|
|
38
40
|
import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
|
|
41
|
+
import { IScrollOverlayOptions } from '@libs-ui/components-scroll-overlay';
|
|
39
42
|
|
|
40
43
|
@Component({
|
|
41
44
|
standalone: true,
|
|
42
45
|
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
43
|
-
// ...
|
|
44
46
|
})
|
|
45
|
-
export class
|
|
47
|
+
export class MyComponent {}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Ví dụ sử dụng
|
|
51
|
+
|
|
52
|
+
### Ví dụ 1 — Scroll dọc cơ bản
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<div
|
|
56
|
+
LibsUiComponentsScrollOverlayDirective
|
|
57
|
+
class="h-64 w-full overflow-y-auto p-4 border rounded bg-white"
|
|
58
|
+
>
|
|
59
|
+
@for (item of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; track item) {
|
|
60
|
+
<div class="p-3 mb-2 bg-gray-50 rounded">
|
|
61
|
+
Item {{ item }}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
62
|
+
</div>
|
|
63
|
+
}
|
|
64
|
+
</div>
|
|
46
65
|
```
|
|
47
66
|
|
|
48
|
-
|
|
67
|
+
```typescript
|
|
68
|
+
import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
|
|
49
69
|
|
|
50
|
-
|
|
70
|
+
@Component({
|
|
71
|
+
standalone: true,
|
|
72
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
73
|
+
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
74
|
+
})
|
|
75
|
+
export class MyComponent {}
|
|
76
|
+
```
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
### Ví dụ 2 — Scroll ngang với nội dung rộng
|
|
53
79
|
|
|
54
80
|
```html
|
|
55
81
|
<div
|
|
56
82
|
LibsUiComponentsScrollOverlayDirective
|
|
57
|
-
class="
|
|
58
|
-
|
|
83
|
+
class="w-full overflow-x-auto border rounded bg-white"
|
|
84
|
+
>
|
|
85
|
+
<div class="flex gap-4 w-[1200px] p-4">
|
|
86
|
+
@for (item of [1, 2, 3, 4, 5, 6, 7, 8]; track item) {
|
|
87
|
+
<div class="w-48 h-32 bg-blue-100 rounded flex-shrink-0 flex items-center justify-center">
|
|
88
|
+
Card {{ item }}
|
|
89
|
+
</div>
|
|
90
|
+
}
|
|
91
|
+
</div>
|
|
59
92
|
</div>
|
|
60
93
|
```
|
|
61
94
|
|
|
62
|
-
###
|
|
95
|
+
### Ví dụ 3 — Tùy chỉnh màu sắc và kích thước qua options
|
|
63
96
|
|
|
64
97
|
```html
|
|
65
98
|
<div
|
|
66
99
|
LibsUiComponentsScrollOverlayDirective
|
|
67
|
-
[options]="
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
[options]="scrollOptions"
|
|
101
|
+
class="h-80 w-full overflow-auto border rounded bg-white p-4"
|
|
102
|
+
>
|
|
103
|
+
<div class="w-[900px]">
|
|
104
|
+
<p>Nội dung có thể scroll cả dọc lẫn ngang...</p>
|
|
105
|
+
@for (i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; track i) {
|
|
106
|
+
<div class="p-3 mb-2 bg-gray-50 rounded">Dòng {{ i }}</div>
|
|
107
|
+
}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
|
|
114
|
+
import { LibsUiComponentsScrollOverlayDirective, IScrollOverlayOptions } from '@libs-ui/components-scroll-overlay';
|
|
115
|
+
|
|
116
|
+
@Component({
|
|
117
|
+
standalone: true,
|
|
118
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
119
|
+
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
120
|
+
})
|
|
121
|
+
export class MyComponent {
|
|
122
|
+
protected readonly scrollOptions: IScrollOverlayOptions = {
|
|
70
123
|
scrollbarWidth: 8,
|
|
71
|
-
scrollbarPadding:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
124
|
+
scrollbarPadding: 2,
|
|
125
|
+
scrollbarColor: '#f1f5f9',
|
|
126
|
+
scrollbarHoverColor: '#e2e8f0',
|
|
127
|
+
scrollThumbColor: '#94a3b8',
|
|
128
|
+
scrollThumbHoverColor: '#64748b',
|
|
129
|
+
scrollX: 'scroll',
|
|
130
|
+
scrollY: 'scroll',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Ví dụ 4 — Lắng nghe sự kiện scroll để lazy load
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<div
|
|
139
|
+
LibsUiComponentsScrollOverlayDirective
|
|
140
|
+
class="h-96 w-full overflow-y-auto border rounded"
|
|
141
|
+
(outScrollBottom)="handlerScrollBottom($event)"
|
|
142
|
+
(outScrollTop)="handlerScrollTop($event)"
|
|
143
|
+
(outScrollY)="handlerScrollY($event)"
|
|
144
|
+
>
|
|
145
|
+
@for (item of items(); track item.id) {
|
|
146
|
+
<div class="p-4 border-b">{{ item.name }}</div>
|
|
147
|
+
}
|
|
148
|
+
@if (loading()) {
|
|
149
|
+
<div class="p-4 text-center text-gray-400">Đang tải thêm...</div>
|
|
150
|
+
}
|
|
75
151
|
</div>
|
|
76
152
|
```
|
|
77
153
|
|
|
78
|
-
|
|
154
|
+
```typescript
|
|
155
|
+
import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core';
|
|
156
|
+
import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
|
|
157
|
+
import { ItemApiService } from './item-api.service';
|
|
79
158
|
|
|
80
|
-
|
|
159
|
+
@Component({
|
|
160
|
+
standalone: true,
|
|
161
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
162
|
+
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
163
|
+
})
|
|
164
|
+
export class MyListComponent {
|
|
165
|
+
private readonly apiService = inject(ItemApiService);
|
|
166
|
+
|
|
167
|
+
protected items = signal<{ id: string; name: string }[]>([]);
|
|
168
|
+
protected loading = signal(false);
|
|
169
|
+
|
|
170
|
+
protected handlerScrollBottom(event: Event): void {
|
|
171
|
+
event.stopPropagation();
|
|
172
|
+
if (this.loading()) return;
|
|
173
|
+
this.loadMore();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected handlerScrollTop(event: Event): void {
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
// xử lý khi scroll lên đầu
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected handlerScrollY(event: Event): void {
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
// xử lý mỗi lần scroll dọc
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private loadMore(): void {
|
|
187
|
+
this.loading.set(true);
|
|
188
|
+
// gọi API tải thêm dữ liệu
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
81
192
|
|
|
82
|
-
|
|
193
|
+
### Ví dụ 5 — Dynamic content với signal (tự cập nhật scrollbar)
|
|
83
194
|
|
|
84
|
-
|
|
195
|
+
```html
|
|
196
|
+
<div class="flex gap-2 mb-3">
|
|
197
|
+
<button (click)="handlerAddItem()">+ Thêm item</button>
|
|
198
|
+
<button (click)="handlerRemoveItem()">- Xóa item</button>
|
|
199
|
+
</div>
|
|
85
200
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
201
|
+
<div
|
|
202
|
+
LibsUiComponentsScrollOverlayDirective
|
|
203
|
+
class="h-64 w-full overflow-y-auto border rounded bg-gray-50 p-3"
|
|
204
|
+
>
|
|
205
|
+
@for (item of items(); track item) {
|
|
206
|
+
<div class="p-3 mb-2 bg-white rounded shadow-sm">{{ item }}</div>
|
|
207
|
+
}
|
|
208
|
+
</div>
|
|
209
|
+
```
|
|
95
210
|
|
|
96
|
-
|
|
211
|
+
```typescript
|
|
212
|
+
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
|
|
213
|
+
import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
|
|
214
|
+
|
|
215
|
+
@Component({
|
|
216
|
+
standalone: true,
|
|
217
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
218
|
+
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
219
|
+
})
|
|
220
|
+
export class DynamicListComponent {
|
|
221
|
+
protected items = signal<string[]>([
|
|
222
|
+
'Thiết kế giao diện người dùng',
|
|
223
|
+
'Viết unit test cho module auth',
|
|
224
|
+
'Review Pull Request #124',
|
|
225
|
+
'Cập nhật tài liệu API',
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
protected handlerAddItem(): void {
|
|
229
|
+
this.items.update((list) => [...list, `Item mới ${list.length + 1}`]);
|
|
230
|
+
// Sau khi thêm item, hover vào vùng scroll để scrollbar tự cập nhật (~1s)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected handlerRemoveItem(): void {
|
|
234
|
+
if (this.items().length <= 1) return;
|
|
235
|
+
this.items.update((list) => list.slice(0, -1));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Ví dụ 6 — Ẩn scrollbar hoàn toàn nhưng vẫn scroll được
|
|
241
|
+
|
|
242
|
+
```html
|
|
243
|
+
<!-- scrollXOpacity0: true → scrollbar ngang không bao giờ hiện, nhưng vẫn scroll được bằng touch/trackpad -->
|
|
244
|
+
<div
|
|
245
|
+
LibsUiComponentsScrollOverlayDirective
|
|
246
|
+
[options]="hiddenScrollbarOptions"
|
|
247
|
+
class="w-full overflow-x-auto"
|
|
248
|
+
>
|
|
249
|
+
<div class="flex gap-4 w-[2000px] p-4">
|
|
250
|
+
@for (item of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; track item) {
|
|
251
|
+
<div class="w-40 h-24 bg-indigo-100 rounded flex-shrink-0 flex items-center justify-center">
|
|
252
|
+
Card {{ item }}
|
|
253
|
+
</div>
|
|
254
|
+
}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
```
|
|
97
258
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
259
|
+
```typescript
|
|
260
|
+
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
261
|
+
import { LibsUiComponentsScrollOverlayDirective, IScrollOverlayOptions } from '@libs-ui/components-scroll-overlay';
|
|
262
|
+
|
|
263
|
+
@Component({
|
|
264
|
+
standalone: true,
|
|
265
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
266
|
+
imports: [LibsUiComponentsScrollOverlayDirective],
|
|
267
|
+
})
|
|
268
|
+
export class HiddenScrollbarComponent {
|
|
269
|
+
protected readonly hiddenScrollbarOptions: IScrollOverlayOptions = {
|
|
270
|
+
scrollX: 'scroll',
|
|
271
|
+
scrollY: 'hidden',
|
|
272
|
+
scrollXOpacity0: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## @Input()
|
|
278
|
+
|
|
279
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
280
|
+
|---|---|---|---|---|
|
|
281
|
+
| `[options]` | `IScrollOverlayOptions` | `{}` | Cấu hình toàn bộ scrollbar: màu sắc, kích thước, chế độ cuộn và các flags | `[options]="{ scrollbarWidth: 8, scrollThumbColor: '#94a3b8' }"` |
|
|
282
|
+
| `[classContainer]` | `string` | `''` | Class CSS bổ sung vào `div` container bao ngoài mà directive tạo ra | `[classContainer]="'shadow-lg rounded-xl'"` |
|
|
283
|
+
| `[elementScroll]` | `HTMLElement` | Host element | Override element cần scroll thay vì dùng host element mặc định | `[elementScroll]="innerScrollRef"` |
|
|
284
|
+
| `[elementCheckScrollX]` | `HTMLElement` | `undefined` | Element dùng để tính `scrollWidth` — dùng khi content element khác với scroll element | `[elementCheckScrollX]="contentRef"` |
|
|
285
|
+
| `[elementCheckScrollY]` | `HTMLElement` | `undefined` | Element dùng để tính `scrollHeight` — dùng khi content element khác với scroll element | `[elementCheckScrollY]="contentRef"` |
|
|
286
|
+
| `[debugMode]` | `boolean` | `false` | Bật chế độ debug: ghi log ra console khi có thay đổi kích thước scrollbar | `[debugMode]="true"` |
|
|
287
|
+
| `[ignoreInit]` | `boolean` | `false` | Nếu `true`, directive bỏ qua toàn bộ khởi tạo scrollbar — dùng cho lazy init có điều kiện | `[ignoreInit]="!isVisible()"` |
|
|
288
|
+
|
|
289
|
+
## @Output()
|
|
290
|
+
|
|
291
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
292
|
+
|---|---|---|---|---|
|
|
293
|
+
| `(outScroll)` | `Event` | Emit mỗi khi có sự kiện scroll (cả X lẫn Y) | `handlerScroll(event: Event): void { event.stopPropagation(); ... }` | `(outScroll)="handlerScroll($event)"` |
|
|
294
|
+
| `(outScrollBottom)` | `Event` | Emit khi scroll xuống đến cuối nội dung (tolerance 3px) | `handlerScrollBottom(event: Event): void { event.stopPropagation(); this.loadMore(); }` | `(outScrollBottom)="handlerScrollBottom($event)"` |
|
|
295
|
+
| `(outScrollTop)` | `Event` | Emit khi scroll về đầu (scrollTop === 0) | `handlerScrollTop(event: Event): void { event.stopPropagation(); ... }` | `(outScrollTop)="handlerScrollTop($event)"` |
|
|
296
|
+
| `(outScrollX)` | `Event` | Emit khi cuộn ngang (chỉ khi scrollLeft thực sự thay đổi) | `handlerScrollX(event: Event): void { event.stopPropagation(); ... }` | `(outScrollX)="handlerScrollX($event)"` |
|
|
297
|
+
| `(outScrollY)` | `Event` | Emit khi cuộn dọc (chỉ khi scrollTop thực sự thay đổi) | `handlerScrollY(event: Event): void { event.stopPropagation(); ... }` | `(outScrollY)="handlerScrollY($event)"` |
|
|
105
298
|
|
|
106
299
|
## Types & Interfaces
|
|
107
300
|
|
|
108
301
|
```typescript
|
|
109
|
-
|
|
110
|
-
|
|
302
|
+
import { IScrollOverlayOptions, TYPE_SCROLL_DIRECTION, TYPE_SCROLL_OVERFLOW } from '@libs-ui/components-scroll-overlay';
|
|
303
|
+
```
|
|
111
304
|
|
|
305
|
+
```typescript
|
|
112
306
|
export interface IScrollOverlayOptions {
|
|
113
|
-
|
|
114
|
-
scrollbarWidth?: number;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
307
|
+
// Kích thước
|
|
308
|
+
scrollbarWidth?: number; // Độ rộng của track scrollbar tính bằng px (default: 10)
|
|
309
|
+
scrollbarPadding?: number; // Khoảng cách padding của thumb so với cạnh track (default: 2)
|
|
310
|
+
|
|
311
|
+
// Màu sắc
|
|
312
|
+
scrollbarColor?: string; // Màu nền của track khi không hover (default: transparent)
|
|
313
|
+
scrollbarHoverColor?: string; // Màu nền của track khi hover (default: '#CDD0D640')
|
|
314
|
+
scrollThumbColor?: string; // Màu của thumb scrollbar (default: '#CDD0D6')
|
|
315
|
+
scrollThumbHoverColor?: string; // Màu của thumb khi hover (default: '#9CA2AD')
|
|
316
|
+
|
|
317
|
+
// Chế độ cuộn
|
|
318
|
+
scrollX?: TYPE_SCROLL_OVERFLOW; // 'scroll' | 'hidden' — kiểm soát overflow-x (default: 'scroll')
|
|
319
|
+
scrollY?: TYPE_SCROLL_OVERFLOW; // 'scroll' | 'hidden' — kiểm soát overflow-y (default: 'scroll')
|
|
320
|
+
|
|
321
|
+
// Flags hành vi
|
|
322
|
+
scrollXOpacity0?: boolean; // Giữ scrollbar ngang luôn ẩn (opacity 0) dù hover — vẫn scroll được
|
|
323
|
+
scrollYOpacity0?: boolean; // Giữ scrollbar dọc luôn ẩn (opacity 0) dù hover — vẫn scroll được
|
|
324
|
+
ignoreTransparentScrollBarColorDefault?: boolean; // Không ép native scrollbar thành trong suốt — dùng 'auto' thay vì 'transparent transparent'
|
|
124
325
|
}
|
|
326
|
+
|
|
327
|
+
export type TYPE_SCROLL_DIRECTION = 'X' | 'Y';
|
|
328
|
+
export type TYPE_SCROLL_OVERFLOW = 'hidden' | 'scroll';
|
|
125
329
|
```
|
|
126
330
|
|
|
127
|
-
##
|
|
331
|
+
## Cấu trúc DOM được tạo ra
|
|
128
332
|
|
|
129
|
-
|
|
130
|
-
Cấu trúc DOM được tạo ra:
|
|
333
|
+
Khi directive khởi tạo, nó tự động wrap host element như sau:
|
|
131
334
|
|
|
132
335
|
```html
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
336
|
+
<!-- Trước khi áp dụng directive -->
|
|
337
|
+
<div class="h-64 overflow-y-auto" LibsUiComponentsScrollOverlayDirective>
|
|
338
|
+
<!-- nội dung -->
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<!-- Sau khi directive khởi tạo -->
|
|
342
|
+
<div class="libs-ui-scroll-overlay-container relative min-h-0 min-w-0 h-64">
|
|
343
|
+
<!-- Host element (đã thêm class libs-ui-scroll-overlay-element) -->
|
|
344
|
+
<div class="h-64 overflow-y-auto libs-ui-scroll-overlay-element" style="scrollbar-width: none; overflow-x: scroll; overflow-y: scroll;">
|
|
345
|
+
<!-- nội dung -->
|
|
346
|
+
</div>
|
|
347
|
+
<!-- Track và thumb cho scroll X -->
|
|
348
|
+
<div class="scrollbar-track scrollbar-track-X" style="height: 10px; display: none;">
|
|
349
|
+
<div class="scrollbar-thumb scrollbar-thumb-X" style="width: 60px; left: 0;"></div>
|
|
137
350
|
</div>
|
|
138
|
-
|
|
139
|
-
|
|
351
|
+
<!-- Track và thumb cho scroll Y -->
|
|
352
|
+
<div class="scrollbar-track scrollbar-track-Y" style="width: 10px; display: none;">
|
|
353
|
+
<div class="scrollbar-thumb scrollbar-thumb-Y" style="height: 60px; top: 0;"></div>
|
|
140
354
|
</div>
|
|
141
355
|
</div>
|
|
142
356
|
```
|
|
143
357
|
|
|
144
|
-
##
|
|
358
|
+
## CSS Custom Properties
|
|
359
|
+
|
|
360
|
+
Mỗi instance directive tự inject CSS variables lên container div để không ảnh hưởng lẫn nhau:
|
|
361
|
+
|
|
362
|
+
| CSS Variable | Tương ứng với | Giá trị mặc định |
|
|
363
|
+
|---|---|---|
|
|
364
|
+
| `--libs-ui-sb-track-color` | `scrollbarColor` | `transparent` |
|
|
365
|
+
| `--libs-ui-sb-track-hover-color` | `scrollbarHoverColor` | `#CDD0D640` |
|
|
366
|
+
| `--libs-ui-sb-thumb-color` | `scrollThumbColor` | `#CDD0D6` |
|
|
367
|
+
| `--libs-ui-sb-thumb-hover-color` | `scrollThumbHoverColor` | `#9CA2AD` |
|
|
368
|
+
| `--libs-ui-sb-padding` | `scrollbarPadding` | `2px` |
|
|
369
|
+
| `--libs-ui-sb-padding-double` | `scrollbarPadding * 2` | `4px` |
|
|
370
|
+
|
|
371
|
+
## Lưu ý quan trọng
|
|
372
|
+
|
|
373
|
+
⚠️ **DOM Wrapping**: Directive tự động bọc host element trong một `div.libs-ui-scroll-overlay-container`. CSS selectors dựa vào cấu trúc cha-con (ví dụ: `.parent > div`) có thể bị ảnh hưởng. Kiểm tra kỹ layout sau khi áp dụng directive.
|
|
374
|
+
|
|
375
|
+
⚠️ **Class kích thước tự động kế thừa**: Directive tự copy một số class kích thước từ host element (`w-full`, `w-screen`, `h-full`, `h-screen`, `h-[Npx]`, `w-[Npx]`, `min-h-*`, `min-w-*`, `shrink-0`) sang container. Nếu layout bị sai, kiểm tra xem class có được copy đúng không.
|
|
376
|
+
|
|
377
|
+
⚠️ **Dynamic content cần hover**: Directive dùng `interval(1000)` khi hover để poll `scrollHeight`/`scrollWidth` và so sánh với cache dimension. Sau khi thêm/xóa nội dung hoặc thay đổi chiều cao element, người dùng cần hover vào vùng scroll để scrollbar cập nhật trong vòng ~1s. Đây là thiết kế chủ ý nhằm tránh dùng `MutationObserver({ attributes, subtree })` gây fire 50-200+ lần/giây khi hover.
|
|
378
|
+
|
|
379
|
+
⚠️ **position: relative**: Container `div` được tạo ra luôn có `position: relative`. Nếu layout cần `position: static` hoặc layout cụ thể, cần dùng `[classContainer]` để điều chỉnh.
|
|
380
|
+
|
|
381
|
+
⚠️ **scrollX/scrollY: 'hidden'**: Khi đặt cả hai thành `'hidden'`, host element sẽ có class `overflow-hidden`, ngăn mọi scroll. Chỉ dùng khi thực sự muốn block scroll hoàn toàn.
|
|
145
382
|
|
|
146
|
-
|
|
147
|
-
| ---------- | ------- | ------------------- |
|
|
148
|
-
| Angular | 18+ | Framework |
|
|
149
|
-
| RxJS | 7.x | Event Handling |
|
|
150
|
-
| DOM API | - | Scroll Manipulation |
|
|
383
|
+
⚠️ **Không dùng với `overflow: visible`**: Directive yêu cầu host element có thể scroll (overflow có giá trị). Nếu element không có nội dung tràn, scrollbar sẽ không hiển thị (ẩn tự động theo `display: none`).
|
|
151
384
|
|
|
152
385
|
## Demo
|
|
153
386
|
|