@gtkx/testing 1.0.0-rc.4 → 1.1.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.
- package/README.md +5 -5
- package/dist/accessible-native.d.ts +16 -0
- package/dist/accessible-native.d.ts.map +1 -0
- package/dist/accessible-native.js +206 -0
- package/dist/accessible-native.js.map +1 -0
- package/dist/act.d.ts +1 -3
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +1 -1
- package/dist/act.js.map +1 -1
- package/dist/build-queries.d.ts +27 -10
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +22 -24
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +1 -1
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -1
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +35 -6
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +23 -7
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +3 -2
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +19 -9
- package/dist/frame-sync.js.map +1 -1
- package/dist/harness-window.d.ts +5 -0
- package/dist/harness-window.d.ts.map +1 -0
- package/dist/harness-window.js +17 -0
- package/dist/harness-window.js.map +1 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +5 -0
- package/dist/internal.js.map +1 -0
- package/dist/matchers.d.ts +88 -46
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +178 -20
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-format.d.ts +2 -0
- package/dist/pretty-format.d.ts.map +1 -0
- package/dist/pretty-format.js +2 -0
- package/dist/pretty-format.js.map +1 -0
- package/dist/pretty-widget.d.ts +5 -2
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +75 -57
- package/dist/pretty-widget.js.map +1 -1
- package/dist/production.d.ts +8 -0
- package/dist/production.d.ts.map +1 -0
- package/dist/production.js +34 -0
- package/dist/production.js.map +1 -0
- package/dist/queries.d.ts +98 -56
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +123 -83
- package/dist/queries.js.map +1 -1
- package/dist/query-helpers.d.ts +44 -0
- package/dist/query-helpers.d.ts.map +1 -0
- package/dist/query-helpers.js +57 -0
- package/dist/query-helpers.js.map +1 -0
- package/dist/render-children.d.ts +9 -0
- package/dist/render-children.d.ts.map +1 -0
- package/dist/render-children.js +11 -0
- package/dist/render-children.js.map +1 -0
- package/dist/render.d.ts +12 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +52 -16
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +12 -3
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +15 -5
- package/dist/role-helpers.js.map +1 -1
- package/dist/role-naming.d.ts +5 -0
- package/dist/role-naming.d.ts.map +1 -0
- package/dist/role-naming.js +96 -0
- package/dist/role-naming.js.map +1 -0
- package/dist/screenshot.d.ts +12 -20
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +179 -93
- package/dist/screenshot.js.map +1 -1
- package/dist/timers.d.ts +9 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +21 -0
- package/dist/timers.js.map +1 -0
- package/dist/traversal.d.ts +7 -1
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +40 -6
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +32 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +3 -0
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +25 -3
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +44 -8
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +176 -44
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/column-header-click.d.ts +4 -0
- package/dist/user-event/column-header-click.d.ts.map +1 -0
- package/dist/user-event/column-header-click.js +55 -0
- package/dist/user-event/column-header-click.js.map +1 -0
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +34 -20
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/index.d.ts +80 -13
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +44 -26
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/indexed-children.d.ts +10 -0
- package/dist/user-event/indexed-children.d.ts.map +1 -0
- package/dist/user-event/indexed-children.js +50 -0
- package/dist/user-event/indexed-children.js.map +1 -0
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +115 -31
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts +4 -0
- package/dist/user-event/list-row-click.d.ts.map +1 -0
- package/dist/user-event/list-row-click.js +38 -0
- package/dist/user-event/list-row-click.js.map +1 -0
- package/dist/user-event/native-click.d.ts +6 -0
- package/dist/user-event/native-click.d.ts.map +1 -0
- package/dist/user-event/native-click.js +36 -0
- package/dist/user-event/native-click.js.map +1 -0
- package/dist/user-event/notebook-tab-click.d.ts +5 -0
- package/dist/user-event/notebook-tab-click.d.ts.map +1 -0
- package/dist/user-event/notebook-tab-click.js +39 -0
- package/dist/user-event/notebook-tab-click.js.map +1 -0
- package/dist/user-event/pointer.d.ts +1 -1
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +33 -9
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +11 -8
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +49 -38
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/text.d.ts +12 -5
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +151 -46
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +4 -3
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +14 -26
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +135 -190
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/widget-getters.d.ts +11 -0
- package/dist/widget-getters.d.ts.map +1 -0
- package/dist/widget-getters.js +46 -0
- package/dist/widget-getters.js.map +1 -0
- package/dist/window-state.d.ts +10 -0
- package/dist/window-state.d.ts.map +1 -0
- package/dist/window-state.js +41 -0
- package/dist/window-state.js.map +1 -0
- package/dist/within.d.ts +3 -2
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +3 -2
- package/dist/within.js.map +1 -1
- package/package.json +21 -8
- package/src/accessible-native.ts +333 -0
- package/src/act.ts +1 -1
- package/src/build-queries.ts +47 -41
- package/src/cleanup-registry.ts +1 -1
- package/src/config.ts +10 -1
- package/src/errors.ts +12 -1
- package/src/fire-event.ts +61 -8
- package/src/frame-sync.ts +30 -9
- package/src/harness-window.ts +23 -0
- package/src/index.ts +18 -11
- package/src/internal.ts +12 -0
- package/src/matchers.ts +457 -72
- package/src/pretty-format.ts +1 -0
- package/src/pretty-widget.ts +98 -70
- package/src/production.ts +51 -0
- package/src/queries.ts +127 -89
- package/src/query-helpers.ts +97 -0
- package/src/render-children.ts +25 -0
- package/src/render.tsx +77 -17
- package/src/role-helpers.ts +16 -5
- package/src/role-naming.ts +99 -0
- package/src/screenshot.ts +266 -104
- package/src/timers.ts +31 -0
- package/src/traversal.ts +66 -6
- package/src/types.ts +34 -17
- package/src/user-event/adjustment.ts +31 -3
- package/src/user-event/click.ts +248 -52
- package/src/user-event/column-header-click.ts +76 -0
- package/src/user-event/event-wrapper.ts +41 -23
- package/src/user-event/index.ts +130 -40
- package/src/user-event/indexed-children.ts +75 -0
- package/src/user-event/keyboard.ts +161 -43
- package/src/user-event/list-row-click.ts +54 -0
- package/src/user-event/native-click.ts +51 -0
- package/src/user-event/notebook-tab-click.ts +57 -0
- package/src/user-event/pointer.ts +48 -9
- package/src/user-event/selection.ts +67 -41
- package/src/user-event/text.ts +192 -46
- package/src/wait-for.ts +4 -4
- package/src/widget-accessible-properties.ts +183 -230
- package/src/widget-getters.ts +85 -0
- package/src/window-state.ts +70 -0
- package/src/within.ts +3 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as Gdk from "@gtkx/gi/gdk";
|
|
2
|
+
import * as Gio from "@gtkx/gi/gio";
|
|
2
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
+
import { hasSignalListener } from "@gtkx/runtime/internal";
|
|
3
5
|
import type { UserEventState } from "./state.js";
|
|
4
6
|
import { getEditableDelegate } from "../editable.js";
|
|
5
7
|
import { fireEvent } from "../fire-event.js";
|
|
6
|
-
import {
|
|
8
|
+
import { ancestors, descendants } from "../traversal.js";
|
|
7
9
|
import { wrapEvent } from "./event-wrapper.js";
|
|
8
10
|
|
|
9
11
|
/** Options for `userEvent.tab`. */
|
|
@@ -14,6 +16,8 @@ type TabOptions = {
|
|
|
14
16
|
|
|
15
17
|
type KeyAction = { keyval: number; isPress: boolean };
|
|
16
18
|
type ParseStep = { actions: KeyAction[]; next: number };
|
|
19
|
+
type KeyStopController = Gtk.EventControllerKey | Gtk.ShortcutController;
|
|
20
|
+
type KeyStop = { widget: Gtk.Widget; controller: KeyStopController };
|
|
17
21
|
|
|
18
22
|
const KEY_MAP: Record<string, number> = {
|
|
19
23
|
Enter: Gdk.KEY_Return,
|
|
@@ -48,6 +52,8 @@ const KEY_MAP: Record<string, number> = {
|
|
|
48
52
|
F12: Gdk.KEY_F12,
|
|
49
53
|
};
|
|
50
54
|
|
|
55
|
+
const KEY_CONTROLLER_SIGNALS = ["key-pressed", "key-released"];
|
|
56
|
+
|
|
51
57
|
const MODIFIER_KEYVAL_TO_MASK: Record<number, number> = {
|
|
52
58
|
[Gdk.KEY_Shift_L]: Gdk.ModifierType.SHIFT_MASK,
|
|
53
59
|
[Gdk.KEY_Shift_R]: Gdk.ModifierType.SHIFT_MASK,
|
|
@@ -179,6 +185,61 @@ const isTriggerMatch = (
|
|
|
179
185
|
return false;
|
|
180
186
|
};
|
|
181
187
|
|
|
188
|
+
const controllersOn = function* (widget: Gtk.Widget): Generator<Gtk.EventController> {
|
|
189
|
+
const controllers = widget.observeControllers();
|
|
190
|
+
const count = controllers.getNItems();
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < count; i++) {
|
|
193
|
+
const controller = controllers.getItem(i);
|
|
194
|
+
|
|
195
|
+
if (controller instanceof Gtk.EventController) {
|
|
196
|
+
yield controller;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const hasShortcut = (controller: Gtk.ShortcutController, shortcut: Gtk.Shortcut): boolean => {
|
|
202
|
+
const count = controller.getNItems();
|
|
203
|
+
|
|
204
|
+
for (let i = 0; i < count; i++) {
|
|
205
|
+
if (controller.getItem(i) === shortcut) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return false;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const isManagingShortcut = (controller: Gtk.EventController, shortcut: Gtk.Shortcut): boolean =>
|
|
214
|
+
controller instanceof Gtk.ShortcutController &&
|
|
215
|
+
controller.getScope() !== Gtk.ShortcutScope.LOCAL &&
|
|
216
|
+
hasShortcut(controller, shortcut);
|
|
217
|
+
|
|
218
|
+
const managedShortcutHost = (manager: Gtk.Widget, shortcut: Gtk.Shortcut): Gtk.Widget | null => {
|
|
219
|
+
for (const current of [manager, ...descendants(manager)]) {
|
|
220
|
+
if (controllersOn(current).some((controller) => isManagingShortcut(controller, shortcut))) {
|
|
221
|
+
return current;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const isMovingFocus = (action: Gtk.ShortcutAction): boolean =>
|
|
229
|
+
action instanceof Gtk.SignalAction && action.getSignalName() === "move-focus";
|
|
230
|
+
|
|
231
|
+
const isShortcutLive = (host: Gtk.Widget): boolean => host.isSensitive() && host.getMapped();
|
|
232
|
+
|
|
233
|
+
const didRunShortcutAction = (shortcut: Gtk.Shortcut, host: Gtk.Widget): boolean => {
|
|
234
|
+
const action = shortcut.getAction();
|
|
235
|
+
|
|
236
|
+
if (action === null || isMovingFocus(action) || !isShortcutLive(host)) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return action.activate(0 as Gtk.ShortcutActionFlags, host, shortcut.getArguments());
|
|
241
|
+
};
|
|
242
|
+
|
|
182
243
|
const didActivateShortcut = (
|
|
183
244
|
shortcut: Gtk.Shortcut,
|
|
184
245
|
widget: Gtk.Widget,
|
|
@@ -189,13 +250,7 @@ const didActivateShortcut = (
|
|
|
189
250
|
return false;
|
|
190
251
|
}
|
|
191
252
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (action instanceof Gtk.SignalAction && action.getSignalName() === "move-focus") {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return action?.activate(0 as Gtk.ShortcutActionFlags, widget, shortcut.getArguments()) ?? false;
|
|
253
|
+
return didRunShortcutAction(shortcut, managedShortcutHost(widget, shortcut) ?? widget);
|
|
199
254
|
};
|
|
200
255
|
|
|
201
256
|
const didActivateMatchingShortcut = (
|
|
@@ -221,71 +276,134 @@ const didActivateMatchingShortcut = (
|
|
|
221
276
|
return false;
|
|
222
277
|
};
|
|
223
278
|
|
|
224
|
-
const
|
|
225
|
-
const
|
|
279
|
+
const isAccelMatch = (accel: string, keyval: number, modifiers: Gdk.ModifierType): boolean => {
|
|
280
|
+
const [isParsed, accelKeyval, accelModifiers] = Gtk.acceleratorParse(accel);
|
|
226
281
|
|
|
227
|
-
|
|
228
|
-
|
|
282
|
+
return isParsed && accelKeyval === keyval && accelModifiers === modifiers;
|
|
283
|
+
};
|
|
229
284
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
285
|
+
const hasMatchingAccel = (
|
|
286
|
+
application: Gtk.Application,
|
|
287
|
+
detailedActionName: string,
|
|
288
|
+
keyval: number,
|
|
289
|
+
modifiers: number,
|
|
290
|
+
): boolean =>
|
|
291
|
+
application.getAccelsForAction(detailedActionName).some((accel) => isAccelMatch(accel, keyval, modifiers));
|
|
292
|
+
|
|
293
|
+
const didActivateDetailedAction = (window: Gtk.Window, detailedActionName: string): boolean => {
|
|
294
|
+
const [isParsed, actionName, target] = Gio.Action.parseDetailedName(detailedActionName);
|
|
295
|
+
|
|
296
|
+
return isParsed && window.activateAction(actionName, target);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const didDispatchAccelsOnWindow = (window: Gtk.Window, keyval: number, modifiers: number): boolean => {
|
|
300
|
+
const application = window.getApplication();
|
|
301
|
+
|
|
302
|
+
if (!application) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for (const detailedActionName of application.listActionDescriptions()) {
|
|
307
|
+
if (hasMatchingAccel(application, detailedActionName, keyval, modifiers)) {
|
|
308
|
+
return didActivateDetailedAction(window, detailedActionName);
|
|
235
309
|
}
|
|
236
310
|
}
|
|
237
311
|
|
|
238
312
|
return false;
|
|
239
313
|
};
|
|
240
314
|
|
|
241
|
-
const
|
|
242
|
-
const
|
|
315
|
+
const didDispatchApplicationAccels = (widget: Gtk.Widget, keyval: number, modifiers: number): boolean => {
|
|
316
|
+
const root = widget.getRoot();
|
|
317
|
+
|
|
318
|
+
return root instanceof Gtk.Window && didDispatchAccelsOnWindow(root, keyval, modifiers);
|
|
319
|
+
};
|
|
243
320
|
|
|
244
|
-
|
|
245
|
-
|
|
321
|
+
const isKeyStopController = (controller: Gtk.EventController): controller is KeyStopController => {
|
|
322
|
+
if (controller instanceof Gtk.ShortcutController) {
|
|
323
|
+
return controller.getScope() === Gtk.ShortcutScope.LOCAL;
|
|
246
324
|
}
|
|
247
325
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
326
|
+
return controller instanceof Gtk.EventControllerKey && hasSignalListener(controller, KEY_CONTROLLER_SIGNALS);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const keyStopsInPhase = (widget: Gtk.Widget, phase: Gtk.PropagationPhase): KeyStop[] => {
|
|
330
|
+
const stops: KeyStop[] = [];
|
|
331
|
+
|
|
332
|
+
for (const controller of controllersOn(widget)) {
|
|
333
|
+
if (controller.getPropagationPhase() === phase && isKeyStopController(controller)) {
|
|
334
|
+
stops.push({ widget, controller });
|
|
251
335
|
}
|
|
252
336
|
}
|
|
253
337
|
|
|
254
|
-
return
|
|
338
|
+
return stops;
|
|
255
339
|
};
|
|
256
340
|
|
|
257
|
-
const
|
|
258
|
-
const
|
|
341
|
+
const propagationChain = (widget: Gtk.Widget): Gtk.Widget[] => {
|
|
342
|
+
const delegate = getEditableDelegate(widget);
|
|
343
|
+
const below = delegate === null ? [] : [delegate];
|
|
259
344
|
|
|
260
|
-
|
|
261
|
-
|
|
345
|
+
return [...below, widget, ...ancestors(widget)];
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const keyPropagationChain = (widget: Gtk.Widget): KeyStop[] => {
|
|
349
|
+
const chain = propagationChain(widget);
|
|
350
|
+
const [eventWidget = widget] = chain;
|
|
351
|
+
|
|
352
|
+
return [
|
|
353
|
+
...chain.toReversed().flatMap((current) => keyStopsInPhase(current, Gtk.PropagationPhase.CAPTURE)),
|
|
354
|
+
...keyStopsInPhase(eventWidget, Gtk.PropagationPhase.TARGET),
|
|
355
|
+
...chain.flatMap((current) => keyStopsInPhase(current, Gtk.PropagationPhase.BUBBLE)),
|
|
356
|
+
];
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const didStopHandlePress = (stop: KeyStop, keyval: number, modifiers: Gdk.ModifierType): boolean => {
|
|
360
|
+
const { controller, widget } = stop;
|
|
361
|
+
|
|
362
|
+
if (controller instanceof Gtk.ShortcutController) {
|
|
363
|
+
return didActivateMatchingShortcut(controller, widget, keyval, modifiers);
|
|
262
364
|
}
|
|
365
|
+
|
|
366
|
+
return controller.emit("key-pressed", keyval, 0, modifiers);
|
|
263
367
|
};
|
|
264
368
|
|
|
265
|
-
const
|
|
266
|
-
widget
|
|
267
|
-
|
|
268
|
-
state: UserEventState,
|
|
269
|
-
action: KeyAction,
|
|
270
|
-
): Promise<void> => {
|
|
271
|
-
updateModifierState(state, action);
|
|
272
|
-
const signalName = action.isPress ? "key-pressed" : "key-released";
|
|
369
|
+
const didHandlePress = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): boolean =>
|
|
370
|
+
didDispatchApplicationAccels(widget, keyval, modifiers) ||
|
|
371
|
+
keyPropagationChain(widget).some((stop) => didStopHandlePress(stop, keyval, modifiers));
|
|
273
372
|
|
|
274
|
-
|
|
275
|
-
|
|
373
|
+
const emitKeyReleased = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): void => {
|
|
374
|
+
for (const stop of keyPropagationChain(widget)) {
|
|
375
|
+
if (stop.controller instanceof Gtk.EventControllerKey) {
|
|
376
|
+
stop.controller.emit("key-released", keyval, 0, modifiers);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const applyKeyPress = async (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): Promise<void> => {
|
|
382
|
+
if (didHandlePress(widget, keyval, modifiers)) {
|
|
383
|
+
return;
|
|
276
384
|
}
|
|
277
385
|
|
|
386
|
+
if (keyval === Gdk.KEY_Return && widget instanceof Gtk.Editable) {
|
|
387
|
+
await fireEvent(widget, "activate");
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const applyKeyAction = async (widget: Gtk.Widget, state: UserEventState, action: KeyAction): Promise<void> => {
|
|
392
|
+
updateModifierState(state, action);
|
|
393
|
+
|
|
278
394
|
if (action.isPress) {
|
|
279
|
-
await
|
|
395
|
+
await applyKeyPress(widget, action.keyval, state.modifierState);
|
|
396
|
+
|
|
397
|
+
return;
|
|
280
398
|
}
|
|
399
|
+
|
|
400
|
+
emitKeyReleased(widget, action.keyval, state.modifierState);
|
|
281
401
|
};
|
|
282
402
|
|
|
283
403
|
const keyboard = (state: UserEventState, widget: Gtk.Widget, input: string): Promise<void> =>
|
|
284
404
|
wrapEvent(widget, async () => {
|
|
285
|
-
const controllers = getOrCreateControllers(widget, Gtk.EventControllerKey);
|
|
286
|
-
|
|
287
405
|
for (const action of parseKeyboardInput(input)) {
|
|
288
|
-
await applyKeyAction(widget,
|
|
406
|
+
await applyKeyAction(widget, state, action);
|
|
289
407
|
}
|
|
290
408
|
});
|
|
291
409
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import * as GLib from "@gtkx/gi/glib";
|
|
3
|
+
import { ancestors } from "../traversal.js";
|
|
4
|
+
import { callBooleanGetter, hasWidgetMethod } from "../widget-getters.js";
|
|
5
|
+
|
|
6
|
+
const SELECT_ACTION = "listitem.select";
|
|
7
|
+
const SINGLE_CLICK_ACTIVATE_GETTER = "getSingleClickActivate";
|
|
8
|
+
const DOUBLE_CLICK_PRESS = 2;
|
|
9
|
+
|
|
10
|
+
const viewFor = (row: Gtk.Widget): Gtk.Widget | null => {
|
|
11
|
+
for (const ancestor of ancestors(row)) {
|
|
12
|
+
if (hasWidgetMethod(ancestor, SINGLE_CLICK_ACTIVATE_GETTER)) {
|
|
13
|
+
return ancestor;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const isViewSingleClickActivating = (row: Gtk.Widget): boolean => {
|
|
21
|
+
const view = viewFor(row);
|
|
22
|
+
|
|
23
|
+
return view === null ? false : callBooleanGetter(view, SINGLE_CLICK_ACTIVATE_GETTER) ?? false;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const selectRow = (row: Gtk.Widget): void => {
|
|
27
|
+
row.activateAction(
|
|
28
|
+
SELECT_ACTION,
|
|
29
|
+
GLib.Variant.newTuple([GLib.Variant.newBoolean(false), GLib.Variant.newBoolean(false)]),
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isRowActivatedByPress = (row: Gtk.Widget, press: number): boolean =>
|
|
34
|
+
press === DOUBLE_CLICK_PRESS || isViewSingleClickActivating(row);
|
|
35
|
+
|
|
36
|
+
const applyRowPress = (row: Gtk.Widget, press: number): void => {
|
|
37
|
+
if (row.getFocusOnClick()) {
|
|
38
|
+
row.grabFocus();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
selectRow(row);
|
|
42
|
+
|
|
43
|
+
if (isRowActivatedByPress(row, press)) {
|
|
44
|
+
row.activate();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const applyRowClick = (row: Gtk.Widget, nPress: number): void => {
|
|
49
|
+
for (let press = 1; press <= nPress; press++) {
|
|
50
|
+
applyRowPress(row, press);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { applyRowClick };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { getWidgetTypeName } from "../widget-getters.js";
|
|
3
|
+
import { applyHeaderClick } from "./column-header-click.js";
|
|
4
|
+
import { applyRowClick } from "./list-row-click.js";
|
|
5
|
+
import { applyTabClick, isNotebookTab } from "./notebook-tab-click.js";
|
|
6
|
+
|
|
7
|
+
type NativeClick = (widget: Gtk.Widget, nPress: number) => void;
|
|
8
|
+
|
|
9
|
+
const COLUMN_ROW_TYPE_NAME = "GtkColumnViewRowWidget";
|
|
10
|
+
const ROW_TYPE_NAMES: Set<string> = new Set([COLUMN_ROW_TYPE_NAME, "GtkListItemWidget"]);
|
|
11
|
+
const CELL_TYPE_NAME = "GtkColumnViewCellWidget";
|
|
12
|
+
const TITLE_TYPE_NAME = "GtkColumnViewTitle";
|
|
13
|
+
const TOGGLE_EXPAND_ACTION = "listitem.toggle-expand";
|
|
14
|
+
|
|
15
|
+
const isHeaderRow = (widget: Gtk.Widget): boolean =>
|
|
16
|
+
getWidgetTypeName(widget) === COLUMN_ROW_TYPE_NAME && widget.getParent() instanceof Gtk.ColumnView;
|
|
17
|
+
|
|
18
|
+
const isClickTransparent = (widget: Gtk.Widget): boolean =>
|
|
19
|
+
getWidgetTypeName(widget) === CELL_TYPE_NAME || isHeaderRow(widget);
|
|
20
|
+
|
|
21
|
+
const isExpanderToggling = (widget: Gtk.Widget): boolean =>
|
|
22
|
+
widget instanceof Gtk.TreeExpander &&
|
|
23
|
+
widget.getListRow()?.isExpandable() === true &&
|
|
24
|
+
!widget.getHideExpander();
|
|
25
|
+
|
|
26
|
+
const isColumnHeader = (widget: Gtk.Widget): boolean => getWidgetTypeName(widget) === TITLE_TYPE_NAME;
|
|
27
|
+
const isFactoryRow = (widget: Gtk.Widget): boolean => ROW_TYPE_NAMES.has(getWidgetTypeName(widget) ?? "");
|
|
28
|
+
|
|
29
|
+
const toggleExpander = (widget: Gtk.Widget, nPress: number): void => {
|
|
30
|
+
for (let press = 1; press <= nPress; press++) {
|
|
31
|
+
widget.activateAction(TOGGLE_EXPAND_ACTION, null);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const nativeClickFor = (widget: Gtk.Widget, isClicked: boolean): NativeClick | null => {
|
|
36
|
+
if (isClicked && isExpanderToggling(widget)) {
|
|
37
|
+
return toggleExpander;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isColumnHeader(widget)) {
|
|
41
|
+
return applyHeaderClick;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isNotebookTab(widget)) {
|
|
45
|
+
return applyTabClick;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return isFactoryRow(widget) ? applyRowClick : null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { isClickTransparent, type NativeClick, nativeClickFor };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { ancestorFor } from "../traversal.js";
|
|
3
|
+
|
|
4
|
+
type NotebookTab = { notebook: Gtk.Notebook; index: number };
|
|
5
|
+
|
|
6
|
+
const tabLabelsFor = (notebook: Gtk.Notebook): (Gtk.Widget | null)[] => {
|
|
7
|
+
const labels: (Gtk.Widget | null)[] = [];
|
|
8
|
+
|
|
9
|
+
for (let index = 0; index < notebook.getNPages(); index++) {
|
|
10
|
+
const child = notebook.getNthPage(index);
|
|
11
|
+
labels.push(child === null ? null : notebook.getPage(child).tab);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return labels;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const isNested = (outer: Gtk.Widget, inner: Gtk.Widget): boolean => outer === inner || inner.isAncestor(outer);
|
|
18
|
+
|
|
19
|
+
const isSameTab = (tab: Gtk.Widget, label: Gtk.Widget | null): boolean =>
|
|
20
|
+
label !== null && (isNested(tab, label) || isNested(label, tab));
|
|
21
|
+
|
|
22
|
+
const tabIndexFor = (notebook: Gtk.Notebook, tab: Gtk.Widget): number => {
|
|
23
|
+
const matches = tabLabelsFor(notebook)
|
|
24
|
+
.map((label, index) => (isSameTab(tab, label) ? index : -1))
|
|
25
|
+
.filter((index) => index >= 0);
|
|
26
|
+
|
|
27
|
+
return matches.length === 1 ? matches[0] ?? -1 : -1;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const notebookTabFor = (widget: Gtk.Widget): NotebookTab | null => {
|
|
31
|
+
if (widget.getAccessibleRole() !== Gtk.AccessibleRole.TAB) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const notebook = ancestorFor(widget, Gtk.Notebook);
|
|
36
|
+
const index = notebook === null ? -1 : tabIndexFor(notebook, widget);
|
|
37
|
+
|
|
38
|
+
return notebook === null || index < 0 ? null : { notebook, index };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isNotebookTab = (widget: Gtk.Widget): boolean => notebookTabFor(widget) !== null;
|
|
42
|
+
|
|
43
|
+
const applyTabClick = (widget: Gtk.Widget): void => {
|
|
44
|
+
const tab = notebookTabFor(widget);
|
|
45
|
+
|
|
46
|
+
if (tab === null) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (tab.notebook.getFocusOnClick()) {
|
|
51
|
+
tab.notebook.grabFocus();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
tab.notebook.setCurrentPage(tab.index);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { applyTabClick, isNotebookTab };
|
|
@@ -1,39 +1,78 @@
|
|
|
1
|
-
import * as Gtk from "@gtkx/gi/gtk";
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type { NativeClick } from "./native-click.js";
|
|
2
3
|
import type { UserEventState } from "./state.js";
|
|
3
|
-
import {
|
|
4
|
-
import { getOrCreateControllers } from "./controller.js";
|
|
4
|
+
import { clickGestures, emitClickPhase, getAuthoredClickGestures } from "./click.js";
|
|
5
5
|
import { wrapEvent } from "./event-wrapper.js";
|
|
6
|
+
import { isClickTransparent, nativeClickFor } from "./native-click.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A pointer action token: a full click (`click`, `[MouseLeft]`), a button press (`down`,
|
|
9
10
|
* `[MouseLeft>]`), or a button release (`up`, `[/MouseLeft]`).
|
|
10
11
|
*/
|
|
11
12
|
type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
|
|
13
|
+
type PointerTarget = { widget: Gtk.Widget; gestures: Gtk.GestureClick[]; native: NativeClick | null };
|
|
12
14
|
|
|
13
15
|
const PRESS_INPUTS: Set<PointerInput> = new Set(["[MouseLeft>]", "down"]);
|
|
14
16
|
const RELEASE_INPUTS: Set<PointerInput> = new Set(["[/MouseLeft]", "up"]);
|
|
15
17
|
const CLICK_INPUTS: Set<PointerInput> = new Set(["[MouseLeft]", "click"]);
|
|
16
18
|
|
|
19
|
+
const pointerWidgetFor = (widget: Gtk.Widget): Gtk.Widget => {
|
|
20
|
+
let current = widget;
|
|
21
|
+
|
|
22
|
+
while (isClickTransparent(current)) {
|
|
23
|
+
const parent = current.getParent();
|
|
24
|
+
|
|
25
|
+
if (parent === null) {
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
current = parent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return current;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const pointerGesturesFor = (widget: Gtk.Widget, native: NativeClick | null): Gtk.GestureClick[] =>
|
|
36
|
+
native === null ? clickGestures(widget) : getAuthoredClickGestures(widget);
|
|
37
|
+
|
|
38
|
+
const pointerTargetFor = (widget: Gtk.Widget): PointerTarget => {
|
|
39
|
+
const target = pointerWidgetFor(widget);
|
|
40
|
+
const native = nativeClickFor(target, true);
|
|
41
|
+
|
|
42
|
+
return { widget: target, gestures: pointerGesturesFor(target, native), native };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const pressPointer = ({ widget, gestures }: PointerTarget): void => {
|
|
46
|
+
emitClickPhase(widget, gestures, 1, "pressed");
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const releasePointer = ({ widget, gestures, native }: PointerTarget): void => {
|
|
50
|
+
emitClickPhase(widget, gestures, 1, "released");
|
|
51
|
+
native?.(widget, 1);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const isPointerPressable = (target: PointerTarget): boolean => target.native !== null || target.gestures.length > 0;
|
|
55
|
+
|
|
17
56
|
const applyPointerInput = (widget: Gtk.Widget, state: UserEventState, input: PointerInput): void => {
|
|
18
|
-
const
|
|
57
|
+
const target = pointerTargetFor(widget);
|
|
19
58
|
|
|
20
59
|
if (CLICK_INPUTS.has(input)) {
|
|
21
|
-
|
|
22
|
-
|
|
60
|
+
pressPointer(target);
|
|
61
|
+
releasePointer(target);
|
|
23
62
|
state.isMouseLeftDown = false;
|
|
24
63
|
|
|
25
64
|
return;
|
|
26
65
|
}
|
|
27
66
|
|
|
28
|
-
if (PRESS_INPUTS.has(input) && !state.isMouseLeftDown) {
|
|
29
|
-
|
|
67
|
+
if (PRESS_INPUTS.has(input) && !state.isMouseLeftDown && isPointerPressable(target)) {
|
|
68
|
+
pressPointer(target);
|
|
30
69
|
state.isMouseLeftDown = true;
|
|
31
70
|
|
|
32
71
|
return;
|
|
33
72
|
}
|
|
34
73
|
|
|
35
74
|
if (RELEASE_INPUTS.has(input) && state.isMouseLeftDown) {
|
|
36
|
-
|
|
75
|
+
releasePointer(target);
|
|
37
76
|
state.isMouseLeftDown = false;
|
|
38
77
|
}
|
|
39
78
|
};
|