@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.1-next.235",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"@codemirror/language": "^6.12.4",
|
|
59
59
|
"@codemirror/state": "^6.7.0",
|
|
60
60
|
"@codemirror/view": "^6.43.4",
|
|
61
|
-
"@gjsify/adwaita-core": "
|
|
62
|
-
"@gjsify/adwaita-fonts": "
|
|
63
|
-
"@gjsify/adwaita-icons": "
|
|
64
|
-
"@gjsify/gtksource-core": "
|
|
61
|
+
"@gjsify/adwaita-core": "0.63.1-next.235",
|
|
62
|
+
"@gjsify/adwaita-fonts": "0.63.1-next.235",
|
|
63
|
+
"@gjsify/adwaita-icons": "0.63.1-next.235",
|
|
64
|
+
"@gjsify/gtksource-core": "0.63.1-next.235",
|
|
65
65
|
"@lezer/highlight": "^1.2.3"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@gjsify/app-settings": "
|
|
68
|
+
"@gjsify/app-settings": "0.63.1-next.235"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
71
71
|
"@gjsify/app-settings": {
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@gjsify/app-settings": "
|
|
77
|
-
"@gjsify/blueprint": "
|
|
78
|
-
"@gjsify/cli": "
|
|
79
|
-
"@gjsify/unit": "
|
|
80
|
-
"@gjsify/vite-plugin-blueprint": "
|
|
76
|
+
"@gjsify/app-settings": "0.63.1-next.235",
|
|
77
|
+
"@gjsify/blueprint": "0.63.1-next.235",
|
|
78
|
+
"@gjsify/cli": "0.63.1-next.235",
|
|
79
|
+
"@gjsify/unit": "0.63.1-next.235",
|
|
80
|
+
"@gjsify/vite-plugin-blueprint": "0.63.1-next.235",
|
|
81
81
|
"sass": "^1.101.0",
|
|
82
82
|
"typescript": "^6.0.3"
|
|
83
83
|
},
|
|
@@ -114,5 +114,6 @@
|
|
|
114
114
|
"bugs": {
|
|
115
115
|
"url": "https://github.com/gjsify/gjsify/issues"
|
|
116
116
|
},
|
|
117
|
-
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme"
|
|
117
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme",
|
|
118
|
+
"gitHead": "9d2cca56170a0215b3e0792e98421ed1f83e7f72"
|
|
118
119
|
}
|
package/scss/_window.scss
CHANGED
|
@@ -35,6 +35,11 @@ adw-application-window {
|
|
|
35
35
|
outline: none;
|
|
36
36
|
box-shadow: none;
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
// `present()` and `hide-on-close` toggle `hidden`, which `display: flex` would override.
|
|
40
|
+
&[hidden] {
|
|
41
|
+
display: none;
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
// The content area — the one child slot `Adw.Window:content` fills. It takes the
|
|
@@ -64,8 +69,8 @@ adw-application-window {
|
|
|
64
69
|
// through a selector list: `_reset.scss` re-roots the box model at each registered element,
|
|
65
70
|
// and a shared `adw-window, gtk-window` block would put two component resets in one rule.
|
|
66
71
|
//
|
|
67
|
-
// NOT the same as `<adw-window>`, deliberately. `Adw.Window`
|
|
68
|
-
//
|
|
72
|
+
// NOT the same as `<adw-window>`, deliberately. `Adw.Window` extends `GtkWindow` but has no
|
|
73
|
+
// titlebar area, while this one answers the FRAME properties:
|
|
69
74
|
// `decorated` drops the titlebar strip, `maximized` and `fullscreened` square the corners
|
|
70
75
|
// off and drop the shadow (`_window.scss:66-72`), and the strip itself is either the
|
|
71
76
|
// application's `titlebar` or the two `<gtk-window-controls>` a server-side decoration would
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
// `Adw.Window` plus `Gtk.ApplicationWindow:show-menubar`, so the window half is
|
|
8
8
|
// <adw-window>'s spec and only the delta is asserted here.
|
|
9
9
|
import { describe, it, expect } from '@gjsify/unit';
|
|
10
|
+
import { driveWindowVectors, WINDOW_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
11
|
+
|
|
12
|
+
import { webWindowSubject } from './gtk-window.spec.js';
|
|
10
13
|
|
|
11
14
|
import type { AdwApplicationWindow } from './elements/adw-application-window.js';
|
|
12
15
|
|
|
@@ -30,6 +33,11 @@ function makeWindow(model: unknown = MENU_MODEL): AdwApplicationWindow {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export const AdwApplicationWindowTest = async () => {
|
|
36
|
+
// `Adw.Window` IS a `Gtk.Window` upstream: the same vectors that run against real GTK and
|
|
37
|
+
// <gtk-window> must hold here, or the state members have fallen off the subclass again.
|
|
38
|
+
const { subject, dispose } = webWindowSubject('adw-application-window');
|
|
39
|
+
await driveWindowVectors(subject, { describe, it, expect }, WINDOW_VECTORS);
|
|
40
|
+
dispose();
|
|
33
41
|
await describe('adw-application-window is an adw-window', async () => {
|
|
34
42
|
await it('places children in the inherited content area', async () => {
|
|
35
43
|
const win = makeWindow();
|
|
@@ -33,6 +33,17 @@ export const AdwDataGridTest = async () => {
|
|
|
33
33
|
grid.remove();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
await it('set_columns / set_rows are the same properties, the spelling the NativeScript port shares', async () => {
|
|
37
|
+
const grid = makeGrid();
|
|
38
|
+
grid.set_columns(BWA_COLUMNS);
|
|
39
|
+
grid.set_rows([{ position: 'Umsatz', total: '1' }]);
|
|
40
|
+
expect(grid.get_columns().map((column) => column.key)).toStrictEqual(['position', 'total']);
|
|
41
|
+
expect(grid.columns).toBe(grid.get_columns());
|
|
42
|
+
expect(grid.get_rows().length).toBe(1);
|
|
43
|
+
expect(grid.querySelectorAll('.adw-data-grid-row:not(.header)').length).toBe(1);
|
|
44
|
+
grid.remove();
|
|
45
|
+
});
|
|
46
|
+
|
|
36
47
|
await it('falls back to the key when no label is given', async () => {
|
|
37
48
|
const grid = makeGrid([{ key: 'amount' }], []);
|
|
38
49
|
const cell = grid.querySelector('.adw-data-grid-row.header .adw-data-grid-cell');
|
package/src/adw-window.spec.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// these specs create elements via document.createElement and assert their DOM
|
|
5
5
|
// behaviour (content area, breakpoints, dialog tracking).
|
|
6
6
|
import { describe, it, expect } from '@gjsify/unit';
|
|
7
|
+
import { driveWindowVectors, WINDOW_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
8
|
+
|
|
9
|
+
import { webWindowSubject } from './gtk-window.spec.js';
|
|
7
10
|
|
|
8
11
|
import type { AdwWindow } from './elements/adw-window.js';
|
|
9
12
|
|
|
@@ -22,6 +25,11 @@ function settle(): Promise<void> {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export const AdwWindowTest = async () => {
|
|
28
|
+
// `Adw.Window` IS a `Gtk.Window` upstream: the same vectors that run against real GTK and
|
|
29
|
+
// <gtk-window> must hold here, or the state members have fallen off the subclass again.
|
|
30
|
+
const { subject, dispose } = webWindowSubject('adw-window');
|
|
31
|
+
await driveWindowVectors(subject, { describe, it, expect }, WINDOW_VECTORS);
|
|
32
|
+
dispose();
|
|
25
33
|
await describe('adw-window content', async () => {
|
|
26
34
|
await it('places children in the content area', async () => {
|
|
27
35
|
const win = makeWindow('<p id="hello">Hello</p>');
|
package/src/application.spec.ts
CHANGED
|
@@ -23,7 +23,7 @@ const made: Element[] = [];
|
|
|
23
23
|
const scene: ApplicationScene = {
|
|
24
24
|
window(app) {
|
|
25
25
|
const window = document.createElement('adw-application-window') as Adw.ApplicationWindow;
|
|
26
|
-
window.application = app;
|
|
26
|
+
window.application = app as unknown as Gtk.Application;
|
|
27
27
|
document.body.append(window);
|
|
28
28
|
made.push(window);
|
|
29
29
|
return window;
|
package/src/bottom-sheet.spec.ts
CHANGED
|
@@ -558,6 +558,15 @@ export const AdwBottomSheetTest = async () => {
|
|
|
558
558
|
sheet.remove();
|
|
559
559
|
});
|
|
560
560
|
|
|
561
|
+
await it('get_modal / set_modal are the same property, the spelling the NativeScript port shares', () => {
|
|
562
|
+
const sheet = mountSheet();
|
|
563
|
+
sheet.set_modal(false);
|
|
564
|
+
expect(sheet.modal).toBe(false);
|
|
565
|
+
expect(sheet.get_modal()).toBe(false);
|
|
566
|
+
expect(sheet.classList.contains('modal')).toBe(false);
|
|
567
|
+
sheet.remove();
|
|
568
|
+
});
|
|
569
|
+
|
|
561
570
|
await it('the dimming layer becomes interactive only while open AND modal', () => {
|
|
562
571
|
const sheet = mountSheet();
|
|
563
572
|
expect(dimming(sheet).classList.contains('visible')).toBe(false);
|
|
@@ -16,7 +16,23 @@
|
|
|
16
16
|
|
|
17
17
|
import { registerTemplateClass } from '../template-classes.js';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The DOM types this element names, resolved through `globalThis` so the published `.d.ts`
|
|
21
|
+
* never names a `lib.dom` type directly: a consumer that loads `lib.dom` gets the real ones,
|
|
22
|
+
* one that does not still type-checks the declarations with `skipLibCheck: false`.
|
|
23
|
+
*/
|
|
24
|
+
export type BridgeCanvas = typeof globalThis extends { HTMLCanvasElement: { prototype: infer T } } ? T : object;
|
|
25
|
+
export type BridgeContext2D = typeof globalThis extends { CanvasRenderingContext2D: { prototype: infer T } }
|
|
26
|
+
? T
|
|
27
|
+
: object;
|
|
28
|
+
/** `FrameRequestCallback` without `lib.dom`. */
|
|
29
|
+
export type FrameCallback = (time: number) => void;
|
|
30
|
+
type ElementConstructor = typeof globalThis extends { HTMLElement: infer C } ? C : abstract new () => object;
|
|
31
|
+
|
|
32
|
+
// biome-ignore lint: the annotation, not the value, is what the declaration file keeps
|
|
33
|
+
const HostElement: ElementConstructor = (globalThis as { HTMLElement?: unknown }).HTMLElement as ElementConstructor;
|
|
34
|
+
|
|
35
|
+
type ReadyCallback = (canvas: BridgeCanvas, ctx: BridgeContext2D) => void;
|
|
20
36
|
type ResizeCallback = (width: number, height: number) => void;
|
|
21
37
|
type SignalHandler = (emitter: Canvas2DBridgeElement, ...args: unknown[]) => unknown;
|
|
22
38
|
|
|
@@ -25,18 +41,18 @@ const TAG = 'gjsify-canvas2d-bridge';
|
|
|
25
41
|
/** `G_MAXINT`, the top of `g_param_spec_int`'s range. */
|
|
26
42
|
const MAX_INT = 2147483647;
|
|
27
43
|
|
|
28
|
-
function intAttribute(element:
|
|
44
|
+
function intAttribute(element: Element, name: string): number {
|
|
29
45
|
const raw = Number.parseInt(element.getAttribute(name) ?? '', 10);
|
|
30
46
|
return Number.isFinite(raw) ? raw : 0;
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
export class Canvas2DBridgeElement extends
|
|
49
|
+
export class Canvas2DBridgeElement extends HostElement {
|
|
34
50
|
static readonly GTypeName = 'GjsifyCanvas2DBridge';
|
|
35
51
|
/** The signals `connect()` accepts, as `Gtk.Widget` names them. */
|
|
36
52
|
static readonly signals: Record<string, string> = { resize: 'resize' };
|
|
37
53
|
|
|
38
|
-
private _canvas:
|
|
39
|
-
private _ctx:
|
|
54
|
+
private _canvas: BridgeCanvas | null = null;
|
|
55
|
+
private _ctx: BridgeContext2D | null = null;
|
|
40
56
|
private _readyCallbacks: ReadyCallback[] = [];
|
|
41
57
|
private _resizeCallbacks: ResizeCallback[] = [];
|
|
42
58
|
private _handlers = new Map<number, { signal: string; handler: SignalHandler }>();
|
|
@@ -89,12 +105,12 @@ export class Canvas2DBridgeElement extends HTMLElement {
|
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
/** The `<canvas>` backing this widget. Available after the first allocation. */
|
|
92
|
-
get canvas():
|
|
108
|
+
get canvas(): BridgeCanvas | null {
|
|
93
109
|
return this._canvas;
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
/** The browser's own 2D context; available after the first allocation. */
|
|
97
|
-
getContext(_id: '2d'):
|
|
113
|
+
getContext(_id: '2d'): BridgeContext2D | null {
|
|
98
114
|
return this._ctx;
|
|
99
115
|
}
|
|
100
116
|
|
|
@@ -113,7 +129,7 @@ export class Canvas2DBridgeElement extends HTMLElement {
|
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
/** The browser's own, here only so the call reads the same on every target. */
|
|
116
|
-
requestAnimationFrame(cb:
|
|
132
|
+
requestAnimationFrame(cb: FrameCallback): number {
|
|
117
133
|
return globalThis.requestAnimationFrame(cb);
|
|
118
134
|
}
|
|
119
135
|
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
// menu-model (the portable menu model, JSON — the stand-in for the model a
|
|
24
24
|
// GtkApplication would own; the same value `<gtk-menu-button menu-model>` takes)
|
|
25
25
|
//
|
|
26
|
-
// Extends <
|
|
27
|
-
// the
|
|
26
|
+
// Extends <gtk-application-window>, as Adw.ApplicationWindow extends Gtk.ApplicationWindow,
|
|
27
|
+
// so the Gtk.Window state members come with it. The content property, the size request, the
|
|
28
|
+
// breakpoints and the dialog bookkeeping are the same AdwWindowFeatures <adw-window> uses.
|
|
28
29
|
//
|
|
29
30
|
// Reference: refs/libadwaita/src/adw-application-window.c (the class adds no behaviour)
|
|
30
31
|
// Reference: Gtk-4.0.gir `Gtk.ApplicationWindow:show-menubar` ("If this property is true,
|
|
@@ -40,21 +41,56 @@ import {
|
|
|
40
41
|
type AdwMenuModel,
|
|
41
42
|
} from '@gjsify/adwaita-core';
|
|
42
43
|
|
|
43
|
-
import {
|
|
44
|
+
import type { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
45
|
+
import type { AdwSlot } from '../slotted-children.js';
|
|
46
|
+
import { AdwWindowFeatures } from './adw-window-features.js';
|
|
47
|
+
import { GtkApplicationWindow } from './gtk-application-window.js';
|
|
44
48
|
import { defineWindowApplication } from '../application.js';
|
|
45
49
|
|
|
46
|
-
export class AdwApplicationWindow extends
|
|
47
|
-
|
|
48
|
-
declare application: object | null;
|
|
49
|
-
private _menubarEl: HTMLDivElement | null = null;
|
|
50
|
+
export class AdwApplicationWindow extends GtkApplicationWindow {
|
|
51
|
+
private readonly _features = new AdwWindowFeatures(this);
|
|
50
52
|
private _model: AdwMenuModel = [];
|
|
53
|
+
private _menubarEl: HTMLDivElement | null = null;
|
|
51
54
|
|
|
52
55
|
// The base's list BY NAME, not `super.observedAttributes`: `AdwPasswordEntryRow`
|
|
53
56
|
// spells its spread this way and the attribute readers follow (`adwaita-elements.mjs`
|
|
54
57
|
// resolves a `[...Base.observedAttributes]` spread across files, and reports a
|
|
55
58
|
// `super` one as unreadable rather than as empty).
|
|
56
59
|
static get observedAttributes(): string[] {
|
|
57
|
-
return [...
|
|
60
|
+
return [...GtkApplicationWindow.observedAttributes, 'width', 'height', 'menu-model'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The content area — the Adw.Window:content property. */
|
|
64
|
+
get contentArea(): HTMLDivElement {
|
|
65
|
+
return this._content;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get content(): HTMLElement | null {
|
|
69
|
+
return this._features.content;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
set content(value: HTMLElement | null) {
|
|
73
|
+
this._features.content = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get breakpoints(): readonly AdwBreakpoint[] {
|
|
77
|
+
return this._features.breakpoints;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get currentBreakpoint(): AdwBreakpoint | null {
|
|
81
|
+
return this._features.currentBreakpoint;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get dialogs(): readonly HTMLElement[] {
|
|
85
|
+
return this._features.dialogs;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get visibleDialog(): HTMLElement | null {
|
|
89
|
+
return this._features.visibleDialog;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addBreakpoint(condition: string): AdwBreakpoint {
|
|
93
|
+
return this._features.addBreakpoint(condition);
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
/** Whether this window shows the application's menubar (Gtk.ApplicationWindow:show-menubar). */
|
|
@@ -84,17 +120,45 @@ export class AdwApplicationWindow extends AdwWindow {
|
|
|
84
120
|
|
|
85
121
|
connectedCallback() {
|
|
86
122
|
super.connectedCallback();
|
|
87
|
-
this.
|
|
123
|
+
this._features.adopt(this._content);
|
|
124
|
+
// EVERY connect, not only the first — see `AdwWindowFeatures.bind`.
|
|
125
|
+
this._features.bind();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
this._features.release();
|
|
130
|
+
super.disconnectedCallback();
|
|
88
131
|
}
|
|
89
132
|
|
|
90
133
|
attributeChangedCallback(name: string) {
|
|
134
|
+
if (name === 'width' || name === 'height') {
|
|
135
|
+
this.renderFrameSize();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
91
138
|
super.attributeChangedCallback(name);
|
|
92
139
|
// The attribute is the declarative form and it wins when it changes — the same
|
|
93
140
|
// precedence `<gtk-menu-button>` gives `menu-model`, through the same parser.
|
|
94
|
-
if (name === 'menu-model')
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
141
|
+
if (name === 'menu-model') {
|
|
142
|
+
this._model = parseMenuModel(this.getAttribute('menu-model'));
|
|
143
|
+
this._renderMenubar();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** `Adw.ApplicationWindow` has no titlebar of its own, like `Adw.Window`. */
|
|
148
|
+
protected override hasTitlebar(): boolean {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected override strips(): Node[] {
|
|
153
|
+
return [this._content];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected override extraSlots(): AdwSlot[] {
|
|
157
|
+
return [{ name: 'content', into: this._content }];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected override renderFrameSize(): void {
|
|
161
|
+
this._features.applySizeRequest();
|
|
98
162
|
}
|
|
99
163
|
|
|
100
164
|
/**
|
|
@@ -104,17 +168,23 @@ export class AdwApplicationWindow extends AdwWindow {
|
|
|
104
168
|
* into the content area, so a sibling would be re-homed into it one tick later. The
|
|
105
169
|
* content area's own children are not watched, so prepending here is stable — and
|
|
106
170
|
* it is also where the bar belongs: above the toolbar view, inside the window box.
|
|
171
|
+
* Replaces the base's observer: this bar is filled from the model, not from a slot,
|
|
172
|
+
* and observing it would re-trigger every render.
|
|
107
173
|
*/
|
|
108
|
-
|
|
109
|
-
if (this._menubarEl)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
174
|
+
protected override _followMenubar(): void {
|
|
175
|
+
if (this._menubarEl === null) {
|
|
176
|
+
this._menubarEl = document.createElement('div');
|
|
177
|
+
this._menubarEl.className = 'adw-application-window-menubar';
|
|
178
|
+
this._content.prepend(this._menubarEl);
|
|
179
|
+
}
|
|
114
180
|
this._renderMenubar();
|
|
115
181
|
}
|
|
116
182
|
|
|
117
|
-
|
|
183
|
+
protected override menubarSection(): HTMLDivElement {
|
|
184
|
+
return this._menubarEl ?? super.menubarSection();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected override _renderMenubar(): void {
|
|
118
188
|
const bar = this._menubarEl;
|
|
119
189
|
if (bar === null) return;
|
|
120
190
|
|
|
@@ -110,6 +110,15 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
110
110
|
this._setBoolAttr('modal', value);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/** `adw_bottom_sheet_get_modal` — the NativeScript port's only door to it (ADR 0112). */
|
|
114
|
+
get_modal(): boolean {
|
|
115
|
+
return this.modal;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
set_modal(modal: boolean): void {
|
|
119
|
+
this.modal = modal;
|
|
120
|
+
}
|
|
121
|
+
|
|
113
122
|
/** Whether the user can dismiss the sheet (dimming / Escape / sheet.close). */
|
|
114
123
|
get canClose(): boolean {
|
|
115
124
|
return this._state.canClose;
|
|
@@ -111,6 +111,24 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
111
111
|
if (this._initialized) this._render();
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// The verbs are the spelling the NativeScript port has to use, where `GridLayout` owns both
|
|
115
|
+
// accessor names (ADR 0112); offered here too so one code-behind runs on both.
|
|
116
|
+
get_columns(): AdwDataGridColumn[] {
|
|
117
|
+
return this.columns;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set_columns(value: ReadonlyArray<AdwDataGridColumn>): void {
|
|
121
|
+
this.columns = value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get_rows(): AdwDataGridRow[] {
|
|
125
|
+
return this.rows;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set_rows(value: ReadonlyArray<AdwDataGridRow>): void {
|
|
129
|
+
this.rows = value;
|
|
130
|
+
}
|
|
131
|
+
|
|
114
132
|
/** The optional grid caption/title (reflected to the attribute). */
|
|
115
133
|
get caption(): string {
|
|
116
134
|
return this.getAttribute('caption') ?? '';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// What `Adw.Window` and `Adw.ApplicationWindow` add on top of their Gtk parent: the content
|
|
2
|
+
// area, the breakpoints and the open-dialog bookkeeping.
|
|
3
|
+
//
|
|
4
|
+
// A HELPER, NOT A BASE CLASS. Upstream `AdwWindow` extends `GtkWindow` and `AdwApplicationWindow`
|
|
5
|
+
// extends `GtkApplicationWindow` (Adw-1.gir), so the two cannot share an Adw base without one of
|
|
6
|
+
// them losing its Gtk parent. A custom element has one prototype chain, so the shared part is
|
|
7
|
+
// composed in: each class owns one of these and forwards to it.
|
|
8
|
+
//
|
|
9
|
+
// Reference: refs/libadwaita/src/adw-window.c
|
|
10
|
+
// Copyright (c) 2020-2023 Alice Mikhaylenko / Purism SPC (libadwaita). LGPLv2.1+
|
|
11
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
12
|
+
|
|
13
|
+
import { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
14
|
+
import { addBreakpoints } from '../breakpoints.js';
|
|
15
|
+
|
|
16
|
+
/** The default minimum size of an AdwWindow, from adw_window_init (C:344). */
|
|
17
|
+
const DEFAULT_WIDTH = 360;
|
|
18
|
+
const DEFAULT_HEIGHT = 200;
|
|
19
|
+
|
|
20
|
+
export class AdwWindowFeatures {
|
|
21
|
+
private _contentEl: HTMLDivElement | null = null;
|
|
22
|
+
private _breakpoints: AdwBreakpoint[] = [];
|
|
23
|
+
private _disposeBreakpoints: (() => void) | null = null;
|
|
24
|
+
private _openDialogs: HTMLElement[] = [];
|
|
25
|
+
|
|
26
|
+
constructor(private readonly _host: HTMLElement) {}
|
|
27
|
+
|
|
28
|
+
/** The content area, or `null` before the window is first connected. */
|
|
29
|
+
get contentArea(): HTMLDivElement | null {
|
|
30
|
+
return this._contentEl;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The content widget — the first child of the content area. */
|
|
34
|
+
get content(): HTMLElement | null {
|
|
35
|
+
return (this._contentEl?.firstElementChild as HTMLElement | null) ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set content(value: HTMLElement | null) {
|
|
39
|
+
if (value) this._contentEl?.appendChild(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get breakpoints(): readonly AdwBreakpoint[] {
|
|
43
|
+
return this._breakpoints;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get currentBreakpoint(): AdwBreakpoint | null {
|
|
47
|
+
return this._breakpoints.find((bp) => bp.applied) ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get dialogs(): readonly HTMLElement[] {
|
|
51
|
+
// A dialog taken out of the document never raises `notify::open` on the way
|
|
52
|
+
// out, so a dialog that was REMOVED while open would otherwise stay listed
|
|
53
|
+
// for the rest of the window's life.
|
|
54
|
+
this._openDialogs = this._openDialogs.filter((dialog) => dialog.isConnected);
|
|
55
|
+
return this._openDialogs;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get visibleDialog(): HTMLElement | null {
|
|
59
|
+
const dialogs = this.dialogs;
|
|
60
|
+
return dialogs.length > 0 ? (dialogs[dialogs.length - 1] as HTMLElement) : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Take the content area the Gtk parent built, once. Tracks open dialogs presented inside
|
|
65
|
+
* the window: an AdwDialog raises `notify::open`, which bubbles to the host.
|
|
66
|
+
*/
|
|
67
|
+
adopt(contentEl: HTMLDivElement): void {
|
|
68
|
+
if (this._contentEl !== null) return;
|
|
69
|
+
this._contentEl = contentEl;
|
|
70
|
+
contentEl.classList.add('adw-window-content');
|
|
71
|
+
this._host.addEventListener('notify::open', (e) => {
|
|
72
|
+
const dialog = e.target as HTMLElement;
|
|
73
|
+
if (dialog === this._host) return;
|
|
74
|
+
if (dialog.hasAttribute('open')) {
|
|
75
|
+
if (!this._openDialogs.includes(dialog)) this._openDialogs.push(dialog);
|
|
76
|
+
} else {
|
|
77
|
+
this._openDialogs = this._openDialogs.filter((d) => d !== dialog);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Add a breakpoint (adw_window_add_breakpoint). The breakpoint evaluates against the
|
|
84
|
+
* window's own box and sets a `data-breakpoint` attribute when applied.
|
|
85
|
+
*/
|
|
86
|
+
addBreakpoint(condition: string): AdwBreakpoint {
|
|
87
|
+
const host = this._host;
|
|
88
|
+
const bp = new AdwBreakpoint(condition, {
|
|
89
|
+
onApply: () => (host.dataset.breakpoint = condition),
|
|
90
|
+
onUnapply: () => {
|
|
91
|
+
if (host.dataset.breakpoint === condition) delete host.dataset.breakpoint;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
this._breakpoints.push(bp);
|
|
95
|
+
// Re-evaluated against a box that may have changed since the last add.
|
|
96
|
+
this.bind();
|
|
97
|
+
return bp;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Bind the breakpoints to THIS box.
|
|
102
|
+
*
|
|
103
|
+
* NOT once per lifetime: `release` drops the disposer, so a window moved between
|
|
104
|
+
* parents would come back deaf — and a window is exactly the thing a client-side route
|
|
105
|
+
* change re-parents. Re-binding is cheap and idempotent, and it re-measures the new box.
|
|
106
|
+
*/
|
|
107
|
+
bind(): void {
|
|
108
|
+
this._disposeBreakpoints?.();
|
|
109
|
+
this._disposeBreakpoints = null;
|
|
110
|
+
if (this._contentEl !== null && this._breakpoints.length > 0) {
|
|
111
|
+
this._disposeBreakpoints = addBreakpoints(this._host, this._breakpoints);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
release(): void {
|
|
116
|
+
this._disposeBreakpoints?.();
|
|
117
|
+
this._disposeBreakpoints = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The window's size request — GTK's, not CSS's, so an absent attribute means the default. */
|
|
121
|
+
applySizeRequest(): void {
|
|
122
|
+
const host = this._host;
|
|
123
|
+
const width = host.getAttribute('default-width') ?? host.getAttribute('width');
|
|
124
|
+
const height = host.getAttribute('default-height') ?? host.getAttribute('height');
|
|
125
|
+
host.style.width = width ? `${width}px` : `${DEFAULT_WIDTH}px`;
|
|
126
|
+
host.style.height = height ? `${height}px` : `${DEFAULT_HEIGHT}px`;
|
|
127
|
+
}
|
|
128
|
+
}
|