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
+ }
@@ -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
+ }