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
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useMemo, useRef, useState, } from "react";
3
- import { textAlignToLabelProps } from "../style/index";
3
+ import { textAlignToLabelProps, textDecorationToAttrs } from "../style/index";
4
4
  import { createTextProbe, Gtk, GtkLabel, measureWidget, Pango, } from "../gtkx/bridge/index";
5
5
  import { useLayoutChild } from "./use-layout-child";
6
6
  const flattenToString = (children) => {
@@ -33,10 +33,19 @@ export const Text = ({ style, onLayout, numberOfLines, children, testID, }) => {
33
33
  const measuredLines = useRef(1);
34
34
  const measure = useMemo(() => {
35
35
  return (width, widthMode) => {
36
- const natural = measureWidget(probe, "horizontal").natural;
36
+ const { minimum, natural } = measureWidget(probe, "horizontal");
37
+ // Floor at the probe's own minimum width, not at 1: gtk_widget_measure()
38
+ // clamps a height-for-width query up to the widget's minimum before
39
+ // computing regardless of what we pass (logging "Trying to measure
40
+ // GtkLabel for width of N, but it needs at least M" while doing it), so
41
+ // asking for anything below `minimum` was never honored — the height
42
+ // below was already being computed at `minimum`, not at our `used`.
43
+ // Clamping here ourselves gets the same height GTK would give us
44
+ // anyway, keeps the returned width consistent with it, and drops the
45
+ // warning as a side effect rather than the goal.
37
46
  const used = widthMode === "undefined"
38
47
  ? natural
39
- : Math.min(natural, Math.max(1, Math.floor(width)));
48
+ : Math.min(natural, Math.max(minimum, Math.floor(width)));
40
49
  const height = measureWidget(probe, "vertical", used).natural;
41
50
  const singleLine = measureWidget(probe, "vertical", natural).natural;
42
51
  measuredLines.current =
@@ -88,6 +97,30 @@ export const Text = ({ style, onLayout, numberOfLines, children, testID, }) => {
88
97
  node.markDirty();
89
98
  }, [probe, node, text, numberOfLines, cssClass]);
90
99
  const align = textAlignToLabelProps(flat.textAlign);
100
+ // textDecorationLine goes through Pango, not CSS: GTK4 has no widget
101
+ // `text-decoration`. Built here rather than in the style module so that
102
+ // module stays bridge-free and unit-testable off Linux — the same split
103
+ // textAlign already uses.
104
+ //
105
+ // The attribute list is applied to the PROBE as well, because it is what
106
+ // Yoga measures: Pango reserves room for an underline below the baseline,
107
+ // so a decorated label measured undecorated would be a pixel short.
108
+ const decoration = textDecorationToAttrs(flat.textDecorationLine);
109
+ const decorated = decoration.underline || decoration.strikethrough;
110
+ useLayoutEffect(() => {
111
+ const attrs = decorated ? Pango.AttrList.new() : null;
112
+ if (attrs) {
113
+ if (decoration.underline) {
114
+ attrs.insert(Pango.attrUnderlineNew(Pango.Underline.SINGLE));
115
+ }
116
+ if (decoration.strikethrough) {
117
+ attrs.insert(Pango.attrStrikethroughNew(true));
118
+ }
119
+ }
120
+ widgetRef.current?.setAttributes(attrs);
121
+ probe.setAttributes(attrs);
122
+ node.markDirty();
123
+ }, [probe, node, decorated, decoration.underline, decoration.strikethrough]);
91
124
  return (_jsx(GtkLabel, { ref: widgetRef, name: testID, label: text, wrap: true, xalign: align.xalign, yalign: 0, justify: JUSTIFICATION[align.justification], lines: numberOfLines,
92
125
  // RN semantics: the ellipsis is opt-in via numberOfLines. Plain text
93
126
  // wraps naturally; an unbreakable word wider than the rect just clips
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/components/text.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { textAlignToLabelProps } from \"../style/index\"\nimport type { MeasureFn, StyleProp } from \"../contracts\"\nimport {\n createTextProbe,\n Gtk,\n GtkLabel,\n measureWidget,\n Pango,\n} from \"../gtkx/bridge/index\"\nimport { useLayoutChild, type LayoutEvent } from \"./use-layout-child\"\n\nexport type TextProps = {\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n numberOfLines?: number\n children?: ReactNode\n testID?: string\n}\n\nconst flattenToString = (children: ReactNode): string => {\n if (children === null || children === undefined || children === false) {\n return \"\"\n }\n if (Array.isArray(children)) {\n return children.map(flattenToString).join(\"\")\n }\n return String(children)\n}\n\nconst JUSTIFICATION = {\n left: Gtk.Justification.LEFT,\n right: Gtk.Justification.RIGHT,\n center: Gtk.Justification.CENTER,\n fill: Gtk.Justification.FILL,\n} as const\n\n// Text is a measured Yoga leaf: an offscreen probe label (with the same CSS\n// class, so fonts match) feeds Pango metrics into the layout engine; the\n// visible GtkLabel is positioned by the commit hook like any other widget.\nexport const Text = ({\n style,\n onLayout,\n numberOfLines,\n children,\n testID,\n}: TextProps) => {\n const widgetRef = useRef<Gtk.Label | null>(null)\n const text = flattenToString(children)\n\n // useState lazy init — see the note in use-layout-child.ts (React Compiler).\n const [probe] = useState<Gtk.Label>(() => createTextProbe())\n\n const lastProbeClass = useRef<string | null>(null)\n\n // GTK collapses wrap+ellipsize labels to a single line unless `lines` is\n // set explicitly — remember how many lines the probe wrapped into, the\n // layout callback pushes it onto the visible label.\n const measuredLines = useRef(1)\n\n const measure = useMemo<MeasureFn>(() => {\n return (width, widthMode) => {\n const natural = measureWidget(probe, \"horizontal\").natural\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(1, Math.floor(width)))\n const height = measureWidget(probe, \"vertical\", used).natural\n const singleLine = measureWidget(probe, \"vertical\", natural).natural\n measuredLines.current =\n singleLine > 0 ? Math.max(1, Math.round(height / singleLine)) : 1\n return { width: used, height }\n }\n // The probe is stable; re-measure is triggered via markDirty below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const layoutWithLines = useMemo(\n () => (event: LayoutEvent) => {\n const label = widgetRef.current\n if (label) {\n label.setLines(numberOfLines ?? measuredLines.current)\n }\n onLayout?.(event)\n },\n [numberOfLines, onLayout],\n )\n\n const { node, cssClass, flat } = useLayoutChild(widgetRef, {\n style,\n onLayout: layoutWithLines,\n measure,\n })\n\n // RN text clips to its box: an under-allocated GtkLabel would otherwise\n // paint its full text past the allocation (spike finding). Containers keep\n // overflow VISIBLE (paint-overflow); the text leaf clips.\n useLayoutEffect(() => {\n widgetRef.current?.setOverflow(Gtk.Overflow.HIDDEN)\n }, [])\n\n // Keep the probe in sync with everything that affects metrics, then\n // invalidate the Yoga leaf so the next pass re-measures.\n useLayoutEffect(() => {\n probe.setText(text)\n if (numberOfLines !== undefined) {\n probe.setLines(numberOfLines)\n probe.setEllipsize(Pango.EllipsizeMode.END)\n } else {\n probe.setLines(-1)\n probe.setEllipsize(Pango.EllipsizeMode.NONE)\n }\n if (lastProbeClass.current !== cssClass) {\n if (lastProbeClass.current) {\n probe.removeCssClass(lastProbeClass.current)\n }\n if (cssClass) {\n probe.addCssClass(cssClass)\n }\n lastProbeClass.current = cssClass\n }\n node.markDirty()\n }, [probe, node, text, numberOfLines, cssClass])\n\n const align = textAlignToLabelProps(flat.textAlign)\n\n return (\n <GtkLabel\n ref={widgetRef}\n name={testID}\n label={text}\n wrap\n xalign={align.xalign}\n yalign={0}\n justify={JUSTIFICATION[align.justification]}\n lines={numberOfLines}\n // RN semantics: the ellipsis is opt-in via numberOfLines. Plain text\n // wraps naturally; an unbreakable word wider than the rect just clips\n // (overflow HIDDEN above) — the layout manager allocates the rect\n // regardless of the label's own minimum, so nothing pushes siblings.\n ellipsize={\n numberOfLines !== undefined\n ? Pango.EllipsizeMode.END\n : Pango.EllipsizeMode.NONE\n }\n cssClasses={cssClass ? [cssClass] : []}\n />\n )\n}\n"]}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/components/text.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { textAlignToLabelProps, textDecorationToAttrs } from \"../style/index\"\nimport type { MeasureFn, StyleProp } from \"../contracts\"\nimport {\n createTextProbe,\n Gtk,\n GtkLabel,\n measureWidget,\n Pango,\n} from \"../gtkx/bridge/index\"\nimport { useLayoutChild, type LayoutEvent } from \"./use-layout-child\"\n\nexport type TextProps = {\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n numberOfLines?: number\n children?: ReactNode\n testID?: string\n}\n\nconst flattenToString = (children: ReactNode): string => {\n if (children === null || children === undefined || children === false) {\n return \"\"\n }\n if (Array.isArray(children)) {\n return children.map(flattenToString).join(\"\")\n }\n return String(children)\n}\n\nconst JUSTIFICATION = {\n left: Gtk.Justification.LEFT,\n right: Gtk.Justification.RIGHT,\n center: Gtk.Justification.CENTER,\n fill: Gtk.Justification.FILL,\n} as const\n\n// Text is a measured Yoga leaf: an offscreen probe label (with the same CSS\n// class, so fonts match) feeds Pango metrics into the layout engine; the\n// visible GtkLabel is positioned by the commit hook like any other widget.\nexport const Text = ({\n style,\n onLayout,\n numberOfLines,\n children,\n testID,\n}: TextProps) => {\n const widgetRef = useRef<Gtk.Label | null>(null)\n const text = flattenToString(children)\n\n // useState lazy init — see the note in use-layout-child.ts (React Compiler).\n const [probe] = useState<Gtk.Label>(() => createTextProbe())\n\n const lastProbeClass = useRef<string | null>(null)\n\n // GTK collapses wrap+ellipsize labels to a single line unless `lines` is\n // set explicitly — remember how many lines the probe wrapped into, the\n // layout callback pushes it onto the visible label.\n const measuredLines = useRef(1)\n\n const measure = useMemo<MeasureFn>(() => {\n return (width, widthMode) => {\n const { minimum, natural } = measureWidget(probe, \"horizontal\")\n // Floor at the probe's own minimum width, not at 1: gtk_widget_measure()\n // clamps a height-for-width query up to the widget's minimum before\n // computing regardless of what we pass (logging \"Trying to measure\n // GtkLabel for width of N, but it needs at least M\" while doing it), so\n // asking for anything below `minimum` was never honored — the height\n // below was already being computed at `minimum`, not at our `used`.\n // Clamping here ourselves gets the same height GTK would give us\n // anyway, keeps the returned width consistent with it, and drops the\n // warning as a side effect rather than the goal.\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(minimum, Math.floor(width)))\n const height = measureWidget(probe, \"vertical\", used).natural\n const singleLine = measureWidget(probe, \"vertical\", natural).natural\n measuredLines.current =\n singleLine > 0 ? Math.max(1, Math.round(height / singleLine)) : 1\n return { width: used, height }\n }\n // The probe is stable; re-measure is triggered via markDirty below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const layoutWithLines = useMemo(\n () => (event: LayoutEvent) => {\n const label = widgetRef.current\n if (label) {\n label.setLines(numberOfLines ?? measuredLines.current)\n }\n onLayout?.(event)\n },\n [numberOfLines, onLayout],\n )\n\n const { node, cssClass, flat } = useLayoutChild(widgetRef, {\n style,\n onLayout: layoutWithLines,\n measure,\n })\n\n // RN text clips to its box: an under-allocated GtkLabel would otherwise\n // paint its full text past the allocation (spike finding). Containers keep\n // overflow VISIBLE (paint-overflow); the text leaf clips.\n useLayoutEffect(() => {\n widgetRef.current?.setOverflow(Gtk.Overflow.HIDDEN)\n }, [])\n\n // Keep the probe in sync with everything that affects metrics, then\n // invalidate the Yoga leaf so the next pass re-measures.\n useLayoutEffect(() => {\n probe.setText(text)\n if (numberOfLines !== undefined) {\n probe.setLines(numberOfLines)\n probe.setEllipsize(Pango.EllipsizeMode.END)\n } else {\n probe.setLines(-1)\n probe.setEllipsize(Pango.EllipsizeMode.NONE)\n }\n if (lastProbeClass.current !== cssClass) {\n if (lastProbeClass.current) {\n probe.removeCssClass(lastProbeClass.current)\n }\n if (cssClass) {\n probe.addCssClass(cssClass)\n }\n lastProbeClass.current = cssClass\n }\n node.markDirty()\n }, [probe, node, text, numberOfLines, cssClass])\n\n const align = textAlignToLabelProps(flat.textAlign)\n\n // textDecorationLine goes through Pango, not CSS: GTK4 has no widget\n // `text-decoration`. Built here rather than in the style module so that\n // module stays bridge-free and unit-testable off Linux — the same split\n // textAlign already uses.\n //\n // The attribute list is applied to the PROBE as well, because it is what\n // Yoga measures: Pango reserves room for an underline below the baseline,\n // so a decorated label measured undecorated would be a pixel short.\n const decoration = textDecorationToAttrs(flat.textDecorationLine)\n const decorated = decoration.underline || decoration.strikethrough\n useLayoutEffect(() => {\n const attrs = decorated ? Pango.AttrList.new() : null\n if (attrs) {\n if (decoration.underline) {\n attrs.insert(Pango.attrUnderlineNew(Pango.Underline.SINGLE))\n }\n if (decoration.strikethrough) {\n attrs.insert(Pango.attrStrikethroughNew(true))\n }\n }\n widgetRef.current?.setAttributes(attrs)\n probe.setAttributes(attrs)\n node.markDirty()\n }, [probe, node, decorated, decoration.underline, decoration.strikethrough])\n\n return (\n <GtkLabel\n ref={widgetRef}\n name={testID}\n label={text}\n wrap\n xalign={align.xalign}\n yalign={0}\n justify={JUSTIFICATION[align.justification]}\n lines={numberOfLines}\n // RN semantics: the ellipsis is opt-in via numberOfLines. Plain text\n // wraps naturally; an unbreakable word wider than the rect just clips\n // (overflow HIDDEN above) — the layout manager allocates the rect\n // regardless of the label's own minimum, so nothing pushes siblings.\n ellipsize={\n numberOfLines !== undefined\n ? Pango.EllipsizeMode.END\n : Pango.EllipsizeMode.NONE\n }\n cssClasses={cssClass ? [cssClass] : []}\n />\n )\n}\n"]}
@@ -4,7 +4,7 @@ import { defaultCssRegistry } from "../style/registry.gtkx";
4
4
  import { allocateChild, attachRnLayout, detachRnLayout, measureWidget, queueAllocate, queueResize, } from "../gtkx/bridge/index";
5
5
  import { perfAddTime, perfBurst, perfCount, perfEnabled, perfNow, } from "../perf";
6
6
  import { useHostNode } from "./host-node";
7
- import { deferDuringAllocate, getStoredOffset, getStoredRect, setStoredRect, } from "./rect-store";
7
+ import { deferDuringAllocate, getStoredOffset, getStoredRect, setStoredRect, setStoredTransform, } from "./rect-store";
8
8
  // Perf: sizeAllocate recurses synchronously into child containers, so a
9
9
  // naive per-pass timer double-counts nested passes. Track depth and time
10
10
  // only the outermost (top-level) allocate — that is the real wall time GTK
@@ -32,6 +32,25 @@ export const useLayoutChild = (widgetRef, options) => {
32
32
  node.setStyle(JSON.parse(layoutKey));
33
33
  }, [node, layoutKey]);
34
34
  const cssClass = defaultCssRegistry.getClassName(visual);
35
+ // RN transforms are visual only: the widget keeps the box Yoga gave it, so
36
+ // a transform never touches the shadow tree — it only asks the parent
37
+ // container to run its allocate hook again. Keyed on the serialized array
38
+ // so a re-render with the same transform costs nothing.
39
+ // (Animated.View owns its own transform and strips it from the static
40
+ // style before it gets here, so the two writers never race.)
41
+ const transformKey = JSON.stringify(visual.transform ?? null);
42
+ useLayoutEffect(() => {
43
+ const widget = widgetRef.current;
44
+ if (!widget) {
45
+ return;
46
+ }
47
+ setStoredTransform(widget, JSON.parse(transformKey));
48
+ const parentWidget = host.widgetRef.current;
49
+ if (parentWidget) {
50
+ queueAllocate(parentWidget);
51
+ }
52
+ // eslint-disable-next-line react-hooks/exhaustive-deps
53
+ }, [transformKey]);
35
54
  useLayoutEffect(() => {
36
55
  const parent = host.node;
37
56
  // The reconciler has already attached our widget in JSX order, so its
@@ -59,6 +78,29 @@ export const useLayoutChild = (widgetRef, options) => {
59
78
  return;
60
79
  }
61
80
  const previous = getStoredRect(widget);
81
+ // A commit does not imply a change. The engine may hand us the rect a
82
+ // widget already has — nodes carrying a measure function are visited
83
+ // even when Yoga did not move them — and queueing an allocation for
84
+ // those makes GTK re-allocate and re-snapshot the container for nothing.
85
+ //
86
+ // `rect.skip` counts how often this guard actually fires and
87
+ // `rect.change` how often it does not, because a guard nobody can see
88
+ // firing is a guard nobody can attribute a measurement to: this one was
89
+ // once credited with a large maximized-window win that a later, properly
90
+ // geometry-controlled run could not reproduce (docs/research/
91
+ // scroll-performance.md, round six). Keep it — it is correct and free —
92
+ // but read the counter before crediting it with anything.
93
+ const unchanged = previous !== undefined &&
94
+ previous.x === rect.x &&
95
+ previous.y === rect.y &&
96
+ previous.width === rect.width &&
97
+ previous.height === rect.height;
98
+ if (perfEnabled) {
99
+ perfCount(unchanged ? "rect.skip" : "rect.change");
100
+ }
101
+ if (unchanged) {
102
+ return;
103
+ }
62
104
  setStoredRect(widget, rect);
63
105
  const sizeChanged = !previous ||
64
106
  previous.width !== rect.width ||
@@ -102,10 +144,14 @@ export const useLayoutChild = (widgetRef, options) => {
102
144
  if (!widget) {
103
145
  return { width: 0, height: 0 };
104
146
  }
105
- const natural = measureWidget(widget, "horizontal").natural;
147
+ const { minimum, natural } = measureWidget(widget, "horizontal");
148
+ // Floor at the widget's own minimum width, not at 1 — see the same
149
+ // clamp in text.tsx's measure for why: gtk_widget_measure() enforces
150
+ // this floor internally regardless of what we request (warning while
151
+ // it does), so anything below `minimum` was never actually honored.
106
152
  const used = widthMode === "undefined"
107
153
  ? natural
108
- : Math.min(natural, Math.max(1, Math.floor(width)));
154
+ : Math.min(natural, Math.max(minimum, Math.floor(width)));
109
155
  return {
110
156
  width: used,
111
157
  height: measureWidget(widget, "vertical", used).natural,
@@ -171,7 +217,7 @@ export const useRnContainer = (widgetRef, node, options) => {
171
217
  const rect = getStoredRect(child);
172
218
  if (rect) {
173
219
  const offset = getStoredOffset(child);
174
- allocateChild(child, rect.x + offset.dx, rect.y + offset.dy, rect.width, rect.height);
220
+ allocateChild(child, rect.x + offset.dx, rect.y + offset.dy, rect.width, rect.height, offset.matrix);
175
221
  children += 1;
176
222
  }
177
223
  child = child.getNextSibling();
@@ -1 +1 @@
1
- {"version":3,"file":"use-layout-child.js","sourceRoot":"","sources":["../../src/components/use-layout-child.ts"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type RefObject } from \"react\"\nimport type { LayoutNode } from \"../layout/index\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport { defaultCssRegistry } from \"../style/registry.gtkx\"\nimport type {\n LayoutStyle,\n MeasureFn,\n Rect,\n StyleProp,\n VisualStyle,\n} from \"../contracts\"\nimport {\n allocateChild,\n attachRnLayout,\n detachRnLayout,\n measureWidget,\n queueAllocate,\n queueResize,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport {\n perfAddTime,\n perfBurst,\n perfCount,\n perfEnabled,\n perfNow,\n} from \"../perf\"\nimport { useHostNode, type HostNode } from \"./host-node\"\nimport {\n deferDuringAllocate,\n getStoredOffset,\n getStoredRect,\n setStoredRect,\n} from \"./rect-store\"\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: { x: number; y: number; width: number; height: number }\n }\n}\n\n// Perf: sizeAllocate recurses synchronously into child containers, so a\n// naive per-pass timer double-counts nested passes. Track depth and time\n// only the outermost (top-level) allocate — that is the real wall time GTK\n// spends in OUR layout code for the frame.\nlet perfAllocDepth = 0\n\nexport type LayoutChildOptions = {\n style: StyleProp | undefined\n onLayout: ((event: LayoutEvent) => void) | undefined\n measure?: MeasureFn\n // Leaves with intrinsic GTK sizes (Entry, Switch, Spinner) measure their\n // own widget so Yoga reserves exactly what the theme will draw.\n measureFromWidget?: boolean\n extraLayout?: LayoutStyle\n}\n\nexport type LayoutChild = {\n host: HostNode\n node: LayoutNode\n flat: ReturnType<typeof StyleSheet.flatten>\n visual: VisualStyle\n cssClass: string | null\n}\n\n// Shared plumbing for every leaf/container component: owns one LayoutNode,\n// keeps it in the parent's shadow tree for the component lifetime, applies\n// the layout half of the style and commits Yoga rects into the rect store,\n// where the parent container's RnGtkxLayout allocate() picks them up.\nexport const useLayoutChild = (\n widgetRef: RefObject<Gtk.Widget | null>,\n options: LayoutChildOptions,\n): LayoutChild => {\n const host = useHostNode()\n\n // useState's lazy initializer is guaranteed once-per-instance even under\n // the React Compiler — a lazy ref here got its creation memoized ACROSS\n // component instances, sharing one Yoga node and double-freeing it.\n const [node] = useState<LayoutNode>(() => host.engine.createNode())\n\n const flat = StyleSheet.flatten(options.style)\n const { layout, visual } = splitStyle(flat)\n const effectiveLayout = options.extraLayout\n ? { ...layout, ...options.extraLayout }\n : layout\n\n // Re-apply layout style only when it actually changed (keyed effect) —\n // setStyle dirties the whole tree.\n const layoutKey = JSON.stringify(effectiveLayout)\n useLayoutEffect(() => {\n node.setStyle(JSON.parse(layoutKey) as LayoutStyle)\n }, [node, layoutKey])\n\n const cssClass = defaultCssRegistry.getClassName(visual)\n\n useLayoutEffect(() => {\n const parent = host.node\n // The reconciler has already attached our widget in JSX order, so its\n // sibling position is the correct Yoga index — this keeps the shadow tree\n // ordered even for mid-list mounts (conditional children, list inserts).\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n let index = parent.children.length\n if (widget && parentWidget) {\n let position = 0\n let sibling = parentWidget.getFirstChild()\n while (sibling !== null && sibling !== widget) {\n position += 1\n sibling = sibling.getNextSibling()\n }\n if (sibling === widget) {\n index = Math.min(position, parent.children.length)\n }\n }\n parent.insertChild(node, index)\n node.setCommit((rect: Rect) => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n const previous = getStoredRect(widget)\n setStoredRect(widget, rect)\n const sizeChanged =\n !previous ||\n previous.width !== rect.width ||\n previous.height !== rect.height\n const queue = (): void => {\n if (sizeChanged) {\n // A size change invalidates this widget's cached measure (nested\n // managers, ScrolledWindow ranges) and re-allocates the ancestors.\n queueResize(widget)\n } else {\n // Pure move: the parent just needs another allocation pass. GTK\n // dedupes queued allocates, so per-child calls batch into one.\n queueAllocate(parentWidget)\n }\n }\n if (!deferDuringAllocate(widget, queue)) {\n queue()\n }\n })\n return () => {\n node.setCommit(null)\n parent.removeChild(node)\n node.free()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const onLayout = options.onLayout\n useLayoutEffect(() => {\n node.setOnLayout(\n onLayout\n ? (rect: Rect) => {\n onLayout({ nativeEvent: { layout: rect } })\n }\n : null,\n )\n }, [node, onLayout])\n\n // The explicit return type keeps the inner params contextually typed: with a\n // bare lazy initializer TS may match the outer arrow against MeasureFn\n // itself (useState accepts S | () => S) and drop the context.\n const [widgetMeasure] = useState<MeasureFn>(\n (): MeasureFn => (width, widthMode) => {\n const widget = widgetRef.current\n if (!widget) {\n return { width: 0, height: 0 }\n }\n const natural = measureWidget(widget, \"horizontal\").natural\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(1, Math.floor(width)))\n return {\n width: used,\n height: measureWidget(widget, \"vertical\", used).natural,\n }\n },\n )\n const measure =\n options.measure ?? (options.measureFromWidget ? widgetMeasure : undefined)\n useLayoutEffect(() => {\n if (!measure) {\n return\n }\n node.setMeasureFn(measure)\n // Widgets measured before their window is mapped report zero (no realized\n // theme context yet, unlike offscreen probes) and Yoga caches it —\n // invalidate once the widget actually maps.\n const widget = widgetRef.current\n const remeasure = (): void => {\n node.markDirty()\n }\n if (options.measureFromWidget && widget) {\n widget.on(\"map\", remeasure)\n }\n return () => {\n if (options.measureFromWidget && widget) {\n widget.off(\"map\", remeasure)\n }\n // Cleared by free() on unmount; explicit unset only when measure is\n // removed while mounted.\n node.setMeasureFn(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, measure])\n\n return { host, node, flat, visual, cssClass }\n}\n\nexport type RnContainerOptions = {\n // Overrides the default engine-rect measure (the window root reports 0 so\n // the window can shrink freely below the current content size; the\n // intrinsic root measures its content through Yoga, honoring forSize for\n // height-for-width).\n measure?: (orientation: \"horizontal\" | \"vertical\", forSize: number) => number\n // Runs before children are placed — the window root syncs the engine\n // viewport to the actual allocation here.\n beforeAllocate?: (width: number, height: number) => void\n}\n\n// Containers (Root, View, Animated.View, ScrollView content) drive their GTK\n// side with an RnGtkxLayout: measure() reports the engine rect of `node`\n// (minimum == natural — GTK minimums of children never leak upward), and\n// allocate() places every child widget at exactly its stored engine rect\n// plus the Animated offset.\nexport const useRnContainer = (\n widgetRef: RefObject<Gtk.Widget | null>,\n node: LayoutNode,\n options?: RnContainerOptions,\n): void => {\n const optionsRef = useRef<RnContainerOptions | undefined>(options)\n optionsRef.current = options\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n attachRnLayout(widget, {\n measure: (orientation, forSize) => {\n const custom = optionsRef.current?.measure\n if (custom) {\n return custom(orientation, forSize)\n }\n const rect = node.getRect()\n return Math.round(\n (orientation === \"horizontal\" ? rect?.width : rect?.height) ?? 0,\n )\n },\n allocate: (width, height) => {\n const start = perfEnabled ? perfNow() : 0\n perfAllocDepth += 1\n optionsRef.current?.beforeAllocate?.(width, height)\n let child = widget.getFirstChild()\n let children = 0\n while (child) {\n const rect = getStoredRect(child)\n if (rect) {\n const offset = getStoredOffset(child)\n allocateChild(\n child,\n rect.x + offset.dx,\n rect.y + offset.dy,\n rect.width,\n rect.height,\n )\n children += 1\n }\n child = child.getNextSibling()\n }\n perfAllocDepth -= 1\n if (perfEnabled) {\n perfCount(\"gtk.allocPass\")\n perfCount(\"gtk.allocChild\", children)\n if (perfAllocDepth === 0) {\n const elapsed = perfNow() - start\n perfAddTime(\"gtk.allocTop\", elapsed)\n perfBurst(\"gtk.allocMsPerFrame\", elapsed)\n }\n }\n },\n })\n return () => {\n detachRnLayout(widget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"]}
1
+ {"version":3,"file":"use-layout-child.js","sourceRoot":"","sources":["../../src/components/use-layout-child.ts"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type RefObject } from \"react\"\nimport type { LayoutNode } from \"../layout/index\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport { defaultCssRegistry } from \"../style/registry.gtkx\"\nimport type {\n LayoutStyle,\n MeasureFn,\n Rect,\n StyleProp,\n TransformPart,\n VisualStyle,\n} from \"../contracts\"\nimport {\n allocateChild,\n attachRnLayout,\n detachRnLayout,\n measureWidget,\n queueAllocate,\n queueResize,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport {\n perfAddTime,\n perfBurst,\n perfCount,\n perfEnabled,\n perfNow,\n} from \"../perf\"\nimport { useHostNode, type HostNode } from \"./host-node\"\nimport {\n deferDuringAllocate,\n getStoredOffset,\n getStoredRect,\n setStoredRect,\n setStoredTransform,\n} from \"./rect-store\"\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: { x: number; y: number; width: number; height: number }\n }\n}\n\n// Perf: sizeAllocate recurses synchronously into child containers, so a\n// naive per-pass timer double-counts nested passes. Track depth and time\n// only the outermost (top-level) allocate — that is the real wall time GTK\n// spends in OUR layout code for the frame.\nlet perfAllocDepth = 0\n\nexport type LayoutChildOptions = {\n style: StyleProp | undefined\n onLayout: ((event: LayoutEvent) => void) | undefined\n measure?: MeasureFn\n // Leaves with intrinsic GTK sizes (Entry, Switch, Spinner) measure their\n // own widget so Yoga reserves exactly what the theme will draw.\n measureFromWidget?: boolean\n extraLayout?: LayoutStyle\n}\n\nexport type LayoutChild = {\n host: HostNode\n node: LayoutNode\n flat: ReturnType<typeof StyleSheet.flatten>\n visual: VisualStyle\n cssClass: string | null\n}\n\n// Shared plumbing for every leaf/container component: owns one LayoutNode,\n// keeps it in the parent's shadow tree for the component lifetime, applies\n// the layout half of the style and commits Yoga rects into the rect store,\n// where the parent container's RnGtkxLayout allocate() picks them up.\nexport const useLayoutChild = (\n widgetRef: RefObject<Gtk.Widget | null>,\n options: LayoutChildOptions,\n): LayoutChild => {\n const host = useHostNode()\n\n // useState's lazy initializer is guaranteed once-per-instance even under\n // the React Compiler — a lazy ref here got its creation memoized ACROSS\n // component instances, sharing one Yoga node and double-freeing it.\n const [node] = useState<LayoutNode>(() => host.engine.createNode())\n\n const flat = StyleSheet.flatten(options.style)\n const { layout, visual } = splitStyle(flat)\n const effectiveLayout = options.extraLayout\n ? { ...layout, ...options.extraLayout }\n : layout\n\n // Re-apply layout style only when it actually changed (keyed effect) —\n // setStyle dirties the whole tree.\n const layoutKey = JSON.stringify(effectiveLayout)\n useLayoutEffect(() => {\n node.setStyle(JSON.parse(layoutKey) as LayoutStyle)\n }, [node, layoutKey])\n\n const cssClass = defaultCssRegistry.getClassName(visual)\n\n // RN transforms are visual only: the widget keeps the box Yoga gave it, so\n // a transform never touches the shadow tree — it only asks the parent\n // container to run its allocate hook again. Keyed on the serialized array\n // so a re-render with the same transform costs nothing.\n // (Animated.View owns its own transform and strips it from the static\n // style before it gets here, so the two writers never race.)\n const transformKey = JSON.stringify(visual.transform ?? null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n setStoredTransform(widget, JSON.parse(transformKey) as TransformPart[])\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n queueAllocate(parentWidget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transformKey])\n\n useLayoutEffect(() => {\n const parent = host.node\n // The reconciler has already attached our widget in JSX order, so its\n // sibling position is the correct Yoga index — this keeps the shadow tree\n // ordered even for mid-list mounts (conditional children, list inserts).\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n let index = parent.children.length\n if (widget && parentWidget) {\n let position = 0\n let sibling = parentWidget.getFirstChild()\n while (sibling !== null && sibling !== widget) {\n position += 1\n sibling = sibling.getNextSibling()\n }\n if (sibling === widget) {\n index = Math.min(position, parent.children.length)\n }\n }\n parent.insertChild(node, index)\n node.setCommit((rect: Rect) => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n const previous = getStoredRect(widget)\n // A commit does not imply a change. The engine may hand us the rect a\n // widget already has — nodes carrying a measure function are visited\n // even when Yoga did not move them — and queueing an allocation for\n // those makes GTK re-allocate and re-snapshot the container for nothing.\n //\n // `rect.skip` counts how often this guard actually fires and\n // `rect.change` how often it does not, because a guard nobody can see\n // firing is a guard nobody can attribute a measurement to: this one was\n // once credited with a large maximized-window win that a later, properly\n // geometry-controlled run could not reproduce (docs/research/\n // scroll-performance.md, round six). Keep it — it is correct and free —\n // but read the counter before crediting it with anything.\n const unchanged =\n previous !== undefined &&\n previous.x === rect.x &&\n previous.y === rect.y &&\n previous.width === rect.width &&\n previous.height === rect.height\n if (perfEnabled) {\n perfCount(unchanged ? \"rect.skip\" : \"rect.change\")\n }\n if (unchanged) {\n return\n }\n setStoredRect(widget, rect)\n const sizeChanged =\n !previous ||\n previous.width !== rect.width ||\n previous.height !== rect.height\n const queue = (): void => {\n if (sizeChanged) {\n // A size change invalidates this widget's cached measure (nested\n // managers, ScrolledWindow ranges) and re-allocates the ancestors.\n queueResize(widget)\n } else {\n // Pure move: the parent just needs another allocation pass. GTK\n // dedupes queued allocates, so per-child calls batch into one.\n queueAllocate(parentWidget)\n }\n }\n if (!deferDuringAllocate(widget, queue)) {\n queue()\n }\n })\n return () => {\n node.setCommit(null)\n parent.removeChild(node)\n node.free()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const onLayout = options.onLayout\n useLayoutEffect(() => {\n node.setOnLayout(\n onLayout\n ? (rect: Rect) => {\n onLayout({ nativeEvent: { layout: rect } })\n }\n : null,\n )\n }, [node, onLayout])\n\n // The explicit return type keeps the inner params contextually typed: with a\n // bare lazy initializer TS may match the outer arrow against MeasureFn\n // itself (useState accepts S | () => S) and drop the context.\n const [widgetMeasure] = useState<MeasureFn>(\n (): MeasureFn => (width, widthMode) => {\n const widget = widgetRef.current\n if (!widget) {\n return { width: 0, height: 0 }\n }\n const { minimum, natural } = measureWidget(widget, \"horizontal\")\n // Floor at the widget's own minimum width, not at 1 — see the same\n // clamp in text.tsx's measure for why: gtk_widget_measure() enforces\n // this floor internally regardless of what we request (warning while\n // it does), so anything below `minimum` was never actually honored.\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(minimum, Math.floor(width)))\n return {\n width: used,\n height: measureWidget(widget, \"vertical\", used).natural,\n }\n },\n )\n const measure =\n options.measure ?? (options.measureFromWidget ? widgetMeasure : undefined)\n useLayoutEffect(() => {\n if (!measure) {\n return\n }\n node.setMeasureFn(measure)\n // Widgets measured before their window is mapped report zero (no realized\n // theme context yet, unlike offscreen probes) and Yoga caches it —\n // invalidate once the widget actually maps.\n const widget = widgetRef.current\n const remeasure = (): void => {\n node.markDirty()\n }\n if (options.measureFromWidget && widget) {\n widget.on(\"map\", remeasure)\n }\n return () => {\n if (options.measureFromWidget && widget) {\n widget.off(\"map\", remeasure)\n }\n // Cleared by free() on unmount; explicit unset only when measure is\n // removed while mounted.\n node.setMeasureFn(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, measure])\n\n return { host, node, flat, visual, cssClass }\n}\n\nexport type RnContainerOptions = {\n // Overrides the default engine-rect measure (the window root reports 0 so\n // the window can shrink freely below the current content size; the\n // intrinsic root measures its content through Yoga, honoring forSize for\n // height-for-width).\n measure?: (orientation: \"horizontal\" | \"vertical\", forSize: number) => number\n // Runs before children are placed — the window root syncs the engine\n // viewport to the actual allocation here.\n beforeAllocate?: (width: number, height: number) => void\n}\n\n// Containers (Root, View, Animated.View, ScrollView content) drive their GTK\n// side with an RnGtkxLayout: measure() reports the engine rect of `node`\n// (minimum == natural — GTK minimums of children never leak upward), and\n// allocate() places every child widget at exactly its stored engine rect\n// plus the Animated offset.\nexport const useRnContainer = (\n widgetRef: RefObject<Gtk.Widget | null>,\n node: LayoutNode,\n options?: RnContainerOptions,\n): void => {\n const optionsRef = useRef<RnContainerOptions | undefined>(options)\n optionsRef.current = options\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n attachRnLayout(widget, {\n measure: (orientation, forSize) => {\n const custom = optionsRef.current?.measure\n if (custom) {\n return custom(orientation, forSize)\n }\n const rect = node.getRect()\n return Math.round(\n (orientation === \"horizontal\" ? rect?.width : rect?.height) ?? 0,\n )\n },\n allocate: (width, height) => {\n const start = perfEnabled ? perfNow() : 0\n perfAllocDepth += 1\n optionsRef.current?.beforeAllocate?.(width, height)\n let child = widget.getFirstChild()\n let children = 0\n while (child) {\n const rect = getStoredRect(child)\n if (rect) {\n const offset = getStoredOffset(child)\n allocateChild(\n child,\n rect.x + offset.dx,\n rect.y + offset.dy,\n rect.width,\n rect.height,\n offset.matrix,\n )\n children += 1\n }\n child = child.getNextSibling()\n }\n perfAllocDepth -= 1\n if (perfEnabled) {\n perfCount(\"gtk.allocPass\")\n perfCount(\"gtk.allocChild\", children)\n if (perfAllocDepth === 0) {\n const elapsed = perfNow() - start\n perfAddTime(\"gtk.allocTop\", elapsed)\n perfBurst(\"gtk.allocMsPerFrame\", elapsed)\n }\n }\n },\n })\n return () => {\n detachRnLayout(widget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"]}
@@ -1,13 +1,18 @@
1
- import { type ReactNode } from "react";
1
+ import { type ReactNode, type Ref } from "react";
2
2
  import type { PointerEventsValue, StyleProp } from "../contracts";
3
+ import type { ResponderProps } from "../responder/types";
4
+ import { type MeasureHandle } from "./measure";
3
5
  import { type LayoutEvent } from "./use-layout-child";
4
- export type ViewProps = {
6
+ /** RN's imperative geometry methods, on a `View` ref. */
7
+ export type ViewHandle = MeasureHandle;
8
+ export type ViewProps = ResponderProps & {
5
9
  style?: StyleProp;
6
10
  pointerEvents?: PointerEventsValue;
7
11
  onLayout?: (event: LayoutEvent) => void;
8
12
  children?: ReactNode;
9
13
  testID?: string;
14
+ ref?: Ref<ViewHandle>;
10
15
  };
11
- export declare const View: ({ style, pointerEvents, onLayout, children, testID, }: ViewProps) => React.JSX.Element;
12
- export declare const SafeAreaView: ({ style, pointerEvents, onLayout, children, testID, }: ViewProps) => React.JSX.Element;
16
+ export declare const View: ({ style, pointerEvents, onLayout, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
17
+ export declare const SafeAreaView: ({ style, pointerEvents, onLayout, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
13
18
  export declare const StatusBar: () => null;
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useLayoutEffect, useRef } from "react";
2
+ import { useImperativeHandle, useLayoutEffect, useRef, } from "react";
3
3
  import { StyleSheet } from "../style/index";
4
4
  import { getViewBoxComponent, setBoxPassthrough, } from "../gtkx/bridge/index";
5
+ import { useResponder } from "../responder/use-responder";
5
6
  import { HostNodeContext } from "./host-node";
7
+ import { createMeasureHandle } from "./measure";
6
8
  import { useLayoutChild, useRnContainer, } from "./use-layout-child";
7
9
  // Every View is a GtkBox subclass (RnGtkxViewBox) driven by RnGtkxLayout:
8
10
  // Yoga computes the children's rects, the manager's allocate() applies
@@ -17,13 +19,17 @@ import { useLayoutChild, useRnContainer, } from "./use-layout-child";
17
19
  // can-target=false; restored when the mode changes. Nesting another
18
20
  // pointerEvents INSIDE a box-only view is not supported (the restore
19
21
  // pass cannot know about it).
20
- export const View = ({ style, pointerEvents, onLayout, children, testID, }) => {
22
+ export const View = ({ style, pointerEvents, onLayout, children, testID, ref, ...responderProps }) => {
21
23
  const widgetRef = useRef(null);
22
24
  const { host, node, cssClass } = useLayoutChild(widgetRef, {
23
25
  style,
24
26
  onLayout,
25
27
  });
26
28
  useRnContainer(widgetRef, node);
29
+ useImperativeHandle(ref, () => createMeasureHandle(widgetRef, node), [node]);
30
+ // Spreading PanResponder's panHandlers onto a View is the portable RN
31
+ // idiom, so every responder prop arrives here as a rest prop.
32
+ useResponder(widgetRef, responderProps);
27
33
  const mode = pointerEvents ?? StyleSheet.flatten(style)?.pointerEvents ?? "auto";
28
34
  useLayoutEffect(() => {
29
35
  const widget = widgetRef.current;
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/components/view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EACL,mBAAmB,EAEnB,iBAAiB,GAElB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,cAAc,EACd,cAAc,GAEf,MAAM,oBAAoB,CAAA;AAW3B,0EAA0E;AAC1E,uEAAuE;AACvE,sEAAsE;AACtE,+CAA+C;AAC/C,0EAA0E;AAC1E,uDAAuD;AACvD,kEAAkE;AAClE,wEAAwE;AACxE,uDAAuD;AACvD,4DAA4D;AAC5D,sEAAsE;AACtE,uEAAuE;AACvE,gCAAgC;AAChC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,MAAM,GACI,EAAE,EAAE;IACd,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC9C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,SAAS,EAAE;QACzD,KAAK;QACL,QAAQ;KACT,CAAC,CAAA;IACF,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAE/B,MAAM,IAAI,GACR,aAAa,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa,IAAI,MAAM,CAAA;IAErE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAM;QACR,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;QACpC,iBAAiB,CAAC,MAAM,EAAE,IAAI,KAAK,UAAU,CAAC,CAAA;IAChD,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAChC,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,KAAK,UAAU,CAAA;QACnC,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,CAAA;YAClC,OAAO,KAAK,EAAE,CAAC;gBACb,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAA;gBAC5B,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAA;YAChC,CAAC;QACH,CAAC;QACD,UAAU,CAAC,OAAO,GAAG,OAAO,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,mBAAmB,EAAmB,CAAA;IACtD,OAAO,CACL,KAAC,OAAO,IACN,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,YAEtC,KAAC,eAAe,CAAC,QAAQ,IACvB,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,YAE9C,QAAQ,GACgB,GACnB,CACX,CAAA;AACH,CAAC,CAAA;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAA;AAEhC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAS,EAAE,CAAC,IAAI,CAAA","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { StyleSheet } from \"../style/index\"\nimport type { PointerEventsValue, StyleProp } from \"../contracts\"\nimport {\n getViewBoxComponent,\n GtkBox,\n setBoxPassthrough,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type ViewProps = {\n style?: StyleProp\n // RN pointerEvents; the prop wins over style.pointerEvents (RN 0.71+).\n pointerEvents?: PointerEventsValue\n onLayout?: (event: LayoutEvent) => void\n children?: ReactNode\n testID?: string\n}\n\n// Every View is a GtkBox subclass (RnGtkxViewBox) driven by RnGtkxLayout:\n// Yoga computes the children's rects, the manager's allocate() applies\n// them. Visual styles arrive as a GTK CSS class produced by the style\n// system. pointerEvents maps onto GTK picking:\n// - none: can-target=false — GTK skips the widget WITHOUT descending, the\n// whole subtree is transparent (exact RN semantics);\n// - box-none: the subclass' contains() fails for this widget (see\n// view-box.ts) — the box is never the pick target while children stay\n// pickable, and toggling never remounts the subtree;\n// - box-only: direct children (and thus their subtrees) get\n// can-target=false; restored when the mode changes. Nesting another\n// pointerEvents INSIDE a box-only view is not supported (the restore\n// pass cannot know about it).\nexport const View = ({\n style,\n pointerEvents,\n onLayout,\n children,\n testID,\n}: ViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const mode: PointerEventsValue =\n pointerEvents ?? StyleSheet.flatten(style)?.pointerEvents ?? \"auto\"\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n widget.setCanTarget(mode !== \"none\")\n setBoxPassthrough(widget, mode === \"box-none\")\n }, [mode])\n\n // box-only walks the current children every commit (the set changes with\n // renders) and restores once when the mode moves away.\n const wasBoxOnly = useRef(false)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const boxOnly = mode === \"box-only\"\n if (boxOnly || wasBoxOnly.current) {\n let child = widget.getFirstChild()\n while (child) {\n child.setCanTarget(!boxOnly)\n child = child.getNextSibling()\n }\n }\n wasBoxOnly.current = boxOnly\n })\n\n const ViewBox = getViewBoxComponent() as typeof GtkBox\n return (\n <ViewBox\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 </ViewBox>\n )\n}\n\n// RN parity aliases (no notches or status bars on the Linux desktop).\nexport const SafeAreaView = View\n\nexport const StatusBar = (): null => null\n"]}
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/components/view.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n type ReactNode,\n type Ref,\n} from \"react\"\nimport { StyleSheet } from \"../style/index\"\nimport type { PointerEventsValue, StyleProp } from \"../contracts\"\nimport {\n getViewBoxComponent,\n GtkBox,\n setBoxPassthrough,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport type { ResponderProps } from \"../responder/types\"\nimport { useResponder } from \"../responder/use-responder\"\nimport { HostNodeContext } from \"./host-node\"\nimport { createMeasureHandle, type MeasureHandle } from \"./measure\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\n/** RN's imperative geometry methods, on a `View` ref. */\nexport type ViewHandle = MeasureHandle\n\nexport type ViewProps = ResponderProps & {\n style?: StyleProp\n // RN pointerEvents; the prop wins over style.pointerEvents (RN 0.71+).\n pointerEvents?: PointerEventsValue\n onLayout?: (event: LayoutEvent) => void\n children?: ReactNode\n testID?: string\n // React 19 takes `ref` as an ordinary prop, which is what we want here:\n // forwardRef is booby-trapped on this stack (useEffectEvent never\n // refreshes inside it — see gtkx/bridge/use-signal.ts).\n ref?: Ref<ViewHandle>\n}\n\n// Every View is a GtkBox subclass (RnGtkxViewBox) driven by RnGtkxLayout:\n// Yoga computes the children's rects, the manager's allocate() applies\n// them. Visual styles arrive as a GTK CSS class produced by the style\n// system. pointerEvents maps onto GTK picking:\n// - none: can-target=false — GTK skips the widget WITHOUT descending, the\n// whole subtree is transparent (exact RN semantics);\n// - box-none: the subclass' contains() fails for this widget (see\n// view-box.ts) — the box is never the pick target while children stay\n// pickable, and toggling never remounts the subtree;\n// - box-only: direct children (and thus their subtrees) get\n// can-target=false; restored when the mode changes. Nesting another\n// pointerEvents INSIDE a box-only view is not supported (the restore\n// pass cannot know about it).\nexport const View = ({\n style,\n pointerEvents,\n onLayout,\n children,\n testID,\n ref,\n ...responderProps\n}: ViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n useImperativeHandle(ref, () => createMeasureHandle(widgetRef, node), [node])\n\n // Spreading PanResponder's panHandlers onto a View is the portable RN\n // idiom, so every responder prop arrives here as a rest prop.\n useResponder(widgetRef, responderProps)\n\n const mode: PointerEventsValue =\n pointerEvents ?? StyleSheet.flatten(style)?.pointerEvents ?? \"auto\"\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n widget.setCanTarget(mode !== \"none\")\n setBoxPassthrough(widget, mode === \"box-none\")\n }, [mode])\n\n // box-only walks the current children every commit (the set changes with\n // renders) and restores once when the mode moves away.\n const wasBoxOnly = useRef(false)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const boxOnly = mode === \"box-only\"\n if (boxOnly || wasBoxOnly.current) {\n let child = widget.getFirstChild()\n while (child) {\n child.setCanTarget(!boxOnly)\n child = child.getNextSibling()\n }\n }\n wasBoxOnly.current = boxOnly\n })\n\n const ViewBox = getViewBoxComponent() as typeof GtkBox\n return (\n <ViewBox\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 </ViewBox>\n )\n}\n\n// RN parity aliases (no notches or status bars on the Linux desktop).\nexport const SafeAreaView = View\n\nexport const StatusBar = (): null => null\n"]}
@@ -44,6 +44,7 @@ export type LayoutStyle = Partial<{
44
44
  top: DimensionValue;
45
45
  width: DimensionValue;
46
46
  }>;
47
+ export type Angle = `${number}deg` | `${number}rad`;
47
48
  export type TransformPart = {
48
49
  translateX: number;
49
50
  } | {
@@ -55,10 +56,30 @@ export type TransformPart = {
55
56
  } | {
56
57
  scaleY: number;
57
58
  } | {
58
- rotate: `${number}deg` | `${number}rad`;
59
+ rotate: Angle;
60
+ } | {
61
+ rotateZ: Angle;
62
+ };
63
+ export type Transform2D = {
64
+ xx: number;
65
+ yx: number;
66
+ xy: number;
67
+ yy: number;
68
+ dx: number;
69
+ dy: number;
70
+ };
71
+ export type BoxShadowValue = {
72
+ offsetX: number | string;
73
+ offsetY: number | string;
74
+ color?: string;
75
+ blurRadius?: number | string;
76
+ spreadDistance?: number | string;
77
+ inset?: boolean;
59
78
  };
79
+ export type TextDecorationLine = "none" | "underline" | "line-through" | "underline line-through";
60
80
  export type VisualStyle = Partial<{
61
81
  backgroundColor: string;
82
+ boxShadow: string | readonly BoxShadowValue[];
62
83
  borderBottomColor: string;
63
84
  borderBottomLeftRadius: number;
64
85
  borderBottomRightRadius: number;
@@ -83,13 +104,19 @@ export type VisualStyle = Partial<{
83
104
  letterSpacing: number;
84
105
  lineHeight: number;
85
106
  opacity: number;
107
+ outlineColor: string;
108
+ outlineOffset: number;
109
+ outlineStyle: "solid" | "dotted" | "dashed";
110
+ outlineWidth: number;
86
111
  textAlign: "auto" | "left" | "right" | "center" | "justify";
112
+ textDecorationLine: TextDecorationLine;
87
113
  transform: TransformPart[];
88
114
  }>;
89
115
  export type PointerEventsValue = "auto" | "none" | "box-none" | "box-only";
90
- export type FlatStyle = LayoutStyle & VisualStyle & {
91
- pointerEvents?: PointerEventsValue;
92
- };
116
+ export type BehavioralStyle = Partial<{
117
+ pointerEvents: PointerEventsValue;
118
+ }>;
119
+ export type FlatStyle = LayoutStyle & VisualStyle & BehavioralStyle;
93
120
  export type StyleProp<T = FlatStyle> = T | null | undefined | false | ReadonlyArray<StyleProp<T>>;
94
121
  export type SplitStyle = {
95
122
  layout: LayoutStyle;
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,sEAAsE","sourcesContent":["// Shared contracts between the layout engine, the style system, the API\n// modules and the components — the one module every layer may import.\n\n// --- style ------------------------------------------------------------------\n\nexport type DimensionValue = number | `${number}%` | \"auto\"\n\nexport type FlexAlignType =\n \"flex-start\" | \"flex-end\" | \"center\" | \"stretch\" | \"baseline\"\n\n// Layout-affecting props: classified by the style system, consumed by the\n// layout engine, which maps them onto Yoga node setters.\nexport type LayoutStyle = Partial<{\n alignContent:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"space-between\"\n | \"space-around\"\n alignItems: FlexAlignType\n alignSelf: \"auto\" | FlexAlignType\n aspectRatio: number | string\n bottom: DimensionValue\n columnGap: number\n direction: \"inherit\" | \"ltr\" | \"rtl\"\n display: \"none\" | \"flex\"\n flex: number\n flexBasis: DimensionValue\n flexDirection: \"row\" | \"row-reverse\" | \"column\" | \"column-reverse\"\n flexGrow: number\n flexShrink: number\n flexWrap: \"wrap\" | \"nowrap\" | \"wrap-reverse\"\n gap: number\n height: DimensionValue\n justifyContent:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\"\n left: DimensionValue\n margin: DimensionValue\n marginBottom: DimensionValue\n marginHorizontal: DimensionValue\n marginLeft: DimensionValue\n marginRight: DimensionValue\n marginTop: DimensionValue\n marginVertical: DimensionValue\n maxHeight: DimensionValue\n maxWidth: DimensionValue\n minHeight: DimensionValue\n minWidth: DimensionValue\n overflow: \"visible\" | \"hidden\" | \"scroll\"\n padding: DimensionValue\n paddingBottom: DimensionValue\n paddingHorizontal: DimensionValue\n paddingLeft: DimensionValue\n paddingRight: DimensionValue\n paddingTop: DimensionValue\n paddingVertical: DimensionValue\n position: \"absolute\" | \"relative\" | \"static\"\n right: DimensionValue\n rowGap: number\n top: DimensionValue\n width: DimensionValue\n}>\n\nexport type TransformPart =\n | { translateX: number }\n | { translateY: number }\n | { scale: number }\n | { scaleX: number }\n | { scaleY: number }\n | { rotate: `${number}deg` | `${number}rad` }\n\n// Visual props: classified by the style system, compiled into GTK CSS\n// classes via the bridge `css` helper. Text-specific props apply to <Text>.\nexport type VisualStyle = Partial<{\n backgroundColor: string\n borderBottomColor: string\n borderBottomLeftRadius: number\n borderBottomRightRadius: number\n borderBottomWidth: number\n borderColor: string\n borderLeftColor: string\n borderLeftWidth: number\n borderRadius: number\n borderRightColor: string\n borderRightWidth: number\n borderStyle: \"solid\" | \"dotted\" | \"dashed\"\n borderTopColor: string\n borderTopLeftRadius: number\n borderTopRightRadius: number\n borderTopWidth: number\n borderWidth: number\n color: string\n fontFamily: string\n fontSize: number\n fontStyle: \"normal\" | \"italic\"\n fontWeight:\n | \"normal\"\n | \"bold\"\n | \"100\"\n | \"200\"\n | \"300\"\n | \"400\"\n | \"500\"\n | \"600\"\n | \"700\"\n | \"800\"\n | \"900\"\n letterSpacing: number\n lineHeight: number\n opacity: number\n // NOT emitted to CSS (no such GTK CSS property): the Text component applies\n // it via label props using the pure style/text-align helper.\n textAlign: \"auto\" | \"left\" | \"right\" | \"center\" | \"justify\"\n // NOT emitted to CSS: applied by the engine through the layout manager\n // layout-child transforms (Animated fast path).\n transform: TransformPart[]\n}>\n\n// RN pointerEvents (also allowed in styles since RN 0.71); behavioral, so\n// it belongs to neither the layout nor the visual bucket — splitStyle\n// ignores it and View reads it from the flattened style.\nexport type PointerEventsValue = \"auto\" | \"none\" | \"box-none\" | \"box-only\"\n\nexport type FlatStyle = LayoutStyle &\n VisualStyle & {\n pointerEvents?: PointerEventsValue\n }\n\n// Style prop as components accept it: single object, array (with falsy holes).\nexport type StyleProp<T = FlatStyle> =\n T | null | undefined | false | ReadonlyArray<StyleProp<T>>\n\n// Result of classifying a flattened style (produced by the style system):\nexport type SplitStyle = {\n layout: LayoutStyle\n visual: VisualStyle\n}\n\n// --- layout engine ----------------------------------------------------------\n\nexport type Rect = {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport type MeasureConstraintMode = \"undefined\" | \"exactly\" | \"at-most\"\n\nexport type MeasureSize = { width: number; height: number }\n\n// Measure callback for leaf nodes (text, images): implemented by components\n// using bridge probes; called by the engine during Yoga layout passes.\nexport type MeasureFn = (\n width: number,\n widthMode: MeasureConstraintMode,\n height: number,\n heightMode: MeasureConstraintMode,\n) => MeasureSize\n\n// Handle the layout engine gives every mounted component.\nexport interface LayoutNodeApi {\n setStyle(style: LayoutStyle): void\n setMeasureFn(measure: MeasureFn | null): void\n markDirty(): void\n getRect(): Rect | null\n setOnLayout(callback: ((rect: Rect) => void) | null): void\n}\n\n// --- subscriptions ----------------------------------------------------------\n\nexport type SubscriptionHandle = { remove(): void }\n"]}
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,sEAAsE","sourcesContent":["// Shared contracts between the layout engine, the style system, the API\n// modules and the components — the one module every layer may import.\n\n// --- style ------------------------------------------------------------------\n\nexport type DimensionValue = number | `${number}%` | \"auto\"\n\nexport type FlexAlignType =\n \"flex-start\" | \"flex-end\" | \"center\" | \"stretch\" | \"baseline\"\n\n// Layout-affecting props: classified by the style system, consumed by the\n// layout engine, which maps them onto Yoga node setters.\nexport type LayoutStyle = Partial<{\n alignContent:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"space-between\"\n | \"space-around\"\n alignItems: FlexAlignType\n alignSelf: \"auto\" | FlexAlignType\n aspectRatio: number | string\n bottom: DimensionValue\n columnGap: number\n direction: \"inherit\" | \"ltr\" | \"rtl\"\n display: \"none\" | \"flex\"\n flex: number\n flexBasis: DimensionValue\n flexDirection: \"row\" | \"row-reverse\" | \"column\" | \"column-reverse\"\n flexGrow: number\n flexShrink: number\n flexWrap: \"wrap\" | \"nowrap\" | \"wrap-reverse\"\n gap: number\n height: DimensionValue\n justifyContent:\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\"\n left: DimensionValue\n margin: DimensionValue\n marginBottom: DimensionValue\n marginHorizontal: DimensionValue\n marginLeft: DimensionValue\n marginRight: DimensionValue\n marginTop: DimensionValue\n marginVertical: DimensionValue\n maxHeight: DimensionValue\n maxWidth: DimensionValue\n minHeight: DimensionValue\n minWidth: DimensionValue\n overflow: \"visible\" | \"hidden\" | \"scroll\"\n padding: DimensionValue\n paddingBottom: DimensionValue\n paddingHorizontal: DimensionValue\n paddingLeft: DimensionValue\n paddingRight: DimensionValue\n paddingTop: DimensionValue\n paddingVertical: DimensionValue\n position: \"absolute\" | \"relative\" | \"static\"\n right: DimensionValue\n rowGap: number\n top: DimensionValue\n width: DimensionValue\n}>\n\nexport type Angle = `${number}deg` | `${number}rad`\n\nexport type TransformPart =\n | { translateX: number }\n | { translateY: number }\n | { scale: number }\n | { scaleX: number }\n | { scaleY: number }\n | { rotate: Angle }\n // RN's 2D alias for `rotate` (rotation about the Z axis).\n | { rotateZ: Angle }\n\n// A 2D affine transform in the component order GSK and cairo use:\n// x' = xx * x + xy * y + dx\n// y' = yx * x + yy * y + dy\n// This is exactly the argument order of gsk_transform_matrix2d(), so a\n// composed TransformPart[] reaches GTK in a single FFI call.\nexport type Transform2D = {\n xx: number\n yx: number\n xy: number\n yy: number\n dx: number\n dy: number\n}\n\n// Visual props: classified by the style system, compiled into GTK CSS\n// classes via the bridge `css` helper. Text-specific props apply to <Text>.\n// One shadow of a `boxShadow`, field for field react-native's own\n// `BoxShadowValue` (RN 0.76+). Lengths are RN DIPs when numeric; the string\n// form accepts a bare number or `px` and nothing else, which is exactly what\n// RN's processBoxShadow.js accepts.\nexport type BoxShadowValue = {\n offsetX: number | string\n offsetY: number | string\n color?: string\n blurRadius?: number | string\n spreadDistance?: number | string\n inset?: boolean\n}\n\nexport type TextDecorationLine =\n \"none\" | \"underline\" | \"line-through\" | \"underline line-through\"\n\nexport type VisualStyle = Partial<{\n backgroundColor: string\n // RN 0.76+. Both forms RN takes: a CSS `box-shadow` string or the\n // structured array. Compiled by style/box-shadow.ts rather than forwarded,\n // so colours go through the same normalizer as every other colour prop.\n boxShadow: string | readonly BoxShadowValue[]\n borderBottomColor: string\n borderBottomLeftRadius: number\n borderBottomRightRadius: number\n borderBottomWidth: number\n borderColor: string\n borderLeftColor: string\n borderLeftWidth: number\n borderRadius: number\n borderRightColor: string\n borderRightWidth: number\n borderStyle: \"solid\" | \"dotted\" | \"dashed\"\n borderTopColor: string\n borderTopLeftRadius: number\n borderTopRightRadius: number\n borderTopWidth: number\n borderWidth: number\n color: string\n fontFamily: string\n fontSize: number\n fontStyle: \"normal\" | \"italic\"\n fontWeight:\n | \"normal\"\n | \"bold\"\n | \"100\"\n | \"200\"\n | \"300\"\n | \"400\"\n | \"500\"\n | \"600\"\n | \"700\"\n | \"800\"\n | \"900\"\n letterSpacing: number\n lineHeight: number\n opacity: number\n // RN 0.77+. A ring drawn around the border box that takes no layout space\n // — CSS `outline`, which GTK4 implements natively and Adwaita itself uses\n // for every focus ring in the theme.\n outlineColor: string\n outlineOffset: number\n outlineStyle: \"solid\" | \"dotted\" | \"dashed\"\n outlineWidth: number\n // NOT emitted to CSS (no such GTK CSS property): the Text component applies\n // it via label props using the pure style/text-align helper.\n textAlign: \"auto\" | \"left\" | \"right\" | \"center\" | \"justify\"\n // NOT emitted to CSS either GTK4 CSS has no widget `text-decoration`.\n // Pango carries it, so the Text component turns it into the label's\n // attribute list using the pure style/text-decoration helper.\n textDecorationLine: TextDecorationLine\n // NOT emitted to CSS (GTK4 has no widget `transform` property): composed\n // into a Transform2D by style/transform.ts and applied as the GskTransform\n // of the child's allocation by the container's layout manager.\n transform: TransformPart[]\n}>\n\n// RN pointerEvents (also allowed in styles since RN 0.71); behavioral, so\n// it belongs to neither the layout nor the visual bucket — splitStyle\n// consumes it without routing it and View reads it from the flattened style.\nexport type PointerEventsValue = \"auto\" | \"none\" | \"box-none\" | \"box-only\"\n\n// The third bucket, named rather than inlined into FlatStyle below: props\n// that are supported but consumed by the component that owns the behavior,\n// not by either half of the style pipeline. Having a name for it is what\n// lets splitStyle state which keys it deliberately routes nowhere, instead\n// of reporting them as unknown — the drift this type was born from.\nexport type BehavioralStyle = Partial<{\n pointerEvents: PointerEventsValue\n}>\n\nexport type FlatStyle = LayoutStyle & VisualStyle & BehavioralStyle\n\n// Style prop as components accept it: single object, array (with falsy holes).\nexport type StyleProp<T = FlatStyle> =\n T | null | undefined | false | ReadonlyArray<StyleProp<T>>\n\n// Result of classifying a flattened style (produced by the style system):\nexport type SplitStyle = {\n layout: LayoutStyle\n visual: VisualStyle\n}\n\n// --- layout engine ----------------------------------------------------------\n\nexport type Rect = {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport type MeasureConstraintMode = \"undefined\" | \"exactly\" | \"at-most\"\n\nexport type MeasureSize = { width: number; height: number }\n\n// Measure callback for leaf nodes (text, images): implemented by components\n// using bridge probes; called by the engine during Yoga layout passes.\nexport type MeasureFn = (\n width: number,\n widthMode: MeasureConstraintMode,\n height: number,\n heightMode: MeasureConstraintMode,\n) => MeasureSize\n\n// Handle the layout engine gives every mounted component.\nexport interface LayoutNodeApi {\n setStyle(style: LayoutStyle): void\n setMeasureFn(measure: MeasureFn | null): void\n markDirty(): void\n getRect(): Rect | null\n setOnLayout(callback: ((rect: Rect) => void) | null): void\n}\n\n// --- subscriptions ----------------------------------------------------------\n\nexport type SubscriptionHandle = { remove(): void }\n"]}
@@ -0,0 +1,7 @@
1
+ export * from "./widgets.generated";
2
+ export { Gdk, Gio, GLib, GObject, Gtk, Pango } from "../gtkx/bridge/index";
3
+ export { GMenu, GtkAdjustment, GtkDragSource, GtkDropTarget, GtkShortcut, GtkShortcutController, GtkTextBuffer, } from "../gtkx/bridge/index";
4
+ export { ApplicationActions, GSimpleAction, WindowActions, WindowControllers, type ApplicationActionsProps, type GSimpleActionProps, type WindowActionsProps, type WindowControllersProps, } from "./window-actions";
5
+ export { useBindSetting, useSetting, type SettingsSchema, type SettingsSchemaKeys, type SettingValue, } from "../gtkx/bridge/index";
6
+ export { css, cx, injectGlobal } from "../gtkx/bridge/index";
7
+ export { quit, useApplication, useParentWindow } from "../gtkx/bridge/index";
@@ -0,0 +1,46 @@
1
+ // The full GTK widget surface lives in a generated file: every Gtk.Widget
2
+ // subclass gtkx binds, wrapped so React Native drives it. See
3
+ // scripts/generate-widget-surface.ts and
4
+ // scripts/widget-surface/classification.json for how the list is derived.
5
+ export * from "./widgets.generated";
6
+ // The GTK namespaces themselves, exported as values because they carry both
7
+ // the enums you need at runtime — `hscrollbarPolicy={Gtk.PolicyType.NEVER}` —
8
+ // and the types you need for refs.
9
+ export { Gdk, Gio, GLib, GObject, Gtk, Pango } from "../gtkx/bridge/index";
10
+ // Auxiliary JSX elements that are not Gtk.Widget subclasses, so the
11
+ // generated widget surface above never sees them — real building blocks a
12
+ // non-trivial app still needs: actions and menus (Gio, used through
13
+ // `Gtk.Application`/`Gtk.ApplicationWindow`), a text buffer and an
14
+ // adjustment (the model objects `GtkTextView`/`GtkSpinRow`-style widgets
15
+ // bind to), keyboard shortcuts, and the two drag-and-drop controllers. See
16
+ // docs/platform-layer.md "Unwrapped by necessity".
17
+ export { GMenu, GtkAdjustment, GtkDragSource, GtkDropTarget, GtkShortcut, GtkShortcutController, GtkTextBuffer, } from "../gtkx/bridge/index";
18
+ // Window and application actions declared inside the app tree instead of
19
+ // around it (the AppRegistry options they replace cannot see app context —
20
+ // see window-actions.tsx). GSimpleAction comes from here rather than
21
+ // straight from the bridge because inside these components it arbitrates
22
+ // duplicate action names; on every other path it is gtkx's element,
23
+ // unchanged.
24
+ export { ApplicationActions, GSimpleAction, WindowActions, WindowControllers, } from "./window-actions";
25
+ // GSettings: reads and writes backed by a compiled `.gschema.xml` schema.
26
+ // `useSetting`/`useBindSetting` come straight from @gtkx/react; loading a
27
+ // `.gschema.xml` file into the `SettingsSchema` object they expect is a
28
+ // build-time concern (see docs/platform-layer.md).
29
+ export { useBindSetting, useSetting, } from "../gtkx/bridge/index";
30
+ // @gtkx/css — a real GTK CSS class from a tagged template, the same
31
+ // mechanism the style prop's visual half uses under the hood. Re-exported
32
+ // here rather than left to a direct @gtkx/css dependency for the same
33
+ // reason the namespaces above are: one subpath for the whole gtkx toolkit
34
+ // surface, not just Gtk.Widget subclasses. `GObject` above is exported
35
+ // mainly for `GObject.Value` — constructing a boxed value is required by
36
+ // APIs like `Adw.Breakpoint.addSetter`, which reject a bare JS primitive
37
+ // (see docs/platform-layer.md, "Two ways to react to size").
38
+ export { css, cx, injectGlobal } from "../gtkx/bridge/index";
39
+ // The window and application AppRegistry itself sits on: useParentWindow
40
+ // reaches the Gtk.Window ancestor (for anything not modeled by a prop —
41
+ // e.g. binding a GSettings key to its own defaultWidth/defaultHeight),
42
+ // useApplication reaches the Adw.Application (e.g. to send a
43
+ // Gio.Notification), quit tears the whole app down programmatically (the
44
+ // same function AppRegistry wires to a window's own close button).
45
+ export { quit, useApplication, useParentWindow } from "../gtkx/bridge/index";
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gtk/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8DAA8D;AAC9D,yCAAyC;AACzC,0EAA0E;AAC1E,cAAc,qBAAqB,CAAA;AAEnC,4EAA4E;AAC5E,8EAA8E;AAC9E,mCAAmC;AACnC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAE1E,oEAAoE;AACpE,0EAA0E;AAC1E,oEAAoE;AACpE,mEAAmE;AACnE,yEAAyE;AACzE,2EAA2E;AAC3E,mDAAmD;AACnD,OAAO,EACL,KAAK,EACL,aAAa,EACb,aAAa,EACb,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,aAAa,GACd,MAAM,sBAAsB,CAAA;AAE7B,yEAAyE;AACzE,2EAA2E;AAC3E,qEAAqE;AACrE,yEAAyE;AACzE,oEAAoE;AACpE,aAAa;AACb,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,iBAAiB,GAKlB,MAAM,kBAAkB,CAAA;AAEzB,0EAA0E;AAC1E,0EAA0E;AAC1E,wEAAwE;AACxE,mDAAmD;AACnD,OAAO,EACL,cAAc,EACd,UAAU,GAIX,MAAM,sBAAsB,CAAA;AAE7B,oEAAoE;AACpE,0EAA0E;AAC1E,sEAAsE;AACtE,0EAA0E;AAC1E,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,6DAA6D;AAC7D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAE5D,yEAAyE;AACzE,wEAAwE;AACxE,uEAAuE;AACvE,6DAA6D;AAC7D,yEAAyE;AACzE,mEAAmE;AACnE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA","sourcesContent":["// The full GTK widget surface lives in a generated file: every Gtk.Widget\n// subclass gtkx binds, wrapped so React Native drives it. See\n// scripts/generate-widget-surface.ts and\n// scripts/widget-surface/classification.json for how the list is derived.\nexport * from \"./widgets.generated\"\n\n// The GTK namespaces themselves, exported as values because they carry both\n// the enums you need at runtime — `hscrollbarPolicy={Gtk.PolicyType.NEVER}` —\n// and the types you need for refs.\nexport { Gdk, Gio, GLib, GObject, Gtk, Pango } from \"../gtkx/bridge/index\"\n\n// Auxiliary JSX elements that are not Gtk.Widget subclasses, so the\n// generated widget surface above never sees them — real building blocks a\n// non-trivial app still needs: actions and menus (Gio, used through\n// `Gtk.Application`/`Gtk.ApplicationWindow`), a text buffer and an\n// adjustment (the model objects `GtkTextView`/`GtkSpinRow`-style widgets\n// bind to), keyboard shortcuts, and the two drag-and-drop controllers. See\n// docs/platform-layer.md \"Unwrapped by necessity\".\nexport {\n GMenu,\n GtkAdjustment,\n GtkDragSource,\n GtkDropTarget,\n GtkShortcut,\n GtkShortcutController,\n GtkTextBuffer,\n} from \"../gtkx/bridge/index\"\n\n// Window and application actions declared inside the app tree instead of\n// around it (the AppRegistry options they replace cannot see app context —\n// see window-actions.tsx). GSimpleAction comes from here rather than\n// straight from the bridge because inside these components it arbitrates\n// duplicate action names; on every other path it is gtkx's element,\n// unchanged.\nexport {\n ApplicationActions,\n GSimpleAction,\n WindowActions,\n WindowControllers,\n type ApplicationActionsProps,\n type GSimpleActionProps,\n type WindowActionsProps,\n type WindowControllersProps,\n} from \"./window-actions\"\n\n// GSettings: reads and writes backed by a compiled `.gschema.xml` schema.\n// `useSetting`/`useBindSetting` come straight from @gtkx/react; loading a\n// `.gschema.xml` file into the `SettingsSchema` object they expect is a\n// build-time concern (see docs/platform-layer.md).\nexport {\n useBindSetting,\n useSetting,\n type SettingsSchema,\n type SettingsSchemaKeys,\n type SettingValue,\n} from \"../gtkx/bridge/index\"\n\n// @gtkx/css — a real GTK CSS class from a tagged template, the same\n// mechanism the style prop's visual half uses under the hood. Re-exported\n// here rather than left to a direct @gtkx/css dependency for the same\n// reason the namespaces above are: one subpath for the whole gtkx toolkit\n// surface, not just Gtk.Widget subclasses. `GObject` above is exported\n// mainly for `GObject.Value` — constructing a boxed value is required by\n// APIs like `Adw.Breakpoint.addSetter`, which reject a bare JS primitive\n// (see docs/platform-layer.md, \"Two ways to react to size\").\nexport { css, cx, injectGlobal } from \"../gtkx/bridge/index\"\n\n// The window and application AppRegistry itself sits on: useParentWindow\n// reaches the Gtk.Window ancestor (for anything not modeled by a prop —\n// e.g. binding a GSettings key to its own defaultWidth/defaultHeight),\n// useApplication reaches the Adw.Application (e.g. to send a\n// Gio.Notification), quit tears the whole app down programmatically (the\n// same function AppRegistry wires to a window's own close button).\nexport { quit, useApplication, useParentWindow } from \"../gtkx/bridge/index\"\n"]}