@gtkx/testing 1.0.0-rc.3 → 1.0.0

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 (215) hide show
  1. package/README.md +8 -9
  2. package/dist/accessible-native.d.ts +16 -0
  3. package/dist/accessible-native.d.ts.map +1 -0
  4. package/dist/accessible-native.js +206 -0
  5. package/dist/accessible-native.js.map +1 -0
  6. package/dist/act.d.ts +2 -0
  7. package/dist/act.d.ts.map +1 -1
  8. package/dist/act.js.map +1 -1
  9. package/dist/build-queries.d.ts +27 -10
  10. package/dist/build-queries.d.ts.map +1 -1
  11. package/dist/build-queries.js +39 -30
  12. package/dist/build-queries.js.map +1 -1
  13. package/dist/cleanup-registry.d.ts +1 -1
  14. package/dist/cleanup-registry.d.ts.map +1 -1
  15. package/dist/cleanup-registry.js.map +1 -1
  16. package/dist/config.d.ts +1 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +3 -3
  19. package/dist/config.js.map +1 -1
  20. package/dist/errors.d.ts +10 -1
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +12 -3
  23. package/dist/errors.js.map +1 -1
  24. package/dist/fire-event.d.ts +35 -6
  25. package/dist/fire-event.d.ts.map +1 -1
  26. package/dist/fire-event.js +23 -7
  27. package/dist/fire-event.js.map +1 -1
  28. package/dist/frame-sync.d.ts +2 -1
  29. package/dist/frame-sync.d.ts.map +1 -1
  30. package/dist/frame-sync.js +15 -5
  31. package/dist/frame-sync.js.map +1 -1
  32. package/dist/harness-window.d.ts +4 -0
  33. package/dist/harness-window.d.ts.map +1 -0
  34. package/dist/harness-window.js +6 -0
  35. package/dist/harness-window.js.map +1 -0
  36. package/dist/index.d.ts +14 -7
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +9 -5
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal.d.ts +4 -0
  41. package/dist/internal.d.ts.map +1 -0
  42. package/dist/internal.js +4 -0
  43. package/dist/internal.js.map +1 -0
  44. package/dist/matchers.d.ts +125 -39
  45. package/dist/matchers.d.ts.map +1 -1
  46. package/dist/matchers.js +156 -18
  47. package/dist/matchers.js.map +1 -1
  48. package/dist/pretty-format.d.ts +2 -0
  49. package/dist/pretty-format.d.ts.map +1 -0
  50. package/dist/pretty-format.js +2 -0
  51. package/dist/pretty-format.js.map +1 -0
  52. package/dist/pretty-widget.d.ts +7 -3
  53. package/dist/pretty-widget.d.ts.map +1 -1
  54. package/dist/pretty-widget.js +74 -57
  55. package/dist/pretty-widget.js.map +1 -1
  56. package/dist/production.d.ts +8 -0
  57. package/dist/production.d.ts.map +1 -0
  58. package/dist/production.js +34 -0
  59. package/dist/production.js.map +1 -0
  60. package/dist/queries.d.ts +99 -56
  61. package/dist/queries.d.ts.map +1 -1
  62. package/dist/queries.js +123 -83
  63. package/dist/queries.js.map +1 -1
  64. package/dist/query-helpers.d.ts +44 -0
  65. package/dist/query-helpers.d.ts.map +1 -0
  66. package/dist/query-helpers.js +57 -0
  67. package/dist/query-helpers.js.map +1 -0
  68. package/dist/render-children.d.ts +9 -0
  69. package/dist/render-children.d.ts.map +1 -0
  70. package/dist/render-children.js +11 -0
  71. package/dist/render-children.js.map +1 -0
  72. package/dist/render.d.ts.map +1 -1
  73. package/dist/render.js +13 -11
  74. package/dist/render.js.map +1 -1
  75. package/dist/role-helpers.d.ts +12 -3
  76. package/dist/role-helpers.d.ts.map +1 -1
  77. package/dist/role-helpers.js +13 -4
  78. package/dist/role-helpers.js.map +1 -1
  79. package/dist/role-naming.d.ts +5 -0
  80. package/dist/role-naming.d.ts.map +1 -0
  81. package/dist/role-naming.js +96 -0
  82. package/dist/role-naming.js.map +1 -0
  83. package/dist/screenshot.d.ts +12 -20
  84. package/dist/screenshot.d.ts.map +1 -1
  85. package/dist/screenshot.js +183 -91
  86. package/dist/screenshot.js.map +1 -1
  87. package/dist/suggestions.d.ts +3 -0
  88. package/dist/suggestions.d.ts.map +1 -1
  89. package/dist/suggestions.js.map +1 -1
  90. package/dist/traversal.d.ts +9 -1
  91. package/dist/traversal.d.ts.map +1 -1
  92. package/dist/traversal.js +26 -6
  93. package/dist/traversal.js.map +1 -1
  94. package/dist/types.d.ts +67 -17
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/types.js.map +1 -1
  97. package/dist/user-event/adjustment.d.ts +16 -0
  98. package/dist/user-event/adjustment.d.ts.map +1 -1
  99. package/dist/user-event/adjustment.js +34 -2
  100. package/dist/user-event/adjustment.js.map +1 -1
  101. package/dist/user-event/click.d.ts +45 -3
  102. package/dist/user-event/click.d.ts.map +1 -1
  103. package/dist/user-event/click.js +177 -39
  104. package/dist/user-event/click.js.map +1 -1
  105. package/dist/user-event/column-header-click.d.ts +4 -0
  106. package/dist/user-event/column-header-click.d.ts.map +1 -0
  107. package/dist/user-event/column-header-click.js +65 -0
  108. package/dist/user-event/column-header-click.js.map +1 -0
  109. package/dist/user-event/controller.d.ts +29 -0
  110. package/dist/user-event/controller.d.ts.map +1 -1
  111. package/dist/user-event/controller.js +28 -0
  112. package/dist/user-event/controller.js.map +1 -1
  113. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  114. package/dist/user-event/event-wrapper.js +2 -1
  115. package/dist/user-event/event-wrapper.js.map +1 -1
  116. package/dist/user-event/gesture.d.ts +51 -0
  117. package/dist/user-event/gesture.d.ts.map +1 -1
  118. package/dist/user-event/gesture.js +47 -4
  119. package/dist/user-event/gesture.js.map +1 -1
  120. package/dist/user-event/index.d.ts +70 -2
  121. package/dist/user-event/index.d.ts.map +1 -1
  122. package/dist/user-event/index.js +43 -26
  123. package/dist/user-event/index.js.map +1 -1
  124. package/dist/user-event/indexed-children.d.ts +10 -0
  125. package/dist/user-event/indexed-children.d.ts.map +1 -0
  126. package/dist/user-event/indexed-children.js +50 -0
  127. package/dist/user-event/indexed-children.js.map +1 -0
  128. package/dist/user-event/keyboard.d.ts +4 -2
  129. package/dist/user-event/keyboard.d.ts.map +1 -1
  130. package/dist/user-event/keyboard.js +46 -16
  131. package/dist/user-event/keyboard.js.map +1 -1
  132. package/dist/user-event/list-row-click.d.ts +4 -0
  133. package/dist/user-event/list-row-click.d.ts.map +1 -0
  134. package/dist/user-event/list-row-click.js +39 -0
  135. package/dist/user-event/list-row-click.js.map +1 -0
  136. package/dist/user-event/native-click.d.ts +6 -0
  137. package/dist/user-event/native-click.d.ts.map +1 -0
  138. package/dist/user-event/native-click.js +32 -0
  139. package/dist/user-event/native-click.js.map +1 -0
  140. package/dist/user-event/pointer.d.ts +1 -1
  141. package/dist/user-event/pointer.d.ts.map +1 -1
  142. package/dist/user-event/pointer.js +37 -13
  143. package/dist/user-event/pointer.js.map +1 -1
  144. package/dist/user-event/selection.d.ts +18 -0
  145. package/dist/user-event/selection.d.ts.map +1 -1
  146. package/dist/user-event/selection.js +56 -30
  147. package/dist/user-event/selection.js.map +1 -1
  148. package/dist/user-event/state.d.ts +1 -1
  149. package/dist/user-event/state.d.ts.map +1 -1
  150. package/dist/user-event/state.js +1 -1
  151. package/dist/user-event/state.js.map +1 -1
  152. package/dist/user-event/text.d.ts +34 -3
  153. package/dist/user-event/text.d.ts.map +1 -1
  154. package/dist/user-event/text.js +94 -17
  155. package/dist/user-event/text.js.map +1 -1
  156. package/dist/wait-for.d.ts +2 -0
  157. package/dist/wait-for.d.ts.map +1 -1
  158. package/dist/wait-for.js.map +1 -1
  159. package/dist/widget-accessible-properties.d.ts +14 -10
  160. package/dist/widget-accessible-properties.d.ts.map +1 -1
  161. package/dist/widget-accessible-properties.js +135 -173
  162. package/dist/widget-accessible-properties.js.map +1 -1
  163. package/dist/widget-getters.d.ts +8 -0
  164. package/dist/widget-getters.d.ts.map +1 -0
  165. package/dist/widget-getters.js +19 -0
  166. package/dist/widget-getters.js.map +1 -0
  167. package/dist/within.d.ts +3 -2
  168. package/dist/within.d.ts.map +1 -1
  169. package/dist/within.js +3 -2
  170. package/dist/within.js.map +1 -1
  171. package/package.json +13 -8
  172. package/src/accessible-native.ts +333 -0
  173. package/src/act.ts +2 -0
  174. package/src/build-queries.ts +66 -48
  175. package/src/cleanup-registry.ts +1 -1
  176. package/src/config.ts +4 -3
  177. package/src/errors.ts +14 -3
  178. package/src/fire-event.ts +61 -8
  179. package/src/frame-sync.ts +21 -5
  180. package/src/harness-window.ts +9 -0
  181. package/src/index.ts +18 -11
  182. package/src/internal.ts +11 -0
  183. package/src/matchers.ts +466 -64
  184. package/src/pretty-format.ts +1 -0
  185. package/src/pretty-widget.ts +100 -72
  186. package/src/production.ts +52 -0
  187. package/src/queries.ts +128 -89
  188. package/src/query-helpers.ts +97 -0
  189. package/src/render-children.ts +25 -0
  190. package/src/render.tsx +18 -13
  191. package/src/role-helpers.ts +14 -4
  192. package/src/role-naming.ts +99 -0
  193. package/src/screenshot.ts +272 -103
  194. package/src/suggestions.ts +3 -0
  195. package/src/traversal.ts +49 -6
  196. package/src/types.ts +69 -19
  197. package/src/user-event/adjustment.ts +42 -2
  198. package/src/user-event/click.ts +248 -46
  199. package/src/user-event/column-header-click.ts +90 -0
  200. package/src/user-event/controller.ts +29 -0
  201. package/src/user-event/event-wrapper.ts +3 -1
  202. package/src/user-event/gesture.ts +57 -6
  203. package/src/user-event/index.ts +119 -29
  204. package/src/user-event/indexed-children.ts +75 -0
  205. package/src/user-event/keyboard.ts +70 -20
  206. package/src/user-event/list-row-click.ts +57 -0
  207. package/src/user-event/native-click.ts +46 -0
  208. package/src/user-event/pointer.ts +52 -13
  209. package/src/user-event/selection.ts +74 -33
  210. package/src/user-event/state.ts +2 -2
  211. package/src/user-event/text.ts +121 -23
  212. package/src/wait-for.ts +2 -0
  213. package/src/widget-accessible-properties.ts +183 -214
  214. package/src/widget-getters.ts +32 -0
  215. package/src/within.ts +3 -2
@@ -1,55 +1,196 @@
1
+ import * as Gdk from "@gtkx/gi/gdk";
2
+ import * as Graphene from "@gtkx/gi/graphene";
1
3
  import * as Gsk from "@gtkx/gi/gsk";
2
4
  import * as Gtk from "@gtkx/gi/gtk";
3
- import { mkdtempSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
5
+ import { mkdirSync, writeFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import { getConfig } from "./config.js";
8
+ import { descendants } from "./traversal.js";
6
9
  import { waitFor } from "./wait-for.js";
7
10
  const DEFAULT_SCREENSHOT_INTERVAL = 10;
11
+ const PRESENTATION_PROBE_MS = 250;
12
+ const NOT_PRESENTING_MESSAGE = "Screenshot failed: the display is not presenting frames to this window, so GTK has no rendered " +
13
+ "content to capture. The window is hidden, minimized, or its compositor is not presenting it; this " +
14
+ "is a display problem, not a widget problem.";
15
+ const EMPTY_WIDGET_HINT = "The display is presenting frames to this window, so the widget itself is empty: it painted nothing.";
16
+ const OTHER_FAILURE_HINT = "The display is presenting frames to this window, so the capture failed for another reason.";
17
+ const NOTHING_ON_SCREEN_MESSAGE = "Nothing is on screen to capture: no toplevel window is mapped";
8
18
  const bytesToBase64 = (bytes) => {
9
19
  return Buffer.from(bytes).toString("base64");
10
20
  };
11
21
  const describeWidgetState = (widget) => `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
12
22
  `visible=${String(widget.getVisible())}`;
13
- const captureSnapshot = (widget, scale) => {
14
- const paintable = new Gtk.WidgetPaintable({ widget });
23
+ const getMappedPopovers = (widget) => {
24
+ const popovers = [];
25
+ for (const descendant of descendants(widget)) {
26
+ if (descendant instanceof Gtk.Popover && descendant.getMapped()) {
27
+ popovers.push(descendant);
28
+ }
29
+ }
30
+ return popovers;
31
+ };
32
+ const appendPopover = (snapshot, target, popover) => {
33
+ const [hasBounds, bounds] = popover.computeBounds(target);
34
+ const paintable = new Gtk.WidgetPaintable({ widget: popover });
15
35
  const width = paintable.getIntrinsicWidth();
16
36
  const height = paintable.getIntrinsicHeight();
17
- if (width <= 0 || height <= 0) {
18
- throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);
37
+ if (!hasBounds || width <= 0 || height <= 0) {
38
+ return;
39
+ }
40
+ snapshot.save();
41
+ snapshot.translate(new Graphene.Point({ x: bounds.origin.x, y: bounds.origin.y }));
42
+ paintable.snapshot(snapshot, width, height);
43
+ snapshot.restore();
44
+ };
45
+ const appendPopovers = (snapshot, target) => {
46
+ for (const popover of getMappedPopovers(target)) {
47
+ appendPopover(snapshot, target, popover);
48
+ }
49
+ };
50
+ const getSurface = (widget) => widget.getNative()?.getSurface() ?? null;
51
+ const isSurfaceOnScreen = (widget) => getSurface(widget)?.getMapped() ?? false;
52
+ const getFrameClock = (widget) => widget.getRoot()?.getFrameClock() ?? widget.getFrameClock();
53
+ const getFrameCounter = (widget) => getFrameClock(widget)?.getFrameCounter() ?? null;
54
+ const describePresentation = (widget) => `${describeWidgetState(widget)} surfaceMapped=${String(isSurfaceOnScreen(widget))} ` +
55
+ `frameCounter=${String(getFrameCounter(widget))}`;
56
+ const requestFrame = (widget) => {
57
+ getFrameClock(widget)?.requestPhase(Gdk.FrameClockPhase.UPDATE);
58
+ };
59
+ const startFrameProbe = (widget) => {
60
+ requestFrame(widget);
61
+ return { counter: getFrameCounter(widget), startedAt: Date.now() };
62
+ };
63
+ const updateFrameProbe = (widget, probe) => {
64
+ requestFrame(widget);
65
+ if (probe.counter !== null) {
66
+ return;
67
+ }
68
+ const counter = getFrameCounter(widget);
69
+ if (counter !== null) {
70
+ probe.counter = counter;
71
+ probe.startedAt = Date.now();
72
+ }
73
+ };
74
+ const hasFrameAdvanced = (widget, probe) => {
75
+ const counter = getFrameCounter(widget);
76
+ return counter !== null && probe.counter !== null && counter > probe.counter;
77
+ };
78
+ const isPresenting = (widget, probe) => isSurfaceOnScreen(widget) && hasFrameAdvanced(widget, probe);
79
+ const isPresentationStalled = (widget, probe) => Date.now() - probe.startedAt >= PRESENTATION_PROBE_MS && !isPresenting(widget, probe);
80
+ const allocateRoot = (widget) => {
81
+ const root = widget.getRoot();
82
+ if (!root) {
83
+ return;
84
+ }
85
+ const width = root.getWidth();
86
+ const height = root.getHeight();
87
+ if (width > 0 && height > 0) {
88
+ root.allocate(width, height, -1, null);
19
89
  }
90
+ };
91
+ const isDirectlyPaintable = (child) => child.getMapped() && child.getWidth() > 0 && !(child instanceof Gtk.Popover);
92
+ const getPaintableNode = (widget, scale, width, height) => {
93
+ const paintable = new Gtk.WidgetPaintable({ widget });
20
94
  const snapshot = new Gtk.Snapshot();
21
95
  snapshot.scale(scale, scale);
22
96
  paintable.snapshot(snapshot, width, height);
23
- const renderNode = snapshot.toNode();
24
- if (!renderNode) {
25
- throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);
97
+ appendPopovers(snapshot, widget);
98
+ return snapshot.toNode();
99
+ };
100
+ const getChildrenNode = (widget, scale) => {
101
+ const snapshot = new Gtk.Snapshot();
102
+ snapshot.scale(scale, scale);
103
+ for (let child = widget.getFirstChild(); child !== null; child = child.getNextSibling()) {
104
+ if (isDirectlyPaintable(child)) {
105
+ widget.snapshotChild(child, snapshot);
106
+ }
107
+ }
108
+ appendPopovers(snapshot, widget);
109
+ return snapshot.toNode();
110
+ };
111
+ const getRenderNode = (widget, scale, width, height) => {
112
+ const presented = getPaintableNode(widget, scale, width, height);
113
+ if (presented) {
114
+ return presented;
26
115
  }
27
- const display = widget.getDisplay();
116
+ allocateRoot(widget);
117
+ return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);
118
+ };
119
+ const renderToPng = (widget, node, width, height) => {
28
120
  const renderer = new Gsk.CairoRenderer();
29
- renderer.realizeForDisplay(display);
121
+ renderer.realizeForDisplay(widget.getDisplay());
30
122
  try {
31
- const texture = renderer.renderTexture(renderNode, null);
32
- const pngBytes = texture.saveToPngBytes();
33
- const data = pngBytes.getData();
123
+ const viewport = Graphene.Rect.alloc().init(0, 0, width, height);
124
+ const texture = renderer.renderTexture(node, viewport);
125
+ const data = texture.saveToPngBytes().getData();
34
126
  if (!data) {
35
127
  throw new Error("Failed to serialize screenshot to PNG");
36
128
  }
37
- return {
38
- data: bytesToBase64(data),
39
- mimeType: "image/png",
40
- width: Math.round(width * scale),
41
- height: Math.round(height * scale),
42
- };
129
+ return { data: bytesToBase64(data), mimeType: "image/png", width, height };
43
130
  }
44
131
  finally {
45
132
  renderer.unrealize();
46
133
  }
47
134
  };
135
+ const captureSnapshot = (widget, scale) => {
136
+ const paintable = new Gtk.WidgetPaintable({ widget });
137
+ const width = paintable.getIntrinsicWidth();
138
+ const height = paintable.getIntrinsicHeight();
139
+ if (width <= 0 || height <= 0) {
140
+ return { status: "failed", failure: "no-size" };
141
+ }
142
+ const renderNode = getRenderNode(widget, scale, width, height);
143
+ if (!renderNode) {
144
+ return { status: "failed", failure: "no-content" };
145
+ }
146
+ const scaledWidth = Math.round(width * scale);
147
+ const scaledHeight = Math.round(height * scale);
148
+ return { status: "captured", result: renderToPng(widget, renderNode, scaledWidth, scaledHeight) };
149
+ };
150
+ const failureMessage = (widget, failure) => failure === "no-size"
151
+ ? `Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`
152
+ : `Widget produced no render content (${describeWidgetState(widget)})`;
153
+ const notPresentingMessage = (widget) => `${NOT_PRESENTING_MESSAGE} (${describePresentation(widget)})`;
154
+ const stalledMessage = (widget, failure) => `${notPresentingMessage(widget)}\n\n${failureMessage(widget, failure)}`;
155
+ const presentingHint = (failure) => failure === "no-content" ? EMPTY_WIDGET_HINT : OTHER_FAILURE_HINT;
156
+ const timeoutHint = (widget, state) => isPresenting(widget, state.probe) ? presentingHint(state.failure) : notPresentingMessage(widget);
157
+ const explainTimeout = (error, widget, state) => getConfig().getElementError(`${error.message}\n\n${timeoutHint(widget, state)}`);
158
+ const attemptCapture = (widget, scale, state) => {
159
+ const attempt = captureSnapshot(widget, scale);
160
+ if (attempt.status === "captured") {
161
+ return attempt;
162
+ }
163
+ state.failure = attempt.failure;
164
+ updateFrameProbe(widget, state.probe);
165
+ if (isPresentationStalled(widget, state.probe)) {
166
+ return { status: "stalled", failure: attempt.failure };
167
+ }
168
+ throw new Error(failureMessage(widget, attempt.failure));
169
+ };
170
+ const writeScreenshot = (result, path) => {
171
+ mkdirSync(dirname(path), { recursive: true });
172
+ writeFileSync(path, Buffer.from(result.data, "base64"));
173
+ };
174
+ const captureUntilPaintable = (widget, scale, options) => {
175
+ const state = { probe: startFrameProbe(widget), failure: null };
176
+ return waitFor(() => attemptCapture(widget, scale, state), {
177
+ timeout: options?.timeout,
178
+ interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
179
+ onTimeout: (error) => explainTimeout(error, widget, state),
180
+ });
181
+ };
48
182
  /**
49
- * Captures a PNG snapshot of a widget, retrying until it has a paintable size.
183
+ * Captures a PNG snapshot of a widget, retrying until it has a paintable size,
184
+ * and writes the image to `options.path` when one is given, creating any
185
+ * missing parent directories. Popovers open inside the widget render on their
186
+ * own surfaces, so they are composited into the image at their on-screen
187
+ * positions. A display that never presents a frame leaves GTK without a cached
188
+ * render node, so the widget's contents are then snapshotted directly; when
189
+ * even that yields nothing, the failure names the display instead of blaming
190
+ * the widget.
50
191
  *
51
192
  * @param widget The widget to render to an image.
52
- * @param options Optional scale, timeout, and retry interval.
193
+ * @param options Optional scale, timeout, retry interval, and output path.
53
194
  * @returns The base64-encoded PNG data along with its mime type and dimensions.
54
195
  */
55
196
  const screenshot = async (widget, options) => {
@@ -57,79 +198,30 @@ const screenshot = async (widget, options) => {
57
198
  if (!Number.isFinite(scale) || scale <= 0) {
58
199
  throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);
59
200
  }
60
- return waitFor(() => captureSnapshot(widget, scale), {
61
- timeout: options?.timeout,
62
- interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
63
- });
64
- };
65
- const firstToplevelWindow = (windows) => {
66
- const [first] = windows;
67
- if (!(first instanceof Gtk.Window)) {
68
- throw new TypeError("First toplevel is not a Window");
201
+ const outcome = await captureUntilPaintable(widget, scale, options);
202
+ if (outcome.status === "stalled") {
203
+ throw new Error(stalledMessage(widget, outcome.failure));
69
204
  }
70
- return first;
71
- };
72
- const windowAtIndex = (windows, index) => {
73
- const indexed = windows[index];
74
- if (!(indexed instanceof Gtk.Window)) {
75
- throw new TypeError(`Window at index ${String(index)} not found`);
205
+ if (options?.path) {
206
+ writeScreenshot(outcome.result, options.path);
76
207
  }
77
- return indexed;
208
+ return outcome.result;
78
209
  };
79
210
  const isWindow = (widget) => widget instanceof Gtk.Window;
80
- const hasMatchingTitle = (window, selector) => {
81
- const title = window.getTitle() ?? "";
82
- return selector instanceof RegExp ? selector.test(title) : title.includes(selector);
83
- };
84
- const describeTitleSelector = (selector) => selector instanceof RegExp ? selector.toString() : `"${selector}"`;
85
- const windowByTitle = (windows, selector) => {
86
- const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));
87
- if (!found) {
88
- throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);
89
- }
90
- return found;
91
- };
92
- const resolveWindow = (selector) => {
93
- const windows = Gtk.Window.listToplevels();
94
- if (windows.length === 0) {
95
- throw new Error("No windows available for screenshot");
96
- }
97
- if (selector === undefined) {
98
- return firstToplevelWindow(windows);
211
+ const isOnScreen = (window) => window.getMapped();
212
+ const isActiveWindow = (window) => window.isActive();
213
+ const activeToplevel = () => {
214
+ const toplevels = Gtk.Window.listToplevels().filter((widget) => isWindow(widget));
215
+ const onScreen = toplevels.filter((window) => isOnScreen(window));
216
+ const target = onScreen.find((window) => isActiveWindow(window)) ?? onScreen[0];
217
+ if (!target) {
218
+ throw new Error(NOTHING_ON_SCREEN_MESSAGE);
99
219
  }
100
- if (typeof selector === "number") {
101
- return windowAtIndex(windows, selector);
102
- }
103
- return windowByTitle(windows, selector);
104
- };
105
- const saveScreenshotToTempFile = (result) => {
106
- const dir = mkdtempSync(join(tmpdir(), "gtkx-screenshots-"));
107
- const filepath = join(dir, "screenshot.png");
108
- writeFileSync(filepath, Buffer.from(result.data, "base64"));
109
- return filepath;
220
+ return target;
110
221
  };
111
- /**
112
- * Prints a clickable file URL for a saved screenshot to the console.
113
- *
114
- * @param filepath Absolute path of the saved screenshot file.
115
- */
116
- const logScreenshotPath = (filepath) => {
117
- console.log(`Screenshot saved: file://${filepath}`);
118
- };
119
- /**
120
- * Captures a screenshot of a toplevel window, writes it to a temporary file,
121
- * logs its path, and returns the image data.
122
- *
123
- * @param selector Chooses the window by index, title substring, or regular
124
- * expression; defaults to the first toplevel.
125
- * @param options Optional scale, timeout, and retry interval.
126
- * @returns The base64-encoded PNG data along with its mime type and dimensions.
127
- */
128
- const captureAndSaveScreenshot = async (selector, options) => {
129
- const target = resolveWindow(selector);
130
- const result = await screenshot(target, options);
131
- logScreenshotPath(saveScreenshotToTempFile(result));
132
- return result;
222
+ const captureScreen = async (options) => {
223
+ const target = activeToplevel();
224
+ return screenshot(target, options);
133
225
  };
134
- export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
226
+ export { captureScreen, screenshot };
135
227
  //# sourceMappingURL=screenshot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult, WindowSelector } from \"./types.js\";\nimport { waitFor } from \"./wait-for.js\";\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): ScreenshotResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);\n }\n\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n const renderNode = snapshot.toNode();\n\n if (!renderNode) {\n throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);\n }\n\n const display = widget.getDisplay();\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(display);\n\n try {\n const texture = renderer.renderTexture(renderNode, null);\n const pngBytes = texture.saveToPngBytes();\n const data = pngBytes.getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return {\n data: bytesToBase64(data),\n mimeType: \"image/png\",\n width: Math.round(width * scale),\n height: Math.round(height * scale),\n };\n } finally {\n renderer.unrealize();\n }\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n return waitFor(() => captureSnapshot(widget, scale), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n });\n};\n\nconst firstToplevelWindow = (windows: Gtk.Widget[]): Gtk.Window => {\n const [first] = windows;\n\n if (!(first instanceof Gtk.Window)) {\n throw new TypeError(\"First toplevel is not a Window\");\n }\n\n return first;\n};\n\nconst windowAtIndex = (windows: Gtk.Widget[], index: number): Gtk.Window => {\n const indexed = windows[index];\n\n if (!(indexed instanceof Gtk.Window)) {\n throw new TypeError(`Window at index ${String(index)} not found`);\n }\n\n return indexed;\n};\n\nconst isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;\n\nconst hasMatchingTitle = (window: Gtk.Window, selector: string | RegExp): boolean => {\n const title = window.getTitle() ?? \"\";\n\n return selector instanceof RegExp ? selector.test(title) : title.includes(selector);\n};\n\nconst describeTitleSelector = (selector: string | RegExp): string =>\n selector instanceof RegExp ? selector.toString() : `\"${selector}\"`;\n\nconst windowByTitle = (windows: Gtk.Widget[], selector: string | RegExp): Gtk.Window => {\n const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));\n\n if (!found) {\n throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);\n }\n\n return found;\n};\n\nconst resolveWindow = (selector?: WindowSelector): Gtk.Window => {\n const windows = Gtk.Window.listToplevels();\n\n if (windows.length === 0) {\n throw new Error(\"No windows available for screenshot\");\n }\n\n if (selector === undefined) {\n return firstToplevelWindow(windows);\n }\n\n if (typeof selector === \"number\") {\n return windowAtIndex(windows, selector);\n }\n\n return windowByTitle(windows, selector);\n};\n\nconst saveScreenshotToTempFile = (result: ScreenshotResult): string => {\n const dir = mkdtempSync(join(tmpdir(), \"gtkx-screenshots-\"));\n const filepath = join(dir, \"screenshot.png\");\n writeFileSync(filepath, Buffer.from(result.data, \"base64\"));\n\n return filepath;\n};\n\n/**\n * Prints a clickable file URL for a saved screenshot to the console.\n *\n * @param filepath Absolute path of the saved screenshot file.\n */\nconst logScreenshotPath = (filepath: string): void => {\n console.log(`Screenshot saved: file://${filepath}`);\n};\n\n/**\n * Captures a screenshot of a toplevel window, writes it to a temporary file,\n * logs its path, and returns the image data.\n *\n * @param selector Chooses the window by index, title substring, or regular\n * expression; defaults to the first toplevel.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst captureAndSaveScreenshot = async (\n selector?: WindowSelector,\n options?: ScreenshotOptions,\n): Promise<ScreenshotResult> => {\n const target = resolveWindow(selector);\n const result = await screenshot(target, options);\n logScreenshotPath(saveScreenshotToTempFile(result));\n\n return result;\n};\n\nexport { screenshot, logScreenshotPath, captureAndSaveScreenshot };\n"]}
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gdk from \"@gtkx/gi/gdk\";\nimport * as Graphene from \"@gtkx/gi/graphene\";\nimport * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult } from \"./types.js\";\nimport { getConfig } from \"./config.js\";\nimport { descendants } from \"./traversal.js\";\nimport { waitFor } from \"./wait-for.js\";\n\ntype FrameProbe = {\n counter: bigint | null;\n startedAt: number;\n};\n\ntype CaptureFailure = \"no-size\" | \"no-content\";\n\ntype CaptureState = {\n probe: FrameProbe;\n failure: CaptureFailure | null;\n};\n\ntype CaptureResult =\n { status: \"captured\"; result: ScreenshotResult } |\n { status: \"failed\"; failure: CaptureFailure };\n\ntype CaptureOutcome =\n { status: \"captured\"; result: ScreenshotResult } |\n { status: \"stalled\"; failure: CaptureFailure };\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\nconst PRESENTATION_PROBE_MS = 250;\n\nconst NOT_PRESENTING_MESSAGE =\n \"Screenshot failed: the display is not presenting frames to this window, so GTK has no rendered \" +\n \"content to capture. The window is hidden, minimized, or its compositor is not presenting it; this \" +\n \"is a display problem, not a widget problem.\";\n\nconst EMPTY_WIDGET_HINT =\n \"The display is presenting frames to this window, so the widget itself is empty: it painted nothing.\";\n\nconst OTHER_FAILURE_HINT =\n \"The display is presenting frames to this window, so the capture failed for another reason.\";\n\nconst NOTHING_ON_SCREEN_MESSAGE = \"Nothing is on screen to capture: no toplevel window is mapped\";\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst getMappedPopovers = (widget: Gtk.Widget): Gtk.Popover[] => {\n const popovers: Gtk.Popover[] = [];\n\n for (const descendant of descendants(widget)) {\n if (descendant instanceof Gtk.Popover && descendant.getMapped()) {\n popovers.push(descendant);\n }\n }\n\n return popovers;\n};\n\nconst appendPopover = (snapshot: Gtk.Snapshot, target: Gtk.Widget, popover: Gtk.Popover): void => {\n const [hasBounds, bounds] = popover.computeBounds(target);\n const paintable = new Gtk.WidgetPaintable({ widget: popover });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (!hasBounds || width <= 0 || height <= 0) {\n return;\n }\n\n snapshot.save();\n snapshot.translate(new Graphene.Point({ x: bounds.origin.x, y: bounds.origin.y }));\n paintable.snapshot(snapshot, width, height);\n snapshot.restore();\n};\n\nconst appendPopovers = (snapshot: Gtk.Snapshot, target: Gtk.Widget): void => {\n for (const popover of getMappedPopovers(target)) {\n appendPopover(snapshot, target, popover);\n }\n};\n\nconst getSurface = (widget: Gtk.Widget): Gdk.Surface | null => widget.getNative()?.getSurface() ?? null;\nconst isSurfaceOnScreen = (widget: Gtk.Widget): boolean => getSurface(widget)?.getMapped() ?? false;\n\nconst getFrameClock = (widget: Gtk.Widget): Gdk.FrameClock | null =>\n widget.getRoot()?.getFrameClock() ?? widget.getFrameClock();\n\nconst getFrameCounter = (widget: Gtk.Widget): bigint | null => getFrameClock(widget)?.getFrameCounter() ?? null;\n\nconst describePresentation = (widget: Gtk.Widget): string =>\n `${describeWidgetState(widget)} surfaceMapped=${String(isSurfaceOnScreen(widget))} ` +\n `frameCounter=${String(getFrameCounter(widget))}`;\n\nconst requestFrame = (widget: Gtk.Widget): void => {\n getFrameClock(widget)?.requestPhase(Gdk.FrameClockPhase.UPDATE);\n};\n\nconst startFrameProbe = (widget: Gtk.Widget): FrameProbe => {\n requestFrame(widget);\n\n return { counter: getFrameCounter(widget), startedAt: Date.now() };\n};\n\nconst updateFrameProbe = (widget: Gtk.Widget, probe: FrameProbe): void => {\n requestFrame(widget);\n\n if (probe.counter !== null) {\n return;\n }\n\n const counter = getFrameCounter(widget);\n\n if (counter !== null) {\n probe.counter = counter;\n probe.startedAt = Date.now();\n }\n};\n\nconst hasFrameAdvanced = (widget: Gtk.Widget, probe: FrameProbe): boolean => {\n const counter = getFrameCounter(widget);\n\n return counter !== null && probe.counter !== null && counter > probe.counter;\n};\n\nconst isPresenting = (widget: Gtk.Widget, probe: FrameProbe): boolean =>\n isSurfaceOnScreen(widget) && hasFrameAdvanced(widget, probe);\n\nconst isPresentationStalled = (widget: Gtk.Widget, probe: FrameProbe): boolean =>\n Date.now() - probe.startedAt >= PRESENTATION_PROBE_MS && !isPresenting(widget, probe);\n\nconst allocateRoot = (widget: Gtk.Widget): void => {\n const root = widget.getRoot();\n\n if (!root) {\n return;\n }\n\n const width = root.getWidth();\n const height = root.getHeight();\n\n if (width > 0 && height > 0) {\n root.allocate(width, height, -1, null);\n }\n};\n\nconst isDirectlyPaintable = (child: Gtk.Widget): boolean =>\n child.getMapped() && child.getWidth() > 0 && !(child instanceof Gtk.Popover);\n\nconst getPaintableNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n appendPopovers(snapshot, widget);\n\n return snapshot.toNode();\n};\n\nconst getChildrenNode = (widget: Gtk.Widget, scale: number): Gsk.RenderNode | null => {\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n\n for (let child = widget.getFirstChild(); child !== null; child = child.getNextSibling()) {\n if (isDirectlyPaintable(child)) {\n widget.snapshotChild(child, snapshot);\n }\n }\n\n appendPopovers(snapshot, widget);\n\n return snapshot.toNode();\n};\n\nconst getRenderNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {\n const presented = getPaintableNode(widget, scale, width, height);\n\n if (presented) {\n return presented;\n }\n\n allocateRoot(widget);\n\n return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);\n};\n\nconst renderToPng = (widget: Gtk.Widget, node: Gsk.RenderNode, width: number, height: number): ScreenshotResult => {\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(widget.getDisplay());\n\n try {\n const viewport = Graphene.Rect.alloc().init(0, 0, width, height);\n const texture = renderer.renderTexture(node, viewport);\n const data = texture.saveToPngBytes().getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return { data: bytesToBase64(data), mimeType: \"image/png\", width, height };\n } finally {\n renderer.unrealize();\n }\n};\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): CaptureResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n return { status: \"failed\", failure: \"no-size\" };\n }\n\n const renderNode = getRenderNode(widget, scale, width, height);\n\n if (!renderNode) {\n return { status: \"failed\", failure: \"no-content\" };\n }\n\n const scaledWidth = Math.round(width * scale);\n const scaledHeight = Math.round(height * scale);\n\n return { status: \"captured\", result: renderToPng(widget, renderNode, scaledWidth, scaledHeight) };\n};\n\nconst failureMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>\n failure === \"no-size\"\n ? `Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`\n : `Widget produced no render content (${describeWidgetState(widget)})`;\n\nconst notPresentingMessage = (widget: Gtk.Widget): string =>\n `${NOT_PRESENTING_MESSAGE} (${describePresentation(widget)})`;\n\nconst stalledMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>\n `${notPresentingMessage(widget)}\\n\\n${failureMessage(widget, failure)}`;\n\nconst presentingHint = (failure: CaptureFailure | null): string =>\n failure === \"no-content\" ? EMPTY_WIDGET_HINT : OTHER_FAILURE_HINT;\n\nconst timeoutHint = (widget: Gtk.Widget, state: CaptureState): string =>\n isPresenting(widget, state.probe) ? presentingHint(state.failure) : notPresentingMessage(widget);\n\nconst explainTimeout = (error: Error, widget: Gtk.Widget, state: CaptureState): Error =>\n getConfig().getElementError(`${error.message}\\n\\n${timeoutHint(widget, state)}`);\n\nconst attemptCapture = (widget: Gtk.Widget, scale: number, state: CaptureState): CaptureOutcome => {\n const attempt = captureSnapshot(widget, scale);\n\n if (attempt.status === \"captured\") {\n return attempt;\n }\n\n state.failure = attempt.failure;\n updateFrameProbe(widget, state.probe);\n\n if (isPresentationStalled(widget, state.probe)) {\n return { status: \"stalled\", failure: attempt.failure };\n }\n\n throw new Error(failureMessage(widget, attempt.failure));\n};\n\nconst writeScreenshot = (result: ScreenshotResult, path: string): void => {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, Buffer.from(result.data, \"base64\"));\n};\n\nconst captureUntilPaintable = (\n widget: Gtk.Widget,\n scale: number,\n options: ScreenshotOptions | undefined,\n): Promise<CaptureOutcome> => {\n const state: CaptureState = { probe: startFrameProbe(widget), failure: null };\n\n return waitFor(() => attemptCapture(widget, scale, state), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n onTimeout: (error) => explainTimeout(error, widget, state),\n });\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size,\n * and writes the image to `options.path` when one is given, creating any\n * missing parent directories. Popovers open inside the widget render on their\n * own surfaces, so they are composited into the image at their on-screen\n * positions. A display that never presents a frame leaves GTK without a cached\n * render node, so the widget's contents are then snapshotted directly; when\n * even that yields nothing, the failure names the display instead of blaming\n * the widget.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, retry interval, and output path.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n const outcome = await captureUntilPaintable(widget, scale, options);\n\n if (outcome.status === \"stalled\") {\n throw new Error(stalledMessage(widget, outcome.failure));\n }\n\n if (options?.path) {\n writeScreenshot(outcome.result, options.path);\n }\n\n return outcome.result;\n};\n\nconst isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;\nconst isOnScreen = (window: Gtk.Window): boolean => window.getMapped();\nconst isActiveWindow = (window: Gtk.Window): boolean => window.isActive();\n\nconst activeToplevel = (): Gtk.Window => {\n const toplevels = Gtk.Window.listToplevels().filter((widget) => isWindow(widget));\n const onScreen = toplevels.filter((window) => isOnScreen(window));\n const target = onScreen.find((window) => isActiveWindow(window)) ?? onScreen[0];\n\n if (!target) {\n throw new Error(NOTHING_ON_SCREEN_MESSAGE);\n }\n\n return target;\n};\n\nconst captureScreen = async (options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const target = activeToplevel();\n\n return screenshot(target, options);\n};\n\nexport { captureScreen, screenshot };\n"]}
@@ -8,10 +8,13 @@ type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue"
8
8
  * `toString` that renders the full call.
9
9
  */
10
10
  type Suggestion = {
11
+ /** The query family, such as `Role`. */
11
12
  queryName: Method;
12
13
  /** The full query function name, such as `getByRole`. */
13
14
  queryMethod: string;
15
+ /** The variant the suggestion was asked for, which prefixes `Suggestion.queryMethod`. */
14
16
  variant: Variant;
17
+ /** Renders the suggested call with its arguments, such as `getByRole(Gtk.AccessibleRole.BUTTON)`. */
15
18
  toString: () => string;
16
19
  };
17
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAYpC,8CAA8C;AAC9C,KAAK,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC5E,6CAA6C;AAC7C,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,MAAM,GAAG,cAAc,GAAG,MAAM,CAAC;AAE1F;;;GAGG;AACH,KAAK,UAAU,GAAG;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAsCF;;;;;;;GAOG;AACH,QAAA,MAAM,iBAAiB,GACnB,QAAQ,GAAG,CAAC,MAAM,EAClB,UAAS,OAAe,EACxB,SAAS,MAAM,KAChB,UAAU,GAAG,SA0Bf,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAYpC,8CAA8C;AAC9C,KAAK,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC5E,6CAA6C;AAC7C,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,MAAM,GAAG,cAAc,GAAG,MAAM,CAAC;AAE1F;;;GAGG;AACH,KAAK,UAAU,GAAG;IACd,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,OAAO,EAAE,OAAO,CAAC;IACjB,qGAAqG;IACrG,QAAQ,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAsCF;;;;;;;GAOG;AACH,QAAA,MAAM,iBAAiB,GACnB,QAAQ,GAAG,CAAC,MAAM,EAClB,UAAS,OAAe,EACxB,SAAS,MAAM,KAChB,UAAU,GAAG,SA0Bf,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\nimport { formatRole } from \"./role-helpers.js\";\nimport {\n getWidgetAccessibleName,\n getWidgetDisplayValue,\n getWidgetLabelledByText,\n getWidgetLabelText,\n getWidgetName,\n getWidgetOwnLabel,\n getWidgetPlaceholderText,\n} from \"./widget-accessible-properties.js\";\n\n/** The query variant a suggestion targets. */\ntype Variant = \"get\" | \"getAll\" | \"query\" | \"queryAll\" | \"find\" | \"findAll\";\n/** The query family a suggestion targets. */\ntype Method = \"Role\" | \"LabelText\" | \"PlaceholderText\" | \"Text\" | \"DisplayValue\" | \"Name\";\n\n/**\n * A suggested query for reaching a widget, including its family, variant, method name, and a\n * `toString` that renders the full call.\n */\ntype Suggestion = {\n queryName: Method;\n /** The full query function name, such as `getByRole`. */\n queryMethod: string;\n variant: Variant;\n toString: () => string;\n};\n\nconst makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {\n const queryMethod = `${variant}By${queryName}`;\n\n return {\n queryName,\n queryMethod,\n variant,\n toString: () => `${queryMethod}(${argsText})`,\n };\n};\n\nconst roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {\n const role = widget.getAccessibleRole();\n\n if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {\n return undefined;\n }\n\n const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;\n const name = getWidgetAccessibleName(widget);\n\n if (name === null) {\n return makeSuggestion(\"Role\", variant, roleText);\n }\n\n return makeSuggestion(\"Role\", variant, `${roleText}, { name: '${name}' }`);\n};\n\nconst textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {\n if (value === null) {\n return undefined;\n }\n\n return makeSuggestion(queryName, variant, `'${value}'`);\n};\n\n/**\n * Computes the recommended query for reaching the given widget, preferring role, then label text,\n * placeholder text, text, display value, and name.\n * @param widget Widget to build a suggestion for.\n * @param variant Query variant the suggestion should use.\n * @param method Restrict the suggestion to a specific query family instead of choosing by priority.\n * @returns The best available suggestion, or undefined when no query applies.\n */\nconst getSuggestedQuery = (\n widget: Gtk.Widget,\n variant: Variant = \"get\",\n method?: Method,\n): Suggestion | undefined => {\n const builders: Record<Method, () => Suggestion | undefined> = {\n Role: () => roleSuggestion(widget, variant),\n LabelText: () =>\n textSuggestion(\"LabelText\", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),\n PlaceholderText: () => textSuggestion(\"PlaceholderText\", variant, getWidgetPlaceholderText(widget)),\n Text: () => textSuggestion(\"Text\", variant, getWidgetLabelText(widget)),\n DisplayValue: () => textSuggestion(\"DisplayValue\", variant, getWidgetDisplayValue(widget)),\n Name: () => textSuggestion(\"Name\", variant, getWidgetName(widget)),\n };\n\n if (method) {\n return builders[method]();\n }\n\n const priority: Method[] = [\"Role\", \"LabelText\", \"PlaceholderText\", \"Text\", \"DisplayValue\", \"Name\"];\n\n for (const candidate of priority) {\n const suggestion = builders[candidate]();\n\n if (suggestion) {\n return suggestion;\n }\n }\n\n return undefined;\n};\n\nexport { getSuggestedQuery, type Variant, type Method, type Suggestion };\n"]}
1
+ {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\nimport { formatRole } from \"./role-helpers.js\";\nimport {\n getWidgetAccessibleName,\n getWidgetDisplayValue,\n getWidgetLabelledByText,\n getWidgetLabelText,\n getWidgetName,\n getWidgetOwnLabel,\n getWidgetPlaceholderText,\n} from \"./widget-accessible-properties.js\";\n\n/** The query variant a suggestion targets. */\ntype Variant = \"get\" | \"getAll\" | \"query\" | \"queryAll\" | \"find\" | \"findAll\";\n/** The query family a suggestion targets. */\ntype Method = \"Role\" | \"LabelText\" | \"PlaceholderText\" | \"Text\" | \"DisplayValue\" | \"Name\";\n\n/**\n * A suggested query for reaching a widget, including its family, variant, method name, and a\n * `toString` that renders the full call.\n */\ntype Suggestion = {\n /** The query family, such as `Role`. */\n queryName: Method;\n /** The full query function name, such as `getByRole`. */\n queryMethod: string;\n /** The variant the suggestion was asked for, which prefixes `Suggestion.queryMethod`. */\n variant: Variant;\n /** Renders the suggested call with its arguments, such as `getByRole(Gtk.AccessibleRole.BUTTON)`. */\n toString: () => string;\n};\n\nconst makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {\n const queryMethod = `${variant}By${queryName}`;\n\n return {\n queryName,\n queryMethod,\n variant,\n toString: () => `${queryMethod}(${argsText})`,\n };\n};\n\nconst roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {\n const role = widget.getAccessibleRole();\n\n if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {\n return undefined;\n }\n\n const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;\n const name = getWidgetAccessibleName(widget);\n\n if (name === null) {\n return makeSuggestion(\"Role\", variant, roleText);\n }\n\n return makeSuggestion(\"Role\", variant, `${roleText}, { name: '${name}' }`);\n};\n\nconst textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {\n if (value === null) {\n return undefined;\n }\n\n return makeSuggestion(queryName, variant, `'${value}'`);\n};\n\n/**\n * Computes the recommended query for reaching the given widget, preferring role, then label text,\n * placeholder text, text, display value, and name.\n * @param widget Widget to build a suggestion for.\n * @param variant Query variant the suggestion should use.\n * @param method Restrict the suggestion to a specific query family instead of choosing by priority.\n * @returns The best available suggestion, or undefined when no query applies.\n */\nconst getSuggestedQuery = (\n widget: Gtk.Widget,\n variant: Variant = \"get\",\n method?: Method,\n): Suggestion | undefined => {\n const builders: Record<Method, () => Suggestion | undefined> = {\n Role: () => roleSuggestion(widget, variant),\n LabelText: () =>\n textSuggestion(\"LabelText\", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),\n PlaceholderText: () => textSuggestion(\"PlaceholderText\", variant, getWidgetPlaceholderText(widget)),\n Text: () => textSuggestion(\"Text\", variant, getWidgetLabelText(widget)),\n DisplayValue: () => textSuggestion(\"DisplayValue\", variant, getWidgetDisplayValue(widget)),\n Name: () => textSuggestion(\"Name\", variant, getWidgetName(widget)),\n };\n\n if (method) {\n return builders[method]();\n }\n\n const priority: Method[] = [\"Role\", \"LabelText\", \"PlaceholderText\", \"Text\", \"DisplayValue\", \"Name\"];\n\n for (const candidate of priority) {\n const suggestion = builders[candidate]();\n\n if (suggestion) {\n return suggestion;\n }\n }\n\n return undefined;\n};\n\nexport { getSuggestedQuery, type Variant, type Method, type Suggestion };\n"]}
@@ -1,4 +1,8 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
+ /**
3
+ * A scope that resolves to a single root widget: the widget itself, the widget a controller or
4
+ * layout manager is attached to, or a list item's or header's child.
5
+ */
2
6
  type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
3
7
  /**
4
8
  * A scope that queries and traversal can run against: a widget, an event
@@ -6,10 +10,14 @@ type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk
6
10
  * sentinel representing all current toplevel windows.
7
11
  */
8
12
  type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
13
+ /** Container sentinel that widens a query to every toplevel window currently open. */
9
14
  declare const TOPLEVELS: unique symbol;
15
+ declare const isOnScreen: (widget: Gtk.Widget) => boolean;
16
+ declare const children: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
10
17
  declare const descendants: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
18
+ declare const relationCandidates: (widget: Gtk.Widget) => Gtk.Accessible[];
11
19
  declare const roots: (container: Container) => Generator<Gtk.Widget>;
12
20
  declare const traverse: (container: Container) => Generator<Gtk.Widget>;
13
21
  declare const findAll: (container: Container, isMatch: (node: Gtk.Widget) => boolean) => Gtk.Widget[];
14
- export { TOPLEVELS, descendants, roots, traverse, findAll, type Container };
22
+ export { TOPLEVELS, children, descendants, findAll, isOnScreen, relationCandidates, roots, traverse, type Container, };
15
23
  //# sourceMappingURL=traversal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC;AAC3G;;;;GAIG;AACH,KAAK,SAAS,GAAG,cAAc,GAAG,GAAG,CAAC,WAAW,GAAG,OAAO,SAAS,CAAC;AAErE,QAAA,MAAM,SAAS,EAAE,OAAO,MAAiC,CAAC;AAc1D,QAAA,MAAM,WAAW,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAIvE,CAAC;AAsBF,QAAA,MAAM,KAAK,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAYnE,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAItE,CAAC;AAEF,QAAA,MAAM,OAAO,GAAI,WAAW,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,OAAO,KAAG,GAAG,CAAC,MAAM,EAUzF,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC;;;GAGG;AACH,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC;AAC3G;;;;GAIG;AACH,KAAK,SAAS,GAAG,cAAc,GAAG,GAAG,CAAC,WAAW,GAAG,OAAO,SAAS,CAAC;AAErE,sFAAsF;AACtF,QAAA,MAAM,SAAS,EAAE,OAAO,MAAiC,CAAC;AAI1D,QAAA,MAAM,UAAU,GAAI,QAAQ,GAAG,CAAC,MAAM,KAAG,OAA6B,CAAC;AAkBvE,QAAA,MAAM,QAAQ,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAOpE,CAAC;AAEF,QAAA,MAAM,WAAW,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAIvE,CAAC;AAEF,QAAA,MAAM,kBAAkB,GAAI,QAAQ,GAAG,CAAC,MAAM,KAAG,GAAG,CAAC,UAAU,EAS9D,CAAC;AAsBF,QAAA,MAAM,KAAK,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAYnE,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAItE,CAAC;AAEF,QAAA,MAAM,OAAO,GAAI,WAAW,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,OAAO,KAAG,GAAG,CAAC,MAAM,EAUzF,CAAC;AAEF,OAAO,EACH,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,KAAK,EACL,QAAQ,EACR,KAAK,SAAS,GACjB,CAAC"}
package/dist/traversal.js CHANGED
@@ -1,19 +1,39 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
+ /** Container sentinel that widens a query to every toplevel window currently open. */
2
3
  const TOPLEVELS = Symbol("gtkx.toplevels");
3
4
  const isApplication = (container) => container instanceof Gtk.Application;
4
- const traverseWidgetTree = function* (root) {
5
- yield root;
5
+ const isAnyWidget = () => true;
6
+ const isOnScreen = (widget) => widget.getMapped();
7
+ const traverseWidgetTree = function* (root, isIncluded) {
8
+ if (isIncluded(root)) {
9
+ yield root;
10
+ }
6
11
  let child = root.getFirstChild();
7
12
  while (child) {
8
- yield* traverseWidgetTree(child);
13
+ yield* traverseWidgetTree(child, isIncluded);
14
+ child = child.getNextSibling();
15
+ }
16
+ };
17
+ const children = function* (widget) {
18
+ let child = widget.getFirstChild();
19
+ while (child) {
20
+ yield child;
9
21
  child = child.getNextSibling();
10
22
  }
11
23
  };
12
24
  const descendants = function* (widget) {
13
- const tree = traverseWidgetTree(widget);
25
+ const tree = traverseWidgetTree(widget, isAnyWidget);
14
26
  tree.next();
15
27
  yield* tree;
16
28
  };
29
+ const relationCandidates = (widget) => {
30
+ const pool = [...widget.listMnemonicLabels(), ...descendants(widget)];
31
+ const root = widget.getRoot();
32
+ if (root instanceof Gtk.Widget) {
33
+ pool.push(...traverseWidgetTree(root, isAnyWidget));
34
+ }
35
+ return [...new Set(pool)];
36
+ };
17
37
  const resolveRoot = (container) => {
18
38
  if (container instanceof Gtk.Widget) {
19
39
  return container;
@@ -41,7 +61,7 @@ const roots = function* (container) {
41
61
  };
42
62
  const traverse = function* (container) {
43
63
  for (const root of roots(container)) {
44
- yield* traverseWidgetTree(root);
64
+ yield* traverseWidgetTree(root, isOnScreen);
45
65
  }
46
66
  };
47
67
  const findAll = (container, isMatch) => {
@@ -53,5 +73,5 @@ const findAll = (container, isMatch) => {
53
73
  }
54
74
  return results;
55
75
  };
56
- export { TOPLEVELS, descendants, roots, traverse, findAll };
76
+ export { TOPLEVELS, children, descendants, findAll, isOnScreen, relationCandidates, roots, traverse, };
57
77
  //# sourceMappingURL=traversal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAUpC,MAAM,SAAS,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,aAAa,GAAG,CAAC,SAAoB,EAAgC,EAAE,CAAC,SAAS,YAAY,GAAG,CAAC,WAAW,CAAC;AAEnH,MAAM,kBAAkB,GAAG,QAAQ,CAAC,EAAE,IAAgB;IAClD,MAAM,IAAI,CAAC;IACX,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAEjC,OAAO,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACjC,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACnC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;IACZ,KAAK,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,SAAyB,EAAqB,EAAE;IACjE,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,eAAe,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,aAAa,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,QAAQ,IAAI,SAAS,YAAY,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3E,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,SAAoB;IACzC,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAElC,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,IAAI,EAAE,CAAC;QACP,MAAM,IAAI,CAAC;IACf,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,SAAoB;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,SAAoB,EAAE,OAAsC,EAAgB,EAAE;IAC3F,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAkB,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\n\ntype QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;\n/**\n * A scope that queries and traversal can run against: a widget, an event\n * controller, a layout manager, a list item or header, an application, or the\n * sentinel representing all current toplevel windows.\n */\ntype Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;\n\nconst TOPLEVELS: unique symbol = Symbol(\"gtkx.toplevels\");\n\nconst isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;\n\nconst traverseWidgetTree = function* (root: Gtk.Widget): Generator<Gtk.Widget> {\n yield root;\n let child = root.getFirstChild();\n\n while (child) {\n yield* traverseWidgetTree(child);\n child = child.getNextSibling();\n }\n};\n\nconst descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n const tree = traverseWidgetTree(widget);\n tree.next();\n yield* tree;\n};\n\nconst resolveRoot = (container: QueryContainer): Gtk.Widget | null => {\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n if (container instanceof Gtk.EventController) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.LayoutManager) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {\n return container.getChild();\n }\n\n return null;\n};\n\nconst roots = function* (container: Container): Generator<Gtk.Widget> {\n if (container === TOPLEVELS || isApplication(container)) {\n yield* Gtk.Window.listToplevels();\n\n return;\n }\n\n const root = resolveRoot(container);\n\n if (root) {\n yield root;\n }\n};\n\nconst traverse = function* (container: Container): Generator<Gtk.Widget> {\n for (const root of roots(container)) {\n yield* traverseWidgetTree(root);\n }\n};\n\nconst findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {\n const results: Gtk.Widget[] = [];\n\n for (const node of traverse(container)) {\n if (isMatch(node)) {\n results.push(node);\n }\n }\n\n return results;\n};\n\nexport { TOPLEVELS, descendants, roots, traverse, findAll, type Container };\n"]}
1
+ {"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\n\n/**\n * A scope that resolves to a single root widget: the widget itself, the widget a controller or\n * layout manager is attached to, or a list item's or header's child.\n */\ntype QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;\n/**\n * A scope that queries and traversal can run against: a widget, an event\n * controller, a layout manager, a list item or header, an application, or the\n * sentinel representing all current toplevel windows.\n */\ntype Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;\n\n/** Container sentinel that widens a query to every toplevel window currently open. */\nconst TOPLEVELS: unique symbol = Symbol(\"gtkx.toplevels\");\n\nconst isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;\nconst isAnyWidget = (): boolean => true;\nconst isOnScreen = (widget: Gtk.Widget): boolean => widget.getMapped();\n\nconst traverseWidgetTree = function* (\n root: Gtk.Widget,\n isIncluded: (widget: Gtk.Widget) => boolean,\n): Generator<Gtk.Widget> {\n if (isIncluded(root)) {\n yield root;\n }\n\n let child = root.getFirstChild();\n\n while (child) {\n yield* traverseWidgetTree(child, isIncluded);\n child = child.getNextSibling();\n }\n};\n\nconst children = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n let child = widget.getFirstChild();\n\n while (child) {\n yield child;\n child = child.getNextSibling();\n }\n};\n\nconst descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n const tree = traverseWidgetTree(widget, isAnyWidget);\n tree.next();\n yield* tree;\n};\n\nconst relationCandidates = (widget: Gtk.Widget): Gtk.Accessible[] => {\n const pool: Gtk.Accessible[] = [...widget.listMnemonicLabels(), ...descendants(widget)];\n const root = widget.getRoot();\n\n if (root instanceof Gtk.Widget) {\n pool.push(...traverseWidgetTree(root, isAnyWidget));\n }\n\n return [...new Set(pool)];\n};\n\nconst resolveRoot = (container: QueryContainer): Gtk.Widget | null => {\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n if (container instanceof Gtk.EventController) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.LayoutManager) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {\n return container.getChild();\n }\n\n return null;\n};\n\nconst roots = function* (container: Container): Generator<Gtk.Widget> {\n if (container === TOPLEVELS || isApplication(container)) {\n yield* Gtk.Window.listToplevels();\n\n return;\n }\n\n const root = resolveRoot(container);\n\n if (root) {\n yield root;\n }\n};\n\nconst traverse = function* (container: Container): Generator<Gtk.Widget> {\n for (const root of roots(container)) {\n yield* traverseWidgetTree(root, isOnScreen);\n }\n};\n\nconst findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {\n const results: Gtk.Widget[] = [];\n\n for (const node of traverse(container)) {\n if (isMatch(node)) {\n results.push(node);\n }\n }\n\n return results;\n};\n\nexport {\n TOPLEVELS,\n children,\n descendants,\n findAll,\n isOnScreen,\n relationCandidates,\n roots,\n traverse,\n type Container,\n};\n"]}