@abydahana/aksara-ui 1.2.2 → 1.2.4
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/dist/aksara.css +24 -1
- package/dist/aksara.d.ts +5 -2
- package/dist/aksara.esm.js +148 -11
- package/dist/aksara.js +148 -11
- package/dist/aksara.min.css +1 -1
- package/dist/aksara.min.js +1 -1
- package/dist/components/navigation/tabs.d.ts +2 -0
- package/dist/components/navigation/tabs.js +7 -5
- package/package.json +1 -1
package/dist/aksara.css
CHANGED
|
@@ -23681,7 +23681,8 @@ img,svg,video {
|
|
|
23681
23681
|
background:var(--aksara-surface);
|
|
23682
23682
|
border:1px solid var(--aksara-border-subtle);
|
|
23683
23683
|
border-radius:var(--aksara-radius);
|
|
23684
|
-
box-shadow:var(--aksara-shadow-md)
|
|
23684
|
+
box-shadow:var(--aksara-shadow-md);
|
|
23685
|
+
overscroll-behavior:contain
|
|
23685
23686
|
}
|
|
23686
23687
|
.dropdown-top .dropdown-menu,.dropdown-menu.dropdown-top {
|
|
23687
23688
|
bottom:100%;
|
|
@@ -24270,6 +24271,28 @@ img,svg,video {
|
|
|
24270
24271
|
.tab-panel {
|
|
24271
24272
|
padding:1rem 0
|
|
24272
24273
|
}
|
|
24274
|
+
.tabs-slider {
|
|
24275
|
+
position:relative;
|
|
24276
|
+
overflow:hidden;
|
|
24277
|
+
width:100%
|
|
24278
|
+
}
|
|
24279
|
+
.tabs-slider-track {
|
|
24280
|
+
display:flex;
|
|
24281
|
+
flex-direction:row;
|
|
24282
|
+
flex-wrap:nowrap;
|
|
24283
|
+
width:100%;
|
|
24284
|
+
will-change:transform;
|
|
24285
|
+
touch-action:pan-y pinch-zoom;
|
|
24286
|
+
transition:transform .3s cubic-bezier(.25,1,.5,1)
|
|
24287
|
+
}
|
|
24288
|
+
.tabs-slider-track > .tab-panel,.tabs-slider-track > .tab-pane {
|
|
24289
|
+
flex:0 0 100%;
|
|
24290
|
+
min-width:100%;
|
|
24291
|
+
max-width:100%;
|
|
24292
|
+
width:100%;
|
|
24293
|
+
box-sizing:border-box;
|
|
24294
|
+
display:block !important
|
|
24295
|
+
}
|
|
24273
24296
|
.tab[aria-selected="true"] {
|
|
24274
24297
|
background:var(--aksara-bg-subtle);
|
|
24275
24298
|
color:var(--aksara-text-body)
|
package/dist/aksara.d.ts
CHANGED
|
@@ -114,7 +114,8 @@ export declare class Dropdown extends Component {
|
|
|
114
114
|
isOpen: boolean;
|
|
115
115
|
originalParent: Node | null;
|
|
116
116
|
originalSibling: Node | null;
|
|
117
|
-
scrollHandler: (() => void) | null;
|
|
117
|
+
scrollHandler: ((event?: Event) => void) | null;
|
|
118
|
+
wheelHandler: ((event: WheelEvent) => void) | null;
|
|
118
119
|
placement: string;
|
|
119
120
|
container: string | HTMLElement;
|
|
120
121
|
offset: number;
|
|
@@ -188,7 +189,9 @@ export declare class Tabs extends Component {
|
|
|
188
189
|
constructor(element: HTMLElement, options?: TabsOptions);
|
|
189
190
|
updateOptions(options?: TabsOptions): this;
|
|
190
191
|
activateInitial(): void;
|
|
191
|
-
|
|
192
|
+
getSliderTrack(): HTMLElement | null;
|
|
193
|
+
bindSliderGestures(): void;
|
|
194
|
+
show(tab: HTMLElement, animate?: boolean): this;
|
|
192
195
|
}
|
|
193
196
|
export declare class Toast extends Component {
|
|
194
197
|
static componentName: string;
|
package/dist/aksara.esm.js
CHANGED
|
@@ -326,6 +326,7 @@ export class Dropdown extends Component {
|
|
|
326
326
|
originalParent;
|
|
327
327
|
originalSibling;
|
|
328
328
|
scrollHandler;
|
|
329
|
+
wheelHandler;
|
|
329
330
|
placement;
|
|
330
331
|
container;
|
|
331
332
|
offset;
|
|
@@ -372,6 +373,7 @@ export class Dropdown extends Component {
|
|
|
372
373
|
this.originalParent = null;
|
|
373
374
|
this.originalSibling = null;
|
|
374
375
|
this.scrollHandler = null;
|
|
376
|
+
this.wheelHandler = null;
|
|
375
377
|
this.updateOptions(options);
|
|
376
378
|
if (this.trigger?.matches(".btn-sm, .input-sm, .form-control-sm, .form-select-sm") ||
|
|
377
379
|
this.element.classList.contains("dropdown-sm")) {
|
|
@@ -427,9 +429,14 @@ export class Dropdown extends Component {
|
|
|
427
429
|
position() {
|
|
428
430
|
if (!this.menu || !this.isOpen || !this.trigger)
|
|
429
431
|
return;
|
|
432
|
+
if (!this.trigger.isConnected || !document.body.contains(this.trigger)) {
|
|
433
|
+
this.hide();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
430
436
|
this.menu.style.display = "block";
|
|
431
437
|
this.menu.style.position = "absolute";
|
|
432
438
|
this.menu.style.zIndex = "1000";
|
|
439
|
+
this.menu.style.overscrollBehavior = "contain";
|
|
433
440
|
const triggerRect = this.trigger.getBoundingClientRect();
|
|
434
441
|
if (this.trigger.classList.contains("form-select") ||
|
|
435
442
|
this.element.classList.contains("variant-form-select") ||
|
|
@@ -441,10 +448,10 @@ export class Dropdown extends Component {
|
|
|
441
448
|
const gap = this.offset;
|
|
442
449
|
let top;
|
|
443
450
|
let left;
|
|
451
|
+
const spaceBelow = window.innerHeight - triggerRect.bottom;
|
|
452
|
+
const spaceAbove = triggerRect.top;
|
|
444
453
|
let placement = this.placement;
|
|
445
454
|
if (placement === "auto") {
|
|
446
|
-
const spaceBelow = window.innerHeight - triggerRect.bottom;
|
|
447
|
-
const spaceAbove = triggerRect.top;
|
|
448
455
|
placement = spaceBelow >= menuRect.height || spaceBelow >= spaceAbove ? "bottom" : "top";
|
|
449
456
|
}
|
|
450
457
|
this.menu.classList.remove("dropdown-top", "dropdown-bottom", "dropdown-start", "dropdown-end");
|
|
@@ -468,6 +475,11 @@ export class Dropdown extends Component {
|
|
|
468
475
|
}
|
|
469
476
|
const viewportWidth = window.innerWidth;
|
|
470
477
|
left = Math.max(8, Math.min(left, viewportWidth - menuRect.width - 8));
|
|
478
|
+
const availableHeight = Math.max(120, Math.floor(placement === "top" ? spaceAbove - gap - 16 : spaceBelow - gap - 16));
|
|
479
|
+
if (this.menu.scrollHeight > availableHeight || availableHeight < 300) {
|
|
480
|
+
this.menu.style.maxHeight = `${availableHeight}px`;
|
|
481
|
+
this.menu.style.overflowY = "auto";
|
|
482
|
+
}
|
|
471
483
|
this.menu.style.top = `${top + window.scrollY}px`;
|
|
472
484
|
this.menu.style.left = `${left + window.scrollX}px`;
|
|
473
485
|
this.menu.style.bottom = "auto";
|
|
@@ -490,9 +502,30 @@ export class Dropdown extends Component {
|
|
|
490
502
|
this.menu.classList.add("dropdown-open");
|
|
491
503
|
this.trigger?.setAttribute("aria-expanded", "true");
|
|
492
504
|
this.position();
|
|
493
|
-
this.scrollHandler = () =>
|
|
494
|
-
|
|
505
|
+
this.scrollHandler = (event) => {
|
|
506
|
+
if ((this.menu && (this.menu.matches(":hover") || this.menu.contains(document.activeElement))) ||
|
|
507
|
+
(this.trigger && this.trigger.matches(":hover")) ||
|
|
508
|
+
(event?.target && this.menu && (this.menu === event.target || this.menu.contains(event.target)))) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
this.position();
|
|
512
|
+
};
|
|
513
|
+
window.addEventListener("scroll", this.scrollHandler, { passive: true, capture: true });
|
|
495
514
|
window.addEventListener("resize", this.scrollHandler);
|
|
515
|
+
this.wheelHandler = (e) => {
|
|
516
|
+
if (!this.menu)
|
|
517
|
+
return;
|
|
518
|
+
const { deltaY } = e;
|
|
519
|
+
if (deltaY === 0)
|
|
520
|
+
return;
|
|
521
|
+
const atTop = this.menu.scrollTop <= 0;
|
|
522
|
+
const atBottom = this.menu.scrollTop + this.menu.clientHeight >= this.menu.scrollHeight - 1;
|
|
523
|
+
if ((deltaY < 0 && atTop) || (deltaY > 0 && atBottom) || this.menu.scrollHeight <= this.menu.clientHeight) {
|
|
524
|
+
e.preventDefault();
|
|
525
|
+
e.stopPropagation();
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
this.menu.addEventListener("wheel", this.wheelHandler, { passive: false });
|
|
496
529
|
emit(this.element, "dropdown:show", { instance: this });
|
|
497
530
|
return this;
|
|
498
531
|
}
|
|
@@ -501,10 +534,14 @@ export class Dropdown extends Component {
|
|
|
501
534
|
return this;
|
|
502
535
|
this.isOpen = false;
|
|
503
536
|
if (this.scrollHandler) {
|
|
504
|
-
window.removeEventListener("scroll", this.scrollHandler);
|
|
537
|
+
window.removeEventListener("scroll", this.scrollHandler, { capture: true });
|
|
505
538
|
window.removeEventListener("resize", this.scrollHandler);
|
|
506
539
|
this.scrollHandler = null;
|
|
507
540
|
}
|
|
541
|
+
if (this.wheelHandler && this.menu) {
|
|
542
|
+
this.menu.removeEventListener("wheel", this.wheelHandler);
|
|
543
|
+
this.wheelHandler = null;
|
|
544
|
+
}
|
|
508
545
|
this.menu.classList.remove("dropdown-open", "dropdown-top", "dropdown-bottom", "dropdown-start", "dropdown-end");
|
|
509
546
|
this.trigger?.setAttribute("aria-expanded", "false");
|
|
510
547
|
if (this.originalParent) {
|
|
@@ -953,6 +990,7 @@ export class Tabs extends Component {
|
|
|
953
990
|
this.show(next);
|
|
954
991
|
}
|
|
955
992
|
});
|
|
993
|
+
this.bindSliderGestures();
|
|
956
994
|
this.activateInitial();
|
|
957
995
|
}
|
|
958
996
|
updateOptions(options = {}) {
|
|
@@ -992,29 +1030,128 @@ export class Tabs extends Component {
|
|
|
992
1030
|
if (initial)
|
|
993
1031
|
this.show(initial);
|
|
994
1032
|
}
|
|
995
|
-
|
|
1033
|
+
getSliderTrack() {
|
|
1034
|
+
const wrapper = this.element.closest(".tabs-wrapper") ||
|
|
1035
|
+
this.element.closest(".feed-main-col, main, .page-container, [data-tabs-wrapper]") ||
|
|
1036
|
+
this.element.parentElement;
|
|
1037
|
+
return (wrapper?.querySelector(".tabs-slider-track, [data-tabs-slider]") ||
|
|
1038
|
+
document.querySelector(".tabs-slider-track, [data-tabs-slider]"));
|
|
1039
|
+
}
|
|
1040
|
+
bindSliderGestures() {
|
|
1041
|
+
const track = this.getSliderTrack();
|
|
1042
|
+
if (!track || track.__sliderGesturesBound)
|
|
1043
|
+
return;
|
|
1044
|
+
track.__sliderGesturesBound = true;
|
|
1045
|
+
let startX = 0;
|
|
1046
|
+
let startY = 0;
|
|
1047
|
+
let deltaX = 0;
|
|
1048
|
+
let deltaY = 0;
|
|
1049
|
+
let isSwiping = false;
|
|
1050
|
+
let isVertical = false;
|
|
1051
|
+
track.addEventListener("touchstart", (e) => {
|
|
1052
|
+
if (e.touches.length !== 1)
|
|
1053
|
+
return;
|
|
1054
|
+
const target = e.target;
|
|
1055
|
+
if (target?.closest("input, textarea, select, [contenteditable='true'], video, audio, .carousel, .bento-carousel, .media-grid-scroll, .map-canvas-container, [data-prevent-swipe]")) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const touch = e.touches[0];
|
|
1059
|
+
startX = touch.clientX;
|
|
1060
|
+
startY = touch.clientY;
|
|
1061
|
+
deltaX = 0;
|
|
1062
|
+
deltaY = 0;
|
|
1063
|
+
isSwiping = false;
|
|
1064
|
+
isVertical = false;
|
|
1065
|
+
}, { passive: true });
|
|
1066
|
+
track.addEventListener("touchmove", (e) => {
|
|
1067
|
+
if (e.touches.length !== 1 || isVertical)
|
|
1068
|
+
return;
|
|
1069
|
+
const touch = e.touches[0];
|
|
1070
|
+
deltaX = touch.clientX - startX;
|
|
1071
|
+
deltaY = touch.clientY - startY;
|
|
1072
|
+
if (!isSwiping) {
|
|
1073
|
+
if (Math.abs(deltaY) > 8 && Math.abs(deltaY) > Math.abs(deltaX)) {
|
|
1074
|
+
isVertical = true;
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (Math.abs(deltaX) > 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
1078
|
+
isSwiping = true;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (isSwiping) {
|
|
1082
|
+
if (e.cancelable)
|
|
1083
|
+
e.preventDefault();
|
|
1084
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1085
|
+
const maxIdx = Math.max(0, tabs.length - 1);
|
|
1086
|
+
let currentDelta = deltaX;
|
|
1087
|
+
const currentIdx = tabs.findIndex((t) => t.classList.contains("active"));
|
|
1088
|
+
const safeIdx = currentIdx >= 0 ? currentIdx : 0;
|
|
1089
|
+
if ((safeIdx === 0 && currentDelta > 0) || (safeIdx === maxIdx && currentDelta < 0)) {
|
|
1090
|
+
currentDelta = currentDelta * 0.3;
|
|
1091
|
+
}
|
|
1092
|
+
track.style.transition = "none";
|
|
1093
|
+
track.style.transform = `translate3d(calc(-${safeIdx * 100}% + ${currentDelta}px), 0, 0)`;
|
|
1094
|
+
}
|
|
1095
|
+
}, { passive: false });
|
|
1096
|
+
const onTouchEnd = () => {
|
|
1097
|
+
if (!isSwiping)
|
|
1098
|
+
return;
|
|
1099
|
+
isSwiping = false;
|
|
1100
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1101
|
+
const maxIdx = Math.max(0, tabs.length - 1);
|
|
1102
|
+
const currentIdx = tabs.findIndex((t) => t.classList.contains("active"));
|
|
1103
|
+
const safeIdx = currentIdx >= 0 ? currentIdx : 0;
|
|
1104
|
+
track.style.transition = "transform 0.3s cubic-bezier(0.25, 1, 0.5, 1)";
|
|
1105
|
+
if (deltaX < -50 && safeIdx < maxIdx) {
|
|
1106
|
+
this.show(tabs[safeIdx + 1]);
|
|
1107
|
+
}
|
|
1108
|
+
else if (deltaX > 50 && safeIdx > 0) {
|
|
1109
|
+
this.show(tabs[safeIdx - 1]);
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
track.style.transform = `translate3d(-${safeIdx * 100}%, 0, 0)`;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
track.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
1116
|
+
track.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
1117
|
+
}
|
|
1118
|
+
show(tab, animate = true) {
|
|
996
1119
|
const selector = tab.getAttribute("data-tabs");
|
|
997
1120
|
const panel = selector ? document.querySelector(selector) : null;
|
|
998
1121
|
if (!panel)
|
|
999
1122
|
return this;
|
|
1000
|
-
this.element.querySelectorAll(this.tabSelector)
|
|
1123
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1124
|
+
const targetIndex = tabs.indexOf(tab);
|
|
1125
|
+
const track = this.getSliderTrack();
|
|
1126
|
+
const isSlider = Boolean(track && track.contains(panel));
|
|
1127
|
+
tabs.forEach((item) => {
|
|
1001
1128
|
const selected = item === tab;
|
|
1002
1129
|
item.setAttribute("role", item.getAttribute("role") || "tab");
|
|
1003
1130
|
item.setAttribute("aria-selected", String(selected));
|
|
1131
|
+
item.classList.toggle("active", selected);
|
|
1004
1132
|
item.tabIndex = selected ? 0 : -1;
|
|
1005
1133
|
const targetSelector = item.getAttribute("data-tabs");
|
|
1006
1134
|
const target = targetSelector ? document.querySelector(targetSelector) : null;
|
|
1007
|
-
if (target)
|
|
1008
|
-
target.
|
|
1135
|
+
if (target) {
|
|
1136
|
+
target.classList.toggle("active", selected);
|
|
1137
|
+
if (!isSlider) {
|
|
1138
|
+
target.hidden = !selected;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1009
1141
|
});
|
|
1010
|
-
if (this.panelSelector) {
|
|
1142
|
+
if (this.panelSelector && !isSlider) {
|
|
1011
1143
|
document.querySelectorAll(this.panelSelector).forEach((item) => {
|
|
1012
1144
|
if (item !== panel)
|
|
1013
1145
|
item.hidden = true;
|
|
1014
1146
|
});
|
|
1015
1147
|
}
|
|
1016
1148
|
panel.setAttribute("role", panel.getAttribute("role") || "tabpanel");
|
|
1017
|
-
|
|
1149
|
+
if (isSlider && track && targetIndex >= 0) {
|
|
1150
|
+
track.style.transition = animate ? "transform 0.3s cubic-bezier(0.25, 1, 0.5, 1)" : "none";
|
|
1151
|
+
track.style.transform = `translate3d(-${targetIndex * 100}%, 0, 0)`;
|
|
1152
|
+
}
|
|
1153
|
+
tab.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
|
|
1154
|
+
emit(this.element, "tabs:show", { tab, panel, index: targetIndex, instance: this });
|
|
1018
1155
|
return this;
|
|
1019
1156
|
}
|
|
1020
1157
|
}
|
package/dist/aksara.js
CHANGED
|
@@ -327,6 +327,7 @@ class Dropdown extends Component {
|
|
|
327
327
|
originalParent;
|
|
328
328
|
originalSibling;
|
|
329
329
|
scrollHandler;
|
|
330
|
+
wheelHandler;
|
|
330
331
|
placement;
|
|
331
332
|
container;
|
|
332
333
|
offset;
|
|
@@ -373,6 +374,7 @@ class Dropdown extends Component {
|
|
|
373
374
|
this.originalParent = null;
|
|
374
375
|
this.originalSibling = null;
|
|
375
376
|
this.scrollHandler = null;
|
|
377
|
+
this.wheelHandler = null;
|
|
376
378
|
this.updateOptions(options);
|
|
377
379
|
if (this.trigger?.matches(".btn-sm, .input-sm, .form-control-sm, .form-select-sm") ||
|
|
378
380
|
this.element.classList.contains("dropdown-sm")) {
|
|
@@ -428,9 +430,14 @@ class Dropdown extends Component {
|
|
|
428
430
|
position() {
|
|
429
431
|
if (!this.menu || !this.isOpen || !this.trigger)
|
|
430
432
|
return;
|
|
433
|
+
if (!this.trigger.isConnected || !document.body.contains(this.trigger)) {
|
|
434
|
+
this.hide();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
431
437
|
this.menu.style.display = "block";
|
|
432
438
|
this.menu.style.position = "absolute";
|
|
433
439
|
this.menu.style.zIndex = "1000";
|
|
440
|
+
this.menu.style.overscrollBehavior = "contain";
|
|
434
441
|
const triggerRect = this.trigger.getBoundingClientRect();
|
|
435
442
|
if (this.trigger.classList.contains("form-select") ||
|
|
436
443
|
this.element.classList.contains("variant-form-select") ||
|
|
@@ -442,10 +449,10 @@ class Dropdown extends Component {
|
|
|
442
449
|
const gap = this.offset;
|
|
443
450
|
let top;
|
|
444
451
|
let left;
|
|
452
|
+
const spaceBelow = window.innerHeight - triggerRect.bottom;
|
|
453
|
+
const spaceAbove = triggerRect.top;
|
|
445
454
|
let placement = this.placement;
|
|
446
455
|
if (placement === "auto") {
|
|
447
|
-
const spaceBelow = window.innerHeight - triggerRect.bottom;
|
|
448
|
-
const spaceAbove = triggerRect.top;
|
|
449
456
|
placement = spaceBelow >= menuRect.height || spaceBelow >= spaceAbove ? "bottom" : "top";
|
|
450
457
|
}
|
|
451
458
|
this.menu.classList.remove("dropdown-top", "dropdown-bottom", "dropdown-start", "dropdown-end");
|
|
@@ -469,6 +476,11 @@ class Dropdown extends Component {
|
|
|
469
476
|
}
|
|
470
477
|
const viewportWidth = window.innerWidth;
|
|
471
478
|
left = Math.max(8, Math.min(left, viewportWidth - menuRect.width - 8));
|
|
479
|
+
const availableHeight = Math.max(120, Math.floor(placement === "top" ? spaceAbove - gap - 16 : spaceBelow - gap - 16));
|
|
480
|
+
if (this.menu.scrollHeight > availableHeight || availableHeight < 300) {
|
|
481
|
+
this.menu.style.maxHeight = `${availableHeight}px`;
|
|
482
|
+
this.menu.style.overflowY = "auto";
|
|
483
|
+
}
|
|
472
484
|
this.menu.style.top = `${top + window.scrollY}px`;
|
|
473
485
|
this.menu.style.left = `${left + window.scrollX}px`;
|
|
474
486
|
this.menu.style.bottom = "auto";
|
|
@@ -491,9 +503,30 @@ class Dropdown extends Component {
|
|
|
491
503
|
this.menu.classList.add("dropdown-open");
|
|
492
504
|
this.trigger?.setAttribute("aria-expanded", "true");
|
|
493
505
|
this.position();
|
|
494
|
-
this.scrollHandler = () =>
|
|
495
|
-
|
|
506
|
+
this.scrollHandler = (event) => {
|
|
507
|
+
if ((this.menu && (this.menu.matches(":hover") || this.menu.contains(document.activeElement))) ||
|
|
508
|
+
(this.trigger && this.trigger.matches(":hover")) ||
|
|
509
|
+
(event?.target && this.menu && (this.menu === event.target || this.menu.contains(event.target)))) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
this.position();
|
|
513
|
+
};
|
|
514
|
+
window.addEventListener("scroll", this.scrollHandler, { passive: true, capture: true });
|
|
496
515
|
window.addEventListener("resize", this.scrollHandler);
|
|
516
|
+
this.wheelHandler = (e) => {
|
|
517
|
+
if (!this.menu)
|
|
518
|
+
return;
|
|
519
|
+
const { deltaY } = e;
|
|
520
|
+
if (deltaY === 0)
|
|
521
|
+
return;
|
|
522
|
+
const atTop = this.menu.scrollTop <= 0;
|
|
523
|
+
const atBottom = this.menu.scrollTop + this.menu.clientHeight >= this.menu.scrollHeight - 1;
|
|
524
|
+
if ((deltaY < 0 && atTop) || (deltaY > 0 && atBottom) || this.menu.scrollHeight <= this.menu.clientHeight) {
|
|
525
|
+
e.preventDefault();
|
|
526
|
+
e.stopPropagation();
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
this.menu.addEventListener("wheel", this.wheelHandler, { passive: false });
|
|
497
530
|
emit(this.element, "dropdown:show", { instance: this });
|
|
498
531
|
return this;
|
|
499
532
|
}
|
|
@@ -502,10 +535,14 @@ class Dropdown extends Component {
|
|
|
502
535
|
return this;
|
|
503
536
|
this.isOpen = false;
|
|
504
537
|
if (this.scrollHandler) {
|
|
505
|
-
window.removeEventListener("scroll", this.scrollHandler);
|
|
538
|
+
window.removeEventListener("scroll", this.scrollHandler, { capture: true });
|
|
506
539
|
window.removeEventListener("resize", this.scrollHandler);
|
|
507
540
|
this.scrollHandler = null;
|
|
508
541
|
}
|
|
542
|
+
if (this.wheelHandler && this.menu) {
|
|
543
|
+
this.menu.removeEventListener("wheel", this.wheelHandler);
|
|
544
|
+
this.wheelHandler = null;
|
|
545
|
+
}
|
|
509
546
|
this.menu.classList.remove("dropdown-open", "dropdown-top", "dropdown-bottom", "dropdown-start", "dropdown-end");
|
|
510
547
|
this.trigger?.setAttribute("aria-expanded", "false");
|
|
511
548
|
if (this.originalParent) {
|
|
@@ -954,6 +991,7 @@ class Tabs extends Component {
|
|
|
954
991
|
this.show(next);
|
|
955
992
|
}
|
|
956
993
|
});
|
|
994
|
+
this.bindSliderGestures();
|
|
957
995
|
this.activateInitial();
|
|
958
996
|
}
|
|
959
997
|
updateOptions(options = {}) {
|
|
@@ -993,29 +1031,128 @@ class Tabs extends Component {
|
|
|
993
1031
|
if (initial)
|
|
994
1032
|
this.show(initial);
|
|
995
1033
|
}
|
|
996
|
-
|
|
1034
|
+
getSliderTrack() {
|
|
1035
|
+
const wrapper = this.element.closest(".tabs-wrapper") ||
|
|
1036
|
+
this.element.closest(".feed-main-col, main, .page-container, [data-tabs-wrapper]") ||
|
|
1037
|
+
this.element.parentElement;
|
|
1038
|
+
return (wrapper?.querySelector(".tabs-slider-track, [data-tabs-slider]") ||
|
|
1039
|
+
document.querySelector(".tabs-slider-track, [data-tabs-slider]"));
|
|
1040
|
+
}
|
|
1041
|
+
bindSliderGestures() {
|
|
1042
|
+
const track = this.getSliderTrack();
|
|
1043
|
+
if (!track || track.__sliderGesturesBound)
|
|
1044
|
+
return;
|
|
1045
|
+
track.__sliderGesturesBound = true;
|
|
1046
|
+
let startX = 0;
|
|
1047
|
+
let startY = 0;
|
|
1048
|
+
let deltaX = 0;
|
|
1049
|
+
let deltaY = 0;
|
|
1050
|
+
let isSwiping = false;
|
|
1051
|
+
let isVertical = false;
|
|
1052
|
+
track.addEventListener("touchstart", (e) => {
|
|
1053
|
+
if (e.touches.length !== 1)
|
|
1054
|
+
return;
|
|
1055
|
+
const target = e.target;
|
|
1056
|
+
if (target?.closest("input, textarea, select, [contenteditable='true'], video, audio, .carousel, .bento-carousel, .media-grid-scroll, .map-canvas-container, [data-prevent-swipe]")) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const touch = e.touches[0];
|
|
1060
|
+
startX = touch.clientX;
|
|
1061
|
+
startY = touch.clientY;
|
|
1062
|
+
deltaX = 0;
|
|
1063
|
+
deltaY = 0;
|
|
1064
|
+
isSwiping = false;
|
|
1065
|
+
isVertical = false;
|
|
1066
|
+
}, { passive: true });
|
|
1067
|
+
track.addEventListener("touchmove", (e) => {
|
|
1068
|
+
if (e.touches.length !== 1 || isVertical)
|
|
1069
|
+
return;
|
|
1070
|
+
const touch = e.touches[0];
|
|
1071
|
+
deltaX = touch.clientX - startX;
|
|
1072
|
+
deltaY = touch.clientY - startY;
|
|
1073
|
+
if (!isSwiping) {
|
|
1074
|
+
if (Math.abs(deltaY) > 8 && Math.abs(deltaY) > Math.abs(deltaX)) {
|
|
1075
|
+
isVertical = true;
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (Math.abs(deltaX) > 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
1079
|
+
isSwiping = true;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (isSwiping) {
|
|
1083
|
+
if (e.cancelable)
|
|
1084
|
+
e.preventDefault();
|
|
1085
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1086
|
+
const maxIdx = Math.max(0, tabs.length - 1);
|
|
1087
|
+
let currentDelta = deltaX;
|
|
1088
|
+
const currentIdx = tabs.findIndex((t) => t.classList.contains("active"));
|
|
1089
|
+
const safeIdx = currentIdx >= 0 ? currentIdx : 0;
|
|
1090
|
+
if ((safeIdx === 0 && currentDelta > 0) || (safeIdx === maxIdx && currentDelta < 0)) {
|
|
1091
|
+
currentDelta = currentDelta * 0.3;
|
|
1092
|
+
}
|
|
1093
|
+
track.style.transition = "none";
|
|
1094
|
+
track.style.transform = `translate3d(calc(-${safeIdx * 100}% + ${currentDelta}px), 0, 0)`;
|
|
1095
|
+
}
|
|
1096
|
+
}, { passive: false });
|
|
1097
|
+
const onTouchEnd = () => {
|
|
1098
|
+
if (!isSwiping)
|
|
1099
|
+
return;
|
|
1100
|
+
isSwiping = false;
|
|
1101
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1102
|
+
const maxIdx = Math.max(0, tabs.length - 1);
|
|
1103
|
+
const currentIdx = tabs.findIndex((t) => t.classList.contains("active"));
|
|
1104
|
+
const safeIdx = currentIdx >= 0 ? currentIdx : 0;
|
|
1105
|
+
track.style.transition = "transform 0.3s cubic-bezier(0.25, 1, 0.5, 1)";
|
|
1106
|
+
if (deltaX < -50 && safeIdx < maxIdx) {
|
|
1107
|
+
this.show(tabs[safeIdx + 1]);
|
|
1108
|
+
}
|
|
1109
|
+
else if (deltaX > 50 && safeIdx > 0) {
|
|
1110
|
+
this.show(tabs[safeIdx - 1]);
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
track.style.transform = `translate3d(-${safeIdx * 100}%, 0, 0)`;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
track.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
1117
|
+
track.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
1118
|
+
}
|
|
1119
|
+
show(tab, animate = true) {
|
|
997
1120
|
const selector = tab.getAttribute("data-tabs");
|
|
998
1121
|
const panel = selector ? document.querySelector(selector) : null;
|
|
999
1122
|
if (!panel)
|
|
1000
1123
|
return this;
|
|
1001
|
-
this.element.querySelectorAll(this.tabSelector)
|
|
1124
|
+
const tabs = Array.from(this.element.querySelectorAll(this.tabSelector));
|
|
1125
|
+
const targetIndex = tabs.indexOf(tab);
|
|
1126
|
+
const track = this.getSliderTrack();
|
|
1127
|
+
const isSlider = Boolean(track && track.contains(panel));
|
|
1128
|
+
tabs.forEach((item) => {
|
|
1002
1129
|
const selected = item === tab;
|
|
1003
1130
|
item.setAttribute("role", item.getAttribute("role") || "tab");
|
|
1004
1131
|
item.setAttribute("aria-selected", String(selected));
|
|
1132
|
+
item.classList.toggle("active", selected);
|
|
1005
1133
|
item.tabIndex = selected ? 0 : -1;
|
|
1006
1134
|
const targetSelector = item.getAttribute("data-tabs");
|
|
1007
1135
|
const target = targetSelector ? document.querySelector(targetSelector) : null;
|
|
1008
|
-
if (target)
|
|
1009
|
-
target.
|
|
1136
|
+
if (target) {
|
|
1137
|
+
target.classList.toggle("active", selected);
|
|
1138
|
+
if (!isSlider) {
|
|
1139
|
+
target.hidden = !selected;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1010
1142
|
});
|
|
1011
|
-
if (this.panelSelector) {
|
|
1143
|
+
if (this.panelSelector && !isSlider) {
|
|
1012
1144
|
document.querySelectorAll(this.panelSelector).forEach((item) => {
|
|
1013
1145
|
if (item !== panel)
|
|
1014
1146
|
item.hidden = true;
|
|
1015
1147
|
});
|
|
1016
1148
|
}
|
|
1017
1149
|
panel.setAttribute("role", panel.getAttribute("role") || "tabpanel");
|
|
1018
|
-
|
|
1150
|
+
if (isSlider && track && targetIndex >= 0) {
|
|
1151
|
+
track.style.transition = animate ? "transform 0.3s cubic-bezier(0.25, 1, 0.5, 1)" : "none";
|
|
1152
|
+
track.style.transform = `translate3d(-${targetIndex * 100}%, 0, 0)`;
|
|
1153
|
+
}
|
|
1154
|
+
tab.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
|
|
1155
|
+
emit(this.element, "tabs:show", { tab, panel, index: targetIndex, instance: this });
|
|
1019
1156
|
return this;
|
|
1020
1157
|
}
|
|
1021
1158
|
}
|