@gjsify/adwaita-web 0.62.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/README.md +31 -0
- 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 +44 -0
- package/lib/types/gtk-window.spec.d.ts +6 -0
- package/lib/types/pango-markup.d.ts +10 -0
- package/lib/types/pango-markup.spec.d.ts +1 -0
- package/lib/types/source-view/copy-glyph.d.ts +6 -0
- package/lib/types/source-view/copy-glyph.spec.d.ts +1 -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 +34 -1
- package/src/application.ts +16 -0
- package/src/bottom-sheet.spec.ts +9 -0
- package/src/canvas2d/canvas2d-bridge.ts +24 -8
- package/src/elements/adw-alert-dialog.ts +8 -5
- package/src/elements/adw-application-window.ts +90 -20
- package/src/elements/adw-banner.ts +14 -9
- 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 +170 -18
- package/src/gtk-window.spec.ts +133 -0
- package/src/pango-markup.spec.ts +257 -0
- package/src/pango-markup.ts +266 -0
- package/src/source-view/adw-source-view.ts +2 -8
- package/src/source-view/copy-glyph.spec.ts +46 -0
- package/src/source-view/copy-glyph.ts +52 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
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;
|
|
@@ -63,6 +63,39 @@ export const ApplicationTest = async () => {
|
|
|
63
63
|
await it('Adw.Application is a Gtk.Application', () => {
|
|
64
64
|
expect(Adw.Application.prototype instanceof Gtk.Application).toBe(true);
|
|
65
65
|
});
|
|
66
|
+
await it('a keydown in the window runs the accelerator, with its target', () => {
|
|
67
|
+
const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebAccel' });
|
|
68
|
+
const window = scene.window(app as never) as Element;
|
|
69
|
+
const hits: unknown[] = [];
|
|
70
|
+
for (const [owner, name] of [
|
|
71
|
+
[app, 'save'],
|
|
72
|
+
[window, 'open'],
|
|
73
|
+
[app, 'view'],
|
|
74
|
+
] as const) {
|
|
75
|
+
const action = new Gio.SimpleAction({ name });
|
|
76
|
+
action.connect('activate', (_a: unknown, parameter: unknown) => hits.push([name, parameter ?? null]));
|
|
77
|
+
(owner as unknown as { add_action(a: object): void }).add_action(action);
|
|
78
|
+
}
|
|
79
|
+
app.set_accels_for_action('app.save', ['<Primary>s']);
|
|
80
|
+
app.set_accels_for_action('win.open', ['<Primary><Shift>o']);
|
|
81
|
+
app.set_accels_for_action('app.view::list', ['<Primary>l']);
|
|
82
|
+
const press = (init: KeyboardEventInit): boolean => {
|
|
83
|
+
const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init });
|
|
84
|
+
window.dispatchEvent(event);
|
|
85
|
+
return event.defaultPrevented;
|
|
86
|
+
};
|
|
87
|
+
expect(press({ key: 's', ctrlKey: true })).toBe(true);
|
|
88
|
+
expect(press({ key: 'O', ctrlKey: true, shiftKey: true })).toBe(true);
|
|
89
|
+
expect(press({ key: 'l', ctrlKey: true })).toBe(true);
|
|
90
|
+
expect(press({ key: 's' })).toBe(false);
|
|
91
|
+
expect(hits).toStrictEqual([
|
|
92
|
+
['save', null],
|
|
93
|
+
['open', null],
|
|
94
|
+
['view', 'list'],
|
|
95
|
+
]);
|
|
96
|
+
(window as unknown as { application: object | null }).application = null;
|
|
97
|
+
expect(press({ key: 's', ctrlKey: true })).toBe(false);
|
|
98
|
+
});
|
|
66
99
|
await it('quit() takes the windows it added off the page', async () => {
|
|
67
100
|
const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebQuit' });
|
|
68
101
|
const window = scene.window(app as never) as Element;
|
package/src/application.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
activateWidgetAction as activateIn,
|
|
12
12
|
activateMenuItem,
|
|
13
13
|
applicationOfWindow,
|
|
14
|
+
dispatchAccelerator,
|
|
14
15
|
menuActionsFor,
|
|
15
16
|
installActionMap,
|
|
16
17
|
type AdwMenuItem,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type AdwMenuModel,
|
|
19
20
|
type ApplicationHost,
|
|
20
21
|
} from '@gjsify/adwaita-core';
|
|
22
|
+
import { webPlatformHost } from './platform-host.js';
|
|
21
23
|
|
|
22
24
|
export const webHost: ApplicationHost = {
|
|
23
25
|
name: 'adwaita-web',
|
|
@@ -70,6 +72,19 @@ export class AdwApplication extends GtkApplication {}
|
|
|
70
72
|
|
|
71
73
|
type WindowApplication = { add_window(window: object): void; remove_window(window: object): void };
|
|
72
74
|
|
|
75
|
+
const ACCEL_LISTENERS = new WeakMap<object, () => void>();
|
|
76
|
+
|
|
77
|
+
/** Key presses in a window of an application run its accelerators; keydown of descendants bubbles here. */
|
|
78
|
+
function followAccelerators(window: object, application: WindowApplication | null): void {
|
|
79
|
+
ACCEL_LISTENERS.get(window)?.();
|
|
80
|
+
ACCEL_LISTENERS.delete(window);
|
|
81
|
+
if (application === null) return;
|
|
82
|
+
ACCEL_LISTENERS.set(
|
|
83
|
+
window,
|
|
84
|
+
webPlatformHost.listenKeys(window, (event) => dispatchAccelerator(window, event, parentOfElement)),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
/**
|
|
74
89
|
* `Gtk.Window:application` and the window's `Gio.ActionMap` (`win.` actions) on an element class.
|
|
75
90
|
* Setting the property is `add_window`, as on GTK; setting it to `null` is `remove_window`.
|
|
@@ -87,6 +102,7 @@ export function defineWindowApplication(proto: object): void {
|
|
|
87
102
|
if (current === value) return;
|
|
88
103
|
current?.remove_window(this);
|
|
89
104
|
value?.add_window(this);
|
|
105
|
+
followAccelerators(this, value);
|
|
90
106
|
},
|
|
91
107
|
});
|
|
92
108
|
}
|
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
|
|
|
@@ -25,9 +25,11 @@
|
|
|
25
25
|
// `Adw.AlertDialog:heading-use-markup` and `:body-use-markup` both default to FALSE
|
|
26
26
|
// (`Adw.Banner:use-markup` is the one that defaults to TRUE, which is why
|
|
27
27
|
// `adw-banner.ts` documents a deliberate departure and this file does not need to).
|
|
28
|
-
// Opting in renders through `
|
|
29
|
-
//
|
|
30
|
-
//
|
|
28
|
+
// Opting in renders PANGO MARKUP through `renderPangoMarkup`: the string is PARSED by
|
|
29
|
+
// `@gjsify/adwaita-core` and turned into nodes one at a time, never handed to
|
|
30
|
+
// `innerHTML`. So opting in is no longer an assertion that the string is trusted —
|
|
31
|
+
// `<b>` is drawn bold whether or not the author vouched for it, and a tag pango does
|
|
32
|
+
// not know comes back as text.
|
|
31
33
|
//
|
|
32
34
|
// Buttons are stacked vertically by default (the Adw.AlertDialog default at medium
|
|
33
35
|
// sizes) and laid out horizontally when prefer-wide-layout is set and there are no more
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
import { AdwAlertResponses } from '@gjsify/adwaita-core';
|
|
57
59
|
import type { AdwResponseAppearance } from '@gjsify/adwaita-core';
|
|
58
60
|
|
|
61
|
+
import { renderPangoMarkup } from '../pango-markup.js';
|
|
59
62
|
import { bindEmptySections } from '../empty-sections.js';
|
|
60
63
|
import { bindSlottedChildren } from '../slotted-children.js';
|
|
61
64
|
import { AdwModalSurface } from './modal-surface.js';
|
|
@@ -463,12 +466,12 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
463
466
|
this.classList.toggle('open', this.open);
|
|
464
467
|
|
|
465
468
|
const heading = this.heading;
|
|
466
|
-
if (this.headingUseMarkup) this._headingEl
|
|
469
|
+
if (this.headingUseMarkup) renderPangoMarkup(this._headingEl, heading);
|
|
467
470
|
else this._headingEl.textContent = heading;
|
|
468
471
|
this._headingEl.hidden = heading.length === 0;
|
|
469
472
|
|
|
470
473
|
const body = this.body;
|
|
471
|
-
if (this.bodyUseMarkup) this._bodyEl
|
|
474
|
+
if (this.bodyUseMarkup) renderPangoMarkup(this._bodyEl, body);
|
|
472
475
|
else this._bodyEl.textContent = body;
|
|
473
476
|
this._bodyEl.hidden = body.length === 0;
|
|
474
477
|
|
|
@@ -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
|
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
parseBannerButtonStyle,
|
|
21
21
|
} from '@gjsify/adwaita-core';
|
|
22
22
|
|
|
23
|
+
import { renderPangoMarkup } from '../pango-markup.js';
|
|
24
|
+
|
|
23
25
|
export class AdwBanner extends HTMLElement {
|
|
24
26
|
private _titleEl!: HTMLSpanElement;
|
|
25
27
|
private _button!: HTMLButtonElement;
|
|
@@ -52,17 +54,20 @@ export class AdwBanner extends HTMLElement {
|
|
|
52
54
|
|
|
53
55
|
private _render() {
|
|
54
56
|
const title = this.getAttribute('title') ?? '';
|
|
55
|
-
// DELIBERATE DEPARTURE FROM THE C DEFAULT
|
|
56
|
-
//
|
|
57
|
-
// carries that as the spec
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
57
|
+
// DELIBERATE DEPARTURE FROM THE C DEFAULT, and the departure is now about
|
|
58
|
+
// SAFETY rather than about how markup is spelt. `Adw.Banner:use-markup`
|
|
59
|
+
// defaults to TRUE and `@gjsify/adwaita-core` carries that as the spec
|
|
60
|
+
// value — but a `title` attribute is an ATTRIBUTE, and a renderer that
|
|
61
|
+
// parsed it as markup would be one an author (or an injected value) could
|
|
62
|
+
// drive. Adopting the C default here would make every banner title markup
|
|
63
|
+
// by default for a widget that is not one in GTK, so on this renderer
|
|
64
|
+
// markup is OPT-IN and the default is text.
|
|
65
|
+
// Opting in renders PANGO markup — parsed by `renderPangoMarkup`, never
|
|
66
|
+
// handed to `innerHTML`, so nothing the title carries can become a node
|
|
67
|
+
// beyond the eleven tags pango itself knows.
|
|
63
68
|
// The title label carries `use-underline=False` in the template
|
|
64
69
|
//, so its underscores are literal — no stripping here.
|
|
65
|
-
if (this.hasAttribute('use-markup')) this._titleEl
|
|
70
|
+
if (this.hasAttribute('use-markup')) renderPangoMarkup(this._titleEl, title);
|
|
66
71
|
else this._titleEl.textContent = title;
|
|
67
72
|
|
|
68
73
|
// `label && label[0]` — a first-character test, so a label of
|
|
@@ -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') ?? '';
|