@stacksjs/mobile 0.75.97 → 0.75.98
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 +24 -0
- package/dist/bridge.d.ts +90 -0
- package/dist/controls.d.ts +41 -0
- package/dist/events.d.ts +28 -0
- package/dist/gestures.d.ts +38 -2
- package/dist/index.d.ts +649 -25
- package/dist/index.js +55 -1
- package/dist/native.d.ts +218 -0
- package/dist/refresh-control.d.ts +12 -0
- package/dist/sheet-gesture.d.ts +66 -0
- package/dist/sheets.d.ts +72 -0
- package/dist/shell.d.ts +21 -0
- package/dist/tab-bar.d.ts +15 -0
- package/dist/types.d.ts +18 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,6 +14,30 @@ if (isNativeMobile())
|
|
|
14
14
|
const position = await location.getCurrentPosition()
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Waiting for the bridge
|
|
18
|
+
|
|
19
|
+
Craft's newer iOS shell injects `window.craft` at document start; an older
|
|
20
|
+
one installs it once the page has loaded. A call made inside a native host
|
|
21
|
+
before the bridge exists now waits for it (up to `BRIDGE_WAIT_MS`, 2.5s)
|
|
22
|
+
instead of quietly taking the web path, and a browser takes the fallback at
|
|
23
|
+
once. `callNative(path, args, fallback)` and `afterBridge(run)` are the
|
|
24
|
+
building blocks, for a bridge API this package does not wrap yet.
|
|
25
|
+
|
|
26
|
+
## Beyond the core services
|
|
27
|
+
|
|
28
|
+
Each with a web fallback, and none throwing for being unsupported:
|
|
29
|
+
`dialog` (alert, confirm, actionSheet; HTML stand-ins in a browser),
|
|
30
|
+
`confirmAction`, `useActionSheet`, `contextMenu`, `browser.open` (in-app
|
|
31
|
+
Safari or an auth session), `symbols.image` (SF Symbols, cached), `statusBar`,
|
|
32
|
+
`chrome`, `refresh` and `observeNativeRefresh` (the native refresh control),
|
|
33
|
+
`background`, `clipboard`, `db`, `files`, `shortcuts`, `widgets`,
|
|
34
|
+
`orientation`, `auth.signInWithApple` and `storeKit`; the phone's events
|
|
35
|
+
`onAppearance`, `onResume`, `onMemoryWarning`, `onSilentPush` and
|
|
36
|
+
`onBackgroundRefresh`; and `haptics.prepare()`. The gesture logic behind the
|
|
37
|
+
Native components (sheet detents, the large title, long press, segment
|
|
38
|
+
scrubbing, pressables, the chrome sync) is exported too. See the
|
|
39
|
+
[Mobile Apps guide](https://stacksjs.org/guide/mobile).
|
|
40
|
+
|
|
17
41
|
## Native bridge availability
|
|
18
42
|
|
|
19
43
|
`mobile` is a typed service collection (`MobileApi`), available in both browsers
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** The page's window, when there is one. */
|
|
2
|
+
export declare function craftHost(): CraftHost | undefined;
|
|
3
|
+
/** `window.craft`, the bridge object, once installed. */
|
|
4
|
+
export declare function craftRoot(): Record<string, any> | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Whether this page runs inside Craft's phone shell, before the bridge exists.
|
|
7
|
+
*
|
|
8
|
+
* Craft installs `window.craft` once the page has finished loading on an older
|
|
9
|
+
* shell — images and all — so a bridge check answers `false` during setup on
|
|
10
|
+
* a phone, and a layout that decides then renders the website's chrome in the
|
|
11
|
+
* app. The transports are there from the start: Android's `CraftAndroid`
|
|
12
|
+
* interface, and on iOS the `craft` message handler. Craft's macOS windows
|
|
13
|
+
* carry that handler too, so on iOS an iPhone or iPad user agent is what makes
|
|
14
|
+
* it the phone app.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hasNativeMobileHost(): boolean;
|
|
17
|
+
/** A native host whose bridge object is not installed yet: worth waiting for. */
|
|
18
|
+
export declare function bridgePending(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves once the bridge object (`window.craft`) is installed: `true` then,
|
|
21
|
+
* `false` in a browser at once, or after `timeoutMs` in a host that never
|
|
22
|
+
* installs it.
|
|
23
|
+
*
|
|
24
|
+
* Not whenNativeMobile(), which answers `true` as soon as the phone shell is
|
|
25
|
+
* recognisable, before the bridge exists and before anything can be asked of it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function whenBridgeReady(timeoutMs?: unknown): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* The bridge function at `path` (`'dialog.confirm'`), or null when this host
|
|
30
|
+
* has none. Synchronous: no waiting for a bridge still on its way.
|
|
31
|
+
*/
|
|
32
|
+
export declare function nativeFunctionNow(path: string): ((...args: any[]) => unknown) | null;
|
|
33
|
+
/**
|
|
34
|
+
* The bridge function at `path`, waiting for a native host's bridge to arrive
|
|
35
|
+
* first. Null in a browser, on a shell that predates the API, or when the
|
|
36
|
+
* bridge did not come within `timeoutMs`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function nativeFunction(path: string, timeoutMs?: unknown): Promise<((...args: any[]) => unknown) | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Calls the bridge function at `path`, or `fallback` where there is none.
|
|
41
|
+
*
|
|
42
|
+
* Inside a native host whose bridge is still loading, the call is held until
|
|
43
|
+
* it arrives, so an early call reaches the native implementation rather than
|
|
44
|
+
* the web one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function callNative<T>(path: string, args: unknown[], fallback: () => T | Promise<T>, options?: CallNativeOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Runs `run` once a native host's bridge is installed: at once when it is
|
|
49
|
+
* there or in a browser, after it arrives (or `BRIDGE_WAIT_MS` passes) in a
|
|
50
|
+
* host still loading it.
|
|
51
|
+
*
|
|
52
|
+
* craft-native's services read `window.craft` at call time and take their web
|
|
53
|
+
* path when it is missing, which during an older shell's launch it is. The
|
|
54
|
+
* services this package exports call through here, as plain object literals
|
|
55
|
+
* rather than wrapped at import: a bundler drops an object literal nobody
|
|
56
|
+
* imports, and could not drop a call made at module scope once minified.
|
|
57
|
+
*/
|
|
58
|
+
export declare function afterBridge<T>(run: () => T | Promise<T>, timeoutMs?: unknown): Promise<T>;
|
|
59
|
+
/**
|
|
60
|
+
* Subscribes once the bridge can take the subscription: at once when it is
|
|
61
|
+
* there or in a browser, otherwise when it arrives. The returned function
|
|
62
|
+
* unsubscribes either way, including before the subscription was made.
|
|
63
|
+
*/
|
|
64
|
+
export declare function subscribeWhenReady(subscribe: () => (() => void) | void, timeoutMs?: unknown): () => void;
|
|
65
|
+
/** Listens for a Craft DOM event on the window; the detail is passed through. */
|
|
66
|
+
export declare function onCraftEvent<T = Record<string, unknown>>(name: string, callback: (detail: T) => void): () => void;
|
|
67
|
+
/**
|
|
68
|
+
* Reaching Craft's bridge at the right moment.
|
|
69
|
+
*
|
|
70
|
+
* Craft's iOS shell injects `window.craft` at document start, but an older
|
|
71
|
+
* shell installs it once the page has finished loading. A call made before
|
|
72
|
+
* then used to find no bridge and quietly take the web path: a secure-storage
|
|
73
|
+
* read answered from localStorage, a confirm drew the browser's own dialog.
|
|
74
|
+
* Inside a native host, a call now waits for the bridge (briefly: a host that
|
|
75
|
+
* never installs one gets the web fallback after `BRIDGE_WAIT_MS`), and only a
|
|
76
|
+
* browser takes the fallback at once.
|
|
77
|
+
*/
|
|
78
|
+
/** How long a call waits for a native host's bridge before using the web fallback. */
|
|
79
|
+
export declare const BRIDGE_WAIT_MS: 2500;
|
|
80
|
+
export declare interface CraftHost extends EventTarget {
|
|
81
|
+
craft?: unknown
|
|
82
|
+
CraftAndroid?: unknown
|
|
83
|
+
webkit?: { messageHandlers?: Record<string, unknown> }
|
|
84
|
+
document?: Document
|
|
85
|
+
navigator?: Navigator
|
|
86
|
+
}
|
|
87
|
+
export declare interface CallNativeOptions {
|
|
88
|
+
fallbackOnError?: boolean
|
|
89
|
+
timeoutMs?: number
|
|
90
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls `onLongPress` when a finger (or mouse) stays on `element` for
|
|
3
|
+
* `delay`. The click that follows a long press is swallowed, so a long press
|
|
4
|
+
* on a link opens its menu without also following it, and the browser's own
|
|
5
|
+
* callout menu is kept out of the way.
|
|
6
|
+
*/
|
|
7
|
+
export declare function observeLongPress(element: HTMLElement, options: LongPressOptions): () => void;
|
|
8
|
+
/** Which of `count` equal segments across `rect` the x coordinate is over, clamped to the ends. */
|
|
9
|
+
export declare function segmentAt(x: number, rect: { left: number, width: number }, count: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* Scrubbing a segmented control: a finger that lands on the chosen segment and
|
|
12
|
+
* slides drags the thumb across, and lifting chooses where it stopped. A tap
|
|
13
|
+
* elsewhere stays the button's own click.
|
|
14
|
+
*/
|
|
15
|
+
export declare function observeSegmentScrub(track: HTMLElement, options: SegmentScrubOptions): () => void;
|
|
16
|
+
/** The segment an arrow, Home or End key moves to from `index`, or null for any other key. */
|
|
17
|
+
export declare function segmentForKey(key: string, index: number, count: number): number | null;
|
|
18
|
+
/** Moves focus to the `index`th tab of a segmented control, as arrow keys do in a tab list. */
|
|
19
|
+
export declare function focusSegment(track: HTMLElement, index: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Wakes the Taptic Engine as a finger lands on anything marked
|
|
22
|
+
* `data-native-pressable`, so the tap that follows its press plays at once.
|
|
23
|
+
*
|
|
24
|
+
* Listening for touchstart at all is also what makes iOS apply `:active`, the
|
|
25
|
+
* pressed state the shell's stylesheet draws.
|
|
26
|
+
*/
|
|
27
|
+
export declare function installPressables(root: EventTarget): () => void;
|
|
28
|
+
export declare interface LongPressOptions {
|
|
29
|
+
delay?: number
|
|
30
|
+
tolerance?: number
|
|
31
|
+
onPressStart?: () => void
|
|
32
|
+
onPressEnd?: () => void
|
|
33
|
+
onLongPress: (event: { x: number, y: number }) => void
|
|
34
|
+
}
|
|
35
|
+
export declare interface SegmentScrubOptions {
|
|
36
|
+
count: () => number
|
|
37
|
+
selected: () => number
|
|
38
|
+
onPress?: (pressed: boolean) => void
|
|
39
|
+
onScrub: (index: number) => void
|
|
40
|
+
onCommit: (index: number) => void
|
|
41
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The appearance as it stands. The shell writes `--craft-font-scale` and
|
|
3
|
+
* `data-craft-reduce-*` on the root element before the page runs, so this is
|
|
4
|
+
* right at setup too; elsewhere it reads the media queries.
|
|
5
|
+
*/
|
|
6
|
+
export declare function currentAppearance(): NativeAppearance;
|
|
7
|
+
/** A `craftAppearance` detail, made whole. */
|
|
8
|
+
export declare function normalizeAppearance(detail: Partial<NativeAppearance> | null | undefined): NativeAppearance;
|
|
9
|
+
/**
|
|
10
|
+
* Calls `callback` with the appearance now and whenever the phone's settings
|
|
11
|
+
* change (Dynamic Type, Reduce Motion, Reduce Transparency, Light/Dark).
|
|
12
|
+
*/
|
|
13
|
+
export declare function onAppearance(callback: (appearance: NativeAppearance) => void, options?: { immediate?: boolean }): () => void;
|
|
14
|
+
/** The app came back to the front after `backgroundedMs` away. */
|
|
15
|
+
export declare function onResume(callback: (detail: { backgroundedMs: number }) => void): () => void;
|
|
16
|
+
/** iOS is short of memory: drop caches that can be rebuilt. */
|
|
17
|
+
export declare function onMemoryWarning(callback: () => void): () => void;
|
|
18
|
+
/** A content-available push arrived. The handler's settling completes it. */
|
|
19
|
+
export declare function onSilentPush(handler: (payload: Record<string, unknown>) => unknown): () => void;
|
|
20
|
+
/** iOS woke the app to refresh (BGAppRefreshTask). The handler's settling completes it. */
|
|
21
|
+
export declare function onBackgroundRefresh(handler: () => unknown): () => void;
|
|
22
|
+
export declare interface NativeAppearance {
|
|
23
|
+
contentSizeCategory?: string
|
|
24
|
+
fontScale: number
|
|
25
|
+
reduceMotion: boolean
|
|
26
|
+
reduceTransparency: boolean
|
|
27
|
+
colorScheme: 'light' | 'dark'
|
|
28
|
+
}
|
package/dist/gestures.d.ts
CHANGED
|
@@ -25,14 +25,50 @@ export declare function pullDistance(scrollTop: number, fingerTravel: number): n
|
|
|
25
25
|
* it disarms. Letting go while armed runs `onRefresh`.
|
|
26
26
|
*/
|
|
27
27
|
export declare function observePullToRefresh(options: PullToRefreshOptions): () => void;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Where a large-title navigation bar is for a scroll offset.
|
|
30
|
+
*
|
|
31
|
+
* The title collapses over its own height. Pulled past the top (iOS's rubber
|
|
32
|
+
* band, a negative offset) it grows a little, at most 10%, anchored at its
|
|
33
|
+
* leading edge.
|
|
34
|
+
*/
|
|
35
|
+
export declare function largeTitleMetrics(offset: number, height?: number): LargeTitleMetrics;
|
|
36
|
+
/**
|
|
37
|
+
* Links a navigation bar to the page's scroll, a frame at a time.
|
|
38
|
+
*
|
|
39
|
+
* Writes `--native-nav-progress` (0 to 1) and `--native-nav-stretch` on
|
|
40
|
+
* `element`, so its CSS can fade the material in, move the small title into
|
|
41
|
+
* the bar and stretch the large one, all on the compositor. Nothing re-renders.
|
|
42
|
+
*/
|
|
43
|
+
export declare function observeLargeTitle(element: HTMLElement, options?: LargeTitleOptions): () => void;
|
|
44
|
+
/**
|
|
45
|
+
* The element a selector names, as a scroll host: for a screen whose content
|
|
46
|
+
* scrolls inside a container rather than the page. Undefined (the window)
|
|
47
|
+
* when nothing matches.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveScrollHost(selector: string | null | undefined): ScrollHost | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Where a page scrolls: the window, a scrolling element (a screen that scrolls
|
|
52
|
+
* inside its own container), or a stand-in with the same shape.
|
|
53
|
+
*/
|
|
29
54
|
export declare interface ScrollHost extends EventTarget {
|
|
30
55
|
scrollY?: number
|
|
56
|
+
scrollTop?: number
|
|
31
57
|
document?: { scrollingElement?: { scrollTop: number } | null }
|
|
32
58
|
}
|
|
33
59
|
export declare interface PullToRefreshOptions {
|
|
34
60
|
threshold?: number
|
|
35
|
-
onPull?: (distance: number, armed: boolean) => void
|
|
61
|
+
onPull?: (distance: number, armed: boolean, pull: { bounce: number }) => void
|
|
36
62
|
onRefresh: () => unknown | Promise<unknown>
|
|
37
63
|
host?: ScrollHost
|
|
38
64
|
}
|
|
65
|
+
export declare interface LargeTitleMetrics {
|
|
66
|
+
progress: number
|
|
67
|
+
stretch: number
|
|
68
|
+
}
|
|
69
|
+
export declare interface LargeTitleOptions {
|
|
70
|
+
height?: number
|
|
71
|
+
title?: HTMLElement | null
|
|
72
|
+
host?: ScrollHost
|
|
73
|
+
onChange?: (metrics: LargeTitleMetrics) => void
|
|
74
|
+
}
|