@gjsify/adwaita-web 0.56.0 → 0.58.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/dist/adwaita-web.css +8 -2
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/application.d.ts +24 -0
- package/lib/types/application.spec.d.ts +1 -0
- package/lib/types/elements/adw-application-window.d.ts +2 -0
- package/lib/types/elements/adw-view-stack.d.ts +3 -0
- package/lib/types/elements/checks.d.ts +6 -0
- package/lib/types/elements/gtk-application-window.d.ts +2 -0
- package/lib/types/elements/gtk-drop-down.d.ts +3 -0
- package/lib/types/elements/gtk-entry.d.ts +10 -0
- package/lib/types/elements/gtk-menu-button.d.ts +2 -0
- package/lib/types/elements/gtk-scrolled-window.d.ts +16 -0
- package/lib/types/elements/gtk-stack.d.ts +3 -0
- package/lib/types/elements/gtk-switch.d.ts +3 -0
- package/lib/types/gio.spec.d.ts +1 -0
- package/lib/types/glib.spec.d.ts +1 -0
- package/lib/types/gobject-door.d.ts +4 -0
- package/lib/types/gobject-door.spec.d.ts +1 -0
- package/lib/types/gobject-elements.d.ts +17 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +78 -0
- package/lib/types/gtksource/gtk-source-view.spec.d.ts +2 -0
- package/lib/types/gtksource/web-editor-driver.d.ts +53 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/menu-actions.spec.d.ts +1 -0
- package/lib/types/namespace/adw.d.ts +2 -0
- package/lib/types/namespace/gio.d.ts +2 -0
- package/lib/types/namespace/glib.d.ts +15 -0
- package/lib/types/namespace/gobject.d.ts +28 -0
- package/lib/types/namespace/gtk-enums.d.ts +22 -0
- package/lib/types/namespace/gtk.d.ts +2 -0
- package/lib/types/namespace/gtksource.d.ts +2 -0
- package/lib/types/shared-tree-builder.d.ts +20 -1
- package/lib/types/style-manager.d.ts +5 -0
- package/lib/types/style-manager.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +11 -9
- package/scss/_reset.scss +1 -1
- package/src/application.spec.ts +75 -0
- package/src/application.ts +92 -0
- package/src/construct-vectors.spec.ts +155 -0
- package/src/elements/adw-application-window.ts +5 -0
- package/src/elements/adw-split-button.ts +5 -1
- package/src/elements/adw-view-stack.ts +14 -0
- package/src/elements/checks.ts +25 -1
- package/src/elements/gtk-application-window.ts +5 -0
- package/src/elements/gtk-drop-down.ts +16 -0
- package/src/elements/gtk-entry.ts +45 -7
- package/src/elements/gtk-menu-button.ts +12 -1
- package/src/elements/gtk-popover-menu.ts +7 -2
- package/src/elements/gtk-scrolled-window.ts +58 -0
- package/src/elements/gtk-stack.ts +8 -0
- package/src/elements/gtk-switch.ts +8 -0
- package/src/gio.spec.ts +23 -0
- package/src/glib.spec.ts +49 -0
- package/src/gobject-door.spec.ts +119 -0
- package/src/gobject-door.ts +137 -0
- package/src/gobject-elements.ts +172 -0
- package/src/gtksource/gtk-source-view.spec.ts +141 -0
- package/src/gtksource/gtk-source-view.ts +386 -0
- package/src/gtksource/web-editor-driver.ts +346 -0
- package/src/index.ts +10 -0
- package/src/menu-actions.spec.ts +47 -0
- package/src/namespace/adw.ts +2 -0
- package/src/namespace/gio.ts +3 -1
- package/src/namespace/glib.ts +37 -0
- package/src/namespace/gobject.ts +38 -0
- package/src/namespace/gtk-enums.ts +15 -0
- package/src/namespace/gtk.ts +2 -0
- package/src/namespace/gtksource.ts +20 -0
- package/src/shared-tree-builder.ts +142 -7
- package/src/split-button.spec.ts +2 -0
- package/src/style-manager.spec.ts +36 -0
- package/src/style-manager.ts +27 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
40
|
"clear": "gjsify clear lib dist tsconfig.tsbuildinfo scss/_icons.generated.scss src/styles.generated.ts",
|
|
41
|
-
"check": "gjsify run build:scss && gjsify tsc --noEmit",
|
|
41
|
+
"check": "gjsify run build:scss && gjsify tsc --noEmit && gjsify run check:consumer",
|
|
42
|
+
"check:consumer": "gjsify tsc -p tsconfig.consumer.json",
|
|
42
43
|
"build": "gjsify run build:scss && gjsify run build:types",
|
|
43
44
|
"build:scss": "node scripts/build-scss.mjs",
|
|
44
45
|
"build:types": "gjsify tsc",
|
|
@@ -49,16 +50,17 @@
|
|
|
49
50
|
"@codemirror/language": "^6.12.4",
|
|
50
51
|
"@codemirror/state": "^6.7.0",
|
|
51
52
|
"@codemirror/view": "^6.43.4",
|
|
52
|
-
"@gjsify/adwaita-core": "^0.
|
|
53
|
-
"@gjsify/adwaita-fonts": "^0.
|
|
54
|
-
"@gjsify/adwaita-icons": "^0.
|
|
53
|
+
"@gjsify/adwaita-core": "^0.58.0",
|
|
54
|
+
"@gjsify/adwaita-fonts": "^0.58.0",
|
|
55
|
+
"@gjsify/adwaita-icons": "^0.58.0",
|
|
56
|
+
"@gjsify/gtksource-core": "^0.58.0",
|
|
55
57
|
"@lezer/highlight": "^1.2.3"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
58
|
-
"@gjsify/blueprint": "^0.
|
|
59
|
-
"@gjsify/cli": "^0.
|
|
60
|
-
"@gjsify/unit": "^0.
|
|
61
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
60
|
+
"@gjsify/blueprint": "^0.58.0",
|
|
61
|
+
"@gjsify/cli": "^0.58.0",
|
|
62
|
+
"@gjsify/unit": "^0.58.0",
|
|
63
|
+
"@gjsify/vite-plugin-blueprint": "^0.58.0",
|
|
62
64
|
"sass": "^1.101.0",
|
|
63
65
|
"typescript": "^6.0.3"
|
|
64
66
|
},
|
package/scss/_reset.scss
CHANGED
|
@@ -47,7 +47,7 @@ $adw-components:
|
|
|
47
47
|
gtk-gl-area, gtk-graphics-offload, gtk-grid, gtk-grid-view, gtk-header-bar, gtk-image, gtk-inscription, gtk-label, gtk-level-bar,
|
|
48
48
|
gtk-link-button, gtk-list-box, gtk-list-box-row, gtk-list-view, gtk-media-controls, gtk-menu-button, gtk-overlay, gtk-page-setup-unix-dialog, gtk-paned,
|
|
49
49
|
gtk-password-entry, gtk-picture, gtk-popover, gtk-popover-bin, gtk-popover-menu, gtk-popover-menu-bar, gtk-print-unix-dialog,
|
|
50
|
-
gtk-progress-bar, gtk-revealer, gtk-scale, gtk-scale-button, gtk-scrollbar, gtk-scrolled-window, gtk-search-bar, gtk-search-entry, gtk-separator,
|
|
50
|
+
gtk-progress-bar, gtk-revealer, gtk-scale, gtk-scale-button, gtk-scrollbar, gtk-scrolled-window, gtk-search-bar, gtk-search-entry, gtk-separator, gtk-source-view,
|
|
51
51
|
gtk-spin-button, gtk-spinner, gtk-stack, gtk-stack-sidebar, gtk-stack-switcher, gtk-switch, gtk-text, gtk-text-view, gtk-toggle-button, gtk-tree-expander, gtk-video, gtk-viewport,
|
|
52
52
|
gtk-window, gtk-window-controls, gtk-window-handle;
|
|
53
53
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// ADR 0098: the web port of `Gtk.Application`, held to the shared vectors (real GJS is their oracle,
|
|
2
|
+
// in adwaita-core's `application.gjs.spec.ts`) through real custom elements.
|
|
3
|
+
import {
|
|
4
|
+
APPLICATION_VECTORS,
|
|
5
|
+
driveApplicationVectors,
|
|
6
|
+
unprovenVfuncs,
|
|
7
|
+
type ApplicationScene,
|
|
8
|
+
type ApplicationSubject,
|
|
9
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
10
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
11
|
+
|
|
12
|
+
import { activateWidgetAction } from './application.js';
|
|
13
|
+
import { Adw, Gio, GObject, Gtk } from './index.js';
|
|
14
|
+
import type { GtkMenuButton } from './elements/gtk-menu-button.js';
|
|
15
|
+
|
|
16
|
+
const place = <T extends Element>(parent: Element, child: T): T => {
|
|
17
|
+
parent.append(child);
|
|
18
|
+
return child;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const made: Element[] = [];
|
|
22
|
+
|
|
23
|
+
const scene: ApplicationScene = {
|
|
24
|
+
window(app) {
|
|
25
|
+
const window = document.createElement('adw-application-window') as Adw.ApplicationWindow;
|
|
26
|
+
window.application = app;
|
|
27
|
+
document.body.append(window);
|
|
28
|
+
made.push(window);
|
|
29
|
+
return window;
|
|
30
|
+
},
|
|
31
|
+
buttonIn: (window) => place(window as Element, document.createElement('gtk-button')),
|
|
32
|
+
popoverButtonIn(window) {
|
|
33
|
+
const menuButton = place(window as Element, document.createElement('gtk-menu-button')) as GtkMenuButton;
|
|
34
|
+
const popover = menuButton.querySelector('gtk-popover') as Element;
|
|
35
|
+
return place(popover, document.createElement('gtk-button'));
|
|
36
|
+
},
|
|
37
|
+
loose: () => document.createElement('gtk-button'),
|
|
38
|
+
activate: (widget, name) => activateWidgetAction(widget as Element, name),
|
|
39
|
+
addWindowAction(window, action) {
|
|
40
|
+
(window as unknown as { add_action(a: object): void }).add_action(action);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const subject: ApplicationSubject = {
|
|
45
|
+
name: 'adwaita-web',
|
|
46
|
+
isOracle: false,
|
|
47
|
+
GObject: GObject as unknown as ApplicationSubject['GObject'],
|
|
48
|
+
Application: Gtk.Application as unknown as ApplicationSubject['Application'],
|
|
49
|
+
SimpleAction: Gio.SimpleAction as unknown as ApplicationSubject['SimpleAction'],
|
|
50
|
+
handlesOpen: Gio.ApplicationFlags.HANDLES_OPEN,
|
|
51
|
+
scene,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const ApplicationTest = async () => {
|
|
55
|
+
await driveApplicationVectors(subject, { describe, it, expect });
|
|
56
|
+
// The page is shared with every other suite: a window left in it moves their layout.
|
|
57
|
+
for (const element of made.splice(0)) element.remove();
|
|
58
|
+
|
|
59
|
+
await describe('adwaita-web: Application (ADR 0098 § 3)', async () => {
|
|
60
|
+
await it('has a vector for every unlocked vfunc', () => {
|
|
61
|
+
expect(unprovenVfuncs(APPLICATION_VECTORS, 'Gio.').length).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
await it('Adw.Application is a Gtk.Application', () => {
|
|
64
|
+
expect(Adw.Application.prototype instanceof Gtk.Application).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
await it('quit() takes the windows it added off the page', async () => {
|
|
67
|
+
const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebQuit' });
|
|
68
|
+
const window = scene.window(app as never) as Element;
|
|
69
|
+
app.connect('activate', () => app.quit());
|
|
70
|
+
await app.runAsync([]);
|
|
71
|
+
expect(window.isConnected).toBe(false);
|
|
72
|
+
made.length = 0;
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// `Gio.Application`, `Gtk.Application` and `Adw.Application` on the browser (ADR 0098 §§ 1, 3).
|
|
2
|
+
//
|
|
3
|
+
// The behaviour is the core's (`@gjsify/adwaita-core`); this file is the HOST: when the platform is
|
|
4
|
+
// ready (`DOMContentLoaded`), how a window leaves it, and the walk that resolves `app.` / `win.`
|
|
5
|
+
// from an element. A web page has no process, so `quit()` removes the windows the application
|
|
6
|
+
// added and the tab stays.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
GioApplicationBase,
|
|
10
|
+
withGtkApplication,
|
|
11
|
+
activateWidgetAction as activateIn,
|
|
12
|
+
activateMenuItem,
|
|
13
|
+
applicationOfWindow,
|
|
14
|
+
menuActionsFor,
|
|
15
|
+
installActionMap,
|
|
16
|
+
type AdwMenuItem,
|
|
17
|
+
type AdwMenuActions,
|
|
18
|
+
type AdwMenuModel,
|
|
19
|
+
type ApplicationHost,
|
|
20
|
+
} from '@gjsify/adwaita-core';
|
|
21
|
+
|
|
22
|
+
export const webHost: ApplicationHost = {
|
|
23
|
+
name: 'adwaita-web',
|
|
24
|
+
requiresWindow: false,
|
|
25
|
+
start(body) {
|
|
26
|
+
return new Promise<void>((resolve) => {
|
|
27
|
+
const go = (): void => {
|
|
28
|
+
body();
|
|
29
|
+
resolve();
|
|
30
|
+
};
|
|
31
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', go, { once: true });
|
|
32
|
+
else go();
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
detach(window) {
|
|
36
|
+
(window as Element).remove();
|
|
37
|
+
},
|
|
38
|
+
exit() {},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** An element's parent for action resolution; a popover is rooted at the widget that owns it. */
|
|
42
|
+
export function parentOfElement(node: object): object | null {
|
|
43
|
+
if (!(node instanceof Element)) return null;
|
|
44
|
+
const anchor = (node as { anchor?: unknown }).anchor;
|
|
45
|
+
return anchor instanceof Element ? anchor : node.parentElement;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What a `Gtk.Actionable` does on activation: the action `prefix.name` found from `widget`. */
|
|
49
|
+
export function activateWidgetAction(widget: Element, fullName: string, parameter: unknown = null): boolean {
|
|
50
|
+
return activateIn(widget, fullName, parentOfElement, parameter);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What the items of `model` read from the actions registered above `el` (ADR 0097 § 3). */
|
|
54
|
+
export function menuActionsOfElement(el: Element, model: AdwMenuModel): AdwMenuActions {
|
|
55
|
+
return menuActionsFor(model, el, parentOfElement);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What choosing `item` in a menu owned by `el` does: the action it names fires. */
|
|
59
|
+
export function activateElementMenuItem(el: Element, item: AdwMenuItem): boolean {
|
|
60
|
+
return activateMenuItem(item, el, parentOfElement);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class GioApplication extends GioApplicationBase {
|
|
64
|
+
static override host = webHost;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class GtkApplication extends withGtkApplication(GioApplication) {}
|
|
68
|
+
|
|
69
|
+
export class AdwApplication extends GtkApplication {}
|
|
70
|
+
|
|
71
|
+
type WindowApplication = { add_window(window: object): void; remove_window(window: object): void };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `Gtk.Window:application` and the window's `Gio.ActionMap` (`win.` actions) on an element class.
|
|
75
|
+
* Setting the property is `add_window`, as on GTK; setting it to `null` is `remove_window`.
|
|
76
|
+
*/
|
|
77
|
+
export function defineWindowApplication(proto: object): void {
|
|
78
|
+
installActionMap(proto);
|
|
79
|
+
Object.defineProperty(proto, 'application', {
|
|
80
|
+
configurable: true,
|
|
81
|
+
enumerable: true,
|
|
82
|
+
get(this: object) {
|
|
83
|
+
return applicationOfWindow(this);
|
|
84
|
+
},
|
|
85
|
+
set(this: object, value: WindowApplication | null) {
|
|
86
|
+
const current = applicationOfWindow(this) as unknown as WindowApplication | null;
|
|
87
|
+
if (current === value) return;
|
|
88
|
+
current?.remove_window(this);
|
|
89
|
+
value?.add_window(this);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -138,6 +138,161 @@ export const AdwConstructVectorsTest = async () => {
|
|
|
138
138
|
'plain handlers only',
|
|
139
139
|
);
|
|
140
140
|
});
|
|
141
|
+
await it('runs a CheckButton `toggled` handler on a click and on a programmatic change', () => {
|
|
142
|
+
let calls = 0;
|
|
143
|
+
const { root, unmount } = mountSharedTree(
|
|
144
|
+
{
|
|
145
|
+
tag: 'GtkBox',
|
|
146
|
+
children: [
|
|
147
|
+
{ tag: 'GtkCheckButton', id: 'box', signals: [{ name: 'toggled', handler: 'onToggled' }] },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{ scope: { onToggled: () => calls++ } },
|
|
151
|
+
);
|
|
152
|
+
try {
|
|
153
|
+
const box = root.querySelector('#box') as unknown as HTMLElement & { active: boolean };
|
|
154
|
+
box.click();
|
|
155
|
+
expect(box.active).toBe(true);
|
|
156
|
+
box.active = false;
|
|
157
|
+
expect(calls).toBe(2);
|
|
158
|
+
} finally {
|
|
159
|
+
unmount();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
await it('makes a bind follow a CheckButton `active`', () => {
|
|
163
|
+
const { root, unmount } = mountSharedTree({
|
|
164
|
+
tag: 'GtkBox',
|
|
165
|
+
children: [
|
|
166
|
+
{ tag: 'GtkCheckButton', id: 'box' },
|
|
167
|
+
{
|
|
168
|
+
tag: 'GtkToggleButton',
|
|
169
|
+
id: 'target',
|
|
170
|
+
bindings: { active: { source: 'box', property: 'active' } },
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
const target = root.querySelector('#target') as unknown as { active: boolean };
|
|
176
|
+
expect(target.active).toBe(false);
|
|
177
|
+
(root.querySelector('#box') as HTMLElement).click();
|
|
178
|
+
expect(target.active).toBe(true);
|
|
179
|
+
} finally {
|
|
180
|
+
unmount();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await it('makes a bind follow a Switch `active`', () => {
|
|
184
|
+
const { root, unmount } = mountSharedTree({
|
|
185
|
+
tag: 'GtkBox',
|
|
186
|
+
children: [
|
|
187
|
+
{ tag: 'GtkSwitch', id: 'source' },
|
|
188
|
+
{ tag: 'GtkSwitch', id: 'target', bindings: { active: { source: 'source', property: 'active' } } },
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
try {
|
|
192
|
+
const target = root.querySelector('#target') as unknown as { active: boolean };
|
|
193
|
+
(root.querySelector('#source') as unknown as { active: boolean }).active = true;
|
|
194
|
+
expect(target.active).toBe(true);
|
|
195
|
+
} finally {
|
|
196
|
+
unmount();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
await it('runs an Entry `changed` and `notify::text` handler on typing and on a programmatic set', () => {
|
|
200
|
+
const seen: string[] = [];
|
|
201
|
+
const { root, unmount } = mountSharedTree(
|
|
202
|
+
{
|
|
203
|
+
tag: 'GtkBox',
|
|
204
|
+
children: [
|
|
205
|
+
{
|
|
206
|
+
tag: 'GtkEntry',
|
|
207
|
+
id: 'entry',
|
|
208
|
+
signals: [
|
|
209
|
+
{ name: 'changed', handler: 'onChanged' },
|
|
210
|
+
{ name: 'notify::text', handler: 'onText' },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{ scope: { onChanged: () => seen.push('changed'), onText: () => seen.push('notify::text') } },
|
|
216
|
+
);
|
|
217
|
+
try {
|
|
218
|
+
const entry = root.querySelector('#entry') as unknown as HTMLElement & {
|
|
219
|
+
text: string;
|
|
220
|
+
input: HTMLInputElement;
|
|
221
|
+
};
|
|
222
|
+
entry.input.value = 'typed';
|
|
223
|
+
entry.input.dispatchEvent(new Event('input'));
|
|
224
|
+
entry.text = 'set';
|
|
225
|
+
entry.text = 'set';
|
|
226
|
+
expect(seen.join()).toBe('changed,notify::text,changed,notify::text');
|
|
227
|
+
} finally {
|
|
228
|
+
unmount();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
await it('dispatches ViewStack `notify::visible-child-name` when the visible page changes', () => {
|
|
232
|
+
const stack = document.createElement('adw-view-stack') as HTMLElement & {
|
|
233
|
+
addTitled(c: HTMLElement, name: string, title: string): unknown;
|
|
234
|
+
visibleChildName: string;
|
|
235
|
+
};
|
|
236
|
+
const names: string[] = [];
|
|
237
|
+
stack.addEventListener('notify::visible-child-name', (e) =>
|
|
238
|
+
names.push((e as CustomEvent).detail['visible-child-name']),
|
|
239
|
+
);
|
|
240
|
+
document.body.append(stack);
|
|
241
|
+
try {
|
|
242
|
+
stack.addTitled(document.createElement('div'), 'a', 'A');
|
|
243
|
+
stack.addTitled(document.createElement('div'), 'b', 'B');
|
|
244
|
+
stack.visibleChildName = 'b';
|
|
245
|
+
expect(names.join()).toBe('a,b');
|
|
246
|
+
} finally {
|
|
247
|
+
stack.remove();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
await it('dispatches DropDown `notify::selected-item` after `notify::selected`, on a real change only', () => {
|
|
251
|
+
const dropDown = document.createElement('gtk-drop-down') as HTMLElement & {
|
|
252
|
+
model: string[];
|
|
253
|
+
selected: number;
|
|
254
|
+
};
|
|
255
|
+
const seen: string[] = [];
|
|
256
|
+
for (const name of ['notify::selected', 'notify::selected-item'])
|
|
257
|
+
dropDown.addEventListener(name, () => seen.push(name));
|
|
258
|
+
document.body.append(dropDown);
|
|
259
|
+
try {
|
|
260
|
+
dropDown.model = ['a', 'b'];
|
|
261
|
+
seen.length = 0;
|
|
262
|
+
dropDown.selected = 1;
|
|
263
|
+
dropDown.selected = 1;
|
|
264
|
+
expect(seen.join()).toBe('notify::selected,notify::selected-item');
|
|
265
|
+
} finally {
|
|
266
|
+
dropDown.remove();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
await it('dispatches Stack `notify::visible-child` before `notify::visible-child-name`', () => {
|
|
270
|
+
const stack = document.createElement('gtk-stack') as HTMLElement & { visibleChildName: string | null };
|
|
271
|
+
const a = document.createElement('div');
|
|
272
|
+
a.setAttribute('name', 'a');
|
|
273
|
+
const b = document.createElement('div');
|
|
274
|
+
b.setAttribute('name', 'b');
|
|
275
|
+
stack.append(a, b);
|
|
276
|
+
document.body.append(stack);
|
|
277
|
+
try {
|
|
278
|
+
const seen: string[] = [];
|
|
279
|
+
for (const name of ['notify::visible-child', 'notify::visible-child-name'])
|
|
280
|
+
stack.addEventListener(name, () => seen.push(name));
|
|
281
|
+
stack.visibleChildName = 'b';
|
|
282
|
+
expect(seen.join()).toBe('notify::visible-child,notify::visible-child-name');
|
|
283
|
+
} finally {
|
|
284
|
+
stack.remove();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
await it('declares the notify each of DropDown, Stack and Switch dispatches', () => {
|
|
288
|
+
const declared = (tag: string) =>
|
|
289
|
+
(customElements.get(tag) as unknown as { signals: Record<string, string> }).signals;
|
|
290
|
+
expect(declared('gtk-drop-down')['notify::selected']).toBe('notify::selected');
|
|
291
|
+
expect(declared('gtk-drop-down')['notify::selected-item']).toBe('notify::selected-item');
|
|
292
|
+
expect(declared('gtk-stack')['notify::visible-child']).toBe('notify::visible-child');
|
|
293
|
+
expect(declared('gtk-stack')['notify::visible-child-name']).toBe('notify::visible-child-name');
|
|
294
|
+
expect(declared('gtk-switch')['notify::active']).toBe('notify::active');
|
|
295
|
+
});
|
|
141
296
|
await it('refuses a bind to an id nothing has, to a source that dispatches no notify, and the flags', () => {
|
|
142
297
|
const bound = (binding: NonNullable<SharedTreeNode['bindings']>[string], sourceTag = 'GtkToggleButton') =>
|
|
143
298
|
({
|
|
@@ -41,8 +41,11 @@ import {
|
|
|
41
41
|
} from '@gjsify/adwaita-core';
|
|
42
42
|
|
|
43
43
|
import { AdwWindow } from './adw-window.js';
|
|
44
|
+
import { defineWindowApplication } from '../application.js';
|
|
44
45
|
|
|
45
46
|
export class AdwApplicationWindow extends AdwWindow {
|
|
47
|
+
/** `Gtk.Window:application`: setting it is `add_window` (ADR 0098), set by `defineWindowApplication`. */
|
|
48
|
+
declare application: object | null;
|
|
46
49
|
private _menubarEl: HTMLDivElement | null = null;
|
|
47
50
|
private _model: AdwMenuModel = [];
|
|
48
51
|
|
|
@@ -152,3 +155,5 @@ export class AdwApplicationWindow extends AdwWindow {
|
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
customElements.define('adw-application-window', AdwApplicationWindow);
|
|
158
|
+
|
|
159
|
+
defineWindowApplication(AdwApplicationWindow.prototype);
|
|
@@ -57,6 +57,7 @@ import type {
|
|
|
57
57
|
SplitButtonChange,
|
|
58
58
|
SplitButtonDirection,
|
|
59
59
|
} from '@gjsify/adwaita-core';
|
|
60
|
+
import { activateElementMenuItem, menuActionsOfElement } from '../application.js';
|
|
60
61
|
|
|
61
62
|
import { PopoverMenuView } from './popover-menu.js';
|
|
62
63
|
|
|
@@ -336,6 +337,7 @@ export class AdwSplitButton extends HTMLElement {
|
|
|
336
337
|
this._menuView = new PopoverMenuView(this._menuEl, 'adw-split-button-menu', (path) => {
|
|
337
338
|
const activated = this._state.activateMenuItem(path);
|
|
338
339
|
if (activated === null) return;
|
|
340
|
+
activateElementMenuItem(this, activated);
|
|
339
341
|
this.dispatchEvent(
|
|
340
342
|
new CustomEvent('menu-activated', {
|
|
341
343
|
bubbles: true,
|
|
@@ -525,7 +527,9 @@ export class AdwSplitButton extends HTMLElement {
|
|
|
525
527
|
|
|
526
528
|
private _renderMenu(): void {
|
|
527
529
|
this._menuEl.open = this._state.open;
|
|
528
|
-
this.
|
|
530
|
+
const model = this._state.menuModel ?? [];
|
|
531
|
+
this._menuView.setMenuModel(model);
|
|
532
|
+
this._menuView.setActions(this._actions ?? menuActionsOfElement(this, model));
|
|
529
533
|
this._menuView.render();
|
|
530
534
|
}
|
|
531
535
|
}
|
|
@@ -50,9 +50,17 @@
|
|
|
50
50
|
import { ViewStackState } from '@gjsify/adwaita-core';
|
|
51
51
|
import type { AdwViewStackPageInfo as CorePageInfo, ViewStackStateChange } from '@gjsify/adwaita-core';
|
|
52
52
|
|
|
53
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
54
|
+
|
|
53
55
|
export type AdwViewStackPageInfo = CorePageInfo<HTMLElement>;
|
|
54
56
|
|
|
55
57
|
export class AdwViewStack extends HTMLElement {
|
|
58
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
59
|
+
static readonly signals: DispatchedSignals = {
|
|
60
|
+
'notify::visible-child': 'notify::visible-child',
|
|
61
|
+
'notify::visible-child-name': 'notify::visible-child-name',
|
|
62
|
+
};
|
|
63
|
+
|
|
56
64
|
private readonly _state = new ViewStackState<HTMLElement>();
|
|
57
65
|
private _initialized = false;
|
|
58
66
|
|
|
@@ -255,6 +263,12 @@ export class AdwViewStack extends HTMLElement {
|
|
|
255
263
|
},
|
|
256
264
|
}),
|
|
257
265
|
);
|
|
266
|
+
this.dispatchEvent(
|
|
267
|
+
new CustomEvent('notify::visible-child-name', {
|
|
268
|
+
bubbles: true,
|
|
269
|
+
detail: { 'visible-child-name': change.name },
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
258
272
|
}
|
|
259
273
|
|
|
260
274
|
private _applyVisibility(): void {
|
package/src/elements/checks.ts
CHANGED
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
|
|
88
88
|
import { RadioGroupState, resolveCheckState } from '@gjsify/adwaita-core';
|
|
89
89
|
|
|
90
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
91
|
+
|
|
90
92
|
/**
|
|
91
93
|
* The document's radio groups. ONE registry, as global as a bare
|
|
92
94
|
* `<input type="radio" name="x">` outside a form is — two independent groups therefore
|
|
@@ -124,6 +126,15 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
124
126
|
this.toggleAttribute('checked', !!value);
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
/** `Gtk.CheckButton:active`, the GObject name for {@link checked}. */
|
|
130
|
+
get active(): boolean {
|
|
131
|
+
return this.checked;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
set active(value: boolean) {
|
|
135
|
+
this.checked = value;
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
/** Whether the control is in the third, inconsistent state. */
|
|
128
139
|
get indeterminate(): boolean {
|
|
129
140
|
return this.hasAttribute('indeterminate');
|
|
@@ -202,10 +213,17 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
202
213
|
this._label.textContent = this.getAttribute('label') ?? '';
|
|
203
214
|
}
|
|
204
215
|
this.render();
|
|
216
|
+
if (name === 'active') {
|
|
217
|
+
// The GObject spelling a projected `.blp` writes; `checked` stays the state.
|
|
218
|
+
this.checked = this.hasAttribute('active');
|
|
219
|
+
}
|
|
205
220
|
if (name === 'checked') {
|
|
206
221
|
this.dispatchEvent(
|
|
207
222
|
new CustomEvent('notify::checked', { bubbles: true, detail: { checked: this.checked } }),
|
|
208
223
|
);
|
|
224
|
+
// `toggled` fires on every change of `active`, programmatic included.
|
|
225
|
+
this.dispatchEvent(new CustomEvent('toggled', { bubbles: true }));
|
|
226
|
+
this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: this.checked } }));
|
|
209
227
|
}
|
|
210
228
|
}
|
|
211
229
|
|
|
@@ -227,8 +245,14 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
227
245
|
|
|
228
246
|
/** The Adwaita checkbox — `checkbutton > check` as a custom element. */
|
|
229
247
|
export class GtkCheckButton extends AdwCheckBase {
|
|
248
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
249
|
+
static readonly signals: DispatchedSignals = {
|
|
250
|
+
toggled: 'toggled',
|
|
251
|
+
'notify::active': 'notify::active',
|
|
252
|
+
};
|
|
253
|
+
|
|
230
254
|
static get observedAttributes() {
|
|
231
|
-
return ['checked', 'indeterminate', 'disabled', 'label'];
|
|
255
|
+
return ['checked', 'active', 'indeterminate', 'disabled', 'label'];
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
protected get inputType(): 'checkbox' {
|
|
@@ -26,8 +26,11 @@
|
|
|
26
26
|
|
|
27
27
|
import type { AdwSlot } from '../slotted-children.js';
|
|
28
28
|
import { GtkWindow } from './gtk-window.js';
|
|
29
|
+
import { defineWindowApplication } from '../application.js';
|
|
29
30
|
|
|
30
31
|
export class GtkApplicationWindow extends GtkWindow {
|
|
32
|
+
/** `Gtk.Window:application`: setting it is `add_window` (ADR 0098), set by `defineWindowApplication`. */
|
|
33
|
+
declare application: object | null;
|
|
31
34
|
private _menubar: HTMLDivElement | null = null;
|
|
32
35
|
private _menubarObserver: MutationObserver | null = null;
|
|
33
36
|
|
|
@@ -106,3 +109,5 @@ export class GtkApplicationWindow extends GtkWindow {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
customElements.define('gtk-application-window', GtkApplicationWindow);
|
|
112
|
+
|
|
113
|
+
defineWindowApplication(GtkApplicationWindow.prototype);
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
// Events:
|
|
48
48
|
// `notify::selected` (CustomEvent, bubbles, detail = { selected }) — the index
|
|
49
49
|
// changed (mirrors the Gtk.DropDown `notify::selected` GObject signal).
|
|
50
|
+
// `notify::selected-item` (CustomEvent, bubbles, detail = { 'selected-item' }) — fired
|
|
51
|
+
// right after `notify::selected`, on the same real change.
|
|
50
52
|
// `change` (CustomEvent, bubbles, detail = { index, value, label }) —
|
|
51
53
|
// a convenience event carrying the full selection. Fires ONLY on a user
|
|
52
54
|
// pick — `ComboState`'s `interactive` flag is what tells the two apart.
|
|
@@ -69,6 +71,7 @@ import { ComboState, normalizeComboOptions, parseListModel } from '@gjsify/adwai
|
|
|
69
71
|
import type { AdwComboOption, AdwListItemsChanged, AdwListModelInput } from '@gjsify/adwaita-core';
|
|
70
72
|
|
|
71
73
|
import { bindSlottedChildren } from '../slotted-children.js';
|
|
74
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
72
75
|
import { stringListSlot } from '../string-list-slot.js';
|
|
73
76
|
|
|
74
77
|
import { createGtkImage } from './gtk-image.js';
|
|
@@ -81,6 +84,12 @@ import { createGtkImage } from './gtk-image.js';
|
|
|
81
84
|
export type GtkDropDownOption = AdwComboOption;
|
|
82
85
|
|
|
83
86
|
export class GtkDropDown extends HTMLElement {
|
|
87
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
88
|
+
static readonly signals: DispatchedSignals = {
|
|
89
|
+
'notify::selected': 'notify::selected',
|
|
90
|
+
'notify::selected-item': 'notify::selected-item',
|
|
91
|
+
};
|
|
92
|
+
|
|
84
93
|
private _buttonEl!: HTMLButtonElement;
|
|
85
94
|
private _labelEl!: HTMLSpanElement;
|
|
86
95
|
private _popoverEl!: GtkPopover;
|
|
@@ -242,6 +251,13 @@ export class GtkDropDown extends HTMLElement {
|
|
|
242
251
|
// `notify::selected` mirrors GObject property-notify: EVERY change,
|
|
243
252
|
// programmatic included.
|
|
244
253
|
this.dispatchEvent(new CustomEvent('notify::selected', { bubbles: true, detail: { selected: index } }));
|
|
254
|
+
// `gtk_drop_down_set_selected` notifies `selected-item` right after `selected`.
|
|
255
|
+
this.dispatchEvent(
|
|
256
|
+
new CustomEvent('notify::selected-item', {
|
|
257
|
+
bubbles: true,
|
|
258
|
+
detail: { 'selected-item': option ?? null },
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
245
261
|
// `change` mirrors the DOM <select> contract: ONLY a user-initiated change. A
|
|
246
262
|
// programmatic assignment stays silent, just as native `select.value = x` fires
|
|
247
263
|
// no `change`, so consumers can set the value without guarding against it.
|