@qontinui/ui-bridge-native 0.6.15 → 0.6.16
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 +17 -0
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.js +42 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +42 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +23 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/types.d.ts +66 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +116 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +112 -2
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/routePattern.d.ts +145 -0
- package/dist/react/routePattern.d.ts.map +1 -0
- package/dist/react/useRouteAwareness.d.ts +106 -0
- package/dist/react/useRouteAwareness.d.ts.map +1 -0
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-pattern helpers for React Native routers — derive a route TEMPLATE
|
|
3
|
+
* from the router's own route identifiers, failing closed.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: `useRouteAwareness({ pattern })` is consumed as a route
|
|
6
|
+
* TEMPLATE (the runner's journey ledger stores it as `pathnameTemplate`, which
|
|
7
|
+
* must never carry user input). The web SDK derives its pattern by VALUE
|
|
8
|
+
* substitution (`routePatternFromParams` in `@qontinui/ui-bridge`), because
|
|
9
|
+
* Next.js exposes only a concrete pathname. Native routers need no
|
|
10
|
+
* substitution: both already expose template identifiers directly.
|
|
11
|
+
*
|
|
12
|
+
* - **Expo Router** — `useSegments()` returns file-route names
|
|
13
|
+
* (`['profile', '[user]']`, `['(tabs)', 'runs']`), never the concrete path.
|
|
14
|
+
* A not-found render reads `['+not-found']` in its segments. The concrete
|
|
15
|
+
* not-found path is never a segment: it is always in `pathname` (which the
|
|
16
|
+
* helpers never take), and on the NESTED not-found branch it is also in
|
|
17
|
+
* `params['not-found']`; on the top-level branch `params` is `{}`
|
|
18
|
+
* (expo-router 6.0.23 `build/global-state/routeInfo.js:22-30, 96-99`).
|
|
19
|
+
* - **React Navigation** — the navigation state carries each route's `name`,
|
|
20
|
+
* a static identifier declared in code (`<Stack.Screen name="Profile">`).
|
|
21
|
+
* User values live only in `route.params`, which these helpers never read.
|
|
22
|
+
*
|
|
23
|
+
* So the helpers only VALIDATE shape. GUARANTEE, and its precondition: a
|
|
24
|
+
* non-null result is built solely from the segments / route names the caller
|
|
25
|
+
* passed, each of which is a non-empty string containing no `/`, `?` or `#`.
|
|
26
|
+
* It carries no user input ONLY when those inputs are the router's own
|
|
27
|
+
* route-identifier output — `useSegments()` or the navigation state — and
|
|
28
|
+
* never `usePathname()`, a URL, or a param value. One residual: when Expo's
|
|
29
|
+
* navigation state is incomplete it pushes `route.params.screen` strings into
|
|
30
|
+
* the segments (`routeInfo.js:59-83`). Those are navigator screen names, not
|
|
31
|
+
* user values, so the guarantee holds as long as an app never navigates with a
|
|
32
|
+
* user-controlled `screen` param; the shape test bounds what such a value
|
|
33
|
+
* could carry.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { useSegments } from 'expo-router';
|
|
38
|
+
* import {
|
|
39
|
+
* expoRouterPattern,
|
|
40
|
+
* expoRouterRouteStack,
|
|
41
|
+
* isExpoRouterUnmatched,
|
|
42
|
+
* } from '@qontinui/ui-bridge-native';
|
|
43
|
+
*
|
|
44
|
+
* function useExpoRoute() {
|
|
45
|
+
* const segments = useSegments();
|
|
46
|
+
* return {
|
|
47
|
+
* pattern: expoRouterPattern(segments),
|
|
48
|
+
* routeStack: expoRouterRouteStack(segments),
|
|
49
|
+
* unmatched: isExpoRouterUnmatched(segments),
|
|
50
|
+
* };
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Structural subset of a React Navigation state object — the ROOT state, as
|
|
56
|
+
* `navigationRef.getRootState()` (from `useNavigationContainerRef()`) or the
|
|
57
|
+
* `NavigationContainer` `onStateChange` callback returns it. Do NOT use
|
|
58
|
+
* `useNavigationState((s) => s)`: outside a navigator it throws, and inside a
|
|
59
|
+
* screen it returns only that navigator's state, truncating the chain.
|
|
60
|
+
* Declared structurally so this package takes no dependency on
|
|
61
|
+
* `@react-navigation/*`. `params` is deliberately absent: the helper never
|
|
62
|
+
* reads it.
|
|
63
|
+
*/
|
|
64
|
+
export interface NavigationStateLike {
|
|
65
|
+
/**
|
|
66
|
+
* Index of the focused route in `routes`. When absent (a partial state),
|
|
67
|
+
* React Navigation's own rule applies: the LAST route for a stack (or a
|
|
68
|
+
* state with no `type`), the FIRST route for any other navigator type.
|
|
69
|
+
*/
|
|
70
|
+
index?: number;
|
|
71
|
+
/** Navigator type (`'stack'`, `'tab'`, `'drawer'`, …), when known. */
|
|
72
|
+
type?: string;
|
|
73
|
+
routes: ReadonlyArray<{
|
|
74
|
+
name: string;
|
|
75
|
+
/** The nested navigator's state, when this route hosts one. */
|
|
76
|
+
state?: NavigationStateLike;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
/** Options for {@link reactNavigationPattern}. */
|
|
80
|
+
export interface ReactNavigationPatternOptions {
|
|
81
|
+
/**
|
|
82
|
+
* Route names that render the app's not-found screen (the `linking`
|
|
83
|
+
* config's catch-all screen). When the focused LEAF route has one of these
|
|
84
|
+
* names the helper returns `null`.
|
|
85
|
+
*/
|
|
86
|
+
notFoundRouteNames?: readonly string[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True when Expo Router's segments name a system route — any segment starting
|
|
90
|
+
* with `+` (`+not-found`; `+html` / `+api` never render). Expo reserves the
|
|
91
|
+
* `+` prefix, so no `+` segment names a user screen. Pass the result as
|
|
92
|
+
* `useRouteAwareness(info, { unmatched })`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isExpoRouterUnmatched(segments: readonly string[]): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Derive a route template from Expo Router's `useSegments()`:
|
|
97
|
+
* `'/' + segments.join('/')`, with layout groups such as `(tabs)` KEPT.
|
|
98
|
+
*
|
|
99
|
+
* - `[]` → `'/'` (Expo already pops a trailing `index`);
|
|
100
|
+
* - `['profile', '[user]']` → `'/profile/[user]'`;
|
|
101
|
+
* - `['(tabs)', 'runs']` → `'/(tabs)/runs'`.
|
|
102
|
+
*
|
|
103
|
+
* Groups are kept because they are developer-declared names, never user
|
|
104
|
+
* input, and because two screens in different groups can share a URL — the
|
|
105
|
+
* group is what keeps them distinct. A consumer that wants the URL shape can
|
|
106
|
+
* strip `(…)` segments; a stripped pattern cannot be un-stripped.
|
|
107
|
+
*
|
|
108
|
+
* Returns `null` when any segment starts with `+` (a system route, see
|
|
109
|
+
* {@link isExpoRouterUnmatched}) or fails the route-identifier shape test
|
|
110
|
+
* (empty, not a string, or containing `/`, `?` or `#`).
|
|
111
|
+
*/
|
|
112
|
+
export declare function expoRouterPattern(segments: readonly string[]): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Derive the cumulative route stack from Expo Router's `useSegments()`, with
|
|
115
|
+
* the same meaning as the web `RouteInfo.routeStack` (`["/", "/tasks",
|
|
116
|
+
* "/tasks/:id"]`): one template per depth, root first.
|
|
117
|
+
*
|
|
118
|
+
* - `[]` → `['/']`;
|
|
119
|
+
* - `['(tabs)', 'runs']` → `['/', '/(tabs)', '/(tabs)/runs']`.
|
|
120
|
+
*
|
|
121
|
+
* Fails closed by the same rules as {@link expoRouterPattern}: returns
|
|
122
|
+
* `undefined` (no stack reported) when any segment is a `+` system route or
|
|
123
|
+
* fails the route-identifier shape test.
|
|
124
|
+
*/
|
|
125
|
+
export declare function expoRouterRouteStack(segments: readonly string[]): string[] | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Derive a route template from a React Navigation state: the focused
|
|
128
|
+
* route-NAME chain, root to leaf, joined with `/` — e.g. `/HomeTabs/Profile`.
|
|
129
|
+
*
|
|
130
|
+
* The whole chain rather than the leaf keeps same-named screens in different
|
|
131
|
+
* navigators (`Settings` under two tabs) distinct. Route `params` are never
|
|
132
|
+
* read, so no param shape can leak into the result. Pass the ROOT state
|
|
133
|
+
* (see {@link NavigationStateLike}); a level with no `index` focuses its last
|
|
134
|
+
* route for a stack (or an untyped state) and its first for any other type.
|
|
135
|
+
*
|
|
136
|
+
* Returns `null` on an empty `routes`, an out-of-range or non-integer
|
|
137
|
+
* `index`, a route name that fails the route-identifier shape test, or a
|
|
138
|
+
* focused leaf whose name is in `options.notFoundRouteNames`.
|
|
139
|
+
*
|
|
140
|
+
* Expo Router apps should use {@link expoRouterPattern} instead: Expo Router
|
|
141
|
+
* is React Navigation underneath, and a raw walk of its state yields Expo's
|
|
142
|
+
* internal route names rather than the file routes.
|
|
143
|
+
*/
|
|
144
|
+
export declare function reactNavigationPattern(state: NavigationStateLike | null | undefined, options?: ReactNavigationPatternOptions): string | null;
|
|
145
|
+
//# sourceMappingURL=routePattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routePattern.d.ts","sourceRoot":"","sources":["../../src/react/routePattern.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,+DAA+D;QAC/D,KAAK,CAAC,EAAE,mBAAmB,CAAC;KAC7B,CAAC,CAAC;CACJ;AAED,kDAAkD;AAClD,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AA2BD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAO5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,CAOtF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,EAC7C,OAAO,GAAE,6BAAkC,GAC1C,MAAM,GAAG,IAAI,CAiBf"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRouteAwareness — report the router's current route TEMPLATE to the
|
|
3
|
+
* native bridge, emitted on every snapshot as `page.route`.
|
|
4
|
+
*
|
|
5
|
+
* `pattern` is a route TEMPLATE, never the concrete pathname: a concrete path
|
|
6
|
+
* (`/search/<what the user typed>`) reported as a pattern leaks user input into
|
|
7
|
+
* anything that stores templates — the runner's journey ledger reads
|
|
8
|
+
* `snapshot.page.route.pattern` and keeps it only under
|
|
9
|
+
* `patternSource: 'router'`. Derive the pattern with `expoRouterPattern`
|
|
10
|
+
* (Expo Router `useSegments()`) or `reactNavigationPattern` (React Navigation
|
|
11
|
+
* state), which return `null` rather than leak, and say where it came from
|
|
12
|
+
* with `patternSource: 'router'`. NEVER pass `usePathname()`, a URL, or a
|
|
13
|
+
* param value as the pattern.
|
|
14
|
+
*
|
|
15
|
+
* The not-found signal is a plain boolean, `{ unmatched }`. The web SDK needs
|
|
16
|
+
* a provider-owned context signal and a layout effect because a Next.js layout
|
|
17
|
+
* renders before its `not-found.tsx` exists, so it cannot know at render time
|
|
18
|
+
* that the render is a 404. Neither native router has that gap: Expo Router's
|
|
19
|
+
* `useSegments()` already reads `['+not-found']` in the same render
|
|
20
|
+
* (`isExpoRouterUnmatched`), and a React Navigation not-found screen is an
|
|
21
|
+
* ordinary named route in the state the caller already reads. So there is no
|
|
22
|
+
* signal to wire. While `unmatched` is true the reported `pattern` is `null`
|
|
23
|
+
* AND `params` is dropped: on a NESTED `+not-found` route expo-router also
|
|
24
|
+
* carries the concrete not-found path in `params['not-found']` (on the
|
|
25
|
+
* top-level branch `params` is `{}`; the path is always in `pathname`, which
|
|
26
|
+
* the hook never takes), so a caller passing the router's search params as
|
|
27
|
+
* `params` would otherwise ship user input.
|
|
28
|
+
*
|
|
29
|
+
* Note: native's `unmatched?: boolean` shares its option KEY with the web
|
|
30
|
+
* hook's `unmatched?: RouteUnmatchedSignal | null`, but not its type.
|
|
31
|
+
*
|
|
32
|
+
* The reported info is cleared when the component unmounts. Outside a
|
|
33
|
+
* `UIBridgeNativeProvider` the hook is a no-op.
|
|
34
|
+
*
|
|
35
|
+
* Usage with Expo Router (in the root layout):
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { useSegments } from 'expo-router';
|
|
40
|
+
* import {
|
|
41
|
+
* expoRouterPattern,
|
|
42
|
+
* expoRouterRouteStack,
|
|
43
|
+
* isExpoRouterUnmatched,
|
|
44
|
+
* useRouteAwareness,
|
|
45
|
+
* } from '@qontinui/ui-bridge-native';
|
|
46
|
+
*
|
|
47
|
+
* function RouteAwareness() {
|
|
48
|
+
* const segments = useSegments();
|
|
49
|
+
* useRouteAwareness(
|
|
50
|
+
* {
|
|
51
|
+
* pattern: expoRouterPattern(segments),
|
|
52
|
+
* patternSource: 'router',
|
|
53
|
+
* routeStack: expoRouterRouteStack(segments),
|
|
54
|
+
* },
|
|
55
|
+
* { unmatched: isExpoRouterUnmatched(segments) }
|
|
56
|
+
* );
|
|
57
|
+
* return null;
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* Usage with React Navigation: read the ROOT state from the container, not
|
|
62
|
+
* with `useNavigationState` (which throws outside a navigator and, inside a
|
|
63
|
+
* screen, sees only that navigator's state). The component must render inside
|
|
64
|
+
* `UIBridgeNativeProvider`:
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* import { useState } from 'react';
|
|
69
|
+
* import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
|
|
70
|
+
* import {
|
|
71
|
+
* reactNavigationPattern,
|
|
72
|
+
* useRouteAwareness,
|
|
73
|
+
* type NavigationStateLike,
|
|
74
|
+
* } from '@qontinui/ui-bridge-native';
|
|
75
|
+
*
|
|
76
|
+
* function Navigation({ children }: { children: React.ReactNode }) {
|
|
77
|
+
* const navigationRef = useNavigationContainerRef();
|
|
78
|
+
* const [rootState, setRootState] = useState<NavigationStateLike | undefined>();
|
|
79
|
+
* const syncRootState = () => setRootState(navigationRef.getRootState());
|
|
80
|
+
*
|
|
81
|
+
* const pattern = reactNavigationPattern(rootState, { notFoundRouteNames: ['NotFound'] });
|
|
82
|
+
* useRouteAwareness({ pattern, patternSource: 'router' }, { unmatched: pattern === null });
|
|
83
|
+
*
|
|
84
|
+
* return (
|
|
85
|
+
* <NavigationContainer ref={navigationRef} onReady={syncRootState} onStateChange={syncRootState}>
|
|
86
|
+
* {children}
|
|
87
|
+
* </NavigationContainer>
|
|
88
|
+
* );
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
import type { RouteInfo } from '../core/types';
|
|
93
|
+
export interface UseRouteAwarenessOptions {
|
|
94
|
+
/**
|
|
95
|
+
* True while the current route is a not-found screen. The reported
|
|
96
|
+
* `pattern` is then `null` and `params` is dropped, whatever `info` says.
|
|
97
|
+
*/
|
|
98
|
+
unmatched?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Report router information to the native bridge's `page.route`.
|
|
102
|
+
*
|
|
103
|
+
* The info is cleared when the component unmounts.
|
|
104
|
+
*/
|
|
105
|
+
export declare function useRouteAwareness(info: RouteInfo, options?: UseRouteAwarenessOptions): void;
|
|
106
|
+
//# sourceMappingURL=useRouteAwareness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRouteAwareness.d.ts","sourceRoot":"","sources":["../../src/react/useRouteAwareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAwB/F"}
|
package/dist/server/index.js
CHANGED
|
@@ -2260,7 +2260,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2260
2260
|
};
|
|
2261
2261
|
if (config?.appInfo) {
|
|
2262
2262
|
response.uiBridge = {
|
|
2263
|
-
version: "0.6.
|
|
2263
|
+
version: "0.6.16",
|
|
2264
2264
|
...config.appInfo,
|
|
2265
2265
|
capabilities: ["elements", "components", "actions", "design"],
|
|
2266
2266
|
elementCount: stats.elements,
|