@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.
- package/LICENSE +21 -0
- package/README.md +435 -2
- package/fesm2022/andes-ng-primitives.mjs +5662 -0
- package/fesm2022/andes-ng-primitives.mjs.map +1 -0
- package/package.json +28 -3
- package/types/andes-ng-primitives.d.ts +3018 -0
|
@@ -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 };
|