@gjsify/gtk-host 0.47.0 → 0.49.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 (53) hide show
  1. package/README.md +158 -2
  2. package/lib/esm/adjustment.js +1 -0
  3. package/lib/esm/conformance/diagnostics.js +1 -1
  4. package/lib/esm/conformance/index.js +1 -2
  5. package/lib/esm/conformance/tree.js +2 -0
  6. package/lib/esm/conformance/window-chrome.js +1 -0
  7. package/lib/esm/descriptors/adw.js +1 -1
  8. package/lib/esm/descriptors/gtk.js +1 -1
  9. package/lib/esm/descriptors/index.js +1 -1
  10. package/lib/esm/errors.js +1 -1
  11. package/lib/esm/font-families.js +1 -0
  12. package/lib/esm/fonts.js +1 -1
  13. package/lib/esm/host.js +1 -1
  14. package/lib/esm/index.js +1 -1
  15. package/lib/esm/list-model.js +1 -0
  16. package/lib/esm/menu.js +1 -0
  17. package/lib/esm/policies.js +1 -1
  18. package/lib/esm/probe.js +1 -1
  19. package/lib/esm/props.js +1 -1
  20. package/lib/esm/style/font-family.js +3 -0
  21. package/lib/esm/style/index.js +1 -1
  22. package/lib/esm/style/paint.js +1 -1
  23. package/lib/types/adjustment.d.ts +28 -0
  24. package/lib/types/adjustment.spec.d.ts +2 -0
  25. package/lib/types/attrs.d.ts +79 -0
  26. package/lib/types/conformance/index.d.ts +28 -29
  27. package/lib/types/conformance/tree.d.ts +30 -0
  28. package/lib/types/conformance/vectors.d.mts +6 -0
  29. package/lib/types/conformance/window-chrome.d.ts +29 -0
  30. package/lib/types/descriptors/gtk.d.ts +32 -1
  31. package/lib/types/errors.d.ts +43 -0
  32. package/lib/types/font-families.d.ts +40 -0
  33. package/lib/types/font-families.spec.d.ts +2 -0
  34. package/lib/types/fonts.d.ts +45 -1
  35. package/lib/types/generated/enum-values.d.mts +38 -0
  36. package/lib/types/generated/methods.d.mts +46 -0
  37. package/lib/types/index.d.ts +1 -1
  38. package/lib/types/jsx-runtime.d.ts +2 -2
  39. package/lib/types/list-model.d.ts +38 -0
  40. package/lib/types/list-model.spec.d.ts +2 -0
  41. package/lib/types/menu.d.ts +43 -0
  42. package/lib/types/menu.spec.d.ts +2 -0
  43. package/lib/types/placement.spec.d.ts +2 -0
  44. package/lib/types/policies.d.ts +241 -1
  45. package/lib/types/react-jsx-runtime.d.ts +2 -2
  46. package/lib/types/shared-trees.spec.d.ts +2 -0
  47. package/lib/types/style/font-family.d.ts +64 -0
  48. package/lib/types/style/index.d.ts +3 -1
  49. package/lib/types/style/paint.d.ts +37 -0
  50. package/lib/types/testing/refusal.d.mts +2 -0
  51. package/lib/types/types.d.ts +177 -1
  52. package/lib/types/vue-components.d.ts +2 -2
  53. package/package.json +8 -5
@@ -74,8 +74,131 @@ export interface HostElement extends HostNodeBase {
74
74
  * computes "first" and the whole rendered tree lands ABOVE the app's own chrome.
75
75
  */
76
76
  foreign: readonly Gtk.Widget[];
77
+ /**
78
+ * A PORTAL node's subscription to its parent's `root`, or null.
79
+ *
80
+ * Only a `portal` placement ever has one, and it is per-attachment state
81
+ * exactly like `wrapper` — a side table keyed on the element would be a second
82
+ * tree that can disagree with this one. See {@link NodePlacement}'s portal arm
83
+ * for what the subscription is for and what it was measured against.
84
+ */
85
+ portalWatch: {
86
+ widget: Gtk.Widget;
87
+ id: number;
88
+ } | null;
77
89
  }
78
90
  export type HostNode = HostElement | HostText | HostAnchor;
91
+ /**
92
+ * The axis ABOVE `ChildPolicy`, and the two are orthogonal on purpose.
93
+ *
94
+ * `ChildPolicy` answers "how does this parent adopt a child". It presumes the
95
+ * answer to a question nobody had to ask until now: *does this node go into its
96
+ * parent at all?* For every widget in the table it does. For an `Adw.Dialog` it
97
+ * does not, and the difference is not a degradation the parent can absorb —
98
+ * MEASURED on libadwaita 1.9.3 / GTK 4.22.4 / gjs 1.88.1, `box.append(dialog)`
99
+ * with the box ROOTED IN A WINDOW reaches
100
+ *
101
+ * Adwaita-ERROR **: Trying to add AdwDialog 0x… to GtkBox 0x…. Use
102
+ * adw_dialog_present() to show dialogs.
103
+ *
104
+ * which is `g_error()`: SIGABRT, exit 134, a core dump. Not an exception a `try`
105
+ * in the reconciler can catch and not a warning a diagnostics gate can count.
106
+ * `adw_dialog_root()` raises it from the ROOT vfunc, which is why a DETACHED box
107
+ * accepts the very same append in silence at exit 0 — re-testing this on a bare
108
+ * box "disproves" it and puts the append back.
109
+ *
110
+ * THE ABORT IS THE LOUD HALF OF A CLASS, and its quiet half arrives through the
111
+ * same door (ADR 0054). MEASURED on the same libraries, one process per case:
112
+ * `box.append(new Gtk.Window())` with the box rooted is **exit 0, silent**, and
113
+ * afterwards `win.get_parent()` is the box while `win.get_root()` is the window
114
+ * ITSELF — a `GtkRoot` with a parent, which is a contradiction GTK states nowhere.
115
+ * Presenting that window then draws it as a toplevel AND leaves it in the box's
116
+ * child list, so the container measures and allocates a window. One node kind
117
+ * aborts, its neighbour says nothing; both are the same question answered wrong,
118
+ * so both are answered here.
119
+ *
120
+ * So the descriptor declares the axis, and `policies.ts` is the only reader.
121
+ * `parented` is every other widget and is what an ABSENT `placement` means; the
122
+ * union still cannot be forgotten because there is exactly one normaliser
123
+ * (`placementOf`) and every switch over it ends in `unhandledPlacement`.
124
+ */
125
+ export type NodePlacement =
126
+ /**
127
+ * The parent's `ChildPolicy` places it — every widget but the two families below.
128
+ *
129
+ * DECLARING it is not the same as leaving it out, and the difference is an
130
+ * escape hatch rather than a nicety: the host refuses a node whose CLASS says
131
+ * it cannot be parented (`unparentableChild`), and that refusal fires only
132
+ * where the descriptor is SILENT. A consumer whose own widget trips the
133
+ * structural oracle — a `present(parent)` method on a widget that really is a
134
+ * child — writes this arm and is placed by its parent again.
135
+ */
136
+ {
137
+ readonly kind: 'parented';
138
+ }
139
+ /**
140
+ * The node places ITSELF against its parent: `present(parent)` / `close()`.
141
+ *
142
+ * Three measurements shape the two method names, all on libadwaita 1.9.3:
143
+ *
144
+ * - `present` TAKES THE PARENT WIDGET (`adw_dialog_present` arity 1, against
145
+ * `gtk_window_present`'s 0). That is what makes this a portal rather than a
146
+ * toplevel: the node has a place in the shadow tree and a different place in
147
+ * the GTK tree, and the parent is what joins them.
148
+ * - `close` IS THE UNCONDITIONAL ONE, `force_close` and not `close`. An
149
+ * unmount is not a user request: with `can-close: false` (which is how
150
+ * `onRequestClose` is honoured one layer up) `adw_dialog_close()` returns
151
+ * FALSE, emits `close-attempt` and LEAVES THE DIALOG ON SCREEN — measured.
152
+ * `force_close()` closes it and emits `closed`.
153
+ * - AND `force_close` IS SAFE ON A NODE THAT WAS NEVER PRESENTED, where
154
+ * `close` is not: measured, `close()` on an unpresented dialog answers
155
+ * `Adwaita-CRITICAL **: Trying to close AdwDialog 0x… that's not presented`
156
+ * at exit 0, and `force_close()` is silent. So the host needs no
157
+ * "is it up?" probe before retracting one.
158
+ */
159
+ | {
160
+ readonly kind: 'portal';
161
+ readonly present: string;
162
+ readonly close: string;
163
+ }
164
+ /**
165
+ * The node IS a root: `present()` / `destroy()`, and no parent anywhere.
166
+ *
167
+ * WHAT SEPARATES IT FROM A PORTAL IS THE ARITY, and that is a fact about the
168
+ * two libraries rather than a convention: measured, `adw_dialog_present` takes
169
+ * 1 argument and `gtk_window_present` takes 0. A portal has two positions in
170
+ * the tree and the parent is what joins them; a toplevel has ONE, its own, so
171
+ * there is nothing to present it against. `descriptorProblems()` holds both
172
+ * arities, so the two arms cannot be swapped by a copy/paste.
173
+ *
174
+ * MEMBERSHIP IS `Gtk.Root`, measured with `GObject.type_is_a` and not read
175
+ * from documentation — `GtkWindow` and `AdwApplicationWindow` down to
176
+ * `GtkPrintUnixDialog`. It is GTK's own word for "this widget is a toplevel",
177
+ * which is why the generic code asks it instead of forming a second opinion,
178
+ * and `placement.spec.ts` is where the membership is walked and counted.
179
+ *
180
+ * `close: 'destroy'` IS THE FORCED ONE, the same choice the portal arm makes
181
+ * and for the same measured reason. On GTK 4.22.4: `gtk_window_close()` emits
182
+ * `close-request`, and a handler returning TRUE leaves the window mapped and
183
+ * visible — which is exactly how an application vetoes a user's close. An
184
+ * unmount is not a user request, so the conditional call is the wrong one;
185
+ * `gtk_window_destroy()` takes it down regardless. Both are silent on a window
186
+ * that was never presented (measured), so the host needs no "is it up?" probe.
187
+ *
188
+ * AND THE FORCED CALL IS TERMINAL HERE, which is where this arm stops being a
189
+ * copy of the portal one. MEASURED: `present()` after either `close()` or
190
+ * `destroy()` answers `Gtk-WARNING **: A window is shown after it has been
191
+ * destroyed. This will leave the window in an inconsistent state.` — GTK4's
192
+ * default `close-request` handler destroys the window, so `close()` is a
193
+ * conditional destroy rather than a hide. A retracted toplevel is therefore
194
+ * gone for good, and a re-mount is a fresh widget (which is what `rebuild`
195
+ * already does for a construct-only write).
196
+ */
197
+ | {
198
+ readonly kind: 'toplevel';
199
+ readonly present: string;
200
+ readonly close: string;
201
+ };
79
202
  export type PolicyKind = 'none' | 'single' | 'ordered' | 'indexed' | 'slotted' | 'keyed' | 'coords';
80
203
  /**
81
204
  * How a parent adopts children. GTK4 deleted `GtkContainer`, so there is no
@@ -110,12 +233,52 @@ export type ChildPolicy = {
110
233
  remove: string;
111
234
  reorder: 'native' | 'remove-all';
112
235
  }
113
- /** `Gtk.ListBox`/`Gtk.FlowBox`: index-addressed, and the parent addresses a WRAPPER row. */
236
+ /**
237
+ * `Gtk.ListBox`/`Gtk.FlowBox`: index-addressed, and the parent addresses a WRAPPER row.
238
+ *
239
+ * `perLineCap` names a property the host keeps EQUAL TO THE CHILD COUNT, and
240
+ * `Gtk.FlowBox:max-children-per-line` is the only one. It is a cap, so every
241
+ * value at or above the child count lays the children out identically — and
242
+ * that equivalence is exactly what hid the cost, because GTK MEASURES THE CAP
243
+ * RATHER THAN THE CHILDREN.
244
+ *
245
+ * MEASURED on GTK 4.22.4, one `Gtk.FlowBox` holding TWO children, per
246
+ * HEIGHT-FOR-WIDTH measure — `measure(VERTICAL, 400)`, which is the one a
247
+ * container in a window asks for:
248
+ *
249
+ * | cap | 7 | 64 | 1024 | 8192 | 32768 | 65535 |
250
+ * | --- | --- | --- | --- | --- | --- | --- |
251
+ * | ms | 0.021 | 0.025 | 0.43 | 18.6 | 391.5 | 1393.2 |
252
+ *
253
+ * Quadratic in the cap, so "as many as fit" spelled as G_MAXUINT cost a second
254
+ * and a half to measure two chips, and a real application froze its main loop
255
+ * for 12.7 s at startup over five two-child rows. The ORIENTATION is part of
256
+ * the claim and not a detail: `measure(HORIZONTAL, -1)` stays under 0.1 ms at
257
+ * every one of those caps, so a re-measurement that asks for the width alone
258
+ * reproduces none of this.
259
+ *
260
+ * The cap is wrong in the ANSWER too, not only in what the answer costs.
261
+ * Natural width is `content + column-spacing × (cap - 1)` — measured, exactly,
262
+ * over five caps — so a cap above the child count `n` carries
263
+ * `column-spacing × (cap - n)` px of gaps that do not exist. 12 children at
264
+ * `column-spacing: 8`: 683 px natural at a cap of 12 and 524 867 px at 65535,
265
+ * the excess exactly 8 × 65 523. At `column-spacing: 0` there is no difference
266
+ * at all, which is how a pinned cap passed for as long as it did.
267
+ *
268
+ * The child count is the exact answer rather than a smaller cap chosen to be
269
+ * safe: a line can never hold more children than exist, so it forbids nothing
270
+ * G_MAXUINT allowed, and it is the largest value that costs nothing.
271
+ *
272
+ * An AUTHORED value wins. `<GtkFlowBox maxChildrenPerLine={3}>` is someone
273
+ * asking for three per line, and a sync that overwrote it would be the same
274
+ * silent kind of wrong this field exists to remove.
275
+ */
114
276
  | {
115
277
  kind: 'indexed';
116
278
  insert: string;
117
279
  remove: string;
118
280
  wrap: 'list-box-row' | 'flow-box-child' | null;
281
+ perLineCap?: string;
119
282
  }
120
283
  /**
121
284
  * `Adw.HeaderBar`, `Adw.ToolbarView`, `Adw.ActionRow`: named attachment points.
@@ -261,6 +424,19 @@ export interface WidgetDescriptor {
261
424
  /** Lazy so `gi://` loads late and an unused descriptor costs nothing. */
262
425
  readonly ctor: () => GObject.ObjectClass & (new (props?: Record<string, unknown>) => GObject.Object);
263
426
  readonly children: ChildPolicy;
427
+ /**
428
+ * How this node reaches the screen. Absent means `{ kind: 'parented' }`.
429
+ *
430
+ * Optional rather than required, and the reason is the shape of the table
431
+ * rather than convenience: the generated half is the whole GIR widget set and
432
+ * not one row of it is a portal, so a required field would be ~900 identical
433
+ * lines. Nothing is left implicit by that — `placementOf()` normalises the
434
+ * absence in ONE place and every switch over the result has a `never` arm, so
435
+ * a third placement kind cannot be added without every reader failing to
436
+ * compile. `descriptorProblems()` holds the declared methods against the
437
+ * installed class, exactly as it does for `children`.
438
+ */
439
+ readonly placement?: NodePlacement;
264
440
  /**
265
441
  * Where a text child goes. Absent means text under this widget is an ERROR
266
442
  * that names the tag — never a silent drop.
@@ -33,11 +33,11 @@
33
33
  * resolve goes unchecked.
34
34
  */
35
35
  import type { DefineComponent } from '@vue/runtime-core';
36
- import type { VueAttributes, WithOnce } from './attrs.js';
36
+ import type { VueAttributes, WithOnce, WithPortableValues } from './attrs.js';
37
37
  import type { WidgetPropsByGType, WidgetPropsVueAliases } from './generated/props.js';
38
38
  type VueWidgetProps = WidgetPropsByGType & WidgetPropsVueAliases;
39
39
  export type GtkGlobalComponents = {
40
- [K in keyof VueWidgetProps]: DefineComponent<WithOnce<VueWidgetProps[K]> & VueAttributes>;
40
+ [K in keyof VueWidgetProps]: DefineComponent<WithOnce<WithPortableValues<VueWidgetProps[K]>> & VueAttributes>;
41
41
  };
42
42
  declare module '@vue/runtime-core' {
43
43
  interface GlobalComponents extends GtkGlobalComponents {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/gtk-host",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "Framework-agnostic GTK4/Adwaita host: the element model UI-framework renderers bind to",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -80,7 +80,9 @@
80
80
  "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
81
81
  "test:gjs": "gjsify run test.gjs.mjs",
82
82
  "test:gjs-on-node": "gjsify run build:gjsify && gjsify run build:test:node && node dist/test.node.mjs",
83
- "generate": "gjsify build src/generator/main.mts --app gjs --outfile tmp/generate.gjs.mjs && gjsify run tmp/generate.gjs.mjs -- --out src/generated && gjsify format src/generated"
83
+ "generate": "gjsify build src/generator/main.mts --app gjs --outfile tmp/generate.gjs.mjs && gjsify run tmp/generate.gjs.mjs -- --out src/generated && gjsify format src/generated",
84
+ "generate:enum-values": "gjs -m ../../../scripts/generate-enum-values.mjs",
85
+ "generate:methods": "gjs -m ../../../scripts/generate-widget-methods.mjs"
84
86
  },
85
87
  "keywords": [
86
88
  "gjs",
@@ -98,7 +100,8 @@
98
100
  "@girs/gobject-2.0": "4.6.0",
99
101
  "@girs/gtk-4.0": "4.6.0",
100
102
  "@girs/pango-1.0": "4.6.0",
101
- "@girs/pangocairo-1.0": "4.6.0"
103
+ "@girs/pangocairo-1.0": "4.6.0",
104
+ "@gjsify/adwaita-core": "^0.49.0"
102
105
  },
103
106
  "peerDependencies": {
104
107
  "@vue/runtime-core": "^3.5.0",
@@ -121,10 +124,10 @@
121
124
  }
122
125
  },
123
126
  "devDependencies": {
124
- "@gjsify/cli": "^0.47.0",
127
+ "@gjsify/cli": "^0.49.0",
125
128
  "@vue/runtime-core": "^3.5.0",
126
129
  "solid-js": "^1.9.0",
127
- "@gjsify/unit": "^0.47.0",
130
+ "@gjsify/unit": "^0.49.0",
128
131
  "@types/node": "^25.9.2",
129
132
  "typescript": "^6.0.3",
130
133
  "@gjsify/node-gi": "file:../../node-gi/node-gi",