@gjsify/adwaita-core 0.46.0 → 0.48.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.
@@ -0,0 +1,198 @@
1
+ /**
2
+ * A normalised portable adjustment — what an `adjustment` property holds on every surface.
3
+ *
4
+ * The six numbers of `Gtk.Adjustment`, under its own field names. Every one is finite, and
5
+ * the invariants {@link normalizeAdjustment} establishes hold: `upper >= lower`,
6
+ * `pageSize >= 0`, `stepIncrement > 0`, and `value` inside {@link adjustmentRange}.
7
+ */
8
+ export interface AdwAdjustment {
9
+ /** The current value, within `[lower, upper - pageSize]`. */
10
+ value: number;
11
+ /** The minimum value. */
12
+ lower: number;
13
+ /** The maximum value — reduced by {@link AdwAdjustment.pageSize} for what a value may reach. */
14
+ upper: number;
15
+ /** How far one stepper press moves the value. */
16
+ stepIncrement: number;
17
+ /** How far one page press (Page Up/Down) moves the value. */
18
+ pageIncrement: number;
19
+ /** The size of the visible page; zero for a scalar value such as a spin row's. */
20
+ pageSize: number;
21
+ }
22
+ /**
23
+ * What an `adjustment` property ACCEPTS: any subset of {@link AdwAdjustment}, with the rest
24
+ * taken from {@link ADW_ADJUSTMENT_DEFAULTS} (or, for {@link SpinState.configure}, from what
25
+ * the adjustment already holds).
26
+ *
27
+ * A bare number is NOT accepted, and the omission is deliberate: an adjustment addressed by
28
+ * one number would be its `value`, and `value` is a property of its own on every widget that
29
+ * takes an adjustment. Accepting it here would make `adjustment={3}` and `value={3}` two
30
+ * spellings of one write.
31
+ */
32
+ export type AdwAdjustmentInput = Partial<AdwAdjustment>;
33
+ /**
34
+ * The range every surface falls back to, and the one figure in this module that is OURS
35
+ * rather than GTK's — see the file header for why 0…100 step 1 rather than GTK's 0…0 step 0.
36
+ */
37
+ export declare const ADW_ADJUSTMENT_DEFAULTS: AdwAdjustment;
38
+ /**
39
+ * The closed interval a value may occupy: `[lower, upper - pageSize]`.
40
+ *
41
+ * The upper end is `set_value`'s documented "effective range … from lower to
42
+ * upper - page_size", floored at `lower` so a page larger than the range yields the single
43
+ * point `lower` rather than an inverted interval.
44
+ */
45
+ export declare function adjustmentRange(adjustment: AdwAdjustment): [number, number];
46
+ /**
47
+ * `value` clamped into {@link adjustmentRange}.
48
+ *
49
+ * The two non-finite inputs are NOT one case, and separating them is a repair rather than a
50
+ * refinement. `±Infinity` carries a DIRECTION, so it clamps to the bound it is heading for,
51
+ * which is what `set_value`'s "clamped to lie between lower and upper" says. `NaN` carries
52
+ * none and lands on the lower bound. The `SpinState` this replaces coerced both to 0 before
53
+ * clamping, which is only harmless while 0 is inside the range: on `[-5, -1]` it turned a
54
+ * `NaN` write into `-1`, the MAXIMUM. `adjustment.spec.ts` holds that case.
55
+ */
56
+ export declare function clampAdjustmentValue(adjustment: AdwAdjustment, value: number): number;
57
+ /**
58
+ * `value` clamped and then moved to the nearest tick — the arithmetic behind
59
+ * `snap-to-ticks`.
60
+ *
61
+ * `AdwSpinRow:snap-to-ticks` (and `GtkSpinButton`'s before it) is documented as "whether
62
+ * erroneous values are automatically changed to the nearest step", and the ticks are the
63
+ * step grid counted FROM THE LOWER BOUND — a range of `[1, 10]` with a step of 3 has ticks
64
+ * at 1, 4, 7 and 10, not at 3, 6, 9.
65
+ *
66
+ * THE TICK INDEX IS WHAT IS CLAMPED, not the value it produces, and the difference is a
67
+ * whole tick wide. Clamping the value lands on the upper BOUND whenever that bound is not
68
+ * itself a tick — `[0, 10]` step 4 answered `10`, which is off the grid `0, 4, 8` by two —
69
+ * so the function promised the nearest step and returned something that was not a step at
70
+ * all. An upper bound off the grid is simply not reachable by snapping; that is what
71
+ * snapping to ticks means. The slider row's own `_snap` had the same defect before this
72
+ * arithmetic had a name (`Math.min(this._max, …)`), so this is a repair and not a port.
73
+ *
74
+ * It is a function rather than a mode on {@link SpinState} because only one renderer needs
75
+ * it: a stepper moves BY the step and can never land between ticks, while a dragged slider
76
+ * lands wherever the finger left it.
77
+ */
78
+ export declare function snapAdjustmentValue(adjustment: AdwAdjustment, value: number): number;
79
+ /**
80
+ * Fill an authored adjustment out to a whole one.
81
+ *
82
+ * Total: every field falls back to `base` (the defaults unless a caller passes what it
83
+ * already holds), and the invariants are established rather than assumed — `upper` cannot
84
+ * sit below `lower`, `pageSize` cannot be negative, `stepIncrement` cannot be zero (a step
85
+ * of zero is a stepper that cannot move, which is GTK's default and not a usable one), and
86
+ * `value` is clamped last, against the range the other five just described.
87
+ */
88
+ export declare function normalizeAdjustment(input?: AdwAdjustmentInput | null, base?: AdwAdjustment): AdwAdjustment;
89
+ /**
90
+ * The MARKUP door: a JSON attribute → the fields it authored.
91
+ *
92
+ * It yields a PARTIAL and not a whole adjustment, and that is what makes attribute order
93
+ * irrelevant: `adjustment='{"upper":20}'` moves the upper bound and leaves the value where
94
+ * it is, whichever attribute the parser reached first. A door that answered with a whole
95
+ * adjustment would carry `value: 0` into every such write and silently reset the row —
96
+ * the ordering hazard `spin-row.gtk.tsx` records for the GTK renderer, arriving through
97
+ * markup instead of through props.
98
+ *
99
+ * Total in every failure mode, and they are all an author slip rather than a state the
100
+ * element may refuse to upgrade in: absent, unparseable, and well-formed JSON that is not an
101
+ * object (an array, a number, `null`) each yield `{}` — no field authored, nothing moves.
102
+ * The same door `parseListModel` and `parseMenuModel` open for their values, for the same
103
+ * reason and on the same surfaces.
104
+ *
105
+ * A key that is not one of the six is DROPPED rather than carried into the state, so a typo
106
+ * (`{"min":1}`) does not travel as a property the value does not have.
107
+ */
108
+ export declare function parseAdjustment(raw: string | null | undefined): AdwAdjustmentInput;
109
+ /** Payload of a {@link SpinState} value change. */
110
+ export interface SpinStateChange {
111
+ /** The new value (already clamped into {@link adjustmentRange}). */
112
+ value: number;
113
+ /** True for a stepper press ({@link SpinState.increment}/{@link SpinState.decrement}); false for a programmatic set or a re-clamp. */
114
+ interactive: boolean;
115
+ }
116
+ /** Subscriber for {@link SpinState} value changes — `Gtk.Adjustment::value-changed`. */
117
+ export type SpinStateListener = (change: SpinStateChange) => void;
118
+ /** Subscriber for {@link SpinState} RANGE changes — `Gtk.Adjustment::changed`. */
119
+ export type SpinStateRangeListener = (adjustment: AdwAdjustment) => void;
120
+ /**
121
+ * An adjustment that can be watched — the portable `Gtk.Adjustment` OBJECT to
122
+ * {@link AdwAdjustment}'s value.
123
+ *
124
+ * It keeps the two signals apart exactly as GTK does, because a renderer needs them apart:
125
+ * `value-changed` redraws a label, `changed` re-sizes a slider's track. {@link subscribe}
126
+ * is the first, {@link subscribeChanged} the second, and {@link configure} may emit both.
127
+ *
128
+ * The `interactive` flag on the value signal is OURS and has no GTK counterpart, because on
129
+ * GTK the two callers are different objects: the widget writes the adjustment on a stepper
130
+ * press and the application writes it programmatically, and a renderer here is both. It
131
+ * mirrors `Adw.SpinRow`'s observable behaviour — a programmatic set refreshes the display
132
+ * without the row re-emitting `notify::value`, a stepper press emits.
133
+ *
134
+ * WHAT IT DOES NOT HAVE is GTK's five individual setters (`set_lower`, `set_upper`, …).
135
+ * They exist in C because there is no record literal there; here the whole range arrives as
136
+ * one value, and `gtk_adjustment_configure`'s own documented reason for existing — it
137
+ * compresses the notifications "into one" — is the shape this class already has.
138
+ */
139
+ export declare class SpinState {
140
+ private _adjustment;
141
+ /**
142
+ * Whether a value has ever been WRITTEN — by a setter, a stepper, or a `configure`
143
+ * carrying one.
144
+ *
145
+ * Until one has, the value is not a value: it is wherever the bottom of the range is,
146
+ * and it follows a bound that moves. After one has, a moved bound RE-CLAMPS it, which
147
+ * is the documented behaviour and a different thing. Without the flag the two are
148
+ * indistinguishable, and a fresh row configured to `[-100, -50]` opened at -50 — the
149
+ * maximum, from an author who wrote no value at all.
150
+ *
151
+ * A WRITE PLACES THE VALUE EVEN WHEN THE NUMBER DOES NOT MOVE: `setValue(0)` on a row
152
+ * already at 0 has still said where the value belongs, and a later range move re-clamps
153
+ * it rather than re-seeding it. The two exceptions are the two writes that say nothing —
154
+ * a non-finite value, and a stepper press at the end of the range that moves nothing.
155
+ */
156
+ private _valueSet;
157
+ private readonly _listeners;
158
+ private readonly _rangeListeners;
159
+ /** Subscribe to value changes (`value-changed`). Returns an unsubscribe function. */
160
+ subscribe(listener: SpinStateListener): () => void;
161
+ /** Subscribe to range changes (`changed`). Returns an unsubscribe function. */
162
+ subscribeChanged(listener: SpinStateRangeListener): () => void;
163
+ private _emit;
164
+ private _emitChanged;
165
+ private _bump;
166
+ /** The whole adjustment. A snapshot: mutating it does not reach this object. */
167
+ get adjustment(): AdwAdjustment;
168
+ /** The current value (always within {@link adjustmentRange}). */
169
+ get value(): number;
170
+ /**
171
+ * Replace the range (and optionally the value) — `gtk_adjustment_configure`.
172
+ *
173
+ * Unwritten fields keep what the adjustment already holds, so a caller may move one
174
+ * bound without restating the rest. Emits `changed` when any of the five range fields
175
+ * moved and `value-changed` when the value did — including a value that moved only
176
+ * because a bound came under it, which notifies `interactive: false`.
177
+ *
178
+ * Returns whether anything changed at all.
179
+ */
180
+ configure(input: AdwAdjustmentInput | null | undefined): boolean;
181
+ /** Programmatic value set — clamp, notify `interactive: false` on change. Returns whether it changed. */
182
+ setValue(value: number): boolean;
183
+ /**
184
+ * The same set, as a USER-driven change — notifies `interactive: true`.
185
+ *
186
+ * {@link increment} and {@link decrement} are the stepper's interactions and move BY the
187
+ * step; this is the DRAGGED one, which lands wherever the finger left it.
188
+ * `ComboState.select` is the same pair on the same reasoning: one method per caller,
189
+ * rather than a flag every caller has to remember to pass.
190
+ *
191
+ * Returns whether it changed.
192
+ */
193
+ setValueInteractive(value: number): boolean;
194
+ /** Step up by `stepIncrement`, clamped. Notifies `interactive: true` on change. Returns whether it changed. */
195
+ increment(): boolean;
196
+ /** Step down by `stepIncrement`, clamped. Notifies `interactive: true` on change. Returns whether it changed. */
197
+ decrement(): boolean;
198
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import type { AdwAdjustment, AdwAdjustmentInput } from '../adjustment.js';
2
+ /** One authored range + value, and where the value lands. */
3
+ export interface AdjustmentAuthoredVector {
4
+ /** What this row pins down. */
5
+ rule: string;
6
+ /** What the author wrote for the range. */
7
+ input: AdwAdjustmentInput;
8
+ /** The whole adjustment it normalises to. */
9
+ adjustment: AdwAdjustment;
10
+ }
11
+ export declare const ADJUSTMENT_AUTHORED_VECTORS: ReadonlyArray<AdjustmentAuthoredVector>;
12
+ /** One JSON attribute, and the fields it authored. */
13
+ export interface AdjustmentParseVector {
14
+ /** What this row pins down. */
15
+ rule: string;
16
+ /** The attribute as written. */
17
+ raw: string | null;
18
+ /** The fields it names — and ONLY those. */
19
+ input: AdwAdjustmentInput;
20
+ }
21
+ export declare const ADJUSTMENT_PARSE_VECTORS: ReadonlyArray<AdjustmentParseVector>;
22
+ /** One dragged value, and the tick it lands on. */
23
+ export interface AdjustmentSnapVector {
24
+ /** What this row pins down. */
25
+ rule: string;
26
+ /** The range being dragged through. */
27
+ input: AdwAdjustmentInput;
28
+ /** Where the drag landed. */
29
+ from: number;
30
+ /** The tick it snaps to. */
31
+ snapped: number;
32
+ }
33
+ export declare const ADJUSTMENT_SNAP_VECTORS: ReadonlyArray<AdjustmentSnapVector>;
@@ -24,8 +24,14 @@ export { ENTRY_MAX_LENGTH_VECTORS, ENTRY_ROW_ACTIVATION_VECTORS, ENTRY_ROW_GUARD
24
24
  export type { EntryMaxLengthVector, EntryRowActivationVector, EntryRowGuardVector, EntryRowStateVector, EntryRowStep, EntryTextLengthVector, PasswordEntryRowStep, PasswordEntryRowVector, PasswordRevealGuardVector, } from './entry-row.js';
25
25
  export { ADW_LENGTH_UNIT_VECTORS, GLIB_CLAMP_VECTORS, NATURAL_SIDEBAR_WIDTH_VECTORS, NAVIGATION_ACTION_VECTORS, NAVIGATION_SPLIT_VIEW_LAYOUT_VECTORS, NAVIGATION_STACK_VECTORS, OVERLAY_COLLAPSE_VECTORS, OVERLAY_SPLIT_VIEW_LAYOUT_VECTORS, OVERLAY_SWIPE_AREA_VECTORS, OVERLAY_SWIPE_CANCEL_VECTORS, OVERLAY_SWIPE_RELEASE_VECTORS, OVERLAY_SWIPE_SNAP_POINT_VECTORS, OVERLAY_SWIPE_START_VECTORS, SIDEBAR_BOUNDS_VECTORS, SIDEBAR_WIDTH_VECTORS, SPLIT_VIEW_MEASURE_VECTORS, TAGS_CONFLICT_VECTORS, } from './split-view.js';
26
26
  export type { AdwLengthUnitVector, GlibClampVector, NaturalSidebarWidthVector, NavigationActionVector, NavigationLayoutVector, NavigationStackVector, OverlayCollapseVector, OverlayLayoutVector, OverlaySplitViewSnapshot, SidebarBoundsVector, SidebarWidthVector, SplitViewMeasureVector, SwipeAreaVector, SwipeCancelVector, SwipeReleaseVector, SwipeSnapPointVector, SwipeStartVector, TagsConflictVector, } from './split-view.js';
27
- export { MENU_BUTTON_DIRECTION_VECTORS, SPLIT_BUTTON_CONTENT_VECTORS, SPLIT_BUTTON_DIRECTION_VECTORS, SPLIT_BUTTON_DROPDOWN_VECTORS, SPLIT_BUTTON_MENU_ACTIVATION_VECTORS, SPLIT_BUTTON_MENU_PARSE_VECTORS, SPLIT_BUTTON_ROOT_STATE_VECTORS, SPLIT_BUTTON_STYLE_CLASS_VECTORS, SPLIT_BUTTON_TOOLTIP_VECTORS, } from './split-button.js';
28
- export type { SplitButtonContentStep, SplitButtonContentVector, SplitButtonDirectionVector, SplitButtonDropdownVector, SplitButtonMenuActivationVector, SplitButtonMenuParseVector, SplitButtonRootStateVector, SplitButtonStyleClassVector, SplitButtonTooltipVector, } from './split-button.js';
27
+ export { MENU_BUTTON_DIRECTION_VECTORS, SPLIT_BUTTON_CONTENT_VECTORS, SPLIT_BUTTON_DIRECTION_VECTORS, SPLIT_BUTTON_DROPDOWN_VECTORS, SPLIT_BUTTON_MENU_ACTIVATION_VECTORS, SPLIT_BUTTON_ROOT_STATE_VECTORS, SPLIT_BUTTON_STYLE_CLASS_VECTORS, SPLIT_BUTTON_TOOLTIP_VECTORS, } from './split-button.js';
28
+ export type { SplitButtonContentStep, SplitButtonContentVector, SplitButtonDirectionVector, SplitButtonDropdownVector, SplitButtonMenuActivationVector, SplitButtonRootStateVector, SplitButtonStyleClassVector, SplitButtonTooltipVector, } from './split-button.js';
29
+ export { ADJUSTMENT_AUTHORED_VECTORS, ADJUSTMENT_PARSE_VECTORS, ADJUSTMENT_SNAP_VECTORS } from './adjustment.js';
30
+ export type { AdjustmentAuthoredVector, AdjustmentParseVector, AdjustmentSnapVector } from './adjustment.js';
31
+ export { LIST_ITEMS_CHANGED_VECTORS, LIST_MODEL_OWNERSHIP_VECTORS, LIST_NORMALIZE_VECTORS, LIST_PARSE_VECTORS, LIST_SELECTION_CLAMP_VECTORS, TAB_PAGES_ITEMS_CHANGED_VECTORS, applyListReadback, replayTabPagesAsSplices, } from './list.js';
32
+ export type { ListItemsChangedVector, ListModelOwnershipVector, ListNormalizeVector, ListParseVector, ListReadbackOp, ListSelectionClampVector, TabPagesItemsChangedVector, } from './list.js';
33
+ export { MENU_DETAILED_ACTION_VECTORS, MENU_FLATTEN_VECTORS, MENU_ITEM_STATE_VECTORS, MENU_NORMALIZE_VECTORS, MENU_PARSE_VECTORS, MENU_REFUSAL_VECTORS, } from './menu.js';
34
+ export type { MenuDetailedActionVector, MenuFlatRowVector, MenuFlattenVector, MenuItemStateVector, MenuNormalizeVector, MenuParseVector, MenuRefusalVector, } from './menu.js';
29
35
  export { INLINE_TOGGLE_VECTORS, INLINE_TOOLTIP_VECTORS, VIEW_SWITCHER_BADGE_VECTORS, VIEW_SWITCHER_BAR_REVEAL_VECTORS, VIEW_SWITCHER_BAR_VECTORS, VIEW_SWITCHER_BUTTON_VECTORS, VIEW_SWITCHER_BUTTON_VISIBILITY_VECTORS, VIEW_SWITCHER_DRAG_VECTORS, VIEW_SWITCHER_ICON_VECTORS, VIEW_SWITCHER_MNEMONIC_VECTORS, VIEW_SWITCHER_REBUILD_VECTORS, VIEW_SWITCHER_SELECTION_VECTORS, createViewSwitcherClock, } from './view-switcher.js';
30
36
  export type { ExpectedInlineToggle, ExpectedViewSwitcherButton, InlineToggleVector, InlineTooltipVector, MnemonicVector, ViewSwitcherBadgeVector, ViewSwitcherBarRevealVector, ViewSwitcherBarSnapshot, ViewSwitcherBarStep, ViewSwitcherBarVector, ViewSwitcherButtonVector, ViewSwitcherButtonVisibilityVector, ViewSwitcherClock, ViewSwitcherDragStep, ViewSwitcherDragVector, ViewSwitcherIconVector, ViewSwitcherRebuildVector, ViewSwitcherSelectionVector, ViewSwitcherVectorChange, ViewSwitcherVectorOp, ViewSwitcherVectorPage, } from './view-switcher.js';
31
37
  export { TABS_REVEALED_VECTORS, TAB_CLOSE_VISIBLE_VECTORS, TAB_DESCENDANT_VECTORS, TAB_ICON_STATE_VECTORS, TAB_PAGE_DESCRIPTOR_VECTORS, TAB_SUCCESSOR_VECTORS, TAB_TOOLTIP_VECTORS, TAB_VIEW_VECTORS, applyTabViewOp, seedTabViewPages, tabViewClosing, tabViewOrder, } from './tab-view.js';
@@ -0,0 +1,171 @@
1
+ import type { AdwComboOption, AdwListItemsChanged, AdwListModel, AdwListModelInput } from '../list.js';
2
+ import type { TabViewPagesChange } from '../tab-view.js';
3
+ /** One {@link normalizeComboOptions} expectation. */
4
+ export interface ListNormalizeVector {
5
+ input: AdwListModelInput;
6
+ model: AdwListModel;
7
+ rule: string;
8
+ }
9
+ /**
10
+ * Authored input → the normalised model.
11
+ *
12
+ * The browser suite drives `LIST_NORMALIZE_VECTORS` through the real `model` property of
13
+ * `<adw-combo-row>` and of `<gtk-drop-down>`, every row of it, and core drives the
14
+ * function.
15
+ *
16
+ * The NativeScript widgets take the same input and NOTHING there asserts it. That
17
+ * package's specs reach `ComboState` and never the widget class, for the reason
18
+ * `drop-down.spec.ts`' own header gives, and the gap is filed in `status/open-todos.md`
19
+ * under "The NativeScript list-model setter is held by core's vectors, not by a widget
20
+ * test". This docblock used to say "every surface that takes a `model` … must answer
21
+ * these rows: … the NativeScript widgets through theirs", which reads as coverage for a
22
+ * surface that answers none of them — the shape #1072 was about, one file further in
23
+ * than any arm of `check-adwaita-conformance-drivers.mjs` looks (its claim arms begin at
24
+ * a citation, and that sentence named no table).
25
+ */
26
+ export declare const LIST_NORMALIZE_VECTORS: ReadonlyArray<ListNormalizeVector>;
27
+ /** One {@link parseListModel} expectation — the JSON attribute door. */
28
+ export interface ListParseVector {
29
+ /** The attribute value exactly as authored. */
30
+ attribute: string;
31
+ model: AdwListModel;
32
+ rule: string;
33
+ }
34
+ /**
35
+ * The `model` ATTRIBUTE → the normalised model. TOTAL: no row throws.
36
+ *
37
+ * An attribute is markup, parsed while the element upgrades, so a throw is reported as an
38
+ * uncaught page error nobody can handle — the reasoning ADR 0042 § 6 records for the menu
39
+ * attribute on the same elements.
40
+ *
41
+ * The browser suite drives `LIST_PARSE_VECTORS` through the live `model` attribute of
42
+ * `<adw-combo-row>` and of `<gtk-drop-down>`. Two elements for one parser because the
43
+ * parser is not what breaks: the row's `attributeChangedCallback` branch is, and it was
44
+ * missing for that element's whole life (#1525).
45
+ */
46
+ export declare const LIST_PARSE_VECTORS: ReadonlyArray<ListParseVector>;
47
+ /** One {@link listItemsChanged} expectation. */
48
+ export interface ListItemsChangedVector {
49
+ previous: AdwListModel;
50
+ next: AdwListModel;
51
+ /** The single splice, or `null` where GTK would emit no signal at all. */
52
+ change: AdwListItemsChanged | null;
53
+ /**
54
+ * How many item views a renderer must carry over UNTOUCHED — `previous.length` minus
55
+ * what the splice removes. A rebuild scores 0 on every row with a non-zero figure
56
+ * here, which is the whole difference this table measures.
57
+ */
58
+ survivors: number;
59
+ rule: string;
60
+ }
61
+ /**
62
+ * Model replacement → the one `items-changed` it emits.
63
+ *
64
+ * The shared prefix and the shared suffix are kept and everything between them is the
65
+ * change, which is `g_list_store_splice`'s shape.
66
+ */
67
+ export declare const LIST_ITEMS_CHANGED_VECTORS: ReadonlyArray<ListItemsChangedVector>;
68
+ /** What a caller does to the array it read back out of a `model` accessor. */
69
+ export type ListReadbackOp = 'append' | 'relabel-first' | 'remove-first';
70
+ /** One read-mutate-assign expectation — see {@link LIST_MODEL_OWNERSHIP_VECTORS}. */
71
+ export interface ListModelOwnershipVector {
72
+ initial: AdwListModel;
73
+ /**
74
+ * Which array the caller mutates: the one `model` handed BACK, or the one they
75
+ * assigned and kept. Two doors into the same failure, and closing one leaves the other.
76
+ */
77
+ source: 'readback' | 'input';
78
+ op: ListReadbackOp;
79
+ /** Whether the mutated array is assigned back to `model` afterwards. */
80
+ assign: boolean;
81
+ /** The model the surface holds when the row is done. */
82
+ model: AdwListModel;
83
+ /** The splice the surface emits, or `null` where it emits none. */
84
+ itemsChanged: AdwListItemsChanged | null;
85
+ /** Item views carried over untouched — `initial.length` when nothing happened. */
86
+ survivors: number;
87
+ rule: string;
88
+ }
89
+ /**
90
+ * WHO OWNS THE ARRAY a `model` accessor hands back — read, mutate, assign.
91
+ *
92
+ * The browser suite drives `LIST_MODEL_OWNERSHIP_VECTORS` through `<adw-combo-row>` and
93
+ * `<gtk-drop-down>`, and core drives it through `ComboState` itself, which is the surface
94
+ * where BOTH doors are reachable: the element setters run `normalizeComboOptions` and so
95
+ * close the input door on their own, while `ComboState.setModel` takes a normalised model
96
+ * straight from a caller.
97
+ *
98
+ * THE ROWS EXIST BECAUSE THE SPLICE MADE THIS A SILENT FAILURE. Under the full rebuild
99
+ * every assignment redrew the list, so an aliased array was invisible; the splice decides
100
+ * what to redraw by COMPARING the assignment against the stored model, and an alias makes
101
+ * both sides read the new value — "nothing changed", nothing drawn, no error. `append`
102
+ * with `assign` is the pattern that regressed, `relabel-first` is the same failure one
103
+ * level down through a shared descriptor (which is why the copy is per ITEM and not just
104
+ * per array), and the `assign: false` row is the invariant the other two rest on: what a
105
+ * caller holds is THEIRS, so touching it moves nothing until they hand it back.
106
+ */
107
+ export declare const LIST_MODEL_OWNERSHIP_VECTORS: ReadonlyArray<ListModelOwnershipVector>;
108
+ /**
109
+ * The caller-side half of one {@link LIST_MODEL_OWNERSHIP_VECTORS} row: mutate the array a
110
+ * `model` accessor handed back, IN PLACE, and hand the same array back.
111
+ *
112
+ * In place and returning its argument is the whole point — a helper that built a new array
113
+ * would test the pattern that never broke.
114
+ */
115
+ export declare function applyListReadback(model: AdwComboOption[], op: ListReadbackOp): AdwComboOption[];
116
+ /** One {@link clampListSelection} expectation. */
117
+ export interface ListSelectionClampVector {
118
+ selected: number;
119
+ length: number;
120
+ result: number;
121
+ rule: string;
122
+ }
123
+ /**
124
+ * Where a selection lands after the model under it was replaced.
125
+ *
126
+ * The browser suite drives `LIST_SELECTION_CLAMP_VECTORS` through `<adw-combo-row>` and
127
+ * through that element only. `<gtk-drop-down>` REFUSES an out-of-range `selected` set
128
+ * outright — its published contract, with `ComboState.hasIndex` as the shared bounds — so
129
+ * the starting index these rows replace cannot be put on that element in the first place.
130
+ * One row is narrower than the vector even on the row, and `adw-row-state.spec.ts` says
131
+ * so in place.
132
+ */
133
+ export declare const LIST_SELECTION_CLAMP_VECTORS: ReadonlyArray<ListSelectionClampVector>;
134
+ /** One {@link tabViewItemsChanged} expectation, with the order it replays. */
135
+ export interface TabPagesItemsChangedVector {
136
+ change: TabViewPagesChange;
137
+ itemsChanged: AdwListItemsChanged;
138
+ /** The page order before the change. */
139
+ before: readonly string[];
140
+ /** The page order after it — what {@link replayTabPagesAsSplices} must reproduce. */
141
+ after: readonly string[];
142
+ rule: string;
143
+ }
144
+ /**
145
+ * `TabViewPagesChange` → the portable `items-changed`, and what replaying it does to the
146
+ * order.
147
+ *
148
+ * The `before`/`after` pair is why these rows are worth having: a mapping asserted only
149
+ * against three numbers proves the arithmetic and not the CLAIM, which is that the
150
+ * portable signal carries everything a renderer needs to follow the collection.
151
+ *
152
+ * CORE-ONLY: the inputs are `TAB_VIEW_VECTORS`' own `pagesChanges`, which both renderer
153
+ * tab-view suites drive — each replays the changes ITS real widget emitted through
154
+ * {@link replayTabPagesAsSplices} and compares the result against the order that widget
155
+ * ended on, so the mapping is covered on both renderers under that table's name. Rows
156
+ * here are the hand-written edges (a move in each direction, a pin) that a fixture may or
157
+ * may not reach, so they are asserted against the function directly rather than left to
158
+ * whichever ops the tab-view fixtures happen to contain.
159
+ */
160
+ export declare const TAB_PAGES_ITEMS_CHANGED_VECTORS: ReadonlyArray<TabPagesItemsChangedVector>;
161
+ /**
162
+ * Replay page changes as splices over an order — the reconstruction that makes the
163
+ * portable signal's sufficiency measurable.
164
+ *
165
+ * It uses nothing but {@link tabViewItemsChanged}'s three numbers and the change's `id`,
166
+ * which is exactly what a GTK consumer has: `items-changed` gives the RANGE and the
167
+ * consumer re-reads the model over it. Both renderer tab-view suites feed it the changes
168
+ * their real widget emitted and compare against the order that widget ended on, so a
169
+ * mapping that quietly loses a page fails there and not only here.
170
+ */
171
+ export declare function replayTabPagesAsSplices(order: readonly string[], changes: readonly TabViewPagesChange[]): string[];
@@ -0,0 +1,118 @@
1
+ import type { AdwMenuActions, AdwMenuInput, AdwMenuItem, AdwMenuItemState, AdwMenuModel, AdwMenuPath } from '../menu.js';
2
+ /** One {@link normalizeMenuModel} expectation. */
3
+ export interface MenuNormalizeVector {
4
+ input: AdwMenuInput;
5
+ model: AdwMenuModel;
6
+ rule: string;
7
+ }
8
+ /**
9
+ * Authored input → the normalised tree.
10
+ *
11
+ * The first row is the shape the whole ADR is about: one menu value carrying a section,
12
+ * a detailed action with a target, an icon and a submenu — everything the three surfaces
13
+ * used to spell three different ways, or not at all.
14
+ */
15
+ export declare const MENU_NORMALIZE_VECTORS: ReadonlyArray<MenuNormalizeVector>;
16
+ /** One {@link parseMenuModel} expectation — the `menu` attribute door. */
17
+ export interface MenuParseVector {
18
+ /** The raw attribute (or `null` when absent). */
19
+ json: string | null;
20
+ model: AdwMenuModel;
21
+ rule: string;
22
+ }
23
+ /**
24
+ * Menu-attribute parsing. Total: nothing an author can type may prevent the widget
25
+ * upgrading, which is why every malformed row below yields a menu rather than a throw.
26
+ */
27
+ export declare const MENU_PARSE_VECTORS: ReadonlyArray<MenuParseVector>;
28
+ /** One expected row of a {@link flattenMenu} walk. */
29
+ export interface MenuFlatRowVector {
30
+ /** The label the row draws. */
31
+ label: string;
32
+ path: AdwMenuPath;
33
+ separated: boolean;
34
+ }
35
+ /** One {@link flattenMenu} expectation. */
36
+ export interface MenuFlattenVector {
37
+ model: AdwMenuModel;
38
+ rows: readonly MenuFlatRowVector[];
39
+ rule: string;
40
+ }
41
+ /**
42
+ * The rows a one-list-at-a-time surface draws, and where a separator goes.
43
+ *
44
+ * A separator NEVER leads: a rule above nothing is a line at the top of the menu, which
45
+ * is what a naive "a section starts, so separate" rule produces for a menu whose first
46
+ * entry is a section.
47
+ */
48
+ export declare const MENU_FLATTEN_VECTORS: ReadonlyArray<MenuFlattenVector>;
49
+ /** One {@link parseDetailedAction} expectation, and what GIO makes of the same string. */
50
+ export interface MenuDetailedActionVector {
51
+ detailed: string;
52
+ /** What the display-side splitter answers. It never throws. */
53
+ name: string;
54
+ target?: string;
55
+ /**
56
+ * Whether `g_action_parse_detailed_name` ACCEPTS the string.
57
+ *
58
+ * The load-bearing field, and the reason this table is driven twice. The splitter is
59
+ * lenient by design — it runs on a menu that has already been accepted, so it
60
+ * answers something for every input — and reading its answer as "this is a legal
61
+ * action name" is exactly the mistake that let `'app.x('` through to
62
+ * `g_menu_item_set_detailed_action`, which ABORTS the process on it. `false` rows
63
+ * are asserted to be REFUSED by `@gjsify/gtk-host`'s `buildGioMenu`; `true` rows are
64
+ * asserted to survive a round trip through a real `Gio.Menu`.
65
+ */
66
+ gioValid: boolean;
67
+ rule: string;
68
+ }
69
+ /**
70
+ * CORE-ONLY: the SPLITTER's answers are never read by a renderer — they exist for
71
+ * MENU_ITEM_STATE_VECTORS, which both renderer suites drive through the widget's action
72
+ * state, and a target that does not come off leaves every radio item reading `normal`.
73
+ * The `gioValid` half IS driven against a real implementation, by
74
+ * packages/framework/gtk-host/src/menu.spec.ts — the GTK host rather than an Adwaita
75
+ * port, so this gate does not count it as a renderer, and holds the PATH instead.
76
+ *
77
+ * `g_action_parse_detailed_name`'s two forms, the strings that are neither, and what the
78
+ * display-side splitter answers for each.
79
+ *
80
+ * IT IS DRIVEN TWICE, and the second drive is what the first cut was missing: the core
81
+ * suite asserts {@link parseDetailedAction}, and `@gjsify/gtk-host`'s menu suite sends
82
+ * every row through `buildGioMenu`. Without the second, this table DECLARED `'app.x('`
83
+ * legal while nothing ever handed it to GIO — and GIO answers a malformed name with
84
+ * `g_error()`, a SIGABRT no `catch` can see. A vector nobody drives against the real
85
+ * implementation is the shape `conformance/index.ts` warns about, one layer down.
86
+ */
87
+ export declare const MENU_DETAILED_ACTION_VECTORS: ReadonlyArray<MenuDetailedActionVector>;
88
+ /** One {@link resolveMenuItemState} expectation. */
89
+ export interface MenuItemStateVector {
90
+ item: AdwMenuItem;
91
+ actions: AdwMenuActions;
92
+ state: AdwMenuItemState;
93
+ rule: string;
94
+ }
95
+ /**
96
+ * Where `enabled` and `checked` come from, which is NOT the menu.
97
+ *
98
+ * Every row cites `gtkmenutrackeritem.c`, because the whole table exists to show that a
99
+ * portable model carrying `enabled: false` on an item would be inventing a field
100
+ * `GMenuModel` has not got — and would then have nowhere to put it when the model is
101
+ * handed to a real `Gio.Menu`.
102
+ */
103
+ export declare const MENU_ITEM_STATE_VECTORS: ReadonlyArray<MenuItemStateVector>;
104
+ /** One {@link menuRefusals} expectation. */
105
+ export interface MenuRefusalVector {
106
+ model: AdwMenuModel;
107
+ /** The paths that must be refused, in walk order. */
108
+ paths: readonly AdwMenuPath[];
109
+ rule: string;
110
+ }
111
+ /**
112
+ * What a surface without `gtk_popover_menu_add_child` will not draw.
113
+ *
114
+ * Every row is asserted for the WEB and NATIVESCRIPT surfaces (which refuse `custom`)
115
+ * and for GTK (which refuses nothing), so the table also pins down that the GTK path
116
+ * accepts every model the other two do.
117
+ */
118
+ export declare const MENU_REFUSAL_VECTORS: ReadonlyArray<MenuRefusalVector>;
@@ -5,8 +5,8 @@ import type { AdwComboOption, ComboStateChange } from '../rows.js';
5
5
  * and compares what the user would see.
6
6
  */
7
7
  export type ComboSelectionStep = {
8
- op: 'setOptions';
9
- options: ReadonlyArray<AdwComboOption>;
8
+ op: 'setModel';
9
+ model: ReadonlyArray<AdwComboOption>;
10
10
  } | {
11
11
  op: 'setSelectedIndex';
12
12
  index: number;
@@ -40,7 +40,7 @@ export interface ComboSelectionVector {
40
40
  * drives, plus the two port answers the C leaves open (see
41
41
  * the module header).
42
42
  *
43
- * Note that EVERY scenario starts with a `setOptions`, which is itself an
43
+ * Note that EVERY scenario starts with a `setModel`, which is itself an
44
44
  * emission: the model is what the label is read out of, so a renderer that
45
45
  * repaints only on a selection change draws a stale value after the list is
46
46
  * replaced. That first change is in `emitted` for exactly that reason.
@@ -1,4 +1,5 @@
1
- import type { AdwArrowIcon, AdwMenuEntry, SplitButtonContentMode, SplitButtonDirection, SplitButtonHalfState, SplitButtonProperty, SplitButtonStyleClass } from '../split-button.js';
1
+ import type { AdwMenuItem, AdwMenuModel, AdwMenuPath } from '../menu.js';
2
+ import type { AdwArrowIcon, SplitButtonContentMode, SplitButtonDirection, SplitButtonHalfState, SplitButtonProperty, SplitButtonStyleClass } from '../split-button.js';
2
3
  /** One mutation in a {@link SplitButtonContentVector} sequence, with its outcome. */
3
4
  export interface SplitButtonContentStep {
4
5
  op: 'label' | 'icon-name' | 'child';
@@ -122,29 +123,18 @@ export interface SplitButtonRootStateVector {
122
123
  * CORE-ONLY: GAP — two of its three axes are hardcoded in the renderer, so there is nothing to vary. Tracked in #1072
123
124
  */
124
125
  export declare const SPLIT_BUTTON_ROOT_STATE_VECTORS: ReadonlyArray<SplitButtonRootStateVector>;
125
- /** One `parseMenuEntries` expectation. */
126
- export interface SplitButtonMenuParseVector {
127
- /** The raw `menu` attribute (or `null` when absent). */
128
- json: string | null;
129
- entries: readonly AdwMenuEntry[];
130
- rule: string;
131
- }
132
- /**
133
- * Menu-attribute parsing — the byte-identical helper both browser elements
134
- * carried. Total: nothing an author can type may prevent the widget upgrading.
135
- */
136
- export declare const SPLIT_BUTTON_MENU_PARSE_VECTORS: ReadonlyArray<SplitButtonMenuParseVector>;
137
126
  /** One activate-by-position expectation. */
138
127
  export interface SplitButtonMenuActivationVector {
139
- entries: readonly AdwMenuEntry[];
140
- index: number;
141
- /** The entry that must be dispatched, or `null`. */
142
- activated: AdwMenuEntry | null;
128
+ model: AdwMenuModel;
129
+ path: AdwMenuPath;
130
+ /** The item that must be dispatched, or `null`. */
131
+ activated: AdwMenuItem | null;
143
132
  rule: string;
144
133
  }
145
134
  /**
146
135
  * Activation is BY POSITION, never by label lookup: a `GMenuModel` addresses its
147
- * items by index and each carries its own detailed action.
136
+ * items by index and each carries its own detailed action. A PATH since ADR 0042,
137
+ * because a link is a model of its own and a flat index cannot name a nested item.
148
138
  */
149
139
  export declare const SPLIT_BUTTON_MENU_ACTIVATION_VECTORS: ReadonlyArray<SplitButtonMenuActivationVector>;
150
140
  /**
@@ -154,7 +144,7 @@ export declare const SPLIT_BUTTON_MENU_ACTIVATION_VECTORS: ReadonlyArray<SplitBu
154
144
  */
155
145
  export interface SplitButtonDropdownVector {
156
146
  /** The menu model to install (empty = none). */
157
- entries: readonly AdwMenuEntry[];
147
+ model: AdwMenuModel;
158
148
  /** Whether a popover is installed before it. */
159
149
  popover: boolean;
160
150
  /** Whether the dropdown half is live. */