@flightdev/mobile 0.2.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,208 @@
1
+ import { ref, onMounted, onUnmounted } from 'vue';
2
+
3
+ // src/vue/index.ts
4
+
5
+ // src/platform.ts
6
+ function getCapacitor() {
7
+ try {
8
+ const Capacitor = globalThis.Capacitor;
9
+ return Capacitor || null;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function getPlatform() {
15
+ const Capacitor = getCapacitor();
16
+ if (!Capacitor) {
17
+ return {
18
+ platform: "web",
19
+ isNative: false,
20
+ isIOS: false,
21
+ isAndroid: false,
22
+ isWeb: true,
23
+ hasCapacitor: false
24
+ };
25
+ }
26
+ const platform = Capacitor.getPlatform();
27
+ const isNative = Capacitor.isNativePlatform();
28
+ return {
29
+ platform,
30
+ isNative,
31
+ isIOS: platform === "ios",
32
+ isAndroid: platform === "android",
33
+ isWeb: platform === "web",
34
+ hasCapacitor: true
35
+ };
36
+ }
37
+ function isPluginAvailable(pluginName) {
38
+ const Capacitor = getCapacitor();
39
+ if (!Capacitor || !Capacitor.isPluginAvailable) {
40
+ return false;
41
+ }
42
+ return Capacitor.isPluginAvailable(pluginName);
43
+ }
44
+
45
+ // src/vue/index.ts
46
+ function usePlatform() {
47
+ return getPlatform();
48
+ }
49
+ function useCamera() {
50
+ const available = isPluginAvailable("Camera");
51
+ const takePhoto = async (options) => {
52
+ if (!available) return null;
53
+ try {
54
+ const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera');
55
+ return await Camera.getPhoto({
56
+ quality: options?.quality ?? 90,
57
+ allowEditing: options?.allowEditing ?? false,
58
+ resultType: CameraResultType.Uri,
59
+ source: CameraSource.Camera
60
+ });
61
+ } catch {
62
+ return null;
63
+ }
64
+ };
65
+ const pickImage = async (options) => {
66
+ if (!available) return null;
67
+ try {
68
+ const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera');
69
+ return await Camera.getPhoto({
70
+ quality: options?.quality ?? 90,
71
+ resultType: CameraResultType.Uri,
72
+ source: CameraSource.Photos
73
+ });
74
+ } catch {
75
+ return null;
76
+ }
77
+ };
78
+ return { takePhoto, pickImage, available };
79
+ }
80
+ function useGeolocation() {
81
+ const position = ref(null);
82
+ const error = ref(null);
83
+ const loading = ref(false);
84
+ const available = isPluginAvailable("Geolocation");
85
+ const getCurrentPosition = async () => {
86
+ if (!available) return null;
87
+ loading.value = true;
88
+ error.value = null;
89
+ try {
90
+ const { Geolocation } = await import('@capacitor/geolocation');
91
+ const pos = await Geolocation.getCurrentPosition();
92
+ position.value = pos;
93
+ return pos;
94
+ } catch (e) {
95
+ error.value = e;
96
+ return null;
97
+ } finally {
98
+ loading.value = false;
99
+ }
100
+ };
101
+ return { position, error, loading, getCurrentPosition, available };
102
+ }
103
+ function usePushNotifications() {
104
+ const token = ref(null);
105
+ const available = isPluginAvailable("PushNotifications");
106
+ onMounted(async () => {
107
+ if (!available) return;
108
+ const { PushNotifications } = await import('@capacitor/push-notifications');
109
+ await PushNotifications.addListener("registration", (t) => {
110
+ token.value = t.value;
111
+ });
112
+ });
113
+ onUnmounted(async () => {
114
+ if (!available) return;
115
+ const { PushNotifications } = await import('@capacitor/push-notifications');
116
+ await PushNotifications.removeAllListeners();
117
+ });
118
+ const requestPermission = async () => {
119
+ if (!available) return false;
120
+ try {
121
+ const { PushNotifications } = await import('@capacitor/push-notifications');
122
+ const result = await PushNotifications.requestPermissions();
123
+ if (result.receive === "granted") {
124
+ await PushNotifications.register();
125
+ return true;
126
+ }
127
+ return false;
128
+ } catch {
129
+ return false;
130
+ }
131
+ };
132
+ return { token, requestPermission, available };
133
+ }
134
+ function useHaptics() {
135
+ const available = isPluginAvailable("Haptics");
136
+ const impact = async (style = "medium") => {
137
+ if (!available) return;
138
+ try {
139
+ const { Haptics, ImpactStyle } = await import('@capacitor/haptics');
140
+ const styleMap = { light: ImpactStyle.Light, medium: ImpactStyle.Medium, heavy: ImpactStyle.Heavy };
141
+ await Haptics.impact({ style: styleMap[style] });
142
+ } catch {
143
+ }
144
+ };
145
+ const notification = async (type = "success") => {
146
+ if (!available) return;
147
+ try {
148
+ const { Haptics, NotificationType } = await import('@capacitor/haptics');
149
+ const typeMap = { success: NotificationType.Success, warning: NotificationType.Warning, error: NotificationType.Error };
150
+ await Haptics.notification({ type: typeMap[type] });
151
+ } catch {
152
+ }
153
+ };
154
+ const vibrate = async (duration = 300) => {
155
+ if (!available) return;
156
+ try {
157
+ const { Haptics } = await import('@capacitor/haptics');
158
+ await Haptics.vibrate({ duration });
159
+ } catch {
160
+ }
161
+ };
162
+ return { impact, notification, vibrate, available };
163
+ }
164
+ function useStorage() {
165
+ const available = isPluginAvailable("Preferences");
166
+ const { isWeb } = usePlatform();
167
+ const get = async (key) => {
168
+ if (isWeb || !available) {
169
+ return localStorage.getItem(key);
170
+ }
171
+ try {
172
+ const { Preferences } = await import('@capacitor/preferences');
173
+ const { value } = await Preferences.get({ key });
174
+ return value;
175
+ } catch {
176
+ return localStorage.getItem(key);
177
+ }
178
+ };
179
+ const set = async (key, value) => {
180
+ if (isWeb || !available) {
181
+ localStorage.setItem(key, value);
182
+ return;
183
+ }
184
+ try {
185
+ const { Preferences } = await import('@capacitor/preferences');
186
+ await Preferences.set({ key, value });
187
+ } catch {
188
+ localStorage.setItem(key, value);
189
+ }
190
+ };
191
+ const remove = async (key) => {
192
+ if (isWeb || !available) {
193
+ localStorage.removeItem(key);
194
+ return;
195
+ }
196
+ try {
197
+ const { Preferences } = await import('@capacitor/preferences');
198
+ await Preferences.remove({ key });
199
+ } catch {
200
+ localStorage.removeItem(key);
201
+ }
202
+ };
203
+ return { get, set, remove, available };
204
+ }
205
+
206
+ export { useCamera, useGeolocation, useHaptics, usePlatform, usePushNotifications, useStorage };
207
+ //# sourceMappingURL=index.js.map
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/platform.ts","../../src/vue/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Platform detection utilities.\r\n * Works without Capacitor installed - gracefully degrades.\r\n */\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type Platform = 'web' | 'ios' | 'android';\r\n\r\nexport interface PlatformInfo {\r\n /** Current platform: 'web', 'ios', or 'android' */\r\n platform: Platform;\r\n /** True if running as a native app (iOS or Android) */\r\n isNative: boolean;\r\n /** True if running on iOS */\r\n isIOS: boolean;\r\n /** True if running on Android */\r\n isAndroid: boolean;\r\n /** True if running as a web app */\r\n isWeb: boolean;\r\n /** True if Capacitor is available */\r\n hasCapacitor: boolean;\r\n}\r\n\r\n// =============================================================================\r\n// Detection\r\n// =============================================================================\r\n\r\n/**\r\n * Get Capacitor instance if available.\r\n * Returns null if Capacitor is not installed.\r\n */\r\nfunction getCapacitor(): any {\r\n try {\r\n // Dynamic import to avoid errors when Capacitor is not installed\r\n const Capacitor = (globalThis as any).Capacitor;\r\n return Capacitor || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Detect the current platform.\r\n * \r\n * @returns Platform information object\r\n * \r\n * @example\r\n * ```typescript\r\n * const { isNative, isIOS, platform } = getPlatform();\r\n * \r\n * if (isNative) {\r\n * // Use native features\r\n * }\r\n * \r\n * if (isIOS) {\r\n * // iOS-specific code\r\n * }\r\n * ```\r\n */\r\nexport function getPlatform(): PlatformInfo {\r\n const Capacitor = getCapacitor();\r\n\r\n if (!Capacitor) {\r\n return {\r\n platform: 'web',\r\n isNative: false,\r\n isIOS: false,\r\n isAndroid: false,\r\n isWeb: true,\r\n hasCapacitor: false,\r\n };\r\n }\r\n\r\n const platform = Capacitor.getPlatform() as Platform;\r\n const isNative = Capacitor.isNativePlatform();\r\n\r\n return {\r\n platform,\r\n isNative,\r\n isIOS: platform === 'ios',\r\n isAndroid: platform === 'android',\r\n isWeb: platform === 'web',\r\n hasCapacitor: true,\r\n };\r\n}\r\n\r\n/**\r\n * Check if a Capacitor plugin is available.\r\n * \r\n * @param pluginName - Name of the plugin to check\r\n * @returns True if the plugin is registered\r\n * \r\n * @example\r\n * ```typescript\r\n * if (isPluginAvailable('Camera')) {\r\n * // Camera plugin is installed\r\n * }\r\n * ```\r\n */\r\nexport function isPluginAvailable(pluginName: string): boolean {\r\n const Capacitor = getCapacitor();\r\n\r\n if (!Capacitor || !Capacitor.isPluginAvailable) {\r\n return false;\r\n }\r\n\r\n return Capacitor.isPluginAvailable(pluginName);\r\n}\r\n\r\n/**\r\n * Get safe area insets for notched devices.\r\n * Returns zeros on web or when not available.\r\n */\r\nexport function getSafeAreaInsets(): {\r\n top: number;\r\n bottom: number;\r\n left: number;\r\n right: number;\r\n} {\r\n // Try to get from CSS env variables\r\n if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {\r\n const style = getComputedStyle(document.documentElement);\r\n return {\r\n top: parseInt(style.getPropertyValue('--sat') || '0', 10),\r\n bottom: parseInt(style.getPropertyValue('--sab') || '0', 10),\r\n left: parseInt(style.getPropertyValue('--sal') || '0', 10),\r\n right: parseInt(style.getPropertyValue('--sar') || '0', 10),\r\n };\r\n }\r\n\r\n return { top: 0, bottom: 0, left: 0, right: 0 };\r\n}\r\n\r\n/**\r\n * Check if running in a PWA installed context.\r\n */\r\nexport function isPWA(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n\r\n return window.matchMedia('(display-mode: standalone)').matches ||\r\n (window.navigator as any).standalone === true;\r\n}\r\n","/**\r\n * Vue composables for @flightdev/mobile\r\n * \r\n * Provides Vue-friendly wrappers around Capacitor plugins.\r\n * All composables gracefully degrade on web.\r\n */\r\n\r\nimport { ref, onMounted, onUnmounted, type Ref } from 'vue';\r\nimport { getPlatform, isPluginAvailable, type PlatformInfo } from '../platform';\r\n\r\n// =============================================================================\r\n// Platform Composable\r\n// =============================================================================\r\n\r\n/**\r\n * Get current platform information.\r\n */\r\nexport function usePlatform(): PlatformInfo {\r\n return getPlatform();\r\n}\r\n\r\n// =============================================================================\r\n// Camera Composable\r\n// =============================================================================\r\n\r\ninterface UseCameraReturn {\r\n takePhoto: (options?: { quality?: number; allowEditing?: boolean }) => Promise<{ webPath?: string } | null>;\r\n pickImage: (options?: { quality?: number }) => Promise<{ webPath?: string } | null>;\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Camera composable for taking photos and picking images.\r\n */\r\nexport function useCamera(): UseCameraReturn {\r\n const available = isPluginAvailable('Camera');\r\n\r\n const takePhoto = async (options?: { quality?: number; allowEditing?: boolean }) => {\r\n if (!available) return null;\r\n\r\n try {\r\n const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera');\r\n\r\n return await Camera.getPhoto({\r\n quality: options?.quality ?? 90,\r\n allowEditing: options?.allowEditing ?? false,\r\n resultType: CameraResultType.Uri,\r\n source: CameraSource.Camera,\r\n });\r\n } catch {\r\n return null;\r\n }\r\n };\r\n\r\n const pickImage = async (options?: { quality?: number }) => {\r\n if (!available) return null;\r\n\r\n try {\r\n const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera');\r\n\r\n return await Camera.getPhoto({\r\n quality: options?.quality ?? 90,\r\n resultType: CameraResultType.Uri,\r\n source: CameraSource.Photos,\r\n });\r\n } catch {\r\n return null;\r\n }\r\n };\r\n\r\n return { takePhoto, pickImage, available };\r\n}\r\n\r\n// =============================================================================\r\n// Geolocation Composable\r\n// =============================================================================\r\n\r\ninterface GeolocationPosition {\r\n coords: {\r\n latitude: number;\r\n longitude: number;\r\n accuracy: number;\r\n };\r\n timestamp: number;\r\n}\r\n\r\ninterface UseGeolocationReturn {\r\n position: Ref<GeolocationPosition | null>;\r\n error: Ref<Error | null>;\r\n loading: Ref<boolean>;\r\n getCurrentPosition: () => Promise<GeolocationPosition | null>;\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Geolocation composable for getting device position.\r\n */\r\nexport function useGeolocation(): UseGeolocationReturn {\r\n const position = ref<GeolocationPosition | null>(null);\r\n const error = ref<Error | null>(null);\r\n const loading = ref(false);\r\n const available = isPluginAvailable('Geolocation');\r\n\r\n const getCurrentPosition = async (): Promise<GeolocationPosition | null> => {\r\n if (!available) return null;\r\n\r\n loading.value = true;\r\n error.value = null;\r\n\r\n try {\r\n const { Geolocation } = await import('@capacitor/geolocation');\r\n const pos = await Geolocation.getCurrentPosition();\r\n position.value = pos;\r\n return pos;\r\n } catch (e) {\r\n error.value = e as Error;\r\n return null;\r\n } finally {\r\n loading.value = false;\r\n }\r\n };\r\n\r\n return { position, error, loading, getCurrentPosition, available };\r\n}\r\n\r\n// =============================================================================\r\n// Push Notifications Composable\r\n// =============================================================================\r\n\r\ninterface UsePushNotificationsReturn {\r\n token: Ref<string | null>;\r\n requestPermission: () => Promise<boolean>;\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Push notifications composable.\r\n */\r\nexport function usePushNotifications(): UsePushNotificationsReturn {\r\n const token = ref<string | null>(null);\r\n const available = isPluginAvailable('PushNotifications');\r\n\r\n onMounted(async () => {\r\n if (!available) return;\r\n\r\n const { PushNotifications } = await import('@capacitor/push-notifications');\r\n\r\n await PushNotifications.addListener('registration', (t) => {\r\n token.value = t.value;\r\n });\r\n });\r\n\r\n onUnmounted(async () => {\r\n if (!available) return;\r\n\r\n const { PushNotifications } = await import('@capacitor/push-notifications');\r\n await PushNotifications.removeAllListeners();\r\n });\r\n\r\n const requestPermission = async (): Promise<boolean> => {\r\n if (!available) return false;\r\n\r\n try {\r\n const { PushNotifications } = await import('@capacitor/push-notifications');\r\n const result = await PushNotifications.requestPermissions();\r\n\r\n if (result.receive === 'granted') {\r\n await PushNotifications.register();\r\n return true;\r\n }\r\n return false;\r\n } catch {\r\n return false;\r\n }\r\n };\r\n\r\n return { token, requestPermission, available };\r\n}\r\n\r\n// =============================================================================\r\n// Haptics Composable\r\n// =============================================================================\r\n\r\ninterface UseHapticsReturn {\r\n impact: (style?: 'light' | 'medium' | 'heavy') => Promise<void>;\r\n notification: (type?: 'success' | 'warning' | 'error') => Promise<void>;\r\n vibrate: (duration?: number) => Promise<void>;\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Haptic feedback composable.\r\n */\r\nexport function useHaptics(): UseHapticsReturn {\r\n const available = isPluginAvailable('Haptics');\r\n\r\n const impact = async (style: 'light' | 'medium' | 'heavy' = 'medium') => {\r\n if (!available) return;\r\n\r\n try {\r\n const { Haptics, ImpactStyle } = await import('@capacitor/haptics');\r\n const styleMap = { light: ImpactStyle.Light, medium: ImpactStyle.Medium, heavy: ImpactStyle.Heavy };\r\n await Haptics.impact({ style: styleMap[style] });\r\n } catch { /* silently ignore haptics errors */ }\r\n };\r\n\r\n const notification = async (type: 'success' | 'warning' | 'error' = 'success') => {\r\n if (!available) return;\r\n\r\n try {\r\n const { Haptics, NotificationType } = await import('@capacitor/haptics');\r\n const typeMap = { success: NotificationType.Success, warning: NotificationType.Warning, error: NotificationType.Error };\r\n await Haptics.notification({ type: typeMap[type] });\r\n } catch { /* silently ignore haptics errors */ }\r\n };\r\n\r\n const vibrate = async (duration: number = 300) => {\r\n if (!available) return;\r\n\r\n try {\r\n const { Haptics } = await import('@capacitor/haptics');\r\n await Haptics.vibrate({ duration });\r\n } catch { /* silently ignore haptics errors */ }\r\n };\r\n\r\n return { impact, notification, vibrate, available };\r\n}\r\n\r\n// =============================================================================\r\n// Storage Composable\r\n// =============================================================================\r\n\r\ninterface UseStorageReturn {\r\n get: (key: string) => Promise<string | null>;\r\n set: (key: string, value: string) => Promise<void>;\r\n remove: (key: string) => Promise<void>;\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Secure storage composable.\r\n */\r\nexport function useStorage(): UseStorageReturn {\r\n const available = isPluginAvailable('Preferences');\r\n const { isWeb } = usePlatform();\r\n\r\n const get = async (key: string): Promise<string | null> => {\r\n if (isWeb || !available) {\r\n return localStorage.getItem(key);\r\n }\r\n\r\n try {\r\n const { Preferences } = await import('@capacitor/preferences');\r\n const { value } = await Preferences.get({ key });\r\n return value;\r\n } catch {\r\n return localStorage.getItem(key);\r\n }\r\n };\r\n\r\n const set = async (key: string, value: string): Promise<void> => {\r\n if (isWeb || !available) {\r\n localStorage.setItem(key, value);\r\n return;\r\n }\r\n\r\n try {\r\n const { Preferences } = await import('@capacitor/preferences');\r\n await Preferences.set({ key, value });\r\n } catch {\r\n localStorage.setItem(key, value);\r\n }\r\n };\r\n\r\n const remove = async (key: string): Promise<void> => {\r\n if (isWeb || !available) {\r\n localStorage.removeItem(key);\r\n return;\r\n }\r\n\r\n try {\r\n const { Preferences } = await import('@capacitor/preferences');\r\n await Preferences.remove({ key });\r\n } catch {\r\n localStorage.removeItem(key);\r\n }\r\n };\r\n\r\n return { get, set, remove, available };\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "@flightdev/mobile",
3
+ "version": "0.2.0",
4
+ "description": "Mobile app support for Flight Framework via Capacitor - iOS & Android",
5
+ "keywords": [
6
+ "flight",
7
+ "mobile",
8
+ "capacitor",
9
+ "ios",
10
+ "android",
11
+ "native"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Flight Contributors",
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./react": {
22
+ "types": "./dist/react/index.d.ts",
23
+ "import": "./dist/react/index.js"
24
+ },
25
+ "./vue": {
26
+ "types": "./dist/vue/index.d.ts",
27
+ "import": "./dist/vue/index.js"
28
+ },
29
+ "./plugins": {
30
+ "types": "./dist/plugins/index.d.ts",
31
+ "import": "./dist/plugins/index.js"
32
+ },
33
+ "./config": {
34
+ "types": "./dist/config.d.ts",
35
+ "import": "./dist/config.js"
36
+ }
37
+ },
38
+ "main": "./dist/index.js",
39
+ "types": "./dist/index.d.ts",
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "@types/react": "^19.0.0",
46
+ "react": "^19.0.0",
47
+ "rimraf": "^6.0.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.7.0",
50
+ "vitest": "^2.0.0",
51
+ "vue": "^3.5.0"
52
+ },
53
+ "peerDependencies": {
54
+ "@capacitor/core": "^6.0.0",
55
+ "@capacitor/camera": "^6.0.0",
56
+ "@capacitor/geolocation": "^6.0.0",
57
+ "@capacitor/push-notifications": "^6.0.0",
58
+ "@capacitor/haptics": "^6.0.0",
59
+ "@capacitor/preferences": "^6.0.0",
60
+ "react": "^18.0.0 || ^19.0.0",
61
+ "vue": "^3.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@capacitor/core": {
65
+ "optional": true
66
+ },
67
+ "@capacitor/camera": {
68
+ "optional": true
69
+ },
70
+ "@capacitor/geolocation": {
71
+ "optional": true
72
+ },
73
+ "@capacitor/push-notifications": {
74
+ "optional": true
75
+ },
76
+ "@capacitor/haptics": {
77
+ "optional": true
78
+ },
79
+ "@capacitor/preferences": {
80
+ "optional": true
81
+ },
82
+ "react": {
83
+ "optional": true
84
+ },
85
+ "vue": {
86
+ "optional": true
87
+ }
88
+ },
89
+ "scripts": {
90
+ "build": "tsup",
91
+ "dev": "tsup --watch",
92
+ "test": "vitest run",
93
+ "test:watch": "vitest",
94
+ "lint": "eslint src/",
95
+ "clean": "rimraf dist",
96
+ "typecheck": "tsc --noEmit"
97
+ }
98
+ }