ketekny-ui-kit 1.0.117 → 1.0.118

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.117",
4
+ "version": "1.0.118",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,8 +1,15 @@
1
1
  <template>
2
- <main class="flex min-h-0 w-full max-w-none flex-1 flex-col overflow-hidden bg-slate-100">
2
+ <main
3
+ class="flex min-h-0 w-full max-w-none flex-1 flex-col overflow-hidden bg-slate-100"
4
+ @touchstart.passive="handleTouchStart"
5
+ @touchmove.passive="handleTouchMove"
6
+ @touchend="handleTouchEnd"
7
+ @touchcancel="resetTouchState"
8
+ >
3
9
  <div
4
- v-if="mobileSidebarOpen && !isDesktopViewport"
10
+ v-if="showMobileOverlay"
5
11
  class="fixed inset-0 z-20 bg-slate-950/40 backdrop-blur-[1px] lg:hidden"
12
+ :style="mobileOverlayStyle"
6
13
  @click="toggleMobileSidebar"
7
14
  />
8
15
 
@@ -70,7 +77,7 @@
70
77
  v-if="showSidebar"
71
78
  class="min-h-0 shrink-0 overflow-y-auto bg-slate-100 px-4 sm:px-6 lg:ml-8 lg:pr-4 lg:pl-0"
72
79
  :class="mobileSidebarClass"
73
- :style="sidebarStyle"
80
+ :style="mobileSidebarStyle"
74
81
  >
75
82
  <slot name="sidebar" />
76
83
  </aside>
@@ -88,6 +95,9 @@ import kButton from '../ui/kButton.vue'
88
95
 
89
96
  const LG_VIEWPORT_QUERY = '(min-width: 1024px)'
90
97
  const MAIN_MENU_TOGGLE_EVENT = 'ketekny:two-col-layout-mobile-menu'
98
+ const MOBILE_EDGE_SWIPE_THRESHOLD = 72
99
+ const MOBILE_SWIPE_OPEN_DISTANCE = 52
100
+ const MOBILE_SWIPE_VERTICAL_TOLERANCE = 48
91
101
 
92
102
  export default {
93
103
  name: 'PageTwoColShell',
@@ -119,6 +129,12 @@ export default {
119
129
  isDesktopViewport: true,
120
130
  mobileSidebarOpen: false,
121
131
  viewportQuery: null,
132
+ touchGesture: {
133
+ startX: 0,
134
+ startY: 0,
135
+ currentX: 0,
136
+ trackingEdgeSwipe: false,
137
+ },
122
138
  }
123
139
  },
124
140
  computed: {
@@ -143,10 +159,38 @@ export default {
143
159
  },
144
160
  mobileSidebarClass() {
145
161
  if (this.isDesktopViewport) return ''
146
- return 'fixed left-0 top-0 z-20 h-full max-w-[85vw] border-r border-slate-200 bg-slate-100 py-4 shadow-xl shadow-slate-900/20'
162
+ return 'fixed left-0 top-0 z-20 h-full max-w-[85vw] border-r border-slate-200 bg-slate-100 py-4 shadow-xl shadow-slate-900/20 transition-transform duration-200 ease-out'
163
+ },
164
+ dragOffset() {
165
+ if (!this.touchGesture.trackingEdgeSwipe) return 0
166
+ return Math.max(0, this.touchGesture.currentX - this.touchGesture.startX)
167
+ },
168
+ mobileSidebarStyle() {
169
+ const baseStyle = this.sidebarStyle
170
+ if (this.isDesktopViewport || this.mobileSidebarOpen || !this.touchGesture.trackingEdgeSwipe) {
171
+ return baseStyle
172
+ }
173
+ const widthValue = Number.parseFloat(this.sidebarWidth)
174
+ const effectiveWidth = Number.isFinite(widthValue) ? widthValue : 300
175
+ const clampedOffset = Math.min(this.dragOffset, effectiveWidth)
176
+ return {
177
+ ...baseStyle,
178
+ transform: `translateX(${clampedOffset - effectiveWidth}px)`,
179
+ transition: 'none',
180
+ }
181
+ },
182
+ showMobileOverlay() {
183
+ return !this.isDesktopViewport && (this.mobileSidebarOpen || this.touchGesture.trackingEdgeSwipe)
184
+ },
185
+ mobileOverlayStyle() {
186
+ if (this.mobileSidebarOpen || !this.touchGesture.trackingEdgeSwipe) return null
187
+ const progress = Math.min(this.dragOffset / MOBILE_SWIPE_OPEN_DISTANCE, 1) * 0.4
188
+ return {
189
+ opacity: String(progress),
190
+ }
147
191
  },
148
192
  showSidebar() {
149
- return this.isDesktopViewport || this.mobileSidebarOpen
193
+ return this.isDesktopViewport || this.mobileSidebarOpen || this.touchGesture.trackingEdgeSwipe
150
194
  },
151
195
  },
152
196
  mounted() {
@@ -175,11 +219,55 @@ export default {
175
219
  syncSidebarForViewport() {
176
220
  if (!this.isDesktopViewport) {
177
221
  this.mobileSidebarOpen = false
222
+ this.resetTouchState()
178
223
  }
179
224
  },
180
225
  toggleMobileSidebar() {
181
226
  if (this.isDesktopViewport) return
182
227
  this.mobileSidebarOpen = !this.mobileSidebarOpen
228
+ this.resetTouchState()
229
+ },
230
+ handleTouchStart(event) {
231
+ if (this.isDesktopViewport || this.mobileSidebarOpen) {
232
+ this.resetTouchState()
233
+ return
234
+ }
235
+ const touch = event.touches?.[0]
236
+ if (!touch) return
237
+ this.touchGesture = {
238
+ startX: touch.clientX,
239
+ startY: touch.clientY,
240
+ currentX: touch.clientX,
241
+ trackingEdgeSwipe: touch.clientX <= MOBILE_EDGE_SWIPE_THRESHOLD,
242
+ }
243
+ },
244
+ handleTouchMove(event) {
245
+ if (!this.touchGesture.trackingEdgeSwipe) return
246
+ const touch = event.touches?.[0]
247
+ if (!touch) return
248
+ this.touchGesture.currentX = touch.clientX
249
+ if (Math.abs(touch.clientY - this.touchGesture.startY) > MOBILE_SWIPE_VERTICAL_TOLERANCE) {
250
+ this.resetTouchState()
251
+ }
252
+ },
253
+ handleTouchEnd() {
254
+ if (!this.touchGesture.trackingEdgeSwipe) {
255
+ this.resetTouchState()
256
+ return
257
+ }
258
+ const deltaX = this.touchGesture.currentX - this.touchGesture.startX
259
+ if (deltaX >= MOBILE_SWIPE_OPEN_DISTANCE) {
260
+ this.mobileSidebarOpen = true
261
+ }
262
+ this.resetTouchState()
263
+ },
264
+ resetTouchState() {
265
+ this.touchGesture = {
266
+ startX: 0,
267
+ startY: 0,
268
+ currentX: 0,
269
+ trackingEdgeSwipe: false,
270
+ }
183
271
  },
184
272
  openExternalUrl() {
185
273
  const target = String(this.externalUrl || '').trim()
@@ -58,7 +58,6 @@ import MultilevelSidebar from "./MultilevelSidebar.vue";
58
58
 
59
59
  const LG_VIEWPORT_QUERY = "(min-width: 1024px)";
60
60
  const MAIN_MENU_TOGGLE_EVENT = "ketekny:two-col-layout-mobile-menu";
61
-
62
61
  export default {
63
62
  name: "TwoColLayout",
64
63
  components: {