@gjsify/adwaita-web 0.64.11 → 0.64.12-next.280
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/lib/types/elements/adw-alert-dialog.d.ts +0 -8
- package/lib/types/elements/adw-overlay-split-view.d.ts +5 -0
- package/lib/types/elements/adw-view-stack.d.ts +20 -0
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -0
- package/lib/types/elements/gtk-box.d.ts +16 -0
- package/lib/types/elements/gtk-menu-button.d.ts +5 -0
- package/lib/types/elements/gtk-scrolled-window.d.ts +15 -2
- package/lib/types/elements/gtk-stack.d.ts +17 -0
- package/lib/types/elements/gtk-window.d.ts +14 -8
- package/lib/types/gobject-elements.d.ts +2 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +2 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/namespace/adw.d.ts +1 -1
- package/lib/types/namespace/gdk.d.ts +1 -0
- package/lib/types/namespace/graphene.d.ts +8 -0
- package/lib/types/widget-element.d.ts +25 -0
- package/package.json +13 -12
- package/src/adw-view-switcher-bar.spec.ts +12 -0
- package/src/elements/adw-alert-dialog.ts +0 -8
- package/src/elements/adw-overlay-split-view.ts +17 -0
- package/src/elements/adw-view-stack.ts +50 -0
- package/src/elements/adw-view-switcher-bar.ts +10 -0
- package/src/elements/gtk-box.ts +49 -1
- package/src/elements/gtk-menu-button.ts +17 -0
- package/src/elements/gtk-scrolled-window.ts +60 -15
- package/src/elements/gtk-stack.ts +42 -0
- package/src/elements/gtk-window.ts +43 -10
- package/src/gdk.spec.ts +9 -0
- package/src/gobject-door.spec.ts +77 -0
- package/src/gobject-elements.ts +26 -2
- package/src/gtk-box.spec.ts +38 -0
- package/src/gtk-menu-button.spec.ts +15 -0
- package/src/gtk-stack.spec.ts +26 -0
- package/src/gtk-window.spec.ts +35 -1
- package/src/gtksource/gtk-source-view.ts +18 -3
- package/src/index.ts +2 -0
- package/src/namespace/adw.ts +1 -1
- package/src/namespace/gdk.ts +2 -0
- package/src/namespace/graphene.ts +26 -0
- package/src/split-views.spec.ts +11 -0
- package/src/style-manager.spec.ts +14 -0
- package/src/style-manager.ts +5 -0
- package/src/view-stack.spec.ts +41 -0
- package/src/widget-element.ts +53 -1
- package/src/widget-members.spec.ts +95 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The Graphene half of `@gjsify/adwaita-web`'s vocabulary: `Graphene.Point`, the type
|
|
2
|
+
// `Gtk.Widget.compute_point` takes and returns. The behaviour lives once in `@gjsify/adwaita-core`
|
|
3
|
+
// (`graphene.ts`).
|
|
4
|
+
//
|
|
5
|
+
// `compute_point` is installed on `WidgetElement` HERE rather than in `widget-element.ts`: that file
|
|
6
|
+
// is the helper every element sits one hop below, and a value import of core there would mark every
|
|
7
|
+
// element core-backed. Loading this namespace is what puts the method on a built-in.
|
|
8
|
+
//
|
|
9
|
+
// A TRUE SUBSET: everything else in Graphene is absent, and the `gi://Graphene` arm refuses it by name.
|
|
10
|
+
|
|
11
|
+
import { computePoint, Point } from '@gjsify/adwaita-core';
|
|
12
|
+
|
|
13
|
+
import { WidgetElement } from '../widget-element.js';
|
|
14
|
+
|
|
15
|
+
declare module '../widget-element.js' {
|
|
16
|
+
interface WidgetElement {
|
|
17
|
+
/** `gtk_widget_compute_point`: `[false, (0, 0)]` when the two share no tree. */
|
|
18
|
+
compute_point(target: WidgetElement, point: Point): [boolean, Point];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
WidgetElement.prototype.compute_point = function (this: WidgetElement, target, point) {
|
|
23
|
+
return computePoint(this, target, point);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { Point };
|
package/src/split-views.spec.ts
CHANGED
|
@@ -110,6 +110,17 @@ export const AdwSplitViewsTest = async () => {
|
|
|
110
110
|
host.remove();
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
await it('show_sidebar / get_show_sidebar / set_show_sidebar mirror showSidebar', () => {
|
|
114
|
+
const { view, host } = mount();
|
|
115
|
+
expect(view.get_show_sidebar()).toBe(true);
|
|
116
|
+
view.set_show_sidebar(false);
|
|
117
|
+
expect(view.showSidebar).toBe(false);
|
|
118
|
+
expect(view.show_sidebar).toBe(false);
|
|
119
|
+
view.show_sidebar = true;
|
|
120
|
+
expect(view.showSidebar).toBe(true);
|
|
121
|
+
host.remove();
|
|
122
|
+
});
|
|
123
|
+
|
|
113
124
|
await it('round-trips the property setter through the attribute', () => {
|
|
114
125
|
const { view, host } = mount();
|
|
115
126
|
view.showSidebar = false;
|
|
@@ -39,6 +39,20 @@ export const StyleManagerTest = async () => {
|
|
|
39
39
|
globalThis.matchMedia = real;
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
|
+
await it('high_contrast follows prefers-contrast: more', () => {
|
|
43
|
+
const real = globalThis.matchMedia;
|
|
44
|
+
try {
|
|
45
|
+
globalThis.matchMedia = ((query: string) => ({
|
|
46
|
+
matches: query.includes('prefers-contrast'),
|
|
47
|
+
media: query,
|
|
48
|
+
})) as never;
|
|
49
|
+
expect(webStyleSource.highContrast?.()).toBe(true);
|
|
50
|
+
globalThis.matchMedia = ((query: string) => ({ matches: false, media: query })) as never;
|
|
51
|
+
expect(webStyleSource.highContrast?.()).toBe(false);
|
|
52
|
+
} finally {
|
|
53
|
+
globalThis.matchMedia = real;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
42
56
|
await it('has a vector row for every vector', () => {
|
|
43
57
|
expect(STYLE_MANAGER_VECTORS.length > 0).toBe(true);
|
|
44
58
|
});
|
package/src/style-manager.ts
CHANGED
|
@@ -14,14 +14,19 @@ export const webStyleSource: StyleSource = {
|
|
|
14
14
|
subscribe(changed) {
|
|
15
15
|
if (typeof document === 'undefined') return () => {};
|
|
16
16
|
const media = globalThis.matchMedia?.('(prefers-color-scheme: dark)');
|
|
17
|
+
const contrast = globalThis.matchMedia?.('(prefers-contrast: more)');
|
|
17
18
|
media?.addEventListener('change', changed);
|
|
19
|
+
contrast?.addEventListener('change', changed);
|
|
18
20
|
const observer = new MutationObserver(changed);
|
|
19
21
|
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
20
22
|
return () => {
|
|
21
23
|
media?.removeEventListener('change', changed);
|
|
24
|
+
contrast?.removeEventListener('change', changed);
|
|
22
25
|
observer.disconnect();
|
|
23
26
|
};
|
|
24
27
|
},
|
|
28
|
+
highContrast: () => globalThis.matchMedia?.('(prefers-contrast: more)').matches === true,
|
|
29
|
+
// A page cannot read the desktop's accent (CSS `AccentColor` is uneven, see ADR 0078): no system accent.
|
|
25
30
|
supportsColorSchemes() {
|
|
26
31
|
const matches = (query: string) => globalThis.matchMedia?.(query).matches === true;
|
|
27
32
|
return matches('(prefers-color-scheme: dark)') || matches('(prefers-color-scheme: light)');
|
package/src/view-stack.spec.ts
CHANGED
|
@@ -260,4 +260,45 @@ export const AdwViewStackTest = async () => {
|
|
|
260
260
|
host.remove();
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
+
|
|
264
|
+
await describe('<adw-view-stack> GIR method names', async () => {
|
|
265
|
+
await it('add_titled_with_icon, add_titled and add_named add pages in order', () => {
|
|
266
|
+
const { stack, host } = mountStack([]);
|
|
267
|
+
const [a, b, c] = [1, 2, 3].map(() => document.createElement('div'));
|
|
268
|
+
stack.add_titled_with_icon(a!, 'a', 'A', 'folder-symbolic');
|
|
269
|
+
stack.add_titled(b!, 'b', 'B');
|
|
270
|
+
stack.add_named(c!, 'c');
|
|
271
|
+
expect(stack.pages.map((page) => page.name)).toStrictEqual(['a', 'b', 'c']);
|
|
272
|
+
expect(stack.pages[0]!.icon).toBe('folder');
|
|
273
|
+
expect(stack.pages[1]!.title).toBe('B');
|
|
274
|
+
expect(stack.get_visible_child_name()).toBe('a');
|
|
275
|
+
host.remove();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await it('remove(child) takes the CHILD, and no argument is still ChildNode.remove()', () => {
|
|
279
|
+
const { stack, host } = mountStack([]);
|
|
280
|
+
const [a, b] = [1, 2].map(() => document.createElement('div'));
|
|
281
|
+
stack.add_titled(a!, 'a', 'A');
|
|
282
|
+
stack.add_titled(b!, 'b', 'B');
|
|
283
|
+
stack.remove(b!);
|
|
284
|
+
expect(stack.pages.map((page) => page.name)).toStrictEqual(['a']);
|
|
285
|
+
expect(b!.parentElement).toBe(null);
|
|
286
|
+
stack.remove();
|
|
287
|
+
expect(stack.parentElement).toBe(null);
|
|
288
|
+
host.remove();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await it('get/set_visible_child(_name) select, and get_visible_child_name is null for none', () => {
|
|
292
|
+
const { stack, host } = mountStack([]);
|
|
293
|
+
expect(stack.get_visible_child_name()).toBe(null);
|
|
294
|
+
const [a, b] = [1, 2].map(() => document.createElement('div'));
|
|
295
|
+
stack.add_titled(a!, 'a', 'A');
|
|
296
|
+
stack.add_titled(b!, 'b', 'B');
|
|
297
|
+
stack.set_visible_child_name('b');
|
|
298
|
+
expect(stack.get_visible_child()).toBe(b!);
|
|
299
|
+
stack.set_visible_child(a!);
|
|
300
|
+
expect(stack.get_visible_child_name()).toBe('a');
|
|
301
|
+
host.remove();
|
|
302
|
+
});
|
|
303
|
+
});
|
|
263
304
|
};
|
package/src/widget-element.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// Reference: GTK gtk/gtkwidget.c (gtk_widget_set_visible, gtk_widget_set_tooltip_text)
|
|
17
17
|
// Copyright (c) GNOME contributors (GTK). LGPLv2.1+.
|
|
18
18
|
|
|
19
|
-
import { WIDGET_VISIBLE_GETTERS, notifyElement } from './gobject-elements.js';
|
|
19
|
+
import { WIDGET_VISIBLE_GETTERS, installElementApi, markLazyElementApi, notifyElement } from './gobject-elements.js';
|
|
20
20
|
import { dispatchedSignalsOf, type DispatchedSignals } from './signals.js';
|
|
21
21
|
import { isMapped } from './widget-mapping.js';
|
|
22
22
|
|
|
@@ -41,12 +41,64 @@ export class WidgetElement extends HTMLElement {
|
|
|
41
41
|
set_visible(visible: boolean): void {
|
|
42
42
|
this.visible = visible;
|
|
43
43
|
}
|
|
44
|
+
/** `gtk_widget_get_first_child`: the first child element, `null` for a leaf. */
|
|
45
|
+
get_first_child(): Element | null {
|
|
46
|
+
return this.firstElementChild;
|
|
47
|
+
}
|
|
48
|
+
/** `gtk_widget_get_last_child`. */
|
|
49
|
+
get_last_child(): Element | null {
|
|
50
|
+
return this.lastElementChild;
|
|
51
|
+
}
|
|
52
|
+
/** `gtk_widget_get_next_sibling`. */
|
|
53
|
+
get_next_sibling(): Element | null {
|
|
54
|
+
return this.nextElementSibling;
|
|
55
|
+
}
|
|
56
|
+
/** `gtk_widget_get_prev_sibling`. */
|
|
57
|
+
get_prev_sibling(): Element | null {
|
|
58
|
+
return this.previousElementSibling;
|
|
59
|
+
}
|
|
60
|
+
/** `gtk_widget_get_parent`. */
|
|
61
|
+
get_parent(): Element | null {
|
|
62
|
+
return this.parentElement;
|
|
63
|
+
}
|
|
64
|
+
/** `gtk_widget_get_width`: the layout box in CSS px; 0 until the widget is rendered. */
|
|
65
|
+
get_width(): number {
|
|
66
|
+
return this.offsetWidth;
|
|
67
|
+
}
|
|
68
|
+
/** `gtk_widget_get_height`: the layout box in CSS px; 0 until the widget is rendered. */
|
|
69
|
+
get_height(): number {
|
|
70
|
+
return this.offsetHeight;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The GObject signal API (ADR 0096 § 3), over the events the element declares in `static signals`:
|
|
74
|
+
* `connect` refuses a signal it does not declare, `disconnect` throws for an id that is not
|
|
75
|
+
* connected. Each of the four installs the real ones on the instance and repeats itself there
|
|
76
|
+
* (`installElementApi`), so a bare `document.createElement()` answers like a template's child
|
|
77
|
+
* and an element nobody connects to pays nothing.
|
|
78
|
+
*/
|
|
79
|
+
connect(signal: string, handler: (emitter: this, ...args: any[]) => unknown): number {
|
|
80
|
+
installElementApi(this);
|
|
81
|
+
return this.connect(signal, handler);
|
|
82
|
+
}
|
|
83
|
+
disconnect(id: number): void {
|
|
84
|
+
installElementApi(this);
|
|
85
|
+
this.disconnect(id);
|
|
86
|
+
}
|
|
87
|
+
emit(signal: string, ...args: unknown[]): void {
|
|
88
|
+
installElementApi(this);
|
|
89
|
+
this.emit(signal, ...args);
|
|
90
|
+
}
|
|
91
|
+
notify(name: string): void {
|
|
92
|
+
installElementApi(this);
|
|
93
|
+
this.notify(name);
|
|
94
|
+
}
|
|
44
95
|
/** `gtk_widget_get_mapped`: on screen, which is in the document and rendered. */
|
|
45
96
|
get_mapped(): boolean {
|
|
46
97
|
return isMapped(this);
|
|
47
98
|
}
|
|
48
99
|
}
|
|
49
100
|
|
|
101
|
+
markLazyElementApi(WidgetElement.prototype);
|
|
50
102
|
WIDGET_VISIBLE_GETTERS.add(Object.getOwnPropertyDescriptor(WidgetElement.prototype, 'visible')!.get!);
|
|
51
103
|
|
|
52
104
|
export class TooltipElement extends WidgetElement {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { describe, expect, it } from '@gjsify/unit';
|
|
5
5
|
|
|
6
|
+
import * as Graphene from './namespace/graphene.js';
|
|
7
|
+
import * as Gtk from './namespace/gtk.js';
|
|
6
8
|
import * as GObject from './namespace/gobject.js';
|
|
7
9
|
import * as Gio from './namespace/gio.js';
|
|
8
10
|
import { writeProp } from './shared-tree-builder.js';
|
|
@@ -16,6 +18,9 @@ interface WidgetLike extends HTMLElement {
|
|
|
16
18
|
get_next_sibling(): Element | null;
|
|
17
19
|
get_prev_sibling(): Element | null;
|
|
18
20
|
get_parent(): Element | null;
|
|
21
|
+
get_width(): number;
|
|
22
|
+
get_height(): number;
|
|
23
|
+
compute_point(target: Element, point: Graphene.Point): [boolean, Graphene.Point];
|
|
19
24
|
insert_action_group(name: string, group: unknown): void;
|
|
20
25
|
}
|
|
21
26
|
|
|
@@ -68,6 +73,21 @@ export const WidgetMembersTest = async () => {
|
|
|
68
73
|
expect(parent.get_parent()).toBe(null);
|
|
69
74
|
});
|
|
70
75
|
|
|
76
|
+
await it('walks the element tree of a built-in widget with the GTK link names', () => {
|
|
77
|
+
const box = new Gtk.Box() as unknown as WidgetLike;
|
|
78
|
+
const a = new Gtk.Label() as unknown as WidgetLike;
|
|
79
|
+
const b = new Gtk.Label() as unknown as WidgetLike;
|
|
80
|
+
expect(box.get_first_child()).toBe(null);
|
|
81
|
+
box.append(a, b);
|
|
82
|
+
expect(box.get_first_child()).toBe(a);
|
|
83
|
+
expect(box.get_last_child()).toBe(b);
|
|
84
|
+
expect(a.get_next_sibling()).toBe(b);
|
|
85
|
+
expect(b.get_prev_sibling()).toBe(a);
|
|
86
|
+
expect(b.get_next_sibling()).toBe(null);
|
|
87
|
+
expect(a.get_parent()).toBe(box);
|
|
88
|
+
expect(box.get_parent()).toBe(null);
|
|
89
|
+
});
|
|
90
|
+
|
|
71
91
|
await it('adds, tests and removes a CSS class with the GTK names', () => {
|
|
72
92
|
const widget = make('WidgetMembersCss') as unknown as {
|
|
73
93
|
add_css_class(c: string): void;
|
|
@@ -80,6 +100,81 @@ export const WidgetMembersTest = async () => {
|
|
|
80
100
|
expect(widget.has_css_class('dim-label')).toBe(false);
|
|
81
101
|
});
|
|
82
102
|
|
|
103
|
+
await it('computes a point from the bounding boxes, on a registered class and a built-in element', () => {
|
|
104
|
+
const placed = <T extends Element>(element: T, left: number, top: number): T => {
|
|
105
|
+
element.getBoundingClientRect = () => ({ left, top }) as DOMRect;
|
|
106
|
+
return element;
|
|
107
|
+
};
|
|
108
|
+
const scrolled = placed(new Gtk.ScrolledWindow(), 10, 100);
|
|
109
|
+
const registered = placed(make('WidgetMembersPoint'), 14, 130);
|
|
110
|
+
const builtIn = placed(new Gtk.Label(), 11, 105);
|
|
111
|
+
scrolled.append(registered, builtIn);
|
|
112
|
+
document.body.append(scrolled);
|
|
113
|
+
|
|
114
|
+
const [ok, point] = registered.compute_point(scrolled, Graphene.Point.zero());
|
|
115
|
+
expect(ok).toBe(true);
|
|
116
|
+
expect(point.x).toBe(4);
|
|
117
|
+
expect(point.y).toBe(30);
|
|
118
|
+
const [okBuiltIn, shifted] = builtIn.compute_point(scrolled, new Graphene.Point({ x: 2, y: 3 }));
|
|
119
|
+
expect(okBuiltIn).toBe(true);
|
|
120
|
+
expect(shifted.x).toBe(3);
|
|
121
|
+
expect(shifted.y).toBe(8);
|
|
122
|
+
scrolled.remove();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await it('measures a real layout: size, offset inside a scrolled window, and the offset after scrolling', async () => {
|
|
126
|
+
const host = document.createElement('div');
|
|
127
|
+
host.style.width = '200px';
|
|
128
|
+
host.style.height = '100px';
|
|
129
|
+
document.body.append(host);
|
|
130
|
+
const scrolled = new Gtk.ScrolledWindow();
|
|
131
|
+
scrolled.style.height = '100px';
|
|
132
|
+
host.append(scrolled);
|
|
133
|
+
const labels: Gtk.Label[] = [];
|
|
134
|
+
for (let i = 0; i < 10; i++) {
|
|
135
|
+
const label = new Gtk.Label();
|
|
136
|
+
// An empty label has no content for the viewport to overflow, so Firefox does not scroll it.
|
|
137
|
+
label.setAttribute('label', `Row ${i}`);
|
|
138
|
+
label.style.height = '40px';
|
|
139
|
+
scrolled.append(label);
|
|
140
|
+
labels.push(label);
|
|
141
|
+
}
|
|
142
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
143
|
+
|
|
144
|
+
expect(scrolled.get_height()).toBe(100);
|
|
145
|
+
expect(scrolled.get_width()).toBe(200);
|
|
146
|
+
expect(labels[0].get_height()).toBe(40);
|
|
147
|
+
|
|
148
|
+
const label = labels[5];
|
|
149
|
+
const [ok, before] = label.compute_point(scrolled, Graphene.Point.zero());
|
|
150
|
+
expect(ok).toBe(true);
|
|
151
|
+
scrolled.viewport.scrollAxis('vertical', 80);
|
|
152
|
+
const [okAfter, after] = label.compute_point(scrolled, Graphene.Point.zero());
|
|
153
|
+
expect(okAfter).toBe(true);
|
|
154
|
+
expect(before.y - after.y).toBe(80);
|
|
155
|
+
host.remove();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await it('reports 0 for width and height while a widget is not rendered', () => {
|
|
159
|
+
const loose = new Gtk.Label();
|
|
160
|
+
expect(loose.get_width()).toBe(0);
|
|
161
|
+
expect(loose.get_height()).toBe(0);
|
|
162
|
+
const registered = make('WidgetMembersSize');
|
|
163
|
+
expect(registered.get_width()).toBe(0);
|
|
164
|
+
expect(registered.get_height()).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await it('computes no point between widgets that share no tree', () => {
|
|
168
|
+
const attached = make('WidgetMembersPointAttached');
|
|
169
|
+
const loose = make('WidgetMembersPointLoose');
|
|
170
|
+
document.body.append(attached);
|
|
171
|
+
const [ok, point] = loose.compute_point(attached, new Graphene.Point({ x: 5, y: 5 }));
|
|
172
|
+
expect(ok).toBe(false);
|
|
173
|
+
expect(point.x).toBe(0);
|
|
174
|
+
expect(point.y).toBe(0);
|
|
175
|
+
attached.remove();
|
|
176
|
+
});
|
|
177
|
+
|
|
83
178
|
await it('takes an action group and drops it again with null', () => {
|
|
84
179
|
const widget = make('WidgetMembersActions');
|
|
85
180
|
const group = new Gio.SimpleActionGroup();
|