@abydahana/aksara-ui 1.2.3 → 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
@@ -24271,6 +24271,28 @@ img,svg,video {
24271
24271
  .tab-panel {
24272
24272
  padding:1rem 0
24273
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
+ }
24274
24296
  .tab[aria-selected="true"] {
24275
24297
  background:var(--aksara-bg-subtle);
24276
24298
  color:var(--aksara-text-body)
package/dist/aksara.d.ts CHANGED
@@ -189,7 +189,9 @@ export declare class Tabs extends Component {
189
189
  constructor(element: HTMLElement, options?: TabsOptions);
190
190
  updateOptions(options?: TabsOptions): this;
191
191
  activateInitial(): void;
192
- show(tab: HTMLElement): this;
192
+ getSliderTrack(): HTMLElement | null;
193
+ bindSliderGestures(): void;
194
+ show(tab: HTMLElement, animate?: boolean): this;
193
195
  }
194
196
  export declare class Toast extends Component {
195
197
  static componentName: string;
@@ -990,6 +990,7 @@ export class Tabs extends Component {
990
990
  this.show(next);
991
991
  }
992
992
  });
993
+ this.bindSliderGestures();
993
994
  this.activateInitial();
994
995
  }
995
996
  updateOptions(options = {}) {
@@ -1029,29 +1030,128 @@ export class Tabs extends Component {
1029
1030
  if (initial)
1030
1031
  this.show(initial);
1031
1032
  }
1032
- 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) {
1033
1119
  const selector = tab.getAttribute("data-tabs");
1034
1120
  const panel = selector ? document.querySelector(selector) : null;
1035
1121
  if (!panel)
1036
1122
  return this;
1037
- 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) => {
1038
1128
  const selected = item === tab;
1039
1129
  item.setAttribute("role", item.getAttribute("role") || "tab");
1040
1130
  item.setAttribute("aria-selected", String(selected));
1131
+ item.classList.toggle("active", selected);
1041
1132
  item.tabIndex = selected ? 0 : -1;
1042
1133
  const targetSelector = item.getAttribute("data-tabs");
1043
1134
  const target = targetSelector ? document.querySelector(targetSelector) : null;
1044
- if (target)
1045
- target.hidden = !selected;
1135
+ if (target) {
1136
+ target.classList.toggle("active", selected);
1137
+ if (!isSlider) {
1138
+ target.hidden = !selected;
1139
+ }
1140
+ }
1046
1141
  });
1047
- if (this.panelSelector) {
1142
+ if (this.panelSelector && !isSlider) {
1048
1143
  document.querySelectorAll(this.panelSelector).forEach((item) => {
1049
1144
  if (item !== panel)
1050
1145
  item.hidden = true;
1051
1146
  });
1052
1147
  }
1053
1148
  panel.setAttribute("role", panel.getAttribute("role") || "tabpanel");
1054
- 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 });
1055
1155
  return this;
1056
1156
  }
1057
1157
  }
package/dist/aksara.js CHANGED
@@ -991,6 +991,7 @@ class Tabs extends Component {
991
991
  this.show(next);
992
992
  }
993
993
  });
994
+ this.bindSliderGestures();
994
995
  this.activateInitial();
995
996
  }
996
997
  updateOptions(options = {}) {
@@ -1030,29 +1031,128 @@ class Tabs extends Component {
1030
1031
  if (initial)
1031
1032
  this.show(initial);
1032
1033
  }
1033
- 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) {
1034
1120
  const selector = tab.getAttribute("data-tabs");
1035
1121
  const panel = selector ? document.querySelector(selector) : null;
1036
1122
  if (!panel)
1037
1123
  return this;
1038
- 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) => {
1039
1129
  const selected = item === tab;
1040
1130
  item.setAttribute("role", item.getAttribute("role") || "tab");
1041
1131
  item.setAttribute("aria-selected", String(selected));
1132
+ item.classList.toggle("active", selected);
1042
1133
  item.tabIndex = selected ? 0 : -1;
1043
1134
  const targetSelector = item.getAttribute("data-tabs");
1044
1135
  const target = targetSelector ? document.querySelector(targetSelector) : null;
1045
- if (target)
1046
- target.hidden = !selected;
1136
+ if (target) {
1137
+ target.classList.toggle("active", selected);
1138
+ if (!isSlider) {
1139
+ target.hidden = !selected;
1140
+ }
1141
+ }
1047
1142
  });
1048
- if (this.panelSelector) {
1143
+ if (this.panelSelector && !isSlider) {
1049
1144
  document.querySelectorAll(this.panelSelector).forEach((item) => {
1050
1145
  if (item !== panel)
1051
1146
  item.hidden = true;
1052
1147
  });
1053
1148
  }
1054
1149
  panel.setAttribute("role", panel.getAttribute("role") || "tabpanel");
1055
- 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 });
1056
1156
  return this;
1057
1157
  }
1058
1158
  }