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
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/gtkx/bridge/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAA;AAInC,MAAM,CAAC,MAAM,YAAY,GAAG,GAAqB,EAAE,CACjD,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,CAAA;AAE/B,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,GAAgB,EAAE,CAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA","sourcesContent":["import * as Adw from \"@gtkx/gi/adw\"\n\nexport type ColorScheme = \"light\" | \"dark\"\n\nexport const styleManager = (): Adw.StyleManager =>\n Adw.StyleManager.getDefault()\n\n// Appearance API (task 007) subscribes to notify::dark on the style manager.\nexport const colorScheme = (): ColorScheme =>\n styleManager().getDark() ? \"dark\" : \"light\"\n"]}
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/gtkx/bridge/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAA;AAInC,MAAM,CAAC,MAAM,YAAY,GAAG,GAAqB,EAAE,CACjD,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,CAAA;AAE/B,sEAAsE;AACtE,MAAM,CAAC,MAAM,WAAW,GAAG,GAAgB,EAAE,CAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA","sourcesContent":["import * as Adw from \"@gtkx/gi/adw\"\n\nexport type ColorScheme = \"light\" | \"dark\"\n\nexport const styleManager = (): Adw.StyleManager =>\n Adw.StyleManager.getDefault()\n\n// The Appearance API subscribes to notify::dark on the style manager.\nexport const colorScheme = (): ColorScheme =>\n styleManager().getDark() ? \"dark\" : \"light\"\n"]}
@@ -0,0 +1,2 @@
1
+ import { useSignal as useGtkxSignal } from "@gtkx/react";
2
+ export declare const useSignal: typeof useGtkxSignal;
@@ -0,0 +1,31 @@
1
+ // RC2-WORKAROUND(use-signal-stale-handler): see docs/gtkx-rc2-notes.md
2
+ // @gtkx/react's useSignal documents that "each emission runs the handler from
3
+ // the latest render", and rc.1 delivered that by pinning the handler in a ref
4
+ // of its own. rc.2 routes it through React's useEffectEvent instead, and under
5
+ // the gtkx reconciler that event ref stops refreshing for components deep in
6
+ // the tree: a ScrollView measured at its 8th render still ran the closure
7
+ // captured at mount (a plain ref in the same component reported 8, the
8
+ // useEffectEvent one reported 1). The consequence is not subtle — the
9
+ // mount-time scroll handler windows a VirtualizedList against the data it saw
10
+ // at mount, so a list whose rows arrive from a fetch renders EMPTY as soon as
11
+ // it is scrolled.
12
+ //
13
+ // Restore the documented contract at the bridge: keep the latest handler in a
14
+ // ref of our own and hand gtkx a stable wrapper (stable so the connection is
15
+ // never re-established, which is what its useEffectEvent was for). The ref is
16
+ // refreshed in an INSERTION effect — the earliest commit phase, ahead of every
17
+ // layout effect, so a signal emitted from one (the list's own scroll
18
+ // corrections) already sees the handler from the render being committed.
19
+ import { useSignal as useGtkxSignal } from "@gtkx/react";
20
+ import { useCallback, useInsertionEffect, useRef } from "react";
21
+ export const useSignal = (object, signal, handler, options) => {
22
+ // The handler type is generic in the signal name; the wrapper only forwards
23
+ // arguments, so it crosses that boundary as an untyped callable.
24
+ const latest = useRef(() => undefined);
25
+ useInsertionEffect(() => {
26
+ latest.current = handler;
27
+ });
28
+ const stable = useCallback((...args) => latest.current(...args), []);
29
+ useGtkxSignal(object, signal, stable, options);
30
+ };
31
+ //# sourceMappingURL=use-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-signal.js","sourceRoot":"","sources":["../../../src/gtkx/bridge/use-signal.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,0EAA0E;AAC1E,uEAAuE;AACvE,sEAAsE;AACtE,8EAA8E;AAC9E,8EAA8E;AAC9E,kBAAkB;AAClB,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,qEAAqE;AACrE,yEAAyE;AACzE,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAI/D,MAAM,CAAC,MAAM,SAAS,GAAyB,CAC7C,MAAM,EACN,MAAM,EACN,OAAO,EACP,OAAO,EACP,EAAE;IACF,4EAA4E;IAC5E,iEAAiE;IACjE,MAAM,MAAM,GAAG,MAAM,CAAa,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAClD,kBAAkB,CAAC,GAAG,EAAE;QACtB,MAAM,CAAC,OAAO,GAAG,OAAgC,CAAA;IACnD,CAAC,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,EACpC,EAAE,CACH,CAAA;IACD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAmC,EAAE,OAAO,CAAC,CAAA;AAC7E,CAAC,CAAA","sourcesContent":["// RC2-WORKAROUND(use-signal-stale-handler): see docs/gtkx-rc2-notes.md\n// @gtkx/react's useSignal documents that \"each emission runs the handler from\n// the latest render\", and rc.1 delivered that by pinning the handler in a ref\n// of its own. rc.2 routes it through React's useEffectEvent instead, and under\n// the gtkx reconciler that event ref stops refreshing for components deep in\n// the tree: a ScrollView measured at its 8th render still ran the closure\n// captured at mount (a plain ref in the same component reported 8, the\n// useEffectEvent one reported 1). The consequence is not subtle — the\n// mount-time scroll handler windows a VirtualizedList against the data it saw\n// at mount, so a list whose rows arrive from a fetch renders EMPTY as soon as\n// it is scrolled.\n//\n// Restore the documented contract at the bridge: keep the latest handler in a\n// ref of our own and hand gtkx a stable wrapper (stable so the connection is\n// never re-established, which is what its useEffectEvent was for). The ref is\n// refreshed in an INSERTION effect — the earliest commit phase, ahead of every\n// layout effect, so a signal emitted from one (the list's own scroll\n// corrections) already sees the handler from the render being committed.\nimport { useSignal as useGtkxSignal } from \"@gtkx/react\"\nimport { useCallback, useInsertionEffect, useRef } from \"react\"\n\ntype AnyHandler = (...args: unknown[]) => unknown\n\nexport const useSignal: typeof useGtkxSignal = (\n object,\n signal,\n handler,\n options,\n) => {\n // The handler type is generic in the signal name; the wrapper only forwards\n // arguments, so it crosses that boundary as an untyped callable.\n const latest = useRef<AnyHandler>(() => undefined)\n useInsertionEffect(() => {\n latest.current = handler as unknown as AnyHandler\n })\n const stable = useCallback<AnyHandler>(\n (...args) => latest.current(...args),\n [],\n )\n useGtkxSignal(object, signal, stable as unknown as typeof handler, options)\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { ActivityIndicator, Animated, AppRegistry, Easing, FlatList, Image, Modal, Pressable, Root, SafeAreaView, ScrollView, SectionList, StatusBar, Switch, Text, TextInput, TouchableOpacity, View, type ActivityIndicatorProps, type FlatListProps, type ImageProps, type ImageSource, type LayoutEvent, type ListRenderItemInfo, type ModalProps, type PressableProps, type PressableStateCallbackType, type PressEvent, type RootProps, type RunApplicationParams, type ScrollEvent, type ScrollViewHandle, type ScrollViewProps, type SectionListProps, type SwitchProps, type TextInputProps, type TextProps, type TouchableOpacityProps, type ViewabilityConfig, type ViewProps, type ViewToken, } from "./components/index";
1
+ export { ActivityIndicator, Animated, AppRegistry, Easing, FlatList, Image, Modal, IntrinsicRoot, NestedRoot, Pressable, Root, SafeAreaView, ScrollView, SectionList, StatusBar, Switch, Text, TextInput, TouchableOpacity, View, type ActivityIndicatorProps, type FlatListProps, type ImageProps, type ImageSource, type LayoutEvent, type ListRenderItemInfo, type ModalProps, type PressableProps, type PressableStateCallbackType, type IntrinsicRootProps, type NestedRootProps, type PressEvent, type RootProps, type RunApplicationParams, type ScrollEvent, type ScrollViewHandle, type ScrollViewProps, type SectionListProps, type SwitchProps, type TextInputProps, type TextProps, type TouchableOpacityProps, type ViewabilityConfig, type ViewProps, type ViewToken, } from "./components/index";
2
2
  export type { FlatListHandle } from "./components/flat-list";
3
- export { Alert, Appearance, AppState, DevSettings, Dimensions, Linking, Platform, useColorScheme, useWindowDimensions, type AlertButton, type AlertButtonStyle, type AlertOptions, type AppearancePreferences, type AppStateEvent, type AppStateStatus, type ColorSchemeName, type DimensionKey, type DimensionsPayload, type PlatformSelectSpec, type ScaledSize, } from "./apis/index";
3
+ export { Alert, Appearance, AppState, BackHandler, DevSettings, Dimensions, I18nManager, InteractionManager, Linking, Platform, useColorScheme, useWindowDimensions, type AlertButton, type AlertButtonStyle, type AlertOptions, type AppearancePreferences, type AppStateEvent, type AppStateStatus, type ColorSchemeName, type DimensionKey, type DimensionsPayload, type InteractionPromise, type PlatformSelectSpec, type ScaledSize, } from "./apis/index";
4
4
  export { PlatformColor, StyleSheet } from "./style/index";
5
5
  export type { DimensionValue, FlatStyle, LayoutStyle, PointerEventsValue, StyleProp, TransformPart, VisualStyle, } from "./contracts";
6
6
  export declare const version = "0.1.0";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Public surface of react-native-gtkx, mirroring the `react-native` module.
2
- // Components: 006 · APIs: 007 · StyleSheet: 005 · layout engine underneath: 004.
3
- export { ActivityIndicator, Animated, AppRegistry, Easing, FlatList, Image, Modal, Pressable, Root, SafeAreaView, ScrollView, SectionList, StatusBar, Switch, Text, TextInput, TouchableOpacity, View, } from "./components/index";
4
- export { Alert, Appearance, AppState, DevSettings, Dimensions, Linking, Platform, useColorScheme, useWindowDimensions, } from "./apis/index";
2
+ // Components · APIs · StyleSheet, with the Yoga layout engine underneath.
3
+ export { ActivityIndicator, Animated, AppRegistry, Easing, FlatList, Image, Modal, IntrinsicRoot, NestedRoot, Pressable, Root, SafeAreaView, ScrollView, SectionList, StatusBar, Switch, Text, TextInput, TouchableOpacity, View, } from "./components/index";
4
+ export { Alert, Appearance, AppState, BackHandler, DevSettings, Dimensions, I18nManager, InteractionManager, Linking, Platform, useColorScheme, useWindowDimensions, } from "./apis/index";
5
5
  export { PlatformColor, StyleSheet } from "./style/index";
6
6
  export const version = "0.1.0";
7
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,iFAAiF;AAEjF,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,KAAK,EACL,KAAK,EACL,SAAS,EACT,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,WAAW,EACX,SAAS,EACT,MAAM,EACN,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,IAAI,GAwBL,MAAM,oBAAoB,CAAA;AAM3B,OAAO,EACL,KAAK,EACL,UAAU,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAQ,EACR,cAAc,EACd,mBAAmB,GAYpB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAYzD,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA","sourcesContent":["// Public surface of react-native-gtkx, mirroring the `react-native` module.\n// Components: 006 · APIs: 007 · StyleSheet: 005 · layout engine underneath: 004.\n\nexport {\n ActivityIndicator,\n Animated,\n AppRegistry,\n Easing,\n FlatList,\n Image,\n Modal,\n Pressable,\n Root,\n SafeAreaView,\n ScrollView,\n SectionList,\n StatusBar,\n Switch,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n type ActivityIndicatorProps,\n type FlatListProps,\n type ImageProps,\n type ImageSource,\n type LayoutEvent,\n type ListRenderItemInfo,\n type ModalProps,\n type PressableProps,\n type PressableStateCallbackType,\n type PressEvent,\n type RootProps,\n type RunApplicationParams,\n type ScrollEvent,\n type ScrollViewHandle,\n type ScrollViewProps,\n type SectionListProps,\n type SwitchProps,\n type TextInputProps,\n type TextProps,\n type TouchableOpacityProps,\n type ViewabilityConfig,\n type ViewProps,\n type ViewToken,\n} from \"./components/index\"\n\n// List scroll handle (scrollTo/scrollToEnd/scrollToIndex/scrollToItem/\n// scrollToOffset) — exported straight from the component module.\nexport type { FlatListHandle } from \"./components/flat-list\"\n\nexport {\n Alert,\n Appearance,\n AppState,\n DevSettings,\n Dimensions,\n Linking,\n Platform,\n useColorScheme,\n useWindowDimensions,\n type AlertButton,\n type AlertButtonStyle,\n type AlertOptions,\n type AppearancePreferences,\n type AppStateEvent,\n type AppStateStatus,\n type ColorSchemeName,\n type DimensionKey,\n type DimensionsPayload,\n type PlatformSelectSpec,\n type ScaledSize,\n} from \"./apis/index\"\n\nexport { PlatformColor, StyleSheet } from \"./style/index\"\n\nexport type {\n DimensionValue,\n FlatStyle,\n LayoutStyle,\n PointerEventsValue,\n StyleProp,\n TransformPart,\n VisualStyle,\n} from \"./contracts\"\n\nexport const version = \"0.1.0\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAE1E,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,KAAK,EACL,KAAK,EACL,aAAa,EACb,UAAU,EACV,SAAS,EACT,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,WAAW,EACX,SAAS,EACT,MAAM,EACN,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,IAAI,GA0BL,MAAM,oBAAoB,CAAA;AAM3B,OAAO,EACL,KAAK,EACL,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,QAAQ,EACR,cAAc,EACd,mBAAmB,GAapB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAYzD,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA","sourcesContent":["// Public surface of react-native-gtkx, mirroring the `react-native` module.\n// Components · APIs · StyleSheet, with the Yoga layout engine underneath.\n\nexport {\n ActivityIndicator,\n Animated,\n AppRegistry,\n Easing,\n FlatList,\n Image,\n Modal,\n IntrinsicRoot,\n NestedRoot,\n Pressable,\n Root,\n SafeAreaView,\n ScrollView,\n SectionList,\n StatusBar,\n Switch,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n type ActivityIndicatorProps,\n type FlatListProps,\n type ImageProps,\n type ImageSource,\n type LayoutEvent,\n type ListRenderItemInfo,\n type ModalProps,\n type PressableProps,\n type PressableStateCallbackType,\n type IntrinsicRootProps,\n type NestedRootProps,\n type PressEvent,\n type RootProps,\n type RunApplicationParams,\n type ScrollEvent,\n type ScrollViewHandle,\n type ScrollViewProps,\n type SectionListProps,\n type SwitchProps,\n type TextInputProps,\n type TextProps,\n type TouchableOpacityProps,\n type ViewabilityConfig,\n type ViewProps,\n type ViewToken,\n} from \"./components/index\"\n\n// List scroll handle (scrollTo/scrollToEnd/scrollToIndex/scrollToItem/\n// scrollToOffset) — exported straight from the component module.\nexport type { FlatListHandle } from \"./components/flat-list\"\n\nexport {\n Alert,\n Appearance,\n AppState,\n BackHandler,\n DevSettings,\n Dimensions,\n I18nManager,\n InteractionManager,\n Linking,\n Platform,\n useColorScheme,\n useWindowDimensions,\n type AlertButton,\n type AlertButtonStyle,\n type AlertOptions,\n type AppearancePreferences,\n type AppStateEvent,\n type AppStateStatus,\n type ColorSchemeName,\n type DimensionKey,\n type DimensionsPayload,\n type InteractionPromise,\n type PlatformSelectSpec,\n type ScaledSize,\n} from \"./apis/index\"\n\nexport { PlatformColor, StyleSheet } from \"./style/index\"\n\nexport type {\n DimensionValue,\n FlatStyle,\n LayoutStyle,\n PointerEventsValue,\n StyleProp,\n TransformPart,\n VisualStyle,\n} from \"./contracts\"\n\nexport const version = \"0.1.0\"\n"]}
@@ -9,14 +9,18 @@ export declare class LayoutEngine {
9
9
  private dirty;
10
10
  private scheduled;
11
11
  private disposed;
12
+ private dirtyNodes;
13
+ private walkAll;
12
14
  constructor(viewport: ViewportSize);
13
15
  createNode(): LayoutNode;
14
16
  setViewport(viewport: ViewportSize): void;
15
17
  getViewport(): ViewportSize;
16
18
  flushSync(): void;
19
+ measureContent(orientation: "horizontal" | "vertical", forSize: number): number;
17
20
  dispose(): void;
18
21
  private requestFlush;
19
22
  private commitTree;
23
+ private dirtyPath;
20
24
  private collectChanges;
21
25
  private freeTree;
22
26
  }
@@ -1,15 +1,38 @@
1
- import { LayoutNode } from "./node";
1
+ import { perfAddTime, perfBurst, perfCount, perfEnabled, perfGauge, perfNow, } from "../perf";
2
+ import { getLiveNodeCount, LayoutNode } from "./node";
2
3
  import { Direction } from "./yoga";
3
4
  // One engine per window root. Batches every mutation (style, tree, measure
4
5
  // invalidation) into a single Yoga pass per microtask, then walks the shadow
5
6
  // tree, diffs rects and fires commit (widget move) + onLayout callbacks only
6
7
  // for nodes whose rect actually changed.
8
+ //
9
+ // The commit walk is INCREMENTAL: it descends only where this pass can have
10
+ // changed something, so a small mutation inside a large stable shell costs
11
+ // O(changed) instead of O(all nodes). Two signals drive the descent, and both
12
+ // are needed:
13
+ // - Yoga's per-node `hasNewLayout` flag, which Yoga sets on every node it
14
+ // actually laid out. It covers the nodes a dirty set cannot know about:
15
+ // mutating one child re-lays out its FOLLOWING SIBLINGS (they shift) and
16
+ // any ancestor whose size followed, while untouched subtrees keep their
17
+ // cached layout — and their rects are parent-relative, so an unvisited
18
+ // subtree of a moved container is genuinely unchanged.
19
+ // - the engine's own dirty set (which node each mutation came from), which
20
+ // covers commits Yoga's flag does not imply: a re-measured leaf whose rect
21
+ // is identical still has to recommit, because measuring reset its widget
22
+ // size request.
7
23
  export class LayoutEngine {
8
24
  root;
9
25
  viewport;
10
26
  dirty = false;
11
27
  scheduled = false;
12
28
  disposed = false;
29
+ // Nodes mutated since the last flush (setStyle/insertChild/removeChild/
30
+ // markDirty/setMeasureFn). Cleared by every flush.
31
+ dirtyNodes = new Set();
32
+ // Set when the engine is dirtied by something that is not a single node —
33
+ // a viewport change or a speculative measureContent pass, both of which can
34
+ // move anything — and for the first flush, where nothing has a rect yet.
35
+ walkAll = true;
13
36
  constructor(viewport) {
14
37
  this.viewport = viewport;
15
38
  this.root = new LayoutNode(this.requestFlush);
@@ -23,6 +46,7 @@ export class LayoutEngine {
23
46
  return;
24
47
  }
25
48
  this.viewport = viewport;
49
+ this.walkAll = true;
26
50
  this.requestFlush();
27
51
  }
28
52
  getViewport() {
@@ -35,8 +59,36 @@ export class LayoutEngine {
35
59
  return;
36
60
  }
37
61
  this.dirty = false;
62
+ const start = perfEnabled ? perfNow() : 0;
38
63
  this.root.yoga.calculateLayout(this.viewport.width, this.viewport.height, Direction.LTR);
64
+ if (perfEnabled) {
65
+ perfAddTime("engine.yoga", perfNow() - start);
66
+ }
39
67
  this.commitTree(this.root);
68
+ if (perfEnabled) {
69
+ const elapsed = perfNow() - start;
70
+ perfAddTime("engine.flush", elapsed);
71
+ perfBurst("engine.flushMsPerFrame", elapsed);
72
+ }
73
+ }
74
+ // Intrinsic content size for measure vfuncs: lays the tree out WITHOUT
75
+ // committing widget rects (GTK forbids allocation during measure) and
76
+ // leaves the engine dirty so the next allocate-time flush recomputes
77
+ // against the real viewport instead of these speculative constraints.
78
+ measureContent(orientation, forSize) {
79
+ if (this.disposed) {
80
+ return 0;
81
+ }
82
+ const width = orientation === "vertical" && forSize > 0 ? forSize : undefined;
83
+ this.root.yoga.calculateLayout(width, undefined, Direction.LTR);
84
+ const size = orientation === "horizontal"
85
+ ? this.root.yoga.getComputedWidth()
86
+ : this.root.yoga.getComputedHeight();
87
+ this.dirty = true;
88
+ // Speculative constraints may have re-measured leaves anywhere in the
89
+ // tree; the next real flush has to re-commit all of them.
90
+ this.walkAll = true;
91
+ return Math.ceil(size);
40
92
  }
41
93
  dispose() {
42
94
  if (this.disposed) {
@@ -44,9 +96,16 @@ export class LayoutEngine {
44
96
  }
45
97
  this.disposed = true;
46
98
  this.freeTree(this.root);
99
+ this.dirtyNodes.clear();
47
100
  }
48
- requestFlush = () => {
101
+ requestFlush = (node) => {
49
102
  this.dirty = true;
103
+ if (node === undefined) {
104
+ this.walkAll = true;
105
+ }
106
+ else {
107
+ this.dirtyNodes.add(node);
108
+ }
50
109
  if (this.scheduled || this.disposed) {
51
110
  return;
52
111
  }
@@ -60,7 +119,17 @@ export class LayoutEngine {
60
119
  // positioned inside an already-sized container.
61
120
  commitTree(node) {
62
121
  const entries = [];
63
- this.collectChanges(node, entries);
122
+ const walkAll = this.walkAll;
123
+ const visited = this.collectChanges(node, entries, walkAll, walkAll ? null : this.dirtyPath());
124
+ // Mutations made by the callbacks below belong to the NEXT flush.
125
+ this.walkAll = false;
126
+ this.dirtyNodes.clear();
127
+ if (perfEnabled) {
128
+ perfCount("engine.flushes");
129
+ perfCount("engine.commits", entries.length);
130
+ perfCount("engine.visited", visited);
131
+ perfGauge("engine.nodes", getLiveNodeCount());
132
+ }
64
133
  for (const entry of entries) {
65
134
  entry.node.notifyCommit(entry.rect);
66
135
  }
@@ -71,14 +140,37 @@ export class LayoutEngine {
71
140
  }
72
141
  }
73
142
  }
74
- collectChanges(node, out) {
143
+ // Every dirty node plus its ancestors: the paths the walk must follow even
144
+ // where Yoga reports no new layout. Nodes already detached from the root
145
+ // (freed mid-tick) simply lead nowhere — the walk never reaches them.
146
+ dirtyPath() {
147
+ const path = new Set();
148
+ for (const node of this.dirtyNodes) {
149
+ let current = node;
150
+ while (current !== null && !path.has(current)) {
151
+ path.add(current);
152
+ current = current.parent;
153
+ }
154
+ }
155
+ return path;
156
+ }
157
+ // Returns the number of visited nodes (perf: the walk size per flush).
158
+ collectChanges(node, out, walkAll, path) {
75
159
  const change = node.collectChange();
76
160
  if (change !== null) {
77
161
  out.push({ node, rect: change.rect, changed: change.changed });
78
162
  }
163
+ // Consume the flag: only nodes Yoga lays out again will raise it anew.
164
+ node.yoga.markLayoutSeen();
165
+ let visited = 1;
79
166
  for (const child of node.children) {
80
- this.collectChanges(child, out);
167
+ if (walkAll ||
168
+ child.yoga.hasNewLayout() ||
169
+ (path !== null && path.has(child))) {
170
+ visited += this.collectChanges(child, out, walkAll, path);
171
+ }
81
172
  }
173
+ return visited;
82
174
  }
83
175
  freeTree(node) {
84
176
  for (const child of [...node.children]) {
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/layout/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Rect } from \"../contracts\"\nimport { LayoutNode } from \"./node\"\nimport { Direction } from \"./yoga\"\n\nexport type ViewportSize = { width: number; height: number }\n\n// One engine per window root. Batches every mutation (style, tree, measure\n// invalidation) into a single Yoga pass per microtask, then walks the shadow\n// tree, diffs rects and fires commit (widget move) + onLayout callbacks only\n// for nodes whose rect actually changed.\nexport class LayoutEngine {\n readonly root: LayoutNode\n\n private viewport: ViewportSize\n private dirty = false\n private scheduled = false\n private disposed = false\n\n constructor(viewport: ViewportSize) {\n this.viewport = viewport\n this.root = new LayoutNode(this.requestFlush)\n }\n\n createNode(): LayoutNode {\n return new LayoutNode(this.requestFlush)\n }\n\n setViewport(viewport: ViewportSize): void {\n if (\n viewport.width === this.viewport.width &&\n viewport.height === this.viewport.height\n ) {\n return\n }\n this.viewport = viewport\n this.requestFlush()\n }\n\n getViewport(): ViewportSize {\n return this.viewport\n }\n\n // Synchronous pass — used by tests and by resize handling when the caller\n // needs rects immediately after a mutation.\n flushSync(): void {\n if (!this.dirty || this.disposed) {\n return\n }\n this.dirty = false\n this.root.yoga.calculateLayout(\n this.viewport.width,\n this.viewport.height,\n Direction.LTR,\n )\n this.commitTree(this.root)\n }\n\n dispose(): void {\n if (this.disposed) {\n return\n }\n this.disposed = true\n this.freeTree(this.root)\n }\n\n private requestFlush = (): void => {\n this.dirty = true\n if (this.scheduled || this.disposed) {\n return\n }\n this.scheduled = true\n queueMicrotask(() => {\n this.scheduled = false\n this.flushSync()\n })\n }\n\n // Pre-order walk: parents commit before children so a child is always\n // positioned inside an already-sized container.\n private commitTree(node: LayoutNode): void {\n const entries: Array<{ node: LayoutNode; rect: Rect; changed: boolean }> =\n []\n this.collectChanges(node, entries)\n for (const entry of entries) {\n entry.node.notifyCommit(entry.rect)\n }\n // onLayout fires after the whole pass is committed, like React Native.\n for (const entry of entries) {\n if (entry.changed) {\n entry.node.notifyLayout(entry.rect)\n }\n }\n }\n\n private collectChanges(\n node: LayoutNode,\n out: Array<{ node: LayoutNode; rect: Rect; changed: boolean }>,\n ): void {\n const change = node.collectChange()\n if (change !== null) {\n out.push({ node, rect: change.rect, changed: change.changed })\n }\n for (const child of node.children) {\n this.collectChanges(child, out)\n }\n }\n\n private freeTree(node: LayoutNode): void {\n for (const child of [...node.children]) {\n this.freeTree(child)\n }\n node.free()\n }\n}\n"]}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/layout/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Rect } from \"../contracts\"\nimport {\n perfAddTime,\n perfBurst,\n perfCount,\n perfEnabled,\n perfGauge,\n perfNow,\n} from \"../perf\"\nimport { getLiveNodeCount, LayoutNode } from \"./node\"\nimport { Direction } from \"./yoga\"\n\nexport type ViewportSize = { width: number; height: number }\n\n// One engine per window root. Batches every mutation (style, tree, measure\n// invalidation) into a single Yoga pass per microtask, then walks the shadow\n// tree, diffs rects and fires commit (widget move) + onLayout callbacks only\n// for nodes whose rect actually changed.\n//\n// The commit walk is INCREMENTAL: it descends only where this pass can have\n// changed something, so a small mutation inside a large stable shell costs\n// O(changed) instead of O(all nodes). Two signals drive the descent, and both\n// are needed:\n// - Yoga's per-node `hasNewLayout` flag, which Yoga sets on every node it\n// actually laid out. It covers the nodes a dirty set cannot know about:\n// mutating one child re-lays out its FOLLOWING SIBLINGS (they shift) and\n// any ancestor whose size followed, while untouched subtrees keep their\n// cached layout — and their rects are parent-relative, so an unvisited\n// subtree of a moved container is genuinely unchanged.\n// - the engine's own dirty set (which node each mutation came from), which\n// covers commits Yoga's flag does not imply: a re-measured leaf whose rect\n// is identical still has to recommit, because measuring reset its widget\n// size request.\nexport class LayoutEngine {\n readonly root: LayoutNode\n\n private viewport: ViewportSize\n private dirty = false\n private scheduled = false\n private disposed = false\n // Nodes mutated since the last flush (setStyle/insertChild/removeChild/\n // markDirty/setMeasureFn). Cleared by every flush.\n private dirtyNodes = new Set<LayoutNode>()\n // Set when the engine is dirtied by something that is not a single node —\n // a viewport change or a speculative measureContent pass, both of which can\n // move anything — and for the first flush, where nothing has a rect yet.\n private walkAll = true\n\n constructor(viewport: ViewportSize) {\n this.viewport = viewport\n this.root = new LayoutNode(this.requestFlush)\n }\n\n createNode(): LayoutNode {\n return new LayoutNode(this.requestFlush)\n }\n\n setViewport(viewport: ViewportSize): void {\n if (\n viewport.width === this.viewport.width &&\n viewport.height === this.viewport.height\n ) {\n return\n }\n this.viewport = viewport\n this.walkAll = true\n this.requestFlush()\n }\n\n getViewport(): ViewportSize {\n return this.viewport\n }\n\n // Synchronous pass — used by tests and by resize handling when the caller\n // needs rects immediately after a mutation.\n flushSync(): void {\n if (!this.dirty || this.disposed) {\n return\n }\n this.dirty = false\n const start = perfEnabled ? perfNow() : 0\n this.root.yoga.calculateLayout(\n this.viewport.width,\n this.viewport.height,\n Direction.LTR,\n )\n if (perfEnabled) {\n perfAddTime(\"engine.yoga\", perfNow() - start)\n }\n this.commitTree(this.root)\n if (perfEnabled) {\n const elapsed = perfNow() - start\n perfAddTime(\"engine.flush\", elapsed)\n perfBurst(\"engine.flushMsPerFrame\", elapsed)\n }\n }\n\n // Intrinsic content size for measure vfuncs: lays the tree out WITHOUT\n // committing widget rects (GTK forbids allocation during measure) and\n // leaves the engine dirty so the next allocate-time flush recomputes\n // against the real viewport instead of these speculative constraints.\n measureContent(\n orientation: \"horizontal\" | \"vertical\",\n forSize: number,\n ): number {\n if (this.disposed) {\n return 0\n }\n const width =\n orientation === \"vertical\" && forSize > 0 ? forSize : undefined\n this.root.yoga.calculateLayout(width, undefined, Direction.LTR)\n const size =\n orientation === \"horizontal\"\n ? this.root.yoga.getComputedWidth()\n : this.root.yoga.getComputedHeight()\n this.dirty = true\n // Speculative constraints may have re-measured leaves anywhere in the\n // tree; the next real flush has to re-commit all of them.\n this.walkAll = true\n return Math.ceil(size)\n }\n\n dispose(): void {\n if (this.disposed) {\n return\n }\n this.disposed = true\n this.freeTree(this.root)\n this.dirtyNodes.clear()\n }\n\n private requestFlush = (node?: LayoutNode): void => {\n this.dirty = true\n if (node === undefined) {\n this.walkAll = true\n } else {\n this.dirtyNodes.add(node)\n }\n if (this.scheduled || this.disposed) {\n return\n }\n this.scheduled = true\n queueMicrotask(() => {\n this.scheduled = false\n this.flushSync()\n })\n }\n\n // Pre-order walk: parents commit before children so a child is always\n // positioned inside an already-sized container.\n private commitTree(node: LayoutNode): void {\n const entries: Array<{ node: LayoutNode; rect: Rect; changed: boolean }> =\n []\n const walkAll = this.walkAll\n const visited = this.collectChanges(\n node,\n entries,\n walkAll,\n walkAll ? null : this.dirtyPath(),\n )\n // Mutations made by the callbacks below belong to the NEXT flush.\n this.walkAll = false\n this.dirtyNodes.clear()\n if (perfEnabled) {\n perfCount(\"engine.flushes\")\n perfCount(\"engine.commits\", entries.length)\n perfCount(\"engine.visited\", visited)\n perfGauge(\"engine.nodes\", getLiveNodeCount())\n }\n for (const entry of entries) {\n entry.node.notifyCommit(entry.rect)\n }\n // onLayout fires after the whole pass is committed, like React Native.\n for (const entry of entries) {\n if (entry.changed) {\n entry.node.notifyLayout(entry.rect)\n }\n }\n }\n\n // Every dirty node plus its ancestors: the paths the walk must follow even\n // where Yoga reports no new layout. Nodes already detached from the root\n // (freed mid-tick) simply lead nowhere — the walk never reaches them.\n private dirtyPath(): Set<LayoutNode> {\n const path = new Set<LayoutNode>()\n for (const node of this.dirtyNodes) {\n let current: LayoutNode | null = node\n while (current !== null && !path.has(current)) {\n path.add(current)\n current = current.parent\n }\n }\n return path\n }\n\n // Returns the number of visited nodes (perf: the walk size per flush).\n private collectChanges(\n node: LayoutNode,\n out: Array<{ node: LayoutNode; rect: Rect; changed: boolean }>,\n walkAll: boolean,\n path: Set<LayoutNode> | null,\n ): number {\n const change = node.collectChange()\n if (change !== null) {\n out.push({ node, rect: change.rect, changed: change.changed })\n }\n // Consume the flag: only nodes Yoga lays out again will raise it anew.\n node.yoga.markLayoutSeen()\n let visited = 1\n for (const child of node.children) {\n if (\n walkAll ||\n child.yoga.hasNewLayout() ||\n (path !== null && path.has(child))\n ) {\n visited += this.collectChanges(child, out, walkAll, path)\n }\n }\n return visited\n }\n\n private freeTree(node: LayoutNode): void {\n for (const child of [...node.children]) {\n this.freeTree(child)\n }\n node.free()\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { LayoutNodeApi, LayoutStyle, MeasureFn, Rect } from "../contracts";
2
2
  import { type YogaNode } from "./yoga";
3
- type DirtyListener = () => void;
3
+ type DirtyListener = (node: LayoutNode) => void;
4
4
  export declare const getLiveNodeCount: () => number;
5
5
  export declare class LayoutNode implements LayoutNodeApi {
6
6
  readonly yoga: YogaNode;
@@ -1,3 +1,4 @@
1
+ import { perfCount } from "../perf";
1
2
  import { applyLayoutStyle, applyNodeDefaults } from "./apply-style";
2
3
  import { createYogaNode, MEASURE_MODE } from "./yoga";
3
4
  let liveNodeCount = 0;
@@ -22,13 +23,14 @@ export class LayoutNode {
22
23
  applyNodeDefaults(this.yoga);
23
24
  this.onDirty = onDirty;
24
25
  liveNodeCount += 1;
26
+ perfCount("node.create");
25
27
  }
26
28
  setStyle(style) {
27
29
  if (this.freed) {
28
30
  return;
29
31
  }
30
32
  applyLayoutStyle(this.yoga, style);
31
- this.onDirty();
33
+ this.onDirty(this);
32
34
  }
33
35
  setMeasureFn(measure) {
34
36
  if (this.freed) {
@@ -46,7 +48,7 @@ export class LayoutNode {
46
48
  this.yoga.setMeasureFunc((width, widthMode, height, heightMode) => measure(width, MEASURE_MODE[widthMode], height, MEASURE_MODE[heightMode]));
47
49
  this.hasMeasure = true;
48
50
  }
49
- this.onDirty();
51
+ this.onDirty(this);
50
52
  }
51
53
  markDirty() {
52
54
  // Only measure-backed leaves may be marked dirty in Yoga.
@@ -57,7 +59,7 @@ export class LayoutNode {
57
59
  // Re-measuring resets the widget size request; even an unchanged rect
58
60
  // must recommit so the request is re-applied.
59
61
  this.forceCommit = true;
60
- this.onDirty();
62
+ this.onDirty(this);
61
63
  }
62
64
  getRect() {
63
65
  return this.lastRect;
@@ -81,7 +83,7 @@ export class LayoutNode {
81
83
  child.parent = this;
82
84
  this.children.splice(index, 0, child);
83
85
  this.yoga.insertChild(child.yoga, index);
84
- this.onDirty();
86
+ this.onDirty(this);
85
87
  }
86
88
  removeChild(child) {
87
89
  const index = this.children.indexOf(child);
@@ -93,7 +95,7 @@ export class LayoutNode {
93
95
  // Touching the wasm node of a freed parent corrupts the emscripten heap.
94
96
  if (!this.freed && !child.freed) {
95
97
  this.yoga.removeChild(child.yoga);
96
- this.onDirty();
98
+ this.onDirty(this);
97
99
  }
98
100
  }
99
101
  // Reads the computed rect (parent-relative). Measured leaves always
@@ -146,6 +148,7 @@ export class LayoutNode {
146
148
  this.freed = true;
147
149
  this.yoga.free();
148
150
  liveNodeCount -= 1;
151
+ perfCount("node.free");
149
152
  }
150
153
  }
151
154
  //# sourceMappingURL=node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/layout/node.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LayoutNodeApi, LayoutStyle, MeasureFn, Rect } from \"../contracts\"\nimport { applyLayoutStyle, applyNodeDefaults } from \"./apply-style\"\nimport { createYogaNode, MEASURE_MODE, type YogaNode } from \"./yoga\"\n\ntype DirtyListener = () => void\n\nlet liveNodeCount = 0\n\n// Number of not-yet-freed nodes; used by tests to detect leaks.\nexport const getLiveNodeCount = (): number => liveNodeCount\n\n// Shadow-tree node: owns one Yoga node, mirrors the React component tree.\n// Components talk to it through the LayoutNodeApi contract; the engine walks\n// it after every layout pass to diff rects and notify commit/onLayout hooks.\nexport class LayoutNode implements LayoutNodeApi {\n readonly yoga: YogaNode\n parent: LayoutNode | null = null\n readonly children: LayoutNode[] = []\n\n private onDirty: DirtyListener\n private onLayoutCallback: ((rect: Rect) => void) | null = null\n private commitCallback: ((rect: Rect) => void) | null = null\n private lastRect: Rect | null = null\n private freed = false\n private hasMeasure = false\n private forceCommit = false\n\n constructor(onDirty: DirtyListener) {\n this.yoga = createYogaNode()\n applyNodeDefaults(this.yoga)\n this.onDirty = onDirty\n liveNodeCount += 1\n }\n\n setStyle(style: LayoutStyle): void {\n if (this.freed) {\n return\n }\n applyLayoutStyle(this.yoga, style)\n this.onDirty()\n }\n\n setMeasureFn(measure: MeasureFn | null): void {\n if (this.freed) {\n return\n }\n if (measure === null) {\n // Unsetting an absent measure func destroys a null wasm binding and\n // crashes yoga-wasm — track presence explicitly.\n if (this.hasMeasure) {\n this.yoga.unsetMeasureFunc()\n this.hasMeasure = false\n }\n } else {\n this.yoga.setMeasureFunc((width, widthMode, height, heightMode) =>\n measure(\n width,\n MEASURE_MODE[widthMode],\n height,\n MEASURE_MODE[heightMode],\n ),\n )\n this.hasMeasure = true\n }\n this.onDirty()\n }\n\n markDirty(): void {\n // Only measure-backed leaves may be marked dirty in Yoga.\n if (this.freed || !this.hasMeasure) {\n return\n }\n this.yoga.markDirty()\n // Re-measuring resets the widget size request; even an unchanged rect\n // must recommit so the request is re-applied.\n this.forceCommit = true\n this.onDirty()\n }\n\n getRect(): Rect | null {\n return this.lastRect\n }\n\n setOnLayout(callback: ((rect: Rect) => void) | null): void {\n this.onLayoutCallback = callback\n }\n\n // The commit hook is the write path to GTK: the component layer moves its\n // widget's stored rect when the engine reports a new one.\n setCommit(callback: ((rect: Rect) => void) | null): void {\n this.commitCallback = callback\n // A late-registered commit hook must still receive the current rect.\n if (callback !== null && this.lastRect !== null) {\n callback(this.lastRect)\n }\n }\n\n insertChild(child: LayoutNode, index: number): void {\n if (this.freed || child.freed) {\n return\n }\n child.parent = this\n this.children.splice(index, 0, child)\n this.yoga.insertChild(child.yoga, index)\n this.onDirty()\n }\n\n removeChild(child: LayoutNode): void {\n const index = this.children.indexOf(child)\n if (index === -1) {\n return\n }\n this.children.splice(index, 1)\n child.parent = null\n // Touching the wasm node of a freed parent corrupts the emscripten heap.\n if (!this.freed && !child.freed) {\n this.yoga.removeChild(child.yoga)\n this.onDirty()\n }\n }\n\n // Reads the computed rect (parent-relative). Measured leaves always\n // recommit: any Yoga-invoked re-measure resets their widget size request,\n // and Yoga may re-measure without markDirty (constraint changes), so the\n // request must be re-applied after every pass. onLayout still fires only\n // on real changes.\n collectChange(): { rect: Rect; changed: boolean } | null {\n const rect: Rect = {\n x: this.yoga.getComputedLeft(),\n y: this.yoga.getComputedTop(),\n width: this.yoga.getComputedWidth(),\n height: this.yoga.getComputedHeight(),\n }\n const previous = this.lastRect\n const unchanged =\n previous !== null &&\n previous.x === rect.x &&\n previous.y === rect.y &&\n previous.width === rect.width &&\n previous.height === rect.height\n const changed = this.forceCommit || !unchanged\n this.forceCommit = false\n if (!changed && !this.hasMeasure) {\n return null\n }\n this.lastRect = rect\n return { rect, changed }\n }\n\n notifyCommit(rect: Rect): void {\n this.commitCallback?.(rect)\n }\n\n notifyLayout(rect: Rect): void {\n this.onLayoutCallback?.(rect)\n }\n\n free(): void {\n if (this.freed) {\n return\n }\n this.parent?.removeChild(this)\n // React unmounts parents before children (effect cleanup order), so a\n // container can be freed while its children are still linked — detach\n // them first or their own free() would touch freed wasm memory.\n for (const child of [...this.children]) {\n this.removeChild(child)\n }\n if (this.hasMeasure) {\n this.yoga.unsetMeasureFunc()\n this.hasMeasure = false\n }\n this.freed = true\n this.yoga.free()\n liveNodeCount -= 1\n }\n}\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/layout/node.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LayoutNodeApi, LayoutStyle, MeasureFn, Rect } from \"../contracts\"\nimport { perfCount } from \"../perf\"\nimport { applyLayoutStyle, applyNodeDefaults } from \"./apply-style\"\nimport { createYogaNode, MEASURE_MODE, type YogaNode } from \"./yoga\"\n\n// The engine records WHICH node was mutated so a flush can walk only the\n// dirty subtrees instead of the whole live tree.\ntype DirtyListener = (node: LayoutNode) => void\n\nlet liveNodeCount = 0\n\n// Number of not-yet-freed nodes; used by tests to detect leaks.\nexport const getLiveNodeCount = (): number => liveNodeCount\n\n// Shadow-tree node: owns one Yoga node, mirrors the React component tree.\n// Components talk to it through the LayoutNodeApi contract; the engine walks\n// it after every layout pass to diff rects and notify commit/onLayout hooks.\nexport class LayoutNode implements LayoutNodeApi {\n readonly yoga: YogaNode\n parent: LayoutNode | null = null\n readonly children: LayoutNode[] = []\n\n private onDirty: DirtyListener\n private onLayoutCallback: ((rect: Rect) => void) | null = null\n private commitCallback: ((rect: Rect) => void) | null = null\n private lastRect: Rect | null = null\n private freed = false\n private hasMeasure = false\n private forceCommit = false\n\n constructor(onDirty: DirtyListener) {\n this.yoga = createYogaNode()\n applyNodeDefaults(this.yoga)\n this.onDirty = onDirty\n liveNodeCount += 1\n perfCount(\"node.create\")\n }\n\n setStyle(style: LayoutStyle): void {\n if (this.freed) {\n return\n }\n applyLayoutStyle(this.yoga, style)\n this.onDirty(this)\n }\n\n setMeasureFn(measure: MeasureFn | null): void {\n if (this.freed) {\n return\n }\n if (measure === null) {\n // Unsetting an absent measure func destroys a null wasm binding and\n // crashes yoga-wasm — track presence explicitly.\n if (this.hasMeasure) {\n this.yoga.unsetMeasureFunc()\n this.hasMeasure = false\n }\n } else {\n this.yoga.setMeasureFunc((width, widthMode, height, heightMode) =>\n measure(\n width,\n MEASURE_MODE[widthMode],\n height,\n MEASURE_MODE[heightMode],\n ),\n )\n this.hasMeasure = true\n }\n this.onDirty(this)\n }\n\n markDirty(): void {\n // Only measure-backed leaves may be marked dirty in Yoga.\n if (this.freed || !this.hasMeasure) {\n return\n }\n this.yoga.markDirty()\n // Re-measuring resets the widget size request; even an unchanged rect\n // must recommit so the request is re-applied.\n this.forceCommit = true\n this.onDirty(this)\n }\n\n getRect(): Rect | null {\n return this.lastRect\n }\n\n setOnLayout(callback: ((rect: Rect) => void) | null): void {\n this.onLayoutCallback = callback\n }\n\n // The commit hook is the write path to GTK: the component layer moves its\n // widget's stored rect when the engine reports a new one.\n setCommit(callback: ((rect: Rect) => void) | null): void {\n this.commitCallback = callback\n // A late-registered commit hook must still receive the current rect.\n if (callback !== null && this.lastRect !== null) {\n callback(this.lastRect)\n }\n }\n\n insertChild(child: LayoutNode, index: number): void {\n if (this.freed || child.freed) {\n return\n }\n child.parent = this\n this.children.splice(index, 0, child)\n this.yoga.insertChild(child.yoga, index)\n this.onDirty(this)\n }\n\n removeChild(child: LayoutNode): void {\n const index = this.children.indexOf(child)\n if (index === -1) {\n return\n }\n this.children.splice(index, 1)\n child.parent = null\n // Touching the wasm node of a freed parent corrupts the emscripten heap.\n if (!this.freed && !child.freed) {\n this.yoga.removeChild(child.yoga)\n this.onDirty(this)\n }\n }\n\n // Reads the computed rect (parent-relative). Measured leaves always\n // recommit: any Yoga-invoked re-measure resets their widget size request,\n // and Yoga may re-measure without markDirty (constraint changes), so the\n // request must be re-applied after every pass. onLayout still fires only\n // on real changes.\n collectChange(): { rect: Rect; changed: boolean } | null {\n const rect: Rect = {\n x: this.yoga.getComputedLeft(),\n y: this.yoga.getComputedTop(),\n width: this.yoga.getComputedWidth(),\n height: this.yoga.getComputedHeight(),\n }\n const previous = this.lastRect\n const unchanged =\n previous !== null &&\n previous.x === rect.x &&\n previous.y === rect.y &&\n previous.width === rect.width &&\n previous.height === rect.height\n const changed = this.forceCommit || !unchanged\n this.forceCommit = false\n if (!changed && !this.hasMeasure) {\n return null\n }\n this.lastRect = rect\n return { rect, changed }\n }\n\n notifyCommit(rect: Rect): void {\n this.commitCallback?.(rect)\n }\n\n notifyLayout(rect: Rect): void {\n this.onLayoutCallback?.(rect)\n }\n\n free(): void {\n if (this.freed) {\n return\n }\n this.parent?.removeChild(this)\n // React unmounts parents before children (effect cleanup order), so a\n // container can be freed while its children are still linked — detach\n // them first or their own free() would touch freed wasm memory.\n for (const child of [...this.children]) {\n this.removeChild(child)\n }\n if (this.hasMeasure) {\n this.yoga.unsetMeasureFunc()\n this.hasMeasure = false\n }\n this.freed = true\n this.yoga.free()\n liveNodeCount -= 1\n perfCount(\"node.free\")\n }\n}\n"]}
@@ -35,6 +35,7 @@ export const HOST_MODULE_EXTERNALS = [
35
35
  "@gtkx/gi/gsk",
36
36
  "@gtkx/gi/gtk",
37
37
  "@gtkx/gi/pango",
38
+ "@gtkx/jsx/adw",
38
39
  "@gtkx/jsx/gtk",
39
40
  "@gtkx/react",
40
41
  "@gtkx/react/internal",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/metro/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,4EAA4E;AAC5E,sBAAsB;AACtB,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,qEAAqE;AACrE,sCAAsC;AACtC,0EAA0E;AAC1E,yEAAyE;AACzE,wCAAwC;AACxC,2EAA2E;AAC3E,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,+CAA+C;AAC/C,oEAAoE;AACpE,oEAAoE;AACpE,2EAA2E;AAC3E,wDAAwD;AACxD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,oEAAoE;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,WAAW;IACX,cAAc;IACd,cAAc;IACd,cAAc;IACd,eAAe;IACf,mBAAmB;IACnB,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,eAAe;IACf,aAAa;IACb,sBAAsB;IACtB,eAAe;IACf,OAAO;IACP,mBAAmB;IACnB,uBAAuB;IACvB,aAAa;CACd,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAA;AAgCrC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,KAAK,CAAA;AAE9E,MAAM,eAAe,GAAG,CAAC,IAAY,EAAU,EAAE;AAC/C,2EAA2E;AAC3E,yCAAyC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAA;AAEpE,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAC5C,yCAAyC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAA;AAEpE,2EAA2E;AAC3E,yEAAyE;AACzE,uEAAuE;AACvE,sCAAsC;AACtC,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,SAAsB,EAAQ,EAAE;IACpE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,CAAA;IACjE,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5D,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,EACnC,YAAY,CAAC,QAAQ,IAAI,EAAE,CAAC,CAC7B,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAYD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAS,EACT,UAAgC,EAAE,EACV,EAAE;IAC1B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;QACxB,GAAG,qBAAqB;QACxB,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;KAC7B,CAAC,CAAA;IACF,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ;QAChB,IAAI,CACF,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,EACnC,cAAc,EACd,oBAAoB,EACpB,iBAAiB,CAClB,CAAA;IACH,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;IACpC,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,EAAE,cAAc,IAAI,IAAI,CAAA;IAE/D,MAAM,QAAQ,GAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,CAChE,eAAe;QACb,CAAC,CAAC,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;QAChD,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAA;IAE3D,MAAM,cAAc,GAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;QACtE,IAAI,QAAQ,KAAK,cAAc,EAAE,CAAC;YAChC,OAAO,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,OAAO;gBACL,IAAI,EAAE,YAAY;gBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;aAC/C,CAAA;QACH,CAAC;QACD,IACE,UAAU,KAAK,cAAc;YAC7B,UAAU,CAAC,UAAU,CAAC,eAAe,CAAC,EACtC,CAAC;YACD,OAAO,QAAQ,CACb,OAAO,EACP,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,mBAAmB,CAAC,EACxD,QAAQ,CACT,CAAA;QACH,CAAC;QACD,OAAO,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAA;IAED,yEAAyE;IACzE,iEAAiE;IACjE,OAAO;QACL,GAAG,MAAM;QACT,QAAQ,EAAE;YACR,GAAG,MAAM,CAAC,QAAQ;YAClB,SAAS,EAAE;gBACT,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;aACpE;YACD,cAAc;SACf;QACD,UAAU,EAAE;YACV,GAAG,MAAM,CAAC,UAAU;YACpB,gEAAgE;YAChE,4DAA4D;YAC5D,gCAAgC;YAChC,6BAA6B,EAAE,GAAG,EAAE,CAAC,EAAE;SACxC;KACwB,CAAA;AAC7B,CAAC,CAAA","sourcesContent":["// Metro preset for the linux out-of-tree platform: wrap the app's Metro\n// config with `withLinuxPlatform` and the standard RN toolchain bundles for\n// `--platform linux`.\n//\n// What it does on top of RN defaults (validated by spike/rn-platform):\n//\n// 1. Adds \"linux\" to resolver.platforms (`.linux.tsx` extensions and\n// Platform.OS come along with it).\n// 2. Redirects `react-native` (and subpaths) to `react-native-gtkx` — the\n// out-of-tree npmPackageName declaration alone does not alias imports\n// for `bundle`, the resolver has to.\n// 3. EXTERNALIZES host-side singletons. Unlike react-native-windows, whose\n// native side lives outside the JS bundle by construction, our \"native\"\n// is Node modules with NAPI bindings (@gtkx/*) plus yoga-layout's WASM —\n// Metro cannot bundle those. react (and its jsx runtimes) must also stay\n// host-side: the reconciler inside @gtkx/react and the app have to share\n// one React instance. Every external resolves to a generated proxy that\n// reads the real module from global.__hostModules — the run-linux host\n// injects them before executing the bundle.\n// 4. Proxies Node builtins through the host's require: apps on this\n// platform may use the whole Node API, it is a platform feature.\n// 5. Drops InitializeCore (RN's mobile environment polyfills): the runtime\n// environment IS Node, the host provides everything.\nimport { mkdirSync, writeFileSync } from \"node:fs\"\nimport { builtinModules, isBuiltin } from \"node:module\"\nimport { join } from \"node:path\"\n\n/** Modules the run-linux host provides to the bundle at runtime. */\nexport const HOST_MODULE_EXTERNALS = [\n \"@gtkx/css\",\n \"@gtkx/gi/adw\",\n \"@gtkx/gi/gdk\",\n \"@gtkx/gi/gio\",\n \"@gtkx/gi/glib\",\n \"@gtkx/gi/graphene\",\n \"@gtkx/gi/gsk\",\n \"@gtkx/gi/gtk\",\n \"@gtkx/gi/pango\",\n \"@gtkx/jsx/gtk\",\n \"@gtkx/react\",\n \"@gtkx/react/internal\",\n \"@gtkx/runtime\",\n \"react\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n \"yoga-layout\",\n]\n\nexport const LINUX_PLATFORM = \"linux\"\n\n// Structural slices of Metro's config/resolution types: Metro is the app's\n// dependency, not ours — depending on its types here would pin versions.\nexport type MetroResolutionContext = {\n resolveRequest: MetroResolver\n}\nexport type MetroResolution = { type: string; filePath?: string }\nexport type MetroResolver = (\n context: MetroResolutionContext,\n moduleName: string,\n platform: string | null,\n) => MetroResolution\n\nexport type MetroLikeConfig = {\n projectRoot?: string\n resolver?: {\n platforms?: readonly string[]\n resolveRequest?: MetroResolver | null\n }\n serializer?: {\n getModulesRunBeforeMainModule?: (entryFilePath: string) => string[]\n }\n}\n\nexport type LinuxPlatformOptions = {\n /** Extra host-provided modules to keep out of the bundle. */\n externals?: readonly string[]\n /** Proxy directory override (default: node_modules/.react-native-gtkx). */\n proxyDir?: string\n}\n\nconst sanitize = (name: string): string => name.replace(/[@/:]/g, \"_\") + \".js\"\n\nconst hostModuleProxy = (name: string): string =>\n // The run-linux host guarantees __hostModules before executing the bundle.\n `module.exports = global.__hostModules[${JSON.stringify(name)}]\\n`\n\nconst builtinProxy = (name: string): string =>\n `module.exports = global.__hostRequire(${JSON.stringify(name)})\\n`\n\n// Every proxy is generated eagerly when the config loads: Metro crawls the\n// file map before transforming, and files that appear mid-build lose the\n// race (\"Failed to get the SHA-1\"). The builtin list is finite, so the\n// whole proxy set can exist up front.\nconst generateProxies = (dir: string, externals: Set<string>): void => {\n mkdirSync(dir, { recursive: true })\n for (const name of externals) {\n writeFileSync(join(dir, sanitize(name)), hostModuleProxy(name))\n }\n for (const bare of builtinModules) {\n writeFileSync(join(dir, sanitize(bare)), builtinProxy(bare))\n writeFileSync(\n join(dir, sanitize(`node:${bare}`)),\n builtinProxy(`node:${bare}`),\n )\n }\n}\n\nexport type LinuxPlatformConfig<T extends MetroLikeConfig> = T & {\n resolver: {\n platforms: string[]\n resolveRequest: MetroResolver\n }\n serializer: {\n getModulesRunBeforeMainModule: (entryFilePath: string) => string[]\n }\n}\n\n/**\n * Wraps a Metro config (RN's getDefaultConfig output) with everything the\n * linux platform needs. Composes with an existing custom resolveRequest —\n * non-linux platforms fall through to it untouched.\n */\nexport const withLinuxPlatform = <T extends MetroLikeConfig>(\n config: T,\n options: LinuxPlatformOptions = {},\n): LinuxPlatformConfig<T> => {\n const externals = new Set([\n ...HOST_MODULE_EXTERNALS,\n ...(options.externals ?? []),\n ])\n const proxyDir =\n options.proxyDir ??\n join(\n config.projectRoot ?? process.cwd(),\n \"node_modules\",\n \".react-native-gtkx\",\n \"metro-externals\",\n )\n generateProxies(proxyDir, externals)\n const previousResolve = config.resolver?.resolveRequest ?? null\n\n const fallback: MetroResolver = (context, moduleName, platform) =>\n previousResolve\n ? previousResolve(context, moduleName, platform)\n : context.resolveRequest(context, moduleName, platform)\n\n const resolveRequest: MetroResolver = (context, moduleName, platform) => {\n if (platform !== LINUX_PLATFORM) {\n return fallback(context, moduleName, platform)\n }\n if (externals.has(moduleName) || isBuiltin(moduleName)) {\n return {\n type: \"sourceFile\",\n filePath: join(proxyDir, sanitize(moduleName)),\n }\n }\n if (\n moduleName === \"react-native\" ||\n moduleName.startsWith(\"react-native/\")\n ) {\n return fallback(\n context,\n moduleName.replace(/^react-native/, \"react-native-gtkx\"),\n platform,\n )\n }\n return fallback(context, moduleName, platform)\n }\n\n // The cast: T may carry narrower resolver/serializer types than the ones\n // rebuilt here — the runtime shape is a strict superset of both.\n return {\n ...config,\n resolver: {\n ...config.resolver,\n platforms: [\n ...new Set([...(config.resolver?.platforms ?? []), LINUX_PLATFORM]),\n ],\n resolveRequest,\n },\n serializer: {\n ...config.serializer,\n // RN prepends InitializeCore (Hermes Promise, nativeLoggingHook\n // console...). Our runtime environment IS Node — nothing to\n // initialize inside the bundle.\n getModulesRunBeforeMainModule: () => [],\n },\n } as LinuxPlatformConfig<T>\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/metro/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Metro preset for the linux out-of-tree platform: wrap the app's Metro\n// config with `withLinuxPlatform` and the standard RN toolchain bundles for\n// `--platform linux`.\n//\n// What it does on top of RN defaults (validated by spike/rn-platform):\n//\n// 1. Adds \"linux\" to resolver.platforms (`.linux.tsx` extensions and\n// Platform.OS come along with it).\n// 2. Redirects `react-native` (and subpaths) to `react-native-gtkx` — the\n// out-of-tree npmPackageName declaration alone does not alias imports\n// for `bundle`, the resolver has to.\n// 3. EXTERNALIZES host-side singletons. Unlike react-native-windows, whose\n// native side lives outside the JS bundle by construction, our \"native\"\n// is Node modules with NAPI bindings (@gtkx/*) plus yoga-layout's WASM —\n// Metro cannot bundle those. react (and its jsx runtimes) must also stay\n// host-side: the reconciler inside @gtkx/react and the app have to share\n// one React instance. Every external resolves to a generated proxy that\n// reads the real module from global.__hostModules — the run-linux host\n// injects them before executing the bundle.\n// 4. Proxies Node builtins through the host's require: apps on this\n// platform may use the whole Node API, it is a platform feature.\n// 5. Drops InitializeCore (RN's mobile environment polyfills): the runtime\n// environment IS Node, the host provides everything.\nimport { mkdirSync, writeFileSync } from \"node:fs\"\nimport { builtinModules, isBuiltin } from \"node:module\"\nimport { join } from \"node:path\"\n\n/** Modules the run-linux host provides to the bundle at runtime. */\nexport const HOST_MODULE_EXTERNALS = [\n \"@gtkx/css\",\n \"@gtkx/gi/adw\",\n \"@gtkx/gi/gdk\",\n \"@gtkx/gi/gio\",\n \"@gtkx/gi/glib\",\n \"@gtkx/gi/graphene\",\n \"@gtkx/gi/gsk\",\n \"@gtkx/gi/gtk\",\n \"@gtkx/gi/pango\",\n \"@gtkx/jsx/adw\",\n \"@gtkx/jsx/gtk\",\n \"@gtkx/react\",\n \"@gtkx/react/internal\",\n \"@gtkx/runtime\",\n \"react\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n \"yoga-layout\",\n]\n\nexport const LINUX_PLATFORM = \"linux\"\n\n// Structural slices of Metro's config/resolution types: Metro is the app's\n// dependency, not ours — depending on its types here would pin versions.\nexport type MetroResolutionContext = {\n resolveRequest: MetroResolver\n}\nexport type MetroResolution = { type: string; filePath?: string }\nexport type MetroResolver = (\n context: MetroResolutionContext,\n moduleName: string,\n platform: string | null,\n) => MetroResolution\n\nexport type MetroLikeConfig = {\n projectRoot?: string\n resolver?: {\n platforms?: readonly string[]\n resolveRequest?: MetroResolver | null\n }\n serializer?: {\n getModulesRunBeforeMainModule?: (entryFilePath: string) => string[]\n }\n}\n\nexport type LinuxPlatformOptions = {\n /** Extra host-provided modules to keep out of the bundle. */\n externals?: readonly string[]\n /** Proxy directory override (default: node_modules/.react-native-gtkx). */\n proxyDir?: string\n}\n\nconst sanitize = (name: string): string => name.replace(/[@/:]/g, \"_\") + \".js\"\n\nconst hostModuleProxy = (name: string): string =>\n // The run-linux host guarantees __hostModules before executing the bundle.\n `module.exports = global.__hostModules[${JSON.stringify(name)}]\\n`\n\nconst builtinProxy = (name: string): string =>\n `module.exports = global.__hostRequire(${JSON.stringify(name)})\\n`\n\n// Every proxy is generated eagerly when the config loads: Metro crawls the\n// file map before transforming, and files that appear mid-build lose the\n// race (\"Failed to get the SHA-1\"). The builtin list is finite, so the\n// whole proxy set can exist up front.\nconst generateProxies = (dir: string, externals: Set<string>): void => {\n mkdirSync(dir, { recursive: true })\n for (const name of externals) {\n writeFileSync(join(dir, sanitize(name)), hostModuleProxy(name))\n }\n for (const bare of builtinModules) {\n writeFileSync(join(dir, sanitize(bare)), builtinProxy(bare))\n writeFileSync(\n join(dir, sanitize(`node:${bare}`)),\n builtinProxy(`node:${bare}`),\n )\n }\n}\n\nexport type LinuxPlatformConfig<T extends MetroLikeConfig> = T & {\n resolver: {\n platforms: string[]\n resolveRequest: MetroResolver\n }\n serializer: {\n getModulesRunBeforeMainModule: (entryFilePath: string) => string[]\n }\n}\n\n/**\n * Wraps a Metro config (RN's getDefaultConfig output) with everything the\n * linux platform needs. Composes with an existing custom resolveRequest —\n * non-linux platforms fall through to it untouched.\n */\nexport const withLinuxPlatform = <T extends MetroLikeConfig>(\n config: T,\n options: LinuxPlatformOptions = {},\n): LinuxPlatformConfig<T> => {\n const externals = new Set([\n ...HOST_MODULE_EXTERNALS,\n ...(options.externals ?? []),\n ])\n const proxyDir =\n options.proxyDir ??\n join(\n config.projectRoot ?? process.cwd(),\n \"node_modules\",\n \".react-native-gtkx\",\n \"metro-externals\",\n )\n generateProxies(proxyDir, externals)\n const previousResolve = config.resolver?.resolveRequest ?? null\n\n const fallback: MetroResolver = (context, moduleName, platform) =>\n previousResolve\n ? previousResolve(context, moduleName, platform)\n : context.resolveRequest(context, moduleName, platform)\n\n const resolveRequest: MetroResolver = (context, moduleName, platform) => {\n if (platform !== LINUX_PLATFORM) {\n return fallback(context, moduleName, platform)\n }\n if (externals.has(moduleName) || isBuiltin(moduleName)) {\n return {\n type: \"sourceFile\",\n filePath: join(proxyDir, sanitize(moduleName)),\n }\n }\n if (\n moduleName === \"react-native\" ||\n moduleName.startsWith(\"react-native/\")\n ) {\n return fallback(\n context,\n moduleName.replace(/^react-native/, \"react-native-gtkx\"),\n platform,\n )\n }\n return fallback(context, moduleName, platform)\n }\n\n // The cast: T may carry narrower resolver/serializer types than the ones\n // rebuilt here — the runtime shape is a strict superset of both.\n return {\n ...config,\n resolver: {\n ...config.resolver,\n platforms: [\n ...new Set([...(config.resolver?.platforms ?? []), LINUX_PLATFORM]),\n ],\n resolveRequest,\n },\n serializer: {\n ...config.serializer,\n // RN prepends InitializeCore (Hermes Promise, nativeLoggingHook\n // console...). Our runtime environment IS Node — nothing to\n // initialize inside the bundle.\n getModulesRunBeforeMainModule: () => [],\n },\n } as LinuxPlatformConfig<T>\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { type NavigationProp, type ParamListBase, type RouteProp, type StackNavigationState } from "@react-navigation/native";
2
+ import { type ComponentType, type ReactNode } from "react";
3
+ import type { HeaderButton } from "./sidebar";
4
+ export type StackNavigationOptions = {
5
+ /** HeaderBar title; defaults to the route name. */
6
+ title?: string;
7
+ /** Render the Adwaita HeaderBar for this screen (default true). */
8
+ headerShown?: boolean;
9
+ /** Buttons packed at the end of this screen's HeaderBar (see
10
+ * HeaderButton); screens usually set them via navigation.setOptions. */
11
+ headerButtons?: HeaderButton[];
12
+ /** RN content packed at the start of the HeaderBar (an intrinsic-size
13
+ * layout root: the content's Yoga size IS the slot size). */
14
+ headerLeft?: () => ReactNode;
15
+ /** RN content packed at the end of the HeaderBar, before headerButtons. */
16
+ headerRight?: () => ReactNode;
17
+ /** false disables the native back button, Escape and the back gesture
18
+ * for this screen (the page's Adwaita can-pop). Programmatic goBack
19
+ * still works. Also the mechanism behind usePreventRemove. */
20
+ gestureEnabled?: boolean;
21
+ };
22
+ type StackNavigatorProps = {
23
+ initialRouteName?: string;
24
+ screenOptions?: StackNavigationOptions;
25
+ children: ReactNode;
26
+ };
27
+ export type StackNavigationHelpers<ParamList extends ParamListBase = ParamListBase, RouteName extends keyof ParamList = keyof ParamList> = NavigationProp<ParamList, RouteName, undefined, StackNavigationState<ParamList>, StackNavigationOptions>;
28
+ export type StackScreenProps<ParamList extends ParamListBase = ParamListBase, RouteName extends keyof ParamList = keyof ParamList> = {
29
+ route: RouteProp<ParamList, RouteName>;
30
+ navigation: StackNavigationHelpers<ParamList, RouteName>;
31
+ };
32
+ export type StackScreenConfig<ParamList extends ParamListBase, RouteName extends keyof ParamList> = {
33
+ name: RouteName;
34
+ component: ComponentType<StackScreenProps<ParamList, RouteName>>;
35
+ options?: StackNavigationOptions | ((props: StackScreenProps<ParamList, RouteName>) => StackNavigationOptions);
36
+ initialParams?: Partial<ParamList[RouteName]>;
37
+ };
38
+ export type TypedStackNavigator<ParamList extends ParamListBase> = {
39
+ Navigator: ComponentType<StackNavigatorProps>;
40
+ Screen: <RouteName extends keyof ParamList>(props: StackScreenConfig<ParamList, RouteName>) => null;
41
+ };
42
+ export declare const createStackNavigator: <ParamList extends ParamListBase = ParamListBase>() => TypedStackNavigator<ParamList>;
43
+ export { createSidebarNavigator, type HeaderButton, type SidebarNavigationOptions, type SidebarScreenConfig, type SidebarScreenProps, type TypedSidebarNavigator, } from "./sidebar";
44
+ export { CommonActions, NavigationContainer, StackActions, useFocusEffect, useIsFocused, useNavigation, useNavigationContainerRef, usePreventRemove, useRoute, } from "@react-navigation/native";