@gjsify/adwaita-web 0.63.0 → 0.63.1-next.235
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 +4 -0
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/canvas2d/canvas2d-bridge.d.ts +26 -5
- package/lib/types/elements/adw-application-window.d.ts +26 -7
- package/lib/types/elements/adw-bottom-sheet.d.ts +3 -0
- package/lib/types/elements/adw-data-grid.d.ts +4 -0
- package/lib/types/elements/adw-window-features.d.ts +39 -0
- package/lib/types/elements/adw-window.d.ts +11 -24
- package/lib/types/elements/gtk-application-window.d.ts +12 -4
- package/lib/types/elements/gtk-window.d.ts +11 -0
- package/lib/types/gtk-window.spec.d.ts +6 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +13 -12
- package/scss/_window.scss +7 -2
- package/src/adw-application-window.spec.ts +8 -0
- package/src/adw-data-grid.spec.ts +11 -0
- package/src/adw-window.spec.ts +8 -0
- package/src/application.spec.ts +1 -1
- package/src/bottom-sheet.spec.ts +9 -0
- package/src/canvas2d/canvas2d-bridge.ts +24 -8
- package/src/elements/adw-application-window.ts +90 -20
- package/src/elements/adw-bottom-sheet.ts +9 -0
- package/src/elements/adw-data-grid.ts +18 -0
- package/src/elements/adw-window-features.ts +128 -0
- package/src/elements/adw-window.ts +45 -108
- package/src/elements/gtk-application-window.ts +17 -6
- package/src/elements/gtk-window.ts +24 -7
- package/src/gtk-window.spec.ts +54 -43
- package/src/styles.generated.ts +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// <adw-window> — the web counterpart of Adw.Window: a
|
|
2
|
-
//
|
|
1
|
+
// <adw-window> — the web counterpart of Adw.Window: a Gtk.Window (so <gtk-window>'s state
|
|
2
|
+
// members — is-active, focus-widget, close-request, present(), maximized — are inherited)
|
|
3
|
+
// with no titlebar area. Adw.Window styles nothing itself — you pair it with
|
|
3
4
|
// Adw.ToolbarView and an Adw.HeaderBar (or a Gtk.HeaderBar) to get the chrome —
|
|
4
5
|
// so this element owns the FRAME and the window's own state, and the chrome is
|
|
5
6
|
// whatever you put in it.
|
|
@@ -38,159 +39,95 @@
|
|
|
38
39
|
// Copyright (c) 2020-2023 Alice Mikhaylenko / Purism SPC (libadwaita). LGPLv2.1+
|
|
39
40
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
40
41
|
|
|
41
|
-
import { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
42
|
+
import type { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
43
|
+
import type { AdwSlot } from '../slotted-children.js';
|
|
44
|
+
import { AdwWindowFeatures } from './adw-window-features.js';
|
|
45
|
+
import { GtkWindow } from './gtk-window.js';
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const DEFAULT_HEIGHT = 200;
|
|
48
|
-
|
|
49
|
-
export class AdwWindow extends HTMLElement {
|
|
50
|
-
private _initialized = false;
|
|
51
|
-
private _contentEl!: HTMLDivElement;
|
|
52
|
-
private _breakpoints: AdwBreakpoint[] = [];
|
|
53
|
-
private _disposeBreakpoints: (() => void) | null = null;
|
|
54
|
-
private _openDialogs: HTMLElement[] = [];
|
|
47
|
+
export class AdwWindow extends GtkWindow {
|
|
48
|
+
private readonly _features = new AdwWindowFeatures(this);
|
|
55
49
|
|
|
56
50
|
static get observedAttributes() {
|
|
57
|
-
return [
|
|
51
|
+
return [...GtkWindow.observedAttributes, 'width', 'height'];
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
/** The content area — the Adw.Window:content property. */
|
|
61
55
|
get contentArea(): HTMLDivElement {
|
|
62
|
-
return this.
|
|
56
|
+
return this._content;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
/** The content widget — the first child of the content area. */
|
|
66
60
|
get content(): HTMLElement | null {
|
|
67
|
-
return this.
|
|
61
|
+
return this._features.content;
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
set content(value: HTMLElement | null) {
|
|
71
|
-
|
|
72
|
-
this._contentEl.appendChild(value);
|
|
73
|
-
}
|
|
65
|
+
this._features.content = value;
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
/** The breakpoints added to this window. */
|
|
77
69
|
get breakpoints(): readonly AdwBreakpoint[] {
|
|
78
|
-
return this.
|
|
70
|
+
return this._features.breakpoints;
|
|
79
71
|
}
|
|
80
72
|
|
|
81
73
|
/** The currently applied breakpoint, or null when none is. */
|
|
82
74
|
get currentBreakpoint(): AdwBreakpoint | null {
|
|
83
|
-
return this.
|
|
75
|
+
return this._features.currentBreakpoint;
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
/** The open dialogs presented inside this window (Adw.Window:dialogs). */
|
|
87
79
|
get dialogs(): readonly HTMLElement[] {
|
|
88
|
-
|
|
89
|
-
// out, so a dialog that was REMOVED while open would otherwise stay listed
|
|
90
|
-
// for the rest of the window's life.
|
|
91
|
-
this._openDialogs = this._openDialogs.filter((dialog) => dialog.isConnected);
|
|
92
|
-
return this._openDialogs;
|
|
80
|
+
return this._features.dialogs;
|
|
93
81
|
}
|
|
94
82
|
|
|
95
83
|
/** The currently visible dialog, or null when none is (Adw.Window:visible-dialog). */
|
|
96
84
|
get visibleDialog(): HTMLElement | null {
|
|
97
|
-
|
|
98
|
-
return dialogs.length > 0 ? (dialogs[dialogs.length - 1] as HTMLElement) : null;
|
|
85
|
+
return this._features.visibleDialog;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
/**
|
|
102
|
-
* Add a breakpoint to this window (adw_window_add_breakpoint).
|
|
103
|
-
* The breakpoint evaluates against the window's own box and sets a
|
|
104
|
-
* `data-breakpoint` attribute when applied.
|
|
105
|
-
*/
|
|
88
|
+
/** Add a breakpoint to this window (adw_window_add_breakpoint). */
|
|
106
89
|
addBreakpoint(condition: string): AdwBreakpoint {
|
|
107
|
-
|
|
108
|
-
onApply: () => (this.dataset.breakpoint = condition),
|
|
109
|
-
onUnapply: () => {
|
|
110
|
-
if (this.dataset.breakpoint === condition) delete this.dataset.breakpoint;
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
this._breakpoints.push(bp);
|
|
114
|
-
// Re-evaluated against a box that may have changed since the last add.
|
|
115
|
-
this._bindBreakpoints();
|
|
116
|
-
return bp;
|
|
90
|
+
return this._features.addBreakpoint(condition);
|
|
117
91
|
}
|
|
118
92
|
|
|
119
93
|
connectedCallback() {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
94
|
+
super.connectedCallback();
|
|
95
|
+
this._features.adopt(this._content);
|
|
96
|
+
// EVERY connect, not only the first — see `AdwWindowFeatures.bind`.
|
|
97
|
+
this._features.bind();
|
|
123
98
|
}
|
|
124
99
|
|
|
125
100
|
disconnectedCallback() {
|
|
126
|
-
this.
|
|
127
|
-
|
|
101
|
+
this._features.release();
|
|
102
|
+
super.disconnectedCallback();
|
|
128
103
|
}
|
|
129
104
|
|
|
130
105
|
attributeChangedCallback(name: string) {
|
|
131
|
-
// The size request is read from the ATTRIBUTE, so it is applied on every
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
106
|
+
// The size request is read from the ATTRIBUTE, so it is applied on every change —
|
|
107
|
+
// including one that arrived before the element was connected.
|
|
108
|
+
if (name === 'width' || name === 'height') {
|
|
109
|
+
this.renderFrameSize();
|
|
110
|
+
return;
|
|
136
111
|
}
|
|
112
|
+
super.attributeChangedCallback(name);
|
|
137
113
|
}
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
// `src/shared-tree-builder.ts` throws on a slot a defined element does not
|
|
152
|
-
// declare, and `showcases/gtk/effect-adw-services/src/window.blp` is rooted at
|
|
153
|
-
// one. The content area becomes the host's own child, so a child appended later
|
|
154
|
-
// lands in it too (`bindSlottedChildren` is LIVE, see `src/slotted-children.ts`).
|
|
155
|
-
bindSlottedChildren(this, [{ name: 'content', into: this._contentEl }, { into: this._contentEl }]).install(
|
|
156
|
-
this._contentEl,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// Track open dialogs presented inside this window: an AdwDialog raises
|
|
160
|
-
// `notify::open`, which bubbles to here.
|
|
161
|
-
this.addEventListener('notify::open', (e) => {
|
|
162
|
-
const dialog = e.target as HTMLElement;
|
|
163
|
-
if (dialog === this) return;
|
|
164
|
-
if (dialog.hasAttribute('open')) {
|
|
165
|
-
if (!this._openDialogs.includes(dialog)) this._openDialogs.push(dialog);
|
|
166
|
-
} else {
|
|
167
|
-
this._openDialogs = this._openDialogs.filter((d) => d !== dialog);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Bind the breakpoints to THIS box.
|
|
174
|
-
*
|
|
175
|
-
* NOT once per lifetime: `disconnectedCallback` releases the disposer, so a
|
|
176
|
-
* window moved between parents would come back deaf — and a window is exactly
|
|
177
|
-
* the thing a client-side route change re-parents. Re-binding is cheap and
|
|
178
|
-
* idempotent, and it re-measures the new box on the way.
|
|
179
|
-
*/
|
|
180
|
-
private _bindBreakpoints() {
|
|
181
|
-
this._disposeBreakpoints?.();
|
|
182
|
-
this._disposeBreakpoints = null;
|
|
183
|
-
if (this._initialized && this._breakpoints.length > 0) {
|
|
184
|
-
this._disposeBreakpoints = addBreakpoints(this, this._breakpoints);
|
|
185
|
-
}
|
|
115
|
+
/** `Adw.Window` has no titlebar of its own: you pair it with a header bar. */
|
|
116
|
+
protected override hasTitlebar(): boolean {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected override strips(): Node[] {
|
|
121
|
+
return [this._content];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected override extraSlots(): AdwSlot[] {
|
|
125
|
+
// `content` is the content area's name too: a `.blp` writes `content:` on the window.
|
|
126
|
+
return [{ name: 'content', into: this._content }];
|
|
186
127
|
}
|
|
187
128
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const width = this.getAttribute('default-width') ?? this.getAttribute('width');
|
|
191
|
-
const height = this.getAttribute('default-height') ?? this.getAttribute('height');
|
|
192
|
-
this.style.width = width ? `${width}px` : `${DEFAULT_WIDTH}px`;
|
|
193
|
-
this.style.height = height ? `${height}px` : `${DEFAULT_HEIGHT}px`;
|
|
129
|
+
protected override renderFrameSize(): void {
|
|
130
|
+
this._features.applySizeRequest();
|
|
194
131
|
}
|
|
195
132
|
}
|
|
196
133
|
|
|
@@ -16,21 +16,32 @@
|
|
|
16
16
|
//
|
|
17
17
|
// THE `win.` / `app.` PREFIXES. `GtkApplicationWindow` exports window actions under `win.` and
|
|
18
18
|
// application-wide ones under `app.` (:48-56), and any `GtkActionable` inside the window can
|
|
19
|
-
// activate either.
|
|
20
|
-
//
|
|
19
|
+
// activate either. `defineWindowApplication` installs the `Gio.ActionMap` half on the prototype
|
|
20
|
+
// (`add_action`, `lookup_action`, `remove_action`, the `win.` registry); the `Gio.ActionGroup`
|
|
21
|
+
// half stays off because a window's `activate_action` is the widget's. Plain `<gtk-window>` has
|
|
22
|
+
// no ActionMap. What this element adds over it is that map, the strip and the property that shows it.
|
|
21
23
|
//
|
|
22
24
|
// Reference: refs/gtk/gtk/gtkapplicationwindow.c
|
|
23
25
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_window.scss (GtkWindow)
|
|
24
26
|
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
25
27
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
26
28
|
|
|
29
|
+
import type { ActionLike } from '@gjsify/adwaita-core';
|
|
30
|
+
import type { GtkApplication } from '../application.js';
|
|
27
31
|
import type { AdwSlot } from '../slotted-children.js';
|
|
28
32
|
import { GtkWindow } from './gtk-window.js';
|
|
29
33
|
import { defineWindowApplication } from '../application.js';
|
|
30
34
|
|
|
31
35
|
export class GtkApplicationWindow extends GtkWindow {
|
|
32
36
|
/** `Gtk.Window:application`: setting it is `add_window` (ADR 0098), set by `defineWindowApplication`. */
|
|
33
|
-
declare application:
|
|
37
|
+
declare application: GtkApplication | null;
|
|
38
|
+
|
|
39
|
+
/** `Gio.ActionMap.add_action`: installed by `installActionMap`; the action is reachable as `win.<name>`. */
|
|
40
|
+
declare add_action: (action: ActionLike) => void;
|
|
41
|
+
/** `Gio.ActionMap.lookup_action`: the window's own action, or `null`. */
|
|
42
|
+
declare lookup_action: (name: string) => ActionLike | null;
|
|
43
|
+
/** `Gio.ActionMap.remove_action`: removing an unknown name does nothing. */
|
|
44
|
+
declare remove_action: (name: string) => void;
|
|
34
45
|
private _menubar: HTMLDivElement | null = null;
|
|
35
46
|
private _menubarObserver: MutationObserver | null = null;
|
|
36
47
|
|
|
@@ -51,7 +62,7 @@ export class GtkApplicationWindow extends GtkWindow {
|
|
|
51
62
|
* The menubar strip, created once and reused: `strips()` and `extraSlots()` both ask for
|
|
52
63
|
* it, so whichever runs first builds it and the other finds it.
|
|
53
64
|
*/
|
|
54
|
-
|
|
65
|
+
protected menubarSection(): HTMLDivElement {
|
|
55
66
|
if (this._menubar === null) {
|
|
56
67
|
const section = document.createElement('div');
|
|
57
68
|
section.className = 'adw-gtk-application-window-menubar';
|
|
@@ -95,14 +106,14 @@ export class GtkApplicationWindow extends GtkWindow {
|
|
|
95
106
|
* The observer is what makes the second half live: `bindSlottedChildren` adopts a
|
|
96
107
|
* `slot="menubar"` child after connect, and a bar that appeared then must appear.
|
|
97
108
|
*/
|
|
98
|
-
|
|
109
|
+
protected _followMenubar(): void {
|
|
99
110
|
this._menubarObserver?.disconnect();
|
|
100
111
|
this._menubarObserver = new MutationObserver(() => this._renderMenubar());
|
|
101
112
|
this._menubarObserver.observe(this.menubarSection(), { childList: true });
|
|
102
113
|
this._renderMenubar();
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
protected _renderMenubar(): void {
|
|
106
117
|
const menubar = this.menubarSection();
|
|
107
118
|
menubar.hidden = !this.showMenubar || menubar.childElementCount === 0;
|
|
108
119
|
}
|
|
@@ -233,7 +233,7 @@ export class GtkWindow extends HTMLElement {
|
|
|
233
233
|
this._content.className = 'adw-gtk-window-content';
|
|
234
234
|
|
|
235
235
|
bindSlottedChildren(this, [
|
|
236
|
-
{ name: 'titlebar', into: this._titlebar },
|
|
236
|
+
...(this.hasTitlebar() ? [{ name: 'titlebar', into: this._titlebar }] : []),
|
|
237
237
|
...this.extraSlots(),
|
|
238
238
|
{ into: this._content },
|
|
239
239
|
]).install(...this.strips());
|
|
@@ -308,6 +308,15 @@ export class GtkWindow extends HTMLElement {
|
|
|
308
308
|
return [this._titlebar, this._content];
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Whether this window has a titlebar strip at all. `Adw.Window` has none (it styles nothing
|
|
313
|
+
* itself, you pair it with a header bar), so a subclass answers `false` and neither the
|
|
314
|
+
* `titlebar` slot nor the server-side decoration is built.
|
|
315
|
+
*/
|
|
316
|
+
protected hasTitlebar(): boolean {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
311
320
|
/** The slots a subclass adds beside `titlebar`; none by default. */
|
|
312
321
|
protected extraSlots(): AdwSlot[] {
|
|
313
322
|
return [];
|
|
@@ -345,6 +354,19 @@ export class GtkWindow extends HTMLElement {
|
|
|
345
354
|
this.classList.toggle('maximized', this.decorated && this.maximized);
|
|
346
355
|
this.classList.toggle('fullscreen', this.decorated && this._offByDefault('fullscreened'));
|
|
347
356
|
|
|
357
|
+
this.renderFrameSize();
|
|
358
|
+
|
|
359
|
+
const title = this.getAttribute('title');
|
|
360
|
+
if (title === null) this.removeAttribute('aria-label');
|
|
361
|
+
else this.setAttribute('aria-label', title);
|
|
362
|
+
this._renderTitlebar();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* The frame's box from `default-width` / `default-height`; an absent one clears it. A subclass
|
|
367
|
+
* with a size request of its own (`Adw.Window`'s 360×200) replaces this.
|
|
368
|
+
*/
|
|
369
|
+
protected renderFrameSize(): void {
|
|
348
370
|
const width = Number.parseFloat(this.getAttribute('default-width') ?? '');
|
|
349
371
|
const height = Number.parseFloat(this.getAttribute('default-height') ?? '');
|
|
350
372
|
for (const [axis, value] of [
|
|
@@ -354,11 +376,6 @@ export class GtkWindow extends HTMLElement {
|
|
|
354
376
|
if (Number.isFinite(value) && value > 0) this.style.setProperty(axis, `${value}px`);
|
|
355
377
|
else this.style.removeProperty(axis);
|
|
356
378
|
}
|
|
357
|
-
|
|
358
|
-
const title = this.getAttribute('title');
|
|
359
|
-
if (title === null) this.removeAttribute('aria-label');
|
|
360
|
-
else this.setAttribute('aria-label', title);
|
|
361
|
-
this._renderTitlebar();
|
|
362
379
|
}
|
|
363
380
|
|
|
364
381
|
/**
|
|
@@ -384,7 +401,7 @@ export class GtkWindow extends HTMLElement {
|
|
|
384
401
|
this._titleLabel = null;
|
|
385
402
|
return;
|
|
386
403
|
}
|
|
387
|
-
if (!this.decorated) return;
|
|
404
|
+
if (!this.decorated || !this.hasTitlebar()) return;
|
|
388
405
|
|
|
389
406
|
if (this._startControls === null) {
|
|
390
407
|
this._startControls = document.createElement('gtk-window-controls');
|
package/src/gtk-window.spec.ts
CHANGED
|
@@ -26,6 +26,58 @@ function titlebar(el: GtkWindow): HTMLElement {
|
|
|
26
26
|
return el.querySelector('.adw-gtk-window-titlebar') as HTMLElement;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** The `Gtk.Window` conformance subject for any window element (`gtk-window`, `adw-window`, …). */
|
|
30
|
+
export function webWindowSubject(tag: string): { subject: WindowSubject; dispose: () => void } {
|
|
31
|
+
const windows = new Map<object, HTMLElement>();
|
|
32
|
+
const subject: WindowSubject = {
|
|
33
|
+
name: `<${tag}>`,
|
|
34
|
+
isOracle: false,
|
|
35
|
+
capabilities: { maximizable: true, observesFocus: true, hideOnClose: true },
|
|
36
|
+
create({ hideOnClose }) {
|
|
37
|
+
// Never in a page: not realized, which is GTK's state for a window before `present()`.
|
|
38
|
+
const el = document.createElement(tag) as GtkWindow;
|
|
39
|
+
if (hideOnClose) el.setAttribute('hide-on-close', 'true');
|
|
40
|
+
el.append(document.createElement('button'), document.createElement('button'));
|
|
41
|
+
return el;
|
|
42
|
+
},
|
|
43
|
+
show(w) {
|
|
44
|
+
const host = document.createElement('div');
|
|
45
|
+
document.body.appendChild(host);
|
|
46
|
+
host.appendChild(w as GtkWindow);
|
|
47
|
+
windows.set(w, host);
|
|
48
|
+
(w as GtkWindow).present();
|
|
49
|
+
},
|
|
50
|
+
connect(w, signal, handler) {
|
|
51
|
+
(w as GtkWindow).addEventListener(signal, (event) => {
|
|
52
|
+
if (signal === 'close-request') {
|
|
53
|
+
if (event.defaultPrevented) return;
|
|
54
|
+
if (handler()) {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
event.stopImmediatePropagation();
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
handler();
|
|
61
|
+
});
|
|
62
|
+
return 1;
|
|
63
|
+
},
|
|
64
|
+
close: (w) => void (w as GtkWindow).close(),
|
|
65
|
+
maximize: (w) => (w as GtkWindow).maximize(),
|
|
66
|
+
unmaximize: (w) => (w as GtkWindow).unmaximize(),
|
|
67
|
+
isMaximized: (w) => (w as GtkWindow).is_maximized(),
|
|
68
|
+
isVisible: (w) => (w as GtkWindow).isConnected && !(w as GtkWindow).hidden,
|
|
69
|
+
focusables: (w) => [...(w as GtkWindow).querySelectorAll('button')] as unknown as [Element, Element],
|
|
70
|
+
setFocus: (w, widget) => (w as GtkWindow).set_focus(widget as Element | null),
|
|
71
|
+
getFocus: (w) => (w as GtkWindow).get_focus(),
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
subject,
|
|
75
|
+
dispose: () => {
|
|
76
|
+
for (const host of windows.values()) host.remove();
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
29
81
|
export const GtkWindowTest = async () => {
|
|
30
82
|
await describe('<gtk-window> structure', async () => {
|
|
31
83
|
await it('is a titlebar strip over a content box', async () => {
|
|
@@ -206,50 +258,9 @@ export const GtkWindowTest = async () => {
|
|
|
206
258
|
// adapter below is that mapping, and it also stops later listeners, which is what the handled
|
|
207
259
|
// accumulator does. So the "third never runs" row exercises the adapter plus the element's
|
|
208
260
|
// sequence, not DOM dispatch alone.
|
|
209
|
-
const
|
|
210
|
-
const webSubject: WindowSubject = {
|
|
211
|
-
name: '<gtk-window>',
|
|
212
|
-
isOracle: false,
|
|
213
|
-
capabilities: { maximizable: true, observesFocus: true, hideOnClose: true },
|
|
214
|
-
create({ hideOnClose }) {
|
|
215
|
-
// Never in a page: not realized, which is GTK's state for a window before `present()`.
|
|
216
|
-
const el = document.createElement('gtk-window') as GtkWindow;
|
|
217
|
-
if (hideOnClose) el.setAttribute('hide-on-close', 'true');
|
|
218
|
-
el.append(document.createElement('button'), document.createElement('button'));
|
|
219
|
-
return el;
|
|
220
|
-
},
|
|
221
|
-
show(w) {
|
|
222
|
-
const host = document.createElement('div');
|
|
223
|
-
document.body.appendChild(host);
|
|
224
|
-
host.appendChild(w as GtkWindow);
|
|
225
|
-
windows.set(w, host);
|
|
226
|
-
(w as GtkWindow).present();
|
|
227
|
-
},
|
|
228
|
-
connect(w, signal, handler) {
|
|
229
|
-
(w as GtkWindow).addEventListener(signal, (event) => {
|
|
230
|
-
if (signal === 'close-request') {
|
|
231
|
-
if (event.defaultPrevented) return;
|
|
232
|
-
if (handler()) {
|
|
233
|
-
event.preventDefault();
|
|
234
|
-
event.stopImmediatePropagation();
|
|
235
|
-
}
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
handler();
|
|
239
|
-
});
|
|
240
|
-
return 1;
|
|
241
|
-
},
|
|
242
|
-
close: (w) => void (w as GtkWindow).close(),
|
|
243
|
-
maximize: (w) => (w as GtkWindow).maximize(),
|
|
244
|
-
unmaximize: (w) => (w as GtkWindow).unmaximize(),
|
|
245
|
-
isMaximized: (w) => (w as GtkWindow).is_maximized(),
|
|
246
|
-
isVisible: (w) => (w as GtkWindow).isConnected && !(w as GtkWindow).hidden,
|
|
247
|
-
focusables: (w) => [...(w as GtkWindow).querySelectorAll('button')] as unknown as [Element, Element],
|
|
248
|
-
setFocus: (w, widget) => (w as GtkWindow).set_focus(widget as Element | null),
|
|
249
|
-
getFocus: (w) => (w as GtkWindow).get_focus(),
|
|
250
|
-
};
|
|
261
|
+
const { subject: webSubject, dispose } = webWindowSubject('gtk-window');
|
|
251
262
|
await driveWindowVectors(webSubject, { describe, it, expect }, WINDOW_VECTORS);
|
|
252
|
-
|
|
263
|
+
dispose();
|
|
253
264
|
|
|
254
265
|
await describe('<gtk-window> state members', async () => {
|
|
255
266
|
await it('a frame that was never connected is not realized: close() does nothing', async () => {
|