@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.
- package/README.md +454 -6
- package/lib/esm/adapters/react.js +1 -0
- package/lib/esm/adapters/solid.js +1 -0
- package/lib/esm/adapters/vue.js +1 -0
- package/lib/esm/attrs.js +0 -0
- package/lib/esm/conformance/diagnostics.js +2 -2
- package/lib/esm/conformance/index.js +2 -2
- package/lib/esm/descriptors/index.js +1 -1
- package/lib/esm/errors.js +1 -1
- package/lib/esm/generated/props.js +0 -0
- package/lib/esm/generated/widgets.js +1 -0
- package/lib/esm/host.js +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/jsx-runtime.js +1 -0
- package/lib/esm/policies.js +1 -1
- package/lib/esm/probe.js +1 -0
- package/lib/esm/props.js +1 -1
- package/lib/esm/react-jsx-runtime.js +1 -0
- package/lib/esm/registry.js +1 -1
- package/lib/esm/signals.js +1 -1
- package/lib/esm/tags.js +2 -0
- package/lib/esm/vue-components.js +0 -0
- package/lib/types/adapters/react.d.ts +175 -0
- package/lib/types/adapters/react.spec.d.ts +2 -0
- package/lib/types/adapters/solid.d.ts +78 -0
- package/lib/types/adapters/solid.spec.d.ts +2 -0
- package/lib/types/adapters/vue.d.ts +24 -0
- package/lib/types/adapters/vue.spec.d.ts +2 -0
- package/lib/types/attrs.d.ts +102 -0
- package/lib/types/buildable.spec.d.ts +2 -0
- package/lib/types/conformance/diagnostics.d.ts +14 -0
- package/lib/types/conformance/index.d.ts +24 -1
- package/lib/types/conformance/vectors.d.mts +34 -0
- package/lib/types/descriptors/index.d.ts +52 -1
- package/lib/types/errors.d.ts +21 -0
- package/lib/types/generated/props.d.ts +5505 -0
- package/lib/types/generated/surface-data.d.mts +22 -0
- package/lib/types/generated/widgets.d.ts +3 -0
- package/lib/types/generated.spec.d.ts +4 -0
- package/lib/types/generator/emit-types.d.mts +54 -0
- package/lib/types/generator/emit.d.mts +50 -0
- package/lib/types/generator/gir.d.mts +156 -0
- package/lib/types/generator/main.d.mts +23 -0
- package/lib/types/generator/mini.fixture.d.mts +30 -0
- package/lib/types/generator/surface.d.mts +62 -0
- package/lib/types/generator/tsmap.d.mts +73 -0
- package/lib/types/generator.spec.d.ts +2 -0
- package/lib/types/host.d.ts +102 -1
- package/lib/types/index.d.ts +6 -3
- package/lib/types/jsx-runtime.d.ts +70 -0
- package/lib/types/policies.d.ts +58 -0
- package/lib/types/probe.d.ts +58 -0
- package/lib/types/props.d.ts +49 -2
- package/lib/types/react-jsx-runtime.d.ts +108 -0
- package/lib/types/registry.d.ts +1 -1
- package/lib/types/signals.d.ts +16 -1
- package/lib/types/tags.d.ts +25 -0
- package/lib/types/testing/gate.d.mts +2 -0
- package/lib/types/types.d.ts +56 -3
- package/lib/types/vue-components.d.ts +46 -0
- 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;
|
package/lib/types/policies.d.ts
CHANGED
|
@@ -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>;
|
package/lib/types/props.d.ts
CHANGED
|
@@ -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).
|
|
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
|
|
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
|
+
}
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -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
|
|
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.
|
package/lib/types/signals.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -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
|
|
72
|
-
* (`typeof headerBar.add_child === 'undefined'`,
|
|
73
|
-
*
|
|
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 {};
|