react-native-gtkx 0.1.0-alpha.2 → 0.1.0-alpha.4

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.
Files changed (105) hide show
  1. package/README.md +41 -8
  2. package/dist/animated/create-animated.js +1 -1
  3. package/dist/animated/create-animated.js.map +1 -1
  4. package/dist/animated/index.js +3 -3
  5. package/dist/animated/index.js.map +1 -1
  6. package/dist/apis/back-handler.d.ts +9 -0
  7. package/dist/apis/back-handler.js +30 -0
  8. package/dist/apis/back-handler.js.map +1 -0
  9. package/dist/apis/host.d.ts +7 -1
  10. package/dist/apis/host.gtkx.js +12 -1
  11. package/dist/apis/host.gtkx.js.map +1 -1
  12. package/dist/apis/host.js.map +1 -1
  13. package/dist/apis/i18n-manager.d.ts +14 -0
  14. package/dist/apis/i18n-manager.js +20 -0
  15. package/dist/apis/i18n-manager.js.map +1 -0
  16. package/dist/apis/index.d.ts +17 -0
  17. package/dist/apis/index.js +5 -0
  18. package/dist/apis/index.js.map +1 -1
  19. package/dist/apis/interaction-manager.d.ts +16 -0
  20. package/dist/apis/interaction-manager.js +96 -0
  21. package/dist/apis/interaction-manager.js.map +1 -0
  22. package/dist/apis/linking.d.ts +4 -0
  23. package/dist/apis/linking.js +9 -0
  24. package/dist/apis/linking.js.map +1 -1
  25. package/dist/components/app-registry.d.ts +2 -0
  26. package/dist/components/app-registry.js +19 -2
  27. package/dist/components/app-registry.js.map +1 -1
  28. package/dist/components/flat-list.d.ts +2 -0
  29. package/dist/components/flat-list.js.map +1 -1
  30. package/dist/components/index.d.ts +1 -1
  31. package/dist/components/index.js +1 -1
  32. package/dist/components/index.js.map +1 -1
  33. package/dist/components/modal.d.ts +1 -1
  34. package/dist/components/pressable.js +4 -4
  35. package/dist/components/pressable.js.map +1 -1
  36. package/dist/components/root.d.ts +10 -1
  37. package/dist/components/root.js +39 -1
  38. package/dist/components/root.js.map +1 -1
  39. package/dist/components/scroll-view.js +58 -5
  40. package/dist/components/scroll-view.js.map +1 -1
  41. package/dist/components/text-input.d.ts +1 -0
  42. package/dist/components/text-input.js +34 -5
  43. package/dist/components/text-input.js.map +1 -1
  44. package/dist/components/use-layout-child.d.ts +1 -1
  45. package/dist/components/use-layout-child.js +22 -2
  46. package/dist/components/use-layout-child.js.map +1 -1
  47. package/dist/components/virtualized-list.d.ts +2 -0
  48. package/dist/components/virtualized-list.js +132 -10
  49. package/dist/components/virtualized-list.js.map +1 -1
  50. package/dist/contracts.js +2 -3
  51. package/dist/contracts.js.map +1 -1
  52. package/dist/gtkx/bridge/index.d.ts +4 -2
  53. package/dist/gtkx/bridge/index.js +7 -3
  54. package/dist/gtkx/bridge/index.js.map +1 -1
  55. package/dist/gtkx/bridge/measure.js +5 -0
  56. package/dist/gtkx/bridge/measure.js.map +1 -1
  57. package/dist/gtkx/bridge/theme.js +1 -1
  58. package/dist/gtkx/bridge/theme.js.map +1 -1
  59. package/dist/gtkx/bridge/use-signal.d.ts +2 -0
  60. package/dist/gtkx/bridge/use-signal.js +31 -0
  61. package/dist/gtkx/bridge/use-signal.js.map +1 -0
  62. package/dist/index.d.ts +2 -2
  63. package/dist/index.js +3 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/layout/engine.d.ts +4 -0
  66. package/dist/layout/engine.js +97 -5
  67. package/dist/layout/engine.js.map +1 -1
  68. package/dist/layout/node.d.ts +1 -1
  69. package/dist/layout/node.js +8 -5
  70. package/dist/layout/node.js.map +1 -1
  71. package/dist/metro/index.js +1 -0
  72. package/dist/metro/index.js.map +1 -1
  73. package/dist/navigation/index.d.ts +44 -0
  74. package/dist/navigation/index.js +216 -0
  75. package/dist/navigation/index.js.map +1 -0
  76. package/dist/navigation/option-warnings.d.ts +2 -0
  77. package/dist/navigation/option-warnings.js +61 -0
  78. package/dist/navigation/option-warnings.js.map +1 -0
  79. package/dist/navigation/sidebar.d.ts +37 -0
  80. package/dist/navigation/sidebar.js +71 -0
  81. package/dist/navigation/sidebar.js.map +1 -0
  82. package/dist/perf.d.ts +9 -0
  83. package/dist/perf.js +156 -0
  84. package/dist/perf.js.map +1 -0
  85. package/dist/runner/host-dev.js +15 -6
  86. package/dist/runner/host-dev.js.map +1 -1
  87. package/dist/runner/host.js +24 -9
  88. package/dist/runner/host.js.map +1 -1
  89. package/dist/runner/index.js +7 -3
  90. package/dist/runner/index.js.map +1 -1
  91. package/dist/style/colors.js +3 -1
  92. package/dist/style/colors.js.map +1 -1
  93. package/dist/style/index.js +2 -2
  94. package/dist/style/index.js.map +1 -1
  95. package/dist/style/registry.gtkx.js +1 -1
  96. package/dist/style/registry.gtkx.js.map +1 -1
  97. package/dist/style/split-style.js +1 -1
  98. package/dist/style/split-style.js.map +1 -1
  99. package/dist/style/text-align.js +1 -1
  100. package/dist/style/text-align.js.map +1 -1
  101. package/dist/vite/index.d.ts +5 -4
  102. package/dist/vite/index.js +42 -7
  103. package/dist/vite/index.js.map +1 -1
  104. package/package.json +18 -10
  105. package/types.d.ts +13 -0
@@ -0,0 +1,216 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // react-native-gtkx/navigation — a react-navigation stack navigator backed
3
+ // by Adw.NavigationView. The model mirrors @react-navigation/native-stack
4
+ // on iOS (UINavigationController): react-navigation state is the source of
5
+ // truth, the native view is imperatively synced to it, and NATIVE pops (the
6
+ // Adwaita back button, Escape, the back gesture) are reported back through
7
+ // the "popped" signal.
8
+ //
9
+ // Sync protocol (the two directions must not echo each other):
10
+ // - state → view: an effect diffs the synced tag stack against
11
+ // state.routes and drives pushByTag / popToTag / replaceWithTags. Pages
12
+ // are declarative children keyed by route.key, so the widgets exist
13
+ // before the effect runs.
14
+ // - view → state: "popped" fires for EVERY popped page, ours and the
15
+ // user's. The discriminator is the state itself: a tag that is still in
16
+ // state.routes means the user popped the view (dispatch POP); a tag
17
+ // already gone from state means the pop was state-driven — ignore.
18
+ import { createNavigatorFactory, StackActions, StackRouter, useNavigationBuilder, usePreventRemoveContext, } from "@react-navigation/native";
19
+ import { useCallback, useEffect, useRef, useState, } from "react";
20
+ import { InteractionManager } from "../apis/interaction-manager";
21
+ import { getActiveChrome } from "../components/app-registry";
22
+ import { IntrinsicRoot, NestedRoot } from "../components/root";
23
+ import { AdwHeaderBar, AdwNavigationPage, AdwNavigationView, AdwToolbarView, GtkButton, } from "../gtkx/bridge/index";
24
+ import { warnIgnoredOptions } from "./option-warnings";
25
+ const STACK_OPTION_KEYS = new Set([
26
+ "title",
27
+ "headerShown",
28
+ "headerButtons",
29
+ "headerLeft",
30
+ "headerRight",
31
+ "gestureEnabled",
32
+ ]);
33
+ const StackNavigator = ({ initialRouteName, screenOptions, children, }) => {
34
+ const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder(StackRouter, {
35
+ initialRouteName,
36
+ screenOptions,
37
+ children,
38
+ });
39
+ const viewRef = useRef(null);
40
+ useEffect(() => {
41
+ for (const route of state.routes) {
42
+ const descriptor = descriptors[route.key];
43
+ if (descriptor) {
44
+ warnIgnoredOptions("createStackNavigator", descriptor.options, STACK_OPTION_KEYS);
45
+ }
46
+ }
47
+ }, [state, descriptors]);
48
+ useEffect(() => {
49
+ if (process.env.NODE_ENV !== "production" &&
50
+ getActiveChrome() === "system") {
51
+ console.warn('[react-native-gtkx/navigation] the app runs with the default window chrome — pages bring their own HeaderBars, so you will see a doubled titlebar. Pass chrome: "content" to AppRegistry.runApplication.');
52
+ }
53
+ }, []);
54
+ // Mirror of the view's visible stack (route keys), maintained by the sync
55
+ // effect and by the popped handler — the two never race: GTK signals run
56
+ // synchronously inside the very push/pop calls the effect makes.
57
+ const syncedRef = useRef([]);
58
+ // Pop ANIMATION support: react-navigation drops a popped route from state
59
+ // immediately, but Adwaita still animates the page out (~200 ms). Pages
60
+ // therefore render from renderedKeys — live routes plus closing pages —
61
+ // and a closing page leaves only on its "hidden" signal (the end of the
62
+ // transition). Its content renders from a snapshot cached while live.
63
+ const [renderedKeys, setRenderedKeys] = useState(() => state.routes.map((route) => route.key));
64
+ const liveKeys = state.routes.map((route) => route.key);
65
+ const missingKeys = liveKeys.filter((key) => !renderedKeys.includes(key));
66
+ if (missingKeys.length > 0) {
67
+ // The sanctioned derive-state-during-render pattern: a new route must be
68
+ // in renderedKeys within the same commit its page is pushed.
69
+ setRenderedKeys([...renderedKeys, ...missingKeys]);
70
+ }
71
+ // A state-held stable Map (not a ref): the render path reads it for
72
+ // closing pages, and the react-hooks/refs rule rightly bans render-time
73
+ // ref reads.
74
+ const [snapshots] = useState(() => new Map());
75
+ useEffect(() => {
76
+ for (const route of state.routes) {
77
+ const descriptor = descriptors[route.key];
78
+ if (descriptor) {
79
+ snapshots.set(route.key, {
80
+ name: route.name,
81
+ options: descriptor.options,
82
+ element: _jsx(NestedRoot, { children: descriptor.render() }),
83
+ });
84
+ }
85
+ }
86
+ });
87
+ const handleHidden = useCallback((key) => {
88
+ // "hidden" also fires for a live page covered by a push — only pages
89
+ // gone from state are actually closing.
90
+ if (navigation.getState().routes.some((route) => route.key === key)) {
91
+ return;
92
+ }
93
+ snapshots.delete(key);
94
+ setRenderedKeys((keys) => keys.filter((rendered) => rendered !== key));
95
+ }, [navigation, snapshots]);
96
+ // "hidden" delivery is not guaranteed in every environment (headless
97
+ // compositors with animations disabled never emit it) — a timer slightly
98
+ // longer than the Adwaita transition is the fallback; handleHidden is
99
+ // idempotent, whichever fires first wins.
100
+ const scheduleRetainedRemoval = useCallback((key) => {
101
+ setTimeout(() => handleHidden(key), 400);
102
+ }, [handleHidden]);
103
+ // Bracket every view transition with an InteractionManager handle so
104
+ // runAfterInteractions-scheduled work (a screen's own data load / heavy
105
+ // render) waits for the slide to finish instead of stealing its frames.
106
+ // The handle is opened when the sync effect mutates the view and cleared
107
+ // one transition-length later; overlapping transitions reference-count.
108
+ const beginTransition = useCallback(() => {
109
+ const handle = InteractionManager.createInteractionHandle();
110
+ setTimeout(() => InteractionManager.clearInteractionHandle(handle), 400);
111
+ }, []);
112
+ useEffect(() => {
113
+ const view = viewRef.current;
114
+ if (!view) {
115
+ return;
116
+ }
117
+ const target = state.routes.map((route) => route.key);
118
+ if (syncedRef.current.length === 0 && target.length > 0) {
119
+ // First mount: NavigationView shows the first added page by itself.
120
+ syncedRef.current = target.slice(0, 1);
121
+ }
122
+ let common = 0;
123
+ while (common < syncedRef.current.length &&
124
+ common < target.length &&
125
+ syncedRef.current[common] === target[common]) {
126
+ common += 1;
127
+ }
128
+ if (common === 0 && syncedRef.current[0] !== target[0]) {
129
+ // The stack root changed (reset): swap the whole visible stack.
130
+ const leaving = syncedRef.current.filter((key) => !target.includes(key));
131
+ syncedRef.current = [...target];
132
+ beginTransition();
133
+ view.replaceWithTags(target);
134
+ for (const key of leaving) {
135
+ scheduleRetainedRemoval(key);
136
+ }
137
+ return;
138
+ }
139
+ if (syncedRef.current.length > common) {
140
+ const anchor = target[common - 1];
141
+ const leaving = syncedRef.current.slice(common);
142
+ syncedRef.current = syncedRef.current.slice(0, common);
143
+ if (anchor !== undefined) {
144
+ beginTransition();
145
+ view.popToTag(anchor);
146
+ }
147
+ for (const key of leaving) {
148
+ scheduleRetainedRemoval(key);
149
+ }
150
+ }
151
+ for (let index = syncedRef.current.length; index < target.length; index += 1) {
152
+ const key = target[index];
153
+ syncedRef.current.push(key);
154
+ beginTransition();
155
+ view.pushByTag(key);
156
+ }
157
+ }, [state, scheduleRetainedRemoval, beginTransition]);
158
+ // `navigation` is identity-stable across renders (react-navigation
159
+ // builder contract) and getState() always reads the live state — the
160
+ // closure needs no ref indirection.
161
+ const handlePopped = (page) => {
162
+ const tag = page?.getTag();
163
+ if (!tag) {
164
+ return;
165
+ }
166
+ syncedRef.current = syncedRef.current.filter((key) => key !== tag);
167
+ const current = navigation.getState();
168
+ if (current.routes.some((route) => route.key === tag)) {
169
+ // The view popped on its own (back button / gesture): follow in state.
170
+ navigation.dispatch(StackActions.pop());
171
+ }
172
+ scheduleRetainedRemoval(tag);
173
+ };
174
+ return (_jsx(NavigationContent, { children: _jsx(StackView, { viewRef: viewRef, renderedKeys: renderedKeys, routeKeys: state.routes.map((route) => route.key), descriptors: descriptors, snapshots: snapshots, onPopped: handlePopped, onHidden: handleHidden }) }));
175
+ };
176
+ // The page list lives inside NavigationContent so it can read the
177
+ // prevent-remove context (which useNavigationBuilder only provides to
178
+ // NavigationContent's children). A page whose route is prevented — or whose
179
+ // screen set gestureEnabled: false — reports canPop: false, so Adwaita
180
+ // disables the back button, Escape and the back gesture for it; a
181
+ // programmatic goBack still pops (once the app lifts the prevention, e.g.
182
+ // after its own confirmation dialog). This is why a native pop can never
183
+ // race react-navigation state for these routes.
184
+ const StackView = ({ viewRef, renderedKeys, routeKeys, descriptors, snapshots, onPopped, onHidden, }) => {
185
+ const { preventedRoutes } = usePreventRemoveContext();
186
+ return (_jsx(AdwNavigationView, { ref: viewRef, onPopped: (page) => onPopped(page), children: renderedKeys.map((key) => {
187
+ const isLive = routeKeys.includes(key);
188
+ const descriptor = isLive
189
+ ? descriptors[key]
190
+ : undefined;
191
+ const snapshot = snapshots.get(key);
192
+ const options = descriptor?.options ?? snapshot?.options ?? {};
193
+ const headerShown = options.headerShown ?? true;
194
+ const canPop = options.gestureEnabled !== false &&
195
+ !preventedRoutes[key]?.preventRemove;
196
+ const content = descriptor ? (_jsx(NestedRoot, { children: descriptor.render() })) : ((snapshot?.element ?? null));
197
+ return (_jsx(AdwNavigationPage, { tag: key,
198
+ // Never the route key — that is an internal identifier, and
199
+ // it would end up in the window title under content chrome.
200
+ title: options.title ?? descriptor?.route.name ?? snapshot?.name ?? key, canPop: canPop, onHidden: () => onHidden(key), children: headerShown ? (_jsx(AdwToolbarView, { topBar: _jsx(AdwHeaderBar, { start: options.headerLeft ? (_jsx(IntrinsicRoot, { children: options.headerLeft() })) : undefined, end: [
201
+ ...(options.headerRight
202
+ ? [
203
+ _jsx(IntrinsicRoot, { children: options.headerRight() }, "header-right"),
204
+ ]
205
+ : []),
206
+ ...(options.headerButtons?.map((button) => (_jsx(GtkButton, { iconName: button.icon, tooltipText: button.tooltip, onClicked: button.onPress }, button.id))) ?? []),
207
+ ] }), children: content })) : (content) }, key));
208
+ }) }));
209
+ };
210
+ const stackFactory = createNavigatorFactory(StackNavigator);
211
+ export const createStackNavigator = () => stackFactory();
212
+ export { createSidebarNavigator, } from "./sidebar";
213
+ // The rest of the react-navigation surface apps need, so a linux app can
214
+ // import everything from one place.
215
+ export { CommonActions, NavigationContainer, StackActions, useFocusEffect, useIsFocused, useNavigation, useNavigationContainerRef, usePreventRemove, useRoute, } from "@react-navigation/native";
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigation/index.tsx"],"names":[],"mappings":"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","sourcesContent":["// react-native-gtkx/navigation — a react-navigation stack navigator backed\n// by Adw.NavigationView. The model mirrors @react-navigation/native-stack\n// on iOS (UINavigationController): react-navigation state is the source of\n// truth, the native view is imperatively synced to it, and NATIVE pops (the\n// Adwaita back button, Escape, the back gesture) are reported back through\n// the \"popped\" signal.\n//\n// Sync protocol (the two directions must not echo each other):\n// - state → view: an effect diffs the synced tag stack against\n// state.routes and drives pushByTag / popToTag / replaceWithTags. Pages\n// are declarative children keyed by route.key, so the widgets exist\n// before the effect runs.\n// - view → state: \"popped\" fires for EVERY popped page, ours and the\n// user's. The discriminator is the state itself: a tag that is still in\n// state.routes means the user popped the view (dispatch POP); a tag\n// already gone from state means the pop was state-driven — ignore.\nimport {\n createNavigatorFactory,\n StackActions,\n StackRouter,\n useNavigationBuilder,\n usePreventRemoveContext,\n type NavigationProp,\n type ParamListBase,\n type RouteProp,\n type StackNavigationState,\n} from \"@react-navigation/native\"\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentType,\n type ReactNode,\n type RefObject,\n} from \"react\"\nimport { InteractionManager } from \"../apis/interaction-manager\"\nimport { getActiveChrome } from \"../components/app-registry\"\nimport { IntrinsicRoot, NestedRoot } from \"../components/root\"\nimport {\n AdwHeaderBar,\n AdwNavigationPage,\n AdwNavigationView,\n AdwToolbarView,\n GtkButton,\n type Adw,\n} from \"../gtkx/bridge/index\"\nimport { warnIgnoredOptions } from \"./option-warnings\"\nimport type { HeaderButton } from \"./sidebar\"\n\nconst STACK_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"title\",\n \"headerShown\",\n \"headerButtons\",\n \"headerLeft\",\n \"headerRight\",\n \"gestureEnabled\",\n])\n\nexport type StackNavigationOptions = {\n /** HeaderBar title; defaults to the route name. */\n title?: string\n /** Render the Adwaita HeaderBar for this screen (default true). */\n headerShown?: boolean\n /** Buttons packed at the end of this screen's HeaderBar (see\n * HeaderButton); screens usually set them via navigation.setOptions. */\n headerButtons?: HeaderButton[]\n /** RN content packed at the start of the HeaderBar (an intrinsic-size\n * layout root: the content's Yoga size IS the slot size). */\n headerLeft?: () => ReactNode\n /** RN content packed at the end of the HeaderBar, before headerButtons. */\n headerRight?: () => ReactNode\n /** false disables the native back button, Escape and the back gesture\n * for this screen (the page's Adwaita can-pop). Programmatic goBack\n * still works. Also the mechanism behind usePreventRemove. */\n gestureEnabled?: boolean\n}\n\ntype StackDescriptor = {\n options: StackNavigationOptions\n render: () => ReactNode\n // react-navigation hands every descriptor its own route.\n route: { key: string; name: string }\n}\n\ntype StackNavigatorProps = {\n initialRouteName?: string\n screenOptions?: StackNavigationOptions\n children: ReactNode\n}\n\nconst StackNavigator = ({\n initialRouteName,\n screenOptions,\n children,\n}: StackNavigatorProps) => {\n const { state, descriptors, navigation, NavigationContent } =\n useNavigationBuilder<\n StackNavigationState<ParamListBase>,\n Record<string, unknown>,\n Record<string, () => void>,\n StackNavigationOptions,\n Record<string, unknown>\n >(StackRouter, {\n initialRouteName,\n screenOptions,\n children,\n })\n\n const viewRef = useRef<Adw.NavigationView | null>(null)\n\n useEffect(() => {\n for (const route of state.routes) {\n const descriptor = descriptors[route.key] as StackDescriptor | undefined\n if (descriptor) {\n warnIgnoredOptions(\n \"createStackNavigator\",\n descriptor.options,\n STACK_OPTION_KEYS,\n )\n }\n }\n }, [state, descriptors])\n\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n getActiveChrome() === \"system\"\n ) {\n console.warn(\n '[react-native-gtkx/navigation] the app runs with the default window chrome — pages bring their own HeaderBars, so you will see a doubled titlebar. Pass chrome: \"content\" to AppRegistry.runApplication.',\n )\n }\n }, [])\n // Mirror of the view's visible stack (route keys), maintained by the sync\n // effect and by the popped handler — the two never race: GTK signals run\n // synchronously inside the very push/pop calls the effect makes.\n const syncedRef = useRef<string[]>([])\n\n // Pop ANIMATION support: react-navigation drops a popped route from state\n // immediately, but Adwaita still animates the page out (~200 ms). Pages\n // therefore render from renderedKeys — live routes plus closing pages —\n // and a closing page leaves only on its \"hidden\" signal (the end of the\n // transition). Its content renders from a snapshot cached while live.\n const [renderedKeys, setRenderedKeys] = useState<string[]>(() =>\n state.routes.map((route) => route.key),\n )\n const liveKeys = state.routes.map((route) => route.key)\n const missingKeys = liveKeys.filter((key) => !renderedKeys.includes(key))\n if (missingKeys.length > 0) {\n // The sanctioned derive-state-during-render pattern: a new route must be\n // in renderedKeys within the same commit its page is pushed.\n setRenderedKeys([...renderedKeys, ...missingKeys])\n }\n // A state-held stable Map (not a ref): the render path reads it for\n // closing pages, and the react-hooks/refs rule rightly bans render-time\n // ref reads.\n const [snapshots] = useState(\n () =>\n new Map<\n string,\n { name: string; options: StackNavigationOptions; element: ReactNode }\n >(),\n )\n useEffect(() => {\n for (const route of state.routes) {\n const descriptor = descriptors[route.key] as StackDescriptor | undefined\n if (descriptor) {\n snapshots.set(route.key, {\n name: route.name,\n options: descriptor.options,\n element: <NestedRoot>{descriptor.render()}</NestedRoot>,\n })\n }\n }\n })\n\n const handleHidden = useCallback(\n (key: string): void => {\n // \"hidden\" also fires for a live page covered by a push — only pages\n // gone from state are actually closing.\n if (navigation.getState().routes.some((route) => route.key === key)) {\n return\n }\n snapshots.delete(key)\n setRenderedKeys((keys) => keys.filter((rendered) => rendered !== key))\n },\n [navigation, snapshots],\n )\n\n // \"hidden\" delivery is not guaranteed in every environment (headless\n // compositors with animations disabled never emit it) — a timer slightly\n // longer than the Adwaita transition is the fallback; handleHidden is\n // idempotent, whichever fires first wins.\n const scheduleRetainedRemoval = useCallback(\n (key: string): void => {\n setTimeout(() => handleHidden(key), 400)\n },\n [handleHidden],\n )\n\n // Bracket every view transition with an InteractionManager handle so\n // runAfterInteractions-scheduled work (a screen's own data load / heavy\n // render) waits for the slide to finish instead of stealing its frames.\n // The handle is opened when the sync effect mutates the view and cleared\n // one transition-length later; overlapping transitions reference-count.\n const beginTransition = useCallback((): void => {\n const handle = InteractionManager.createInteractionHandle()\n setTimeout(() => InteractionManager.clearInteractionHandle(handle), 400)\n }, [])\n\n useEffect(() => {\n const view = viewRef.current\n if (!view) {\n return\n }\n const target = state.routes.map((route) => route.key)\n if (syncedRef.current.length === 0 && target.length > 0) {\n // First mount: NavigationView shows the first added page by itself.\n syncedRef.current = target.slice(0, 1)\n }\n let common = 0\n while (\n common < syncedRef.current.length &&\n common < target.length &&\n syncedRef.current[common] === target[common]\n ) {\n common += 1\n }\n if (common === 0 && syncedRef.current[0] !== target[0]) {\n // The stack root changed (reset): swap the whole visible stack.\n const leaving = syncedRef.current.filter((key) => !target.includes(key))\n syncedRef.current = [...target]\n beginTransition()\n view.replaceWithTags(target)\n for (const key of leaving) {\n scheduleRetainedRemoval(key)\n }\n return\n }\n if (syncedRef.current.length > common) {\n const anchor = target[common - 1]\n const leaving = syncedRef.current.slice(common)\n syncedRef.current = syncedRef.current.slice(0, common)\n if (anchor !== undefined) {\n beginTransition()\n view.popToTag(anchor)\n }\n for (const key of leaving) {\n scheduleRetainedRemoval(key)\n }\n }\n for (\n let index = syncedRef.current.length;\n index < target.length;\n index += 1\n ) {\n const key = target[index]!\n syncedRef.current.push(key)\n beginTransition()\n view.pushByTag(key)\n }\n }, [state, scheduleRetainedRemoval, beginTransition])\n\n // `navigation` is identity-stable across renders (react-navigation\n // builder contract) and getState() always reads the live state — the\n // closure needs no ref indirection.\n const handlePopped = (page: Adw.NavigationPage | null): void => {\n const tag = page?.getTag()\n if (!tag) {\n return\n }\n syncedRef.current = syncedRef.current.filter((key) => key !== tag)\n const current = navigation.getState()\n if (current.routes.some((route) => route.key === tag)) {\n // The view popped on its own (back button / gesture): follow in state.\n navigation.dispatch(StackActions.pop())\n }\n scheduleRetainedRemoval(tag)\n }\n\n return (\n <NavigationContent>\n <StackView\n viewRef={viewRef}\n renderedKeys={renderedKeys}\n routeKeys={state.routes.map((route) => route.key)}\n descriptors={descriptors}\n snapshots={snapshots}\n onPopped={handlePopped}\n onHidden={handleHidden}\n />\n </NavigationContent>\n )\n}\n\ntype StackViewProps = {\n viewRef: RefObject<Adw.NavigationView | null>\n renderedKeys: string[]\n routeKeys: string[]\n descriptors: Record<string, unknown>\n snapshots: Map<\n string,\n { name: string; options: StackNavigationOptions; element: ReactNode }\n >\n onPopped: (page: Adw.NavigationPage | null) => void\n onHidden: (key: string) => void\n}\n\n// The page list lives inside NavigationContent so it can read the\n// prevent-remove context (which useNavigationBuilder only provides to\n// NavigationContent's children). A page whose route is prevented — or whose\n// screen set gestureEnabled: false — reports canPop: false, so Adwaita\n// disables the back button, Escape and the back gesture for it; a\n// programmatic goBack still pops (once the app lifts the prevention, e.g.\n// after its own confirmation dialog). This is why a native pop can never\n// race react-navigation state for these routes.\nconst StackView = ({\n viewRef,\n renderedKeys,\n routeKeys,\n descriptors,\n snapshots,\n onPopped,\n onHidden,\n}: StackViewProps) => {\n const { preventedRoutes } = usePreventRemoveContext()\n return (\n <AdwNavigationView\n ref={viewRef}\n onPopped={(page) => onPopped(page)}\n >\n {renderedKeys.map((key) => {\n const isLive = routeKeys.includes(key)\n const descriptor = isLive\n ? (descriptors[key] as StackDescriptor | undefined)\n : undefined\n const snapshot = snapshots.get(key)\n const options = descriptor?.options ?? snapshot?.options ?? {}\n const headerShown = options.headerShown ?? true\n const canPop =\n options.gestureEnabled !== false &&\n !preventedRoutes[key]?.preventRemove\n const content = descriptor ? (\n <NestedRoot>{descriptor.render()}</NestedRoot>\n ) : (\n (snapshot?.element ?? null)\n )\n return (\n <AdwNavigationPage\n key={key}\n tag={key}\n // Never the route key — that is an internal identifier, and\n // it would end up in the window title under content chrome.\n title={\n options.title ?? descriptor?.route.name ?? snapshot?.name ?? key\n }\n canPop={canPop}\n onHidden={() => onHidden(key)}\n >\n {headerShown ? (\n <AdwToolbarView\n topBar={\n <AdwHeaderBar\n start={\n options.headerLeft ? (\n <IntrinsicRoot>{options.headerLeft()}</IntrinsicRoot>\n ) : undefined\n }\n end={[\n ...(options.headerRight\n ? [\n <IntrinsicRoot key=\"header-right\">\n {options.headerRight()}\n </IntrinsicRoot>,\n ]\n : []),\n ...(options.headerButtons?.map((button) => (\n <GtkButton\n key={button.id}\n iconName={button.icon}\n tooltipText={button.tooltip}\n onClicked={button.onPress}\n />\n )) ?? []),\n ]}\n />\n }\n >\n {content}\n </AdwToolbarView>\n ) : (\n content\n )}\n </AdwNavigationPage>\n )\n })}\n </AdwNavigationView>\n )\n}\n\n// ---- typed factory --------------------------------------------------------\n// The upstream createNavigatorFactory returns `any` — mirroring the typed\n// wrapper pattern of native-stack gives Screen configs, options and screen\n// props real types without changing runtime behavior.\n\nexport type StackNavigationHelpers<\n ParamList extends ParamListBase = ParamListBase,\n RouteName extends keyof ParamList = keyof ParamList,\n> = NavigationProp<\n ParamList,\n RouteName,\n undefined,\n StackNavigationState<ParamList>,\n StackNavigationOptions\n>\n\nexport type StackScreenProps<\n ParamList extends ParamListBase = ParamListBase,\n RouteName extends keyof ParamList = keyof ParamList,\n> = {\n route: RouteProp<ParamList, RouteName>\n navigation: StackNavigationHelpers<ParamList, RouteName>\n}\n\nexport type StackScreenConfig<\n ParamList extends ParamListBase,\n RouteName extends keyof ParamList,\n> = {\n name: RouteName\n component: ComponentType<StackScreenProps<ParamList, RouteName>>\n options?:\n | StackNavigationOptions\n | ((\n props: StackScreenProps<ParamList, RouteName>,\n ) => StackNavigationOptions)\n initialParams?: Partial<ParamList[RouteName]>\n}\n\nexport type TypedStackNavigator<ParamList extends ParamListBase> = {\n Navigator: ComponentType<StackNavigatorProps>\n Screen: <RouteName extends keyof ParamList>(\n props: StackScreenConfig<ParamList, RouteName>,\n ) => null\n}\n\nconst stackFactory = createNavigatorFactory(StackNavigator)\n\nexport const createStackNavigator = <\n ParamList extends ParamListBase = ParamListBase,\n>(): TypedStackNavigator<ParamList> =>\n stackFactory() as TypedStackNavigator<ParamList>\n\nexport {\n createSidebarNavigator,\n type HeaderButton,\n type SidebarNavigationOptions,\n type SidebarScreenConfig,\n type SidebarScreenProps,\n type TypedSidebarNavigator,\n} from \"./sidebar\"\n\n// The rest of the react-navigation surface apps need, so a linux app can\n// import everything from one place.\nexport {\n CommonActions,\n NavigationContainer,\n StackActions,\n useFocusEffect,\n useIsFocused,\n useNavigation,\n useNavigationContainerRef,\n usePreventRemove,\n useRoute,\n} from \"@react-navigation/native\"\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const warnIgnoredOptions: (navigator: string, options: Record<string, unknown>, supported: ReadonlySet<string>) => void;
2
+ export declare const resetIgnoredOptionWarnings: () => void;
@@ -0,0 +1,61 @@
1
+ // Dev-mode guidance for silently ignored screen options: react-navigation's
2
+ // factory is untyped upstream, so neither TS nor the runtime would otherwise
3
+ // say a word when a ported app sets `presentation: "modal"` and nothing
4
+ // happens. Each unknown key warns ONCE per navigator kind with an honest
5
+ // verdict (see docs/research/navigation-extensibility.md).
6
+ const VERDICTS = {
7
+ header: "full header replacement is not supported yet",
8
+ headerTitle: "string titles map to `title`; a component title-widget is not " +
9
+ "exposed yet",
10
+ headerStyle: "Adwaita's theme owns the chrome styling on this platform",
11
+ headerTintColor: "Adwaita's theme owns the chrome styling on this platform",
12
+ headerTitleStyle: "Adwaita's theme owns the chrome styling on this platform",
13
+ headerTransparent: "planned on AdwToolbarView's top-bar-style",
14
+ headerShadowVisible: "planned on AdwToolbarView's top-bar-style",
15
+ headerSearchBarOptions: "planned on Gtk.SearchBar",
16
+ headerBackVisible: "planned on AdwNavigationPage's can-pop",
17
+ headerBackTitle: "the GNOME back button is icon-only by HIG",
18
+ headerBackButtonDisplayMode: "the GNOME back button is icon-only by HIG",
19
+ headerBackButtonMenuEnabled: "the Adwaita back button already shows a history menu natively",
20
+ headerLargeTitle: "no large-title idiom on this desktop",
21
+ headerBlurEffect: "no blur idiom on this desktop",
22
+ presentation: 'only "card" exists today; "modal" is planned on Adw.Dialog',
23
+ animation: 'transitions are Adwaita\'s own; "none" is planned',
24
+ animationDuration: "transitions are Adwaita's own",
25
+ animationTypeForReplace: "transitions are Adwaita's own",
26
+ gestureDirection: "the platform defines gesture directions",
27
+ fullScreenGestureEnabled: "the platform defines gestures",
28
+ statusBarStyle: "no status bar on desktop",
29
+ statusBarHidden: "no status bar on desktop",
30
+ statusBarAnimation: "no status bar on desktop",
31
+ navigationBarColor: "no Android navigation bar on desktop",
32
+ navigationBarHidden: "no Android navigation bar on desktop",
33
+ orientation: "window orientation is the compositor's business",
34
+ autoHideHomeIndicator: "no home indicator on desktop",
35
+ freezeOnBlur: "screens below the stack top already stay mounted",
36
+ contentStyle: "style a wrapper View inside the screen instead",
37
+ };
38
+ const warned = new Set();
39
+ export const warnIgnoredOptions = (navigator, options, supported) => {
40
+ if (process.env.NODE_ENV === "production") {
41
+ return;
42
+ }
43
+ for (const key of Object.keys(options)) {
44
+ if (supported.has(key)) {
45
+ continue;
46
+ }
47
+ const dedupe = `${navigator}:${key}`;
48
+ if (warned.has(dedupe)) {
49
+ continue;
50
+ }
51
+ warned.add(dedupe);
52
+ const verdict = VERDICTS[key] ?? "not supported by this navigator";
53
+ console.warn(`[react-native-gtkx/navigation] ${navigator} ignores the screen ` +
54
+ `option "${key}" — ${verdict}.`);
55
+ }
56
+ };
57
+ // Test hook: warnings are once-per-process by design.
58
+ export const resetIgnoredOptionWarnings = () => {
59
+ warned.clear();
60
+ };
61
+ //# sourceMappingURL=option-warnings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"option-warnings.js","sourceRoot":"","sources":["../../src/navigation/option-warnings.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,wEAAwE;AACxE,yEAAyE;AACzE,2DAA2D;AAC3D,MAAM,QAAQ,GAA2B;IACvC,MAAM,EAAE,8CAA8C;IACtD,WAAW,EACT,gEAAgE;QAChE,aAAa;IACf,WAAW,EAAE,0DAA0D;IACvE,eAAe,EAAE,0DAA0D;IAC3E,gBAAgB,EAAE,0DAA0D;IAC5E,iBAAiB,EAAE,2CAA2C;IAC9D,mBAAmB,EAAE,2CAA2C;IAChE,sBAAsB,EAAE,0BAA0B;IAClD,iBAAiB,EAAE,wCAAwC;IAC3D,eAAe,EAAE,2CAA2C;IAC5D,2BAA2B,EAAE,2CAA2C;IACxE,2BAA2B,EACzB,+DAA+D;IACjE,gBAAgB,EAAE,sCAAsC;IACxD,gBAAgB,EAAE,+BAA+B;IACjD,YAAY,EAAE,4DAA4D;IAC1E,SAAS,EAAE,mDAAmD;IAC9D,iBAAiB,EAAE,+BAA+B;IAClD,uBAAuB,EAAE,+BAA+B;IACxD,gBAAgB,EAAE,yCAAyC;IAC3D,wBAAwB,EAAE,+BAA+B;IACzD,cAAc,EAAE,0BAA0B;IAC1C,eAAe,EAAE,0BAA0B;IAC3C,kBAAkB,EAAE,0BAA0B;IAC9C,kBAAkB,EAAE,sCAAsC;IAC1D,mBAAmB,EAAE,sCAAsC;IAC3D,WAAW,EAAE,iDAAiD;IAC9D,qBAAqB,EAAE,8BAA8B;IACrD,YAAY,EAAE,kDAAkD;IAChE,YAAY,EAAE,gDAAgD;CAC/D,CAAA;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;AAEhC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,SAAiB,EACjB,OAAgC,EAChC,SAA8B,EACxB,EAAE;IACR,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC1C,OAAM;IACR,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,SAAQ;QACV,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,SAAS,IAAI,GAAG,EAAE,CAAA;QACpC,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,SAAQ;QACV,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAClB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,iCAAiC,CAAA;QAClE,OAAO,CAAC,IAAI,CACV,kCAAkC,SAAS,sBAAsB;YAC/D,WAAW,GAAG,OAAO,OAAO,GAAG,CAClC,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,sDAAsD;AACtD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAS,EAAE;IACnD,MAAM,CAAC,KAAK,EAAE,CAAA;AAChB,CAAC,CAAA","sourcesContent":["// Dev-mode guidance for silently ignored screen options: react-navigation's\n// factory is untyped upstream, so neither TS nor the runtime would otherwise\n// say a word when a ported app sets `presentation: \"modal\"` and nothing\n// happens. Each unknown key warns ONCE per navigator kind with an honest\n// verdict (see docs/research/navigation-extensibility.md).\nconst VERDICTS: Record<string, string> = {\n header: \"full header replacement is not supported yet\",\n headerTitle:\n \"string titles map to `title`; a component title-widget is not \" +\n \"exposed yet\",\n headerStyle: \"Adwaita's theme owns the chrome styling on this platform\",\n headerTintColor: \"Adwaita's theme owns the chrome styling on this platform\",\n headerTitleStyle: \"Adwaita's theme owns the chrome styling on this platform\",\n headerTransparent: \"planned on AdwToolbarView's top-bar-style\",\n headerShadowVisible: \"planned on AdwToolbarView's top-bar-style\",\n headerSearchBarOptions: \"planned on Gtk.SearchBar\",\n headerBackVisible: \"planned on AdwNavigationPage's can-pop\",\n headerBackTitle: \"the GNOME back button is icon-only by HIG\",\n headerBackButtonDisplayMode: \"the GNOME back button is icon-only by HIG\",\n headerBackButtonMenuEnabled:\n \"the Adwaita back button already shows a history menu natively\",\n headerLargeTitle: \"no large-title idiom on this desktop\",\n headerBlurEffect: \"no blur idiom on this desktop\",\n presentation: 'only \"card\" exists today; \"modal\" is planned on Adw.Dialog',\n animation: 'transitions are Adwaita\\'s own; \"none\" is planned',\n animationDuration: \"transitions are Adwaita's own\",\n animationTypeForReplace: \"transitions are Adwaita's own\",\n gestureDirection: \"the platform defines gesture directions\",\n fullScreenGestureEnabled: \"the platform defines gestures\",\n statusBarStyle: \"no status bar on desktop\",\n statusBarHidden: \"no status bar on desktop\",\n statusBarAnimation: \"no status bar on desktop\",\n navigationBarColor: \"no Android navigation bar on desktop\",\n navigationBarHidden: \"no Android navigation bar on desktop\",\n orientation: \"window orientation is the compositor's business\",\n autoHideHomeIndicator: \"no home indicator on desktop\",\n freezeOnBlur: \"screens below the stack top already stay mounted\",\n contentStyle: \"style a wrapper View inside the screen instead\",\n}\n\nconst warned = new Set<string>()\n\nexport const warnIgnoredOptions = (\n navigator: string,\n options: Record<string, unknown>,\n supported: ReadonlySet<string>,\n): void => {\n if (process.env.NODE_ENV === \"production\") {\n return\n }\n for (const key of Object.keys(options)) {\n if (supported.has(key)) {\n continue\n }\n const dedupe = `${navigator}:${key}`\n if (warned.has(dedupe)) {\n continue\n }\n warned.add(dedupe)\n const verdict = VERDICTS[key] ?? \"not supported by this navigator\"\n console.warn(\n `[react-native-gtkx/navigation] ${navigator} ignores the screen ` +\n `option \"${key}\" — ${verdict}.`,\n )\n }\n}\n\n// Test hook: warnings are once-per-process by design.\nexport const resetIgnoredOptionWarnings = (): void => {\n warned.clear()\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import { type NavigationProp, type ParamListBase, type RouteProp, type TabNavigationState } from "@react-navigation/native";
2
+ import { type ComponentType, type ReactNode } from "react";
3
+ export type SidebarNavigationOptions = {
4
+ /** Sidebar row and content HeaderBar title; defaults to the route name. */
5
+ title?: string;
6
+ };
7
+ export type HeaderButton = {
8
+ id: string;
9
+ icon: string;
10
+ tooltip?: string;
11
+ onPress: () => void;
12
+ };
13
+ type SidebarNavigatorProps = {
14
+ initialRouteName?: string;
15
+ screenOptions?: SidebarNavigationOptions;
16
+ /** Title of the sidebar pane's HeaderBar. */
17
+ sidebarTitle?: string;
18
+ /** Buttons packed at the end of the content HeaderBar. */
19
+ headerButtons?: HeaderButton[];
20
+ children: ReactNode;
21
+ };
22
+ export type SidebarScreenProps<ParamList extends ParamListBase = ParamListBase, RouteName extends keyof ParamList = keyof ParamList> = {
23
+ route: RouteProp<ParamList, RouteName>;
24
+ navigation: NavigationProp<ParamList, RouteName, undefined, TabNavigationState<ParamList>, SidebarNavigationOptions>;
25
+ };
26
+ export type SidebarScreenConfig<ParamList extends ParamListBase, RouteName extends keyof ParamList> = {
27
+ name: RouteName;
28
+ component: ComponentType<SidebarScreenProps<ParamList, RouteName>>;
29
+ options?: SidebarNavigationOptions;
30
+ initialParams?: Partial<ParamList[RouteName]>;
31
+ };
32
+ export type TypedSidebarNavigator<ParamList extends ParamListBase> = {
33
+ Navigator: ComponentType<SidebarNavigatorProps>;
34
+ Screen: <RouteName extends keyof ParamList>(props: SidebarScreenConfig<ParamList, RouteName>) => null;
35
+ };
36
+ export declare const createSidebarNavigator: <ParamList extends ParamListBase = ParamListBase>() => TypedSidebarNavigator<ParamList>;
37
+ export {};
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // createSidebarNavigator — the desktop equivalent of a drawer navigator on
3
+ // Adw.NavigationSplitView: a persistent native sidebar (GtkListBox with the
4
+ // Adwaita navigation-sidebar styling) selects between parallel screens.
5
+ // TabRouter fits these semantics (one focused screen out of a set, no
6
+ // stack); the name follows the desktop mental model rather than "drawer" —
7
+ // nothing slides over content on this platform.
8
+ //
9
+ // Selection sync mirrors the stack navigator's protocol: the native
10
+ // row-selected signal dispatches jumpTo only when it disagrees with state,
11
+ // and an effect re-selects the row when state changes programmatically —
12
+ // the guard on both sides breaks the echo loop.
13
+ import { createNavigatorFactory, TabActions, TabRouter, useNavigationBuilder, } from "@react-navigation/native";
14
+ import { useEffect, useRef } from "react";
15
+ import { getActiveChrome } from "../components/app-registry";
16
+ import { NestedRoot } from "../components/root";
17
+ import { AdwHeaderBar, AdwNavigationPage, AdwNavigationSplitView, AdwToolbarView, Gtk, GtkButton, GtkLabel, GtkListBox, GtkListBoxRow, GtkScrolledWindow, } from "../gtkx/bridge/index";
18
+ import { warnIgnoredOptions } from "./option-warnings";
19
+ const SIDEBAR_OPTION_KEYS = new Set(["title"]);
20
+ const SidebarNavigator = ({ initialRouteName, screenOptions, sidebarTitle = "Sidebar", headerButtons, children, }) => {
21
+ const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder(TabRouter, {
22
+ initialRouteName,
23
+ screenOptions,
24
+ children,
25
+ });
26
+ const listRef = useRef(null);
27
+ useEffect(() => {
28
+ for (const route of state.routes) {
29
+ const descriptor = descriptors[route.key];
30
+ if (descriptor) {
31
+ warnIgnoredOptions("createSidebarNavigator", descriptor.options, SIDEBAR_OPTION_KEYS);
32
+ }
33
+ }
34
+ }, [state, descriptors]);
35
+ useEffect(() => {
36
+ if (process.env.NODE_ENV !== "production" &&
37
+ getActiveChrome() === "system") {
38
+ console.warn('[react-native-gtkx/navigation] the app runs with the default window chrome — the split view brings its own HeaderBars, so you will see a doubled titlebar. Pass chrome: "content" to AppRegistry.runApplication.');
39
+ }
40
+ }, []);
41
+ // State → native selection (initial mount and programmatic navigation).
42
+ useEffect(() => {
43
+ const list = listRef.current;
44
+ if (!list) {
45
+ return;
46
+ }
47
+ const row = list.getRowAtIndex(state.index);
48
+ if (row && list.getSelectedRow() !== row) {
49
+ list.selectRow(row);
50
+ }
51
+ }, [state.index]);
52
+ const active = state.routes[state.index];
53
+ const activeDescriptor = descriptors[active.key];
54
+ const titleOf = (routeKey, fallback) => descriptors[routeKey]?.options.title ??
55
+ fallback;
56
+ return (_jsx(NavigationContent, { children: _jsx(AdwNavigationSplitView, { sidebar: _jsx(AdwNavigationPage, { title: sidebarTitle, tag: "sidebar", children: _jsx(AdwToolbarView, { topBar: _jsx(AdwHeaderBar, {}), children: _jsx(GtkScrolledWindow, { hscrollbarPolicy: Gtk.PolicyType.NEVER, propagateNaturalWidth: true, children: _jsx(GtkListBox, { ref: listRef, cssClasses: ["navigation-sidebar"], onRowSelected: (row) => {
57
+ if (!row) {
58
+ return;
59
+ }
60
+ const route = state.routes[row.getIndex()];
61
+ if (route && route.key !== active.key) {
62
+ navigation.dispatch({
63
+ ...TabActions.jumpTo(route.name),
64
+ target: state.key,
65
+ });
66
+ }
67
+ }, children: state.routes.map((route) => (_jsx(GtkListBoxRow, { children: _jsx(GtkLabel, { label: titleOf(route.key, route.name), xalign: 0, marginTop: 8, marginBottom: 8, marginStart: 6, marginEnd: 6 }) }, route.key))) }) }) }) }), children: _jsx(AdwNavigationPage, { title: titleOf(active.key, active.name), tag: "content", children: _jsx(AdwToolbarView, { topBar: _jsx(AdwHeaderBar, { end: headerButtons?.map((button) => (_jsx(GtkButton, { iconName: button.icon, tooltipText: button.tooltip, onClicked: button.onPress }, button.id))) }), children: _jsx(NestedRoot, { children: activeDescriptor?.render() }, active.key) }) }) }) }));
68
+ };
69
+ const sidebarFactory = createNavigatorFactory(SidebarNavigator);
70
+ export const createSidebarNavigator = () => sidebarFactory();
71
+ //# sourceMappingURL=sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../src/navigation/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["// createSidebarNavigator — the desktop equivalent of a drawer navigator on\n// Adw.NavigationSplitView: a persistent native sidebar (GtkListBox with the\n// Adwaita navigation-sidebar styling) selects between parallel screens.\n// TabRouter fits these semantics (one focused screen out of a set, no\n// stack); the name follows the desktop mental model rather than \"drawer\" —\n// nothing slides over content on this platform.\n//\n// Selection sync mirrors the stack navigator's protocol: the native\n// row-selected signal dispatches jumpTo only when it disagrees with state,\n// and an effect re-selects the row when state changes programmatically —\n// the guard on both sides breaks the echo loop.\nimport {\n createNavigatorFactory,\n TabActions,\n TabRouter,\n useNavigationBuilder,\n type NavigationProp,\n type ParamListBase,\n type RouteProp,\n type TabNavigationState,\n} from \"@react-navigation/native\"\nimport { useEffect, useRef, type ComponentType, type ReactNode } from \"react\"\nimport { getActiveChrome } from \"../components/app-registry\"\nimport { NestedRoot } from \"../components/root\"\nimport {\n AdwHeaderBar,\n AdwNavigationPage,\n AdwNavigationSplitView,\n AdwToolbarView,\n Gtk,\n GtkButton,\n GtkLabel,\n GtkListBox,\n GtkListBoxRow,\n GtkScrolledWindow,\n} from \"../gtkx/bridge/index\"\nimport { warnIgnoredOptions } from \"./option-warnings\"\n\nconst SIDEBAR_OPTION_KEYS: ReadonlySet<string> = new Set([\"title\"])\n\nexport type SidebarNavigationOptions = {\n /** Sidebar row and content HeaderBar title; defaults to the route name. */\n title?: string\n}\n\n// A declarative HeaderBar button: the RN-facing API stays GTK-free — the\n// navigator renders the native button itself. `icon` is an Adwaita symbolic\n// icon name (e.g. \"weather-clear-night-symbolic\").\nexport type HeaderButton = {\n id: string\n icon: string\n tooltip?: string\n onPress: () => void\n}\n\ntype SidebarDescriptor = {\n options: SidebarNavigationOptions\n render: () => ReactNode\n}\n\ntype SidebarNavigatorProps = {\n initialRouteName?: string\n screenOptions?: SidebarNavigationOptions\n /** Title of the sidebar pane's HeaderBar. */\n sidebarTitle?: string\n /** Buttons packed at the end of the content HeaderBar. */\n headerButtons?: HeaderButton[]\n children: ReactNode\n}\n\nconst SidebarNavigator = ({\n initialRouteName,\n screenOptions,\n sidebarTitle = \"Sidebar\",\n headerButtons,\n children,\n}: SidebarNavigatorProps) => {\n const { state, descriptors, navigation, NavigationContent } =\n useNavigationBuilder<\n TabNavigationState<ParamListBase>,\n Record<string, unknown>,\n Record<string, () => void>,\n SidebarNavigationOptions,\n Record<string, unknown>\n >(TabRouter, {\n initialRouteName,\n screenOptions,\n children,\n })\n\n const listRef = useRef<Gtk.ListBox | null>(null)\n\n useEffect(() => {\n for (const route of state.routes) {\n const descriptor = descriptors[route.key] as SidebarDescriptor | undefined\n if (descriptor) {\n warnIgnoredOptions(\n \"createSidebarNavigator\",\n descriptor.options,\n SIDEBAR_OPTION_KEYS,\n )\n }\n }\n }, [state, descriptors])\n\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n getActiveChrome() === \"system\"\n ) {\n console.warn(\n '[react-native-gtkx/navigation] the app runs with the default window chrome — the split view brings its own HeaderBars, so you will see a doubled titlebar. Pass chrome: \"content\" to AppRegistry.runApplication.',\n )\n }\n }, [])\n\n // State → native selection (initial mount and programmatic navigation).\n useEffect(() => {\n const list = listRef.current\n if (!list) {\n return\n }\n const row = list.getRowAtIndex(state.index)\n if (row && list.getSelectedRow() !== row) {\n list.selectRow(row)\n }\n }, [state.index])\n\n const active = state.routes[state.index]!\n const activeDescriptor = descriptors[active.key] as\n SidebarDescriptor | undefined\n const titleOf = (routeKey: string, fallback: string): string =>\n (descriptors[routeKey] as SidebarDescriptor | undefined)?.options.title ??\n fallback\n\n return (\n <NavigationContent>\n <AdwNavigationSplitView\n sidebar={\n <AdwNavigationPage\n title={sidebarTitle}\n tag=\"sidebar\"\n >\n <AdwToolbarView topBar={<AdwHeaderBar />}>\n {/* The list must not dictate the window minimum: the sidebar\n scrolls when the window is shorter than its rows — the\n Adwaita sidebar pattern (and RN semantics: scrolling is\n explicit, never the window's). */}\n <GtkScrolledWindow\n hscrollbarPolicy={Gtk.PolicyType.NEVER}\n propagateNaturalWidth\n >\n <GtkListBox\n ref={listRef}\n cssClasses={[\"navigation-sidebar\"]}\n onRowSelected={(row) => {\n if (!row) {\n return\n }\n const route = state.routes[row.getIndex()]\n if (route && route.key !== active.key) {\n navigation.dispatch({\n ...TabActions.jumpTo(route.name),\n target: state.key,\n })\n }\n }}\n >\n {state.routes.map((route) => (\n <GtkListBoxRow key={route.key}>\n <GtkLabel\n label={titleOf(route.key, route.name)}\n xalign={0}\n marginTop={8}\n marginBottom={8}\n marginStart={6}\n marginEnd={6}\n />\n </GtkListBoxRow>\n ))}\n </GtkListBox>\n </GtkScrolledWindow>\n </AdwToolbarView>\n </AdwNavigationPage>\n }\n >\n <AdwNavigationPage\n title={titleOf(active.key, active.name)}\n tag=\"content\"\n >\n <AdwToolbarView\n topBar={\n <AdwHeaderBar\n end={headerButtons?.map((button) => (\n <GtkButton\n key={button.id}\n iconName={button.icon}\n tooltipText={button.tooltip}\n onClicked={button.onPress}\n />\n ))}\n />\n }\n >\n {/* Keyed by route: switching sections swaps the whole screen —\n a fresh NestedRoot per section, the previous one disposes. */}\n <NestedRoot key={active.key}>\n {activeDescriptor?.render()}\n </NestedRoot>\n </AdwToolbarView>\n </AdwNavigationPage>\n </AdwNavigationSplitView>\n </NavigationContent>\n )\n}\n\nexport type SidebarScreenProps<\n ParamList extends ParamListBase = ParamListBase,\n RouteName extends keyof ParamList = keyof ParamList,\n> = {\n route: RouteProp<ParamList, RouteName>\n navigation: NavigationProp<\n ParamList,\n RouteName,\n undefined,\n TabNavigationState<ParamList>,\n SidebarNavigationOptions\n >\n}\n\nexport type SidebarScreenConfig<\n ParamList extends ParamListBase,\n RouteName extends keyof ParamList,\n> = {\n name: RouteName\n component: ComponentType<SidebarScreenProps<ParamList, RouteName>>\n options?: SidebarNavigationOptions\n initialParams?: Partial<ParamList[RouteName]>\n}\n\nexport type TypedSidebarNavigator<ParamList extends ParamListBase> = {\n Navigator: ComponentType<SidebarNavigatorProps>\n Screen: <RouteName extends keyof ParamList>(\n props: SidebarScreenConfig<ParamList, RouteName>,\n ) => null\n}\n\nconst sidebarFactory = createNavigatorFactory(SidebarNavigator)\n\nexport const createSidebarNavigator = <\n ParamList extends ParamListBase = ParamListBase,\n>(): TypedSidebarNavigator<ParamList> =>\n sidebarFactory() as TypedSidebarNavigator<ParamList>\n"]}
package/dist/perf.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export declare const perfEnabled: boolean;
2
+ export declare const perfCount: (name: string, n?: number) => void;
3
+ export declare const perfNow: () => number;
4
+ export declare const perfAddTime: (name: string, ms: number) => void;
5
+ export declare const perfBurst: (name: string, value?: number) => void;
6
+ export declare const perfGauge: (name: string, value: number) => void;
7
+ export declare const perfFrameTick: (now: number) => void;
8
+ export declare const perfMark: (label: string) => void;
9
+ export declare const ensurePerfReporter: () => void;