@stacksjs/mobile 0.75.25 → 0.75.26

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
@@ -76,6 +76,33 @@ const hrv = await health.getDailyStatistics('heartRateVariability', { startDate:
76
76
 
77
77
  Both are iOS only for now; on Android they reject as unavailable.
78
78
 
79
+ ## Recording a route
80
+
81
+ Craft records the route natively: fixes go to disk, keep coming with the screen
82
+ locked (`capabilities.backgroundLocation`), and survive the app being closed.
83
+ `createRouteRecorder` drives that recording and reads it back every few seconds
84
+ while the page is in front, turning it into the numbers a runner watches:
85
+
86
+ ```ts
87
+ import { createRouteRecorder, location, paceLabel } from '@stacksjs/mobile'
88
+
89
+ const recorder = createRouteRecorder({
90
+ location,
91
+ onUpdate: stats => console.log(stats.distanceM, paceLabel(stats.paceSPerKm)),
92
+ })
93
+
94
+ await recorder.attach() ?? await recorder.start() // pick up a recording that outlived the app
95
+ await recorder.pause()
96
+ await recorder.resume()
97
+ const fixes = await recorder.stop() // every fix, for the server to keep
98
+ ```
99
+
100
+ `routeStats(fixes)` is the arithmetic on its own: distance counted from an
101
+ anchor so GPS drift while standing still adds nothing, fixes less accurate than
102
+ 25 m left out, a gap of more than `ROUTE_GAP_S` (a pause) not counted as ground
103
+ covered, moving time, and pace over the last 30 seconds and the whole route.
104
+ `paceLabel` and `speedLabel` format min/km and km/h.
105
+
79
106
  ## Before the bridge arrives
80
107
 
81
108
  Craft installs `window.craft` once the page has finished loading, so
@@ -105,4 +132,3 @@ The phone-native behaviour a screen expects, kept out of STX templates:
105
132
 
106
133
  The framework's `NativeNavBar`, `NativePullToRefresh`, `NativeSegmentedControl`
107
134
  and `NativeSheet` components are built on these.
108
-
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Bundled TypeScript declarations
3
- * Generated from 4 source files
3
+ * Generated from 5 source files
4
4
  */
5
5
 
6
6
 
@@ -122,6 +122,67 @@ export declare function deepLinkPath(link: string, origin?: string | undefined):
122
122
  */
123
123
  export declare function followDeepLinks(go: (path: string) => void, links: DeepLinksApi): () => void;
124
124
 
125
+ // From: src/route.ts
126
+ /** A fix as Craft records it. */
127
+ export declare interface RouteFix extends Location {
128
+ altitudeAccuracy?: number
129
+ }
130
+ export declare interface RouteStats {
131
+ distanceM: number
132
+ movingS: number
133
+ paceSPerKm: number | null
134
+ averagePaceSPerKm: number | null
135
+ speedMps: number | null
136
+ accuracyM: number | null
137
+ fixes: number
138
+ }
139
+ export declare interface RouteStatsOptions {
140
+ maxAccuracyM?: number
141
+ paceWindowS?: number
142
+ }
143
+ /** Fixes further apart than this, seconds, are a break in the route (a pause). */
144
+ export declare const ROUTE_GAP_S: unknown;
145
+ /** Great-circle distance between two fixes, metres. */
146
+ export declare function fixDistance(a: { latitude: number, longitude: number }, b: { latitude: number, longitude: number }): number;
147
+ /** The fixes worth counting, oldest first: a known position, accurate enough. */
148
+ export declare function usableFixes<T extends RouteFix>(fixes: readonly T[], maxAccuracyM?: number): T[];
149
+ /**
150
+ * The live numbers for a recording.
151
+ *
152
+ * Standing still, GPS wanders a few metres about the true spot, and summing
153
+ * every hop would add distance nobody covered. So distance is counted from an
154
+ * anchor: a fix only counts once it is further from the last counted one than
155
+ * the signal could explain (the larger of 3 m and the fixes' accuracy, capped
156
+ * at 10 m), and then becomes the anchor. Fixes more than ROUTE_GAP_S apart
157
+ * (a pause) are a break: the ground between them is not counted.
158
+ */
159
+ export declare function routeStats(fixes: readonly RouteFix[], options?: RouteStatsOptions): RouteStats;
160
+ /** "5:12" per kilometre, or "–" without a pace. */
161
+ export declare function paceLabel(secondsPerKm: number | null | undefined): string;
162
+ /** "24.3" kilometres per hour. */
163
+ export declare function speedLabel(metresPerSecond: number | null | undefined): string;
164
+ export declare interface RouteRecorderOptions extends RouteStatsOptions {
165
+ intervalMs?: number
166
+ onUpdate?: (stats: RouteStats) => void
167
+ location: LocationApi
168
+ locationOptions?: LocationOptions
169
+ }
170
+ export declare interface RouteRecorder {
171
+ start: () => Promise<RouteStats>
172
+ attach: () => Promise<LocationRecordingState | null>
173
+ pause: () => Promise<void>
174
+ resume: () => Promise<void>
175
+ stop: () => Promise<RouteFix[]>
176
+ refresh: () => Promise<RouteStats>
177
+ readonly stats: RouteStats
178
+ }
179
+ /**
180
+ * A recording to follow: start, pause, resume and stop the native recording,
181
+ * and the live numbers read back from it every few seconds while the page is
182
+ * in front. Reading pauses with the page hidden; the recording does not.
183
+ */
184
+ export declare function createRouteRecorder(options: RouteRecorderOptions): RouteRecorder;
185
+
125
186
  // From: src/types.ts
126
187
  /** Enabled native features, not the hardware report from device.getCapabilities(). */
127
188
  export declare interface CraftMobileCapabilities {
package/dist/index.js CHANGED
@@ -1 +1 @@
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};
1
+ import{appReview as O,biometrics as U,camera as _,deepLinks as M,device as B,haptics as I,health as Y,keepAwake as K,lifecycle as j,liveActivities as q,location as G,network as W,notifications as z,permissions as J,pushNotifications as Q,secureStorage as V,share as X,watchConnectivity as Z}from"craft-native/mobile";import{haptics as E}from"craft-native/mobile";function w(){if(typeof window>"u")return;return window}function v(e=w()){if(!e)return 0;if(typeof e.scrollY==="number")return e.scrollY;return e.document?.scrollingElement?.scrollTop??0}function Ae(e,t=w()){if(!t)return()=>{};let r=!1,n=typeof requestAnimationFrame==="function"?requestAnimationFrame:(o)=>setTimeout(o,16),i=()=>{if(r)return;r=!0,n(()=>{r=!1,e(v(t))})};return e(v(t)),t.addEventListener("scroll",i,{passive:!0}),()=>t.removeEventListener("scroll",i)}function H(e,t){let r=Math.max(0,-e),n=e<=0?Math.max(0,t)*0.5:0;return Math.round(Math.max(r,n))}function Re(e){let t=e.host??w();if(!t)return()=>{};let r=e.threshold??72,n=null,i=!1,o=!1,s=(a)=>e.onPull?.(a,i),d=(a)=>{let f=a.touches?.[0];return f?f.clientY:null},u=(a)=>{if(o||v(t)>0)return;n=d(a),i=!1},l=(a)=>{if(n===null)return;let f=d(a);if(f===null)return;let p=H(v(t),f-n);if(!i&&p>=r)i=!0,Promise.resolve(E.impact("medium")).catch(()=>{});else if(i&&p<r*0.8)i=!1;s(p)},m=async()=>{if(n===null)return;if(n=null,!i){s(0);return}i=!1,o=!0,e.onPull?.(r,!1);try{await e.onRefresh()}finally{o=!1,s(0)}},c=()=>{if(n===null)return;n=null,i=!1,s(0)};return t.addEventListener("touchstart",u,{passive:!0}),t.addEventListener("touchmove",l,{passive:!0}),t.addEventListener("touchend",m),t.addEventListener("touchcancel",c),()=>{t.removeEventListener("touchstart",u),t.removeEventListener("touchmove",l),t.removeEventListener("touchend",m),t.removeEventListener("touchcancel",c)}}function R(){if(typeof window>"u")return;return window}function T(e=R()){let t=e?.history?.state;return t!==null&&t!==void 0}function Le(e=R()){if(!T(e))return!1;return e.history.back(),!0}function C(e,t=N()){let r;try{r=new URL(e)}catch{return null}let n=`${r.search}${r.hash}`;if(r.protocol==="http:"||r.protocol==="https:"){if(!t||r.origin!==t)return null;return`${r.pathname||"/"}${n}`}return`${`/${[r.host,r.pathname.replace(/^\/+/,"")].filter(Boolean).join("/")}`}${n}`}function N(){if(typeof window>"u")return;return window.location?.origin}var A="stacks-mobile-deep-link";function Me(e,t){let r=(n)=>{let i=C(n);if(i)e(i)};return t.getInitialURL().then((n)=>{if(!n)return;try{if(sessionStorage.getItem(A)===n)return;sessionStorage.setItem(A,n)}catch{}r(n)}).catch(()=>{}),t.onLink((n,i)=>{if(i?.initial)return;r(n)})}var Pe=30;function F(e,t){let r=Math.PI/180,n=(t.latitude-e.latitude)*r,i=(t.longitude-e.longitude)*r,o=Math.sin(n/2)**2+Math.cos(e.latitude*r)*Math.cos(t.latitude*r)*Math.sin(i/2)**2;return 12742017.6*Math.asin(Math.min(1,Math.sqrt(o)))}function D(e,t=25){let r=new Set;return e.filter((n)=>Number.isFinite(n.latitude)&&Number.isFinite(n.longitude)&&Number.isFinite(n.timestamp)&&n.accuracy>=0&&n.accuracy<=t).sort((n,i)=>n.timestamp-i.timestamp).filter((n)=>{if(r.has(n.timestamp))return!1;return r.add(n.timestamp),!0})}function S(e,t={}){let r=D(e,t.maxAccuracyM??25),n=t.paceWindowS??30,i=[],o=0,s=0,d=null;for(let c of r){let a=i[i.length-1],f=d?(c.timestamp-d.timestamp)/1000:0;if(d=c,!a){i.push({fix:c,cumM:0});continue}if(f>30){i.push({fix:c,cumM:o});continue}let p=(c.timestamp-a.fix.timestamp)/1000,h=F(a.fix,c),P=Math.min(10,Math.max(3,a.fix.accuracy,c.accuracy));if(h<P)continue;if(o+=h,p>0&&h/p>=0.5)s+=p;i.push({fix:c,cumM:o})}let u=i[i.length-1],l=null,m=null;if(u){let c=u.fix.timestamp-n*1000,a=i.find((h)=>h.fix.timestamp>=c)??u,f=u.cumM-a.cumM,p=(u.fix.timestamp-a.fix.timestamp)/1000;if(f>=20&&p>0)m=f/p,l=p/(f/1000)}return{distanceM:o,movingS:s,paceSPerKm:l,averagePaceSPerKm:o>=50&&s>0?s/(o/1000):null,speedMps:m,accuracyM:r.length?r[r.length-1].accuracy:e.length?e[e.length-1].accuracy??null:null,fixes:r.length}}function Ee(e){if(!e||!Number.isFinite(e)||e>3600)return"–";let t=Math.round(e);return`${Math.floor(t/60)}:${String(t%60).padStart(2,"0")}`}function He(e){return e&&Number.isFinite(e)?(e*3.6).toFixed(1):"–"}var L={distanceM:0,movingS:0,paceSPerKm:null,averagePaceSPerKm:null,speedMps:null,accuracyM:null,fixes:0};function Te(e){let t=e.location,r=e.intervalMs??3000,n=L,i=null,o=()=>typeof document<"u"&&document.visibilityState==="hidden";async function s(){let l=await t.readRecording();return n=S(l,e),e.onUpdate?.(n),n}function d(){u(),i=setInterval(()=>{if(!o())s().catch(()=>{})},r)}function u(){if(i)clearInterval(i);i=null}return{get stats(){return n},async start(){return await t.startRecording({enableHighAccuracy:!0,...e.locationOptions}),n=L,e.onUpdate?.(n),d(),n},async attach(){let l=await t.getRecordingState().catch(()=>null);if(!l?.active)return null;if(await s().catch(()=>{}),!l.paused)d();return l},async pause(){u(),await t.pauseRecording(),await s().catch(()=>{})},async resume(){await t.resumeRecording(),d()},async stop(){u();let m=(await t.stopRecording())?.locations??[];return n=S(m,e),e.onUpdate?.(n),m},refresh:s}}var ee=U,te=_,ne=B,y=I,re=j,ie=G,oe=z,ae=J,se=V,ce=X,ue=O;function k(e){if(typeof e==="string")return e.trim()||null;if(!e||typeof e!=="object")return null;let t=e.url;return typeof t==="string"&&t.trim()?t:null}var le={async getInitialURL(){return k(await M.getInitialURL?.())},onLink(e){return M.onLink?.((t,r)=>{let n=k(t);if(n)e(n,{initial:r?.initial===!0})})??(()=>{})}},fe=K,pe=W,de=Q,me=Y,he=q,ve=Z;function x(){if(typeof window>"u")return;return window}function b(){let e=x()?.craft;if(!e||typeof e!=="object")return null;if(!("platform"in e)||e.platform!=="ios"&&e.platform!=="android")return null;let t="capabilities"in e?e.capabilities:void 0,r=t&&typeof t==="object"&&!Array.isArray(t)?Object.fromEntries(Object.entries(t).filter(([,n])=>typeof n==="boolean")):{};return{platform:e.platform,capabilities:r}}function g(){return b()!==null}function ye(){if(typeof window>"u")return!1;let e=window;if(e.CraftAndroid)return!0;let t=typeof navigator>"u"?"":navigator.userAgent;return Boolean(e.webkit?.messageHandlers?.craft)&&/iPhone|iPad|iPod/.test(t)}function be(e=2000){if(g()||ye())return Promise.resolve(!0);let t=x(),r=t;if(!t||!(r?.craft||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((n)=>{let i=()=>{clearTimeout(o),t.removeEventListener("craftReady",i),n(g())},o=setTimeout(i,e);t.addEventListener("craftReady",i,{once:!0})})}function we(e){let t=x();if(!t)return()=>{};if(b()){let n=!1;return queueMicrotask(()=>{if(!n)e(new Event("craftReady"))}),()=>{n=!0}}let r=(n)=>{if(!b())return;t.removeEventListener("craftReady",r),e(n)};return t.addEventListener("craftReady",r),()=>t.removeEventListener("craftReady",r)}async function ge(e){await y.impact("light");try{let t=await e();return await y.notification("success"),t}catch(t){throw await y.notification("error"),t}}var Fe={get nativeBridge(){return b()},biometrics:ee,camera:te,device:ne,haptics:y,lifecycle:re,location:ie,notifications:oe,permissions:ae,secureStorage:se,share:ce,appReview:ue,deepLinks:le,keepAwake:fe,network:pe,pushNotifications:de,health:me,liveActivities:he,watchConnectivity:ve,isNativeMobile:g,whenNative:be,onReady:we,withFeedback:ge};export{Pe as ROUTE_GAP_S,ue as appReview,ee as biometrics,te as camera,T as canGoBack,Te as createRouteRecorder,C as deepLinkPath,le as deepLinks,ne as device,F as fixDistance,Me as followDeepLinks,b as getNativeMobileBridge,Le as goBack,y as haptics,ye as hasNativeMobileHost,me as health,g as isNativeMobile,fe as keepAwake,re as lifecycle,he as liveActivities,ie as location,Fe as mobile,pe as network,k as normalizeDeepLinkURL,oe as notifications,Ae as observePageScroll,Re as observePullToRefresh,we as onMobileReady,Ee as paceLabel,v as pageScrollTop,ae as permissions,H as pullDistance,de as pushNotifications,S as routeStats,se as secureStorage,ce as share,He as speedLabel,D as usableFixes,ve as watchConnectivity,be as whenNativeMobile,ge as withNativeFeedback};
@@ -0,0 +1,60 @@
1
+ import type { Location, LocationApi, LocationOptions, LocationRecordingState } from './types';
2
+ /** Great-circle distance between two fixes, metres. */
3
+ export declare function fixDistance(a: { latitude: number, longitude: number }, b: { latitude: number, longitude: number }): number;
4
+ /** The fixes worth counting, oldest first: a known position, accurate enough. */
5
+ export declare function usableFixes<T extends RouteFix>(fixes: readonly T[], maxAccuracyM?: number): T[];
6
+ /**
7
+ * The live numbers for a recording.
8
+ *
9
+ * Standing still, GPS wanders a few metres about the true spot, and summing
10
+ * every hop would add distance nobody covered. So distance is counted from an
11
+ * anchor: a fix only counts once it is further from the last counted one than
12
+ * the signal could explain (the larger of 3 m and the fixes' accuracy, capped
13
+ * at 10 m), and then becomes the anchor. Fixes more than ROUTE_GAP_S apart
14
+ * (a pause) are a break: the ground between them is not counted.
15
+ */
16
+ export declare function routeStats(fixes: readonly RouteFix[], options?: RouteStatsOptions): RouteStats;
17
+ /** "5:12" per kilometre, or "–" without a pace. */
18
+ export declare function paceLabel(secondsPerKm: number | null | undefined): string;
19
+ /** "24.3" kilometres per hour. */
20
+ export declare function speedLabel(metresPerSecond: number | null | undefined): string;
21
+ /**
22
+ * A recording to follow: start, pause, resume and stop the native recording,
23
+ * and the live numbers read back from it every few seconds while the page is
24
+ * in front. Reading pauses with the page hidden; the recording does not.
25
+ */
26
+ export declare function createRouteRecorder(options: RouteRecorderOptions): RouteRecorder;
27
+ /** Fixes further apart than this, seconds, are a break in the route (a pause). */
28
+ export declare const ROUTE_GAP_S: 30;
29
+ /** A fix as Craft records it. */
30
+ export declare interface RouteFix extends Location {
31
+ altitudeAccuracy?: number
32
+ }
33
+ export declare interface RouteStats {
34
+ distanceM: number
35
+ movingS: number
36
+ paceSPerKm: number | null
37
+ averagePaceSPerKm: number | null
38
+ speedMps: number | null
39
+ accuracyM: number | null
40
+ fixes: number
41
+ }
42
+ export declare interface RouteStatsOptions {
43
+ maxAccuracyM?: number
44
+ paceWindowS?: number
45
+ }
46
+ export declare interface RouteRecorderOptions extends RouteStatsOptions {
47
+ intervalMs?: number
48
+ onUpdate?: (stats: RouteStats) => void
49
+ location: LocationApi
50
+ locationOptions?: LocationOptions
51
+ }
52
+ export declare interface RouteRecorder {
53
+ start: () => Promise<RouteStats>
54
+ attach: () => Promise<LocationRecordingState | null>
55
+ pause: () => Promise<void>
56
+ resume: () => Promise<void>
57
+ stop: () => Promise<RouteFix[]>
58
+ refresh: () => Promise<RouteStats>
59
+ readonly stats: RouteStats
60
+ }
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.25",
5
+ "version": "0.75.26",
6
6
  "description": "Craft-powered native mobile runtime for Stacks and STX applications",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "craft-native": ">=0.0.103"
36
36
  },
37
37
  "devDependencies": {
38
- "@stacksjs/build": "0.75.25",
38
+ "@stacksjs/build": "0.75.26",
39
39
  "better-dx": "^0.2.24"
40
40
  }
41
41
  }