@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 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
- show(tab: HTMLElement): this;
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;
@@ -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 = () => this.position();
494
- window.addEventListener("scroll", this.scrollHandler, { passive: true });
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
- show(tab) {
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).forEach((item) => {
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.hidden = !selected;
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
- emit(this.element, "tabs:show", { tab, panel, instance: this });
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 = () => this.position();
495
- window.addEventListener("scroll", this.scrollHandler, { passive: true });
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
- show(tab) {
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).forEach((item) => {
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.hidden = !selected;
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
- emit(this.element, "tabs:show", { tab, panel, instance: this });
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
  }