@gjsify/gtk-host 0.44.0 → 0.46.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 (65) hide show
  1. package/README.md +50 -4
  2. package/lib/esm/adapters/solid.js +1 -1
  3. package/lib/esm/conformance/diagnostics.js +1 -1
  4. package/lib/esm/conformance/index.js +2 -2
  5. package/lib/esm/descriptors/adw.js +1 -1
  6. package/lib/esm/descriptors/gtk.js +1 -1
  7. package/lib/esm/errors.js +1 -1
  8. package/lib/esm/generated/widgets.js +1 -1
  9. package/lib/esm/host.js +1 -1
  10. package/lib/esm/index.js +1 -1
  11. package/lib/esm/list/controller.js +1 -0
  12. package/lib/esm/list/index.js +1 -0
  13. package/lib/esm/list/scroll.js +1 -0
  14. package/lib/esm/policies.js +1 -1
  15. package/lib/esm/probe.js +1 -1
  16. package/lib/esm/props.js +1 -1
  17. package/lib/esm/registry.js +1 -1
  18. package/lib/esm/signals.js +1 -1
  19. package/lib/esm/style/document.js +1 -0
  20. package/lib/esm/style/gtk-props.js +1 -1
  21. package/lib/esm/style/index.js +1 -1
  22. package/lib/esm/style/layout.js +1 -1
  23. package/lib/esm/style/paint.js +1 -1
  24. package/lib/esm/style/sheet.js +3 -3
  25. package/lib/esm/style/theme.js +3 -0
  26. package/lib/esm/style/tokens.js +1 -1
  27. package/lib/types/adapters/solid.d.ts +35 -0
  28. package/lib/types/conformance/diagnostics.d.ts +1 -1
  29. package/lib/types/descriptors/index.d.ts +3 -3
  30. package/lib/types/errors.d.ts +10 -0
  31. package/lib/types/generated/props.d.ts +684 -816
  32. package/lib/types/generated/widgets.d.ts +1 -1
  33. package/lib/types/generator/emit-types.d.mts +2 -2
  34. package/lib/types/generator/emit.d.mts +16 -6
  35. package/lib/types/generator/girs-vocabulary.d.mts +45 -0
  36. package/lib/types/generator/main.d.mts +16 -8
  37. package/lib/types/generator/model.d.mts +95 -0
  38. package/lib/types/generator/names.d.mts +19 -0
  39. package/lib/types/generator/vocabulary-dts.d.mts +47 -0
  40. package/lib/types/host.d.ts +3 -3
  41. package/lib/types/index.d.ts +1 -1
  42. package/lib/types/list/controller.d.ts +112 -0
  43. package/lib/types/list/index.d.ts +2 -0
  44. package/lib/types/list/list.spec.d.ts +2 -0
  45. package/lib/types/list/scroll.d.ts +26 -0
  46. package/lib/types/policies.d.ts +18 -1
  47. package/lib/types/probe.d.ts +49 -0
  48. package/lib/types/probe.spec.d.ts +2 -0
  49. package/lib/types/props.d.ts +13 -1
  50. package/lib/types/registry.d.ts +1 -1
  51. package/lib/types/signals.d.ts +3 -1
  52. package/lib/types/style/document.d.ts +22 -0
  53. package/lib/types/style/index.d.ts +6 -3
  54. package/lib/types/style/layout.d.ts +34 -0
  55. package/lib/types/style/sheet.d.ts +0 -5
  56. package/lib/types/style/theme.d.ts +158 -0
  57. package/lib/types/style/theme.spec.d.ts +2 -0
  58. package/lib/types/style/tokens.d.ts +76 -0
  59. package/lib/types/style/tokens.spec.d.ts +2 -0
  60. package/lib/types/types.d.ts +84 -4
  61. package/package.json +22 -16
  62. package/lib/types/generator/gir.d.mts +0 -164
  63. package/lib/types/generator/mini.fixture.d.mts +0 -30
  64. package/lib/types/generator/surface.d.mts +0 -62
  65. package/lib/types/generator/tsmap.d.mts +0 -73
@@ -0,0 +1,22 @@
1
+ import Gdk from 'gi://Gdk?version=4.0';
2
+ import Gtk from 'gi://Gtk?version=4.0';
3
+ /** A document GTK refused, or one that would have discarded what follows it. */
4
+ export declare class StyleSheetError extends Error {
5
+ readonly name = "StyleSheetError";
6
+ constructor(message: string);
7
+ }
8
+ /**
9
+ * Refuse a document that would take everything after it with it.
10
+ *
11
+ * `subject` names what is being loaded, because the two callers have very different
12
+ * readers: a generated rule is this package's bug, and an application's theme
13
+ * document is the application's.
14
+ */
15
+ export declare function assertContained(document: string, subject: string): void;
16
+ /**
17
+ * Put a provider on a display at `priority`, and say which display that was.
18
+ *
19
+ * `subject` names what is being installed, because the failure a caller has to act
20
+ * on is "there is no display yet" and the fix depends on which document it is.
21
+ */
22
+ export declare function installProvider(provider: Gtk.CssProvider, subject: string, display: Gdk.Display | null | undefined, priority: number): Gdk.Display;
@@ -1,13 +1,16 @@
1
1
  export { GTK_CSS_PROBES, GTK_CSS_PROPERTIES, NOT_GTK_CSS } from './gtk-css.js';
2
2
  export { GTK_WIDGET_PROPERTIES, GTK_WIDGET_PROPERTY_PROBES, NOT_GTK_WIDGET_PROPERTIES } from './gtk-props.js';
3
- export { MINIMAL_TOKENS, lookupToken, requireToken } from './tokens.js';
3
+ export { MINIMAL_TOKENS, TAILWIND_DEFAULT_TOKENS, lookupToken, mergeTokens, requireToken, tailwindDefaultHint, } from './tokens.js';
4
4
  export type { Scale, StyleTokens } from './tokens.js';
5
5
  export { UnknownUtilityError } from './errors.js';
6
6
  export { PAINT_PROPERTIES, partitionPaint, resolvePaintUtility } from './paint.js';
7
7
  export type { PaintProps } from './paint.js';
8
8
  export { LAYOUT_PROPERTIES, partitionLayout, resolveLayoutUtility } from './layout.js';
9
- export type { AlignValue, Edge, JustifyValue, LayoutIntent, LayoutPartition, LayoutProps, OverlayIntent, } from './layout.js';
9
+ export type { AlignValue, Edge, JustifyValue, LayoutIntent, LayoutPartition, LayoutProps, OverlayIntent, WrapIntent, } from './layout.js';
10
10
  export { partition, resolveUtilities, resolveUtility } from './resolve.js';
11
11
  export type { Partitioned, StyleProps } from './resolve.js';
12
- export { StyleSheet, StyleSheetError, VARIANT_PSEUDO } from './sheet.js';
12
+ export { StyleSheet, VARIANT_PSEUDO } from './sheet.js';
13
13
  export type { StyleSheetOptions } from './sheet.js';
14
+ export { assertContained, StyleSheetError } from './document.js';
15
+ export { ADWAITA_NAMED_COLORS, NEUTRAL_THEME, NOT_ADWAITA_NAMED_COLORS, THEME_PROVIDER_PRIORITY, ThemeRegistry, } from './theme.js';
16
+ export type { Theme, ThemeRegistryOptions } from './theme.js';
@@ -27,6 +27,7 @@ export interface LayoutProps {
27
27
  paddingBottom?: string;
28
28
  paddingLeft?: string;
29
29
  flexDirection?: 'row' | 'column';
30
+ flexWrap?: 'nowrap' | 'wrap';
30
31
  flexGrow?: string;
31
32
  alignItems?: AlignValue;
32
33
  justifyContent?: JustifyValue;
@@ -114,6 +115,25 @@ export interface LayoutIntent {
114
115
  * `relative` would make it undetectable.
115
116
  */
116
117
  readonly overlay?: OverlayIntent;
118
+ /**
119
+ * `flex-wrap` — the element becomes a DIFFERENT WIDGET.
120
+ *
121
+ * A `Gtk.Box` lays its children on one line and has no second line to put
122
+ * anything on. Wrapping on GTK is `Gtk.FlowBox`, which is not another property
123
+ * on the box but another class — so this is the same shape as
124
+ * {@link LayoutIntent.distribute}'s `space-between`: L1 names the widget the
125
+ * answer needs and L2 swaps it, because L1 resolves properties for an element
126
+ * and has no say in what the element becomes.
127
+ *
128
+ * The SPACINGS travel with it, and that is the reason this field carries values
129
+ * at all rather than being a flag. `Gtk.FlowBox` has NO `spacing` (measured,
130
+ * `gtk-props.ts`) — it has `row-spacing` and `column-spacing`, both real at once
131
+ * — so a gap on a wrapping element cannot take the `Gtk.Box:spacing` route the
132
+ * gap routes below use, and the axis-qualified spellings stop being an
133
+ * orientation question. Routing them here is what keeps `gap-*` from emitting a
134
+ * property the widget does not install.
135
+ */
136
+ readonly wrap?: WrapIntent;
117
137
  /**
118
138
  * `text-*` — only a text widget aligns text.
119
139
  *
@@ -124,6 +144,20 @@ export interface LayoutIntent {
124
144
  */
125
145
  readonly textAlign?: 'left' | 'center' | 'right' | 'justify';
126
146
  }
147
+ /**
148
+ * `flex-nowrap` / `flex-wrap`, with the spacings a wrapping widget needs.
149
+ *
150
+ * `single` is carried rather than dropped for the reason `relative` is: it is the
151
+ * half L2 can hold the other half against, and a resolution that never sees it
152
+ * cannot tell "this element does not wrap" from "nobody asked".
153
+ */
154
+ export type WrapIntent = {
155
+ readonly lines: 'single';
156
+ } | {
157
+ readonly lines: 'multi';
158
+ readonly rowSpacing?: number;
159
+ readonly columnSpacing?: number;
160
+ };
127
161
  export type OverlayIntent = {
128
162
  readonly role: 'context';
129
163
  } | {
@@ -1,11 +1,6 @@
1
1
  import Gdk from 'gi://Gdk?version=4.0';
2
2
  /** A variant this sheet knows how to express, and the pseudo-class it becomes. */
3
3
  export declare const VARIANT_PSEUDO: Readonly<Record<string, string>>;
4
- /** A rule GTK refused, or one that would have poisoned the document. */
5
- export declare class StyleSheetError extends Error {
6
- readonly name = "StyleSheetError";
7
- constructor(message: string);
8
- }
9
4
  export interface StyleSheetOptions {
10
5
  /**
11
6
  * Where the provider is installed. Omitted means the default display — the
@@ -0,0 +1,158 @@
1
+ import Gdk from 'gi://Gdk?version=4.0';
2
+ /**
3
+ * Where a theme document is installed. See decision 2 for why it is this number.
4
+ *
5
+ * Exported because it is the fact a reader has to check against their own providers,
6
+ * and because a consumer installing a fourth document needs to know what it has to
7
+ * sit between.
8
+ */
9
+ export declare const THEME_PROVIDER_PRIORITY: number;
10
+ /**
11
+ * The named colours libadwaita defines, measured rather than read.
12
+ *
13
+ * MEASURED on libadwaita 1.9.3 by resolving `var(--<name>, <sentinel>)` on a real
14
+ * widget and checking the sentinel did NOT come back — the only test that
15
+ * distinguishes "the name exists" from "the name is a typo that defines nothing".
16
+ * `theme.spec.ts` re-measures both directions against the libadwaita that is
17
+ * running, so a release that renames one goes red here rather than in a window.
18
+ *
19
+ * The list is what a theme may OVERRIDE, and overriding a name libadwaita does not
20
+ * define is the exact silent failure this set exists to catch: `--acccent-bg-color`
21
+ * is a perfectly valid custom property that nothing ever reads.
22
+ */
23
+ export declare const ADWAITA_NAMED_COLORS: readonly string[];
24
+ /**
25
+ * Names that are NOT custom properties, and the claim each one carries.
26
+ *
27
+ * Without this direction the set above could name every string in GTK and still
28
+ * pass. `borders` is the interesting row: it is a real Adwaita colour under the
29
+ * LEGACY `@define-color` mechanism and is not a CSS variable, which is exactly the
30
+ * confusion decision 1 is about. `accent_bg_color` is the same point in the other
31
+ * spelling — the underscored legacy name of a variable that does exist.
32
+ */
33
+ export declare const NOT_ADWAITA_NAMED_COLORS: readonly string[];
34
+ /** One named look: a GTK CSS document, a palette, and which desktops default to it. */
35
+ export interface Theme {
36
+ /** How the application asks for it. Also what a refusal names. */
37
+ readonly name: string;
38
+ /**
39
+ * The application's own GTK CSS document.
40
+ *
41
+ * Goes through the same containment probe as a generated rule, and for a
42
+ * stronger reason: this is the document this package did not write.
43
+ */
44
+ readonly css?: string;
45
+ /**
46
+ * Adwaita named colours to redefine, WITHOUT the `--` prefix.
47
+ *
48
+ * `{ 'accent-bg-color': 'rgb(53 132 228)' }`. Emitted as a `:root` block ahead of
49
+ * {@link Theme.css}, so a rule in {@link Theme.css} redefining one of these names
50
+ * WINS — the last definition in a document is the one that applies.
51
+ */
52
+ readonly namedColors?: Readonly<Record<string, string>>;
53
+ /**
54
+ * The `process.platform` values this theme is the default for, or `'*'` for any.
55
+ *
56
+ * Data on the theme rather than a table in the registry, because "this look
57
+ * belongs on macOS" is a fact about the look.
58
+ *
59
+ * `'*'` exists because `process.platform` has more values than the three desktops
60
+ * this project targets — `freebsd`, `openbsd`, `android`, `sunos`, … — and GTK
61
+ * runs on several of them. A registry that only knew `linux`/`darwin`/`win32`
62
+ * would make `selectDefault(process.platform)`, the call this seam is documented
63
+ * with, THROW on a platform where nothing is wrong. A fallback theme says so once
64
+ * instead, and a named theme still wins over it by being registered later.
65
+ */
66
+ readonly defaultOn?: readonly string[];
67
+ }
68
+ /**
69
+ * The one theme shipped now, and it deliberately changes nothing.
70
+ *
71
+ * A GTK application should look like the desktop it is running on, and the accent is
72
+ * the user's — they picked it in Settings and expect their applications to use it.
73
+ * So the neutral theme is an EMPTY document, and that is a statement rather than a
74
+ * placeholder: selecting it is how an application returns to Adwaita exactly, which
75
+ * is measurably true because reloading the provider to the empty string restores
76
+ * every named colour to libadwaita's own value.
77
+ *
78
+ * It is also what makes the registry useful with one theme registered: there is
79
+ * always something to switch back TO.
80
+ */
81
+ export declare const NEUTRAL_THEME: Theme;
82
+ export interface ThemeRegistryOptions {
83
+ /**
84
+ * Where the provider is installed. Omitted means the default display — the right
85
+ * answer for an application and the wrong one for a test, which is why it is
86
+ * injectable at all.
87
+ */
88
+ readonly display?: Gdk.Display | null;
89
+ /** Provider priority. Defaults to {@link THEME_PROVIDER_PRIORITY}; see decision 2. */
90
+ readonly priority?: number;
91
+ }
92
+ /**
93
+ * Named themes, one selected, swapped on a single provider.
94
+ *
95
+ * NOT a module-level singleton, unlike the token configuration one layer up. A
96
+ * registry holds a `Gdk.Display` and a `Gtk.CssProvider`, so a test that could not
97
+ * make its own would have to share the session's — and the registry is exactly the
98
+ * object whose whole behaviour is "what is installed on the display right now".
99
+ */
100
+ export declare class ThemeRegistry {
101
+ #private;
102
+ constructor(options?: ThemeRegistryOptions);
103
+ /**
104
+ * Add a theme, refusing anything that would be a silent no-op.
105
+ *
106
+ * The document is PROBED here rather than at selection, so the error names the
107
+ * line that declared the theme instead of the line that switched to it — which
108
+ * may be a user clicking a row in the application's settings, hours later.
109
+ */
110
+ register(theme: Theme): this;
111
+ /** The registered names, in registration order. */
112
+ get names(): readonly string[];
113
+ /** The selected theme's name, or null before anything is selected. */
114
+ get current(): string | null;
115
+ /** The document a theme produces. Exposed because a test that cannot read it proves nothing. */
116
+ documentOf(theme: Theme): string;
117
+ /**
118
+ * Switch to a registered theme, replacing whatever was loaded.
119
+ *
120
+ * Idempotent, and cheap enough to call from a settings row's handler: the
121
+ * document was probed at registration, so this is one `load_from_string` plus, on
122
+ * the first call only, one `add_provider_for_display`.
123
+ */
124
+ select(name: string): void;
125
+ /**
126
+ * Select the theme that declares itself the default for `platform`.
127
+ *
128
+ * The platform is a PARAMETER and this module never reads `process.platform`
129
+ * itself — the same discipline the intent seam one layer up argues for. It keeps
130
+ * a `process` read (and the OS declaration that comes with one) out of a package
131
+ * whose job is GTK, and it lets a settings screen preview another desktop's look
132
+ * by passing that desktop's name.
133
+ *
134
+ * The last registered match wins, so an application registering its own theme
135
+ * after the neutral one gets its own — including over a theme that declares
136
+ * `'*'`, which is how the shipped neutral default stays a fallback rather than a
137
+ * ceiling.
138
+ */
139
+ selectDefault(platform: string): void;
140
+ /**
141
+ * Take the theme document off the display, leaving nothing behind.
142
+ *
143
+ * Selecting the neutral theme reloads the document to empty, which is all an
144
+ * application needs. It is NOT all a TEST needs, and the difference was measured
145
+ * in this file's own spec: a vector that read "libadwaita's own accent" read the
146
+ * PREVIOUS vector's green instead, because that registry's provider was still on
147
+ * the shared display with a `--accent-bg-color` in it — and the assertion named
148
+ * "back to Adwaita exactly" then passed against the green. Ownership belongs with
149
+ * whoever created the registry, so there has to be a way to give it up.
150
+ *
151
+ * The provider is removed from the display it was INSTALLED on rather than from
152
+ * whatever `Gdk.Display.get_default()` answers now. A later `select()` installs
153
+ * again: position relative to the generated sheet is decided by the priority
154
+ * numbers, not by insertion order, so re-installing cannot land in the wrong
155
+ * place (decision 2).
156
+ */
157
+ dispose(): void;
158
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -43,8 +43,84 @@ export interface StyleTokens {
43
43
  * scale would let a project's typo resolve against a value nobody chose, which is
44
44
  * the failure the declared-vocabulary rule exists to prevent — and it would make
45
45
  * "the values come from the project" false in the common case.
46
+ *
47
+ * That decision stands, and {@link TAILWIND_DEFAULT_TOKENS} is not a retreat from
48
+ * it: the measured gap is that a project REPLACING this default with its own
49
+ * generated scales loses `0` and `full`, which nothing in its `@theme` was ever
50
+ * going to declare. The answer is an opt-in a reader can see in their own code, not
51
+ * a wider default nobody chose. This constant stays as small as it is.
46
52
  */
47
53
  export declare const MINIMAL_TOKENS: StyleTokens;
54
+ /**
55
+ * The Tailwind defaults a project's own `@theme` does not replace — OPT-IN.
56
+ *
57
+ * MEASURED, and this is the highest-frequency finding of the whole exercise. Running
58
+ * one production-shaped application's real class vocabulary — 87 distinct utilities,
59
+ * 826 occurrences — through this partition with tokens generated mechanically from
60
+ * its Tailwind v4 `@theme` resolves **81 of 87 distinct and 803 of 826 occurrences**.
61
+ * Five of the six failures are one thing: `rounded-full` (11 uses) and
62
+ * `inset-0`/`left-0`/`right-0`/`top-0` (9 between them). The project's declared
63
+ * scales simply have no `full` and no `0`.
64
+ *
65
+ * They have no reason to. **`0` and `full` are not design decisions.** A token source
66
+ * emits the values a designer chose; `inset-0` means "flush" and `rounded-full` means
67
+ * "a pill", and neither is a number anybody picked. A project that declares its whole
68
+ * palette still leans on Tailwind for the STRUCTURAL tokens, and finds out one class
69
+ * at a time.
70
+ *
71
+ * So this set is deliberately NOT a copy of Tailwind's default theme, and the name
72
+ * would be a lie if it carried the numeric ladder: shipping `spacing-4` … `spacing-96`
73
+ * would be shipping a design decision the project has already made differently, which
74
+ * is the opposite of the point. It carries the structural tokens and the keyword sets
75
+ * — the ones a `@theme` has no reason to name — and nothing that is a value someone
76
+ * chose.
77
+ *
78
+ * THREE DIVERGENCES FROM TAILWIND'S OWN NUMBERS, each forced and each measured:
79
+ *
80
+ * - **Every length is px, not rem.** Tailwind v4's spacing is rem-based. A `rem`
81
+ * token reaches CSS padding unchanged and is a NAMED ERROR the moment the same
82
+ * token is asked to become a margin, because `Gtk.Widget:margin-top` is a `gint`
83
+ * of device pixels with no unit conversion behind it. A rem scale here would
84
+ * therefore trade "the token is missing" for "the token throws on half the
85
+ * families", which is worse.
86
+ * - **`full` is `9999px`, not `calc(infinity * 1px)`.** GTK's parser has no
87
+ * infinity, and a border radius larger than the box is clamped anyway.
88
+ * - **`inherit` is absent from the colours.** `color: inherit` parses, but
89
+ * `alpha(inherit, 0.5)` does not (measured: *"inherit" is not a valid color
90
+ * name*), so `bg-inherit/50` would be a refusal rather than a colour. The other
91
+ * four keywords survive the alpha modifier and are carried.
92
+ *
93
+ * Opting in is a VISIBLE LINE in the consumer's code, which is the whole design:
94
+ *
95
+ * ```ts
96
+ * configureStyle({ tokens: mergeTokens(TAILWIND_DEFAULT_TOKENS, projectTokens) });
97
+ * ```
98
+ *
99
+ * Use {@link mergeTokens} and not a spread. `{ ...TAILWIND_DEFAULT_TOKENS,
100
+ * ...projectTokens }` replaces whole SCALES, so a project declaring any `spacing` at
101
+ * all loses `0` again — which is the exact bug this constant exists to answer.
102
+ */
103
+ export declare const TAILWIND_DEFAULT_TOKENS: StyleTokens;
104
+ /**
105
+ * Several token sets into one, merging PER TOKEN and not per scale.
106
+ *
107
+ * The distinction is the whole function. `{ ...defaults, ...project }` is an object
108
+ * spread over the SCALE names, so a project that declares any `spacing` replaces the
109
+ * default `spacing` wholesale and loses `0` — silently, and visible only as
110
+ * `inset-0` throwing much later. Later sets win token by token, which is what a
111
+ * reader means by "my values on top of the defaults".
112
+ */
113
+ export declare function mergeTokens(...sets: readonly StyleTokens[]): StyleTokens;
114
+ /**
115
+ * The sentence a scale-miss error adds when Tailwind's defaults DO define the token.
116
+ *
117
+ * The measured six failures each become a one-line fix with this and stay a search
118
+ * without it: "not in the borderRadius scale. Known: l, m, s" sends a reader to
119
+ * invent a radius, where naming the opt-in sends them to the line that was missing.
120
+ * Empty for a token nothing would have answered, so an ordinary typo is not offered
121
+ * a remedy that would not have helped.
122
+ */
123
+ export declare function tailwindDefaultHint(scaleName: string, token: string): string;
48
124
  /** A token's value, or `undefined` when the scale is absent or does not carry it. */
49
125
  export declare const lookupToken: (scale: Scale | undefined, token: string) => string | undefined;
50
126
  /**
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -117,12 +117,43 @@ export type ChildPolicy = {
117
117
  remove: string;
118
118
  wrap: 'list-box-row' | 'flow-box-child' | null;
119
119
  }
120
- /** `Adw.HeaderBar`, `Adw.ToolbarView`, `Adw.ActionRow`: named attachment points. */
120
+ /**
121
+ * `Adw.HeaderBar`, `Adw.ToolbarView`, `Adw.ActionRow`: named attachment points.
122
+ *
123
+ * `remove` is OPTIONAL because a slot can be backed two ways and only one of them
124
+ * needs it. A `set_`-prefixed slot is emptied by writing `null` back through the
125
+ * same setter (`detachChild` already does exactly that, via `setterSlotOf`), so a
126
+ * policy whose slots are ALL setters never reaches a remove method — and
127
+ * `Adw.NavigationSplitView` / `Adw.OverlaySplitView` have none to name: measured on
128
+ * libadwaita 1.9.3, their only `remove*` methods are `GtkWidget`'s
129
+ * (`remove_controller`, `remove_css_class`, …). Declaring `remove: 'remove'` there
130
+ * would be a claim `descriptorProblems()` correctly rejects.
131
+ *
132
+ * An ADDER-backed slot (`add_top_bar`, `pack_start`) is the opposite: nothing takes
133
+ * a child back out of it but a remove method, so `remove` is REQUIRED the moment one
134
+ * slot does not start with `set_`. That rule is machine-checked in `policyProblems()`
135
+ * rather than left to this comment — optional in the type and unchecked would turn
136
+ * `Adw.ToolbarView` losing its `remove` into a `TypeError` deep inside an unmount.
137
+ */
121
138
  | {
122
139
  kind: 'slotted';
123
140
  slots: Record<string, string>;
124
141
  defaultSlot: string;
125
- remove: string;
142
+ remove?: string;
143
+ /**
144
+ * Slots whose adder hands the child to an inner `Gtk.ListBox`, and which
145
+ * therefore need the same wrap `indexed` declares — keyed by slot, because a
146
+ * widget can have one such slot and two ordinary ones.
147
+ *
148
+ * MEASURED on GTK 4.22.4, and it is the wrap or a leak: `gtk_list_box_remove`
149
+ * does NOT unwrap. A `Gtk.ListBox` given a non-row child puts it inside an
150
+ * implicit `GtkListBoxRow`, and removing the ORIGINAL child then answers
151
+ * `Gtk-WARNING **: Tried to remove non-child 0x…` and leaves it parented —
152
+ * with a plain `Gtk.Label` and with a `Gtk.Button` alike. `Adw.ExpanderRow`'s
153
+ * `add_row` is that adder, so its disclosure slot declares the wrap and the
154
+ * host addresses the row it made.
155
+ */
156
+ wrapSlots?: Readonly<Record<string, 'list-box-row'>>;
126
157
  }
127
158
  /**
128
159
  * `Gtk.Stack`, `Adw.NavigationView`: children addressed by name/tag.
@@ -139,6 +170,55 @@ export type ChildPolicy = {
139
170
  remove: string;
140
171
  nameFrom: string;
141
172
  titled: boolean;
173
+ /**
174
+ * Take the child out of view BEFORE the parent removes it, and put its
175
+ * own `visible` back afterwards. Declared per widget, because it is a
176
+ * defect in one of them rather than a property of being keyed.
177
+ *
178
+ * The defect is libadwaita's. `stack_remove` — what both
179
+ * `adw_view_stack_remove` and dispose call — clears `visible_child`, then
180
+ * does `g_clear_object (&page->widget)`, and never touches
181
+ * `last_visible_child`, which can still point at that same page. A later
182
+ * reader then dereferences a page whose widget is NULL:
183
+ *
184
+ * Gtk-CRITICAL **: gtk_widget_set_child_visible: assertion 'GTK_IS_WIDGET (widget)' failed
185
+ *
186
+ * Source: `refs/libadwaita/src/adw-view-stack.c` — `stack_remove` at 1184,
187
+ * the stale read at 934 in `transition_done_cb`. Read at 42f647f
188
+ * (1.10.alpha.1); that file is byte-identical at the 1.9.3 tag, which is
189
+ * what is installed here, so one reading covers both.
190
+ *
191
+ * MEASURED on libadwaita 1.9.3 / GTK 4.22.4 / GJS 1.88.1. Reproducer, no
192
+ * renderer involved: add `a` and `b`, present a window holding the stack,
193
+ * `set_visible_child_name('b')`, `remove(a)`, then let the window go away.
194
+ * Each precondition alone is silent, a stack never made visible included —
195
+ * `set_visible_child` only records `last_visible_child` when
196
+ * `gtk_widget_is_visible` answers yes. `Gtk.Stack` and
197
+ * `Adw.NavigationView`, the other two `keyed` descriptors, do not
198
+ * reproduce it at all, which is why this is a field and not the kind.
199
+ *
200
+ * WHICH read fires decides what a test has to do, so it is measured rather
201
+ * than listed: the UNMAP one. `AdwAnimation` connects `adw_animation_skip`
202
+ * to its widget's `unmap` (`adw-animation.c`) and the skip runs
203
+ * `transition_done_cb`, so merely hiding the window is enough and
204
+ * destroying it does the same — while a queued resize plus draw, and a
205
+ * direct `measure()`, both stayed quiet. The file's other reads (snapshot,
206
+ * size-allocate, measure) are real paths this reproducer does not reach.
207
+ *
208
+ * Hiding first is libadwaita's OWN cleanup: the visibility notify runs
209
+ * `update_child_visible`, which does clear `last_visible_child`. Measured
210
+ * SUFFICIENT and not merely necessary — 400 randomised
211
+ * add/remove/switch/reorder steps against a live 200 ms transition, three
212
+ * seeds, 44/23/25 criticals unpatched against 0/0/0 patched.
213
+ *
214
+ * The restore is load-bearing. `reorderMode` answers `remove-all` for
215
+ * `keyed`, and measured, a reversal that hides without restoring leaves the
216
+ * stack with no visible child and no way back: the last line of
217
+ * `adw_view_stack_set_visible_child_name` is
218
+ * `if (gtk_widget_get_visible (page->widget)) set_visible_child (...)`, so
219
+ * for a hidden page it is a no-op with no warning at all.
220
+ */
221
+ hideBeforeRemove?: boolean;
142
222
  }
143
223
  /** `Gtk.Grid`: position is data on the child, so document order carries nothing. */
144
224
  | {
@@ -150,14 +230,14 @@ export type ChildPolicy = {
150
230
  * Generated, not curated: the tag exists, its placement rule does not.
151
231
  *
152
232
  * The generated table adds every concrete GtkWidget descendant the GIR
153
- * describes — 164 of them — while the curated table measures placement for a
233
+ * describes, plus the placement carriers, while the curated table measures placement for a
154
234
  * fraction. Guessing an adder for the rest would be the worst of the three
155
235
  * options: `add` and `append` and `set_child` all exist somewhere in GTK, and
156
236
  * calling the wrong one is a warning at exit 0.
157
237
  *
158
238
  * So the honest state is spelled out. Such a widget can be CREATED and given
159
239
  * 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
240
+ * which tag needs a curated policy. A leaf widget — most of the table — needs
161
241
  * nothing more.
162
242
  */
163
243
  | {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/gtk-host",
3
- "version": "0.44.0",
3
+ "version": "0.46.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",
@@ -14,6 +14,10 @@
14
14
  "types": "./lib/types/style/index.d.ts",
15
15
  "default": "./lib/esm/style/index.js"
16
16
  },
17
+ "./list": {
18
+ "types": "./lib/types/list/index.d.ts",
19
+ "default": "./lib/esm/list/index.js"
20
+ },
17
21
  "./vue": {
18
22
  "types": "./lib/types/adapters/vue.d.ts",
19
23
  "default": "./lib/esm/adapters/vue.js"
@@ -67,11 +71,11 @@
67
71
  "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
68
72
  "build:types": "gjsify tsc",
69
73
  "build:test": "gjsify run build:test:gjs",
70
- "build:test:node": "gjsify build src/test.mts --app node --outfile dist/test.node.mjs --define '__VUE_OPTIONS_API__=false' --define '__VUE_PROD_DEVTOOLS__=false' --define '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__=false' --define 'process.env.NODE_ENV=\"production\"' --exclude-globals navigator",
71
- "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs --define '__VUE_OPTIONS_API__=false' --define '__VUE_PROD_DEVTOOLS__=false' --define '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__=false' --define 'process.env.NODE_ENV=\"production\"' --exclude-globals navigator",
74
+ "build:test:node": "gjsify build src/test.mts --app node --outfile dist/test.node.mjs --define __VUE_OPTIONS_API__=false --define __VUE_PROD_DEVTOOLS__=false --define __VUE_PROD_HYDRATION_MISMATCH_DETAILS__=false --define \"process.env.NODE_ENV='production'\" --exclude-globals navigator",
75
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs --define __VUE_OPTIONS_API__=false --define __VUE_PROD_DEVTOOLS__=false --define __VUE_PROD_HYDRATION_MISMATCH_DETAILS__=false --define \"process.env.NODE_ENV='production'\" --exclude-globals navigator",
72
76
  "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
73
77
  "test:gjs": "gjsify run test.gjs.mjs",
74
- "test:gjs-on-node": "gjsify run build:gjsify && gjsify run build:test:node && NODE_GI_NATIVE=${NODE_GI_NATIVE:-prebuild} node dist/test.node.mjs",
78
+ "test:gjs-on-node": "gjsify run build:gjsify && gjsify run build:test:node && node dist/test.node.mjs",
75
79
  "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"
76
80
  },
77
81
  "keywords": [
@@ -82,14 +86,14 @@
82
86
  "host"
83
87
  ],
84
88
  "dependencies": {
85
- "@girs/adw-1": "^4.1.0",
86
- "@girs/gdk-4.0": "^4.1.0",
87
- "@girs/gio-2.0": "^4.1.0",
88
- "@girs/gjs": "^4.1.0",
89
- "@girs/glib-2.0": "^4.1.0",
90
- "@girs/gobject-2.0": "^4.1.0",
91
- "@girs/gtk-4.0": "^4.1.0",
92
- "@girs/pango-1.0": "^4.1.0"
89
+ "@girs/adw-1": "4.6.0",
90
+ "@girs/gdk-4.0": "4.6.0",
91
+ "@girs/gio-2.0": "4.6.0",
92
+ "@girs/gjs": "4.6.0",
93
+ "@girs/glib-2.0": "4.6.0",
94
+ "@girs/gobject-2.0": "4.6.0",
95
+ "@girs/gtk-4.0": "4.6.0",
96
+ "@girs/pango-1.0": "4.6.0"
93
97
  },
94
98
  "peerDependencies": {
95
99
  "@vue/runtime-core": "^3.5.0",
@@ -112,13 +116,12 @@
112
116
  }
113
117
  },
114
118
  "devDependencies": {
115
- "@gjsify/cli": "^0.44.0",
119
+ "@gjsify/cli": "^0.46.0",
116
120
  "@vue/runtime-core": "^3.5.0",
117
121
  "solid-js": "^1.9.0",
118
- "@gjsify/unit": "^0.44.0",
122
+ "@gjsify/unit": "^0.46.0",
119
123
  "@types/node": "^25.9.2",
120
124
  "typescript": "^6.0.3",
121
- "@gjsify/domparser": "^0.44.0",
122
125
  "@gjsify/node-gi": "file:../../node-gi/node-gi",
123
126
  "vue": "^3.5.41",
124
127
  "vue-tsc": "^3.3.11",
@@ -134,7 +137,10 @@
134
137
  "browser": "none",
135
138
  "nativescript": "none"
136
139
  },
137
- "tier": 3
140
+ "tier": 3,
141
+ "widgetVocabulary": {
142
+ "role": "reference"
143
+ }
138
144
  },
139
145
  "license": "MIT",
140
146
  "repository": {