@gtkx/testing 1.6.0 → 2.0.0-beta.10

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 (132) hide show
  1. package/README.md +47 -44
  2. package/dist/accessible-native.d.ts +1 -1
  3. package/dist/accessible-native.d.ts.map +1 -1
  4. package/dist/accessible-native.js +29 -8
  5. package/dist/accessible-native.js.map +1 -1
  6. package/dist/build-queries.d.ts +9 -9
  7. package/dist/build-queries.d.ts.map +1 -1
  8. package/dist/build-queries.js +2 -1
  9. package/dist/build-queries.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +1 -3
  12. package/dist/config.js.map +1 -1
  13. package/dist/fire-event.d.ts +3 -3
  14. package/dist/fire-event.d.ts.map +1 -1
  15. package/dist/fire-event.js +14 -4
  16. package/dist/fire-event.js.map +1 -1
  17. package/dist/matchers.d.ts +52 -56
  18. package/dist/matchers.d.ts.map +1 -1
  19. package/dist/matchers.js +4 -16
  20. package/dist/matchers.js.map +1 -1
  21. package/dist/queries.d.ts +37 -133
  22. package/dist/queries.d.ts.map +1 -1
  23. package/dist/queries.js +48 -136
  24. package/dist/queries.js.map +1 -1
  25. package/dist/query-helpers.d.ts +2 -2
  26. package/dist/query-helpers.d.ts.map +1 -1
  27. package/dist/query-helpers.js +10 -3
  28. package/dist/query-helpers.js.map +1 -1
  29. package/dist/render.d.ts.map +1 -1
  30. package/dist/render.js +4 -0
  31. package/dist/render.js.map +1 -1
  32. package/dist/role-helpers.d.ts +1 -1
  33. package/dist/role-helpers.d.ts.map +1 -1
  34. package/dist/role-helpers.js +2 -1
  35. package/dist/role-helpers.js.map +1 -1
  36. package/dist/screenshot.d.ts +1 -1
  37. package/dist/screenshot.d.ts.map +1 -1
  38. package/dist/screenshot.js +26 -8
  39. package/dist/screenshot.js.map +1 -1
  40. package/dist/suggestions.d.ts +1 -1
  41. package/dist/suggestions.d.ts.map +1 -1
  42. package/dist/suggestions.js +3 -1
  43. package/dist/suggestions.js.map +1 -1
  44. package/dist/traversal.d.ts +4 -4
  45. package/dist/traversal.d.ts.map +1 -1
  46. package/dist/traversal.js +25 -7
  47. package/dist/traversal.js.map +1 -1
  48. package/dist/types.d.ts +10 -10
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/dist/user-event/adjustment.d.ts +2 -14
  52. package/dist/user-event/adjustment.d.ts.map +1 -1
  53. package/dist/user-event/adjustment.js +2 -14
  54. package/dist/user-event/adjustment.js.map +1 -1
  55. package/dist/user-event/click.d.ts +20 -45
  56. package/dist/user-event/click.d.ts.map +1 -1
  57. package/dist/user-event/click.js +31 -50
  58. package/dist/user-event/click.js.map +1 -1
  59. package/dist/user-event/controller.d.ts +5 -5
  60. package/dist/user-event/controller.d.ts.map +1 -1
  61. package/dist/user-event/controller.js +5 -2
  62. package/dist/user-event/controller.js.map +1 -1
  63. package/dist/user-event/gesture.d.ts +9 -43
  64. package/dist/user-event/gesture.d.ts.map +1 -1
  65. package/dist/user-event/gesture.js +9 -43
  66. package/dist/user-event/gesture.js.map +1 -1
  67. package/dist/user-event/index.d.ts +38 -107
  68. package/dist/user-event/index.d.ts.map +1 -1
  69. package/dist/user-event/index.js +29 -30
  70. package/dist/user-event/index.js.map +1 -1
  71. package/dist/user-event/keyboard.d.ts +1 -1
  72. package/dist/user-event/keyboard.d.ts.map +1 -1
  73. package/dist/user-event/keyboard.js +24 -3
  74. package/dist/user-event/keyboard.js.map +1 -1
  75. package/dist/user-event/list-row-click.d.ts +1 -1
  76. package/dist/user-event/list-row-click.d.ts.map +1 -1
  77. package/dist/user-event/list-row-click.js +2 -1
  78. package/dist/user-event/list-row-click.js.map +1 -1
  79. package/dist/user-event/pointer.d.ts.map +1 -1
  80. package/dist/user-event/pointer.js +9 -11
  81. package/dist/user-event/pointer.js.map +1 -1
  82. package/dist/user-event/selection.d.ts +4 -20
  83. package/dist/user-event/selection.d.ts.map +1 -1
  84. package/dist/user-event/selection.js +11 -23
  85. package/dist/user-event/selection.js.map +1 -1
  86. package/dist/user-event/text.d.ts +5 -34
  87. package/dist/user-event/text.d.ts.map +1 -1
  88. package/dist/user-event/text.js +5 -34
  89. package/dist/user-event/text.js.map +1 -1
  90. package/dist/wait-for.d.ts +2 -2
  91. package/dist/wait-for.d.ts.map +1 -1
  92. package/dist/wait-for.js +6 -4
  93. package/dist/wait-for.js.map +1 -1
  94. package/dist/widget-accessible-properties.d.ts +4 -3
  95. package/dist/widget-accessible-properties.d.ts.map +1 -1
  96. package/dist/widget-accessible-properties.js +38 -51
  97. package/dist/widget-accessible-properties.js.map +1 -1
  98. package/dist/widget-getters.d.ts +7 -9
  99. package/dist/widget-getters.d.ts.map +1 -1
  100. package/dist/widget-getters.js.map +1 -1
  101. package/dist/widget-target.d.ts +4 -0
  102. package/dist/widget-target.d.ts.map +1 -0
  103. package/dist/widget-target.js +9 -0
  104. package/dist/widget-target.js.map +1 -0
  105. package/package.json +17 -10
  106. package/src/accessible-native.ts +45 -9
  107. package/src/build-queries.ts +60 -30
  108. package/src/config.ts +1 -3
  109. package/src/fire-event.ts +32 -8
  110. package/src/matchers.ts +63 -85
  111. package/src/queries.ts +94 -156
  112. package/src/query-helpers.ts +17 -9
  113. package/src/render.tsx +7 -2
  114. package/src/role-helpers.ts +3 -1
  115. package/src/screenshot.ts +49 -8
  116. package/src/suggestions.ts +3 -1
  117. package/src/traversal.ts +35 -11
  118. package/src/types.ts +14 -10
  119. package/src/user-event/adjustment.ts +2 -14
  120. package/src/user-event/click.ts +40 -58
  121. package/src/user-event/controller.ts +8 -5
  122. package/src/user-event/gesture.ts +9 -43
  123. package/src/user-event/index.ts +149 -132
  124. package/src/user-event/keyboard.ts +33 -4
  125. package/src/user-event/list-row-click.ts +2 -2
  126. package/src/user-event/pointer.ts +11 -15
  127. package/src/user-event/selection.ts +15 -25
  128. package/src/user-event/text.ts +5 -34
  129. package/src/wait-for.ts +9 -6
  130. package/src/widget-accessible-properties.ts +61 -69
  131. package/src/widget-getters.ts +11 -13
  132. package/src/widget-target.ts +11 -0
package/src/traversal.ts CHANGED
@@ -1,25 +1,41 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
+ import { getClassType, getInstanceType, typeIsA } from "@gtkx/runtime";
2
3
 
3
4
  /**
4
5
  * A scope that resolves to a single root widget: the widget itself, the widget a controller or
5
6
  * layout manager is attached to, or a list item's or header's child.
6
7
  */
7
- type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
8
+ type QueryContainer = Gtk.Accessible | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
8
9
  /**
9
10
  * A scope that queries and traversal can run against: a widget, an event
10
11
  * controller, a layout manager, a list item or header, an application, or the
11
12
  * sentinel representing all current toplevel windows.
12
13
  */
13
14
  type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
14
- type WidgetClass<T extends Gtk.Widget> = abstract new (...args: never[]) => T;
15
+ type WidgetClass<T extends object> = abstract new (...args: never[]) => T;
15
16
 
16
17
  /** Container sentinel that widens a query to every toplevel window currently open. */
17
18
  const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
19
+ const nativeWidgetTypes: Map<bigint, boolean> = new Map();
18
20
 
19
21
  const isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;
20
22
  const isAnyWidget = (): boolean => true;
21
23
  const isOnScreen = (widget: Gtk.Widget): boolean => widget.getMapped();
22
24
 
25
+ const isNativeWidget = (widget: Gtk.Widget): boolean => {
26
+ const type = getInstanceType(widget);
27
+ const cached = nativeWidgetTypes.get(type);
28
+
29
+ if (cached !== undefined) {
30
+ return cached;
31
+ }
32
+
33
+ const isNative = typeIsA(type, getClassType(Gtk.Native));
34
+ nativeWidgetTypes.set(type, isNative);
35
+
36
+ return isNative;
37
+ };
38
+
23
39
  const traverseWidgetTree = function* (
24
40
  root: Gtk.Widget,
25
41
  isIncluded: (widget: Gtk.Widget) => boolean,
@@ -60,7 +76,7 @@ const ancestors = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
60
76
  }
61
77
  };
62
78
 
63
- const ancestorFor = <T extends Gtk.Widget>(widget: Gtk.Widget, type: WidgetClass<T>): T | null => {
79
+ const ancestorFor = <T extends object>(widget: Gtk.Widget, type: WidgetClass<T>): T | null => {
64
80
  for (const ancestor of ancestors(widget)) {
65
81
  if (ancestor instanceof type) {
66
82
  return ancestor;
@@ -70,15 +86,23 @@ const ancestorFor = <T extends Gtk.Widget>(widget: Gtk.Widget, type: WidgetClass
70
86
  return null;
71
87
  };
72
88
 
73
- const relationCandidates = (widget: Gtk.Widget): Gtk.Accessible[] => {
74
- const pool: Gtk.Accessible[] = [...widget.listMnemonicLabels(), ...descendants(widget)];
75
- const root = widget.getRoot();
89
+ const mappedWidgets = function* (widget: Gtk.Widget, isParentMapped: boolean): Generator<Gtk.Widget> {
90
+ const isMapped = (isParentMapped || isNativeWidget(widget)) && isOnScreen(widget);
76
91
 
77
- if (root instanceof Gtk.Widget) {
78
- pool.push(...traverseWidgetTree(root, isAnyWidget));
92
+ if (isMapped) {
93
+ yield widget;
79
94
  }
80
95
 
81
- return [...new Set(pool)];
96
+ for (const child of children(widget)) {
97
+ yield* mappedWidgets(child, isMapped);
98
+ }
99
+ };
100
+
101
+ const relationCandidates = (widget: Gtk.Widget): Iterable<Gtk.Accessible>[] => {
102
+ const root = widget.getRoot();
103
+ const tree: Iterable<Gtk.Accessible> = root instanceof Gtk.Widget ? traverseWidgetTree(root, isAnyWidget) : [];
104
+
105
+ return [widget.listMnemonicLabels(), descendants(widget), tree];
82
106
  };
83
107
 
84
108
  const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
@@ -98,7 +122,7 @@ const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
98
122
  return container.getChild();
99
123
  }
100
124
 
101
- return null;
125
+ throw new TypeError("Query container must resolve to a Gtk.Widget");
102
126
  };
103
127
 
104
128
  const roots = function* (container: Container): Generator<Gtk.Widget> {
@@ -117,7 +141,7 @@ const roots = function* (container: Container): Generator<Gtk.Widget> {
117
141
 
118
142
  const traverse = function* (container: Container): Generator<Gtk.Widget> {
119
143
  for (const root of roots(container)) {
120
- yield* traverseWidgetTree(root, isOnScreen);
144
+ yield* mappedWidgets(root, true);
121
145
  }
122
146
  };
123
147
 
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import type { RootElement } from "@gtkx/react";
2
+ import type { CaughtErrorInfo, RootElement } from "@gtkx/react";
3
3
  import type { ComponentType, ErrorInfo, ReactNode } from "react";
4
4
  import type { PrettyWidgetOptions } from "./pretty-widget.js";
5
5
  import type { Container } from "./traversal.js";
@@ -15,9 +15,11 @@ type Matcher = string | number | RegExp | MatcherFunction;
15
15
  type NormalizerFn = (text: string) => string;
16
16
  /**
17
17
  * A widget class usable as a query's `as` constraint, such as `Gtk.Button`. Abstract classes and
18
- * generated GInterface pseudo-classes are accepted.
18
+ * generated GInterface pseudo-classes that extend `Gtk.Accessible` are accepted.
19
19
  */
20
- type WidgetType<T extends Gtk.Widget = Gtk.Widget> = abstract new (...args: never[]) => T;
20
+ type WidgetType<T extends Gtk.Accessible = Gtk.Accessible> = abstract new (
21
+ ...args: never[]
22
+ ) => T;
21
23
 
22
24
  /** Options controlling the default text normalizer. */
23
25
  type NormalizerOptions = {
@@ -40,7 +42,7 @@ type WaitForOptions = {
40
42
  };
41
43
 
42
44
  /** Options controlling text matching and, for asynchronous queries, polling behavior. */
43
- type MatcherOptions<T extends Gtk.Widget = Gtk.Widget> = {
45
+ type MatcherOptions<T extends Gtk.Accessible = Gtk.Accessible> = {
44
46
  /** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
45
47
  exact?: boolean | undefined;
46
48
  /** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
@@ -74,7 +76,7 @@ type ByRoleValue = {
74
76
  };
75
77
 
76
78
  /** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
77
- type ByRoleOptions<T extends Gtk.Widget = Gtk.Widget> = MatcherOptions<T> & {
79
+ type ByRoleOptions<T extends Gtk.Accessible = Gtk.Accessible> = MatcherOptions<T> & {
78
80
  /** Matcher for the widget's accessible name. */
79
81
  name?: Matcher | undefined;
80
82
  /** Required checked state; a mixed check button reads as neither, so it matches neither value. */
@@ -116,7 +118,7 @@ type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: inf
116
118
  type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
117
119
 
118
120
  /** What each variant of a query family yields for the widget type it matched. */
119
- type QueryFamilyReturns<T extends Gtk.Widget> = {
121
+ type QueryFamilyReturns<T extends Gtk.Accessible> = {
120
122
  /** The single match, or null when nothing matched; throws when more than one matched. */
121
123
  queryBy: T | null;
122
124
  /** Every match, empty when nothing matched. */
@@ -135,7 +137,7 @@ type QueryFamilyReturns<T extends Gtk.Widget> = {
135
137
  type QueryKind = "role" | "text" | "name" | "value";
136
138
 
137
139
  /** The arguments a query of the given kind takes after the family's leading ones. */
138
- type QueryArgs<Kind extends QueryKind, T extends Gtk.Widget> = Kind extends "role"
140
+ type QueryArgs<Kind extends QueryKind, T extends Gtk.Accessible> = Kind extends "role"
139
141
  ? [role: Gtk.AccessibleRole, options?: ByRoleOptions<T>]
140
142
  : Kind extends "name"
141
143
  ? [name: Matcher, options?: MatcherOptions<T>]
@@ -148,7 +150,9 @@ type QueryArgs<Kind extends QueryKind, T extends Gtk.Widget> = Kind extends "rol
148
150
  * ahead of the family's own arguments.
149
151
  */
150
152
  type QueryFamily<Suffix extends string, Kind extends QueryKind, Head extends unknown[]> = {
151
- [K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <T extends Gtk.Widget = Gtk.Widget>(
153
+ [K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <
154
+ T extends Gtk.Accessible = Gtk.Widget,
155
+ >(
152
156
  ...args: [...Head, ...QueryArgs<Kind, T>]
153
157
  ) => QueryFamilyReturns<T>[K];
154
158
  };
@@ -175,7 +179,7 @@ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
175
179
  * out of reach of a pointer and a keyboard, so every `userEvent` helper aimed at it rejects
176
180
  * after `actionabilityTimeout`; `fireEvent` drives such a tree instead.
177
181
  */
178
- container?: Gtk.Widget | RootElement | undefined;
182
+ container?: Gtk.Accessible | RootElement | undefined;
179
183
  /** Root of the subtree that bound queries search. */
180
184
  baseElement?: Container | undefined;
181
185
  /** Component wrapped around the rendered element, such as a context provider. */
@@ -185,7 +189,7 @@ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
185
189
  /** Enable widget animations during the test. */
186
190
  areAnimationsEnabled?: boolean | undefined;
187
191
  /** Called for errors caught by React error boundaries. */
188
- onCaughtError?: ((error: unknown, errorInfo: ErrorInfo) => void) | undefined;
192
+ onCaughtError?: ((error: unknown, errorInfo: CaughtErrorInfo) => void) | undefined;
189
193
  /** Called for errors React recovered from automatically. */
190
194
  onRecoverableError?: ((error: unknown, errorInfo: ErrorInfo) => void) | undefined;
191
195
  /** Custom queries to bind to the rendered result. */
@@ -16,11 +16,7 @@ type ScrollAdjustments = {
16
16
  vertical: Gtk.Adjustment | null;
17
17
  };
18
18
 
19
- /**
20
- * Emits a jump `change-value` so a Gtk.Range moves to the given value.
21
- *
22
- * @throws When the widget is not a Gtk.Range.
23
- */
19
+ /** Moves a range to a value. */
24
20
  const slide = (widget: Gtk.Widget, value: number): Promise<void> =>
25
21
  wrapEvent(widget, () => {
26
22
  if (!(widget instanceof Gtk.Range)) {
@@ -76,15 +72,7 @@ const applyScrollDelta = (adjustment: Gtk.Adjustment | null, delta: number): voi
76
72
  rampTo(adjustment, adjustment.getValue() + delta, pageSize > 0 ? pageSize : Math.abs(delta));
77
73
  };
78
74
 
79
- /**
80
- * Adds the delta to the adjustments of the widget itself, or of its nearest Gtk.ScrolledWindow or
81
- * Gtk.Scrollable ancestor.
82
- *
83
- * Each adjustment advances in viewport-sized steps rather than one jump, so virtualized views such
84
- * as Gtk.ListView, Gtk.GridView and Gtk.ColumnView re-anchor onto the rows the new offset shows.
85
- *
86
- * @throws When neither the widget nor any of its ancestors is scrollable.
87
- */
75
+ /** Scrolls the widget or its nearest scrollable ancestor. */
88
76
  const scroll = (widget: Gtk.Widget, delta: ScrollDelta): Promise<void> =>
89
77
  wrapEvent(widget, async () => {
90
78
  const adjustments = resolveScrollAdjustments(widget);
@@ -11,6 +11,7 @@ type PressPoint = { x: number; y: number };
11
11
  type ClickPhase = "pressed" | "released";
12
12
  type ClickSite = { gestures: Gtk.GestureClick[]; point: PressPoint };
13
13
  type ClickScope = { isClicked: boolean; isInternalAllowed: boolean };
14
+ type DirectClick = { sites: ClickSite[]; outcome: NativeClick | null };
14
15
 
15
16
  type ClickTarget = {
16
17
  widget: Gtk.Widget;
@@ -59,15 +60,6 @@ const emitGesture = (
59
60
  }
60
61
  };
61
62
 
62
- const emitClickPhase = (
63
- widget: Gtk.Widget,
64
- controllers: Gtk.GestureClick[],
65
- nPress: number,
66
- phase: ClickPhase,
67
- ): void => {
68
- emitGesture(getCenterPoint(widget), controllers, nPress, phase);
69
- };
70
-
71
63
  const emitSitePhase = (sites: ClickSite[], nPress: number, phase: ClickPhase): void => {
72
64
  for (const site of sites) {
73
65
  emitGesture(site.point, site.gestures, nPress, phase);
@@ -97,14 +89,23 @@ const clickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
97
89
  const getAuthoredClickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
98
90
  clickGestures(widget).filter((gesture) => hasClickHandlers(gesture));
99
91
 
100
- const isClaimingTarget = (widget: Gtk.Widget): boolean =>
101
- widget instanceof Gtk.Button || containerFor(widget) !== null;
102
-
103
92
  const gestureTargetFor = (widget: Gtk.Widget, isInternalAllowed: boolean): ClickTarget | null => {
104
93
  const gestures = clickGestures(widget);
105
94
 
106
- if (isClaimingTarget(widget)) {
107
- return { widget, container: containerFor(widget), gestures, isClaiming: true, native: null };
95
+ if (widget instanceof Gtk.Button) {
96
+ return { widget, container: null, gestures, isClaiming: true, native: null };
97
+ }
98
+
99
+ const container = containerFor(widget);
100
+
101
+ if (container !== null) {
102
+ return {
103
+ widget,
104
+ container,
105
+ gestures: gestures.filter((gesture) => hasClickHandlers(gesture)),
106
+ isClaiming: true,
107
+ native: null,
108
+ };
108
109
  }
109
110
 
110
111
  const authored = gestures.filter((gesture) => hasClickHandlers(gesture));
@@ -205,7 +206,7 @@ const applyContainerOutcome = (target: ClickTarget, nPress: number): void => {
205
206
  }
206
207
 
207
208
  if (isActivatedByClick(container, nPress)) {
208
- widget.activate();
209
+ Gtk.Widget.prototype.activate.call(widget);
209
210
  }
210
211
 
211
212
  if (isSelectionReplacedByClick(container, nPress)) {
@@ -223,6 +224,24 @@ const applyClickOutcome = (target: ClickTarget, nPress: number): void => {
223
224
  target.native(target.widget, nPress);
224
225
  };
225
226
 
227
+ const applyContainerClick: NativeClick = (widget, nPress) => {
228
+ const container = containerFor(widget);
229
+
230
+ if (container !== null) {
231
+ applyContainerOutcome({ widget, container, gestures: [], isClaiming: true, native: null }, nPress);
232
+ }
233
+ };
234
+
235
+ const directClickFor = (widget: Gtk.Widget): DirectClick => {
236
+ const native = nativeClickFor(widget, true);
237
+ const container = native === null ? containerFor(widget) : null;
238
+ const gestures =
239
+ native !== null || container !== null ? getAuthoredClickGestures(widget) : clickGestures(widget);
240
+ const target: ClickTarget = { widget, container, gestures, isClaiming: true, native };
241
+
242
+ return { sites: targetSites(widget, target), outcome: native ?? (container === null ? null : applyContainerClick) };
243
+ };
244
+
226
245
  const applyClickOutcomes = (targets: ClickTarget[], nPress: number): void => {
227
246
  for (const target of targets) {
228
247
  applyClickOutcome(target, nPress);
@@ -246,39 +265,13 @@ const tryActivate = async (widget: Gtk.Widget): Promise<boolean> => {
246
265
  let isActivated = false;
247
266
 
248
267
  await wrapEvent(widget, () => {
249
- isActivated = widget.activate();
268
+ isActivated = Gtk.Widget.prototype.activate.call(widget);
250
269
  });
251
270
 
252
271
  return isActivated;
253
272
  };
254
273
 
255
- /**
256
- * Activates a widget that neither claims the click nor carries a click gesture of its own, and
257
- * otherwise delivers a press and release. The press travels outwards from the clicked widget
258
- * through every widget carrying a click gesture with a pressed or released handler, the way GTK
259
- * hands the same press to each of them, and stops at the first Gtk.Button or indexed child of a
260
- * list box or flow box, which claims it. A gesture GTK attached itself takes the press only when no
261
- * widget below it handles one, and stops it there, so an expander or a notebook tab nested in a row
262
- * opens instead of activating the row. Coordinates are the clicked widget's position in each
263
- * carrier, so a gesture the container of an indexed child carries reads the child's position rather
264
- * than the container's center. An indexed child that the press reaches is then activated, or
265
- * exclusively selected when its container does not activate on a single click. A widget with the
266
- * label role is never activated, but does consume the click when it carries a gesture of its own.
267
- *
268
- * The press also stops at a widget whose click GTK4 implements in C on a gesture it attached
269
- * itself, reading a GdkEvent that off-screen synthesis cannot produce. Gestures that widget carries
270
- * of its own still take the press and release, the way GTK hands them the press before its own
271
- * gesture claims it, and the same outcome is then applied through the public action GTK's own
272
- * handler invokes: a list, grid, or column-view row is focused and selected, and activated as well
273
- * on a second press or when its view activates on a single click; an expandable tree expander
274
- * toggles its expansion, once per press, and only when it is the widget clicked, so a click on its
275
- * child falls through to the enclosing row; a notebook tab, clicked directly or through its label,
276
- * focuses its notebook and switches to its page; a column header sorts by its column. Sorting goes
277
- * through Gtk.ColumnView.sortByColumn, so the primary sort column and order match what a pointer
278
- * produces while previously sorted columns are dropped rather than kept as secondary keys. A
279
- * column-view cell and the row that carries the column headers stand in the way of the click rather
280
- * than taking it, so a click on either reaches the row or the view behind it.
281
- */
274
+ /** Activates or presses and releases a widget. */
282
275
  const click = async (widget: Gtk.Widget): Promise<void> => {
283
276
  if (!isSelfClickTarget(widget) && (await tryActivate(widget))) {
284
277
  return;
@@ -287,21 +280,10 @@ const click = async (widget: Gtk.Widget): Promise<void> => {
287
280
  await deliverClick(widget, 1);
288
281
  };
289
282
 
290
- /**
291
- * Delivers a two-press click gesture the way {@link click} delivers a single press, without trying
292
- * activation first. A list box row or flow box child the presses reach, clicked directly or through
293
- * a descendant, is activated and exclusively selected, as GTK's double-click path does whether or
294
- * not the container activates on a single click. A widget whose click GTK4 implements itself
295
- * receives that outcome once per press, so a list, grid, or column-view row is selected and then
296
- * activated by the second press, a tree expander ends back where it started, a notebook tab stays on
297
- * the page the first press opened, and a column header sorts and then inverts its order.
298
- */
283
+ /** Delivers a double-click gesture. */
299
284
  const dblClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 2);
300
- /**
301
- * Delivers a three-press click gesture the same way a double click is delivered, applying the same
302
- * outcome to a list box row or flow box child the presses reach, and applying the outcome GTK4
303
- * implements itself once per press to a row, tree expander, notebook tab, or column header.
304
- */
285
+ /** Delivers a triple-click gesture. */
305
286
  const tripleClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 3);
306
287
 
307
- export { clickGestures, emitClickPhase, getAuthoredClickGestures, click, dblClick, tripleClick };
288
+ export { directClickFor, emitSitePhase, click, dblClick, tripleClick };
289
+ export type { DirectClick };
@@ -1,4 +1,5 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
+ import { requireWidget } from "../widget-target.js";
2
3
 
3
4
  /** An event controller class that can be constructed without arguments, such as `Gtk.GestureClick`. */
4
5
  type ControllerConstructor<T extends Gtk.EventController> = new () => T;
@@ -11,9 +12,10 @@ type ControllerConstructor<T extends Gtk.EventController> = new () => T;
11
12
  * @returns The matching controllers, or an empty array when the widget has none.
12
13
  */
13
14
  const queryAllControllers = <T extends Gtk.EventController>(
14
- widget: Gtk.Widget,
15
+ target: Gtk.Accessible,
15
16
  controllerType: ControllerConstructor<T>,
16
17
  ): T[] => {
18
+ const widget = requireWidget(target);
17
19
  const controllers = widget.observeControllers();
18
20
  const nItems = controllers.getNItems();
19
21
  const matches: T[] = [];
@@ -37,7 +39,7 @@ const queryAllControllers = <T extends Gtk.EventController>(
37
39
  * @returns The first matching controller, or null when the widget has none.
38
40
  */
39
41
  const queryController = <T extends Gtk.EventController>(
40
- widget: Gtk.Widget,
42
+ widget: Gtk.Accessible,
41
43
  controllerType: ControllerConstructor<T>,
42
44
  ): T | null => queryAllControllers(widget, controllerType)[0] ?? null;
43
45
 
@@ -49,7 +51,7 @@ const queryController = <T extends Gtk.EventController>(
49
51
  * @throws When the widget has no controller of that type.
50
52
  */
51
53
  const getAllControllers = <T extends Gtk.EventController>(
52
- widget: Gtk.Widget,
54
+ widget: Gtk.Accessible,
53
55
  controllerType: ControllerConstructor<T>,
54
56
  ): T[] => {
55
57
  const controllers = queryAllControllers(widget, controllerType);
@@ -69,7 +71,7 @@ const getAllControllers = <T extends Gtk.EventController>(
69
71
  * @throws When the widget has no controller of that type.
70
72
  */
71
73
  const getController = <T extends Gtk.EventController>(
72
- widget: Gtk.Widget,
74
+ widget: Gtk.Accessible,
73
75
  controllerType: ControllerConstructor<T>,
74
76
  ): T => {
75
77
  const [controller] = getAllControllers(widget, controllerType);
@@ -82,9 +84,10 @@ const getController = <T extends Gtk.EventController>(
82
84
  };
83
85
 
84
86
  const getOrCreateControllers = <T extends Gtk.EventController>(
85
- widget: Gtk.Widget,
87
+ target: Gtk.Accessible,
86
88
  controllerType: ControllerConstructor<T>,
87
89
  ): T[] => {
90
+ const widget = requireWidget(target);
88
91
  const existing = queryAllControllers(widget, controllerType);
89
92
 
90
93
  if (existing.length > 0) {
@@ -83,56 +83,38 @@ const buildDropValue = (content: DropContent): GObject.Value => {
83
83
  });
84
84
  };
85
85
 
86
- /** Emits `enter` at the widget's origin on its motion controllers, adding one when it has none. */
86
+ /** Enters a widget. */
87
87
  const hover = (widget: Gtk.Widget): Promise<void> =>
88
88
  dispatchOnOrCreateControllers(widget, Gtk.EventControllerMotion, (controller) => {
89
89
  controller.emit("enter", 0, 0);
90
90
  });
91
91
 
92
- /** Emits `leave` on the widget's motion controllers, adding one when it has none. */
92
+ /** Leaves a widget. */
93
93
  const unhover = (widget: Gtk.Widget): Promise<void> =>
94
94
  dispatchOnOrCreateControllers(widget, Gtk.EventControllerMotion, (controller) => {
95
95
  controller.emit("leave");
96
96
  });
97
97
 
98
- /**
99
- * Emits `angle-changed` on the widget's rotate gestures.
100
- *
101
- * @param angle Current angle, in radians.
102
- * @param deltaAngle Difference from the angle the gesture started at; defaults to `angle`.
103
- * @throws When the widget has no Gtk.GestureRotate.
104
- */
98
+ /** Rotates a widget's gestures. */
105
99
  const rotate = (widget: Gtk.Widget, angle: number, deltaAngle: number = angle): Promise<void> =>
106
100
  dispatchOnControllers(widget, Gtk.GestureRotate, (controller) => {
107
101
  controller.emit("angle-changed", angle, deltaAngle);
108
102
  },
109
103
  );
110
104
 
111
- /**
112
- * Emits `scale-changed` with the given scale delta on the widget's zoom gestures.
113
- *
114
- * @throws When the widget has no Gtk.GestureZoom.
115
- */
105
+ /** Zooms a widget's gestures. */
116
106
  const zoom = (widget: Gtk.Widget, scale: number): Promise<void> =>
117
107
  dispatchOnControllers(widget, Gtk.GestureZoom, (controller) => {
118
108
  controller.emit("scale-changed", scale);
119
109
  });
120
110
 
121
- /**
122
- * Emits `swipe` with the given per-axis velocity, in pixels per second, on the widget's swipe gestures.
123
- *
124
- * @throws When the widget has no Gtk.GestureSwipe.
125
- */
111
+ /** Swipes a widget's gestures. */
126
112
  const swipe = (widget: Gtk.Widget, velocityX: number, velocityY: number): Promise<void> =>
127
113
  dispatchOnControllers(widget, Gtk.GestureSwipe, (controller) => {
128
114
  controller.emit("swipe", velocityX, velocityY);
129
115
  });
130
116
 
131
- /**
132
- * Emits `pressed` at the given point in widget coordinates on the widget's long-press gestures.
133
- *
134
- * @throws When the widget has no Gtk.GestureLongPress.
135
- */
117
+ /** Long-presses a widget. */
136
118
  const longPress = (widget: Gtk.Widget, x = 0, y = 0): Promise<void> =>
137
119
  dispatchOnControllers(widget, Gtk.GestureLongPress, (controller) => {
138
120
  controller.emit("pressed", x, y);
@@ -219,14 +201,7 @@ const resolveDragUpdates = (dx: number, dy: number, options: DragOptions): DragO
219
201
  return updates;
220
202
  };
221
203
 
222
- /**
223
- * Runs a `drag-begin`, `drag-update`, `drag-end` sequence ending at the offset `dx`, `dy` on the
224
- * widget's drag gestures, overriding each gesture's start point and offset for the duration so
225
- * handlers read the simulated values back.
226
- *
227
- * @throws When the widget is a Gtk.Range, whose slider reads pointer coordinates from the display,
228
- * or when it has no Gtk.GestureDrag.
229
- */
204
+ /** Drags a widget by an offset. */
230
205
  const drag = async (widget: Gtk.Widget, dx: number, dy: number, options: DragOptions = {}): Promise<void> => {
231
206
  if (widget instanceof Gtk.Range) {
232
207
  throw new TypeError(
@@ -252,22 +227,13 @@ const emitDrop = (target: Gtk.Widget, content: DropContent, options: DropOptions
252
227
  }
253
228
  };
254
229
 
255
- /**
256
- * Emits `drop` with the given content on every drop target attached to the widget.
257
- *
258
- * @throws When the widget has no Gtk.DropTarget.
259
- */
230
+ /** Drops content on a widget. */
260
231
  const drop = (widget: Gtk.Widget, content: DropContent, options: DropOptions = {}): Promise<void> =>
261
232
  wrapEvent(widget, () => {
262
233
  emitDrop(widget, content, options);
263
234
  });
264
235
 
265
- /**
266
- * Emits `drop` with the given content on the target's drop targets, after checking that the source
267
- * carries a drag source.
268
- *
269
- * @throws When the source has no Gtk.DragSource, or the target no Gtk.DropTarget.
270
- */
236
+ /** Drags content from one widget and drops it on another. */
271
237
  const dragAndDrop = async (
272
238
  source: Gtk.Widget,
273
239
  target: Gtk.Widget,