@radix-ng/primitives 1.0.0-beta.5 → 1.0.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.
Files changed (62) hide show
  1. package/composite/README.md +3 -0
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +20 -44
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +134 -58
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-composite.mjs +599 -0
  7. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -0
  8. package/fesm2022/radix-ng-primitives-drawer.mjs +442 -2
  9. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-menu.mjs +315 -68
  11. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-menubar.mjs +91 -36
  13. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +281 -88
  15. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-popover.mjs +40 -15
  17. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-popper.mjs +73 -65
  19. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-radio.mjs +63 -27
  21. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-scroll-area.mjs +56 -25
  23. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-select.mjs +59 -29
  25. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-slider.mjs +57 -13
  27. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-tabs.mjs +335 -73
  29. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-toggle-group.mjs +66 -21
  31. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-toggle.mjs +29 -11
  33. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-toolbar.mjs +68 -36
  35. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  36. package/navigation-menu/README.md +5 -2
  37. package/package.json +6 -10
  38. package/types/radix-ng-primitives-accordion.d.ts +12 -16
  39. package/types/radix-ng-primitives-checkbox.d.ts +98 -70
  40. package/types/radix-ng-primitives-composite.d.ts +195 -0
  41. package/types/radix-ng-primitives-drawer.d.ts +40 -2
  42. package/types/radix-ng-primitives-menu.d.ts +46 -16
  43. package/types/radix-ng-primitives-menubar.d.ts +12 -5
  44. package/types/radix-ng-primitives-navigation-menu.d.ts +65 -33
  45. package/types/radix-ng-primitives-popover.d.ts +9 -5
  46. package/types/radix-ng-primitives-popper.d.ts +1 -0
  47. package/types/radix-ng-primitives-radio.d.ts +11 -9
  48. package/types/radix-ng-primitives-scroll-area.d.ts +4 -1
  49. package/types/radix-ng-primitives-select.d.ts +46 -32
  50. package/types/radix-ng-primitives-slider.d.ts +19 -4
  51. package/types/radix-ng-primitives-tabs.d.ts +69 -14
  52. package/types/radix-ng-primitives-toggle-group.d.ts +27 -16
  53. package/types/radix-ng-primitives-toggle.d.ts +5 -5
  54. package/types/radix-ng-primitives-toolbar.d.ts +84 -69
  55. package/collection/README.md +0 -1
  56. package/fesm2022/radix-ng-primitives-collection.mjs +0 -72
  57. package/fesm2022/radix-ng-primitives-collection.mjs.map +0 -1
  58. package/fesm2022/radix-ng-primitives-roving-focus.mjs +0 -388
  59. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +0 -1
  60. package/roving-focus/README.md +0 -3
  61. package/types/radix-ng-primitives-collection.d.ts +0 -44
  62. package/types/radix-ng-primitives-roving-focus.d.ts +0 -187
@@ -0,0 +1,599 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, signal, output, computed, effect, Directive, input, linkedSignal, afterNextRender, untracked, booleanAttribute, model, NgModule } from '@angular/core';
3
+ import { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, HOME, END, createContext } from '@radix-ng/primitives/core';
4
+ import { injectDirection } from '@radix-ng/primitives/direction-provider';
5
+
6
+ const ACTIVE_COMPOSITE_ITEM = 'data-composite-item-active';
7
+ const ARROW_KEYS = new Set([ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT]);
8
+ const COMPOSITE_KEYS = new Set([...ARROW_KEYS, HOME, END]);
9
+ const MODIFIER_KEYS = ['Shift', 'Control', 'Alt', 'Meta'];
10
+ function sortByDocumentPosition(items) {
11
+ return [...items]
12
+ .filter((item) => item.element.isConnected)
13
+ .sort((a, b) => {
14
+ const position = a.element.compareDocumentPosition(b.element);
15
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
16
+ return -1;
17
+ }
18
+ if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
19
+ return 1;
20
+ }
21
+ return 0;
22
+ });
23
+ }
24
+ function isModifierKeySet(event, allowedModifierKeys) {
25
+ return MODIFIER_KEYS.some((key) => !allowedModifierKeys.includes(key) && event.getModifierState(key));
26
+ }
27
+ function isNativeTextInput(target) {
28
+ if (!(target instanceof HTMLElement)) {
29
+ return false;
30
+ }
31
+ return target.tagName === 'TEXTAREA' || target.tagName === 'INPUT';
32
+ }
33
+ function getCompositeNavigationKeys(orientation, dir) {
34
+ const horizontalForwardKey = dir === 'rtl' ? ARROW_LEFT : ARROW_RIGHT;
35
+ const horizontalBackwardKey = dir === 'rtl' ? ARROW_RIGHT : ARROW_LEFT;
36
+ return {
37
+ forwardKeys: orientation === 'horizontal'
38
+ ? [horizontalForwardKey]
39
+ : orientation === 'vertical'
40
+ ? [ARROW_DOWN]
41
+ : [horizontalForwardKey, ARROW_DOWN],
42
+ backwardKeys: orientation === 'horizontal'
43
+ ? [horizontalBackwardKey]
44
+ : orientation === 'vertical'
45
+ ? [ARROW_UP]
46
+ : [horizontalBackwardKey, ARROW_UP]
47
+ };
48
+ }
49
+ function shouldKeepNativeTextInputBehavior(event, target, orientation, dir) {
50
+ const { forwardKeys, backwardKeys } = getCompositeNavigationKeys(orientation, dir);
51
+ const selectionStart = target.selectionStart;
52
+ const selectionEnd = target.selectionEnd;
53
+ const value = target.value ?? '';
54
+ if (selectionStart == null || selectionEnd == null || event.shiftKey || selectionStart !== selectionEnd) {
55
+ return true;
56
+ }
57
+ if (!backwardKeys.includes(event.key) && selectionStart < value.length) {
58
+ return true;
59
+ }
60
+ if (!forwardKeys.includes(event.key) && selectionStart > 0) {
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ function isIndexOutOfListBounds(list, index) {
66
+ return index < 0 || index >= list.length;
67
+ }
68
+ function getMinListIndex(list, disabledIndices) {
69
+ return findNonDisabledListIndex(list, { startingIndex: -1, disabledIndices });
70
+ }
71
+ function getMaxListIndex(list, disabledIndices) {
72
+ return findNonDisabledListIndex(list, { startingIndex: list.length, decrement: true, disabledIndices });
73
+ }
74
+ function findNonDisabledListIndex(list, options = {}) {
75
+ const { startingIndex = -1, decrement = false, disabledIndices } = options;
76
+ const step = decrement ? -1 : 1;
77
+ for (let index = startingIndex + step; index >= 0 && index < list.length; index += step) {
78
+ if (!isListIndexDisabled(list, index, disabledIndices)) {
79
+ return index;
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+ function isListIndexDisabled(list, index, disabledIndices) {
85
+ if (disabledIndices?.includes(index)) {
86
+ return true;
87
+ }
88
+ const element = list[index];
89
+ if (!element) {
90
+ return false;
91
+ }
92
+ if (!isElementVisible(element)) {
93
+ return true;
94
+ }
95
+ return (disabledIndices === undefined &&
96
+ (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true'));
97
+ }
98
+ function isElementDisabled(element) {
99
+ return element === null || element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true';
100
+ }
101
+ function isElementVisible(element) {
102
+ if (!element || !element.isConnected) {
103
+ return false;
104
+ }
105
+ if (typeof Element !== 'undefined' && !(element instanceof Element)) {
106
+ return true;
107
+ }
108
+ let styles;
109
+ try {
110
+ styles = getComputedStyle(element);
111
+ }
112
+ catch {
113
+ return true;
114
+ }
115
+ return styles.visibility !== 'hidden' && styles.visibility !== 'collapse';
116
+ }
117
+ function scrollIntoViewIfNeeded(scrollContainer, element, direction, orientation) {
118
+ if (!scrollContainer || !element || typeof scrollContainer.scrollTo !== 'function') {
119
+ return;
120
+ }
121
+ const isOverflowingX = scrollContainer.clientWidth < scrollContainer.scrollWidth;
122
+ const isOverflowingY = scrollContainer.clientHeight < scrollContainer.scrollHeight;
123
+ let left = scrollContainer.scrollLeft;
124
+ let top = scrollContainer.scrollTop;
125
+ if (isOverflowingX && orientation !== 'vertical') {
126
+ const elementOffsetLeft = getOffset(scrollContainer, element, 'left');
127
+ const containerStyles = getScrollStyles(scrollContainer);
128
+ const elementStyles = getScrollStyles(element);
129
+ if (elementOffsetLeft - elementStyles.scrollMarginLeft <
130
+ scrollContainer.scrollLeft + containerStyles.scrollPaddingLeft) {
131
+ left = elementOffsetLeft - elementStyles.scrollMarginLeft - containerStyles.scrollPaddingLeft;
132
+ }
133
+ else if (elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight >
134
+ scrollContainer.scrollLeft + scrollContainer.clientWidth - containerStyles.scrollPaddingRight) {
135
+ left =
136
+ elementOffsetLeft +
137
+ element.offsetWidth +
138
+ elementStyles.scrollMarginRight -
139
+ scrollContainer.clientWidth +
140
+ containerStyles.scrollPaddingRight;
141
+ }
142
+ if (direction === 'rtl') {
143
+ left = Math.max(left, 0);
144
+ }
145
+ }
146
+ if (isOverflowingY && orientation !== 'horizontal') {
147
+ const elementOffsetTop = getOffset(scrollContainer, element, 'top');
148
+ const containerStyles = getScrollStyles(scrollContainer);
149
+ const elementStyles = getScrollStyles(element);
150
+ if (elementOffsetTop - elementStyles.scrollMarginTop <
151
+ scrollContainer.scrollTop + containerStyles.scrollPaddingTop) {
152
+ top = elementOffsetTop - elementStyles.scrollMarginTop - containerStyles.scrollPaddingTop;
153
+ }
154
+ else if (elementOffsetTop + element.offsetHeight + elementStyles.scrollMarginBottom >
155
+ scrollContainer.scrollTop + scrollContainer.clientHeight - containerStyles.scrollPaddingBottom) {
156
+ top =
157
+ elementOffsetTop +
158
+ element.offsetHeight +
159
+ elementStyles.scrollMarginBottom -
160
+ scrollContainer.clientHeight +
161
+ containerStyles.scrollPaddingBottom;
162
+ }
163
+ }
164
+ scrollContainer.scrollTo({ left, top, behavior: 'auto' });
165
+ }
166
+ function getOffset(ancestor, element, side) {
167
+ const propName = side === 'left' ? 'offsetLeft' : 'offsetTop';
168
+ let result = 0;
169
+ let current = element;
170
+ while (current?.offsetParent) {
171
+ result += current[propName];
172
+ if (current.offsetParent === ancestor) {
173
+ break;
174
+ }
175
+ current = current.offsetParent;
176
+ }
177
+ return result;
178
+ }
179
+ function getScrollStyles(element) {
180
+ const styles = getComputedStyle(element);
181
+ return {
182
+ scrollMarginTop: parseFloat(styles.scrollMarginTop) || 0,
183
+ scrollMarginRight: parseFloat(styles.scrollMarginRight) || 0,
184
+ scrollMarginBottom: parseFloat(styles.scrollMarginBottom) || 0,
185
+ scrollMarginLeft: parseFloat(styles.scrollMarginLeft) || 0,
186
+ scrollPaddingTop: parseFloat(styles.scrollPaddingTop) || 0,
187
+ scrollPaddingRight: parseFloat(styles.scrollPaddingRight) || 0,
188
+ scrollPaddingBottom: parseFloat(styles.scrollPaddingBottom) || 0,
189
+ scrollPaddingLeft: parseFloat(styles.scrollPaddingLeft) || 0
190
+ };
191
+ }
192
+
193
+ const listContext = () => {
194
+ const list = inject(RdxCompositeList);
195
+ return {
196
+ listElement: list.elementRef.nativeElement,
197
+ items: list.items,
198
+ itemMap: list.itemMap,
199
+ registerItem: (item) => list.registerItem(item),
200
+ indexOf: (element) => list.indexOf(element)
201
+ };
202
+ };
203
+ const [injectRdxCompositeListContext, provideRdxCompositeListContext] = createContext('RdxCompositeListContext', 'utils/composite');
204
+ /**
205
+ * Base UI-style composite list. Owns item registration and DOM-order metadata without applying
206
+ * roving tabindex or keyboard navigation.
207
+ */
208
+ class RdxCompositeList {
209
+ constructor() {
210
+ this.elementRef = inject(ElementRef);
211
+ this.registeredItems = signal([], ...(ngDevMode ? [{ debugName: "registeredItems" }] : /* istanbul ignore next */ []));
212
+ /** Emits when the ordered item map changes. */
213
+ this.onMapChange = output();
214
+ /** Items registered with this list, sorted in DOM order. */
215
+ this.items = computed(() => sortByDocumentPosition(this.registeredItems()), ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
216
+ /** Ordered metadata keyed by item element. */
217
+ this.itemMap = computed(() => {
218
+ const map = new Map();
219
+ this.items().forEach((item, index) => {
220
+ map.set(item.element, { ...(item.metadata() ?? {}), index });
221
+ });
222
+ return map;
223
+ }, ...(ngDevMode ? [{ debugName: "itemMap" }] : /* istanbul ignore next */ []));
224
+ effect(() => {
225
+ this.onMapChange.emit(this.itemMap());
226
+ });
227
+ }
228
+ registerItem(item) {
229
+ this.registeredItems.update((items) => [
230
+ ...items.filter((registered) => registered.element !== item.element),
231
+ item
232
+ ]);
233
+ return () => {
234
+ this.registeredItems.update((items) => items.filter((registered) => registered.element !== item.element));
235
+ };
236
+ }
237
+ indexOf(element) {
238
+ return this.items().findIndex((item) => item.element === element);
239
+ }
240
+ elements() {
241
+ return this.items().map((item) => item.element);
242
+ }
243
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
244
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCompositeList, isStandalone: true, selector: "[rdxCompositeList]", outputs: { onMapChange: "onMapChange" }, providers: [provideRdxCompositeListContext(listContext)], exportAs: ["rdxCompositeList"], ngImport: i0 }); }
245
+ }
246
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeList, decorators: [{
247
+ type: Directive,
248
+ args: [{
249
+ selector: '[rdxCompositeList]',
250
+ exportAs: 'rdxCompositeList',
251
+ providers: [provideRdxCompositeListContext(listContext)]
252
+ }]
253
+ }], ctorParameters: () => [], propDecorators: { onMapChange: [{ type: i0.Output, args: ["onMapChange"] }] } });
254
+
255
+ /**
256
+ * Registers the host with the nearest composite list without changing focus behavior.
257
+ */
258
+ class RdxCompositeListItem {
259
+ constructor() {
260
+ this.listContext = injectRdxCompositeListContext(true);
261
+ this.elementRef = inject(ElementRef);
262
+ this.hasRendered = signal(false, ...(ngDevMode ? [{ debugName: "hasRendered" }] : /* istanbul ignore next */ []));
263
+ /** Arbitrary metadata included in the list's ordered item map. */
264
+ this.metadataInput = input(undefined, { ...(ngDevMode ? { debugName: "metadataInput" } : /* istanbul ignore next */ {}), alias: 'metadata' });
265
+ this._metadata = linkedSignal(() => this.metadataInput(), ...(ngDevMode ? [{ debugName: "_metadata" }] : /* istanbul ignore next */ []));
266
+ this.index = computed(() => this.listContext?.indexOf(this.elementRef.nativeElement) ?? -1, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
267
+ this.inListElement = computed(() => {
268
+ const listContext = this.listContext;
269
+ return !!listContext && listContext.listElement.contains(this.elementRef.nativeElement);
270
+ }, ...(ngDevMode ? [{ debugName: "inListElement" }] : /* istanbul ignore next */ []));
271
+ afterNextRender(() => {
272
+ this.hasRendered.set(true);
273
+ });
274
+ effect((onCleanup) => {
275
+ const listContext = this.listContext;
276
+ if (!listContext || !this.hasRendered() || !this.inListElement()) {
277
+ return;
278
+ }
279
+ const element = this.elementRef.nativeElement;
280
+ const unregister = untracked(() => listContext.registerItem({ element, metadata: this._metadata.asReadonly() }));
281
+ onCleanup(unregister);
282
+ });
283
+ }
284
+ setMetadata(value) {
285
+ this._metadata.set(value);
286
+ }
287
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeListItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
288
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxCompositeListItem, isStandalone: true, selector: "[rdxCompositeListItem]", inputs: { metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["rdxCompositeListItem"], ngImport: i0 }); }
289
+ }
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeListItem, decorators: [{
291
+ type: Directive,
292
+ args: [{
293
+ selector: '[rdxCompositeListItem]',
294
+ exportAs: 'rdxCompositeListItem'
295
+ }]
296
+ }], ctorParameters: () => [], propDecorators: { metadataInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "metadata", required: false }] }] } });
297
+
298
+ const rootContext = () => {
299
+ const root = inject(RdxCompositeRoot);
300
+ return {
301
+ rootElement: root.elementRef.nativeElement,
302
+ highlightedIndex: root.highlightedIndex.asReadonly(),
303
+ highlightItemOnHover: root.highlightItemOnHover,
304
+ orientation: root.orientation,
305
+ dir: root.dir,
306
+ isIndexDisabled: (index) => root.isIndexDisabled(index),
307
+ setHighlightedIndex: (index, shouldScrollIntoView) => root.setHighlightedIndex(index, shouldScrollIntoView),
308
+ relayKeyboardEvent: (event) => root.relayKeyboardEvent(event)
309
+ };
310
+ };
311
+ const [injectRdxCompositeRootContext, provideRdxCompositeRootContext] = createContext('RdxCompositeRootContext', 'utils/composite');
312
+ /**
313
+ * Internal Base UI-style composite root for roving index and arrow-key navigation.
314
+ */
315
+ class RdxCompositeRoot {
316
+ constructor() {
317
+ this.elementRef = inject(ElementRef);
318
+ this.compositeList = inject(RdxCompositeList, { self: true });
319
+ this.hasSetInitialIndex = false;
320
+ /** The composite orientation. */
321
+ this.orientationInput = input('both', { ...(ngDevMode ? { debugName: "orientationInput" } : /* istanbul ignore next */ {}), alias: 'orientation' });
322
+ this._orientation = linkedSignal(() => this.orientationInput(), ...(ngDevMode ? [{ debugName: "_orientation" }] : /* istanbul ignore next */ []));
323
+ this.orientation = this._orientation.asReadonly();
324
+ /** Text direction for horizontal arrow-key navigation. */
325
+ this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
326
+ this.effectiveDir = injectDirection(this.dirInput);
327
+ this._dir = linkedSignal(() => this.effectiveDir(), ...(ngDevMode ? [{ debugName: "_dir" }] : /* istanbul ignore next */ []));
328
+ this.dir = this._dir.asReadonly();
329
+ /** Whether arrow-key navigation wraps at the first/last item. */
330
+ this.loopFocusInput = input(true, { ...(ngDevMode ? { debugName: "loopFocusInput" } : /* istanbul ignore next */ {}), alias: 'loopFocus',
331
+ transform: booleanAttribute });
332
+ this._loopFocus = linkedSignal(() => this.loopFocusInput(), ...(ngDevMode ? [{ debugName: "_loopFocus" }] : /* istanbul ignore next */ []));
333
+ this.loopFocus = this._loopFocus.asReadonly();
334
+ /** Enables Home and End keys. */
335
+ this.enableHomeAndEndKeysInput = input(false, { ...(ngDevMode ? { debugName: "enableHomeAndEndKeysInput" } : /* istanbul ignore next */ {}), alias: 'enableHomeAndEndKeys',
336
+ transform: booleanAttribute });
337
+ this._enableHomeAndEndKeys = linkedSignal(() => this.enableHomeAndEndKeysInput(), ...(ngDevMode ? [{ debugName: "_enableHomeAndEndKeys" }] : /* istanbul ignore next */ []));
338
+ this.enableHomeAndEndKeys = this._enableHomeAndEndKeys.asReadonly();
339
+ /** Indices that are skipped by keyboard navigation. */
340
+ this.disabledIndicesInput = input(undefined, { ...(ngDevMode ? { debugName: "disabledIndicesInput" } : /* istanbul ignore next */ {}), alias: 'disabledIndices' });
341
+ this._disabledIndices = linkedSignal(() => this.disabledIndicesInput(), ...(ngDevMode ? [{ debugName: "_disabledIndices" }] : /* istanbul ignore next */ []));
342
+ this.disabledIndices = this._disabledIndices.asReadonly();
343
+ /** Modifier keys that should not block composite navigation. */
344
+ this.modifierKeysInput = input([], { ...(ngDevMode ? { debugName: "modifierKeysInput" } : /* istanbul ignore next */ {}), alias: 'modifierKeys' });
345
+ this._modifierKeys = linkedSignal(() => this.modifierKeysInput(), ...(ngDevMode ? [{ debugName: "_modifierKeys" }] : /* istanbul ignore next */ []));
346
+ this.modifierKeys = this._modifierKeys.asReadonly();
347
+ /** Whether hovering an item should focus it. */
348
+ this.highlightItemOnHover = input(false, { ...(ngDevMode ? { debugName: "highlightItemOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
349
+ /** Whether handled navigation keys stop propagation. */
350
+ this.stopEventPropagation = input(true, { ...(ngDevMode ? { debugName: "stopEventPropagation" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
351
+ /** The currently highlighted item index. */
352
+ this.highlightedIndex = model(0, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : /* istanbul ignore next */ []));
353
+ /** Emits when this root changes the highlighted index. */
354
+ this.onHighlightedIndexChange = output();
355
+ /** Emits when the ordered item map changes. */
356
+ this.onMapChange = output();
357
+ this.items = this.compositeList.items;
358
+ this.itemMap = this.compositeList.itemMap;
359
+ effect(() => {
360
+ const items = this.items();
361
+ if (items.length === 0) {
362
+ return;
363
+ }
364
+ if (!this.hasSetInitialIndex) {
365
+ this.hasSetInitialIndex = true;
366
+ const activeIndex = items.findIndex((item) => item.element.hasAttribute(ACTIVE_COMPOSITE_ITEM));
367
+ if (activeIndex !== -1) {
368
+ this.setHighlightedIndex(activeIndex, true);
369
+ return;
370
+ }
371
+ }
372
+ if (this.isIndexDisabled(this.highlightedIndex())) {
373
+ const firstEnabledIndex = getMinListIndex(this.elements(), this.disabledIndices());
374
+ if (!isIndexOutOfListBounds(items, firstEnabledIndex)) {
375
+ this.setHighlightedIndex(firstEnabledIndex);
376
+ }
377
+ }
378
+ });
379
+ effect(() => {
380
+ this.onMapChange.emit(this.itemMap());
381
+ });
382
+ }
383
+ indexOf(element) {
384
+ return this.compositeList.indexOf(element);
385
+ }
386
+ setOrientation(value) {
387
+ this._orientation.set(value);
388
+ }
389
+ setLoopFocus(value) {
390
+ this._loopFocus.set(value);
391
+ }
392
+ setDir(value) {
393
+ this._dir.set(value);
394
+ }
395
+ setEnableHomeAndEndKeys(value) {
396
+ this._enableHomeAndEndKeys.set(value);
397
+ }
398
+ setDisabledIndices(value) {
399
+ this._disabledIndices.set(value);
400
+ }
401
+ setModifierKeys(value) {
402
+ this._modifierKeys.set(value);
403
+ }
404
+ isIndexDisabled(index) {
405
+ return isListIndexDisabled(this.elements(), index, this.disabledIndices());
406
+ }
407
+ setHighlightedIndex(index, shouldScrollIntoView = false) {
408
+ if (this.highlightedIndex() !== index) {
409
+ this.highlightedIndex.set(index);
410
+ this.onHighlightedIndexChange.emit(index);
411
+ }
412
+ if (shouldScrollIntoView) {
413
+ scrollIntoViewIfNeeded(this.elementRef.nativeElement, this.elements()[index], this.dir(), this.orientation());
414
+ }
415
+ }
416
+ relayKeyboardEvent(event) {
417
+ this.handleCompositeKeydown(event, true);
418
+ }
419
+ handleKeydown(event) {
420
+ this.handleCompositeKeydown(event, false);
421
+ }
422
+ handleCompositeKeydown(event, relayed) {
423
+ const relevantKeys = this.enableHomeAndEndKeys() ? COMPOSITE_KEYS : ARROW_KEYS;
424
+ if (!relevantKeys.has(event.key) || isModifierKeySet(event, this.modifierKeys())) {
425
+ return;
426
+ }
427
+ const target = event.target;
428
+ const rootElement = this.elementRef.nativeElement;
429
+ if (!relayed && target instanceof HTMLElement) {
430
+ const closestRoot = target.closest('[rdxCompositeRoot]');
431
+ if (closestRoot && closestRoot !== rootElement) {
432
+ return;
433
+ }
434
+ }
435
+ if (isNativeTextInput(target) && !isElementDisabled(target)) {
436
+ try {
437
+ if (shouldKeepNativeTextInputBehavior(event, target, this.orientation(), this.dir())) {
438
+ return;
439
+ }
440
+ }
441
+ catch {
442
+ return;
443
+ }
444
+ }
445
+ const elements = this.elements();
446
+ if (elements.length === 0) {
447
+ return;
448
+ }
449
+ const nextIndex = this.getNextIndex(event, elements);
450
+ if (nextIndex === this.highlightedIndex() || isIndexOutOfListBounds(elements, nextIndex)) {
451
+ return;
452
+ }
453
+ if (this.stopEventPropagation()) {
454
+ event.stopPropagation();
455
+ }
456
+ event.preventDefault();
457
+ this.setHighlightedIndex(nextIndex, true);
458
+ queueMicrotask(() => {
459
+ this.elements()[nextIndex]?.focus();
460
+ });
461
+ }
462
+ getNextIndex(event, elements) {
463
+ const highlightedIndex = this.highlightedIndex();
464
+ const minIndex = getMinListIndex(elements, this.disabledIndices());
465
+ const maxIndex = getMaxListIndex(elements, this.disabledIndices());
466
+ if (this.enableHomeAndEndKeys()) {
467
+ if (event.key === 'Home') {
468
+ return minIndex;
469
+ }
470
+ if (event.key === 'End') {
471
+ return maxIndex;
472
+ }
473
+ }
474
+ const { forwardKeys, backwardKeys } = getCompositeNavigationKeys(this.orientation(), this.dir());
475
+ const isForward = forwardKeys.includes(event.key);
476
+ const isBackward = backwardKeys.includes(event.key);
477
+ if (!isForward && !isBackward) {
478
+ return highlightedIndex;
479
+ }
480
+ const decrement = isBackward;
481
+ const boundaryIndex = decrement ? minIndex : maxIndex;
482
+ const loopIndex = decrement ? maxIndex : minIndex;
483
+ if (this.loopFocus() && highlightedIndex === boundaryIndex) {
484
+ return loopIndex;
485
+ }
486
+ return findNonDisabledListIndex(elements, {
487
+ startingIndex: highlightedIndex,
488
+ decrement,
489
+ disabledIndices: this.disabledIndices()
490
+ });
491
+ }
492
+ elements() {
493
+ return this.compositeList.elements();
494
+ }
495
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
496
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxCompositeRoot, isStandalone: true, selector: "[rdxCompositeRoot]", inputs: { orientationInput: { classPropertyName: "orientationInput", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, loopFocusInput: { classPropertyName: "loopFocusInput", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, enableHomeAndEndKeysInput: { classPropertyName: "enableHomeAndEndKeysInput", publicName: "enableHomeAndEndKeys", isSignal: true, isRequired: false, transformFunction: null }, disabledIndicesInput: { classPropertyName: "disabledIndicesInput", publicName: "disabledIndices", isSignal: true, isRequired: false, transformFunction: null }, modifierKeysInput: { classPropertyName: "modifierKeysInput", publicName: "modifierKeys", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", isSignal: true, isRequired: false, transformFunction: null }, stopEventPropagation: { classPropertyName: "stopEventPropagation", publicName: "stopEventPropagation", isSignal: true, isRequired: false, transformFunction: null }, highlightedIndex: { classPropertyName: "highlightedIndex", publicName: "highlightedIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { highlightedIndex: "highlightedIndexChange", onHighlightedIndexChange: "onHighlightedIndexChange", onMapChange: "onMapChange" }, host: { listeners: { "keydown": "handleKeydown($event)" } }, providers: [provideRdxCompositeRootContext(rootContext)], exportAs: ["rdxCompositeRoot"], hostDirectives: [{ directive: RdxCompositeList }], ngImport: i0 }); }
497
+ }
498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeRoot, decorators: [{
499
+ type: Directive,
500
+ args: [{
501
+ selector: '[rdxCompositeRoot]',
502
+ exportAs: 'rdxCompositeRoot',
503
+ hostDirectives: [RdxCompositeList],
504
+ providers: [provideRdxCompositeRootContext(rootContext)],
505
+ host: {
506
+ '(keydown)': 'handleKeydown($event)'
507
+ }
508
+ }]
509
+ }], ctorParameters: () => [], propDecorators: { orientationInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], loopFocusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], enableHomeAndEndKeysInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableHomeAndEndKeys", required: false }] }], disabledIndicesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledIndices", required: false }] }], modifierKeysInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "modifierKeys", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], stopEventPropagation: [{ type: i0.Input, args: [{ isSignal: true, alias: "stopEventPropagation", required: false }] }], highlightedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightedIndex", required: false }] }, { type: i0.Output, args: ["highlightedIndexChange"] }], onHighlightedIndexChange: [{ type: i0.Output, args: ["onHighlightedIndexChange"] }], onMapChange: [{ type: i0.Output, args: ["onMapChange"] }] } });
510
+
511
+ /**
512
+ * Internal Base UI-style composite item. Registers itself with the nearest composite root and
513
+ * receives the roving `tabindex` from the root's highlighted index.
514
+ */
515
+ class RdxCompositeItem {
516
+ constructor() {
517
+ this.rootContext = injectRdxCompositeRootContext(true);
518
+ this.listItem = inject(RdxCompositeListItem, { self: true });
519
+ this.elementRef = inject(ElementRef);
520
+ this.index = this.listItem.index;
521
+ this.inRootElement = computed(() => {
522
+ const rootContext = this.rootContext;
523
+ return !!rootContext && rootContext.rootElement.contains(this.elementRef.nativeElement);
524
+ }, ...(ngDevMode ? [{ debugName: "inRootElement" }] : /* istanbul ignore next */ []));
525
+ this.highlighted = computed(() => this.rootContext?.highlightedIndex() === this.index(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
526
+ this.tabIndex = computed(() => {
527
+ const rootContext = this.rootContext;
528
+ if (!rootContext || !this.inRootElement()) {
529
+ return null;
530
+ }
531
+ const index = this.index();
532
+ return index !== -1 && rootContext.highlightedIndex() === index && !rootContext.isIndexDisabled(index) ? 0 : -1;
533
+ }, ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
534
+ }
535
+ setMetadata(value) {
536
+ this.listItem.setMetadata(value);
537
+ }
538
+ handleFocus() {
539
+ const index = this.index();
540
+ if (this.inRootElement() && index !== -1) {
541
+ this.rootContext?.setHighlightedIndex(index);
542
+ }
543
+ }
544
+ handleMouseMove() {
545
+ const rootContext = this.rootContext;
546
+ const index = this.index();
547
+ if (!this.inRootElement() ||
548
+ !rootContext ||
549
+ index === -1 ||
550
+ !rootContext.highlightItemOnHover() ||
551
+ this.highlighted()) {
552
+ return;
553
+ }
554
+ if (!rootContext.isIndexDisabled(index)) {
555
+ this.elementRef.nativeElement.focus();
556
+ }
557
+ }
558
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
559
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCompositeItem, isStandalone: true, selector: "[rdxCompositeItem]", host: { listeners: { "focus": "handleFocus()", "mousemove": "handleMouseMove()" }, properties: { "attr.tabindex": "tabIndex()" } }, exportAs: ["rdxCompositeItem"], hostDirectives: [{ directive: RdxCompositeListItem, inputs: ["metadata", "metadata"] }], ngImport: i0 }); }
560
+ }
561
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeItem, decorators: [{
562
+ type: Directive,
563
+ args: [{
564
+ selector: '[rdxCompositeItem]',
565
+ exportAs: 'rdxCompositeItem',
566
+ hostDirectives: [
567
+ {
568
+ directive: RdxCompositeListItem,
569
+ inputs: ['metadata']
570
+ }
571
+ ],
572
+ host: {
573
+ '[attr.tabindex]': 'tabIndex()',
574
+ '(focus)': 'handleFocus()',
575
+ '(mousemove)': 'handleMouseMove()'
576
+ }
577
+ }]
578
+ }] });
579
+
580
+ const compositeImports = [RdxCompositeList, RdxCompositeListItem, RdxCompositeRoot, RdxCompositeItem];
581
+ class RdxCompositeModule {
582
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
583
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeModule, imports: [RdxCompositeList, RdxCompositeListItem, RdxCompositeRoot, RdxCompositeItem], exports: [RdxCompositeList, RdxCompositeListItem, RdxCompositeRoot, RdxCompositeItem] }); }
584
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeModule }); }
585
+ }
586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCompositeModule, decorators: [{
587
+ type: NgModule,
588
+ args: [{
589
+ imports: [...compositeImports],
590
+ exports: [...compositeImports]
591
+ }]
592
+ }] });
593
+
594
+ /**
595
+ * Generated bundle index. Do not edit.
596
+ */
597
+
598
+ export { ACTIVE_COMPOSITE_ITEM, ARROW_KEYS, COMPOSITE_KEYS, MODIFIER_KEYS, RdxCompositeItem, RdxCompositeList, RdxCompositeListItem, RdxCompositeModule, RdxCompositeRoot, compositeImports, findNonDisabledListIndex, getCompositeNavigationKeys, getMaxListIndex, getMinListIndex, injectRdxCompositeListContext, injectRdxCompositeRootContext, isElementDisabled, isElementVisible, isIndexOutOfListBounds, isListIndexDisabled, isModifierKeySet, isNativeTextInput, provideRdxCompositeListContext, provideRdxCompositeRootContext, scrollIntoViewIfNeeded, shouldKeepNativeTextInputBehavior, sortByDocumentPosition };
599
+ //# sourceMappingURL=radix-ng-primitives-composite.mjs.map