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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/README.md +22 -9
  2. package/dist/adw/index.d.ts +3 -0
  3. package/dist/adw/index.js +41 -0
  4. package/dist/adw/index.js.map +1 -0
  5. package/dist/adw/widgets.generated.d.ts +63 -0
  6. package/dist/adw/widgets.generated.js +125 -0
  7. package/dist/adw/widgets.generated.js.map +1 -0
  8. package/dist/animated/create-animated.d.ts +2 -0
  9. package/dist/animated/create-animated.js +2 -0
  10. package/dist/animated/create-animated.js.map +1 -1
  11. package/dist/animated/index.d.ts +2 -0
  12. package/dist/animated/index.js +1 -0
  13. package/dist/animated/index.js.map +1 -1
  14. package/dist/animated/value-xy.d.ts +41 -0
  15. package/dist/animated/value-xy.js +90 -0
  16. package/dist/animated/value-xy.js.map +1 -0
  17. package/dist/animated/value.d.ts +9 -0
  18. package/dist/animated/value.js +30 -2
  19. package/dist/animated/value.js.map +1 -1
  20. package/dist/common/content.d.ts +69 -0
  21. package/dist/common/content.js +84 -0
  22. package/dist/common/content.js.map +1 -0
  23. package/dist/common/icon.d.ts +23 -0
  24. package/dist/common/icon.js +14 -0
  25. package/dist/common/icon.js.map +1 -0
  26. package/dist/common/index.d.ts +5 -0
  27. package/dist/common/index.js +21 -0
  28. package/dist/common/index.js.map +1 -0
  29. package/dist/common/list.d.ts +82 -0
  30. package/dist/common/list.js +166 -0
  31. package/dist/common/list.js.map +1 -0
  32. package/dist/common/navigation-stack.d.ts +84 -0
  33. package/dist/common/navigation-stack.js +313 -0
  34. package/dist/common/navigation-stack.js.map +1 -0
  35. package/dist/common/widget.d.ts +62 -0
  36. package/dist/common/widget.js +209 -0
  37. package/dist/common/widget.js.map +1 -0
  38. package/dist/components/animated.d.ts +7 -4
  39. package/dist/components/animated.js +88 -67
  40. package/dist/components/animated.js.map +1 -1
  41. package/dist/components/app-registry.d.ts +36 -1
  42. package/dist/components/app-registry.js +8 -3
  43. package/dist/components/app-registry.js.map +1 -1
  44. package/dist/components/host-node.d.ts +7 -0
  45. package/dist/components/host-node.js +26 -1
  46. package/dist/components/host-node.js.map +1 -1
  47. package/dist/components/index.d.ts +6 -2
  48. package/dist/components/index.js +2 -1
  49. package/dist/components/index.js.map +1 -1
  50. package/dist/components/measure.d.ts +13 -0
  51. package/dist/components/measure.js +51 -0
  52. package/dist/components/measure.js.map +1 -0
  53. package/dist/components/modal.d.ts +1 -1
  54. package/dist/components/press-event.d.ts +28 -0
  55. package/dist/components/press-event.js +46 -0
  56. package/dist/components/press-event.js.map +1 -0
  57. package/dist/components/pressable.d.ts +2 -6
  58. package/dist/components/pressable.js +119 -20
  59. package/dist/components/pressable.js.map +1 -1
  60. package/dist/components/rect-store.d.ts +11 -1
  61. package/dist/components/rect-store.js +27 -3
  62. package/dist/components/rect-store.js.map +1 -1
  63. package/dist/components/scroll-view.d.ts +4 -0
  64. package/dist/components/scroll-view.js +8 -2
  65. package/dist/components/scroll-view.js.map +1 -1
  66. package/dist/components/svg/animated-support.d.ts +19 -0
  67. package/dist/components/svg/animated-support.js +64 -0
  68. package/dist/components/svg/animated-support.js.map +1 -0
  69. package/dist/components/svg/context.d.ts +8 -0
  70. package/dist/components/svg/context.js +15 -0
  71. package/dist/components/svg/context.js.map +1 -0
  72. package/dist/components/svg/gradients.d.ts +31 -0
  73. package/dist/components/svg/gradients.js +93 -0
  74. package/dist/components/svg/gradients.js.map +1 -0
  75. package/dist/components/svg/group.d.ts +8 -0
  76. package/dist/components/svg/group.js +30 -0
  77. package/dist/components/svg/group.js.map +1 -0
  78. package/dist/components/svg/index.d.ts +6 -0
  79. package/dist/components/svg/index.js +5 -0
  80. package/dist/components/svg/index.js.map +1 -0
  81. package/dist/components/svg/node.d.ts +7 -0
  82. package/dist/components/svg/node.js +7 -0
  83. package/dist/components/svg/node.js.map +1 -0
  84. package/dist/components/svg/paint.d.ts +24 -0
  85. package/dist/components/svg/paint.js +57 -0
  86. package/dist/components/svg/paint.js.map +1 -0
  87. package/dist/components/svg/shapes.d.ts +47 -0
  88. package/dist/components/svg/shapes.js +51 -0
  89. package/dist/components/svg/shapes.js.map +1 -0
  90. package/dist/components/svg/svg.d.ts +14 -0
  91. package/dist/components/svg/svg.js +48 -0
  92. package/dist/components/svg/svg.js.map +1 -0
  93. package/dist/components/svg/use-shape-node.d.ts +12 -0
  94. package/dist/components/svg/use-shape-node.js +52 -0
  95. package/dist/components/svg/use-shape-node.js.map +1 -0
  96. package/dist/components/text-input.js +1 -1
  97. package/dist/components/text-input.js.map +1 -1
  98. package/dist/components/text.js +36 -3
  99. package/dist/components/text.js.map +1 -1
  100. package/dist/components/use-layout-child.js +50 -4
  101. package/dist/components/use-layout-child.js.map +1 -1
  102. package/dist/components/view.d.ts +9 -4
  103. package/dist/components/view.js +8 -2
  104. package/dist/components/view.js.map +1 -1
  105. package/dist/contracts.d.ts +31 -4
  106. package/dist/contracts.js.map +1 -1
  107. package/dist/gtk/index.d.ts +7 -0
  108. package/dist/gtk/index.js +46 -0
  109. package/dist/gtk/index.js.map +1 -0
  110. package/dist/gtk/widgets.generated.d.ts +103 -0
  111. package/dist/gtk/widgets.generated.js +205 -0
  112. package/dist/gtk/widgets.generated.js.map +1 -0
  113. package/dist/gtk/window-actions.d.ts +77 -0
  114. package/dist/gtk/window-actions.js +193 -0
  115. package/dist/gtk/window-actions.js.map +1 -0
  116. package/dist/gtkx/bridge/geometry.d.ts +15 -0
  117. package/dist/gtkx/bridge/geometry.js +38 -0
  118. package/dist/gtkx/bridge/geometry.js.map +1 -0
  119. package/dist/gtkx/bridge/index.d.ts +11 -4
  120. package/dist/gtkx/bridge/index.js +47 -6
  121. package/dist/gtkx/bridge/index.js.map +1 -1
  122. package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
  123. package/dist/gtkx/bridge/layout-manager.js +31 -9
  124. package/dist/gtkx/bridge/layout-manager.js.map +1 -1
  125. package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
  126. package/dist/gtkx/bridge/slot-portal.js +17 -0
  127. package/dist/gtkx/bridge/slot-portal.js.map +1 -0
  128. package/dist/gtkx/bridge/svg-node.d.ts +79 -0
  129. package/dist/gtkx/bridge/svg-node.js +323 -0
  130. package/dist/gtkx/bridge/svg-node.js.map +1 -0
  131. package/dist/gtkx/bridge/use-signal.js +19 -9
  132. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  133. package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
  134. package/dist/gtkx/bridge/widgets.generated.js +14 -0
  135. package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
  136. package/dist/index.d.ts +2 -2
  137. package/dist/index.js +1 -1
  138. package/dist/index.js.map +1 -1
  139. package/dist/mcp/bin.d.ts +2 -0
  140. package/dist/mcp/bin.js +17 -0
  141. package/dist/mcp/bin.js.map +1 -0
  142. package/dist/mcp/data/generated.d.ts +1304 -0
  143. package/dist/mcp/data/generated.js +1179 -0
  144. package/dist/mcp/data/generated.js.map +1 -0
  145. package/dist/mcp/index.d.ts +5 -0
  146. package/dist/mcp/index.js +9 -0
  147. package/dist/mcp/index.js.map +1 -0
  148. package/dist/mcp/list.d.ts +4 -0
  149. package/dist/mcp/list.js +56 -0
  150. package/dist/mcp/list.js.map +1 -0
  151. package/dist/mcp/resolve.d.ts +55 -0
  152. package/dist/mcp/resolve.js +151 -0
  153. package/dist/mcp/resolve.js.map +1 -0
  154. package/dist/mcp/search.d.ts +13 -0
  155. package/dist/mcp/search.js +48 -0
  156. package/dist/mcp/search.js.map +1 -0
  157. package/dist/mcp/server.d.ts +7 -0
  158. package/dist/mcp/server.js +135 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/mcp/tool.d.ts +21 -0
  161. package/dist/mcp/tool.js +36 -0
  162. package/dist/mcp/tool.js.map +1 -0
  163. package/dist/metro/index.js +10 -0
  164. package/dist/metro/index.js.map +1 -1
  165. package/dist/navigation/index.d.ts +50 -13
  166. package/dist/navigation/index.js +183 -148
  167. package/dist/navigation/index.js.map +1 -1
  168. package/dist/navigation/option-warnings.js +9 -1
  169. package/dist/navigation/option-warnings.js.map +1 -1
  170. package/dist/navigation/sidebar.d.ts +207 -9
  171. package/dist/navigation/sidebar.js +294 -10
  172. package/dist/navigation/sidebar.js.map +1 -1
  173. package/dist/responder/system.d.ts +27 -0
  174. package/dist/responder/system.js +184 -0
  175. package/dist/responder/system.js.map +1 -0
  176. package/dist/responder/touch-history.d.ts +10 -0
  177. package/dist/responder/touch-history.js +78 -0
  178. package/dist/responder/touch-history.js.map +1 -0
  179. package/dist/responder/types.d.ts +63 -0
  180. package/dist/responder/types.js +29 -0
  181. package/dist/responder/types.js.map +1 -0
  182. package/dist/responder/use-responder.d.ts +12 -0
  183. package/dist/responder/use-responder.js +98 -0
  184. package/dist/responder/use-responder.js.map +1 -0
  185. package/dist/runner/host-dev.js +3 -1
  186. package/dist/runner/host-dev.js.map +1 -1
  187. package/dist/runner/host.js +5 -1
  188. package/dist/runner/host.js.map +1 -1
  189. package/dist/runner/index.d.ts +22 -2
  190. package/dist/runner/index.js +163 -13
  191. package/dist/runner/index.js.map +1 -1
  192. package/dist/sea/assemble.d.ts +29 -0
  193. package/dist/sea/assemble.js +129 -0
  194. package/dist/sea/assemble.js.map +1 -0
  195. package/dist/sea/bundle.d.ts +61 -0
  196. package/dist/sea/bundle.js +202 -0
  197. package/dist/sea/bundle.js.map +1 -0
  198. package/dist/sea/gtkx-config-module.d.ts +18 -0
  199. package/dist/sea/gtkx-config-module.js +67 -0
  200. package/dist/sea/gtkx-config-module.js.map +1 -0
  201. package/dist/sea/native-shim.d.ts +40 -0
  202. package/dist/sea/native-shim.js +121 -0
  203. package/dist/sea/native-shim.js.map +1 -0
  204. package/dist/sea/react-anchor-plugin.d.ts +2 -0
  205. package/dist/sea/react-anchor-plugin.js +30 -0
  206. package/dist/sea/react-anchor-plugin.js.map +1 -0
  207. package/dist/style/box-shadow.d.ts +7 -0
  208. package/dist/style/box-shadow.js +197 -0
  209. package/dist/style/box-shadow.js.map +1 -0
  210. package/dist/style/index.d.ts +3 -0
  211. package/dist/style/index.js +3 -0
  212. package/dist/style/index.js.map +1 -1
  213. package/dist/style/split-style.d.ts +5 -2
  214. package/dist/style/split-style.js +33 -5
  215. package/dist/style/split-style.js.map +1 -1
  216. package/dist/style/text-decoration.d.ts +15 -0
  217. package/dist/style/text-decoration.js +26 -0
  218. package/dist/style/text-decoration.js.map +1 -0
  219. package/dist/style/transform.d.ts +22 -0
  220. package/dist/style/transform.js +137 -0
  221. package/dist/style/transform.js.map +1 -0
  222. package/dist/style/visual-css.js +40 -3
  223. package/dist/style/visual-css.js.map +1 -1
  224. package/dist/svg/geometry.d.ts +19 -0
  225. package/dist/svg/geometry.js +80 -0
  226. package/dist/svg/geometry.js.map +1 -0
  227. package/dist/svg/gradient-geometry.d.ts +15 -0
  228. package/dist/svg/gradient-geometry.js +15 -0
  229. package/dist/svg/gradient-geometry.js.map +1 -0
  230. package/dist/svg/transform.d.ts +22 -0
  231. package/dist/svg/transform.js +74 -0
  232. package/dist/svg/transform.js.map +1 -0
  233. package/dist/svg/view-box.d.ts +34 -0
  234. package/dist/svg/view-box.js +108 -0
  235. package/dist/svg/view-box.js.map +1 -0
  236. package/dist/svg-compat/index.d.ts +1 -0
  237. package/dist/svg-compat/index.js +16 -0
  238. package/dist/svg-compat/index.js.map +1 -0
  239. package/dist/testing/index.d.ts +14 -0
  240. package/dist/testing/index.js +14 -0
  241. package/dist/testing/index.js.map +1 -0
  242. package/dist/vendor/react-native/pan-responder.d.ts +102 -0
  243. package/dist/vendor/react-native/pan-responder.js +196 -0
  244. package/dist/vendor/react-native/pan-responder.js.map +1 -0
  245. package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
  246. package/dist/vendor/react-native/touch-history-math.js +102 -0
  247. package/dist/vendor/react-native/touch-history-math.js.map +1 -0
  248. package/dist/vite/index.d.ts +7 -3
  249. package/dist/vite/index.js +16 -4
  250. package/dist/vite/index.js.map +1 -1
  251. package/dist/vitest/index.d.ts +52 -0
  252. package/dist/vitest/index.js +58 -0
  253. package/dist/vitest/index.js.map +1 -0
  254. package/dist/vitest/setup.d.ts +4 -0
  255. package/dist/vitest/setup.js +22 -0
  256. package/dist/vitest/setup.js.map +1 -0
  257. package/package.json +52 -13
@@ -0,0 +1,22 @@
1
+ import type { Transform2D, TransformPart } from "../contracts";
2
+ export declare const IDENTITY_TRANSFORM: Transform2D;
3
+ /**
4
+ * RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns
5
+ * degrees, or null for anything unparseable (warned once, then dropped —
6
+ * a bad angle must not take the whole style down).
7
+ */
8
+ export declare const parseAngle: (value: unknown) => number | null;
9
+ /**
10
+ * Folds an RN transform array into a single matrix. Order is RN's (and
11
+ * CSS's): the leftmost entry is the outermost matrix, so it is applied to a
12
+ * point LAST — `[{rotate}, {translateX}]` translates, then rotates the
13
+ * result about the origin.
14
+ */
15
+ export declare const composeTransform: (parts: readonly TransformPart[]) => Transform2D;
16
+ export declare const isIdentityTransform: (matrix: Transform2D) => boolean;
17
+ /**
18
+ * True when the matrix is a pure translation. Those keep the existing
19
+ * positional fast path (the rect is simply offset), which costs no
20
+ * GskTransform at all — only rotate/scale need one.
21
+ */
22
+ export declare const isTranslationOnly: (matrix: Transform2D) => boolean;
@@ -0,0 +1,137 @@
1
+ // Composes an RN `transform` array into one 2D affine matrix. Pure module —
2
+ // no bridge imports — so the whole ordering contract is unit-testable on any
3
+ // OS; the bridge only turns the finished matrix into a GskTransform.
4
+ //
5
+ // Why a matrix and not a chain of GskTransform calls: measured in the VM
6
+ // (docs/research/transforms.md), a five-op GskTransform chain costs 18.3 us
7
+ // per build against 5.7 us for one gsk_transform_matrix2d(), and both
8
+ // produce a bit-identical to2d(). Composition is six multiplies of plain
9
+ // numbers, so it stays on the Animated fast path.
10
+ import { warnOnce } from "./dev-warning";
11
+ export const IDENTITY_TRANSFORM = {
12
+ xx: 1,
13
+ yx: 0,
14
+ xy: 0,
15
+ yy: 1,
16
+ dx: 0,
17
+ dy: 0,
18
+ };
19
+ const DEG_PER_RAD = 180 / Math.PI;
20
+ /**
21
+ * RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns
22
+ * degrees, or null for anything unparseable (warned once, then dropped —
23
+ * a bad angle must not take the whole style down).
24
+ */
25
+ export const parseAngle = (value) => {
26
+ if (typeof value === "number") {
27
+ // RN rejects unitless angles in styles, but an Animated node driving a
28
+ // rotation with a plain numeric outputRange is a common shortcut and
29
+ // degrees are the only sane reading of it.
30
+ return Number.isFinite(value) ? value : null;
31
+ }
32
+ if (typeof value !== "string") {
33
+ return null;
34
+ }
35
+ const match = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)(deg|rad)\s*$/.exec(value);
36
+ if (!match) {
37
+ warnOnce(`invalid-angle:${value}`, `[react-native-gtkx] Invalid angle ${JSON.stringify(value)} in "transform" — expected "<number>deg" or "<number>rad"; the entry is ignored`);
38
+ return null;
39
+ }
40
+ const amount = parseFloat(match[1]);
41
+ return match[2] === "rad" ? amount * DEG_PER_RAD : amount;
42
+ };
43
+ // left * right, both in the Transform2D component order.
44
+ const multiply = (left, right) => ({
45
+ xx: left.xx * right.xx + left.xy * right.yx,
46
+ yx: left.yx * right.xx + left.yy * right.yx,
47
+ xy: left.xx * right.xy + left.xy * right.yy,
48
+ yy: left.yx * right.xy + left.yy * right.yy,
49
+ dx: left.xx * right.dx + left.xy * right.dy + left.dx,
50
+ dy: left.yx * right.dx + left.yy * right.dy + left.dy,
51
+ });
52
+ const scaleMatrix = (x, y) => Number.isFinite(x) && Number.isFinite(y)
53
+ ? { xx: x, yx: 0, xy: 0, yy: y, dx: 0, dy: 0 }
54
+ : null;
55
+ const rotateMatrix = (angle) => {
56
+ const degrees = parseAngle(angle);
57
+ if (degrees === null) {
58
+ return null;
59
+ }
60
+ const radians = degrees / DEG_PER_RAD;
61
+ const cos = Math.cos(radians);
62
+ const sin = Math.sin(radians);
63
+ return { xx: cos, yx: sin, xy: -sin, yy: cos, dx: 0, dy: 0 };
64
+ };
65
+ // A TransformPart is a single-key object, and this runs on every animated
66
+ // frame — so the supported keys are probed directly (a monomorphic property
67
+ // load) instead of being discovered with Object.keys/for-in, and the matrices
68
+ // are plain literals rather than spreads of the identity. Key discovery only
69
+ // happens on the miss path, to name the unsupported entry in the warning.
70
+ const partToMatrix = (part) => {
71
+ const entry = part;
72
+ const translateX = entry.translateX;
73
+ if (typeof translateX === "number") {
74
+ return Number.isFinite(translateX)
75
+ ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: translateX, dy: 0 }
76
+ : null;
77
+ }
78
+ const translateY = entry.translateY;
79
+ if (typeof translateY === "number") {
80
+ return Number.isFinite(translateY)
81
+ ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: 0, dy: translateY }
82
+ : null;
83
+ }
84
+ const scale = entry.scale;
85
+ if (typeof scale === "number") {
86
+ return scaleMatrix(scale, scale);
87
+ }
88
+ const scaleX = entry.scaleX;
89
+ if (typeof scaleX === "number") {
90
+ return scaleMatrix(scaleX, 1);
91
+ }
92
+ const scaleY = entry.scaleY;
93
+ if (typeof scaleY === "number") {
94
+ return scaleMatrix(1, scaleY);
95
+ }
96
+ if (entry.rotate !== undefined) {
97
+ return rotateMatrix(entry.rotate);
98
+ }
99
+ if (entry.rotateZ !== undefined) {
100
+ return rotateMatrix(entry.rotateZ);
101
+ }
102
+ for (const key of Object.keys(entry)) {
103
+ warnOnce(`unsupported-transform:${key}`, `[react-native-gtkx] Unsupported transform "${key}" — supported: translateX, translateY, scale, scaleX, scaleY, rotate, rotateZ`);
104
+ }
105
+ return null;
106
+ };
107
+ /**
108
+ * Folds an RN transform array into a single matrix. Order is RN's (and
109
+ * CSS's): the leftmost entry is the outermost matrix, so it is applied to a
110
+ * point LAST — `[{rotate}, {translateX}]` translates, then rotates the
111
+ * result about the origin.
112
+ */
113
+ export const composeTransform = (parts) => {
114
+ // `matrix === null` means "still the identity", which lets the common
115
+ // single-entry array skip the multiply entirely.
116
+ let matrix = null;
117
+ for (const part of parts) {
118
+ const next = partToMatrix(part);
119
+ if (next) {
120
+ matrix = matrix === null ? next : multiply(matrix, next);
121
+ }
122
+ }
123
+ return matrix ?? IDENTITY_TRANSFORM;
124
+ };
125
+ export const isIdentityTransform = (matrix) => matrix.xx === 1 &&
126
+ matrix.yx === 0 &&
127
+ matrix.xy === 0 &&
128
+ matrix.yy === 1 &&
129
+ matrix.dx === 0 &&
130
+ matrix.dy === 0;
131
+ /**
132
+ * True when the matrix is a pure translation. Those keep the existing
133
+ * positional fast path (the rect is simply offset), which costs no
134
+ * GskTransform at all — only rotate/scale need one.
135
+ */
136
+ export const isTranslationOnly = (matrix) => matrix.xx === 1 && matrix.yx === 0 && matrix.xy === 0 && matrix.yy === 1;
137
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/style/transform.ts"],"names":[],"mappings":"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","sourcesContent":["// Composes an RN `transform` array into one 2D affine matrix. Pure module —\n// no bridge imports — so the whole ordering contract is unit-testable on any\n// OS; the bridge only turns the finished matrix into a GskTransform.\n//\n// Why a matrix and not a chain of GskTransform calls: measured in the VM\n// (docs/research/transforms.md), a five-op GskTransform chain costs 18.3 us\n// per build against 5.7 us for one gsk_transform_matrix2d(), and both\n// produce a bit-identical to2d(). Composition is six multiplies of plain\n// numbers, so it stays on the Animated fast path.\n\nimport type { Transform2D, TransformPart } from \"../contracts\"\nimport { warnOnce } from \"./dev-warning\"\n\nexport const IDENTITY_TRANSFORM: Transform2D = {\n xx: 1,\n yx: 0,\n xy: 0,\n yy: 1,\n dx: 0,\n dy: 0,\n}\n\nconst DEG_PER_RAD = 180 / Math.PI\n\n/**\n * RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns\n * degrees, or null for anything unparseable (warned once, then dropped —\n * a bad angle must not take the whole style down).\n */\nexport const parseAngle = (value: unknown): number | null => {\n if (typeof value === \"number\") {\n // RN rejects unitless angles in styles, but an Animated node driving a\n // rotation with a plain numeric outputRange is a common shortcut and\n // degrees are the only sane reading of it.\n return Number.isFinite(value) ? value : null\n }\n if (typeof value !== \"string\") {\n return null\n }\n const match =\n /^\\s*([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)(deg|rad)\\s*$/.exec(value)\n if (!match) {\n warnOnce(\n `invalid-angle:${value}`,\n `[react-native-gtkx] Invalid angle ${JSON.stringify(value)} in \"transform\" — expected \"<number>deg\" or \"<number>rad\"; the entry is ignored`,\n )\n return null\n }\n const amount = parseFloat(match[1]!)\n return match[2] === \"rad\" ? amount * DEG_PER_RAD : amount\n}\n\n// left * right, both in the Transform2D component order.\nconst multiply = (left: Transform2D, right: Transform2D): Transform2D => ({\n xx: left.xx * right.xx + left.xy * right.yx,\n yx: left.yx * right.xx + left.yy * right.yx,\n xy: left.xx * right.xy + left.xy * right.yy,\n yy: left.yx * right.xy + left.yy * right.yy,\n dx: left.xx * right.dx + left.xy * right.dy + left.dx,\n dy: left.yx * right.dx + left.yy * right.dy + left.dy,\n})\n\nconst scaleMatrix = (x: number, y: number): Transform2D | null =>\n Number.isFinite(x) && Number.isFinite(y)\n ? { xx: x, yx: 0, xy: 0, yy: y, dx: 0, dy: 0 }\n : null\n\nconst rotateMatrix = (angle: unknown): Transform2D | null => {\n const degrees = parseAngle(angle)\n if (degrees === null) {\n return null\n }\n const radians = degrees / DEG_PER_RAD\n const cos = Math.cos(radians)\n const sin = Math.sin(radians)\n return { xx: cos, yx: sin, xy: -sin, yy: cos, dx: 0, dy: 0 }\n}\n\n// A TransformPart is a single-key object, and this runs on every animated\n// frame — so the supported keys are probed directly (a monomorphic property\n// load) instead of being discovered with Object.keys/for-in, and the matrices\n// are plain literals rather than spreads of the identity. Key discovery only\n// happens on the miss path, to name the unsupported entry in the warning.\nconst partToMatrix = (part: TransformPart): Transform2D | null => {\n const entry = part as Record<string, unknown>\n\n const translateX = entry.translateX\n if (typeof translateX === \"number\") {\n return Number.isFinite(translateX)\n ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: translateX, dy: 0 }\n : null\n }\n const translateY = entry.translateY\n if (typeof translateY === \"number\") {\n return Number.isFinite(translateY)\n ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: 0, dy: translateY }\n : null\n }\n const scale = entry.scale\n if (typeof scale === \"number\") {\n return scaleMatrix(scale, scale)\n }\n const scaleX = entry.scaleX\n if (typeof scaleX === \"number\") {\n return scaleMatrix(scaleX, 1)\n }\n const scaleY = entry.scaleY\n if (typeof scaleY === \"number\") {\n return scaleMatrix(1, scaleY)\n }\n if (entry.rotate !== undefined) {\n return rotateMatrix(entry.rotate)\n }\n if (entry.rotateZ !== undefined) {\n return rotateMatrix(entry.rotateZ)\n }\n\n for (const key of Object.keys(entry)) {\n warnOnce(\n `unsupported-transform:${key}`,\n `[react-native-gtkx] Unsupported transform \"${key}\" — supported: translateX, translateY, scale, scaleX, scaleY, rotate, rotateZ`,\n )\n }\n return null\n}\n\n/**\n * Folds an RN transform array into a single matrix. Order is RN's (and\n * CSS's): the leftmost entry is the outermost matrix, so it is applied to a\n * point LAST — `[{rotate}, {translateX}]` translates, then rotates the\n * result about the origin.\n */\nexport const composeTransform = (\n parts: readonly TransformPart[],\n): Transform2D => {\n // `matrix === null` means \"still the identity\", which lets the common\n // single-entry array skip the multiply entirely.\n let matrix: Transform2D | null = null\n for (const part of parts) {\n const next = partToMatrix(part)\n if (next) {\n matrix = matrix === null ? next : multiply(matrix, next)\n }\n }\n return matrix ?? IDENTITY_TRANSFORM\n}\n\nexport const isIdentityTransform = (matrix: Transform2D): boolean =>\n matrix.xx === 1 &&\n matrix.yx === 0 &&\n matrix.xy === 0 &&\n matrix.yy === 1 &&\n matrix.dx === 0 &&\n matrix.dy === 0\n\n/**\n * True when the matrix is a pure translation. Those keep the existing\n * positional fast path (the rect is simply offset), which costs no\n * GskTransform at all — only rotate/scale need one.\n */\nexport const isTranslationOnly = (matrix: Transform2D): boolean =>\n matrix.xx === 1 && matrix.yx === 0 && matrix.xy === 0 && matrix.yy === 1\n"]}
@@ -5,11 +5,19 @@
5
5
  // GTK4 CSS is not web CSS. What this generator relies on:
6
6
  // - background-color, border-*, border-radius (incl. per-corner), opacity,
7
7
  // color, font-*, letter-spacing: supported by GTK4;
8
+ // - box-shadow and outline-*: supported by GTK4, and used by Adwaita itself
9
+ // for the `.card`/`.boxed-list` frame and for every focus ring in the
10
+ // theme — which is why an app cannot reproduce the platform's own look
11
+ // without them. box-shadow is compiled by ./box-shadow.ts;
8
12
  // - line-height: supported since GTK 4.6 (we target 4.20+);
9
13
  // - text-align does NOT exist in GTK CSS — textAlign is applied by the Text
10
14
  // component via widget properties (justify/xalign), so it is skipped here;
11
- // - transform is applied by the layout engine via Fixed.Child matrices,
12
- // never through CSS, and is skipped here as well.
15
+ // - text-decoration does not exist for GTK widgets either: Pango carries it,
16
+ // and the Text component applies textDecorationLine as label attributes;
17
+ // - transform does NOT exist as a widget property in GTK4 CSS either: it is
18
+ // composed into a matrix by ./transform.ts and applied as the GskTransform
19
+ // of the child's allocation, so it is skipped here as well.
20
+ import { boxShadowToCss } from "./box-shadow";
13
21
  import { parseColor } from "./colors";
14
22
  import { warnOnce } from "./dev-warning";
15
23
  const px = (value) => `${value}px`;
@@ -43,6 +51,12 @@ export const visualStyleToCss = (visual) => {
43
51
  if (visual.opacity !== undefined) {
44
52
  decls.push(`opacity: ${clamp01(visual.opacity)};`);
45
53
  }
54
+ if (visual.boxShadow !== undefined) {
55
+ const shadow = boxShadowToCss(visual.boxShadow);
56
+ if (shadow !== null) {
57
+ decls.push(`box-shadow: ${shadow};`);
58
+ }
59
+ }
46
60
  // GTK defaults border-style to none — width alone renders nothing. An
47
61
  // explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);
48
62
  // otherwise any positive border width turns the border on with "solid".
@@ -106,6 +120,28 @@ export const visualStyleToCss = (visual) => {
106
120
  if (visual.borderBottomLeftRadius !== undefined) {
107
121
  decls.push(`border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`);
108
122
  }
123
+ // outline is a ring around the border box that takes no layout space, so
124
+ // unlike `border` it needs no Yoga involvement at all — GTK4 draws it from
125
+ // CSS exactly as the web does, which is how Adwaita itself paints every
126
+ // focus ring in the theme. GTK, like the web, defaults outline-style to
127
+ // `none`, so a width on its own would draw nothing; the same auto-solid
128
+ // rule the borders above use applies.
129
+ const hasVisibleOutlineWidth = visual.outlineWidth !== undefined && visual.outlineWidth > 0;
130
+ if (visual.outlineStyle !== undefined) {
131
+ decls.push(`outline-style: ${visual.outlineStyle};`);
132
+ }
133
+ else if (hasVisibleOutlineWidth) {
134
+ decls.push("outline-style: solid;");
135
+ }
136
+ if (visual.outlineWidth !== undefined) {
137
+ decls.push(`outline-width: ${px(visual.outlineWidth)};`);
138
+ }
139
+ if (visual.outlineColor !== undefined) {
140
+ pushColor(decls, "outline-color", visual.outlineColor, "outlineColor");
141
+ }
142
+ if (visual.outlineOffset !== undefined) {
143
+ decls.push(`outline-offset: ${px(visual.outlineOffset)};`);
144
+ }
109
145
  if (visual.color !== undefined) {
110
146
  pushColor(decls, "color", visual.color, "color");
111
147
  }
@@ -127,7 +163,8 @@ export const visualStyleToCss = (visual) => {
127
163
  if (visual.lineHeight !== undefined) {
128
164
  decls.push(`line-height: ${px(visual.lineHeight)};`);
129
165
  }
130
- // visual.textAlign, visual.transform: intentionally not CSS (see header).
166
+ // visual.textAlign, visual.textDecorationLine, visual.transform:
167
+ // intentionally not CSS (see header).
131
168
  return decls.join("\n");
132
169
  };
133
170
  //# sourceMappingURL=visual-css.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visual-css.js","sourceRoot":"","sources":["../../src/style/visual-css.ts"],"names":[],"mappings":"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","sourcesContent":["// Compiles a VisualStyle into a GTK4 CSS declaration block (no selector).\n// Pure module — the resulting string is registered as a class through an\n// injected `css` function (see registry.ts / registry.gtkx.ts).\n//\n// GTK4 CSS is not web CSS. What this generator relies on:\n// - background-color, border-*, border-radius (incl. per-corner), opacity,\n// color, font-*, letter-spacing: supported by GTK4;\n// - line-height: supported since GTK 4.6 (we target 4.20+);\n// - text-align does NOT exist in GTK CSS — textAlign is applied by the Text\n// component via widget properties (justify/xalign), so it is skipped here;\n// - transform is applied by the layout engine via Fixed.Child matrices,\n// never through CSS, and is skipped here as well.\n\nimport type { VisualStyle } from \"../contracts\"\nimport { parseColor } from \"./colors\"\nimport { warnOnce } from \"./dev-warning\"\n\nconst px = (value: number): string => `${value}px`\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value))\n\nconst pushColor = (\n declarations: string[],\n cssProperty: string,\n value: string,\n styleProperty: string,\n): void => {\n const parsed = parseColor(value)\n if (parsed === null) {\n warnOnce(\n `invalid-color:${styleProperty}:${value}`,\n `[react-native-gtkx] Invalid color ${JSON.stringify(value)} in \"${styleProperty}\" — declaration dropped`,\n )\n return\n }\n declarations.push(`${cssProperty}: ${parsed};`)\n}\n\nconst formatFontFamily = (family: string): string => {\n const trimmed = family.trim()\n if (/^(\".*\"|'.*')$/.test(trimmed)) {\n return trimmed\n }\n return /^[A-Za-z][\\w-]*$/.test(trimmed)\n ? trimmed\n : `\"${trimmed.replace(/\"/g, '\\\\\"')}\"`\n}\n\n/**\n * Renders a VisualStyle as GTK CSS declarations (one per line, stable\n * order). Returns \"\" when the style produces no CSS (e.g. transform-only).\n */\nexport const visualStyleToCss = (visual: VisualStyle): string => {\n const decls: string[] = []\n\n if (visual.backgroundColor !== undefined) {\n pushColor(\n decls,\n \"background-color\",\n visual.backgroundColor,\n \"backgroundColor\",\n )\n }\n if (visual.opacity !== undefined) {\n decls.push(`opacity: ${clamp01(visual.opacity)};`)\n }\n\n // GTK defaults border-style to none — width alone renders nothing. An\n // explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);\n // otherwise any positive border width turns the border on with \"solid\".\n const hasVisibleBorderWidth = [\n visual.borderWidth,\n visual.borderTopWidth,\n visual.borderRightWidth,\n visual.borderBottomWidth,\n visual.borderLeftWidth,\n ].some((width) => width !== undefined && width > 0)\n if (visual.borderStyle !== undefined) {\n decls.push(`border-style: ${visual.borderStyle};`)\n } else if (hasVisibleBorderWidth) {\n decls.push(\"border-style: solid;\")\n }\n // Shorthands first, then per-side overrides so the sides win.\n if (visual.borderWidth !== undefined) {\n decls.push(`border-width: ${px(visual.borderWidth)};`)\n }\n if (visual.borderTopWidth !== undefined) {\n decls.push(`border-top-width: ${px(visual.borderTopWidth)};`)\n }\n if (visual.borderRightWidth !== undefined) {\n decls.push(`border-right-width: ${px(visual.borderRightWidth)};`)\n }\n if (visual.borderBottomWidth !== undefined) {\n decls.push(`border-bottom-width: ${px(visual.borderBottomWidth)};`)\n }\n if (visual.borderLeftWidth !== undefined) {\n decls.push(`border-left-width: ${px(visual.borderLeftWidth)};`)\n }\n if (visual.borderColor !== undefined) {\n pushColor(decls, \"border-color\", visual.borderColor, \"borderColor\")\n }\n if (visual.borderTopColor !== undefined) {\n pushColor(\n decls,\n \"border-top-color\",\n visual.borderTopColor,\n \"borderTopColor\",\n )\n }\n if (visual.borderRightColor !== undefined) {\n pushColor(\n decls,\n \"border-right-color\",\n visual.borderRightColor,\n \"borderRightColor\",\n )\n }\n if (visual.borderBottomColor !== undefined) {\n pushColor(\n decls,\n \"border-bottom-color\",\n visual.borderBottomColor,\n \"borderBottomColor\",\n )\n }\n if (visual.borderLeftColor !== undefined) {\n pushColor(\n decls,\n \"border-left-color\",\n visual.borderLeftColor,\n \"borderLeftColor\",\n )\n }\n if (visual.borderRadius !== undefined) {\n decls.push(`border-radius: ${px(visual.borderRadius)};`)\n }\n // Per-corner radii come after the shorthand so they override it.\n if (visual.borderTopLeftRadius !== undefined) {\n decls.push(`border-top-left-radius: ${px(visual.borderTopLeftRadius)};`)\n }\n if (visual.borderTopRightRadius !== undefined) {\n decls.push(`border-top-right-radius: ${px(visual.borderTopRightRadius)};`)\n }\n if (visual.borderBottomRightRadius !== undefined) {\n decls.push(\n `border-bottom-right-radius: ${px(visual.borderBottomRightRadius)};`,\n )\n }\n if (visual.borderBottomLeftRadius !== undefined) {\n decls.push(\n `border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`,\n )\n }\n\n if (visual.color !== undefined) {\n pushColor(decls, \"color\", visual.color, \"color\")\n }\n if (visual.fontFamily !== undefined) {\n decls.push(`font-family: ${formatFontFamily(visual.fontFamily)};`)\n }\n if (visual.fontSize !== undefined) {\n decls.push(`font-size: ${px(visual.fontSize)};`)\n }\n if (visual.fontStyle !== undefined) {\n decls.push(`font-style: ${visual.fontStyle};`)\n }\n if (visual.fontWeight !== undefined) {\n decls.push(`font-weight: ${visual.fontWeight};`)\n }\n if (visual.letterSpacing !== undefined) {\n decls.push(`letter-spacing: ${px(visual.letterSpacing)};`)\n }\n if (visual.lineHeight !== undefined) {\n decls.push(`line-height: ${px(visual.lineHeight)};`)\n }\n\n // visual.textAlign, visual.transform: intentionally not CSS (see header).\n\n return decls.join(\"\\n\")\n}\n"]}
1
+ {"version":3,"file":"visual-css.js","sourceRoot":"","sources":["../../src/style/visual-css.ts"],"names":[],"mappings":"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","sourcesContent":["// Compiles a VisualStyle into a GTK4 CSS declaration block (no selector).\n// Pure module — the resulting string is registered as a class through an\n// injected `css` function (see registry.ts / registry.gtkx.ts).\n//\n// GTK4 CSS is not web CSS. What this generator relies on:\n// - background-color, border-*, border-radius (incl. per-corner), opacity,\n// color, font-*, letter-spacing: supported by GTK4;\n// - box-shadow and outline-*: supported by GTK4, and used by Adwaita itself\n// for the `.card`/`.boxed-list` frame and for every focus ring in the\n// theme — which is why an app cannot reproduce the platform's own look\n// without them. box-shadow is compiled by ./box-shadow.ts;\n// - line-height: supported since GTK 4.6 (we target 4.20+);\n// - text-align does NOT exist in GTK CSS — textAlign is applied by the Text\n// component via widget properties (justify/xalign), so it is skipped here;\n// - text-decoration does not exist for GTK widgets either: Pango carries it,\n// and the Text component applies textDecorationLine as label attributes;\n// - transform does NOT exist as a widget property in GTK4 CSS either: it is\n// composed into a matrix by ./transform.ts and applied as the GskTransform\n// of the child's allocation, so it is skipped here as well.\n\nimport type { VisualStyle } from \"../contracts\"\nimport { boxShadowToCss } from \"./box-shadow\"\nimport { parseColor } from \"./colors\"\nimport { warnOnce } from \"./dev-warning\"\n\nconst px = (value: number): string => `${value}px`\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value))\n\nconst pushColor = (\n declarations: string[],\n cssProperty: string,\n value: string,\n styleProperty: string,\n): void => {\n const parsed = parseColor(value)\n if (parsed === null) {\n warnOnce(\n `invalid-color:${styleProperty}:${value}`,\n `[react-native-gtkx] Invalid color ${JSON.stringify(value)} in \"${styleProperty}\" — declaration dropped`,\n )\n return\n }\n declarations.push(`${cssProperty}: ${parsed};`)\n}\n\nconst formatFontFamily = (family: string): string => {\n const trimmed = family.trim()\n if (/^(\".*\"|'.*')$/.test(trimmed)) {\n return trimmed\n }\n return /^[A-Za-z][\\w-]*$/.test(trimmed)\n ? trimmed\n : `\"${trimmed.replace(/\"/g, '\\\\\"')}\"`\n}\n\n/**\n * Renders a VisualStyle as GTK CSS declarations (one per line, stable\n * order). Returns \"\" when the style produces no CSS (e.g. transform-only).\n */\nexport const visualStyleToCss = (visual: VisualStyle): string => {\n const decls: string[] = []\n\n if (visual.backgroundColor !== undefined) {\n pushColor(\n decls,\n \"background-color\",\n visual.backgroundColor,\n \"backgroundColor\",\n )\n }\n if (visual.opacity !== undefined) {\n decls.push(`opacity: ${clamp01(visual.opacity)};`)\n }\n if (visual.boxShadow !== undefined) {\n const shadow = boxShadowToCss(visual.boxShadow)\n if (shadow !== null) {\n decls.push(`box-shadow: ${shadow};`)\n }\n }\n\n // GTK defaults border-style to none — width alone renders nothing. An\n // explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);\n // otherwise any positive border width turns the border on with \"solid\".\n const hasVisibleBorderWidth = [\n visual.borderWidth,\n visual.borderTopWidth,\n visual.borderRightWidth,\n visual.borderBottomWidth,\n visual.borderLeftWidth,\n ].some((width) => width !== undefined && width > 0)\n if (visual.borderStyle !== undefined) {\n decls.push(`border-style: ${visual.borderStyle};`)\n } else if (hasVisibleBorderWidth) {\n decls.push(\"border-style: solid;\")\n }\n // Shorthands first, then per-side overrides so the sides win.\n if (visual.borderWidth !== undefined) {\n decls.push(`border-width: ${px(visual.borderWidth)};`)\n }\n if (visual.borderTopWidth !== undefined) {\n decls.push(`border-top-width: ${px(visual.borderTopWidth)};`)\n }\n if (visual.borderRightWidth !== undefined) {\n decls.push(`border-right-width: ${px(visual.borderRightWidth)};`)\n }\n if (visual.borderBottomWidth !== undefined) {\n decls.push(`border-bottom-width: ${px(visual.borderBottomWidth)};`)\n }\n if (visual.borderLeftWidth !== undefined) {\n decls.push(`border-left-width: ${px(visual.borderLeftWidth)};`)\n }\n if (visual.borderColor !== undefined) {\n pushColor(decls, \"border-color\", visual.borderColor, \"borderColor\")\n }\n if (visual.borderTopColor !== undefined) {\n pushColor(\n decls,\n \"border-top-color\",\n visual.borderTopColor,\n \"borderTopColor\",\n )\n }\n if (visual.borderRightColor !== undefined) {\n pushColor(\n decls,\n \"border-right-color\",\n visual.borderRightColor,\n \"borderRightColor\",\n )\n }\n if (visual.borderBottomColor !== undefined) {\n pushColor(\n decls,\n \"border-bottom-color\",\n visual.borderBottomColor,\n \"borderBottomColor\",\n )\n }\n if (visual.borderLeftColor !== undefined) {\n pushColor(\n decls,\n \"border-left-color\",\n visual.borderLeftColor,\n \"borderLeftColor\",\n )\n }\n if (visual.borderRadius !== undefined) {\n decls.push(`border-radius: ${px(visual.borderRadius)};`)\n }\n // Per-corner radii come after the shorthand so they override it.\n if (visual.borderTopLeftRadius !== undefined) {\n decls.push(`border-top-left-radius: ${px(visual.borderTopLeftRadius)};`)\n }\n if (visual.borderTopRightRadius !== undefined) {\n decls.push(`border-top-right-radius: ${px(visual.borderTopRightRadius)};`)\n }\n if (visual.borderBottomRightRadius !== undefined) {\n decls.push(\n `border-bottom-right-radius: ${px(visual.borderBottomRightRadius)};`,\n )\n }\n if (visual.borderBottomLeftRadius !== undefined) {\n decls.push(\n `border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`,\n )\n }\n\n // outline is a ring around the border box that takes no layout space, so\n // unlike `border` it needs no Yoga involvement at all — GTK4 draws it from\n // CSS exactly as the web does, which is how Adwaita itself paints every\n // focus ring in the theme. GTK, like the web, defaults outline-style to\n // `none`, so a width on its own would draw nothing; the same auto-solid\n // rule the borders above use applies.\n const hasVisibleOutlineWidth =\n visual.outlineWidth !== undefined && visual.outlineWidth > 0\n if (visual.outlineStyle !== undefined) {\n decls.push(`outline-style: ${visual.outlineStyle};`)\n } else if (hasVisibleOutlineWidth) {\n decls.push(\"outline-style: solid;\")\n }\n if (visual.outlineWidth !== undefined) {\n decls.push(`outline-width: ${px(visual.outlineWidth)};`)\n }\n if (visual.outlineColor !== undefined) {\n pushColor(decls, \"outline-color\", visual.outlineColor, \"outlineColor\")\n }\n if (visual.outlineOffset !== undefined) {\n decls.push(`outline-offset: ${px(visual.outlineOffset)};`)\n }\n\n if (visual.color !== undefined) {\n pushColor(decls, \"color\", visual.color, \"color\")\n }\n if (visual.fontFamily !== undefined) {\n decls.push(`font-family: ${formatFontFamily(visual.fontFamily)};`)\n }\n if (visual.fontSize !== undefined) {\n decls.push(`font-size: ${px(visual.fontSize)};`)\n }\n if (visual.fontStyle !== undefined) {\n decls.push(`font-style: ${visual.fontStyle};`)\n }\n if (visual.fontWeight !== undefined) {\n decls.push(`font-weight: ${visual.fontWeight};`)\n }\n if (visual.letterSpacing !== undefined) {\n decls.push(`letter-spacing: ${px(visual.letterSpacing)};`)\n }\n if (visual.lineHeight !== undefined) {\n decls.push(`line-height: ${px(visual.lineHeight)};`)\n }\n\n // visual.textAlign, visual.textDecorationLine, visual.transform:\n // intentionally not CSS (see header).\n\n return decls.join(\"\\n\")\n}\n"]}
@@ -0,0 +1,19 @@
1
+ export type Point = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export declare const parsePoints: (points: string) => Point[];
6
+ export type RectShape = {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ rx?: number;
12
+ ry?: number;
13
+ };
14
+ export declare const rectToPathData: ({ x, y, width, height, rx, ry, }: RectShape) => string;
15
+ export declare const ellipseToPathData: (cx: number, cy: number, rx: number, ry: number) => string;
16
+ export declare const circleToPathData: (cx: number, cy: number, r: number) => string;
17
+ export declare const lineToPathData: (x1: number, y1: number, x2: number, y2: number) => string;
18
+ export declare const polygonToPathData: (points: string | Point[]) => string;
19
+ export declare const polylineToPathData: (points: string | Point[]) => string;
@@ -0,0 +1,80 @@
1
+ // Pure shape → SVG path `d` string generators. No gtkx imports (unit-testable
2
+ // on any OS, like style/colors.ts) — the only consumer of the output is
3
+ // `Gsk.Path.parse()` in the bridge, so every generator here produces (a
4
+ // superset of) SVG path syntax and nothing else. This is the "no parser of
5
+ // our own" bet from the PRD: <Path> passes its `d` straight through, and
6
+ // every other shape is just a small amount of arithmetic away from the same
7
+ // syntax.
8
+ // Accepts both the comma-separated and whitespace-separated forms RN-svg
9
+ // tolerates ("0,0 10,5" and "0 0 10 5"), and odd trailing tokens are
10
+ // dropped rather than throwing — malformed `points` should render nothing
11
+ // useful, not crash the tree.
12
+ export const parsePoints = (points) => {
13
+ const numbers = points
14
+ .trim()
15
+ .split(/[\s,]+/)
16
+ .filter((token) => token.length > 0)
17
+ .map(Number)
18
+ .filter((value) => !Number.isNaN(value));
19
+ const result = [];
20
+ for (let i = 0; i + 1 < numbers.length; i += 2) {
21
+ result.push({ x: numbers[i], y: numbers[i + 1] });
22
+ }
23
+ return result;
24
+ };
25
+ // SVG's rx/ry resolution: either one fills in for a missing other, and both
26
+ // clamp to half the box so opposite corners never overlap.
27
+ const resolveCornerRadii = (width, height, rx, ry) => {
28
+ const rawRx = rx ?? ry ?? 0;
29
+ const rawRy = ry ?? rx ?? 0;
30
+ return [
31
+ Math.max(0, Math.min(rawRx, width / 2)),
32
+ Math.max(0, Math.min(rawRy, height / 2)),
33
+ ];
34
+ };
35
+ export const rectToPathData = ({ x, y, width, height, rx, ry, }) => {
36
+ if (width <= 0 || height <= 0) {
37
+ return "";
38
+ }
39
+ const [radiusX, radiusY] = resolveCornerRadii(width, height, rx, ry);
40
+ if (radiusX <= 0 || radiusY <= 0) {
41
+ return `M ${x} ${y} H ${x + width} V ${y + height} H ${x} Z`;
42
+ }
43
+ const right = x + width;
44
+ const bottom = y + height;
45
+ return (`M ${x + radiusX} ${y} ` +
46
+ `H ${right - radiusX} ` +
47
+ `A ${radiusX} ${radiusY} 0 0 1 ${right} ${y + radiusY} ` +
48
+ `V ${bottom - radiusY} ` +
49
+ `A ${radiusX} ${radiusY} 0 0 1 ${right - radiusX} ${bottom} ` +
50
+ `H ${x + radiusX} ` +
51
+ `A ${radiusX} ${radiusY} 0 0 1 ${x} ${bottom - radiusY} ` +
52
+ `V ${y + radiusY} ` +
53
+ `A ${radiusX} ${radiusY} 0 0 1 ${x + radiusX} ${y} Z`);
54
+ };
55
+ // Two semicircle arcs — the standard trick for a full ellipse/circle in a
56
+ // path grammar that has no dedicated "ellipse" command.
57
+ export const ellipseToPathData = (cx, cy, rx, ry) => {
58
+ if (rx <= 0 || ry <= 0) {
59
+ return "";
60
+ }
61
+ return (`M ${cx - rx} ${cy} ` +
62
+ `A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy} ` +
63
+ `A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`);
64
+ };
65
+ export const circleToPathData = (cx, cy, r) => ellipseToPathData(cx, cy, r, r);
66
+ export const lineToPathData = (x1, y1, x2, y2) => `M ${x1} ${y1} L ${x2} ${y2}`;
67
+ const pointsToPathData = (points, close) => {
68
+ if (points.length === 0) {
69
+ return "";
70
+ }
71
+ const [first, ...rest] = points;
72
+ const segments = rest.map((p) => `L ${p.x} ${p.y}`).join(" ");
73
+ const body = segments.length > 0
74
+ ? `M ${first.x} ${first.y} ${segments}`
75
+ : `M ${first.x} ${first.y}`;
76
+ return close ? `${body} Z` : body;
77
+ };
78
+ export const polygonToPathData = (points) => pointsToPathData(typeof points === "string" ? parsePoints(points) : points, true);
79
+ export const polylineToPathData = (points) => pointsToPathData(typeof points === "string" ? parsePoints(points) : points, false);
80
+ //# sourceMappingURL=geometry.js.map
@@ -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,15 @@
1
+ // Pure objectBoundingBox / userSpaceOnUse coordinate math for gradients — no
2
+ // gtkx imports, so it is unit-testable off Linux; the bridge
3
+ // (gtkx/bridge/svg-node.ts) feeds the results to Gsk's gradient nodes.
4
+ export const resolveGradientPoint = (bounds, fractionX, fractionY, units) => units === "userSpaceOnUse"
5
+ ? { x: fractionX, y: fractionY }
6
+ : {
7
+ x: bounds.x + fractionX * bounds.width,
8
+ y: bounds.y + fractionY * bounds.height,
9
+ };
10
+ // SVG only has one radial radius; objectBoundingBox stretches it into an
11
+ // ellipse matching the shape's own aspect ratio (what browsers do too).
12
+ export const resolveGradientRadius = (bounds, r, units) => units === "userSpaceOnUse"
13
+ ? { hradius: r, vradius: r }
14
+ : { hradius: r * bounds.width, vradius: r * bounds.height };
15
+ //# 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,6DAA6D;AAC7D,uEAAuE;AAUvE,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 is unit-testable off Linux; the bridge\n// (gtkx/bridge/svg-node.ts) feeds the results to Gsk's gradient nodes.\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;