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

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 (257) hide show
  1. package/README.md +22 -9
  2. package/dist/adw/index.d.ts +3 -0
  3. package/dist/adw/index.js +41 -0
  4. package/dist/adw/index.js.map +1 -0
  5. package/dist/adw/widgets.generated.d.ts +63 -0
  6. package/dist/adw/widgets.generated.js +125 -0
  7. package/dist/adw/widgets.generated.js.map +1 -0
  8. package/dist/animated/create-animated.d.ts +2 -0
  9. package/dist/animated/create-animated.js +2 -0
  10. package/dist/animated/create-animated.js.map +1 -1
  11. package/dist/animated/index.d.ts +2 -0
  12. package/dist/animated/index.js +1 -0
  13. package/dist/animated/index.js.map +1 -1
  14. package/dist/animated/value-xy.d.ts +41 -0
  15. package/dist/animated/value-xy.js +90 -0
  16. package/dist/animated/value-xy.js.map +1 -0
  17. package/dist/animated/value.d.ts +9 -0
  18. package/dist/animated/value.js +30 -2
  19. package/dist/animated/value.js.map +1 -1
  20. package/dist/common/content.d.ts +69 -0
  21. package/dist/common/content.js +84 -0
  22. package/dist/common/content.js.map +1 -0
  23. package/dist/common/icon.d.ts +23 -0
  24. package/dist/common/icon.js +14 -0
  25. package/dist/common/icon.js.map +1 -0
  26. package/dist/common/index.d.ts +5 -0
  27. package/dist/common/index.js +21 -0
  28. package/dist/common/index.js.map +1 -0
  29. package/dist/common/list.d.ts +82 -0
  30. package/dist/common/list.js +166 -0
  31. package/dist/common/list.js.map +1 -0
  32. package/dist/common/navigation-stack.d.ts +84 -0
  33. package/dist/common/navigation-stack.js +313 -0
  34. package/dist/common/navigation-stack.js.map +1 -0
  35. package/dist/common/widget.d.ts +62 -0
  36. package/dist/common/widget.js +209 -0
  37. package/dist/common/widget.js.map +1 -0
  38. package/dist/components/animated.d.ts +7 -4
  39. package/dist/components/animated.js +88 -67
  40. package/dist/components/animated.js.map +1 -1
  41. package/dist/components/app-registry.d.ts +36 -1
  42. package/dist/components/app-registry.js +8 -3
  43. package/dist/components/app-registry.js.map +1 -1
  44. package/dist/components/host-node.d.ts +7 -0
  45. package/dist/components/host-node.js +26 -1
  46. package/dist/components/host-node.js.map +1 -1
  47. package/dist/components/index.d.ts +6 -2
  48. package/dist/components/index.js +2 -1
  49. package/dist/components/index.js.map +1 -1
  50. package/dist/components/measure.d.ts +13 -0
  51. package/dist/components/measure.js +51 -0
  52. package/dist/components/measure.js.map +1 -0
  53. package/dist/components/modal.d.ts +1 -1
  54. package/dist/components/press-event.d.ts +28 -0
  55. package/dist/components/press-event.js +46 -0
  56. package/dist/components/press-event.js.map +1 -0
  57. package/dist/components/pressable.d.ts +2 -6
  58. package/dist/components/pressable.js +119 -20
  59. package/dist/components/pressable.js.map +1 -1
  60. package/dist/components/rect-store.d.ts +11 -1
  61. package/dist/components/rect-store.js +27 -3
  62. package/dist/components/rect-store.js.map +1 -1
  63. package/dist/components/scroll-view.d.ts +4 -0
  64. package/dist/components/scroll-view.js +8 -2
  65. package/dist/components/scroll-view.js.map +1 -1
  66. package/dist/components/svg/animated-support.d.ts +19 -0
  67. package/dist/components/svg/animated-support.js +64 -0
  68. package/dist/components/svg/animated-support.js.map +1 -0
  69. package/dist/components/svg/context.d.ts +8 -0
  70. package/dist/components/svg/context.js +15 -0
  71. package/dist/components/svg/context.js.map +1 -0
  72. package/dist/components/svg/gradients.d.ts +31 -0
  73. package/dist/components/svg/gradients.js +93 -0
  74. package/dist/components/svg/gradients.js.map +1 -0
  75. package/dist/components/svg/group.d.ts +8 -0
  76. package/dist/components/svg/group.js +30 -0
  77. package/dist/components/svg/group.js.map +1 -0
  78. package/dist/components/svg/index.d.ts +6 -0
  79. package/dist/components/svg/index.js +5 -0
  80. package/dist/components/svg/index.js.map +1 -0
  81. package/dist/components/svg/node.d.ts +7 -0
  82. package/dist/components/svg/node.js +7 -0
  83. package/dist/components/svg/node.js.map +1 -0
  84. package/dist/components/svg/paint.d.ts +24 -0
  85. package/dist/components/svg/paint.js +57 -0
  86. package/dist/components/svg/paint.js.map +1 -0
  87. package/dist/components/svg/shapes.d.ts +47 -0
  88. package/dist/components/svg/shapes.js +51 -0
  89. package/dist/components/svg/shapes.js.map +1 -0
  90. package/dist/components/svg/svg.d.ts +14 -0
  91. package/dist/components/svg/svg.js +48 -0
  92. package/dist/components/svg/svg.js.map +1 -0
  93. package/dist/components/svg/use-shape-node.d.ts +12 -0
  94. package/dist/components/svg/use-shape-node.js +52 -0
  95. package/dist/components/svg/use-shape-node.js.map +1 -0
  96. package/dist/components/text-input.js +1 -1
  97. package/dist/components/text-input.js.map +1 -1
  98. package/dist/components/text.js +36 -3
  99. package/dist/components/text.js.map +1 -1
  100. package/dist/components/use-layout-child.js +50 -4
  101. package/dist/components/use-layout-child.js.map +1 -1
  102. package/dist/components/view.d.ts +9 -4
  103. package/dist/components/view.js +8 -2
  104. package/dist/components/view.js.map +1 -1
  105. package/dist/contracts.d.ts +31 -4
  106. package/dist/contracts.js.map +1 -1
  107. package/dist/gtk/index.d.ts +7 -0
  108. package/dist/gtk/index.js +46 -0
  109. package/dist/gtk/index.js.map +1 -0
  110. package/dist/gtk/widgets.generated.d.ts +103 -0
  111. package/dist/gtk/widgets.generated.js +205 -0
  112. package/dist/gtk/widgets.generated.js.map +1 -0
  113. package/dist/gtk/window-actions.d.ts +77 -0
  114. package/dist/gtk/window-actions.js +193 -0
  115. package/dist/gtk/window-actions.js.map +1 -0
  116. package/dist/gtkx/bridge/geometry.d.ts +15 -0
  117. package/dist/gtkx/bridge/geometry.js +38 -0
  118. package/dist/gtkx/bridge/geometry.js.map +1 -0
  119. package/dist/gtkx/bridge/index.d.ts +11 -4
  120. package/dist/gtkx/bridge/index.js +47 -6
  121. package/dist/gtkx/bridge/index.js.map +1 -1
  122. package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
  123. package/dist/gtkx/bridge/layout-manager.js +31 -9
  124. package/dist/gtkx/bridge/layout-manager.js.map +1 -1
  125. package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
  126. package/dist/gtkx/bridge/slot-portal.js +17 -0
  127. package/dist/gtkx/bridge/slot-portal.js.map +1 -0
  128. package/dist/gtkx/bridge/svg-node.d.ts +79 -0
  129. package/dist/gtkx/bridge/svg-node.js +323 -0
  130. package/dist/gtkx/bridge/svg-node.js.map +1 -0
  131. package/dist/gtkx/bridge/use-signal.js +19 -9
  132. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  133. package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
  134. package/dist/gtkx/bridge/widgets.generated.js +14 -0
  135. package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
  136. package/dist/index.d.ts +2 -2
  137. package/dist/index.js +1 -1
  138. package/dist/index.js.map +1 -1
  139. package/dist/mcp/bin.d.ts +2 -0
  140. package/dist/mcp/bin.js +17 -0
  141. package/dist/mcp/bin.js.map +1 -0
  142. package/dist/mcp/data/generated.d.ts +1304 -0
  143. package/dist/mcp/data/generated.js +1179 -0
  144. package/dist/mcp/data/generated.js.map +1 -0
  145. package/dist/mcp/index.d.ts +5 -0
  146. package/dist/mcp/index.js +9 -0
  147. package/dist/mcp/index.js.map +1 -0
  148. package/dist/mcp/list.d.ts +4 -0
  149. package/dist/mcp/list.js +56 -0
  150. package/dist/mcp/list.js.map +1 -0
  151. package/dist/mcp/resolve.d.ts +55 -0
  152. package/dist/mcp/resolve.js +151 -0
  153. package/dist/mcp/resolve.js.map +1 -0
  154. package/dist/mcp/search.d.ts +13 -0
  155. package/dist/mcp/search.js +48 -0
  156. package/dist/mcp/search.js.map +1 -0
  157. package/dist/mcp/server.d.ts +7 -0
  158. package/dist/mcp/server.js +135 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/mcp/tool.d.ts +21 -0
  161. package/dist/mcp/tool.js +36 -0
  162. package/dist/mcp/tool.js.map +1 -0
  163. package/dist/metro/index.js +10 -0
  164. package/dist/metro/index.js.map +1 -1
  165. package/dist/navigation/index.d.ts +50 -13
  166. package/dist/navigation/index.js +183 -148
  167. package/dist/navigation/index.js.map +1 -1
  168. package/dist/navigation/option-warnings.js +9 -1
  169. package/dist/navigation/option-warnings.js.map +1 -1
  170. package/dist/navigation/sidebar.d.ts +207 -9
  171. package/dist/navigation/sidebar.js +294 -10
  172. package/dist/navigation/sidebar.js.map +1 -1
  173. package/dist/responder/system.d.ts +27 -0
  174. package/dist/responder/system.js +184 -0
  175. package/dist/responder/system.js.map +1 -0
  176. package/dist/responder/touch-history.d.ts +10 -0
  177. package/dist/responder/touch-history.js +78 -0
  178. package/dist/responder/touch-history.js.map +1 -0
  179. package/dist/responder/types.d.ts +63 -0
  180. package/dist/responder/types.js +29 -0
  181. package/dist/responder/types.js.map +1 -0
  182. package/dist/responder/use-responder.d.ts +12 -0
  183. package/dist/responder/use-responder.js +98 -0
  184. package/dist/responder/use-responder.js.map +1 -0
  185. package/dist/runner/host-dev.js +3 -1
  186. package/dist/runner/host-dev.js.map +1 -1
  187. package/dist/runner/host.js +5 -1
  188. package/dist/runner/host.js.map +1 -1
  189. package/dist/runner/index.d.ts +22 -2
  190. package/dist/runner/index.js +163 -13
  191. package/dist/runner/index.js.map +1 -1
  192. package/dist/sea/assemble.d.ts +29 -0
  193. package/dist/sea/assemble.js +129 -0
  194. package/dist/sea/assemble.js.map +1 -0
  195. package/dist/sea/bundle.d.ts +61 -0
  196. package/dist/sea/bundle.js +202 -0
  197. package/dist/sea/bundle.js.map +1 -0
  198. package/dist/sea/gtkx-config-module.d.ts +18 -0
  199. package/dist/sea/gtkx-config-module.js +67 -0
  200. package/dist/sea/gtkx-config-module.js.map +1 -0
  201. package/dist/sea/native-shim.d.ts +40 -0
  202. package/dist/sea/native-shim.js +121 -0
  203. package/dist/sea/native-shim.js.map +1 -0
  204. package/dist/sea/react-anchor-plugin.d.ts +2 -0
  205. package/dist/sea/react-anchor-plugin.js +30 -0
  206. package/dist/sea/react-anchor-plugin.js.map +1 -0
  207. package/dist/style/box-shadow.d.ts +7 -0
  208. package/dist/style/box-shadow.js +197 -0
  209. package/dist/style/box-shadow.js.map +1 -0
  210. package/dist/style/index.d.ts +3 -0
  211. package/dist/style/index.js +3 -0
  212. package/dist/style/index.js.map +1 -1
  213. package/dist/style/split-style.d.ts +5 -2
  214. package/dist/style/split-style.js +33 -5
  215. package/dist/style/split-style.js.map +1 -1
  216. package/dist/style/text-decoration.d.ts +15 -0
  217. package/dist/style/text-decoration.js +26 -0
  218. package/dist/style/text-decoration.js.map +1 -0
  219. package/dist/style/transform.d.ts +22 -0
  220. package/dist/style/transform.js +137 -0
  221. package/dist/style/transform.js.map +1 -0
  222. package/dist/style/visual-css.js +40 -3
  223. package/dist/style/visual-css.js.map +1 -1
  224. package/dist/svg/geometry.d.ts +19 -0
  225. package/dist/svg/geometry.js +80 -0
  226. package/dist/svg/geometry.js.map +1 -0
  227. package/dist/svg/gradient-geometry.d.ts +15 -0
  228. package/dist/svg/gradient-geometry.js +15 -0
  229. package/dist/svg/gradient-geometry.js.map +1 -0
  230. package/dist/svg/transform.d.ts +22 -0
  231. package/dist/svg/transform.js +74 -0
  232. package/dist/svg/transform.js.map +1 -0
  233. package/dist/svg/view-box.d.ts +34 -0
  234. package/dist/svg/view-box.js +108 -0
  235. package/dist/svg/view-box.js.map +1 -0
  236. package/dist/svg-compat/index.d.ts +1 -0
  237. package/dist/svg-compat/index.js +16 -0
  238. package/dist/svg-compat/index.js.map +1 -0
  239. package/dist/testing/index.d.ts +14 -0
  240. package/dist/testing/index.js +14 -0
  241. package/dist/testing/index.js.map +1 -0
  242. package/dist/vendor/react-native/pan-responder.d.ts +102 -0
  243. package/dist/vendor/react-native/pan-responder.js +196 -0
  244. package/dist/vendor/react-native/pan-responder.js.map +1 -0
  245. package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
  246. package/dist/vendor/react-native/touch-history-math.js +102 -0
  247. package/dist/vendor/react-native/touch-history-math.js.map +1 -0
  248. package/dist/vite/index.d.ts +7 -3
  249. package/dist/vite/index.js +16 -4
  250. package/dist/vite/index.js.map +1 -1
  251. package/dist/vitest/index.d.ts +52 -0
  252. package/dist/vitest/index.js +58 -0
  253. package/dist/vitest/index.js.map +1 -0
  254. package/dist/vitest/setup.d.ts +4 -0
  255. package/dist/vitest/setup.js +22 -0
  256. package/dist/vitest/setup.js.map +1 -0
  257. package/package.json +52 -13
@@ -0,0 +1,209 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Making a raw GTK widget a first-class citizen of React Native layout.
3
+ //
4
+ // The widgets this subpath re-exports are real GTK widgets, and GTK widgets
5
+ // know nothing about Yoga. Dropped into a <View> as-is they would never be
6
+ // measured or positioned, which would make "we export everything" a hollow
7
+ // promise. `Widget` closes that gap: it gives the widget a Yoga node, applies
8
+ // the layout half of a React Native style to it, and — this is the part that
9
+ // matters — measures the widget itself, so it lands at the size the GTK theme
10
+ // actually wants instead of collapsing to zero.
11
+ //
12
+ // The visual half of the style (background, border, radius) is applied as a
13
+ // GTK CSS class, the same mechanism View uses.
14
+ import { isValidElement, useContext, useRef, } from "react";
15
+ import { HostNodeContext, SlotContext, } from "../components/host-node";
16
+ import { useLayoutChild, } from "../components/use-layout-child";
17
+ import { GtkBox } from "../gtkx/bridge/index";
18
+ /**
19
+ * Put any GTK widget into React Native layout.
20
+ *
21
+ * ```tsx
22
+ * <View style={{ flexDirection: "row", gap: 8, padding: 12 }}>
23
+ * <Widget style={{ flex: 1 }}>
24
+ * <GtkEntry placeholderText="Search" />
25
+ * </Widget>
26
+ * <Widget>
27
+ * <GtkButton iconName="edit-find-symbolic" />
28
+ * </Widget>
29
+ * </View>
30
+ * ```
31
+ *
32
+ * The entry flexes, the button takes its natural size, both sit in the row
33
+ * exactly like React Native children — because to Yoga they now are.
34
+ *
35
+ * For the reverse direction (React Native content inside a GTK slot) see
36
+ * `SlotContent` and `IntrinsicContent`.
37
+ */
38
+ export const Widget = ({ style, onLayout, children, testID }) => {
39
+ // The wrapper box IS the Yoga leaf, and it is measured through GTK, so the
40
+ // widget inside reports its natural size up to Yoga. Wrapping rather than
41
+ // cloning keeps the child's own ref and props untouched.
42
+ const boxRef = useRef(null);
43
+ const { cssClass } = useLayoutChild(boxRef, {
44
+ style,
45
+ onLayout,
46
+ // Without this a widget with no explicit size would measure as zero —
47
+ // the whole reason a naked GtkButton in a View looks broken.
48
+ measureFromWidget: true,
49
+ });
50
+ return (_jsx(GtkBox, { ref: boxRef, name: testID, cssClasses: cssClass ? [cssClass] : undefined, children: children }));
51
+ };
52
+ /**
53
+ * The same thing as {@link Widget}, without the extra component — attach the
54
+ * ref yourself when you already hold one.
55
+ *
56
+ * ```tsx
57
+ * const ref = useRef<Gtk.Button | null>(null)
58
+ * useWidgetLayout(ref, { style: { width: 40, height: 40 } })
59
+ * return <GtkButton ref={ref} iconName="open-menu-symbolic" />
60
+ * ```
61
+ *
62
+ * Returns the GTK CSS class produced by the visual half of the style, or
63
+ * null — pass it to the widget's `cssClasses` if you want backgrounds and
64
+ * borders from the style to apply.
65
+ */
66
+ export const useWidgetLayout = (ref, options = {}) => {
67
+ const { cssClass } = useLayoutChild(ref, {
68
+ style: options.style,
69
+ onLayout: options.onLayout,
70
+ measureFromWidget: true,
71
+ });
72
+ return cssClass;
73
+ };
74
+ /**
75
+ * Give a GTK component React Native layout, keeping every prop it already
76
+ * has.
77
+ *
78
+ * ```tsx
79
+ * const Button = wrapReactNative(GtkButton)
80
+ *
81
+ * <View style={{ flexDirection: "row", gap: 8 }}>
82
+ * <Button style={{ flex: 1 }} label="Save" onClicked={save} />
83
+ * </View>
84
+ * ```
85
+ *
86
+ * The result is generic in the wrapped component's props, so `label`,
87
+ * `onClicked` and everything else gtkx binds keep their types; `style` and
88
+ * `onLayout` are added on top.
89
+ *
90
+ * **Outside React Native layout it steps aside.** The same widget is often
91
+ * used in a pure GTK slot — a HeaderBar's `start`, a ToolbarView's `topBar` —
92
+ * where there is no Yoga tree to join. The wrapper detects that and renders
93
+ * the bare component, so one exported symbol works in both worlds.
94
+ */
95
+ // A wrapped widget is a Yoga LEAF, so everything INSIDE it is GTK's
96
+ // territory — its children and its slots alike.
97
+ //
98
+ // A widget SLOT is a property that takes a widget (`titleWidget`, `sheet`,
99
+ // `sidebar`, `startChild`); a widget's content area is an ordinary child.
100
+ // The distinction is gtkx's, it moves between releases — rc.3 took the
101
+ // `content`/`child` props off single-child widgets and made that content a
102
+ // child — and it has never had anything to do with layout. Both keep
103
+ // rendering where they were written, so both keep seeing the enclosing React
104
+ // Native layout root even though GTK has parented them somewhere else
105
+ // entirely. Content then joins a Yoga tree whose viewport is the WINDOW
106
+ // while GTK gives it the widget's own rectangle: laid out against one box,
107
+ // drawn in another, and silently stealing space from a tree it was never
108
+ // meant to be in.
109
+ //
110
+ // So the boundary clears the layout root on the way in. The widgets a slot
111
+ // or a child usually holds want exactly that (it is what `WidgetContent`
112
+ // does by hand), and React Native content entering has to bring its own root
113
+ // — `SlotContent` to fill the area, `IntrinsicContent` to size the area to
114
+ // itself. Which of the two is right cannot be inferred, and measurement says
115
+ // so louder than argument: `AdwBottomSheet` alone FILLS in its content child
116
+ // but HUGS in both `sheet` (a bottom sheet rises to the height of its own
117
+ // contents) and `bottomBar`. One widget, three content areas, two answers,
118
+ // with nothing in the name or the GIR type to tell them apart — the answer
119
+ // lives in the widget's own layout code. The boundary is what makes NOT
120
+ // saying a readable error instead of a silent mislayout — see useHostNode.
121
+ const SlotBoundary = ({ location, children, }) => (
122
+ // Two providers, no widget: a context provider is invisible to the gtkx
123
+ // reconciler, so this is safe even on slots whose value is NOT a widget
124
+ // (`breakpoints`, `menuModel`, `buffer`, `adjustment` — the majority of
125
+ // element-valued props, in fact) — wrapping those in a real box would put
126
+ // a GtkBox where an AdwBreakpoint belongs.
127
+ _jsx(SlotContext.Provider, { value: location, children: _jsx(HostNodeContext.Provider, { value: null, children: children }) }));
128
+ const holdsElement = (value) => isValidElement(value) || (Array.isArray(value) && value.some(holdsElement));
129
+ // Puts every element-valued prop, and the children, behind a boundary.
130
+ // `ref` is never content; anything that holds no element (a string child, a
131
+ // number, an already-constructed GObject) is left exactly as it was, so the
132
+ // common widget pays nothing.
133
+ const withSlotBoundaries = (props, widget) => {
134
+ let bounded;
135
+ for (const key in props) {
136
+ if (key === "ref") {
137
+ continue;
138
+ }
139
+ const value = props[key];
140
+ if (!holdsElement(value)) {
141
+ continue;
142
+ }
143
+ bounded ??= {};
144
+ bounded[key] = (
145
+ // Children are not a named slot: the error says "inside AdwBottomSheet"
146
+ // rather than naming a property that does not exist.
147
+ _jsx(SlotBoundary, { location: { widget, slot: key === "children" ? null : key }, children: value }));
148
+ }
149
+ // The common case is a widget with no element-valued prop at all: return the
150
+ // props object untouched rather than allocate a copy per render.
151
+ return bounded ? { ...props, ...bounded } : props;
152
+ };
153
+ export const wrapReactNative = (Component,
154
+ // The widget's name, for error messages and React devtools. gtkx builds its
155
+ // components from a factory, so they carry no name of their own — the
156
+ // generated widget surface passes the class name it already knows.
157
+ widgetName) => {
158
+ const name = widgetName ||
159
+ Component.displayName ||
160
+ Component.name ||
161
+ "Widget";
162
+ const InLayout = (props) => {
163
+ const { style, onLayout, ...rest } = props;
164
+ const boxRef = useRef(null);
165
+ const { cssClass } = useLayoutChild(boxRef, {
166
+ style,
167
+ onLayout,
168
+ // Without this a widget with no explicit size measures as zero — the
169
+ // whole reason a naked GtkButton in a View looks broken.
170
+ measureFromWidget: true,
171
+ });
172
+ const inner = rest;
173
+ return (_jsx(GtkBox, { ref: boxRef, children: _jsx(Component, { ...inner,
174
+ // The style's visual half lands on the WIDGET, not on the wrapper,
175
+ // so backgrounds and borders colour the button itself. This is the
176
+ // point: on this platform React Native style drives GTK.
177
+ cssClasses: cssClass
178
+ ? [...(inner.cssClasses ?? []), cssClass]
179
+ : inner.cssClasses,
180
+ // Fill the rect Yoga computed, so `flex: 1` and explicit sizes mean
181
+ // what they mean everywhere else in React Native. An explicit
182
+ // hexpand/vexpand from the caller still wins.
183
+ hexpand: inner.hexpand ?? true, vexpand: inner.vexpand ?? true }) }));
184
+ };
185
+ const Wrapped = (props) => {
186
+ // Every slot this widget is given becomes GTK territory, in both branches
187
+ // below: whether the widget itself joined React Native layout has nothing
188
+ // to do with what its slots hold.
189
+ const withSlots = withSlotBoundaries(props, name);
190
+ // Read the context directly rather than through useHostNode, which throws
191
+ // by design: here its absence is a supported case, not a mistake. The
192
+ // same widget is often used in a pure GTK slot (a HeaderBar's `start`, a
193
+ // ToolbarView's `topBar`) where there is no Yoga tree to join.
194
+ const host = useContext(HostNodeContext);
195
+ if (!host) {
196
+ // style/onLayout are meaningless without a Yoga tree — drop them rather
197
+ // than forward them onto a GObject that has no such properties.
198
+ const rest = { ...withSlots };
199
+ delete rest.style;
200
+ delete rest.onLayout;
201
+ return _jsx(Component, { ...rest });
202
+ }
203
+ return _jsx(InLayout, { ...withSlots });
204
+ };
205
+ InLayout.displayName = `wrapReactNative(${name}).InLayout`;
206
+ Wrapped.displayName = `wrapReactNative(${name})`;
207
+ return Wrapped;
208
+ };
209
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/common/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["// Making a raw GTK widget a first-class citizen of React Native layout.\n//\n// The widgets this subpath re-exports are real GTK widgets, and GTK widgets\n// know nothing about Yoga. Dropped into a <View> as-is they would never be\n// measured or positioned, which would make \"we export everything\" a hollow\n// promise. `Widget` closes that gap: it gives the widget a Yoga node, applies\n// the layout half of a React Native style to it, and — this is the part that\n// matters — measures the widget itself, so it lands at the size the GTK theme\n// actually wants instead of collapsing to zero.\n//\n// The visual half of the style (background, border, radius) is applied as a\n// GTK CSS class, the same mechanism View uses.\nimport {\n isValidElement,\n useContext,\n useRef,\n type ComponentType,\n type ReactNode,\n} from \"react\"\nimport {\n HostNodeContext,\n SlotContext,\n type SlotLocation,\n} from \"../components/host-node\"\nimport {\n useLayoutChild,\n type LayoutEvent,\n} from \"../components/use-layout-child\"\nimport type { StyleProp } from \"../contracts\"\nimport { GtkBox, type Gtk } from \"../gtkx/bridge/index\"\n\nexport type WidgetProps = {\n /** React Native style. The layout half drives Yoga, the visual half becomes\n * a GTK CSS class. Omit sizing and the widget's own natural size wins. */\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n /** The GTK widget(s) to lay out. Your own `ref` on them is untouched. */\n children: ReactNode\n testID?: string\n}\n\n/**\n * Put any GTK widget into React Native layout.\n *\n * ```tsx\n * <View style={{ flexDirection: \"row\", gap: 8, padding: 12 }}>\n * <Widget style={{ flex: 1 }}>\n * <GtkEntry placeholderText=\"Search\" />\n * </Widget>\n * <Widget>\n * <GtkButton iconName=\"edit-find-symbolic\" />\n * </Widget>\n * </View>\n * ```\n *\n * The entry flexes, the button takes its natural size, both sit in the row\n * exactly like React Native children — because to Yoga they now are.\n *\n * For the reverse direction (React Native content inside a GTK slot) see\n * `SlotContent` and `IntrinsicContent`.\n */\nexport const Widget = ({ style, onLayout, children, testID }: WidgetProps) => {\n // The wrapper box IS the Yoga leaf, and it is measured through GTK, so the\n // widget inside reports its natural size up to Yoga. Wrapping rather than\n // cloning keeps the child's own ref and props untouched.\n const boxRef = useRef<Gtk.Box | null>(null)\n const { cssClass } = useLayoutChild(boxRef, {\n style,\n onLayout,\n // Without this a widget with no explicit size would measure as zero —\n // the whole reason a naked GtkButton in a View looks broken.\n measureFromWidget: true,\n })\n\n return (\n <GtkBox\n ref={boxRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : undefined}\n >\n {children}\n </GtkBox>\n )\n}\n\nexport type UseWidgetLayoutOptions = {\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n}\n\n/**\n * The same thing as {@link Widget}, without the extra component — attach the\n * ref yourself when you already hold one.\n *\n * ```tsx\n * const ref = useRef<Gtk.Button | null>(null)\n * useWidgetLayout(ref, { style: { width: 40, height: 40 } })\n * return <GtkButton ref={ref} iconName=\"open-menu-symbolic\" />\n * ```\n *\n * Returns the GTK CSS class produced by the visual half of the style, or\n * null — pass it to the widget's `cssClasses` if you want backgrounds and\n * borders from the style to apply.\n */\nexport const useWidgetLayout = (\n ref: { current: Gtk.Widget | null },\n options: UseWidgetLayoutOptions = {},\n): string | null => {\n const { cssClass } = useLayoutChild(ref, {\n style: options.style,\n onLayout: options.onLayout,\n measureFromWidget: true,\n })\n return cssClass\n}\n\nexport type ReactNativeLayoutProps = {\n /** React Native style. The layout half drives Yoga, the visual half becomes\n * a GTK CSS class. Omit sizing and the widget's natural size wins. */\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n}\n\n/**\n * Give a GTK component React Native layout, keeping every prop it already\n * has.\n *\n * ```tsx\n * const Button = wrapReactNative(GtkButton)\n *\n * <View style={{ flexDirection: \"row\", gap: 8 }}>\n * <Button style={{ flex: 1 }} label=\"Save\" onClicked={save} />\n * </View>\n * ```\n *\n * The result is generic in the wrapped component's props, so `label`,\n * `onClicked` and everything else gtkx binds keep their types; `style` and\n * `onLayout` are added on top.\n *\n * **Outside React Native layout it steps aside.** The same widget is often\n * used in a pure GTK slot — a HeaderBar's `start`, a ToolbarView's `topBar` —\n * where there is no Yoga tree to join. The wrapper detects that and renders\n * the bare component, so one exported symbol works in both worlds.\n */\n// A wrapped widget is a Yoga LEAF, so everything INSIDE it is GTK's\n// territory — its children and its slots alike.\n//\n// A widget SLOT is a property that takes a widget (`titleWidget`, `sheet`,\n// `sidebar`, `startChild`); a widget's content area is an ordinary child.\n// The distinction is gtkx's, it moves between releases — rc.3 took the\n// `content`/`child` props off single-child widgets and made that content a\n// child — and it has never had anything to do with layout. Both keep\n// rendering where they were written, so both keep seeing the enclosing React\n// Native layout root even though GTK has parented them somewhere else\n// entirely. Content then joins a Yoga tree whose viewport is the WINDOW\n// while GTK gives it the widget's own rectangle: laid out against one box,\n// drawn in another, and silently stealing space from a tree it was never\n// meant to be in.\n//\n// So the boundary clears the layout root on the way in. The widgets a slot\n// or a child usually holds want exactly that (it is what `WidgetContent`\n// does by hand), and React Native content entering has to bring its own root\n// — `SlotContent` to fill the area, `IntrinsicContent` to size the area to\n// itself. Which of the two is right cannot be inferred, and measurement says\n// so louder than argument: `AdwBottomSheet` alone FILLS in its content child\n// but HUGS in both `sheet` (a bottom sheet rises to the height of its own\n// contents) and `bottomBar`. One widget, three content areas, two answers,\n// with nothing in the name or the GIR type to tell them apart — the answer\n// lives in the widget's own layout code. The boundary is what makes NOT\n// saying a readable error instead of a silent mislayout — see useHostNode.\nconst SlotBoundary = ({\n location,\n children,\n}: {\n location: SlotLocation\n children: ReactNode\n}) => (\n // Two providers, no widget: a context provider is invisible to the gtkx\n // reconciler, so this is safe even on slots whose value is NOT a widget\n // (`breakpoints`, `menuModel`, `buffer`, `adjustment` — the majority of\n // element-valued props, in fact) — wrapping those in a real box would put\n // a GtkBox where an AdwBreakpoint belongs.\n <SlotContext.Provider value={location}>\n <HostNodeContext.Provider value={null}>{children}</HostNodeContext.Provider>\n </SlotContext.Provider>\n)\n\nconst holdsElement = (value: unknown): boolean =>\n isValidElement(value) || (Array.isArray(value) && value.some(holdsElement))\n\n// Puts every element-valued prop, and the children, behind a boundary.\n// `ref` is never content; anything that holds no element (a string child, a\n// number, an already-constructed GObject) is left exactly as it was, so the\n// common widget pays nothing.\nconst withSlotBoundaries = <P extends object>(props: P, widget: string): P => {\n let bounded: Record<string, unknown> | undefined\n for (const key in props) {\n if (key === \"ref\") {\n continue\n }\n const value = (props as Record<string, unknown>)[key]\n if (!holdsElement(value)) {\n continue\n }\n bounded ??= {}\n bounded[key] = (\n // Children are not a named slot: the error says \"inside AdwBottomSheet\"\n // rather than naming a property that does not exist.\n <SlotBoundary\n location={{ widget, slot: key === \"children\" ? null : key }}\n >\n {value as ReactNode}\n </SlotBoundary>\n )\n }\n // The common case is a widget with no element-valued prop at all: return the\n // props object untouched rather than allocate a copy per render.\n return bounded ? ({ ...props, ...bounded } as P) : props\n}\n\nexport const wrapReactNative = <P extends object>(\n Component: ComponentType<P>,\n // The widget's name, for error messages and React devtools. gtkx builds its\n // components from a factory, so they carry no name of their own — the\n // generated widget surface passes the class name it already knows.\n widgetName?: string,\n): ComponentType<P & ReactNativeLayoutProps> => {\n const name =\n widgetName ||\n (Component as { displayName?: string; name?: string }).displayName ||\n (Component as { name?: string }).name ||\n \"Widget\"\n\n const InLayout = (props: P & ReactNativeLayoutProps) => {\n const { style, onLayout, ...rest } = props\n const boxRef = useRef<Gtk.Box | null>(null)\n const { cssClass } = useLayoutChild(boxRef, {\n style,\n onLayout,\n // Without this a widget with no explicit size measures as zero — the\n // whole reason a naked GtkButton in a View looks broken.\n measureFromWidget: true,\n })\n const inner = rest as P & { cssClasses?: string[] } & {\n hexpand?: boolean\n vexpand?: boolean\n }\n return (\n <GtkBox ref={boxRef}>\n <Component\n {...inner}\n // The style's visual half lands on the WIDGET, not on the wrapper,\n // so backgrounds and borders colour the button itself. This is the\n // point: on this platform React Native style drives GTK.\n cssClasses={\n cssClass\n ? [...(inner.cssClasses ?? []), cssClass]\n : inner.cssClasses\n }\n // Fill the rect Yoga computed, so `flex: 1` and explicit sizes mean\n // what they mean everywhere else in React Native. An explicit\n // hexpand/vexpand from the caller still wins.\n hexpand={inner.hexpand ?? true}\n vexpand={inner.vexpand ?? true}\n />\n </GtkBox>\n )\n }\n\n const Wrapped = (props: P & ReactNativeLayoutProps) => {\n // Every slot this widget is given becomes GTK territory, in both branches\n // below: whether the widget itself joined React Native layout has nothing\n // to do with what its slots hold.\n const withSlots = withSlotBoundaries(props, name)\n // Read the context directly rather than through useHostNode, which throws\n // by design: here its absence is a supported case, not a mistake. The\n // same widget is often used in a pure GTK slot (a HeaderBar's `start`, a\n // ToolbarView's `topBar`) where there is no Yoga tree to join.\n const host = useContext(HostNodeContext)\n if (!host) {\n // style/onLayout are meaningless without a Yoga tree — drop them rather\n // than forward them onto a GObject that has no such properties.\n const rest = { ...withSlots } as Partial<ReactNativeLayoutProps>\n delete rest.style\n delete rest.onLayout\n return <Component {...(rest as P)} />\n }\n return <InLayout {...withSlots} />\n }\n\n InLayout.displayName = `wrapReactNative(${name}).InLayout`\n Wrapped.displayName = `wrapReactNative(${name})`\n return Wrapped\n}\n"]}
@@ -1,12 +1,14 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { FlatStyle, TransformPart } from "../contracts";
3
+ import type { ResponderProps } from "../responder/types";
3
4
  import { type LayoutEvent } from "./use-layout-child";
5
+ type AnimatedValue = number | string;
4
6
  type AnimatedNode = {
5
7
  addListener(callback: (state: {
6
- value: number;
8
+ value: AnimatedValue;
7
9
  }) => void): string;
8
10
  removeListener(id: string): void;
9
- __getValue(): number;
11
+ __getValue(): AnimatedValue;
10
12
  };
11
13
  type AnimatedTransformPart = {
12
14
  [key: string]: number | string | AnimatedNode;
@@ -15,15 +17,16 @@ export type AnimatedViewStyle = Omit<FlatStyle, "opacity" | "transform"> & {
15
17
  opacity?: number | AnimatedNode;
16
18
  transform?: (TransformPart | AnimatedTransformPart)[];
17
19
  };
18
- export type AnimatedViewProps = {
20
+ export type AnimatedViewProps = ResponderProps & {
19
21
  style?: AnimatedViewStyle | AnimatedViewStyle[];
20
22
  children?: ReactNode;
21
23
  onLayout?: (event: LayoutEvent) => void;
22
24
  testID?: string;
23
25
  };
24
26
  export declare const Animated: {
25
- View: ({ style, children, onLayout, testID, }: AnimatedViewProps) => React.JSX.Element;
27
+ View: ({ style, children, onLayout, testID, ...responderProps }: AnimatedViewProps) => React.JSX.Element;
26
28
  Value: typeof import("../animated/value").AnimatedValue;
29
+ ValueXY: typeof import("../animated/value-xy").AnimatedValueXY;
27
30
  timing(value: import("../animated/value").AnimatedValue, config: import("../animated/types").TimingConfig): import("../animated/types").CompositeAnimation;
28
31
  spring(value: import("../animated/value").AnimatedValue, config: import("../animated/types").SpringConfig): import("../animated/types").CompositeAnimation;
29
32
  sequence(animations: import("../animated/types").CompositeAnimation[]): import("../animated/types").CompositeAnimation;
@@ -2,8 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useRef } from "react";
3
3
  import { createAnimated } from "../animated/index";
4
4
  import { GLib, GtkBox, queueAllocate } from "../gtkx/bridge/index";
5
+ import { useResponder } from "../responder/use-responder";
5
6
  import { HostNodeContext } from "./host-node";
6
- import { setStoredOffset } from "./rect-store";
7
+ import { setStoredTransform } from "./rect-store";
7
8
  import { useLayoutChild, useRnContainer, } from "./use-layout-child";
8
9
  // ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per
9
10
  // window) is a later optimization; timeouts keep the driver widget-free.
@@ -29,6 +30,19 @@ const isAnimatedNode = (value) => typeof value === "object" &&
29
30
  value !== null &&
30
31
  typeof value.addListener === "function" &&
31
32
  typeof value.__getValue === "function";
33
+ // Stable per-node identity for the effect's dependency key: Animated nodes
34
+ // are objects, and the bindings must be rebuilt when the node behind a slot
35
+ // changes — not on every render that rebuilds an equal-looking array.
36
+ let nextNodeId = 1;
37
+ const nodeIds = new WeakMap();
38
+ const nodeId = (value) => {
39
+ let id = nodeIds.get(value);
40
+ if (id === undefined) {
41
+ id = nextNodeId++;
42
+ nodeIds.set(value, id);
43
+ }
44
+ return id;
45
+ };
32
46
  const splitAnimated = (style) => {
33
47
  const flat = {};
34
48
  const collect = (entry) => {
@@ -43,105 +57,112 @@ const splitAnimated = (style) => {
43
57
  };
44
58
  collect(style);
45
59
  let opacity = null;
46
- let translateX = null;
47
- let translateY = null;
60
+ const slots = [];
48
61
  if (isAnimatedNode(flat.opacity)) {
49
62
  opacity = flat.opacity;
50
63
  delete flat.opacity;
51
64
  }
52
65
  if (Array.isArray(flat.transform)) {
53
- const staticParts = [];
54
66
  for (const part of flat.transform) {
55
67
  const key = Object.keys(part)[0];
56
- const value = part[key];
57
- if (key === "translateX" && isAnimatedNode(value)) {
58
- translateX = value;
59
- }
60
- else if (key === "translateY" && isAnimatedNode(value)) {
61
- translateY = value;
62
- }
63
- else if (!isAnimatedNode(value)) {
64
- staticParts.push(part);
68
+ if (key === undefined) {
69
+ continue;
65
70
  }
71
+ const value = part[key];
72
+ slots.push(isAnimatedNode(value)
73
+ ? { key, node: value, value: value.__getValue() }
74
+ : { key, node: null, value: value });
66
75
  }
67
- if (staticParts.length > 0) {
68
- flat.transform = staticParts;
69
- }
70
- else {
71
- delete flat.transform;
72
- }
76
+ // The whole array is owned here from now on, static entries included:
77
+ // leaving them in the style would make useLayoutChild write the same
78
+ // rect-store slot from the other side, and the two would overwrite
79
+ // each other every render.
80
+ delete flat.transform;
73
81
  }
74
- return { staticStyle: flat, opacity, translateX, translateY };
82
+ return { staticStyle: flat, opacity, slots };
75
83
  };
76
84
  // Animated values bypass React entirely: listeners write straight to the
77
- // widget (opacity) and to the rect store (translate offsets applied by the
85
+ // widget (opacity) and to the rect store (the transform applied by the
78
86
  // parent's allocate), on top of the engine-committed base rect — the fast
79
87
  // path measured in the spike. Animation frames never touch Yoga.
80
- const AnimatedView = ({ style, children, onLayout, testID, }) => {
88
+ const AnimatedView = ({ style, children, onLayout, testID, ...responderProps }) => {
81
89
  const widgetRef = useRef(null);
82
- const { staticStyle, opacity, translateX, translateY } = splitAnimated(style);
90
+ const { staticStyle, opacity, slots } = splitAnimated(style);
83
91
  const { host, node, cssClass } = useLayoutChild(widgetRef, {
84
92
  style: staticStyle,
85
93
  onLayout,
86
94
  });
87
95
  useRnContainer(widgetRef, node);
88
- const offsets = useRef({ x: 0, y: 0 });
96
+ useResponder(widgetRef, responderProps);
97
+ // The effect reads the slots of the render that (re)armed it; the values
98
+ // inside are then owned by the listeners.
99
+ const slotsRef = useRef(slots);
100
+ slotsRef.current = slots;
101
+ // Rebind on a change of shape (which transforms, in which order), of the
102
+ // node behind an animated entry, or of a static entry's value — not on
103
+ // every render that rebuilds an equal-looking array.
104
+ const bindingKey = (opacity ? `opacity#${nodeId(opacity)}` : "") +
105
+ slots
106
+ .map((slot) => slot.node
107
+ ? `|${slot.key}#${nodeId(slot.node)}`
108
+ : `|${slot.key}=${String(slot.value)}`)
109
+ .join("");
89
110
  useLayoutEffect(() => {
90
- // RN transform semantics: translate is paint-only. The offset goes to
91
- // the rect store unclamped — the parent's measure ignores children, so an
92
- // out-of-bounds child draws past the boundary over its neighbors without
111
+ const widget = widgetRef.current;
112
+ if (!widget) {
113
+ return;
114
+ }
115
+ // The live transform array, mutated in place: an Animated write must
116
+ // stay one numeric store plus one queued allocation — no React render,
117
+ // no Yoga pass, no allocation per frame beyond the composed matrix.
118
+ //
119
+ // RN transform semantics: this is paint-only. The result goes to the
120
+ // rect store unclamped — the parent's measure ignores children, so a
121
+ // transformed child draws past the boundary over its neighbors without
93
122
  // moving a single ancestor.
94
- const applyTranslate = () => {
95
- const widget = widgetRef.current;
123
+ const parts = slotsRef.current.map((slot) => ({ [slot.key]: slot.value }));
124
+ const flush = () => {
125
+ setStoredTransform(widget, parts);
96
126
  const parentWidget = host.widgetRef.current;
97
- if (!widget || !parentWidget) {
98
- return;
127
+ if (parentWidget) {
128
+ queueAllocate(parentWidget);
99
129
  }
100
- setStoredOffset(widget, offsets.current.x, offsets.current.y);
101
- queueAllocate(parentWidget);
102
130
  };
103
- const bindings = [];
131
+ const subscriptions = [];
132
+ slotsRef.current.forEach((slot, index) => {
133
+ if (!slot.node) {
134
+ return;
135
+ }
136
+ const part = parts[index];
137
+ subscriptions.push({
138
+ node: slot.node,
139
+ id: slot.node.addListener(({ value }) => {
140
+ part[slot.key] = value;
141
+ flush();
142
+ }),
143
+ });
144
+ });
104
145
  if (opacity) {
105
- bindings.push({
146
+ const applyOpacity = (value) => {
147
+ const numeric = typeof value === "number" ? value : parseFloat(value);
148
+ widgetRef.current?.setOpacity(Math.min(1, Math.max(0, numeric)));
149
+ };
150
+ subscriptions.push({
106
151
  node: opacity,
107
- apply: (value) => {
108
- widgetRef.current?.setOpacity(Math.min(1, Math.max(0, value)));
109
- },
152
+ id: opacity.addListener(({ value }) => {
153
+ applyOpacity(value);
154
+ }),
110
155
  });
156
+ applyOpacity(opacity.__getValue());
111
157
  }
112
- if (translateX) {
113
- bindings.push({
114
- node: translateX,
115
- apply: (value) => {
116
- offsets.current.x = value;
117
- applyTranslate();
118
- },
119
- });
120
- }
121
- if (translateY) {
122
- bindings.push({
123
- node: translateY,
124
- apply: (value) => {
125
- offsets.current.y = value;
126
- applyTranslate();
127
- },
128
- });
129
- }
130
- const subscriptions = bindings.map((binding) => ({
131
- binding,
132
- id: binding.node.addListener(({ value }) => binding.apply(value)),
133
- }));
134
- for (const { binding } of subscriptions) {
135
- binding.apply(binding.node.__getValue());
136
- }
158
+ flush();
137
159
  return () => {
138
- for (const { binding, id } of subscriptions) {
139
- binding.node.removeListener(id);
160
+ for (const { node: animated, id } of subscriptions) {
161
+ animated.removeListener(id);
140
162
  }
141
163
  };
142
- // Re-bind when the animated node identities change.
143
164
  // eslint-disable-next-line react-hooks/exhaustive-deps
144
- }, [opacity, translateX, translateY, node]);
165
+ }, [bindingKey, node]);
145
166
  return (_jsx(GtkBox, { ref: widgetRef, name: testID, cssClasses: cssClass ? [cssClass] : [], children: _jsx(HostNodeContext.Provider, { value: { engine: host.engine, node, widgetRef }, children: children }) }));
146
167
  };
147
168
  export const Animated = {
@@ -1 +1 @@
1
- {"version":3,"file":"animated.js","sourceRoot":"","sources":["../../src/components/animated.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { createAnimated, type FrameScheduler } from \"../animated/index\"\nimport type { FlatStyle, StyleProp, TransformPart } from \"../contracts\"\nimport { GLib, GtkBox, queueAllocate, type Gtk } from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport { setStoredOffset } from \"./rect-store\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\n// ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per\n// window) is a later optimization; timeouts keep the driver widget-free.\nconst glibScheduler: FrameScheduler = {\n schedule(callback) {\n let cancelled = false\n const id = GLib.timeoutAdd(GLib.PRIORITY_DEFAULT, 16, () => {\n if (!cancelled) {\n callback(Number(GLib.getMonotonicTime()) / 1000)\n }\n return false\n })\n return () => {\n if (!cancelled) {\n cancelled = true\n GLib.Source.remove(id)\n }\n }\n },\n}\n\nconst api = createAnimated(glibScheduler)\n\ntype AnimatedNode = {\n addListener(callback: (state: { value: number }) => void): string\n removeListener(id: string): void\n __getValue(): number\n}\n\nconst isAnimatedNode = (value: unknown): value is AnimatedNode =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AnimatedNode).addListener === \"function\" &&\n typeof (value as AnimatedNode).__getValue === \"function\"\n\ntype AnimatedTransformPart = { [key: string]: number | string | AnimatedNode }\n\n// Omit, not plain intersection: intersecting would AND the animated transform\n// with FlatStyle's numeric one, rejecting Animated.Value entries.\nexport type AnimatedViewStyle = Omit<FlatStyle, \"opacity\" | \"transform\"> & {\n opacity?: number | AnimatedNode\n transform?: (TransformPart | AnimatedTransformPart)[]\n}\n\ntype Binding = {\n node: AnimatedNode\n apply: (value: number) => void\n}\n\nexport type AnimatedViewProps = {\n style?: AnimatedViewStyle | AnimatedViewStyle[]\n children?: ReactNode\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst splitAnimated = (\n style: AnimatedViewProps[\"style\"],\n): {\n staticStyle: StyleProp\n opacity: AnimatedNode | null\n translateX: AnimatedNode | null\n translateY: AnimatedNode | null\n} => {\n const flat: Record<string, unknown> = {}\n const collect = (entry: AnimatedViewProps[\"style\"]): void => {\n if (!entry) {\n return\n }\n if (Array.isArray(entry)) {\n entry.forEach(collect)\n return\n }\n Object.assign(flat, entry)\n }\n collect(style)\n\n let opacity: AnimatedNode | null = null\n let translateX: AnimatedNode | null = null\n let translateY: AnimatedNode | null = null\n\n if (isAnimatedNode(flat.opacity)) {\n opacity = flat.opacity\n delete flat.opacity\n }\n if (Array.isArray(flat.transform)) {\n const staticParts: TransformPart[] = []\n for (const part of flat.transform as AnimatedTransformPart[]) {\n const key = Object.keys(part)[0]\n const value = part[key!]\n if (key === \"translateX\" && isAnimatedNode(value)) {\n translateX = value\n } else if (key === \"translateY\" && isAnimatedNode(value)) {\n translateY = value\n } else if (!isAnimatedNode(value)) {\n staticParts.push(part as TransformPart)\n }\n }\n if (staticParts.length > 0) {\n flat.transform = staticParts\n } else {\n delete flat.transform\n }\n }\n\n return { staticStyle: flat as StyleProp, opacity, translateX, translateY }\n}\n\n// Animated values bypass React entirely: listeners write straight to the\n// widget (opacity) and to the rect store (translate offsets applied by the\n// parent's allocate), on top of the engine-committed base rect — the fast\n// path measured in the spike. Animation frames never touch Yoga.\nconst AnimatedView = ({\n style,\n children,\n onLayout,\n testID,\n}: AnimatedViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { staticStyle, opacity, translateX, translateY } = splitAnimated(style)\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: staticStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const offsets = useRef({ x: 0, y: 0 })\n\n useLayoutEffect(() => {\n // RN transform semantics: translate is paint-only. The offset goes to\n // the rect store unclamped — the parent's measure ignores children, so an\n // out-of-bounds child draws past the boundary over its neighbors without\n // moving a single ancestor.\n const applyTranslate = (): void => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n setStoredOffset(widget, offsets.current.x, offsets.current.y)\n queueAllocate(parentWidget)\n }\n\n const bindings: Binding[] = []\n if (opacity) {\n bindings.push({\n node: opacity,\n apply: (value) => {\n widgetRef.current?.setOpacity(Math.min(1, Math.max(0, value)))\n },\n })\n }\n if (translateX) {\n bindings.push({\n node: translateX,\n apply: (value) => {\n offsets.current.x = value\n applyTranslate()\n },\n })\n }\n if (translateY) {\n bindings.push({\n node: translateY,\n apply: (value) => {\n offsets.current.y = value\n applyTranslate()\n },\n })\n }\n\n const subscriptions = bindings.map((binding) => ({\n binding,\n id: binding.node.addListener(({ value }) => binding.apply(value)),\n }))\n for (const { binding } of subscriptions) {\n binding.apply(binding.node.__getValue())\n }\n return () => {\n for (const { binding, id } of subscriptions) {\n binding.node.removeListener(id)\n }\n }\n // Re-bind when the animated node identities change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [opacity, translateX, translateY, node])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport const Animated = {\n ...api,\n View: AnimatedView,\n}\n\nexport { Easing } from \"../animated/index\"\n"]}
1
+ {"version":3,"file":"animated.js","sourceRoot":"","sources":["../../src/components/animated.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { createAnimated, type FrameScheduler } from \"../animated/index\"\nimport type { FlatStyle, StyleProp, TransformPart } from \"../contracts\"\nimport { GLib, GtkBox, queueAllocate, type Gtk } from \"../gtkx/bridge/index\"\nimport type { ResponderProps } from \"../responder/types\"\nimport { useResponder } from \"../responder/use-responder\"\nimport { HostNodeContext } from \"./host-node\"\nimport { setStoredTransform } from \"./rect-store\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\n// ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per\n// window) is a later optimization; timeouts keep the driver widget-free.\nconst glibScheduler: FrameScheduler = {\n schedule(callback) {\n let cancelled = false\n const id = GLib.timeoutAdd(GLib.PRIORITY_DEFAULT, 16, () => {\n if (!cancelled) {\n callback(Number(GLib.getMonotonicTime()) / 1000)\n }\n return false\n })\n return () => {\n if (!cancelled) {\n cancelled = true\n GLib.Source.remove(id)\n }\n }\n },\n}\n\nconst api = createAnimated(glibScheduler)\n\n// Interpolations produce suffixed strings (\"45deg\"), so a driven value is\n// not necessarily a number — `rotate` is normally an interpolate() with a\n// deg outputRange.\ntype AnimatedValue = number | string\n\ntype AnimatedNode = {\n addListener(callback: (state: { value: AnimatedValue }) => void): string\n removeListener(id: string): void\n __getValue(): AnimatedValue\n}\n\nconst isAnimatedNode = (value: unknown): value is AnimatedNode =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AnimatedNode).addListener === \"function\" &&\n typeof (value as AnimatedNode).__getValue === \"function\"\n\ntype AnimatedTransformPart = { [key: string]: number | string | AnimatedNode }\n\n// One entry of the style's transform array, kept in source order. The array\n// order IS the composition order in RN, so an animated entry updates its own\n// slot in place instead of being lifted out of the list — that is what makes\n// [{rotate}, {translateX}] compose differently from the reverse, as in RN.\ntype TransformSlot = {\n key: string\n node: AnimatedNode | null\n value: AnimatedValue\n}\n\n// Stable per-node identity for the effect's dependency key: Animated nodes\n// are objects, and the bindings must be rebuilt when the node behind a slot\n// changes — not on every render that rebuilds an equal-looking array.\nlet nextNodeId = 1\nconst nodeIds = new WeakMap<object, number>()\nconst nodeId = (value: object): number => {\n let id = nodeIds.get(value)\n if (id === undefined) {\n id = nextNodeId++\n nodeIds.set(value, id)\n }\n return id\n}\n\n// Omit, not plain intersection: intersecting would AND the animated transform\n// with FlatStyle's numeric one, rejecting Animated.Value entries.\nexport type AnimatedViewStyle = Omit<FlatStyle, \"opacity\" | \"transform\"> & {\n opacity?: number | AnimatedNode\n transform?: (TransformPart | AnimatedTransformPart)[]\n}\n\n// Animated.View is where a PanResponder drag lands in idiomatic RN — the\n// dragged box is animated by definition — so it takes the responder props\n// exactly as View does.\nexport type AnimatedViewProps = ResponderProps & {\n style?: AnimatedViewStyle | AnimatedViewStyle[]\n children?: ReactNode\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst splitAnimated = (\n style: AnimatedViewProps[\"style\"],\n): {\n staticStyle: StyleProp\n opacity: AnimatedNode | null\n slots: TransformSlot[]\n} => {\n const flat: Record<string, unknown> = {}\n const collect = (entry: AnimatedViewProps[\"style\"]): void => {\n if (!entry) {\n return\n }\n if (Array.isArray(entry)) {\n entry.forEach(collect)\n return\n }\n Object.assign(flat, entry)\n }\n collect(style)\n\n let opacity: AnimatedNode | null = null\n const slots: TransformSlot[] = []\n\n if (isAnimatedNode(flat.opacity)) {\n opacity = flat.opacity\n delete flat.opacity\n }\n if (Array.isArray(flat.transform)) {\n for (const part of flat.transform as AnimatedTransformPart[]) {\n const key = Object.keys(part)[0]\n if (key === undefined) {\n continue\n }\n const value = part[key]\n slots.push(\n isAnimatedNode(value)\n ? { key, node: value, value: value.__getValue() }\n : { key, node: null, value: value as AnimatedValue },\n )\n }\n // The whole array is owned here from now on, static entries included:\n // leaving them in the style would make useLayoutChild write the same\n // rect-store slot from the other side, and the two would overwrite\n // each other every render.\n delete flat.transform\n }\n\n return { staticStyle: flat as StyleProp, opacity, slots }\n}\n\n// Animated values bypass React entirely: listeners write straight to the\n// widget (opacity) and to the rect store (the transform applied by the\n// parent's allocate), on top of the engine-committed base rect — the fast\n// path measured in the spike. Animation frames never touch Yoga.\nconst AnimatedView = ({\n style,\n children,\n onLayout,\n testID,\n ...responderProps\n}: AnimatedViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { staticStyle, opacity, slots } = splitAnimated(style)\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: staticStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n useResponder(widgetRef, responderProps)\n\n // The effect reads the slots of the render that (re)armed it; the values\n // inside are then owned by the listeners.\n const slotsRef = useRef<TransformSlot[]>(slots)\n slotsRef.current = slots\n\n // Rebind on a change of shape (which transforms, in which order), of the\n // node behind an animated entry, or of a static entry's value — not on\n // every render that rebuilds an equal-looking array.\n const bindingKey =\n (opacity ? `opacity#${nodeId(opacity)}` : \"\") +\n slots\n .map((slot) =>\n slot.node\n ? `|${slot.key}#${nodeId(slot.node)}`\n : `|${slot.key}=${String(slot.value)}`,\n )\n .join(\"\")\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n\n // The live transform array, mutated in place: an Animated write must\n // stay one numeric store plus one queued allocation — no React render,\n // no Yoga pass, no allocation per frame beyond the composed matrix.\n //\n // RN transform semantics: this is paint-only. The result goes to the\n // rect store unclamped — the parent's measure ignores children, so a\n // transformed child draws past the boundary over its neighbors without\n // moving a single ancestor.\n const parts = slotsRef.current.map(\n (slot) => ({ [slot.key]: slot.value }) as Record<string, AnimatedValue>,\n )\n\n const flush = (): void => {\n setStoredTransform(widget, parts as unknown as TransformPart[])\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n queueAllocate(parentWidget)\n }\n }\n\n const subscriptions: { node: AnimatedNode; id: string }[] = []\n slotsRef.current.forEach((slot, index) => {\n if (!slot.node) {\n return\n }\n const part = parts[index]!\n subscriptions.push({\n node: slot.node,\n id: slot.node.addListener(({ value }) => {\n part[slot.key] = value\n flush()\n }),\n })\n })\n\n if (opacity) {\n const applyOpacity = (value: AnimatedValue): void => {\n const numeric = typeof value === \"number\" ? value : parseFloat(value)\n widgetRef.current?.setOpacity(Math.min(1, Math.max(0, numeric)))\n }\n subscriptions.push({\n node: opacity,\n id: opacity.addListener(({ value }) => {\n applyOpacity(value)\n }),\n })\n applyOpacity(opacity.__getValue())\n }\n\n flush()\n\n return () => {\n for (const { node: animated, id } of subscriptions) {\n animated.removeListener(id)\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [bindingKey, node])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport const Animated = {\n ...api,\n View: AnimatedView,\n}\n\nexport { Easing } from \"../animated/index\"\n"]}
@@ -1,12 +1,47 @@
1
- import { type ComponentType } from "react";
1
+ import { type ComponentType, type ReactNode } from "react";
2
2
  type ComponentProvider = () => ComponentType<Record<string, unknown>>;
3
3
  export declare const getActiveChrome: () => "system" | "content" | null;
4
+ export type ActionAccel = {
5
+ detailedActionName: string;
6
+ accels: string[];
7
+ };
4
8
  export type RunApplicationParams = {
5
9
  initialProps?: Record<string, unknown>;
6
10
  title?: string;
7
11
  width?: number;
8
12
  height?: number;
9
13
  chrome?: "system" | "content";
14
+ /**
15
+ * GSimpleAction elements registered on the GApplication itself
16
+ * ("app.<name>" — reachable from anywhere, including a Gio.Notification's
17
+ * action buttons, which can only ever target an app-level action).
18
+ *
19
+ * @deprecated Render `<ApplicationActions>` (react-native-gtkx/gtk) inside
20
+ * the app instead. Options render as SIBLINGS of the app tree, so no
21
+ * context of the app's can reach them and the set is fixed for the
22
+ * process's lifetime. Still supported; see docs/api.md.
23
+ */
24
+ applicationActions?: ReactNode;
25
+ actionAccels?: ActionAccel[];
26
+ /**
27
+ * GSimpleAction elements registered on the window ("win.<name>" — what a
28
+ * HeaderBar button's actionName or a GMenu item usually targets).
29
+ *
30
+ * @deprecated Render `<WindowActions>` (react-native-gtkx/gtk) inside the
31
+ * app instead — it can read the app's context and registers/unregisters
32
+ * with the component that declares it. Still supported; see docs/api.md.
33
+ */
34
+ windowActions?: ReactNode;
35
+ /**
36
+ * Event controllers attached to the window itself — a
37
+ * GtkShortcutController scoped to the whole window is the common case.
38
+ *
39
+ * @deprecated Render `<WindowControllers>` (react-native-gtkx/gtk) inside
40
+ * the app instead, for the same reasons as `windowActions`. Still
41
+ * supported; see docs/api.md.
42
+ */
43
+ windowControllers?: ReactNode;
44
+ breakpoints?: ReactNode;
10
45
  };
11
46
  export declare const AppRegistry: {
12
47
  registerComponent(appKey: string, provider: ComponentProvider): string;
@@ -70,9 +70,14 @@ export const AppRegistry = {
70
70
  const height = params.height ?? 600;
71
71
  const contentChrome = params.chrome === "content";
72
72
  activeChrome = contentChrome ? "content" : "system";
73
- const Window = contentChrome ? AdwApplicationWindow : GtkApplicationWindow;
74
- const AppWindow = () => (_jsx(Window, { title: params.title ?? appKey, defaultWidth: width, defaultHeight: height, onCloseRequest: quit, children: contentChrome ? (_jsx(ContentChrome, { App: App, initialProps: params.initialProps ?? {} })) : (_jsx(WindowContent, { App: App, initialProps: params.initialProps ?? {}, initialWidth: width, initialHeight: height })) }));
75
- createRoot().render(_jsx(GtkApplication, { children: _jsx(AppWindow, {}) }));
73
+ if (process.env.NODE_ENV !== "production" &&
74
+ params.breakpoints &&
75
+ !contentChrome) {
76
+ console.warn('[react-native-gtkx] breakpoints was passed to runApplication without chrome: "content" — GtkApplicationWindow (the "system" chrome default) has no breakpoints concept, so it is ignored.');
77
+ }
78
+ const content = contentChrome ? (_jsx(ContentChrome, { App: App, initialProps: params.initialProps ?? {} })) : (_jsx(WindowContent, { App: App, initialProps: params.initialProps ?? {}, initialWidth: width, initialHeight: height }));
79
+ const AppWindow = () => contentChrome ? (_jsx(AdwApplicationWindow, { title: params.title ?? appKey, defaultWidth: width, defaultHeight: height, onCloseRequest: quit, actions: params.windowActions, controllers: params.windowControllers, breakpoints: params.breakpoints, children: content })) : (_jsx(GtkApplicationWindow, { title: params.title ?? appKey, defaultWidth: width, defaultHeight: height, onCloseRequest: quit, actions: params.windowActions, controllers: params.windowControllers, children: content }));
80
+ createRoot().render(_jsx(GtkApplication, { actions: params.applicationActions, actionAccels: params.actionAccels, children: _jsx(AppWindow, {}) }));
76
81
  },
77
82
  };
78
83
  //# sourceMappingURL=app-registry.js.map