@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.
- package/README.md +47 -44
- package/dist/accessible-native.d.ts +1 -1
- package/dist/accessible-native.d.ts.map +1 -1
- package/dist/accessible-native.js +29 -8
- package/dist/accessible-native.js.map +1 -1
- package/dist/build-queries.d.ts +9 -9
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +2 -1
- package/dist/build-queries.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -3
- package/dist/config.js.map +1 -1
- package/dist/fire-event.d.ts +3 -3
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +14 -4
- package/dist/fire-event.js.map +1 -1
- package/dist/matchers.d.ts +52 -56
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +4 -16
- package/dist/matchers.js.map +1 -1
- package/dist/queries.d.ts +37 -133
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +48 -136
- package/dist/queries.js.map +1 -1
- package/dist/query-helpers.d.ts +2 -2
- package/dist/query-helpers.d.ts.map +1 -1
- package/dist/query-helpers.js +10 -3
- package/dist/query-helpers.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -0
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +1 -1
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +2 -1
- package/dist/role-helpers.js.map +1 -1
- package/dist/screenshot.d.ts +1 -1
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +26 -8
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +3 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +4 -4
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +25 -7
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +2 -14
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +2 -14
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +20 -45
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +31 -50
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/controller.d.ts +5 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +5 -2
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/gesture.d.ts +9 -43
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +9 -43
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +38 -107
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +29 -30
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts +1 -1
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +24 -3
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts +1 -1
- package/dist/user-event/list-row-click.d.ts.map +1 -1
- package/dist/user-event/list-row-click.js +2 -1
- package/dist/user-event/list-row-click.js.map +1 -1
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +9 -11
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +4 -20
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +11 -23
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/text.d.ts +5 -34
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +5 -34
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +2 -2
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +6 -4
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +4 -3
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +38 -51
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/widget-getters.d.ts +7 -9
- package/dist/widget-getters.d.ts.map +1 -1
- package/dist/widget-getters.js.map +1 -1
- package/dist/widget-target.d.ts +4 -0
- package/dist/widget-target.d.ts.map +1 -0
- package/dist/widget-target.js +9 -0
- package/dist/widget-target.js.map +1 -0
- package/package.json +17 -10
- package/src/accessible-native.ts +45 -9
- package/src/build-queries.ts +60 -30
- package/src/config.ts +1 -3
- package/src/fire-event.ts +32 -8
- package/src/matchers.ts +63 -85
- package/src/queries.ts +94 -156
- package/src/query-helpers.ts +17 -9
- package/src/render.tsx +7 -2
- package/src/role-helpers.ts +3 -1
- package/src/screenshot.ts +49 -8
- package/src/suggestions.ts +3 -1
- package/src/traversal.ts +35 -11
- package/src/types.ts +14 -10
- package/src/user-event/adjustment.ts +2 -14
- package/src/user-event/click.ts +40 -58
- package/src/user-event/controller.ts +8 -5
- package/src/user-event/gesture.ts +9 -43
- package/src/user-event/index.ts +149 -132
- package/src/user-event/keyboard.ts +33 -4
- package/src/user-event/list-row-click.ts +2 -2
- package/src/user-event/pointer.ts +11 -15
- package/src/user-event/selection.ts +15 -25
- package/src/user-event/text.ts +5 -34
- package/src/wait-for.ts +9 -6
- package/src/widget-accessible-properties.ts +61 -69
- package/src/widget-getters.ts +11 -13
- 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.
|
|
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
|
|
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
|
|
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
|
|
74
|
-
const
|
|
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 (
|
|
78
|
-
|
|
92
|
+
if (isMapped) {
|
|
93
|
+
yield widget;
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
|
|
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
|
-
|
|
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*
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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}`]: <
|
|
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.
|
|
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:
|
|
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);
|
package/src/user-event/click.ts
CHANGED
|
@@ -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 (
|
|
107
|
-
return { widget, container:
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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,
|