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
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useLayoutEffect, useRef, useState } from "react";
2
+ import { useLayoutEffect, useReducer, useRef, useState, } from "react";
3
+ import { splitStyle, StyleSheet } from "../style/index";
4
+ import { defaultCssRegistry } from "../style/registry.gtkx";
3
5
  import { Gtk, GtkBox } from "../gtkx/bridge/index";
6
+ import { perfAddTime, perfCount, perfEnabled, perfNow } from "../perf";
4
7
  import { HostNodeContext } from "./host-node";
5
8
  import { useLayoutChild, useRnContainer, } from "./use-layout-child";
6
9
  const pressEvent = (x, y) => ({
@@ -11,14 +14,34 @@ const pressEvent = (x, y) => ({
11
14
  export const Pressable = ({ style, children, onPress, onPressIn, onPressOut, onLongPress, onHoverIn, onHoverOut, disabled = false, delayLongPress = 500, onLayout, testID, }) => {
12
15
  const widgetRef = useRef(null);
13
16
  const [pressed, setPressed] = useState(false);
14
- const [hovered, setHovered] = useState(false);
17
+ // Hover is not React state: a boundary crossing should cost what it costs
18
+ // a native GtkListBox row to swap a CSS class, not a setState + render +
19
+ // Yoga + allocate cycle (measured: roughly 500x the latency of a native
20
+ // row at rest, see docs/research/scroll-performance.md, task 007).
21
+ // `hoveredRef` is the single source of truth, read directly at render
22
+ // time — safe here because this component already mutates
23
+ // `handlersRef.current` in the render body below, so render was never
24
+ // React-DOM-pure to begin with. `forceRender` is used only for the two
25
+ // cases that genuinely need a real render: `children` reads `hovered`,
26
+ // or hovering would itself change layout (not just paint).
27
+ const hoveredRef = useRef(false);
28
+ const [, forceRender] = useReducer((n) => n + 1, 0);
15
29
  const longPressTimer = useRef(null);
16
30
  const longPressFired = useRef(false);
17
- const state = { pressed, hovered };
18
- // RC1-WORKAROUND(controllers-as-children): see docs/gtkx-rc1-vs-main.md
19
- // rc.1 has no controller-as-JSX-children support (main-only feature):
20
- // gestures are attached imperatively; handlers read the latest props via a
21
- // ref so the controllers are wired exactly once.
31
+ // TEMPORARY perf instrumentation (task 007, see
32
+ // docs/research/scroll-performance.md): timestamp the native enter/leave
33
+ // signal so hover's real signal-to-applied latency can be reported —
34
+ // synchronously for the fast path below, after the resulting commit for
35
+ // the slow one. perf.ts hooks are no-ops unless GTKX_PERF=1.
36
+ const hoverSignalAt = useRef(null);
37
+ const state = {
38
+ pressed,
39
+ hovered: hoveredRef.current,
40
+ };
41
+ // Gestures are attached imperatively even though rc.2 has a declarative
42
+ // `controllers` slot: the controllers are wired exactly once per widget and
43
+ // the handlers read the latest props through a ref, so a re-render never
44
+ // detaches and re-adds a gesture mid-press.
22
45
  const handlersRef = useRef({
23
46
  handlePressed: (n, x, y) => {
24
47
  void n;
@@ -36,11 +59,54 @@ export const Pressable = ({ style, children, onPress, onPressIn, onPressOut, onL
36
59
  });
37
60
  const resolvedStyle = typeof style === "function" ? style(state) : style;
38
61
  const resolvedChildren = typeof children === "function" ? children(state) : children;
39
- const { host, node, cssClass } = useLayoutChild(widgetRef, {
62
+ const { host, node, cssClass, flat } = useLayoutChild(widgetRef, {
40
63
  style: resolvedStyle,
41
64
  onLayout,
42
65
  });
43
66
  useRnContainer(widgetRef, node);
67
+ // Hover fast path: when `children` does not read state, hovering can
68
+ // never change what is rendered — only `style` might. Precompute the CSS
69
+ // class for the OTHER hover value so the native enter/leave handlers
70
+ // below can swap classes directly on the widget, with no React involved
71
+ // at all. If hovering would also change LAYOUT (rare — every hover style
72
+ // in this codebase's own examples is background/border) that swap is
73
+ // unsafe, since a real reflow is required; fall back to the ordinary
74
+ // render in that case, exactly as before this change.
75
+ let hoverOnClass = null;
76
+ let hoverOffClass = null;
77
+ let canSwapHoverClass = typeof children !== "function";
78
+ if (canSwapHoverClass && typeof style === "function") {
79
+ const otherState = {
80
+ pressed,
81
+ hovered: !hoveredRef.current,
82
+ };
83
+ const otherFlat = StyleSheet.flatten(style(otherState));
84
+ const otherSplit = splitStyle(otherFlat);
85
+ const currentLayout = splitStyle(flat).layout;
86
+ if (JSON.stringify(currentLayout) !== JSON.stringify(otherSplit.layout)) {
87
+ // Hovering resizes or repositions this widget — a CSS class alone
88
+ // cannot do that, it needs Yoga. Fall back to a real render.
89
+ canSwapHoverClass = false;
90
+ }
91
+ else {
92
+ const otherClass = defaultCssRegistry.getClassName(otherSplit.visual);
93
+ hoverOnClass = hoveredRef.current ? cssClass : otherClass;
94
+ hoverOffClass = hoveredRef.current ? otherClass : cssClass;
95
+ }
96
+ }
97
+ // TEMPORARY perf instrumentation (task 007): runs after every commit, so
98
+ // it always lands right after whatever render the SLOW hover path
99
+ // triggered. A no-op when no hover signal is pending (the common case:
100
+ // renders triggered by anything else, or the fast path above, which
101
+ // measures itself synchronously) or when GTKX_PERF is unset.
102
+ useLayoutEffect(() => {
103
+ if (!perfEnabled || hoverSignalAt.current === null) {
104
+ return;
105
+ }
106
+ perfAddTime("pressable.hoverApply", perfNow() - hoverSignalAt.current);
107
+ perfCount("pressable.hoverFullCycle");
108
+ hoverSignalAt.current = null;
109
+ });
44
110
  const clearLongPress = () => {
45
111
  if (longPressTimer.current !== null) {
46
112
  clearTimeout(longPressTimer.current);
@@ -77,18 +143,45 @@ export const Pressable = ({ style, children, onPress, onPressIn, onPressOut, onL
77
143
  clearLongPress();
78
144
  setPressed(false);
79
145
  };
146
+ // Applies a hover transition: fires the RN callback, then either swaps
147
+ // the precomputed CSS class directly on the widget (fast path) or forces
148
+ // the ordinary render (slow path) — see the `canSwapHoverClass` block
149
+ // above for which one applies this render.
150
+ const applyHover = (next, fire) => {
151
+ const t0 = perfEnabled ? perfNow() : 0;
152
+ hoveredRef.current = next;
153
+ fire?.();
154
+ if (!canSwapHoverClass) {
155
+ if (perfEnabled) {
156
+ hoverSignalAt.current = t0;
157
+ perfCount("pressable.hoverEvent");
158
+ }
159
+ forceRender();
160
+ return;
161
+ }
162
+ const widget = widgetRef.current;
163
+ const applyClass = next ? hoverOnClass : hoverOffClass;
164
+ const removeClass = next ? hoverOffClass : hoverOnClass;
165
+ if (widget && hoverOnClass !== hoverOffClass) {
166
+ if (removeClass) {
167
+ widget.removeCssClass(removeClass);
168
+ }
169
+ if (applyClass) {
170
+ widget.addCssClass(applyClass);
171
+ }
172
+ }
173
+ if (perfEnabled) {
174
+ perfAddTime("pressable.hoverApply", perfNow() - t0);
175
+ perfCount("pressable.hoverEvent");
176
+ perfCount("pressable.hoverFast");
177
+ }
178
+ };
80
179
  handlersRef.current = {
81
180
  handlePressed,
82
181
  handleReleased,
83
182
  handleCancel,
84
- handleEnter: () => {
85
- setHovered(true);
86
- onHoverIn?.();
87
- },
88
- handleLeave: () => {
89
- setHovered(false);
90
- onHoverOut?.();
91
- },
183
+ handleEnter: () => applyHover(true, onHoverIn),
184
+ handleLeave: () => applyHover(false, onHoverOut),
92
185
  };
93
186
  useLayoutEffect(() => {
94
187
  const widget = widgetRef.current;
@@ -1 +1 @@
1
- {"version":3,"file":"pressable.js","sourceRoot":"","sources":["../../src/components/pressable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type ReactNode } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport { Gtk, GtkBox } from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type PressableStateCallbackType = {\n pressed: boolean\n hovered: boolean\n}\n\nexport type PressEvent = { nativeEvent: { x: number; y: number } }\n\nexport type PressableProps = {\n style?: StyleProp | ((state: PressableStateCallbackType) => StyleProp)\n children?: ReactNode | ((state: PressableStateCallbackType) => ReactNode)\n onPress?: (event: PressEvent) => void\n onPressIn?: (event: PressEvent) => void\n onPressOut?: (event: PressEvent) => void\n onLongPress?: (event: PressEvent) => void\n onHoverIn?: () => void\n onHoverOut?: () => void\n disabled?: boolean\n delayLongPress?: number\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst pressEvent = (x: number, y: number): PressEvent => ({\n nativeEvent: { x, y },\n})\n\n// A View with a click gesture and hover tracking. State-dependent style and\n// children follow the RN Pressable function-prop contract.\nexport const Pressable = ({\n style,\n children,\n onPress,\n onPressIn,\n onPressOut,\n onLongPress,\n onHoverIn,\n onHoverOut,\n disabled = false,\n delayLongPress = 500,\n onLayout,\n testID,\n}: PressableProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const [pressed, setPressed] = useState(false)\n const [hovered, setHovered] = useState(false)\n const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const longPressFired = useRef(false)\n\n const state: PressableStateCallbackType = { pressed, hovered }\n\n // RC1-WORKAROUND(controllers-as-children): see docs/gtkx-rc1-vs-main.md\n // rc.1 has no controller-as-JSX-children support (main-only feature):\n // gestures are attached imperatively; handlers read the latest props via a\n // ref so the controllers are wired exactly once.\n const handlersRef = useRef({\n handlePressed: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleReleased: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleCancel: (): void => {},\n handleEnter: (): void => {},\n handleLeave: (): void => {},\n })\n const resolvedStyle = typeof style === \"function\" ? style(state) : style\n const resolvedChildren =\n typeof children === \"function\" ? children(state) : children\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: resolvedStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const clearLongPress = (): void => {\n if (longPressTimer.current !== null) {\n clearTimeout(longPressTimer.current)\n longPressTimer.current = null\n }\n }\n\n const handlePressed = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n longPressFired.current = false\n setPressed(true)\n onPressIn?.(pressEvent(x, y))\n if (onLongPress) {\n clearLongPress()\n longPressTimer.current = setTimeout(() => {\n longPressFired.current = true\n onLongPress(pressEvent(x, y))\n }, delayLongPress)\n }\n }\n\n const handleReleased = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n clearLongPress()\n setPressed(false)\n onPressOut?.(pressEvent(x, y))\n if (!longPressFired.current) {\n onPress?.(pressEvent(x, y))\n }\n }\n\n const handleCancel = (): void => {\n clearLongPress()\n setPressed(false)\n }\n\n handlersRef.current = {\n handlePressed,\n handleReleased,\n handleCancel,\n handleEnter: () => {\n setHovered(true)\n onHoverIn?.()\n },\n handleLeave: () => {\n setHovered(false)\n onHoverOut?.()\n },\n }\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const click = new Gtk.GestureClick()\n click.on(\"pressed\", (n: number, x: number, y: number) =>\n handlersRef.current.handlePressed(n, x, y),\n )\n click.on(\"released\", (n: number, x: number, y: number) =>\n handlersRef.current.handleReleased(n, x, y),\n )\n click.on(\"cancel\", () => handlersRef.current.handleCancel())\n widget.addController(click)\n\n const motion = new Gtk.EventControllerMotion()\n motion.on(\"enter\", () => handlersRef.current.handleEnter())\n motion.on(\"leave\", () => handlersRef.current.handleLeave())\n widget.addController(motion)\n\n return () => {\n widget.removeController(click)\n widget.removeController(motion)\n }\n }, [])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {resolvedChildren}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport type TouchableOpacityProps = Omit<PressableProps, \"style\"> & {\n style?: StyleProp\n activeOpacity?: number\n}\n\n// Classic RN touchable: dims itself while pressed via widget opacity.\nexport const TouchableOpacity = ({\n style,\n activeOpacity = 0.5,\n ...rest\n}: TouchableOpacityProps) => (\n <Pressable\n {...rest}\n style={({ pressed }) => [style, pressed && { opacity: activeOpacity }]}\n />\n)\n"]}
1
+ {"version":3,"file":"pressable.js","sourceRoot":"","sources":["../../src/components/pressable.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useLayoutEffect,\n useReducer,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport { defaultCssRegistry } from \"../style/registry.gtkx\"\nimport type { StyleProp } from \"../contracts\"\nimport { Gtk, GtkBox } from \"../gtkx/bridge/index\"\nimport { perfAddTime, perfCount, perfEnabled, perfNow } from \"../perf\"\nimport { HostNodeContext } from \"./host-node\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type PressableStateCallbackType = {\n pressed: boolean\n hovered: boolean\n}\n\nexport type PressEvent = { nativeEvent: { x: number; y: number } }\n\nexport type PressableProps = {\n style?: StyleProp | ((state: PressableStateCallbackType) => StyleProp)\n children?: ReactNode | ((state: PressableStateCallbackType) => ReactNode)\n onPress?: (event: PressEvent) => void\n onPressIn?: (event: PressEvent) => void\n onPressOut?: (event: PressEvent) => void\n onLongPress?: (event: PressEvent) => void\n onHoverIn?: () => void\n onHoverOut?: () => void\n disabled?: boolean\n delayLongPress?: number\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst pressEvent = (x: number, y: number): PressEvent => ({\n nativeEvent: { x, y },\n})\n\n// A View with a click gesture and hover tracking. State-dependent style and\n// children follow the RN Pressable function-prop contract.\nexport const Pressable = ({\n style,\n children,\n onPress,\n onPressIn,\n onPressOut,\n onLongPress,\n onHoverIn,\n onHoverOut,\n disabled = false,\n delayLongPress = 500,\n onLayout,\n testID,\n}: PressableProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const [pressed, setPressed] = useState(false)\n // Hover is not React state: a boundary crossing should cost what it costs\n // a native GtkListBox row to swap a CSS class, not a setState + render +\n // Yoga + allocate cycle (measured: roughly 500x the latency of a native\n // row at rest, see docs/research/scroll-performance.md, task 007).\n // `hoveredRef` is the single source of truth, read directly at render\n // time — safe here because this component already mutates\n // `handlersRef.current` in the render body below, so render was never\n // React-DOM-pure to begin with. `forceRender` is used only for the two\n // cases that genuinely need a real render: `children` reads `hovered`,\n // or hovering would itself change layout (not just paint).\n const hoveredRef = useRef(false)\n const [, forceRender] = useReducer((n: number) => n + 1, 0)\n const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const longPressFired = useRef(false)\n // TEMPORARY perf instrumentation (task 007, see\n // docs/research/scroll-performance.md): timestamp the native enter/leave\n // signal so hover's real signal-to-applied latency can be reported —\n // synchronously for the fast path below, after the resulting commit for\n // the slow one. perf.ts hooks are no-ops unless GTKX_PERF=1.\n const hoverSignalAt = useRef<number | null>(null)\n\n const state: PressableStateCallbackType = {\n pressed,\n hovered: hoveredRef.current,\n }\n\n // Gestures are attached imperatively even though rc.2 has a declarative\n // `controllers` slot: the controllers are wired exactly once per widget and\n // the handlers read the latest props through a ref, so a re-render never\n // detaches and re-adds a gesture mid-press.\n const handlersRef = useRef({\n handlePressed: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleReleased: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleCancel: (): void => {},\n handleEnter: (): void => {},\n handleLeave: (): void => {},\n })\n const resolvedStyle = typeof style === \"function\" ? style(state) : style\n const resolvedChildren =\n typeof children === \"function\" ? children(state) : children\n\n const { host, node, cssClass, flat } = useLayoutChild(widgetRef, {\n style: resolvedStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n // Hover fast path: when `children` does not read state, hovering can\n // never change what is rendered — only `style` might. Precompute the CSS\n // class for the OTHER hover value so the native enter/leave handlers\n // below can swap classes directly on the widget, with no React involved\n // at all. If hovering would also change LAYOUT (rare — every hover style\n // in this codebase's own examples is background/border) that swap is\n // unsafe, since a real reflow is required; fall back to the ordinary\n // render in that case, exactly as before this change.\n let hoverOnClass: string | null = null\n let hoverOffClass: string | null = null\n let canSwapHoverClass = typeof children !== \"function\"\n if (canSwapHoverClass && typeof style === \"function\") {\n const otherState: PressableStateCallbackType = {\n pressed,\n hovered: !hoveredRef.current,\n }\n const otherFlat = StyleSheet.flatten(style(otherState))\n const otherSplit = splitStyle(otherFlat)\n const currentLayout = splitStyle(flat).layout\n if (JSON.stringify(currentLayout) !== JSON.stringify(otherSplit.layout)) {\n // Hovering resizes or repositions this widget — a CSS class alone\n // cannot do that, it needs Yoga. Fall back to a real render.\n canSwapHoverClass = false\n } else {\n const otherClass = defaultCssRegistry.getClassName(otherSplit.visual)\n hoverOnClass = hoveredRef.current ? cssClass : otherClass\n hoverOffClass = hoveredRef.current ? otherClass : cssClass\n }\n }\n\n // TEMPORARY perf instrumentation (task 007): runs after every commit, so\n // it always lands right after whatever render the SLOW hover path\n // triggered. A no-op when no hover signal is pending (the common case:\n // renders triggered by anything else, or the fast path above, which\n // measures itself synchronously) or when GTKX_PERF is unset.\n useLayoutEffect(() => {\n if (!perfEnabled || hoverSignalAt.current === null) {\n return\n }\n perfAddTime(\"pressable.hoverApply\", perfNow() - hoverSignalAt.current)\n perfCount(\"pressable.hoverFullCycle\")\n hoverSignalAt.current = null\n })\n\n const clearLongPress = (): void => {\n if (longPressTimer.current !== null) {\n clearTimeout(longPressTimer.current)\n longPressTimer.current = null\n }\n }\n\n const handlePressed = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n longPressFired.current = false\n setPressed(true)\n onPressIn?.(pressEvent(x, y))\n if (onLongPress) {\n clearLongPress()\n longPressTimer.current = setTimeout(() => {\n longPressFired.current = true\n onLongPress(pressEvent(x, y))\n }, delayLongPress)\n }\n }\n\n const handleReleased = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n clearLongPress()\n setPressed(false)\n onPressOut?.(pressEvent(x, y))\n if (!longPressFired.current) {\n onPress?.(pressEvent(x, y))\n }\n }\n\n const handleCancel = (): void => {\n clearLongPress()\n setPressed(false)\n }\n\n // Applies a hover transition: fires the RN callback, then either swaps\n // the precomputed CSS class directly on the widget (fast path) or forces\n // the ordinary render (slow path) — see the `canSwapHoverClass` block\n // above for which one applies this render.\n const applyHover = (next: boolean, fire: (() => void) | undefined): void => {\n const t0 = perfEnabled ? perfNow() : 0\n hoveredRef.current = next\n fire?.()\n if (!canSwapHoverClass) {\n if (perfEnabled) {\n hoverSignalAt.current = t0\n perfCount(\"pressable.hoverEvent\")\n }\n forceRender()\n return\n }\n const widget = widgetRef.current\n const applyClass = next ? hoverOnClass : hoverOffClass\n const removeClass = next ? hoverOffClass : hoverOnClass\n if (widget && hoverOnClass !== hoverOffClass) {\n if (removeClass) {\n widget.removeCssClass(removeClass)\n }\n if (applyClass) {\n widget.addCssClass(applyClass)\n }\n }\n if (perfEnabled) {\n perfAddTime(\"pressable.hoverApply\", perfNow() - t0)\n perfCount(\"pressable.hoverEvent\")\n perfCount(\"pressable.hoverFast\")\n }\n }\n\n handlersRef.current = {\n handlePressed,\n handleReleased,\n handleCancel,\n handleEnter: () => applyHover(true, onHoverIn),\n handleLeave: () => applyHover(false, onHoverOut),\n }\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const click = new Gtk.GestureClick()\n click.on(\"pressed\", (n: number, x: number, y: number) =>\n handlersRef.current.handlePressed(n, x, y),\n )\n click.on(\"released\", (n: number, x: number, y: number) =>\n handlersRef.current.handleReleased(n, x, y),\n )\n click.on(\"cancel\", () => handlersRef.current.handleCancel())\n widget.addController(click)\n\n const motion = new Gtk.EventControllerMotion()\n motion.on(\"enter\", () => handlersRef.current.handleEnter())\n motion.on(\"leave\", () => handlersRef.current.handleLeave())\n widget.addController(motion)\n\n return () => {\n widget.removeController(click)\n widget.removeController(motion)\n }\n }, [])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {resolvedChildren}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport type TouchableOpacityProps = Omit<PressableProps, \"style\"> & {\n style?: StyleProp\n activeOpacity?: number\n}\n\n// Classic RN touchable: dims itself while pressed via widget opacity.\nexport const TouchableOpacity = ({\n style,\n activeOpacity = 0.5,\n ...rest\n}: TouchableOpacityProps) => (\n <Pressable\n {...rest}\n style={({ pressed }) => [style, pressed && { opacity: activeOpacity }]}\n />\n)\n"]}
@@ -1,3 +1,4 @@
1
+ import type { Transform2D, TransformPart } from "../contracts";
1
2
  export type StoredRect = {
2
3
  x: number;
3
4
  y: number;
@@ -7,11 +8,20 @@ export type StoredRect = {
7
8
  export type StoredOffset = {
8
9
  dx: number;
9
10
  dy: number;
11
+ matrix: Transform2D | null;
10
12
  };
11
13
  export declare const setStoredRect: (widget: object, rect: StoredRect) => void;
12
14
  export declare const getStoredRect: (widget: object) => StoredRect | undefined;
13
- export declare const setStoredOffset: (widget: object, dx: number, dy: number) => void;
15
+ export declare const setStoredOffset: (widget: object, dx: number, dy: number, matrix?: Transform2D | null) => void;
14
16
  export declare const getStoredOffset: (widget: object) => StoredOffset;
17
+ /**
18
+ * Stores a child's whole RN `transform` array, split the way the allocate
19
+ * hook wants it: a pure translation keeps the positional path (offset the
20
+ * rect, no GskTransform at all — the pre-rotate/scale behaviour), anything
21
+ * that rotates or scales carries the composed matrix instead, translations
22
+ * folded in so the array's order is preserved exactly.
23
+ */
24
+ export declare const setStoredTransform: (widget: object, parts: readonly TransformPart[] | undefined) => void;
15
25
  export declare const beginAllocatePass: () => void;
16
26
  export declare const endAllocatePass: () => void;
17
27
  export declare const deferDuringAllocate: (widget: object, job: () => void) => boolean;
@@ -1,14 +1,38 @@
1
- const ZERO_OFFSET = { dx: 0, dy: 0 };
1
+ // Committed engine rects and animated offsets, keyed by the CHILD widget.
2
+ // Written by commit callbacks (use-layout-child) and by Animated bindings;
3
+ // read by the parent container's RnGtkxLayout allocate() hook. Pure data —
4
+ // no FFI here, so both writers and the bridge-side reader stay testable.
5
+ import { composeTransform, isTranslationOnly } from "../style/transform";
6
+ const ZERO_OFFSET = { dx: 0, dy: 0, matrix: null };
2
7
  const rects = new WeakMap();
3
8
  const offsets = new WeakMap();
4
9
  export const setStoredRect = (widget, rect) => {
5
10
  rects.set(widget, rect);
6
11
  };
7
12
  export const getStoredRect = (widget) => rects.get(widget);
8
- export const setStoredOffset = (widget, dx, dy) => {
9
- offsets.set(widget, { dx, dy });
13
+ export const setStoredOffset = (widget, dx, dy, matrix = null) => {
14
+ offsets.set(widget, { dx, dy, matrix });
10
15
  };
11
16
  export const getStoredOffset = (widget) => offsets.get(widget) ?? ZERO_OFFSET;
17
+ /**
18
+ * Stores a child's whole RN `transform` array, split the way the allocate
19
+ * hook wants it: a pure translation keeps the positional path (offset the
20
+ * rect, no GskTransform at all — the pre-rotate/scale behaviour), anything
21
+ * that rotates or scales carries the composed matrix instead, translations
22
+ * folded in so the array's order is preserved exactly.
23
+ */
24
+ export const setStoredTransform = (widget, parts) => {
25
+ if (!parts || parts.length === 0) {
26
+ setStoredOffset(widget, 0, 0, null);
27
+ return;
28
+ }
29
+ const matrix = composeTransform(parts);
30
+ if (isTranslationOnly(matrix)) {
31
+ setStoredOffset(widget, matrix.dx, matrix.dy, null);
32
+ return;
33
+ }
34
+ setStoredOffset(widget, 0, 0, matrix);
35
+ };
12
36
  // While a window-root allocate() pass runs the engine synchronously, commit
13
37
  // callbacks must not queue GTK resizes mid-pass — the pass itself is about to
14
38
  // place everything. Queue jobs are deferred (deduped per widget) and run once
@@ -1 +1 @@
1
- {"version":3,"file":"rect-store.js","sourceRoot":"","sources":["../../src/components/rect-store.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,GAAiB,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;AAElD,MAAM,KAAK,GAAG,IAAI,OAAO,EAAsB,CAAA;AAC/C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAwB,CAAA;AAEnD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAc,EAAE,IAAgB,EAAQ,EAAE;IACtE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACzB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAc,EAA0B,EAAE,CACtE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAEnB,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAc,EACd,EAAU,EACV,EAAU,EACJ,EAAE;IACR,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAgB,EAAE,CAC9D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,WAAW,CAAA;AAEpC,4EAA4E;AAC5E,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,uBAAuB;AACvB,IAAI,iBAAiB,GAAG,CAAC,CAAA;AACzB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAA;AAElD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAS,EAAE;IAC1C,iBAAiB,IAAI,CAAC,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,iBAAiB,IAAI,CAAC,CAAA;IACtB,IAAI,iBAAiB,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;QACvC,YAAY,CAAC,KAAK,EAAE,CAAA;QACpB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,GAAG,EAAE,CAAA;QACP,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAc,EACd,GAAe,EACN,EAAE;IACX,IAAI,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,OAAO,IAAI,CAAA;AACb,CAAC,CAAA","sourcesContent":["// Committed engine rects and animated offsets, keyed by the CHILD widget.\n// Written by commit callbacks (use-layout-child) and by Animated bindings;\n// read by the parent container's RnGtkxLayout allocate() hook. Pure data —\n// no FFI here, so both writers and the bridge-side reader stay testable.\nexport type StoredRect = {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport type StoredOffset = { dx: number; dy: number }\n\nconst ZERO_OFFSET: StoredOffset = { dx: 0, dy: 0 }\n\nconst rects = new WeakMap<object, StoredRect>()\nconst offsets = new WeakMap<object, StoredOffset>()\n\nexport const setStoredRect = (widget: object, rect: StoredRect): void => {\n rects.set(widget, rect)\n}\n\nexport const getStoredRect = (widget: object): StoredRect | undefined =>\n rects.get(widget)\n\nexport const setStoredOffset = (\n widget: object,\n dx: number,\n dy: number,\n): void => {\n offsets.set(widget, { dx, dy })\n}\n\nexport const getStoredOffset = (widget: object): StoredOffset =>\n offsets.get(widget) ?? ZERO_OFFSET\n\n// While a window-root allocate() pass runs the engine synchronously, commit\n// callbacks must not queue GTK resizes mid-pass — the pass itself is about to\n// place everything. Queue jobs are deferred (deduped per widget) and run once\n// the pass ends; commits whose rect did not change never re-defer, so the\n// follow-up converges.\nlet allocatePassDepth = 0\nconst deferredJobs = new Map<object, () => void>()\n\nexport const beginAllocatePass = (): void => {\n allocatePassDepth += 1\n}\n\nexport const endAllocatePass = (): void => {\n allocatePassDepth -= 1\n if (allocatePassDepth === 0 && deferredJobs.size > 0) {\n const jobs = [...deferredJobs.values()]\n deferredJobs.clear()\n for (const job of jobs) {\n job()\n }\n }\n}\n\n// Returns true when the job was deferred (an allocate pass is running).\nexport const deferDuringAllocate = (\n widget: object,\n job: () => void,\n): boolean => {\n if (allocatePassDepth === 0) {\n return false\n }\n deferredJobs.set(widget, job)\n return true\n}\n"]}
1
+ {"version":3,"file":"rect-store.js","sourceRoot":"","sources":["../../src/components/rect-store.ts"],"names":[],"mappings":"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","sourcesContent":["// Committed engine rects and animated offsets, keyed by the CHILD widget.\n// Written by commit callbacks (use-layout-child) and by Animated bindings;\n// read by the parent container's RnGtkxLayout allocate() hook. Pure data —\n// no FFI here, so both writers and the bridge-side reader stay testable.\nimport { composeTransform, isTranslationOnly } from \"../style/transform\"\nimport type { Transform2D, TransformPart } from \"../contracts\"\n\nexport type StoredRect = {\n x: number\n y: number\n width: number\n height: number\n}\n\n// The visual transform of one child, split by cost. A pure translation stays\n// `matrix: null` and is applied by simply offsetting the rect — the path that\n// existed before rotate/scale and that costs no GskTransform. Anything that\n// rotates or scales carries the whole composed matrix (translations\n// included, so the RN ordering is preserved) and pays one gsk call per\n// allocation. Both live in ONE record on purpose: the allocate hook already\n// reads it per child, so a transform costs no extra WeakMap lookup.\nexport type StoredOffset = {\n dx: number\n dy: number\n matrix: Transform2D | null\n}\n\nconst ZERO_OFFSET: StoredOffset = { dx: 0, dy: 0, matrix: null }\n\nconst rects = new WeakMap<object, StoredRect>()\nconst offsets = new WeakMap<object, StoredOffset>()\n\nexport const setStoredRect = (widget: object, rect: StoredRect): void => {\n rects.set(widget, rect)\n}\n\nexport const getStoredRect = (widget: object): StoredRect | undefined =>\n rects.get(widget)\n\nexport const setStoredOffset = (\n widget: object,\n dx: number,\n dy: number,\n matrix: Transform2D | null = null,\n): void => {\n offsets.set(widget, { dx, dy, matrix })\n}\n\nexport const getStoredOffset = (widget: object): StoredOffset =>\n offsets.get(widget) ?? ZERO_OFFSET\n\n/**\n * Stores a child's whole RN `transform` array, split the way the allocate\n * hook wants it: a pure translation keeps the positional path (offset the\n * rect, no GskTransform at all — the pre-rotate/scale behaviour), anything\n * that rotates or scales carries the composed matrix instead, translations\n * folded in so the array's order is preserved exactly.\n */\nexport const setStoredTransform = (\n widget: object,\n parts: readonly TransformPart[] | undefined,\n): void => {\n if (!parts || parts.length === 0) {\n setStoredOffset(widget, 0, 0, null)\n return\n }\n const matrix = composeTransform(parts)\n if (isTranslationOnly(matrix)) {\n setStoredOffset(widget, matrix.dx, matrix.dy, null)\n return\n }\n setStoredOffset(widget, 0, 0, matrix)\n}\n\n// While a window-root allocate() pass runs the engine synchronously, commit\n// callbacks must not queue GTK resizes mid-pass — the pass itself is about to\n// place everything. Queue jobs are deferred (deduped per widget) and run once\n// the pass ends; commits whose rect did not change never re-defer, so the\n// follow-up converges.\nlet allocatePassDepth = 0\nconst deferredJobs = new Map<object, () => void>()\n\nexport const beginAllocatePass = (): void => {\n allocatePassDepth += 1\n}\n\nexport const endAllocatePass = (): void => {\n allocatePassDepth -= 1\n if (allocatePassDepth === 0 && deferredJobs.size > 0) {\n const jobs = [...deferredJobs.values()]\n deferredJobs.clear()\n for (const job of jobs) {\n job()\n }\n }\n}\n\n// Returns true when the job was deferred (an allocate pass is running).\nexport const deferDuringAllocate = (\n widget: object,\n job: () => void,\n): boolean => {\n if (allocatePassDepth === 0) {\n return false\n }\n deferredJobs.set(widget, job)\n return true\n}\n"]}
@@ -0,0 +1,19 @@
1
+ export type AnimatedNodeLike = {
2
+ addListener(callback: (state: {
3
+ value: number;
4
+ }) => void): string;
5
+ removeListener(id: string): void;
6
+ __getValue(): number;
7
+ };
8
+ export type AnimatableNumber = number | AnimatedNodeLike;
9
+ export declare const isAnimatedNode: (value: unknown) => value is AnimatedNodeLike;
10
+ /**
11
+ * Resolves a set of possibly-animated numeric fields and calls `build` with
12
+ * the fully-resolved snapshot: once synchronously (mount / whenever a
13
+ * field's identity changes — a new plain number, a new Animated node, or a
14
+ * switch between the two) and again on every tick of any field that is
15
+ * currently animated. `build` itself is read from a ref on every call, so a
16
+ * re-render between ticks always reaches the latest closure without forcing
17
+ * a re-subscribe.
18
+ */
19
+ export declare const useAnimatedShapeBuild: (fields: Record<string, AnimatableNumber>, build: (resolved: Record<string, number>) => void, extraDeps?: readonly unknown[]) => void;
@@ -0,0 +1,64 @@
1
+ // Shared "own invalidation channel" for SVG: geometry/paint numeric props
2
+ // accept a plain number OR an Animated node (Animated.Value /
3
+ // AnimatedInterpolation). Same duck-typed detection as components/
4
+ // animated.tsx's isAnimatedNode — kept as a separate small copy rather than
5
+ // a shared export because the two call sites want different subscription
6
+ // targets (rect-store + queueAllocate there, an SVG descriptor + queueDraw
7
+ // here) and duplicating a five-line predicate is cheaper than coupling them.
8
+ //
9
+ // Every tick mutates the resolved-value snapshot and calls `build` directly
10
+ // — no React re-render, no setState, matching the epic's answer to "how does
11
+ // Animated redraw a path": queueDraw, not queueAllocate.
12
+ import { useLayoutEffect, useRef } from "react";
13
+ export const isAnimatedNode = (value) => typeof value === "object" &&
14
+ value !== null &&
15
+ typeof value.addListener === "function" &&
16
+ typeof value.__getValue === "function";
17
+ const resolve = (value) => isAnimatedNode(value) ? value.__getValue() : value;
18
+ /**
19
+ * Resolves a set of possibly-animated numeric fields and calls `build` with
20
+ * the fully-resolved snapshot: once synchronously (mount / whenever a
21
+ * field's identity changes — a new plain number, a new Animated node, or a
22
+ * switch between the two) and again on every tick of any field that is
23
+ * currently animated. `build` itself is read from a ref on every call, so a
24
+ * re-render between ticks always reaches the latest closure without forcing
25
+ * a re-subscribe.
26
+ */
27
+ // `extraDeps` covers everything `build` closes over that is NOT one of the
28
+ // animatable `fields` — d/points strings, transform strings, fill/stroke and
29
+ // the rest of the static paint props. Without them here, a change to (say)
30
+ // `fill` alone would not re-run `build` until some unrelated field changed.
31
+ export const useAnimatedShapeBuild = (fields, build, extraDeps = []) => {
32
+ const buildRef = useRef(build);
33
+ buildRef.current = build;
34
+ const keys = Object.keys(fields);
35
+ const values = [...keys.map((key) => fields[key]), ...extraDeps];
36
+ useLayoutEffect(() => {
37
+ const resolved = {};
38
+ for (const key of keys) {
39
+ resolved[key] = resolve(fields[key]);
40
+ }
41
+ const run = () => buildRef.current(resolved);
42
+ run();
43
+ const subscriptions = [];
44
+ for (const key of keys) {
45
+ const source = fields[key];
46
+ if (isAnimatedNode(source)) {
47
+ const id = source.addListener(({ value }) => {
48
+ resolved[key] = value;
49
+ run();
50
+ });
51
+ subscriptions.push({ node: source, id });
52
+ }
53
+ }
54
+ return () => {
55
+ for (const { node, id } of subscriptions) {
56
+ node.removeListener(id);
57
+ }
58
+ };
59
+ // `values` is the real dependency list (flattened field values/node
60
+ // identities); `keys`/`fields`/`build` are read through refs/closures.
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, values);
63
+ };
64
+ //# sourceMappingURL=animated-support.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-support.js","sourceRoot":"","sources":["../../../src/components/svg/animated-support.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8DAA8D;AAC9D,mEAAmE;AACnE,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,6EAA6E;AAC7E,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,yDAAyD;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAU/C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAc,EAA6B,EAAE,CAC1E,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,OAAQ,KAA0B,CAAC,WAAW,KAAK,UAAU;IAC7D,OAAQ,KAA0B,CAAC,UAAU,KAAK,UAAU,CAAA;AAE9D,MAAM,OAAO,GAAG,CAAC,KAAuB,EAAU,EAAE,CAClD,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;AAEpD;;;;;;;;GAQG;AACH,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,MAAwC,EACxC,KAAiD,EACjD,YAAgC,EAAE,EAC5B,EAAE;IACR,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAA;IAExB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAChC,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAA;IAEhE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,QAAQ,GAA2B,EAAE,CAAA;QAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,QAAQ,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAE,CAAC,CAAA;QACvC,CAAC;QACD,MAAM,GAAG,GAAG,GAAS,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAClD,GAAG,EAAE,CAAA;QAEL,MAAM,aAAa,GAA6C,EAAE,CAAA;QAClE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAE,CAAA;YAC3B,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;oBAC1C,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;oBACrB,GAAG,EAAE,CAAA;gBACP,CAAC,CAAC,CAAA;gBACF,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;YAC1C,CAAC;QACH,CAAC;QACD,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,aAAa,EAAE,CAAC;gBACzC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;YACzB,CAAC;QACH,CAAC,CAAA;QACD,oEAAoE;QACpE,uEAAuE;QACvE,uDAAuD;IACzD,CAAC,EAAE,MAAM,CAAC,CAAA;AACZ,CAAC,CAAA","sourcesContent":["// Shared \"own invalidation channel\" for SVG: geometry/paint numeric props\n// accept a plain number OR an Animated node (Animated.Value /\n// AnimatedInterpolation). Same duck-typed detection as components/\n// animated.tsx's isAnimatedNode — kept as a separate small copy rather than\n// a shared export because the two call sites want different subscription\n// targets (rect-store + queueAllocate there, an SVG descriptor + queueDraw\n// here) and duplicating a five-line predicate is cheaper than coupling them.\n//\n// Every tick mutates the resolved-value snapshot and calls `build` directly\n// — no React re-render, no setState, matching the epic's answer to \"how does\n// Animated redraw a path\": queueDraw, not queueAllocate.\nimport { useLayoutEffect, useRef } from \"react\"\n\nexport type AnimatedNodeLike = {\n addListener(callback: (state: { value: number }) => void): string\n removeListener(id: string): void\n __getValue(): number\n}\n\nexport type AnimatableNumber = number | AnimatedNodeLike\n\nexport const isAnimatedNode = (value: unknown): value is AnimatedNodeLike =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AnimatedNodeLike).addListener === \"function\" &&\n typeof (value as AnimatedNodeLike).__getValue === \"function\"\n\nconst resolve = (value: AnimatableNumber): number =>\n isAnimatedNode(value) ? value.__getValue() : value\n\n/**\n * Resolves a set of possibly-animated numeric fields and calls `build` with\n * the fully-resolved snapshot: once synchronously (mount / whenever a\n * field's identity changes — a new plain number, a new Animated node, or a\n * switch between the two) and again on every tick of any field that is\n * currently animated. `build` itself is read from a ref on every call, so a\n * re-render between ticks always reaches the latest closure without forcing\n * a re-subscribe.\n */\n// `extraDeps` covers everything `build` closes over that is NOT one of the\n// animatable `fields` — d/points strings, transform strings, fill/stroke and\n// the rest of the static paint props. Without them here, a change to (say)\n// `fill` alone would not re-run `build` until some unrelated field changed.\nexport const useAnimatedShapeBuild = (\n fields: Record<string, AnimatableNumber>,\n build: (resolved: Record<string, number>) => void,\n extraDeps: readonly unknown[] = [],\n): void => {\n const buildRef = useRef(build)\n buildRef.current = build\n\n const keys = Object.keys(fields)\n const values = [...keys.map((key) => fields[key]), ...extraDeps]\n\n useLayoutEffect(() => {\n const resolved: Record<string, number> = {}\n for (const key of keys) {\n resolved[key] = resolve(fields[key]!)\n }\n const run = (): void => buildRef.current(resolved)\n run()\n\n const subscriptions: { node: AnimatedNodeLike; id: string }[] = []\n for (const key of keys) {\n const source = fields[key]!\n if (isAnimatedNode(source)) {\n const id = source.addListener(({ value }) => {\n resolved[key] = value\n run()\n })\n subscriptions.push({ node: source, id })\n }\n }\n return () => {\n for (const { node, id } of subscriptions) {\n node.removeListener(id)\n }\n }\n // `values` is the real dependency list (flattened field values/node\n // identities); `keys`/`fields`/`build` are read through refs/closures.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, values)\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { type RefObject } from "react";
2
+ import type { Gtk } from "../../gtkx/bridge/index";
3
+ export type SvgRootContextValue = {
4
+ rootRef: RefObject<Gtk.Widget | null>;
5
+ requestRedraw: () => void;
6
+ };
7
+ export declare const SvgRootContext: React.Context<SvgRootContextValue | null>;
8
+ export declare const useSvgRoot: (componentName: string) => SvgRootContextValue;
@@ -0,0 +1,15 @@
1
+ // Context handed down from <Svg> to every descendant: a ref to the root
2
+ // widget (so a leaf can call queueDraw without walking the GTK tree upward)
3
+ // plus a bound convenience wrapper. Mirrors HostNodeContext (host-node.ts)
4
+ // for the Yoga tree — same idea, separate tree, because SVG nodes carry no
5
+ // Yoga node at all (see svg.tsx).
6
+ import { createContext, useContext } from "react";
7
+ export const SvgRootContext = createContext(null);
8
+ export const useSvgRoot = (componentName) => {
9
+ const context = useContext(SvgRootContext);
10
+ if (!context) {
11
+ throw new Error(`<${componentName}> must be rendered inside <Svg>`);
12
+ }
13
+ return context;
14
+ };
15
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/components/svg/context.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,kCAAkC;AAClC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAA;AAQjE,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAA6B,IAAI,CAAC,CAAA;AAE7E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,aAAqB,EAAuB,EAAE;IACvE,MAAM,OAAO,GAAG,UAAU,CAAC,cAAc,CAAC,CAAA;IAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,IAAI,aAAa,iCAAiC,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA","sourcesContent":["// Context handed down from <Svg> to every descendant: a ref to the root\n// widget (so a leaf can call queueDraw without walking the GTK tree upward)\n// plus a bound convenience wrapper. Mirrors HostNodeContext (host-node.ts)\n// for the Yoga tree — same idea, separate tree, because SVG nodes carry no\n// Yoga node at all (see svg.tsx).\nimport { createContext, useContext, type RefObject } from \"react\"\nimport type { Gtk } from \"../../gtkx/bridge/index\"\n\nexport type SvgRootContextValue = {\n rootRef: RefObject<Gtk.Widget | null>\n requestRedraw: () => void\n}\n\nexport const SvgRootContext = createContext<SvgRootContextValue | null>(null)\n\nexport const useSvgRoot = (componentName: string): SvgRootContextValue => {\n const context = useContext(SvgRootContext)\n if (!context) {\n throw new Error(`<${componentName}> must be rendered inside <Svg>`)\n }\n return context\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from "react";
2
+ import { type SvgGradientUnits } from "../../gtkx/bridge/index";
3
+ export type DefsProps = {
4
+ children?: ReactNode;
5
+ };
6
+ export declare const Defs: ({ children }: DefsProps) => React.JSX.Element;
7
+ export type LinearGradientProps = {
8
+ id: string;
9
+ x1?: number;
10
+ y1?: number;
11
+ x2?: number;
12
+ y2?: number;
13
+ gradientUnits?: SvgGradientUnits;
14
+ children?: ReactNode;
15
+ };
16
+ export declare const LinearGradient: ({ id, x1, y1, x2, y2, gradientUnits, children, }: LinearGradientProps) => React.JSX.Element;
17
+ export type RadialGradientProps = {
18
+ id: string;
19
+ cx?: number;
20
+ cy?: number;
21
+ r?: number;
22
+ gradientUnits?: SvgGradientUnits;
23
+ children?: ReactNode;
24
+ };
25
+ export declare const RadialGradient: ({ id, cx, cy, r, gradientUnits, children, }: RadialGradientProps) => React.JSX.Element;
26
+ export type StopProps = {
27
+ offset: number | string;
28
+ stopColor?: string;
29
+ stopOpacity?: number;
30
+ };
31
+ export declare const Stop: ({ offset, stopColor, stopOpacity, }: StopProps) => React.JSX.Element;
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // <Defs>/<LinearGradient>/<RadialGradient>/<Stop> — paint resources, not
3
+ // visible nodes. They ride the same RnGtkxSvgNode tree as everything else
4
+ // (so the bridge's snapshot() can walk <Defs> separately, before the main
5
+ // paint pass, to build an id → gradient map — see svg-node.ts), but are
6
+ // never part of the Animated numeric channel: gradients are definitions,
7
+ // not painted geometry, and nothing in scope needs an animated stop.
8
+ import { useLayoutEffect, useRef } from "react";
9
+ import { Gdk, resolveSvgColor, setSvgNodeDescriptor, } from "../../gtkx/bridge/index";
10
+ import { SvgNodeElement } from "./node";
11
+ export const Defs = ({ children }) => {
12
+ const widgetRef = useRef(null);
13
+ useLayoutEffect(() => {
14
+ const widget = widgetRef.current;
15
+ if (widget) {
16
+ setSvgNodeDescriptor(widget, { kind: "defs" });
17
+ }
18
+ }, []);
19
+ return _jsx(SvgNodeElement, { widgetRef: widgetRef, children: children });
20
+ };
21
+ export const LinearGradient = ({ id, x1 = 0, y1 = 0, x2 = 1, y2 = 0, gradientUnits = "objectBoundingBox", children, }) => {
22
+ const widgetRef = useRef(null);
23
+ useLayoutEffect(() => {
24
+ const widget = widgetRef.current;
25
+ if (widget) {
26
+ setSvgNodeDescriptor(widget, {
27
+ kind: "gradient",
28
+ type: "linear",
29
+ id,
30
+ units: gradientUnits,
31
+ x1,
32
+ y1,
33
+ x2,
34
+ y2,
35
+ });
36
+ }
37
+ }, [id, x1, y1, x2, y2, gradientUnits]);
38
+ return _jsx(SvgNodeElement, { widgetRef: widgetRef, children: children });
39
+ };
40
+ export const RadialGradient = ({ id, cx = 0.5, cy = 0.5, r = 0.5, gradientUnits = "objectBoundingBox", children, }) => {
41
+ const widgetRef = useRef(null);
42
+ useLayoutEffect(() => {
43
+ const widget = widgetRef.current;
44
+ if (widget) {
45
+ setSvgNodeDescriptor(widget, {
46
+ kind: "gradient",
47
+ type: "radial",
48
+ id,
49
+ units: gradientUnits,
50
+ cx,
51
+ cy,
52
+ r,
53
+ });
54
+ }
55
+ }, [id, cx, cy, r, gradientUnits]);
56
+ return _jsx(SvgNodeElement, { widgetRef: widgetRef, children: children });
57
+ };
58
+ // "50%" or 0.5 — both spellings are common in real SVGO output.
59
+ const parseOffset = (offset) => {
60
+ if (typeof offset === "number") {
61
+ return offset;
62
+ }
63
+ const trimmed = offset.trim();
64
+ return trimmed.endsWith("%")
65
+ ? Number.parseFloat(trimmed.slice(0, -1)) / 100
66
+ : Number.parseFloat(trimmed);
67
+ };
68
+ export const Stop = ({ offset, stopColor = "black", stopOpacity = 1, }) => {
69
+ const widgetRef = useRef(null);
70
+ useLayoutEffect(() => {
71
+ const widget = widgetRef.current;
72
+ if (!widget) {
73
+ return;
74
+ }
75
+ const color = resolveSvgColor(stopColor) ??
76
+ new Gdk.RGBA({ red: 0, green: 0, blue: 0, alpha: 1 });
77
+ const clampedOpacity = Math.max(0, Math.min(1, stopOpacity));
78
+ setSvgNodeDescriptor(widget, {
79
+ kind: "stop",
80
+ offset: Math.max(0, Math.min(1, parseOffset(offset))),
81
+ color: clampedOpacity >= 1
82
+ ? color
83
+ : new Gdk.RGBA({
84
+ red: color.red,
85
+ green: color.green,
86
+ blue: color.blue,
87
+ alpha: color.alpha * clampedOpacity,
88
+ }),
89
+ });
90
+ }, [offset, stopColor, stopOpacity]);
91
+ return _jsx(SvgNodeElement, { widgetRef: widgetRef });
92
+ };
93
+ //# sourceMappingURL=gradients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradients.js","sourceRoot":"","sources":["../../../src/components/svg/gradients.tsx"],"names":[],"mappings":"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","sourcesContent":["// <Defs>/<LinearGradient>/<RadialGradient>/<Stop> — paint resources, not\n// visible nodes. They ride the same RnGtkxSvgNode tree as everything else\n// (so the bridge's snapshot() can walk <Defs> separately, before the main\n// paint pass, to build an id → gradient map — see svg-node.ts), but are\n// never part of the Animated numeric channel: gradients are definitions,\n// not painted geometry, and nothing in scope needs an animated stop.\nimport { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport {\n Gdk,\n resolveSvgColor,\n setSvgNodeDescriptor,\n type Gtk,\n type SvgGradientUnits,\n} from \"../../gtkx/bridge/index\"\nimport { SvgNodeElement } from \"./node\"\n\nexport type DefsProps = { children?: ReactNode }\n\nexport const Defs = ({ children }: DefsProps) => {\n const widgetRef = useRef<Gtk.Widget | null>(null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (widget) {\n setSvgNodeDescriptor(widget, { kind: \"defs\" })\n }\n }, [])\n return <SvgNodeElement widgetRef={widgetRef}>{children}</SvgNodeElement>\n}\n\nexport type LinearGradientProps = {\n id: string\n x1?: number\n y1?: number\n x2?: number\n y2?: number\n gradientUnits?: SvgGradientUnits\n children?: ReactNode\n}\n\nexport const LinearGradient = ({\n id,\n x1 = 0,\n y1 = 0,\n x2 = 1,\n y2 = 0,\n gradientUnits = \"objectBoundingBox\",\n children,\n}: LinearGradientProps) => {\n const widgetRef = useRef<Gtk.Widget | null>(null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (widget) {\n setSvgNodeDescriptor(widget, {\n kind: \"gradient\",\n type: \"linear\",\n id,\n units: gradientUnits,\n x1,\n y1,\n x2,\n y2,\n })\n }\n }, [id, x1, y1, x2, y2, gradientUnits])\n return <SvgNodeElement widgetRef={widgetRef}>{children}</SvgNodeElement>\n}\n\nexport type RadialGradientProps = {\n id: string\n cx?: number\n cy?: number\n r?: number\n gradientUnits?: SvgGradientUnits\n children?: ReactNode\n}\n\nexport const RadialGradient = ({\n id,\n cx = 0.5,\n cy = 0.5,\n r = 0.5,\n gradientUnits = \"objectBoundingBox\",\n children,\n}: RadialGradientProps) => {\n const widgetRef = useRef<Gtk.Widget | null>(null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (widget) {\n setSvgNodeDescriptor(widget, {\n kind: \"gradient\",\n type: \"radial\",\n id,\n units: gradientUnits,\n cx,\n cy,\n r,\n })\n }\n }, [id, cx, cy, r, gradientUnits])\n return <SvgNodeElement widgetRef={widgetRef}>{children}</SvgNodeElement>\n}\n\n// \"50%\" or 0.5 — both spellings are common in real SVGO output.\nconst parseOffset = (offset: number | string): number => {\n if (typeof offset === \"number\") {\n return offset\n }\n const trimmed = offset.trim()\n return trimmed.endsWith(\"%\")\n ? Number.parseFloat(trimmed.slice(0, -1)) / 100\n : Number.parseFloat(trimmed)\n}\n\nexport type StopProps = {\n offset: number | string\n stopColor?: string\n stopOpacity?: number\n}\n\nexport const Stop = ({\n offset,\n stopColor = \"black\",\n stopOpacity = 1,\n}: StopProps) => {\n const widgetRef = useRef<Gtk.Widget | null>(null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const color =\n resolveSvgColor(stopColor) ??\n new Gdk.RGBA({ red: 0, green: 0, blue: 0, alpha: 1 })\n const clampedOpacity = Math.max(0, Math.min(1, stopOpacity))\n setSvgNodeDescriptor(widget, {\n kind: \"stop\",\n offset: Math.max(0, Math.min(1, parseOffset(offset))),\n color:\n clampedOpacity >= 1\n ? color\n : new Gdk.RGBA({\n red: color.red,\n green: color.green,\n blue: color.blue,\n alpha: color.alpha * clampedOpacity,\n }),\n })\n }, [offset, stopColor, stopOpacity])\n return <SvgNodeElement widgetRef={widgetRef} />\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ import { type AnimatableNumber } from "./animated-support";
3
+ export type GProps = {
4
+ transform?: string;
5
+ opacity?: AnimatableNumber;
6
+ children?: ReactNode;
7
+ };
8
+ export declare const G: ({ transform, opacity, children }: GProps) => React.JSX.Element;