@natsuneko-laboratory/react-native-desktop-navigation 0.1.0-alpha.12

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 (103) hide show
  1. package/DesktopNavigation.podspec +18 -0
  2. package/LICENSE +21 -0
  3. package/NATIVE.md +300 -0
  4. package/README.md +127 -0
  5. package/dist/core/NavigationContainer.d.ts +23 -0
  6. package/dist/core/NavigationContainer.js +68 -0
  7. package/dist/core/NavigationItem.d.ts +27 -0
  8. package/dist/core/NavigationItem.js +71 -0
  9. package/dist/core/Scene.d.ts +19 -0
  10. package/dist/core/Scene.js +124 -0
  11. package/dist/core/SelectionNavigator.d.ts +38 -0
  12. package/dist/core/SelectionNavigator.js +194 -0
  13. package/dist/core/builder.d.ts +40 -0
  14. package/dist/core/builder.js +81 -0
  15. package/dist/core/context.d.ts +26 -0
  16. package/dist/core/context.js +36 -0
  17. package/dist/core/focus.d.ts +14 -0
  18. package/dist/core/focus.js +39 -0
  19. package/dist/core/hooks.d.ts +8 -0
  20. package/dist/core/hooks.js +48 -0
  21. package/dist/core/navigation.d.ts +6 -0
  22. package/dist/core/navigation.js +57 -0
  23. package/dist/core/store.d.ts +39 -0
  24. package/dist/core/store.js +391 -0
  25. package/dist/core/types.d.ts +155 -0
  26. package/dist/core/types.js +2 -0
  27. package/dist/index.d.ts +15 -0
  28. package/dist/index.js +46 -0
  29. package/dist/native/host.d.ts +100 -0
  30. package/dist/native/host.js +190 -0
  31. package/dist/native/icons.d.ts +56 -0
  32. package/dist/native/icons.js +55 -0
  33. package/dist/native/index.d.ts +13 -0
  34. package/dist/native/index.js +44 -0
  35. package/dist/native/sidebar.d.ts +39 -0
  36. package/dist/native/sidebar.js +126 -0
  37. package/dist/native/specs/DesktopNavigationHostNativeComponent.d.ts +10 -0
  38. package/dist/native/specs/DesktopNavigationHostNativeComponent.js +4 -0
  39. package/dist/native/split.d.ts +27 -0
  40. package/dist/native/split.js +105 -0
  41. package/dist/native/stack.d.ts +26 -0
  42. package/dist/native/stack.js +66 -0
  43. package/dist/platform/index.d.ts +52 -0
  44. package/dist/platform/index.js +28 -0
  45. package/dist/platform/macos.d.ts +2 -0
  46. package/dist/platform/macos.js +18 -0
  47. package/dist/platform/windows.d.ts +2 -0
  48. package/dist/platform/windows.js +18 -0
  49. package/dist/routers/index.d.ts +23 -0
  50. package/dist/routers/index.js +393 -0
  51. package/dist/routers/types.d.ts +116 -0
  52. package/dist/routers/types.js +2 -0
  53. package/dist/sidebar/index.d.ts +17 -0
  54. package/dist/sidebar/index.js +18 -0
  55. package/dist/split/index.d.ts +42 -0
  56. package/dist/split/index.js +199 -0
  57. package/dist/stack/index.d.ts +23 -0
  58. package/dist/stack/index.js +88 -0
  59. package/dist/tabs/index.d.ts +13 -0
  60. package/dist/tabs/index.js +15 -0
  61. package/docs/GUIDE.md +384 -0
  62. package/macos/DDNNavigationComponentView.h +4 -0
  63. package/macos/DDNNavigationComponentView.mm +40 -0
  64. package/macos/DDNNavigationView.swift +398 -0
  65. package/macos/DDNNavigationViewManager.mm +11 -0
  66. package/package.json +82 -0
  67. package/react-native.config.js +19 -0
  68. package/src/core/NavigationContainer.tsx +137 -0
  69. package/src/core/NavigationItem.tsx +149 -0
  70. package/src/core/Scene.tsx +194 -0
  71. package/src/core/SelectionNavigator.tsx +371 -0
  72. package/src/core/builder.tsx +147 -0
  73. package/src/core/context.tsx +43 -0
  74. package/src/core/focus.tsx +50 -0
  75. package/src/core/hooks.ts +42 -0
  76. package/src/core/navigation.ts +77 -0
  77. package/src/core/store.ts +471 -0
  78. package/src/core/types.ts +176 -0
  79. package/src/index.ts +36 -0
  80. package/src/native/host.tsx +377 -0
  81. package/src/native/icons.ts +110 -0
  82. package/src/native/index.ts +56 -0
  83. package/src/native/sidebar.tsx +230 -0
  84. package/src/native/specs/DesktopNavigationHostNativeComponent.ts +12 -0
  85. package/src/native/split.tsx +160 -0
  86. package/src/native/stack.tsx +157 -0
  87. package/src/platform/index.tsx +76 -0
  88. package/src/platform/macos.ts +15 -0
  89. package/src/platform/windows.ts +15 -0
  90. package/src/routers/index.ts +442 -0
  91. package/src/routers/types.ts +117 -0
  92. package/src/sidebar/index.tsx +42 -0
  93. package/src/split/index.tsx +350 -0
  94. package/src/stack/index.tsx +213 -0
  95. package/src/tabs/index.tsx +40 -0
  96. package/windows/DesktopNavigation/DesktopNavigation.def +3 -0
  97. package/windows/DesktopNavigation/DesktopNavigation.vcxproj +125 -0
  98. package/windows/DesktopNavigation/NavigationHost.cpp +432 -0
  99. package/windows/DesktopNavigation/ReactPackageProvider.cpp +9 -0
  100. package/windows/DesktopNavigation/ReactPackageProvider.h +10 -0
  101. package/windows/DesktopNavigation/ReactPackageProvider.idl +6 -0
  102. package/windows/DesktopNavigation/pch.cpp +1 -0
  103. package/windows/DesktopNavigation/pch.h +27 -0
@@ -0,0 +1,155 @@
1
+ import type React from 'react';
2
+ import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
3
+ import type { ParamListBase, RouteName, RouteArgs, NavigationRoute, NavigationState, NavigationAction, RootNavigationState } from '../routers';
4
+ import type { NavigationEvent, NavigationEventType } from './store';
5
+ export interface Navigation<P extends ParamListBase = ParamListBase> {
6
+ navigate(...args: RouteArgs<P>): void;
7
+ goBack(): void;
8
+ goForward(): void;
9
+ canGoBack(): boolean;
10
+ canGoForward(): boolean;
11
+ dispatch(action: NavigationAction): void;
12
+ getState(): NavigationState;
13
+ getParent(): Navigation | undefined;
14
+ isFocused(): boolean;
15
+ addListener(type: NavigationEventType, listener: (event: NavigationEvent) => void): () => void;
16
+ }
17
+ export interface StackNavigation<P extends ParamListBase = ParamListBase> extends Navigation<P> {
18
+ push(...args: RouteArgs<P>): void;
19
+ replace(...args: RouteArgs<P>): void;
20
+ pop(count?: number): void;
21
+ popTo(...args: RouteArgs<P>): void;
22
+ popToRoot(): void;
23
+ }
24
+ export interface SidebarNavigation<P extends ParamListBase = ParamListBase> extends Navigation<P> {
25
+ select(name: RouteName<P>): void;
26
+ collapseSidebar(): void;
27
+ expandSidebar(): void;
28
+ toggleSidebar(): void;
29
+ }
30
+ export interface TabNavigation<P extends ParamListBase = ParamListBase> extends Navigation<P> {
31
+ select(name: RouteName<P>): void;
32
+ nextTab(): void;
33
+ previousTab(): void;
34
+ }
35
+ export type StackScreenProps<P extends ParamListBase, N extends RouteName<P>> = {
36
+ navigation: StackNavigation<P>;
37
+ route: NavigationRoute<N, P[N]>;
38
+ };
39
+ export type SidebarScreenProps<P extends ParamListBase, N extends RouteName<P>> = {
40
+ navigation: SidebarNavigation<P>;
41
+ route: NavigationRoute<N, P[N]>;
42
+ };
43
+ export type TabScreenProps<P extends ParamListBase, N extends RouteName<P>> = {
44
+ navigation: TabNavigation<P>;
45
+ route: NavigationRoute<N, P[N]>;
46
+ };
47
+ export type FocusBehavior = 'none' | 'first' | 'restore';
48
+ export interface CommonScreenOptions {
49
+ title?: string;
50
+ focusBehavior?: FocusBehavior;
51
+ inactiveBehavior?: 'keep' | 'unmount';
52
+ contentStyle?: StyleProp<ViewStyle>;
53
+ sceneStyle?: StyleProp<ViewStyle>;
54
+ }
55
+ export interface HeaderActionProps {
56
+ navigation: StackNavigation;
57
+ route: NavigationRoute;
58
+ canGoBack: boolean;
59
+ }
60
+ export interface HeaderProps extends HeaderActionProps {
61
+ options: StackScreenOptions;
62
+ }
63
+ export type StackAnimation = 'default' | 'none' | 'fade' | 'slide-horizontal' | 'slide-vertical';
64
+ export interface StackScreenOptions extends CommonScreenOptions {
65
+ headerShown?: boolean;
66
+ headerStyle?: StyleProp<ViewStyle>;
67
+ headerTitleStyle?: StyleProp<TextStyle>;
68
+ headerLeftContainerStyle?: StyleProp<ViewStyle>;
69
+ headerRightContainerStyle?: StyleProp<ViewStyle>;
70
+ headerBackButtonStyle?: StyleProp<ViewStyle>;
71
+ headerBackTitleStyle?: StyleProp<TextStyle>;
72
+ headerBackTitle?: string;
73
+ headerTintColor?: string;
74
+ overlayStyle?: StyleProp<ViewStyle>;
75
+ dialogStyle?: StyleProp<ViewStyle>;
76
+ header?: React.ReactNode | ((props: HeaderProps) => React.ReactNode);
77
+ headerLeft?: React.ReactNode | ((props: HeaderActionProps) => React.ReactNode);
78
+ headerRight?: React.ReactNode | ((props: HeaderActionProps) => React.ReactNode);
79
+ animation?: StackAnimation;
80
+ presentation?: 'card' | 'modal' | 'dialog';
81
+ }
82
+ export interface NavigationItemState {
83
+ selected: boolean;
84
+ /** Keyboard/native focus, independent of selection. */
85
+ focused: boolean;
86
+ pressed: boolean;
87
+ hovered: boolean;
88
+ disabled: boolean;
89
+ collapsed: boolean;
90
+ }
91
+ export type NavigationItemStyle<T extends ViewStyle | TextStyle> = StyleProp<T> | ((state: NavigationItemState) => StyleProp<T>);
92
+ export interface NavigationItemContentProps extends NavigationItemState {
93
+ route: NavigationRoute;
94
+ label: string;
95
+ /** Default icon, label, and badge, with their configured styles. */
96
+ children: React.ReactNode;
97
+ }
98
+ export interface SidebarFooterProps {
99
+ collapsed: boolean;
100
+ collapseSidebar(): void;
101
+ expandSidebar(): void;
102
+ toggleSidebar(): void;
103
+ /** The complete default collapse button, or null when it is hidden. */
104
+ children: React.ReactNode;
105
+ }
106
+ export interface SidebarSectionOptions {
107
+ title?: string;
108
+ style?: StyleProp<ViewStyle>;
109
+ titleStyle?: StyleProp<TextStyle>;
110
+ renderTitle?: (props: {
111
+ title: string;
112
+ }) => React.ReactNode;
113
+ }
114
+ export interface SidebarScreenOptions extends CommonScreenOptions {
115
+ itemStyle?: NavigationItemStyle<ViewStyle>;
116
+ labelStyle?: NavigationItemStyle<TextStyle>;
117
+ iconContainerStyle?: NavigationItemStyle<ViewStyle>;
118
+ badgeStyle?: NavigationItemStyle<TextStyle>;
119
+ renderItemContent?: (props: NavigationItemContentProps) => React.ReactNode;
120
+ label?: string;
121
+ icon?: React.ReactNode | ((props: {
122
+ focused: boolean;
123
+ disabled: boolean;
124
+ }) => React.ReactNode);
125
+ badge?: React.ReactNode;
126
+ hidden?: boolean;
127
+ disabled?: boolean;
128
+ }
129
+ export interface TabScreenOptions extends SidebarScreenOptions {
130
+ }
131
+ export interface NavigationTheme {
132
+ dark: boolean;
133
+ colors: {
134
+ background: string;
135
+ surface: string;
136
+ text: string;
137
+ mutedText: string;
138
+ border: string;
139
+ accent: string;
140
+ selectedBackground: string;
141
+ };
142
+ }
143
+ export interface NavigationRef<P extends ParamListBase> {
144
+ current: NavigationRefHandle<P> | null;
145
+ isReady(): boolean;
146
+ navigate(...args: RouteArgs<P>): void;
147
+ goBack(): void;
148
+ goForward(): void;
149
+ canGoBack(): boolean;
150
+ canGoForward(): boolean;
151
+ dispatch(action: NavigationAction): void;
152
+ getRootState(): RootNavigationState | undefined;
153
+ }
154
+ export interface NavigationRefHandle<P extends ParamListBase> extends Omit<NavigationRef<P>, 'current'> {
155
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,15 @@
1
+ export { NavigationContainer, type NavigationContainerProps, type KeyboardShortcutOptions, } from './core/NavigationContainer';
2
+ export { createNavigationRef } from './core/navigation';
3
+ export { useNavigation, useRoute, useNavigationState, useFocusEffect, useIsFocused, } from './core/hooks';
4
+ export { NavigationFocusable, type FocusTarget } from './core/focus';
5
+ export { DefaultTheme, useNavigationTheme } from './core/context';
6
+ export { NavigationStore, serializeNavigationState, restoreNavigationState, type NavigationEvent, type NavigationEventType, } from './core/store';
7
+ export * from './core/types';
8
+ export * from './routers';
9
+ export * from './stack';
10
+ export * from './sidebar';
11
+ export * from './tabs';
12
+ export * from './split';
13
+ export { macosAdapter } from './platform/macos';
14
+ export { windowsAdapter } from './platform/windows';
15
+ export { type NavigationPlatformAdapter, type KeyboardShortcut, type DesktopKeyEvent, type TransitionPreset, } from './platform';
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.windowsAdapter = exports.macosAdapter = exports.restoreNavigationState = exports.serializeNavigationState = exports.NavigationStore = exports.useNavigationTheme = exports.DefaultTheme = exports.NavigationFocusable = exports.useIsFocused = exports.useFocusEffect = exports.useNavigationState = exports.useRoute = exports.useNavigation = exports.createNavigationRef = exports.NavigationContainer = void 0;
18
+ var NavigationContainer_1 = require("./core/NavigationContainer");
19
+ Object.defineProperty(exports, "NavigationContainer", { enumerable: true, get: function () { return NavigationContainer_1.NavigationContainer; } });
20
+ var navigation_1 = require("./core/navigation");
21
+ Object.defineProperty(exports, "createNavigationRef", { enumerable: true, get: function () { return navigation_1.createNavigationRef; } });
22
+ var hooks_1 = require("./core/hooks");
23
+ Object.defineProperty(exports, "useNavigation", { enumerable: true, get: function () { return hooks_1.useNavigation; } });
24
+ Object.defineProperty(exports, "useRoute", { enumerable: true, get: function () { return hooks_1.useRoute; } });
25
+ Object.defineProperty(exports, "useNavigationState", { enumerable: true, get: function () { return hooks_1.useNavigationState; } });
26
+ Object.defineProperty(exports, "useFocusEffect", { enumerable: true, get: function () { return hooks_1.useFocusEffect; } });
27
+ Object.defineProperty(exports, "useIsFocused", { enumerable: true, get: function () { return hooks_1.useIsFocused; } });
28
+ var focus_1 = require("./core/focus");
29
+ Object.defineProperty(exports, "NavigationFocusable", { enumerable: true, get: function () { return focus_1.NavigationFocusable; } });
30
+ var context_1 = require("./core/context");
31
+ Object.defineProperty(exports, "DefaultTheme", { enumerable: true, get: function () { return context_1.DefaultTheme; } });
32
+ Object.defineProperty(exports, "useNavigationTheme", { enumerable: true, get: function () { return context_1.useNavigationTheme; } });
33
+ var store_1 = require("./core/store");
34
+ Object.defineProperty(exports, "NavigationStore", { enumerable: true, get: function () { return store_1.NavigationStore; } });
35
+ Object.defineProperty(exports, "serializeNavigationState", { enumerable: true, get: function () { return store_1.serializeNavigationState; } });
36
+ Object.defineProperty(exports, "restoreNavigationState", { enumerable: true, get: function () { return store_1.restoreNavigationState; } });
37
+ __exportStar(require("./core/types"), exports);
38
+ __exportStar(require("./routers"), exports);
39
+ __exportStar(require("./stack"), exports);
40
+ __exportStar(require("./sidebar"), exports);
41
+ __exportStar(require("./tabs"), exports);
42
+ __exportStar(require("./split"), exports);
43
+ var macos_1 = require("./platform/macos");
44
+ Object.defineProperty(exports, "macosAdapter", { enumerable: true, get: function () { return macos_1.macosAdapter; } });
45
+ var windows_1 = require("./platform/windows");
46
+ Object.defineProperty(exports, "windowsAdapter", { enumerable: true, get: function () { return windows_1.windowsAdapter; } });
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import { type StyleProp, type ViewStyle, type LayoutChangeEvent } from 'react-native';
3
+ import type { ResolvedNativeIcon } from './icons';
4
+ export interface NativeAppearance {
5
+ /**
6
+ * Hex colors, e.g. #202124 or #202124ff. Native controls retain OS interaction states.
7
+ * `null` skips the Container theme and uses the OS default (e.g. the macOS
8
+ * sidebar material, the system accent color, or the adaptive label color).
9
+ */
10
+ backgroundColor?: string | null;
11
+ foregroundColor?: string | null;
12
+ accentColor?: string | null;
13
+ sidebarBackgroundColor?: string | null;
14
+ }
15
+ export interface NativeItem {
16
+ key: string;
17
+ title: string;
18
+ disabled?: boolean;
19
+ hidden?: boolean;
20
+ section?: string;
21
+ icon?: ResolvedNativeIcon;
22
+ badge?: string;
23
+ }
24
+ export interface NativeColumn {
25
+ key: string;
26
+ width: number;
27
+ minWidth: number;
28
+ maxWidth?: number;
29
+ }
30
+ export interface NativeConfiguration {
31
+ mode: 'stack' | 'sidebar' | 'split';
32
+ items: NativeItem[];
33
+ activeKey: string;
34
+ appearance?: NativeAppearance;
35
+ headerShown?: boolean;
36
+ canGoBack?: boolean;
37
+ backTitle?: string;
38
+ collapsed?: boolean;
39
+ paneWidth?: number;
40
+ /** Height reserved below the sidebar list for a React footer. */
41
+ footerHeight?: number;
42
+ columns?: NativeColumn[];
43
+ }
44
+ export type NativeRequest = {
45
+ type: 'back';
46
+ } | {
47
+ type: 'pop';
48
+ count: number;
49
+ } | {
50
+ type: 'select';
51
+ key: string;
52
+ } | {
53
+ type: 'collapse';
54
+ collapsed: boolean;
55
+ } | {
56
+ type: 'resize';
57
+ key: string;
58
+ width: number;
59
+ };
60
+ export interface NativeRect {
61
+ x: number;
62
+ y: number;
63
+ width: number;
64
+ height: number;
65
+ }
66
+ interface NativeIconFrame {
67
+ frame: NativeRect;
68
+ clip: NativeRect;
69
+ }
70
+ interface LayoutEvent {
71
+ type: 'layout';
72
+ revision: number;
73
+ frames: Record<string, NativeRect>;
74
+ iconFrames?: Record<string, NativeIconFrame>;
75
+ footerFrame?: NativeRect;
76
+ }
77
+ export declare function parseNativeEvent(payload: string): (NativeRequest & {
78
+ revision: number;
79
+ }) | LayoutEvent | null;
80
+ export interface NativeSurfaceProps {
81
+ configuration: NativeConfiguration;
82
+ slots: {
83
+ key: string;
84
+ visible: boolean;
85
+ content: React.ReactNode | ((ready: boolean) => React.ReactNode);
86
+ }[];
87
+ icons?: {
88
+ key: string;
89
+ content: React.ReactElement;
90
+ }[];
91
+ /** Interactive React content placed in the native footer region. */
92
+ footer?: React.ReactNode;
93
+ onFooterHeight?: (height: number) => void;
94
+ style?: StyleProp<ViewStyle>;
95
+ onRequest: (request: NativeRequest) => void;
96
+ onLayout?: (event: LayoutChangeEvent) => void;
97
+ onMeasurements?: (frames: Record<string, NativeRect>) => void;
98
+ }
99
+ export declare function NativeSurface({ configuration, slots, icons, footer, onFooterHeight, style, onRequest, onLayout, onMeasurements, }: NativeSurfaceProps): React.JSX.Element;
100
+ export {};
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseNativeEvent = parseNativeEvent;
7
+ exports.NativeSurface = NativeSurface;
8
+ const jsx_runtime_1 = require("react/jsx-runtime");
9
+ const react_1 = __importDefault(require("react"));
10
+ const react_native_1 = require("react-native");
11
+ const DesktopNavigationHostNativeComponent_1 = __importDefault(require("./specs/DesktopNavigationHostNativeComponent"));
12
+ const context_1 = require("../core/context");
13
+ function isRect(frame) {
14
+ return (!!frame &&
15
+ ['x', 'y', 'width', 'height'].every((key) => typeof frame[key] === 'number' && Number.isFinite(frame[key])) &&
16
+ frame.width >= 0 &&
17
+ frame.height >= 0);
18
+ }
19
+ // Keep native event payloads at this boundary. Native UI requests changes; the
20
+ // Store decides whether to accept them. Never restore a native state snapshot.
21
+ function parseNativeEvent(payload) {
22
+ let value;
23
+ try {
24
+ value = JSON.parse(payload);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ if (!value ||
30
+ typeof value !== 'object' ||
31
+ !Number.isSafeInteger(value.revision))
32
+ return null;
33
+ switch (value.type) {
34
+ case 'back':
35
+ return value;
36
+ case 'pop':
37
+ return Number.isSafeInteger(value.count) && value.count > 0
38
+ ? value
39
+ : null;
40
+ case 'select':
41
+ return typeof value.key === 'string' ? value : null;
42
+ case 'collapse':
43
+ return typeof value.collapsed === 'boolean' ? value : null;
44
+ case 'resize':
45
+ return typeof value.key === 'string' &&
46
+ Number.isFinite(value.width) &&
47
+ value.width >= 0
48
+ ? value
49
+ : null;
50
+ case 'layout':
51
+ if (!Number.isSafeInteger(value.revision) ||
52
+ !value.frames ||
53
+ typeof value.frames !== 'object' ||
54
+ Array.isArray(value.frames))
55
+ return null;
56
+ if (!Object.values(value.frames).every(isRect))
57
+ return null;
58
+ if (value.iconFrames !== undefined &&
59
+ (!value.iconFrames ||
60
+ typeof value.iconFrames !== 'object' ||
61
+ Array.isArray(value.iconFrames) ||
62
+ !Object.values(value.iconFrames).every((entry) => entry && isRect(entry.frame) && isRect(entry.clip))))
63
+ return null;
64
+ if (value.footerFrame !== undefined && !isRect(value.footerFrame))
65
+ return null;
66
+ return value;
67
+ default:
68
+ return null;
69
+ }
70
+ }
71
+ function NativeSurface({ configuration, slots, icons = [], footer, onFooterHeight, style, onRequest, onLayout, onMeasurements, }) {
72
+ if (react_native_1.Platform.OS !== 'macos' && react_native_1.Platform.OS !== 'windows')
73
+ throw new Error('Desktop native navigation supports macOS and Windows only.');
74
+ if (!globalThis.nativeFabricUIManager)
75
+ throw new Error('Desktop native navigation requires Fabric (New Architecture).');
76
+ if (!react_native_1.UIManager.hasViewManagerConfig('DesktopNavigationHost'))
77
+ throw new Error('DesktopNavigationHost is not registered. Install the desktop native sources, enable Fabric, and rebuild the app. See the /native installation guide.');
78
+ const { colors } = (0, context_1.useNavigationTheme)();
79
+ // Omitted keys fall back to OS defaults natively, so `null` is simply dropped.
80
+ const appearance = Object.fromEntries(Object.entries({
81
+ backgroundColor: colors.background,
82
+ foregroundColor: colors.text,
83
+ accentColor: colors.accent,
84
+ sidebarBackgroundColor: colors.surface,
85
+ ...configuration.appearance,
86
+ }).filter(([, color]) => color != null));
87
+ configuration = { ...configuration, appearance };
88
+ for (const color of Object.values(appearance)) {
89
+ if (typeof color !== 'string' ||
90
+ !/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(color))
91
+ throw new Error('Native navigation appearance requires #RRGGBB or #RRGGBBAA colors.');
92
+ }
93
+ const [acknowledgement, acknowledge] = react_1.default.useReducer((n) => n + 1, 0);
94
+ const serialized = JSON.stringify(configuration);
95
+ const version = react_1.default.useRef({ serialized, acknowledgement, revision: 0 });
96
+ if (version.current.serialized !== serialized ||
97
+ version.current.acknowledgement !== acknowledgement) {
98
+ version.current = {
99
+ serialized,
100
+ acknowledgement,
101
+ revision: version.current.revision + 1,
102
+ };
103
+ }
104
+ const revision = version.current.revision;
105
+ const [layout, setLayout] = react_1.default.useState(null);
106
+ const footerFrame = layout?.footerFrame;
107
+ const footerPlaced = !!footerFrame && footerFrame.width > 0;
108
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, style], onLayout: onLayout, children: [(0, jsx_runtime_1.jsx)(DesktopNavigationHostNativeComponent_1.default, { style: react_native_1.StyleSheet.absoluteFillObject, configuration: JSON.stringify({ ...configuration, revision }), onNavigationEvent: (event) => {
109
+ const message = parseNativeEvent(event.nativeEvent.payload);
110
+ if (!message || message.revision !== revision)
111
+ return;
112
+ if (message.type === 'layout') {
113
+ onMeasurements?.(message.frames);
114
+ setLayout((previous) => JSON.stringify(previous) === JSON.stringify(message)
115
+ ? previous
116
+ : message);
117
+ }
118
+ else {
119
+ onRequest(message);
120
+ // Also send an acknowledgement after vetoed/no-op actions so that
121
+ // optimistic native selection/open state is rolled back.
122
+ acknowledge();
123
+ }
124
+ } }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "box-none", style: react_native_1.StyleSheet.absoluteFillObject, children: slots.map((slot) => {
125
+ const frame = layout?.frames[slot.key];
126
+ const visible = slot.visible && !!frame && frame.width > 0 && frame.height > 0;
127
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { collapsable: false, pointerEvents: visible ? 'auto' : 'none', accessibilityElementsHidden: !visible, importantForAccessibility: visible ? 'auto' : 'no-hide-descendants', style: [
128
+ styles.slot,
129
+ frame && {
130
+ left: frame.x,
131
+ top: frame.y,
132
+ width: frame.width,
133
+ height: frame.height,
134
+ },
135
+ !visible && styles.hidden,
136
+ ], children: typeof slot.content === 'function'
137
+ ? slot.content(visible)
138
+ : slot.content }, slot.key));
139
+ }) }), footer != null && footer !== false && (
140
+ // Measured at its natural height so native can reserve exactly that
141
+ // much space; kept invisible until native reports the reserved frame.
142
+ (0, jsx_runtime_1.jsx)(react_native_1.View, { collapsable: false, pointerEvents: footerPlaced ? 'box-none' : 'none', accessibilityElementsHidden: !footerPlaced, importantForAccessibility: footerPlaced ? 'auto' : 'no-hide-descendants', onLayout: (event) => onFooterHeight?.(event.nativeEvent.layout.height), style: [
143
+ styles.footer,
144
+ footerPlaced
145
+ ? {
146
+ left: footerFrame.x,
147
+ top: footerFrame.y,
148
+ width: footerFrame.width,
149
+ }
150
+ : {
151
+ left: 0,
152
+ top: 0,
153
+ width: configuration.paneWidth ?? 240,
154
+ opacity: 0,
155
+ },
156
+ ], children: footer })), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: react_native_1.StyleSheet.absoluteFillObject, children: icons.map((icon) => {
157
+ // Never display a removed/resized icon at a previous revision's position.
158
+ const geometry = layout?.revision === revision
159
+ ? layout.iconFrames?.[icon.key]
160
+ : undefined;
161
+ if (!geometry ||
162
+ geometry.clip.width <= 0 ||
163
+ geometry.clip.height <= 0)
164
+ return null;
165
+ const { frame, clip } = geometry;
166
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { collapsable: false, pointerEvents: "none", style: [
167
+ styles.slot,
168
+ {
169
+ left: clip.x,
170
+ top: clip.y,
171
+ width: clip.width,
172
+ height: clip.height,
173
+ },
174
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: {
175
+ position: 'absolute',
176
+ left: frame.x - clip.x,
177
+ top: frame.y - clip.y,
178
+ width: frame.width,
179
+ height: frame.height,
180
+ alignItems: 'center',
181
+ justifyContent: 'center',
182
+ }, children: icon.content }) }, icon.key));
183
+ }) })] }));
184
+ }
185
+ const styles = react_native_1.StyleSheet.create({
186
+ container: { flex: 1, overflow: 'hidden' },
187
+ slot: { position: 'absolute', overflow: 'hidden' },
188
+ footer: { position: 'absolute' },
189
+ hidden: { display: 'none' },
190
+ });
@@ -0,0 +1,56 @@
1
+ import { type ReactElement } from 'react';
2
+ /** Serializable alternatives to React elements inside native menu controls. */
3
+ export type NativeSidebarIcon = (({
4
+ type: 'system';
5
+ } & ({
6
+ /** SF Symbols name. */
7
+ macos: string;
8
+ /** Unicode glyph; fontFamily defaults to WinUI's system icon font. */
9
+ windows?: {
10
+ glyph: string;
11
+ fontFamily?: string;
12
+ };
13
+ } | {
14
+ macos?: string;
15
+ windows: {
16
+ glyph: string;
17
+ fontFamily?: string;
18
+ };
19
+ })) | {
20
+ type: 'image';
21
+ /** Metro require('./icon.png') or a native-readable image URI. */
22
+ source: number | {
23
+ uri: string;
24
+ };
25
+ /** Tint the image as a monochrome template (default: false). */
26
+ template?: boolean;
27
+ }) & {
28
+ /** Logical points / DIPs, default 16. */
29
+ size?: number;
30
+ /** #RRGGBB or #RRGGBBAA; omitted colors follow native item styling. */
31
+ color?: string;
32
+ };
33
+ export interface NativeSidebarIconState {
34
+ /** Selected route, matching the JS Sidebar's icon callback. */
35
+ focused: boolean;
36
+ disabled: boolean;
37
+ }
38
+ export type NativeSidebarIconOption = NativeSidebarIcon | ReactElement | null | ((state: NativeSidebarIconState) => NativeSidebarIcon | ReactElement | null);
39
+ export type ResolvedNativeIcon = ({
40
+ type: 'react';
41
+ } | {
42
+ type: 'symbol';
43
+ name: string;
44
+ } | {
45
+ type: 'font';
46
+ glyph: string;
47
+ fontFamily?: string;
48
+ } | {
49
+ type: 'image';
50
+ uri: string;
51
+ template: boolean;
52
+ }) & {
53
+ size: number;
54
+ color?: string;
55
+ };
56
+ export declare function resolveNativeIcon(option: NativeSidebarIconOption | undefined, state: NativeSidebarIconState, reactSize?: number): ResolvedNativeIcon | undefined;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveNativeIcon = resolveNativeIcon;
4
+ const react_1 = require("react");
5
+ const react_native_1 = require("react-native");
6
+ function resolveNativeIcon(option, state, reactSize = 24) {
7
+ const icon = typeof option === 'function' ? option(state) : option;
8
+ if (icon == null)
9
+ return undefined;
10
+ const element = (0, react_1.isValidElement)(icon);
11
+ const size = element ? reactSize : (icon.size ?? 16);
12
+ if (!Number.isFinite(size) || size <= 0)
13
+ throw new Error('Native sidebar icon size must be a positive finite number.');
14
+ if (element)
15
+ return { type: 'react', size };
16
+ if (icon.color !== undefined &&
17
+ !/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(icon.color))
18
+ throw new Error('Native sidebar icon color requires #RRGGBB or #RRGGBBAA.');
19
+ const style = { size, color: icon.color };
20
+ if (icon.type === 'system') {
21
+ if (react_native_1.Platform.OS === 'macos') {
22
+ if (icon.macos === undefined)
23
+ return undefined;
24
+ if (typeof icon.macos !== 'string' || !icon.macos.trim())
25
+ throw new Error('Native sidebar SF Symbols name must be a non-empty string.');
26
+ return { ...style, type: 'symbol', name: icon.macos };
27
+ }
28
+ if (!icon.windows)
29
+ return undefined;
30
+ if (typeof icon.windows.glyph !== 'string' || !icon.windows.glyph.trim())
31
+ throw new Error('Native sidebar font icon requires a Unicode glyph.');
32
+ return {
33
+ ...style,
34
+ type: 'font',
35
+ glyph: icon.windows.glyph,
36
+ fontFamily: icon.windows.fontFamily,
37
+ };
38
+ }
39
+ if (icon.type !== 'image')
40
+ throw new Error('Native sidebar icons must be React elements, system descriptors or image descriptors.');
41
+ const source = react_native_1.Image.resolveAssetSource(icon.source);
42
+ if (!source?.uri || typeof source.uri !== 'string')
43
+ throw new Error('Native sidebar image could not be resolved to a URI.');
44
+ const protocols = react_native_1.Platform.OS === 'macos'
45
+ ? /^(https?|file):\/\//i
46
+ : /^(https?|file|ms-appx|ms-appdata):\/\//i;
47
+ if (!protocols.test(source.uri))
48
+ throw new Error('Native sidebar images require an HTTP(S), file, or Windows ms-appx/ms-appdata URI.');
49
+ return {
50
+ ...style,
51
+ type: 'image',
52
+ uri: source.uri,
53
+ template: icon.template ?? false,
54
+ };
55
+ }
@@ -0,0 +1,13 @@
1
+ export { NavigationContainer, type NavigationContainerProps, type KeyboardShortcutOptions, } from '../core/NavigationContainer';
2
+ export { createNavigationRef } from '../core/navigation';
3
+ export { useNavigation, useRoute, useNavigationState, useFocusEffect, useIsFocused, } from '../core/hooks';
4
+ export { NavigationFocusable, type FocusTarget } from '../core/focus';
5
+ export { DefaultTheme, useNavigationTheme } from '../core/context';
6
+ export { NavigationStore, serializeNavigationState, restoreNavigationState, type NavigationEvent, type NavigationEventType, } from '../core/store';
7
+ export * from '../routers';
8
+ export type { Navigation, StackNavigation, SidebarNavigation, StackScreenProps, SidebarScreenProps, SidebarFooterProps, NavigationTheme, NavigationRef, } from '../core/types';
9
+ export { createStackNavigator, type NativeStackNavigatorProps, type NativeStackScreenOptions, } from './stack';
10
+ export { createSidebarNavigator, type NativeSidebarNavigatorProps, type NativeSidebarScreenOptions, } from './sidebar';
11
+ export { createSplitNavigator, type NativeSplitNavigatorProps, type NativeSplitColumnProps, } from './split';
12
+ export type { NativeAppearance } from './host';
13
+ export type { NativeSidebarIcon, NativeSidebarIconOption, NativeSidebarIconState, } from './icons';