@gjsify/gtk-host 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +158 -2
  2. package/lib/esm/adjustment.js +1 -0
  3. package/lib/esm/conformance/diagnostics.js +1 -1
  4. package/lib/esm/conformance/index.js +1 -2
  5. package/lib/esm/conformance/tree.js +2 -0
  6. package/lib/esm/conformance/window-chrome.js +1 -0
  7. package/lib/esm/descriptors/adw.js +1 -1
  8. package/lib/esm/descriptors/gtk.js +1 -1
  9. package/lib/esm/descriptors/index.js +1 -1
  10. package/lib/esm/errors.js +1 -1
  11. package/lib/esm/font-families.js +1 -0
  12. package/lib/esm/fonts.js +1 -1
  13. package/lib/esm/host.js +1 -1
  14. package/lib/esm/index.js +1 -1
  15. package/lib/esm/list-model.js +1 -0
  16. package/lib/esm/menu.js +1 -0
  17. package/lib/esm/policies.js +1 -1
  18. package/lib/esm/probe.js +1 -1
  19. package/lib/esm/props.js +1 -1
  20. package/lib/esm/style/font-family.js +3 -0
  21. package/lib/esm/style/index.js +1 -1
  22. package/lib/esm/style/paint.js +1 -1
  23. package/lib/types/adjustment.d.ts +28 -0
  24. package/lib/types/adjustment.spec.d.ts +2 -0
  25. package/lib/types/attrs.d.ts +79 -0
  26. package/lib/types/conformance/index.d.ts +28 -29
  27. package/lib/types/conformance/tree.d.ts +30 -0
  28. package/lib/types/conformance/vectors.d.mts +6 -0
  29. package/lib/types/conformance/window-chrome.d.ts +29 -0
  30. package/lib/types/descriptors/gtk.d.ts +32 -1
  31. package/lib/types/errors.d.ts +43 -0
  32. package/lib/types/font-families.d.ts +40 -0
  33. package/lib/types/font-families.spec.d.ts +2 -0
  34. package/lib/types/fonts.d.ts +45 -1
  35. package/lib/types/generated/enum-values.d.mts +38 -0
  36. package/lib/types/generated/methods.d.mts +46 -0
  37. package/lib/types/index.d.ts +1 -1
  38. package/lib/types/jsx-runtime.d.ts +2 -2
  39. package/lib/types/list-model.d.ts +38 -0
  40. package/lib/types/list-model.spec.d.ts +2 -0
  41. package/lib/types/menu.d.ts +43 -0
  42. package/lib/types/menu.spec.d.ts +2 -0
  43. package/lib/types/placement.spec.d.ts +2 -0
  44. package/lib/types/policies.d.ts +241 -1
  45. package/lib/types/react-jsx-runtime.d.ts +2 -2
  46. package/lib/types/shared-trees.spec.d.ts +2 -0
  47. package/lib/types/style/font-family.d.ts +64 -0
  48. package/lib/types/style/index.d.ts +3 -1
  49. package/lib/types/style/paint.d.ts +37 -0
  50. package/lib/types/testing/refusal.d.mts +2 -0
  51. package/lib/types/types.d.ts +177 -1
  52. package/lib/types/vue-components.d.ts +2 -2
  53. package/package.json +8 -5
@@ -0,0 +1,43 @@
1
+ import Gio from 'gi://Gio?version=2.0';
2
+ import type { AdwMenuInput, AdwMenuModel } from '@gjsify/adwaita-core';
3
+ /**
4
+ * A portable menu model as a live `Gio.Menu`.
5
+ *
6
+ * Accepts the AUTHORED form as well as the normalised one, so a JSX attribute needs no
7
+ * conversion step of its own: `normalizeMenuModel` is idempotent, which is what makes
8
+ * the two safe to take through one door.
9
+ */
10
+ export declare function buildGioMenu(input: AdwMenuInput | AdwMenuModel): Gio.Menu;
11
+ /**
12
+ * Whether a value is a portable menu model rather than a `GMenuModel`.
13
+ *
14
+ * An ARRAY, and nothing else. `Gio.MenuModel` is a GObject and never an array, so the
15
+ * test cannot confuse the two — and a caller passing a real `Gio.Menu` (the imperative
16
+ * path every existing GJS application uses) keeps passing it straight through.
17
+ */
18
+ export declare const isPortableMenu: (value: unknown) => value is AdwMenuInput;
19
+ /**
20
+ * A `GMenuModel` back as a portable model — the inverse of {@link buildGioMenu}.
21
+ *
22
+ * IT EXISTS TO MAKE THE LOSSLESS CLAIM MEASURABLE. ADR 0042 says the model maps onto
23
+ * `Gio.Menu` with nothing left over; a builder alone cannot show that, because whatever
24
+ * it drops is dropped in silence. `conformance/menu.ts` drives every vector through
25
+ * `buildGioMenu` and back through this function and compares, so a forgotten attribute
26
+ * fails a test naming it instead of disappearing from a popup nobody re-opened.
27
+ *
28
+ * It reads what GTK reads plus `id`, so an application's OWN extra attributes do not
29
+ * survive the round trip — stated rather than implied: this is not a general
30
+ * `GMenuModel` serialiser, it is the inverse of the builder above.
31
+ *
32
+ * TWO EDGES A HAND-BUILT `Gio.Menu` CAN REACH AND THE MODEL CANNOT, both matching GTK
33
+ * rather than diverging from it:
34
+ *
35
+ * · an item carrying BOTH links reads as a SECTION and its submenu is not walked.
36
+ * `GtkMenuTracker` decides the same way (`gtkmenutracker.c:317-352`): it tests
37
+ * `G_MENU_LINK_SECTION` first and
38
+ * recurses, so the submenu link is unreachable there too.
39
+ * · a non-string `label` reads as `''`. `attr()` asks for type `s`; so does
40
+ * `g_menu_item_get_attribute (item, "label", "&s", …)`, which fails the same way and
41
+ * leaves GTK drawing an unlabelled row.
42
+ */
43
+ export declare function fromGioMenu(model: Gio.MenuModel): AdwMenuModel;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -1,5 +1,6 @@
1
+ import GObject from 'gi://GObject?version=2.0';
1
2
  import Gtk from 'gi://Gtk?version=4.0';
2
- import type { ChildPolicy, HostElement } from './types.js';
3
+ import type { ChildPolicy, HostElement, NodePlacement, WidgetDescriptor } from './types.js';
3
4
  /** The object the PARENT addresses: a wrapper row when the policy demands one. */
4
5
  export declare function addressOf(el: HostElement): Gtk.Widget;
5
6
  /**
@@ -86,6 +87,245 @@ export declare function setterSlots(policy: ChildPolicy): string[];
86
87
  * occupant is the widget they handed over.
87
88
  */
88
89
  export declare function slotOccupant(widget: Gtk.Widget, setter: string): Gtk.Widget | null | undefined;
90
+ /** A placement that does NOT put the node into its parent's child list. */
91
+ export type OutsideParent = Exclude<NodePlacement, {
92
+ kind: 'parented';
93
+ }>;
94
+ /**
95
+ * The declared placement of a node. The ONE place absence is turned into a value.
96
+ *
97
+ * Everything downstream switches on the union rather than on `descriptor.placement
98
+ * !== undefined`, so the absent case is a member with a name instead of a
99
+ * falsiness test that a third kind would quietly join.
100
+ */
101
+ export declare const placementOf: (descriptor: WidgetDescriptor) => NodePlacement;
102
+ /**
103
+ * The placement, when it is NOT `parented` — the narrow question every caller asks.
104
+ *
105
+ * Returning the arm rather than a boolean is what keeps the two non-parented kinds
106
+ * ONE question: a caller switches over what it gets and its `never` arm fails to
107
+ * compile the day a fourth kind arrives.
108
+ */
109
+ export declare function outsideParentOf(descriptor: WidgetDescriptor): OutsideParent | null;
110
+ /** The portal arm specifically, or null. */
111
+ export declare function portalOf(descriptor: WidgetDescriptor): Extract<NodePlacement, {
112
+ kind: 'portal';
113
+ }> | null;
114
+ /** `unhandledPolicy`'s twin for the placement axis, and it exists for the same reason. */
115
+ export declare function unhandledPlacement(placement: never): never;
116
+ /** Is this node placed against its parent rather than into it? */
117
+ export declare const isPortal: (el: HostElement) => boolean;
118
+ /**
119
+ * Does this node take NO position in its parent's child list?
120
+ *
121
+ * The question four sibling walks ask, and it is the placement AXIS rather than
122
+ * the portal arm: a toplevel occupies a parent's child list exactly as little as a
123
+ * portal does. Asking `isPortal` there was correct while `portal` was the only
124
+ * non-parented kind and became a silent hole the moment it was not — counting a
125
+ * toplevel as a sibling shifts every later child by one and hands
126
+ * `insert_child_after` a widget that is not in the container (a critical at exit 0),
127
+ * and rotating one calls the parent's adder on a node that must never enter it.
128
+ */
129
+ export declare const isUnparented: (el: HostElement) => boolean;
130
+ /**
131
+ * Show a portal node against its parent — or subscribe and wait, if it is too early.
132
+ *
133
+ * Returns whether GTK has actually taken the node, which is what `attached` means.
134
+ *
135
+ * WHY THE WAIT IS THE FEATURE. Every framework builds bottom-up: React creates the
136
+ * whole subtree, appends its children, and inserts the ROOT into the container
137
+ * last, so at the moment a `<Modal>` is inserted its parent is usually not in a
138
+ * window yet. MEASURED on libadwaita 1.9.3 / GTK 4.22.4, presenting against an
139
+ * unrooted box: `adw_dialog_present` finds no `AdwDialogHost` among the parent's
140
+ * ancestors and takes its documented other branch, `present_as_window` — the
141
+ * dialog opens as a SEPARATE `GtkWindow`, `win.visibleDialog` stays false, exit 0,
142
+ * no diagnostic. A modal that floats out of its own application is exactly the
143
+ * green-and-wrong this host exists to refuse, and nothing in the shadow tree can
144
+ * see it.
145
+ *
146
+ * `notify::root` is the instrument. MEASURED: it fires on a GRANDCHILD box when
147
+ * the toplevel takes the subtree (root -> AdwWindow), and again on unroot (root ->
148
+ * null). The subscription STAYS for the life of the attachment rather than being
149
+ * one-shot, because re-rooting is real: measured, unrooting the parent leaves an
150
+ * already-presented dialog in the OLD window's host — `w1.visibleDialog` still
151
+ * true after `w1.set_content(null)` — so a subtree moved to a second window would
152
+ * silently keep showing its modal in the first.
153
+ *
154
+ * SYMMETRIC, and the second direction is not free. GTK does not take the dialog
155
+ * down when the anchor loses its window, so losing a toplevel RETRACTS the node
156
+ * rather than merely failing to present it (see `placeAgainst`). Without that, a
157
+ * subtree that is detached and never re-rooted keeps its sheet on screen in the
158
+ * window it left, and only a re-root — which such a subtree never gets — repairs it.
159
+ */
160
+ export declare function presentPortal(parent: HostElement, child: HostElement, portal: Extract<NodePlacement, {
161
+ kind: 'portal';
162
+ }>): boolean;
163
+ /**
164
+ * Take a portal node back down, with no probe and no `attached` guard.
165
+ *
166
+ * UNCONDITIONALLY, twice over, and both halves are measured. The method the
167
+ * descriptor names is the FORCED close (`force_close`, not `close`): an unmount is
168
+ * not a user request, and `close()` on a dialog whose `can-close` is FALSE returns
169
+ * FALSE, emits `close-attempt` and leaves it on screen. And `force_close()` on a
170
+ * node that was never presented is silent, where `close()` is
171
+ * `Adwaita-CRITICAL **: Trying to close … that's not presented` at exit 0 — so the
172
+ * host needs no "is it up?" question, which is the one it could not answer without
173
+ * knowing what a dialog is.
174
+ */
175
+ export declare function retractPortal(child: HostElement, portal: Extract<NodePlacement, {
176
+ kind: 'portal';
177
+ }>): void;
178
+ /**
179
+ * Show a toplevel node — and it needs nothing from its parent, which is the point.
180
+ *
181
+ * NO ANCHOR, NO WAIT, NO SUBSCRIPTION. A portal has two positions in the tree and
182
+ * defers until the parent supplies the second one; a toplevel HAS no second
183
+ * position, so the whole `notify::root` machinery next door has nothing to watch
184
+ * for. Measured: `gtk_window_present` takes 0 arguments where
185
+ * `adw_dialog_present` takes 1, and that arity is exactly the difference.
186
+ *
187
+ * Returns whether GTK has taken the node — always true once there is a widget,
188
+ * because a root is taken by nobody: measured, a presented window answers
189
+ * `get_parent() === null` and `get_root() === itself`. That is not the portal's
190
+ * "claimed but not yet taken" state, it is the finished one.
191
+ *
192
+ * AN AUTHORED `visible: false` IS HONOURED rather than overwritten. `present()`
193
+ * sets the window visible (measured), so presenting unconditionally would make
194
+ * `<gtk-window visible={false}>` a property this host silently reverses — the
195
+ * exact shape it exists to refuse. The node is still `attached`: GTK holds it as
196
+ * its own root whether or not it is on screen, and a later `setProp(el,
197
+ * 'visible', true)` shows it through the ordinary property path.
198
+ */
199
+ export declare function presentToplevel(child: HostElement, placement: Extract<NodePlacement, {
200
+ kind: 'toplevel';
201
+ }>): boolean;
202
+ /**
203
+ * Take a toplevel back down — the FORCED call, and it is TERMINAL.
204
+ *
205
+ * `destroy` and not `close`, measured on GTK 4.22.4: `gtk_window_close()` emits
206
+ * `close-request`, and an application handler returning TRUE leaves the window
207
+ * mapped and visible. That veto is how an application asks the USER to confirm,
208
+ * and an unmount is not a user request — the same reasoning that makes the portal
209
+ * arm name `force_close`.
210
+ *
211
+ * Unconditional for the same reason too: measured, `destroy()` on a window that
212
+ * was never presented is silent, so no "is it up?" probe is needed. What differs
213
+ * from the portal is that there IS no way back — measured, `present()` after a
214
+ * destroy (or after a `close()`, whose default handler destroys) answers
215
+ * `Gtk-WARNING **: A window is shown after it has been destroyed`.
216
+ *
217
+ * SO THIS IS `destroy`'s CALL AND NOT `remove`'s, which is the whole reason
218
+ * `detachToplevel` exists next door. `remove` documents itself as reversible.
219
+ */
220
+ export declare function closeToplevel(child: HostElement, placement: Extract<NodePlacement, {
221
+ kind: 'toplevel';
222
+ }>): void;
223
+ /**
224
+ * Take a toplevel OFF SCREEN, reversibly — `remove`'s call.
225
+ *
226
+ * `remove` is documented as a detach that a later `insert` undoes ("Frameworks
227
+ * move nodes; `remove` must not destroy one"), and Solid takes it literally: its
228
+ * `removeNode` calls `remove(node)` for a move and reaches `destroy` only through
229
+ * the root disposer. Running the declared, TERMINAL close there made a move
230
+ * `destroy()` the window and then present the corpse —
231
+ * `Gtk-WARNING **: A window is shown after it has been destroyed`, at exit 0,
232
+ * visible only because `installDiagnosticsGate()` was watching.
233
+ *
234
+ * A PROPERTY WRITE AND NOT A DECLARED METHOD, and that is the honest shape rather
235
+ * than a shortcut: a window's presence on screen IS its `visible` property — which
236
+ * is why `presentToplevel` already reads it — so every toplevel detaches the same
237
+ * way and a per-row name would be the same string once per declared row. The portal
238
+ * arm needs no counterpart at all, because its declared close is ALREADY reversible.
239
+ *
240
+ * MEASURED on GTK 4.22.4, one window, in order: `set_visible(false)` leaves it
241
+ * `visible` false with one `unmap`, emits NO `close-request`, and keeps it in
242
+ * `Gtk.Window.list_toplevels()` — so it is a detach and not a teardown; `present()`
243
+ * afterwards maps it again with no diagnostic at all; a second `set_visible(false)`
244
+ * is a no-op; and `destroy()` on the hidden window adds no `unmap` and DOES drop it
245
+ * from the toplevel list. `set_visible` rather than the deprecated `hide()`, which
246
+ * is the same call one rename older.
247
+ *
248
+ * Unbracketed, unlike `writeVisible`'s host writes: this is not bookkeeping around
249
+ * a libadwaita defect that the consumer's model never mentions — the window really
250
+ * did leave the screen, and a bound `notify::visible` is entitled to hear it.
251
+ */
252
+ export declare function detachToplevel(child: HostElement): void;
253
+ /**
254
+ * Place a node that does NOT go into its parent's child list, and say whether GTK took it.
255
+ *
256
+ * The ONE dispatch over the non-parented arms. Every caller in `host.ts` reaches
257
+ * the axis through this and its two twins below, so a fourth placement kind is
258
+ * three `never` arms away from compiling rather than a search through the host.
259
+ */
260
+ export declare function placeOutsideParent(parent: HostElement, child: HostElement, placement: OutsideParent): boolean;
261
+ /**
262
+ * The REVERSIBLE take-down — what `remove` means, per arm.
263
+ *
264
+ * THE TWO ARMS DIFFER HERE AND NOWHERE ELSE, which is why one function could not
265
+ * serve both: measured, `force_close()` followed by `present(parent)` re-hosts a
266
+ * dialog against the SAME parent with no diagnostic, while `destroy()` followed by
267
+ * `present()` is `Gtk-WARNING **: A window is shown after it has been destroyed`.
268
+ * The portal arm therefore never showed this defect and the toplevel arm shipped
269
+ * with it: a single `retractOutsideParent` treated a reversible verb and a terminal
270
+ * one as the same thing.
271
+ */
272
+ export declare function detachOutsideParent(child: HostElement, placement: OutsideParent): void;
273
+ /**
274
+ * The TERMINAL take-down — what `destroy` means, and what a DISCARD means.
275
+ *
276
+ * Its one caller is `releaseWidget` in `host.ts`, which is every place the host
277
+ * drops `el.widget`: a toplevel is held by GTK's own list rather than by a parent
278
+ * (measured, `list_toplevels()` holds a window from CONSTRUCTION and loses it only
279
+ * on `destroy`), so a discard that merely detached would leak one window each time
280
+ * — per construct-only write, and per half-built element a failed replay rolls back.
281
+ */
282
+ export declare function closeOutsideParent(child: HostElement, placement: OutsideParent): void;
283
+ /**
284
+ * The placement the installed libraries give this class, or null for an ordinary child.
285
+ *
286
+ * STRUCTURAL, never a name. ADR 0027 rule 1 forbids widget knowledge in the host,
287
+ * and a list of gtypes here would be exactly that — so the class is asked two
288
+ * questions it answers itself. Both were measured over the WHOLE shipped table on
289
+ * GTK 4.22.4 / libadwaita 1.9.3 rather than over a sample, which is what makes the
290
+ * second one a partition and not an observation:
291
+ *
292
+ * - `Gtk.Root` is GTK's OWN word for "this widget is a toplevel", and every class
293
+ * in the table implementing it — `GtkWindow` through `GtkPrintUnixDialog` — is
294
+ * one no parent may legally take.
295
+ * - A `present()` that TAKES AN ARGUMENT is a node presented AGAINST something.
296
+ * In the table that set is EXACTLY the `Adw.Dialog` descendants and nothing
297
+ * else — the ones whose `root` vfunc calls `g_error()`.
298
+ * `Gtk.Popover.present()` is the discriminator that makes the ARITY part of the
299
+ * question rather than the name: it exists, it takes 0 arguments, and a popover
300
+ * is parented with `set_parent()` like any other child.
301
+ *
302
+ * The order matters and is not alphabetical: every `Gtk.Root` in the table that has
303
+ * a `present` at all has a 0-argument one, and `GtkDragIcon` has none — so the root
304
+ * test has to come first and cannot be replaced by an arity test.
305
+ *
306
+ * @param gtype the class's GType — `type_is_a` walks interfaces as well as parents
307
+ * @param present the class's own `present`, or the value found on an instance
308
+ */
309
+ export declare function classPlacementKind(gtype: GObject.GType, present: unknown): OutsideParent['kind'] | null;
310
+ /** `classPlacementKind` for a live widget, which is what the insert path holds. */
311
+ export declare function nodePlacementKind(node: Gtk.Widget): OutsideParent['kind'] | null;
312
+ /**
313
+ * Refuse a child the installed libraries will not adopt, BEFORE the adder runs.
314
+ *
315
+ * This is the catchable half of the abort class (ADR 0054). `descriptorProblems()`
316
+ * says the same thing about the whole TABLE up front, which is what keeps the
317
+ * shipped rows honest — but `registerWidget` takes descriptors from applications
318
+ * and nothing checks those, and for one of the two families the consequence is not
319
+ * a wrong window: `adw_dialog_root()` is `g_error()`, so the process is gone before
320
+ * any handler runs. A refusal is only possible while there is still a process.
321
+ *
322
+ * ONLY WHERE THE DESCRIPTOR IS SILENT, and that is the escape hatch rather than an
323
+ * oversight. `placement` present — including an explicit `{ kind: 'parented' }` —
324
+ * means the author has answered this question, and the host does not overrule an
325
+ * answer with a heuristic. A consumer widget that trips the `present(parent)` half
326
+ * of the oracle while really being a child says so in one line.
327
+ */
328
+ export declare function refuseUnparentable(parent: HostElement, child: HostElement): void;
89
329
  export interface Placement {
90
330
  parent: HostElement;
91
331
  child: HostElement;
@@ -33,7 +33,7 @@
33
33
  */
34
34
  import type { Key, ReactElement, ReactNode, Ref } from 'react';
35
35
  import type { WidgetClassByTag, WidgetPropsByTag } from './generated/props.js';
36
- import type { RawSignalAttributes, SlotAttribute, WithOnce } from './attrs.js';
36
+ import type { RawSignalAttributes, SlotAttribute, WithOnce, WithPortableValues } from './attrs.js';
37
37
  export { Fragment } from 'react/jsx-runtime';
38
38
  /**
39
39
  * React's own factories, RE-TYPED over this package's element list.
@@ -74,7 +74,7 @@ export interface ReactWidgetAttributes<T> extends SlotAttribute, RawSignalAttrib
74
74
  }
75
75
  /** Every GTK/Adwaita tag, with its properties, its handlers, its `ref` and its `key`. */
76
76
  export type GtkReactIntrinsicElements = {
77
- [K in keyof WidgetPropsByTag]: WithOnce<WidgetPropsByTag[K]> & ReactWidgetAttributes<WidgetClassByTag[K]>;
77
+ [K in keyof WidgetPropsByTag]: WithOnce<WithPortableValues<WidgetPropsByTag[K]>> & ReactWidgetAttributes<WidgetClassByTag[K]>;
78
78
  };
79
79
  export declare namespace JSX {
80
80
  /**
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A `font-family` value → the text that goes into the declaration.
3
+ *
4
+ * Exported because a consumer that has been quoting defensively (see the incident in
5
+ * the header) can call this instead and stop, and because both routes into the
6
+ * partition — a `style={{ fontFamily }}` object and a `font-*` utility resolving
7
+ * against `tokens.fontFamily` — must go through exactly this one function.
8
+ */
9
+ export declare function serialiseFontFamily(value: string): string;
10
+ /**
11
+ * What GTK does with an authored value that was NOT serialised.
12
+ *
13
+ * Three states rather than a boolean, and the third one is why. `refused` is the
14
+ * loud bug — GTK rejects the declaration, the containment probe rejects the rule,
15
+ * and the screen is gone. `misread` is the QUIET one: GTK accepts the bare text and
16
+ * resolves a DIFFERENT family, so the application renders in a substituted font with
17
+ * no diagnostic anywhere. A boolean column would have merged `misread` into
18
+ * `accepted` and asserted that serialising changes nothing — which is false for
19
+ * exactly the vectors where changing something is the whole point.
20
+ */
21
+ export type BareReading = 'refused' | 'accepted' | 'misread';
22
+ /** One authored `font-family` value, what it must become, and what GTK does with it unserialised. */
23
+ export interface FontFamilyVector {
24
+ /** What a `tokens.fontFamily` entry or a `style={{ fontFamily }}` carries. */
25
+ readonly authored: string;
26
+ /** What {@link serialiseFontFamily} must emit for it. */
27
+ readonly emitted: string;
28
+ /**
29
+ * What GTK does with `authored` AS IT STANDS.
30
+ *
31
+ * The column that makes this table a test rather than a restatement, and every
32
+ * state of it is RE-MEASURED against the running GTK by `gtk-css.spec.ts`:
33
+ * `refused` must be rejected, `accepted` must parse to the same computed value
34
+ * as `emitted`, and `misread` must parse to a DIFFERENT one. So the state is a
35
+ * finding and not a claim, and a vector cannot quietly stop exercising the rule.
36
+ */
37
+ readonly bare: BareReading;
38
+ }
39
+ /**
40
+ * The conformance set for `font-family` values.
41
+ *
42
+ * WHY THIS TABLE EXISTS, and it is the whole finding behind #1539. `gtk-css.ts`
43
+ * probed `['font-family', 'Cantarell']` — ONE word, valid as a bare identifier, and
44
+ * therefore the single family for which the missing quoting is invisible. The suite
45
+ * asserted that `font-family` is emitted and never that it is emitted CORRECTLY, so
46
+ * it would have gone green against an implementation that quoted nothing. A vector
47
+ * whose value cannot exercise the rule reads exactly like a vector that passed.
48
+ *
49
+ * So the set carries, deliberately: a family that NEEDS quoting, a family that does
50
+ * not, a bare multi-identifier family GTK already accepted (the case a
51
+ * quote-on-space reading would call broken), a stack mixing a quoted family with a
52
+ * bare keyword, a value already quoted, a `var()` reference, and a value GTK MISREADS
53
+ * rather than refuses. `bare` is re-measured by `gtk-css.spec.ts`, which also refuses
54
+ * a table that has lost its refused or its misread vectors.
55
+ *
56
+ * The three added after the first review are the shapes the SPLIT and the two
57
+ * PASSTHROUGHS have to survive rather than the quoter: a bare name carrying an odd
58
+ * quote character in front of a fallback, a nested `var()`, and an already-quoted
59
+ * member holding a carriage return. Each was emitted as something GTK accepted and
60
+ * was wrong about — so `gtk-css.spec.ts` also holds the three CLASSES directly, by
61
+ * asking GTK where the fallback member ended up, whether the document survived, and
62
+ * whether a declaration appeared that nothing here wrote.
63
+ */
64
+ export declare const FONT_FAMILY_VECTORS: readonly FontFamilyVector[];
@@ -3,8 +3,10 @@ export { GTK_WIDGET_PROPERTIES, GTK_WIDGET_PROPERTY_PROBES, NOT_GTK_WIDGET_PROPE
3
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
- export { PAINT_PROPERTIES, partitionPaint, resolvePaintUtility } from './paint.js';
6
+ export { CSS_VALUE, CSS_VALUE_KIND, PAINT_PROPERTIES, partitionPaint, resolvePaintUtility } from './paint.js';
7
7
  export type { PaintProps } from './paint.js';
8
+ export { FONT_FAMILY_VECTORS, serialiseFontFamily } from './font-family.js';
9
+ export type { FontFamilyVector } from './font-family.js';
8
10
  export { LAYOUT_PROPERTIES, partitionLayout, resolveLayoutUtility } from './layout.js';
9
11
  export type { AlignValue, Edge, JustifyValue, LayoutIntent, LayoutPartition, LayoutProps, OverlayIntent, WrapIntent, } from './layout.js';
10
12
  export { partition, resolveUtilities, resolveUtility } from './resolve.js';
@@ -32,6 +32,38 @@ export interface PaintProps {
32
32
  * `partition`'s per-call guard, which no input can reach while the table is right.
33
33
  */
34
34
  export declare const CSS_NAME: Readonly<Record<keyof PaintProps, string>>;
35
+ /**
36
+ * What KIND of value each property carries — and therefore whether that value can
37
+ * end the declaration early.
38
+ *
39
+ * DECLARED, not derived, because it is a judgement about a vocabulary rather than
40
+ * something the code can compute. It is here so the judgement is machine-held: the
41
+ * key set is `keyof PaintProps`, so a new property cannot skip it, and
42
+ * `paint.spec.ts` asserts that the `name`-valued properties are EXACTLY the ones
43
+ * {@link CSS_VALUE} serialises. A colour, a length, a number and a keyword are all
44
+ * self-delimiting; a `name` is supplied verbatim by the project and is the only kind
45
+ * that can carry a space, a comma or a quote.
46
+ *
47
+ * The measured answer today is that `font-family` is the only one — checked against
48
+ * the layout half too, which emits margins and paddings and nothing else. The next
49
+ * candidate is not hypothetical: `background-image: url(…)` would be the second.
50
+ */
51
+ export declare const CSS_VALUE_KIND: Readonly<Record<keyof PaintProps, 'colour' | 'length' | 'number' | 'keyword' | 'name'>>;
52
+ /**
53
+ * The properties whose value is SERIALISED rather than interpolated.
54
+ *
55
+ * PER PROPERTY, and that is the design rather than an accident of there being one
56
+ * entry: a global rule would have to decide what a comma or a space means without
57
+ * knowing which property it is in, and it means something different in every one.
58
+ * The table is the seam a second such property joins at, and
59
+ * {@link CSS_VALUE_KIND} is what makes joining it compulsory.
60
+ *
61
+ * Both routes to a `font-family` — a `style={{ fontFamily }}` object and a `font-*`
62
+ * utility resolving against `tokens.fontFamily` — arrive at `partitionPaint`, so
63
+ * one entry here covers both. That is why the serialisation lives at the emitter
64
+ * and not at either front end.
65
+ */
66
+ export declare const CSS_VALUE: Readonly<Partial<Record<keyof PaintProps, (value: string) => string>>>;
35
67
  /**
36
68
  * One utility class → the paint properties it sets, or `null` for "not mine".
37
69
  *
@@ -49,5 +81,10 @@ export declare const PAINT_PROPERTIES: ReadonlySet<string>;
49
81
  * GTK's parser with no diagnostic, which is the exact silence the whole partition
50
82
  * exists to avoid. The check is cheap and it has caught the one property that looks
51
83
  * like paint and is not.
84
+ *
85
+ * The VALUE gets the same treatment, per property, through {@link CSS_VALUE}. A
86
+ * checked name with an unserialised value is only half the guard: GTK refused the
87
+ * whole rule for `font-family: Source Sans 3` while the property name was right
88
+ * (#1539), and the value is the half a project supplies verbatim.
52
89
  */
53
90
  export declare function partitionPaint(props: PaintProps): readonly string[];
@@ -0,0 +1,2 @@
1
+ /** The refusal code `fn` raises, or what it did instead — so a wrong refusal reads as one. */
2
+ export declare function codeOf(fn: () => unknown): string;