@gjsify/react-native 0.43.0 → 0.45.0
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 +127 -31
- package/lib/esm/apis/alert.js +1 -0
- package/lib/esm/apis/appearance.js +1 -0
- package/lib/esm/apis/color-scheme.js +1 -1
- package/lib/esm/apis/dimensions.js +1 -0
- package/lib/esm/apis/display.js +1 -0
- package/lib/esm/apis/index.js +1 -1
- package/lib/esm/apis/keyboard.js +1 -0
- package/lib/esm/components.js +1 -1
- package/lib/esm/generated/own-exports.js +1 -0
- package/lib/esm/generated/unsupported-exports.js +1 -1
- package/lib/esm/generated/unsupported-router-exports.js +1 -0
- package/lib/esm/hooks.js +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/lists/components.js +1 -0
- package/lib/esm/lists/controller.js +1 -0
- package/lib/esm/parent-context.js +1 -0
- package/lib/esm/press.js +1 -0
- package/lib/esm/primitives/intents.js +1 -1
- package/lib/esm/primitives/resolve.js +1 -1
- package/lib/esm/primitives/table.js +1 -1
- package/lib/esm/router/errors.js +1 -0
- package/lib/esm/router/index.js +1 -0
- package/lib/esm/router/navigation.js +1 -0
- package/lib/esm/router/root.js +1 -0
- package/lib/esm/router/routes.js +1 -0
- package/lib/esm/router/screens.js +1 -0
- package/lib/esm/router/stack.js +1 -0
- package/lib/esm/router/tabs.js +1 -0
- package/lib/esm/solid/index.js +1 -1
- package/lib/esm/stylesheet.js +1 -0
- package/lib/esm/support-table.js +1 -1
- package/lib/types/apis/alert.d.ts +46 -0
- package/lib/types/apis/appearance.d.ts +28 -0
- package/lib/types/apis/color-scheme.d.ts +21 -0
- package/lib/types/apis/dimensions.d.ts +42 -0
- package/lib/types/apis/display.d.ts +87 -0
- package/lib/types/apis/index.d.ts +10 -1
- package/lib/types/apis/keyboard.d.ts +22 -0
- package/lib/types/components.d.ts +166 -10
- package/lib/types/generated/own-exports.d.ts +1 -0
- package/lib/types/generated/unsupported-exports.d.ts +0 -19
- package/lib/types/generated/unsupported-router-exports.d.ts +17 -0
- package/lib/types/hooks.d.ts +16 -0
- package/lib/types/index.d.ts +10 -6
- package/lib/types/lists/components.d.ts +102 -0
- package/lib/types/lists/controller.d.ts +45 -0
- package/lib/types/lists/lists.spec.d.ts +2 -0
- package/lib/types/parent-context.d.ts +15 -0
- package/lib/types/press.d.ts +27 -0
- package/lib/types/primitives/index.d.ts +2 -2
- package/lib/types/primitives/resolve.d.ts +36 -0
- package/lib/types/primitives/table.d.ts +79 -2
- package/lib/types/router/errors.d.ts +37 -0
- package/lib/types/router/index.d.ts +10 -0
- package/lib/types/router/navigation.d.ts +72 -0
- package/lib/types/router/root.d.ts +24 -0
- package/lib/types/router/router.spec.d.ts +2 -0
- package/lib/types/router/routes.d.ts +90 -0
- package/lib/types/router/routes.spec.d.ts +2 -0
- package/lib/types/router/screens.d.ts +48 -0
- package/lib/types/router/stack.d.ts +118 -0
- package/lib/types/router/tabs.d.ts +36 -0
- package/lib/types/solid/index.d.ts +9 -4
- package/lib/types/stylesheet.d.ts +75 -0
- package/lib/types/stylesheet.spec.d.ts +2 -0
- package/lib/types/support-table.d.ts +36 -2
- package/package.json +19 -5
|
@@ -13,10 +13,18 @@ export type Coercion =
|
|
|
13
13
|
| 'map'
|
|
14
14
|
/** A number is a pixel count; a string is looked up in the map. */
|
|
15
15
|
| 'pixels-or-map';
|
|
16
|
+
/**
|
|
17
|
+
* Which of an element's up-to-three GTK nodes a prop lands on.
|
|
18
|
+
*
|
|
19
|
+
* `outer` is the node the parent adopts, `content` the node the element's children
|
|
20
|
+
* go into, `backdrop` a node that takes no children and sits BEHIND them —
|
|
21
|
+
* `ImageBackground`'s picture, and the only reason the third one exists.
|
|
22
|
+
*/
|
|
23
|
+
export type NodeKind = 'outer' | 'content' | 'backdrop';
|
|
16
24
|
export interface PropertyRoute {
|
|
17
25
|
readonly to: 'property';
|
|
18
26
|
/** Which node it lands on. `content` is the implicit inner box, where there is one. */
|
|
19
|
-
readonly on?:
|
|
27
|
+
readonly on?: NodeKind;
|
|
20
28
|
/** The GTK properties this one prop fills — plural for `size`, which is two requests. */
|
|
21
29
|
readonly names: readonly string[];
|
|
22
30
|
readonly as: Coercion;
|
|
@@ -63,7 +71,42 @@ export interface IgnoredRoute {
|
|
|
63
71
|
readonly to: 'ignored';
|
|
64
72
|
readonly why: string;
|
|
65
73
|
}
|
|
66
|
-
|
|
74
|
+
/**
|
|
75
|
+
* A prop whose value becomes a `Gio.File` — and the reason it is its own kind.
|
|
76
|
+
*
|
|
77
|
+
* `Gtk.Picture:file` takes a `Gio.File`, an OBJECT, and building one needs
|
|
78
|
+
* `gi://Gio`. Nothing under `primitives/` imports `gi://` (that is what makes L2
|
|
79
|
+
* testable without a display and reusable by a binding that never loads GTK), so the
|
|
80
|
+
* table cannot produce the value — only the DECISION about it. So L2 does the whole
|
|
81
|
+
* of the decision, which is where every refusal lives (`http:` has no synchronous
|
|
82
|
+
* loader, a `require()` id has no asset registry, an array is a device-scale
|
|
83
|
+
* picker), and hands the framework layer a `{ kind, value }` pair it turns into one
|
|
84
|
+
* call: `Gio.File.new_for_path` or `Gio.File.new_for_uri`.
|
|
85
|
+
*/
|
|
86
|
+
export interface FileRoute {
|
|
87
|
+
readonly to: 'file';
|
|
88
|
+
/** Which node it lands on. */
|
|
89
|
+
readonly on?: NodeKind;
|
|
90
|
+
/** The GTK property that takes the file — `file` on a `Gtk.Picture`. */
|
|
91
|
+
readonly property: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A prop bound through a gesture CONTROLLER rather than a signal on the widget.
|
|
95
|
+
*
|
|
96
|
+
* `TouchableWithoutFeedback` is the one primitive that needs it: it has no button
|
|
97
|
+
* chrome, so its widget is a `Gtk.Box`, and a box emits no `clicked` (measured —
|
|
98
|
+
* `Gtk.Button`'s two signals are `activate` and `clicked`, and a box has neither).
|
|
99
|
+
* The desktop answer is a `Gtk.GestureClick` added to the widget, whose `released`
|
|
100
|
+
* signal is the press completing. A controller is `add_controller(new
|
|
101
|
+
* Gtk.GestureClick())` — a constructed OBJECT, so the same rule as {@link FileRoute}
|
|
102
|
+
* applies: the table names the signal, the framework layer builds the controller.
|
|
103
|
+
*/
|
|
104
|
+
export interface GestureRoute {
|
|
105
|
+
readonly to: 'gesture';
|
|
106
|
+
/** The `Gtk.GestureClick` signal — `released` for a completed press. */
|
|
107
|
+
readonly signal: string;
|
|
108
|
+
}
|
|
109
|
+
export type PropRoute = PropertyRoute | StylePropertyRoute | EventRoute | RefusedRoute | IgnoredRoute | FileRoute | GestureRoute;
|
|
67
110
|
/** A second styleable node inside the element, which the children go into. */
|
|
68
111
|
export interface ContentSpec {
|
|
69
112
|
readonly tag: string;
|
|
@@ -82,6 +125,16 @@ export interface ContentSpec {
|
|
|
82
125
|
* the one that holds every child.
|
|
83
126
|
*/
|
|
84
127
|
readonly classNameProp: string | null;
|
|
128
|
+
/**
|
|
129
|
+
* The slot this node declares to the OUTER node, when the outer node is slotted.
|
|
130
|
+
*
|
|
131
|
+
* Absent means the outer node's default slot, which is what every `single`-child
|
|
132
|
+
* container has. `ImageBackground` is the one primitive that needs it: its outer
|
|
133
|
+
* node is a `Gtk.Overlay`, whose two slots are not interchangeable — `child`
|
|
134
|
+
* holds the widget the overlay SIZES ITSELF TO and `add_overlay` stacks widgets
|
|
135
|
+
* on top of it.
|
|
136
|
+
*/
|
|
137
|
+
readonly slot?: string;
|
|
85
138
|
}
|
|
86
139
|
export interface PrimitiveSpec {
|
|
87
140
|
/** GType name, which is the tag the host takes. */
|
|
@@ -114,6 +167,30 @@ export interface PrimitiveSpec {
|
|
|
114
167
|
readonly prop: string;
|
|
115
168
|
readonly whenTrue: PrimitiveSpec;
|
|
116
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* A node BEHIND the children, which takes no children of its own.
|
|
172
|
+
*
|
|
173
|
+
* `ImageBackground` is the whole of this field, and the shape is forced rather
|
|
174
|
+
* than chosen. React Native's own `ImageBackground` is a `View` whose first child
|
|
175
|
+
* is an absolutely positioned `Image` — which is exactly the `overlayOnAbsoluteChild`
|
|
176
|
+
* switch above. But MEASURED on gtk 4.22.4: a `Gtk.Overlay` paints every overlay
|
|
177
|
+
* child ABOVE its main child, so a picture in the overlay slot covers the
|
|
178
|
+
* children instead of sitting behind them. The picture therefore has to be the
|
|
179
|
+
* MAIN child, and the children move into the overlay slot — which is a third
|
|
180
|
+
* node, not a variation of the second.
|
|
181
|
+
*/
|
|
182
|
+
readonly backdrop?: ContentSpec;
|
|
183
|
+
/**
|
|
184
|
+
* This primitive answers for no `style` and no `className`, and why.
|
|
185
|
+
*
|
|
186
|
+
* `Button` only. React Native's `Button` takes neither — its documented styling
|
|
187
|
+
* story is "you cannot", and GTK agrees: an `Adw`-themed button is styled by the
|
|
188
|
+
* theme and by its own classes (`suggested-action`), not by a caller's utility
|
|
189
|
+
* list. Refusing here rather than in a component's prop type is what makes the
|
|
190
|
+
* refusal reach a JavaScript caller and both L3s, and it is a REFUSAL rather than
|
|
191
|
+
* a silent drop for the reason every other one in this file is.
|
|
192
|
+
*/
|
|
193
|
+
readonly refusesStyle?: string;
|
|
117
194
|
}
|
|
118
195
|
export declare const PRIMITIVES: Readonly<Record<string, PrimitiveSpec>>;
|
|
119
196
|
/** The primitives this layer answers for. Derived, so it cannot disagree with the table. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** What went wrong, as a stable identifier. */
|
|
2
|
+
export type RouterErrorCode =
|
|
3
|
+
/** A file under the routes directory matches none of the four conventions. */
|
|
4
|
+
'unknown-convention'
|
|
5
|
+
/** A `[…]` segment with nothing between the brackets. */
|
|
6
|
+
| 'param-without-name'
|
|
7
|
+
/** Two route files resolve to the same URL. */
|
|
8
|
+
| 'duplicate-route'
|
|
9
|
+
/** `[...rest]` — a catch-all segment, which this layer does not answer yet. */
|
|
10
|
+
| 'deep-dynamic-unsupported'
|
|
11
|
+
/** `(a,b)` — expo-router's shared groups, which this layer does not answer. */
|
|
12
|
+
| 'shared-group-unsupported'
|
|
13
|
+
/** A `_layout` file whose directory holds no routes. */
|
|
14
|
+
| 'layout-without-routes'
|
|
15
|
+
/** The route manifest is empty, or not the shape the plugin emits. */
|
|
16
|
+
| 'bad-manifest'
|
|
17
|
+
/** A route module with no default export, or one that is not a component. */
|
|
18
|
+
| 'bad-route-module'
|
|
19
|
+
/** `router.*` was called with no router mounted. */
|
|
20
|
+
| 'no-router-mounted'
|
|
21
|
+
/** An href no route in the manifest matches. */
|
|
22
|
+
| 'unresolved-href'
|
|
23
|
+
/** A child of `<Stack>` / `<Tabs>` that is not its own `.Screen`. */
|
|
24
|
+
| 'not-a-screen-child'
|
|
25
|
+
/** An `options` key the navigator has no GTK answer for. */
|
|
26
|
+
| 'unknown-screen-option'
|
|
27
|
+
/** A navigator rendered outside the router, so it has no route node to build from. */
|
|
28
|
+
| 'no-route-node';
|
|
29
|
+
/** A routing input, file name or call this layer cannot answer for, and why. */
|
|
30
|
+
export declare class RouterError extends Error {
|
|
31
|
+
readonly name = "RouterError";
|
|
32
|
+
/** The stable identifier. Assert this in a test; print the message to a human. */
|
|
33
|
+
readonly code: RouterErrorCode;
|
|
34
|
+
/** The file, href or prop the refusal is about. */
|
|
35
|
+
readonly subject: string;
|
|
36
|
+
constructor(code: RouterErrorCode, subject: string, detail: string);
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { router, usePathname, useLocalSearchParams, navigationRef } from './navigation.js';
|
|
2
|
+
export { Stack, type StackProps, type StackScreenOptions } from './stack.js';
|
|
3
|
+
export { Tabs, type TabsProps, type TabScreenOptions } from './tabs.js';
|
|
4
|
+
export { RouterRoot, type RouterRootProps } from './root.js';
|
|
5
|
+
export { RouterError, type RouterErrorCode } from './errors.js';
|
|
6
|
+
export { buildRouteTree, pathConfigOf, screenUrls, type PathConfigLeaf, type PathConfigNode, type PathConfigTree, type RouteManifest, type RouteManifestEntry, type RouteNode, } from './routes.js';
|
|
7
|
+
export { componentFor, provideRouteNode, screenOptionsFrom, useRouteNode, useSynthesisedScreens, type ScreenDeclaration, } from './screens.js';
|
|
8
|
+
export * from '../generated/unsupported-router-exports.js';
|
|
9
|
+
export { ROUTER_NAMES, ROUTER_SUPPORT_TABLE, explainUnsupported, isImportable } from '../support-table.js';
|
|
10
|
+
export type { SupportEntry, SupportStatus, SupportTier } from '../support-table.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type RouteNode } from './routes.js';
|
|
2
|
+
/** The container ref every navigator in the tree hangs under. */
|
|
3
|
+
export declare const navigationRef: import("@react-navigation/core").NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>;
|
|
4
|
+
/**
|
|
5
|
+
* Bind the module-level `router` to a mounted tree. Called by `RouterRoot`.
|
|
6
|
+
*
|
|
7
|
+
* Refuses a second binding by name. The alternative — the newest `RouterRoot` wins —
|
|
8
|
+
* makes `router.push` navigate a tree the user is not looking at.
|
|
9
|
+
*/
|
|
10
|
+
export declare function installRouter(tree: RouteNode): void;
|
|
11
|
+
/** Release the binding. Called when `RouterRoot` unmounts. */
|
|
12
|
+
export declare function uninstallRouter(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Tell every subscriber the navigation state moved.
|
|
15
|
+
*
|
|
16
|
+
* Called from the container's `onReady` and `onStateChange`. It carries no state,
|
|
17
|
+
* because the state is read live — see `listeners` for the measurement behind that.
|
|
18
|
+
*/
|
|
19
|
+
export declare function notifyStateChanged(): void;
|
|
20
|
+
/**
|
|
21
|
+
* `expo-router`'s `router`: four methods, and every argument a URL.
|
|
22
|
+
*
|
|
23
|
+
* ADR 0032 measured the calls — `push` 19 times, `back` and `replace` a handful
|
|
24
|
+
* each, `navigate` where a screen must not stack up. That is the whole object.
|
|
25
|
+
*/
|
|
26
|
+
export declare const router: {
|
|
27
|
+
/** Go to `href`, adding a history entry even when that screen is already showing. */
|
|
28
|
+
readonly push: (href: string) => void;
|
|
29
|
+
/** Go to `href`, reusing the screen when it is already the current one. */
|
|
30
|
+
readonly navigate: (href: string) => void;
|
|
31
|
+
/** Replace the current screen with `href`, leaving the history length alone. */
|
|
32
|
+
readonly replace: (href: string) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Go back one entry.
|
|
35
|
+
*
|
|
36
|
+
* REFUSES when there is nowhere to go, which React Navigation's own `goBack`
|
|
37
|
+
* does not — it returns quietly. A quiet return is the silent drop this layer
|
|
38
|
+
* refuses everywhere else: the user presses back, the window does not change,
|
|
39
|
+
* and nothing anywhere says why.
|
|
40
|
+
*/
|
|
41
|
+
readonly back: () => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The current URL, without its query string.
|
|
45
|
+
*
|
|
46
|
+
* `useSyncExternalStore` and not `useNavigationState`, for two reasons that both
|
|
47
|
+
* matter: the value is derived from the ROOT state while a hook inside a screen sees
|
|
48
|
+
* only its own navigator's, and the store can change between render and commit —
|
|
49
|
+
* the tear `useSyncExternalStore` exists to prevent. The snapshot is a STRING, which
|
|
50
|
+
* is what makes reading it live from the container legal: React compares snapshots by
|
|
51
|
+
* identity, and a fresh object every call is the "getSnapshot should be cached"
|
|
52
|
+
* warning.
|
|
53
|
+
*
|
|
54
|
+
* The `/` it answers before the container is ready is not a placeholder: nothing has
|
|
55
|
+
* been navigated to yet, and the root URL is what the tree is about to show.
|
|
56
|
+
*/
|
|
57
|
+
export declare function usePathname(): string;
|
|
58
|
+
/**
|
|
59
|
+
* The params of the CURRENT route — the `[param]` values and the query string.
|
|
60
|
+
*
|
|
61
|
+
* `Local`, in expo-router's own sense: the params of the screen that calls it, not
|
|
62
|
+
* of whichever screen is focused elsewhere in the tree. That is `useRoute()`, which
|
|
63
|
+
* reads the nearest route context, minus React Navigation's own nesting keys —
|
|
64
|
+
* `screen`, `params`, `initial`, `state`, `path`, `pop` and `merge` are how a nested
|
|
65
|
+
* NAVIGATE addresses a child navigator, and handing them back as though the author
|
|
66
|
+
* had written them makes those seven param names silently unusable.
|
|
67
|
+
*
|
|
68
|
+
* Values are strings, because a URL segment is one. A number or a boolean that
|
|
69
|
+
* arrived through a dispatched param is stringified rather than dropped; anything
|
|
70
|
+
* structural (an object, an array) is left out, and `[param]` cannot produce one.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useLocalSearchParams<T extends Record<string, string> = Record<string, string>>(): Partial<T>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import { type RouteManifest } from './routes.js';
|
|
3
|
+
/** What `<RouterRoot>` needs. */
|
|
4
|
+
export interface RouterRootProps {
|
|
5
|
+
/**
|
|
6
|
+
* Every route file, as `{ contextKey, module }`.
|
|
7
|
+
*
|
|
8
|
+
* From the bundler plugin's virtual module in the ordinary case; a hand-written
|
|
9
|
+
* array works identically, which is the point of putting the conventions in this
|
|
10
|
+
* package rather than in the plugin (ADR 0032 § 12 — the build chain belongs to
|
|
11
|
+
* the consumer).
|
|
12
|
+
*/
|
|
13
|
+
manifest: RouteManifest;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The router, mounted.
|
|
17
|
+
*
|
|
18
|
+
* Throws — by name, with the file to create — when the routes directory has no
|
|
19
|
+
* `_layout`. expo-router requires one too, and the reason is worth stating: the root
|
|
20
|
+
* layout is where the application says WHICH navigator its top level is, and there is
|
|
21
|
+
* no defensible default. Quietly supplying a `<Stack>` would make a missing file look
|
|
22
|
+
* like a working application whose tabs never appear.
|
|
23
|
+
*/
|
|
24
|
+
export declare function RouterRoot(props: RouterRootProps): ReactElement;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** One entry of the manifest a bundler plugin emits, or a consumer writes by hand. */
|
|
2
|
+
export interface RouteManifestEntry {
|
|
3
|
+
/**
|
|
4
|
+
* The file's path relative to the routes directory, extension included —
|
|
5
|
+
* `(tabs)/home.tsx`. expo-router calls this the context key and so does this,
|
|
6
|
+
* because it is the string every diagnostic quotes back.
|
|
7
|
+
*/
|
|
8
|
+
readonly contextKey: string;
|
|
9
|
+
/**
|
|
10
|
+
* The module the file exported, ALREADY EVALUATED.
|
|
11
|
+
*
|
|
12
|
+
* Eager, not `() => import(…)`, and that is a decision rather than a shortcut.
|
|
13
|
+
* A lazy route needs a Suspense boundary, and `@gjsify/gtk-host/react`'s
|
|
14
|
+
* `render()` is synchronous by construction — a boundary that suspends on the
|
|
15
|
+
* first commit leaves the container EMPTY and returns cleanly, which is the
|
|
16
|
+
* silent-empty-window failure that host exists to refuse. A desktop application
|
|
17
|
+
* also ships as one bundle (ADR 0024), so there is no download for laziness to
|
|
18
|
+
* defer. `unknown` rather than a React type keeps this module framework-free;
|
|
19
|
+
* `screens.ts` is where the shape is checked, by name.
|
|
20
|
+
*/
|
|
21
|
+
readonly module: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** What a bundler plugin emits: every route file it found, in a stable order. */
|
|
24
|
+
export type RouteManifest = readonly RouteManifestEntry[];
|
|
25
|
+
/** A node of the route tree — one navigator, or one screen inside one. */
|
|
26
|
+
export interface RouteNode {
|
|
27
|
+
/** `layout` renders a navigator and has children; `screen` renders a route file. */
|
|
28
|
+
readonly kind: 'layout' | 'screen';
|
|
29
|
+
/**
|
|
30
|
+
* The React Navigation route name: this node's path RELATIVE to the navigator
|
|
31
|
+
* that holds it. `index`, `home`, `(tabs)`, `detail/[id]`, `+not-found`.
|
|
32
|
+
*/
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** The URL pattern this node contributes, in React Navigation's `:param` spelling. */
|
|
35
|
+
readonly path: string;
|
|
36
|
+
/** Param names this node's own pattern introduces, in declaration order. */
|
|
37
|
+
readonly params: readonly string[];
|
|
38
|
+
/** The file behind it. `null` for the synthesised root, which has no `_layout`. */
|
|
39
|
+
readonly contextKey: string | null;
|
|
40
|
+
/** That file's evaluated module. `null` when `contextKey` is. */
|
|
41
|
+
readonly module: unknown;
|
|
42
|
+
/** Children. Non-empty exactly when `kind` is `layout`. */
|
|
43
|
+
readonly children: readonly RouteNode[];
|
|
44
|
+
/** The `+not-found` route. Its pattern is `*`, so it matches last. */
|
|
45
|
+
readonly notFound: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The routes directory → the navigator tree.
|
|
49
|
+
*
|
|
50
|
+
* @throws RouterError for every one of the conventions' failure modes: a file that
|
|
51
|
+
* matches none of them, a `[param]` with no name, two files claiming one URL, a
|
|
52
|
+
* `_layout` with nothing under it, and a manifest that is empty or misshapen.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildRouteTree(manifest: RouteManifest): RouteNode;
|
|
55
|
+
/** A navigator's entry in React Navigation's path config: children, and which is first. */
|
|
56
|
+
export interface PathConfigTree {
|
|
57
|
+
readonly initialRouteName?: string;
|
|
58
|
+
readonly screens: Readonly<Record<string, PathConfigNode>>;
|
|
59
|
+
}
|
|
60
|
+
/** A screen's entry: one URL pattern, and no children. */
|
|
61
|
+
export interface PathConfigLeaf {
|
|
62
|
+
readonly path: string;
|
|
63
|
+
}
|
|
64
|
+
/** Either, which is what makes the config a tree. */
|
|
65
|
+
export type PathConfigNode = PathConfigLeaf | PathConfigTree;
|
|
66
|
+
/**
|
|
67
|
+
* The tree → the config `getStateFromPath` / `getPathFromState` take.
|
|
68
|
+
*
|
|
69
|
+
* REUSED RATHER THAN WRITTEN, and this function is the whole reason the reuse
|
|
70
|
+
* works: React Navigation already turns a URL into navigation state and state back
|
|
71
|
+
* into a URL, correctly, for nested navigators and for params. What it needs is a
|
|
72
|
+
* description of the tree, and the tree is what the file convention produces. So
|
|
73
|
+
* `router.navigate('/detail/7')` is `getStateFromPath` + `getActionFromState`, and
|
|
74
|
+
* `usePathname()` is `getPathFromState` — no matcher of our own in either
|
|
75
|
+
* direction, which is where a second router would have started.
|
|
76
|
+
*
|
|
77
|
+
* ONLY A SCREEN CARRIES A `path`, AND ITS PATH IS THE JOIN OF ITS ANCESTORS'.
|
|
78
|
+
* MEASURED, because the obvious shape does not work: giving the navigator its own
|
|
79
|
+
* `path` (`'(tabs)'` → `''`, `detail` → `'detail'`) makes React Navigation build a
|
|
80
|
+
* config entry for the navigator ITSELF as well as for its screens, and it then
|
|
81
|
+
* refuses the whole config — "Found conflicting screens with the same pattern. The
|
|
82
|
+
* pattern '' resolves to both 'index' and '(tabs)'". A navigator is not a
|
|
83
|
+
* destination; only the screen inside it is. Folding the prefix into the children
|
|
84
|
+
* keeps the `screens` nesting (which is what makes the resulting STATE nest, and
|
|
85
|
+
* therefore what makes a nested navigator work at all) while leaving exactly one
|
|
86
|
+
* pattern per screen.
|
|
87
|
+
*/
|
|
88
|
+
export declare function pathConfigOf(tree: RouteNode, prefix?: string): PathConfigTree;
|
|
89
|
+
/** Every screen's URL pattern, for a diagnostic that lists what WOULD have matched. */
|
|
90
|
+
export declare function screenUrls(tree: RouteNode, prefix?: string): readonly string[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ComponentType, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import type { RouteNode } from './routes.js';
|
|
3
|
+
/** Publish `node` to the navigator a layout component is about to render. */
|
|
4
|
+
export declare const provideRouteNode: (node: RouteNode, children: ReactNode) => ReactElement;
|
|
5
|
+
/** The layout node this navigator belongs to, or a refusal naming the fix. */
|
|
6
|
+
export declare function useRouteNode(navigator: string): RouteNode;
|
|
7
|
+
export declare function componentFor(node: RouteNode): ComponentType<Record<string, unknown>>;
|
|
8
|
+
/** What a `<Stack.Screen>` / `<Tabs.Screen>` declaration carries. */
|
|
9
|
+
export interface ScreenDeclaration {
|
|
10
|
+
/** The route name — the file's path relative to this navigator, e.g. `index`. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Per-screen options. Validated against the navigator's own allow-list. */
|
|
13
|
+
options?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A navigator's declared children → options by route name.
|
|
17
|
+
*
|
|
18
|
+
* REFUSES ANY OTHER CHILD BY NAME. `<Stack>{loading ? <Spinner/> : null}</Stack>` is
|
|
19
|
+
* the shape that gets written, and expo-router ignores it — which means a widget the
|
|
20
|
+
* author put in a navigator vanishes with no message. The refusal names the child
|
|
21
|
+
* and says where content belongs, which is inside a route file.
|
|
22
|
+
*
|
|
23
|
+
* `allowed` is the navigator's own option vocabulary, and an option outside it is
|
|
24
|
+
* refused for the same reason: `headerTintColor` silently doing nothing is a defect
|
|
25
|
+
* that only a screenshot can find.
|
|
26
|
+
*/
|
|
27
|
+
export declare function screenOptionsFrom(navigator: string, ScreenMarker: ComponentType<ScreenDeclaration>, allowed: readonly string[], children: ReactNode): ReadonlyMap<string, Record<string, unknown>>;
|
|
28
|
+
/**
|
|
29
|
+
* The React Navigation `<Screen>` elements a navigator's node implies.
|
|
30
|
+
*
|
|
31
|
+
* Memoised on the node and the options, because `useNavigationBuilder` reads these
|
|
32
|
+
* children on every render and rebuilding the elements is cheap while rebuilding the
|
|
33
|
+
* COMPONENTS is not — `componentFor` is what holds that line, and this hook keeps
|
|
34
|
+
* the element array from churning as well.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useSynthesisedScreens(node: RouteNode, Screen: ComponentType<ScreenDeclaration>, options: ReadonlyMap<string, Record<string, unknown>>): readonly ReactElement[];
|
|
37
|
+
/**
|
|
38
|
+
* Core's own `Screen`, which is the only component `useNavigationBuilder` accepts.
|
|
39
|
+
*
|
|
40
|
+
* Reached through the public factory rather than imported: `@react-navigation/core`
|
|
41
|
+
* does not export `Screen` from its index (it is meant to arrive with a `Navigator`),
|
|
42
|
+
* and calling the factory with a component nothing renders is the documented way to
|
|
43
|
+
* get the pair. The typed shape is asserted here once so no navigator repeats it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function navigationPair<Props>(View: ComponentType<Props>): {
|
|
46
|
+
readonly Navigator: ComponentType<Props>;
|
|
47
|
+
readonly Screen: ComponentType<ScreenDeclaration>;
|
|
48
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import type Adw from '@girs/adw-1';
|
|
3
|
+
import { type ScreenDeclaration } from './screens.js';
|
|
4
|
+
/** What one screen of a stack can be told. */
|
|
5
|
+
export interface StackScreenOptions {
|
|
6
|
+
/** The page title, shown by the page's own header bar. Defaults to the route name. */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** `false` renders the screen with no `Adw.HeaderBar`, and so with no back button. */
|
|
9
|
+
headerShown?: boolean;
|
|
10
|
+
/** `none` turns the transition off for this screen only. */
|
|
11
|
+
animation?: 'default' | 'none';
|
|
12
|
+
}
|
|
13
|
+
/** What `applyStack` did, so a vector can assert the classification and not the paint. */
|
|
14
|
+
export type StackChange = 'pop' | 'push' | 'replace' | 'none';
|
|
15
|
+
/**
|
|
16
|
+
* Classify the difference, and apply the one operation that fits it.
|
|
17
|
+
*
|
|
18
|
+
* `animate` is asked about the page that is MOVING — the departing one for a pop and
|
|
19
|
+
* the arriving one for a push, because the transition belongs to the page the user is
|
|
20
|
+
* about to stop or start looking at.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyStack(view: Adw.NavigationView, current: readonly string[], desired: readonly string[], animate: (key: string | undefined) => boolean): StackChange;
|
|
23
|
+
/**
|
|
24
|
+
* Which pages exist, and which of them are on their way out.
|
|
25
|
+
*
|
|
26
|
+
* Generic over the descriptor, and exported with the three functions that advance
|
|
27
|
+
* it, because this is where behaviour 3's edge cases live and none of them needs a
|
|
28
|
+
* widget or a reconciler to be wrong: a key held after its page is gone leaks for
|
|
29
|
+
* the life of the process, a key dropped too early flashes an empty page, and a
|
|
30
|
+
* reordered `order` makes the host reorder `Adw.NavigationView`'s children — which
|
|
31
|
+
* that widget answers by taking pages out of the navigation stack. Pure arithmetic
|
|
32
|
+
* over key sets is exactly what a spec can pin exhaustively.
|
|
33
|
+
*/
|
|
34
|
+
export interface Tracking<D> {
|
|
35
|
+
/** Page order — append-only for as long as a key is needed. See the header. */
|
|
36
|
+
readonly order: readonly string[];
|
|
37
|
+
/** The focused route key at the last advance, so a departure can be recognised. */
|
|
38
|
+
readonly focused: string | undefined;
|
|
39
|
+
/** Descriptors whose routes are gone and whose pages GTK is still animating out. */
|
|
40
|
+
readonly closing: Readonly<Record<string, D>>;
|
|
41
|
+
}
|
|
42
|
+
/** The tracking a first render starts from. */
|
|
43
|
+
export declare const initialTracking: <D>(live: readonly string[], focused: string | undefined) => Tracking<D>;
|
|
44
|
+
/** Every key that must still be rendered: the live routes plus what is closing. */
|
|
45
|
+
export declare const neededKeys: <D>(tracking: Tracking<D>, live: readonly string[]) => readonly string[];
|
|
46
|
+
/** Has anything changed that the tracking has to answer for? */
|
|
47
|
+
export declare const needsAdvance: <D>(tracking: Tracking<D>, live: readonly string[], focused: string | undefined) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The next tracking, given the live routes and the descriptors of the LAST commit.
|
|
50
|
+
*
|
|
51
|
+
* `previous` and not the current descriptors: the descriptor of a route that just
|
|
52
|
+
* left the state is not in the current map any more, and the closing page still has
|
|
53
|
+
* to render something.
|
|
54
|
+
*
|
|
55
|
+
* ONLY THE DEPARTING FOCUSED KEY BECOMES `closing`. A route removed from the middle
|
|
56
|
+
* of the stack was never on screen, so nothing is animating it out and holding it
|
|
57
|
+
* would be a leak with no symptom.
|
|
58
|
+
*/
|
|
59
|
+
export declare function advanceTracking<D>(tracking: Tracking<D>, live: readonly string[], focused: string | undefined, previous: Readonly<Record<string, D>>): Tracking<D>;
|
|
60
|
+
/** Let go of one closing page. Identity-stable when the key was not closing. */
|
|
61
|
+
export declare function releaseFrom<D>(tracking: Tracking<D>, key: string): Tracking<D>;
|
|
62
|
+
/**
|
|
63
|
+
* Run `action` at most once per microtask, however many times the trigger fires.
|
|
64
|
+
*
|
|
65
|
+
* MEASURED on libadwaita 1.9.3: `pop_to_tag` over two pages emits `popped` TWICE,
|
|
66
|
+
* and the widget's navigation stack is ALREADY FINAL at the first emission — both
|
|
67
|
+
* handlers see `a,b`. So every emission in a burst asks the same question.
|
|
68
|
+
*
|
|
69
|
+
* AND THE OBVIOUS JUSTIFICATION IS NOT THE ONE THAT HOLDS, which is worth writing
|
|
70
|
+
* down because it is the one a reader will supply. "Without this it over-pops" is
|
|
71
|
+
* NOT measured: with the coalescer disabled, a four-to-two gesture still lands on two
|
|
72
|
+
* routes, because `@react-navigation/core` 7.21.13 updates `navigation.getState()`
|
|
73
|
+
* eagerly on dispatch, so the second handler computes a delta of zero. What the
|
|
74
|
+
* coalescer actually buys is:
|
|
75
|
+
*
|
|
76
|
+
* - ATTRIBUTABILITY. Undebounced, correctness rests on that eagerness, which is an
|
|
77
|
+
* internal and not a documented contract. If a future version batched its state,
|
|
78
|
+
* an undebounced bridge would dispatch the same pop N times and the symptom —
|
|
79
|
+
* "back skips two screens" — would surface nowhere near the cause.
|
|
80
|
+
* - N − 1 fewer passes per gesture: one dispatch and one walk of the widget's list
|
|
81
|
+
* model instead of one per popped page.
|
|
82
|
+
*
|
|
83
|
+
* Also measured, and the reason no echo guard is needed anywhere else here:
|
|
84
|
+
* `replace_with_tags` emits NO `popped` at all, so this layer's own React→widget sync
|
|
85
|
+
* cannot re-enter through the bridge.
|
|
86
|
+
*/
|
|
87
|
+
export declare function coalesce(action: () => void): () => void;
|
|
88
|
+
/**
|
|
89
|
+
* A `<Stack.Screen>` declaration. Read as data by `<Stack>`; never rendered.
|
|
90
|
+
*
|
|
91
|
+
* It throws if it ever IS rendered, because that means it was put somewhere no
|
|
92
|
+
* navigator reads it — inside a `<View>`, or in a route file — and a component that
|
|
93
|
+
* rendered nothing there would be a silent drop.
|
|
94
|
+
*/
|
|
95
|
+
declare function StackScreen(_props: ScreenDeclaration & {
|
|
96
|
+
options?: StackScreenOptions;
|
|
97
|
+
}): never;
|
|
98
|
+
declare namespace StackScreen {
|
|
99
|
+
var displayName: string;
|
|
100
|
+
}
|
|
101
|
+
/** What `<Stack>` accepts. */
|
|
102
|
+
export interface StackProps {
|
|
103
|
+
/** `<Stack.Screen>` declarations, and nothing else. */
|
|
104
|
+
children?: ReactNode;
|
|
105
|
+
/** Options for every screen, before its own. */
|
|
106
|
+
screenOptions?: StackScreenOptions;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* `expo-router`'s `<Stack>`: its screens are the FILES this `_layout` owns.
|
|
110
|
+
*
|
|
111
|
+
* Its children are read as declarations, so the ordinary React reflex — putting
|
|
112
|
+
* content in a navigator — is refused by name rather than dropped.
|
|
113
|
+
*/
|
|
114
|
+
export declare function Stack(props: StackProps): ReactElement;
|
|
115
|
+
export declare namespace Stack {
|
|
116
|
+
var Screen: typeof StackScreen;
|
|
117
|
+
}
|
|
118
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { type ScreenDeclaration } from './screens.js';
|
|
3
|
+
/** What one tab can be told. */
|
|
4
|
+
export interface TabScreenOptions {
|
|
5
|
+
/** The switcher button's label. Defaults to the route name. */
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A `<Tabs.Screen>` declaration. Read as data by `<Tabs>`; never rendered. */
|
|
9
|
+
declare function TabsScreen(_props: ScreenDeclaration & {
|
|
10
|
+
options?: TabScreenOptions;
|
|
11
|
+
}): never;
|
|
12
|
+
declare namespace TabsScreen {
|
|
13
|
+
var displayName: string;
|
|
14
|
+
}
|
|
15
|
+
/** What `<Tabs>` accepts. */
|
|
16
|
+
export interface TabsProps {
|
|
17
|
+
/** `<Tabs.Screen>` declarations, and nothing else. */
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
/** Options for every tab, before its own. */
|
|
20
|
+
screenOptions?: TabScreenOptions;
|
|
21
|
+
/** `false` drops the header bar, and with it the switcher. */
|
|
22
|
+
headerShown?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `expo-router`'s `<Tabs>`: its tabs are the FILES this `_layout` owns.
|
|
26
|
+
*
|
|
27
|
+
* Every tab is mounted. React Native's tab navigator mounts lazily because a phone
|
|
28
|
+
* cannot afford five screens; a GTK page that is not the visible child of an
|
|
29
|
+
* `Adw.ViewStack` is not realised and costs a widget, and the laziness would buy a
|
|
30
|
+
* flash on first switch. Declared as a limit rather than left as a surprise.
|
|
31
|
+
*/
|
|
32
|
+
export declare function Tabs(props: TabsProps): ReactElement;
|
|
33
|
+
export declare namespace Tabs {
|
|
34
|
+
var Screen: typeof TabsScreen;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -48,10 +48,15 @@ export interface PressableProps extends CommonProps {
|
|
|
48
48
|
/**
|
|
49
49
|
* Never a function of `{ pressed }`.
|
|
50
50
|
*
|
|
51
|
-
* `PrimitiveChildren` excludes functions, which is the build-time half of
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* `PrimitiveChildren` excludes functions, which is the build-time half of the
|
|
52
|
+
* refusal. The runtime half is in `factsOf`, and it is generic rather than
|
|
53
|
+
* per-primitive here because Solid resolves children before any primitive sees
|
|
54
|
+
* them.
|
|
55
|
+
*
|
|
56
|
+
* The REACT binding implements the function form; this one does not, and the
|
|
57
|
+
* reason is in `factsOf`' message: a Solid component body runs once, so the press
|
|
58
|
+
* state has to reach the child as a signal rather than as a re-render, which is a
|
|
59
|
+
* design of its own rather than a port.
|
|
55
60
|
*/
|
|
56
61
|
children?: PrimitiveChildren;
|
|
57
62
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type StyleInput, type StyleObject } from './primitives/style.js';
|
|
2
|
+
/** React Native's own shape: a name per style, each a property record. */
|
|
3
|
+
export type NamedStyles<T> = {
|
|
4
|
+
readonly [K in keyof T]: StyleObject;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* `{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }`.
|
|
8
|
+
*
|
|
9
|
+
* React Native's own object, value for value — and every one of the five properties
|
|
10
|
+
* is routed by L1 (`position` and the four edges), so it needs no special case here.
|
|
11
|
+
* The numeric zeros are what React Native writes and `normalise()` turns each into
|
|
12
|
+
* `"0px"`, which is what the partition reads.
|
|
13
|
+
*
|
|
14
|
+
* On GTK it resolves to the overlay intent with all four edges at 0, which L2 answers
|
|
15
|
+
* as `halign: fill, valign: fill` on an overlay child — the one case where React
|
|
16
|
+
* Native's four offsets and GTK's two alignments agree exactly, because filling both
|
|
17
|
+
* axes is the only inset an overlay child can express without a coordinate pair.
|
|
18
|
+
*/
|
|
19
|
+
export declare const absoluteFillObject: StyleObject;
|
|
20
|
+
export declare const StyleSheet: {
|
|
21
|
+
/**
|
|
22
|
+
* The style set, unchanged — see the header for why that is the honest answer.
|
|
23
|
+
*
|
|
24
|
+
* The signature is React Native's: the generic keeps each key's type, so
|
|
25
|
+
* `styles.title` is a style and `styles.titlee` is a type error.
|
|
26
|
+
*/
|
|
27
|
+
readonly create: <T extends NamedStyles<T>>(styles: T) => T;
|
|
28
|
+
/**
|
|
29
|
+
* A `style` prop → one flat object, later entries winning.
|
|
30
|
+
*
|
|
31
|
+
* L2's `flattenStyle`, exported under React Native's name rather than
|
|
32
|
+
* reimplemented: the resolver has to flatten the same prop with the same
|
|
33
|
+
* precedence, and two functions answering "what does `[a, cond && b]` mean" is
|
|
34
|
+
* the second truth this package keeps lifting out.
|
|
35
|
+
*/
|
|
36
|
+
readonly flatten: (style: StyleInput) => StyleObject;
|
|
37
|
+
/**
|
|
38
|
+
* Two styles into one, React Native's own semantics.
|
|
39
|
+
*
|
|
40
|
+
* `b` wins where both set a property, `null`/`undefined` fall away, and the result
|
|
41
|
+
* is an ARRAY when both are present — because that is what React Native returns
|
|
42
|
+
* and code downstream compares identities. `flatten` is what collapses it.
|
|
43
|
+
*/
|
|
44
|
+
readonly compose: (a: StyleInput, b: StyleInput) => StyleInput;
|
|
45
|
+
/**
|
|
46
|
+
* One device pixel, in logical pixels — read fresh, never cached.
|
|
47
|
+
*
|
|
48
|
+
* A GETTER because `Gdk.Display.get_default()` is null until `Gtk.init()`
|
|
49
|
+
* (measured) and real code reads this at module scope: before the display exists
|
|
50
|
+
* the answer is 1, and the same read once the application is running answers from
|
|
51
|
+
* the monitors. A constant would freeze the first of those for the process.
|
|
52
|
+
* `apis/display.ts` holds the measurement and the reason it takes the SMALLEST
|
|
53
|
+
* monitor scale.
|
|
54
|
+
*/
|
|
55
|
+
readonly hairlineWidth: number;
|
|
56
|
+
/**
|
|
57
|
+
* React Native's `absoluteFill`, which is the same object as `absoluteFillObject`.
|
|
58
|
+
*
|
|
59
|
+
* It was a registered style id and is now the object itself, so the two members
|
|
60
|
+
* differ in name only. Both are exported because ported code uses both spellings
|
|
61
|
+
* and an absent one is a `MISSING_EXPORT` rather than a value that behaves.
|
|
62
|
+
*/
|
|
63
|
+
readonly absoluteFill: Readonly<Record<string, unknown>>;
|
|
64
|
+
readonly absoluteFillObject: Readonly<Record<string, unknown>>;
|
|
65
|
+
/**
|
|
66
|
+
* Refused by name: it installs a GLOBAL transform on every style in the process.
|
|
67
|
+
*
|
|
68
|
+
* React Native uses it to let a platform rewrite style values before they reach
|
|
69
|
+
* the native side. Here the equivalent hook already exists and is scoped:
|
|
70
|
+
* `configureStyle({ tokens })` is where a project's own values enter, and a
|
|
71
|
+
* process-wide preprocessor would be a second, invisible one that no element
|
|
72
|
+
* could opt out of.
|
|
73
|
+
*/
|
|
74
|
+
readonly setStyleAttributePreprocessor: () => never;
|
|
75
|
+
};
|