@gjsify/adwaita-core 0.54.0 → 0.56.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 (54) hide show
  1. package/lib/esm/box-selection.js +1 -0
  2. package/lib/esm/breakpoint-driver.js +1 -0
  3. package/lib/esm/calendar.js +1 -0
  4. package/lib/esm/conformance/box-selection.js +1 -0
  5. package/lib/esm/conformance/calendar.js +1 -0
  6. package/lib/esm/conformance/constructs.js +2 -0
  7. package/lib/esm/conformance/grid-navigation.js +1 -0
  8. package/lib/esm/conformance/index.js +1 -1
  9. package/lib/esm/conformance/list-view.js +1 -0
  10. package/lib/esm/conformance/shared-trees.js +1 -1
  11. package/lib/esm/conformance/tab-view.js +1 -1
  12. package/lib/esm/conformance/tree-expander.js +1 -0
  13. package/lib/esm/conformance/view-switcher.js +1 -1
  14. package/lib/esm/grid-navigation.js +1 -0
  15. package/lib/esm/index.js +1 -1
  16. package/lib/esm/list-view.js +1 -0
  17. package/lib/esm/markup.js +2 -2
  18. package/lib/esm/tab-view.js +1 -1
  19. package/lib/esm/tags.js +1 -1
  20. package/lib/esm/tokens.generated.js +1 -1
  21. package/lib/esm/tree-expander.js +1 -0
  22. package/lib/esm/view-stack.js +1 -1
  23. package/lib/esm/view-switcher.js +1 -1
  24. package/lib/types/box-selection.d.ts +104 -0
  25. package/lib/types/box-selection.spec.d.ts +2 -0
  26. package/lib/types/breakpoint-driver.d.ts +31 -0
  27. package/lib/types/breakpoint-driver.spec.d.ts +2 -0
  28. package/lib/types/calendar.d.ts +142 -0
  29. package/lib/types/calendar.spec.d.ts +2 -0
  30. package/lib/types/conformance/box-selection.d.ts +47 -0
  31. package/lib/types/conformance/breakpoint-bin.d.ts +11 -8
  32. package/lib/types/conformance/calendar.d.ts +71 -0
  33. package/lib/types/conformance/constructs.d.ts +87 -0
  34. package/lib/types/conformance/grid-navigation.d.ts +12 -0
  35. package/lib/types/conformance/index.d.ts +16 -4
  36. package/lib/types/conformance/list-view.d.ts +56 -0
  37. package/lib/types/conformance/shared-trees.d.ts +78 -1
  38. package/lib/types/conformance/tab-view.d.ts +29 -0
  39. package/lib/types/conformance/tree-expander.d.ts +29 -0
  40. package/lib/types/conformance/view-switcher.d.ts +25 -0
  41. package/lib/types/constructs.spec.d.ts +2 -0
  42. package/lib/types/grid-navigation.d.ts +11 -0
  43. package/lib/types/grid-navigation.spec.d.ts +2 -0
  44. package/lib/types/index.d.ts +14 -3
  45. package/lib/types/list-view.d.ts +276 -0
  46. package/lib/types/list-view.spec.d.ts +2 -0
  47. package/lib/types/sidebar.d.ts +9 -0
  48. package/lib/types/tab-view.d.ts +27 -0
  49. package/lib/types/tags.d.ts +43 -1
  50. package/lib/types/tree-expander.d.ts +83 -0
  51. package/lib/types/tree-expander.spec.d.ts +2 -0
  52. package/lib/types/view-stack.d.ts +11 -0
  53. package/lib/types/view-switcher.d.ts +28 -0
  54. package/package.json +3 -3
@@ -0,0 +1,276 @@
1
+ import type { DataGridTrack } from './data-grid.js';
2
+ import { type AdwComboOption, type AdwListItemsChanged } from './list.js';
3
+ /**
4
+ * Which `Gtk.SelectionModel` wraps the view's model, as a WORD.
5
+ *
6
+ * GTK composes the three — `Gtk.NoSelection`, `Gtk.SingleSelection`, `Gtk.MultiSelection`
7
+ * — around a `Gio.ListModel` and hands the result to `GtkListView:model`. ADR 0046
8
+ * declined that layering as a portable VALUE and ADR 0089 keeps the declination, so what
9
+ * crosses here is the choice rather than the wrapper: one of three nicks, on the widget.
10
+ *
11
+ * `single` is the default because it is the one a `Gtk.ListView` is built with in every
12
+ * upstream example, and because a view that answers no click at all is the surprising one.
13
+ */
14
+ export type AdwListSelectionMode = 'none' | 'single' | 'multiple';
15
+ /** An unknown mode is `single`, the way an unknown enum nick falls back to a default. */
16
+ export declare function normalizeListSelectionMode(raw: unknown): AdwListSelectionMode;
17
+ /**
18
+ * What a factory is handed for ONE item — `GtkListItem`'s three readable properties that
19
+ * a renderer actually binds against (`item`, `position`, `selected`).
20
+ *
21
+ * `GtkListItem` carries five more (`activatable`, `focusable`, `selectable`, `child`, and
22
+ * the two accessible strings). They are the LIST's business rather than the item's
23
+ * content: the view decides whether a row can be activated, so making them inputs to the
24
+ * factory would offer a per-item answer the view then overrides.
25
+ */
26
+ export interface AdwListItemContext {
27
+ /** The model item at {@link position}. */
28
+ readonly item: AdwComboOption;
29
+ /** Where the item sits in the model. */
30
+ readonly position: number;
31
+ /** Whether the view has this position selected. */
32
+ readonly selected: boolean;
33
+ }
34
+ /**
35
+ * `GtkSignalListItemFactory`'s `setup` + `bind`, as one call.
36
+ *
37
+ * GTK keeps them apart because it RECYCLES row widgets while scrolling: setup builds,
38
+ * bind refills, and a view of ten thousand items holds two hundred widgets. This port
39
+ * builds a node per item and splices ({@link ListViewState}), so there is nothing to
40
+ * recycle and nothing for the split to buy — and a factory that is one function is a
41
+ * factory an author can write inline.
42
+ *
43
+ * Generic in the node so the seam is not a DOM one: `@gjsify/adwaita-web` instantiates it
44
+ * at `Node`, and a renderer with another node type takes the same interface.
45
+ */
46
+ export type AdwListItemFactory<TNode> = (context: AdwListItemContext) => TNode;
47
+ /** One positional selection, normalised: ascending, no duplicates. */
48
+ export type AdwListSelection = readonly number[];
49
+ /** What a click (or a Space press) on a row means, in `gtk_list_base_select_item`'s words. */
50
+ export interface AdwListSelectStep {
51
+ /** `<Ctrl>` — toggle this row rather than replacing the selection. */
52
+ readonly modify?: boolean;
53
+ /** `<Shift>` — extend from the anchor to this row. */
54
+ readonly extend?: boolean;
55
+ }
56
+ /** The selection plus the position a later `extend` measures from. */
57
+ export interface AdwListSelectResult {
58
+ /** The next selection, ascending. */
59
+ readonly selection: number[];
60
+ /** Where the next `extend` starts — GTK's `priv->selected` tracker. */
61
+ readonly anchor: number;
62
+ }
63
+ /**
64
+ * One click on row `position` → the selection that follows it.
65
+ *
66
+ * This is `gtk_list_base_select_item` (gtklistbase.c:374-446) with the selection model's
67
+ * answer folded in, because on this surface the two are not separable: GTK asks the model
68
+ * and falls back when it says no, and the three models say different things.
69
+ *
70
+ * The C, step for step:
71
+ *
72
+ * · a position outside the model is ignored — `pos >= n_items` returns early;
73
+ * · `extend` asks the model for a RANGE from the anchor. With `modify` the range takes
74
+ * the anchor's own state (select the span if the anchor is selected, unselect it
75
+ * otherwise); without, it is a plain `select_range(…, TRUE)` — unselect the rest;
76
+ * · a range the model refuses falls THROUGH to the single-item path, which is why
77
+ * Shift+click in single mode just moves the selection rather than doing nothing:
78
+ * `GtkSingleSelection` has no `select_range`;
79
+ * · `modify` alone toggles — except that `gtk_single_selection_unselect_item` refuses
80
+ * while `autoselect` is on (gtksingleselection.c:160-173, and `autoselect` defaults
81
+ * TRUE), so Ctrl+click on the SELECTED row in single mode leaves it selected;
82
+ * · plain click selects exclusively;
83
+ * · and the anchor moves to `pos` in every case the function does not return early
84
+ * from — it is the last statement of the function, after the range branch.
85
+ *
86
+ * `none` answers every step with the empty selection: `GtkNoSelection` implements the
87
+ * interface by returning FALSE from each of the four mutators.
88
+ */
89
+ export declare function listViewSelect(selection: AdwListSelection, anchor: number, position: number, length: number, mode: AdwListSelectionMode, step?: AdwListSelectStep): AdwListSelectResult;
90
+ /** `list.select-all` — refused by every model but `GtkMultiSelection`. */
91
+ export declare function listViewSelectAll(length: number, mode: AdwListSelectionMode): number[];
92
+ /** A selection subscriber — the positions, and whether a USER produced them. */
93
+ export type ListViewSelectionListener = (change: ListViewSelectionChange) => void;
94
+ /** What a selection subscriber is handed. */
95
+ export interface ListViewSelectionChange {
96
+ /** The selected positions, ascending. */
97
+ readonly selection: AdwListSelection;
98
+ /** False for a programmatic write, the way `ComboStateChange.interactive` reads. */
99
+ readonly interactive: boolean;
100
+ }
101
+ /** An items subscriber — one `Gio.ListModel::items-changed` per model assignment. */
102
+ export type ListViewItemsListener = (change: AdwListItemsChanged) => void;
103
+ /**
104
+ * The model + selection a view holds — headless (ADR 0004, ADR 0089).
105
+ *
106
+ * `ComboState` is the same shape one widget over and is NOT reused: it holds ONE index
107
+ * because `Adw.ComboRow:selected` is one `guint`, and widening it to a set would put a
108
+ * multi-selection vocabulary on a widget that has none. What the two DO share is already
109
+ * shared — the item vocabulary, the splice, and the autoselect clamp all live in
110
+ * `list.ts` and both classes call them.
111
+ *
112
+ * THE MODEL IS COPIED IN BOTH DIRECTIONS, for the reason ADR 0046 § 3 measured on
113
+ * `ComboState`: the splice is computed by COMPARING the assigned model against the stored
114
+ * one, so a shared array or a shared item makes both sides read the new value and answer
115
+ * "nothing changed" — silently, because there is nothing to throw.
116
+ */
117
+ export declare class ListViewState {
118
+ private _model;
119
+ private _selection;
120
+ private _anchor;
121
+ private _mode;
122
+ private readonly _itemListeners;
123
+ private readonly _selectionListeners;
124
+ /** Subscribe to MODEL changes — one splice per {@link setModel}. Returns an unsubscribe. */
125
+ subscribeItems(listener: ListViewItemsListener): () => void;
126
+ /** Subscribe to SELECTION changes. Returns an unsubscribe. */
127
+ subscribeSelection(listener: ListViewSelectionListener): () => void;
128
+ /** The list model, as a COPY — see the class header. */
129
+ get model(): AdwComboOption[];
130
+ /** How many items the model holds (`g_list_model_get_n_items`). */
131
+ get count(): number;
132
+ /** The selected positions, ascending, as a copy. */
133
+ get selection(): number[];
134
+ /** Which selection model the view behaves as. */
135
+ get selectionMode(): AdwListSelectionMode;
136
+ /**
137
+ * Replace the model: emit the splice that turns the old one into the new, then drop
138
+ * any selected position the new model does not have.
139
+ *
140
+ * A VIEW DOES NOT AUTOSELECT, and that is the one place this class deliberately
141
+ * differs from `ComboState`. `clampListSelection` is `GtkSingleSelection`'s
142
+ * `autoselect`, which exists so a combo row always has a value to SHOW; a list view
143
+ * has no inline value and GTK's own `GtkMultiSelection` keeps nothing selected after
144
+ * a model it cannot map. So positions are filtered, not clamped.
145
+ */
146
+ setModel(model: readonly AdwComboOption[] | null | undefined): void;
147
+ /** `Gtk.SelectionModel` choice. Narrowing it drops what the new mode cannot hold. */
148
+ setSelectionMode(mode: AdwListSelectionMode): void;
149
+ /** Whether `position` is selected. */
150
+ isSelected(position: number): boolean;
151
+ /**
152
+ * One click or Space on `position` — {@link listViewSelect} applied to this state.
153
+ *
154
+ * Returns whether the selection CHANGED, which is what gates a notify: the anchor
155
+ * moves on every accepted step and is not itself observable, so reporting it would
156
+ * make a repeat click on one row look like a selection change.
157
+ */
158
+ selectItem(position: number, step?: AdwListSelectStep, interactive?: boolean): boolean;
159
+ /** `list.select-all`. */
160
+ selectAll(interactive?: boolean): boolean;
161
+ /** `list.unselect-all`. */
162
+ unselectAll(interactive?: boolean): boolean;
163
+ /** Programmatic assignment — out-of-range positions are dropped, as a model replacement drops them. */
164
+ setSelection(positions: Iterable<number>, interactive?: boolean): boolean;
165
+ /** The context a factory is handed for `position`, or null past the end of the model. */
166
+ itemContext(position: number): AdwListItemContext | null;
167
+ private _applySelection;
168
+ private _emitSelection;
169
+ }
170
+ /** `GtkGridView:max-columns`' default — `DEFAULT_MAX_COLUMNS` (gtkgridview.c:42). */
171
+ export declare const ADW_GRID_VIEW_MAX_COLUMNS = 7;
172
+ /** `GtkGridView:min-columns`' default — the ParamSpec's own (gtkgridview.c:1164-1167). */
173
+ export declare const ADW_GRID_VIEW_MIN_COLUMNS = 1;
174
+ /**
175
+ * How many columns a grid lays out in `forSize` pixels — `gtk_grid_view_compute_n_columns`
176
+ * (gtkgridview.c:619-641).
177
+ *
178
+ * The arithmetic is upstream's, including the `+ spacing` on both sides, which is what
179
+ * makes the LAST column need no trailing gap, and the integer division, which the C calls
180
+ * out as deliberate ("rounding down is exactly what we want here").
181
+ *
182
+ * `MAX (1, …)` in the denominator is kept: an item that measures zero would otherwise
183
+ * divide by zero, and the C asserts the result is positive.
184
+ *
185
+ * The clamp order is `CLAMP (n, min, max)` as written, so `max < min` answers `min` for a
186
+ * narrow view and `max` for a wide one — the asymmetry the property's own documentation
187
+ * describes from one side only ("if this number is smaller than min-columns, that value is
188
+ * used instead", gtkgridview.c:1147-1157).
189
+ */
190
+ export declare function gridViewColumns(forSize: number, itemSize: number, spacing: number, minColumns?: number, maxColumns?: number): number;
191
+ /**
192
+ * Which half of a portable item a column draws, for a column that declares no factory.
193
+ *
194
+ * A `GtkColumnViewColumn` has no such property: upstream a column draws whatever its
195
+ * FACTORY pulls off the item, and the item is an arbitrary GObject. A portable item is
196
+ * `AdwComboOption` — `value` and `label`, and nothing else by contract (ADR 0046 § 2) —
197
+ * so a column that wants to draw without a factory has exactly these two choices, and
198
+ * `key` is the declarative door onto them. Anything else is a `factory`, which is the
199
+ * same thing GTK requires of every column.
200
+ */
201
+ export type AdwListItemField = 'label' | 'value';
202
+ /** `item[field]`, with the field clamped — the one place a column's `key` is resolved. */
203
+ export declare function listItemField(item: AdwComboOption, field: AdwListItemField): string;
204
+ /**
205
+ * One `Gtk.ColumnViewColumn`, in plain data.
206
+ *
207
+ * Every other member IS a GTK property; `key` is the exception and {@link AdwListItemField}
208
+ * says why. `id` is kept beside it rather than derived from it because it is a real
209
+ * property with a different job — it is what `Gtk.ColumnView` persists a column layout by
210
+ * — and because two columns over the same field are a legitimate thing to author.
211
+ */
212
+ export interface AdwColumnViewColumn {
213
+ /** Which portable field this column draws when it has no factory. */
214
+ key: AdwListItemField;
215
+ /** `Gtk.ColumnViewColumn:title` — the header text. Defaults to {@link key}. */
216
+ title?: string;
217
+ /** `Gtk.ColumnViewColumn:id`. Defaults to {@link key}. */
218
+ id?: string;
219
+ /** `Gtk.ColumnViewColumn:expand` — take a share of the extra width. */
220
+ expand?: boolean;
221
+ /** `Gtk.ColumnViewColumn:fixed-width`, in pixels. `-1` is "not fixed", as in C. */
222
+ fixedWidth?: number;
223
+ /** `Gtk.ColumnViewColumn:visible`. */
224
+ visible?: boolean;
225
+ /** Whether the header sorts — the portable stand-in for a non-null `Gtk.Sorter`. */
226
+ sortable?: boolean;
227
+ }
228
+ /** Which column a column view is sorted by, and in which direction. */
229
+ export interface AdwColumnViewSort {
230
+ /** The sorted column's {@link AdwColumnViewColumn.id}. */
231
+ readonly column: string;
232
+ /** `Gtk.SortType.DESCENDING` — the sorter's `inverted` flag. */
233
+ readonly descending: boolean;
234
+ }
235
+ /** `fixed-width`'s "no fixed width" sentinel (`gtk_column_view_column_set_fixed_width`). */
236
+ export declare const ADW_COLUMN_VIEW_NO_FIXED_WIDTH = -1;
237
+ /**
238
+ * Raw authored columns → stable descriptors. TOTAL, for the reason
239
+ * `normalizeComboOptions` is: the doors it reaches are a property assignment and a markup
240
+ * attribute, and an author's typo must not take the widget down.
241
+ *
242
+ * An entry with no `key` is DROPPED rather than defaulted — a column that draws no field
243
+ * is an empty strip a reader cannot tell from a layout bug, and `normalizeDataGridColumns`
244
+ * answers the identical input the identical way.
245
+ */
246
+ export declare function normalizeColumnViewColumns(raw: unknown): AdwColumnViewColumn[];
247
+ /** The MARKUP door: a JSON attribute → normalised columns. Total in both failure modes. */
248
+ export declare function parseColumnViewColumns(raw: string | null | undefined): AdwColumnViewColumn[];
249
+ /**
250
+ * A click on a sortable header → the sort that follows it —
251
+ * `gtk_column_view_sorter_add_column` (gtkcolumnviewsorter.c:294-344).
252
+ *
253
+ * The rule is two lines of that function: if the clicked column is ALREADY the primary
254
+ * sorter, `inverted` flips; otherwise the column becomes primary, ascending. A column
255
+ * with no sorter is refused (`sorter == NULL` returns FALSE), which is what `sortable`
256
+ * stands in for here.
257
+ *
258
+ * The secondary sorters upstream keeps in a sequence are not modelled: they are reachable
259
+ * only through `GtkColumnViewSorter`'s own API, never through a header click, and a
260
+ * portable multi-key sort would need the comparators this surface does not carry.
261
+ */
262
+ export declare function columnViewSort(current: AdwColumnViewSort | null, column: AdwColumnViewColumn): AdwColumnViewSort | null;
263
+ /**
264
+ * The column tracks a column view lays out — in `data-grid.ts`'s renderer-neutral
265
+ * {@link DataGridTrack}, reused rather than restated.
266
+ *
267
+ * The two widgets answer the SAME question (which track is pinned, which absorbs the
268
+ * slack) from two property names, and `AdwDataGrid` is where that vocabulary already
269
+ * lives. What differs is only the mapping: `fixed-width` is a pixel number rather than a
270
+ * CSS length, and `expand` is a boolean share rather than a weight — GTK divides the extra
271
+ * width evenly among the expanding columns, so every one of them is `1fr`.
272
+ *
273
+ * A view with NO expanding column gets `auto` throughout, which leaves the trailing space
274
+ * unclaimed exactly as `gtk_column_view_measure` does.
275
+ */
276
+ export declare function columnViewTracks(columns: readonly AdwColumnViewColumn[]): DataGridTrack[];
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -26,6 +26,15 @@ export interface AdwSidebarItemSpec {
26
26
  visible?: boolean;
27
27
  /** Whether the row can be activated — bound to `row:sensitive`. Default true. */
28
28
  enabled?: boolean;
29
+ /**
30
+ * The row's INDICATOR — `AdwSidebarItem`'s suffix bin as `AdwViewSwitcherSidebar`
31
+ * drives it (`update_badge`, adw-view-switcher-sidebar.c:133-181). `libadwaita`'s own
32
+ * sidebar leaves the suffix alone; the switcher sidebar puts an unread dot or a badge
33
+ * count there, and `Adw.Sidebar` itself never does.
34
+ */
35
+ badgeNumber?: number;
36
+ /** Whether that indicator is drawn in the attention colour (`.needs-attention`). */
37
+ needsAttention?: boolean;
29
38
  }
30
39
  /**
31
40
  * A titled group of rows — the value half of `AdwSidebarSection`.
@@ -22,6 +22,12 @@ export interface AdwTabPageSpec<T = unknown> {
22
22
  title?: string | null;
23
23
  /** Tooltip; falls back to the title when empty ({@link tabTooltip}). */
24
24
  tooltip?: string | null;
25
+ /**
26
+ * `AdwTabPage:keyword` — the string the tab overview's SEARCH matches on besides the
27
+ * title and the tooltip ("Use keywords to search in e.g. page URLs in a web browser",
28
+ * adw-tab-overview.c:1646-1648).
29
+ */
30
+ keyword?: string | null;
25
31
  /** GTK icon name shown in the tab, or `null` for none. */
26
32
  icon?: string | null;
27
33
  /** Icon shown in the indicator slot (`AdwTabPage:indicator-icon`), or `null`. */
@@ -44,6 +50,8 @@ export interface AdwTabPageState<T = unknown> {
44
50
  readonly title: string;
45
51
  /** Tooltip, already coerced from `null` to `''`. Empty means "use the title". */
46
52
  readonly tooltip: string;
53
+ /** `AdwTabPage:keyword` — the search term beside the title and tooltip ({@link tabSearchMatches}). */
54
+ readonly keyword: string;
47
55
  /** GTK icon name, or `null` when the page has none. */
48
56
  readonly icon: string | null;
49
57
  readonly indicatorIcon: string | null;
@@ -208,6 +216,23 @@ export declare function tabTooltip(page: Pick<AdwTabPageState, 'tooltip' | 'titl
208
216
  * one that escaped the title would show the escapes.
209
217
  */
210
218
  export declare function tabTooltipIsMarkup(page: Pick<AdwTabPageState, 'tooltip'>): boolean;
219
+ /**
220
+ * Whether a page SURVIVES the tab overview's search terms — the predicate behind
221
+ * `AdwTabGrid`'s three `GtkStringFilter`s (adw-tab-grid.c:3532-3539: a property
222
+ * expression over `title`, `tooltip` and `keyword`, combined with a `GtkMultiFilter`,
223
+ * which is a logical AND of the three).
224
+ *
225
+ * `GtkStringFilter`'s defaults decide the rest (gtkstringfilter.c:276,297): match mode
226
+ * SUBSTRING and case folding ON, with both sides run through `g_utf8_normalize` first.
227
+ * An EMPTY property never matches — `gtk_string_filter_prepare` returns NULL for it
228
+ * (:76-77) — which is why a page with no tooltip does not match every term, and an
229
+ * EMPTY term set matches everything (`has_search`, :93-98).
230
+ *
231
+ * The DOM has neither `g_utf8_normalize` nor full case folding. `String.normalize('NFKC')`
232
+ * is the same normalisation step and `toLocaleLowerCase()` the case folding for the
233
+ * scripts this gallery ships; the search therefore matches the C rule, not a locale's.
234
+ */
235
+ export declare function tabSearchMatches(page: Pick<AdwTabPageState, 'title' | 'tooltip' | 'keyword'>, terms: string): boolean;
211
236
  /**
212
237
  * What a tab paints in its icon + indicator slots (`update_icons`).
213
238
  *
@@ -551,6 +576,8 @@ export declare class TabViewState<T = unknown> {
551
576
  setPageTitle(id: string, title: string | null): boolean;
552
577
  /** Set a page's tooltip; `null` becomes `''`. */
553
578
  setPageTooltip(id: string, tooltip: string | null): boolean;
579
+ /** Set a page's search keyword; `null` becomes `''` (`adw_tab_page_set_keyword`). */
580
+ setPageKeyword(id: string, keyword: string | null): boolean;
554
581
  /** Set a page's icon name, `null` for none (`adw_tab_page_set_icon`). */
555
582
  setPageIcon(id: string, icon: string | null): boolean;
556
583
  /** Set a page's indicator icon, `null` for none. */
@@ -37,7 +37,7 @@ export declare const attributeOf: (prop: string) => string;
37
37
  */
38
38
  export declare const propertyOf: (name: string) => string;
39
39
  /**
40
- * `GtkWidget`'s four margins, as the CSS property each one is — `start`/`end` the LOGICAL
40
+ * `GtkWidget`'s four margins, as the CSS property each is — `start`/`end` the LOGICAL
41
41
  * edges `gtk_widget_set_margin_start` documents, `top`/`bottom` physical as in GTK.
42
42
  *
43
43
  * A markup renderer writes them as inline style, because an attribute cannot carry a length
@@ -46,3 +46,45 @@ export declare const propertyOf: (name: string) => string;
46
46
  * `./markup.ts`, whose markup has to parse back to exactly what that builder made.
47
47
  */
48
48
  export declare const GTK_WIDGET_MARGIN_CSS: Readonly<Record<string, string>>;
49
+ /**
50
+ * `GtkWidget:hexpand` / `vexpand`, the two booleans whose `false` is an AUTHORED answer: the
51
+ * attribute's absence says "not written", and a widget that expands by default
52
+ * (`<gtk-scrolled-window>`) needs the author's `false` to stop. A renderer writes `true` as
53
+ * the bare attribute and `false` as `hexpand="false"`, which the stylesheet reads.
54
+ */
55
+ export declare const GTK_WIDGET_EXPAND: readonly ["hexpand", "vexpand"];
56
+ /**
57
+ * `GtkWidget:width-request` / `height-request`, as the CSS minimum each one is. A size request
58
+ * is a MINIMUM in GTK (`gtk_widget_set_size_request`: the widget is never measured below it,
59
+ * and its natural size still wins when larger), so `min-*` is the same sentence and `width`
60
+ * would claim an exactness GTK does not give. -1 is the pspec's "not set".
61
+ */
62
+ export declare const GTK_WIDGET_SIZE_CSS: Readonly<Record<string, string>>;
63
+ /**
64
+ * The inline-style declaration an authored GtkWidget length property stands for, or
65
+ * `undefined` when `attribute` is not one. `null` is a size request of -1: the author said
66
+ * "unset", so nothing is written (and an earlier value is to be cleared).
67
+ */
68
+ export declare function widgetLengthStyle(attribute: string, value: string | number | boolean): readonly [property: string, length: string | null] | undefined;
69
+ /**
70
+ * Kebab-case attribute names of GTK boolean properties that default to TRUE.
71
+ * These are VALUE-BASED: `attr="false"` means false, absent means true (the default).
72
+ * Style classes like `flat`/`round`/`compact` are PRESENCE-BASED and NOT in this set.
73
+ * ADR 0049, ADR 0034 own the presence vs. value split.
74
+ *
75
+ * @deprecated Use {@link isValueBasedBooleanAttr} instead. This flat set cannot express
76
+ * that the same attribute name (e.g. `revealed`) is value-based for one widget
77
+ * (`gtk-action-bar`) but presence-based for another (`adw-banner`).
78
+ */
79
+ export declare const VALUE_BASED_BOOLEAN_ATTRS: Set<string>;
80
+ /**
81
+ * Whether the given attribute on the given widget tag is VALUE-BASED.
82
+ *
83
+ * Value-based means: `attr="false"` spells false, absent spells the default (TRUE).
84
+ * Presence-based means: the attribute's PRESENCE spells true, absence spells false.
85
+ *
86
+ * @param tagName The element's tag name (e.g. `adw-banner`, `gtk-action-bar`).
87
+ * @param attrName The kebab-case attribute name (e.g. `revealed`, `can-pop`).
88
+ * @returns True if the attribute should be written as `attr="true"|"false"`.
89
+ */
90
+ export declare function isValueBasedBooleanAttr(tagName: string, attrName: string): boolean;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * What a `Gtk.TreeListRow` tells the expander — depth, expandability, expanded state.
3
+ *
4
+ * `gtk_tree_list_row_get_depth` is 0 for a root row, so a three-level tree runs 0, 1, 2 —
5
+ * and `is_expandable` is independent of `expanded`: a collapsed row whose children the
6
+ * model has not built yet is still expandable.
7
+ */
8
+ export interface AdwTreeExpanderRow {
9
+ /** `gtk_tree_list_row_get_depth` — 0 at the root. */
10
+ readonly depth: number;
11
+ /** `gtk_tree_list_row_is_expandable`. */
12
+ readonly expandable: boolean;
13
+ /** `Gtk.TreeListRow:expanded`. */
14
+ readonly expanded: boolean;
15
+ }
16
+ /** The three properties that shape the expander, in GTK's own defaults. */
17
+ export interface AdwTreeExpanderOptions {
18
+ /** `Gtk.TreeExpander:hide-expander` — default FALSE. */
19
+ readonly hideExpander?: boolean;
20
+ /** `Gtk.TreeExpander:indent-for-depth` — default TRUE (gtktreeexpander.c:750). */
21
+ readonly indentForDepth?: boolean;
22
+ /** `Gtk.TreeExpander:indent-for-icon` — default TRUE (gtktreeexpander.c:749). */
23
+ readonly indentForIcon?: boolean;
24
+ }
25
+ /** What the expander node is, when there is one. */
26
+ export type AdwTreeExpanderIcon = 'none' | 'collapsed' | 'expanded';
27
+ /** The subtree one expander draws, as counts rather than nodes. */
28
+ export interface AdwTreeExpanderLayout {
29
+ /** How many `indent` nodes precede the expander/child. */
30
+ readonly indents: number;
31
+ /** The `expander` node's state — `none` when the row draws no expander at all. */
32
+ readonly expander: AdwTreeExpanderIcon;
33
+ /** `GTK_ACCESSIBLE_PROPERTY_LEVEL`, which "is >= 1" (gtktreeexpander.c:264). */
34
+ readonly level: number;
35
+ }
36
+ /**
37
+ * The nodes one expander draws — `gtk_tree_expander_update_for_list_row`
38
+ * (gtktreeexpander.c:166-265), as a pure function.
39
+ *
40
+ * Read off the C in its own order, because the order is where the subtlety is:
41
+ *
42
+ * 1. `depth = indent_for_depth ? row.depth : 0`. Turning the property off does not hide
43
+ * the expander, it only stops the INDENT growing with the level;
44
+ * 2. an expander node exists when the row `is_expandable` AND `hide-expander` is off,
45
+ * and carries `:checked` when the row is expanded — which is also the
46
+ * `aria-expanded` state;
47
+ * 3. with NO expander, `indent_for_icon` adds ONE to the depth, so a leaf lines up with
48
+ * its expandable siblings' content instead of sitting an icon-width to their left.
49
+ * It is the only place the count differs from the level, and it applies to a
50
+ * hide-expander row as much as to a leaf;
51
+ * 4. `level` is `depth + 1` and is computed AFTER that increment, so a leaf at depth 0
52
+ * with `indent-for-icon` on reports level 2. That is upstream's arithmetic, not a
53
+ * transcription slip: the C writes `depth + 1` at the end of the same block.
54
+ *
55
+ * A null row is `EMPTY_LAYOUT`: GTK unparents every indent and the expander, leaving the
56
+ * child alone, and resets the expanded state.
57
+ */
58
+ export declare function treeExpanderLayout(row: AdwTreeExpanderRow | null | undefined, options?: AdwTreeExpanderOptions): AdwTreeExpanderLayout;
59
+ /** What a key press on an expander asks for. */
60
+ export type AdwTreeExpanderAction = 'expand' | 'collapse' | 'toggle' | 'none';
61
+ /** The modifier state a shortcut is read against. */
62
+ export interface AdwTreeExpanderKeyState {
63
+ /** `GDK_CONTROL_MASK`. */
64
+ readonly ctrlKey?: boolean;
65
+ /** `GDK_SHIFT_MASK`. */
66
+ readonly shiftKey?: boolean;
67
+ /** Right-to-left text direction, which swaps the two arrows. */
68
+ readonly rtl?: boolean;
69
+ }
70
+ /**
71
+ * One key press → the `listitem.*` action it maps to (gtktreeexpander.c:654-691).
72
+ *
73
+ * `+` and `*` expand, `-` and `/` collapse, Ctrl+Space toggles; the arrows expand or
74
+ * collapse with Shift or Ctrl+Shift, in the direction the locale reads
75
+ * (`expand_collapse_right` is EXPAND in LTR). The keypad duplicates upstream declares
76
+ * (`KP_Add`, `KP_Multiply`, …) need no entry: a browser reports the same `key` value for
77
+ * a keypad `+` as for the main one, and distinguishing them would need `code`.
78
+ *
79
+ * Backspace — "go to parent row" — is `#if 0` in the C and is therefore not here either.
80
+ */
81
+ export declare function treeExpanderAction(key: string, state?: AdwTreeExpanderKeyState): AdwTreeExpanderAction;
82
+ /** Apply an action to an expanded flag — `listitem.expand`/`collapse`/`toggle-expand`. */
83
+ export declare function treeExpanderExpanded(expanded: boolean, action: AdwTreeExpanderAction): boolean;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -22,6 +22,13 @@ export interface AdwViewStackPageSpec<T = unknown> {
22
22
  needsAttention?: boolean;
23
23
  /** `AdwViewStackPage:use-underline`. Defaults to `false`. */
24
24
  useUnderline?: boolean;
25
+ /**
26
+ * `AdwViewStackPage:starts-section` — the page opens a NEW section, which is what
27
+ * `AdwViewSwitcherSidebar` groups on (`populate_sidebar`, adw-view-switcher-sidebar.c:225-243).
28
+ */
29
+ startsSection?: boolean;
30
+ /** `AdwViewStackPage:section-title` — the heading of the section this page opens. */
31
+ sectionTitle?: string | null;
25
32
  }
26
33
  /**
27
34
  * A resolved page descriptor, as a bound switcher reads it. `title` and `icon` are
@@ -46,6 +53,10 @@ export interface AdwViewStackPageInfo<T = unknown> {
46
53
  readonly needsAttention: boolean;
47
54
  /** `AdwViewStackPage:use-underline` — whether the title carries a mnemonic. */
48
55
  readonly useUnderline: boolean;
56
+ /** `AdwViewStackPage:starts-section` — whether this page opens a new section. */
57
+ readonly startsSection: boolean;
58
+ /** `AdwViewStackPage:section-title`, `''` when the page declares none. */
59
+ readonly sectionTitle: string;
49
60
  }
50
61
  /** Payload of a selection change. */
51
62
  export interface ViewStackStateChange<T = unknown> {
@@ -70,6 +70,13 @@ export interface AdwViewSwitcherPage {
70
70
  badgeNumber: number;
71
71
  /** `AdwViewStackPage:needs-attention` — default `false`, i.e. no dot. */
72
72
  needsAttention: boolean;
73
+ /**
74
+ * `AdwViewStackPage:starts-section` — default `false`. Only
75
+ * {@link viewSwitcherSidebarSections} reads it; every other switcher is flat.
76
+ */
77
+ startsSection: boolean;
78
+ /** `AdwViewStackPage:section-title` — the heading of the section the page opens. */
79
+ sectionTitle: string | null;
73
80
  }
74
81
  /** What a caller has to supply for a page: the name, plus whatever it knows. */
75
82
  export interface AdwViewSwitcherPageInit extends Partial<AdwViewSwitcherPage> {
@@ -92,6 +99,27 @@ export declare function createViewSwitcherPage(init: AdwViewSwitcherPageInit): A
92
99
  export declare function viewSwitcherPageFromStackPage(page: AdwViewStackPageInfo): AdwViewSwitcherPage;
93
100
  /** {@link viewSwitcherPageFromStackPage} over a whole page list. */
94
101
  export declare function viewSwitcherPagesFromStack(pages: readonly AdwViewStackPageInfo[]): AdwViewSwitcherPage[];
102
+ /** One section of a {@link viewSwitcherSidebarSections} grouping. */
103
+ export interface ViewSwitcherSidebarSection {
104
+ /** `AdwViewStackPage:section-title` of the page that OPENED the section. */
105
+ readonly title: string;
106
+ /** The pages in it, in stack order. Never empty — C only appends a section it filled. */
107
+ readonly pages: readonly AdwViewSwitcherPage[];
108
+ }
109
+ /**
110
+ * `populate_sidebar` (adw-view-switcher-sidebar.c:225-243), which is the ONE thing that
111
+ * makes `AdwViewSwitcherSidebar` a switcher: every other Adw switcher is one flat row of
112
+ * buttons, and this one groups a stack's pages into `AdwSidebarSection`s.
113
+ *
114
+ * Two rules, both of them C:
115
+ * · a page with `starts-section` OPENS a section; the page BEFORE it is flushed first,
116
+ * and the FIRST page opens one whatever it says — the `!section` test is C's own
117
+ * (`if (!section || adw_view_stack_page_get_starts_section (page))`, :232);
118
+ * · the heading is the opening page's `section-title`, bound with `G_BINDING_SYNC_CREATE`
119
+ * (:237), so a page that opens a section WITHOUT a title gets the separator header —
120
+ * the same empty-title rule `AdwSidebar`'s own sections follow.
121
+ */
122
+ export declare function viewSwitcherSidebarSections(pages: readonly AdwViewSwitcherPage[]): ViewSwitcherSidebarSection[];
95
123
  /**
96
124
  * `update_button`'s visibility rule: `visible && (title != NULL || icon_name != NULL)`.
97
125
  * The empty string is NOT NULL, so `title: ''` still yields a button; `undefined` is read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/adwaita-core",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "Headless Adwaita widget behavior (breakpoint condition grammar + apply/unapply state machine, color-scheme observable, avatar derivation) shared by the web and NativeScript Adwaita renderers — pure TS, no platform imports",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -58,8 +58,8 @@
58
58
  "headless"
59
59
  ],
60
60
  "devDependencies": {
61
- "@gjsify/cli": "^0.54.0",
62
- "@gjsify/unit": "^0.54.0",
61
+ "@gjsify/cli": "^0.56.0",
62
+ "@gjsify/unit": "^0.56.0",
63
63
  "@types/node": "^25.9.2",
64
64
  "typescript": "^6.0.3"
65
65
  },