@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.
Files changed (75) hide show
  1. package/dist/adwaita-web.css +8 -2
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/application.d.ts +24 -0
  4. package/lib/types/application.spec.d.ts +1 -0
  5. package/lib/types/elements/adw-application-window.d.ts +2 -0
  6. package/lib/types/elements/adw-view-stack.d.ts +3 -0
  7. package/lib/types/elements/checks.d.ts +6 -0
  8. package/lib/types/elements/gtk-application-window.d.ts +2 -0
  9. package/lib/types/elements/gtk-drop-down.d.ts +3 -0
  10. package/lib/types/elements/gtk-entry.d.ts +10 -0
  11. package/lib/types/elements/gtk-menu-button.d.ts +2 -0
  12. package/lib/types/elements/gtk-scrolled-window.d.ts +16 -0
  13. package/lib/types/elements/gtk-stack.d.ts +3 -0
  14. package/lib/types/elements/gtk-switch.d.ts +3 -0
  15. package/lib/types/gio.spec.d.ts +1 -0
  16. package/lib/types/glib.spec.d.ts +1 -0
  17. package/lib/types/gobject-door.d.ts +4 -0
  18. package/lib/types/gobject-door.spec.d.ts +1 -0
  19. package/lib/types/gobject-elements.d.ts +17 -0
  20. package/lib/types/gtksource/gtk-source-view.d.ts +78 -0
  21. package/lib/types/gtksource/gtk-source-view.spec.d.ts +2 -0
  22. package/lib/types/gtksource/web-editor-driver.d.ts +53 -0
  23. package/lib/types/index.d.ts +3 -0
  24. package/lib/types/menu-actions.spec.d.ts +1 -0
  25. package/lib/types/namespace/adw.d.ts +2 -0
  26. package/lib/types/namespace/gio.d.ts +2 -0
  27. package/lib/types/namespace/glib.d.ts +15 -0
  28. package/lib/types/namespace/gobject.d.ts +28 -0
  29. package/lib/types/namespace/gtk-enums.d.ts +22 -0
  30. package/lib/types/namespace/gtk.d.ts +2 -0
  31. package/lib/types/namespace/gtksource.d.ts +2 -0
  32. package/lib/types/shared-tree-builder.d.ts +20 -1
  33. package/lib/types/style-manager.d.ts +5 -0
  34. package/lib/types/style-manager.spec.d.ts +1 -0
  35. package/lib/types/styles.generated.d.ts +1 -1
  36. package/package.json +11 -9
  37. package/scss/_reset.scss +1 -1
  38. package/src/application.spec.ts +75 -0
  39. package/src/application.ts +92 -0
  40. package/src/construct-vectors.spec.ts +155 -0
  41. package/src/elements/adw-application-window.ts +5 -0
  42. package/src/elements/adw-split-button.ts +5 -1
  43. package/src/elements/adw-view-stack.ts +14 -0
  44. package/src/elements/checks.ts +25 -1
  45. package/src/elements/gtk-application-window.ts +5 -0
  46. package/src/elements/gtk-drop-down.ts +16 -0
  47. package/src/elements/gtk-entry.ts +45 -7
  48. package/src/elements/gtk-menu-button.ts +12 -1
  49. package/src/elements/gtk-popover-menu.ts +7 -2
  50. package/src/elements/gtk-scrolled-window.ts +58 -0
  51. package/src/elements/gtk-stack.ts +8 -0
  52. package/src/elements/gtk-switch.ts +8 -0
  53. package/src/gio.spec.ts +23 -0
  54. package/src/glib.spec.ts +49 -0
  55. package/src/gobject-door.spec.ts +119 -0
  56. package/src/gobject-door.ts +137 -0
  57. package/src/gobject-elements.ts +172 -0
  58. package/src/gtksource/gtk-source-view.spec.ts +141 -0
  59. package/src/gtksource/gtk-source-view.ts +386 -0
  60. package/src/gtksource/web-editor-driver.ts +346 -0
  61. package/src/index.ts +10 -0
  62. package/src/menu-actions.spec.ts +47 -0
  63. package/src/namespace/adw.ts +2 -0
  64. package/src/namespace/gio.ts +3 -1
  65. package/src/namespace/glib.ts +37 -0
  66. package/src/namespace/gobject.ts +38 -0
  67. package/src/namespace/gtk-enums.ts +15 -0
  68. package/src/namespace/gtk.ts +2 -0
  69. package/src/namespace/gtksource.ts +20 -0
  70. package/src/shared-tree-builder.ts +142 -7
  71. package/src/split-button.spec.ts +2 -0
  72. package/src/style-manager.spec.ts +36 -0
  73. package/src/style-manager.ts +27 -0
  74. package/src/styles.generated.ts +1 -1
  75. package/src/test.browser.mts +14 -0
@@ -0,0 +1,386 @@
1
+ // <gtk-source-view> — `GtkSource.View`, built on `@gjsify/gtksource-core`.
2
+ //
3
+ // WHY NOT <adw-source-view>. That element is CodeMirror with its own grammar; this one reads the
4
+ // same `.lang` and style-scheme files GtkSourceView does, through the same `EditorSession` the
5
+ // Android port runs, so GNOME, Android and the web have one source of truth for highlighting.
6
+ //
7
+ // THE SURFACE IS A TRUE SUBSET of GtkSource.View: the names and semantics below are GtkSourceView's,
8
+ // and what is absent is absent. The editing itself is the browser's (see `web-editor-driver.ts`).
9
+ //
10
+ // IN THIS SLICE: `buffer`, `auto-indent`, `indent-width` (held and read back, as on Android),
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` (the LEFT gutter paints its renderers' `vfunc_query_data`; the right one is a model).
14
+ //
15
+ // Reference: GtkSourceView 5 gtksourceview.c, upstream GNOME/gtksourceview (properties and defaults)
16
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
17
+
18
+ import { adwaitaColorScheme, GtkAdjustment, onAdwaitaColorSchemeChanged } from '@gjsify/adwaita-core';
19
+ import type { Buffer, Gutter } from '@gjsify/gtksource-core';
20
+ import { EditorSession, GutterSet, toBoolean, toNumber } from '@gjsify/gtksource-core';
21
+
22
+ import { WebEditorDriver } from './web-editor-driver.js';
23
+
24
+ const STYLE_ID = 'gjsify-gtk-source-view-style';
25
+
26
+ const STYLE = `
27
+ gtk-source-view {
28
+ display: flex;
29
+ flex: 1 1 auto;
30
+ min-width: 0;
31
+ min-height: 12em;
32
+ height: 100%;
33
+ box-sizing: border-box;
34
+ position: relative;
35
+ overflow: hidden;
36
+ background: var(--gsv-bg, var(--view-bg-color));
37
+ color: var(--gsv-fg, var(--view-fg-color));
38
+ font: inherit;
39
+ --gsv-line-height: 1.4;
40
+ }
41
+ gtk-source-view.monospace { font-family: var(--monospace-font-family, ui-monospace, monospace); }
42
+ gtk-source-view .gsv-gutter {
43
+ display: none;
44
+ flex: none;
45
+ overflow: hidden;
46
+ box-sizing: content-box;
47
+ padding: var(--gsv-margin-top, 0px) 0 0;
48
+ text-align: end;
49
+ line-height: var(--gsv-line-height);
50
+ color: var(--gsv-number-fg);
51
+ background: var(--gsv-number-bg);
52
+ user-select: none;
53
+ }
54
+ gtk-source-view.show-line-numbers .gsv-gutter,
55
+ gtk-source-view.has-columns .gsv-gutter { display: block; }
56
+ gtk-source-view .gsv-columns { display: flex; }
57
+ gtk-source-view .gsv-numbers,
58
+ gtk-source-view .gsv-column { flex: none; box-sizing: content-box; }
59
+ gtk-source-view .gsv-numbers { width: var(--gsv-gutter-width, 2ch); padding: 0 var(--gsv-gutter-padding, 8px); }
60
+ gtk-source-view .gsv-cell { height: calc(1em * var(--gsv-line-height)); white-space: pre; text-align: start; }
61
+ gtk-source-view .gsv-number.current { color: var(--gsv-current-number-fg); background: var(--gsv-current-number-bg); }
62
+ gtk-source-view .gsv-stage { position: relative; flex: 1 1 auto; min-width: 0; }
63
+ gtk-source-view .gsv-backdrop,
64
+ gtk-source-view .gsv-area {
65
+ position: absolute;
66
+ inset: 0;
67
+ margin: 0;
68
+ border: 0;
69
+ box-sizing: border-box;
70
+ padding: var(--gsv-margin-top, 0px) var(--gsv-margin-right, 0px) var(--gsv-margin-bottom, 0px) var(--gsv-margin-left, 0px);
71
+ font: inherit;
72
+ line-height: var(--gsv-line-height);
73
+ white-space: pre;
74
+ tab-size: 8;
75
+ letter-spacing: normal;
76
+ }
77
+ gtk-source-view .gsv-backdrop { overflow: hidden; pointer-events: none; }
78
+ gtk-source-view .gsv-line { min-height: calc(1em * var(--gsv-line-height)); }
79
+ gtk-source-view .gsv-line.current { background: var(--gsv-current-line); }
80
+ gtk-source-view .gsv-area {
81
+ resize: none;
82
+ overflow: auto;
83
+ outline: none;
84
+ background: transparent;
85
+ color: transparent;
86
+ caret-color: var(--gsv-fg, currentColor);
87
+ }
88
+ gtk-source-view.cursor-hidden .gsv-area { caret-color: transparent; }
89
+ gtk-source-view .gsv-area::selection { background: var(--gsv-selection); color: transparent; }
90
+ `;
91
+
92
+ function ensureStyleInjected(): void {
93
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID) !== null) return;
94
+ const style = document.createElement('style');
95
+ style.id = STYLE_ID;
96
+ style.textContent = STYLE;
97
+ document.head.appendChild(style);
98
+ }
99
+
100
+ type Coerce = (value: unknown, name: string) => boolean | number;
101
+
102
+ /** attribute → [property, default, coercion] */
103
+ const ATTRIBUTES: Readonly<Record<string, readonly [string, boolean | number, Coerce]>> = {
104
+ 'auto-indent': ['autoIndent', false, toBoolean],
105
+ 'indent-width': ['indentWidth', -1, toNumber],
106
+ 'show-line-numbers': ['showLineNumbers', false, toBoolean],
107
+ 'highlight-current-line': ['highlightCurrentLine', false, toBoolean],
108
+ monospace: ['monospace', false, toBoolean],
109
+ editable: ['editable', true, toBoolean],
110
+ 'cursor-visible': ['cursorVisible', true, toBoolean],
111
+ 'left-margin': ['leftMargin', 0, toNumber],
112
+ 'right-margin': ['rightMargin', 0, toNumber],
113
+ 'top-margin': ['topMargin', 0, toNumber],
114
+ 'bottom-margin': ['bottomMargin', 0, toNumber],
115
+ };
116
+
117
+ const OBSERVED_ATTRIBUTES = [
118
+ 'auto-indent',
119
+ 'indent-width',
120
+ 'show-line-numbers',
121
+ 'highlight-current-line',
122
+ 'monospace',
123
+ 'editable',
124
+ 'cursor-visible',
125
+ 'left-margin',
126
+ 'right-margin',
127
+ 'top-margin',
128
+ 'bottom-margin',
129
+ ];
130
+
131
+ export class GtkSourceView extends HTMLElement {
132
+ static get observedAttributes(): string[] {
133
+ return OBSERVED_ATTRIBUTES;
134
+ }
135
+
136
+ private readonly session: EditorSession;
137
+ private readonly driver: WebEditorDriver;
138
+ private unsubscribe: (() => void) | null = null;
139
+ private built = false;
140
+ private readonly gutter = document.createElement('div');
141
+ private readonly backdrop = document.createElement('div');
142
+ private readonly area = document.createElement('textarea');
143
+ private readonly vadj = new GtkAdjustment();
144
+ private readonly hadj = new GtkAdjustment();
145
+ private direction = 1;
146
+ private readonly gutters = new GutterSet(this);
147
+
148
+ constructor() {
149
+ super();
150
+ this.gutter.className = 'gsv-gutter';
151
+ this.backdrop.className = 'gsv-backdrop';
152
+ this.area.className = 'gsv-area';
153
+ this.area.wrap = 'off';
154
+ this.area.spellcheck = false;
155
+ this.area.autocapitalize = 'off';
156
+ this.area.autocomplete = 'off';
157
+ this.driver = new WebEditorDriver({
158
+ root: this,
159
+ area: this.area,
160
+ backdrop: this.backdrop,
161
+ gutter: this.gutter,
162
+ });
163
+ this.session = new EditorSession(this.driver, undefined, adwaitaColorScheme());
164
+ this.session.bindGutters(this.gutters);
165
+ }
166
+
167
+ private syncAdjustments(): void {
168
+ const { area } = this;
169
+ this.vadj.configure(area.scrollTop, 0, area.scrollHeight, 20, area.clientHeight, area.clientHeight);
170
+ this.hadj.configure(area.scrollLeft, 0, area.scrollWidth, 20, area.clientWidth, area.clientWidth);
171
+ }
172
+
173
+ private readonly onAreaScroll = (): void => this.syncAdjustments();
174
+
175
+ connectedCallback(): void {
176
+ ensureStyleInjected();
177
+ this.area.addEventListener('scroll', this.onAreaScroll);
178
+ if (!this.built) {
179
+ this.built = true;
180
+ const stage = document.createElement('div');
181
+ stage.className = 'gsv-stage';
182
+ stage.append(this.backdrop, this.area);
183
+ this.append(this.gutter, stage);
184
+ }
185
+ this.driver.attach();
186
+ this.unsubscribe = onAdwaitaColorSchemeChanged(() => this.session.setColorScheme(adwaitaColorScheme()));
187
+ this.session.setColorScheme(adwaitaColorScheme());
188
+ }
189
+
190
+ disconnectedCallback(): void {
191
+ this.area.removeEventListener('scroll', this.onAreaScroll);
192
+ this.unsubscribe?.();
193
+ this.unsubscribe = null;
194
+ this.driver.detach();
195
+ }
196
+
197
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
198
+ const [property, fallback, coerce] = ATTRIBUTES[name];
199
+ // Presence is true for a boolean written as a bare attribute (`toggleAttribute`).
200
+ const next =
201
+ value === null
202
+ ? fallback
203
+ : value === '' && typeof fallback === 'boolean'
204
+ ? true
205
+ : coerce(value, `GtkSource.View.${property}`);
206
+ (this as unknown as Record<string, unknown>)[property] = next;
207
+ }
208
+
209
+ /** `GtkSource.View:buffer`. */
210
+ get buffer(): Buffer {
211
+ return this.session.buffer;
212
+ }
213
+ set buffer(value: Buffer) {
214
+ this.session.buffer = value;
215
+ }
216
+
217
+ get autoIndent(): boolean {
218
+ return this.session.autoIndent;
219
+ }
220
+ set autoIndent(value: boolean | string) {
221
+ this.session.autoIndent = toBoolean(value, 'GtkSource.View.autoIndent');
222
+ }
223
+
224
+ /** Held and read back; a textarea has no indent step to apply it to (ADR 0094). */
225
+ get indentWidth(): number {
226
+ return this.session.indentWidth;
227
+ }
228
+ set indentWidth(value: number | string) {
229
+ this.session.indentWidth = toNumber(value, 'GtkSource.View.indentWidth');
230
+ }
231
+
232
+ get showLineNumbers(): boolean {
233
+ return this.session.showLineNumbers;
234
+ }
235
+ set showLineNumbers(value: boolean | string) {
236
+ this.session.showLineNumbers = toBoolean(value, 'GtkSource.View.showLineNumbers');
237
+ }
238
+
239
+ get highlightCurrentLine(): boolean {
240
+ return this.session.highlightCurrentLine;
241
+ }
242
+ set highlightCurrentLine(value: boolean | string) {
243
+ this.session.highlightCurrentLine = toBoolean(value, 'GtkSource.View.highlightCurrentLine');
244
+ }
245
+
246
+ get monospace(): boolean {
247
+ return this.session.monospace;
248
+ }
249
+ set monospace(value: boolean | string) {
250
+ this.session.monospace = toBoolean(value, 'GtkSource.View.monospace');
251
+ }
252
+
253
+ get editable(): boolean {
254
+ return this.session.editable;
255
+ }
256
+ set editable(value: boolean | string) {
257
+ this.session.editable = toBoolean(value, 'GtkSource.View.editable');
258
+ }
259
+
260
+ /** `GtkTextView:cursor-visible` — whether the insertion cursor is drawn; TRUE by default. */
261
+ get cursorVisible(): boolean {
262
+ return this.session.cursorVisible;
263
+ }
264
+ set cursorVisible(value: boolean | string) {
265
+ this.session.cursorVisible = toBoolean(value, 'GtkSource.View.cursorVisible');
266
+ }
267
+
268
+ // The GJS snake_case names of the same properties.
269
+ get cursor_visible(): boolean {
270
+ return this.cursorVisible;
271
+ }
272
+ set cursor_visible(value: boolean) {
273
+ this.cursorVisible = value;
274
+ }
275
+ get_cursor_visible(): boolean {
276
+ return this.cursorVisible;
277
+ }
278
+ set_cursor_visible(value: boolean): void {
279
+ this.cursorVisible = value;
280
+ }
281
+ get_editable(): boolean {
282
+ return this.editable;
283
+ }
284
+ set_editable(value: boolean): void {
285
+ this.editable = value;
286
+ }
287
+ get highlight_current_line(): boolean {
288
+ return this.highlightCurrentLine;
289
+ }
290
+ set highlight_current_line(value: boolean) {
291
+ this.highlightCurrentLine = value;
292
+ }
293
+ get show_line_numbers(): boolean {
294
+ return this.showLineNumbers;
295
+ }
296
+ set show_line_numbers(value: boolean) {
297
+ this.showLineNumbers = value;
298
+ }
299
+
300
+ /** `GtkScrollable:vadjustment` — one stable adjustment that follows the textarea's scroll. */
301
+ get vadjustment(): GtkAdjustment {
302
+ return this.vadj;
303
+ }
304
+ get hadjustment(): GtkAdjustment {
305
+ return this.hadj;
306
+ }
307
+ get_vadjustment(): GtkAdjustment {
308
+ return this.vadj;
309
+ }
310
+ get_hadjustment(): GtkAdjustment {
311
+ return this.hadj;
312
+ }
313
+
314
+ /** `gtk_widget_set_direction`: held, and mirrored onto `dir` for LTR and RTL. */
315
+ set_direction(direction: number): void {
316
+ if (![0, 1, 2].includes(direction)) {
317
+ throw new TypeError(`${direction} is not a valid value for enum argument dir`);
318
+ }
319
+ this.direction = direction;
320
+ if (direction === 0) this.removeAttribute('dir');
321
+ else this.dir = direction === 2 ? 'rtl' : 'ltr';
322
+ }
323
+ get_direction(): number {
324
+ return this.direction;
325
+ }
326
+
327
+ /** A view has no widget children of its own here: its parts are the browser's. */
328
+ get_first_child(): null {
329
+ return null;
330
+ }
331
+ get_next_sibling(): Element | null {
332
+ return this.nextElementSibling;
333
+ }
334
+ get_parent(): Element | null {
335
+ return this.parentElement;
336
+ }
337
+
338
+ /** `null` for any side but LEFT and RIGHT, as in GtkSourceView (ADR 0103). */
339
+ get_gutter(window_type: number): Gutter | null {
340
+ return this.gutters.get(window_type);
341
+ }
342
+
343
+ get leftMargin(): number {
344
+ return this.session.leftMargin;
345
+ }
346
+ set leftMargin(value: number | string) {
347
+ this.session.leftMargin = toNumber(value, 'GtkSource.View.leftMargin');
348
+ }
349
+
350
+ get rightMargin(): number {
351
+ return this.session.rightMargin;
352
+ }
353
+ set rightMargin(value: number | string) {
354
+ this.session.rightMargin = toNumber(value, 'GtkSource.View.rightMargin');
355
+ }
356
+
357
+ get topMargin(): number {
358
+ return this.session.topMargin;
359
+ }
360
+ set topMargin(value: number | string) {
361
+ this.session.topMargin = toNumber(value, 'GtkSource.View.topMargin');
362
+ }
363
+
364
+ get bottomMargin(): number {
365
+ return this.session.bottomMargin;
366
+ }
367
+ set bottomMargin(value: number | string) {
368
+ this.session.bottomMargin = toNumber(value, 'GtkSource.View.bottomMargin');
369
+ }
370
+
371
+ /** GJS `connect(name, cb) → id`; the handler's first argument is this view. */
372
+ connect(name: string, callback: (self: GtkSourceView, ...args: never[]) => void): number {
373
+ return this.session.connect(name, ((_session: unknown, ...args: never[]) => callback(this, ...args)) as never);
374
+ }
375
+
376
+ disconnect(id: number): void {
377
+ this.session.disconnect(id);
378
+ }
379
+
380
+ /** The inner textarea, for focus and tests. */
381
+ get textarea(): HTMLTextAreaElement {
382
+ return this.area;
383
+ }
384
+ }
385
+
386
+ customElements.define('gtk-source-view', GtkSourceView);