@v1nt1248/3nclient-lib 0.3.36 → 0.3.38

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.
@@ -0,0 +1,697 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
3
+ import type {
4
+ Ui3nScrollbarProps,
5
+ Ui3nScrollbarEmits,
6
+ Ui3nScrollbarSlots,
7
+ Ui3nScrollbarExpose,
8
+ Ui3nScrollbarVerticalOptions,
9
+ Ui3nScrollbarHorizontalOptions,
10
+ Ui3nScrollbarAxes,
11
+ Ui3nScrollbarScrollPayload,
12
+ } from './types';
13
+
14
+ const props = withDefaults(defineProps<Ui3nScrollbarProps>(), {
15
+ axes: 'both',
16
+ vertical: () => ({
17
+ thumbMinHeight: 32,
18
+ thumbHeight: 'auto',
19
+ thumbRadius: 4,
20
+ thumbColor: 'var(--color-bg-control-accent-default)',
21
+ thumbHoverColor: 'var(--color-bg-control-accent-hover)',
22
+ thumbActiveColor: 'var(--color-bg-control-accent-focused)',
23
+ trackWidth: 6,
24
+ trackRadius: 4,
25
+ trackColor: 'transparent',
26
+ }),
27
+ horizontal: () => ({
28
+ thumbMinWidth: 32,
29
+ thumbWidth: 'auto',
30
+ thumbRadius: 4,
31
+ thumbColor: 'var(--color-bg-control-accent-default)',
32
+ thumbHoverColor: 'var(--color-bg-control-accent-hover)',
33
+ thumbActiveColor: 'var(--color-bg-control-accent-focused)',
34
+ trackHeight: 6,
35
+ trackRadius: 4,
36
+ trackColor: 'transparent',
37
+ }),
38
+ autoUpdate: true,
39
+ });
40
+ const emits = defineEmits<Ui3nScrollbarEmits>();
41
+ defineSlots<Ui3nScrollbarSlots>();
42
+
43
+ const containerRef = ref<HTMLDivElement | null>(null);
44
+ const trackRefV = ref<HTMLDivElement | null>(null);
45
+ const trackRefH = ref<HTMLDivElement | null>(null);
46
+ const thumbRefV = ref<HTMLDivElement | null>(null);
47
+ const thumbRefH = ref<HTMLDivElement | null>(null);
48
+
49
+ const axes = computed<Ui3nScrollbarAxes>(() => props.axes ?? 'both');
50
+ const vOpts = computed<Ui3nScrollbarVerticalOptions>(() => props.vertical ?? {});
51
+ const hOpts = computed<Ui3nScrollbarHorizontalOptions>(() => props.horizontal ?? {});
52
+
53
+ const scrollHeight = ref(0);
54
+ const clientHeight = ref(0);
55
+ const scrollTop = ref(0);
56
+ const scrollWidth = ref(0);
57
+ const clientWidth = ref(0);
58
+ const scrollLeft = ref(0);
59
+ const prevScrollTop = ref(0);
60
+ const prevScrollLeft = ref(0);
61
+
62
+ const isDraggingV = ref(false);
63
+ const isDraggingH = ref(false);
64
+ const isHovered = ref(false);
65
+ const isScrollingNow = ref(false);
66
+
67
+ let resizeObserver: ResizeObserver | null = null;
68
+ let scrollTimeout: ReturnType<typeof setTimeout> | undefined;
69
+ let startY = 0;
70
+ let startScrollTop = 0;
71
+ let startX = 0;
72
+ let startScrollLeft = 0;
73
+
74
+ const isScrollableX = computed(() => scrollWidth.value > clientWidth.value);
75
+ const isScrollableY = computed(() => scrollHeight.value > clientHeight.value);
76
+
77
+ const isBarVisibleX = computed(() => {
78
+ if (isDraggingH.value) {
79
+ return true;
80
+ }
81
+
82
+ return isScrollableX.value && (isHovered.value || isScrollingNow.value);
83
+ });
84
+
85
+ const isBarVisibleY = computed(() => {
86
+ if (isDraggingV.value) {
87
+ return true;
88
+ }
89
+
90
+ return isScrollableY.value && (isHovered.value || isScrollingNow.value);
91
+ });
92
+
93
+ const showVerticalTrack = computed(() => axes.value === 'vertical' || axes.value === 'both');
94
+ const showHorizontalTrack = computed(() => axes.value === 'horizontal' || axes.value === 'both');
95
+
96
+ /** Both axes can scroll — leave a free corner so tracks do not cross. */
97
+ const needsCornerGap = computed(
98
+ () => showVerticalTrack.value && showHorizontalTrack.value && isScrollableX.value && isScrollableY.value,
99
+ );
100
+
101
+ const trackWidthVPx = computed(() => {
102
+ const num = Number(vOpts.value.trackWidth ?? 6);
103
+ return isNaN(num) ? 6 : num;
104
+ });
105
+
106
+ const trackHeightHPx = computed(() => {
107
+ const num = Number(hOpts.value.trackHeight ?? 6);
108
+ return isNaN(num) ? 6 : num;
109
+ });
110
+
111
+ const TRACK_EDGE_OFFSET = 2;
112
+ const CORNER_GAP = 2;
113
+
114
+ const trackOffsetTopPx = computed(() => {
115
+ const raw = vOpts.value.trackOffsetTop;
116
+ if (raw == null || raw === '') {
117
+ return 0;
118
+ }
119
+
120
+ const num = Number(raw);
121
+ return isNaN(num) ? 0 : Math.max(0, num);
122
+ });
123
+
124
+ const vTrackInsetTop = computed(() => TRACK_EDGE_OFFSET + trackOffsetTopPx.value);
125
+
126
+ const vTrackInsetBottom = computed(() =>
127
+ needsCornerGap.value ? TRACK_EDGE_OFFSET + trackHeightHPx.value + CORNER_GAP : TRACK_EDGE_OFFSET,
128
+ );
129
+
130
+ const hTrackInsetRight = computed(() =>
131
+ needsCornerGap.value ? TRACK_EDGE_OFFSET + trackWidthVPx.value + CORNER_GAP : TRACK_EDGE_OFFSET,
132
+ );
133
+
134
+ const vTrackLength = computed(() =>
135
+ Math.max(0, clientHeight.value - vTrackInsetTop.value - vTrackInsetBottom.value),
136
+ );
137
+ const hTrackLength = computed(() => Math.max(0, clientWidth.value - hTrackInsetRight.value - TRACK_EDGE_OFFSET));
138
+
139
+ const thumbHeightV = computed(() => {
140
+ if (!scrollHeight.value || !vTrackLength.value) {
141
+ return 0;
142
+ }
143
+
144
+ const cfgThumb = vOpts.value.thumbHeight;
145
+ const minHeight = Number(vOpts.value.thumbMinHeight ?? 32);
146
+ if (cfgThumb && cfgThumb !== 'auto') {
147
+ const fixedNum = Number(cfgThumb);
148
+ return Math.max(isNaN(fixedNum) ? minHeight : fixedNum, minHeight);
149
+ }
150
+
151
+ const ratio = clientHeight.value / scrollHeight.value;
152
+ const calculated = vTrackLength.value * ratio;
153
+ return Math.min(vTrackLength.value, Math.max(minHeight, calculated));
154
+ });
155
+
156
+ const thumbWidthH = computed(() => {
157
+ if (!scrollWidth.value || !hTrackLength.value) {
158
+ return 0;
159
+ }
160
+
161
+ const cfgThumb = hOpts.value.thumbWidth;
162
+ const minWidth = Number(hOpts.value.thumbMinWidth ?? 32);
163
+ if (cfgThumb && cfgThumb !== 'auto') {
164
+ const fixedNum = Number(cfgThumb);
165
+ return Math.max(isNaN(fixedNum) ? minWidth : fixedNum, minWidth);
166
+ }
167
+
168
+ const ratio = clientWidth.value / scrollWidth.value;
169
+ const calculated = hTrackLength.value * ratio;
170
+ return Math.min(hTrackLength.value, Math.max(minWidth, calculated));
171
+ });
172
+
173
+ const thumbTopV = computed(() => {
174
+ if (!scrollHeight.value || scrollHeight.value === clientHeight.value || !vTrackLength.value) {
175
+ return 0;
176
+ }
177
+
178
+ const maxScrollTop = scrollHeight.value - clientHeight.value;
179
+ const maxThumbTop = Math.max(0, vTrackLength.value - thumbHeightV.value);
180
+ if (!maxScrollTop || !maxThumbTop) {
181
+ return 0;
182
+ }
183
+
184
+ return (scrollTop.value / maxScrollTop) * maxThumbTop;
185
+ });
186
+
187
+ const thumbLeftH = computed(() => {
188
+ if (!scrollWidth.value || scrollWidth.value === clientWidth.value || !hTrackLength.value) {
189
+ return 0;
190
+ }
191
+
192
+ const maxScrollLeft = scrollWidth.value - clientWidth.value;
193
+ const maxThumbLeft = Math.max(0, hTrackLength.value - thumbWidthH.value);
194
+ if (!maxScrollLeft || !maxThumbLeft) {
195
+ return 0;
196
+ }
197
+
198
+ return (scrollLeft.value / maxScrollLeft) * maxThumbLeft;
199
+ });
200
+
201
+ const trackRadiusVCss = computed(() => {
202
+ const num = Number(vOpts.value.trackRadius);
203
+ return isNaN(num) ? String(vOpts.value.trackRadius ?? 4) : `${num}px`;
204
+ });
205
+ const trackColorVCss = computed(() => vOpts.value.trackColor ?? 'transparent');
206
+ const thumbRadiusVCss = computed(() => {
207
+ const num = Number(vOpts.value.thumbRadius);
208
+ return isNaN(num) ? String(vOpts.value.thumbRadius ?? 4) : `${num}px`;
209
+ });
210
+ const thumbColorVCss = computed(() => vOpts.value.thumbColor ?? 'var(--color-bg-control-accent-default)');
211
+ const thumbHoverColorVCss = computed(() => vOpts.value.thumbHoverColor ?? 'var(--color-bg-control-accent-hover)');
212
+ const thumbActiveColorVCss = computed(() => vOpts.value.thumbActiveColor ?? 'var(--color-bg-control-accent-focused)');
213
+ const trackWidthVCss = computed(() => {
214
+ const num = Number(vOpts.value.trackWidth);
215
+ return isNaN(num) ? String(vOpts.value.trackWidth ?? 6) : `${num}px`;
216
+ });
217
+
218
+ const trackRadiusHCss = computed(() => {
219
+ const num = Number(hOpts.value.trackRadius);
220
+ return isNaN(num) ? String(hOpts.value.trackRadius ?? 4) : `${num}px`;
221
+ });
222
+ const trackColorHCss = computed(() => hOpts.value.trackColor ?? 'transparent');
223
+ const thumbRadiusHCss = computed(() => {
224
+ const num = Number(hOpts.value.thumbRadius);
225
+ return isNaN(num) ? String(hOpts.value.thumbRadius ?? 4) : `${num}px`;
226
+ });
227
+ const thumbColorHCss = computed(() => hOpts.value.thumbColor ?? 'var(--color-bg-control-accent-default)');
228
+ const thumbHoverColorHCss = computed(() => hOpts.value.thumbHoverColor ?? 'var(--color-bg-control-accent-hover)');
229
+ const thumbActiveColorHCss = computed(() => hOpts.value.thumbActiveColor ?? 'var(--color-bg-control-accent-focused)');
230
+ const trackHeightHCss = computed(() => {
231
+ const num = Number(hOpts.value.trackHeight);
232
+ return isNaN(num) ? String(hOpts.value.trackHeight ?? 6) : `${num}px`;
233
+ });
234
+
235
+ function updateMetrics() {
236
+ const el = containerRef.value;
237
+ if (!el) {
238
+ return;
239
+ }
240
+
241
+ scrollHeight.value = el.scrollHeight;
242
+ clientHeight.value = el.clientHeight;
243
+ scrollTop.value = el.scrollTop;
244
+ scrollWidth.value = el.scrollWidth;
245
+ clientWidth.value = el.clientWidth;
246
+ scrollLeft.value = el.scrollLeft;
247
+ }
248
+
249
+ function emitScroll(event: Event) {
250
+ const payload: Ui3nScrollbarScrollPayload = {
251
+ event,
252
+ scrollTop: scrollTop.value,
253
+ scrollLeft: scrollLeft.value,
254
+ };
255
+
256
+ emits('scroll', payload);
257
+
258
+ if (scrollTop.value !== prevScrollTop.value) {
259
+ emits('scroll:vertical', payload);
260
+ }
261
+ if (scrollLeft.value !== prevScrollLeft.value) {
262
+ emits('scroll:horizontal', payload);
263
+ }
264
+
265
+ prevScrollTop.value = scrollTop.value;
266
+ prevScrollLeft.value = scrollLeft.value;
267
+ }
268
+
269
+ function onScroll(event: Event) {
270
+ if (!isDraggingV.value) {
271
+ scrollTop.value = (event.target as HTMLDivElement).scrollTop;
272
+ }
273
+ if (!isDraggingH.value) {
274
+ scrollLeft.value = (event.target as HTMLDivElement).scrollLeft;
275
+ }
276
+
277
+ isScrollingNow.value = true;
278
+ clearTimeout(scrollTimeout);
279
+
280
+ emitScroll(event);
281
+
282
+ scrollTimeout = setTimeout(() => {
283
+ isScrollingNow.value = false;
284
+ }, 1500);
285
+ }
286
+
287
+ function onVTrackClick(e: MouseEvent) {
288
+ if (e.target !== trackRefV.value || !containerRef.value || !trackRefV.value) {
289
+ return;
290
+ }
291
+
292
+ const trackRect = trackRefV.value.getBoundingClientRect();
293
+ const clickY = e.clientY - trackRect.top;
294
+ const targetThumbTop = clickY - thumbHeightV.value / 2;
295
+ const maxScrollTop = scrollHeight.value - clientHeight.value;
296
+ const maxThumbTop = Math.max(0, vTrackLength.value - thumbHeightV.value);
297
+ if (!maxThumbTop || !maxScrollTop) {
298
+ return;
299
+ }
300
+
301
+ let targetScrollTop = (targetThumbTop / maxThumbTop) * maxScrollTop;
302
+ targetScrollTop = Math.max(0, Math.min(maxScrollTop, targetScrollTop));
303
+
304
+ containerRef.value.scrollTo({
305
+ top: targetScrollTop,
306
+ behavior: 'smooth',
307
+ });
308
+ }
309
+
310
+ function onHTrackClick(e: MouseEvent) {
311
+ if (e.target !== trackRefH.value || !containerRef.value || !trackRefH.value) {
312
+ return;
313
+ }
314
+
315
+ const trackRect = trackRefH.value.getBoundingClientRect();
316
+ const clickX = e.clientX - trackRect.left;
317
+ const targetThumbLeft = clickX - thumbWidthH.value / 2;
318
+ const maxScrollLeft = scrollWidth.value - clientWidth.value;
319
+ const maxThumbLeft = Math.max(0, hTrackLength.value - thumbWidthH.value);
320
+ if (!maxThumbLeft || !maxScrollLeft) {
321
+ return;
322
+ }
323
+
324
+ let targetScrollLeft = (targetThumbLeft / maxThumbLeft) * maxScrollLeft;
325
+ targetScrollLeft = Math.max(0, Math.min(maxScrollLeft, targetScrollLeft));
326
+
327
+ containerRef.value.scrollTo({
328
+ left: targetScrollLeft,
329
+ behavior: 'smooth',
330
+ });
331
+ }
332
+
333
+ function onVThumbPointerDown(e: PointerEvent) {
334
+ if (!thumbRefV.value || !containerRef.value) {
335
+ return;
336
+ }
337
+
338
+ if (e.pointerType === 'mouse') {
339
+ e.preventDefault();
340
+ }
341
+ e.stopPropagation();
342
+
343
+ thumbRefV.value.setPointerCapture(e.pointerId);
344
+
345
+ isDraggingV.value = true;
346
+ startY = e.clientY;
347
+ startScrollTop = scrollTop.value;
348
+
349
+ document.body.style.userSelect = 'none';
350
+
351
+ thumbRefV.value.addEventListener('pointermove', onVThumbPointerMove);
352
+ thumbRefV.value.addEventListener('pointerup', onVThumbPointerUp);
353
+ thumbRefV.value.addEventListener('pointercancel', onVThumbPointerUp);
354
+ }
355
+
356
+ function onVThumbPointerMove(e: PointerEvent) {
357
+ if (!isDraggingV.value || !containerRef.value || !trackRefV.value) {
358
+ return;
359
+ }
360
+
361
+ const deltaY = e.clientY - startY;
362
+ const maxScrollTop = scrollHeight.value - clientHeight.value;
363
+ const maxThumbTop = Math.max(0, vTrackLength.value - thumbHeightV.value);
364
+ if (!maxThumbTop || !maxScrollTop) {
365
+ return;
366
+ }
367
+
368
+ const scrollDelta = (deltaY / maxThumbTop) * maxScrollTop;
369
+ containerRef.value.scrollTop = startScrollTop + scrollDelta;
370
+ scrollTop.value = containerRef.value.scrollTop;
371
+ }
372
+
373
+ function onVThumbPointerUp() {
374
+ if (!isDraggingV.value) {
375
+ return;
376
+ }
377
+
378
+ isDraggingV.value = false;
379
+ document.body.style.userSelect = '';
380
+
381
+ if (thumbRefV.value) {
382
+ thumbRefV.value.removeEventListener('pointermove', onVThumbPointerMove);
383
+ thumbRefV.value.removeEventListener('pointerup', onVThumbPointerUp);
384
+ thumbRefV.value.removeEventListener('pointercancel', onVThumbPointerUp);
385
+ }
386
+ }
387
+
388
+ function onHThumbPointerDown(e: PointerEvent) {
389
+ if (!thumbRefH.value || !containerRef.value) {
390
+ return;
391
+ }
392
+
393
+ if (e.pointerType === 'mouse') {
394
+ e.preventDefault();
395
+ }
396
+ e.stopPropagation();
397
+
398
+ thumbRefH.value.setPointerCapture(e.pointerId);
399
+
400
+ isDraggingH.value = true;
401
+ startX = e.clientX;
402
+ startScrollLeft = scrollLeft.value;
403
+
404
+ document.body.style.userSelect = 'none';
405
+
406
+ thumbRefH.value.addEventListener('pointermove', onHThumbPointerMove);
407
+ thumbRefH.value.addEventListener('pointerup', onHThumbPointerUp);
408
+ thumbRefH.value.addEventListener('pointercancel', onHThumbPointerUp);
409
+ }
410
+
411
+ function onHThumbPointerMove(e: PointerEvent) {
412
+ if (!isDraggingH.value || !containerRef.value || !trackRefH.value) {
413
+ return;
414
+ }
415
+
416
+ const deltaX = e.clientX - startX;
417
+ const maxScrollLeft = scrollWidth.value - clientWidth.value;
418
+ const maxThumbLeft = Math.max(0, hTrackLength.value - thumbWidthH.value);
419
+ if (!maxThumbLeft || !maxScrollLeft) {
420
+ return;
421
+ }
422
+
423
+ const scrollDelta = (deltaX / maxThumbLeft) * maxScrollLeft;
424
+ containerRef.value.scrollLeft = startScrollLeft + scrollDelta;
425
+ scrollLeft.value = containerRef.value.scrollLeft;
426
+ }
427
+
428
+ function onHThumbPointerUp() {
429
+ if (!isDraggingH.value) {
430
+ return;
431
+ }
432
+
433
+ isDraggingH.value = false;
434
+ document.body.style.userSelect = '';
435
+
436
+ if (thumbRefH.value) {
437
+ thumbRefH.value.removeEventListener('pointermove', onHThumbPointerMove);
438
+ thumbRefH.value.removeEventListener('pointerup', onHThumbPointerUp);
439
+ thumbRefH.value.removeEventListener('pointercancel', onHThumbPointerUp);
440
+ }
441
+ }
442
+
443
+ onMounted(() => {
444
+ updateMetrics();
445
+
446
+ if (props.autoUpdate) {
447
+ resizeObserver = new ResizeObserver(updateMetrics);
448
+ resizeObserver.observe(containerRef.value!);
449
+
450
+ const firstChild = containerRef.value?.firstElementChild;
451
+ if (firstChild) {
452
+ resizeObserver.observe(firstChild);
453
+ }
454
+ }
455
+ });
456
+
457
+ onBeforeUnmount(() => {
458
+ resizeObserver?.disconnect();
459
+ scrollTimeout && clearTimeout(scrollTimeout);
460
+
461
+ if (thumbRefV.value) {
462
+ thumbRefV.value.removeEventListener('pointermove', onVThumbPointerMove);
463
+ thumbRefV.value.removeEventListener('pointerup', onVThumbPointerUp);
464
+ thumbRefV.value.removeEventListener('pointercancel', onVThumbPointerUp);
465
+ }
466
+ if (thumbRefH.value) {
467
+ thumbRefH.value.removeEventListener('pointermove', onHThumbPointerMove);
468
+ thumbRefH.value.removeEventListener('pointerup', onHThumbPointerUp);
469
+ thumbRefH.value.removeEventListener('pointercancel', onHThumbPointerUp);
470
+ }
471
+
472
+ document.body.style.userSelect = '';
473
+ });
474
+
475
+ defineExpose<Ui3nScrollbarExpose>({
476
+ scrollTo: (options: ScrollToOptions) => {
477
+ containerRef.value?.scrollTo(options);
478
+ },
479
+ scrollToVertical: (options = {}) => {
480
+ const container = containerRef.value;
481
+ if (!container) {
482
+ return;
483
+ }
484
+
485
+ const opts: ScrollToOptions = {};
486
+ if (typeof options.top === 'number') {
487
+ opts.top = options.top;
488
+ }
489
+ if (options.behavior) {
490
+ opts.behavior = options.behavior;
491
+ }
492
+ container.scrollTo(opts);
493
+ },
494
+ scrollToHorizontal: (options = {}) => {
495
+ const container = containerRef.value;
496
+ if (!container) {
497
+ return;
498
+ }
499
+
500
+ const opts: ScrollToOptions = {};
501
+ if (typeof options.left === 'number') {
502
+ opts.left = options.left;
503
+ }
504
+ if (options.behavior) {
505
+ opts.behavior = options.behavior;
506
+ }
507
+ container.scrollTo(opts);
508
+ },
509
+ getContainer: () => containerRef.value!,
510
+ updateMetrics,
511
+ });
512
+ </script>
513
+
514
+ <template>
515
+ <div
516
+ :class="$style.ui3nScrollbar"
517
+ :style="{
518
+ '--ui3n-scrollbar-vertical-thumb-radius': thumbRadiusVCss,
519
+ '--ui3n-scrollbar-vertical-thumb-color': thumbColorVCss,
520
+ '--ui3n-scrollbar-vertical-thumb-hover-color': thumbHoverColorVCss,
521
+ '--ui3n-scrollbar-vertical-thumb-active-color': thumbActiveColorVCss,
522
+ '--ui3n-scrollbar-vertical-track-width': trackWidthVCss,
523
+ '--ui3n-scrollbar-vertical-track-radius': trackRadiusVCss,
524
+ '--ui3n-scrollbar-vertical-track-color': trackColorVCss,
525
+ '--ui3n-scrollbar-horizontal-thumb-radius': thumbRadiusHCss,
526
+ '--ui3n-scrollbar-horizontal-thumb-color': thumbColorHCss,
527
+ '--ui3n-scrollbar-horizontal-thumb-hover-color': thumbHoverColorHCss,
528
+ '--ui3n-scrollbar-horizontal-thumb-active-color': thumbActiveColorHCss,
529
+ '--ui3n-scrollbar-horizontal-track-height': trackHeightHCss,
530
+ '--ui3n-scrollbar-horizontal-track-radius': trackRadiusHCss,
531
+ '--ui3n-scrollbar-horizontal-track-color': trackColorHCss,
532
+ }"
533
+ @mouseenter="isHovered = true"
534
+ @mouseleave="isHovered = false"
535
+ >
536
+ <div
537
+ ref="containerRef"
538
+ :class="[
539
+ $style.scrollbarContainer,
540
+ axes === 'vertical' && $style.scrollbarContainerVertical,
541
+ axes === 'horizontal' && $style.scrollbarContainerHorizontal,
542
+ ]"
543
+ @scroll="onScroll"
544
+ >
545
+ <slot />
546
+ </div>
547
+
548
+ <div
549
+ v-if="showVerticalTrack"
550
+ ref="trackRefV"
551
+ :class="[$style.trackVertical, { [$style.trackVisible]: isBarVisibleY }]"
552
+ :style="{ top: `${vTrackInsetTop}px`, bottom: `${vTrackInsetBottom}px` }"
553
+ @mousedown="onVTrackClick"
554
+ >
555
+ <div
556
+ ref="thumbRefV"
557
+ :class="[$style.thumbVertical, { [$style.active]: isDraggingV }]"
558
+ :style="{
559
+ height: `${thumbHeightV}px`,
560
+ transform: `translateY(${thumbTopV}px)`,
561
+ }"
562
+ @pointerdown="onVThumbPointerDown"
563
+ />
564
+ </div>
565
+
566
+ <div
567
+ v-if="showHorizontalTrack"
568
+ ref="trackRefH"
569
+ :class="[$style.trackHorizontal, { [$style.trackVisible]: isBarVisibleX }]"
570
+ :style="{ right: `${hTrackInsetRight}px` }"
571
+ @mousedown="onHTrackClick"
572
+ >
573
+ <div
574
+ ref="thumbRefH"
575
+ :class="[$style.thumbHorizontal, { [$style.active]: isDraggingH }]"
576
+ :style="{
577
+ width: `${thumbWidthH}px`,
578
+ transform: `translateX(${thumbLeftH}px)`,
579
+ }"
580
+ @pointerdown="onHThumbPointerDown"
581
+ />
582
+ </div>
583
+ </div>
584
+ </template>
585
+
586
+ <style lang="scss" module>
587
+ .ui3nScrollbar {
588
+ position: relative;
589
+ width: 100%;
590
+ height: 100%;
591
+ overflow: visible;
592
+ }
593
+
594
+ .scrollbarContainer {
595
+ position: relative;
596
+ width: 100%;
597
+ height: 100%;
598
+ overflow: auto;
599
+ scrollbar-width: none;
600
+
601
+ &::-webkit-scrollbar {
602
+ display: none !important;
603
+ width: 0 !important;
604
+ height: 0 !important;
605
+ }
606
+ }
607
+
608
+ .scrollbarContainerVertical {
609
+ overflow-x: hidden;
610
+ }
611
+
612
+ .scrollbarContainerHorizontal {
613
+ overflow-y: hidden;
614
+ }
615
+
616
+ .trackVertical {
617
+ position: absolute;
618
+ top: 2px;
619
+ right: 2px;
620
+ bottom: 2px;
621
+ width: var(--ui3n-scrollbar-vertical-track-width);
622
+ background: var(--ui3n-scrollbar-vertical-track-color);
623
+ border-radius: var(--ui3n-scrollbar-vertical-track-radius);
624
+ z-index: 10;
625
+ visibility: hidden;
626
+ opacity: 0;
627
+ pointer-events: none;
628
+ transition:
629
+ opacity 0.3s ease,
630
+ visibility 0.3s ease;
631
+ }
632
+
633
+ .trackHorizontal {
634
+ position: absolute;
635
+ left: 2px;
636
+ right: 2px;
637
+ bottom: 2px;
638
+ height: var(--ui3n-scrollbar-horizontal-track-height);
639
+ background: var(--ui3n-scrollbar-horizontal-track-color);
640
+ border-radius: var(--ui3n-scrollbar-horizontal-track-radius);
641
+ z-index: 10;
642
+ visibility: hidden;
643
+ opacity: 0;
644
+ pointer-events: none;
645
+ transition:
646
+ opacity 0.3s ease,
647
+ visibility 0.3s ease;
648
+ }
649
+
650
+ .trackVisible {
651
+ visibility: visible;
652
+ opacity: 1;
653
+ pointer-events: auto;
654
+ }
655
+
656
+ .thumbVertical {
657
+ position: absolute;
658
+ top: 0;
659
+ left: 0;
660
+ width: 100%;
661
+ border-radius: var(--ui3n-scrollbar-vertical-thumb-radius);
662
+ background-color: var(--ui3n-scrollbar-vertical-thumb-color);
663
+ cursor: pointer;
664
+ transition: background-color 0.15s ease;
665
+ touch-action: none;
666
+
667
+ &:hover {
668
+ background-color: var(--ui3n-scrollbar-vertical-thumb-hover-color);
669
+ }
670
+
671
+ &:active,
672
+ &.active {
673
+ background-color: var(--ui3n-scrollbar-vertical-thumb-active-color);
674
+ }
675
+ }
676
+
677
+ .thumbHorizontal {
678
+ position: absolute;
679
+ top: 0;
680
+ left: 0;
681
+ height: 100%;
682
+ border-radius: var(--ui3n-scrollbar-horizontal-thumb-radius);
683
+ background-color: var(--ui3n-scrollbar-horizontal-thumb-color);
684
+ cursor: pointer;
685
+ transition: background-color 0.15s ease;
686
+ touch-action: none;
687
+
688
+ &:hover {
689
+ background-color: var(--ui3n-scrollbar-horizontal-thumb-hover-color);
690
+ }
691
+
692
+ &:active,
693
+ &.active {
694
+ background-color: var(--ui3n-scrollbar-horizontal-thumb-active-color);
695
+ }
696
+ }
697
+ </style>