@andes-ng/primitives 0.0.0-stage → 0.1.0-rc.2

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,3018 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { Signal, OnInit, OnDestroy, WritableSignal, InjectionToken, Injector, ElementRef, Type, Provider, TemplateRef, ViewContainerRef } from '@angular/core';
3
+ import * as _andes_ng_primitives from '@andes-ng/primitives';
4
+ import { Observable } from 'rxjs';
5
+ import { ValidationErrors, ControlValueAccessor } from '@angular/forms';
6
+ import { ComponentType, ConnectedPosition, PositionStrategy } from '@angular/cdk/overlay';
7
+
8
+ declare class AndesButtonPrimitive {
9
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
10
+ protected readonly isAnchor: boolean;
11
+ constructor();
12
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesButtonPrimitive, never>;
13
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesButtonPrimitive, "button[andesButtonPrimitive], a[andesButtonPrimitive]", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
14
+ }
15
+
16
+ /**
17
+ * Which arrow keys move the active item.
18
+ *
19
+ * - `vertical` — `ArrowUp`/`ArrowDown` (Select and Dropdown Menu listboxes, Combobox popups).
20
+ * - `horizontal` — `ArrowLeft`/`ArrowRight`, mirrored for RTL (a horizontal `Tabs` tablist).
21
+ * - `both` — every arrow key moves through the same flat list (grid-ish toolbars, wrapping
22
+ * chip rows). There is no two-dimensional row/column model here: `both` simply means all
23
+ * four arrow keys walk the one list.
24
+ */
25
+ type AndesListOrientation = 'vertical' | 'horizontal' | 'both';
26
+ /**
27
+ * How the active item is surfaced to assistive technology.
28
+ *
29
+ * - `roving-tabindex` — the items are genuinely focusable and real DOM focus moves between
30
+ * them; exactly one item carries `tabindex="0"` while the rest carry `tabindex="-1"`.
31
+ * Use this whenever the container itself is not a text input: Dropdown Menu and Tabs.
32
+ * - `active-descendant` — real DOM focus never leaves one element (typically a text
33
+ * `<input>`); the active item is communicated purely through `aria-activedescendant`
34
+ * pointing at that item's `id`, and items get no `tabindex` at all.
35
+ * Use this for Combobox, whose input must keep focus while the user navigates the popup.
36
+ */
37
+ type AndesListFocusMode = 'roving-tabindex' | 'active-descendant';
38
+ /** Text direction used to mirror horizontal arrow keys. */
39
+ type AndesListTextDirection = 'ltr' | 'rtl';
40
+ /**
41
+ * Options accepted by {@link AndesListNavigation.configure}. Every key is optional; an
42
+ * omitted key leaves the current value untouched, so `configure` is safe to call from an
43
+ * `effect` that only knows about some of the options.
44
+ */
45
+ interface AndesListNavigationConfig {
46
+ /** Which arrow keys navigate. Default `'vertical'`. */
47
+ orientation?: AndesListOrientation;
48
+ /**
49
+ * Whether navigation wraps around the ends of the list. Default `true` (the WAI-ARIA
50
+ * Tabs and Menu convention). Set to `false` for listboxes that should stop at the
51
+ * first/last option instead.
52
+ */
53
+ wrap?: boolean;
54
+ /** Whether typing characters moves the active item. Default `true`. */
55
+ typeahead?: boolean;
56
+ /** How long to wait after the last keystroke before matching. Default `200`ms. */
57
+ typeaheadDebounce?: number;
58
+ /** Whether `Home`/`End` jump to the first/last enabled item. Default `true`. */
59
+ homeAndEnd?: boolean;
60
+ /** How the active item is surfaced. Default `'roving-tabindex'`. */
61
+ focusMode?: AndesListFocusMode;
62
+ /** Text direction used to mirror horizontal arrow keys. Default `'ltr'`. */
63
+ textDirection?: AndesListTextDirection;
64
+ /** Whether disabled items are skipped while navigating. Default `true`. */
65
+ skipDisabled?: boolean;
66
+ }
67
+ /**
68
+ * The contract an item must satisfy to take part in list navigation.
69
+ *
70
+ * {@link AndesListNavigationItem} implements this; the interface exists so that
71
+ * {@link AndesListNavigation} never has to import the item directive (which imports the
72
+ * service), and so consumers can register their own item implementation if they need to.
73
+ *
74
+ * It is deliberately shaped to satisfy the CDK's `Highlightable` and `FocusableOption`
75
+ * interfaces structurally — note that `disabled` is *not* part of it: the CDK's own
76
+ * `ListKeyManagerOption.disabled` is a plain `boolean`, which cannot coexist with a signal
77
+ * input of the same name, so disabled-ness is exposed as {@link isDisabled} and wired into
78
+ * the key manager through its `skipPredicate` instead.
79
+ */
80
+ interface AndesListNavigationItemRef {
81
+ /** Stable DOM `id`, used to wire `aria-activedescendant` in `active-descendant` mode. */
82
+ readonly itemId: string;
83
+ /** Whether this item should be skipped while navigating. */
84
+ readonly isDisabled: Signal<boolean>;
85
+ /** The item's host element. */
86
+ readonly element: HTMLElement;
87
+ /** Text matched against by typeahead. */
88
+ getLabel(): string;
89
+ /** Moves real DOM focus to the item. Only ever called in `roving-tabindex` mode. */
90
+ focus(): void;
91
+ /** Required by the CDK's `Highlightable`; see {@link AndesListNavigationItem}. */
92
+ setActiveStyles(): void;
93
+ /** Required by the CDK's `Highlightable`; see {@link AndesListNavigationItem}. */
94
+ setInactiveStyles(): void;
95
+ }
96
+
97
+ /**
98
+ * How many items can be selected at once.
99
+ *
100
+ * - `single` — at most one item; `value` is that item's value, or `null` when none is.
101
+ * - `multiple` — any number of items; `value` is an array (`null` is read as empty).
102
+ */
103
+ type AndesChoiceMode = 'single' | 'multiple';
104
+ /**
105
+ * The value held by a choice group: one item's value (or `null`) in `single` mode, an array
106
+ * of values in `multiple` mode. The two shapes share one type so `[(value)]` stays a single
107
+ * binding whatever the mode.
108
+ */
109
+ type AndesChoiceValue<T> = T | readonly T[] | null;
110
+ /**
111
+ * The `role` of the group element. It selects a whole {@link AndesChoiceRoleMapping}, so one
112
+ * primitive can serve every "pick from a set" pattern:
113
+ *
114
+ * - `radiogroup` — radio buttons and segmented controls.
115
+ * - `tablist` — tabs.
116
+ * - `toolbar` / `group` — toggle-button groups (`group` when it is not a toolbar).
117
+ * - `listbox` — a listbox whose options are the choices.
118
+ */
119
+ type AndesChoiceGroupRole = 'radiogroup' | 'tablist' | 'toolbar' | 'group' | 'listbox';
120
+ /** The `role` of each item; implied by the group's {@link AndesChoiceGroupRole}. */
121
+ type AndesChoiceItemRole = 'radio' | 'tab' | 'button' | 'option';
122
+ /** The ARIA attribute that carries an item's selected state under a given role. */
123
+ type AndesChoiceStateAttribute = 'aria-checked' | 'aria-selected' | 'aria-pressed';
124
+ /** The value of an item's `data-state` styling hook; which pair applies depends on its role. */
125
+ type AndesChoiceDataState = 'checked' | 'unchecked' | 'active' | 'inactive' | 'on' | 'off' | 'selected' | 'unselected';
126
+ /**
127
+ * Everything that differs between the patterns a choice group can render. Each
128
+ * {@link AndesChoiceGroupRole} has a default mapping in {@link ANDES_CHOICE_ROLE_MAPPINGS};
129
+ * the group's `roleMapping` input overrides single fields of it.
130
+ */
131
+ interface AndesChoiceRoleMapping {
132
+ /** `role` set on every item. */
133
+ readonly itemRole: AndesChoiceItemRole;
134
+ /** The ARIA attribute that reports whether an item is selected. */
135
+ readonly stateAttribute: AndesChoiceStateAttribute;
136
+ /**
137
+ * Whether activating the selected item in `single` mode clears it. Radios and tabs never
138
+ * deselect; a toggle button does. (`multiple` mode always toggles.)
139
+ */
140
+ readonly toggles: boolean;
141
+ /**
142
+ * Whether keyboard navigation also selects the item it lands on, rather than waiting for
143
+ * `Space`/`Enter`. Only meaningful in `single` mode.
144
+ */
145
+ readonly selectionFollowsFocus: boolean;
146
+ /** Whether the role supports `aria-required`, which `required` is then published as. */
147
+ readonly ariaRequired: boolean;
148
+ /** Which arrow keys navigate when `orientation` is not set explicitly. */
149
+ readonly orientation: 'horizontal' | 'vertical' | 'both';
150
+ }
151
+ /**
152
+ * Default {@link AndesChoiceRoleMapping} for every {@link AndesChoiceGroupRole}, following
153
+ * the WAI-ARIA Authoring Practices pattern of the same name.
154
+ */
155
+ declare const ANDES_CHOICE_ROLE_MAPPINGS: Readonly<Record<AndesChoiceGroupRole, AndesChoiceRoleMapping>>;
156
+ /**
157
+ * What the group needs to know about one of its items. {@link AndesChoiceItem} implements
158
+ * it; the group only ever sees items through this contract, which is what lets the two
159
+ * classes live in separate files without importing each other.
160
+ */
161
+ interface AndesChoiceItemRef extends AndesListNavigationItemRef {
162
+ /** The value this item stands for, compared against the group's value. */
163
+ readonly value: Signal<unknown>;
164
+ }
165
+
166
+ /**
167
+ * The shared behavior of every "choose from a set" control: one tab stop, arrow keys move
168
+ * between items, and `Space`/`Enter` (or focus itself) select. It owns the selection and
169
+ * the roving focus, and publishes the ARIA that goes with them; the consuming component
170
+ * owns the markup and the styling.
171
+ *
172
+ * The same directive serves radio groups, segmented controls, tabs, toggle-button groups
173
+ * and listbox-style pickers. What changes between them is only data — the group's
174
+ * {@link role} picks a default {@link AndesChoiceRoleMapping} (item role, which `aria-*`
175
+ * attribute carries the selected state, whether focus selects, which arrows navigate).
176
+ *
177
+ * Roving focus is {@link AndesListNavigation}'s: the group provides its own instance, so
178
+ * items — including ones rendered by a descendant component — find it through DI, and
179
+ * arrow-key movement, `Home`/`End`, wrap-around and disabled-item skipping are the shared
180
+ * implementation rather than a per-component copy.
181
+ *
182
+ * ```html
183
+ * <div andesChoiceGroup role="radiogroup" [(value)]="size" aria-label="Size">
184
+ * @for (option of options; track option.value) {
185
+ * <button type="button" andesChoiceItem [value]="option.value" [disabled]="option.disabled">
186
+ * {{ option.label }}
187
+ * </button>
188
+ * }
189
+ * </div>
190
+ * ```
191
+ *
192
+ * `value` follows the user and the program alike: setting it (or binding `[value]`)
193
+ * updates every item, but only the user's own selection fires {@link changed} — the
194
+ * convention form-control wrappers need to tell the two apart.
195
+ */
196
+ declare class AndesChoiceGroup<T = unknown> {
197
+ private readonly navigation;
198
+ private readonly directionality;
199
+ private readonly host;
200
+ /**
201
+ * The selected value: one item's value (or `null`) in `single` mode, an array in
202
+ * `multiple` mode. Two-way bindable.
203
+ */
204
+ readonly value: _angular_core.ModelSignal<AndesChoiceValue<T>>;
205
+ /** Whether one or many items can be selected. Default `'single'`. */
206
+ readonly mode: _angular_core.InputSignal<AndesChoiceMode>;
207
+ /**
208
+ * The ARIA role of the group, which also decides the role and selected-state attribute
209
+ * of its items (see {@link AndesChoiceRoleMapping}). Default `'radiogroup'`.
210
+ */
211
+ readonly role: _angular_core.InputSignal<AndesChoiceGroupRole>;
212
+ /**
213
+ * Overrides individual fields of the mapping {@link role} selects, for the rare pattern
214
+ * that sits between two of them — e.g. a `toolbar` of items that do not deselect.
215
+ */
216
+ readonly roleMapping: _angular_core.InputSignal<Partial<AndesChoiceRoleMapping> | undefined>;
217
+ /**
218
+ * Whether `single` mode must always keep a selection: activating the selected item then
219
+ * does nothing instead of clearing it. Also published as `aria-required` on roles that
220
+ * support it. Has no effect on `multiple` mode.
221
+ */
222
+ readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
223
+ /**
224
+ * Whether activating the selected item clears it in `single` mode. Defaults to what the
225
+ * {@link role} does natively — a toggle button deselects, a radio or tab does not.
226
+ */
227
+ readonly deselectable: _angular_core.InputSignal<boolean | undefined>;
228
+ /** Disables every item: none can be focused, navigated to or selected. */
229
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
230
+ /**
231
+ * Whether keyboard navigation selects the item it lands on (radio buttons, automatic
232
+ * tabs) or only moves focus until `Space`/`Enter` (manual tabs, toggle groups). Defaults
233
+ * to what the {@link role} does natively. Ignored in `multiple` mode, where moving
234
+ * through the items must not change a selection.
235
+ */
236
+ readonly selectionFollowsFocus: _angular_core.InputSignal<boolean | undefined>;
237
+ /**
238
+ * Which arrow keys navigate: `horizontal` (mirrored for right-to-left), `vertical`, or
239
+ * `both`. Defaults to what the {@link role} does natively.
240
+ */
241
+ readonly orientation: _angular_core.InputSignal<AndesListOrientation | undefined>;
242
+ /** Whether navigation wraps from the last item to the first, and back. Default `true`. */
243
+ readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
244
+ /**
245
+ * Whether typing a character moves focus to the matching item. Off by default: only a
246
+ * listbox-style picker usually wants it, and it would otherwise compete with `Space`.
247
+ */
248
+ readonly typeahead: _angular_core.InputSignalWithTransform<boolean, unknown>;
249
+ /**
250
+ * Overrides the text direction otherwise read from the surrounding `dir`, so a right-to-left
251
+ * group can be forced inside a left-to-right page (and vice versa).
252
+ */
253
+ readonly textDirection: _angular_core.InputSignal<AndesListTextDirection | undefined>;
254
+ /**
255
+ * Decides whether two values are the same choice. Defaults to `Object.is`; supply one
256
+ * when items stand for objects that are re-created between renders.
257
+ */
258
+ readonly compareWith: _angular_core.InputSignal<(a: T, b: T) => boolean>;
259
+ /**
260
+ * Fires with the new value when the *user* changes the selection — by clicking, by
261
+ * `Space`/`Enter`, or by navigating when selection follows focus. It does not fire for
262
+ * writes to {@link value}. (Named `changed` so it cannot be mistaken for the native
263
+ * `change` event that bubbles out of form controls placed inside.)
264
+ */
265
+ readonly changed: _angular_core.OutputEmitterRef<AndesChoiceValue<T>>;
266
+ private readonly forcedDisabled;
267
+ /**
268
+ * Whether the group is disabled, by its input or through {@link setDisabledState}.
269
+ * Items read this to disable themselves.
270
+ */
271
+ readonly isDisabled: Signal<boolean>;
272
+ /** The role mapping in effect: the {@link role}'s default with any overrides applied. */
273
+ readonly mapping: Signal<AndesChoiceRoleMapping>;
274
+ /** The orientation in effect, after falling back to the role's default. */
275
+ protected readonly resolvedOrientation: Signal<AndesListOrientation>;
276
+ /** The text direction in effect: the {@link textDirection} input, else the surrounding `dir`. */
277
+ readonly direction: Signal<AndesListTextDirection>;
278
+ /** Whether activating the selected item clears it. `multiple` mode always toggles. */
279
+ readonly toggles: Signal<boolean>;
280
+ /** Whether navigating selects. Only ever true in `single` mode. */
281
+ readonly followsFocus: Signal<boolean>;
282
+ /** The selected values as an array, whatever the mode. */
283
+ readonly values: Signal<readonly T[]>;
284
+ /**
285
+ * Every choice item registered with the group, in DOM order. Items register themselves,
286
+ * so `@if`/`@for`-rendered items need no bookkeeping.
287
+ */
288
+ readonly items: Signal<readonly AndesChoiceItemRef[]>;
289
+ /** The first selected item that can be focused: the one that carries the tab stop. */
290
+ readonly selectedItem: Signal<AndesChoiceItemRef | null>;
291
+ protected readonly ariaOrientation: Signal<"vertical" | "horizontal" | null>;
292
+ protected readonly ariaMultiselectable: Signal<"true" | null>;
293
+ protected readonly ariaRequired: Signal<"true" | null>;
294
+ constructor();
295
+ /** Whether `value` is part of the current selection. */
296
+ isSelected(value: T): boolean;
297
+ /**
298
+ * Selects `value` as the user would: in `multiple` mode it is added, in `single` mode it
299
+ * replaces the selection. Disabled groups and unknown values are ignored. Fires
300
+ * {@link changed} when the selection actually changes.
301
+ */
302
+ select(value: T): void;
303
+ /**
304
+ * Deselects `value` as the user would. In `single` mode this is refused when the group is
305
+ * {@link required}. Fires {@link changed} when the selection actually changes.
306
+ */
307
+ deselect(value: T): void;
308
+ /** Selects `value` if it is not selected, otherwise deselects it. */
309
+ toggle(value: T): void;
310
+ /**
311
+ * Empties the selection as the user would. Refused in `single` mode when the group is
312
+ * {@link required}. Fires {@link changed} when the selection actually changes.
313
+ */
314
+ clear(): void;
315
+ /**
316
+ * Moves focus into the group: onto the selected item, or the first one that can take
317
+ * focus — the same item a Tab press lands on.
318
+ */
319
+ focus(options?: FocusOptions): void;
320
+ /**
321
+ * Disables the group from outside the template, for wrappers that implement
322
+ * `ControlValueAccessor.setDisabledState`. Combined with the `disabled` input, so
323
+ * either one disables the group.
324
+ */
325
+ setDisabledState(isDisabled: boolean): void;
326
+ /**
327
+ * The user activated an item (click, `Space` or `Enter`). Called by
328
+ * {@link AndesChoiceItem}; follows the toggle rules of the current mapping.
329
+ * @internal
330
+ */
331
+ activate(item: AndesChoiceItemRef): void;
332
+ protected onKeydown(event: KeyboardEvent): void;
333
+ protected onKeyup(event: KeyboardEvent): void;
334
+ protected onFocusout(event: FocusEvent): void;
335
+ private isTypingSpace;
336
+ private itemFor;
337
+ private moveTabStopTo;
338
+ private commit;
339
+ private syncNavigation;
340
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesChoiceGroup<any>, never>;
341
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesChoiceGroup<any>, "[andesChoiceGroup]", ["andesChoiceGroup"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "role": { "alias": "role"; "required": false; "isSignal": true; }; "roleMapping": { "alias": "roleMapping"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "deselectable": { "alias": "deselectable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionFollowsFocus": { "alias": "selectionFollowsFocus"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeahead": { "alias": "typeahead"; "required": false; "isSignal": true; }; "textDirection": { "alias": "textDirection"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "changed": "changed"; }, never, never, true, never>;
342
+ }
343
+
344
+ /**
345
+ * One choice inside an {@link AndesChoiceGroup}: a radio, a tab, a toggle button or an
346
+ * option. Registers itself with the group on init and removes itself on destroy.
347
+ *
348
+ * It publishes the roving `tabindex`, the item `role`, the selected-state attribute the
349
+ * group's role mapping calls for (`aria-checked`, `aria-selected` or `aria-pressed`), and
350
+ * `data-state`/`data-disabled`/`data-active` hooks for styling. It sets no CSS.
351
+ *
352
+ * ```html
353
+ * <button type="button" andesChoiceItem value="list" [controls]="'panel-list'">List</button>
354
+ * ```
355
+ *
356
+ * Put it on the element that takes keyboard focus. A `<button>` is the usual choice — it
357
+ * gets native `disabled` for free — but any element works, since `Space` and `Enter` are
358
+ * handled by the group instead of relying on a native click.
359
+ */
360
+ declare class AndesChoiceItem<T = unknown> implements AndesChoiceItemRef, OnInit, OnDestroy {
361
+ private readonly group;
362
+ private readonly navigation;
363
+ /** The value this item stands for. Compared with the group's value (see its `compareWith`). */
364
+ readonly value: _angular_core.InputSignal<T>;
365
+ /** Disables this item: it is skipped by navigation and cannot be selected. */
366
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
367
+ /**
368
+ * The `id` of the element this item controls — the tab panel, for a tab. Published as
369
+ * `aria-controls`; leaving it unset omits the attribute, which is right for patterns
370
+ * that control nothing.
371
+ */
372
+ readonly controls: _angular_core.InputSignal<string | undefined>;
373
+ /**
374
+ * Text that typeahead matches against. Defaults to the item's own text content; set it
375
+ * when the visible text is an icon or is split across elements.
376
+ */
377
+ readonly typeaheadLabel: _angular_core.InputSignal<string | undefined>;
378
+ readonly element: HTMLElement;
379
+ /** The item's DOM `id` — its own, when it has one, so authored ids are never replaced. */
380
+ readonly itemId: string;
381
+ /** Only a native button has a `disabled` attribute worth setting; other elements get ARIA only. */
382
+ protected readonly nativeDisabled: boolean;
383
+ /** Whether the item is disabled, on its own or because its group is. */
384
+ readonly isDisabled: Signal<boolean>;
385
+ /** Whether this item is part of the group's selection. */
386
+ readonly selected: Signal<boolean>;
387
+ /** Whether this item holds the group's roving focus (the one tab stop). */
388
+ readonly active: Signal<boolean>;
389
+ protected readonly role: Signal<_andes_ng_primitives.AndesChoiceItemRole>;
390
+ protected readonly stateAttribute: Signal<AndesChoiceStateAttribute>;
391
+ /** Styling hook whose vocabulary matches the role: checked, active (tab), on, or selected. */
392
+ protected readonly dataState: Signal<AndesChoiceDataState>;
393
+ /** `0` on the one roving tab stop, `-1` on the rest (and on everything while disabled). */
394
+ protected readonly tabIndex: Signal<0 | -1>;
395
+ ngOnInit(): void;
396
+ ngOnDestroy(): void;
397
+ getLabel(): string;
398
+ focus(): void;
399
+ setActiveStyles(): void;
400
+ setInactiveStyles(): void;
401
+ protected onClick(): void;
402
+ /** Keeps the tab stop on whichever item the user reached, however they reached it. */
403
+ protected onFocusin(): void;
404
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesChoiceItem<any>, never>;
405
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesChoiceItem<any>, "[andesChoiceItem]", ["andesChoiceItem"], { "value": { "alias": "value"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "typeaheadLabel": { "alias": "typeaheadLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
406
+ }
407
+
408
+ /** Identifies a node. Keys are compared with `===`, so `1` and `'1'` are different nodes. */
409
+ type AndesTreeNavigationKey = string | number;
410
+ /**
411
+ * Which arrow keys move between nodes.
412
+ *
413
+ * - `vertical` — `ArrowUp`/`ArrowDown` move, `ArrowRight`/`ArrowLeft` (mirrored for RTL) open
414
+ * and close. The WAI-ARIA TreeView layout; also nested menus and cascading columns.
415
+ * - `horizontal` — `ArrowLeft`/`ArrowRight` (mirrored for RTL) move, `ArrowDown` opens and
416
+ * `ArrowUp` closes. The menubar layout, where the top level runs sideways and submenus drop
417
+ * down.
418
+ *
419
+ * The orientation is a property of the whole navigation. A bar whose panels run in the other
420
+ * direction uses one navigation for the bar and one per panel, because each panel is its own
421
+ * keyboard scope anyway.
422
+ */
423
+ type AndesTreeOrientation = 'vertical' | 'horizontal';
424
+ /**
425
+ * Which nodes a move key walks over.
426
+ *
427
+ * - `tree` — every visible node, depth first, regardless of level. `Up`/`Down` step across
428
+ * level boundaries and `Home`/`End` reach the first/last visible node. The TreeView default.
429
+ * - `level` — only the siblings of the active node. `Up`/`Down`, `Home`/`End` and typeahead
430
+ * stay inside the current group, and only the open/close keys change level. This is how a
431
+ * cascading column picker or a menu with fly-out submenus behaves: each group is its own
432
+ * list.
433
+ */
434
+ type AndesTreeNavigationScope = 'tree' | 'level';
435
+ /**
436
+ * How the active node is surfaced to assistive technology.
437
+ *
438
+ * - `roving-tabindex` — nodes are genuinely focusable and real DOM focus moves between them;
439
+ * exactly one node carries `tabindex="0"` and the rest `tabindex="-1"`. Use this when the
440
+ * tree itself holds focus.
441
+ * - `active-descendant` — real DOM focus stays on one element (a search input, a popup
442
+ * container) and the active node is communicated through `aria-activedescendant`. Nodes get
443
+ * no `tabindex`.
444
+ */
445
+ type AndesTreeFocusMode = 'roving-tabindex' | 'active-descendant';
446
+ /** Text direction used to mirror the horizontal arrow keys. */
447
+ type AndesTreeTextDirection = 'ltr' | 'rtl';
448
+ /**
449
+ * A node as the navigation needs to know it. This is what an {@link AndesTreeNavigationNodeSource}
450
+ * returns, and what items contribute when no source is set.
451
+ */
452
+ interface AndesTreeNavigationNode {
453
+ /** Unique key of the node. */
454
+ readonly key: AndesTreeNavigationKey;
455
+ /**
456
+ * Key of the parent node. `null`, `undefined` and a key that names no known node all make
457
+ * the node a root, so a partially loaded tree degrades to "shown at the top" rather than
458
+ * disappearing.
459
+ */
460
+ readonly parentKey?: AndesTreeNavigationKey | null;
461
+ /** Whether keyboard navigation skips the node (see {@link AndesTreeNavigationConfig.skipDisabled}). */
462
+ readonly disabled?: boolean;
463
+ /**
464
+ * Whether the node can be expanded. Leave it out to derive it from "has known children";
465
+ * set it to `true` for a parent whose children are not loaded or not rendered yet, which is
466
+ * what lets `ArrowRight` open it and trigger the load.
467
+ */
468
+ readonly expandable?: boolean;
469
+ /** Text typeahead matches against. Defaults to the mounted item's own text, else `''`. */
470
+ readonly label?: string;
471
+ }
472
+ /**
473
+ * Supplies the nodes of the tree in **data order**: siblings in the order they appear, parents
474
+ * identified by `parentKey`. Parents do not have to precede their children, and the nodes do
475
+ * not have to be mounted.
476
+ *
477
+ * This is what makes windowed (virtualized) trees work: rows that are scrolled out of view have
478
+ * no item, but they are still part of the keyboard order. Read signals inside it freely — it is
479
+ * called from a `computed`, so the navigation follows whatever it reads.
480
+ */
481
+ type AndesTreeNavigationNodeSource = () => readonly AndesTreeNavigationNode[];
482
+ /**
483
+ * A node as the navigation resolved it: the hierarchy facts a tree row needs for its ARIA
484
+ * attributes, independent of whether the node is expanded.
485
+ */
486
+ interface AndesTreeNavigationNodeInfo {
487
+ /** The node's key. */
488
+ readonly key: AndesTreeNavigationKey;
489
+ /** Key of the parent, or `null` for a root. */
490
+ readonly parentKey: AndesTreeNavigationKey | null;
491
+ /** 1 for a root; becomes `aria-level`. */
492
+ readonly level: number;
493
+ /**
494
+ * 1-based position among all siblings; becomes `aria-posinset`. Collapsed and hidden
495
+ * siblings count, as the WAI-ARIA pattern asks.
496
+ */
497
+ readonly posInSet: number;
498
+ /** Number of siblings, the node included; becomes `aria-setsize`. */
499
+ readonly setSize: number;
500
+ /** Whether keyboard navigation skips the node. */
501
+ readonly disabled: boolean;
502
+ /** Whether the node can be expanded: declared explicitly, or because it has known children. */
503
+ readonly expandable: boolean;
504
+ }
505
+ /** Reported when navigation expands or collapses a node. */
506
+ interface AndesTreeNavigationExpandChange {
507
+ /** The node that changed. */
508
+ readonly key: AndesTreeNavigationKey;
509
+ /** Its new state. */
510
+ readonly expanded: boolean;
511
+ }
512
+ /**
513
+ * Reported when keyboard navigation lands on a node that has no mounted item, i.e. a row
514
+ * outside a virtual list's rendered window. The consumer scrolls to {@link index}; when the
515
+ * row's item mounts it takes focus (or scrolls itself into view) on its own.
516
+ */
517
+ interface AndesTreeNavigationRevealRequest {
518
+ /** The node navigation moved to. */
519
+ readonly key: AndesTreeNavigationKey;
520
+ /** Its index among the visible nodes. */
521
+ readonly index: number;
522
+ }
523
+ /**
524
+ * A writable signal the navigation can keep the expanded keys in, so the owner of the state
525
+ * stays the consumer. A `Set` and a plain array are both accepted — whichever shape the signal
526
+ * currently holds is the shape that gets written back.
527
+ */
528
+ type AndesTreeNavigationExpandedSource<K extends AndesTreeNavigationKey = AndesTreeNavigationKey> = WritableSignal<ReadonlySet<K>> | WritableSignal<readonly K[]>;
529
+ /**
530
+ * Options accepted by {@link AndesTreeNavigation.configure}. Every key is optional; an omitted
531
+ * key leaves the current value untouched, so `configure` is safe to call from an `effect` that
532
+ * only knows about some of the options.
533
+ */
534
+ interface AndesTreeNavigationConfig {
535
+ /** Which arrow keys move and which open/close. Default `'vertical'`. */
536
+ orientation?: AndesTreeOrientation;
537
+ /** Which nodes a move walks over. Default `'tree'`. */
538
+ scope?: AndesTreeNavigationScope;
539
+ /**
540
+ * Whether moving past the first/last node wraps around. Default `false`, as in the
541
+ * TreeView pattern. Menus and accordions turn it on.
542
+ */
543
+ wrap?: boolean;
544
+ /** Whether typing characters moves to the next node whose label matches. Default `true`. */
545
+ typeahead?: boolean;
546
+ /** How long a typeahead query stays buffered after the last keystroke. Default `500`ms. */
547
+ typeaheadDebounce?: number;
548
+ /** Whether `Home`/`End` jump to the first/last node. Default `true`. */
549
+ homeAndEnd?: boolean;
550
+ /** Whether `*` expands every sibling of the active node. Default `true`. */
551
+ expandSiblings?: boolean;
552
+ /** How the active node is surfaced. Default `'roving-tabindex'`. */
553
+ focusMode?: AndesTreeFocusMode;
554
+ /** Text direction used to mirror the horizontal arrow keys. Default `'ltr'`. */
555
+ textDirection?: AndesTreeTextDirection;
556
+ /** Whether disabled nodes are skipped while navigating. Default `true`. */
557
+ skipDisabled?: boolean;
558
+ /**
559
+ * Whether the keyboard may expand and collapse a disabled node. Only matters when
560
+ * {@link skipDisabled} is `false` and disabled nodes can therefore hold focus. Default
561
+ * `false`: a disabled node is inert. Trees whose "disabled" nodes are only unselectable, not
562
+ * unopenable, turn this on.
563
+ */
564
+ expandDisabled?: boolean;
565
+ /**
566
+ * Whether the open key, pressed on a collapsed parent, also moves into its first child
567
+ * instead of only opening it. Default `false` (the TreeView pattern: open, stay). Menus and
568
+ * cascading columns turn it on. If the children are not there yet the move happens as soon
569
+ * as they appear.
570
+ */
571
+ enterOnExpand?: boolean;
572
+ /**
573
+ * Whether the close key, pressed on a child, also collapses the parent it steps back to.
574
+ * Default `false` (the TreeView pattern: just move). Menus turn it on, because leaving a
575
+ * submenu closes it.
576
+ */
577
+ collapseOnLeave?: boolean;
578
+ /**
579
+ * Initial or replacement expanded keys. Applies to whichever store is current: the
580
+ * navigation's own, or the one passed to {@link AndesTreeNavigation.bindExpanded}.
581
+ */
582
+ expanded?: Iterable<AndesTreeNavigationKey>;
583
+ }
584
+ /**
585
+ * The contract an item must satisfy to take part in tree navigation.
586
+ *
587
+ * {@link AndesTreeNavigationItem} implements this; the interface exists so that
588
+ * {@link AndesTreeNavigation} never has to import the item directive (which imports the
589
+ * service), and so consumers can register their own item implementation if they need to.
590
+ */
591
+ interface AndesTreeNavigationItemRef {
592
+ /** Stable DOM `id`, used to wire `aria-activedescendant` in `active-descendant` mode. */
593
+ readonly itemId: string;
594
+ /** The item's host element. */
595
+ readonly element: HTMLElement;
596
+ /** The node's key. */
597
+ readonly itemKey: Signal<AndesTreeNavigationKey>;
598
+ /** The parent's key, `null` for a root. */
599
+ readonly itemParentKey: Signal<AndesTreeNavigationKey | null>;
600
+ /** Whether navigation should skip the item. */
601
+ readonly isDisabled: Signal<boolean>;
602
+ /** Explicit expandability; `undefined` means "derive it from the children". */
603
+ readonly isExpandable: Signal<boolean | undefined>;
604
+ /** Text matched against by typeahead. */
605
+ getLabel(): string;
606
+ /** Moves real DOM focus to the item. Only called in `roving-tabindex` mode. */
607
+ focus(): void;
608
+ /** Brings the item into view without moving focus. Only called in `active-descendant` mode. */
609
+ reveal(): void;
610
+ }
611
+
612
+ /** Default time a typeahead query stays buffered, matching the WAI-ARIA examples. */
613
+ declare const ANDES_TREE_TYPEAHEAD_DEBOUNCE = 500;
614
+ /**
615
+ * Shared keyboard-navigation state for hierarchical widgets: trees, cascading pickers, menus
616
+ * with fly-out submenus, grouped navigation. It owns the *visible-node* order (collapsed
617
+ * subtrees are skipped), the active node, the expanded keys, the WAI-ARIA TreeView keys and
618
+ * the ARIA facts (`aria-level`, `aria-posinset`, `aria-setsize`, `aria-expanded`) a node needs.
619
+ *
620
+ * It is a DI-scoped service, not a directive: provide it from the consuming component so that
621
+ * every descendant item — including items rendered into a CDK overlay, which are injector
622
+ * descendants but not DOM descendants — can inject the same instance.
623
+ *
624
+ * ```ts
625
+ * @Component({
626
+ * selector: 'andes-tree',
627
+ * providers: [AndesTreeNavigation],
628
+ * template: `
629
+ * <ul role="tree" andesTreeNavigationKeys>
630
+ * @for (node of nodes(); track node.key) {
631
+ * <li
632
+ * role="treeitem"
633
+ * [andesTreeNavigationItem]="node.key"
634
+ * [parentKey]="node.parentKey"
635
+ * [disabled]="node.disabled"
636
+ * >
637
+ * {{ node.title }}
638
+ * </li>
639
+ * }
640
+ * </ul>
641
+ * `,
642
+ * })
643
+ * export class AndesTree {
644
+ * protected readonly navigation = inject(AndesTreeNavigation);
645
+ * }
646
+ * ```
647
+ *
648
+ * ### Where the structure comes from
649
+ *
650
+ * By default the structure is read from the registered items: each item names its key and its
651
+ * parent, and their DOM order is the sibling order. That is exact for any tree that renders all
652
+ * of its nodes. A **windowed** tree renders only the rows in view, so items come and go as the
653
+ * user scrolls; for those, give the navigation the nodes directly with {@link setNodes}. Its
654
+ * data order then drives everything, and rows that are not mounted are still part of the
655
+ * keyboard order: moving onto one emits {@link revealRequested} so the list can scroll to it,
656
+ * and the item takes focus the moment it mounts.
657
+ *
658
+ * ### What it deliberately does not do
659
+ *
660
+ * It owns no selection or checked state, sets no `role`, and never interprets `Enter` or
661
+ * `Space` — activation is the consumer's. "Active node" is keyboard focus, not selection.
662
+ */
663
+ declare class AndesTreeNavigation {
664
+ private readonly destroyRef;
665
+ private readonly document;
666
+ private readonly isBrowser;
667
+ /** Which arrow keys move and which open/close. */
668
+ readonly orientation: WritableSignal<AndesTreeOrientation>;
669
+ /** Which nodes a move key walks over. */
670
+ readonly scope: WritableSignal<AndesTreeNavigationScope>;
671
+ /** Whether moving past the first/last node wraps around. */
672
+ readonly wrap: WritableSignal<boolean>;
673
+ /** Whether typing characters moves to the next matching node. */
674
+ readonly typeahead: WritableSignal<boolean>;
675
+ /** How long a typeahead query stays buffered after the last keystroke. */
676
+ readonly typeaheadDebounce: WritableSignal<number>;
677
+ /** Whether `Home`/`End` jump to the first/last node. */
678
+ readonly homeAndEnd: WritableSignal<boolean>;
679
+ /** Whether `*` expands every sibling of the active node. */
680
+ readonly expandSiblings: WritableSignal<boolean>;
681
+ /** How the active node is surfaced to assistive technology. */
682
+ readonly focusMode: WritableSignal<AndesTreeFocusMode>;
683
+ /** Text direction used to mirror the horizontal arrow keys. */
684
+ readonly textDirection: WritableSignal<AndesTreeTextDirection>;
685
+ /** Whether disabled nodes are skipped while navigating. */
686
+ readonly skipDisabled: WritableSignal<boolean>;
687
+ /** Whether the keyboard may expand and collapse a disabled node. */
688
+ readonly expandDisabled: WritableSignal<boolean>;
689
+ /** Whether the open key on a collapsed parent also steps into its first child. */
690
+ readonly enterOnExpand: WritableSignal<boolean>;
691
+ /** Whether the close key on a child also collapses the parent it steps back to. */
692
+ readonly collapseOnLeave: WritableSignal<boolean>;
693
+ private readonly source;
694
+ private readonly registered;
695
+ private readonly orderVersion;
696
+ /**
697
+ * Every registered item, in DOM order. Items that scroll out of a windowed tree are not in
698
+ * here, which is why the navigation reads its structure from {@link setNodes} there.
699
+ */
700
+ readonly items: Signal<readonly AndesTreeNavigationItemRef[]>;
701
+ private readonly itemByKey;
702
+ private readonly itemByElement;
703
+ private readonly nodes;
704
+ private readonly structure;
705
+ private readonly ownExpanded;
706
+ private readonly expandedStore;
707
+ /** The keys of the expanded nodes. */
708
+ readonly expandedKeys: Signal<ReadonlySet<AndesTreeNavigationKey>>;
709
+ private readonly visible;
710
+ /**
711
+ * The nodes a user can reach, in reading order: every node whose ancestors are all expanded.
712
+ * This — not the DOM — is the order the arrow keys follow, so collapsed subtrees cost
713
+ * nothing and an unmounted row still has its place. A virtual list renders a window of it.
714
+ */
715
+ readonly visibleNodes: Signal<readonly AndesTreeNavigationNodeInfo[]>;
716
+ private readonly visibleIndex;
717
+ private readonly _activeKey;
718
+ /** The active node's key, or `null` when nothing is active yet. */
719
+ readonly activeKey: Signal<AndesTreeNavigationKey | null>;
720
+ /** The active node's mounted item, or `null` — also when the node is outside a window. */
721
+ readonly activeItem: Signal<AndesTreeNavigationItemRef | null>;
722
+ /** Index of the active node within {@link visibleNodes}, or `-1`. */
723
+ readonly activeIndex: Signal<number>;
724
+ /**
725
+ * The `id` to publish as `aria-activedescendant`, or `null` in `roving-tabindex` mode (where
726
+ * real focus already conveys the active node) and while the active node has no mounted item.
727
+ */
728
+ readonly activeDescendantId: Signal<string | null>;
729
+ /**
730
+ * The node that should carry `tabindex="0"` in `roving-tabindex` mode: the active node, or —
731
+ * before anything is active, or once the active node is hidden or skipped — the first node
732
+ * the user can navigate to, so the tree is always reachable with one Tab press.
733
+ */
734
+ readonly tabbableKey: Signal<AndesTreeNavigationKey | null>;
735
+ /**
736
+ * `0` when the tree has a tab stop but no mounted item to put it on — the tabbable node is
737
+ * outside a windowed tree's rendered range — and `null` otherwise. Bind it to the
738
+ * container's `tabindex` in virtualized templates; {@link AndesTreeNavigationKeys} forwards
739
+ * focus on the container to the tabbable node, which scrolls it in.
740
+ */
741
+ readonly containerTabIndex: Signal<0 | null>;
742
+ private readonly _expandedChange;
743
+ /**
744
+ * Emits whenever the navigation expands or collapses a node on the user's behalf — an arrow
745
+ * key, `*`, or {@link expand}/{@link collapse}/{@link toggle}. This is where a lazy tree
746
+ * starts loading children. {@link setExpanded} and `configure({ expanded })` are the consumer
747
+ * speaking, so they do not echo back.
748
+ */
749
+ readonly expandedChange: Observable<AndesTreeNavigationExpandChange>;
750
+ private readonly _revealRequested;
751
+ /**
752
+ * Emits when navigation lands on a node that has no mounted item (see {@link setNodes}).
753
+ * Scroll the list to the given index; the item focuses itself when it mounts.
754
+ */
755
+ readonly revealRequested: Observable<AndesTreeNavigationRevealRequest>;
756
+ private readonly _tabOut;
757
+ /** Emits when `Tab` is pressed, so a menu or popup can close and hand focus back. */
758
+ readonly tabOut: Observable<void>;
759
+ private typeaheadBuffer;
760
+ private typeaheadTimer;
761
+ /** The node focus should land on as soon as its item mounts. */
762
+ private pendingFocusKey;
763
+ /** The parent to step into as soon as it has a child (`enterOnExpand` with lazy children). */
764
+ private pendingEnterKey;
765
+ /** The active node's ancestors, nearest first, kept for when the node itself disappears. */
766
+ private activeAncestry;
767
+ /** Whether keyboard focus was last seen on one of this navigation's items. */
768
+ private focusInside;
769
+ private destroyed;
770
+ constructor();
771
+ /** Applies any subset of the configuration options in one call. */
772
+ configure(config: AndesTreeNavigationConfig): void;
773
+ /**
774
+ * Supplies the nodes in data order instead of reading them from the registered items — the
775
+ * mode for windowed trees, and for any tree that wants to navigate nodes it has not rendered.
776
+ * Pass `null` to go back to deriving the structure from items.
777
+ *
778
+ * The function runs inside a `computed`, so signals it reads (the node list, a loaded-children
779
+ * map) keep the navigation up to date by themselves.
780
+ */
781
+ setNodes(source: AndesTreeNavigationNodeSource | null): void;
782
+ /**
783
+ * Keeps the expanded keys in a signal the consumer owns, e.g. a component's `model()`, so
784
+ * the state has one home and two-way binding just works. A `Set` and an array are both
785
+ * accepted; whichever shape the signal holds is written back. Pass `null` to return to the
786
+ * navigation's own store.
787
+ */
788
+ bindExpanded<K extends AndesTreeNavigationKey>(source: AndesTreeNavigationExpandedSource<K> | null): void;
789
+ /** Adds an item to the tree. Called by `AndesTreeNavigationItem` on init. */
790
+ register(item: AndesTreeNavigationItemRef): void;
791
+ /**
792
+ * Removes an item from the tree. Called by `AndesTreeNavigationItem` on destroy. If that item
793
+ * was the active one, the reconcile that follows decides where the active node goes.
794
+ */
795
+ unregister(item: AndesTreeNavigationItemRef): void;
796
+ /**
797
+ * Re-reads the DOM order of the items. The order is re-checked whenever the tree's structure
798
+ * changes, but moving existing elements around (re-sorting a `@for` list) changes no signal,
799
+ * so call this after such a reorder. Trees fed by {@link setNodes} never need it.
800
+ */
801
+ refreshOrder(): void;
802
+ /** The hierarchy facts of a node, or `null` for a key the navigation does not know. */
803
+ nodeInfo(key: AndesTreeNavigationKey): AndesTreeNavigationNodeInfo | null;
804
+ /** Index of a node among the {@link visibleNodes}, or `-1` when it is hidden or unknown. */
805
+ visibleIndexOf(key: AndesTreeNavigationKey): number;
806
+ /** The mounted item of a node, if it has one. */
807
+ itemForKey(key: AndesTreeNavigationKey): AndesTreeNavigationItemRef | null;
808
+ /** Whether a node is expanded. */
809
+ isExpanded(key: AndesTreeNavigationKey): boolean;
810
+ /** Expands a node that can be expanded. Returns whether anything changed. */
811
+ expand(key: AndesTreeNavigationKey): boolean;
812
+ /** Collapses a node. If the active node was inside it, the active node becomes this one. */
813
+ collapse(key: AndesTreeNavigationKey): boolean;
814
+ /** Expands a collapsed node and collapses an expanded one. */
815
+ toggle(key: AndesTreeNavigationKey): boolean;
816
+ /** Expands every known node that can be expanded. */
817
+ expandAll(): void;
818
+ /** Collapses every node. */
819
+ collapseAll(): void;
820
+ /** Expands every ancestor of a node, so that the node becomes visible. */
821
+ expandTo(key: AndesTreeNavigationKey): void;
822
+ /**
823
+ * Replaces the expanded keys. This is the consumer setting state, so it emits no
824
+ * {@link expandedChange}.
825
+ */
826
+ setExpanded(keys: Iterable<AndesTreeNavigationKey>): void;
827
+ /**
828
+ * Makes a visible, navigable node active and brings it into focus. In `roving-tabindex` mode
829
+ * that moves real focus to its item; in `active-descendant` mode it scrolls the item into
830
+ * view. If the node has no mounted item (a windowed tree) it is still made active, and
831
+ * {@link revealRequested} asks the list to render it. Returns `false` for a hidden, unknown
832
+ * or skipped node, leaving everything as it was.
833
+ */
834
+ focusKey(key: AndesTreeNavigationKey): boolean;
835
+ /** Moves the active node to the first navigable node of the current scope. */
836
+ focusFirst(): void;
837
+ /** Moves the active node to the last navigable node of the current scope. */
838
+ focusLast(): void;
839
+ /** Moves the active node to the next navigable node, respecting {@link wrap}. */
840
+ focusNext(): void;
841
+ /** Moves the active node to the previous navigable node, respecting {@link wrap}. */
842
+ focusPrevious(): void;
843
+ /**
844
+ * Moves the active pointer without focusing anything — for restoring the active node on
845
+ * init (say, the selected one), or following a click that already moved real focus. Accepts
846
+ * any known node, even one that is currently collapsed away. Returns `false` for an unknown
847
+ * key.
848
+ */
849
+ setActiveKeySilently(key: AndesTreeNavigationKey | null): boolean;
850
+ /** Clears the active node, e.g. when a popup closes. */
851
+ clearActive(): void;
852
+ /** Whether a typeahead query is currently buffered. */
853
+ isTyping(): boolean;
854
+ /** Discards any buffered typeahead query. */
855
+ cancelTypeahead(): void;
856
+ private clearTypeaheadTimer;
857
+ /**
858
+ * Called by an item when real focus lands on it, so the active node follows the mouse or a
859
+ * direct Tab and Tabbing away and back returns to the same place.
860
+ */
861
+ onItemFocusin(item: AndesTreeNavigationItemRef): void;
862
+ /**
863
+ * Called by an item when it loses focus. Focus that merely moves to another node keeps the
864
+ * "focus is in the tree" memory; so does an item that was removed while focused, which is
865
+ * exactly the case where focus must be handed to an ancestor instead of being lost.
866
+ */
867
+ onItemFocusout(item: AndesTreeNavigationItemRef, event: FocusEvent): void;
868
+ /**
869
+ * Called by an item once it has rendered, so a node that navigation reached before it was
870
+ * mounted (a windowed tree) receives the focus it was promised.
871
+ */
872
+ onItemRendered(item: AndesTreeNavigationItemRef): void;
873
+ /**
874
+ * Handles a `keydown` on whichever element owns keyboard focus for this tree — the tree or a
875
+ * node in `roving-tabindex` mode, the input in `active-descendant` mode.
876
+ * `AndesTreeNavigationKeys` wires this up for you.
877
+ *
878
+ * Only navigation keys are consumed (and `preventDefault`ed): the four arrows, `Home`, `End`,
879
+ * `*` and typeahead characters. `Enter`, `Space` (outside a typeahead query) and `Escape`
880
+ * are left entirely to the consumer, as are keys typed into a control nested inside a node
881
+ * and any event a consumer handler already `preventDefault`ed — which is how a multi-select
882
+ * tree claims `Shift`+arrow before the navigation sees it.
883
+ */
884
+ onKeydown(event: KeyboardEvent): void;
885
+ private handleKey;
886
+ /** Maps an arrow key to what it means in the current orientation and direction. */
887
+ private commandFor;
888
+ /**
889
+ * Where a key press came from: the key of the node whose item it was typed on, `null` for
890
+ * the container itself, or `'inside-node'` for a control nested inside a node, whose keys
891
+ * belong to that control.
892
+ */
893
+ private keyOrigin;
894
+ /** The active node's record, provided it is still visible. */
895
+ private currentRecord;
896
+ private isSkipped;
897
+ private isNavigable;
898
+ private canToggle;
899
+ /** The nodes a move key walks over: everything visible, or just the active node's group. */
900
+ private scopeList;
901
+ private indexInScope;
902
+ private move;
903
+ private stepFrom;
904
+ private edgeOf;
905
+ private focusEdge;
906
+ private firstNavigableChild;
907
+ /** Opens a collapsed parent, or steps into the first child of an open one. */
908
+ private open;
909
+ private stepIntoOrWait;
910
+ /** Closes an open parent, or steps out to the nearest navigable ancestor. */
911
+ private close;
912
+ private expandSiblingsOf;
913
+ private matchTypeahead;
914
+ private findByPrefix;
915
+ private labelOf;
916
+ /** Records the active node and its ancestry, and drops waits that belonged to the old one. */
917
+ private activate;
918
+ private applyFocus;
919
+ private currentStore;
920
+ /**
921
+ * Writes a new expanded set, optionally announcing what changed, and then makes sure the
922
+ * active node is still somewhere a user can be.
923
+ */
924
+ private commitExpanded;
925
+ /**
926
+ * Keeps the active node somewhere reachable. When it has been hidden by a collapse, it moves
927
+ * to the nearest visible ancestor — and takes real focus along if the tree had it. When it
928
+ * has been deleted from a node source, the same applies to its remembered ancestry. When an
929
+ * item-derived node merely disappears, nothing is guessed and the active node is cleared:
930
+ * an unmounted item is not evidence that the node is gone.
931
+ */
932
+ private reconcileActive;
933
+ /** Whether keyboard focus is on this tree right now, or was when its node was removed. */
934
+ private hadFocus;
935
+ /** Finishes a step into a parent whose children were not there when the key was pressed. */
936
+ private resolvePendingEnter;
937
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesTreeNavigation, never>;
938
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesTreeNavigation>;
939
+ }
940
+
941
+ /**
942
+ * One node of a tree governed by {@link AndesTreeNavigation}: a tree item, a menu item with a
943
+ * submenu, a cascader option. It tells the navigation who it is (`key`), where it sits
944
+ * (`parentKey`, or the enclosing item when items are nested in the template), whether it is
945
+ * `disabled` or `expandable`, and in return receives everything a node needs as signals:
946
+ * `ariaLevel`, `ariaPosinset`, `ariaSetsize`, `ariaExpanded`, `tabIndex`, `active`.
947
+ *
948
+ * It registers on init and unregisters on destroy, so `@if`/`@for`-driven trees and
949
+ * windowed lists, whose items come and go as the user scrolls, need no bookkeeping.
950
+ *
951
+ * By default it also writes those facts to the host (`id`, `tabindex`, `aria-level`,
952
+ * `aria-posinset`, `aria-setsize`, `aria-expanded`, `aria-disabled`, and `data-active`,
953
+ * `data-disabled`, `data-expanded` hooks for styling). Roles and selection attributes
954
+ * (`role="treeitem"`, `aria-selected`, `aria-checked`) stay with the consuming component.
955
+ * Turn the ARIA attributes off with `[manageAria]="false"` when the component binds them itself.
956
+ *
957
+ * ```html
958
+ * <!-- Flat rows, as a virtual list renders them -->
959
+ * <div role="treeitem"
960
+ * [andesTreeNavigationItem]="row.key"
961
+ * [parentKey]="row.parentKey"
962
+ * [expandable]="row.hasChildren">
963
+ * {{ row.title }}
964
+ * </div>
965
+ *
966
+ * <!-- Nested markup: the parent is the enclosing item -->
967
+ * <li role="treeitem" [andesTreeNavigationItem]="'docs'">
968
+ * Docs
969
+ * <ul role="group">
970
+ * <li role="treeitem" [andesTreeNavigationItem]="'docs/api'">API</li>
971
+ * </ul>
972
+ * </li>
973
+ * ```
974
+ */
975
+ declare class AndesTreeNavigationItem implements AndesTreeNavigationItemRef, OnInit, OnDestroy {
976
+ /**
977
+ * The node's unique key. Bound through the directive's own selector so the common case needs
978
+ * no extra attribute: `[andesTreeNavigationItem]="node.key"`.
979
+ */
980
+ readonly key: _angular_core.InputSignal<AndesTreeNavigationKey>;
981
+ /**
982
+ * Key of the parent node, or `null` for a root. Leave it unset when items are nested in the
983
+ * template, in which case the enclosing item is the parent; set it for flat markup, where
984
+ * nesting is expressed by data rather than by the DOM.
985
+ */
986
+ readonly parentKey: _angular_core.InputSignal<AndesTreeNavigationKey | null | undefined>;
987
+ /** Whether navigation skips the node. */
988
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
989
+ /**
990
+ * Whether the node can be expanded. Leave it unset to derive it from "has registered
991
+ * children"; set it for a parent whose children are not rendered yet (collapsed subtrees
992
+ * that are not in the DOM, lazily loaded children), so that `ArrowRight` can open it.
993
+ */
994
+ readonly expandable: _angular_core.InputSignalWithTransform<boolean | undefined, string | boolean | null | undefined>;
995
+ /**
996
+ * Text typeahead matches against. Defaults to the item's own text content, which is usually
997
+ * right; set it when the visible text is decorative or split across elements.
998
+ */
999
+ readonly typeaheadLabel: _angular_core.InputSignal<string | undefined>;
1000
+ /**
1001
+ * Overrides `aria-level` for trees that render only a slice of the hierarchy and so cannot
1002
+ * count ancestors. By default the level is derived from the parent chain.
1003
+ */
1004
+ readonly level: _angular_core.InputSignalWithTransform<number | undefined, string | number | null | undefined>;
1005
+ /** Overrides `aria-posinset`, e.g. a server-side position in a paged list of siblings. */
1006
+ readonly posInSet: _angular_core.InputSignalWithTransform<number | undefined, string | number | null | undefined>;
1007
+ /** Overrides `aria-setsize`, e.g. the total sibling count of a partly loaded list. */
1008
+ readonly setSize: _angular_core.InputSignalWithTransform<number | undefined, string | number | null | undefined>;
1009
+ /**
1010
+ * Whether the directive writes `aria-level`, `aria-posinset`, `aria-setsize`,
1011
+ * `aria-expanded` and `aria-disabled` to the host. Turn it off when the component binds
1012
+ * them itself, from the signals below.
1013
+ */
1014
+ readonly manageAria: _angular_core.InputSignalWithTransform<boolean, unknown>;
1015
+ /** @internal Mirrors {@link key} under the name the navigation's item contract uses. */
1016
+ readonly itemKey: Signal<AndesTreeNavigationKey>;
1017
+ /** @internal Mirrors {@link disabled} under the name the navigation's item contract uses. */
1018
+ readonly isDisabled: Signal<boolean>;
1019
+ /** @internal Mirrors {@link expandable} under the name the navigation's item contract uses. */
1020
+ readonly isExpandable: Signal<boolean | undefined>;
1021
+ private readonly navigation;
1022
+ /** The enclosing item of the same tree, when the markup is nested. */
1023
+ private readonly enclosingItem;
1024
+ readonly element: HTMLElement;
1025
+ /** The item's DOM `id` — its own, when it has one, so authored ids are never clobbered. */
1026
+ readonly itemId: string;
1027
+ /** @internal The parent's key as the navigation should see it. */
1028
+ readonly itemParentKey: Signal<AndesTreeNavigationKey | null>;
1029
+ private readonly info;
1030
+ /** Whether this is the navigation's active node. */
1031
+ readonly active: Signal<boolean>;
1032
+ /** Whether the navigation treats this node as disabled. */
1033
+ readonly nodeDisabled: Signal<boolean>;
1034
+ /** Whether the node can be expanded: declared, or because it has children. */
1035
+ readonly isExpandableNode: Signal<boolean>;
1036
+ /** Whether the node is expanded. */
1037
+ readonly expanded: Signal<boolean>;
1038
+ /** `aria-level`: 1 for a root. */
1039
+ readonly ariaLevel: Signal<number>;
1040
+ /** `aria-posinset`: 1-based position among all siblings, collapsed ones included. */
1041
+ readonly ariaPosinset: Signal<number>;
1042
+ /** `aria-setsize`: number of siblings, this node included. */
1043
+ readonly ariaSetsize: Signal<number>;
1044
+ /** `aria-expanded`: `true`/`false` on a parent, `null` on a leaf (the attribute is omitted). */
1045
+ readonly ariaExpanded: Signal<boolean | null>;
1046
+ /**
1047
+ * `0` on the one roving-tabindex target, `-1` on every other node, and absent entirely in
1048
+ * `active-descendant` mode, where nodes are not focusable at all.
1049
+ */
1050
+ readonly tabIndex: Signal<number | null>;
1051
+ constructor();
1052
+ ngOnInit(): void;
1053
+ ngOnDestroy(): void;
1054
+ getLabel(): string;
1055
+ focus(): void;
1056
+ /**
1057
+ * Scrolls the node into view without moving focus — what `active-descendant` mode needs,
1058
+ * since real focus never reaches the node.
1059
+ */
1060
+ reveal(): void;
1061
+ /** Expands the node. A chevron button calls this. Returns whether anything changed. */
1062
+ expand(): boolean;
1063
+ /** Collapses the node. Returns whether anything changed. */
1064
+ collapse(): boolean;
1065
+ /** Expands a collapsed node and collapses an expanded one. */
1066
+ toggle(): boolean;
1067
+ protected onFocusin(): void;
1068
+ protected onFocusout(event: FocusEvent): void;
1069
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesTreeNavigationItem, never>;
1070
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesTreeNavigationItem, "[andesTreeNavigationItem]", ["andesTreeNavigationItem"], { "key": { "alias": "andesTreeNavigationItem"; "required": true; "isSignal": true; }; "parentKey": { "alias": "parentKey"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "typeaheadLabel": { "alias": "typeaheadLabel"; "required": false; "isSignal": true; }; "level": { "alias": "level"; "required": false; "isSignal": true; }; "posInSet": { "alias": "posInSet"; "required": false; "isSignal": true; }; "setSize": { "alias": "setSize"; "required": false; "isSignal": true; }; "manageAria": { "alias": "manageAria"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1071
+ }
1072
+
1073
+ /**
1074
+ * Wires the keyboard-owning element of a tree to its {@link AndesTreeNavigation}: forwards
1075
+ * `keydown` to it and publishes `aria-activedescendant` when the tree is in `active-descendant`
1076
+ * mode.
1077
+ *
1078
+ * Put it on whichever element holds real DOM focus:
1079
+ *
1080
+ * - a tree whose nodes take focus — the `role="tree"` element (keys bubble up from the nodes);
1081
+ * - a cascader or tree-select — the field or the `<input>`, which keeps focus the whole time;
1082
+ * - a menu — the `role="menu"` popup.
1083
+ *
1084
+ * It only consumes navigation keys, so the consumer is free to bind its own `keydown` handler on
1085
+ * the same element for `Enter`, `Space` and `Escape`.
1086
+ *
1087
+ * In a windowed tree the tab stop can fall on a node that is not rendered. Binding
1088
+ * `[attr.tabindex]="navigation.containerTabIndex()"` on the same element then keeps the tree
1089
+ * reachable with Tab, and this directive passes the focus on to the node, scrolling it in.
1090
+ *
1091
+ * ```html
1092
+ * <ul role="tree" andesTreeNavigationKeys (keydown.enter)="activate()">…</ul>
1093
+ * ```
1094
+ */
1095
+ declare class AndesTreeNavigationKeys {
1096
+ protected readonly navigation: AndesTreeNavigation;
1097
+ protected onKeydown(event: KeyboardEvent): void;
1098
+ protected onFocusin(event: FocusEvent): void;
1099
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesTreeNavigationKeys, never>;
1100
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesTreeNavigationKeys, "[andesTreeNavigationKeys]", never, {}, {}, never, never, true, never>;
1101
+ }
1102
+
1103
+ /** The names of Andes' responsive breakpoints. */
1104
+ type AndesBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
1105
+ /** Breakpoint names, smallest first. */
1106
+ declare const ANDES_BREAKPOINT_NAMES: readonly AndesBreakpoint[];
1107
+ /**
1108
+ * The minimum viewport (or container) width, in pixels, at which each breakpoint starts.
1109
+ * Breakpoints are cumulative and mobile-first: `md` applies from 768px up, until `lg` takes
1110
+ * over. One table shared by every component keeps "mobile" meaning the same width everywhere.
1111
+ */
1112
+ declare const ANDES_BREAKPOINTS: Readonly<Record<AndesBreakpoint, number>>;
1113
+ /**
1114
+ * The largest breakpoint whose minimum width fits `width`. Negative and `NaN` widths count as
1115
+ * `xs`.
1116
+ */
1117
+ declare function andesBreakpointForWidth(width: number): AndesBreakpoint;
1118
+ /**
1119
+ * The breakpoint reported where the viewport cannot be measured (server rendering, browsers
1120
+ * without `matchMedia`). Override it to match your most common client so the server-rendered
1121
+ * markup is the one most users keep.
1122
+ */
1123
+ declare const ANDES_BREAKPOINT_SSR_DEFAULT: InjectionToken<AndesBreakpoint>;
1124
+ /**
1125
+ * The current viewport breakpoint and ready-made "at least / narrower than / in range" signals,
1126
+ * shared by the whole application.
1127
+ *
1128
+ * It listens to one `matchMedia` query per breakpoint (never to `resize`), so nothing runs
1129
+ * while the viewport stays inside a breakpoint, and the listeners are removed with the root
1130
+ * injector. On the server, or where `matchMedia` does not exist, `active` holds
1131
+ * `ANDES_BREAKPOINT_SSR_DEFAULT` and never changes.
1132
+ *
1133
+ * The comparisons are half-open on purpose: `up(x)` is true from the start of `x`, `down(x)` is
1134
+ * true strictly before it, so `down(x)` is always `!up(x)` and `between(a, b)` ranges never
1135
+ * overlap or leave a gap when chained (`between('sm', 'md')` then `between('md', 'lg')`).
1136
+ */
1137
+ declare class AndesBreakpoints {
1138
+ private readonly state;
1139
+ private readonly comparisons;
1140
+ /** The largest breakpoint whose minimum width the viewport has reached. */
1141
+ readonly active: Signal<AndesBreakpoint>;
1142
+ constructor();
1143
+ /**
1144
+ * True once the viewport is at least as wide as `name` (`up('md')` is true from 768px).
1145
+ * `up('xs')` is always true. Repeated calls with the same name return the same signal.
1146
+ */
1147
+ up(name: AndesBreakpoint): Signal<boolean>;
1148
+ /**
1149
+ * True while the viewport is narrower than `name` (`down('lg')` is true below 992px, the
1150
+ * usual "collapse on small screens" test). `down('xs')` is always false.
1151
+ */
1152
+ down(name: AndesBreakpoint): Signal<boolean>;
1153
+ /**
1154
+ * True from the start of `from` until the start of `to` (`between('sm', 'lg')` covers 576px
1155
+ * up to, but excluding, 992px). A range that is empty or reversed (`from` not below `to`) is
1156
+ * never true, rather than being silently swapped.
1157
+ */
1158
+ between(from: AndesBreakpoint, to: AndesBreakpoint): Signal<boolean>;
1159
+ private memo;
1160
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesBreakpoints, never>;
1161
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesBreakpoints>;
1162
+ }
1163
+
1164
+ /** Which box of an element is measured. */
1165
+ type AndesElementBox = 'content-box' | 'border-box';
1166
+ /** A measured width and height, in CSS pixels. */
1167
+ interface AndesElementMeasure {
1168
+ readonly width: number;
1169
+ readonly height: number;
1170
+ }
1171
+ /** Options for {@link AndesResizeObserver.observe}. */
1172
+ interface AndesResizeObserveOptions {
1173
+ /**
1174
+ * The box to measure. `content-box` (default) excludes padding and border, which is what a
1175
+ * component laying out its children cares about; `border-box` is the space the element takes
1176
+ * in its parent.
1177
+ */
1178
+ readonly box?: AndesElementBox;
1179
+ }
1180
+ /**
1181
+ * One shared `ResizeObserver` (per measured box) for every element Andes watches, instead of
1182
+ * each component creating, guarding and disconnecting its own.
1183
+ *
1184
+ * Why it is shared: a single observer delivers all changes of a frame together, and the
1185
+ * bookkeeping that is easy to get wrong in each component (server guard, missing
1186
+ * `ResizeObserver`, disconnect on destroy) lives here once. The native observer is created with
1187
+ * the first subscription and disconnected with the last, so an idle page holds none.
1188
+ *
1189
+ * Why it is coalesced: bursts of changes within a frame (a window being dragged, a loop of
1190
+ * layout-triggered resizes) are reduced to the latest measurement per subscription and
1191
+ * delivered in one animation frame, so consumers write their state once per frame. The first
1192
+ * measurement of a subscription is the exception and is delivered at once, otherwise the first
1193
+ * paint would show the unmeasured state for an extra frame.
1194
+ *
1195
+ * It does nothing on the server or where `ResizeObserver` does not exist.
1196
+ */
1197
+ declare class AndesResizeObserver {
1198
+ private readonly browser;
1199
+ private readonly channels;
1200
+ private frame;
1201
+ private readonly dirty;
1202
+ constructor();
1203
+ /**
1204
+ * Calls `callback` with the element's size now (once the browser reports it) and whenever it
1205
+ * changes. Returns the function that stops observing; calling it twice is harmless.
1206
+ */
1207
+ observe(element: Element, callback: (measure: AndesElementMeasure) => void, options?: AndesResizeObserveOptions): () => void;
1208
+ private release;
1209
+ private receive;
1210
+ private deliver;
1211
+ private scheduleFrame;
1212
+ private cancelFrame;
1213
+ private flush;
1214
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesResizeObserver, never>;
1215
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesResizeObserver>;
1216
+ }
1217
+
1218
+ /** What {@link andesElementSize} can watch: an element, its `ElementRef`, or a signal of either. */
1219
+ type AndesElementSizeTarget = Element | ElementRef<Element> | Signal<Element | ElementRef<Element> | null | undefined>;
1220
+ /** Options for {@link andesElementSize}. */
1221
+ interface AndesElementSizeOptions {
1222
+ /**
1223
+ * The box to measure. `content-box` (default) is the room available to the element's
1224
+ * children; `border-box` is the space the element occupies in its parent.
1225
+ */
1226
+ readonly box?: AndesElementBox;
1227
+ /**
1228
+ * The size reported before the first measurement and on the server, where nothing can be
1229
+ * measured. Defaults to `0 x 0`. Use `measured` to tell a real zero from "not measured yet".
1230
+ */
1231
+ readonly initial?: AndesElementMeasure;
1232
+ /** The injector that owns the observation. Only needed outside an injection context. */
1233
+ readonly injector?: Injector;
1234
+ }
1235
+ /** The signals returned by {@link andesElementSize}. */
1236
+ interface AndesElementSizeRef {
1237
+ /** The element's width in CSS pixels. */
1238
+ readonly width: Signal<number>;
1239
+ /** The element's height in CSS pixels. */
1240
+ readonly height: Signal<number>;
1241
+ /** Width and height together, for reading both without two dependencies. */
1242
+ readonly size: Signal<AndesElementMeasure>;
1243
+ /**
1244
+ * False until the browser has reported a size for the current element, and again whenever the
1245
+ * element goes away or is swapped. Lets callers keep a server-rendered layout rather than
1246
+ * collapse to the `initial` size.
1247
+ */
1248
+ readonly measured: Signal<boolean>;
1249
+ }
1250
+ /**
1251
+ * Tracks an element's size as signals. Replaces a hand-written `ResizeObserver` plus its
1252
+ * server guard and teardown.
1253
+ *
1254
+ * Observation starts when the element exists and stops when it goes away, is swapped for
1255
+ * another, or the owning injector is destroyed, so a signal target such as a `viewChild` can
1256
+ * come and go freely. All elements share one native observer and updates are coalesced to one
1257
+ * per animation frame (see `AndesResizeObserver`). On the server and where `ResizeObserver` is
1258
+ * missing nothing is observed and the signals keep `initial`.
1259
+ *
1260
+ * Must be called in an injection context unless `options.injector` is given.
1261
+ */
1262
+ declare function andesElementSize(target: AndesElementSizeTarget, options?: AndesElementSizeOptions): AndesElementSizeRef;
1263
+
1264
+ /** Options for {@link andesContainerBreakpoint}. */
1265
+ interface AndesContainerBreakpointOptions<K extends string> extends Pick<AndesElementSizeOptions, 'box' | 'injector'> {
1266
+ /**
1267
+ * The bucket reported before the element is measured and on the server. Defaults to the
1268
+ * smallest bucket, the safe choice for a "collapse when narrow" layout that is then expanded
1269
+ * once the real width is known.
1270
+ */
1271
+ readonly initial?: NoInfer<K>;
1272
+ }
1273
+ /**
1274
+ * The container-width bucket an element currently falls in, for components that adapt to the
1275
+ * room they are given rather than to the viewport (collapsing pagination, a grid row inside a
1276
+ * narrow sidebar).
1277
+ *
1278
+ * `buckets` maps a name to the minimum width at which it starts, mobile-first like the viewport
1279
+ * breakpoints, so `ANDES_BREAKPOINTS` works as is and `{ compact: 0, full: 480 }` is a
1280
+ * two-state switch. The signal changes only when the element crosses a threshold, not on every
1281
+ * pixel, so effects and templates reading it stay quiet while the element resizes inside a
1282
+ * bucket. Pass a signal for `buckets` when the thresholds come from inputs.
1283
+ *
1284
+ * Must be called in an injection context unless `options.injector` is given.
1285
+ */
1286
+ declare function andesContainerBreakpoint<K extends string>(element: AndesElementSizeTarget, buckets: Readonly<Record<K, number>> | Signal<Readonly<Record<K, number>>>, options?: AndesContainerBreakpointOptions<K>): Signal<K>;
1287
+
1288
+ /** Options for {@link andesMediaQuery} and the ready-made preference signals. */
1289
+ interface AndesMediaQueryOptions {
1290
+ /**
1291
+ * The value reported where the query cannot be evaluated: server rendering, or a browser
1292
+ * without `matchMedia`. It is also what the signal holds until the first evaluation. Defaults
1293
+ * to `false`, i.e. "the condition does not hold", which for preference queries means the
1294
+ * default experience.
1295
+ */
1296
+ readonly ssrDefault?: boolean;
1297
+ /**
1298
+ * The injector that owns the listener. Only needed when calling outside an injection context;
1299
+ * the listener is removed when this injector is destroyed.
1300
+ */
1301
+ readonly injector?: Injector;
1302
+ }
1303
+ /**
1304
+ * Whether a CSS media query currently matches, as a signal that stays in sync.
1305
+ *
1306
+ * The first value is read synchronously, so the first render already uses the right answer
1307
+ * instead of flashing the default. After that one `change` listener keeps it current; it is
1308
+ * removed when the query changes (a `Signal` query) and when the owning injector is destroyed.
1309
+ * Where `matchMedia` is unavailable the signal holds `ssrDefault` and nothing is registered.
1310
+ *
1311
+ * Must be called in an injection context unless `options.injector` is given.
1312
+ *
1313
+ * @param query A media query string, or a signal of one for queries that depend on an input.
1314
+ */
1315
+ declare function andesMediaQuery(query: string | Signal<string>, options?: AndesMediaQueryOptions): Signal<boolean>;
1316
+ /**
1317
+ * Whether the user asked the system to reduce motion. Components consult it before running
1318
+ * non-essential animation or smooth scrolling.
1319
+ */
1320
+ declare function andesPrefersReducedMotion(options?: AndesMediaQueryOptions): Signal<boolean>;
1321
+ /** Whether the system is set to a dark color scheme. */
1322
+ declare function andesPrefersDark(options?: AndesMediaQueryOptions): Signal<boolean>;
1323
+ /** Whether the user asked for higher contrast than the default. */
1324
+ declare function andesPrefersContrast(options?: AndesMediaQueryOptions): Signal<boolean>;
1325
+
1326
+ /**
1327
+ * The validation state of the Angular forms control bound to a component's host element,
1328
+ * exposed as signals so a template or `computed()` can react to it without subscribing to
1329
+ * anything.
1330
+ *
1331
+ * Every signal reads `false` / `null` while nothing is bound, so a component used outside a
1332
+ * form behaves exactly as if the control were valid and untouched.
1333
+ */
1334
+ interface AndesControlStatus {
1335
+ /** Whether a forms directive is bound to the host and has produced its control. */
1336
+ readonly bound: Signal<boolean>;
1337
+ /** The bound control failed validation. */
1338
+ readonly invalid: Signal<boolean>;
1339
+ /** The bound control passed validation (and has no async validator still running). */
1340
+ readonly valid: Signal<boolean>;
1341
+ /** The user has left the bound control at least once. */
1342
+ readonly touched: Signal<boolean>;
1343
+ /** The value of the bound control was changed from the UI. */
1344
+ readonly dirty: Signal<boolean>;
1345
+ /** An async validator is still running. */
1346
+ readonly pending: Signal<boolean>;
1347
+ /** The bound control is disabled on the forms side. */
1348
+ readonly disabled: Signal<boolean>;
1349
+ /** The bound control's current errors, or `null` when it has none. */
1350
+ readonly errors: Signal<ValidationErrors | null>;
1351
+ /**
1352
+ * `invalid` once the user has interacted (`touched` or `dirty`). This is the usual "do not
1353
+ * shout at someone who has not typed yet" rule, so a component can paint its error state
1354
+ * from it directly instead of the page wiring `aria-invalid` by hand.
1355
+ */
1356
+ readonly showError: Signal<boolean>;
1357
+ }
1358
+ /** What `injectAndesControlStatus` accepts. */
1359
+ interface AndesControlStatusOptions {
1360
+ /**
1361
+ * A signal that changes whenever the forms directive (re)registers this component as its
1362
+ * value accessor. Re-binding a `[formControl]` to a different control registers the accessor
1363
+ * again, and that is the only moment the status has a reason to re-subscribe, because
1364
+ * Angular offers no event for "this directive now points at another control".
1365
+ * `AndesValueAccessor` already supplies it.
1366
+ */
1367
+ readonly rebindOn?: Signal<unknown>;
1368
+ }
1369
+ /**
1370
+ * Reads the status of the Angular forms control sitting on the same element as the calling
1371
+ * component or directive. Works unchanged with `formControl`, `formControlName`, `ngModel`
1372
+ * and signal forms' `[formField]`.
1373
+ *
1374
+ * Must run in an injection context (a field initializer or constructor).
1375
+ *
1376
+ * The `NgControl` is looked up lazily, from an effect, and never from the constructor. A
1377
+ * component that provides `NG_VALUE_ACCESSOR` cannot `inject(NgControl)` while it is being
1378
+ * constructed: the forms directive injects the accessor to build itself, so the two wait on
1379
+ * each other and Angular reports a circular dependency. By the time the first change
1380
+ * detection pass runs, both exist and the lookup is plain. The cost is that status reads
1381
+ * before that pass see the "unbound" defaults.
1382
+ *
1383
+ * ```ts
1384
+ * @Component({ ... })
1385
+ * class AndesTextbox {
1386
+ * private readonly status = injectAndesControlStatus();
1387
+ * protected readonly invalid = computed(() => this.status.showError());
1388
+ * }
1389
+ * ```
1390
+ */
1391
+ declare function injectAndesControlStatus(options?: AndesControlStatusOptions): AndesControlStatus;
1392
+
1393
+ /**
1394
+ * Base class for a component whose state is a single `value` and that should work with every
1395
+ * kind of Angular form. It owns the `value` model, the `disabled` input, the
1396
+ * `ControlValueAccessor` methods and the status of the bound control, so a subclass only
1397
+ * renders the value and calls {@link commit} when the user changes it.
1398
+ *
1399
+ * ```ts
1400
+ * @Component({
1401
+ * selector: 'andes-rating',
1402
+ * providers: [provideAndesValueAccessor(() => AndesRating)],
1403
+ * })
1404
+ * class AndesRating extends AndesFormControlBase<number> {
1405
+ * protected emptyValue(): number {
1406
+ * return 0;
1407
+ * }
1408
+ * protected pick(stars: number): void {
1409
+ * this.commit(stars);
1410
+ * }
1411
+ * }
1412
+ * ```
1413
+ *
1414
+ * The class also satisfies signal forms' custom-control contract (`value` model, `disabled`
1415
+ * input, `touch` output). A subclass that omits `provideAndesValueAccessor` therefore binds
1416
+ * to `[formField]` natively and is simply not usable with reactive or template-driven forms;
1417
+ * one that provides it works with all of them, signal forms going through the accessor.
1418
+ *
1419
+ * It declares the `value` and `disabled` members itself, so a component whose value signal
1420
+ * has another name (`checked`, `pressed`) uses {@link AndesValueAccessor} directly instead.
1421
+ */
1422
+ declare abstract class AndesFormControlBase<T> implements ControlValueAccessor {
1423
+ /**
1424
+ * The control's value. A `model()`, so `[(value)]` works beside any forms binding and a
1425
+ * user's pick and a form's write land in the same signal. Note that this means
1426
+ * `valueChange` also fires when a form writes a different value; `commit()` is what tells
1427
+ * the form about changes made by the user.
1428
+ */
1429
+ readonly value: _angular_core.ModelSignal<T>;
1430
+ /** Disables the control. A disabled state pushed by Angular forms disables it as well. */
1431
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1432
+ /**
1433
+ * Fires each time the user leaves the control. It is the touched signal of signal forms'
1434
+ * custom-control contract; reactive and template-driven forms are told through their
1435
+ * accessor callback instead.
1436
+ */
1437
+ readonly touch: _angular_core.OutputEmitterRef<void>;
1438
+ private readonly accessor;
1439
+ /** `disabled` merged with the disabled state of the bound form control. */
1440
+ protected readonly isDisabled: Signal<boolean>;
1441
+ /** The validation state of the bound form control; see {@link AndesControlStatus}. */
1442
+ protected readonly status: AndesControlStatus;
1443
+ constructor();
1444
+ /**
1445
+ * What the control holds before a form writes anything, and what a form's `null` /
1446
+ * `undefined` (a `reset()`) means: `''` for text, `[]` for a multi-select. A method rather
1447
+ * than a constant so that every control gets its own array or object. It runs while the
1448
+ * base class is being constructed, so it must not read the subclass's own fields.
1449
+ */
1450
+ protected abstract emptyValue(): T;
1451
+ /**
1452
+ * Applies a value chosen by the user and notifies Angular forms exactly once. Does nothing,
1453
+ * and returns `false`, when {@link equals} says nothing changed.
1454
+ */
1455
+ protected commit(value: T): boolean;
1456
+ /** Reports that the user left the control. Call it on blur. */
1457
+ protected markTouched(): void;
1458
+ /**
1459
+ * Turns what a form writes into a `T`. By default `null` / `undefined` become the empty
1460
+ * value and everything else passes through unchanged. Override
1461
+ * it to normalize (`String(raw)`, clamping) or to make the empty value depend on inputs.
1462
+ */
1463
+ protected coerce(raw: unknown): T;
1464
+ /**
1465
+ * Whether two values are the same for the purpose of {@link commit}. `Object.is` by
1466
+ * default; override it for array and object values.
1467
+ */
1468
+ protected equals(previous: T, next: T): boolean;
1469
+ /**
1470
+ * Called after a form wrote a value, to refresh state derived from it. Never reported back
1471
+ * to the form.
1472
+ */
1473
+ protected onWritten(value: T): void;
1474
+ /** Called by Angular forms to write a value into the control; never echoes back. */
1475
+ writeValue(raw: unknown): void;
1476
+ /** Called by Angular forms to receive value changes made by the user. */
1477
+ registerOnChange(fn: (value: T) => void): void;
1478
+ /** Called by Angular forms to receive the touched notification. */
1479
+ registerOnTouched(fn: () => void): void;
1480
+ /** Called by Angular forms when the bound control is enabled or disabled. */
1481
+ setDisabledState(isDisabled: boolean): void;
1482
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesFormControlBase<any>, never>;
1483
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesFormControlBase<any>, never, never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
1484
+ }
1485
+
1486
+ /**
1487
+ * Registers a component as the `ControlValueAccessor` of the forms directive on its element,
1488
+ * replacing the `{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(...), multi: true }`
1489
+ * block each component repeated. The component is passed as a thunk so the class can be
1490
+ * referenced from its own decorator, which is what the `forwardRef` was for.
1491
+ *
1492
+ * With only the component, the component itself must implement `ControlValueAccessor`
1493
+ * (extending `AndesFormControlBase` does). Pass `select` to register a different object, such
1494
+ * as an `AndesValueAccessor` the component owns, so the component's public API does not grow
1495
+ * `writeValue` and friends.
1496
+ *
1497
+ * Because a value accessor is registered, signal forms' `[formField]` binds through it too,
1498
+ * so one provider covers `formControl`, `formControlName`, `ngModel` and `[formField]`.
1499
+ *
1500
+ * ```ts
1501
+ * @Component({
1502
+ * selector: 'andes-rating',
1503
+ * providers: [provideAndesValueAccessor(() => AndesRating)],
1504
+ * })
1505
+ * class AndesRating extends AndesFormControlBase<number> { ... }
1506
+ * ```
1507
+ */
1508
+ declare function provideAndesValueAccessor<C extends ControlValueAccessor>(component: () => Type<C>): Provider;
1509
+ declare function provideAndesValueAccessor<C>(component: () => Type<C>, select: (instance: C) => ControlValueAccessor): Provider;
1510
+
1511
+ /** What `AndesValueAccessor` needs to know about the component it serves. */
1512
+ interface AndesValueAccessorOptions<T> {
1513
+ /**
1514
+ * The signal that holds the control's value: usually the component's `model()`, but any
1515
+ * writable signal works, including a `linkedSignal`. The accessor writes to it for both
1516
+ * user commits and form writes, so there is one source of truth to render from.
1517
+ */
1518
+ readonly value: WritableSignal<T>;
1519
+ /**
1520
+ * The component's own `disabled` input. It is merged with the disabled state Angular forms
1521
+ * pushes in, so either side can disable the control, as with a native `<fieldset>`.
1522
+ */
1523
+ readonly disabled?: Signal<boolean>;
1524
+ /**
1525
+ * Decides whether a commit actually changes the value. A commit that does not is dropped,
1526
+ * so re-picking the selected option never marks the control dirty. Defaults to `Object.is`;
1527
+ * array and object values need their own comparison (see {@link andesShallowArrayEquals}).
1528
+ */
1529
+ readonly equals?: (previous: T, next: T) => boolean;
1530
+ /**
1531
+ * What a form's `null` / `undefined` (a `reset()`, an unset model) means for this control,
1532
+ * for example `''` for text or `[]` for a multi-select. Treated as immutable. Ignored when
1533
+ * `coerce` is given.
1534
+ */
1535
+ readonly emptyValue?: T;
1536
+ /**
1537
+ * Turns whatever a form writes into a value of type `T`, e.g. `String(value)` or clamping a
1538
+ * number. Takes precedence over `emptyValue`.
1539
+ */
1540
+ readonly coerce?: (raw: unknown) => T;
1541
+ /**
1542
+ * Runs after a form wrote a value, for state derived from it that is not itself the value:
1543
+ * the text shown in a field, a discarded draft. It runs on writes only, never on commits,
1544
+ * and nothing it does is reported back to the form.
1545
+ */
1546
+ readonly onWrite?: (value: T) => void;
1547
+ /** Runs each time the control is marked touched, so a component can surface it as an output. */
1548
+ readonly onTouch?: () => void;
1549
+ }
1550
+ /**
1551
+ * Shallow, order-sensitive equality for array values, treating `null` / `undefined` as the
1552
+ * empty array. Pass it as `equals` for multi-value controls so that committing an array with
1553
+ * the same items does not count as a change.
1554
+ */
1555
+ declare function andesShallowArrayEquals<T>(previous: readonly T[] | null | undefined, next: readonly T[] | null | undefined): boolean;
1556
+ /**
1557
+ * The `ControlValueAccessor` plumbing every value-holding component re-implements, as one
1558
+ * object a component owns rather than inherits. Use it directly when the value signal is not
1559
+ * called `value` or the component already extends another class; extend
1560
+ * {@link AndesFormControlBase} when it is.
1561
+ *
1562
+ * It encodes the contract the duplicated code kept getting subtly wrong:
1563
+ *
1564
+ * - `commit()` sets the signal and tells the form, once, and only if the value changed;
1565
+ * - `writeValue()` sets the signal but never tells the form back, so `setValue()` /
1566
+ * `reset()` cannot loop;
1567
+ * - `disabled` is the component's input OR the form's state, whichever says so;
1568
+ * - `markTouched()` is forwarded every time, because `updateOn: 'blur'` controls only
1569
+ * publish their pending value from that call.
1570
+ *
1571
+ * Must be created in an injection context. After the component is destroyed it stops
1572
+ * notifying the form.
1573
+ *
1574
+ * ```ts
1575
+ * @Component({
1576
+ * selector: 'andes-switch',
1577
+ * providers: [provideAndesValueAccessor(() => AndesSwitch, (self) => self.accessor)],
1578
+ * })
1579
+ * class AndesSwitch {
1580
+ * readonly checked = model(false);
1581
+ * readonly disabled = input(false, { transform: booleanAttribute });
1582
+ * readonly accessor = new AndesValueAccessor({
1583
+ * value: this.checked,
1584
+ * disabled: this.disabled,
1585
+ * coerce: Boolean,
1586
+ * });
1587
+ *
1588
+ * protected toggle(): void {
1589
+ * this.accessor.commit(!this.checked());
1590
+ * }
1591
+ * }
1592
+ * ```
1593
+ */
1594
+ declare class AndesValueAccessor<T> implements ControlValueAccessor {
1595
+ private readonly options;
1596
+ private onChange;
1597
+ private onTouched;
1598
+ private destroyed;
1599
+ private readonly disabledByForm;
1600
+ private readonly registrations;
1601
+ /** The component's value signal, as passed in. */
1602
+ readonly value: WritableSignal<T>;
1603
+ /** The component's own `disabled` input OR'd with the state Angular forms pushed in. */
1604
+ readonly disabled: Signal<boolean>;
1605
+ /** The validation state of the bound forms control; see {@link AndesControlStatus}. */
1606
+ readonly status: AndesControlStatus;
1607
+ constructor(options: AndesValueAccessorOptions<T>);
1608
+ /**
1609
+ * Applies a value chosen by the user: updates the signal and notifies Angular forms,
1610
+ * exactly once. Returns `false`, doing nothing, when `value` equals the current one.
1611
+ *
1612
+ * It does not look at `disabled`: the component decides whether an interaction may happen
1613
+ * at all, because only it knows which interactions count (a disabled control may still
1614
+ * scroll or expand).
1615
+ */
1616
+ commit(value: T): boolean;
1617
+ /**
1618
+ * Tells Angular forms the user has left the control. Call it on blur, and when an
1619
+ * interaction ends without a blur (a popup closing). Safe to call repeatedly.
1620
+ */
1621
+ markTouched(): void;
1622
+ /**
1623
+ * Called by Angular forms to put a value into the control. Updates the signal and runs
1624
+ * `onWrite`, and never reports back to the form.
1625
+ */
1626
+ writeValue(raw: unknown): void;
1627
+ /** Called by Angular forms to receive value changes made by the user. */
1628
+ registerOnChange(fn: (value: T) => void): void;
1629
+ /** Called by Angular forms to receive the touched notification. */
1630
+ registerOnTouched(fn: () => void): void;
1631
+ /** Called by Angular forms when the bound control is enabled or disabled. */
1632
+ setDisabledState(isDisabled: boolean): void;
1633
+ }
1634
+
1635
+ /**
1636
+ * How many items a selection may hold.
1637
+ *
1638
+ * - `single` — at most one item; selecting another replaces it (radio-like lists, a single
1639
+ * select, a table with radio selection).
1640
+ * - `multiple` — any number of items, bounded only by `max` (checkbox groups, multi selects,
1641
+ * transfer lists, tables with checkbox selection).
1642
+ */
1643
+ type AndesSelectionMode = 'single' | 'multiple';
1644
+ /**
1645
+ * Aggregate state of a selection over a list of items — what a "select all" control renders.
1646
+ *
1647
+ * - `none` — no considered item is selected (also the answer when nothing is considered).
1648
+ * - `some` — a strict subset is selected: the "mixed" / indeterminate state.
1649
+ * - `all` — every considered item is selected.
1650
+ */
1651
+ type AndesSelectionState = 'none' | 'some' | 'all';
1652
+ /**
1653
+ * Where a change came from.
1654
+ *
1655
+ * Forms integrations need the distinction: an `interaction` (the user clicked, a key was
1656
+ * pressed) must be reported to the form, while a `programmatic` write (a form pushing its own
1657
+ * value in, a source list being reconciled) must not be echoed back at it.
1658
+ */
1659
+ type AndesSelectionChangeSource = 'interaction' | 'programmatic';
1660
+ /**
1661
+ * What one mutation changed. Membership only: a call that merely refreshes object instances
1662
+ * or reorders the same members reports nothing.
1663
+ */
1664
+ interface AndesSelectionChange<T> {
1665
+ /** Items that became selected, in the order they were added. */
1666
+ readonly added: readonly T[];
1667
+ /** Items that stopped being selected, in their former selection order. */
1668
+ readonly removed: readonly T[];
1669
+ /** Whether the change was an `interaction` or a `programmatic` write. */
1670
+ readonly source: AndesSelectionChangeSource;
1671
+ }
1672
+ /** Options for {@link AndesSelectionModel}. Every key is optional. */
1673
+ interface AndesSelectionModelConfig<T> {
1674
+ /**
1675
+ * Single or multiple selection; a `Signal` keeps the model in step with a component input.
1676
+ * Switching to `single` while several items are selected shows only the earliest one
1677
+ * selected. Default `'multiple'`.
1678
+ */
1679
+ mode?: AndesSelectionMode | Signal<AndesSelectionMode>;
1680
+ /**
1681
+ * The most items `multiple` mode may hold; `undefined` means no limit. Lowering it below the
1682
+ * current size shows only the earliest items selected. Default `undefined`.
1683
+ */
1684
+ max?: number | undefined | Signal<number | undefined>;
1685
+ /**
1686
+ * Maps an item to the value used to tell items apart (an id, a path string). Use it when
1687
+ * the same logical item can arrive as different object instances — rows re-fetched from a
1688
+ * server, options rebuilt on every change detection. It is hashed, so lookups stay O(1)
1689
+ * with thousands of items. Default: the item itself.
1690
+ */
1691
+ keyOf?: (item: T) => unknown;
1692
+ /**
1693
+ * Escape hatch for equality that cannot be reduced to a key. Every lookup scans the
1694
+ * selection, so prefer `keyOf` for large selections. Ignored when `keyOf` is given.
1695
+ */
1696
+ compareWith?: (a: T, b: T) => boolean;
1697
+ /**
1698
+ * Marks items the user cannot change: selecting, deselecting, clearing or range-selecting
1699
+ * them does nothing, and "select all" ignores them. It may read signals; the model's
1700
+ * derived state tracks them. Programmatic writes (`setSelection`) bypass it so a form can
1701
+ * still pre-select a disabled option.
1702
+ */
1703
+ isDisabled?: (item: T) => boolean;
1704
+ /** Items selected from the start, without reporting a change. */
1705
+ initial?: readonly T[];
1706
+ /**
1707
+ * Called synchronously after every change, with the same object that `changes` publishes.
1708
+ * Components use it to emit an output at the exact moment the selection changes.
1709
+ */
1710
+ onChange?: (change: AndesSelectionChange<T>) => void;
1711
+ }
1712
+ /**
1713
+ * Check state of one tree node, mirroring `aria-checked`.
1714
+ *
1715
+ * `mixed` is a parent with some, but not all, of its checkable descendants checked.
1716
+ */
1717
+ type AndesCheckState = 'checked' | 'unchecked' | 'mixed';
1718
+ /**
1719
+ * Which checked nodes a consumer wants reported when parents cascade.
1720
+ *
1721
+ * - `all` — every checked node, parents included.
1722
+ * - `parent` — only the topmost checked nodes: a checked folder stands for its whole subtree.
1723
+ * - `child` — only checked nodes without children (the leaves).
1724
+ */
1725
+ type AndesCheckedStrategy = 'all' | 'parent' | 'child';
1726
+ /** Options for {@link AndesCheckedTree}. */
1727
+ interface AndesCheckedTreeConfig<T, K = T> {
1728
+ /**
1729
+ * The root nodes, or a function (typically a signal) returning them. Read reactively: when
1730
+ * it returns a new array the tree is re-indexed. Treat nodes as immutable — mutating a
1731
+ * `children` array in place is not noticed.
1732
+ */
1733
+ nodes: readonly T[] | (() => readonly T[]);
1734
+ /**
1735
+ * Reads a node's children. Nodes without a children field can return `undefined`.
1736
+ */
1737
+ childrenOf: (node: T) => readonly T[] | null | undefined;
1738
+ /**
1739
+ * Identifies a node. Keys must be unique: when a key repeats, the first node wins and the
1740
+ * later one (with its subtree) is ignored, which also makes cyclic data harmless.
1741
+ * Default: the node itself.
1742
+ */
1743
+ keyOf?: (node: T) => K;
1744
+ /**
1745
+ * Locks a node's checkbox. A locked node keeps whatever state it has, cannot be toggled by
1746
+ * the user, and takes no part in propagation: a cascade neither reaches it (nor anything
1747
+ * below it) and its state is ignored when a parent aggregates its children.
1748
+ */
1749
+ isDisabled?: (node: T) => boolean;
1750
+ /**
1751
+ * `true` makes every node independent (no propagation in either direction); a `Signal`
1752
+ * keeps the tree in step with a component input. Default `false`.
1753
+ */
1754
+ strict?: boolean | Signal<boolean>;
1755
+ /** Keys checked from the start, without reporting a change. */
1756
+ initial?: readonly K[];
1757
+ /** Called synchronously after every change, with the same object `changes` publishes. */
1758
+ onChange?: (change: AndesSelectionChange<K>) => void;
1759
+ }
1760
+
1761
+ /**
1762
+ * Check state for a hierarchy — a tree, a cascader with multiple values, or a table whose rows
1763
+ * have child rows — with parent/child propagation.
1764
+ *
1765
+ * Checking a node checks everything below it; a parent is checked once all its children are and
1766
+ * is `mixed` (half-checked) while only some are. The `strict` option turns propagation off so
1767
+ * every node is independent. Locked nodes (`isDisabled`) are islands: they keep their state and
1768
+ * neither receive nor pass on a cascade, so a parent aggregates only its unlocked children and
1769
+ * a partially locked parent can still be checked by its available children alone.
1770
+ *
1771
+ * The model stores keys, not nodes, because a checked value usually arrives from a form or a
1772
+ * lazy-loading source before (or without) the node being present. Keys that match no node are
1773
+ * kept and reported by `checkedKeys` until {@link prune} removes them, so a source list that
1774
+ * is temporarily empty or paged does not erase the value. The index is rebuilt once per
1775
+ * `nodes` change and every update is a single linear pass, which keeps a few thousand nodes
1776
+ * instantaneous.
1777
+ *
1778
+ * ```ts
1779
+ * readonly tree = new AndesCheckedTree<Folder, string>({
1780
+ * nodes: this.folders, // a signal
1781
+ * childrenOf: (folder) => folder.children,
1782
+ * keyOf: (folder) => folder.id,
1783
+ * strict: this.checkStrictly, // a signal input
1784
+ * onChange: (change) => this.checkedChange.emit(change),
1785
+ * });
1786
+ * // template: [checked]="tree.stateOf(id) === 'checked'" [indeterminate]="tree.stateOf(id) === 'mixed'"
1787
+ * ```
1788
+ */
1789
+ declare class AndesCheckedTree<T, K = T> {
1790
+ private readonly nodesSource;
1791
+ private readonly childrenOf;
1792
+ private readonly keyOf;
1793
+ private readonly disabledPredicate;
1794
+ private readonly onChange;
1795
+ private readonly raw;
1796
+ private readonly lastChange;
1797
+ private readonly index;
1798
+ private readonly locked;
1799
+ private readonly flags;
1800
+ private readonly unknownKeys;
1801
+ /** Whether propagation is off. */
1802
+ readonly strict: Signal<boolean>;
1803
+ /** Every checked node's key, parents included, in tree order. */
1804
+ readonly checked: Signal<ReadonlySet<K>>;
1805
+ /** Keys of parents with some, but not all, unlocked children checked. Always empty when strict. */
1806
+ readonly halfChecked: Signal<ReadonlySet<K>>;
1807
+ /**
1808
+ * The checked value: {@link checked} in tree order, followed by keys that match no node
1809
+ * (kept until {@link prune}). This is what a form control or two-way binding holds.
1810
+ */
1811
+ readonly checkedKeys: Signal<readonly K[]>;
1812
+ /**
1813
+ * Summary over every unlocked node: `all` when each is checked, `none` when none is, `some`
1814
+ * otherwise. Drives a tree-wide "select all" checkbox.
1815
+ */
1816
+ readonly overallState: Signal<AndesSelectionState>;
1817
+ /** The most recent change, or `null`. A fresh object per change; see `AndesSelectionModel.changes`. */
1818
+ readonly changes: Signal<AndesSelectionChange<K> | null>;
1819
+ constructor(config: AndesCheckedTreeConfig<T, K>);
1820
+ /** Whether the tree has a node with this key. */
1821
+ has(key: K): boolean;
1822
+ /** The key of a node's parent, or `undefined` for roots and unknown keys. */
1823
+ parentKeyOf(key: K): K | undefined;
1824
+ /** The keys of a node's children, in source order; empty for leaves and unknown keys. */
1825
+ childKeysOf(key: K): readonly K[];
1826
+ /** Whether the node is locked by `isDisabled`. Unknown keys are not locked. */
1827
+ isDisabled(key: K): boolean;
1828
+ /** Whether the node is fully checked. */
1829
+ isChecked(key: K): boolean;
1830
+ /** Whether the node is a parent with only some of its children checked. */
1831
+ isHalfChecked(key: K): boolean;
1832
+ /** The node's state, shaped for `aria-checked`. Unknown keys are `unchecked`. */
1833
+ stateOf(key: K): AndesCheckState;
1834
+ /**
1835
+ * The checked keys narrowed to what a consumer wants reported: `all` (the default), `parent`
1836
+ * (a checked folder stands for its subtree) or `child` (leaves only). Strict mode has no
1837
+ * hierarchy to collapse, so it always reports everything. Keys with no node are appended
1838
+ * unchanged in every strategy because they cannot be classified.
1839
+ */
1840
+ checkedKeysByStrategy(strategy: AndesCheckedStrategy): readonly K[];
1841
+ /**
1842
+ * Checks a node (and, unless strict, everything below it). Does nothing for an unknown key,
1843
+ * a locked node, or a node that is already checked.
1844
+ *
1845
+ * @returns whether the checked value changed.
1846
+ */
1847
+ check(key: K): boolean;
1848
+ /**
1849
+ * Unchecks a node, everything reachable below it and, unless strict, the ancestors that were
1850
+ * checked only because this node was — which then become `mixed` if siblings stay checked.
1851
+ * A `mixed` parent is unchecked too. Does nothing for an unknown or locked key, or a node
1852
+ * with nothing checked in it.
1853
+ *
1854
+ * @returns whether the checked value changed.
1855
+ */
1856
+ uncheck(key: K): boolean;
1857
+ /**
1858
+ * Flips a node: a `checked` node is unchecked; `unchecked` and `mixed` ones are checked, which
1859
+ * is what completes a half-checked parent on click.
1860
+ *
1861
+ * @returns whether the checked value changed.
1862
+ */
1863
+ toggle(key: K): boolean;
1864
+ /**
1865
+ * Checks every unlocked node. Locked nodes keep their state.
1866
+ *
1867
+ * @returns whether the checked value changed.
1868
+ */
1869
+ checkAll(): boolean;
1870
+ /**
1871
+ * Unchecks every unlocked node and forgets keys that match no node; a locked checked node
1872
+ * stays checked, since the user cannot change it.
1873
+ *
1874
+ * @returns whether the checked value changed.
1875
+ */
1876
+ clear(): boolean;
1877
+ /**
1878
+ * Replaces the checked value, as a form or a two-way binding writes it in. The keys are taken
1879
+ * as the seed: unless strict, a listed parent checks its whole subtree and a parent whose
1880
+ * children are all listed becomes checked. Locking is ignored. Keys with no node are kept.
1881
+ *
1882
+ * Reports `source: 'programmatic'`.
1883
+ *
1884
+ * @returns whether the checked value changed.
1885
+ */
1886
+ setChecked(keys: readonly K[]): boolean;
1887
+ /**
1888
+ * Forgets keys that match no node. Call it after the source list shrinks and the leftover
1889
+ * value should not be reported any more. Reports `source: 'programmatic'`.
1890
+ *
1891
+ * @returns whether any key was dropped.
1892
+ */
1893
+ prune(): boolean;
1894
+ private keysWhere;
1895
+ private setNode;
1896
+ /**
1897
+ * The single write path: runs propagation on `seed`, stores the normalized keys (so the stored
1898
+ * value is always the full checked set, parents included) and publishes `{added, removed}`
1899
+ * relative to the previous `checkedKeys`.
1900
+ */
1901
+ private commit;
1902
+ }
1903
+
1904
+ /**
1905
+ * A signal-based selection over a list of items: single or multiple, with a maximum, per-item
1906
+ * locking, object identity by key, change reporting, shift-click ranges and the tri-state
1907
+ * "select all" summary.
1908
+ *
1909
+ * It is plain TypeScript state — no DOM, no injection context — so any component, directive or
1910
+ * service can own one. The recurring shapes it replaces are a table's row selection, a
1911
+ * checkbox group, a transfer list's two panes, a multi-select's value and a list's selected
1912
+ * rows.
1913
+ *
1914
+ * Every mutating method returns whether the selection actually changed, so a caller can skip
1915
+ * emitting an output for a click that was blocked or redundant. Interaction methods
1916
+ * (`select`, `deselect`, `toggle`, `clear`, `selectAll`, `deselectAll`, `toggleAll`,
1917
+ * `invert`, `selectRange`) honor `isDisabled` and `max`; `setSelection` and `retainOnly` are
1918
+ * programmatic and do not.
1919
+ *
1920
+ * ```ts
1921
+ * readonly rows = signal<readonly Row[]>([]);
1922
+ * readonly selection = new AndesSelectionModel<Row>({
1923
+ * keyOf: (row) => row.id,
1924
+ * isDisabled: (row) => row.locked,
1925
+ * onChange: (change) => this.selectionChange.emit(change),
1926
+ * });
1927
+ * readonly headerState = computed(() => this.selection.stateOf(this.rows()));
1928
+ *
1929
+ * onRowClick(row: Row, event: MouseEvent) {
1930
+ * if (event.shiftKey && this.selection.anchor()) {
1931
+ * this.selection.selectRange(this.rows(), this.selection.anchor()!, row, !this.selection.isSelected(row));
1932
+ * } else {
1933
+ * this.selection.toggle(row);
1934
+ * }
1935
+ * }
1936
+ * ```
1937
+ */
1938
+ declare class AndesSelectionModel<T> {
1939
+ private readonly keyOf;
1940
+ private readonly compareWith;
1941
+ private readonly disabledPredicate;
1942
+ private readonly onChange;
1943
+ private readonly raw;
1944
+ private readonly lastChange;
1945
+ private readonly anchorItem;
1946
+ /** Current mode. */
1947
+ readonly mode: Signal<AndesSelectionMode>;
1948
+ /** The effective limit of `multiple` mode, or `undefined` for none. */
1949
+ readonly max: Signal<number | undefined>;
1950
+ /**
1951
+ * The selected items in the order they were selected. Never longer than the mode and `max`
1952
+ * allow, even if those were lowered after items were selected: the extra, later items are
1953
+ * simply not part of the selection any more.
1954
+ */
1955
+ readonly selected: Signal<readonly T[]>;
1956
+ /** Number of selected items. */
1957
+ readonly size: Signal<number>;
1958
+ /** Whether nothing is selected. */
1959
+ readonly isEmpty: Signal<boolean>;
1960
+ /**
1961
+ * The most recent change, or `null` before the first one. Each change is a fresh object, so
1962
+ * an `effect` reading this runs once per change — including two consecutive changes that
1963
+ * add the same item. Unchanged calls never publish.
1964
+ */
1965
+ readonly changes: Signal<AndesSelectionChange<T> | null>;
1966
+ /**
1967
+ * The item the last interaction targeted — the starting point of a shift-click range.
1968
+ * Blocked interactions (a disabled item) leave it alone.
1969
+ */
1970
+ readonly anchor: Signal<T | undefined>;
1971
+ private readonly limit;
1972
+ private readonly selectedLookup;
1973
+ constructor(config?: AndesSelectionModelConfig<T>);
1974
+ /** Whether `item` is selected. Reactive when called from a `computed` or a template. */
1975
+ isSelected(item: T): boolean;
1976
+ /** Whether `isDisabled` locks `item`. Reactive if the predicate reads signals. */
1977
+ isDisabled(item: T): boolean;
1978
+ /**
1979
+ * Selects `item`. In `single` mode it replaces the current item, unless that one is locked.
1980
+ * In `multiple` mode it is refused once `max` is reached.
1981
+ *
1982
+ * @returns whether the selection changed.
1983
+ */
1984
+ select(item: T): boolean;
1985
+ /**
1986
+ * Deselects `item`; a locked item stays selected.
1987
+ *
1988
+ * @returns whether the selection changed.
1989
+ */
1990
+ deselect(item: T): boolean;
1991
+ /**
1992
+ * Flips `item`. In `single` mode a selected item is deselected too, so a list can be
1993
+ * emptied by clicking its selected row; use `select` for radio behavior.
1994
+ *
1995
+ * @returns whether the selection changed.
1996
+ */
1997
+ toggle(item: T): boolean;
1998
+ /**
1999
+ * Deselects everything the user is allowed to change. Locked items stay: a "clear" button
2000
+ * must not silently drop a mandatory choice.
2001
+ *
2002
+ * @returns whether the selection changed.
2003
+ */
2004
+ clear(): boolean;
2005
+ /**
2006
+ * Replaces the selection, as a form or a two-way binding writes a value in. Duplicates are
2007
+ * dropped (the first wins), the list is cut to what the mode and `max` allow, and locking
2008
+ * is ignored — a disabled option can still be pre-selected.
2009
+ *
2010
+ * Unlike the interaction methods this reports `source: 'programmatic'`.
2011
+ *
2012
+ * @returns whether membership changed. Swapping in new object instances for the same keys
2013
+ * updates `selected()` but returns `false` and publishes no change.
2014
+ */
2015
+ setSelection(items: readonly T[]): boolean;
2016
+ /**
2017
+ * Drops selected items that are not in `items`, keeping the order of the rest. Call it when
2018
+ * the source list loses entries and the selection must not point at ghosts. Locking is
2019
+ * ignored: a row that no longer exists cannot stay selected just because it was locked.
2020
+ *
2021
+ * @returns whether anything was dropped.
2022
+ */
2023
+ retainOnly(items: readonly T[]): boolean;
2024
+ /**
2025
+ * Selects every unlocked item of `items`. With `max` it fills the remaining room in list
2026
+ * order and stops, rather than refusing the whole call, so "select all" on a long list
2027
+ * still does something useful. Does nothing in `single` mode, where "all" has no meaning.
2028
+ *
2029
+ * @returns whether the selection changed.
2030
+ */
2031
+ selectAll(items: readonly T[]): boolean;
2032
+ /**
2033
+ * Deselects every unlocked item of `items`. Selected items outside `items`, and locked ones,
2034
+ * are untouched — which is what lets one pane's "select none" leave another pane alone.
2035
+ *
2036
+ * @returns whether the selection changed.
2037
+ */
2038
+ deselectAll(items: readonly T[]): boolean;
2039
+ /**
2040
+ * The header checkbox's click: deselects the unlocked items of `items` when all of them are
2041
+ * selected, otherwise selects them. If selecting changes nothing because `max` is already
2042
+ * reached, it deselects instead, so the control can never get stuck in the mixed state.
2043
+ * Does nothing in `single` mode.
2044
+ *
2045
+ * @returns whether the selection changed.
2046
+ */
2047
+ toggleAll(items: readonly T[]): boolean;
2048
+ /**
2049
+ * Flips the selection of every unlocked item of `items`: selected ones are deselected and the
2050
+ * rest selected (a table's "invert selection"). Items outside `items` and locked items are
2051
+ * untouched. With `max`, newly selected items fill whatever room is left, in list order.
2052
+ * Does nothing in `single` mode.
2053
+ *
2054
+ * @returns whether the selection changed.
2055
+ */
2056
+ invert(items: readonly T[]): boolean;
2057
+ /**
2058
+ * Applies shift-click semantics: every unlocked item between `from` and `to` (inclusive, in
2059
+ * the order of `items`, whichever comes first) is selected, or deselected with
2060
+ * `selected = false`. Callers pass the item that was last interacted with, normally
2061
+ * {@link anchor}, as `from`, and take `selected` from the item being clicked, so a range
2062
+ * extends whatever the click would have done on its own.
2063
+ *
2064
+ * `to` becomes the new anchor. If either item is not in `items` nothing happens; in `single`
2065
+ * mode only `to` is acted on.
2066
+ *
2067
+ * @returns whether the selection changed.
2068
+ */
2069
+ selectRange(items: readonly T[], from: T, to: T, selected?: boolean): boolean;
2070
+ /**
2071
+ * Summarizes the selection over `items`: `all` when every unlocked item is selected, `none`
2072
+ * when none is, `some` otherwise. Locked items and repeated items are not counted — a locked
2073
+ * item can never be changed by "select all", so counting it would leave the control
2074
+ * mixed forever. Items selected but absent from `items` are ignored, so a filtered page
2075
+ * reports on what it shows.
2076
+ *
2077
+ * With a `max` below the number of unlocked items `all` is unreachable; see {@link toggleAll}.
2078
+ *
2079
+ * Reactive: wrap it in `computed(() => model.stateOf(items()))` for a list signal.
2080
+ */
2081
+ stateOf(items: readonly T[]): AndesSelectionState;
2082
+ /** Whether {@link stateOf} is `all`: a "select all" checkbox is checked. */
2083
+ allSelected(items: readonly T[]): boolean;
2084
+ /**
2085
+ * Whether {@link stateOf} is `some`: a strict subset is selected, i.e. the checkbox is
2086
+ * indeterminate. It is `false` for an empty or complete selection.
2087
+ */
2088
+ someSelected(items: readonly T[]): boolean;
2089
+ /** Sets the shift-click anchor directly, for example after a programmatic focus move. */
2090
+ setAnchor(item: T | undefined): void;
2091
+ private addOne;
2092
+ private removeOne;
2093
+ private addMany;
2094
+ private removeMany;
2095
+ /**
2096
+ * The single write path: derives `{added, removed}` by membership, stores the new array and
2097
+ * publishes the change. A call that only swaps instances or reorders members is stored but
2098
+ * stays silent.
2099
+ */
2100
+ private apply;
2101
+ private clip;
2102
+ private dedupe;
2103
+ private same;
2104
+ private createLookup;
2105
+ }
2106
+
2107
+ /**
2108
+ * Named stacking layer. Each value maps 1:1 onto an `--andes-z-index-*` token in
2109
+ * `@andes-ng/tokens`, so overlay stacking is a token decision, not a magic number
2110
+ * picked per component.
2111
+ */
2112
+ type AndesOverlayLayer = 'dropdown' | 'overlay' | 'modal' | 'popover' | 'tooltip' | 'toast';
2113
+ /**
2114
+ * Numeric fallbacks that mirror the current `--andes-z-index-*` token values. They are
2115
+ * only used when `@andes-ng/tokens`' `theme.css` is not loaded, so an overlay still
2116
+ * stacks in the right order instead of collapsing to `auto`.
2117
+ */
2118
+ declare const ANDES_OVERLAY_Z_INDEX_FALLBACKS: Readonly<Record<AndesOverlayLayer, number>>;
2119
+ /** The CSS value applied as `z-index` for a layer, token first, numeric fallback second. */
2120
+ declare function andesOverlayZIndex(layer: AndesOverlayLayer): string;
2121
+ /** The CSS value applied as the backdrop scrim's background. */
2122
+ declare const ANDES_OVERLAY_SCRIM = "var(--andes-color-overlay, rgb(15 23 42 / 45%))";
2123
+ /** Side of the anchor an anchored overlay prefers to render on. */
2124
+ type AndesOverlaySide = 'top' | 'bottom' | 'left' | 'right';
2125
+ /** Alignment of an anchored overlay along its side. */
2126
+ type AndesOverlayAlign = 'start' | 'center' | 'end';
2127
+ /** Viewport edge an edge-anchored overlay (Drawer/Sheet) slides in from. */
2128
+ type AndesOverlayEdge = 'top' | 'right' | 'bottom' | 'left';
2129
+ /**
2130
+ * Anchor-relative positioning, for Popover / Tooltip / Dropdown Menu. Collision
2131
+ * handling (flip to the opposite side, then shift along the cross axis) is on by
2132
+ * default — an overlay that would render off-screen is a bug, not a variant.
2133
+ */
2134
+ interface AndesOverlayAnchoredPositioning {
2135
+ readonly kind: 'anchored';
2136
+ /** Preferred side. Default `'bottom'`. */
2137
+ readonly side?: AndesOverlaySide;
2138
+ /** Alignment along the side. Default `'center'`. */
2139
+ readonly align?: AndesOverlayAlign;
2140
+ /** Gap in px between anchor and overlay along the side axis. Default `0`. */
2141
+ readonly sideOffset?: number;
2142
+ /** Extra px offset along the alignment axis. Default `0`. */
2143
+ readonly alignOffset?: number;
2144
+ /** Flip to the opposite side when the preferred one does not fit. Default `true`. */
2145
+ readonly flip?: boolean;
2146
+ /**
2147
+ * When flipping, also try the two perpendicular sides once neither the preferred side
2148
+ * nor its opposite fits. Default `true`. A list dropping down from a field turns this
2149
+ * off: placed beside the field it no longer reads as that field's list, so it stays
2150
+ * above or below and is shifted into view instead.
2151
+ */
2152
+ readonly crossSideFallback?: boolean;
2153
+ /** Shift along the cross axis to stay on screen. Default `true`. */
2154
+ readonly shift?: boolean;
2155
+ /** Minimum px kept between the overlay and the viewport edge. Default `8`. */
2156
+ readonly viewportMargin?: number;
2157
+ /** Size the overlay to the anchor's width (Select/Combobox-style). Default `false`. */
2158
+ readonly matchAnchorWidth?: boolean;
2159
+ /** Never re-position after the first open, even on scroll. Default `false`. */
2160
+ readonly lockPosition?: boolean;
2161
+ }
2162
+ /** Viewport-centered positioning, for Dialog / Alert Dialog. */
2163
+ interface AndesOverlayCenteredPositioning {
2164
+ readonly kind: 'centered';
2165
+ }
2166
+ /** Edge-pinned positioning, for Drawer / Sheet. */
2167
+ interface AndesOverlayEdgePositioning {
2168
+ readonly kind: 'edge';
2169
+ /** Which viewport edge to pin to. Default `'right'`. */
2170
+ readonly edge?: AndesOverlayEdge;
2171
+ }
2172
+ type AndesOverlayPositioning = AndesOverlayAnchoredPositioning | AndesOverlayCenteredPositioning | AndesOverlayEdgePositioning;
2173
+ /** Why an overlay closed. Forwarded to consumers so they can veto or branch on it. */
2174
+ type AndesOverlayCloseReason = 'imperative' | 'escape-key' | 'outside-click' | 'backdrop-click' | 'close-button' | 'trigger' | 'destroyed';
2175
+ /** What receives focus when the overlay opens. */
2176
+ type AndesOverlayAutoFocus =
2177
+ /** The first tabbable element inside the overlay (honours `cdkFocusInitial`). */
2178
+ 'first-tabbable'
2179
+ /** The overlay pane itself, via `tabindex="-1"`. */
2180
+ | 'container'
2181
+ /** Nothing — focus stays where the user left it. Correct for Tooltip. */
2182
+ | 'none';
2183
+ /** ARIA role applied to the overlay content by `AndesOverlayContentPrimitive`. */
2184
+ type AndesOverlayRole = 'dialog' | 'alertdialog' | 'menu' | 'listbox' | 'tooltip' | 'none';
2185
+ /** Explicit sizing forwarded to the CDK overlay pane. */
2186
+ interface AndesOverlaySize {
2187
+ readonly width?: number | string;
2188
+ readonly height?: number | string;
2189
+ readonly minWidth?: number | string;
2190
+ readonly minHeight?: number | string;
2191
+ readonly maxWidth?: number | string;
2192
+ readonly maxHeight?: number | string;
2193
+ }
2194
+ /**
2195
+ * The full behavior contract of an andes-ng overlay. Every dismissal, focus and
2196
+ * scroll behavior is an independent flag: Dialog wants all of them, Tooltip wants
2197
+ * almost none, and Popover/Dropdown Menu sit in between.
2198
+ */
2199
+ interface AndesOverlayConfig {
2200
+ /** Stacking layer, sourced from `--andes-z-index-*`. */
2201
+ readonly layer: AndesOverlayLayer;
2202
+ /** How the overlay is placed. */
2203
+ readonly positioning: AndesOverlayPositioning;
2204
+ /** Keep Tab/Shift+Tab inside the overlay while it is open. */
2205
+ readonly trapFocus: boolean;
2206
+ /** Return focus to the trigger (or the element focused at open time) on close. */
2207
+ readonly restoreFocus: boolean;
2208
+ /** What receives focus when the overlay opens. */
2209
+ readonly autoFocus: AndesOverlayAutoFocus;
2210
+ /** Escape closes the overlay. Only the topmost open overlay reacts. */
2211
+ readonly closeOnEscape: boolean;
2212
+ /** A pointer event outside the overlay (or on its backdrop) closes it. */
2213
+ readonly closeOnOutsideClick: boolean;
2214
+ /** Block document scroll while open. */
2215
+ readonly lockScroll: boolean;
2216
+ /**
2217
+ * Mark everything outside the overlay `inert` while it is open, so a screen
2218
+ * reader browsing the page (not just tabbing through it) cannot reach content
2219
+ * behind a modal. `trapFocus` only governs Tab and `ariaModal` is a hint AT does
2220
+ * not universally honour, so a truly modal overlay needs this as well.
2221
+ *
2222
+ * Applies to every direct child of `<body>` except the one the overlay itself is
2223
+ * portalled into, and is reference-counted across nested overlays.
2224
+ *
2225
+ * On for the `dialog`, `alert-dialog` and `drawer` presets; off for the
2226
+ * non-modal `popover`, `menu` and `tooltip`, which must leave the page usable.
2227
+ */
2228
+ readonly inertBackground: boolean;
2229
+ /** Render a backdrop scrim using `--andes-color-overlay`. */
2230
+ readonly hasBackdrop: boolean;
2231
+ /** ARIA role for the content element. */
2232
+ readonly role: AndesOverlayRole;
2233
+ /** Whether the content element gets `aria-modal="true"`. */
2234
+ readonly ariaModal: boolean;
2235
+ /** Extra classes on the CDK overlay pane. */
2236
+ readonly panelClass: string | readonly string[] | null;
2237
+ /** Extra classes on the backdrop, in addition to `andes-overlay-backdrop`. */
2238
+ readonly backdropClass: string | readonly string[] | null;
2239
+ /** Re-position on scroll. Only meaningful for anchored positioning. */
2240
+ readonly repositionOnScroll: boolean;
2241
+ /** Dispose the overlay on browser back/forward. */
2242
+ readonly disposeOnNavigation: boolean;
2243
+ /** Explicit pane sizing. */
2244
+ readonly size: AndesOverlaySize;
2245
+ }
2246
+ /**
2247
+ * Deliberately conservative defaults: no focus trap, no scroll lock, no backdrop,
2248
+ * no inert background. A consumer that forgets to configure gets the least
2249
+ * invasive overlay rather than one that silently hijacks the page.
2250
+ */
2251
+ declare const ANDES_OVERLAY_DEFAULT_CONFIG: AndesOverlayConfig;
2252
+ /** The overlay flavours the five planned components map onto. */
2253
+ type AndesOverlayPreset = 'dialog' | 'alert-dialog' | 'drawer' | 'popover' | 'menu' | 'tooltip';
2254
+ /**
2255
+ * Starting points, not policy: every consumer is expected to spread a preset and
2256
+ * then override. They exist so the five components do not each invent their own
2257
+ * answer to "should a menu trap focus?" independently.
2258
+ *
2259
+ * - `dialog` / `alert-dialog` / `drawer` are modal: trap, lock scroll, backdrop,
2260
+ * and an inert background so AT cannot reach the page behind them.
2261
+ * `alert-dialog` additionally refuses outside-click dismissal, per the WAI-ARIA
2262
+ * distinction between `dialog` and `alertdialog`.
2263
+ * - `drawer` sits on the `overlay` layer rather than `modal`, so a confirmation
2264
+ * dialog opened from inside a drawer stacks above it.
2265
+ * - `popover` and `menu` move focus into the overlay (both are click-opened and
2266
+ * contain interactive content, so keyboard users must be able to reach it) but do
2267
+ * not trap it: the page behind stays usable and scrollable, as with any non-modal
2268
+ * popover, and they leave the rest of the page readable by
2269
+ * assistive tech. Consumers that render a close button inside and want modal
2270
+ * semantics can flip `trapFocus` and `inertBackground` on together.
2271
+ * - `tooltip` moves nothing, traps nothing and restores nothing — it is a passive
2272
+ * hint attached to an element that already has focus.
2273
+ */
2274
+ declare const ANDES_OVERLAY_PRESETS: Readonly<Record<AndesOverlayPreset, Partial<AndesOverlayConfig>>>;
2275
+ /** Reads a preset. A thin helper so consumers never hand-copy a preset's fields. */
2276
+ declare function andesOverlayPreset(preset: AndesOverlayPreset): Partial<AndesOverlayConfig>;
2277
+ /** What can be rendered into an overlay. */
2278
+ type AndesOverlayContent<C = unknown> = TemplateRef<C> | ComponentType<unknown>;
2279
+ /** Class applied to every andes-ng backdrop, as a styling hook for consumers. */
2280
+ declare const ANDES_OVERLAY_BACKDROP_CLASS = "andes-overlay-backdrop";
2281
+ /** Class applied to every andes-ng overlay pane, as a styling hook for consumers. */
2282
+ declare const ANDES_OVERLAY_PANE_CLASS = "andes-overlay-pane";
2283
+ /**
2284
+ * Attribute (empty value) the overlay primitive sets while an overlay is closing,
2285
+ * so a component can play an exit animation before the pane is removed.
2286
+ *
2287
+ * When `close()` is called, the attribute goes on the overlay pane, on each direct
2288
+ * element child of the pane (the root elements of the component's own panel
2289
+ * template, which is where emulated-encapsulation styles can reach it) and on the
2290
+ * backdrop, if there is one. The pane and backdrop also stop receiving pointer
2291
+ * events. The primitive then waits for every animation or transition running in
2292
+ * the pane (and on the backdrop) to finish, at most
2293
+ * {@link ANDES_OVERLAY_EXIT_TIMEOUT_MS}, before detaching.
2294
+ *
2295
+ * A component opts in by styling its panel root, for example
2296
+ *
2297
+ * ```css
2298
+ * .panel[data-andes-closing] {
2299
+ * animation: panel-exit 120ms ease-in forwards;
2300
+ * }
2301
+ * @media (prefers-reduced-motion: reduce) {
2302
+ * .panel[data-andes-closing] { animation: none; }
2303
+ * }
2304
+ * ```
2305
+ *
2306
+ * Use `animation-fill-mode: forwards` (or a transition) so the panel does not
2307
+ * snap back to full opacity between the end of the animation and the detach.
2308
+ * Without any exit style the close stays instant and synchronous, and it is also
2309
+ * instant when the user prefers reduced motion.
2310
+ *
2311
+ * While the attribute is present `isOpen()` is already `false`; focus is restored
2312
+ * and `closed` emits only after the pane is detached. If focus moved to another
2313
+ * element on the page during the exit, it stays there instead of returning to
2314
+ * the trigger. Opening again before then
2315
+ * removes the attribute and keeps the same pane, and neither `closed` nor `opened`
2316
+ * emits for the interrupted close.
2317
+ */
2318
+ declare const ANDES_OVERLAY_CLOSING_ATTRIBUTE = "data-andes-closing";
2319
+ /**
2320
+ * Longest a close waits for exit animations before detaching the overlay anyway,
2321
+ * so a stalled or endless animation can never keep a closed overlay on screen.
2322
+ */
2323
+ declare const ANDES_OVERLAY_EXIT_TIMEOUT_MS = 300;
2324
+
2325
+ /**
2326
+ * Translates one andes-ng `side`/`align` pair into a CDK `ConnectedPosition`.
2327
+ *
2328
+ * `sideOffset` always pushes the overlay *away* from the anchor, so its sign
2329
+ * depends on the side; `alignOffset` shifts along the perpendicular axis.
2330
+ */
2331
+ declare function andesConnectedPosition(side: AndesOverlaySide, align: AndesOverlayAlign, sideOffset: number, alignOffset: number): ConnectedPosition;
2332
+ /**
2333
+ * The preferred position first, then the fallbacks CDK walks when it does not fit:
2334
+ * the opposite side (a flip), then - unless `crossSideFallback` is off - the two
2335
+ * perpendicular sides (a last resort before CDK falls back to pushing the overlay into
2336
+ * view).
2337
+ */
2338
+ declare function andesConnectedPositions(side: AndesOverlaySide, align: AndesOverlayAlign, sideOffset: number, alignOffset: number, flip: boolean, crossSideFallback?: boolean): ConnectedPosition[];
2339
+ /**
2340
+ * Builds the CDK position strategy for a config. Anchored positioning needs the
2341
+ * anchor element; without one it degrades to centered rather than throwing, so a
2342
+ * Popover whose trigger has not attached yet still renders somewhere sane.
2343
+ */
2344
+ declare function createAndesPositionStrategy(injector: Injector, positioning: AndesOverlayPositioning, anchor: HTMLElement | null): PositionStrategy;
2345
+ /**
2346
+ * Sizing implied by the positioning, before the consumer's explicit `size` wins.
2347
+ * An edge-anchored panel spans the edge it is pinned to; nothing else implies a size.
2348
+ */
2349
+ declare function andesImpliedSize(positioning: AndesOverlayPositioning): Pick<AndesOverlayConfig['size'], 'width' | 'height'>;
2350
+
2351
+ /**
2352
+ * Handle for one overlay's claim on an inert background. Releasing it undoes
2353
+ * *that claim only*: elements another still-open overlay also depends on stay
2354
+ * inert. Releasing twice is a no-op, so a double `close()` cannot under-count.
2355
+ */
2356
+ interface AndesOverlayInertHandle {
2357
+ release(): void;
2358
+ }
2359
+ /**
2360
+ * Reference-counted registry of "everything outside the modal is inert".
2361
+ *
2362
+ * ## Why this exists
2363
+ *
2364
+ * A focus trap only governs Tab. A screen-reader user in browse mode (VoiceOver's
2365
+ * `VO`-arrow, NVDA/JAWS virtual cursor) walks the accessibility tree directly and
2366
+ * happily reads — and activates — content behind an open modal. `aria-modal="true"`
2367
+ * on the dialog is a *hint* that browsers and AT do not universally honour on its
2368
+ * own, which is why this primitive additionally marks the rest of the page inert.
2369
+ *
2370
+ * ## What gets marked
2371
+ *
2372
+ * Every **direct child of `<body>`** except the one containing the overlay itself.
2373
+ * Not `<body>`, because the overlay's own portal is rendered into the CDK overlay
2374
+ * container, which is itself a body child — inerting `<body>` would make the modal
2375
+ * unusable. Also excluded: CDK's own accessibility infrastructure (`LiveAnnouncer`,
2376
+ * `AriaDescriber`), which CDK likewise appends directly under `<body>` — see
2377
+ * `NON_RENDERED_SELECTORS` below.
2378
+ *
2379
+ * ## Nesting
2380
+ *
2381
+ * Every andes-ng overlay portals into the *same* CDK overlay container, so a
2382
+ * confirm dialog opened on top of a dialog needs the exact same background inert.
2383
+ * Rather than re-applying (and then prematurely releasing) it, each open overlay
2384
+ * takes a reference on the elements it needs; an element loses `inert` only when
2385
+ * the last overlay holding it closes. Closing the inner dialog therefore leaves
2386
+ * the outer dialog's background inert, and only the outermost close restores the
2387
+ * page.
2388
+ *
2389
+ * Exempting the shared container is not enough on its own, though: it would leave
2390
+ * the outer dialog — a sibling of the inner one inside that container — reachable
2391
+ * behind it. So a modal also claims every *other* child of the overlay container
2392
+ * (each earlier overlay's host wrapper and backdrop). Only what exists at open
2393
+ * time is claimed, which is exactly what lies beneath the new modal: a popover or
2394
+ * select opened later, from inside it, stays interactive. Those claims go through
2395
+ * the same reference count, so closing the inner dialog hands the outer one back
2396
+ * only once no other modal above it still holds it, in any closing order.
2397
+ *
2398
+ * ## What it refuses to touch
2399
+ *
2400
+ * An element that is *already* inert when we first see it is left alone entirely
2401
+ * and never tracked: something outside our control put it there for its own
2402
+ * reasons, and it is not ours to clear on close. Same for a pre-existing
2403
+ * `aria-hidden`.
2404
+ *
2405
+ * Provided in root because the reference count is a property of the document, not
2406
+ * of any one overlay instance — the same reason CDK's block-scroll strategy keeps
2407
+ * its own global count.
2408
+ */
2409
+ declare class AndesOverlayInertRegistry {
2410
+ private readonly document;
2411
+ private readonly entries;
2412
+ /**
2413
+ * The elements currently inert *because of us*. Elements that were already
2414
+ * inert for their own reasons are deliberately absent. Exposed so a consumer
2415
+ * (or a test) can assert on the primitive's footprint rather than on every
2416
+ * `[inert]` element in the document.
2417
+ */
2418
+ get inertElements(): readonly Element[];
2419
+ /**
2420
+ * Marks every direct child of `<body>` inert, except the subtrees the given
2421
+ * elements live in (the overlay's own pane and backdrop), plus every other
2422
+ * overlay already open in the CDK overlay container — the layers beneath this
2423
+ * one. Returns the handle that undoes exactly this claim.
2424
+ */
2425
+ inertBackgroundExcept(exemptElements: readonly (Element | null | undefined)[]): AndesOverlayInertHandle;
2426
+ /**
2427
+ * Takes a reference on one element. Returns whether the caller now holds one —
2428
+ * `false` for an element that was already inert before we ever saw it, which we
2429
+ * deliberately neither track nor modify.
2430
+ */
2431
+ private claim;
2432
+ /** Drops one reference, clearing our attributes only when it was the last. */
2433
+ private release;
2434
+ private backgroundChildren;
2435
+ /**
2436
+ * The children of the CDK overlay container holding the exempt elements, other
2437
+ * than the ones containing them: the host wrappers and backdrops of overlays
2438
+ * opened earlier, and so stacked beneath this one.
2439
+ */
2440
+ private lowerOverlayLayers;
2441
+ /**
2442
+ * True for a body child that is itself an accessibility mechanism rather than
2443
+ * page content — CDK's own (matched by class, since `NON_RENDERED_TAGS` only
2444
+ * matches by tag), or a live region a consumer appended directly under `<body>`
2445
+ * for the same reason CDK does: so it keeps announcing regardless of what else
2446
+ * on the page is inert.
2447
+ *
2448
+ * `aria-live="off"` is excluded because it explicitly opts out of announcing —
2449
+ * exempting it would serve no one and would needlessly leave an otherwise
2450
+ * ordinary piece of background content out of the inert set.
2451
+ */
2452
+ private isAccessibilityInfrastructure;
2453
+ /**
2454
+ * Walks up from an element to the `<body>` child whose subtree contains it, so
2455
+ * exempting an overlay pane exempts the whole portal outlet it was rendered into.
2456
+ */
2457
+ private bodyChildOf;
2458
+ private supportsInert;
2459
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesOverlayInertRegistry, never>;
2460
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesOverlayInertRegistry>;
2461
+ }
2462
+
2463
+ /** Options accepted when opening an overlay. */
2464
+ interface AndesOverlayOpenOptions<C = unknown> {
2465
+ /** Template context, for `TemplateRef` content. */
2466
+ readonly context?: C;
2467
+ /**
2468
+ * View container used to instantiate `TemplateRef` content. Defaults to the one
2469
+ * available where `AndesOverlayPrimitive` was provided, which is what a compound
2470
+ * component wants; pass it explicitly only when opening a template that lives in
2471
+ * some other view.
2472
+ */
2473
+ readonly viewContainerRef?: ViewContainerRef;
2474
+ /** Injector for `ComponentType` content, so the component can inject the overlay. */
2475
+ readonly injector?: Injector;
2476
+ /**
2477
+ * Element focus returns to on close, overriding the registered trigger.
2478
+ */
2479
+ readonly restoreFocusTo?: HTMLElement | ElementRef<HTMLElement> | null;
2480
+ }
2481
+ /**
2482
+ * The shared overlay behavior primitive: portal rendering, positioning, focus
2483
+ * management, dismissal and stacking for every andes-ng overlay component.
2484
+ *
2485
+ * It is a thin, tokens-integrated wrapper around Angular CDK's `Overlay` and
2486
+ * `ConfigurableFocusTrap` rather than a reimplementation — the failure modes here
2487
+ * (focus escaping a trap, focus returning to the wrong element, stacking-context
2488
+ * bugs) are exactly the ones CDK already solves.
2489
+ *
2490
+ * ## Consuming it
2491
+ *
2492
+ * `AndesOverlayPrimitive` is deliberately *not* `providedIn: 'root'`. A compound
2493
+ * component provides one instance for itself, and its own sub-directives inject it
2494
+ * — Angular's equivalent of React context:
2495
+ *
2496
+ * ```ts
2497
+ * @Component({
2498
+ * selector: 'andes-dialog',
2499
+ * providers: [provideAndesOverlay()],
2500
+ * template: `
2501
+ * <ng-content select="[andesOverlayTrigger]" />
2502
+ * <ng-template #content>
2503
+ * <div andesOverlayContent>
2504
+ * <h2 id="title">Delete project</h2>
2505
+ * <button andesOverlayClose>Cancel</button>
2506
+ * </div>
2507
+ * </ng-template>
2508
+ * `,
2509
+ * })
2510
+ * export class AndesDialog {
2511
+ * private readonly overlay = inject(AndesOverlayPrimitive);
2512
+ * private readonly content = viewChild.required<TemplateRef<unknown>>('content');
2513
+ *
2514
+ * constructor() {
2515
+ * this.overlay.configure(andesOverlayPreset('dialog'));
2516
+ * }
2517
+ *
2518
+ * open() {
2519
+ * this.overlay.open(this.content());
2520
+ * }
2521
+ * }
2522
+ * ```
2523
+ *
2524
+ * ## Stacking
2525
+ *
2526
+ * Overlays are rendered inside CDK's overlay container (never as a native
2527
+ * `popover`, which would move them to the top layer where `z-index` is ignored and
2528
+ * ordering is by call time). `z-index` comes from `--andes-z-index-*` per layer, so
2529
+ * a Popover opened from inside a Dialog stacks above it without either component
2530
+ * hard-coding a number.
2531
+ *
2532
+ * ## Modality
2533
+ *
2534
+ * A modal overlay needs three separate things, and `aria-modal` is only the weakest
2535
+ * of them: `trapFocus` for Tab, `ariaModal` as a hint, and `inertBackground` to
2536
+ * actually mark every other body child `inert` so a screen reader's browse mode
2537
+ * cannot walk into the page behind it. The modal presets turn on all three.
2538
+ *
2539
+ * ## Exit animations
2540
+ *
2541
+ * `close()` does not always detach at once. It sets
2542
+ * {@link ANDES_OVERLAY_CLOSING_ATTRIBUTE} on the pane, its direct element children
2543
+ * and the backdrop, and detaches once the animations that starts have finished
2544
+ * (capped at {@link ANDES_OVERLAY_EXIT_TIMEOUT_MS}). With no exit animation, with
2545
+ * reduced motion, or where the browser cannot report animations, the close is
2546
+ * synchronous. `isOpen()` turns `false` immediately either way.
2547
+ */
2548
+ declare class AndesOverlayPrimitive {
2549
+ private readonly injector;
2550
+ private readonly document;
2551
+ private readonly focusTrapFactory;
2552
+ private readonly inertRegistry;
2553
+ private readonly hostViewContainerRef;
2554
+ private readonly _config;
2555
+ private readonly _isOpen;
2556
+ private readonly _anchor;
2557
+ private readonly _panelElement;
2558
+ private readonly _backdropElement;
2559
+ private readonly _contentElement;
2560
+ private readonly _isBackgroundInert;
2561
+ /** The resolved behavior configuration. */
2562
+ readonly config: _angular_core.Signal<AndesOverlayConfig>;
2563
+ /**
2564
+ * Whether the overlay is open. Turns `false` as soon as a close is requested,
2565
+ * even while the pane is still attached playing its exit animation.
2566
+ */
2567
+ readonly isOpen: _angular_core.Signal<boolean>;
2568
+ /** The registered trigger element, if any. */
2569
+ readonly anchor: _angular_core.Signal<HTMLElement | null>;
2570
+ /** The CDK overlay pane, while attached (including during its exit animation). */
2571
+ readonly panelElement: _angular_core.Signal<HTMLElement | null>;
2572
+ /** The backdrop scrim, while open and if the config renders one. */
2573
+ readonly backdropElement: _angular_core.Signal<HTMLElement | null>;
2574
+ /** The element carrying `andesOverlayContent`, while open. */
2575
+ readonly contentElement: _angular_core.Signal<HTMLElement | null>;
2576
+ /**
2577
+ * Whether this overlay currently holds the page's background inert. False for a
2578
+ * non-modal overlay, and false again as soon as it closes — even if an outer
2579
+ * overlay still keeps the background inert for its own sake.
2580
+ */
2581
+ readonly isBackgroundInert: _angular_core.Signal<boolean>;
2582
+ /**
2583
+ * Stable id shared by the trigger's `aria-controls`/`aria-describedby` and the
2584
+ * content element's `id`, so assistive tech can relate the two.
2585
+ */
2586
+ readonly contentId: string;
2587
+ private readonly _opened;
2588
+ private readonly _closed;
2589
+ private readonly _keydown;
2590
+ private readonly _outsidePointer;
2591
+ /** Emits after the overlay has been attached. */
2592
+ readonly opened: Observable<void>;
2593
+ /**
2594
+ * Emits the reason after the overlay has been detached — after its exit
2595
+ * animation, if it has one. Not emitted for a close cancelled by re-opening.
2596
+ */
2597
+ readonly closed: Observable<AndesOverlayCloseReason>;
2598
+ /**
2599
+ * Keydown events reaching the open overlay. Only the topmost overlay receives
2600
+ * them. Dropdown Menu builds its arrow-key/typeahead navigation on this.
2601
+ */
2602
+ readonly keydownEvents: Observable<KeyboardEvent>;
2603
+ /** Pointer events outside the open overlay, whether or not they close it. */
2604
+ readonly outsidePointerEvents: Observable<MouseEvent>;
2605
+ private overlayRef;
2606
+ private focusTrap;
2607
+ private inertHandle;
2608
+ private focusOrigin;
2609
+ private explicitRestoreTarget;
2610
+ private openSubscriptions;
2611
+ private pendingExit;
2612
+ private attachedContent;
2613
+ private attachedContext;
2614
+ constructor();
2615
+ /**
2616
+ * Merges a partial configuration in. Intended to be called from a consuming
2617
+ * component's constructor with a preset, then again from an `effect` as its own
2618
+ * inputs change.
2619
+ *
2620
+ * While the overlay is open, `positioning`, `size` and `layer` are re-applied
2621
+ * immediately. The structural flags (`hasBackdrop`, `trapFocus`, `lockScroll`,
2622
+ * `inertBackground`, `autoFocus`) take effect on the next `open()`; the
2623
+ * dismissal flags (`closeOnEscape`, `closeOnOutsideClick`) are read at event
2624
+ * time and so apply immediately.
2625
+ */
2626
+ configure(patch: Partial<AndesOverlayConfig>): void;
2627
+ /**
2628
+ * Registers the element anchored positioning measures against, and the element
2629
+ * focus returns to on close. `AndesOverlayTriggerPrimitive` calls this for you.
2630
+ */
2631
+ registerAnchor(element: HTMLElement | ElementRef<HTMLElement> | null | undefined): void;
2632
+ /** Registers the content element. `AndesOverlayContentPrimitive` calls this. */
2633
+ registerContent(element: HTMLElement | ElementRef<HTMLElement> | null | undefined): void;
2634
+ /**
2635
+ * Opens the overlay with the given content. A no-op if already open.
2636
+ *
2637
+ * Called while the overlay is still playing its exit animation with the same
2638
+ * content and context, it cancels the close and keeps the same pane, so the
2639
+ * panel does not flicker; with different content the old pane is detached first.
2640
+ */
2641
+ open<C>(content: AndesOverlayContent<C>, options?: AndesOverlayOpenOptions<C>): void;
2642
+ /**
2643
+ * Closes the overlay. A no-op if already closed or closing.
2644
+ *
2645
+ * `isOpen()` turns `false` at once. If the closing attribute starts an exit
2646
+ * animation, the pane stays attached until it ends; focus restoration and the
2647
+ * `closed` emission follow the detach.
2648
+ */
2649
+ close(reason?: AndesOverlayCloseReason): void;
2650
+ /** Opens the overlay if closed, closes it if open. */
2651
+ toggle<C>(content: AndesOverlayContent<C>, options?: AndesOverlayOpenOptions<C>): void;
2652
+ /** Recomputes the overlay's position. Cheap to call on resize or content change. */
2653
+ updatePosition(): void;
2654
+ /**
2655
+ * Moves focus to the first tabbable element inside the overlay. Exposed for
2656
+ * consumers that focus lazily — a Dropdown Menu opened by keyboard, say.
2657
+ */
2658
+ focusFirstTabbable(): void;
2659
+ private beginClose;
2660
+ /**
2661
+ * Marks the pane, its direct element children and the backdrop as closing and
2662
+ * returns the animations that marking started. Nothing is marked when the exit
2663
+ * would be skipped anyway: reduced motion, or no `getAnimations` (SSR, jsdom).
2664
+ */
2665
+ private startExit;
2666
+ /** Re-opening during the exit: undo the close on the pane that is still there. */
2667
+ private cancelExit;
2668
+ /** Detaches the overlay and finishes a requested close. */
2669
+ private completeClose;
2670
+ /**
2671
+ * Whether focus now sits on a page element outside the pane. Focus inside the
2672
+ * pane (an Escape or close-button close) or dropped on `body` still restores.
2673
+ */
2674
+ private focusMovedOutside;
2675
+ private prefersReducedMotion;
2676
+ private buildOverlayConfig;
2677
+ private anchorWidth;
2678
+ private buildScrollStrategy;
2679
+ private applyLayerStyles;
2680
+ private styleBackdrop;
2681
+ private wireDismissal;
2682
+ /**
2683
+ * Makes the page outside the overlay inert, so AT browsing the document — not
2684
+ * just tabbing through it — cannot reach content behind a modal. That includes
2685
+ * overlays already open beneath it in the same CDK container (an outer dialog).
2686
+ * The pane and backdrop are exempt, and the registry reference-counts across
2687
+ * nested overlays.
2688
+ */
2689
+ private applyInertBackground;
2690
+ private releaseInertBackground;
2691
+ private installFocusManagement;
2692
+ /**
2693
+ * Focus goes back to, in order: an explicit `restoreFocusTo`, the registered
2694
+ * trigger, the element that was focused when the overlay opened. Each candidate
2695
+ * is skipped if it is no longer in the document — a trigger inside a list row
2696
+ * that the overlay's own action deleted, say. If none survive, focus is left to
2697
+ * the browser's own fallback (`document.body`).
2698
+ */
2699
+ private restoreFocus;
2700
+ private activeElement;
2701
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesOverlayPrimitive, never>;
2702
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesOverlayPrimitive>;
2703
+ }
2704
+ /**
2705
+ * Providers a compound overlay component puts in its own `providers` array to own
2706
+ * one overlay instance, scoped to itself and injectable by its sub-directives.
2707
+ */
2708
+ declare function provideAndesOverlay(): Provider[];
2709
+
2710
+ /**
2711
+ * How a trigger advertises its overlay to assistive tech.
2712
+ *
2713
+ * - `expanded` — `aria-haspopup` + `aria-expanded` + `aria-controls`. Correct for
2714
+ * Dialog, Drawer, Popover and Dropdown Menu, where the trigger opens something
2715
+ * the user then interacts with.
2716
+ * - `described-by` — `aria-describedby` pointing at the overlay. Correct for
2717
+ * Tooltip: the overlay describes the trigger, it is not a thing you open.
2718
+ * - `none` — the consumer wires its own ARIA.
2719
+ */
2720
+ type AndesOverlayAriaAttachment = 'expanded' | 'described-by' | 'none';
2721
+ /**
2722
+ * Marks an element as an overlay's trigger. Registers it as the positioning anchor
2723
+ * and as the element focus returns to on close, and keeps its ARIA state in sync.
2724
+ *
2725
+ * It intentionally does *not* bind a click handler: whether the overlay opens on
2726
+ * click, hover, focus or context menu is the consuming component's decision (and
2727
+ * differs across Dialog, Popover, Tooltip and Dropdown Menu). Call
2728
+ * `trigger.overlay.toggle(template)` from wherever that decision is made.
2729
+ */
2730
+ declare class AndesOverlayTriggerPrimitive {
2731
+ /** The overlay this trigger belongs to. */
2732
+ readonly overlay: AndesOverlayPrimitive;
2733
+ private readonly elementRef;
2734
+ /** Which ARIA relationship to expose. Defaults to `expanded`. */
2735
+ readonly ariaAttachment: _angular_core.InputSignal<AndesOverlayAriaAttachment>;
2736
+ protected readonly ariaHasPopup: _angular_core.Signal<"listbox" | "dialog" | "menu" | undefined>;
2737
+ protected readonly ariaExpanded: _angular_core.Signal<string | undefined>;
2738
+ protected readonly ariaControls: _angular_core.Signal<string | undefined>;
2739
+ protected readonly ariaDescribedBy: _angular_core.Signal<string | undefined>;
2740
+ constructor();
2741
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesOverlayTriggerPrimitive, never>;
2742
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesOverlayTriggerPrimitive, "[andesOverlayTrigger]", ["andesOverlayTrigger"], { "ariaAttachment": { "alias": "ariaAttachment"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2743
+ }
2744
+
2745
+ /**
2746
+ * Marks the root element of an overlay's projected content. Applies the ARIA role
2747
+ * and the id the trigger points at, and reports the element back to the primitive
2748
+ * so consumers can measure or query it.
2749
+ *
2750
+ * The focus trap brackets the CDK pane rather than this element, so content can
2751
+ * safely render siblings (an arrow, a swipe handle) outside it.
2752
+ */
2753
+ declare class AndesOverlayContentPrimitive {
2754
+ /** The overlay this content belongs to. */
2755
+ readonly overlay: AndesOverlayPrimitive;
2756
+ private readonly elementRef;
2757
+ protected readonly role: _angular_core.Signal<"listbox" | "tooltip" | "dialog" | "alertdialog" | "menu" | undefined>;
2758
+ protected readonly ariaModal: _angular_core.Signal<"true" | undefined>;
2759
+ protected readonly tabIndex: _angular_core.Signal<"-1" | undefined>;
2760
+ constructor();
2761
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesOverlayContentPrimitive, never>;
2762
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesOverlayContentPrimitive, "[andesOverlayContent]", ["andesOverlayContent"], {}, {}, never, never, true, never>;
2763
+ }
2764
+
2765
+ /**
2766
+ * Closes the overlay when activated. Every modal overlay should render one of
2767
+ * these inside its content: Escape and outside-click are not reachable for touch
2768
+ * screen-reader users, so an in-content dismiss affordance is an accessibility
2769
+ * requirement, not a convenience.
2770
+ */
2771
+ declare class AndesOverlayClosePrimitive {
2772
+ /** The overlay this control closes. */
2773
+ readonly overlay: AndesOverlayPrimitive;
2774
+ protected close(): void;
2775
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesOverlayClosePrimitive, never>;
2776
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesOverlayClosePrimitive, "[andesOverlayClose]", ["andesOverlayClose"], {}, {}, never, never, true, never>;
2777
+ }
2778
+
2779
+ /** The CDK's own typeahead debounce default, restated so consumers can reference it. */
2780
+ declare const ANDES_LIST_TYPEAHEAD_DEBOUNCE = 200;
2781
+ /**
2782
+ * Shared list-navigation state for a listbox, menu, tablist or combobox popup: roving
2783
+ * tabindex or `aria-activedescendant` focus management, arrow-key navigation in either
2784
+ * orientation, `Home`/`End`, typeahead and disabled-item skipping.
2785
+ *
2786
+ * This is a thin, signal-shaped wrapper around Angular CDK's own `ListKeyManager` family
2787
+ * (`FocusKeyManager` for `roving-tabindex`, `ActiveDescendantKeyManager` for
2788
+ * `active-descendant`) — the key handling, typeahead buffering and wrap-around arithmetic
2789
+ * are the CDK's, not a reimplementation.
2790
+ *
2791
+ * It is a DI-scoped service, not a directive: provide it from the consuming component so
2792
+ * that every descendant item — including items rendered into a CDK overlay, which are
2793
+ * injector descendants but not DOM descendants — can inject the same instance.
2794
+ *
2795
+ * ```ts
2796
+ * @Component({
2797
+ * selector: 'andes-tabs',
2798
+ * providers: [AndesListNavigation],
2799
+ * template: `
2800
+ * <div role="tablist" andesListNavigationKeys>
2801
+ * @for (tab of tabs(); track tab.value) {
2802
+ * <button role="tab" type="button" andesListNavigationItem [disabled]="tab.disabled">
2803
+ * {{ tab.label }}
2804
+ * </button>
2805
+ * }
2806
+ * </div>
2807
+ * `,
2808
+ * })
2809
+ * export class AndesTabs {
2810
+ * protected readonly navigation = inject(AndesListNavigation);
2811
+ *
2812
+ * constructor() {
2813
+ * this.navigation.configure({ orientation: 'horizontal', wrap: true });
2814
+ * }
2815
+ * }
2816
+ * ```
2817
+ *
2818
+ * What this primitive deliberately does *not* do: it owns no selection state, sets no
2819
+ * `role`/`aria-selected`/`aria-checked`, and never interprets `Enter`, `Space` or `Escape`.
2820
+ * "Active item" is focus, not selection — a component that activates on focus (Tabs in
2821
+ * automatic-activation mode) reacts to {@link activeItem} in an `effect`, while one that
2822
+ * activates manually handles `Enter`/`Space` itself.
2823
+ */
2824
+ declare class AndesListNavigation {
2825
+ private readonly injector;
2826
+ private readonly destroyRef;
2827
+ /** Which arrow keys navigate. */
2828
+ readonly orientation: _angular_core.WritableSignal<AndesListOrientation>;
2829
+ /** Whether navigation wraps around the ends of the list. */
2830
+ readonly wrap: _angular_core.WritableSignal<boolean>;
2831
+ /** Whether typing characters moves the active item. */
2832
+ readonly typeahead: _angular_core.WritableSignal<boolean>;
2833
+ /** How long to wait after the last keystroke before matching. */
2834
+ readonly typeaheadDebounce: _angular_core.WritableSignal<number>;
2835
+ /** Whether `Home`/`End` jump to the first/last enabled item. */
2836
+ readonly homeAndEnd: _angular_core.WritableSignal<boolean>;
2837
+ /** How the active item is surfaced to assistive technology. */
2838
+ readonly focusMode: _angular_core.WritableSignal<AndesListFocusMode>;
2839
+ /** Text direction used to mirror horizontal arrow keys. */
2840
+ readonly textDirection: _angular_core.WritableSignal<AndesListTextDirection>;
2841
+ /** Whether disabled items are skipped while navigating. */
2842
+ readonly skipDisabled: _angular_core.WritableSignal<boolean>;
2843
+ private readonly _items;
2844
+ /** Every registered item, in DOM order. */
2845
+ readonly items: Signal<readonly AndesListNavigationItemRef[]>;
2846
+ private readonly _activeItem;
2847
+ /** The active item, or `null` when nothing is active yet. */
2848
+ readonly activeItem: Signal<AndesListNavigationItemRef | null>;
2849
+ private readonly _activeIndex;
2850
+ /** Index of the active item within {@link items}, or `-1` when nothing is active. */
2851
+ readonly activeIndex: Signal<number>;
2852
+ /**
2853
+ * The `id` to publish as `aria-activedescendant`, or `null` in `roving-tabindex` mode
2854
+ * (where real focus already conveys the active item).
2855
+ */
2856
+ readonly activeDescendantId: Signal<string | null>;
2857
+ /**
2858
+ * The single item that should carry `tabindex="0"` in `roving-tabindex` mode: the active
2859
+ * item, or — before anything is active — the first item the user can navigate to, so the
2860
+ * list is always reachable with one Tab press.
2861
+ */
2862
+ readonly rovingTabindexTarget: Signal<AndesListNavigationItemRef | null>;
2863
+ private readonly _tabOut;
2864
+ /** Emits when `Tab` is pressed, so a menu or popup can close and hand focus back. */
2865
+ readonly tabOut: Observable<void>;
2866
+ private keyManager;
2867
+ private keyManagerMode;
2868
+ private appliedTypeaheadDebounce;
2869
+ private destroyed;
2870
+ constructor();
2871
+ /** Applies any subset of the configuration options in one call. */
2872
+ configure(config: AndesListNavigationConfig): void;
2873
+ /** Adds an item to the list. Called by {@link AndesListNavigationItem} on init. */
2874
+ register(item: AndesListNavigationItemRef): void;
2875
+ /**
2876
+ * Removes an item from the list. Called by {@link AndesListNavigationItem} on destroy.
2877
+ * Removing the active item clears the active state rather than guessing a neighbour.
2878
+ */
2879
+ unregister(item: AndesListNavigationItemRef): void;
2880
+ /**
2881
+ * Handles a `keydown` on whichever element owns keyboard focus for this list — the
2882
+ * tablist/menu in `roving-tabindex` mode, the text input in `active-descendant` mode.
2883
+ * {@link AndesListNavigationKeys} wires this up for you.
2884
+ *
2885
+ * Only navigation keys are consumed (and `preventDefault`ed); `Enter`, `Space` and
2886
+ * `Escape` are left entirely to the consumer.
2887
+ *
2888
+ * Note: this delegates to the CDK, which reads the (deprecated but universally
2889
+ * populated) `KeyboardEvent.keyCode`. Real browsers always set it; synthetic events in
2890
+ * unit tests must set it explicitly.
2891
+ */
2892
+ onKeydown(event: KeyboardEvent): void;
2893
+ /** Moves the active item to the first enabled item. */
2894
+ focusFirst(): void;
2895
+ /** Moves the active item to the last enabled item. */
2896
+ focusLast(): void;
2897
+ /** Moves the active item forwards, respecting {@link wrap} and {@link skipDisabled}. */
2898
+ focusNext(): void;
2899
+ /** Moves the active item backwards, respecting {@link wrap} and {@link skipDisabled}. */
2900
+ focusPrevious(): void;
2901
+ /** Makes a specific item active, applying the current focus mode's effects. */
2902
+ focusItem(item: AndesListNavigationItemRef | number): void;
2903
+ /**
2904
+ * Moves the active pointer without focusing the item — for restoring an active item
2905
+ * after the list is re-filtered, or for following a click/`focusin` that already moved
2906
+ * real focus. Being silent, it also skips scrolling the item into view; use
2907
+ * {@link focusItem} when the item should be revealed.
2908
+ */
2909
+ setActiveItemSilently(item: AndesListNavigationItemRef | number | null): void;
2910
+ /** Clears the active item, e.g. when a popup closes. */
2911
+ clearActive(): void;
2912
+ /** Whether a typeahead query is currently buffered. */
2913
+ isTyping(): boolean;
2914
+ /** Discards any buffered typeahead query. */
2915
+ cancelTypeahead(): void;
2916
+ private shouldSkip;
2917
+ /**
2918
+ * Returns the CDK key manager for the current focus mode, creating it on first use and
2919
+ * rebuilding it (carrying the active item over) if the focus mode changes.
2920
+ */
2921
+ private resolveKeyManager;
2922
+ private applyConfig;
2923
+ private syncActive;
2924
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesListNavigation, never>;
2925
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AndesListNavigation>;
2926
+ }
2927
+
2928
+ /**
2929
+ * One navigable item inside a list governed by {@link AndesListNavigation}: an option, a
2930
+ * menu item or a tab. Registers itself with the parent on init and removes itself on
2931
+ * destroy, so `@if`/`@for`-driven lists and CDK-overlay popups work without any manual
2932
+ * bookkeeping.
2933
+ *
2934
+ * It manages only the mechanics of being navigable — the roving `tabindex`, a stable `id`
2935
+ * for `aria-activedescendant`, and `data-active`/`data-disabled` hooks for styling. Roles
2936
+ * and selection attributes (`role="option"`, `aria-selected`, `aria-checked`) stay with the
2937
+ * consuming component.
2938
+ *
2939
+ * ```html
2940
+ * <button type="button" andesListNavigationItem [disabled]="option.disabled">
2941
+ * {{ option.label }}
2942
+ * </button>
2943
+ * ```
2944
+ */
2945
+ declare class AndesListNavigationItem implements AndesListNavigationItemRef, OnInit, OnDestroy {
2946
+ /** Whether the item is skipped while navigating. */
2947
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
2948
+ /**
2949
+ * Text typeahead matches against. Defaults to the item's own text content, which is
2950
+ * usually right; set it when the visible text is decorative or split across elements.
2951
+ */
2952
+ readonly typeaheadLabel: _angular_core.InputSignal<string | undefined>;
2953
+ /** @internal Mirrors {@link disabled} under the name the parent's item contract uses. */
2954
+ readonly isDisabled: Signal<boolean>;
2955
+ private readonly navigation;
2956
+ readonly element: HTMLElement;
2957
+ /** The item's DOM `id` — its own, when it has one, so authored ids are never clobbered. */
2958
+ readonly itemId: string;
2959
+ /** Whether this is the parent's active item. */
2960
+ readonly active: Signal<boolean>;
2961
+ /**
2962
+ * `0` on the one roving-tabindex target, `-1` on every other item, and absent entirely in
2963
+ * `active-descendant` mode, where items are not focusable at all.
2964
+ */
2965
+ protected readonly tabIndex: Signal<number | null>;
2966
+ ngOnInit(): void;
2967
+ ngOnDestroy(): void;
2968
+ getLabel(): string;
2969
+ focus(): void;
2970
+ /**
2971
+ * The CDK's `Highlightable` hook, called only in `active-descendant` mode. Andes derives
2972
+ * the active *styling* from the parent's signal instead (see {@link active}), so that it
2973
+ * works identically in both focus modes; what is genuinely mode-specific is scrolling —
2974
+ * real focus never moves here, so the item has to bring itself into view.
2975
+ */
2976
+ setActiveStyles(): void;
2977
+ /**
2978
+ * Counterpart to {@link setActiveStyles}. Nothing to undo: the active styling is derived
2979
+ * from the parent's signal, and scrolling is not reversible.
2980
+ */
2981
+ setInactiveStyles(): void;
2982
+ /**
2983
+ * Keeps the roving `tabindex` on whichever item the user actually reached, including via
2984
+ * mouse or a direct Tab, so Tabbing away and back returns to the same place.
2985
+ */
2986
+ protected onFocusin(): void;
2987
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesListNavigationItem, never>;
2988
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesListNavigationItem, "[andesListNavigationItem]", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "typeaheadLabel": { "alias": "typeaheadLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2989
+ }
2990
+
2991
+ /**
2992
+ * Wires the keyboard-owning element of a list to its {@link AndesListNavigation}: forwards
2993
+ * `keydown` to the key manager and publishes `aria-activedescendant` when the list is in
2994
+ * `active-descendant` mode.
2995
+ *
2996
+ * Put it on whichever element holds real DOM focus:
2997
+ *
2998
+ * - Tabs — the `role="tablist"` element.
2999
+ * - Dropdown Menu — the `role="menu"` popup.
3000
+ * - Select — the trigger while closed, or the `role="listbox"` popup once focus moves into it.
3001
+ * - Combobox — the `<input>` itself, which keeps focus the whole time.
3002
+ *
3003
+ * It only consumes navigation keys, so the consumer is free to bind its own `keydown`
3004
+ * handler on the same element for `Enter`, `Space` and `Escape`.
3005
+ *
3006
+ * ```html
3007
+ * <input andesListNavigationKeys role="combobox" (keydown.enter)="commit()" />
3008
+ * ```
3009
+ */
3010
+ declare class AndesListNavigationKeys {
3011
+ protected readonly navigation: AndesListNavigation;
3012
+ protected onKeydown(event: KeyboardEvent): void;
3013
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AndesListNavigationKeys, never>;
3014
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AndesListNavigationKeys, "[andesListNavigationKeys]", never, {}, {}, never, never, true, never>;
3015
+ }
3016
+
3017
+ export { ANDES_BREAKPOINTS, ANDES_BREAKPOINT_NAMES, ANDES_BREAKPOINT_SSR_DEFAULT, ANDES_CHOICE_ROLE_MAPPINGS, ANDES_LIST_TYPEAHEAD_DEBOUNCE, ANDES_OVERLAY_BACKDROP_CLASS, ANDES_OVERLAY_CLOSING_ATTRIBUTE, ANDES_OVERLAY_DEFAULT_CONFIG, ANDES_OVERLAY_EXIT_TIMEOUT_MS, ANDES_OVERLAY_PANE_CLASS, ANDES_OVERLAY_PRESETS, ANDES_OVERLAY_SCRIM, ANDES_OVERLAY_Z_INDEX_FALLBACKS, ANDES_TREE_TYPEAHEAD_DEBOUNCE, AndesBreakpoints, AndesButtonPrimitive, AndesCheckedTree, AndesChoiceGroup, AndesChoiceItem, AndesFormControlBase, AndesListNavigation, AndesListNavigationItem, AndesListNavigationKeys, AndesOverlayClosePrimitive, AndesOverlayContentPrimitive, AndesOverlayInertRegistry, AndesOverlayPrimitive, AndesOverlayTriggerPrimitive, AndesResizeObserver, AndesSelectionModel, AndesTreeNavigation, AndesTreeNavigationItem, AndesTreeNavigationKeys, AndesValueAccessor, andesBreakpointForWidth, andesConnectedPosition, andesConnectedPositions, andesContainerBreakpoint, andesElementSize, andesImpliedSize, andesMediaQuery, andesOverlayPreset, andesOverlayZIndex, andesPrefersContrast, andesPrefersDark, andesPrefersReducedMotion, andesShallowArrayEquals, createAndesPositionStrategy, injectAndesControlStatus, provideAndesOverlay, provideAndesValueAccessor };
3018
+ export type { AndesBreakpoint, AndesCheckState, AndesCheckedStrategy, AndesCheckedTreeConfig, AndesChoiceDataState, AndesChoiceGroupRole, AndesChoiceItemRef, AndesChoiceItemRole, AndesChoiceMode, AndesChoiceRoleMapping, AndesChoiceStateAttribute, AndesChoiceValue, AndesContainerBreakpointOptions, AndesControlStatus, AndesControlStatusOptions, AndesElementBox, AndesElementMeasure, AndesElementSizeOptions, AndesElementSizeRef, AndesElementSizeTarget, AndesListFocusMode, AndesListNavigationConfig, AndesListNavigationItemRef, AndesListOrientation, AndesListTextDirection, AndesMediaQueryOptions, AndesOverlayAlign, AndesOverlayAnchoredPositioning, AndesOverlayAriaAttachment, AndesOverlayAutoFocus, AndesOverlayCenteredPositioning, AndesOverlayCloseReason, AndesOverlayConfig, AndesOverlayContent, AndesOverlayEdge, AndesOverlayEdgePositioning, AndesOverlayInertHandle, AndesOverlayLayer, AndesOverlayOpenOptions, AndesOverlayPositioning, AndesOverlayPreset, AndesOverlayRole, AndesOverlaySide, AndesOverlaySize, AndesResizeObserveOptions, AndesSelectionChange, AndesSelectionChangeSource, AndesSelectionMode, AndesSelectionModelConfig, AndesSelectionState, AndesTreeFocusMode, AndesTreeNavigationConfig, AndesTreeNavigationExpandChange, AndesTreeNavigationExpandedSource, AndesTreeNavigationItemRef, AndesTreeNavigationKey, AndesTreeNavigationNode, AndesTreeNavigationNodeInfo, AndesTreeNavigationNodeSource, AndesTreeNavigationRevealRequest, AndesTreeNavigationScope, AndesTreeOrientation, AndesTreeTextDirection, AndesValueAccessorOptions };