@stacksjs/mobile 0.75.54 → 0.75.56
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 +30 -0
- package/dist/index.d.ts +200 -2
- package/dist/index.js +1 -1
- package/dist/spotlight.d.ts +196 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,6 +117,36 @@ hasNativeMobileHost() // true inside the phone shell, even before the bridge
|
|
|
117
117
|
const native = await whenNativeMobile() // resolves once it knows, false in a browser
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
## Device search index
|
|
121
|
+
|
|
122
|
+
What a device may index is configuration (`config/mobile.ts`, `spotlight`): one
|
|
123
|
+
entry per kind of content, with the number of slots it may claim, the route a
|
|
124
|
+
tapped entry opens, and the noun that names a record with no name of its own.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import mobileConfig from '../config/mobile'
|
|
128
|
+
import { createSpotlightIndex, onSpotlightTap } from '@stacksjs/mobile'
|
|
129
|
+
|
|
130
|
+
const spotlight = createSpotlightIndex({ kinds: mobileConfig.spotlight.kinds })
|
|
131
|
+
|
|
132
|
+
await spotlight.index('trail', { id: 42, name: 'Eagle Peak Loop' })
|
|
133
|
+
await spotlight.sync('club', myClubs)
|
|
134
|
+
await spotlight.remove('club', 7)
|
|
135
|
+
await spotlight.clear()
|
|
136
|
+
onSpotlightTap(spotlight, route => location.assign(route))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
iOS indexes donated `NSUserActivity` objects — Craft's `siri` bridge — and
|
|
140
|
+
hands a tapped one back only for an activity type the build declares in
|
|
141
|
+
`Info.plist`, a list fixed at build time. A type per record id cannot be
|
|
142
|
+
declared, so each kind gets a fixed number of slots, each slot holds whichever
|
|
143
|
+
record is in it, and the oldest donation makes room; `buddy build:ios` writes
|
|
144
|
+
the declarations from the same config.
|
|
145
|
+
|
|
146
|
+
Every call is a no-op that reports as much off a native host, on a build whose
|
|
147
|
+
host predates the bridge, in a WebView with no `localStorage`, and where the
|
|
148
|
+
index is turned off, so a page can donate unconditionally.
|
|
149
|
+
|
|
120
150
|
## Page gestures and navigation
|
|
121
151
|
|
|
122
152
|
The phone-native behaviour a screen expects, kept out of STX templates:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled TypeScript declarations
|
|
3
|
-
* Generated from
|
|
3
|
+
* Generated from 6 source files
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// From: src/index.ts
|
|
@@ -205,6 +205,204 @@ declare interface RouteRecorder {
|
|
|
205
205
|
readonly stats: RouteStats
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// From: src/spotlight.ts
|
|
209
|
+
/**
|
|
210
|
+
* Read configured kinds, leaving out any that could not work.
|
|
211
|
+
*
|
|
212
|
+
* Dropping rather than throwing: a typo in one kind's budget should cost that
|
|
213
|
+
* kind its index, not the app's startup. The problems come back so a caller
|
|
214
|
+
* can say what it ignored — silence here looks exactly like a kind nothing has
|
|
215
|
+
* donated to yet.
|
|
216
|
+
*/
|
|
217
|
+
declare function readSpotlightKinds(raw: unknown): SpotlightKindsResult;
|
|
218
|
+
/** One kind by name, or null for anything this build does not index. */
|
|
219
|
+
declare function spotlightKind(kinds: readonly SpotlightKind[], name: string): SpotlightKind | null;
|
|
220
|
+
/** The action a kind's slot is donated under, or null for no such slot. */
|
|
221
|
+
declare function spotlightAction(kinds: readonly SpotlightKind[], kind: string, slot: number): string | null;
|
|
222
|
+
/**
|
|
223
|
+
* The kind and slot an action names, or null for anything else.
|
|
224
|
+
*
|
|
225
|
+
* Strict about the form — no padding, no trailing text, and the kind has to be
|
|
226
|
+
* one these kinds carry — because this is what decides whether something the
|
|
227
|
+
* system handed back is one of the app's own.
|
|
228
|
+
*/
|
|
229
|
+
declare function spotlightSlot(kinds: readonly SpotlightKind[], action: string): { kind: string, slot: number } | null;
|
|
230
|
+
/** Every action these kinds may be donated under, in configured order. */
|
|
231
|
+
declare function spotlightActions(kinds: readonly SpotlightKind[]): string[];
|
|
232
|
+
/**
|
|
233
|
+
* Every activity type a build has to declare, for an `Info.plist`.
|
|
234
|
+
*
|
|
235
|
+
* The slots of every configured kind, plus whatever the app donates outside
|
|
236
|
+
* the registry (`activityTypes`: a Siri phrase, an App Intent), each prefixed
|
|
237
|
+
* with the bundle id the way Craft types the activity it donates.
|
|
238
|
+
*/
|
|
239
|
+
declare function spotlightActivityTypes(config: unknown, bundleId: string): string[];
|
|
240
|
+
/**
|
|
241
|
+
* What the device shows for a record.
|
|
242
|
+
*
|
|
243
|
+
* The record's name alone: Craft hands the same string to Spotlight as the
|
|
244
|
+
* activity's title and to Siri as its invocation phrase, and a name is the one
|
|
245
|
+
* form that reads well in both — it is what somebody types looking for the
|
|
246
|
+
* record, and what they could say.
|
|
247
|
+
*/
|
|
248
|
+
declare function spotlightTitle(kinds: readonly SpotlightKind[], kind: string, item: SpotlightItem): string;
|
|
249
|
+
/**
|
|
250
|
+
* Put a record in its kind's slots, taking the oldest when every one is taken.
|
|
251
|
+
*
|
|
252
|
+
* Returns the whole next list rather than mutating, so a caller stores exactly
|
|
253
|
+
* what it donated: a failure between the two cannot leave bookkeeping that
|
|
254
|
+
* claims an entry the device does not have.
|
|
255
|
+
*/
|
|
256
|
+
declare function placeSpotlightItem(kinds: readonly SpotlightKind[], entries: readonly SpotlightEntry[], kind: string, item: SpotlightItem, now?: number): SpotlightPlacement | null;
|
|
257
|
+
/** Take a record out — what it held is the slot to un-donate. */
|
|
258
|
+
declare function removeSpotlightItem(entries: readonly SpotlightEntry[], kind: string, itemId: number): { entries: SpotlightEntry[], removed: SpotlightEntry | null };
|
|
259
|
+
/** The route a tapped entry means, or null for a slot holding nothing. */
|
|
260
|
+
declare function spotlightRoute(kinds: readonly SpotlightKind[], entries: readonly SpotlightEntry[], action: string): string | null;
|
|
261
|
+
/**
|
|
262
|
+
* Read back a stored assignment.
|
|
263
|
+
*
|
|
264
|
+
* Stored state outlives the build that wrote it — a kind can be renamed and a
|
|
265
|
+
* budget can shrink — so anything that is not a slot these kinds declare is
|
|
266
|
+
* dropped rather than trusted: a tap on it would resolve to a record the
|
|
267
|
+
* `Info.plist` no longer lets through.
|
|
268
|
+
*/
|
|
269
|
+
declare function readSpotlightEntries(kinds: readonly SpotlightKind[], raw: unknown): SpotlightEntry[];
|
|
270
|
+
/** The host's Siri bridge, or undefined anywhere that is not a native build. */
|
|
271
|
+
declare function spotlightBridge(): SpotlightBridge | undefined;
|
|
272
|
+
declare function createSpotlightIndex(options: SpotlightIndexOptions): SpotlightIndex;
|
|
273
|
+
/**
|
|
274
|
+
* Follow a tap on an indexed entry.
|
|
275
|
+
*
|
|
276
|
+
* Craft reports one as a `craftSiriShortcut` event carrying `{action}` (a
|
|
277
|
+
* home-screen quick action arrives as `craftShortcut` with `{type}`, handled
|
|
278
|
+
* here too), and the handler is called with the route the action resolves to —
|
|
279
|
+
* anything this index does not recognise is left alone, so an app with its own
|
|
280
|
+
* shortcut actions can keep handling those itself.
|
|
281
|
+
*
|
|
282
|
+
* These are plain window events, listened for directly rather than through
|
|
283
|
+
* Craft's `shortcuts.onShortcut` and `siri.onInvoke`: both of those are
|
|
284
|
+
* themselves listeners on these same events, so going through them would run
|
|
285
|
+
* every tap twice and leave a subscription their wrappers cannot take back
|
|
286
|
+
* down.
|
|
287
|
+
*/
|
|
288
|
+
declare function onSpotlightTap(index: SpotlightIndex, handler: (route: string) => void): () => void;
|
|
289
|
+
/**
|
|
290
|
+
* The most slots one kind may claim.
|
|
291
|
+
*
|
|
292
|
+
* Every slot is a line in the generated `Info.plist` and a possible donation
|
|
293
|
+
* at launch, so a mistyped budget is worth catching before a build log.
|
|
294
|
+
*/
|
|
295
|
+
declare const MAX_SPOTLIGHT_SLOTS_PER_KIND: 64;
|
|
296
|
+
/**
|
|
297
|
+
* The app's own content in a device's search index.
|
|
298
|
+
*
|
|
299
|
+
* An app somebody opens is not the same thing as a record somebody finds: a
|
|
300
|
+
* trail, a club, a document that turns up when its name is typed into the home
|
|
301
|
+
* screen is reached without the app being thought of first. iOS builds that
|
|
302
|
+
* index out of donated `NSUserActivity` objects, which Craft's `siri` bridge
|
|
303
|
+
* is: a title and an action, and what the app gets back on a tap is that
|
|
304
|
+
* action and nothing else. Two consequences shape this whole module:
|
|
305
|
+
*
|
|
306
|
+
* 1. iOS only hands a tapped activity back to an app that declares its type
|
|
307
|
+
* in `NSUserActivityTypes`, and that list is fixed at build time. A type
|
|
308
|
+
* per record id would be unbounded, so it cannot be declared — and an
|
|
309
|
+
* entry iOS will not hand back is worse than no entry, because it opens
|
|
310
|
+
* the app on whatever screen it was last on.
|
|
311
|
+
* 2. The activity carries no payload of the app's. Whatever a tap needs to
|
|
312
|
+
* know has to be derivable from the action alone.
|
|
313
|
+
*
|
|
314
|
+
* So each kind of content gets a fixed number of *slots* — `trail-slot-0`,
|
|
315
|
+
* `club-slot-0`, every one of them declared in the generated `Info.plist` by
|
|
316
|
+
* the iOS build — and each slot holds whichever record is currently in it.
|
|
317
|
+
* This module is that assignment: the pure part decides it, and
|
|
318
|
+
* `createSpotlightIndex` stores it and makes the bridge calls. Because a
|
|
319
|
+
* slot's identifier is also the activity's `persistentIdentifier`, re-donating
|
|
320
|
+
* a slot replaces the entry iOS already has rather than adding another.
|
|
321
|
+
*
|
|
322
|
+
* What is indexed, how many of each, and where a tap opens is configuration
|
|
323
|
+
* (`config/mobile.ts`, `spotlight`), so an app adds a kind with an entry and a
|
|
324
|
+
* call rather than with another copy of the bookkeeping below.
|
|
325
|
+
*
|
|
326
|
+
* Nothing here needs a native host: off one, every call is a no-op that says
|
|
327
|
+
* so, which is what lets a page donate unconditionally.
|
|
328
|
+
*/
|
|
329
|
+
/** One kind of content, as the index holds it. */
|
|
330
|
+
declare interface SpotlightKind {
|
|
331
|
+
name: string
|
|
332
|
+
slots: number
|
|
333
|
+
route: string
|
|
334
|
+
noun: string
|
|
335
|
+
}
|
|
336
|
+
/** A kind as an app configures it, before it is checked. */
|
|
337
|
+
declare interface SpotlightKindInput {
|
|
338
|
+
slots: number
|
|
339
|
+
route: string
|
|
340
|
+
noun?: string
|
|
341
|
+
}
|
|
342
|
+
/** What this module needs of a record. A list row and a detail row both fit. */
|
|
343
|
+
declare interface SpotlightItem {
|
|
344
|
+
id: number
|
|
345
|
+
name?: string | null
|
|
346
|
+
}
|
|
347
|
+
/** One record's place in the index. */
|
|
348
|
+
declare interface SpotlightEntry {
|
|
349
|
+
kind: string
|
|
350
|
+
slot: number
|
|
351
|
+
itemId: number
|
|
352
|
+
title: string
|
|
353
|
+
donatedAt: number
|
|
354
|
+
}
|
|
355
|
+
/** Configured kinds, and what was wrong with the ones left out. */
|
|
356
|
+
declare interface SpotlightKindsResult {
|
|
357
|
+
kinds: SpotlightKind[]
|
|
358
|
+
problems: string[]
|
|
359
|
+
}
|
|
360
|
+
/** The result of fitting a record into its kind's slots. */
|
|
361
|
+
declare interface SpotlightPlacement {
|
|
362
|
+
entries: SpotlightEntry[]
|
|
363
|
+
entry: SpotlightEntry
|
|
364
|
+
evicted: SpotlightEntry | null
|
|
365
|
+
changed: boolean
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Craft's Siri calls, which are also its Spotlight calls.
|
|
369
|
+
*
|
|
370
|
+
* `register` builds an `NSUserActivity` typed `<bundle id>.<action>`, titles it
|
|
371
|
+
* with the phrase, marks it eligible for search and prediction and makes it
|
|
372
|
+
* current — so a registered phrase is both a Siri suggestion and a Spotlight
|
|
373
|
+
* entry — and `remove` deletes the saved activity by that same identifier.
|
|
374
|
+
*
|
|
375
|
+
* Typed here because `craft-native`'s browser SDK exports neither: they exist
|
|
376
|
+
* only on the object Craft injects into the WebView, so without this seam every
|
|
377
|
+
* caller reaches into `globalThis.craft` and guesses at shapes.
|
|
378
|
+
*/
|
|
379
|
+
declare interface SpotlightBridge {
|
|
380
|
+
register?: (phrase: string, action: string) => Promise<unknown>
|
|
381
|
+
remove?: (action: string) => Promise<unknown>
|
|
382
|
+
}
|
|
383
|
+
declare interface SpotlightIndexOptions {
|
|
384
|
+
kinds: Record<string, SpotlightKindInput> | readonly SpotlightKind[]
|
|
385
|
+
enabled?: boolean
|
|
386
|
+
storageKey?: string
|
|
387
|
+
legacyStorageKeys?: readonly string[]
|
|
388
|
+
timeoutMs?: number
|
|
389
|
+
bridge?: () => SpotlightBridge | undefined
|
|
390
|
+
now?: () => number
|
|
391
|
+
onProblem?: (message: string) => void
|
|
392
|
+
}
|
|
393
|
+
/** The device's index, as an app drives it. */
|
|
394
|
+
declare interface SpotlightIndex {
|
|
395
|
+
readonly kinds: readonly SpotlightKind[]
|
|
396
|
+
index: (kind: string, item: SpotlightItem) => Promise<boolean>
|
|
397
|
+
sync: (kind: string, items: readonly SpotlightItem[]) => Promise<number>
|
|
398
|
+
remove: (kind: string, itemId: number) => Promise<boolean>
|
|
399
|
+
clear: () => Promise<number>
|
|
400
|
+
routeFor: (action: string) => string | null
|
|
401
|
+
entries: () => readonly SpotlightEntry[]
|
|
402
|
+
activityTypes: (bundleId: string) => string[]
|
|
403
|
+
reset: () => void
|
|
404
|
+
}
|
|
405
|
+
|
|
208
406
|
// From: src/types.ts
|
|
209
407
|
/** Enabled native features, not the hardware report from device.getCapabilities(). */
|
|
210
408
|
declare interface CraftMobileCapabilities {
|
|
@@ -568,4 +766,4 @@ type HealthWorkoutActivity = | 'running' | 'cycling' | 'walking' | 'hiking' | 's
|
|
|
568
766
|
| 'stairClimbing' | 'yoga' | 'pilates' | 'strength' | 'hiit' | 'crossTraining'
|
|
569
767
|
| 'crossCountrySkiing' | 'skiing' | 'paddling' | 'climbing' | 'dance' | 'mobility' | 'other';
|
|
570
768
|
|
|
571
|
-
export { normalizeDeepLinkURL, getNativeMobileBridge, isNativeMobile, hasNativeMobileHost, whenNativeMobile, whenBridgeReady, onMobileReady, withNativeFeedback, biometrics, camera, device, haptics, lifecycle, location, notifications, permissions, secureStorage, share, appReview, deepLinks, keepAwake, network, pushNotifications, health, liveActivities, watchConnectivity, mobile, pageScrollTop, observePageScroll, pullDistance, observePullToRefresh, ScrollHost, PullToRefreshOptions, canGoBack, goBack, deepLinkPath, followDeepLinks, HistoryHost, fixDistance, usableFixes, routeStats, paceLabel, speedLabel, createRouteRecorder, ROUTE_GAP_S, RouteFix, RouteStats, RouteStatsOptions, RouteRecorderOptions, RouteRecorder, CraftMobileCapabilities, CraftMobileBridge, CraftReadyEvent, MobileApi, DeviceInfo, DeviceCapabilities, CameraOptions, PhotoResult, Location, LocationOptions, LocationRecordingState, LocationRecordingResult, ShareOptions, NotificationOptions, DeviceApi, HapticsApi, PermissionsApi, CameraApi, BiometricsApi, SecureStorageApi, LocationApi, ShareApi, LifecycleApi, NotificationsApi, KeepAwakeApi, DeepLinksApi, NetworkStatus, NetworkApi, AppReviewApi, PushNotificationsApi, HealthDataOptions, HealthDataResult, HealthWorkoutLocation, HealthWorkout, HealthWorkoutResult, HealthWorkoutQuery, HealthWorkoutSample, HealthDailyValue, HealthAuthorizationOptions, HealthApi, LiveActivityState, LiveActivityOptions, LiveActivitiesApi, WatchConnectivityApi, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, BiometricType, AppState, HealthDataType, HealthDailyType, HealthWorkoutType, HealthWorkoutActivity };
|
|
769
|
+
export { normalizeDeepLinkURL, getNativeMobileBridge, isNativeMobile, hasNativeMobileHost, whenNativeMobile, whenBridgeReady, onMobileReady, withNativeFeedback, biometrics, camera, device, haptics, lifecycle, location, notifications, permissions, secureStorage, share, appReview, deepLinks, keepAwake, network, pushNotifications, health, liveActivities, watchConnectivity, mobile, pageScrollTop, observePageScroll, pullDistance, observePullToRefresh, ScrollHost, PullToRefreshOptions, canGoBack, goBack, deepLinkPath, followDeepLinks, HistoryHost, fixDistance, usableFixes, routeStats, paceLabel, speedLabel, createRouteRecorder, ROUTE_GAP_S, RouteFix, RouteStats, RouteStatsOptions, RouteRecorderOptions, RouteRecorder, readSpotlightKinds, spotlightKind, spotlightAction, spotlightSlot, spotlightActions, spotlightActivityTypes, spotlightTitle, placeSpotlightItem, removeSpotlightItem, spotlightRoute, readSpotlightEntries, spotlightBridge, createSpotlightIndex, onSpotlightTap, MAX_SPOTLIGHT_SLOTS_PER_KIND, SpotlightKind, SpotlightKindInput, SpotlightItem, SpotlightEntry, SpotlightKindsResult, SpotlightPlacement, SpotlightBridge, SpotlightIndexOptions, SpotlightIndex, CraftMobileCapabilities, CraftMobileBridge, CraftReadyEvent, MobileApi, DeviceInfo, DeviceCapabilities, CameraOptions, PhotoResult, Location, LocationOptions, LocationRecordingState, LocationRecordingResult, ShareOptions, NotificationOptions, DeviceApi, HapticsApi, PermissionsApi, CameraApi, BiometricsApi, SecureStorageApi, LocationApi, ShareApi, LifecycleApi, NotificationsApi, KeepAwakeApi, DeepLinksApi, NetworkStatus, NetworkApi, AppReviewApi, PushNotificationsApi, HealthDataOptions, HealthDataResult, HealthWorkoutLocation, HealthWorkout, HealthWorkoutResult, HealthWorkoutQuery, HealthWorkoutSample, HealthDailyValue, HealthAuthorizationOptions, HealthApi, LiveActivityState, LiveActivityOptions, LiveActivitiesApi, WatchConnectivityApi, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, BiometricType, AppState, HealthDataType, HealthDailyType, HealthWorkoutType, HealthWorkoutActivity };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{appReview as _,biometrics as I,camera as q,deepLinks as P,device as Y,haptics as K,health as j,keepAwake as G,lifecycle as W,liveActivities as z,location as J,network as Q,notifications as V,permissions as X,pushNotifications as Z,secureStorage as ee,share as te,watchConnectivity as ne}from"craft-native/mobile";import{haptics as C}from"craft-native/mobile";function A(){if(typeof window>"u")return;return window}function b(e=A()){if(!e)return 0;if(typeof e.scrollY==="number")return e.scrollY;return e.document?.scrollingElement?.scrollTop??0}function Se(e,t=A()){if(!t)return()=>{};let r=!1,n=typeof requestAnimationFrame==="function"?requestAnimationFrame:(i)=>setTimeout(i,16),o=()=>{if(r)return;r=!0,n(()=>{r=!1,e(b(t))})};return e(b(t)),t.addEventListener("scroll",o,{passive:!0}),()=>t.removeEventListener("scroll",o)}function N(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 Le(e){let t=e.host??A();if(!t)return()=>{};let r=e.threshold??72,n=null,o=!1,i=!1,c=typeof requestAnimationFrame==="function"?requestAnimationFrame:(a)=>setTimeout(a,16),u=null,s=(a)=>{u=null,e.onPull?.(a,o)},f=(a)=>{let m=u!==null;if(u=a,m)return;c(()=>{if(u!==null)s(u)})},v=(a)=>{let m=a.touches?.[0];return m?m.clientY:null},l=(a)=>{if(i||b(t)>0)return;n=v(a),o=!1},p=(a)=>{if(n===null)return;let m=v(a);if(m===null)return;let x=N(b(t),m-n);if(!o&&x>=r)o=!0,Promise.resolve(C.impact("medium")).catch(()=>{});else if(o&&x<r*0.8)o=!1;f(x)},h=async()=>{if(n===null)return;if(n=null,!o){s(0);return}o=!1,i=!0,s(r);try{await e.onRefresh()}finally{i=!1,s(0)}},d=()=>{if(n===null)return;n=null,o=!1,s(0)};return t.addEventListener("touchstart",l,{passive:!0}),t.addEventListener("touchmove",p,{passive:!0}),t.addEventListener("touchend",h),t.addEventListener("touchcancel",d),()=>{u=null,t.removeEventListener("touchstart",l),t.removeEventListener("touchmove",p),t.removeEventListener("touchend",h),t.removeEventListener("touchcancel",d)}}function L(){if(typeof window>"u")return;return window}function F(e=L()){let t=e?.history?.state;return t!==null&&t!==void 0}function Me(e=L()){if(!F(e))return!1;return e.history.back(),!0}function D(e,t=O()){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 O(){if(typeof window>"u")return;return window.location?.origin}var S="stacks-mobile-deep-link";function Pe(e,t){let r=(n)=>{let o=D(n);if(o)e(o)};return t.getInitialURL().then((n)=>{if(!n)return;try{if(sessionStorage.getItem(S)===n)return;sessionStorage.setItem(S,n)}catch{}r(n)}).catch(()=>{}),t.onLink((n,o)=>{if(o?.initial)return;r(n)})}var He=30;function U(e,t){let r=Math.PI/180,n=(t.latitude-e.latitude)*r,o=(t.longitude-e.longitude)*r,i=Math.sin(n/2)**2+Math.cos(e.latitude*r)*Math.cos(t.latitude*r)*Math.sin(o/2)**2;return 12742017.6*Math.asin(Math.min(1,Math.sqrt(i)))}function B(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,o)=>n.timestamp-o.timestamp).filter((n)=>{if(r.has(n.timestamp))return!1;return r.add(n.timestamp),!0})}function k(e,t={}){let r=B(e,t.maxAccuracyM??25),n=t.paceWindowS??30,o=[],i=0,c=0,u=null;for(let l of r){let p=o[o.length-1],h=u?(l.timestamp-u.timestamp)/1000:0;if(u=l,!p){o.push({fix:l,cumM:0});continue}if(h>30){o.push({fix:l,cumM:i});continue}let d=(l.timestamp-p.fix.timestamp)/1000,a=U(p.fix,l),m=Math.min(10,Math.max(3,p.fix.accuracy,l.accuracy));if(a<m)continue;if(i+=a,d>0&&a/d>=0.5)c+=d;o.push({fix:l,cumM:i})}let s=o[o.length-1],f=null,v=null;if(s){let l=s.fix.timestamp-n*1000,p=o.find((a)=>a.fix.timestamp>=l)??s,h=s.cumM-p.cumM,d=(s.fix.timestamp-p.fix.timestamp)/1000;if(h>=20&&d>0)v=h/d,f=d/(h/1000)}return{distanceM:i,movingS:c,paceSPerKm:f,averagePaceSPerKm:i>=50&&c>0?c/(i/1000):null,speedMps:v,accuracyM:r.length?r[r.length-1].accuracy:e.length?e[e.length-1].accuracy??null:null,fixes:r.length}}function Te(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 Ce(e){return e&&Number.isFinite(e)?(e*3.6).toFixed(1):"–"}var M={distanceM:0,movingS:0,paceSPerKm:null,averagePaceSPerKm:null,speedMps:null,accuracyM:null,fixes:0};function Ne(e){let t=e.location,r=e.intervalMs??3000,n=M,o=null,i=()=>typeof document<"u"&&document.visibilityState==="hidden";async function c(){let f=await t.readRecording();return n=k(f,e),e.onUpdate?.(n),n}function u(){s(),o=setInterval(()=>{if(!i())c().catch(()=>{})},r)}function s(){if(o)clearInterval(o);o=null}return{get stats(){return n},async start(){return await t.startRecording({enableHighAccuracy:!0,...e.locationOptions}),n=M,e.onUpdate?.(n),u(),n},async attach(){let f=await t.getRecordingState().catch(()=>null);if(!f?.active)return null;if(await c().catch(()=>{}),!f.paused)u();return f},async pause(){s(),await t.pauseRecording(),await c().catch(()=>{})},async resume(){await t.resumeRecording(),u()},async stop(){s();let v=(await t.stopRecording())?.locations??[];return n=k(v,e),e.onUpdate?.(n),v},refresh:c}}var re=I,oe=q,ie=Y,w=K,ae=W,se=J,ce=V,ue=X,le=ee,fe=te,pe=_;function E(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 de={async getInitialURL(){if(!await H())return null;return E(await P.getInitialURL?.())},onLink(e){let t=null,r=!1,n=()=>{if(r)return;t=P.onLink?.((o,i)=>{let c=E(o);if(c)e(c,{initial:i?.initial===!0})})??null};if(y()?.craft)n();else H().then(n);return()=>{r=!0,t?.()}}},me=G,ve=Q,he=Z,ye=j,be=z,we=ne;function y(){if(typeof window>"u")return;return window}function g(){let e=y()?.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 R(){return g()!==null}function T(){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 ge(e=2000){if(R()||T())return Promise.resolve(!0);let t=y(),r=t;if(!t||!(r?.craft||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((n)=>{let o=()=>{clearTimeout(i),t.removeEventListener("craftReady",o),n(R())},i=setTimeout(o,e);t.addEventListener("craftReady",o,{once:!0})})}function H(e=15000){let t=y();if(t?.craft)return Promise.resolve(!0);let r=t;if(!t||!(T()||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((n)=>{let o=()=>{clearTimeout(i),t.removeEventListener("craftReady",o),n(Boolean(t.craft))},i=setTimeout(o,e);t.addEventListener("craftReady",o)})}function xe(e){let t=y();if(!t)return()=>{};if(g()){let n=!1;return queueMicrotask(()=>{if(!n)e(new Event("craftReady"))}),()=>{n=!0}}let r=(n)=>{if(!g())return;t.removeEventListener("craftReady",r),e(n)};return t.addEventListener("craftReady",r),()=>t.removeEventListener("craftReady",r)}async function Ae(e){await w.impact("light");try{let t=await e();return await w.notification("success"),t}catch(t){throw await w.notification("error"),t}}var Oe={get nativeBridge(){return g()},biometrics:re,camera:oe,device:ie,haptics:w,lifecycle:ae,location:se,notifications:ce,permissions:ue,secureStorage:le,share:fe,appReview:pe,deepLinks:de,keepAwake:me,network:ve,pushNotifications:he,health:ye,liveActivities:be,watchConnectivity:we,isNativeMobile:R,whenNative:ge,onReady:xe,withFeedback:Ae};export{He as ROUTE_GAP_S,pe as appReview,re as biometrics,oe as camera,F as canGoBack,Ne as createRouteRecorder,D as deepLinkPath,de as deepLinks,ie as device,U as fixDistance,Pe as followDeepLinks,g as getNativeMobileBridge,Me as goBack,w as haptics,T as hasNativeMobileHost,ye as health,R as isNativeMobile,me as keepAwake,ae as lifecycle,be as liveActivities,se as location,Oe as mobile,ve as network,E as normalizeDeepLinkURL,ce as notifications,Se as observePageScroll,Le as observePullToRefresh,xe as onMobileReady,Te as paceLabel,b as pageScrollTop,ue as permissions,N as pullDistance,he as pushNotifications,k as routeStats,le as secureStorage,fe as share,Ce as speedLabel,B as usableFixes,we as watchConnectivity,H as whenBridgeReady,ge as whenNativeMobile,Ae as withNativeFeedback};
|
|
1
|
+
import{appReview as mt,biometrics as gt,camera as ht,deepLinks as U,device as yt,haptics as St,health as vt,keepAwake as bt,lifecycle as At,liveActivities as xt,location as wt,network as Rt,notifications as Tt,permissions as Et,pushNotifications as kt,secureStorage as Lt,share as Pt,watchConnectivity as Mt}from"craft-native/mobile";import{haptics as z}from"craft-native/mobile";function M(){if(typeof window>"u")return;return window}function k(t=M()){if(!t)return 0;if(typeof t.scrollY==="number")return t.scrollY;return t.document?.scrollingElement?.scrollTop??0}function Qt(t,n=M()){if(!n)return()=>{};let r=!1,e=typeof requestAnimationFrame==="function"?requestAnimationFrame:(i)=>setTimeout(i,16),o=()=>{if(r)return;r=!0,e(()=>{r=!1,t(k(n))})};return t(k(n)),n.addEventListener("scroll",o,{passive:!0}),()=>n.removeEventListener("scroll",o)}function J(t,n){let r=Math.max(0,-t),e=t<=0?Math.max(0,n)*0.5:0;return Math.round(Math.max(r,e))}function Zt(t){let n=t.host??M();if(!n)return()=>{};let r=t.threshold??72,e=null,o=!1,i=!1,s=typeof requestAnimationFrame==="function"?requestAnimationFrame:(g)=>setTimeout(g,16),l=null,a=(g)=>{l=null,t.onPull?.(g,o)},u=(g)=>{let b=l!==null;if(l=g,b)return;s(()=>{if(l!==null)a(l)})},h=(g)=>{let b=g.touches?.[0];return b?b.clientY:null},d=(g)=>{if(i||k(n)>0)return;e=h(g),o=!1},p=(g)=>{if(e===null)return;let b=h(g);if(b===null)return;let x=J(k(n),b-e);if(!o&&x>=r)o=!0,Promise.resolve(z.impact("medium")).catch(()=>{});else if(o&&x<r*0.8)o=!1;u(x)},v=async()=>{if(e===null)return;if(e=null,!o){a(0);return}o=!1,i=!0,a(r);try{await t.onRefresh()}finally{i=!1,a(0)}},m=()=>{if(e===null)return;e=null,o=!1,a(0)};return n.addEventListener("touchstart",d,{passive:!0}),n.addEventListener("touchmove",p,{passive:!0}),n.addEventListener("touchend",v),n.addEventListener("touchcancel",m),()=>{l=null,n.removeEventListener("touchstart",d),n.removeEventListener("touchmove",p),n.removeEventListener("touchend",v),n.removeEventListener("touchcancel",m)}}function O(){if(typeof window>"u")return;return window}function V(t=O()){let n=t?.history?.state;return n!==null&&n!==void 0}function ee(t=O()){if(!V(t))return!1;return t.history.back(),!0}function Q(t,n=Z()){let r;try{r=new URL(t)}catch{return null}let e=`${r.search}${r.hash}`;if(r.protocol==="http:"||r.protocol==="https:"){if(!n||r.origin!==n)return null;return`${r.pathname||"/"}${e}`}return`${`/${[r.host,r.pathname.replace(/^\/+/,"")].filter(Boolean).join("/")}`}${e}`}function Z(){if(typeof window>"u")return;return window.location?.origin}var _="stacks-mobile-deep-link";function ne(t,n){let r=(e)=>{let o=Q(e);if(o)t(o)};return n.getInitialURL().then((e)=>{if(!e)return;try{if(sessionStorage.getItem(_)===e)return;sessionStorage.setItem(_,e)}catch{}r(e)}).catch(()=>{}),n.onLink((e,o)=>{if(o?.initial)return;r(e)})}var oe=30;function tt(t,n){let r=Math.PI/180,e=(n.latitude-t.latitude)*r,o=(n.longitude-t.longitude)*r,i=Math.sin(e/2)**2+Math.cos(t.latitude*r)*Math.cos(n.latitude*r)*Math.sin(o/2)**2;return 12742017.6*Math.asin(Math.min(1,Math.sqrt(i)))}function et(t,n=25){let r=new Set;return t.filter((e)=>Number.isFinite(e.latitude)&&Number.isFinite(e.longitude)&&Number.isFinite(e.timestamp)&&e.accuracy>=0&&e.accuracy<=n).sort((e,o)=>e.timestamp-o.timestamp).filter((e)=>{if(r.has(e.timestamp))return!1;return r.add(e.timestamp),!0})}function H(t,n={}){let r=et(t,n.maxAccuracyM??25),e=n.paceWindowS??30,o=[],i=0,s=0,l=null;for(let d of r){let p=o[o.length-1],v=l?(d.timestamp-l.timestamp)/1000:0;if(l=d,!p){o.push({fix:d,cumM:0});continue}if(v>30){o.push({fix:d,cumM:i});continue}let m=(d.timestamp-p.fix.timestamp)/1000,g=tt(p.fix,d),b=Math.min(10,Math.max(3,p.fix.accuracy,d.accuracy));if(g<b)continue;if(i+=g,m>0&&g/m>=0.5)s+=m;o.push({fix:d,cumM:i})}let a=o[o.length-1],u=null,h=null;if(a){let d=a.fix.timestamp-e*1000,p=o.find((g)=>g.fix.timestamp>=d)??a,v=a.cumM-p.cumM,m=(a.fix.timestamp-p.fix.timestamp)/1000;if(v>=20&&m>0)h=v/m,u=m/(v/1000)}return{distanceM:i,movingS:s,paceSPerKm:u,averagePaceSPerKm:i>=50&&s>0?s/(i/1000):null,speedMps:h,accuracyM:r.length?r[r.length-1].accuracy:t.length?t[t.length-1].accuracy??null:null,fixes:r.length}}function ie(t){if(!t||!Number.isFinite(t)||t>3600)return"–";let n=Math.round(t);return`${Math.floor(n/60)}:${String(n%60).padStart(2,"0")}`}function se(t){return t&&Number.isFinite(t)?(t*3.6).toFixed(1):"–"}var K={distanceM:0,movingS:0,paceSPerKm:null,averagePaceSPerKm:null,speedMps:null,accuracyM:null,fixes:0};function ae(t){let n=t.location,r=t.intervalMs??3000,e=K,o=null,i=()=>typeof document<"u"&&document.visibilityState==="hidden";async function s(){let u=await n.readRecording();return e=H(u,t),t.onUpdate?.(e),e}function l(){a(),o=setInterval(()=>{if(!i())s().catch(()=>{})},r)}function a(){if(o)clearInterval(o);o=null}return{get stats(){return e},async start(){return await n.startRecording({enableHighAccuracy:!0,...t.locationOptions}),e=K,t.onUpdate?.(e),l(),e},async attach(){let u=await n.getRecordingState().catch(()=>null);if(!u?.active)return null;if(await s().catch(()=>{}),!u.paused)l();return u},async pause(){a(),await n.pauseRecording(),await s().catch(()=>{})},async resume(){await n.resumeRecording(),l()},async stop(){a();let h=(await n.stopRecording())?.locations??[];return e=H(h,t),t.onUpdate?.(e),h},refresh:s}}var ce=64;var nt=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,rt=/^(?:0|[1-9]\d*)$/;function F(t){let n=[],r=[],e=t&&typeof t==="object"&&!Array.isArray(t)?t:{};for(let[o,i]of Object.entries(e)){let s=i&&typeof i==="object"?i:null;if(!s){r.push(`spotlight kind "${o}" is not an object`);continue}let{slots:l,route:a}=s,u=typeof s.noun==="string"&&s.noun.trim()?s.noun.trim():"Item";if(!nt.test(o)||o.includes("-slot-")){r.push(`spotlight kind "${o}" is not a usable name: lowercase letters, digits and single dashes, and never "-slot-"`);continue}if(typeof l!=="number"||!Number.isInteger(l)||l<=0||l>64){r.push(`spotlight kind "${o}" needs slots between 1 and 64, not ${String(l)}`);continue}if(typeof a!=="string"||!a.startsWith("/")||!a.includes(":id")){r.push(`spotlight kind "${o}" needs a route starting with "/" and carrying ":id", not ${String(a)}`);continue}n.push({name:o,slots:l,route:a,noun:u})}return{kinds:n,problems:r}}function T(t,n){return t.find((r)=>r.name===n)??null}function R(t,n,r){let e=T(t,n);if(!e||!Number.isInteger(r)||r<0||r>=e.slots)return null;return`${n}-slot-${r}`}function ot(t,n){if(typeof n!=="string")return null;let r=n.lastIndexOf("-slot-");if(r<=0)return null;let e=n.slice(0,r),o=n.slice(r+6);if(!rt.test(o))return null;let i=Number(o);return R(t,e,i)===n?{kind:e,slot:i}:null}function C(t){return t.flatMap((n)=>Array.from({length:n.slots},(r,e)=>`${n.name}-slot-${e}`))}function ue(t,n){let r=t&&typeof t==="object"?t:{};if(r.enabled===!1)return[];let o=[...(Array.isArray(r.activityTypes)?r.activityTypes.filter((i)=>typeof i==="string"&&i.trim().length>0):[]).map((i)=>i.trim()),...C(F(r.kinds).kinds)];return[...new Set(o)].map((i)=>`${n}.${i}`)}function D(t,n,r){let e=typeof r.name==="string"?r.name.replace(/\s+/g," ").trim():"";if(!e)return`${T(t,n)?.noun??"Item"} #${r.id}`;return e.length>120?`${e.slice(0,119).trimEnd()}…`:e}function it(t,n,r,e,o=Date.now()){let i=T(t,r);if(!i||!Number.isInteger(e.id)||e.id<=0)return null;let s=n.filter((m)=>m.kind===r),l=D(t,r,e),a=s.find((m)=>m.itemId===e.id)??null,u=a?null:ct(s,i.slots),h=a||u!==null?null:ut(s),d=a?.slot??u??h?.slot;if(d===void 0||d===null)return null;let p={kind:r,slot:d,itemId:e.id,title:l,donatedAt:o},v=n.filter((m)=>m.kind!==r||m.slot!==d&&m.itemId!==e.id);return{entries:B(t,[...v,p]),entry:p,evicted:h,changed:!a||a.title!==l}}function st(t,n,r){let e=t.find((o)=>o.kind===n&&o.itemId===r)??null;return{entries:e?t.filter((o)=>o!==e):[...t],removed:e}}function at(t,n,r){let e=ot(t,r);if(!e)return null;let o=n.find((s)=>s.kind===e.kind&&s.slot===e.slot),i=T(t,e.kind)?.route;return o&&i?i.replace(":id",String(o.itemId)):null}function lt(t,n){if(!Array.isArray(n))return[];let r=new Map,e=new Set;for(let o of n){if(!o||typeof o!=="object")continue;let i=o,{kind:s,slot:l,itemId:a,title:u,donatedAt:h}=i;if(typeof s!=="string"||typeof l!=="number"||R(t,s,l)===null)continue;if(typeof a!=="number"||!Number.isInteger(a)||a<=0)continue;let d=`${s}-slot-${l}`,p=`${s}#${a}`;if(r.has(d)||e.has(p))continue;r.set(d,{kind:s,slot:l,itemId:a,title:typeof u==="string"&&u.trim()?u:D(t,s,{id:a}),donatedAt:typeof h==="number"&&Number.isFinite(h)&&h>0?h:0}),e.add(p)}return B(t,[...r.values()])}function B(t,n){let r=new Map(t.map((e,o)=>[e.name,o]));return[...n].sort((e,o)=>(r.get(e.kind)??0)-(r.get(o.kind)??0)||e.slot-o.slot)}function ct(t,n){let r=new Set(t.map((e)=>e.slot));for(let e=0;e<n;e++)if(!r.has(e))return e;return null}function ut(t){return t.reduce((n,r)=>!n||r.donatedAt<n.donatedAt?r:n,null)}function dt(){let t=globalThis.craft;return t&&typeof t==="object"?t.siri:void 0}var ft="stacks_spotlight_index",pt=3000;function de(t){let n=t.onProblem??((c)=>console.warn(`[spotlight] ${c}`)),r=Array.isArray(t.kinds)?{kinds:[...t.kinds],problems:[]}:F(t.kinds);for(let c of r.problems)n(c);let e=t.enabled===!1?[]:r.kinds,o=t.storageKey??ft,i=t.legacyStorageKeys??[],s=t.timeoutMs??pt,l=t.now??(()=>Date.now()),a=t.bridge??dt,u=null;function h(){try{return typeof localStorage>"u"?null:localStorage}catch{return null}}function d(){if(u)return u;u=[];let c=h();if(!c)return u;try{let f=[o,...i].map((y)=>c.getItem(y)).find((y)=>y);u=f?lt(e,JSON.parse(f)):[]}catch{u=[]}return u}function p(c){u=c;let f=h();if(!f)return;try{f.setItem(o,JSON.stringify(c));for(let y of i)f.removeItem(y)}catch{}}async function v(c){if(!c)return null;let f;try{return await Promise.race([c,new Promise((y)=>{f=setTimeout(()=>y(null),s)})])}catch{return null}finally{if(f!==void 0)clearTimeout(f)}}let m=Promise.resolve();function g(c){let f=m.then(c,c);return m=f.catch(()=>{return}),f}async function b(c,f){let y=a()?.register;if(!y||e.length===0)return!1;return g(async()=>{let S=it(e,d(),c,f,l()),A=S&&R(e,c,S.entry.slot);if(!S||!A)return!1;if(!S.changed)return p(S.entries),!0;let w=await v(y(S.entry.title,A))!==null;if(w)p(S.entries);return w})}async function x(c,f){let y=a()?.remove;if(!y)return!1;return g(async()=>{let{entries:S,removed:A}=st(d(),c,f);if(!A)return!1;let w=R(e,c,A.slot),X=w?await v(y(w))!==null:!1;return p(S),X})}async function Y(c,f){let y=T(e,c)?.slots;if(!y||!a()?.register)return 0;let S=0;for(let A of[...f].slice(0,y).reverse())if(await b(c,A))S++;return S}async function W(){let c=a()?.remove;if(!c)return p([]),0;return g(async()=>{let f=0;for(let y of d()){let S=R(e,y.kind,y.slot);if(S&&await v(c(S))!==null)f++}return p([]),f})}return{kinds:e,index:b,sync:Y,remove:x,clear:W,routeFor:(c)=>at(e,d(),c),entries:()=>d(),activityTypes:(c)=>C(e).map((f)=>`${c}.${f}`),reset:()=>{u=null}}}var N=["craftShortcut","craftSiriShortcut"];function fe(t,n){let r=(e)=>{let o=e.detail,i=typeof o==="string"?o:o&&typeof o==="object"?o.action??o.type:null,s=typeof i==="string"?t.routeFor(i):null;if(s)n(s)};if(typeof globalThis.addEventListener!=="function")return()=>{};for(let e of N)globalThis.addEventListener(e,r);return()=>{for(let e of N)globalThis.removeEventListener(e,r)}}var It=gt,_t=ht,Ot=yt,L=St,Ht=At,Kt=wt,Nt=Tt,$t=Et,Ft=Lt,Ct=Pt,Dt=mt;function j(t){if(typeof t==="string")return t.trim()||null;if(!t||typeof t!=="object")return null;let n=t.url;return typeof n==="string"&&n.trim()?n:null}var Bt={async getInitialURL(){if(!await G())return null;return j(await U.getInitialURL?.())},onLink(t){let n=null,r=!1,e=()=>{if(r)return;n=U.onLink?.((o,i)=>{let s=j(o);if(s)t(s,{initial:i?.initial===!0})})??null};if(E()?.craft)e();else G().then(e);return()=>{r=!0,n?.()}}},Ut=bt,jt=Rt,Gt=kt,qt=vt,Yt=xt,Wt=Mt;function E(){if(typeof window>"u")return;return window}function P(){let t=E()?.craft;if(!t||typeof t!=="object")return null;if(!("platform"in t)||t.platform!=="ios"&&t.platform!=="android")return null;let n="capabilities"in t?t.capabilities:void 0,r=n&&typeof n==="object"&&!Array.isArray(n)?Object.fromEntries(Object.entries(n).filter(([,e])=>typeof e==="boolean")):{};return{platform:t.platform,capabilities:r}}function I(){return P()!==null}function q(){if(typeof window>"u")return!1;let t=window;if(t.CraftAndroid)return!0;let n=typeof navigator>"u"?"":navigator.userAgent;return Boolean(t.webkit?.messageHandlers?.craft)&&/iPhone|iPad|iPod/.test(n)}function Xt(t=2000){if(I()||q())return Promise.resolve(!0);let n=E(),r=n;if(!n||!(r?.craft||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((e)=>{let o=()=>{clearTimeout(i),n.removeEventListener("craftReady",o),e(I())},i=setTimeout(o,t);n.addEventListener("craftReady",o,{once:!0})})}function G(t=15000){let n=E();if(n?.craft)return Promise.resolve(!0);let r=n;if(!n||!(q()||r?.webkit?.messageHandlers?.craft))return Promise.resolve(!1);return new Promise((e)=>{let o=()=>{clearTimeout(i),n.removeEventListener("craftReady",o),e(Boolean(n.craft))},i=setTimeout(o,t);n.addEventListener("craftReady",o)})}function zt(t){let n=E();if(!n)return()=>{};if(P()){let e=!1;return queueMicrotask(()=>{if(!e)t(new Event("craftReady"))}),()=>{e=!0}}let r=(e)=>{if(!P())return;n.removeEventListener("craftReady",r),t(e)};return n.addEventListener("craftReady",r),()=>n.removeEventListener("craftReady",r)}async function Jt(t){await L.impact("light");try{let n=await t();return await L.notification("success"),n}catch(n){throw await L.notification("error"),n}}var ge={get nativeBridge(){return P()},biometrics:It,camera:_t,device:Ot,haptics:L,lifecycle:Ht,location:Kt,notifications:Nt,permissions:$t,secureStorage:Ft,share:Ct,appReview:Dt,deepLinks:Bt,keepAwake:Ut,network:jt,pushNotifications:Gt,health:qt,liveActivities:Yt,watchConnectivity:Wt,isNativeMobile:I,whenNative:Xt,onReady:zt,withFeedback:Jt};export{ce as MAX_SPOTLIGHT_SLOTS_PER_KIND,oe as ROUTE_GAP_S,Dt as appReview,It as biometrics,_t as camera,V as canGoBack,ae as createRouteRecorder,de as createSpotlightIndex,Q as deepLinkPath,Bt as deepLinks,Ot as device,tt as fixDistance,ne as followDeepLinks,P as getNativeMobileBridge,ee as goBack,L as haptics,q as hasNativeMobileHost,qt as health,I as isNativeMobile,Ut as keepAwake,Ht as lifecycle,Yt as liveActivities,Kt as location,ge as mobile,jt as network,j as normalizeDeepLinkURL,Nt as notifications,Qt as observePageScroll,Zt as observePullToRefresh,zt as onMobileReady,fe as onSpotlightTap,ie as paceLabel,k as pageScrollTop,$t as permissions,it as placeSpotlightItem,J as pullDistance,Gt as pushNotifications,lt as readSpotlightEntries,F as readSpotlightKinds,st as removeSpotlightItem,H as routeStats,Ft as secureStorage,Ct as share,se as speedLabel,R as spotlightAction,C as spotlightActions,ue as spotlightActivityTypes,dt as spotlightBridge,T as spotlightKind,at as spotlightRoute,ot as spotlightSlot,D as spotlightTitle,et as usableFixes,Wt as watchConnectivity,G as whenBridgeReady,Xt as whenNativeMobile,Jt as withNativeFeedback};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read configured kinds, leaving out any that could not work.
|
|
3
|
+
*
|
|
4
|
+
* Dropping rather than throwing: a typo in one kind's budget should cost that
|
|
5
|
+
* kind its index, not the app's startup. The problems come back so a caller
|
|
6
|
+
* can say what it ignored — silence here looks exactly like a kind nothing has
|
|
7
|
+
* donated to yet.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readSpotlightKinds(raw: unknown): SpotlightKindsResult;
|
|
10
|
+
/** One kind by name, or null for anything this build does not index. */
|
|
11
|
+
export declare function spotlightKind(kinds: readonly SpotlightKind[], name: string): SpotlightKind | null;
|
|
12
|
+
/** The action a kind's slot is donated under, or null for no such slot. */
|
|
13
|
+
export declare function spotlightAction(kinds: readonly SpotlightKind[], kind: string, slot: number): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* The kind and slot an action names, or null for anything else.
|
|
16
|
+
*
|
|
17
|
+
* Strict about the form — no padding, no trailing text, and the kind has to be
|
|
18
|
+
* one these kinds carry — because this is what decides whether something the
|
|
19
|
+
* system handed back is one of the app's own.
|
|
20
|
+
*/
|
|
21
|
+
export declare function spotlightSlot(kinds: readonly SpotlightKind[], action: string): { kind: string, slot: number } | null;
|
|
22
|
+
/** Every action these kinds may be donated under, in configured order. */
|
|
23
|
+
export declare function spotlightActions(kinds: readonly SpotlightKind[]): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Every activity type a build has to declare, for an `Info.plist`.
|
|
26
|
+
*
|
|
27
|
+
* The slots of every configured kind, plus whatever the app donates outside
|
|
28
|
+
* the registry (`activityTypes`: a Siri phrase, an App Intent), each prefixed
|
|
29
|
+
* with the bundle id the way Craft types the activity it donates.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spotlightActivityTypes(config: unknown, bundleId: string): string[];
|
|
32
|
+
/**
|
|
33
|
+
* What the device shows for a record.
|
|
34
|
+
*
|
|
35
|
+
* The record's name alone: Craft hands the same string to Spotlight as the
|
|
36
|
+
* activity's title and to Siri as its invocation phrase, and a name is the one
|
|
37
|
+
* form that reads well in both — it is what somebody types looking for the
|
|
38
|
+
* record, and what they could say.
|
|
39
|
+
*/
|
|
40
|
+
export declare function spotlightTitle(kinds: readonly SpotlightKind[], kind: string, item: SpotlightItem): string;
|
|
41
|
+
/**
|
|
42
|
+
* Put a record in its kind's slots, taking the oldest when every one is taken.
|
|
43
|
+
*
|
|
44
|
+
* Returns the whole next list rather than mutating, so a caller stores exactly
|
|
45
|
+
* what it donated: a failure between the two cannot leave bookkeeping that
|
|
46
|
+
* claims an entry the device does not have.
|
|
47
|
+
*/
|
|
48
|
+
export declare function placeSpotlightItem(kinds: readonly SpotlightKind[], entries: readonly SpotlightEntry[], kind: string, item: SpotlightItem, now?: number): SpotlightPlacement | null;
|
|
49
|
+
/** Take a record out — what it held is the slot to un-donate. */
|
|
50
|
+
export declare function removeSpotlightItem(entries: readonly SpotlightEntry[], kind: string, itemId: number): { entries: SpotlightEntry[], removed: SpotlightEntry | null };
|
|
51
|
+
/** The route a tapped entry means, or null for a slot holding nothing. */
|
|
52
|
+
export declare function spotlightRoute(kinds: readonly SpotlightKind[], entries: readonly SpotlightEntry[], action: string): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Read back a stored assignment.
|
|
55
|
+
*
|
|
56
|
+
* Stored state outlives the build that wrote it — a kind can be renamed and a
|
|
57
|
+
* budget can shrink — so anything that is not a slot these kinds declare is
|
|
58
|
+
* dropped rather than trusted: a tap on it would resolve to a record the
|
|
59
|
+
* `Info.plist` no longer lets through.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readSpotlightEntries(kinds: readonly SpotlightKind[], raw: unknown): SpotlightEntry[];
|
|
62
|
+
/** The host's Siri bridge, or undefined anywhere that is not a native build. */
|
|
63
|
+
export declare function spotlightBridge(): SpotlightBridge | undefined;
|
|
64
|
+
export declare function createSpotlightIndex(options: SpotlightIndexOptions): SpotlightIndex;
|
|
65
|
+
/**
|
|
66
|
+
* Follow a tap on an indexed entry.
|
|
67
|
+
*
|
|
68
|
+
* Craft reports one as a `craftSiriShortcut` event carrying `{action}` (a
|
|
69
|
+
* home-screen quick action arrives as `craftShortcut` with `{type}`, handled
|
|
70
|
+
* here too), and the handler is called with the route the action resolves to —
|
|
71
|
+
* anything this index does not recognise is left alone, so an app with its own
|
|
72
|
+
* shortcut actions can keep handling those itself.
|
|
73
|
+
*
|
|
74
|
+
* These are plain window events, listened for directly rather than through
|
|
75
|
+
* Craft's `shortcuts.onShortcut` and `siri.onInvoke`: both of those are
|
|
76
|
+
* themselves listeners on these same events, so going through them would run
|
|
77
|
+
* every tap twice and leave a subscription their wrappers cannot take back
|
|
78
|
+
* down.
|
|
79
|
+
*/
|
|
80
|
+
export declare function onSpotlightTap(index: SpotlightIndex, handler: (route: string) => void): () => void;
|
|
81
|
+
/**
|
|
82
|
+
* The most slots one kind may claim.
|
|
83
|
+
*
|
|
84
|
+
* Every slot is a line in the generated `Info.plist` and a possible donation
|
|
85
|
+
* at launch, so a mistyped budget is worth catching before a build log.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MAX_SPOTLIGHT_SLOTS_PER_KIND: 64;
|
|
88
|
+
/**
|
|
89
|
+
* The app's own content in a device's search index.
|
|
90
|
+
*
|
|
91
|
+
* An app somebody opens is not the same thing as a record somebody finds: a
|
|
92
|
+
* trail, a club, a document that turns up when its name is typed into the home
|
|
93
|
+
* screen is reached without the app being thought of first. iOS builds that
|
|
94
|
+
* index out of donated `NSUserActivity` objects, which Craft's `siri` bridge
|
|
95
|
+
* is: a title and an action, and what the app gets back on a tap is that
|
|
96
|
+
* action and nothing else. Two consequences shape this whole module:
|
|
97
|
+
*
|
|
98
|
+
* 1. iOS only hands a tapped activity back to an app that declares its type
|
|
99
|
+
* in `NSUserActivityTypes`, and that list is fixed at build time. A type
|
|
100
|
+
* per record id would be unbounded, so it cannot be declared — and an
|
|
101
|
+
* entry iOS will not hand back is worse than no entry, because it opens
|
|
102
|
+
* the app on whatever screen it was last on.
|
|
103
|
+
* 2. The activity carries no payload of the app's. Whatever a tap needs to
|
|
104
|
+
* know has to be derivable from the action alone.
|
|
105
|
+
*
|
|
106
|
+
* So each kind of content gets a fixed number of *slots* — `trail-slot-0`,
|
|
107
|
+
* `club-slot-0`, every one of them declared in the generated `Info.plist` by
|
|
108
|
+
* the iOS build — and each slot holds whichever record is currently in it.
|
|
109
|
+
* This module is that assignment: the pure part decides it, and
|
|
110
|
+
* `createSpotlightIndex` stores it and makes the bridge calls. Because a
|
|
111
|
+
* slot's identifier is also the activity's `persistentIdentifier`, re-donating
|
|
112
|
+
* a slot replaces the entry iOS already has rather than adding another.
|
|
113
|
+
*
|
|
114
|
+
* What is indexed, how many of each, and where a tap opens is configuration
|
|
115
|
+
* (`config/mobile.ts`, `spotlight`), so an app adds a kind with an entry and a
|
|
116
|
+
* call rather than with another copy of the bookkeeping below.
|
|
117
|
+
*
|
|
118
|
+
* Nothing here needs a native host: off one, every call is a no-op that says
|
|
119
|
+
* so, which is what lets a page donate unconditionally.
|
|
120
|
+
*/
|
|
121
|
+
/** One kind of content, as the index holds it. */
|
|
122
|
+
export declare interface SpotlightKind {
|
|
123
|
+
name: string
|
|
124
|
+
slots: number
|
|
125
|
+
route: string
|
|
126
|
+
noun: string
|
|
127
|
+
}
|
|
128
|
+
/** A kind as an app configures it, before it is checked. */
|
|
129
|
+
export declare interface SpotlightKindInput {
|
|
130
|
+
slots: number
|
|
131
|
+
route: string
|
|
132
|
+
noun?: string
|
|
133
|
+
}
|
|
134
|
+
/** What this module needs of a record. A list row and a detail row both fit. */
|
|
135
|
+
export declare interface SpotlightItem {
|
|
136
|
+
id: number
|
|
137
|
+
name?: string | null
|
|
138
|
+
}
|
|
139
|
+
/** One record's place in the index. */
|
|
140
|
+
export declare interface SpotlightEntry {
|
|
141
|
+
kind: string
|
|
142
|
+
slot: number
|
|
143
|
+
itemId: number
|
|
144
|
+
title: string
|
|
145
|
+
donatedAt: number
|
|
146
|
+
}
|
|
147
|
+
/** Configured kinds, and what was wrong with the ones left out. */
|
|
148
|
+
export declare interface SpotlightKindsResult {
|
|
149
|
+
kinds: SpotlightKind[]
|
|
150
|
+
problems: string[]
|
|
151
|
+
}
|
|
152
|
+
/** The result of fitting a record into its kind's slots. */
|
|
153
|
+
export declare interface SpotlightPlacement {
|
|
154
|
+
entries: SpotlightEntry[]
|
|
155
|
+
entry: SpotlightEntry
|
|
156
|
+
evicted: SpotlightEntry | null
|
|
157
|
+
changed: boolean
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Craft's Siri calls, which are also its Spotlight calls.
|
|
161
|
+
*
|
|
162
|
+
* `register` builds an `NSUserActivity` typed `<bundle id>.<action>`, titles it
|
|
163
|
+
* with the phrase, marks it eligible for search and prediction and makes it
|
|
164
|
+
* current — so a registered phrase is both a Siri suggestion and a Spotlight
|
|
165
|
+
* entry — and `remove` deletes the saved activity by that same identifier.
|
|
166
|
+
*
|
|
167
|
+
* Typed here because `craft-native`'s browser SDK exports neither: they exist
|
|
168
|
+
* only on the object Craft injects into the WebView, so without this seam every
|
|
169
|
+
* caller reaches into `globalThis.craft` and guesses at shapes.
|
|
170
|
+
*/
|
|
171
|
+
export declare interface SpotlightBridge {
|
|
172
|
+
register?: (phrase: string, action: string) => Promise<unknown>
|
|
173
|
+
remove?: (action: string) => Promise<unknown>
|
|
174
|
+
}
|
|
175
|
+
export declare interface SpotlightIndexOptions {
|
|
176
|
+
kinds: Record<string, SpotlightKindInput> | readonly SpotlightKind[]
|
|
177
|
+
enabled?: boolean
|
|
178
|
+
storageKey?: string
|
|
179
|
+
legacyStorageKeys?: readonly string[]
|
|
180
|
+
timeoutMs?: number
|
|
181
|
+
bridge?: () => SpotlightBridge | undefined
|
|
182
|
+
now?: () => number
|
|
183
|
+
onProblem?: (message: string) => void
|
|
184
|
+
}
|
|
185
|
+
/** The device's index, as an app drives it. */
|
|
186
|
+
export declare interface SpotlightIndex {
|
|
187
|
+
readonly kinds: readonly SpotlightKind[]
|
|
188
|
+
index: (kind: string, item: SpotlightItem) => Promise<boolean>
|
|
189
|
+
sync: (kind: string, items: readonly SpotlightItem[]) => Promise<number>
|
|
190
|
+
remove: (kind: string, itemId: number) => Promise<boolean>
|
|
191
|
+
clear: () => Promise<number>
|
|
192
|
+
routeFor: (action: string) => string | null
|
|
193
|
+
entries: () => readonly SpotlightEntry[]
|
|
194
|
+
activityTypes: (bundleId: string) => string[]
|
|
195
|
+
reset: () => void
|
|
196
|
+
}
|
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.
|
|
5
|
+
"version": "0.75.56",
|
|
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.107"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@stacksjs/build": "0.75.
|
|
38
|
+
"@stacksjs/build": "0.75.56",
|
|
39
39
|
"better-dx": "^0.2.24"
|
|
40
40
|
}
|
|
41
41
|
}
|