@gjsify/adwaita-web 0.58.0 → 0.59.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/lib/types/clipboard-host.d.ts +7 -0
- package/lib/types/gdk.spec.d.ts +1 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +7 -1
- package/lib/types/gtksource/web-editor-driver.d.ts +9 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/namespace/gdk.d.ts +7 -0
- package/lib/types/namespace/gobject.d.ts +1 -0
- package/lib/types/namespace/gtk-enums.d.ts +5 -0
- package/lib/types/namespace/gtk.d.ts +1 -1
- package/package.json +9 -9
- package/src/clipboard-host.ts +47 -0
- package/src/gdk.spec.ts +122 -0
- package/src/gobject-door.ts +7 -0
- package/src/gtksource/gtk-source-view.spec.ts +89 -12
- package/src/gtksource/gtk-source-view.ts +21 -5
- package/src/gtksource/web-editor-driver.ts +50 -7
- package/src/index.ts +2 -0
- package/src/namespace/gdk.ts +17 -0
- package/src/namespace/gobject.ts +1 -0
- package/src/namespace/gtk-enums.ts +24 -5
- package/src/namespace/gtk.ts +1 -1
- package/src/test.browser.mts +2 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClipboardHost } from '@gjsify/adwaita-core';
|
|
2
|
+
/**
|
|
3
|
+
* Runs `run` as the handler of a DOM `copy` event: `Clipboard.set_content` calls made meanwhile
|
|
4
|
+
* land in `data`. Returns whether any did, so the caller knows to `preventDefault()`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function interceptCopy(data: DataTransfer | null, run: () => void): boolean;
|
|
7
|
+
export declare const webClipboardHost: ClipboardHost;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GdkTest: () => Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GtkAdjustment } from '@gjsify/adwaita-core';
|
|
2
2
|
import type { Buffer, Gutter } from '@gjsify/gtksource-core';
|
|
3
|
+
import { STOP_EMISSION } from '@gjsify/gtksource-core';
|
|
3
4
|
export declare class GtkSourceView extends HTMLElement {
|
|
4
5
|
static get observedAttributes(): string[];
|
|
5
6
|
private readonly session;
|
|
@@ -7,6 +8,7 @@ export declare class GtkSourceView extends HTMLElement {
|
|
|
7
8
|
private unsubscribe;
|
|
8
9
|
private built;
|
|
9
10
|
private readonly gutter;
|
|
11
|
+
private readonly rightGutter;
|
|
10
12
|
private readonly backdrop;
|
|
11
13
|
private readonly area;
|
|
12
14
|
private readonly vadj;
|
|
@@ -71,7 +73,11 @@ export declare class GtkSourceView extends HTMLElement {
|
|
|
71
73
|
get bottomMargin(): number;
|
|
72
74
|
set bottomMargin(value: number | string);
|
|
73
75
|
/** GJS `connect(name, cb) → id`; the handler's first argument is this view. */
|
|
74
|
-
connect(name: string, callback: (self: GtkSourceView, ...args: never[]) =>
|
|
76
|
+
connect(name: string, callback: (self: GtkSourceView, ...args: never[]) => unknown): number;
|
|
77
|
+
/** `g_signal_connect_after`: runs once the plain handlers and the default of the emission have run. */
|
|
78
|
+
connect_after(name: string, callback: (self: GtkSourceView, ...args: never[]) => unknown): number;
|
|
79
|
+
/** `GObject.signal_stop_emission_by_name(view, …)`: the session owns the emissions. */
|
|
80
|
+
[STOP_EMISSION](detailedSignal: string): void;
|
|
75
81
|
disconnect(id: number): void;
|
|
76
82
|
/** The inner textarea, for focus and tests. */
|
|
77
83
|
get textarea(): HTMLTextAreaElement;
|
|
@@ -4,9 +4,12 @@ export interface EditorParts {
|
|
|
4
4
|
readonly area: HTMLTextAreaElement;
|
|
5
5
|
readonly backdrop: HTMLElement;
|
|
6
6
|
readonly gutter: HTMLElement;
|
|
7
|
+
/** The RIGHT gutter's element, right of the text. */
|
|
8
|
+
readonly rightGutter: HTMLElement;
|
|
7
9
|
}
|
|
8
10
|
export declare class WebEditorDriver implements EditorDriver {
|
|
9
11
|
private readonly parts;
|
|
12
|
+
readonly gutterSides: number[];
|
|
10
13
|
private host;
|
|
11
14
|
private text;
|
|
12
15
|
private caret;
|
|
@@ -25,6 +28,11 @@ export declare class WebEditorDriver implements EditorDriver {
|
|
|
25
28
|
detach(): void;
|
|
26
29
|
private onInput;
|
|
27
30
|
private onSelection;
|
|
31
|
+
/**
|
|
32
|
+
* The second and third press of a multi-click: GTK asks `extend-selection` before it selects the word
|
|
33
|
+
* or line. The first press has already placed the caret, which is the location asked about.
|
|
34
|
+
*/
|
|
35
|
+
private onMouseDown;
|
|
28
36
|
/** `accepts-tab` is GTK's default; a textarea would move focus, so Tab inserts a tab. */
|
|
29
37
|
private onKey;
|
|
30
38
|
setText(text: string): void;
|
|
@@ -43,6 +51,7 @@ export declare class WebEditorDriver implements EditorDriver {
|
|
|
43
51
|
/** One repaint per task: a burst of `paintLine` calls from one edit costs one DOM rebuild. */
|
|
44
52
|
private scheduleRender;
|
|
45
53
|
private hasColumns;
|
|
54
|
+
private hasRightColumns;
|
|
46
55
|
private render;
|
|
47
56
|
/** The lines in view, or all of them where nothing is laid out (a detached element, a test DOM). */
|
|
48
57
|
private visibleLines;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { isIconAvailable, registerIcon } from './icon-registry.js';
|
|
|
12
12
|
export * as Adw from './namespace/adw.js';
|
|
13
13
|
export * as Gtk from './namespace/gtk.js';
|
|
14
14
|
export * as Gio from './namespace/gio.js';
|
|
15
|
+
export * as Gdk from './namespace/gdk.js';
|
|
15
16
|
export * as GLib from './namespace/glib.js';
|
|
16
17
|
export * as GObject from './namespace/gobject.js';
|
|
17
18
|
export * as GtkSource from './namespace/gtksource.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const gdk: import("@gjsify/adwaita-core").GdkClipboard;
|
|
2
|
+
export declare const Display: {
|
|
3
|
+
get_default(): import("@gjsify/adwaita-core").Display;
|
|
4
|
+
};
|
|
5
|
+
export declare const Clipboard: typeof import("@gjsify/adwaita-core").Clipboard;
|
|
6
|
+
export declare const ContentProvider: Pick<typeof import("@gjsify/adwaita-core").ContentProvider, "new_for_value">;
|
|
7
|
+
export declare const ContentFormats: typeof import("@gjsify/adwaita-core").ContentFormats;
|
|
@@ -11,6 +11,7 @@ export declare const ParamFlags: {
|
|
|
11
11
|
readonly CONSTRUCT: 4;
|
|
12
12
|
readonly CONSTRUCT_ONLY: 8;
|
|
13
13
|
};
|
|
14
|
+
export declare const Value: typeof import("@gjsify/adwaita-core").Value;
|
|
14
15
|
export declare const TYPE_STRING: import("@gjsify/adwaita-core").GType;
|
|
15
16
|
export declare const TYPE_BOOLEAN: import("@gjsify/adwaita-core").GType;
|
|
16
17
|
export declare const TYPE_INT: import("@gjsify/adwaita-core").GType;
|
|
@@ -20,3 +20,8 @@ export declare const TextWindowType: Readonly<{
|
|
|
20
20
|
readonly TOP: 5;
|
|
21
21
|
readonly BOTTOM: 6;
|
|
22
22
|
}>;
|
|
23
|
+
/** `Gtk.TextExtendSelection`: `WORD` 0 (a double click), `LINE` 1 (a triple click). */
|
|
24
|
+
export declare const TextExtendSelection: Readonly<{
|
|
25
|
+
readonly WORD: 0;
|
|
26
|
+
readonly LINE: 1;
|
|
27
|
+
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { GtkAdjustment as Adjustment } from '@gjsify/adwaita-core';
|
|
2
|
-
export { PolicyType, TextDirection, TextWindowType } from './gtk-enums.js';
|
|
2
|
+
export { PolicyType, TextDirection, TextExtendSelection, TextWindowType } from './gtk-enums.js';
|
|
3
3
|
export { GtkAboutDialog as AboutDialog } from '../elements/gtk-about-dialog.js';
|
|
4
4
|
export { GtkActionBar as ActionBar } from '../elements/gtk-action-bar.js';
|
|
5
5
|
export { GtkApplicationWindow as ApplicationWindow } from '../elements/gtk-application-window.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
"@codemirror/language": "^6.12.4",
|
|
51
51
|
"@codemirror/state": "^6.7.0",
|
|
52
52
|
"@codemirror/view": "^6.43.4",
|
|
53
|
-
"@gjsify/adwaita-core": "^0.
|
|
54
|
-
"@gjsify/adwaita-fonts": "^0.
|
|
55
|
-
"@gjsify/adwaita-icons": "^0.
|
|
56
|
-
"@gjsify/gtksource-core": "^0.
|
|
53
|
+
"@gjsify/adwaita-core": "^0.59.0",
|
|
54
|
+
"@gjsify/adwaita-fonts": "^0.59.0",
|
|
55
|
+
"@gjsify/adwaita-icons": "^0.59.0",
|
|
56
|
+
"@gjsify/gtksource-core": "^0.59.0",
|
|
57
57
|
"@lezer/highlight": "^1.2.3"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@gjsify/blueprint": "^0.
|
|
61
|
-
"@gjsify/cli": "^0.
|
|
62
|
-
"@gjsify/unit": "^0.
|
|
63
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
60
|
+
"@gjsify/blueprint": "^0.59.0",
|
|
61
|
+
"@gjsify/cli": "^0.59.0",
|
|
62
|
+
"@gjsify/unit": "^0.59.0",
|
|
63
|
+
"@gjsify/vite-plugin-blueprint": "^0.59.0",
|
|
64
64
|
"sass": "^1.101.0",
|
|
65
65
|
"typescript": "^6.0.3"
|
|
66
66
|
},
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// How the web door publishes `Gdk.Clipboard` content (ADR 0096 Amendment 2).
|
|
2
|
+
//
|
|
3
|
+
// The browser has two paths and they are not interchangeable:
|
|
4
|
+
// - inside a DOM `copy` event, `event.clipboardData.setData` is synchronous and needs no
|
|
5
|
+
// permission, but only until the event returns; the handler must then `preventDefault()` so the
|
|
6
|
+
// browser does not replace the data with the selection. This is the path of a `copy-clipboard`
|
|
7
|
+
// handler that replaces the default copy.
|
|
8
|
+
// - anywhere else, `navigator.clipboard.writeText` is asynchronous and needs a secure context and
|
|
9
|
+
// a user activation (or the permission); a refusal rejects and the core logs it.
|
|
10
|
+
|
|
11
|
+
import type { ClipboardHost } from '@gjsify/adwaita-core';
|
|
12
|
+
|
|
13
|
+
let copying: { data: DataTransfer; wrote: boolean } | null = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Runs `run` as the handler of a DOM `copy` event: `Clipboard.set_content` calls made meanwhile
|
|
17
|
+
* land in `data`. Returns whether any did, so the caller knows to `preventDefault()`.
|
|
18
|
+
*/
|
|
19
|
+
export function interceptCopy(data: DataTransfer | null, run: () => void): boolean {
|
|
20
|
+
if (!data) {
|
|
21
|
+
run();
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const outer = copying;
|
|
25
|
+
const frame = { data, wrote: false };
|
|
26
|
+
copying = frame;
|
|
27
|
+
try {
|
|
28
|
+
run();
|
|
29
|
+
} finally {
|
|
30
|
+
copying = outer;
|
|
31
|
+
}
|
|
32
|
+
return frame.wrote;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const webClipboardHost: ClipboardHost = {
|
|
36
|
+
writeText(text) {
|
|
37
|
+
if (copying) {
|
|
38
|
+
copying.data.setData('text/plain', text);
|
|
39
|
+
copying.wrote = true;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard) {
|
|
43
|
+
return Promise.reject(new Error('navigator.clipboard is not available here'));
|
|
44
|
+
}
|
|
45
|
+
return navigator.clipboard.writeText(text);
|
|
46
|
+
},
|
|
47
|
+
};
|
package/src/gdk.spec.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// The Gdk clipboard door of adwaita-web, held to the shared vectors (real GJS is their oracle, in
|
|
2
|
+
// adwaita-core's `gdk.gjs.spec.ts`), plus what only a browser shows: the async Clipboard API for a
|
|
3
|
+
// write outside an event, and the `copy` event's `clipboardData` inside one. Neither needs the
|
|
4
|
+
// permission: `navigator.clipboard` is replaced by a recording fake, and the event carries a
|
|
5
|
+
// `DataTransfer` the test reads.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
driveGdkClipboardVectors,
|
|
9
|
+
GDK_CLIPBOARD_VECTORS,
|
|
10
|
+
type GdkClipboardNamespaces,
|
|
11
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
12
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
13
|
+
|
|
14
|
+
import { GtkSourceView } from './gtksource/gtk-source-view.js';
|
|
15
|
+
import * as Gdk from './namespace/gdk.js';
|
|
16
|
+
import * as GObject from './namespace/gobject.js';
|
|
17
|
+
|
|
18
|
+
async function withClipboardApi<T>(
|
|
19
|
+
api: { writeText(text: string): Promise<void> },
|
|
20
|
+
body: () => Promise<T>,
|
|
21
|
+
): Promise<T> {
|
|
22
|
+
const original = Object.getOwnPropertyDescriptor(navigator, 'clipboard');
|
|
23
|
+
Object.defineProperty(navigator, 'clipboard', { value: api, configurable: true });
|
|
24
|
+
try {
|
|
25
|
+
return await body();
|
|
26
|
+
} finally {
|
|
27
|
+
if (original) Object.defineProperty(navigator, 'clipboard', original);
|
|
28
|
+
else delete (navigator as unknown as Record<string, unknown>).clipboard;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
33
|
+
|
|
34
|
+
export const GdkTest = async () => {
|
|
35
|
+
const written: string[] = [];
|
|
36
|
+
await withClipboardApi({ writeText: async (text) => void written.push(text) }, () =>
|
|
37
|
+
driveGdkClipboardVectors(
|
|
38
|
+
{
|
|
39
|
+
name: 'adwaita-web',
|
|
40
|
+
isOracle: false,
|
|
41
|
+
display: true,
|
|
42
|
+
GObject,
|
|
43
|
+
Gdk,
|
|
44
|
+
readBack: async () => {
|
|
45
|
+
await tick();
|
|
46
|
+
return written[written.length - 1] ?? null;
|
|
47
|
+
},
|
|
48
|
+
} as unknown as GdkClipboardNamespaces & Parameters<typeof driveGdkClipboardVectors>[0],
|
|
49
|
+
{ describe, it, expect },
|
|
50
|
+
GDK_CLIPBOARD_VECTORS,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await describe('adwaita-web: Gdk clipboard door', async () => {
|
|
55
|
+
const mounted = () => {
|
|
56
|
+
const view = document.createElement('gtk-source-view') as GtkSourceView;
|
|
57
|
+
document.body.append(view);
|
|
58
|
+
return view;
|
|
59
|
+
};
|
|
60
|
+
const copyEvent = () =>
|
|
61
|
+
new ClipboardEvent('copy', { cancelable: true, bubbles: true, clipboardData: new DataTransfer() });
|
|
62
|
+
const setText = (text: string) => {
|
|
63
|
+
const value = new GObject.Value().init(GObject.TYPE_STRING);
|
|
64
|
+
value.set_string(text);
|
|
65
|
+
Gdk.Display.get_default().get_clipboard().set_content(Gdk.ContentProvider.new_for_value(value));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
await it('writes into the copy event when a copy-clipboard handler replaces the default copy', () => {
|
|
69
|
+
const view = mounted();
|
|
70
|
+
view.buffer.text = 'ff 00';
|
|
71
|
+
view.textarea.setSelectionRange(0, 2);
|
|
72
|
+
view.connect_after('copy-clipboard', () => setText('0xFF'));
|
|
73
|
+
const event = copyEvent();
|
|
74
|
+
view.textarea.dispatchEvent(event);
|
|
75
|
+
expect(event.clipboardData?.getData('text/plain')).toBe('0xFF');
|
|
76
|
+
expect(event.defaultPrevented).toBe(true);
|
|
77
|
+
view.remove();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await it('leaves the browser copy alone when no handler wrote anything', () => {
|
|
81
|
+
const view = mounted();
|
|
82
|
+
const event = copyEvent();
|
|
83
|
+
view.textarea.dispatchEvent(event);
|
|
84
|
+
expect(event.defaultPrevented).toBe(false);
|
|
85
|
+
view.remove();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await it('publishes through navigator.clipboard outside a copy event', async () => {
|
|
89
|
+
const seen: string[] = [];
|
|
90
|
+
await withClipboardApi({ writeText: async (text) => void seen.push(text) }, async () => {
|
|
91
|
+
setText('outside');
|
|
92
|
+
await tick();
|
|
93
|
+
});
|
|
94
|
+
expect(seen[0]).toBe('outside');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await it('warns, and keeps the content, when the browser refuses the write', async () => {
|
|
98
|
+
const warnings: unknown[][] = [];
|
|
99
|
+
const original = console.warn;
|
|
100
|
+
console.warn = (...args: unknown[]) => void warnings.push(args);
|
|
101
|
+
try {
|
|
102
|
+
await withClipboardApi({ writeText: () => Promise.reject(new Error('NotAllowedError')) }, async () => {
|
|
103
|
+
setText('refused');
|
|
104
|
+
await tick();
|
|
105
|
+
});
|
|
106
|
+
} finally {
|
|
107
|
+
console.warn = original;
|
|
108
|
+
}
|
|
109
|
+
expect(warnings.length).toBe(1);
|
|
110
|
+
expect(Gdk.Display.get_default().get_clipboard().get_formats().to_string()).toBe(
|
|
111
|
+
'gchararray text/plain;charset=utf-8 text/plain',
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await it('puts get_display and get_clipboard on a registered class', () => {
|
|
116
|
+
const Widget = GObject.registerClass({ GTypeName: 'GdkSpecWidget' }, class extends HTMLElement {});
|
|
117
|
+
const widget = new (Widget as unknown as new () => { get_display(): unknown; get_clipboard(): unknown })();
|
|
118
|
+
expect(widget.get_display()).toBe(Gdk.Display.get_default());
|
|
119
|
+
expect(widget.get_clipboard()).toBe(Gdk.Display.get_default().get_clipboard());
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
};
|
package/src/gobject-door.ts
CHANGED
|
@@ -18,9 +18,11 @@ import {
|
|
|
18
18
|
type GObjectDoor,
|
|
19
19
|
type RegisteredClass,
|
|
20
20
|
type TemplateScope,
|
|
21
|
+
widgetDisplayMembers,
|
|
21
22
|
} from '@gjsify/adwaita-core';
|
|
22
23
|
import { propertyOf } from '@gjsify/adwaita-core/tags';
|
|
23
24
|
|
|
25
|
+
import { gdk } from './namespace/gdk.js';
|
|
24
26
|
import { dispatchElement, listenElement } from './gobject-elements.js';
|
|
25
27
|
import { buildTemplateTree, type BuiltTemplateTree } from './shared-tree-builder.js';
|
|
26
28
|
import { registerTemplateClass } from './template-classes.js';
|
|
@@ -119,6 +121,11 @@ export const webDoor: GObjectDoor = {
|
|
|
119
121
|
Object.setPrototypeOf((klass as { prototype: object }).prototype, Constructing.prototype);
|
|
120
122
|
|
|
121
123
|
const proto = (klass as { prototype: Record<string, unknown> }).prototype;
|
|
124
|
+
// `this.get_display()` / `this.get_clipboard()`, as on a realized Gtk.Widget; a class's own wins.
|
|
125
|
+
for (const [name, member] of Object.entries(widgetDisplayMembers(gdk))) {
|
|
126
|
+
if (!(name in proto))
|
|
127
|
+
Object.defineProperty(proto, name, { value: member, writable: true, configurable: true });
|
|
128
|
+
}
|
|
122
129
|
const inherited = proto.connectedCallback as (() => void) | undefined;
|
|
123
130
|
Object.defineProperty(proto, 'connectedCallback', {
|
|
124
131
|
configurable: true,
|
|
@@ -2,10 +2,13 @@ import { describe, expect, it } from '@gjsify/unit';
|
|
|
2
2
|
import {
|
|
3
3
|
GUTTER_PAINT_VECTORS,
|
|
4
4
|
GTKSOURCE_BUFFER_VECTORS,
|
|
5
|
+
GTKSOURCE_STOP_VECTORS,
|
|
5
6
|
GTKSOURCE_VIEW_DEFAULT_VECTORS,
|
|
6
7
|
GTKSOURCE_VIEW_SURFACE_VECTORS,
|
|
7
8
|
type GtkSourceViewSurfaceLike,
|
|
8
9
|
type GtkSourceBufferLike,
|
|
10
|
+
type GtkSourceStopGestures,
|
|
11
|
+
type GtkSourceStopSurface,
|
|
9
12
|
type GutterPaintSurface,
|
|
10
13
|
type ViewLike,
|
|
11
14
|
} from '@gjsify/gtksource-core/conformance';
|
|
@@ -34,9 +37,44 @@ export const GtkSourceViewTest = async () => {
|
|
|
34
37
|
|
|
35
38
|
for (const vector of GTKSOURCE_VIEW_SURFACE_VECTORS) {
|
|
36
39
|
await it(`surface: ${vector.rule}`, () => {
|
|
37
|
-
expect(
|
|
38
|
-
vector.
|
|
39
|
-
|
|
40
|
+
expect(
|
|
41
|
+
vector.observe({
|
|
42
|
+
Gtk,
|
|
43
|
+
GtkSource,
|
|
44
|
+
} as unknown as GtkSourceViewSurfaceLike),
|
|
45
|
+
).toStrictEqual(vector.shows);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The driver listens to the textarea only while the view is in a document.
|
|
50
|
+
const mounted = (view: unknown): GtkSourceView => {
|
|
51
|
+
const element = view as GtkSourceView;
|
|
52
|
+
if (!element.isConnected) document.body.append(element);
|
|
53
|
+
return element;
|
|
54
|
+
};
|
|
55
|
+
const gestures: GtkSourceStopGestures = {
|
|
56
|
+
extendSelection: (view, offset) => {
|
|
57
|
+
const area = mounted(view).textarea;
|
|
58
|
+
area.setSelectionRange(offset, offset);
|
|
59
|
+
const down = new MouseEvent('mousedown', {
|
|
60
|
+
detail: 2,
|
|
61
|
+
button: 0,
|
|
62
|
+
cancelable: true,
|
|
63
|
+
bubbles: true,
|
|
64
|
+
});
|
|
65
|
+
area.dispatchEvent(down);
|
|
66
|
+
return down.defaultPrevented ? [area.selectionStart, area.selectionEnd] : 'native';
|
|
67
|
+
},
|
|
68
|
+
copyClipboard: (view) => {
|
|
69
|
+
const copy = new Event('copy', { cancelable: true, bubbles: true });
|
|
70
|
+
mounted(view).textarea.dispatchEvent(copy);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
for (const vector of GTKSOURCE_STOP_VECTORS) {
|
|
74
|
+
await it(`stopping an emission: ${vector.rule}`, () => {
|
|
75
|
+
const seen = vector.observe({ GtkSource, GObject } as unknown as GtkSourceStopSurface, gestures);
|
|
76
|
+
for (const view of document.body.querySelectorAll('gtk-source-view')) view.remove();
|
|
77
|
+
expect(seen).toStrictEqual(vector.shows);
|
|
40
78
|
});
|
|
41
79
|
}
|
|
42
80
|
|
|
@@ -67,11 +105,18 @@ export const GtkSourceViewTest = async () => {
|
|
|
67
105
|
view.showLineNumbers = true;
|
|
68
106
|
document.body.append(view);
|
|
69
107
|
view.buffer.text = 'a\nb';
|
|
70
|
-
const renderer = new GtkSource.GutterRendererText({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
108
|
+
const renderer = new GtkSource.GutterRendererText({
|
|
109
|
+
width_request: 24,
|
|
110
|
+
margin_start: 2,
|
|
111
|
+
margin_end: 3,
|
|
112
|
+
});
|
|
113
|
+
(
|
|
114
|
+
renderer as unknown as {
|
|
115
|
+
vfunc_query_data(lines: unknown, line: number): void;
|
|
116
|
+
}
|
|
117
|
+
).vfunc_query_data = function (this: { text: string }, _lines, line) {
|
|
118
|
+
this.text = `r${line}`;
|
|
119
|
+
};
|
|
75
120
|
view.get_gutter(Gtk.TextWindowType.LEFT)!.insert(renderer, -1);
|
|
76
121
|
await task();
|
|
77
122
|
const columns = view.querySelectorAll<HTMLElement>('.gsv-columns > *');
|
|
@@ -90,15 +135,47 @@ export const GtkSourceViewTest = async () => {
|
|
|
90
135
|
view.remove();
|
|
91
136
|
});
|
|
92
137
|
|
|
138
|
+
await it('paints the RIGHT gutter as a column right of the text, apart from the left one', async () => {
|
|
139
|
+
const view = document.createElement('gtk-source-view') as GtkSourceView;
|
|
140
|
+
document.body.append(view);
|
|
141
|
+
view.buffer.text = 'a\nb';
|
|
142
|
+
const renderer = new GtkSource.GutterRendererText({ width_request: 12 });
|
|
143
|
+
(
|
|
144
|
+
renderer as unknown as {
|
|
145
|
+
vfunc_query_data(lines: unknown, line: number): void;
|
|
146
|
+
}
|
|
147
|
+
).vfunc_query_data = function (this: { text: string }, _lines, line) {
|
|
148
|
+
this.text = `R${line}`;
|
|
149
|
+
};
|
|
150
|
+
const right = view.get_gutter(Gtk.TextWindowType.RIGHT)!;
|
|
151
|
+
right.insert(renderer, 0);
|
|
152
|
+
await task();
|
|
153
|
+
const element = view.querySelector<HTMLElement>('.gsv-gutter-right')!;
|
|
154
|
+
expect([...element.querySelectorAll('.gsv-cell')].map((cell) => cell.textContent)).toStrictEqual([
|
|
155
|
+
'R0',
|
|
156
|
+
'R1',
|
|
157
|
+
]);
|
|
158
|
+
expect(view.classList.contains('has-right-columns')).toBe(true);
|
|
159
|
+
expect(view.classList.contains('has-columns')).toBe(false);
|
|
160
|
+
expect(view.lastElementChild).toBe(element);
|
|
161
|
+
right.remove(renderer);
|
|
162
|
+
await task();
|
|
163
|
+
expect(view.classList.contains('has-right-columns')).toBe(false);
|
|
164
|
+
view.remove();
|
|
165
|
+
});
|
|
166
|
+
|
|
93
167
|
await it('paints markup as styled spans and drops the column with its renderer', async () => {
|
|
94
168
|
const view = document.createElement('gtk-source-view') as GtkSourceView;
|
|
95
169
|
document.body.append(view);
|
|
96
170
|
view.buffer.text = 'a';
|
|
97
171
|
const renderer = new GtkSource.GutterRendererText({ width_request: 10 });
|
|
98
|
-
(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
172
|
+
(
|
|
173
|
+
renderer as unknown as {
|
|
174
|
+
vfunc_query_data(lines: unknown, line: number): void;
|
|
175
|
+
}
|
|
176
|
+
).vfunc_query_data = function (this: { markup: string }) {
|
|
177
|
+
this.markup = '<b>x</b> & y';
|
|
178
|
+
};
|
|
102
179
|
const gutter = view.get_gutter(Gtk.TextWindowType.LEFT)!;
|
|
103
180
|
gutter.insert(renderer, 0);
|
|
104
181
|
await task();
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
//
|
|
10
10
|
// IN THIS SLICE: `buffer`, `auto-indent`, `indent-width` (held and read back, as on Android),
|
|
11
11
|
// `show-line-numbers`, `highlight-current-line`, `monospace`, `editable`, `cursor-visible`, the four
|
|
12
|
-
// margins, `connect`/`disconnect`, the GJS snake_case accessors, `vadjustment`/`hadjustment`,
|
|
13
|
-
// `set_direction`, `get_first_child`/`get_next_sibling`, `get_gutter` (
|
|
12
|
+
// margins, `connect`/`connect_after`/`disconnect`, `extend-selection` and `copy-clipboard` (stoppable), the GJS snake_case accessors, `vadjustment`/`hadjustment`,
|
|
13
|
+
// `set_direction`, `get_first_child`/`get_next_sibling`, `get_gutter` (both gutters paint their renderers' `vfunc_query_data`).
|
|
14
14
|
//
|
|
15
15
|
// Reference: GtkSourceView 5 gtksourceview.c, upstream GNOME/gtksourceview (properties and defaults)
|
|
16
16
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
17
17
|
|
|
18
18
|
import { adwaitaColorScheme, GtkAdjustment, onAdwaitaColorSchemeChanged } from '@gjsify/adwaita-core';
|
|
19
19
|
import type { Buffer, Gutter } from '@gjsify/gtksource-core';
|
|
20
|
-
import { EditorSession, GutterSet, toBoolean, toNumber } from '@gjsify/gtksource-core';
|
|
20
|
+
import { EditorSession, GutterSet, STOP_EMISSION, toBoolean, toNumber } from '@gjsify/gtksource-core';
|
|
21
21
|
|
|
22
22
|
import { WebEditorDriver } from './web-editor-driver.js';
|
|
23
23
|
|
|
@@ -53,6 +53,8 @@ gtk-source-view .gsv-gutter {
|
|
|
53
53
|
}
|
|
54
54
|
gtk-source-view.show-line-numbers .gsv-gutter,
|
|
55
55
|
gtk-source-view.has-columns .gsv-gutter { display: block; }
|
|
56
|
+
gtk-source-view .gsv-gutter-right { display: none; }
|
|
57
|
+
gtk-source-view.has-right-columns .gsv-gutter-right { display: block; }
|
|
56
58
|
gtk-source-view .gsv-columns { display: flex; }
|
|
57
59
|
gtk-source-view .gsv-numbers,
|
|
58
60
|
gtk-source-view .gsv-column { flex: none; box-sizing: content-box; }
|
|
@@ -138,6 +140,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
138
140
|
private unsubscribe: (() => void) | null = null;
|
|
139
141
|
private built = false;
|
|
140
142
|
private readonly gutter = document.createElement('div');
|
|
143
|
+
private readonly rightGutter = document.createElement('div');
|
|
141
144
|
private readonly backdrop = document.createElement('div');
|
|
142
145
|
private readonly area = document.createElement('textarea');
|
|
143
146
|
private readonly vadj = new GtkAdjustment();
|
|
@@ -148,6 +151,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
148
151
|
constructor() {
|
|
149
152
|
super();
|
|
150
153
|
this.gutter.className = 'gsv-gutter';
|
|
154
|
+
this.rightGutter.className = 'gsv-gutter gsv-gutter-right';
|
|
151
155
|
this.backdrop.className = 'gsv-backdrop';
|
|
152
156
|
this.area.className = 'gsv-area';
|
|
153
157
|
this.area.wrap = 'off';
|
|
@@ -159,6 +163,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
159
163
|
area: this.area,
|
|
160
164
|
backdrop: this.backdrop,
|
|
161
165
|
gutter: this.gutter,
|
|
166
|
+
rightGutter: this.rightGutter,
|
|
162
167
|
});
|
|
163
168
|
this.session = new EditorSession(this.driver, undefined, adwaitaColorScheme());
|
|
164
169
|
this.session.bindGutters(this.gutters);
|
|
@@ -180,7 +185,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
180
185
|
const stage = document.createElement('div');
|
|
181
186
|
stage.className = 'gsv-stage';
|
|
182
187
|
stage.append(this.backdrop, this.area);
|
|
183
|
-
this.append(this.gutter, stage);
|
|
188
|
+
this.append(this.gutter, stage, this.rightGutter);
|
|
184
189
|
}
|
|
185
190
|
this.driver.attach();
|
|
186
191
|
this.unsubscribe = onAdwaitaColorSchemeChanged(() => this.session.setColorScheme(adwaitaColorScheme()));
|
|
@@ -369,10 +374,21 @@ export class GtkSourceView extends HTMLElement {
|
|
|
369
374
|
}
|
|
370
375
|
|
|
371
376
|
/** GJS `connect(name, cb) → id`; the handler's first argument is this view. */
|
|
372
|
-
connect(name: string, callback: (self: GtkSourceView, ...args: never[]) =>
|
|
377
|
+
connect(name: string, callback: (self: GtkSourceView, ...args: never[]) => unknown): number {
|
|
373
378
|
return this.session.connect(name, ((_session: unknown, ...args: never[]) => callback(this, ...args)) as never);
|
|
374
379
|
}
|
|
375
380
|
|
|
381
|
+
/** `g_signal_connect_after`: runs once the plain handlers and the default of the emission have run. */
|
|
382
|
+
connect_after(name: string, callback: (self: GtkSourceView, ...args: never[]) => unknown): number {
|
|
383
|
+
return this.session.connect_after(name, ((_session: unknown, ...args: never[]) =>
|
|
384
|
+
callback(this, ...args)) as never);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** `GObject.signal_stop_emission_by_name(view, …)`: the session owns the emissions. */
|
|
388
|
+
[STOP_EMISSION](detailedSignal: string): void {
|
|
389
|
+
this.session[STOP_EMISSION](detailedSignal);
|
|
390
|
+
}
|
|
391
|
+
|
|
376
392
|
disconnect(id: number): void {
|
|
377
393
|
this.session.disconnect(id);
|
|
378
394
|
}
|
|
@@ -16,7 +16,9 @@ import type {
|
|
|
16
16
|
GutterColumn,
|
|
17
17
|
StyledRun,
|
|
18
18
|
} from '@gjsify/gtksource-core';
|
|
19
|
-
import { digitCount, LineStore } from '@gjsify/gtksource-core';
|
|
19
|
+
import { digitCount, LineStore, WINDOW_LEFT, WINDOW_RIGHT } from '@gjsify/gtksource-core';
|
|
20
|
+
|
|
21
|
+
import { interceptCopy } from '../clipboard-host.js';
|
|
20
22
|
|
|
21
23
|
const GUTTER_PADDING_PX = 8;
|
|
22
24
|
|
|
@@ -31,9 +33,12 @@ export interface EditorParts {
|
|
|
31
33
|
readonly area: HTMLTextAreaElement;
|
|
32
34
|
readonly backdrop: HTMLElement;
|
|
33
35
|
readonly gutter: HTMLElement;
|
|
36
|
+
/** The RIGHT gutter's element, right of the text. */
|
|
37
|
+
readonly rightGutter: HTMLElement;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export class WebEditorDriver implements EditorDriver {
|
|
41
|
+
readonly gutterSides = [WINDOW_LEFT, WINDOW_RIGHT];
|
|
37
42
|
private host: EditorHost | null = null;
|
|
38
43
|
private text = '';
|
|
39
44
|
private caret = 0;
|
|
@@ -65,17 +70,30 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
65
70
|
on(area, 'scroll', () => {
|
|
66
71
|
backdrop.scrollTop = area.scrollTop;
|
|
67
72
|
backdrop.scrollLeft = area.scrollLeft;
|
|
68
|
-
(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
for (const side of [gutter, this.parts.rightGutter]) {
|
|
74
|
+
(side.firstElementChild as HTMLElement | null)?.style.setProperty(
|
|
75
|
+
'transform',
|
|
76
|
+
`translateY(${-area.scrollTop}px)`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
72
79
|
// Only the lines in view are asked; a scroll brings new ones.
|
|
73
|
-
if (this.hasColumns) this.scheduleRender('gutter');
|
|
80
|
+
if (this.hasColumns || this.hasRightColumns) this.scheduleRender('gutter');
|
|
74
81
|
});
|
|
75
82
|
on(area, 'select', () => this.onSelection());
|
|
76
83
|
on(area, 'keyup', () => this.onSelection());
|
|
77
84
|
on(area, 'mouseup', () => this.onSelection());
|
|
78
85
|
on(area, 'keydown', (event) => this.onKey(event as KeyboardEvent));
|
|
86
|
+
on(area, 'mousedown', (event) => this.onMouseDown(event as MouseEvent));
|
|
87
|
+
on(area, 'copy', (event) => {
|
|
88
|
+
// A handler of `copy-clipboard` stopped the emission: the default, the copy, does not happen.
|
|
89
|
+
// One that put content on the Gdk clipboard meanwhile wrote it into this event
|
|
90
|
+
// (`clipboard-host.ts`); the browser must not replace it with the selection.
|
|
91
|
+
let ran = false;
|
|
92
|
+
const wrote = interceptCopy((event as ClipboardEvent).clipboardData, () => {
|
|
93
|
+
ran = this.host?.onNativeCopy() ?? false;
|
|
94
|
+
});
|
|
95
|
+
if (!ran || wrote) event.preventDefault();
|
|
96
|
+
});
|
|
79
97
|
// Firefox and Chromium fire `selectionchange` on different targets for a textarea.
|
|
80
98
|
on(area.ownerDocument, 'selectionchange', () => {
|
|
81
99
|
if (area.ownerDocument.activeElement === area) this.onSelection();
|
|
@@ -122,6 +140,20 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
122
140
|
this.scheduleRender();
|
|
123
141
|
}
|
|
124
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The second and third press of a multi-click: GTK asks `extend-selection` before it selects the word
|
|
145
|
+
* or line. The first press has already placed the caret, which is the location asked about.
|
|
146
|
+
*/
|
|
147
|
+
private onMouseDown(event: MouseEvent): void {
|
|
148
|
+
if (event.button !== 0 || (event.detail !== 2 && event.detail !== 3)) return;
|
|
149
|
+
const { area } = this.parts;
|
|
150
|
+
const range = this.host?.onNativeExtendSelection(event.detail === 2 ? 0 : 1, area.selectionStart ?? 0);
|
|
151
|
+
if (!range) return;
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
area.setSelectionRange(range[0], range[1]);
|
|
154
|
+
this.onSelection();
|
|
155
|
+
}
|
|
156
|
+
|
|
125
157
|
/** `accepts-tab` is GTK's default; a textarea would move focus, so Tab inserts a tab. */
|
|
126
158
|
private onKey(event: KeyboardEvent): void {
|
|
127
159
|
if (event.key !== 'Tab' || event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return;
|
|
@@ -234,6 +266,7 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
234
266
|
}
|
|
235
267
|
|
|
236
268
|
private hasColumns = false;
|
|
269
|
+
private hasRightColumns = false;
|
|
237
270
|
|
|
238
271
|
private render(): void {
|
|
239
272
|
const { backdrop } = this.parts;
|
|
@@ -325,7 +358,7 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
325
358
|
|
|
326
359
|
/** The gutter: the renderers left of the line numbers, the numbers, the renderers right of them. */
|
|
327
360
|
private renderGutter(): void {
|
|
328
|
-
const { gutter, root, area } = this.parts;
|
|
361
|
+
const { gutter, rightGutter, root, area } = this.parts;
|
|
329
362
|
const host = this.host;
|
|
330
363
|
const count = this.text.split('\n').length;
|
|
331
364
|
const [first, last] = this.visibleLines(count);
|
|
@@ -342,5 +375,15 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
342
375
|
wrapper.append(...elements);
|
|
343
376
|
wrapper.style.transform = `translateY(${-area.scrollTop}px)`;
|
|
344
377
|
gutter.replaceChildren(wrapper);
|
|
378
|
+
|
|
379
|
+
// The RIGHT gutter holds renderers only: the line numbers are a LEFT-side feature.
|
|
380
|
+
const right = host?.queryGutter(first, last, WINDOW_RIGHT) ?? [];
|
|
381
|
+
this.hasRightColumns = right.length > 0;
|
|
382
|
+
root.classList.toggle('has-right-columns', this.hasRightColumns);
|
|
383
|
+
const rightWrapper = document.createElement('div');
|
|
384
|
+
rightWrapper.className = 'gsv-columns';
|
|
385
|
+
rightWrapper.append(...right.map((column) => this.columnElement(column, first)));
|
|
386
|
+
rightWrapper.style.transform = `translateY(${-area.scrollTop}px)`;
|
|
387
|
+
rightGutter.replaceChildren(rightWrapper);
|
|
345
388
|
}
|
|
346
389
|
}
|
package/src/index.ts
CHANGED
|
@@ -119,6 +119,8 @@ export * as Gtk from './namespace/gtk.js';
|
|
|
119
119
|
// program here as on GJS — `./namespace/gio.ts` says what does not go in it.
|
|
120
120
|
export * as Gio from './namespace/gio.js';
|
|
121
121
|
|
|
122
|
+
// Gdk's clipboard (`Display`, `Clipboard`, `ContentProvider`): `./namespace/gdk.ts` says what is absent.
|
|
123
|
+
export * as Gdk from './namespace/gdk.js';
|
|
122
124
|
// GLib's timers (`idle_add`, `timeout_add`, `source_remove`): `./namespace/glib.ts` says what is absent.
|
|
123
125
|
export * as GLib from './namespace/glib.js';
|
|
124
126
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The Gdk half of `@gjsify/adwaita-web`'s vocabulary, as far as the clipboard goes (ADR 0096
|
|
2
|
+
// Amendment 2): `Display.get_default().get_clipboard()`, `Clipboard.set_content` / `set` and
|
|
3
|
+
// `ContentProvider.new_for_value`. The behaviour lives once in `@gjsify/adwaita-core` (`gdk.ts`); this
|
|
4
|
+
// file hands it the browser's clipboard, `../clipboard-host.ts`.
|
|
5
|
+
//
|
|
6
|
+
// A TRUE SUBSET: everything else in Gdk is absent, and the `gi://Gdk` arm refuses it by name.
|
|
7
|
+
|
|
8
|
+
import { createGdk } from '@gjsify/adwaita-core';
|
|
9
|
+
|
|
10
|
+
import { webClipboardHost } from '../clipboard-host.js';
|
|
11
|
+
|
|
12
|
+
export const gdk = createGdk(webClipboardHost);
|
|
13
|
+
|
|
14
|
+
export const Display = gdk.Display;
|
|
15
|
+
export const Clipboard = gdk.Clipboard;
|
|
16
|
+
export const ContentProvider = gdk.ContentProvider;
|
|
17
|
+
export const ContentFormats = gdk.ContentFormats;
|
package/src/namespace/gobject.ts
CHANGED
|
@@ -21,6 +21,7 @@ export const type_ensure = gobject.type_ensure;
|
|
|
21
21
|
export const signal_stop_emission_by_name = gobject.signal_stop_emission_by_name;
|
|
22
22
|
export const ParamSpec = gobject.ParamSpec;
|
|
23
23
|
export const ParamFlags = gobject.ParamFlags;
|
|
24
|
+
export const Value = gobject.Value;
|
|
24
25
|
export const TYPE_STRING = gobject.TYPE_STRING;
|
|
25
26
|
export const TYPE_BOOLEAN = gobject.TYPE_BOOLEAN;
|
|
26
27
|
export const TYPE_INT = gobject.TYPE_INT;
|
|
@@ -1,15 +1,34 @@
|
|
|
1
|
-
// The plain GTK enums an application reads off `Gtk` — `Gtk.PolicyType`, `Gtk.TextDirection
|
|
2
|
-
// `Gtk.TextWindowType`, as value tables. The numbers are the typelib's (GIR declaration order),
|
|
1
|
+
// The plain GTK enums an application reads off `Gtk` — `Gtk.PolicyType`, `Gtk.TextDirection`,
|
|
2
|
+
// `Gtk.TextExtendSelection` and `Gtk.TextWindowType`, as value tables. The numbers are the typelib's (GIR declaration order),
|
|
3
3
|
// and the same tables the NativeScript port publishes.
|
|
4
4
|
//
|
|
5
5
|
// Reference: refs/gtk gtk/gtkenums.h (GtkPolicyType, GtkTextDirection), gtk/gtktextview.h (GtkTextWindowType)
|
|
6
6
|
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
7
7
|
|
|
8
8
|
/** `Gtk.PolicyType`: `ALWAYS` 0, `AUTOMATIC` 1, `NEVER` 2, `EXTERNAL` 3. */
|
|
9
|
-
export const PolicyType = Object.freeze({
|
|
9
|
+
export const PolicyType = Object.freeze({
|
|
10
|
+
ALWAYS: 0,
|
|
11
|
+
AUTOMATIC: 1,
|
|
12
|
+
NEVER: 2,
|
|
13
|
+
EXTERNAL: 3,
|
|
14
|
+
} as const);
|
|
10
15
|
|
|
11
16
|
/** `Gtk.TextDirection`: `NONE` 0, `LTR` 1, `RTL` 2. */
|
|
12
|
-
export const TextDirection = Object.freeze({
|
|
17
|
+
export const TextDirection = Object.freeze({
|
|
18
|
+
NONE: 0,
|
|
19
|
+
LTR: 1,
|
|
20
|
+
RTL: 2,
|
|
21
|
+
} as const);
|
|
13
22
|
|
|
14
23
|
/** `Gtk.TextWindowType`: `WIDGET` 1, `TEXT` 2, `LEFT` 3, `RIGHT` 4, `TOP` 5, `BOTTOM` 6. */
|
|
15
|
-
export const TextWindowType = Object.freeze({
|
|
24
|
+
export const TextWindowType = Object.freeze({
|
|
25
|
+
WIDGET: 1,
|
|
26
|
+
TEXT: 2,
|
|
27
|
+
LEFT: 3,
|
|
28
|
+
RIGHT: 4,
|
|
29
|
+
TOP: 5,
|
|
30
|
+
BOTTOM: 6,
|
|
31
|
+
} as const);
|
|
32
|
+
|
|
33
|
+
/** `Gtk.TextExtendSelection`: `WORD` 0 (a double click), `LINE` 1 (a triple click). */
|
|
34
|
+
export const TextExtendSelection = Object.freeze({ WORD: 0, LINE: 1 } as const);
|
package/src/namespace/gtk.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// are the same write and nothing in this package had to learn a second input shape.
|
|
22
22
|
|
|
23
23
|
export { GtkAdjustment as Adjustment } from '@gjsify/adwaita-core';
|
|
24
|
-
export { PolicyType, TextDirection, TextWindowType } from './gtk-enums.js';
|
|
24
|
+
export { PolicyType, TextDirection, TextExtendSelection, TextWindowType } from './gtk-enums.js';
|
|
25
25
|
export { GtkAboutDialog as AboutDialog } from '../elements/gtk-about-dialog.js';
|
|
26
26
|
export { GtkActionBar as ActionBar } from '../elements/gtk-action-bar.js';
|
|
27
27
|
export { GtkApplicationWindow as ApplicationWindow } from '../elements/gtk-application-window.js';
|
package/src/test.browser.mts
CHANGED
|
@@ -141,6 +141,7 @@ import { AdwFontsTest } from './adw-fonts.spec.js';
|
|
|
141
141
|
import { AdwConstructVectorsTest } from './construct-vectors.spec.js';
|
|
142
142
|
import { GObjectDoorTest } from './gobject-door.spec.js';
|
|
143
143
|
import { ApplicationTest } from './application.spec.js';
|
|
144
|
+
import { GdkTest } from './gdk.spec.js';
|
|
144
145
|
import { GLibTest } from './glib.spec.js';
|
|
145
146
|
import { StyleManagerTest } from './style-manager.spec.js';
|
|
146
147
|
import { GioActionsTest } from './gio.spec.js';
|
|
@@ -160,6 +161,7 @@ run({
|
|
|
160
161
|
AdwConstructVectorsTest,
|
|
161
162
|
GObjectDoorTest,
|
|
162
163
|
ApplicationTest,
|
|
164
|
+
GdkTest,
|
|
163
165
|
GLibTest,
|
|
164
166
|
StyleManagerTest,
|
|
165
167
|
GioActionsTest,
|