nexa-mobile 0.6.0
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 @@
|
|
|
1
|
+
export declare const NBottomSheet: import("nexa-runtime").Component;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineComponent, h, onMounted, onUnmounted } from 'nexa-runtime';
|
|
2
|
+
import { signal, computed, effect } from 'nexa-reactivity';
|
|
3
|
+
import { useGestures } from '../gestures/GestureManager.js';
|
|
4
|
+
export const NBottomSheet = defineComponent({
|
|
5
|
+
name: 'NBottomSheet',
|
|
6
|
+
props: {
|
|
7
|
+
show: { type: Boolean, default: false }
|
|
8
|
+
},
|
|
9
|
+
emits: ['close'],
|
|
10
|
+
setup(props, { emit, slots }) {
|
|
11
|
+
const gestures = signal(null);
|
|
12
|
+
// Internal state
|
|
13
|
+
const yOffset = signal(0);
|
|
14
|
+
const isDragging = signal(false);
|
|
15
|
+
onMounted(() => {
|
|
16
|
+
const el = document.getElementById('nexa-bottom-sheet-content');
|
|
17
|
+
if (el) {
|
|
18
|
+
const g = useGestures(el);
|
|
19
|
+
gestures.value = g;
|
|
20
|
+
effect(() => {
|
|
21
|
+
if (g.isSwiping.value && g.swipeDirection.value === 'down') {
|
|
22
|
+
isDragging.value = true;
|
|
23
|
+
yOffset.value = Math.max(0, g.deltaY.value);
|
|
24
|
+
}
|
|
25
|
+
else if (!g.isPressed.value && isDragging.value) {
|
|
26
|
+
// Check if we should close
|
|
27
|
+
if (yOffset.value > 100) {
|
|
28
|
+
emit('close');
|
|
29
|
+
}
|
|
30
|
+
isDragging.value = false;
|
|
31
|
+
yOffset.value = 0;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
onUnmounted(() => {
|
|
37
|
+
gestures.value?.destroy();
|
|
38
|
+
});
|
|
39
|
+
const style = computed(() => ({
|
|
40
|
+
transform: `translateY(${props.show ? (yOffset.value + 'px') : '100%'})`,
|
|
41
|
+
transition: isDragging.value ? 'none' : 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
bottom: '0',
|
|
44
|
+
left: '0',
|
|
45
|
+
right: '0',
|
|
46
|
+
background: '#1e293b',
|
|
47
|
+
borderTopLeftRadius: '1.5rem',
|
|
48
|
+
borderTopRightRadius: '1.5rem',
|
|
49
|
+
boxShadow: '0 -10px 25px -5px rgba(0, 0, 0, 0.3)',
|
|
50
|
+
zIndex: '1000',
|
|
51
|
+
padding: '1.5rem',
|
|
52
|
+
touchAction: 'none',
|
|
53
|
+
display: props.show || isDragging.value ? 'block' : 'none'
|
|
54
|
+
}));
|
|
55
|
+
const overlayStyle = computed(() => ({
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
top: '0',
|
|
58
|
+
left: '0',
|
|
59
|
+
right: '0',
|
|
60
|
+
bottom: '0',
|
|
61
|
+
background: 'rgba(0,0,0,0.5)',
|
|
62
|
+
display: props.show ? 'block' : 'none',
|
|
63
|
+
zIndex: '999',
|
|
64
|
+
opacity: props.show ? (1 - (yOffset.value / 400)) : 0,
|
|
65
|
+
transition: 'opacity 0.3s ease',
|
|
66
|
+
pointerEvents: props.show ? 'auto' : 'none'
|
|
67
|
+
}));
|
|
68
|
+
return () => h('div', { class: 'n-bottom-sheet-wrapper' }, [
|
|
69
|
+
h('div', {
|
|
70
|
+
style: overlayStyle.value,
|
|
71
|
+
onClick: () => emit('close')
|
|
72
|
+
}),
|
|
73
|
+
h('div', {
|
|
74
|
+
id: 'nexa-bottom-sheet-content',
|
|
75
|
+
style: style.value
|
|
76
|
+
}, [
|
|
77
|
+
// Handle bar
|
|
78
|
+
h('div', {
|
|
79
|
+
style: {
|
|
80
|
+
width: '40px',
|
|
81
|
+
height: '4px',
|
|
82
|
+
background: '#475569',
|
|
83
|
+
borderRadius: '2px',
|
|
84
|
+
margin: '0 auto 1.5rem auto'
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
87
|
+
slots.default?.()
|
|
88
|
+
])
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type SwipeDirection = 'left' | 'right' | 'up' | 'down' | null;
|
|
2
|
+
export declare class GestureManager {
|
|
3
|
+
private el;
|
|
4
|
+
private startX;
|
|
5
|
+
private startY;
|
|
6
|
+
isSwiping: import("nexa-reactivity").Signal<boolean>;
|
|
7
|
+
swipeDirection: import("nexa-reactivity").Signal<SwipeDirection>;
|
|
8
|
+
deltaX: import("nexa-reactivity").Signal<number>;
|
|
9
|
+
deltaY: import("nexa-reactivity").Signal<number>;
|
|
10
|
+
isPressed: import("nexa-reactivity").Signal<boolean>;
|
|
11
|
+
private boundHandleStart;
|
|
12
|
+
private boundHandleMove;
|
|
13
|
+
private boundHandleEnd;
|
|
14
|
+
constructor(el: HTMLElement);
|
|
15
|
+
private handleStart;
|
|
16
|
+
private handleMove;
|
|
17
|
+
private handleEnd;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function useGestures(el: HTMLElement): GestureManager;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { signal, batch } from 'nexa-reactivity';
|
|
2
|
+
export class GestureManager {
|
|
3
|
+
el;
|
|
4
|
+
startX = 0;
|
|
5
|
+
startY = 0;
|
|
6
|
+
isSwiping = signal(false);
|
|
7
|
+
swipeDirection = signal(null);
|
|
8
|
+
deltaX = signal(0);
|
|
9
|
+
deltaY = signal(0);
|
|
10
|
+
isPressed = signal(false);
|
|
11
|
+
boundHandleStart;
|
|
12
|
+
boundHandleMove;
|
|
13
|
+
boundHandleEnd;
|
|
14
|
+
constructor(el) {
|
|
15
|
+
this.el = el;
|
|
16
|
+
this.boundHandleStart = this.handleStart.bind(this);
|
|
17
|
+
this.boundHandleMove = this.handleMove.bind(this);
|
|
18
|
+
this.boundHandleEnd = this.handleEnd.bind(this);
|
|
19
|
+
this.el.addEventListener('touchstart', this.boundHandleStart, { passive: true });
|
|
20
|
+
this.el.addEventListener('touchmove', this.boundHandleMove, { passive: false });
|
|
21
|
+
this.el.addEventListener('touchend', this.boundHandleEnd, { passive: true });
|
|
22
|
+
this.el.addEventListener('touchcancel', this.boundHandleEnd, { passive: true });
|
|
23
|
+
}
|
|
24
|
+
handleStart(e) {
|
|
25
|
+
const touch = e.touches[0];
|
|
26
|
+
this.startX = touch.clientX;
|
|
27
|
+
this.startY = touch.clientY;
|
|
28
|
+
batch(() => {
|
|
29
|
+
this.isPressed.value = true;
|
|
30
|
+
this.isSwiping.value = false;
|
|
31
|
+
this.swipeDirection.value = null;
|
|
32
|
+
this.deltaX.value = 0;
|
|
33
|
+
this.deltaY.value = 0;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
handleMove(e) {
|
|
37
|
+
const touch = e.touches[0];
|
|
38
|
+
const dx = touch.clientX - this.startX;
|
|
39
|
+
const dy = touch.clientY - this.startY;
|
|
40
|
+
batch(() => {
|
|
41
|
+
this.deltaX.value = dx;
|
|
42
|
+
this.deltaY.value = dy;
|
|
43
|
+
if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
|
|
44
|
+
this.isSwiping.value = true;
|
|
45
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
46
|
+
this.swipeDirection.value = dx > 0 ? 'right' : 'left';
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.swipeDirection.value = dy > 0 ? 'down' : 'up';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
handleEnd() {
|
|
55
|
+
batch(() => {
|
|
56
|
+
this.isPressed.value = false;
|
|
57
|
+
// Keep swiping state for a moment or clear it?
|
|
58
|
+
// For now, clear it.
|
|
59
|
+
this.isSwiping.value = false;
|
|
60
|
+
this.swipeDirection.value = null;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
destroy() {
|
|
64
|
+
this.el.removeEventListener('touchstart', this.boundHandleStart);
|
|
65
|
+
this.el.removeEventListener('touchmove', this.boundHandleMove);
|
|
66
|
+
this.el.removeEventListener('touchend', this.boundHandleEnd);
|
|
67
|
+
this.el.removeEventListener('touchcancel', this.boundHandleEnd);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function useGestures(el) {
|
|
71
|
+
return new GestureManager(el);
|
|
72
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './gestures/GestureManager.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './gestures/GestureManager.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nexa-mobile",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"nexa-reactivity": "0.6.0",
|
|
17
|
+
"nexa-runtime": "0.6.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"clean": "rm -rf dist"
|
|
28
|
+
}
|
|
29
|
+
}
|