@libs-ui/components-scroll-overlay 0.2.29 → 0.2.30-6.2
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 +134 -2
- package/demo/scroll-overlay.demo.d.ts +44 -0
- package/esm2022/demo/scroll-overlay.demo.mjs +78 -0
- package/esm2022/index.mjs +2 -1
- package/esm2022/scroll-overlay.directive.mjs +174 -75
- package/esm2022/scroll.interface.mjs +1 -1
- package/fesm2022/libs-ui-components-scroll-overlay.mjs +250 -76
- package/fesm2022/libs-ui-components-scroll-overlay.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +4 -3
- package/scroll-overlay.directive.d.ts +19 -8
- package/scroll.interface.d.ts +6 -0
|
@@ -1,84 +1,107 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { computed, signal, input, output, inject, ElementRef, Renderer2, effect, untracked, Directive, Component } from '@angular/core';
|
|
3
|
+
import { getDragEventByElement, checkMouseOverInContainer } from '@libs-ui/utils';
|
|
4
|
+
import { Subscription, Subject, fromEvent, tap, takeUntil, switchMap, interval } from 'rxjs';
|
|
5
|
+
import * as i1 from '@angular/common';
|
|
6
|
+
import { CommonModule } from '@angular/common';
|
|
4
7
|
|
|
5
8
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
9
|
class LibsUiComponentsScrollOverlayDirective {
|
|
7
|
-
|
|
8
|
-
styles =
|
|
10
|
+
// #region PROPERTY
|
|
11
|
+
styles = computed(() => `
|
|
12
|
+
.scrollbar-track{
|
|
13
|
+
background-color:${this.scrollbarColor()};
|
|
14
|
+
}
|
|
15
|
+
.scrollbar-track:hover{
|
|
16
|
+
background-color:${this.scrollbarHoverColor()};
|
|
17
|
+
}
|
|
9
18
|
.scrollbar-track-X {
|
|
19
|
+
width:100%;
|
|
10
20
|
position: absolute;
|
|
11
21
|
bottom: 0;
|
|
12
22
|
left: 0;
|
|
13
|
-
height: 8px;
|
|
14
|
-
pointer-events: none;
|
|
15
23
|
visibility: hidden;
|
|
24
|
+
cursor: pointer;
|
|
16
25
|
opacity: 0;
|
|
26
|
+
z-index: 1;
|
|
17
27
|
transition: opacity 0.3s ease, visibility 0.3s ease;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
|
-
.scrollbar-thumb-X {
|
|
21
|
-
height: 100%;
|
|
22
|
-
background-color: #CDD0D6;
|
|
23
|
-
border-radius: 4px;
|
|
24
|
-
cursor: pointer;
|
|
25
|
-
transition: background-color 0.3s;
|
|
26
|
-
pointer-events: auto;
|
|
27
|
-
position: absolute;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
30
|
.scrollbar-track-Y {
|
|
31
|
+
height:100%;
|
|
31
32
|
position: absolute;
|
|
32
33
|
top: 0;
|
|
33
34
|
right: 0;
|
|
34
|
-
width: 8px;
|
|
35
|
-
pointer-events: none;
|
|
36
35
|
visibility: hidden;
|
|
36
|
+
cursor: pointer;
|
|
37
37
|
opacity: 0;
|
|
38
|
+
z-index: 1;
|
|
38
39
|
transition: opacity 0.3s ease, visibility 0.3s ease;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
.scrollbar-thumb
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
.scrollbar-thumb{
|
|
43
|
+
background-color:${this.scrollThumbColor()};
|
|
44
|
+
}
|
|
45
|
+
.scrollbar-thumb:hover{
|
|
46
|
+
background-color:${this.scrollThumbHoverColor()};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.scrollbar-thumb-X {
|
|
50
|
+
height: calc(100% - ${this.scrollbarPadding() * 2}px);
|
|
51
|
+
bottom: ${this.scrollbarPadding()}px;
|
|
44
52
|
border-radius: 4px;
|
|
45
|
-
cursor:
|
|
53
|
+
cursor: grabbing;
|
|
46
54
|
transition: background-color 0.3s;
|
|
47
|
-
pointer-events: auto;
|
|
48
55
|
position: absolute;
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
.scrollbar-thumb
|
|
52
|
-
|
|
58
|
+
.scrollbar-thumb-Y {
|
|
59
|
+
width: calc(100% - ${this.scrollbarPadding() * 2}px);
|
|
60
|
+
right: ${this.scrollbarPadding()}px;
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
cursor: grabbing;
|
|
63
|
+
transition: background-color 0.3s;
|
|
64
|
+
position: absolute;
|
|
53
65
|
}
|
|
54
|
-
`, {})
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
scrollbarWidth = computed(() => this.options()?.scrollbarWidth
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
`, {});
|
|
67
|
+
isScrollThumb = signal(false);
|
|
68
|
+
keepDisplayThumb = signal(false);
|
|
69
|
+
subsX = new Subscription();
|
|
70
|
+
subsY = new Subscription();
|
|
71
|
+
scrollbarWidth = computed(() => this.options()?.scrollbarWidth ?? 10); // Chiều rộng thanh cuộn
|
|
72
|
+
scrollbarPadding = computed(() => this.options()?.scrollbarPadding ?? 2); // Chiều rộng thanh cuộn
|
|
73
|
+
scrollbarColor = computed(() => this.options()?.scrollbarColor ?? '');
|
|
74
|
+
scrollbarHoverColor = computed(() => this.options()?.scrollbarColor ?? '#CDD0D640');
|
|
75
|
+
scrollThumbColor = computed(() => this.options()?.scrollThumbColor ?? '#CDD0D6');
|
|
76
|
+
scrollThumbHoverColor = computed(() => this.options()?.scrollThumbHoverColor ?? '#9CA2AD');
|
|
63
77
|
divContainer = document.createElement('div');
|
|
64
78
|
trackX = document.createElement('div');
|
|
65
79
|
thumbX = document.createElement('div');
|
|
66
80
|
trackY = document.createElement('div');
|
|
67
81
|
thumbY = document.createElement('div');
|
|
68
82
|
onDestroy = new Subject();
|
|
69
|
-
|
|
83
|
+
// #region INPUT
|
|
84
|
+
debugMode = input(false);
|
|
85
|
+
ignoreInit = input(false);
|
|
70
86
|
classContainer = input('', { transform: value => value ?? '' });
|
|
71
87
|
options = input(Object.assign({}));
|
|
72
|
-
|
|
88
|
+
elementCheckScrollX = input();
|
|
89
|
+
elementCheckScrollY = input();
|
|
90
|
+
elementScroll = input();
|
|
91
|
+
// #region OUTPUT
|
|
92
|
+
outScroll = output();
|
|
73
93
|
outScrollX = output();
|
|
74
94
|
outScrollY = output();
|
|
75
95
|
outScrollTop = output();
|
|
76
96
|
outScrollBottom = output();
|
|
77
|
-
|
|
97
|
+
// #region INJECT
|
|
78
98
|
element = inject(ElementRef);
|
|
79
99
|
render2 = inject(Renderer2);
|
|
80
100
|
constructor() {
|
|
81
101
|
effect(() => {
|
|
102
|
+
if (this.ignoreInit()) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
82
105
|
const options = this.options();
|
|
83
106
|
this.divContainer.className = '';
|
|
84
107
|
this.classContainer()?.split(' ').forEach(className => {
|
|
@@ -88,30 +111,33 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
88
111
|
this.divContainer.classList.add(className);
|
|
89
112
|
});
|
|
90
113
|
untracked(() => {
|
|
114
|
+
this.Element.classList.toggle('overflow-hidden', options?.scrollX === 'hidden' && options?.scrollY === 'hidden');
|
|
91
115
|
if (options?.scrollX !== 'hidden') {
|
|
92
|
-
this.subsX
|
|
116
|
+
this.subsX.unsubscribe();
|
|
117
|
+
this.trackX.className = '';
|
|
118
|
+
this.thumbX.className = '';
|
|
119
|
+
this.trackX.style.height = `${this.scrollbarWidth()}px`;
|
|
93
120
|
this.createScrollbar('X', this.trackX, this.thumbX);
|
|
94
121
|
this.bindEventsScrollBar('X', this.trackX);
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
this.mutationObserverX.set(new MutationObserver(() => this.mutationObserverSubjectX.next()));
|
|
98
|
-
this.mutationObserverX()?.observe(this.Element, { attributes: true, childList: true, subtree: true });
|
|
122
|
+
this.handlerDragAndDropThumb('X');
|
|
123
|
+
this.handlerClickTrack('X');
|
|
99
124
|
}
|
|
100
125
|
if (options?.scrollY !== 'hidden') {
|
|
101
|
-
this.
|
|
126
|
+
this.trackY.className = '';
|
|
127
|
+
this.thumbY.className = '';
|
|
128
|
+
this.trackY.style.width = `${this.scrollbarWidth()}px`;
|
|
129
|
+
this.subsY.unsubscribe();
|
|
102
130
|
this.createScrollbar('Y', this.trackY, this.thumbY);
|
|
103
131
|
this.bindEventsScrollBar('Y', this.trackY);
|
|
104
|
-
this.
|
|
105
|
-
this.
|
|
106
|
-
this.mutationObserverY.set(new MutationObserver(() => this.mutationObserverSubjectY.next()));
|
|
107
|
-
this.mutationObserverY()?.observe(this.Element, { attributes: true, childList: true, subtree: true });
|
|
132
|
+
this.handlerDragAndDropThumb('Y');
|
|
133
|
+
this.handlerClickTrack('Y');
|
|
108
134
|
}
|
|
109
135
|
});
|
|
110
136
|
});
|
|
111
137
|
}
|
|
112
|
-
|
|
138
|
+
// #region FUNCTIONS
|
|
113
139
|
get Element() {
|
|
114
|
-
return this.element.nativeElement;
|
|
140
|
+
return this.elementScroll() || this.element.nativeElement;
|
|
115
141
|
}
|
|
116
142
|
createScrollbar(scrollDirection, trackEl, thumbEl) {
|
|
117
143
|
const idStyleTag = "#id-style-tag-custom-scroll-overlay";
|
|
@@ -133,28 +159,27 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
133
159
|
Object.keys(stylesProperty).forEach(key => {
|
|
134
160
|
this.render2.setStyle(this.Element, key, stylesProperty[key], 1);
|
|
135
161
|
});
|
|
162
|
+
trackEl.classList.add(`scrollbar-track`);
|
|
136
163
|
trackEl.classList.add(`scrollbar-track-${scrollDirection}`);
|
|
137
|
-
|
|
138
|
-
trackEl.style.width = `100%`;
|
|
139
|
-
trackEl.style.height = `${this.scrollbarWidth()}px`;
|
|
140
|
-
}
|
|
141
|
-
if (scrollDirection === 'Y') {
|
|
142
|
-
trackEl.style.height = `100%`;
|
|
143
|
-
trackEl.style.width = `${this.scrollbarWidth()}px`;
|
|
144
|
-
}
|
|
164
|
+
thumbEl.classList.add(`scrollbar-thumb`);
|
|
145
165
|
thumbEl.classList.add(`scrollbar-thumb-${scrollDirection}`);
|
|
146
|
-
thumbEl.style.backgroundColor = this.scrollbarColor();
|
|
147
166
|
trackEl.appendChild(thumbEl);
|
|
148
167
|
if (this.Element.className) {
|
|
149
168
|
this.Element.className.split(' ').forEach((className) => {
|
|
150
|
-
if (className && (['w-full', 'w-screen', 'h-full', 'h-screen'].includes(className) || /^(h|w)-\[[0-9]+px\]$/.test(className)) && !this.divContainer.classList.contains(className)) {
|
|
169
|
+
if (className && (['w-full', 'w-screen', 'h-full', 'h-screen', 'shrink-0'].includes(className) || className.includes('min-h-') || className.includes('min-w-') || /^(!?)(h|w)-\[[0-9]+px\]$/.test(className)) && !this.divContainer.classList.contains(className)) {
|
|
151
170
|
this.divContainer.classList.add(className);
|
|
152
171
|
}
|
|
153
172
|
});
|
|
173
|
+
if (!this.Element.className.includes('min-h-')) {
|
|
174
|
+
this.divContainer.classList.add('min-h-0');
|
|
175
|
+
}
|
|
176
|
+
if (!this.Element.className.includes('min-w-')) {
|
|
177
|
+
this.divContainer.classList.add('min-w-0');
|
|
178
|
+
}
|
|
154
179
|
}
|
|
155
180
|
this.divContainer.appendChild(trackEl);
|
|
156
181
|
if (!this.divContainer.style.position) {
|
|
157
|
-
this.Element.parentElement
|
|
182
|
+
this.Element.parentElement?.insertBefore(this.divContainer, this.Element);
|
|
158
183
|
this.divContainer.style.position = 'relative';
|
|
159
184
|
}
|
|
160
185
|
this.divContainer.append(this.Element);
|
|
@@ -165,6 +190,7 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
165
190
|
let scrollTop = this.Element.scrollTop;
|
|
166
191
|
const subs = fromEvent(this.Element, 'scroll').pipe(tap((event) => {
|
|
167
192
|
const target = this.Element;
|
|
193
|
+
this.outScroll.emit(event);
|
|
168
194
|
if (scrollDirection === 'X') {
|
|
169
195
|
if (target.scrollLeft && (target.scrollLeft + target.offsetWidth >= target.scrollWidth)) {
|
|
170
196
|
target.scrollLeft = target.scrollWidth - target.offsetWidth - (target.offsetWidth - target.clientWidth);
|
|
@@ -189,13 +215,20 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
189
215
|
return this.outScrollBottom.emit(event);
|
|
190
216
|
}
|
|
191
217
|
}), takeUntil(this.onDestroy)).subscribe();
|
|
192
|
-
subs.add(fromEvent(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
subs.add(fromEvent(document, 'resize').pipe(tap(this.updateScrollbarSize.bind(this, scrollDirection)), takeUntil(this.onDestroy)).subscribe());
|
|
219
|
+
const mouseLeave = fromEvent(this.divContainer, 'mouseleave');
|
|
220
|
+
const mouseenter = fromEvent(this.divContainer, 'mouseenter');
|
|
221
|
+
subs.add(mouseenter.pipe(tap(() => {
|
|
222
|
+
if (scrollDirection === 'X' && !this.options()?.scrollXOpacity0 || scrollDirection === 'Y' && !this.options()?.scrollYOpacity0) {
|
|
223
|
+
trackEl.style.visibility = 'visible';
|
|
224
|
+
trackEl.style.opacity = '1';
|
|
225
|
+
}
|
|
196
226
|
this.updateScrollbarSize(scrollDirection);
|
|
197
|
-
}), takeUntil(this.onDestroy)).subscribe());
|
|
198
|
-
subs.add(
|
|
227
|
+
}), switchMap(() => interval(1000).pipe(takeUntil(mouseLeave))), tap(this.updateScrollbarSize.bind(this, scrollDirection)), takeUntil(this.onDestroy)).subscribe());
|
|
228
|
+
subs.add(mouseLeave.pipe(tap(() => {
|
|
229
|
+
if (this.keepDisplayThumb()) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
199
232
|
trackEl.style.visibility = 'hidden';
|
|
200
233
|
trackEl.style.opacity = '0';
|
|
201
234
|
}), takeUntil(this.onDestroy)).subscribe());
|
|
@@ -208,12 +241,79 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
208
241
|
return;
|
|
209
242
|
}
|
|
210
243
|
}
|
|
244
|
+
handlerClickTrack(scrollDirection) {
|
|
245
|
+
const elementTrack = scrollDirection === 'X' ? this.trackX : this.trackY;
|
|
246
|
+
const elementThumb = scrollDirection === 'X' ? this.thumbX : this.thumbY;
|
|
247
|
+
const subs = scrollDirection === 'X' ? this.subsX : this.subsY;
|
|
248
|
+
subs.add(fromEvent(elementTrack, 'click').subscribe(e => {
|
|
249
|
+
if (this.isScrollThumb()) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if ((scrollDirection === 'X' && e.clientX < elementThumb.getBoundingClientRect().left) || (scrollDirection === 'Y' && e.clientY < elementThumb.getBoundingClientRect().top)) {
|
|
253
|
+
this.updateScrollPositionByUserAction(scrollDirection, e, 'smooth', 0);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (scrollDirection === 'X') {
|
|
257
|
+
this.updateScrollPositionByUserAction(scrollDirection, e, 'smooth', -1 * elementThumb.getBoundingClientRect().width);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this.updateScrollPositionByUserAction(scrollDirection, e, 'smooth', -1 * elementThumb.getBoundingClientRect().height);
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
handlerDragAndDropThumb(scrollDirection) {
|
|
264
|
+
const elementTrack = scrollDirection === 'X' ? this.trackX : this.trackY;
|
|
265
|
+
const elementThumb = scrollDirection === 'X' ? this.thumbX : this.thumbY;
|
|
266
|
+
const subs = scrollDirection === 'X' ? this.subsX : this.subsY;
|
|
267
|
+
let lengthThumbToPointClick = 0;
|
|
268
|
+
subs.add(getDragEventByElement({
|
|
269
|
+
elementMouseDown: elementThumb,
|
|
270
|
+
functionMouseDown: (mouseEvent) => {
|
|
271
|
+
this.isScrollThumb.set(true);
|
|
272
|
+
this.keepDisplayThumb.set(true);
|
|
273
|
+
if (scrollDirection === 'X') {
|
|
274
|
+
lengthThumbToPointClick = elementThumb.getBoundingClientRect().left - mouseEvent.clientX;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
lengthThumbToPointClick = elementThumb.getBoundingClientRect().top - mouseEvent.clientY;
|
|
278
|
+
},
|
|
279
|
+
functionMouseUp: (mouseEvent) => {
|
|
280
|
+
this.keepDisplayThumb.set(false);
|
|
281
|
+
lengthThumbToPointClick = 0;
|
|
282
|
+
if (!checkMouseOverInContainer(mouseEvent, this.Element)) {
|
|
283
|
+
elementTrack.style.visibility = 'hidden';
|
|
284
|
+
elementTrack.style.opacity = '0';
|
|
285
|
+
}
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
this.isScrollThumb.set(false);
|
|
288
|
+
}, 250);
|
|
289
|
+
},
|
|
290
|
+
onDestroy: this.onDestroy
|
|
291
|
+
}).subscribe((mouseEvent) => {
|
|
292
|
+
this.updateScrollPositionByUserAction(scrollDirection, mouseEvent, 'auto', lengthThumbToPointClick);
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
updateScrollPositionByUserAction(scrollDirection, e, behavior, lengthThumbToPointClick = 0) {
|
|
296
|
+
e.stopPropagation();
|
|
297
|
+
if (scrollDirection === 'X') {
|
|
298
|
+
const containerWidth = this.Element.offsetWidth;
|
|
299
|
+
const contentWidth = (this.elementCheckScrollX() || this.Element).scrollWidth;
|
|
300
|
+
const thumbPosition = e.clientX - this.Element.getBoundingClientRect().left + lengthThumbToPointClick;
|
|
301
|
+
const scrollLeft = (thumbPosition / (containerWidth - this.thumbX.offsetWidth)) * (contentWidth - containerWidth);
|
|
302
|
+
this.Element.scroll({ left: scrollLeft, behavior });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const containerHeight = this.Element.offsetHeight;
|
|
306
|
+
const contentHeight = (this.elementCheckScrollY() || this.Element).scrollHeight;
|
|
307
|
+
const thumbPosition = e.clientY - this.Element.getBoundingClientRect().top + lengthThumbToPointClick;
|
|
308
|
+
const scrollTop = (thumbPosition / (containerHeight - this.thumbY.offsetHeight)) * (contentHeight - containerHeight);
|
|
309
|
+
this.Element.scroll({ top: scrollTop, behavior });
|
|
310
|
+
}
|
|
211
311
|
updateScrollbarSize(scrollDirection) {
|
|
212
312
|
if (scrollDirection === 'X') {
|
|
213
313
|
const containerWidth = this.Element.offsetWidth;
|
|
214
|
-
const contentWidth = this.Element.scrollWidth;
|
|
314
|
+
const contentWidth = (this.elementCheckScrollX() || this.Element).scrollWidth;
|
|
215
315
|
const thumbWidth = (containerWidth / contentWidth) * (containerWidth);
|
|
216
|
-
this.thumbX.style.width = `${thumbWidth}px`;
|
|
316
|
+
this.thumbX.style.width = `${Math.max(20, thumbWidth)}px`;
|
|
217
317
|
this.trackX.style.display = 'none';
|
|
218
318
|
if (contentWidth > containerWidth) {
|
|
219
319
|
this.trackX.style.display = 'block';
|
|
@@ -222,9 +322,9 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
222
322
|
return;
|
|
223
323
|
}
|
|
224
324
|
const containerHeight = this.Element.offsetHeight;
|
|
225
|
-
const contentHeight = this.Element.scrollHeight;
|
|
325
|
+
const contentHeight = (this.elementCheckScrollY() || this.Element).scrollHeight;
|
|
226
326
|
const thumbHeight = (containerHeight / contentHeight) * (containerHeight);
|
|
227
|
-
this.thumbY.style.height = `${thumbHeight}px`;
|
|
327
|
+
this.thumbY.style.height = `${Math.max(20, thumbHeight)}px`;
|
|
228
328
|
this.trackY.style.display = 'none';
|
|
229
329
|
if (contentHeight > containerHeight) {
|
|
230
330
|
this.trackY.style.display = 'block';
|
|
@@ -234,26 +334,27 @@ class LibsUiComponentsScrollOverlayDirective {
|
|
|
234
334
|
updateScrollbarPosition(scrollDirection) {
|
|
235
335
|
if (scrollDirection === 'X') {
|
|
236
336
|
const containerWidth = this.Element.offsetWidth;
|
|
237
|
-
const contentWidth = this.Element.scrollWidth;
|
|
337
|
+
const contentWidth = (this.elementCheckScrollX() || this.Element).scrollWidth;
|
|
238
338
|
const scrollLeft = this.Element.scrollLeft;
|
|
239
339
|
const thumbPosition = (scrollLeft / (contentWidth - containerWidth)) * (containerWidth - this.thumbX.offsetWidth);
|
|
240
340
|
this.thumbX.style.left = `${thumbPosition}px`;
|
|
241
341
|
return;
|
|
242
342
|
}
|
|
243
343
|
const containerHeight = this.Element.offsetHeight;
|
|
244
|
-
const contentHeight = this.Element.scrollHeight;
|
|
344
|
+
const contentHeight = (this.elementCheckScrollY() || this.Element).scrollHeight;
|
|
245
345
|
const scrollTop = this.Element.scrollTop;
|
|
246
346
|
const thumbPosition = (scrollTop / (contentHeight - containerHeight)) * (containerHeight - this.thumbY.offsetHeight);
|
|
247
347
|
this.thumbY.style.top = `${thumbPosition}px`;
|
|
248
348
|
}
|
|
249
349
|
ngOnDestroy() {
|
|
250
|
-
this.
|
|
251
|
-
this.
|
|
350
|
+
this.divContainer.remove();
|
|
351
|
+
this.subsX.unsubscribe();
|
|
352
|
+
this.subsY.unsubscribe();
|
|
252
353
|
this.onDestroy.next();
|
|
253
354
|
this.onDestroy.complete();
|
|
254
355
|
}
|
|
255
356
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LibsUiComponentsScrollOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
256
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.13", type: LibsUiComponentsScrollOverlayDirective, isStandalone: true, selector: "[LibsUiComponentsScrollOverlayDirective]", inputs: { classContainer: { classPropertyName: "classContainer", publicName: "classContainer", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outScrollX: "outScrollX", outScrollY: "outScrollY", outScrollTop: "outScrollTop", outScrollBottom: "outScrollBottom" }, ngImport: i0 });
|
|
357
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.13", type: LibsUiComponentsScrollOverlayDirective, isStandalone: true, selector: "[LibsUiComponentsScrollOverlayDirective]", inputs: { debugMode: { classPropertyName: "debugMode", publicName: "debugMode", isSignal: true, isRequired: false, transformFunction: null }, ignoreInit: { classPropertyName: "ignoreInit", publicName: "ignoreInit", isSignal: true, isRequired: false, transformFunction: null }, classContainer: { classPropertyName: "classContainer", publicName: "classContainer", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, elementCheckScrollX: { classPropertyName: "elementCheckScrollX", publicName: "elementCheckScrollX", isSignal: true, isRequired: false, transformFunction: null }, elementCheckScrollY: { classPropertyName: "elementCheckScrollY", publicName: "elementCheckScrollY", isSignal: true, isRequired: false, transformFunction: null }, elementScroll: { classPropertyName: "elementScroll", publicName: "elementScroll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outScroll: "outScroll", outScrollX: "outScrollX", outScrollY: "outScrollY", outScrollTop: "outScrollTop", outScrollBottom: "outScrollBottom" }, ngImport: i0 });
|
|
257
358
|
}
|
|
258
359
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LibsUiComponentsScrollOverlayDirective, decorators: [{
|
|
259
360
|
type: Directive,
|
|
@@ -266,9 +367,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
266
367
|
|
|
267
368
|
;
|
|
268
369
|
|
|
370
|
+
class LibsUiComponentsScrollOverlayDemoComponent {
|
|
371
|
+
// Demo text content: generate 5000 words for vertical scrolling
|
|
372
|
+
longContent = signal(Array.from({ length: 5000 }, (_, i) => `Lorem${i + 1}`).join(' '));
|
|
373
|
+
// Demo horizontal content: generate 5000 words for horizontal scrolling
|
|
374
|
+
longHorizontalContent = signal(Array.from({ length: 5000 }, (_, i) => `Word${i + 1}`).join(' '));
|
|
375
|
+
// Scenario selection
|
|
376
|
+
scenarioOptions = ['default', 'customStyle', 'autoHide', 'horizontal'];
|
|
377
|
+
selectedScenario = 'default';
|
|
378
|
+
// Options signals
|
|
379
|
+
customStyleOptions = signal({ scrollbarWidth: 12, scrollbarColor: '#f3f4f6', scrollbarHoverColor: '#e5e7eb', scrollThumbColor: '#3b82f6', scrollThumbHoverColor: '#2563eb', scrollbarPadding: 4 });
|
|
380
|
+
autoHideOptions = signal({ scrollYOpacity0: true, scrollbarWidth: 8, scrollbarColor: 'transparent', scrollThumbColor: '#9ca3af', scrollThumbHoverColor: '#6b7280' });
|
|
381
|
+
horizontalOptions = signal({ scrollX: 'scroll', scrollY: 'hidden', scrollbarWidth: 8, scrollbarColor: '#f3f4f6', scrollThumbColor: '#3b82f6' });
|
|
382
|
+
// Scroll event log
|
|
383
|
+
lastEvent = signal('No events yet');
|
|
384
|
+
// Helpers
|
|
385
|
+
get options() {
|
|
386
|
+
switch (this.selectedScenario) {
|
|
387
|
+
case 'customStyle': return this.customStyleOptions();
|
|
388
|
+
case 'autoHide': return this.autoHideOptions();
|
|
389
|
+
case 'horizontal': return this.horizontalOptions();
|
|
390
|
+
default: return undefined;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
get content() {
|
|
394
|
+
return this.selectedScenario === 'horizontal'
|
|
395
|
+
? this.longHorizontalContent()
|
|
396
|
+
: this.longContent();
|
|
397
|
+
}
|
|
398
|
+
// Event handlers
|
|
399
|
+
onScroll() { this.lastEvent.set('Scroll event fired'); }
|
|
400
|
+
onScrollTop() { this.lastEvent.set('Reached top'); }
|
|
401
|
+
onScrollBottom() { this.lastEvent.set('Reached bottom'); }
|
|
402
|
+
// API docs
|
|
403
|
+
inputsDoc = [
|
|
404
|
+
{ name: 'options', type: 'IScrollOverlayOptions', default: 'undefined', description: 'Cấu hình tuỳ chỉnh scrollbar' },
|
|
405
|
+
{ name: 'debugMode', type: 'boolean', default: 'false', description: 'Bật chế độ debug' },
|
|
406
|
+
{ name: 'notShowScrollBarX', type: 'boolean', default: 'false', description: 'Ẩn scrollbar ngang' },
|
|
407
|
+
{ name: 'notShowScrollBarY', type: 'boolean', default: 'false', description: 'Ẩn scrollbar dọc' }
|
|
408
|
+
];
|
|
409
|
+
outputsDoc = [
|
|
410
|
+
{ name: 'outScroll', type: 'Event', description: 'Bắn ra khi scroll bất kỳ' },
|
|
411
|
+
{ name: 'outScrollTop', type: 'Event', description: 'Bắn ra khi scroll đến top' },
|
|
412
|
+
{ name: 'outScrollBottom', type: 'Event', description: 'Bắn ra khi scroll đến bottom' }
|
|
413
|
+
];
|
|
414
|
+
optionsDoc = [
|
|
415
|
+
{ name: 'scrollbarWidth', type: 'number', default: 'undefined', description: 'Chiều rộng scrollbar (px)' },
|
|
416
|
+
{ name: 'scrollbarColor', type: 'string', default: 'undefined', description: 'Màu track scrollbar' },
|
|
417
|
+
{ name: 'scrollbarHoverColor', type: 'string', default: 'undefined', description: 'Màu track khi hover' },
|
|
418
|
+
{ name: 'scrollThumbColor', type: 'string', default: 'undefined', description: 'Màu thumb' },
|
|
419
|
+
{ name: 'scrollThumbHoverColor', type: 'string', default: 'undefined', description: 'Màu thumb khi hover' },
|
|
420
|
+
{ name: 'scrollbarPadding', type: 'number', default: 'undefined', description: 'Padding scrollbar' },
|
|
421
|
+
{ name: 'scrollX', type: `'hidden' | 'scroll'`, default: 'undefined', description: 'Kiểu scroll X' },
|
|
422
|
+
{ name: 'scrollXOpacity0', type: 'boolean', default: 'undefined', description: 'Ẩn track X khi không hover' },
|
|
423
|
+
{ name: 'scrollY', type: `'hidden' | 'scroll'`, default: 'undefined', description: 'Kiểu scroll Y' },
|
|
424
|
+
{ name: 'scrollYOpacity0', type: 'boolean', default: 'undefined', description: 'Ẩn track Y khi không hover' }
|
|
425
|
+
];
|
|
426
|
+
interfacesDoc = [
|
|
427
|
+
{ name: 'IScrollOverlayOptions', type: 'interface', description: 'Các tuỳ chọn cấu hình scroll-overlay' }
|
|
428
|
+
];
|
|
429
|
+
// Installation commands
|
|
430
|
+
installCommandNpm = 'npm install @libs-ui/components-scroll-overlay';
|
|
431
|
+
installCommandYarn = 'yarn add @libs-ui/components-scroll-overlay';
|
|
432
|
+
copyToClipboard(text) {
|
|
433
|
+
navigator.clipboard.writeText(text).then(() => console.log('Copied:', text));
|
|
434
|
+
}
|
|
435
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LibsUiComponentsScrollOverlayDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
436
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: LibsUiComponentsScrollOverlayDemoComponent, isStandalone: true, selector: "lib-scroll-overlay-demo", ngImport: i0, template: "<div class=\"max-w-6xl mx-auto p-5 font-sans text-gray-800\">\n <header class=\"text-center py-10 bg-white rounded-lg mb-8 shadow-sm\">\n <h1 class=\"text-4xl font-bold mb-2\">Demo Scroll Overlay</h1>\n <p class=\"text-xl text-gray-500\">@libs-ui/components-scroll-overlay</p>\n </header>\n\n <main>\n <!-- Gi\u1EDBi thi\u1EC7u -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">Gi\u1EDBi thi\u1EC7u</h2>\n <p><code>scroll-overlay</code> l\u00E0 m\u1ED9t directive gi\u00FAp tu\u1EF3 bi\u1EBFn scrollbar tr\u00EAn b\u1EA5t k\u1EF3 ph\u1EA7n t\u1EED n\u00E0o trong Angular, h\u1ED7 tr\u1EE3 tu\u1EF3 ch\u1EC9nh m\u00E0u, k\u00EDch th\u01B0\u1EDBc, \u1EA9n/hi\u1EC7n v\u00E0 s\u1EF1 ki\u1EC7n scroll.</p>\n </section>\n\n <!-- C\u00E0i \u0111\u1EB7t -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">C\u00E0i \u0111\u1EB7t</h2>\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-4\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>{{ installCommandNpm }}</code></pre>\n <button class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600\" (click)=\"copyToClipboard(installCommandNpm)\">Sao ch\u00E9p</button>\n </div>\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>{{ installCommandYarn }}</code></pre>\n <button class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600\" (click)=\"copyToClipboard(installCommandYarn)\">Sao ch\u00E9p</button>\n </div>\n </section>\n\n <!-- Demo Tr\u1EF1c ti\u1EBFp -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">Demo Tr\u1EF1c ti\u1EBFp</h2>\n <div class=\"grid grid-cols-4 gap-4 mb-6\">\n <button *ngFor=\"let sc of scenarioOptions\"\n (click)=\"selectedScenario = sc\"\n class=\"px-3 py-1 border rounded\"\n [class.bg-blue-500]=\"selectedScenario===sc\"\n [class.text-white]=\"selectedScenario===sc\">\n {{ sc }}\n </button>\n </div>\n <div class=\"p-4 bg-gray-50 rounded-lg h-[300px]\">\n <div LibsUiComponentsScrollOverlayDirective\n [options]=\"options\"\n (outScroll)=\"onScroll()\"\n (outScrollTop)=\"onScrollTop()\"\n (outScrollBottom)=\"onScrollBottom()\"\n class=\"w-full h-full\">\n <div class=\"p-2\"\n [innerText]=\"content\"\n [class.whitespace-pre-wrap]=\"selectedScenario!=='horizontal'\"\n [class.whitespace-nowrap]=\"selectedScenario==='horizontal'\"></div>\n </div>\n </div>\n <p class=\"mt-4 text-gray-700\">S\u1EF1 ki\u1EC7n: {{ lastEvent() }}</p>\n </section>\n\n <!-- C\u00E1ch s\u1EED d\u1EE5ng -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">C\u00E1ch s\u1EED d\u1EE5ng</h2>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-auto text-sm\">\n <code ngNonBindable><div LibsUiComponentsScrollOverlayDirective [options]=\"{ scrollbarWidth:12, scrollThumbColor:'#3b82f6' }\" style=\"width:300px;height:200px;overflow:auto;\">\n N\u1ED9i dung d\u00E0i...\n</div></code>\n </pre>\n </section>\n\n <!-- API Reference -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">API Reference</h2>\n <h3 class=\"text-xl font-semibold mb-3\">Inputs</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">T\u00EAn</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let input of inputsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ input.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ input.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ input.default }}</td>\n <td class=\"py-2 px-4 border-b\">{{ input.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Outputs</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">T\u00EAn</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let output of outputsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ output.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ output.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ output.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Options</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Thu\u1ED9c t\u00EDnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let opt of optionsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ opt.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ opt.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ opt.default }}</td>\n <td class=\"py-2 px-4 border-b\">{{ opt.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Interfaces</h3>\n <div class=\"space-y-6\">\n <div *ngFor=\"let intf of interfacesDoc\" class=\"bg-gray-50 p-6 rounded-lg\">\n <h4 class=\"font-semibold mb-2\">{{ intf.name }}</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-auto text-sm mb-3\"><code>{{ intf.type }}</code></pre>\n <p>{{ intf.description }}</p>\n </div>\n </div>\n </section>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.demo-container{padding:24px;font-family:system-ui,-apple-system,sans-serif;max-width:1200px;margin:0 auto}.demo-intro{margin-bottom:32px}.demo-intro h3{color:#333;margin-bottom:16px}.demo-intro ul{list-style:none;padding:0}.demo-intro ul li{margin-bottom:8px;padding-left:20px;position:relative}.demo-intro ul li:before{content:\"\\2022\";position:absolute;left:0;color:#3b82f6}.demo-features{margin-bottom:32px}.demo-features h3{color:#333;margin-bottom:16px}.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.feature-item{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0}.feature-item h4{color:#1e40af;margin:0 0 8px}.feature-item p{margin:0;color:#64748b}.demo-api{margin-bottom:48px}.demo-api h3{color:#333;margin-bottom:24px}.api-section{margin-bottom:32px}.api-section h4{color:#1e40af;margin-bottom:16px}.api-table{overflow-x:auto}.api-table table{width:100%;border-collapse:collapse;background:#fff;border-radius:8px;border:1px solid #e2e8f0}.api-table table th,.api-table table td{padding:12px 16px;text-align:left;border-bottom:1px solid #e2e8f0}.api-table table th{background:#f8fafc;font-weight:600;color:#1e40af}.api-table table td{color:#64748b}.api-table table td:first-child{font-family:Fira Code,monospace;color:#334155}.api-table table td:nth-child(2){font-family:Fira Code,monospace;color:#3b82f6}.api-table table tr:last-child td{border-bottom:none}.demo-section{margin-bottom:48px}.demo-section h3{color:#333;margin-bottom:16px}.demo-description{margin-bottom:16px}.demo-description p{color:#64748b;margin-bottom:12px}.demo-description pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.demo-description pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155}.demo-box{display:flex;gap:16px;align-items:flex-start}.event-log{padding:16px;background:#f5f5f5;border-radius:4px;min-width:200px}.event-log p{margin:0;font-size:14px;color:#666}.demo-notes{margin-top:48px;padding:24px;background:#f8fafc;border-radius:8px;border:1px solid #e2e8f0}.demo-notes h3{color:#333;margin-bottom:16px}.demo-notes ul{list-style:none;padding:0;margin:0}.demo-notes ul li{color:#64748b;margin-bottom:12px;padding-left:24px;position:relative}.demo-notes ul li:before{content:\"\\2192\";position:absolute;left:0;color:#3b82f6}.demo-notes ul li:last-child{margin-bottom:0}.interface-description{margin-bottom:24px}.interface-description p{color:#64748b;margin-bottom:16px}.interface-description pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.interface-description pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155;line-height:1.6}.interface-usage{margin-bottom:24px}.interface-usage h5{color:#1e40af;margin-bottom:12px;font-size:16px}.interface-usage pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.interface-usage pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155;line-height:1.6}.interface-usage pre code .comment{color:#64748b}.interface-notes{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0}.interface-notes h5{color:#1e40af;margin-bottom:12px;font-size:16px}.interface-notes ul{list-style:none;padding:0;margin:0}.interface-notes ul li{color:#64748b;margin-bottom:8px;padding-left:20px;position:relative}.interface-notes ul li:before{content:\"\\2022\";position:absolute;left:0;color:#3b82f6}.interface-notes ul li:last-child{margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: LibsUiComponentsScrollOverlayDirective, selector: "[LibsUiComponentsScrollOverlayDirective]", inputs: ["debugMode", "ignoreInit", "classContainer", "options", "elementCheckScrollX", "elementCheckScrollY", "elementScroll"], outputs: ["outScroll", "outScrollX", "outScrollY", "outScrollTop", "outScrollBottom"] }] });
|
|
437
|
+
}
|
|
438
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LibsUiComponentsScrollOverlayDemoComponent, decorators: [{
|
|
439
|
+
type: Component,
|
|
440
|
+
args: [{ selector: 'lib-scroll-overlay-demo', standalone: true, imports: [CommonModule, LibsUiComponentsScrollOverlayDirective], template: "<div class=\"max-w-6xl mx-auto p-5 font-sans text-gray-800\">\n <header class=\"text-center py-10 bg-white rounded-lg mb-8 shadow-sm\">\n <h1 class=\"text-4xl font-bold mb-2\">Demo Scroll Overlay</h1>\n <p class=\"text-xl text-gray-500\">@libs-ui/components-scroll-overlay</p>\n </header>\n\n <main>\n <!-- Gi\u1EDBi thi\u1EC7u -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">Gi\u1EDBi thi\u1EC7u</h2>\n <p><code>scroll-overlay</code> l\u00E0 m\u1ED9t directive gi\u00FAp tu\u1EF3 bi\u1EBFn scrollbar tr\u00EAn b\u1EA5t k\u1EF3 ph\u1EA7n t\u1EED n\u00E0o trong Angular, h\u1ED7 tr\u1EE3 tu\u1EF3 ch\u1EC9nh m\u00E0u, k\u00EDch th\u01B0\u1EDBc, \u1EA9n/hi\u1EC7n v\u00E0 s\u1EF1 ki\u1EC7n scroll.</p>\n </section>\n\n <!-- C\u00E0i \u0111\u1EB7t -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">C\u00E0i \u0111\u1EB7t</h2>\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg mb-4\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>{{ installCommandNpm }}</code></pre>\n <button class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600\" (click)=\"copyToClipboard(installCommandNpm)\">Sao ch\u00E9p</button>\n </div>\n <div class=\"flex items-center bg-gray-100 p-4 rounded-lg\">\n <pre class=\"flex-1 text-sm overflow-x-auto\"><code>{{ installCommandYarn }}</code></pre>\n <button class=\"ml-4 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600\" (click)=\"copyToClipboard(installCommandYarn)\">Sao ch\u00E9p</button>\n </div>\n </section>\n\n <!-- Demo Tr\u1EF1c ti\u1EBFp -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">Demo Tr\u1EF1c ti\u1EBFp</h2>\n <div class=\"grid grid-cols-4 gap-4 mb-6\">\n <button *ngFor=\"let sc of scenarioOptions\"\n (click)=\"selectedScenario = sc\"\n class=\"px-3 py-1 border rounded\"\n [class.bg-blue-500]=\"selectedScenario===sc\"\n [class.text-white]=\"selectedScenario===sc\">\n {{ sc }}\n </button>\n </div>\n <div class=\"p-4 bg-gray-50 rounded-lg h-[300px]\">\n <div LibsUiComponentsScrollOverlayDirective\n [options]=\"options\"\n (outScroll)=\"onScroll()\"\n (outScrollTop)=\"onScrollTop()\"\n (outScrollBottom)=\"onScrollBottom()\"\n class=\"w-full h-full\">\n <div class=\"p-2\"\n [innerText]=\"content\"\n [class.whitespace-pre-wrap]=\"selectedScenario!=='horizontal'\"\n [class.whitespace-nowrap]=\"selectedScenario==='horizontal'\"></div>\n </div>\n </div>\n <p class=\"mt-4 text-gray-700\">S\u1EF1 ki\u1EC7n: {{ lastEvent() }}</p>\n </section>\n\n <!-- C\u00E1ch s\u1EED d\u1EE5ng -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">C\u00E1ch s\u1EED d\u1EE5ng</h2>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-auto text-sm\">\n <code ngNonBindable><div LibsUiComponentsScrollOverlayDirective [options]=\"{ scrollbarWidth:12, scrollThumbColor:'#3b82f6' }\" style=\"width:300px;height:200px;overflow:auto;\">\n N\u1ED9i dung d\u00E0i...\n</div></code>\n </pre>\n </section>\n\n <!-- API Reference -->\n <section class=\"bg-white rounded-lg p-8 mb-8 shadow-sm\">\n <h2 class=\"text-2xl font-bold mb-5 pb-3 border-b border-gray-200\">API Reference</h2>\n <h3 class=\"text-xl font-semibold mb-3\">Inputs</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">T\u00EAn</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let input of inputsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ input.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ input.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ input.default }}</td>\n <td class=\"py-2 px-4 border-b\">{{ input.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Outputs</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">T\u00EAn</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let output of outputsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ output.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ output.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ output.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Options</h3>\n <table class=\"min-w-full bg-white border border-gray-200 mb-6\">\n <thead>\n <tr>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Thu\u1ED9c t\u00EDnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">Ki\u1EC3u</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u1EB7c \u0111\u1ECBnh</th>\n <th class=\"py-2 px-4 border-b bg-gray-100\">M\u00F4 t\u1EA3</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let opt of optionsDoc\">\n <td class=\"py-2 px-4 border-b\"><code>{{ opt.name }}</code></td>\n <td class=\"py-2 px-4 border-b\"><code>{{ opt.type }}</code></td>\n <td class=\"py-2 px-4 border-b\">{{ opt.default }}</td>\n <td class=\"py-2 px-4 border-b\">{{ opt.description }}</td>\n </tr>\n </tbody>\n </table>\n <h3 class=\"text-xl font-semibold mb-3\">Interfaces</h3>\n <div class=\"space-y-6\">\n <div *ngFor=\"let intf of interfacesDoc\" class=\"bg-gray-50 p-6 rounded-lg\">\n <h4 class=\"font-semibold mb-2\">{{ intf.name }}</h4>\n <pre class=\"bg-gray-100 p-4 rounded-lg overflow-auto text-sm mb-3\"><code>{{ intf.type }}</code></pre>\n <p>{{ intf.description }}</p>\n </div>\n </div>\n </section>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.demo-container{padding:24px;font-family:system-ui,-apple-system,sans-serif;max-width:1200px;margin:0 auto}.demo-intro{margin-bottom:32px}.demo-intro h3{color:#333;margin-bottom:16px}.demo-intro ul{list-style:none;padding:0}.demo-intro ul li{margin-bottom:8px;padding-left:20px;position:relative}.demo-intro ul li:before{content:\"\\2022\";position:absolute;left:0;color:#3b82f6}.demo-features{margin-bottom:32px}.demo-features h3{color:#333;margin-bottom:16px}.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.feature-item{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0}.feature-item h4{color:#1e40af;margin:0 0 8px}.feature-item p{margin:0;color:#64748b}.demo-api{margin-bottom:48px}.demo-api h3{color:#333;margin-bottom:24px}.api-section{margin-bottom:32px}.api-section h4{color:#1e40af;margin-bottom:16px}.api-table{overflow-x:auto}.api-table table{width:100%;border-collapse:collapse;background:#fff;border-radius:8px;border:1px solid #e2e8f0}.api-table table th,.api-table table td{padding:12px 16px;text-align:left;border-bottom:1px solid #e2e8f0}.api-table table th{background:#f8fafc;font-weight:600;color:#1e40af}.api-table table td{color:#64748b}.api-table table td:first-child{font-family:Fira Code,monospace;color:#334155}.api-table table td:nth-child(2){font-family:Fira Code,monospace;color:#3b82f6}.api-table table tr:last-child td{border-bottom:none}.demo-section{margin-bottom:48px}.demo-section h3{color:#333;margin-bottom:16px}.demo-description{margin-bottom:16px}.demo-description p{color:#64748b;margin-bottom:12px}.demo-description pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.demo-description pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155}.demo-box{display:flex;gap:16px;align-items:flex-start}.event-log{padding:16px;background:#f5f5f5;border-radius:4px;min-width:200px}.event-log p{margin:0;font-size:14px;color:#666}.demo-notes{margin-top:48px;padding:24px;background:#f8fafc;border-radius:8px;border:1px solid #e2e8f0}.demo-notes h3{color:#333;margin-bottom:16px}.demo-notes ul{list-style:none;padding:0;margin:0}.demo-notes ul li{color:#64748b;margin-bottom:12px;padding-left:24px;position:relative}.demo-notes ul li:before{content:\"\\2192\";position:absolute;left:0;color:#3b82f6}.demo-notes ul li:last-child{margin-bottom:0}.interface-description{margin-bottom:24px}.interface-description p{color:#64748b;margin-bottom:16px}.interface-description pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.interface-description pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155;line-height:1.6}.interface-usage{margin-bottom:24px}.interface-usage h5{color:#1e40af;margin-bottom:12px;font-size:16px}.interface-usage pre{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;overflow-x:auto}.interface-usage pre code{font-family:Fira Code,monospace;font-size:14px;color:#334155;line-height:1.6}.interface-usage pre code .comment{color:#64748b}.interface-notes{background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0}.interface-notes h5{color:#1e40af;margin-bottom:12px;font-size:16px}.interface-notes ul{list-style:none;padding:0;margin:0}.interface-notes ul li{color:#64748b;margin-bottom:8px;padding-left:20px;position:relative}.interface-notes ul li:before{content:\"\\2022\";position:absolute;left:0;color:#3b82f6}.interface-notes ul li:last-child{margin-bottom:0}\n"] }]
|
|
441
|
+
}] });
|
|
442
|
+
|
|
269
443
|
/**
|
|
270
444
|
* Generated bundle index. Do not edit.
|
|
271
445
|
*/
|
|
272
446
|
|
|
273
|
-
export { LibsUiComponentsScrollOverlayDirective };
|
|
447
|
+
export { LibsUiComponentsScrollOverlayDemoComponent, LibsUiComponentsScrollOverlayDirective };
|
|
274
448
|
//# sourceMappingURL=libs-ui-components-scroll-overlay.mjs.map
|