@gtkx/testing 1.0.0-rc.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/act.d.ts +6 -6
  2. package/dist/act.d.ts.map +1 -1
  3. package/dist/act.js +34 -26
  4. package/dist/act.js.map +1 -1
  5. package/dist/bound-queries.d.ts +4 -3
  6. package/dist/bound-queries.d.ts.map +1 -1
  7. package/dist/bound-queries.js.map +1 -1
  8. package/dist/build-queries.d.ts +5 -5
  9. package/dist/build-queries.d.ts.map +1 -1
  10. package/dist/build-queries.js +21 -9
  11. package/dist/build-queries.js.map +1 -1
  12. package/dist/cleanup-registry.d.ts +4 -3
  13. package/dist/cleanup-registry.d.ts.map +1 -1
  14. package/dist/cleanup-registry.js +13 -3
  15. package/dist/cleanup-registry.js.map +1 -1
  16. package/dist/config.d.ts +5 -4
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +11 -9
  19. package/dist/config.js.map +1 -1
  20. package/dist/editable.d.ts +6 -5
  21. package/dist/editable.d.ts.map +1 -1
  22. package/dist/editable.js +7 -5
  23. package/dist/editable.js.map +1 -1
  24. package/dist/errors.d.ts +7 -6
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +64 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/fire-event.d.ts +2 -1
  29. package/dist/fire-event.d.ts.map +1 -1
  30. package/dist/fire-event.js +2 -1
  31. package/dist/fire-event.js.map +1 -1
  32. package/dist/frame-sync.d.ts +2 -1
  33. package/dist/frame-sync.d.ts.map +1 -1
  34. package/dist/frame-sync.js +28 -21
  35. package/dist/frame-sync.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +38 -34
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +72 -58
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/pretty-widget.d.ts +6 -4
  45. package/dist/pretty-widget.d.ts.map +1 -1
  46. package/dist/pretty-widget.js +64 -24
  47. package/dist/pretty-widget.js.map +1 -1
  48. package/dist/queries.d.ts +143 -86
  49. package/dist/queries.d.ts.map +1 -1
  50. package/dist/queries.js +231 -147
  51. package/dist/queries.js.map +1 -1
  52. package/dist/register-test-hooks.js +2 -1
  53. package/dist/register-test-hooks.js.map +1 -1
  54. package/dist/render-hook.d.ts +3 -2
  55. package/dist/render-hook.d.ts.map +1 -1
  56. package/dist/render-hook.js +2 -1
  57. package/dist/render-hook.js.map +1 -1
  58. package/dist/render.d.ts +3 -2
  59. package/dist/render.d.ts.map +1 -1
  60. package/dist/render.js +24 -18
  61. package/dist/render.js.map +1 -1
  62. package/dist/role-helpers.d.ts +6 -5
  63. package/dist/role-helpers.d.ts.map +1 -1
  64. package/dist/role-helpers.js +16 -12
  65. package/dist/role-helpers.js.map +1 -1
  66. package/dist/screen.d.ts +4 -3
  67. package/dist/screen.d.ts.map +1 -1
  68. package/dist/screen.js +13 -11
  69. package/dist/screen.js.map +1 -1
  70. package/dist/screenshot.d.ts +4 -3
  71. package/dist/screenshot.d.ts.map +1 -1
  72. package/dist/screenshot.js +43 -36
  73. package/dist/screenshot.js.map +1 -1
  74. package/dist/suggestions.d.ts +11 -6
  75. package/dist/suggestions.d.ts.map +1 -1
  76. package/dist/suggestions.js +10 -5
  77. package/dist/suggestions.js.map +1 -1
  78. package/dist/traversal.d.ts +7 -7
  79. package/dist/traversal.d.ts.map +1 -1
  80. package/dist/traversal.js +17 -11
  81. package/dist/traversal.js.map +1 -1
  82. package/dist/types.d.ts +39 -24
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/user-event/adjustment.d.ts +5 -3
  86. package/dist/user-event/adjustment.d.ts.map +1 -1
  87. package/dist/user-event/adjustment.js +6 -4
  88. package/dist/user-event/adjustment.js.map +1 -1
  89. package/dist/user-event/click.d.ts +6 -5
  90. package/dist/user-event/click.d.ts.map +1 -1
  91. package/dist/user-event/click.js +29 -21
  92. package/dist/user-event/click.js.map +1 -1
  93. package/dist/user-event/controller.d.ts +6 -5
  94. package/dist/user-event/controller.d.ts.map +1 -1
  95. package/dist/user-event/controller.js +9 -6
  96. package/dist/user-event/controller.js.map +1 -1
  97. package/dist/user-event/dispatch.d.ts +3 -3
  98. package/dist/user-event/dispatch.d.ts.map +1 -1
  99. package/dist/user-event/dispatch.js +3 -2
  100. package/dist/user-event/dispatch.js.map +1 -1
  101. package/dist/user-event/event-wrapper.d.ts +2 -1
  102. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  103. package/dist/user-event/event-wrapper.js +27 -16
  104. package/dist/user-event/event-wrapper.js.map +1 -1
  105. package/dist/user-event/gesture.d.ts +18 -14
  106. package/dist/user-event/gesture.d.ts.map +1 -1
  107. package/dist/user-event/gesture.js +70 -37
  108. package/dist/user-event/gesture.js.map +1 -1
  109. package/dist/user-event/index.d.ts +17 -10
  110. package/dist/user-event/index.d.ts.map +1 -1
  111. package/dist/user-event/index.js +7 -3
  112. package/dist/user-event/index.js.map +1 -1
  113. package/dist/user-event/keyboard.d.ts +4 -3
  114. package/dist/user-event/keyboard.d.ts.map +1 -1
  115. package/dist/user-event/keyboard.js +89 -58
  116. package/dist/user-event/keyboard.js.map +1 -1
  117. package/dist/user-event/pointer.d.ts +7 -3
  118. package/dist/user-event/pointer.d.ts.map +1 -1
  119. package/dist/user-event/pointer.js +4 -1
  120. package/dist/user-event/pointer.js.map +1 -1
  121. package/dist/user-event/selection.d.ts +3 -2
  122. package/dist/user-event/selection.d.ts.map +1 -1
  123. package/dist/user-event/selection.js +26 -27
  124. package/dist/user-event/selection.js.map +1 -1
  125. package/dist/user-event/state.d.ts +3 -2
  126. package/dist/user-event/state.d.ts.map +1 -1
  127. package/dist/user-event/state.js +2 -1
  128. package/dist/user-event/state.js.map +1 -1
  129. package/dist/user-event/text.d.ts +8 -7
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +39 -33
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/wait-for.d.ts +5 -5
  134. package/dist/wait-for.d.ts.map +1 -1
  135. package/dist/wait-for.js +45 -30
  136. package/dist/wait-for.js.map +1 -1
  137. package/dist/widget-accessible-properties.d.ts +26 -25
  138. package/dist/widget-accessible-properties.d.ts.map +1 -1
  139. package/dist/widget-accessible-properties.js +106 -61
  140. package/dist/widget-accessible-properties.js.map +1 -1
  141. package/dist/within.d.ts +2 -1
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +2 -1
  144. package/dist/within.js.map +1 -1
  145. package/package.json +11 -9
  146. package/src/act.ts +44 -35
  147. package/src/bound-queries.ts +5 -4
  148. package/src/build-queries.ts +84 -43
  149. package/src/cleanup-registry.ts +18 -5
  150. package/src/config.ts +16 -12
  151. package/src/editable.ts +13 -6
  152. package/src/errors.ts +106 -45
  153. package/src/fire-event.ts +3 -1
  154. package/src/frame-sync.ts +38 -21
  155. package/src/index.ts +2 -1
  156. package/src/matchers.ts +158 -107
  157. package/src/pretty-widget.ts +114 -42
  158. package/src/queries.ts +422 -233
  159. package/src/register-test-hooks.ts +4 -1
  160. package/src/render-hook.tsx +10 -4
  161. package/src/render.tsx +58 -33
  162. package/src/role-helpers.ts +29 -16
  163. package/src/screen.ts +17 -12
  164. package/src/screenshot.ts +61 -38
  165. package/src/suggestions.ts +29 -10
  166. package/src/traversal.ts +35 -15
  167. package/src/types.ts +65 -32
  168. package/src/user-event/adjustment.ts +23 -10
  169. package/src/user-event/click.ts +49 -20
  170. package/src/user-event/controller.ts +21 -7
  171. package/src/user-event/dispatch.ts +4 -2
  172. package/src/user-event/event-wrapper.ts +42 -16
  173. package/src/user-event/gesture.ts +135 -50
  174. package/src/user-event/index.ts +19 -12
  175. package/src/user-event/keyboard.ts +147 -61
  176. package/src/user-event/pointer.ts +20 -8
  177. package/src/user-event/selection.ts +69 -32
  178. package/src/user-event/state.ts +4 -2
  179. package/src/user-event/text.ts +73 -38
  180. package/src/wait-for.ts +77 -36
  181. package/src/widget-accessible-properties.ts +218 -74
  182. package/src/within.ts +7 -4
@@ -1,25 +1,18 @@
1
1
  import * as Gdk from "@gtkx/gi/gdk";
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
+ import type { UserEventState } from "./state.js";
3
4
  import { getEditableDelegate } from "../editable.js";
4
5
  import { fireEvent } from "../fire-event.js";
5
6
  import { getOrCreateControllers } from "./controller.js";
6
7
  import { wrapEvent } from "./event-wrapper.js";
7
- import type { UserEventState } from "./state.js";
8
8
 
9
9
  /** Options for {@link tab}: when `shift` is set, move focus backward instead of forward. */
10
- export type TabOptions = {
10
+ type TabOptions = {
11
11
  shift?: boolean;
12
12
  };
13
13
 
14
- export const tab = (widget: Gtk.Widget, options?: TabOptions): Promise<void> =>
15
- wrapEvent(widget, () => {
16
- const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
17
- const root = widget.getRoot();
18
-
19
- if (root) {
20
- root.childFocus(direction);
21
- }
22
- });
14
+ type KeyAction = { keyval: number; press: boolean };
15
+ type ParseStep = { actions: KeyAction[]; next: number };
23
16
 
24
17
  const KEY_MAP: Record<string, number> = {
25
18
  Enter: Gdk.KEY_Return,
@@ -54,67 +47,110 @@ const KEY_MAP: Record<string, number> = {
54
47
  F12: Gdk.KEY_F12,
55
48
  };
56
49
 
57
- type KeyAction = { keyval: number; press: boolean };
50
+ const MODIFIER_KEYVAL_TO_MASK: Record<number, number> = {
51
+ [Gdk.KEY_Shift_L]: Gdk.ModifierType.SHIFT_MASK,
52
+ [Gdk.KEY_Shift_R]: Gdk.ModifierType.SHIFT_MASK,
53
+ [Gdk.KEY_Control_L]: Gdk.ModifierType.CONTROL_MASK,
54
+ [Gdk.KEY_Control_R]: Gdk.ModifierType.CONTROL_MASK,
55
+ [Gdk.KEY_Alt_L]: Gdk.ModifierType.ALT_MASK,
56
+ [Gdk.KEY_Alt_R]: Gdk.ModifierType.ALT_MASK,
57
+ [Gdk.KEY_Meta_L]: Gdk.ModifierType.META_MASK,
58
+ [Gdk.KEY_Meta_R]: Gdk.ModifierType.META_MASK,
59
+ };
60
+
61
+ const tab = (widget: Gtk.Widget, options?: TabOptions): Promise<void> =>
62
+ wrapEvent(widget, () => {
63
+ const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
64
+ const root = widget.getRoot();
65
+
66
+ if (root) {
67
+ root.childFocus(direction);
68
+ }
69
+ });
58
70
 
59
71
  const parseKeyToken = (token: string): { keyval: number; press: boolean; release: boolean } => {
60
72
  let keyName = token;
61
- let press = true;
62
- let release = true;
73
+ let isPress = true;
74
+ let isRelease = true;
63
75
 
64
76
  if (keyName.startsWith("/")) {
65
77
  keyName = keyName.slice(1);
66
- press = false;
78
+ isPress = false;
67
79
  } else if (keyName.endsWith(">")) {
68
80
  keyName = keyName.slice(0, -1);
69
- release = false;
81
+ isRelease = false;
70
82
  }
71
83
 
72
84
  const keyval = KEY_MAP[keyName];
85
+
73
86
  if (keyval === undefined) {
74
87
  throw new Error(`Unknown key: {${keyName}}`);
75
88
  }
76
- return { keyval, press, release };
89
+
90
+ return { keyval, press: isPress, release: isRelease };
91
+ };
92
+
93
+ const parseCharAt = (input: string, i: number): ParseStep => {
94
+ const keyval = input.codePointAt(i) ?? 0;
95
+
96
+ return {
97
+ actions: [
98
+ { keyval, press: true },
99
+ { keyval, press: false },
100
+ ],
101
+ next: i + 1,
102
+ };
103
+ };
104
+
105
+ const parseBraceAt = (input: string, i: number): ParseStep | null => {
106
+ const endBrace = input.indexOf("}", i);
107
+
108
+ if (endBrace === -1) {
109
+ return null;
110
+ }
111
+
112
+ const { keyval, press, release } = parseKeyToken(input.slice(i + 1, endBrace));
113
+ const actions: KeyAction[] = [];
114
+
115
+ if (press) {
116
+ actions.push({ keyval, press: true });
117
+ }
118
+
119
+ if (release) {
120
+ actions.push({ keyval, press: false });
121
+ }
122
+
123
+ return { actions, next: endBrace + 1 };
77
124
  };
78
125
 
126
+ const parseStepAt = (input: string, i: number): ParseStep | null =>
127
+ input[i] === "{" ? parseBraceAt(input, i) : parseCharAt(input, i);
128
+
79
129
  const parseKeyboardInput = (input: string): KeyAction[] => {
80
130
  const actions: KeyAction[] = [];
81
131
  let i = 0;
82
132
 
83
133
  while (i < input.length) {
84
- if (input[i] !== "{") {
85
- const keyval = input.codePointAt(i) ?? 0;
86
- actions.push({ keyval, press: true }, { keyval, press: false });
87
- i++;
88
- continue;
89
- }
134
+ const step = parseStepAt(input, i);
90
135
 
91
- const endBrace = input.indexOf("}", i);
92
- if (endBrace === -1) break;
93
-
94
- const { keyval, press, release } = parseKeyToken(input.slice(i + 1, endBrace));
95
- if (press) actions.push({ keyval, press: true });
96
- if (release) actions.push({ keyval, press: false });
136
+ if (step === null) {
137
+ break;
138
+ }
97
139
 
98
- i = endBrace + 1;
140
+ actions.push(...step.actions);
141
+ i = step.next;
99
142
  }
100
143
 
101
144
  return actions;
102
145
  };
103
146
 
104
- const MODIFIER_KEYVAL_TO_MASK: Record<number, number> = {
105
- [Gdk.KEY_Shift_L]: Gdk.ModifierType.SHIFT_MASK,
106
- [Gdk.KEY_Shift_R]: Gdk.ModifierType.SHIFT_MASK,
107
- [Gdk.KEY_Control_L]: Gdk.ModifierType.CONTROL_MASK,
108
- [Gdk.KEY_Control_R]: Gdk.ModifierType.CONTROL_MASK,
109
- [Gdk.KEY_Alt_L]: Gdk.ModifierType.ALT_MASK,
110
- [Gdk.KEY_Alt_R]: Gdk.ModifierType.ALT_MASK,
111
- [Gdk.KEY_Meta_L]: Gdk.ModifierType.META_MASK,
112
- [Gdk.KEY_Meta_R]: Gdk.ModifierType.META_MASK,
113
- };
114
-
115
147
  const updateModifierState = (state: UserEventState, action: KeyAction): void => {
116
148
  const mask = MODIFIER_KEYVAL_TO_MASK[action.keyval];
117
- if (!mask) return;
149
+
150
+ if (!mask) {
151
+ return;
152
+ }
153
+
118
154
  if (action.press) {
119
155
  state.modifierState |= mask;
120
156
  } else {
@@ -122,60 +158,108 @@ const updateModifierState = (state: UserEventState, action: KeyAction): void =>
122
158
  }
123
159
  };
124
160
 
125
- const matchesTrigger = (trigger: Gtk.ShortcutTrigger | null, keyval: number, modifiers: number): boolean => {
161
+ const isTriggerMatch = (
162
+ trigger: Gtk.ShortcutTrigger | null,
163
+ keyval: number,
164
+ modifiers: Gdk.ModifierType,
165
+ ): boolean => {
126
166
  if (trigger instanceof Gtk.KeyvalTrigger) {
127
167
  return trigger.getKeyval() === keyval && trigger.getModifiers() === modifiers;
128
168
  }
169
+
129
170
  if (trigger instanceof Gtk.AlternativeTrigger) {
130
171
  return (
131
- matchesTrigger(trigger.getFirst(), keyval, modifiers) ||
132
- matchesTrigger(trigger.getSecond(), keyval, modifiers)
172
+ isTriggerMatch(trigger.getFirst(), keyval, modifiers) ||
173
+ isTriggerMatch(trigger.getSecond(), keyval, modifiers)
133
174
  );
134
175
  }
176
+
135
177
  return false;
136
178
  };
137
179
 
138
- const activateMatchingShortcut = (
180
+ const didActivateShortcut = (
181
+ shortcut: Gtk.Shortcut,
182
+ widget: Gtk.Widget,
183
+ keyval: number,
184
+ modifiers: Gdk.ModifierType,
185
+ ): boolean => {
186
+ if (!isTriggerMatch(shortcut.getTrigger(), keyval, modifiers)) {
187
+ return false;
188
+ }
189
+
190
+ const action = shortcut.getAction();
191
+
192
+ if (action instanceof Gtk.SignalAction && action.getSignalName() === "move-focus") {
193
+ return false;
194
+ }
195
+
196
+ return action?.activate(0 as Gtk.ShortcutActionFlags, widget, shortcut.getArguments()) ?? false;
197
+ };
198
+
199
+ const didActivateMatchingShortcut = (
139
200
  controller: Gtk.ShortcutController,
140
201
  widget: Gtk.Widget,
141
202
  keyval: number,
142
203
  modifiers: number,
143
204
  ): boolean => {
144
205
  const count = controller.getNItems();
206
+
145
207
  for (let j = 0; j < count; j++) {
146
208
  const shortcut = controller.getItem(j);
147
- if (!(shortcut instanceof Gtk.Shortcut)) continue;
148
- if (!matchesTrigger(shortcut.getTrigger(), keyval, modifiers)) continue;
149
- const action = shortcut.getAction();
150
- if (action instanceof Gtk.SignalAction && action.getSignalName() === "move-focus") continue;
151
- if (action?.activate(0 as Gtk.ShortcutActionFlags, widget, shortcut.getArguments())) return true;
209
+
210
+ if (!(shortcut instanceof Gtk.Shortcut)) {
211
+ continue;
212
+ }
213
+
214
+ if (didActivateShortcut(shortcut, widget, keyval, modifiers)) {
215
+ return true;
216
+ }
152
217
  }
218
+
153
219
  return false;
154
220
  };
155
221
 
156
- const dispatchShortcutsOnWidget = (widget: Gtk.Widget, keyval: number, modifiers: number): boolean => {
222
+ const didDispatchShortcutsOnWidget = (widget: Gtk.Widget, keyval: number, modifiers: number): boolean => {
157
223
  const controllers = widget.observeControllers();
224
+
158
225
  for (let i = 0; i < controllers.getNItems(); i++) {
159
226
  const controller = controllers.getItem(i);
227
+
160
228
  if (
161
229
  controller instanceof Gtk.ShortcutController &&
162
- activateMatchingShortcut(controller, widget, keyval, modifiers)
230
+ didActivateMatchingShortcut(controller, widget, keyval, modifiers)
163
231
  ) {
164
232
  return true;
165
233
  }
166
234
  }
235
+
167
236
  return false;
168
237
  };
169
238
 
170
- const dispatchShortcuts = (widget: Gtk.Widget, keyval: number, modifiers: number): boolean => {
239
+ const didDispatchShortcuts = (widget: Gtk.Widget, keyval: number, modifiers: number): boolean => {
171
240
  const delegate = getEditableDelegate(widget);
172
- if (delegate && dispatchShortcutsOnWidget(delegate, keyval, modifiers)) return true;
241
+
242
+ if (delegate && didDispatchShortcutsOnWidget(delegate, keyval, modifiers)) {
243
+ return true;
244
+ }
245
+
173
246
  for (let current: Gtk.Widget | null = widget; current; current = current.getParent()) {
174
- if (dispatchShortcutsOnWidget(current, keyval, modifiers)) return true;
247
+ if (didDispatchShortcutsOnWidget(current, keyval, modifiers)) {
248
+ return true;
249
+ }
175
250
  }
251
+
176
252
  return false;
177
253
  };
178
254
 
255
+ const handleKeyPress = async (widget: Gtk.Widget, keyval: number, modifiers: number): Promise<void> => {
256
+ const isHandled = didDispatchShortcuts(widget, keyval, modifiers);
257
+
258
+ if (!isHandled && keyval === Gdk.KEY_Return && widget instanceof Gtk.Editable) {
259
+ await fireEvent(widget, "activate");
260
+ }
261
+ };
262
+
179
263
  const applyKeyAction = async (
180
264
  widget: Gtk.Widget,
181
265
  controllers: Gtk.EventControllerKey[],
@@ -184,21 +268,23 @@ const applyKeyAction = async (
184
268
  ): Promise<void> => {
185
269
  updateModifierState(state, action);
186
270
  const signalName = action.press ? "key-pressed" : "key-released";
271
+
187
272
  for (const controller of controllers) {
188
273
  controller.emit(signalName, action.keyval, 0, state.modifierState);
189
274
  }
275
+
190
276
  if (action.press) {
191
- const handled = dispatchShortcuts(widget, action.keyval, state.modifierState);
192
- if (!handled && action.keyval === Gdk.KEY_Return && widget instanceof Gtk.Editable) {
193
- await fireEvent(widget, "activate");
194
- }
277
+ await handleKeyPress(widget, action.keyval, state.modifierState);
195
278
  }
196
279
  };
197
280
 
198
- export const keyboard = (state: UserEventState, widget: Gtk.Widget, input: string): Promise<void> =>
281
+ const keyboard = (state: UserEventState, widget: Gtk.Widget, input: string): Promise<void> =>
199
282
  wrapEvent(widget, async () => {
200
283
  const controllers = getOrCreateControllers(widget, Gtk.EventControllerKey);
284
+
201
285
  for (const action of parseKeyboardInput(input)) {
202
286
  await applyKeyAction(widget, controllers, state, action);
203
287
  }
204
288
  });
289
+
290
+ export { tab, keyboard, type TabOptions };
@@ -1,34 +1,46 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
+ import type { UserEventState } from "./state.js";
2
3
  import { emitPress, emitRelease } from "./click.js";
3
4
  import { getOrCreateControllers } from "./controller.js";
4
5
  import { wrapEvent } from "./event-wrapper.js";
5
- import type { UserEventState } from "./state.js";
6
6
 
7
- /** A pointer action token: a full click (`click`, `[MouseLeft]`), a button press (`down`, `[MouseLeft>]`), or a button release (`up`, `[/MouseLeft]`). */
8
- export type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
7
+ /**
8
+ * A pointer action token: a full click (`click`, `[MouseLeft]`), a button press (`down`,
9
+ * `[MouseLeft>]`), or a button release (`up`, `[/MouseLeft]`).
10
+ */
11
+ type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
9
12
 
10
- const PRESS_INPUTS = new Set<PointerInput>(["[MouseLeft>]", "down"]);
11
- const RELEASE_INPUTS = new Set<PointerInput>(["[/MouseLeft]", "up"]);
12
- const CLICK_INPUTS = new Set<PointerInput>(["[MouseLeft]", "click"]);
13
+ const PRESS_INPUTS: Set<PointerInput> = new Set(["[MouseLeft>]", "down"]);
14
+ const RELEASE_INPUTS: Set<PointerInput> = new Set(["[/MouseLeft]", "up"]);
15
+ const CLICK_INPUTS: Set<PointerInput> = new Set(["[MouseLeft]", "click"]);
13
16
 
14
17
  const applyPointerInput = (widget: Gtk.Widget, state: UserEventState, input: PointerInput): void => {
15
18
  const controllers = getOrCreateControllers(widget, Gtk.GestureClick);
19
+
16
20
  if (CLICK_INPUTS.has(input)) {
17
21
  emitPress(widget, controllers, 1);
18
22
  emitRelease(widget, controllers, 1);
19
23
  state.mouseLeftDown = false;
24
+
20
25
  return;
21
26
  }
27
+
22
28
  if (PRESS_INPUTS.has(input) && !state.mouseLeftDown) {
23
29
  emitPress(widget, controllers, 1);
24
30
  state.mouseLeftDown = true;
31
+
25
32
  return;
26
33
  }
34
+
27
35
  if (RELEASE_INPUTS.has(input) && state.mouseLeftDown) {
28
36
  emitRelease(widget, controllers, 1);
29
37
  state.mouseLeftDown = false;
30
38
  }
31
39
  };
32
40
 
33
- export const pointer = (state: UserEventState, widget: Gtk.Widget, input: PointerInput): Promise<void> =>
34
- wrapEvent(widget, () => applyPointerInput(widget, state, input));
41
+ const pointer = (state: UserEventState, widget: Gtk.Widget, input: PointerInput): Promise<void> =>
42
+ wrapEvent(widget, () => {
43
+ applyPointerInput(widget, state, input);
44
+ });
45
+
46
+ export { pointer, type PointerInput };
@@ -2,21 +2,31 @@ import * as Gtk from "@gtkx/gi/gtk";
2
2
  import { formatRoleList } from "../role-helpers.js";
3
3
  import { wrapEvent } from "./event-wrapper.js";
4
4
 
5
- const SELECTABLE_ROLES = new Set<Gtk.AccessibleRole>([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
5
+ type CollectionWidget = Gtk.ListView | Gtk.GridView | Gtk.ColumnView;
6
+ type ListBoxRowAction = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow) => void;
7
+
8
+ const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
6
9
 
7
10
  const isSelectable = (widget: Gtk.Widget): boolean => {
8
11
  return SELECTABLE_ROLES.has(widget.getAccessibleRole());
9
12
  };
10
13
 
11
- const selectListViewItems = (selectionModel: Gtk.SelectionModel, positions: number[], exclusive: boolean): void => {
14
+ const selectListViewItems = (
15
+ selectionModel: Gtk.SelectionModel,
16
+ positions: number[],
17
+ isExclusive: boolean,
18
+ ): void => {
12
19
  if (positions.length === 0) {
13
20
  selectionModel.unselectRange(0, selectionModel.getNItems());
21
+
14
22
  return;
15
23
  }
16
24
 
17
25
  const [first] = positions;
18
- if (exclusive && positions.length === 1 && first !== undefined) {
26
+
27
+ if (isExclusive && first !== undefined && positions.length === 1) {
19
28
  selectionModel.selectItem(first, true);
29
+
20
30
  return;
21
31
  }
22
32
 
@@ -31,16 +41,20 @@ const selectListViewItems = (selectionModel: Gtk.SelectionModel, positions: numb
31
41
  selectionModel.setSelection(selected, mask);
32
42
  };
33
43
 
34
- const isListView = (widget: Gtk.Widget): widget is Gtk.ListView | Gtk.GridView | Gtk.ColumnView => {
35
- return widget instanceof Gtk.ListView || widget instanceof Gtk.GridView || widget instanceof Gtk.ColumnView;
36
- };
44
+ const isListView = (widget: Gtk.Widget): widget is CollectionWidget =>
45
+ widget instanceof Gtk.ListView || widget instanceof Gtk.GridView || widget instanceof Gtk.ColumnView;
37
46
 
38
47
  const selectComboBoxOption = (widget: Gtk.Widget, valueArray: number[]): void => {
39
48
  if (valueArray.length > 1) {
40
49
  throw new Error("Cannot select multiple options: ComboBox only supports single selection");
41
50
  }
51
+
42
52
  const [selection] = valueArray;
43
- if (selection === undefined) return;
53
+
54
+ if (selection === undefined) {
55
+ return;
56
+ }
57
+
44
58
  if (widget instanceof Gtk.DropDown) {
45
59
  widget.setSelected(selection);
46
60
  } else if (widget instanceof Gtk.ComboBox) {
@@ -48,31 +62,41 @@ const selectComboBoxOption = (widget: Gtk.Widget, valueArray: number[]): void =>
48
62
  }
49
63
  };
50
64
 
51
- const applyListBoxRows = (listBox: Gtk.ListBox, valueArray: number[], select: boolean): void => {
65
+ const selectListBoxRow = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow): void => {
66
+ listBox.selectRow(row);
67
+ row.activate();
68
+ };
69
+
70
+ const unselectListBoxRow = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow): void => {
71
+ listBox.unselectRow(row);
72
+ };
73
+
74
+ const applyListBoxRows = (listBox: Gtk.ListBox, valueArray: number[], apply: ListBoxRowAction): void => {
52
75
  for (const value of valueArray) {
53
76
  const row = listBox.getRowAtIndex(value);
54
- if (!row) continue;
55
- if (select) {
56
- listBox.selectRow(row);
57
- row.activate();
58
- } else {
59
- listBox.unselectRow(row);
77
+
78
+ if (!row) {
79
+ continue;
60
80
  }
81
+
82
+ apply(listBox, row);
61
83
  }
62
84
  };
63
85
 
64
86
  const requireSelectionModel = (
65
- widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView,
87
+ widget: CollectionWidget,
66
88
  verb: string,
67
89
  ): Gtk.SelectionModel => {
68
90
  const selectionModel = widget.getModel();
91
+
69
92
  if (selectionModel === null) {
70
93
  throw new Error(`Cannot ${verb} options: list view has no selection model`);
71
94
  }
95
+
72
96
  return selectionModel;
73
97
  };
74
98
 
75
- const selectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView, valueArray: number[]): void => {
99
+ const selectInListView = (widget: CollectionWidget, valueArray: number[]): void => {
76
100
  const selectionModel = requireSelectionModel(widget, "select");
77
101
  const isMultiSelection = selectionModel instanceof Gtk.MultiSelection;
78
102
  selectListViewItems(selectionModel, valueArray, !isMultiSelection);
@@ -84,39 +108,52 @@ const selectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
84
108
  }
85
109
 
86
110
  const role = widget.getAccessibleRole();
111
+
87
112
  if (role === Gtk.AccessibleRole.COMBO_BOX) {
88
113
  selectComboBoxOption(widget, valueArray);
89
114
  } else if (widget instanceof Gtk.ListBox) {
90
- applyListBoxRows(widget, valueArray, true);
115
+ applyListBoxRows(widget, valueArray, selectListBoxRow);
91
116
  }
92
117
  };
93
118
 
94
- export const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
119
+ const runSelectionEvent = (
120
+ widget: Gtk.Widget,
121
+ values: number | number[],
122
+ inListView: (view: Gtk.ListView | Gtk.GridView | Gtk.ColumnView, valueArray: number[]) => void,
123
+ byRole: (widget: Gtk.Widget, valueArray: number[]) => void,
124
+ ): Promise<void> =>
95
125
  wrapEvent(widget, () => {
96
126
  const valueArray = Array.isArray(values) ? values : [values];
127
+
97
128
  if (isListView(widget)) {
98
- selectInListView(widget, valueArray);
129
+ inListView(widget, valueArray);
130
+
99
131
  return;
100
132
  }
101
- selectByRole(widget, valueArray);
133
+
134
+ byRole(widget, valueArray);
102
135
  });
103
136
 
137
+ const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
138
+ runSelectionEvent(widget, values, selectInListView, selectByRole);
139
+
104
140
  const deselectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView, valueArray: number[]): void => {
105
141
  const selectionModel = requireSelectionModel(widget, "deselect");
142
+
106
143
  for (const pos of valueArray) {
107
144
  selectionModel.unselectItem(pos);
108
145
  }
109
146
  };
110
147
 
111
- export const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
112
- wrapEvent(widget, () => {
113
- const valueArray = Array.isArray(values) ? values : [values];
114
- if (isListView(widget)) {
115
- deselectInListView(widget, valueArray);
116
- return;
117
- }
118
- if (!(widget instanceof Gtk.ListBox)) {
119
- throw new Error("Cannot deselect options: only ListBox supports deselection");
120
- }
121
- applyListBoxRows(widget, valueArray, false);
122
- });
148
+ const deselectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
149
+ if (!(widget instanceof Gtk.ListBox)) {
150
+ throw new TypeError("Cannot deselect options: only ListBox supports deselection");
151
+ }
152
+
153
+ applyListBoxRows(widget, valueArray, unselectListBoxRow);
154
+ };
155
+
156
+ const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
157
+ runSelectionEvent(widget, values, deselectInListView, deselectByRole);
158
+
159
+ export { selectOptions, deselectOptions };
@@ -1,6 +1,8 @@
1
- export type UserEventState = {
1
+ type UserEventState = {
2
2
  modifierState: number;
3
3
  mouseLeftDown: boolean;
4
4
  };
5
5
 
6
- export const createInitialState = (): UserEventState => ({ modifierState: 0, mouseLeftDown: false });
6
+ const createInitialState = (): UserEventState => ({ modifierState: 0, mouseLeftDown: false });
7
+
8
+ export { createInitialState, type UserEventState };