react-native-gtkx 0.1.0-alpha.3 → 0.2.0-alpha.1

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 (193) hide show
  1. package/README.md +41 -3
  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.js +1 -1
  9. package/dist/animated/create-animated.js.map +1 -1
  10. package/dist/animated/index.js +3 -3
  11. package/dist/animated/index.js.map +1 -1
  12. package/dist/common/content.d.ts +69 -0
  13. package/dist/common/content.js +84 -0
  14. package/dist/common/content.js.map +1 -0
  15. package/dist/common/index.d.ts +3 -0
  16. package/dist/common/index.js +19 -0
  17. package/dist/common/index.js.map +1 -0
  18. package/dist/common/navigation-stack.d.ts +84 -0
  19. package/dist/common/navigation-stack.js +313 -0
  20. package/dist/common/navigation-stack.js.map +1 -0
  21. package/dist/common/widget.d.ts +83 -0
  22. package/dist/common/widget.js +142 -0
  23. package/dist/common/widget.js.map +1 -0
  24. package/dist/components/animated.d.ts +3 -2
  25. package/dist/components/animated.js +85 -66
  26. package/dist/components/animated.js.map +1 -1
  27. package/dist/components/app-registry.d.ts +10 -1
  28. package/dist/components/app-registry.js +8 -3
  29. package/dist/components/app-registry.js.map +1 -1
  30. package/dist/components/pressable.js +109 -16
  31. package/dist/components/pressable.js.map +1 -1
  32. package/dist/components/rect-store.d.ts +11 -1
  33. package/dist/components/rect-store.js +27 -3
  34. package/dist/components/rect-store.js.map +1 -1
  35. package/dist/components/svg/animated-support.d.ts +19 -0
  36. package/dist/components/svg/animated-support.js +64 -0
  37. package/dist/components/svg/animated-support.js.map +1 -0
  38. package/dist/components/svg/context.d.ts +8 -0
  39. package/dist/components/svg/context.js +15 -0
  40. package/dist/components/svg/context.js.map +1 -0
  41. package/dist/components/svg/gradients.d.ts +31 -0
  42. package/dist/components/svg/gradients.js +93 -0
  43. package/dist/components/svg/gradients.js.map +1 -0
  44. package/dist/components/svg/group.d.ts +8 -0
  45. package/dist/components/svg/group.js +30 -0
  46. package/dist/components/svg/group.js.map +1 -0
  47. package/dist/components/svg/index.d.ts +6 -0
  48. package/dist/components/svg/index.js +5 -0
  49. package/dist/components/svg/index.js.map +1 -0
  50. package/dist/components/svg/node.d.ts +7 -0
  51. package/dist/components/svg/node.js +7 -0
  52. package/dist/components/svg/node.js.map +1 -0
  53. package/dist/components/svg/paint.d.ts +24 -0
  54. package/dist/components/svg/paint.js +57 -0
  55. package/dist/components/svg/paint.js.map +1 -0
  56. package/dist/components/svg/shapes.d.ts +47 -0
  57. package/dist/components/svg/shapes.js +51 -0
  58. package/dist/components/svg/shapes.js.map +1 -0
  59. package/dist/components/svg/svg.d.ts +14 -0
  60. package/dist/components/svg/svg.js +48 -0
  61. package/dist/components/svg/svg.js.map +1 -0
  62. package/dist/components/svg/use-shape-node.d.ts +12 -0
  63. package/dist/components/svg/use-shape-node.js +52 -0
  64. package/dist/components/svg/use-shape-node.js.map +1 -0
  65. package/dist/components/text-input.js +3 -2
  66. package/dist/components/text-input.js.map +1 -1
  67. package/dist/components/text.js +11 -2
  68. package/dist/components/text.js.map +1 -1
  69. package/dist/components/use-layout-child.js +47 -4
  70. package/dist/components/use-layout-child.js.map +1 -1
  71. package/dist/contracts.d.ts +12 -1
  72. package/dist/contracts.js +2 -3
  73. package/dist/contracts.js.map +1 -1
  74. package/dist/gtk/index.d.ts +6 -0
  75. package/dist/gtk/index.js +39 -0
  76. package/dist/gtk/index.js.map +1 -0
  77. package/dist/gtk/widgets.generated.d.ts +103 -0
  78. package/dist/gtk/widgets.generated.js +206 -0
  79. package/dist/gtk/widgets.generated.js.map +1 -0
  80. package/dist/gtkx/bridge/index.d.ts +9 -4
  81. package/dist/gtkx/bridge/index.js +41 -5
  82. package/dist/gtkx/bridge/index.js.map +1 -1
  83. package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
  84. package/dist/gtkx/bridge/layout-manager.js +31 -9
  85. package/dist/gtkx/bridge/layout-manager.js.map +1 -1
  86. package/dist/gtkx/bridge/svg-node.d.ts +79 -0
  87. package/dist/gtkx/bridge/svg-node.js +367 -0
  88. package/dist/gtkx/bridge/svg-node.js.map +1 -0
  89. package/dist/gtkx/bridge/theme.js +1 -1
  90. package/dist/gtkx/bridge/theme.js.map +1 -1
  91. package/dist/gtkx/bridge/use-signal.js +21 -10
  92. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  93. package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
  94. package/dist/gtkx/bridge/widgets.generated.js +14 -0
  95. package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
  96. package/dist/index.js +1 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/mcp/bin.d.ts +2 -0
  99. package/dist/mcp/bin.js +17 -0
  100. package/dist/mcp/bin.js.map +1 -0
  101. package/dist/mcp/data/generated.d.ts +1282 -0
  102. package/dist/mcp/data/generated.js +1148 -0
  103. package/dist/mcp/data/generated.js.map +1 -0
  104. package/dist/mcp/index.d.ts +5 -0
  105. package/dist/mcp/index.js +9 -0
  106. package/dist/mcp/index.js.map +1 -0
  107. package/dist/mcp/list.d.ts +4 -0
  108. package/dist/mcp/list.js +56 -0
  109. package/dist/mcp/list.js.map +1 -0
  110. package/dist/mcp/resolve.d.ts +55 -0
  111. package/dist/mcp/resolve.js +151 -0
  112. package/dist/mcp/resolve.js.map +1 -0
  113. package/dist/mcp/search.d.ts +13 -0
  114. package/dist/mcp/search.js +48 -0
  115. package/dist/mcp/search.js.map +1 -0
  116. package/dist/mcp/server.d.ts +7 -0
  117. package/dist/mcp/server.js +135 -0
  118. package/dist/mcp/server.js.map +1 -0
  119. package/dist/mcp/tool.d.ts +21 -0
  120. package/dist/mcp/tool.js +36 -0
  121. package/dist/mcp/tool.js.map +1 -0
  122. package/dist/metro/index.js +10 -0
  123. package/dist/metro/index.js.map +1 -1
  124. package/dist/navigation/index.d.ts +50 -13
  125. package/dist/navigation/index.js +183 -148
  126. package/dist/navigation/index.js.map +1 -1
  127. package/dist/navigation/option-warnings.js +9 -1
  128. package/dist/navigation/option-warnings.js.map +1 -1
  129. package/dist/navigation/sidebar.d.ts +177 -9
  130. package/dist/navigation/sidebar.js +284 -10
  131. package/dist/navigation/sidebar.js.map +1 -1
  132. package/dist/runner/index.d.ts +22 -2
  133. package/dist/runner/index.js +163 -12
  134. package/dist/runner/index.js.map +1 -1
  135. package/dist/sea/assemble.d.ts +29 -0
  136. package/dist/sea/assemble.js +129 -0
  137. package/dist/sea/assemble.js.map +1 -0
  138. package/dist/sea/bundle.d.ts +61 -0
  139. package/dist/sea/bundle.js +202 -0
  140. package/dist/sea/bundle.js.map +1 -0
  141. package/dist/sea/gtkx-config-module.d.ts +18 -0
  142. package/dist/sea/gtkx-config-module.js +67 -0
  143. package/dist/sea/gtkx-config-module.js.map +1 -0
  144. package/dist/sea/native-shim.d.ts +40 -0
  145. package/dist/sea/native-shim.js +121 -0
  146. package/dist/sea/native-shim.js.map +1 -0
  147. package/dist/sea/react-anchor-plugin.d.ts +2 -0
  148. package/dist/sea/react-anchor-plugin.js +30 -0
  149. package/dist/sea/react-anchor-plugin.js.map +1 -0
  150. package/dist/style/colors.js +3 -1
  151. package/dist/style/colors.js.map +1 -1
  152. package/dist/style/index.d.ts +1 -0
  153. package/dist/style/index.js +3 -2
  154. package/dist/style/index.js.map +1 -1
  155. package/dist/style/registry.gtkx.js +1 -1
  156. package/dist/style/registry.gtkx.js.map +1 -1
  157. package/dist/style/split-style.js +3 -3
  158. package/dist/style/split-style.js.map +1 -1
  159. package/dist/style/text-align.js +1 -1
  160. package/dist/style/text-align.js.map +1 -1
  161. package/dist/style/transform.d.ts +22 -0
  162. package/dist/style/transform.js +137 -0
  163. package/dist/style/transform.js.map +1 -0
  164. package/dist/style/visual-css.js +3 -2
  165. package/dist/style/visual-css.js.map +1 -1
  166. package/dist/svg/geometry.d.ts +19 -0
  167. package/dist/svg/geometry.js +80 -0
  168. package/dist/svg/geometry.js.map +1 -0
  169. package/dist/svg/gradient-geometry.d.ts +15 -0
  170. package/dist/svg/gradient-geometry.js +18 -0
  171. package/dist/svg/gradient-geometry.js.map +1 -0
  172. package/dist/svg/transform.d.ts +22 -0
  173. package/dist/svg/transform.js +74 -0
  174. package/dist/svg/transform.js.map +1 -0
  175. package/dist/svg/view-box.d.ts +34 -0
  176. package/dist/svg/view-box.js +108 -0
  177. package/dist/svg/view-box.js.map +1 -0
  178. package/dist/svg-compat/index.d.ts +1 -0
  179. package/dist/svg-compat/index.js +16 -0
  180. package/dist/svg-compat/index.js.map +1 -0
  181. package/dist/testing/index.d.ts +14 -0
  182. package/dist/testing/index.js +14 -0
  183. package/dist/testing/index.js.map +1 -0
  184. package/dist/vite/index.d.ts +7 -3
  185. package/dist/vite/index.js +16 -3
  186. package/dist/vite/index.js.map +1 -1
  187. package/dist/vitest/index.d.ts +52 -0
  188. package/dist/vitest/index.js +58 -0
  189. package/dist/vitest/index.js.map +1 -0
  190. package/dist/vitest/setup.d.ts +4 -0
  191. package/dist/vitest/setup.js +22 -0
  192. package/dist/vitest/setup.js.map +1 -0
  193. package/package.json +47 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../../src/svg/geometry.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure shape → SVG path `d` string generators. No gtkx imports (unit-testable\n// on any OS, like style/colors.ts) — the only consumer of the output is\n// `Gsk.Path.parse()` in the bridge, so every generator here produces (a\n// superset of) SVG path syntax and nothing else. This is the \"no parser of\n// our own\" bet from the PRD: <Path> passes its `d` straight through, and\n// every other shape is just a small amount of arithmetic away from the same\n// syntax.\n\nexport type Point = { x: number; y: number }\n\n// Accepts both the comma-separated and whitespace-separated forms RN-svg\n// tolerates (\"0,0 10,5\" and \"0 0 10 5\"), and odd trailing tokens are\n// dropped rather than throwing — malformed `points` should render nothing\n// useful, not crash the tree.\nexport const parsePoints = (points: string): Point[] => {\n const numbers = points\n .trim()\n .split(/[\\s,]+/)\n .filter((token) => token.length > 0)\n .map(Number)\n .filter((value) => !Number.isNaN(value))\n const result: Point[] = []\n for (let i = 0; i + 1 < numbers.length; i += 2) {\n result.push({ x: numbers[i]!, y: numbers[i + 1]! })\n }\n return result\n}\n\nexport type RectShape = {\n x: number\n y: number\n width: number\n height: number\n rx?: number\n ry?: number\n}\n\n// SVG's rx/ry resolution: either one fills in for a missing other, and both\n// clamp to half the box so opposite corners never overlap.\nconst resolveCornerRadii = (\n width: number,\n height: number,\n rx: number | undefined,\n ry: number | undefined,\n): [number, number] => {\n const rawRx = rx ?? ry ?? 0\n const rawRy = ry ?? rx ?? 0\n return [\n Math.max(0, Math.min(rawRx, width / 2)),\n Math.max(0, Math.min(rawRy, height / 2)),\n ]\n}\n\nexport const rectToPathData = ({\n x,\n y,\n width,\n height,\n rx,\n ry,\n}: RectShape): string => {\n if (width <= 0 || height <= 0) {\n return \"\"\n }\n const [radiusX, radiusY] = resolveCornerRadii(width, height, rx, ry)\n if (radiusX <= 0 || radiusY <= 0) {\n return `M ${x} ${y} H ${x + width} V ${y + height} H ${x} Z`\n }\n const right = x + width\n const bottom = y + height\n return (\n `M ${x + radiusX} ${y} ` +\n `H ${right - radiusX} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${right} ${y + radiusY} ` +\n `V ${bottom - radiusY} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${right - radiusX} ${bottom} ` +\n `H ${x + radiusX} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${x} ${bottom - radiusY} ` +\n `V ${y + radiusY} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${x + radiusX} ${y} Z`\n )\n}\n\n// Two semicircle arcs — the standard trick for a full ellipse/circle in a\n// path grammar that has no dedicated \"ellipse\" command.\nexport const ellipseToPathData = (\n cx: number,\n cy: number,\n rx: number,\n ry: number,\n): string => {\n if (rx <= 0 || ry <= 0) {\n return \"\"\n }\n return (\n `M ${cx - rx} ${cy} ` +\n `A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy} ` +\n `A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`\n )\n}\n\nexport const circleToPathData = (cx: number, cy: number, r: number): string =>\n ellipseToPathData(cx, cy, r, r)\n\nexport const lineToPathData = (\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n): string => `M ${x1} ${y1} L ${x2} ${y2}`\n\nconst pointsToPathData = (points: Point[], close: boolean): string => {\n if (points.length === 0) {\n return \"\"\n }\n const [first, ...rest] = points\n const segments = rest.map((p) => `L ${p.x} ${p.y}`).join(\" \")\n const body =\n segments.length > 0\n ? `M ${first!.x} ${first!.y} ${segments}`\n : `M ${first!.x} ${first!.y}`\n return close ? `${body} Z` : body\n}\n\nexport const polygonToPathData = (points: string | Point[]): string =>\n pointsToPathData(\n typeof points === \"string\" ? parsePoints(points) : points,\n true,\n )\n\nexport const polylineToPathData = (points: string | Point[]): string =>\n pointsToPathData(\n typeof points === \"string\" ? parsePoints(points) : points,\n false,\n )\n"]}
@@ -0,0 +1,15 @@
1
+ export type BoundingBox = {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ export type GradientUnits = "objectBoundingBox" | "userSpaceOnUse";
8
+ export declare const resolveGradientPoint: (bounds: BoundingBox, fractionX: number, fractionY: number, units: GradientUnits) => {
9
+ x: number;
10
+ y: number;
11
+ };
12
+ export declare const resolveGradientRadius: (bounds: BoundingBox, r: number, units: GradientUnits) => {
13
+ hradius: number;
14
+ vradius: number;
15
+ };
@@ -0,0 +1,18 @@
1
+ // Pure objectBoundingBox / userSpaceOnUse coordinate math for gradients — no
2
+ // gtkx imports, so it stays testable even though the bridge's actual
3
+ // gradient PAINT is currently blocked by an upstream gtkx-rc2 native bug
4
+ // (see the WORKAROUND note by makeColorStop in gtkx/bridge/svg-node.ts).
5
+ // This is the part of gradient support that already works end to end today
6
+ // and will need no changes once that bug is fixed upstream.
7
+ export const resolveGradientPoint = (bounds, fractionX, fractionY, units) => units === "userSpaceOnUse"
8
+ ? { x: fractionX, y: fractionY }
9
+ : {
10
+ x: bounds.x + fractionX * bounds.width,
11
+ y: bounds.y + fractionY * bounds.height,
12
+ };
13
+ // SVG only has one radial radius; objectBoundingBox stretches it into an
14
+ // ellipse matching the shape's own aspect ratio (what browsers do too).
15
+ export const resolveGradientRadius = (bounds, r, units) => units === "userSpaceOnUse"
16
+ ? { hradius: r, vradius: r }
17
+ : { hradius: r * bounds.width, vradius: r * bounds.height };
18
+ //# sourceMappingURL=gradient-geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradient-geometry.js","sourceRoot":"","sources":["../../src/svg/gradient-geometry.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,qEAAqE;AACrE,yEAAyE;AACzE,yEAAyE;AACzE,2EAA2E;AAC3E,4DAA4D;AAU5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAmB,EACnB,SAAiB,EACjB,SAAiB,EACjB,KAAoB,EACM,EAAE,CAC5B,KAAK,KAAK,gBAAgB;IACxB,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE;IAChC,CAAC,CAAC;QACE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,KAAK;QACtC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM;KACxC,CAAA;AAEP,yEAAyE;AACzE,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,MAAmB,EACnB,CAAS,EACT,KAAoB,EACkB,EAAE,CACxC,KAAK,KAAK,gBAAgB;IACxB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;IAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA","sourcesContent":["// Pure objectBoundingBox / userSpaceOnUse coordinate math for gradients — no\n// gtkx imports, so it stays testable even though the bridge's actual\n// gradient PAINT is currently blocked by an upstream gtkx-rc2 native bug\n// (see the WORKAROUND note by makeColorStop in gtkx/bridge/svg-node.ts).\n// This is the part of gradient support that already works end to end today\n// and will need no changes once that bug is fixed upstream.\n\nexport type BoundingBox = {\n x: number\n y: number\n width: number\n height: number\n}\nexport type GradientUnits = \"objectBoundingBox\" | \"userSpaceOnUse\"\n\nexport const resolveGradientPoint = (\n bounds: BoundingBox,\n fractionX: number,\n fractionY: number,\n units: GradientUnits,\n): { x: number; y: number } =>\n units === \"userSpaceOnUse\"\n ? { x: fractionX, y: fractionY }\n : {\n x: bounds.x + fractionX * bounds.width,\n y: bounds.y + fractionY * bounds.height,\n }\n\n// SVG only has one radial radius; objectBoundingBox stretches it into an\n// ellipse matching the shape's own aspect ratio (what browsers do too).\nexport const resolveGradientRadius = (\n bounds: BoundingBox,\n r: number,\n units: GradientUnits,\n): { hradius: number; vradius: number } =>\n units === \"userSpaceOnUse\"\n ? { hradius: r, vradius: r }\n : { hradius: r * bounds.width, vradius: r * bounds.height }\n"]}
@@ -0,0 +1,22 @@
1
+ export type SvgTransformOp = {
2
+ type: "translate";
3
+ x: number;
4
+ y: number;
5
+ } | {
6
+ type: "scale";
7
+ x: number;
8
+ y: number;
9
+ } | {
10
+ type: "rotate";
11
+ angleDeg: number;
12
+ } | {
13
+ type: "matrix";
14
+ a: number;
15
+ b: number;
16
+ c: number;
17
+ d: number;
18
+ e: number;
19
+ f: number;
20
+ };
21
+ /** Parses an SVG transform-list string left to right into primitive ops. */
22
+ export declare const parseSvgTransform: (value: string | undefined) => SvgTransformOp[];
@@ -0,0 +1,74 @@
1
+ // Pure parser for the SVG `transform` attribute's function-list syntax
2
+ // (translate/scale/rotate/matrix — no gtkx imports, unit-testable anywhere).
3
+ // `skewX`/`skewY` are a deliberate cut (see epic.md): nothing in the shapes
4
+ // this API ships needs them, and Gsk's skew() takes degrees on both axes
5
+ // while SVG's skewX/skewY are single-axis, different enough to not be a
6
+ // one-line addition later if it turns out to matter.
7
+ //
8
+ // `rotate(angle, cx, cy)` is expanded here into translate/rotate/translate
9
+ // so the bridge only ever has to apply four primitive ops, never carry
10
+ // pivot-point bookkeeping of its own.
11
+ const CALL_PATTERN = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
12
+ const parseArgs = (raw) => raw
13
+ .trim()
14
+ .split(/[\s,]+/)
15
+ .filter((token) => token.length > 0)
16
+ .map(Number);
17
+ const isAllFinite = (values) => values.every((value) => Number.isFinite(value));
18
+ const buildOps = (name, args) => {
19
+ switch (name) {
20
+ case "translate": {
21
+ if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {
22
+ return [];
23
+ }
24
+ const [x, y = 0] = args;
25
+ return [{ type: "translate", x: x, y }];
26
+ }
27
+ case "scale": {
28
+ if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {
29
+ return [];
30
+ }
31
+ const [x, y = x] = args;
32
+ return [{ type: "scale", x: x, y: y }];
33
+ }
34
+ case "rotate": {
35
+ if ((args.length !== 1 && args.length !== 3) || !isAllFinite(args)) {
36
+ return [];
37
+ }
38
+ const [angleDeg, cx, cy] = args;
39
+ if (cx === undefined || cy === undefined) {
40
+ return [{ type: "rotate", angleDeg: angleDeg }];
41
+ }
42
+ return [
43
+ { type: "translate", x: cx, y: cy },
44
+ { type: "rotate", angleDeg: angleDeg },
45
+ { type: "translate", x: -cx, y: -cy },
46
+ ];
47
+ }
48
+ case "matrix": {
49
+ if (args.length !== 6 || !isAllFinite(args)) {
50
+ return [];
51
+ }
52
+ const [a, b, c, d, e, f] = args;
53
+ return [{ type: "matrix", a: a, b: b, c: c, d: d, e: e, f: f }];
54
+ }
55
+ default:
56
+ // skewX/skewY (deliberate cut) and anything unrecognized: ignored,
57
+ // same leniency RN-svg shows toward transform-list garbage.
58
+ return [];
59
+ }
60
+ };
61
+ /** Parses an SVG transform-list string left to right into primitive ops. */
62
+ export const parseSvgTransform = (value) => {
63
+ if (!value) {
64
+ return [];
65
+ }
66
+ const ops = [];
67
+ for (const match of value.matchAll(CALL_PATTERN)) {
68
+ const name = match[1];
69
+ const args = parseArgs(match[2] ?? "");
70
+ ops.push(...buildOps(name, args));
71
+ }
72
+ return ops;
73
+ };
74
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/svg/transform.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure parser for the SVG `transform` attribute's function-list syntax\n// (translate/scale/rotate/matrix — no gtkx imports, unit-testable anywhere).\n// `skewX`/`skewY` are a deliberate cut (see epic.md): nothing in the shapes\n// this API ships needs them, and Gsk's skew() takes degrees on both axes\n// while SVG's skewX/skewY are single-axis, different enough to not be a\n// one-line addition later if it turns out to matter.\n//\n// `rotate(angle, cx, cy)` is expanded here into translate/rotate/translate\n// so the bridge only ever has to apply four primitive ops, never carry\n// pivot-point bookkeeping of its own.\n\nexport type SvgTransformOp =\n | { type: \"translate\"; x: number; y: number }\n | { type: \"scale\"; x: number; y: number }\n | { type: \"rotate\"; angleDeg: number }\n | {\n type: \"matrix\"\n a: number\n b: number\n c: number\n d: number\n e: number\n f: number\n }\n\nconst CALL_PATTERN = /([a-zA-Z]+)\\s*\\(([^)]*)\\)/g\n\nconst parseArgs = (raw: string): number[] =>\n raw\n .trim()\n .split(/[\\s,]+/)\n .filter((token) => token.length > 0)\n .map(Number)\n\nconst isAllFinite = (values: number[]): boolean =>\n values.every((value) => Number.isFinite(value))\n\nconst buildOps = (name: string, args: number[]): SvgTransformOp[] => {\n switch (name) {\n case \"translate\": {\n if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {\n return []\n }\n const [x, y = 0] = args\n return [{ type: \"translate\", x: x!, y }]\n }\n case \"scale\": {\n if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {\n return []\n }\n const [x, y = x] = args\n return [{ type: \"scale\", x: x!, y: y! }]\n }\n case \"rotate\": {\n if ((args.length !== 1 && args.length !== 3) || !isAllFinite(args)) {\n return []\n }\n const [angleDeg, cx, cy] = args\n if (cx === undefined || cy === undefined) {\n return [{ type: \"rotate\", angleDeg: angleDeg! }]\n }\n return [\n { type: \"translate\", x: cx, y: cy },\n { type: \"rotate\", angleDeg: angleDeg! },\n { type: \"translate\", x: -cx, y: -cy },\n ]\n }\n case \"matrix\": {\n if (args.length !== 6 || !isAllFinite(args)) {\n return []\n }\n const [a, b, c, d, e, f] = args\n return [{ type: \"matrix\", a: a!, b: b!, c: c!, d: d!, e: e!, f: f! }]\n }\n default:\n // skewX/skewY (deliberate cut) and anything unrecognized: ignored,\n // same leniency RN-svg shows toward transform-list garbage.\n return []\n }\n}\n\n/** Parses an SVG transform-list string left to right into primitive ops. */\nexport const parseSvgTransform = (\n value: string | undefined,\n): SvgTransformOp[] => {\n if (!value) {\n return []\n }\n const ops: SvgTransformOp[] = []\n for (const match of value.matchAll(CALL_PATTERN)) {\n const name = match[1]!\n const args = parseArgs(match[2] ?? \"\")\n ops.push(...buildOps(name, args))\n }\n return ops\n}\n"]}
@@ -0,0 +1,34 @@
1
+ export type ViewBox = {
2
+ minX: number;
3
+ minY: number;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ export declare const parseViewBox: (value: string | undefined) => ViewBox | null;
8
+ export type AlignX = "min" | "mid" | "max";
9
+ export type AlignY = "min" | "mid" | "max";
10
+ export type MeetOrSlice = "meet" | "slice";
11
+ export type PreserveAspectRatio = {
12
+ align: "none" | {
13
+ x: AlignX;
14
+ y: AlignY;
15
+ };
16
+ meetOrSlice: MeetOrSlice;
17
+ };
18
+ export declare const DEFAULT_PRESERVE_ASPECT_RATIO: PreserveAspectRatio;
19
+ export declare const parsePreserveAspectRatio: (value: string | undefined) => PreserveAspectRatio;
20
+ export type ViewBoxTransform = {
21
+ scaleX: number;
22
+ scaleY: number;
23
+ translateX: number;
24
+ translateY: number;
25
+ };
26
+ /**
27
+ * Maps viewBox user-space coordinates onto a `viewportWidth` x
28
+ * `viewportHeight` box: `screen = user * scale + translate`. Mirrors the SVG
29
+ * algorithm exactly (see spec §7.11) — "none" stretches independently per
30
+ * axis, "meet" fits the whole viewBox inside the viewport (letterboxing),
31
+ * "slice" fills the viewport and overflows the viewBox (the bridge clips to
32
+ * the viewport bounds, same as a real SVG renderer).
33
+ */
34
+ export declare const resolveViewBoxTransform: (viewBox: ViewBox | null, viewportWidth: number, viewportHeight: number, preserveAspectRatio?: PreserveAspectRatio) => ViewBoxTransform;
@@ -0,0 +1,108 @@
1
+ // Pure viewBox / preserveAspectRatio math (SVG 1.1 §7.11). No gtkx imports —
2
+ // the bridge feeds the result into Gtk.Snapshot.scale()/translate(), but the
3
+ // arithmetic itself has nothing to do with GTK and is fully unit-testable.
4
+ // "minX minY width height", comma or whitespace separated (SVG allows
5
+ // either). Returns null for anything that doesn't parse to exactly 4
6
+ // numbers or has a non-positive size — callers fall back to the identity
7
+ // transform, same as SVG does when viewBox is absent.
8
+ export const parseViewBox = (value) => {
9
+ if (!value) {
10
+ return null;
11
+ }
12
+ const parts = value
13
+ .trim()
14
+ .split(/[\s,]+/)
15
+ .map(Number);
16
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) {
17
+ return null;
18
+ }
19
+ const [minX, minY, width, height] = parts;
20
+ if (width <= 0 || height <= 0) {
21
+ return null;
22
+ }
23
+ return { minX, minY, width, height };
24
+ };
25
+ const ALIGN_PATTERN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/;
26
+ export const DEFAULT_PRESERVE_ASPECT_RATIO = {
27
+ align: { x: "mid", y: "mid" },
28
+ meetOrSlice: "meet",
29
+ };
30
+ // Tolerant like the transform-string parser: unrecognized tokens fall back
31
+ // to the SVG default (xMidYMid meet) rather than throwing.
32
+ export const parsePreserveAspectRatio = (value) => {
33
+ if (!value) {
34
+ return DEFAULT_PRESERVE_ASPECT_RATIO;
35
+ }
36
+ const tokens = value.trim().split(/\s+/);
37
+ const alignToken = tokens[0] ?? "";
38
+ const meetOrSlice = tokens[1] === "slice" ? "slice" : "meet";
39
+ if (alignToken === "none") {
40
+ return { align: "none", meetOrSlice };
41
+ }
42
+ const match = ALIGN_PATTERN.exec(alignToken);
43
+ if (!match) {
44
+ return DEFAULT_PRESERVE_ASPECT_RATIO;
45
+ }
46
+ const x = match[1].toLowerCase();
47
+ const y = match[2].toLowerCase();
48
+ return { align: { x, y }, meetOrSlice };
49
+ };
50
+ const IDENTITY = {
51
+ scaleX: 1,
52
+ scaleY: 1,
53
+ translateX: 0,
54
+ translateY: 0,
55
+ };
56
+ /**
57
+ * Maps viewBox user-space coordinates onto a `viewportWidth` x
58
+ * `viewportHeight` box: `screen = user * scale + translate`. Mirrors the SVG
59
+ * algorithm exactly (see spec §7.11) — "none" stretches independently per
60
+ * axis, "meet" fits the whole viewBox inside the viewport (letterboxing),
61
+ * "slice" fills the viewport and overflows the viewBox (the bridge clips to
62
+ * the viewport bounds, same as a real SVG renderer).
63
+ */
64
+ export const resolveViewBoxTransform = (viewBox, viewportWidth, viewportHeight, preserveAspectRatio = DEFAULT_PRESERVE_ASPECT_RATIO) => {
65
+ if (!viewBox || viewportWidth <= 0 || viewportHeight <= 0) {
66
+ return IDENTITY;
67
+ }
68
+ const rawScaleX = viewportWidth / viewBox.width;
69
+ const rawScaleY = viewportHeight / viewBox.height;
70
+ let scaleX;
71
+ let scaleY;
72
+ if (preserveAspectRatio.align === "none") {
73
+ scaleX = rawScaleX;
74
+ scaleY = rawScaleY;
75
+ }
76
+ else {
77
+ const scale = preserveAspectRatio.meetOrSlice === "meet"
78
+ ? Math.min(rawScaleX, rawScaleY)
79
+ : Math.max(rawScaleX, rawScaleY);
80
+ scaleX = scale;
81
+ scaleY = scale;
82
+ }
83
+ let alignOffsetX = 0;
84
+ let alignOffsetY = 0;
85
+ if (preserveAspectRatio.align !== "none") {
86
+ const extraX = viewportWidth - viewBox.width * scaleX;
87
+ const extraY = viewportHeight - viewBox.height * scaleY;
88
+ if (preserveAspectRatio.align.x === "mid") {
89
+ alignOffsetX = extraX / 2;
90
+ }
91
+ else if (preserveAspectRatio.align.x === "max") {
92
+ alignOffsetX = extraX;
93
+ }
94
+ if (preserveAspectRatio.align.y === "mid") {
95
+ alignOffsetY = extraY / 2;
96
+ }
97
+ else if (preserveAspectRatio.align.y === "max") {
98
+ alignOffsetY = extraY;
99
+ }
100
+ }
101
+ return {
102
+ scaleX,
103
+ scaleY,
104
+ translateX: alignOffsetX - viewBox.minX * scaleX,
105
+ translateY: alignOffsetY - viewBox.minY * scaleY,
106
+ };
107
+ };
108
+ //# sourceMappingURL=view-box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-box.js","sourceRoot":"","sources":["../../src/svg/view-box.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure viewBox / preserveAspectRatio math (SVG 1.1 §7.11). No gtkx imports —\n// the bridge feeds the result into Gtk.Snapshot.scale()/translate(), but the\n// arithmetic itself has nothing to do with GTK and is fully unit-testable.\n\nexport type ViewBox = {\n minX: number\n minY: number\n width: number\n height: number\n}\n\n// \"minX minY width height\", comma or whitespace separated (SVG allows\n// either). Returns null for anything that doesn't parse to exactly 4\n// numbers or has a non-positive size — callers fall back to the identity\n// transform, same as SVG does when viewBox is absent.\nexport const parseViewBox = (value: string | undefined): ViewBox | null => {\n if (!value) {\n return null\n }\n const parts = value\n .trim()\n .split(/[\\s,]+/)\n .map(Number)\n if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) {\n return null\n }\n const [minX, minY, width, height] = parts as [number, number, number, number]\n if (width <= 0 || height <= 0) {\n return null\n }\n return { minX, minY, width, height }\n}\n\nexport type AlignX = \"min\" | \"mid\" | \"max\"\nexport type AlignY = \"min\" | \"mid\" | \"max\"\nexport type MeetOrSlice = \"meet\" | \"slice\"\n\nexport type PreserveAspectRatio = {\n align: \"none\" | { x: AlignX; y: AlignY }\n meetOrSlice: MeetOrSlice\n}\n\nconst ALIGN_PATTERN = /^x(Min|Mid|Max)Y(Min|Mid|Max)$/\n\nexport const DEFAULT_PRESERVE_ASPECT_RATIO: PreserveAspectRatio = {\n align: { x: \"mid\", y: \"mid\" },\n meetOrSlice: \"meet\",\n}\n\n// Tolerant like the transform-string parser: unrecognized tokens fall back\n// to the SVG default (xMidYMid meet) rather than throwing.\nexport const parsePreserveAspectRatio = (\n value: string | undefined,\n): PreserveAspectRatio => {\n if (!value) {\n return DEFAULT_PRESERVE_ASPECT_RATIO\n }\n const tokens = value.trim().split(/\\s+/)\n const alignToken = tokens[0] ?? \"\"\n const meetOrSlice: MeetOrSlice = tokens[1] === \"slice\" ? \"slice\" : \"meet\"\n if (alignToken === \"none\") {\n return { align: \"none\", meetOrSlice }\n }\n const match = ALIGN_PATTERN.exec(alignToken)\n if (!match) {\n return DEFAULT_PRESERVE_ASPECT_RATIO\n }\n const x = match[1]!.toLowerCase() as AlignX\n const y = match[2]!.toLowerCase() as AlignY\n return { align: { x, y }, meetOrSlice }\n}\n\nexport type ViewBoxTransform = {\n scaleX: number\n scaleY: number\n translateX: number\n translateY: number\n}\n\nconst IDENTITY: ViewBoxTransform = {\n scaleX: 1,\n scaleY: 1,\n translateX: 0,\n translateY: 0,\n}\n\n/**\n * Maps viewBox user-space coordinates onto a `viewportWidth` x\n * `viewportHeight` box: `screen = user * scale + translate`. Mirrors the SVG\n * algorithm exactly (see spec §7.11) — \"none\" stretches independently per\n * axis, \"meet\" fits the whole viewBox inside the viewport (letterboxing),\n * \"slice\" fills the viewport and overflows the viewBox (the bridge clips to\n * the viewport bounds, same as a real SVG renderer).\n */\nexport const resolveViewBoxTransform = (\n viewBox: ViewBox | null,\n viewportWidth: number,\n viewportHeight: number,\n preserveAspectRatio: PreserveAspectRatio = DEFAULT_PRESERVE_ASPECT_RATIO,\n): ViewBoxTransform => {\n if (!viewBox || viewportWidth <= 0 || viewportHeight <= 0) {\n return IDENTITY\n }\n const rawScaleX = viewportWidth / viewBox.width\n const rawScaleY = viewportHeight / viewBox.height\n\n let scaleX: number\n let scaleY: number\n if (preserveAspectRatio.align === \"none\") {\n scaleX = rawScaleX\n scaleY = rawScaleY\n } else {\n const scale =\n preserveAspectRatio.meetOrSlice === \"meet\"\n ? Math.min(rawScaleX, rawScaleY)\n : Math.max(rawScaleX, rawScaleY)\n scaleX = scale\n scaleY = scale\n }\n\n let alignOffsetX = 0\n let alignOffsetY = 0\n if (preserveAspectRatio.align !== \"none\") {\n const extraX = viewportWidth - viewBox.width * scaleX\n const extraY = viewportHeight - viewBox.height * scaleY\n if (preserveAspectRatio.align.x === \"mid\") {\n alignOffsetX = extraX / 2\n } else if (preserveAspectRatio.align.x === \"max\") {\n alignOffsetX = extraX\n }\n if (preserveAspectRatio.align.y === \"mid\") {\n alignOffsetY = extraY / 2\n } else if (preserveAspectRatio.align.y === \"max\") {\n alignOffsetY = extraY\n }\n }\n\n return {\n scaleX,\n scaleY,\n translateX: alignOffsetX - viewBox.minX * scaleX,\n translateY: alignOffsetY - viewBox.minY * scaleY,\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export { Circle, Defs, Ellipse, G, Line, LinearGradient, Path, Polygon, Polyline, RadialGradient, Rect, Stop, Svg, Svg as default, type AnimatableNumber, type CircleProps, type DefsProps, type EllipseProps, type GProps, type LinearGradientProps, type LineProps, type PathProps, type PolygonProps, type PolylineProps, type RadialGradientProps, type RectProps, type StopProps, type SvgFillRule, type SvgLineCap, type SvgLineJoin, type SvgProps, } from "../components/svg/index";
@@ -0,0 +1,16 @@
1
+ // react-native-svg compatibility subpath (by the same pattern as
2
+ // react-native-gtkx/navigation): re-exports our SVG component set in the
3
+ // shape portable code expects — `Svg` as both the default and a named
4
+ // export, everything else named — once an app aliases the bare
5
+ // react-native-svg package name to this subpath. See the alias the metro/
6
+ // vite presets add automatically (packages/react-native-gtkx/src/metro/
7
+ // index.ts, src/vite/index.ts), or a manual bundler alias for apps using
8
+ // neither. The upstream package itself is never a dependency of this one;
9
+ // nothing here imports it — this module only mirrors its export shape.
10
+ //
11
+ // Deliberate gaps against react-native-svg's full surface (see docs/api.md
12
+ // "Svg" for the complete list and the reasoning): no SvgXml/SvgUri (SVG
13
+ // files already load through Image, a different mechanism), no Text/
14
+ // TSpan/TextPath, no Mask/ClipPath/Filter/Marker/Symbol/Use/Pattern.
15
+ export { Circle, Defs, Ellipse, G, Line, LinearGradient, Path, Polygon, Polyline, RadialGradient, Rect, Stop, Svg, Svg as default, } from "../components/svg/index";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/svg-compat/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,yEAAyE;AACzE,sEAAsE;AACtE,+DAA+D;AAC/D,0EAA0E;AAC1E,wEAAwE;AACxE,yEAAyE;AACzE,0EAA0E;AAC1E,uEAAuE;AACvE,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,qEAAqE;AACrE,qEAAqE;AACrE,OAAO,EACL,MAAM,EACN,IAAI,EACJ,OAAO,EACP,CAAC,EACD,IAAI,EACJ,cAAc,EACd,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,GAAG,IAAI,OAAO,GAkBf,MAAM,yBAAyB,CAAA","sourcesContent":["// react-native-svg compatibility subpath (by the same pattern as\n// react-native-gtkx/navigation): re-exports our SVG component set in the\n// shape portable code expects — `Svg` as both the default and a named\n// export, everything else named — once an app aliases the bare\n// react-native-svg package name to this subpath. See the alias the metro/\n// vite presets add automatically (packages/react-native-gtkx/src/metro/\n// index.ts, src/vite/index.ts), or a manual bundler alias for apps using\n// neither. The upstream package itself is never a dependency of this one;\n// nothing here imports it — this module only mirrors its export shape.\n//\n// Deliberate gaps against react-native-svg's full surface (see docs/api.md\n// \"Svg\" for the complete list and the reasoning): no SvgXml/SvgUri (SVG\n// files already load through Image, a different mechanism), no Text/\n// TSpan/TextPath, no Mask/ClipPath/Filter/Marker/Symbol/Use/Pattern.\nexport {\n Circle,\n Defs,\n Ellipse,\n G,\n Line,\n LinearGradient,\n Path,\n Polygon,\n Polyline,\n RadialGradient,\n Rect,\n Stop,\n Svg,\n Svg as default,\n type AnimatableNumber,\n type CircleProps,\n type DefsProps,\n type EllipseProps,\n type GProps,\n type LinearGradientProps,\n type LineProps,\n type PathProps,\n type PolygonProps,\n type PolylineProps,\n type RadialGradientProps,\n type RectProps,\n type StopProps,\n type SvgFillRule,\n type SvgLineCap,\n type SvgLineJoin,\n type SvgProps,\n} from \"../components/svg/index\"\n"]}
@@ -0,0 +1,14 @@
1
+ import type { RenderHookOptions, RenderHookResult, RenderResult } from "@gtkx/testing";
2
+ export * from "@gtkx/testing";
3
+ /**
4
+ * Like `renderHook`, but first creates and presents a harness window (via
5
+ * `render(null)`), so window-dependent hooks have a toplevel to read.
6
+ * Cleanup is automatic — @gtkx/testing tears down every active render
7
+ * (including the harness window) after each test.
8
+ */
9
+ export declare function renderHookWithWindow<Result>(callback: () => Result, options?: RenderHookOptions<undefined>): Promise<RenderHookResult<Result, undefined> & {
10
+ window: RenderResult["container"];
11
+ }>;
12
+ export declare function renderHookWithWindow<Result, Props>(callback: (props: Props) => Result, options: RenderHookOptions<Props>): Promise<RenderHookResult<Result, Props> & {
13
+ window: RenderResult["container"];
14
+ }>;
@@ -0,0 +1,14 @@
1
+ import { render, renderHook } from "@gtkx/testing";
2
+ // react-native-gtkx/testing — @gtkx/testing's queries, render, renderHook,
3
+ // fireEvent and userEvent already operate on the real GTK widget tree our
4
+ // RN components render into, so `getByText` on a `Text` or `userEvent.click`
5
+ // on a `Pressable` already work with no RN-flavoured wrapper: this subpath
6
+ // re-exports that surface as-is, one import source for a consumer, plus the
7
+ // one genuinely repeated workaround (see below).
8
+ export * from "@gtkx/testing";
9
+ export async function renderHookWithWindow(callback, options) {
10
+ const { container: window } = await render(null);
11
+ const hook = await renderHook(callback, options);
12
+ return { ...hook, window };
13
+ }
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAOlD,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,iDAAiD;AACjD,cAAc,eAAe,CAAA;AA2B7B,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAmC,EACnC,OAAkC;IAIlC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;IAChD,MAAM,IAAI,GAAG,MAAM,UAAU,CAC3B,QAAoC,EACpC,OAAmC,CACpC,CAAA;IACD,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["import { render, renderHook } from \"@gtkx/testing\"\nimport type {\n RenderHookOptions,\n RenderHookResult,\n RenderResult,\n} from \"@gtkx/testing\"\n\n// react-native-gtkx/testing — @gtkx/testing's queries, render, renderHook,\n// fireEvent and userEvent already operate on the real GTK widget tree our\n// RN components render into, so `getByText` on a `Text` or `userEvent.click`\n// on a `Pressable` already work with no RN-flavoured wrapper: this subpath\n// re-exports that surface as-is, one import source for a consumer, plus the\n// one genuinely repeated workaround (see below).\nexport * from \"@gtkx/testing\"\n\n// RC2-WORKAROUND(renderhook-no-window): see docs/gtkx-rc2-notes.md\n// @gtkx/testing's renderHook() always mounts its test component into a bare\n// Gtk.Box, so a hook that reads the active toplevel (useWindowDimensions,\n// anything through Gtk.Window.getToplevels()) has nothing to read. render()\n// always creates and presents a harness window when no container is given,\n// regardless of what it renders — rendering null is enough to get one.\n\n/**\n * Like `renderHook`, but first creates and presents a harness window (via\n * `render(null)`), so window-dependent hooks have a toplevel to read.\n * Cleanup is automatic — @gtkx/testing tears down every active render\n * (including the harness window) after each test.\n */\nexport function renderHookWithWindow<Result>(\n callback: () => Result,\n options?: RenderHookOptions<undefined>,\n): Promise<\n RenderHookResult<Result, undefined> & { window: RenderResult[\"container\"] }\n>\nexport function renderHookWithWindow<Result, Props>(\n callback: (props: Props) => Result,\n options: RenderHookOptions<Props>,\n): Promise<\n RenderHookResult<Result, Props> & { window: RenderResult[\"container\"] }\n>\nexport async function renderHookWithWindow<Result, Props>(\n callback: (props?: Props) => Result,\n options?: RenderHookOptions<Props>,\n): Promise<\n RenderHookResult<Result, Props> & { window: RenderResult[\"container\"] }\n> {\n const { container: window } = await render(null)\n const hook = await renderHook(\n callback as (props: Props) => Result,\n options as RenderHookOptions<Props>,\n )\n return { ...hook, window }\n}\n"]}
@@ -11,9 +11,13 @@ export type FileExists = (filePath: string) => boolean;
11
11
  export declare const DEFAULT_PLATFORMS: readonly string[];
12
12
  export declare const DEFAULT_EXTENSIONS: readonly string[];
13
13
  /**
14
- * Maps `react-native` (and its subpaths) to `react-native-gtkx`.
15
- * Returns null for every other specifier, including `react-native-gtkx`
16
- * itself and lookalikes such as `react-native-web`.
14
+ * Maps `react-native` (and its subpaths) to `react-native-gtkx`, and
15
+ * `react-native-svg` (and its subpaths) to the `react-native-gtkx/svg`
16
+ * compat subpath (see src/svg-compat/index.ts) — same rationale, same exact-
17
+ * match-or-slash-prefix guard so a lookalike package name (`react-native-
18
+ * svg-icons`, the same shape of trap `react-native-web` is for the plain
19
+ * `react-native` case below) is never aliased by accident. Returns null for
20
+ * every other specifier, including `react-native-gtkx` itself.
17
21
  */
18
22
  export declare const rewriteReactNativeImport: (source: string) => string | null;
19
23
  /** Splits a vite specifier into its path part and its `?query` suffix. */
@@ -14,10 +14,16 @@ export const DEFAULT_PLATFORMS = ["linux", "native"];
14
14
  export const DEFAULT_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
15
15
  const REACT_NATIVE = "react-native";
16
16
  const REACT_NATIVE_GTKX = "react-native-gtkx";
17
+ const REACT_NATIVE_SVG = "react-native-svg";
18
+ const REACT_NATIVE_GTKX_SVG = "react-native-gtkx/svg";
17
19
  /**
18
- * Maps `react-native` (and its subpaths) to `react-native-gtkx`.
19
- * Returns null for every other specifier, including `react-native-gtkx`
20
- * itself and lookalikes such as `react-native-web`.
20
+ * Maps `react-native` (and its subpaths) to `react-native-gtkx`, and
21
+ * `react-native-svg` (and its subpaths) to the `react-native-gtkx/svg`
22
+ * compat subpath (see src/svg-compat/index.ts) — same rationale, same exact-
23
+ * match-or-slash-prefix guard so a lookalike package name (`react-native-
24
+ * svg-icons`, the same shape of trap `react-native-web` is for the plain
25
+ * `react-native` case below) is never aliased by accident. Returns null for
26
+ * every other specifier, including `react-native-gtkx` itself.
21
27
  */
22
28
  export const rewriteReactNativeImport = (source) => {
23
29
  if (source === REACT_NATIVE) {
@@ -26,6 +32,12 @@ export const rewriteReactNativeImport = (source) => {
26
32
  if (source.startsWith(`${REACT_NATIVE}/`)) {
27
33
  return `${REACT_NATIVE_GTKX}${source.slice(REACT_NATIVE.length)}`;
28
34
  }
35
+ if (source === REACT_NATIVE_SVG) {
36
+ return REACT_NATIVE_GTKX_SVG;
37
+ }
38
+ if (source.startsWith(`${REACT_NATIVE_SVG}/`)) {
39
+ return `${REACT_NATIVE_GTKX_SVG}${source.slice(REACT_NATIVE_SVG.length)}`;
40
+ }
29
41
  return null;
30
42
  };
31
43
  /** Splits a vite specifier into its path part and its `?query` suffix. */
@@ -128,6 +140,7 @@ export const reactNativeGtkx = (options = {}) => ({
128
140
  noExternal: ["react-native-gtkx", "react-native", /^@react-navigation\//],
129
141
  },
130
142
  resolve: {
143
+ // RC2-WORKAROUND(runtime-dedupe): see docs/gtkx-rc2-notes.md
131
144
  // The gtkx runtime and react are single-instance hosts: when the app
132
145
  // and react-native-gtkx resolve them from different node_modules
133
146
  // (file:-installed package, nested installs), two bundled copies
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["// react-native-gtkx/vite — makes a gtkx project React Native compatible.\n// The gtkx CLI (dev and build) starts vite with an inline config that never\n// sets `configFile: false`, so vite picks up the project's vite.config.ts\n// from the root and merges it beneath the CLI config; this preset plugs in\n// there as a single plugin.\n//\n// Single self-contained file on purpose: vite loads the config's imported\n// packages with BARE Node, so this subpath (like ./metro and ./runner) must\n// not contain extensionless relative imports — the pure resolution helpers\n// live here rather than in a sibling module.\nimport { existsSync } from \"node:fs\"\nimport { dirname, extname, isAbsolute, resolve } from \"node:path\"\nimport type { Plugin } from \"vite\"\n\n// --- pure resolution logic (unit-tested directly) -----------------------\n\n/** Options shared by the preset factory and the pure resolver helpers. */\nexport type PlatformResolutionOptions = {\n /** Platform suffix priority, most specific first. Default: [\"linux\", \"native\"]. */\n platforms?: readonly string[]\n /** Source extensions tried for every platform suffix. Default: [\"tsx\", \"ts\", \"jsx\", \"js\"]. */\n extensions?: readonly string[]\n}\n\n/** Predicate the plugin injects as fs.existsSync; tests inject fakes. */\nexport type FileExists = (filePath: string) => boolean\n\nexport const DEFAULT_PLATFORMS: readonly string[] = [\"linux\", \"native\"]\nexport const DEFAULT_EXTENSIONS: readonly string[] = [\"tsx\", \"ts\", \"jsx\", \"js\"]\n\nconst REACT_NATIVE = \"react-native\"\nconst REACT_NATIVE_GTKX = \"react-native-gtkx\"\n\n/**\n * Maps `react-native` (and its subpaths) to `react-native-gtkx`.\n * Returns null for every other specifier, including `react-native-gtkx`\n * itself and lookalikes such as `react-native-web`.\n */\nexport const rewriteReactNativeImport = (source: string): string | null => {\n if (source === REACT_NATIVE) {\n return REACT_NATIVE_GTKX\n }\n if (source.startsWith(`${REACT_NATIVE}/`)) {\n return `${REACT_NATIVE_GTKX}${source.slice(REACT_NATIVE.length)}`\n }\n return null\n}\n\n/** Splits a vite specifier into its path part and its `?query` suffix. */\nexport const splitQuery = (\n source: string,\n): { specifier: string; query: string } => {\n const index = source.indexOf(\"?\")\n if (index === -1) {\n return { specifier: source, query: \"\" }\n }\n return { specifier: source.slice(0, index), query: source.slice(index) }\n}\n\nconst isRelative = (specifier: string): boolean =>\n specifier.startsWith(\"./\") || specifier.startsWith(\"../\")\n\n/**\n * Ordered platform suffixes, platform-major: every extension of the first\n * platform is tried before any extension of the next one\n * (`.linux.tsx`, `.linux.ts`, …, `.native.tsx`, …).\n */\nexport const platformSuffixes = (\n options: PlatformResolutionOptions = {},\n): string[] => {\n const platforms = options.platforms ?? DEFAULT_PLATFORMS\n const extensions = options.extensions ?? DEFAULT_EXTENSIONS\n return platforms.flatMap((platform) =>\n extensions.map((extension) => `.${platform}.${extension}`),\n )\n}\n\n/**\n * Candidate file paths for an extensionless base path, in priority order:\n * direct platform files first, then platform index files of a directory —\n * mirroring Metro, which exhausts file resolution before directory resolution.\n * The base file itself is not a candidate: when no platform file exists the\n * plugin bails out and the default resolver picks the base module.\n */\nexport const platformCandidates = (\n base: string,\n options: PlatformResolutionOptions = {},\n): string[] => {\n const suffixes = platformSuffixes(options)\n return [\n ...suffixes.map((suffix) => `${base}${suffix}`),\n ...suffixes.map((suffix) => `${base}/index${suffix}`),\n ]\n}\n\n// Source extensions Metro strips before trying platform variants: an\n// import of \"./useLinking.js\" (the TS-ESM style react-navigation and most\n// compiled RN libraries use) must still find useLinking.native.tsx /\n// useLinking.native.js — Metro resolves the BASE name platform-first, the\n// literal file is only the fallback.\nconst STRIPPABLE_EXTENSIONS = new Set([\".js\", \".jsx\", \".mjs\", \".ts\", \".tsx\"])\n\n/**\n * Metro platform-extension resolution for a single import: relative (or\n * absolute) specifiers that are extensionless — or carry a strippable\n * source extension — try platform candidates first; the first existing\n * candidate wins; the `?query` suffix is carried over. Returns null when\n * the import is out of scope or no platform file exists, handing the\n * import back to the default resolver.\n */\nexport const resolvePlatformSpecifier = (\n source: string,\n importer: string | undefined,\n exists: FileExists,\n options: PlatformResolutionOptions = {},\n): string | null => {\n if (importer === undefined || source.startsWith(\"\\0\")) {\n return null\n }\n const { specifier, query } = splitQuery(source)\n if (!isRelative(specifier) && !isAbsolute(specifier)) {\n return null\n }\n const extension = extname(specifier)\n if (extension !== \"\" && !STRIPPABLE_EXTENSIONS.has(extension)) {\n return null\n }\n const withExtension = isAbsolute(specifier)\n ? specifier\n : resolve(dirname(splitQuery(importer).specifier), specifier)\n const base =\n extension === \"\" ? withExtension : withExtension.slice(0, -extension.length)\n for (const candidate of platformCandidates(base, options)) {\n if (exists(candidate)) {\n return candidate + query\n }\n }\n return null\n}\n\n// --- the vite plugin ----------------------------------------------------\n\nexport type ReactNativeGtkxOptions = PlatformResolutionOptions\n\n/**\n * Vite plugin: aliases `react-native` (and subpaths) to `react-native-gtkx`\n * and resolves Metro-style platform extensions\n * (`.linux.tsx` → `.native.tsx` → base) for extensionless imports.\n */\nexport const reactNativeGtkx = (\n options: ReactNativeGtkxOptions = {},\n): Plugin => ({\n name: \"react-native-gtkx:preset\",\n // Before vite's own resolver and the gtkx CLI plugins: the alias must win\n // over node resolution and platform files must win over the base file.\n enforce: \"pre\",\n\n config: () => ({\n ssr: {\n // `gtkx dev` runs vite with ssr.external: true, which would hand\n // react-native-gtkx straight to node. Keep the package inside the\n // vite pipeline; noExternal wins over external: true.\n //\n // \"react-native\" must be listed too: with ssr.external: true vite\n // externalizes a bare import BEFORE the plugin pipeline whenever the\n // package resolves natively — and the real react-native (a Flow\n // codebase Node cannot parse) exists in RN monorepos. noExternal\n // forces the full plugin resolution, where the alias rewrites the\n // import to react-native-gtkx.\n // @react-navigation must go through the pipeline too: it imports\n // \"react-native\", and an externalized copy resolves that to the real\n // Flow package (SyntaxError: Unexpected token 'typeof') instead of\n // our alias. Only bites on the dev path — a production build inlines\n // everything anyway.\n noExternal: [\"react-native-gtkx\", \"react-native\", /^@react-navigation\\//],\n },\n resolve: {\n // The gtkx runtime and react are single-instance hosts: when the app\n // and react-native-gtkx resolve them from different node_modules\n // (file:-installed package, nested installs), two bundled copies\n // double-init the runtime and GLib aborts. dedupe pins every copy to\n // the project's own resolution.\n dedupe: [\n \"@gtkx/css\",\n \"@gtkx/gi\",\n \"@gtkx/jsx\",\n \"@gtkx/native\",\n \"@gtkx/react\",\n \"@gtkx/runtime\",\n // react-navigation is context-based: two bundled copies of core\n // fail with \"couldn't find a navigation context\".\n \"@react-navigation/core\",\n \"@react-navigation/native\",\n \"react\",\n ],\n },\n }),\n\n async resolveId(source, importer) {\n const aliased = rewriteReactNativeImport(source)\n if (aliased !== null) {\n const resolved = await this.resolve(aliased, importer, { skipSelf: true })\n return resolved ?? aliased\n }\n return resolvePlatformSpecifier(source, importer, existsSync, options)\n },\n})\n\nexport default reactNativeGtkx\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["// react-native-gtkx/vite — makes a gtkx project React Native compatible.\n// The gtkx CLI (dev and build) starts vite with an inline config that never\n// sets `configFile: false`, so vite picks up the project's vite.config.ts\n// from the root and merges it beneath the CLI config; this preset plugs in\n// there as a single plugin.\n//\n// Single self-contained file on purpose: vite loads the config's imported\n// packages with BARE Node, so this subpath (like ./metro and ./runner) must\n// not contain extensionless relative imports — the pure resolution helpers\n// live here rather than in a sibling module.\nimport { existsSync } from \"node:fs\"\nimport { dirname, extname, isAbsolute, resolve } from \"node:path\"\nimport type { Plugin } from \"vite\"\n\n// --- pure resolution logic (unit-tested directly) -----------------------\n\n/** Options shared by the preset factory and the pure resolver helpers. */\nexport type PlatformResolutionOptions = {\n /** Platform suffix priority, most specific first. Default: [\"linux\", \"native\"]. */\n platforms?: readonly string[]\n /** Source extensions tried for every platform suffix. Default: [\"tsx\", \"ts\", \"jsx\", \"js\"]. */\n extensions?: readonly string[]\n}\n\n/** Predicate the plugin injects as fs.existsSync; tests inject fakes. */\nexport type FileExists = (filePath: string) => boolean\n\nexport const DEFAULT_PLATFORMS: readonly string[] = [\"linux\", \"native\"]\nexport const DEFAULT_EXTENSIONS: readonly string[] = [\"tsx\", \"ts\", \"jsx\", \"js\"]\n\nconst REACT_NATIVE = \"react-native\"\nconst REACT_NATIVE_GTKX = \"react-native-gtkx\"\nconst REACT_NATIVE_SVG = \"react-native-svg\"\nconst REACT_NATIVE_GTKX_SVG = \"react-native-gtkx/svg\"\n\n/**\n * Maps `react-native` (and its subpaths) to `react-native-gtkx`, and\n * `react-native-svg` (and its subpaths) to the `react-native-gtkx/svg`\n * compat subpath (see src/svg-compat/index.ts) — same rationale, same exact-\n * match-or-slash-prefix guard so a lookalike package name (`react-native-\n * svg-icons`, the same shape of trap `react-native-web` is for the plain\n * `react-native` case below) is never aliased by accident. Returns null for\n * every other specifier, including `react-native-gtkx` itself.\n */\nexport const rewriteReactNativeImport = (source: string): string | null => {\n if (source === REACT_NATIVE) {\n return REACT_NATIVE_GTKX\n }\n if (source.startsWith(`${REACT_NATIVE}/`)) {\n return `${REACT_NATIVE_GTKX}${source.slice(REACT_NATIVE.length)}`\n }\n if (source === REACT_NATIVE_SVG) {\n return REACT_NATIVE_GTKX_SVG\n }\n if (source.startsWith(`${REACT_NATIVE_SVG}/`)) {\n return `${REACT_NATIVE_GTKX_SVG}${source.slice(REACT_NATIVE_SVG.length)}`\n }\n return null\n}\n\n/** Splits a vite specifier into its path part and its `?query` suffix. */\nexport const splitQuery = (\n source: string,\n): { specifier: string; query: string } => {\n const index = source.indexOf(\"?\")\n if (index === -1) {\n return { specifier: source, query: \"\" }\n }\n return { specifier: source.slice(0, index), query: source.slice(index) }\n}\n\nconst isRelative = (specifier: string): boolean =>\n specifier.startsWith(\"./\") || specifier.startsWith(\"../\")\n\n/**\n * Ordered platform suffixes, platform-major: every extension of the first\n * platform is tried before any extension of the next one\n * (`.linux.tsx`, `.linux.ts`, …, `.native.tsx`, …).\n */\nexport const platformSuffixes = (\n options: PlatformResolutionOptions = {},\n): string[] => {\n const platforms = options.platforms ?? DEFAULT_PLATFORMS\n const extensions = options.extensions ?? DEFAULT_EXTENSIONS\n return platforms.flatMap((platform) =>\n extensions.map((extension) => `.${platform}.${extension}`),\n )\n}\n\n/**\n * Candidate file paths for an extensionless base path, in priority order:\n * direct platform files first, then platform index files of a directory —\n * mirroring Metro, which exhausts file resolution before directory resolution.\n * The base file itself is not a candidate: when no platform file exists the\n * plugin bails out and the default resolver picks the base module.\n */\nexport const platformCandidates = (\n base: string,\n options: PlatformResolutionOptions = {},\n): string[] => {\n const suffixes = platformSuffixes(options)\n return [\n ...suffixes.map((suffix) => `${base}${suffix}`),\n ...suffixes.map((suffix) => `${base}/index${suffix}`),\n ]\n}\n\n// Source extensions Metro strips before trying platform variants: an\n// import of \"./useLinking.js\" (the TS-ESM style react-navigation and most\n// compiled RN libraries use) must still find useLinking.native.tsx /\n// useLinking.native.js — Metro resolves the BASE name platform-first, the\n// literal file is only the fallback.\nconst STRIPPABLE_EXTENSIONS = new Set([\".js\", \".jsx\", \".mjs\", \".ts\", \".tsx\"])\n\n/**\n * Metro platform-extension resolution for a single import: relative (or\n * absolute) specifiers that are extensionless — or carry a strippable\n * source extension — try platform candidates first; the first existing\n * candidate wins; the `?query` suffix is carried over. Returns null when\n * the import is out of scope or no platform file exists, handing the\n * import back to the default resolver.\n */\nexport const resolvePlatformSpecifier = (\n source: string,\n importer: string | undefined,\n exists: FileExists,\n options: PlatformResolutionOptions = {},\n): string | null => {\n if (importer === undefined || source.startsWith(\"\\0\")) {\n return null\n }\n const { specifier, query } = splitQuery(source)\n if (!isRelative(specifier) && !isAbsolute(specifier)) {\n return null\n }\n const extension = extname(specifier)\n if (extension !== \"\" && !STRIPPABLE_EXTENSIONS.has(extension)) {\n return null\n }\n const withExtension = isAbsolute(specifier)\n ? specifier\n : resolve(dirname(splitQuery(importer).specifier), specifier)\n const base =\n extension === \"\" ? withExtension : withExtension.slice(0, -extension.length)\n for (const candidate of platformCandidates(base, options)) {\n if (exists(candidate)) {\n return candidate + query\n }\n }\n return null\n}\n\n// --- the vite plugin ----------------------------------------------------\n\nexport type ReactNativeGtkxOptions = PlatformResolutionOptions\n\n/**\n * Vite plugin: aliases `react-native` (and subpaths) to `react-native-gtkx`\n * and resolves Metro-style platform extensions\n * (`.linux.tsx` → `.native.tsx` → base) for extensionless imports.\n */\nexport const reactNativeGtkx = (\n options: ReactNativeGtkxOptions = {},\n): Plugin => ({\n name: \"react-native-gtkx:preset\",\n // Before vite's own resolver and the gtkx CLI plugins: the alias must win\n // over node resolution and platform files must win over the base file.\n enforce: \"pre\",\n\n config: () => ({\n ssr: {\n // `gtkx dev` runs vite with ssr.external: true, which would hand\n // react-native-gtkx straight to node. Keep the package inside the\n // vite pipeline; noExternal wins over external: true.\n //\n // \"react-native\" must be listed too: with ssr.external: true vite\n // externalizes a bare import BEFORE the plugin pipeline whenever the\n // package resolves natively — and the real react-native (a Flow\n // codebase Node cannot parse) exists in RN monorepos. noExternal\n // forces the full plugin resolution, where the alias rewrites the\n // import to react-native-gtkx.\n // @react-navigation must go through the pipeline too: it imports\n // \"react-native\", and an externalized copy resolves that to the real\n // Flow package (SyntaxError: Unexpected token 'typeof') instead of\n // our alias. Only bites on the dev path — a production build inlines\n // everything anyway.\n noExternal: [\"react-native-gtkx\", \"react-native\", /^@react-navigation\\//],\n },\n resolve: {\n // RC2-WORKAROUND(runtime-dedupe): see docs/gtkx-rc2-notes.md\n // The gtkx runtime and react are single-instance hosts: when the app\n // and react-native-gtkx resolve them from different node_modules\n // (file:-installed package, nested installs), two bundled copies\n // double-init the runtime and GLib aborts. dedupe pins every copy to\n // the project's own resolution.\n dedupe: [\n \"@gtkx/css\",\n \"@gtkx/gi\",\n \"@gtkx/jsx\",\n \"@gtkx/native\",\n \"@gtkx/react\",\n \"@gtkx/runtime\",\n // react-navigation is context-based: two bundled copies of core\n // fail with \"couldn't find a navigation context\".\n \"@react-navigation/core\",\n \"@react-navigation/native\",\n \"react\",\n ],\n },\n }),\n\n async resolveId(source, importer) {\n const aliased = rewriteReactNativeImport(source)\n if (aliased !== null) {\n const resolved = await this.resolve(aliased, importer, { skipSelf: true })\n return resolved ?? aliased\n }\n return resolvePlatformSpecifier(source, importer, existsSync, options)\n },\n})\n\nexport default reactNativeGtkx\n"]}
@@ -0,0 +1,52 @@
1
+ import type { GtkxPluginOptions } from "@gtkx/vitest";
2
+ import type { UserWorkspaceConfig } from "vitest/config";
3
+ import { type ReactNativeGtkxOptions } from "../vite/index.js";
4
+ /**
5
+ * Options accepted by {@link reactNativeGtkxTest}. Every field is optional
6
+ * and falls back to the recipe this repo's own test suite runs on.
7
+ */
8
+ export type ReactNativeGtkxTestOptions = {
9
+ /** Vitest project name. Default: "gtk". */
10
+ name?: string;
11
+ /** Test file glob(s), relative to the project root. Default: `["**\/*.gtk.test.{ts,tsx}"]`. */
12
+ include?: string[];
13
+ /** Headless compositor size/compositor, forwarded to @gtkx/vitest's plugin. */
14
+ headless?: GtkxPluginOptions;
15
+ /** Metro-style platform resolution options, forwarded to the vite preset. */
16
+ platform?: ReactNativeGtkxOptions;
17
+ /**
18
+ * Extra `server.deps.inline` patterns, merged after the default
19
+ * (`/@react-navigation/`). Needed for any node_modules library that
20
+ * itself imports "react-native" — Vitest externalizes node_modules code
21
+ * by default, a path the react-native alias cannot reach.
22
+ */
23
+ inlineDeps?: (string | RegExp)[];
24
+ /** Extra setup files, run after the built-in React act-environment setup. */
25
+ setupFiles?: string[];
26
+ /**
27
+ * Run this project's test files serially. Default: false — window-resize
28
+ * signal delivery races when several per-worker headless compositors run
29
+ * at once (see docs/gtkx-rc2-notes.md); the suite this recipe is proven
30
+ * on takes seconds, so serial execution is the cheap fix. Override once a
31
+ * suite is large enough for parallelism to matter more than that race.
32
+ */
33
+ fileParallelism?: boolean;
34
+ };
35
+ /**
36
+ * A ready Vitest project config for react-native-gtkx component tests: the
37
+ * headless Wayland compositor, the react-native alias and platform
38
+ * extensions, and the React act-environment setup — the same recipe this
39
+ * repo's own suite runs on. Use the result as a whole config
40
+ * (`defineConfig(reactNativeGtkxTest())`) or as one entry of
41
+ * `test.projects` alongside a portable "unit" project.
42
+ *
43
+ * Requires a headless Wayland compositor on PATH (`sway` by default —
44
+ * `apt install sway xwayland dbus`, see docs/getting-started.md) and
45
+ * `gtkx codegen` to have already generated the project's `@gtkx/gi`
46
+ * bindings — a bare `vitest run` does not trigger codegen itself, unlike
47
+ * `gtkx dev`/`gtkx build`. A missing compositor or codegen store fails with
48
+ * a readable error (e.g. `Cannot find the "sway" executable on PATH`), not
49
+ * a hang.
50
+ */
51
+ export declare const reactNativeGtkxTest: (options?: ReactNativeGtkxTestOptions) => Promise<UserWorkspaceConfig>;
52
+ export default reactNativeGtkxTest;