@gjsify/adwaita-web 0.56.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adwaita-web.css +8 -2
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/application.d.ts +24 -0
- package/lib/types/application.spec.d.ts +1 -0
- package/lib/types/elements/adw-application-window.d.ts +2 -0
- package/lib/types/elements/adw-view-stack.d.ts +3 -0
- package/lib/types/elements/checks.d.ts +6 -0
- package/lib/types/elements/gtk-application-window.d.ts +2 -0
- package/lib/types/elements/gtk-drop-down.d.ts +3 -0
- package/lib/types/elements/gtk-entry.d.ts +10 -0
- package/lib/types/elements/gtk-menu-button.d.ts +2 -0
- package/lib/types/elements/gtk-scrolled-window.d.ts +16 -0
- package/lib/types/elements/gtk-stack.d.ts +3 -0
- package/lib/types/elements/gtk-switch.d.ts +3 -0
- package/lib/types/gio.spec.d.ts +1 -0
- package/lib/types/glib.spec.d.ts +1 -0
- package/lib/types/gobject-door.d.ts +4 -0
- package/lib/types/gobject-door.spec.d.ts +1 -0
- package/lib/types/gobject-elements.d.ts +17 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +78 -0
- package/lib/types/gtksource/gtk-source-view.spec.d.ts +2 -0
- package/lib/types/gtksource/web-editor-driver.d.ts +53 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/menu-actions.spec.d.ts +1 -0
- package/lib/types/namespace/adw.d.ts +2 -0
- package/lib/types/namespace/gio.d.ts +2 -0
- package/lib/types/namespace/glib.d.ts +15 -0
- package/lib/types/namespace/gobject.d.ts +28 -0
- package/lib/types/namespace/gtk-enums.d.ts +22 -0
- package/lib/types/namespace/gtk.d.ts +2 -0
- package/lib/types/namespace/gtksource.d.ts +2 -0
- package/lib/types/shared-tree-builder.d.ts +20 -1
- package/lib/types/style-manager.d.ts +5 -0
- package/lib/types/style-manager.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +11 -9
- package/scss/_reset.scss +1 -1
- package/src/application.spec.ts +75 -0
- package/src/application.ts +92 -0
- package/src/construct-vectors.spec.ts +155 -0
- package/src/elements/adw-application-window.ts +5 -0
- package/src/elements/adw-split-button.ts +5 -1
- package/src/elements/adw-view-stack.ts +14 -0
- package/src/elements/checks.ts +25 -1
- package/src/elements/gtk-application-window.ts +5 -0
- package/src/elements/gtk-drop-down.ts +16 -0
- package/src/elements/gtk-entry.ts +45 -7
- package/src/elements/gtk-menu-button.ts +12 -1
- package/src/elements/gtk-popover-menu.ts +7 -2
- package/src/elements/gtk-scrolled-window.ts +58 -0
- package/src/elements/gtk-stack.ts +8 -0
- package/src/elements/gtk-switch.ts +8 -0
- package/src/gio.spec.ts +23 -0
- package/src/glib.spec.ts +49 -0
- package/src/gobject-door.spec.ts +119 -0
- package/src/gobject-door.ts +137 -0
- package/src/gobject-elements.ts +172 -0
- package/src/gtksource/gtk-source-view.spec.ts +141 -0
- package/src/gtksource/gtk-source-view.ts +386 -0
- package/src/gtksource/web-editor-driver.ts +346 -0
- package/src/index.ts +10 -0
- package/src/menu-actions.spec.ts +47 -0
- package/src/namespace/adw.ts +2 -0
- package/src/namespace/gio.ts +3 -1
- package/src/namespace/glib.ts +37 -0
- package/src/namespace/gobject.ts +38 -0
- package/src/namespace/gtk-enums.ts +15 -0
- package/src/namespace/gtk.ts +2 -0
- package/src/namespace/gtksource.ts +20 -0
- package/src/shared-tree-builder.ts +142 -7
- package/src/split-button.spec.ts +2 -0
- package/src/style-manager.spec.ts +36 -0
- package/src/style-manager.ts +27 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +14 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// The browser half of `GtkSource.View`: a `<textarea>` that owns the caret, selection, input,
|
|
2
|
+
// IME and undo, with the highlight painted on a backdrop behind its transparent text and the
|
|
3
|
+
// line numbers in a gutter beside it. The structure mirrors the Android driver
|
|
4
|
+
// (`packages/nativescript-bridge/gtksource/src/native-editor.android.ts`): the platform edits
|
|
5
|
+
// text natively, and this class reports what the user did to an `EditorHost`.
|
|
6
|
+
//
|
|
7
|
+
// NO WORD WRAP, as on Android: `wrap="off"` and `white-space: pre` keep every logical line on
|
|
8
|
+
// one layout line, so the backdrop and the gutter stay aligned by a fixed line height.
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
EditorDriver,
|
|
12
|
+
EditorHost,
|
|
13
|
+
EditorLayout,
|
|
14
|
+
EditorPalette,
|
|
15
|
+
GutterCell,
|
|
16
|
+
GutterColumn,
|
|
17
|
+
StyledRun,
|
|
18
|
+
} from '@gjsify/gtksource-core';
|
|
19
|
+
import { digitCount, LineStore } from '@gjsify/gtksource-core';
|
|
20
|
+
|
|
21
|
+
const GUTTER_PADDING_PX = 8;
|
|
22
|
+
|
|
23
|
+
/** A signed ARGB int (the core's colour unit) as a CSS colour. */
|
|
24
|
+
function cssColor(argb: number): string {
|
|
25
|
+
const alpha = ((argb >>> 24) & 0xff) / 255;
|
|
26
|
+
return `rgba(${(argb >>> 16) & 0xff}, ${(argb >>> 8) & 0xff}, ${argb & 0xff}, ${alpha})`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface EditorParts {
|
|
30
|
+
readonly root: HTMLElement;
|
|
31
|
+
readonly area: HTMLTextAreaElement;
|
|
32
|
+
readonly backdrop: HTMLElement;
|
|
33
|
+
readonly gutter: HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class WebEditorDriver implements EditorDriver {
|
|
37
|
+
private host: EditorHost | null = null;
|
|
38
|
+
private text = '';
|
|
39
|
+
private caret = 0;
|
|
40
|
+
private reported = { start: 0, end: 0 };
|
|
41
|
+
private layout: EditorLayout | null = null;
|
|
42
|
+
private readonly runs = new LineStore<readonly StyledRun[]>();
|
|
43
|
+
private scheduled: 'none' | 'gutter' | 'all' = 'none';
|
|
44
|
+
private numbers: HTMLElement | null = null;
|
|
45
|
+
private listening: Array<() => void> = [];
|
|
46
|
+
private palette: EditorPalette | null = null;
|
|
47
|
+
private attached = false;
|
|
48
|
+
|
|
49
|
+
constructor(private readonly parts: EditorParts) {}
|
|
50
|
+
|
|
51
|
+
bind(host: EditorHost): void {
|
|
52
|
+
this.host = host;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Starts listening to the textarea; the counterpart of the Android `attach()`. */
|
|
56
|
+
attach(): void {
|
|
57
|
+
if (this.listening.length > 0) return;
|
|
58
|
+
this.attached = true;
|
|
59
|
+
const { area, backdrop, gutter } = this.parts;
|
|
60
|
+
const on = <T extends EventTarget>(target: T, type: string, listener: EventListener) => {
|
|
61
|
+
target.addEventListener(type, listener);
|
|
62
|
+
this.listening.push(() => target.removeEventListener(type, listener));
|
|
63
|
+
};
|
|
64
|
+
on(area, 'input', () => this.onInput());
|
|
65
|
+
on(area, 'scroll', () => {
|
|
66
|
+
backdrop.scrollTop = area.scrollTop;
|
|
67
|
+
backdrop.scrollLeft = area.scrollLeft;
|
|
68
|
+
(gutter.firstElementChild as HTMLElement | null)?.style.setProperty(
|
|
69
|
+
'transform',
|
|
70
|
+
`translateY(${-area.scrollTop}px)`,
|
|
71
|
+
);
|
|
72
|
+
// Only the lines in view are asked; a scroll brings new ones.
|
|
73
|
+
if (this.hasColumns) this.scheduleRender('gutter');
|
|
74
|
+
});
|
|
75
|
+
on(area, 'select', () => this.onSelection());
|
|
76
|
+
on(area, 'keyup', () => this.onSelection());
|
|
77
|
+
on(area, 'mouseup', () => this.onSelection());
|
|
78
|
+
on(area, 'keydown', (event) => this.onKey(event as KeyboardEvent));
|
|
79
|
+
// Firefox and Chromium fire `selectionchange` on different targets for a textarea.
|
|
80
|
+
on(area.ownerDocument, 'selectionchange', () => {
|
|
81
|
+
if (area.ownerDocument.activeElement === area) this.onSelection();
|
|
82
|
+
});
|
|
83
|
+
if (this.layout) this.applyLayout(this.layout);
|
|
84
|
+
if (this.palette) this.applyPalette(this.palette);
|
|
85
|
+
this.scheduleRender();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
detach(): void {
|
|
89
|
+
this.attached = false;
|
|
90
|
+
for (const stop of this.listening.splice(0)) stop();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- what the user did -------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
private onInput(): void {
|
|
96
|
+
const next = this.parts.area.value;
|
|
97
|
+
const prev = this.text;
|
|
98
|
+
let prefix = 0;
|
|
99
|
+
const shortest = Math.min(prev.length, next.length);
|
|
100
|
+
while (prefix < shortest && prev.charCodeAt(prefix) === next.charCodeAt(prefix)) prefix++;
|
|
101
|
+
let suffix = 0;
|
|
102
|
+
while (
|
|
103
|
+
suffix < shortest - prefix &&
|
|
104
|
+
prev.charCodeAt(prev.length - 1 - suffix) === next.charCodeAt(next.length - 1 - suffix)
|
|
105
|
+
) {
|
|
106
|
+
suffix++;
|
|
107
|
+
}
|
|
108
|
+
this.text = next;
|
|
109
|
+
this.host?.onNativeEdit(prefix, prev.length - prefix - suffix, next.slice(prefix, next.length - suffix));
|
|
110
|
+
this.onSelection();
|
|
111
|
+
this.scheduleRender();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private onSelection(): void {
|
|
115
|
+
const { area } = this.parts;
|
|
116
|
+
const start = area.selectionStart ?? 0;
|
|
117
|
+
const end = area.selectionEnd ?? start;
|
|
118
|
+
if (start === this.reported.start && end === this.reported.end) return;
|
|
119
|
+
this.reported = { start, end };
|
|
120
|
+
this.caret = end;
|
|
121
|
+
this.host?.onNativeSelection(start, end);
|
|
122
|
+
this.scheduleRender();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** `accepts-tab` is GTK's default; a textarea would move focus, so Tab inserts a tab. */
|
|
126
|
+
private onKey(event: KeyboardEvent): void {
|
|
127
|
+
if (event.key !== 'Tab' || event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return;
|
|
128
|
+
if (this.parts.area.readOnly) return;
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
// `insertText` keeps the edit on the browser's own undo stack and fires `input`.
|
|
131
|
+
if (!this.parts.area.ownerDocument.execCommand('insertText', false, '\t')) {
|
|
132
|
+
const { area } = this.parts;
|
|
133
|
+
area.setRangeText('\t', area.selectionStart, area.selectionEnd, 'end');
|
|
134
|
+
this.onInput();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- what to show ------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
setText(text: string): void {
|
|
141
|
+
this.text = text;
|
|
142
|
+
this.parts.area.value = text;
|
|
143
|
+
this.runs.clear();
|
|
144
|
+
this.scheduleRender();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
replaceRange(start: number, end: number, text: string): void {
|
|
148
|
+
const { area } = this.parts;
|
|
149
|
+
this.text = this.text.slice(0, start) + text + this.text.slice(end);
|
|
150
|
+
// 'preserve' leaves the selection to the session, which sets it explicitly afterwards.
|
|
151
|
+
area.setRangeText(text, start, end, 'preserve');
|
|
152
|
+
this.scheduleRender();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setSelection(start: number, end: number): void {
|
|
156
|
+
this.reported = { start, end };
|
|
157
|
+
this.caret = end;
|
|
158
|
+
this.parts.area.setSelectionRange(start, end);
|
|
159
|
+
this.scheduleRender();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setLineCount(): void {
|
|
163
|
+
this.scheduleRender();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setLayout(layout: EditorLayout): void {
|
|
167
|
+
this.layout = layout;
|
|
168
|
+
if (this.attached) this.applyLayout(layout);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Attributes and styles on the host wait for `attach()`: a custom element's constructor may not write them. */
|
|
172
|
+
private applyLayout(layout: EditorLayout): void {
|
|
173
|
+
const { root, area } = this.parts;
|
|
174
|
+
area.readOnly = !layout.editable;
|
|
175
|
+
root.classList.toggle('monospace', layout.monospace);
|
|
176
|
+
root.classList.toggle('show-line-numbers', layout.showLineNumbers);
|
|
177
|
+
root.classList.toggle('cursor-hidden', !layout.cursorVisible);
|
|
178
|
+
root.style.setProperty('--gsv-margin-left', `${layout.leftMargin}px`);
|
|
179
|
+
root.style.setProperty('--gsv-margin-right', `${layout.rightMargin}px`);
|
|
180
|
+
root.style.setProperty('--gsv-margin-top', `${layout.topMargin}px`);
|
|
181
|
+
root.style.setProperty('--gsv-margin-bottom', `${layout.bottomMargin}px`);
|
|
182
|
+
this.scheduleRender();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setPalette(palette: EditorPalette): void {
|
|
186
|
+
this.palette = palette;
|
|
187
|
+
if (this.attached) this.applyPalette(palette);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private applyPalette(palette: EditorPalette): void {
|
|
191
|
+
const { style } = this.parts.root;
|
|
192
|
+
style.setProperty('--gsv-fg', cssColor(palette.foreground));
|
|
193
|
+
style.setProperty('--gsv-bg', cssColor(palette.background));
|
|
194
|
+
style.setProperty('--gsv-selection', cssColor(palette.selectionBackground));
|
|
195
|
+
style.setProperty('--gsv-current-line', cssColor(palette.currentLineBackground));
|
|
196
|
+
style.setProperty('--gsv-number-fg', cssColor(palette.lineNumberForeground));
|
|
197
|
+
style.setProperty('--gsv-number-bg', cssColor(palette.lineNumberBackground));
|
|
198
|
+
style.setProperty('--gsv-current-number-fg', cssColor(palette.currentLineNumberForeground));
|
|
199
|
+
style.setProperty('--gsv-current-number-bg', cssColor(palette.currentLineNumberBackground));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
invalidateGutter(): void {
|
|
203
|
+
this.scheduleRender('gutter');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
spliceLines(first: number, removed: number, inserted: number): void {
|
|
207
|
+
this.runs.splice(first, removed, inserted);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
paintLine(line: number, _lineStart: number, runs: readonly StyledRun[]): void {
|
|
211
|
+
this.runs.set(line, runs);
|
|
212
|
+
this.scheduleRender();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
clearAll(): void {
|
|
216
|
+
this.runs.clear();
|
|
217
|
+
this.scheduleRender();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- painting ----------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
/** One repaint per task: a burst of `paintLine` calls from one edit costs one DOM rebuild. */
|
|
223
|
+
private scheduleRender(only: 'gutter' | 'all' = 'all'): void {
|
|
224
|
+
if (!this.attached) return;
|
|
225
|
+
const pending = this.scheduled;
|
|
226
|
+
this.scheduled = only === 'all' || pending === 'all' ? 'all' : 'gutter';
|
|
227
|
+
if (pending !== 'none') return;
|
|
228
|
+
queueMicrotask(() => {
|
|
229
|
+
const level = this.scheduled;
|
|
230
|
+
this.scheduled = 'none';
|
|
231
|
+
if (level === 'all') this.render();
|
|
232
|
+
else if (level === 'gutter') this.renderGutter();
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private hasColumns = false;
|
|
237
|
+
|
|
238
|
+
private render(): void {
|
|
239
|
+
const { backdrop } = this.parts;
|
|
240
|
+
const lines = this.text.split('\n');
|
|
241
|
+
const highlight = this.layout?.highlightCurrentLine === true;
|
|
242
|
+
const caretLine = this.text.slice(0, this.caret).split('\n').length - 1;
|
|
243
|
+
|
|
244
|
+
const rows: HTMLElement[] = [];
|
|
245
|
+
const numbers: HTMLElement[] = [];
|
|
246
|
+
lines.forEach((line, index) => {
|
|
247
|
+
const row = document.createElement('div');
|
|
248
|
+
row.className = highlight && index === caretLine ? 'gsv-line current' : 'gsv-line';
|
|
249
|
+
let at = 0;
|
|
250
|
+
for (const run of this.runs.get(index) ?? []) {
|
|
251
|
+
if (run.start > at) row.append(line.slice(at, run.start));
|
|
252
|
+
const span = document.createElement('span');
|
|
253
|
+
span.textContent = line.slice(run.start, run.end);
|
|
254
|
+
const { style } = run;
|
|
255
|
+
if (style.foreground !== undefined) span.style.color = cssColor(style.foreground);
|
|
256
|
+
if (style.background !== undefined) span.style.backgroundColor = cssColor(style.background);
|
|
257
|
+
if (style.bold) span.style.fontWeight = 'bold';
|
|
258
|
+
if (style.italic) span.style.fontStyle = 'italic';
|
|
259
|
+
const decorations = [style.underline ? 'underline' : '', style.strikethrough ? 'line-through' : ''];
|
|
260
|
+
span.style.textDecoration = decorations.filter(Boolean).join(' ');
|
|
261
|
+
row.append(span);
|
|
262
|
+
at = run.end;
|
|
263
|
+
}
|
|
264
|
+
if (at < line.length) row.append(line.slice(at));
|
|
265
|
+
rows.push(row);
|
|
266
|
+
|
|
267
|
+
const number = document.createElement('div');
|
|
268
|
+
number.className = highlight && index === caretLine ? 'gsv-number current' : 'gsv-number';
|
|
269
|
+
number.textContent = String(index + 1);
|
|
270
|
+
numbers.push(number);
|
|
271
|
+
});
|
|
272
|
+
backdrop.replaceChildren(...rows);
|
|
273
|
+
const column = document.createElement('div');
|
|
274
|
+
column.className = 'gsv-numbers';
|
|
275
|
+
column.append(...numbers);
|
|
276
|
+
this.numbers = column;
|
|
277
|
+
const { root } = this.parts;
|
|
278
|
+
root.style.setProperty('--gsv-gutter-width', `${digitCount(lines.length) + 0}ch`);
|
|
279
|
+
root.style.setProperty('--gsv-gutter-padding', `${GUTTER_PADDING_PX}px`);
|
|
280
|
+
this.renderGutter();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The lines in view, or all of them where nothing is laid out (a detached element, a test DOM). */
|
|
284
|
+
private visibleLines(count: number): [number, number] {
|
|
285
|
+
const { area, backdrop } = this.parts;
|
|
286
|
+
const row = backdrop.firstElementChild;
|
|
287
|
+
const height = row === null ? 0 : row.getBoundingClientRect().height;
|
|
288
|
+
if (height <= 0 || area.clientHeight <= 0) return [0, count - 1];
|
|
289
|
+
const top = Math.max(0, area.scrollTop - (this.layout?.topMargin ?? 0));
|
|
290
|
+
const first = Math.min(count - 1, Math.floor(top / height));
|
|
291
|
+
const last = Math.min(count - 1, Math.max(first, Math.ceil((top + area.clientHeight) / height) - 1));
|
|
292
|
+
return [first, last];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private cellElement(cell: GutterCell): HTMLElement {
|
|
296
|
+
const element = document.createElement('div');
|
|
297
|
+
element.className = 'gsv-cell';
|
|
298
|
+
if (cell.runs === null) {
|
|
299
|
+
element.textContent = cell.text;
|
|
300
|
+
return element;
|
|
301
|
+
}
|
|
302
|
+
for (const run of cell.runs) {
|
|
303
|
+
const span = document.createElement('span');
|
|
304
|
+
span.textContent = run.text;
|
|
305
|
+
if (run.bold) span.style.fontWeight = 'bold';
|
|
306
|
+
if (run.italic) span.style.fontStyle = 'italic';
|
|
307
|
+
const decorations = [run.underline ? 'underline' : '', run.strikethrough ? 'line-through' : ''];
|
|
308
|
+
span.style.textDecoration = decorations.filter(Boolean).join(' ');
|
|
309
|
+
element.append(span);
|
|
310
|
+
}
|
|
311
|
+
return element;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private columnElement(column: GutterColumn, first: number): HTMLElement {
|
|
315
|
+
const element = document.createElement('div');
|
|
316
|
+
element.className = 'gsv-column';
|
|
317
|
+
element.style.minWidth = `${column.widthRequest}px`;
|
|
318
|
+
element.style.paddingLeft = `${column.marginStart}px`;
|
|
319
|
+
element.style.paddingRight = `${column.marginEnd}px`;
|
|
320
|
+
const spacer = document.createElement('div');
|
|
321
|
+
spacer.style.height = `calc(${first} * 1em * var(--gsv-line-height))`;
|
|
322
|
+
element.append(spacer, ...column.cells.map((cell) => this.cellElement(cell)));
|
|
323
|
+
return element;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** The gutter: the renderers left of the line numbers, the numbers, the renderers right of them. */
|
|
327
|
+
private renderGutter(): void {
|
|
328
|
+
const { gutter, root, area } = this.parts;
|
|
329
|
+
const host = this.host;
|
|
330
|
+
const count = this.text.split('\n').length;
|
|
331
|
+
const [first, last] = this.visibleLines(count);
|
|
332
|
+
const columns = host?.queryGutter(first, last) ?? [];
|
|
333
|
+
this.hasColumns = columns.length > 0;
|
|
334
|
+
const showNumbers = this.layout?.showLineNumbers === true;
|
|
335
|
+
root.classList.toggle('has-columns', this.hasColumns);
|
|
336
|
+
const elements: HTMLElement[] = [];
|
|
337
|
+
for (const column of columns.filter((c) => c.position < 0)) elements.push(this.columnElement(column, first));
|
|
338
|
+
if (showNumbers && this.numbers !== null) elements.push(this.numbers);
|
|
339
|
+
for (const column of columns.filter((c) => c.position >= 0)) elements.push(this.columnElement(column, first));
|
|
340
|
+
const wrapper = document.createElement('div');
|
|
341
|
+
wrapper.className = 'gsv-columns';
|
|
342
|
+
wrapper.append(...elements);
|
|
343
|
+
wrapper.style.transform = `translateY(${-area.scrollTop}px)`;
|
|
344
|
+
gutter.replaceChildren(wrapper);
|
|
345
|
+
}
|
|
346
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -119,6 +119,16 @@ 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
|
+
// GLib's timers (`idle_add`, `timeout_add`, `source_remove`): `./namespace/glib.ts` says what is absent.
|
|
123
|
+
export * as GLib from './namespace/glib.js';
|
|
124
|
+
|
|
125
|
+
// The fourth, and the one that makes an unmodified GNOME component class run (ADR 0096): the GObject
|
|
126
|
+
// subset, `registerClass` first. `./namespace/gobject.ts` says what it is.
|
|
127
|
+
export * as GObject from './namespace/gobject.js';
|
|
128
|
+
|
|
129
|
+
// GtkSource (ADR 0094 amendment): `GtkSource.View` over `@gjsify/gtksource-core`, `./namespace/gtksource.ts`.
|
|
130
|
+
export * as GtkSource from './namespace/gtksource.js';
|
|
131
|
+
|
|
122
132
|
// WHAT DID NOT MOVE INTO THE NAMESPACE, and the rule that decides it. A member exists
|
|
123
133
|
// for an element whose GIR tag names a real widget; `WEB_ELEMENT_ALIGNMENT` declares the
|
|
124
134
|
// rest `webOnly`, meaning no widget in the reference vocabulary stands behind it, so
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// ADR 0097 §§ 1, 3 on the browser: the action resolution from the owning `gtk-menu-button`, and a
|
|
2
|
+
// tree's menus reaching its `menuModel`.
|
|
3
|
+
import {
|
|
4
|
+
MENU_ACTION_VECTORS,
|
|
5
|
+
MENU_TREE_VECTORS,
|
|
6
|
+
driveMenuActionVectors,
|
|
7
|
+
registryMenuActionScene,
|
|
8
|
+
type SharedTreeNode,
|
|
9
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
10
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
11
|
+
|
|
12
|
+
import { parentOfElement } from './application.js';
|
|
13
|
+
import { buildSharedTree } from './shared-tree-builder.js';
|
|
14
|
+
import type { GtkMenuButton } from './elements/gtk-menu-button.js';
|
|
15
|
+
|
|
16
|
+
const made: Element[] = [];
|
|
17
|
+
|
|
18
|
+
export const MenuActionsTest = async () => {
|
|
19
|
+
expect(MENU_ACTION_VECTORS.length > 0).toBe(true);
|
|
20
|
+
await driveMenuActionVectors(
|
|
21
|
+
'adwaita-web (gtk-menu-button)',
|
|
22
|
+
registryMenuActionScene(() => {
|
|
23
|
+
const outer = document.createElement('div');
|
|
24
|
+
const button = document.createElement('gtk-menu-button');
|
|
25
|
+
outer.append(button);
|
|
26
|
+
document.body.append(outer);
|
|
27
|
+
made.push(outer);
|
|
28
|
+
return { outer, button };
|
|
29
|
+
}, parentOfElement),
|
|
30
|
+
{ describe, it, expect },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
await describe('adwaita-web: menus in a shared tree (ADR 0097 § 1)', async () => {
|
|
34
|
+
for (const { result, rule, tree } of MENU_TREE_VECTORS) {
|
|
35
|
+
await it(rule, () => {
|
|
36
|
+
const node = tree as unknown as SharedTreeNode;
|
|
37
|
+
if ('refused' in result) {
|
|
38
|
+
expect(() => buildSharedTree(node)).toThrow(result.refused);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const button = buildSharedTree(node) as unknown as GtkMenuButton;
|
|
42
|
+
expect(button.menuModel.map((entry) => entry.kind).join(',')).toBe(result.kinds.join(','));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
for (const element of made.splice(0)) element.remove();
|
|
47
|
+
};
|
package/src/namespace/adw.ts
CHANGED
|
@@ -109,3 +109,5 @@ export { AdwViewSwitcherBar as ViewSwitcherBar } from '../elements/adw-view-swit
|
|
|
109
109
|
export { AdwWindow as Window } from '../elements/adw-window.js';
|
|
110
110
|
export { AdwWindowTitle as WindowTitle } from '../elements/adw-window-title.js';
|
|
111
111
|
export { AdwWrapBox as WrapBox } from '../elements/adw-wrap-box.js';
|
|
112
|
+
export { AdwApplication as Application } from '../application.js';
|
|
113
|
+
export { AdwStyleManager as StyleManager } from '../style-manager.js';
|
package/src/namespace/gio.ts
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
// `check-vocabulary-alignment.mjs` derives their members from the elements this package
|
|
13
13
|
// defines. GIO defines none: what lands here is the handful of GObject types an Adwaita
|
|
14
14
|
// author CONSTRUCTS while building a tree — today the menu a `menuModel` property takes.
|
|
15
|
-
// `Gio.File`, `Gio.Settings
|
|
15
|
+
// `Gio.File`, `Gio.Settings` and the rest of the library are NOT this
|
|
16
16
|
// package's business; a namespace that grows past what the elements consume is a second,
|
|
17
17
|
// unheld vocabulary.
|
|
18
18
|
|
|
19
19
|
export { GioMenu as Menu, GioMenuItem as MenuItem } from '@gjsify/adwaita-core';
|
|
20
|
+
export { SimpleAction, SimpleActionGroup, ApplicationFlags } from '@gjsify/adwaita-core';
|
|
21
|
+
export { GioApplication as Application } from '../application.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The GLib half of the vocabulary: the main-loop sources a GNOME app schedules work with —
|
|
2
|
+
// `idle_add`, `timeout_add`, `source_remove` and the `PRIORITY_*` / `SOURCE_*` constants. The
|
|
3
|
+
// behaviour lives once in `@gjsify/adwaita-core` (`glib-timers.ts`, which states where a host
|
|
4
|
+
// event loop cannot honour GLib); this file is the namespace door over the host's `setTimeout`.
|
|
5
|
+
//
|
|
6
|
+
// The path helpers (`MAXUINT32`, `build_filenamev`, `get_current_dir`, `get_system_data_dirs`) live
|
|
7
|
+
// in `glib-paths.ts` of the core; the host has no filesystem, so `get_current_dir()` is `"/"` and
|
|
8
|
+
// `get_system_data_dirs()` is `[]`. A port that knows better replaces the member in its own file.
|
|
9
|
+
//
|
|
10
|
+
// A TRUE SUBSET: everything else in GLib (`MainLoop`, `Source`, `timeout_add_seconds`, `Bytes`,
|
|
11
|
+
// `get_home_dir`, …) is absent, and the `gi://GLib` arm refuses it by name.
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
buildFilenamev,
|
|
15
|
+
createGLibTimers,
|
|
16
|
+
getCurrentDir,
|
|
17
|
+
getSystemDataDirs,
|
|
18
|
+
MAXUINT32 as GLIB_MAXUINT32,
|
|
19
|
+
} from '@gjsify/adwaita-core';
|
|
20
|
+
|
|
21
|
+
const glib = createGLibTimers();
|
|
22
|
+
|
|
23
|
+
export const PRIORITY_HIGH = glib.PRIORITY_HIGH;
|
|
24
|
+
export const PRIORITY_DEFAULT = glib.PRIORITY_DEFAULT;
|
|
25
|
+
export const PRIORITY_HIGH_IDLE = glib.PRIORITY_HIGH_IDLE;
|
|
26
|
+
export const PRIORITY_DEFAULT_IDLE = glib.PRIORITY_DEFAULT_IDLE;
|
|
27
|
+
export const PRIORITY_LOW = glib.PRIORITY_LOW;
|
|
28
|
+
export const SOURCE_REMOVE = glib.SOURCE_REMOVE;
|
|
29
|
+
export const SOURCE_CONTINUE = glib.SOURCE_CONTINUE;
|
|
30
|
+
export const idle_add = glib.idle_add;
|
|
31
|
+
export const timeout_add = glib.timeout_add;
|
|
32
|
+
export const source_remove = glib.source_remove;
|
|
33
|
+
|
|
34
|
+
export const MAXUINT32 = GLIB_MAXUINT32;
|
|
35
|
+
export const build_filenamev = buildFilenamev;
|
|
36
|
+
export const get_current_dir = getCurrentDir;
|
|
37
|
+
export const get_system_data_dirs = getSystemDataDirs;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The GObject half of `@gjsify/adwaita-web`'s vocabulary (ADR 0096) — `GObject.registerClass`,
|
|
2
|
+
// `GObject.ParamSpec` and the rest of the subset, the way `./adw.ts` and `./gtk.ts` export their
|
|
3
|
+
// members. `export * as GObject from './namespace/gobject.js'` in `src/index.ts` makes it the
|
|
4
|
+
// namespace. Everything is the core's (`@gjsify/adwaita-core`); this port supplies only the door
|
|
5
|
+
// to custom elements, in `../gobject-door.ts`.
|
|
6
|
+
//
|
|
7
|
+
// `Object` is re-exported under its GIR name, so it is bound to a local first: a module-level
|
|
8
|
+
// `export const Object` would shadow the global one for this file.
|
|
9
|
+
|
|
10
|
+
import { createGObject } from '@gjsify/adwaita-core';
|
|
11
|
+
|
|
12
|
+
import { webDoor } from '../gobject-door.js';
|
|
13
|
+
|
|
14
|
+
const gobject = createGObject(webDoor);
|
|
15
|
+
|
|
16
|
+
const GObjectObject = gobject.Object;
|
|
17
|
+
|
|
18
|
+
export { GObjectObject as Object };
|
|
19
|
+
export const registerClass = gobject.registerClass;
|
|
20
|
+
export const type_ensure = gobject.type_ensure;
|
|
21
|
+
export const signal_stop_emission_by_name = gobject.signal_stop_emission_by_name;
|
|
22
|
+
export const ParamSpec = gobject.ParamSpec;
|
|
23
|
+
export const ParamFlags = gobject.ParamFlags;
|
|
24
|
+
export const TYPE_STRING = gobject.TYPE_STRING;
|
|
25
|
+
export const TYPE_BOOLEAN = gobject.TYPE_BOOLEAN;
|
|
26
|
+
export const TYPE_INT = gobject.TYPE_INT;
|
|
27
|
+
export const TYPE_UINT = gobject.TYPE_UINT;
|
|
28
|
+
export const TYPE_DOUBLE = gobject.TYPE_DOUBLE;
|
|
29
|
+
export const GTypeName = gobject.GTypeName;
|
|
30
|
+
export const GTypeFlags = gobject.GTypeFlags;
|
|
31
|
+
export const interfaces = gobject.interfaces;
|
|
32
|
+
export const properties = gobject.properties;
|
|
33
|
+
export const signals = gobject.signals;
|
|
34
|
+
export const requires = gobject.requires;
|
|
35
|
+
export const __gtkTemplate__ = gobject.__gtkTemplate__;
|
|
36
|
+
export const __gtkCssName__ = gobject.__gtkCssName__;
|
|
37
|
+
export const __gtkChildren__ = gobject.__gtkChildren__;
|
|
38
|
+
export const __gtkInternalChildren__ = gobject.__gtkInternalChildren__;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The plain GTK enums an application reads off `Gtk` — `Gtk.PolicyType`, `Gtk.TextDirection` and
|
|
2
|
+
// `Gtk.TextWindowType`, as value tables. The numbers are the typelib's (GIR declaration order),
|
|
3
|
+
// and the same tables the NativeScript port publishes.
|
|
4
|
+
//
|
|
5
|
+
// Reference: refs/gtk gtk/gtkenums.h (GtkPolicyType, GtkTextDirection), gtk/gtktextview.h (GtkTextWindowType)
|
|
6
|
+
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
7
|
+
|
|
8
|
+
/** `Gtk.PolicyType`: `ALWAYS` 0, `AUTOMATIC` 1, `NEVER` 2, `EXTERNAL` 3. */
|
|
9
|
+
export const PolicyType = Object.freeze({ ALWAYS: 0, AUTOMATIC: 1, NEVER: 2, EXTERNAL: 3 } as const);
|
|
10
|
+
|
|
11
|
+
/** `Gtk.TextDirection`: `NONE` 0, `LTR` 1, `RTL` 2. */
|
|
12
|
+
export const TextDirection = Object.freeze({ NONE: 0, LTR: 1, RTL: 2 } as const);
|
|
13
|
+
|
|
14
|
+
/** `Gtk.TextWindowType`: `WIDGET` 1, `TEXT` 2, `LEFT` 3, `RIGHT` 4, `TOP` 5, `BOTTOM` 6. */
|
|
15
|
+
export const TextWindowType = Object.freeze({ WIDGET: 1, TEXT: 2, LEFT: 3, RIGHT: 4, TOP: 5, BOTTOM: 6 } as const);
|
package/src/namespace/gtk.ts
CHANGED
|
@@ -21,6 +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
25
|
export { GtkAboutDialog as AboutDialog } from '../elements/gtk-about-dialog.js';
|
|
25
26
|
export { GtkActionBar as ActionBar } from '../elements/gtk-action-bar.js';
|
|
26
27
|
export { GtkApplicationWindow as ApplicationWindow } from '../elements/gtk-application-window.js';
|
|
@@ -95,3 +96,4 @@ export { GtkWindow as Window } from '../elements/gtk-window.js';
|
|
|
95
96
|
export { GtkWindowControls as WindowControls } from '../elements/gtk-window-controls.js';
|
|
96
97
|
export { GtkViewport as Viewport } from '../elements/gtk-viewport.js';
|
|
97
98
|
export { GtkWindowHandle as WindowHandle } from '../elements/gtk-window-handle.js';
|
|
99
|
+
export { GtkApplication as Application } from '../application.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The GtkSource vocabulary of `@gjsify/adwaita-web` (ADR 0094 amendment): `GtkSource.View`,
|
|
2
|
+
// `Buffer`, `LanguageManager`, `StyleSchemeManager`, over `@gjsify/gtksource-core`, the same names
|
|
3
|
+
// the NativeScript port publishes. `export * as GtkSource from './namespace/gtksource.js'` in
|
|
4
|
+
// `src/index.ts` makes it the namespace the `gi://GtkSource` arm answers from.
|
|
5
|
+
//
|
|
6
|
+
// A TRUE SUBSET: GtkSource's completion, search and the rest are absent, and the arm refuses each
|
|
7
|
+
// by name.
|
|
8
|
+
export {
|
|
9
|
+
Buffer,
|
|
10
|
+
Gutter,
|
|
11
|
+
GutterLines,
|
|
12
|
+
GutterRenderer,
|
|
13
|
+
GutterRendererText,
|
|
14
|
+
Language,
|
|
15
|
+
LanguageManager,
|
|
16
|
+
StyleScheme,
|
|
17
|
+
StyleSchemeManager,
|
|
18
|
+
init,
|
|
19
|
+
} from '@gjsify/gtksource-core';
|
|
20
|
+
export { GtkSourceView as View } from '../gtksource/gtk-source-view.js';
|