@stacksjs/mobile 0.75.22 → 0.75.23

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/README.md CHANGED
@@ -55,3 +55,54 @@ The `health` facade reads authorized Apple Health or Android Health Connect
55
55
  metrics and writes completed workouts using one payload. `watchConnectivity`
56
56
  keeps companion controls and recording state synchronized without exposing
57
57
  platform bridge globals to application code.
58
+
59
+ ### Reading Apple Health
60
+
61
+ `health.getWorkouts()` lists the workouts in Apple Health — the watch's, and
62
+ every app's that writes there — newest first, with duration, distance, energy,
63
+ heart rate and elevation, keyed on HealthKit's UUID so an import can be run
64
+ again without duplicating. `health.getDailyStatistics(type)` returns one value
65
+ per local day: a sum for `steps`, `activeEnergy` and `distance`, an average for
66
+ `heartRate`, `restingHeartRate` and `heartRateVariability`, the latest
67
+ `bodyMass`, and hours asleep for `sleep`, counted on the day you woke.
68
+
69
+ ```ts
70
+ import { health } from '@stacksjs/mobile'
71
+
72
+ await health.requestAuthorization(['workouts', 'heartRate', 'sleep', 'heartRateVariability'])
73
+ const workouts = await health.getWorkouts({ startDate: Date.now() - 30 * 86400000 })
74
+ const hrv = await health.getDailyStatistics('heartRateVariability', { startDate: Date.now() - 30 * 86400000 })
75
+ ```
76
+
77
+ Both are iOS only for now; on Android they reject as unavailable.
78
+
79
+ ## Before the bridge arrives
80
+
81
+ Craft installs `window.craft` once the page has finished loading, so
82
+ `isNativeMobile()` answers `false` during setup on a phone. A decision made
83
+ then — hide the website's header, show the app's tab bar — needs the answer
84
+ when it is right:
85
+
86
+ ```ts
87
+ import { hasNativeMobileHost, whenNativeMobile } from '@stacksjs/mobile'
88
+
89
+ hasNativeMobileHost() // true inside the phone shell, even before the bridge
90
+ const native = await whenNativeMobile() // resolves once it knows, false in a browser
91
+ ```
92
+
93
+ ## Page gestures and navigation
94
+
95
+ The phone-native behaviour a screen expects, kept out of STX templates:
96
+
97
+ - `observePullToRefresh({ onRefresh, onPull, threshold })` pulls the page down
98
+ from its top. On iOS it rides the system's rubber band; elsewhere the finger's
99
+ travel drives it. Crossing the threshold taps the haptic engine once.
100
+ - `observePageScroll(offset => …)` reports the scroll offset at most once a
101
+ frame, for a bar that collapses as the page scrolls.
102
+ - `goBack()` goes back through history when the app pushed the current screen,
103
+ and answers `false` otherwise so a back button follows its own link and never
104
+ leaves the app.
105
+
106
+ The framework's `NativeNavBar`, `NativePullToRefresh`, `NativeSegmentedControl`
107
+ and `NativeSheet` components are built on these.
108
+
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The page's vertical scroll offset. Negative while iOS rubber-bands past the
3
+ * top, which is what pull-to-refresh reads.
4
+ */
5
+ export declare function pageScrollTop(host?: ScrollHost | undefined): number;
6
+ /**
7
+ * Calls `callback` with the page's scroll offset now and on every scroll, at
8
+ * most once a frame. Returns the unsubscribe.
9
+ */
10
+ export declare function observePageScroll(callback: (offset: number) => void, host?: ScrollHost | undefined): () => void;
11
+ /**
12
+ * How far a pull has travelled.
13
+ *
14
+ * iOS rubber-bands the page past its top, so the scroll offset itself goes
15
+ * negative and carries the system's own resistance curve. Android and desktop
16
+ * browsers stop at zero, so there the finger's travel counts, halved to feel
17
+ * like the same resistance.
18
+ */
19
+ export declare function pullDistance(scrollTop: number, fingerTravel: number): number;
20
+ /**
21
+ * Pull-to-refresh for the whole page.
22
+ *
23
+ * A pull starts only with the page at its top. Crossing the threshold gives a
24
+ * medium haptic tap, the moment a native list does; pulling back under 80% of
25
+ * it disarms. Letting go while armed runs `onRefresh`.
26
+ */
27
+ export declare function observePullToRefresh(options: PullToRefreshOptions): () => void;
28
+ /** Where a page scrolls: the window, or a stand-in with the same shape. */
29
+ export declare interface ScrollHost extends EventTarget {
30
+ scrollY?: number
31
+ document?: { scrollingElement?: { scrollTop: number } | null }
32
+ }
33
+ export declare interface PullToRefreshOptions {
34
+ threshold?: number
35
+ onPull?: (distance: number, armed: boolean) => void
36
+ onRefresh: () => unknown | Promise<unknown>
37
+ host?: ScrollHost
38
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Bundled TypeScript declarations
3
- * Generated from 2 source files
3
+ * Generated from 4 source files
4
4
  */
5
5
 
6
6
 
@@ -27,10 +27,101 @@ export declare const watchConnectivity: WatchConnectivityApi;
27
27
  /** Reads the current host, including a bridge injected after this module loaded. */
28
28
  export declare function getNativeMobileBridge(): CraftMobileBridge | null;
29
29
  export declare function isNativeMobile(): boolean;
30
+ /**
31
+ * Whether this page runs inside Craft's phone shell, before the bridge exists.
32
+ *
33
+ * Craft installs `window.craft` once the page has finished loading — images
34
+ * and all — so `isNativeMobile()` answers `false` during setup on a phone, and
35
+ * a layout that decides then renders the website's chrome in the app. The
36
+ * transports are there from the start: Android's `CraftAndroid` interface, and
37
+ * on iOS the `craft` message handler. Craft's macOS windows carry that handler
38
+ * too, so on iOS an iPhone or iPad user agent is what makes it the phone app.
39
+ */
40
+ export declare function hasNativeMobileHost(): boolean;
41
+ /**
42
+ * Resolves `true` inside the phone shell and `false` in a browser, at a moment
43
+ * when the answer is right.
44
+ *
45
+ * A browser answers at once. A phone answers at once too when its host is
46
+ * already recognisable, and otherwise once `craftReady` fires. The timeout is
47
+ * a floor for a host that never fires it: a caller waiting on this has to
48
+ * decide something to render anything.
49
+ */
50
+ export declare function whenNativeMobile(timeoutMs?: number): Promise<boolean>;
30
51
  export declare function onMobileReady(callback: (event: CraftReadyEvent) => void): () => void;
31
52
  export declare function withNativeFeedback<T>(action: () => T | Promise<T>): Promise<T>;
32
53
  export declare const mobile: MobileApi;
33
54
 
55
+ // From: src/gestures.ts
56
+ /** Where a page scrolls: the window, or a stand-in with the same shape. */
57
+ export declare interface ScrollHost extends EventTarget {
58
+ scrollY?: number
59
+ document?: { scrollingElement?: { scrollTop: number } | null }
60
+ }
61
+ /**
62
+ * The page's vertical scroll offset. Negative while iOS rubber-bands past the
63
+ * top, which is what pull-to-refresh reads.
64
+ */
65
+ export declare function pageScrollTop(host?: ScrollHost | undefined): number;
66
+ /**
67
+ * Calls `callback` with the page's scroll offset now and on every scroll, at
68
+ * most once a frame. Returns the unsubscribe.
69
+ */
70
+ export declare function observePageScroll(callback: (offset: number) => void, host?: ScrollHost | undefined): () => void;
71
+ export declare interface PullToRefreshOptions {
72
+ threshold?: number
73
+ onPull?: (distance: number, armed: boolean) => void
74
+ onRefresh: () => unknown | Promise<unknown>
75
+ host?: ScrollHost
76
+ }
77
+ /**
78
+ * How far a pull has travelled.
79
+ *
80
+ * iOS rubber-bands the page past its top, so the scroll offset itself goes
81
+ * negative and carries the system's own resistance curve. Android and desktop
82
+ * browsers stop at zero, so there the finger's travel counts, halved to feel
83
+ * like the same resistance.
84
+ */
85
+ export declare function pullDistance(scrollTop: number, fingerTravel: number): number;
86
+ /**
87
+ * Pull-to-refresh for the whole page.
88
+ *
89
+ * A pull starts only with the page at its top. Crossing the threshold gives a
90
+ * medium haptic tap, the moment a native list does; pulling back under 80% of
91
+ * it disarms. Letting go while armed runs `onRefresh`.
92
+ */
93
+ export declare function observePullToRefresh(options: PullToRefreshOptions): () => void;
94
+
95
+ // From: src/navigation.ts
96
+ /** The part of the window back navigation reads. */
97
+ export declare interface HistoryHost {
98
+ history?: { state: unknown, back: () => void }
99
+ }
100
+ /** Whether going back stays inside this app. */
101
+ export declare function canGoBack(host?: HistoryHost | undefined): boolean;
102
+ /**
103
+ * Goes back through history when that stays inside the app, and says whether
104
+ * it did. When it answers `false` the caller follows its own link.
105
+ */
106
+ export declare function goBack(host?: HistoryHost | undefined): boolean;
107
+ /**
108
+ * The in-app path a deep link opens, or null when it is not this app's.
109
+ *
110
+ * A custom scheme carries the path after it — `myapp://m/workout/5` and
111
+ * `myapp:///m/workout/5` both open `/m/workout/5` — and a universal link on
112
+ * this app's origin opens its own path. A link to anywhere else is not
113
+ * followed from inside the app.
114
+ */
115
+ export declare function deepLinkPath(link: string, origin?: string | undefined): string | null;
116
+ /**
117
+ * Opens every deep link the app receives with `go`: the one it was launched
118
+ * with, once, and each one after. Returns the unsubscribe.
119
+ *
120
+ * The launch link is remembered for the session, so a full page load inside
121
+ * the app does not open it again.
122
+ */
123
+ export declare function followDeepLinks(go: (path: string) => void, links: DeepLinksApi): () => void;
124
+
34
125
  // From: src/types.ts
35
126
  /** Enabled native features, not the hardware report from device.getCapabilities(). */
36
127
  export declare interface CraftMobileCapabilities {
@@ -107,6 +198,7 @@ export declare interface MobileApi {
107
198
  liveActivities: LiveActivitiesApi
108
199
  watchConnectivity: WatchConnectivityApi
109
200
  isNativeMobile: () => boolean
201
+ whenNative: (timeoutMs?: number) => Promise<boolean>
110
202
  onReady: (callback: (event: CraftReadyEvent) => void) => () => void
111
203
  withFeedback: <T>(action: () => T | Promise<T>) => Promise<T>
112
204
  }
@@ -274,7 +366,7 @@ export declare interface KeepAwakeApi {
274
366
  }
275
367
  export declare interface DeepLinksApi {
276
368
  getInitialURL: () => Promise<string | null>
277
- onLink: (callback: (url: string) => void) => () => void
369
+ onLink: (callback: (url: string, link?: { initial: boolean }) => void) => () => void
278
370
  }
279
371
  export declare interface NetworkStatus {
280
372
  type: 'wifi' | 'cellular' | 'ethernet' | 'none' | 'unknown'
@@ -292,7 +384,17 @@ export declare interface PushNotificationsApi {
292
384
  onToken: (callback: (token: string) => void) => () => void
293
385
  onNotification: (callback: (data: Record<string, unknown>) => void) => () => void
294
386
  }
295
- export type HealthDataType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts';
387
+ export type HealthDataType = | 'steps'
388
+ | 'heartRate'
389
+ | 'activeEnergy'
390
+ | 'distance'
391
+ | 'workouts'
392
+ | 'restingHeartRate'
393
+ | 'heartRateVariability'
394
+ | 'bodyMass'
395
+ | 'sleep';
396
+ /** The types with one value per day. `sleep` is hours asleep, counted on the day you woke. */
397
+ export type HealthDailyType = Exclude<HealthDataType, 'workouts'>;
296
398
  export declare interface HealthDataOptions {
297
399
  startDate?: number
298
400
  endDate?: number
@@ -321,10 +423,44 @@ export declare interface HealthWorkout {
321
423
  export declare interface HealthWorkoutResult {
322
424
  id: string
323
425
  }
426
+ export declare interface HealthWorkoutQuery {
427
+ startDate?: number
428
+ endDate?: number
429
+ limit?: number
430
+ }
431
+ /** The activity of a workout read from Apple Health, named independently of the SDK. */
432
+ export type HealthWorkoutActivity = | 'running' | 'cycling' | 'walking' | 'hiking' | 'swimming' | 'rowing' | 'elliptical'
433
+ | 'stairClimbing' | 'yoga' | 'pilates' | 'strength' | 'hiit' | 'crossTraining'
434
+ | 'crossCountrySkiing' | 'skiing' | 'paddling' | 'climbing' | 'dance' | 'mobility' | 'other';
435
+ /** A workout recorded by the watch, or by any app that writes to Apple Health. */
436
+ export declare interface HealthWorkoutSample {
437
+ id: string
438
+ type: HealthWorkoutActivity
439
+ startDate: number
440
+ endDate: number
441
+ durationSeconds: number
442
+ sourceName: string
443
+ indoor: boolean
444
+ distanceMeters?: number
445
+ elevationGainMeters?: number
446
+ activeEnergyCalories?: number
447
+ averageHeartRate?: number
448
+ maxHeartRate?: number
449
+ }
450
+ export declare interface HealthDailyValue {
451
+ date: string
452
+ value: number
453
+ unit: string
454
+ }
455
+ export declare interface HealthAuthorizationOptions {
456
+ write?: boolean
457
+ }
324
458
  export declare interface HealthApi {
325
- requestAuthorization: (types: HealthDataType[]) => Promise<boolean>
459
+ requestAuthorization: (types: HealthDataType[], options?: HealthAuthorizationOptions) => Promise<boolean>
326
460
  getData: (type: HealthDataType, options?: HealthDataOptions) => Promise<HealthDataResult>
327
461
  saveWorkout: (workout: HealthWorkout) => Promise<HealthWorkoutResult>
462
+ getWorkouts: (options?: HealthWorkoutQuery) => Promise<HealthWorkoutSample[]>
463
+ getDailyStatistics: (type: HealthDailyType, options?: HealthDataOptions) => Promise<HealthDailyValue[]>
328
464
  }
329
465
  export declare interface LiveActivityState {
330
466
  status?: string
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{appReview as p,biometrics as f,camera as l,deepLinks as a,device as u,haptics as A,health as v,keepAwake as d,lifecycle as m,liveActivities as w,location as y,network as h,notifications as k,permissions as x,pushNotifications as L,secureStorage as b,share as g,watchConnectivity as R}from"craft-native/mobile";var C=f,N=l,E=u,o=A,S=m,H=y,M=k,P=x,B=b,D=g,j=p;function c(t){if(typeof t==="string")return t.trim()||null;if(!t||typeof t!=="object")return null;let e=t.url;return typeof e==="string"&&e.trim()?e:null}var T={async getInitialURL(){return c(await a.getInitialURL?.())},onLink(t){return a.onLink?.((e)=>{let i=c(e);if(i)t(i)})??(()=>{})}},K=d,U=h,W=L,F=v,I=w,O=R;function s(){if(typeof window>"u")return;return window}function n(){let t=s()?.craft;if(!t||typeof t!=="object")return null;if(!("platform"in t)||t.platform!=="ios"&&t.platform!=="android")return null;let e="capabilities"in t?t.capabilities:void 0,i=e&&typeof e==="object"&&!Array.isArray(e)?Object.fromEntries(Object.entries(e).filter(([,r])=>typeof r==="boolean")):{};return{platform:t.platform,capabilities:i}}function q(){return n()!==null}function z(t){let e=s();if(!e)return()=>{};if(n()){let r=!1;return queueMicrotask(()=>{if(!r)t(new Event("craftReady"))}),()=>{r=!0}}let i=(r)=>{if(!n())return;e.removeEventListener("craftReady",i),t(r)};return e.addEventListener("craftReady",i),()=>e.removeEventListener("craftReady",i)}async function G(t){await o.impact("light");try{let e=await t();return await o.notification("success"),e}catch(e){throw await o.notification("error"),e}}var Q={get nativeBridge(){return n()},biometrics:C,camera:N,device:E,haptics:o,lifecycle:S,location:H,notifications:M,permissions:P,secureStorage:B,share:D,appReview:j,deepLinks:T,keepAwake:K,network:U,pushNotifications:W,health:F,liveActivities:I,watchConnectivity:O,isNativeMobile:q,onReady:z,withFeedback:G};export{j as appReview,C as biometrics,N as camera,T as deepLinks,E as device,n as getNativeMobileBridge,o as haptics,F as health,q as isNativeMobile,K as keepAwake,S as lifecycle,I as liveActivities,H as location,Q as mobile,U as network,c as normalizeDeepLinkURL,M as notifications,z as onMobileReady,P as permissions,W as pushNotifications,B as secureStorage,D as share,O as watchConnectivity,G as withNativeFeedback};
1
+ import{appReview as S,biometrics as N,camera as T,deepLinks as x,device as M,haptics as D,health as B,keepAwake as Y,lifecycle as j,liveActivities as U,location as I,network as O,notifications as q,permissions as F,pushNotifications as K,secureStorage as W,share as z,watchConnectivity as G}from"craft-native/mobile";import{haptics as E}from"craft-native/mobile";function d(){if(typeof window>"u")return;return window}function u(t=d()){if(!t)return 0;if(typeof t.scrollY==="number")return t.scrollY;return t.document?.scrollingElement?.scrollTop??0}function he(t,e=d()){if(!e)return()=>{};let r=!1,n=typeof requestAnimationFrame==="function"?requestAnimationFrame:(o)=>setTimeout(o,16),i=()=>{if(r)return;r=!0,n(()=>{r=!1,t(u(e))})};return t(u(e)),e.addEventListener("scroll",i,{passive:!0}),()=>e.removeEventListener("scroll",i)}function H(t,e){let r=Math.max(0,-t),n=t<=0?Math.max(0,e)*0.5:0;return Math.round(Math.max(r,n))}function we(t){let e=t.host??d();if(!e)return()=>{};let r=t.threshold??72,n=null,i=!1,o=!1,c=(s)=>t.onPull?.(s,i),h=(s)=>{let a=s.touches?.[0];return a?a.clientY:null},w=(s)=>{if(o||u(e)>0)return;n=h(s),i=!1},A=(s)=>{if(n===null)return;let a=h(s);if(a===null)return;let p=H(u(e),a-n);if(!i&&p>=r)i=!0,Promise.resolve(E.impact("medium")).catch(()=>{});else if(i&&p<r*0.8)i=!1;c(p)},y=async()=>{if(n===null)return;if(n=null,!i){c(0);return}i=!1,o=!0,t.onPull?.(r,!1);try{await t.onRefresh()}finally{o=!1,c(0)}},b=()=>{if(n===null)return;n=null,i=!1,c(0)};return e.addEventListener("touchstart",w,{passive:!0}),e.addEventListener("touchmove",A,{passive:!0}),e.addEventListener("touchend",y),e.addEventListener("touchcancel",b),()=>{e.removeEventListener("touchstart",w),e.removeEventListener("touchmove",A),e.removeEventListener("touchend",y),e.removeEventListener("touchcancel",b)}}function k(){if(typeof window>"u")return;return window}function R(t=k()){let e=t?.history?.state;return e!==null&&e!==void 0}function ye(t=k()){if(!R(t))return!1;return t.history.back(),!0}function P(t,e=C()){let r;try{r=new URL(t)}catch{return null}let n=`${r.search}${r.hash}`;if(r.protocol==="http:"||r.protocol==="https:"){if(!e||r.origin!==e)return null;return`${r.pathname||"/"}${n}`}return`${`/${[r.host,r.pathname.replace(/^\/+/,"")].filter(Boolean).join("/")}`}${n}`}function C(){if(typeof window>"u")return;return window.location?.origin}var g="stacks-mobile-deep-link";function be(t,e){let r=(n)=>{let i=P(n);if(i)t(i)};return e.getInitialURL().then((n)=>{if(!n)return;try{if(sessionStorage.getItem(g)===n)return;sessionStorage.setItem(g,n)}catch{}r(n)}).catch(()=>{}),e.onLink((n,i)=>{if(i?.initial)return;r(n)})}var _=N,J=T,Q=M,f=D,V=j,X=I,Z=q,ee=F,te=W,ne=z,re=S;function L(t){if(typeof t==="string")return t.trim()||null;if(!t||typeof t!=="object")return null;let e=t.url;return typeof e==="string"&&e.trim()?e:null}var ie={async getInitialURL(){return L(await x.getInitialURL?.())},onLink(t){return x.onLink?.((e,r)=>{let n=L(e);if(n)t(n,{initial:r?.initial===!0})})??(()=>{})}},oe=Y,se=O,ae=K,ce=B,ue=U,fe=G;function v(){if(typeof window>"u")return;return window}function l(){let t=v()?.craft;if(!t||typeof t!=="object")return null;if(!("platform"in t)||t.platform!=="ios"&&t.platform!=="android")return null;let e="capabilities"in t?t.capabilities:void 0,r=e&&typeof e==="object"&&!Array.isArray(e)?Object.fromEntries(Object.entries(e).filter(([,n])=>typeof n==="boolean")):{};return{platform:t.platform,capabilities:r}}function m(){return l()!==null}function le(){if(typeof window>"u")return!1;let t=window;if(t.CraftAndroid)return!0;let e=typeof navigator>"u"?"":navigator.userAgent;return Boolean(t.webkit?.messageHandlers?.craft)&&/iPhone|iPad|iPod/.test(e)}function pe(t=2000){if(m()||le())return Promise.resolve(!0);let e=v(),r=e;if(!e||!(r?.craft||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((n)=>{let i=()=>{clearTimeout(o),e.removeEventListener("craftReady",i),n(m())},o=setTimeout(i,t);e.addEventListener("craftReady",i,{once:!0})})}function de(t){let e=v();if(!e)return()=>{};if(l()){let n=!1;return queueMicrotask(()=>{if(!n)t(new Event("craftReady"))}),()=>{n=!0}}let r=(n)=>{if(!l())return;e.removeEventListener("craftReady",r),t(n)};return e.addEventListener("craftReady",r),()=>e.removeEventListener("craftReady",r)}async function me(t){await f.impact("light");try{let e=await t();return await f.notification("success"),e}catch(e){throw await f.notification("error"),e}}var xe={get nativeBridge(){return l()},biometrics:_,camera:J,device:Q,haptics:f,lifecycle:V,location:X,notifications:Z,permissions:ee,secureStorage:te,share:ne,appReview:re,deepLinks:ie,keepAwake:oe,network:se,pushNotifications:ae,health:ce,liveActivities:ue,watchConnectivity:fe,isNativeMobile:m,whenNative:pe,onReady:de,withFeedback:me};export{re as appReview,_ as biometrics,J as camera,R as canGoBack,P as deepLinkPath,ie as deepLinks,Q as device,be as followDeepLinks,l as getNativeMobileBridge,ye as goBack,f as haptics,le as hasNativeMobileHost,ce as health,m as isNativeMobile,oe as keepAwake,V as lifecycle,ue as liveActivities,X as location,xe as mobile,se as network,L as normalizeDeepLinkURL,Z as notifications,he as observePageScroll,we as observePullToRefresh,de as onMobileReady,u as pageScrollTop,ee as permissions,H as pullDistance,ae as pushNotifications,te as secureStorage,ne as share,fe as watchConnectivity,pe as whenNativeMobile,me as withNativeFeedback};
@@ -0,0 +1,29 @@
1
+ import type { DeepLinksApi } from './types';
2
+ /** Whether going back stays inside this app. */
3
+ export declare function canGoBack(host?: HistoryHost | undefined): boolean;
4
+ /**
5
+ * Goes back through history when that stays inside the app, and says whether
6
+ * it did. When it answers `false` the caller follows its own link.
7
+ */
8
+ export declare function goBack(host?: HistoryHost | undefined): boolean;
9
+ /**
10
+ * The in-app path a deep link opens, or null when it is not this app's.
11
+ *
12
+ * A custom scheme carries the path after it — `myapp://m/workout/5` and
13
+ * `myapp:///m/workout/5` both open `/m/workout/5` — and a universal link on
14
+ * this app's origin opens its own path. A link to anywhere else is not
15
+ * followed from inside the app.
16
+ */
17
+ export declare function deepLinkPath(link: string, origin?: string | undefined): string | null;
18
+ /**
19
+ * Opens every deep link the app receives with `go`: the one it was launched
20
+ * with, once, and each one after. Returns the unsubscribe.
21
+ *
22
+ * The launch link is remembered for the session, so a full page load inside
23
+ * the app does not open it again.
24
+ */
25
+ export declare function followDeepLinks(go: (path: string) => void, links: DeepLinksApi): () => void;
26
+ /** The part of the window back navigation reads. */
27
+ export declare interface HistoryHost {
28
+ history?: { state: unknown, back: () => void }
29
+ }
package/dist/types.d.ts CHANGED
@@ -73,6 +73,7 @@ export declare interface MobileApi {
73
73
  liveActivities: LiveActivitiesApi
74
74
  watchConnectivity: WatchConnectivityApi
75
75
  isNativeMobile: () => boolean
76
+ whenNative: (timeoutMs?: number) => Promise<boolean>
76
77
  onReady: (callback: (event: CraftReadyEvent) => void) => () => void
77
78
  withFeedback: <T>(action: () => T | Promise<T>) => Promise<T>
78
79
  }
@@ -223,7 +224,7 @@ export declare interface KeepAwakeApi {
223
224
  }
224
225
  export declare interface DeepLinksApi {
225
226
  getInitialURL: () => Promise<string | null>
226
- onLink: (callback: (url: string) => void) => () => void
227
+ onLink: (callback: (url: string, link?: { initial: boolean }) => void) => () => void
227
228
  }
228
229
  export declare interface NetworkStatus {
229
230
  type: 'wifi' | 'cellular' | 'ethernet' | 'none' | 'unknown'
@@ -268,10 +269,40 @@ export declare interface HealthWorkout {
268
269
  export declare interface HealthWorkoutResult {
269
270
  id: string
270
271
  }
272
+ export declare interface HealthWorkoutQuery {
273
+ startDate?: number
274
+ endDate?: number
275
+ limit?: number
276
+ }
277
+ /** A workout recorded by the watch, or by any app that writes to Apple Health. */
278
+ export declare interface HealthWorkoutSample {
279
+ id: string
280
+ type: HealthWorkoutActivity
281
+ startDate: number
282
+ endDate: number
283
+ durationSeconds: number
284
+ sourceName: string
285
+ indoor: boolean
286
+ distanceMeters?: number
287
+ elevationGainMeters?: number
288
+ activeEnergyCalories?: number
289
+ averageHeartRate?: number
290
+ maxHeartRate?: number
291
+ }
292
+ export declare interface HealthDailyValue {
293
+ date: string
294
+ value: number
295
+ unit: string
296
+ }
297
+ export declare interface HealthAuthorizationOptions {
298
+ write?: boolean
299
+ }
271
300
  export declare interface HealthApi {
272
- requestAuthorization: (types: HealthDataType[]) => Promise<boolean>
301
+ requestAuthorization: (types: HealthDataType[], options?: HealthAuthorizationOptions) => Promise<boolean>
273
302
  getData: (type: HealthDataType, options?: HealthDataOptions) => Promise<HealthDataResult>
274
303
  saveWorkout: (workout: HealthWorkout) => Promise<HealthWorkoutResult>
304
+ getWorkouts: (options?: HealthWorkoutQuery) => Promise<HealthWorkoutSample[]>
305
+ getDailyStatistics: (type: HealthDailyType, options?: HealthDataOptions) => Promise<HealthDailyValue[]>
275
306
  }
276
307
  export declare interface LiveActivityState {
277
308
  status?: string
@@ -312,5 +343,19 @@ export type PermissionType = | 'camera'
312
343
  export type PermissionStatus = 'granted' | 'denied' | 'undetermined' | 'restricted';
313
344
  export type BiometricType = 'faceId' | 'touchId' | 'fingerprint' | 'face' | 'iris';
314
345
  export type AppState = 'active' | 'inactive' | 'background';
315
- export type HealthDataType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts';
346
+ export type HealthDataType = | 'steps'
347
+ | 'heartRate'
348
+ | 'activeEnergy'
349
+ | 'distance'
350
+ | 'workouts'
351
+ | 'restingHeartRate'
352
+ | 'heartRateVariability'
353
+ | 'bodyMass'
354
+ | 'sleep';
355
+ /** The types with one value per day. `sleep` is hours asleep, counted on the day you woke. */
356
+ export type HealthDailyType = Exclude<HealthDataType, 'workouts'>;
316
357
  export type HealthWorkoutType = 'running' | 'walking' | 'hiking' | 'cycling';
358
+ /** The activity of a workout read from Apple Health, named independently of the SDK. */
359
+ export type HealthWorkoutActivity = | 'running' | 'cycling' | 'walking' | 'hiking' | 'swimming' | 'rowing' | 'elliptical'
360
+ | 'stairClimbing' | 'yoga' | 'pilates' | 'strength' | 'hiit' | 'crossTraining'
361
+ | 'crossCountrySkiing' | 'skiing' | 'paddling' | 'climbing' | 'dance' | 'mobility' | 'other';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/mobile",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.22",
5
+ "version": "0.75.23",
6
6
  "description": "Craft-powered native mobile runtime for Stacks and STX applications",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -32,10 +32,10 @@
32
32
  "test": "bun test"
33
33
  },
34
34
  "dependencies": {
35
- "craft-native": ">=0.0.92"
35
+ "craft-native": ">=0.0.100"
36
36
  },
37
37
  "devDependencies": {
38
- "@stacksjs/build": "0.75.22",
38
+ "@stacksjs/build": "0.75.23",
39
39
  "better-dx": "^0.2.24"
40
40
  }
41
41
  }