@gjsify/gtk-host 0.41.0 → 0.43.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 (61) hide show
  1. package/README.md +454 -6
  2. package/lib/esm/adapters/react.js +1 -0
  3. package/lib/esm/adapters/solid.js +1 -0
  4. package/lib/esm/adapters/vue.js +1 -0
  5. package/lib/esm/attrs.js +0 -0
  6. package/lib/esm/conformance/diagnostics.js +2 -2
  7. package/lib/esm/conformance/index.js +2 -2
  8. package/lib/esm/descriptors/index.js +1 -1
  9. package/lib/esm/errors.js +1 -1
  10. package/lib/esm/generated/props.js +0 -0
  11. package/lib/esm/generated/widgets.js +1 -0
  12. package/lib/esm/host.js +1 -1
  13. package/lib/esm/index.js +1 -1
  14. package/lib/esm/jsx-runtime.js +1 -0
  15. package/lib/esm/policies.js +1 -1
  16. package/lib/esm/probe.js +1 -0
  17. package/lib/esm/props.js +1 -1
  18. package/lib/esm/react-jsx-runtime.js +1 -0
  19. package/lib/esm/registry.js +1 -1
  20. package/lib/esm/signals.js +1 -1
  21. package/lib/esm/tags.js +2 -0
  22. package/lib/esm/vue-components.js +0 -0
  23. package/lib/types/adapters/react.d.ts +175 -0
  24. package/lib/types/adapters/react.spec.d.ts +2 -0
  25. package/lib/types/adapters/solid.d.ts +78 -0
  26. package/lib/types/adapters/solid.spec.d.ts +2 -0
  27. package/lib/types/adapters/vue.d.ts +24 -0
  28. package/lib/types/adapters/vue.spec.d.ts +2 -0
  29. package/lib/types/attrs.d.ts +102 -0
  30. package/lib/types/buildable.spec.d.ts +2 -0
  31. package/lib/types/conformance/diagnostics.d.ts +14 -0
  32. package/lib/types/conformance/index.d.ts +24 -1
  33. package/lib/types/conformance/vectors.d.mts +34 -0
  34. package/lib/types/descriptors/index.d.ts +52 -1
  35. package/lib/types/errors.d.ts +21 -0
  36. package/lib/types/generated/props.d.ts +5505 -0
  37. package/lib/types/generated/surface-data.d.mts +22 -0
  38. package/lib/types/generated/widgets.d.ts +3 -0
  39. package/lib/types/generated.spec.d.ts +4 -0
  40. package/lib/types/generator/emit-types.d.mts +54 -0
  41. package/lib/types/generator/emit.d.mts +50 -0
  42. package/lib/types/generator/gir.d.mts +156 -0
  43. package/lib/types/generator/main.d.mts +23 -0
  44. package/lib/types/generator/mini.fixture.d.mts +30 -0
  45. package/lib/types/generator/surface.d.mts +62 -0
  46. package/lib/types/generator/tsmap.d.mts +73 -0
  47. package/lib/types/generator.spec.d.ts +2 -0
  48. package/lib/types/host.d.ts +102 -1
  49. package/lib/types/index.d.ts +6 -3
  50. package/lib/types/jsx-runtime.d.ts +70 -0
  51. package/lib/types/policies.d.ts +58 -0
  52. package/lib/types/probe.d.ts +58 -0
  53. package/lib/types/props.d.ts +49 -2
  54. package/lib/types/react-jsx-runtime.d.ts +108 -0
  55. package/lib/types/registry.d.ts +1 -1
  56. package/lib/types/signals.d.ts +16 -1
  57. package/lib/types/tags.d.ts +25 -0
  58. package/lib/types/testing/gate.d.mts +2 -0
  59. package/lib/types/types.d.ts +56 -3
  60. package/lib/types/vue-components.d.ts +46 -0
  61. package/package.json +77 -7
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The JSX surface — our OWN jsx-runtime, and the measurement that made that the
3
+ * only option.
4
+ *
5
+ * The obvious alternative is to augment Solid's namespace:
6
+ * `declare module 'solid-js' { namespace JSX { interface IntrinsicElements … } }`.
7
+ * Measured, that leaves all 208 tags Solid pre-declares (113 HTML + 4 deprecated
8
+ * HTML + 59 SVG + 32 MathML) valid on a GTK renderer: `<div/>` and `<circle/>`
9
+ * type-check clean and then render NOTHING, silently. Closing them needs 208
10
+ * `never` overrides and produces `Type '{}' is not assignable to type 'never'`
11
+ * instead of "no such element". With our own runtime, `<div/>` is
12
+ * `TS2339: Property 'div' does not exist on type 'JSX.IntrinsicElements'`.
13
+ *
14
+ * TWO CONFIGURATION FACTS A CONSUMER MUST KNOW, both measured:
15
+ *
16
+ * - `jsxImportSource` must point here, and `noImplicitAny` must be ON. With
17
+ * `jsx: "preserve"`, no `jsxImportSource` and `noImplicitAny` off, every JSX
18
+ * element is implicitly `any` and `tsc` exits 0 having checked NOTHING — the one
19
+ * configuration in which this whole surface evaporates in silence.
20
+ * - `jsx: "react"` is refused outright (TS5089); `preserve` (what
21
+ * babel-preset-solid needs) and `react-jsx` both work.
22
+ *
23
+ * AND ONE HOLE THAT CANNOT BE CLOSED: TypeScript exempts every HYPHENATED JSX
24
+ * attribute from excess-property checking, so `<gtk-box no-such={1}/>` is accepted
25
+ * — on intrinsics and on components alike. Three index-signature shapes were tried
26
+ * (`\`${string}-${string}\`` mapped to `unknown`, to `never`, and to a subtracted
27
+ * union); all three either changed nothing or collided with the declared kebab
28
+ * keys (TS2411). The camelCase spelling of every property IS checked, which is why
29
+ * both spellings are generated and camelCase is the one to prefer.
30
+ */
31
+ import type { JsxAttributes, WithOnce } from './attrs.js';
32
+ import type { HostNode } from './types.js';
33
+ import type { WidgetClassByTag, WidgetPropsByTag } from './generated/props.js';
34
+ /** Every GTK/Adwaita tag, with its properties, its handlers and its `ref`. */
35
+ export type GtkIntrinsicElements = {
36
+ [K in keyof WidgetPropsByTag]: WithOnce<WidgetPropsByTag[K]> & JsxAttributes<WidgetClassByTag[K]>;
37
+ };
38
+ export declare namespace JSX {
39
+ /**
40
+ * The type of a JSX EXPRESSION — a host node, not `ElementChild`.
41
+ *
42
+ * It was `ElementChild` (`HostNode | ElementChildren | string | number |
43
+ * boolean | null | undefined`), which reads like the right answer because that
44
+ * is what may be PASSED as a child. It is not what a JSX element evaluates
45
+ * TO: the compiler's `createElement` returns a node, always.
46
+ *
47
+ * The imprecision was not cosmetic. `<For>`, `<Index>` and `<Show>` are typed
48
+ * in `adapters/solid.ts` against `HostNode` — deliberately, because Solid's own
49
+ * signatures pin `JSX.Element` to the DOM's `Element` — so with `Element` this
50
+ * wide the FIRST `<For>` in any application failed with
51
+ * `Type 'ElementChild' is not assignable to type 'HostNode'`, and the whole
52
+ * control-flow surface was untypeable. Found by the first real JSX app
53
+ * (`showcases/gtk/solid-host-counter`); the fixtures in `type-tests/jsx/` had
54
+ * never used a control-flow component, which is why they went green over it.
55
+ * `type-tests/jsx/positive-element-is-a-node.tsx` is that gap closed.
56
+ *
57
+ * Children stay wide: they are typed by `JsxAttributes.children`, not by this.
58
+ */
59
+ type Element = HostNode;
60
+ /** Names the prop children arrive on — without it, nesting is an error. */
61
+ interface ElementChildrenAttribute {
62
+ children: Record<never, never>;
63
+ }
64
+ interface IntrinsicElements extends GtkIntrinsicElements {
65
+ }
66
+ }
67
+ export declare function jsx(): never;
68
+ export declare function jsxs(): never;
69
+ export declare function jsxDEV(): never;
70
+ export declare function Fragment(): never;
@@ -11,6 +11,64 @@ export declare function addressOf(el: HostElement): Gtk.Widget;
11
11
  * activation from the one the author configured.
12
12
  */
13
13
  export declare function makeWrapper(policy: ChildPolicy, child: Gtk.Widget): Gtk.Widget | null;
14
+ /**
15
+ * A container the host owns that no window shows — the DOM's detached `<div>`.
16
+ *
17
+ * `<KeepAlive>` and `<Suspense>` ask a renderer for off-screen storage:
18
+ * `KeepAliveImpl.setup` opens with `createElement("div")` and `SuspenseImpl` does
19
+ * the same for its `hiddenContainer`. The Vue adapter answered that with its own
20
+ * `gi://Gtk` import and a literal `new Gtk.Box()` — the ONE runtime toolkit import
21
+ * and the ONE concrete widget class in any adapter, i.e. exactly the widget
22
+ * knowledge ADR 0027 § 7 forbids one. It lives here because this is already the
23
+ * file that builds widgets the author did not write (see `makeWrapper`), and it is
24
+ * the only one with a runtime `gi://Gtk` import.
25
+ *
26
+ * A `Gtk.Box` and not an `Adw.Bin`: the deactivated subtree may hold SEVERAL
27
+ * children, and a one-child container would silently keep the last.
28
+ */
29
+ export declare const makeDetachedContainer: () => Gtk.Widget;
30
+ /**
31
+ * The ONE-CHILD setter this child's placement goes through, or null.
32
+ *
33
+ * `single` always is one (`set_child`, `set_content`); a `slotted` slot is one
34
+ * when its method is a setter (`set_content`, `set_title_widget`) rather than an
35
+ * adder. Such a slot REPLACES — and it does so silently, which is why four call
36
+ * sites need the same question answered. Two of them are about an ELEMENT
37
+ * arriving; the other two are about TEXT, because GTK's text sink is the SAME
38
+ * slot: measured on gtk 4.22, `button.set_child(w)` followed by
39
+ * `set_property('label', …)` leaves `w.get_parent() === null`, and `set_child`
40
+ * after a `label` write leaves `label === null`. One widget, one slot, two APIs.
41
+ */
42
+ /**
43
+ * Compile-time exhaustiveness, which this package does NOT get for free.
44
+ *
45
+ * `tsconfig.json` sets `strict: false`, so a switch that stops covering its union
46
+ * simply falls through and returns `undefined` — no error. Adding the `uncurated`
47
+ * policy kind passed `tsc` cleanly while five switches silently ignored it, which
48
+ * is exactly the class of green-and-wrong this package exists to prevent
49
+ * elsewhere. Assignability to `never` is not a strictness option, so a `default`
50
+ * arm calling this DOES fail the build (measured) and is the only mechanism that
51
+ * makes the next union member impossible to forget.
52
+ */
53
+ export declare function unhandledPolicy(policy: never): never;
54
+ export declare function setterSlotOf(parent: HostElement, child: HostElement): string | null;
55
+ /** Every one-child slot this policy has, by setter name — `single` has exactly one. */
56
+ export declare function setterSlots(policy: ChildPolicy): string[];
57
+ /**
58
+ * Who GTK says is in a one-child slot. `undefined` means there is no getter to ask.
59
+ *
60
+ * The slot's own getter is the ONLY honest reader of this, and a child-list walk
61
+ * is not a substitute: measured on gtk 4.22.4 / libadwaita 1.9.3, a FRESH widget
62
+ * already has direct children the application never put there —
63
+ * `Gtk.ScrolledWindow` two `GtkScrollbar`s, `Adw.ToolbarView` two
64
+ * `GtkRevealer`s, `Adw.Window` an `AdwDialogHost` + an `AdwGizmo`,
65
+ * `Adw.StatusPage` a `GtkScrolledWindow` — while every one of those widgets
66
+ * answers `null` from its getter. The getter also survives GTK wrapping the
67
+ * child: `Gtk.ScrolledWindow.set_child(label)` reports a `GtkViewport`, not the
68
+ * label, so callers may compare occupants for IDENTITY but never assume the
69
+ * occupant is the widget they handed over.
70
+ */
71
+ export declare function slotOccupant(widget: Gtk.Widget, setter: string): Gtk.Widget | null | undefined;
14
72
  export interface Placement {
15
73
  parent: HostElement;
16
74
  child: HostElement;
@@ -0,0 +1,58 @@
1
+ import Adw from 'gi://Adw?version=1';
2
+ /** Record one assertion. A `false` is a finding; nothing else is. */
3
+ export type ProbeCheck = (what: string, ok: boolean) => void;
4
+ /** What a showcase gives the harness. */
5
+ export interface HostProbe<T> {
6
+ /**
7
+ * Build the UI. `app` is `null` on the headless path, where there is no
8
+ * `GApplication` and the window is never presented.
9
+ */
10
+ build(app: Adw.Application | null): T;
11
+ /**
12
+ * Assert against the REAL widget tree — `get_first_child()`/`get_next_sibling()`
13
+ * and the exact getters, never the host's own bookkeeping, which would agree
14
+ * with itself while the window is wrong. Return whatever belongs in the result
15
+ * line; the harness adds the failure list and the diagnostic count.
16
+ */
17
+ assert(ui: T, check: ProbeCheck): Record<string, unknown>;
18
+ /** Prefix of the machine-readable result line. Default `PROBE`. */
19
+ label?: string;
20
+ }
21
+ /** A showcase that also has a GUI path — i.e. every one of them. */
22
+ export interface HostProbeApp<T> extends HostProbe<T> {
23
+ /** GApplication id for the GUI path. */
24
+ applicationId: string;
25
+ /** Present the toplevel. Reached only after the assertions have passed. */
26
+ present(ui: T): void;
27
+ }
28
+ /**
29
+ * Truthy-env check, matching `@gjsify/devtools`' `GJSIFY_DEVTOOLS` gate semantics
30
+ * and `@gjsify/storybook`'s `probeEnabled` — one spelling of "opt in" across the
31
+ * repo, so `=0` and `=false` mean off rather than "any value is on".
32
+ */
33
+ export declare function probeEnabled(): boolean;
34
+ /**
35
+ * Run the showcase's assertions once and return the process exit code.
36
+ *
37
+ * The diagnostics gate is reset first and asserted last, and both halves are the
38
+ * harness's rather than the showcase's. Reset, because in the GUI path this runs
39
+ * from `activate` — AFTER Adw startup, where a session-bus, portal, theme or a11y
40
+ * warning is routine in a container, and counting those would fail the showcase
41
+ * with the host's name on a diagnostic it did not cause. Asserted, because GTK's
42
+ * failure mode is exit 0: a mis-parented widget floods `Gtk-WARNING` and the
43
+ * process still succeeds.
44
+ */
45
+ export declare function runHostProbe<T>(probe: HostProbe<T>, app?: Adw.Application | null): number;
46
+ /**
47
+ * The whole `main` of a gtk-host showcase.
48
+ *
49
+ * `GJSIFY_HOST_PROBE` truthy: assert headlessly and exit, no window and no main
50
+ * loop. Otherwise: run the SAME assertions from `activate` before the window is
51
+ * shown, exit non-zero if any of them failed, then present. The GUI path carrying
52
+ * the assertions is the load-bearing half — `scripts/showcase-smoke.mjs` launches
53
+ * the app and waits, so without it the CI leg proves only that the process started.
54
+ *
55
+ * `runAsync`, never the sync `run()`: a sync `run()` leaves promise jobs undrained
56
+ * and hangs a view load on its spinner (the class `@gjsify/adwaita-app` documents).
57
+ */
58
+ export declare function runHostProbeApp<T>(probe: HostProbeApp<T>): Promise<void>;
@@ -1,12 +1,38 @@
1
1
  import GObject from 'gi://GObject';
2
+ import type { WidgetDescriptor } from './types.js';
2
3
  /** `backgroundColor` and `background-color` both name the GObject property `background-color`. */
3
4
  export declare function toPropertyName(name: string): string;
5
+ /**
6
+ * A props object without the keys a framework reserves for itself.
7
+ *
8
+ * The SET is per-framework and stays with the adapter that owns it — Vue's is
9
+ * `@vue/shared`'s `isReservedProp`, React's is `children` — but the LOOP was
10
+ * written twice, byte-for-byte apart from the identifier names. That is the second
11
+ * copy this repo lifts on: the two would drift, and the drifted one fails in a
12
+ * consumer while the host stays green.
13
+ *
14
+ * `undefined` rather than `{}` for an empty result, and that is load-bearing:
15
+ * `createElement(tag, undefined)` skips the property loop entirely, so a vnode
16
+ * whose props are ALL reserved must not look like a vnode with one unknown
17
+ * property.
18
+ */
19
+ export declare function withoutKeys(props: Record<string, unknown> | null | undefined, skip: ReadonlySet<string>): Record<string, unknown> | undefined;
4
20
  /** All ParamSpecs of a class, by kebab name. Cached per GType — `list_properties()` is not cheap. */
5
21
  export declare function paramSpecs(klass: GObject.ObjectClass, gtypeName: string): Map<string, GObject.ParamSpec>;
6
22
  export declare const isWritable: (spec: GObject.ParamSpec) => boolean;
7
23
  export declare const isConstructOnly: (spec: GObject.ParamSpec) => boolean;
8
24
  /** Construct-only property names of a class, in declaration order. */
9
25
  export declare function constructOnlyNames(klass: GObject.ObjectClass, gtypeName: string): string[];
26
+ /**
27
+ * The value a nick names on an enum GType, or undefined if it names none.
28
+ *
29
+ * Exported for the generated surface's own check: the type surface offers a union
30
+ * of nicks per enum, and a nick this host cannot resolve would type-check and then
31
+ * be refused at runtime. `generated.spec.ts` resolves every emitted nick through
32
+ * this function, so the GIR-member-to-nick spelling is measured rather than
33
+ * assumed. Also the primitive a renderer needs for a "did you mean" diagnostic.
34
+ */
35
+ export declare const lookupEnumNick: (gtypeName: string, nick: string) => number | undefined;
10
36
  /**
11
37
  * Turn an authored value into one GObject will actually store.
12
38
  *
@@ -22,8 +48,29 @@ export declare function coerce(spec: GObject.ParamSpec, value: unknown, tag: str
22
48
  *
23
49
  * React hands `undefined` for a prop that disappeared, and GObject cannot store
24
50
  * that: `set_property(name, undefined)` throws "Could not guess unspecified
25
- * GValue type" (measured). The ParamSpec knows the right answer.
51
+ * GValue type" (measured). What "removed" means is the value the widget would
52
+ * have carried had the prop never been authored — and that is what CONSTRUCTION
53
+ * leaves behind, not what the ParamSpec declares.
54
+ *
55
+ * The two disagree far too often to treat the ParamSpec as an approximation.
56
+ * Measured on gjs 1.88.1 / GTK 4.22.4 / Adw 1.10 across the 26 shipped
57
+ * descriptors: 953 scalar properties are both readable and writable, and
58
+ * **104 of them disagree** — 78 once construct-only `css-name` is set aside.
59
+ * Twelve property names carry it, four of them behavioural:
60
+ *
61
+ * GtkWindow.visible spec=true constructed=false
62
+ * AdwActionRow.activatable spec=true constructed=false
63
+ * GtkToggleButton.receives-default spec=false constructed=true
64
+ * GtkListBox.focusable spec=false constructed=true (7 types)
65
+ *
66
+ * So `<AdwActionRow activatable={cond}>` with `cond` going `undefined` made a
67
+ * non-activatable row activatable, and removing `visible` SHOWED a window —
68
+ * silently, at exit 0, in the package that exists to refuse exactly that.
69
+ *
70
+ * `name` is kept as the probe reports it rather than as the ParamSpec's `null`:
71
+ * `gtk_widget_get_name` falls back to the type name when none was set, so the
72
+ * probe's answer is the one CSS `#id` matching already sees.
26
73
  */
27
- export declare function defaultValue(spec: GObject.ParamSpec): unknown;
74
+ export declare function removedValue(descriptor: WidgetDescriptor, spec: GObject.ParamSpec): unknown;
28
75
  /** Look a property up, refusing the two silent failures: unknown and read-only. */
29
76
  export declare function requireSpec(specs: Map<string, GObject.ParamSpec>, tag: string, name: string): GObject.ParamSpec;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The React JSX surface: React's OWN automatic runtime, our OWN element list.
3
+ *
4
+ * `./jsx-runtime` in this package deliberately THROWS from `jsx()`/`jsxs()`, and
5
+ * that refusal is correct for the dialects it serves — Solid's reactivity lives in
6
+ * what `babel-preset-solid` emits from `jsx: "preserve"`, and Vue's in its SFC
7
+ * compiler, so an automatic runtime reaching those functions means the pipeline was
8
+ * misconfigured and nothing would render. React is the opposite case and the
9
+ * difference is structural rather than a matter of taste: React's `jsx()` builds a
10
+ * plain, renderer-agnostic React ELEMENT (a `{type, props, key}` record), and the
11
+ * host mapping happens later, in `react-reconciler`. So the automatic runtime is
12
+ * exactly the right thing here, and it is React's own — reimplementing it would be
13
+ * a second `createElement` to keep in step with React's element format.
14
+ *
15
+ * That is why this is a SEPARATE subpath instead of a change to `./jsx-runtime`:
16
+ * one module cannot both refuse the automatic runtime (for Solid) and provide it
17
+ * (for React). A consumer picks by pointing `jsxImportSource` at one or the other,
18
+ * and TypeScript appends `/jsx-runtime` (`/jsx-dev-runtime` under
19
+ * `jsx: "react-jsxdev"`) — hence two export keys beside `./react`. The tsconfig
20
+ * recipe is in the package README.
21
+ *
22
+ * WHY NOT just `jsxImportSource: "react"`. Then the element list comes from
23
+ * `@types/react`'s own `JSX.IntrinsicElements`, which is the 208-tag problem ADR
24
+ * 0028 § 8 measured on Solid, one package over: every HTML, SVG and MathML tag
25
+ * type-checks clean on a GTK renderer and then renders NOTHING. Declaring the
26
+ * namespace here is what makes `<div/>` a `TS2339` naming the element.
27
+ *
28
+ * The element list itself is the generated `WidgetPropsByTag`/`WidgetClassByTag`,
29
+ * which `scripts/check-type-surfaces.mjs` already holds negative-first through its
30
+ * `jsx` half. The React-specific plumbing below has no half yet — that gap, and
31
+ * why adding one is not a one-liner, is in `status/open-todos.md`. Its RUNTIME
32
+ * half is `adapters/react.spec.ts`.
33
+ */
34
+ import type { Key, ReactElement, ReactNode, Ref } from 'react';
35
+ import type { WidgetClassByTag, WidgetPropsByTag } from './generated/props.js';
36
+ import type { RawSignalAttributes, SlotAttribute, WithOnce } from './attrs.js';
37
+ export { Fragment } from 'react/jsx-runtime';
38
+ /**
39
+ * React's own factories, RE-TYPED over this package's element list.
40
+ *
41
+ * The functions are React's — identity matters, because they build React's
42
+ * element format and a reimplementation would be a second one to keep in step.
43
+ * Only the SIGNATURE is ours: `@types/react` types `type` as React's
44
+ * `ElementType`, i.e. every HTML/SVG/MathML tag, so a direct call with a GTK tag
45
+ * would be a type error while a JSX tag (checked against `JSX.IntrinsicElements`
46
+ * below) would not. The cast is what makes the two agree.
47
+ */
48
+ export declare const jsx: GtkJsxFactory;
49
+ export declare const jsxs: GtkJsxFactory;
50
+ export declare const jsxDEV: GtkJsxDevFactory;
51
+ /** What may stand in a JSX tag position here: a GTK tag or a component. */
52
+ export type GtkElementType = keyof GtkReactIntrinsicElements | ((props: never) => ReactNode);
53
+ type GtkJsxFactory = (type: GtkElementType, props: unknown, key?: Key) => ReactElement;
54
+ type GtkJsxDevFactory = (type: GtkElementType, props: unknown, key: Key | undefined, isStatic: boolean, source?: unknown, self?: unknown) => ReactElement;
55
+ /**
56
+ * Per-element attributes React needs on top of the GObject properties.
57
+ *
58
+ * Deliberately not `JsxAttributes` from `./attrs.js`: that one types `ref` as
59
+ * `T | ((el: T) => void)`, which is Solid's spelling. React's `ref` additionally
60
+ * accepts a `useRef` object, so a shared type would reject the single most common
61
+ * way a React author holds a widget.
62
+ *
63
+ * `ref`, `children` and `key` are declared per element rather than left to
64
+ * `JSX.IntrinsicAttributes`, which TypeScript unions into the attributes of a
65
+ * COMPONENT and not of an intrinsic element — measured in ADR 0028 § 8: an
66
+ * undeclared `ref` is TS2322 and an undeclared `children` makes every nested
67
+ * element TS2559. `children` must also be optional, or a self-closing tag is
68
+ * TS2741.
69
+ */
70
+ export interface ReactWidgetAttributes<T> extends SlotAttribute, RawSignalAttributes {
71
+ children?: ReactNode;
72
+ ref?: Ref<T> | undefined;
73
+ key?: Key | null | undefined;
74
+ }
75
+ /** Every GTK/Adwaita tag, with its properties, its handlers, its `ref` and its `key`. */
76
+ export type GtkReactIntrinsicElements = {
77
+ [K in keyof WidgetPropsByTag]: WithOnce<WidgetPropsByTag[K]> & ReactWidgetAttributes<WidgetClassByTag[K]>;
78
+ };
79
+ export declare namespace JSX {
80
+ /**
81
+ * DELIBERATELY NOT `HostNode`, and not to be made "consistent" with the Solid
82
+ * surface next door.
83
+ *
84
+ * `jsx-runtime.ts` declares `Element = HostNode`, because Solid's control-flow
85
+ * components (`<For>`, `<Index>`, `<Show>`) are typed against host nodes and the
86
+ * first `<For>` in an application was otherwise a type error. React's is
87
+ * `ReactElement` because that is what a React JSX expression evaluates to: `jsx()`
88
+ * builds a renderer-agnostic element record and the host mapping happens later, in
89
+ * the reconciler. React's control flow is plain JavaScript, not typed components,
90
+ * so nothing here needs a host node in that position.
91
+ *
92
+ * The two files therefore answer two different questions and will keep disagreeing
93
+ * on this line. That is the design, which is why it is written down at the line
94
+ * rather than left to be rediscovered by whoever reaches for the one-line fix.
95
+ */
96
+ type Element = ReactElement;
97
+ type ElementType = GtkElementType;
98
+ /** Names the prop children arrive on — without it, nesting is an error. */
99
+ interface ElementChildrenAttribute {
100
+ children: Record<never, never>;
101
+ }
102
+ /** What React itself adds to every element, intrinsic or component. */
103
+ interface IntrinsicAttributes {
104
+ key?: Key | null | undefined;
105
+ }
106
+ interface IntrinsicElements extends GtkReactIntrinsicElements {
107
+ }
108
+ }
@@ -4,7 +4,7 @@ export declare function registerWidget(descriptor: WidgetDescriptor): void;
4
4
  export declare function registerWidgets(descriptors: readonly WidgetDescriptor[]): void;
5
5
  export declare function lookupWidget(tag: string): WidgetDescriptor;
6
6
  export declare const hasWidget: (tag: string) => boolean;
7
- /** Every registered tag — the conformance suite walks this, so coverage is data. */
7
+ /** Every registered GType name — the conformance suite walks this, so coverage is data. */
8
8
  export declare const registeredTags: () => string[];
9
9
  /**
10
10
  * The nearest registered ancestor of a GType, most specific first.
@@ -1,6 +1,21 @@
1
1
  import type { HostElement } from './types.js';
2
+ export interface EventBinding {
3
+ /** The GObject signal name, modifiers removed. */
4
+ signal: string;
5
+ /** `.once`: disconnect after the first emission that actually reaches the callback. */
6
+ once: boolean;
7
+ }
8
+ /**
9
+ * Split an event prop into the signal GObject knows and the options it does not.
10
+ *
11
+ * Both escape hatches come FIRST and are verbatim: an `eventAliases` entry and
12
+ * the `on:<raw-signal-name>` spelling are the way to bind a signal that really
13
+ * does end in `-once` or `-capture`, exactly as they are the way to bind any
14
+ * other irregular name.
15
+ */
16
+ export declare function parseEventProp(prop: string, aliases?: Readonly<Record<string, string>>): EventBinding;
2
17
  /** `onRowActivated` -> `row-activated`; `onNotifyVisible` -> `notify::visible`. */
3
- export declare function toSignalName(prop: string, aliases?: Readonly<Record<string, string>>): string;
18
+ export declare const toSignalName: (prop: string, aliases?: Readonly<Record<string, string>>) => string;
4
19
  /** An event prop is `on:` + a raw signal name, or `on` + an uppercase letter. */
5
20
  export declare function isEventProp(prop: string): boolean;
6
21
  export declare const beginHostWrite: () => void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * GType name <-> kebab tag, and the map must be INJECTIVE.
3
+ *
4
+ * Two spellings exist because two dialects insist on different ones, and the
5
+ * difference is measured rather than chosen (ADR 0028 § 7):
6
+ *
7
+ * - JSX intrinsic elements MUST be kebab. A capitalized `JSX.IntrinsicElements`
8
+ * key is never consulted — `<GtkBox/>` is `TS2304: Cannot find name 'GtkBox'`,
9
+ * because TypeScript reads a capitalized JSX name as a value reference.
10
+ * - A Vue `GlobalComponents` key is the GTYPE name. Volar resolves a kebab tag
11
+ * to EITHER spelling but a Pascal tag only to a Pascal key, so one key covers
12
+ * `<gtk-box>` and `<GtkBox>` both.
13
+ *
14
+ * The GType name therefore stays the table key, the GtkBuilder XML key and the
15
+ * Vue key; kebab is the JSX spelling and an accepted registry alias.
16
+ */
17
+ /** `GtkGLArea` -> `gtk-gl-area`, `AdwSpinRow` -> `adw-spin-row`, `GtkATContext` -> `gtk-at-context`. */
18
+ export declare function tagOf(gtype: string): string;
19
+ /**
20
+ * Refuse a table whose tags are not unique.
21
+ *
22
+ * A collision would make one of the two widgets unreachable by tag, silently —
23
+ * so it is an error at generation time, never a warning in a log nobody reads.
24
+ */
25
+ export declare function assertInjective(gtypes: readonly string[]): void;
@@ -0,0 +1,2 @@
1
+ import type { DiagnosticsGate } from '../conformance/diagnostics.js';
2
+ export declare const gated: (gate: DiagnosticsGate, name: string, body: () => Promise<void>) => Promise<void>;
@@ -63,14 +63,31 @@ export interface HostElement extends HostNodeBase {
63
63
  * criticals per replay, at exit 0.
64
64
  */
65
65
  attached: boolean;
66
+ /** True once `destroy` has torn this element down. It never comes back. */
67
+ destroyed: boolean;
68
+ /**
69
+ * Children this element already had before the host adopted it.
70
+ *
71
+ * Only an ADOPTED root has any: a renderer mounts into a container the
72
+ * application built, and those children are in the GTK tree while being absent
73
+ * from the shadow tree. Placement offsets past them, or the first insertion
74
+ * computes "first" and the whole rendered tree lands ABOVE the app's own chrome.
75
+ */
76
+ foreign: readonly Gtk.Widget[];
66
77
  }
67
78
  export type HostNode = HostElement | HostText | HostAnchor;
68
79
  export type PolicyKind = 'none' | 'single' | 'ordered' | 'indexed' | 'slotted' | 'keyed' | 'coords';
69
80
  /**
70
81
  * How a parent adopts children. GTK4 deleted `GtkContainer`, so there is no
71
- * generic `add` — and `Gtk.Buildable.add_child` is introspected as a vfunc only
72
- * (`typeof headerBar.add_child === 'undefined'`, measured on gjs 1.88.1), so it
73
- * is not an escape hatch either. Every container states its own rules here.
82
+ * generic `add`. `Gtk.Buildable.add_child` is introspected as a vfunc only
83
+ * (`typeof headerBar.add_child === 'undefined'`, gjs 1.88.1) — but the vfunc form
84
+ * IS callable and DOES dispatch correctly, measured across 29 containers, so it is
85
+ * a real escape hatch. This comment used to claim otherwise.
86
+ *
87
+ * The table exists because the generic call is unsafe as a default, not because it
88
+ * is unavailable: `GtkLabel.vfunc_add_child` accepts a child and says nothing until
89
+ * `Finalizing GtkLabel …, but it still has children left` at teardown. Every
90
+ * container states its own rules here, and a widget with no rule is REFUSED.
74
91
  */
75
92
  export type ChildPolicy = {
76
93
  kind: 'none';
@@ -128,7 +145,36 @@ export type ChildPolicy = {
128
145
  kind: 'coords';
129
146
  attach: string;
130
147
  remove: string;
148
+ }
149
+ /**
150
+ * Generated, not curated: the tag exists, its placement rule does not.
151
+ *
152
+ * The generated table adds every concrete GtkWidget descendant the GIR
153
+ * describes — 164 of them — while the curated table measures placement for a
154
+ * fraction. Guessing an adder for the rest would be the worst of the three
155
+ * options: `add` and `append` and `set_child` all exist somewhere in GTK, and
156
+ * calling the wrong one is a warning at exit 0.
157
+ *
158
+ * So the honest state is spelled out. Such a widget can be CREATED and given
159
+ * properties and handlers; inserting a child raises a named error that says
160
+ * which tag needs a curated policy. A leaf widget — most of the 164 — needs
161
+ * nothing more.
162
+ */
163
+ | {
164
+ kind: 'uncurated';
131
165
  };
166
+ /**
167
+ * One row of the GENERATED table: a tag, its GType, and how to reach the class.
168
+ *
169
+ * Deliberately not a `WidgetDescriptor`: it carries no placement rule, so making
170
+ * it the same type would invite a `children` field to appear here and become a
171
+ * second source for what `descriptors/` owns.
172
+ */
173
+ export interface GeneratedWidget {
174
+ readonly gtype: string;
175
+ readonly tag: string;
176
+ readonly ctor: () => GObject.ObjectClass & (new (props?: Record<string, unknown>) => GObject.Object);
177
+ }
132
178
  export interface WidgetDescriptor {
133
179
  /** GType name — and the tag a renderer writes. `GtkButton`, `AdwActionRow`. */
134
180
  readonly gtype: string;
@@ -142,4 +188,11 @@ export interface WidgetDescriptor {
142
188
  readonly textSink?: string;
143
189
  /** `onActivate` -> `activate` is derived; irregular pairs live here, in the TABLE. */
144
190
  readonly eventAliases?: Readonly<Record<string, string>>;
191
+ /**
192
+ * Construct-only properties whose ABSENCE aborts the process rather than
193
+ * raising — see `REQUIRED_CONSTRUCT_PROPS` in `descriptors/` for the measurement.
194
+ * Checked in `materialize`, because after `g_object_new` there is nothing left
195
+ * to check with.
196
+ */
197
+ readonly requiresProps?: readonly string[];
145
198
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The Vue surface: `GlobalComponents`, keyed by GType name.
3
+ *
4
+ * Import it once, for its side effect, anywhere in a Vue project:
5
+ *
6
+ * import '@gjsify/gtk-host/vue-components';
7
+ *
8
+ * WHY GTYPE NAMES AND NOT KEBAB. Volar camelizes and capitalises a template tag
9
+ * before it looks the component up, so ONE `GtkBox` key answers both `<GtkBox>`
10
+ * and `<gtk-box>`. A kebab key answers only the kebab spelling — measured, a
11
+ * Pascal tag against a kebab key is TS2339. So the GType name is the key, exactly
12
+ * as it is the registry key and the GtkBuilder key.
13
+ *
14
+ * The exception is generated, not curated: that camelize has no acronym knowledge,
15
+ * so `gtk-gl-area` becomes `GtkGlArea` and misses `GtkGLArea`. Those widgets get
16
+ * an extra kebab key in `WidgetPropsVueAliases`, and the generator finds them by
17
+ * rule rather than by bug report — one today.
18
+ *
19
+ * `strictTemplates: true` IS REQUIRED, AND IT MUST BE SET IN THE BASE OF THE
20
+ * `extends` CHAIN. Measured on `vue-tsc@3.3.11`, four cells, all four: the BASE
21
+ * tsconfig's value wins and the child's is ignored outright — a base with
22
+ * `strictTemplates: true` stays strict under a child that sets `false`, and a lax
23
+ * base stays lax under a child that sets `true`. So in a monorepo the shared base
24
+ * config decides this for every package, a per-package override does nothing, and
25
+ * nothing says so. Check the value with `vue-tsc --showConfig`, not by reading the
26
+ * nearest tsconfig.
27
+ *
28
+ * This is the load-bearing warning.
29
+ * Measured: without it, an unknown prop, an unknown event and an entirely
30
+ * UNRESOLVED tag are all silently accepted, while wrong VALUE types still error.
31
+ * A project without it therefore sees type errors appear and concludes the surface
32
+ * is working, while every misspelled property and every widget whose key does not
33
+ * resolve goes unchecked.
34
+ */
35
+ import type { DefineComponent } from '@vue/runtime-core';
36
+ import type { VueAttributes, WithOnce } from './attrs.js';
37
+ import type { WidgetPropsByGType, WidgetPropsVueAliases } from './generated/props.js';
38
+ type VueWidgetProps = WidgetPropsByGType & WidgetPropsVueAliases;
39
+ export type GtkGlobalComponents = {
40
+ [K in keyof VueWidgetProps]: DefineComponent<WithOnce<VueWidgetProps[K]> & VueAttributes>;
41
+ };
42
+ declare module '@vue/runtime-core' {
43
+ interface GlobalComponents extends GtkGlobalComponents {
44
+ }
45
+ }
46
+ export {};