@radix-ng/primitives 1.0.0-beta.1 → 1.0.0-beta.3

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 (107) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +76 -6
  3. package/fesm2022/radix-ng-primitives-accordion.mjs +2 -2
  4. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +30 -24
  6. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs +1786 -0
  8. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
  9. package/fesm2022/radix-ng-primitives-calendar.mjs +14 -1
  10. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  11. package/fesm2022/radix-ng-primitives-checkbox.mjs +2 -2
  12. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  13. package/fesm2022/radix-ng-primitives-collapsible.mjs +1 -1
  14. package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
  15. package/fesm2022/radix-ng-primitives-combobox.mjs +1983 -0
  16. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -0
  17. package/fesm2022/radix-ng-primitives-context-menu.mjs +1 -1
  18. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  19. package/fesm2022/radix-ng-primitives-core.mjs +480 -469
  20. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  21. package/fesm2022/radix-ng-primitives-cropper.mjs +1 -1
  22. package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
  23. package/fesm2022/radix-ng-primitives-date-field.mjs +11 -0
  24. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  25. package/fesm2022/radix-ng-primitives-dialog.mjs +44 -46
  26. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  27. package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
  28. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  29. package/fesm2022/radix-ng-primitives-editable.mjs +1 -1
  30. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  31. package/fesm2022/radix-ng-primitives-field.mjs +86 -6
  32. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  33. package/fesm2022/radix-ng-primitives-fieldset.mjs +1 -1
  34. package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -1
  35. package/fesm2022/radix-ng-primitives-focus-scope.mjs +1 -1
  36. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  37. package/fesm2022/radix-ng-primitives-form.mjs +207 -0
  38. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -0
  39. package/fesm2022/radix-ng-primitives-input.mjs +85 -4
  40. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  41. package/fesm2022/radix-ng-primitives-menu.mjs +44 -24
  42. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  43. package/fesm2022/radix-ng-primitives-menubar.mjs +1 -1
  44. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  45. package/fesm2022/radix-ng-primitives-meter.mjs +1 -1
  46. package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -1
  47. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +39 -55
  48. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  49. package/fesm2022/radix-ng-primitives-number-field.mjs +2 -2
  50. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  51. package/fesm2022/radix-ng-primitives-popover.mjs +36 -51
  52. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  53. package/fesm2022/radix-ng-primitives-popper.mjs +12 -6
  54. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  55. package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
  56. package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
  57. package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
  58. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  59. package/fesm2022/radix-ng-primitives-preview-card.mjs +37 -51
  60. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  61. package/fesm2022/radix-ng-primitives-progress.mjs +1 -1
  62. package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
  63. package/fesm2022/radix-ng-primitives-roving-focus.mjs +1 -1
  64. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  65. package/fesm2022/radix-ng-primitives-scroll-area.mjs +3 -3
  66. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  67. package/fesm2022/radix-ng-primitives-select.mjs +469 -258
  68. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  69. package/fesm2022/radix-ng-primitives-slider.mjs +1 -1
  70. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  71. package/fesm2022/radix-ng-primitives-switch.mjs +3 -2
  72. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  73. package/fesm2022/radix-ng-primitives-tabs.mjs +1 -1
  74. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  75. package/fesm2022/radix-ng-primitives-time-field.mjs +27 -3
  76. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  77. package/fesm2022/radix-ng-primitives-toast.mjs +1 -1
  78. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  79. package/fesm2022/radix-ng-primitives-toggle-group.mjs +1 -1
  80. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  81. package/fesm2022/radix-ng-primitives-toolbar.mjs +2 -2
  82. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  83. package/fesm2022/radix-ng-primitives-tooltip.mjs +39 -42
  84. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  85. package/package.json +13 -1
  86. package/schematics/ng-add/index.js +57 -0
  87. package/schematics/ng-add/index.js.map +1 -1
  88. package/schematics/ng-add/schema.d.ts +1 -0
  89. package/schematics/ng-add/schema.json +6 -0
  90. package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
  91. package/types/radix-ng-primitives-autocomplete.d.ts +596 -0
  92. package/types/radix-ng-primitives-combobox.d.ts +1310 -0
  93. package/types/radix-ng-primitives-core.d.ts +148 -56
  94. package/types/radix-ng-primitives-dialog.d.ts +32 -25
  95. package/types/radix-ng-primitives-drawer.d.ts +49 -22
  96. package/types/radix-ng-primitives-field.d.ts +71 -2
  97. package/types/radix-ng-primitives-form.d.ts +124 -0
  98. package/types/radix-ng-primitives-input.d.ts +75 -5
  99. package/types/radix-ng-primitives-menu.d.ts +19 -10
  100. package/types/radix-ng-primitives-navigation-menu.d.ts +24 -26
  101. package/types/radix-ng-primitives-popover.d.ts +23 -23
  102. package/types/radix-ng-primitives-popper.d.ts +7 -1
  103. package/types/radix-ng-primitives-portal.d.ts +53 -8
  104. package/types/radix-ng-primitives-presence.d.ts +98 -17
  105. package/types/radix-ng-primitives-preview-card.d.ts +24 -23
  106. package/types/radix-ng-primitives-select.d.ts +294 -137
  107. package/types/radix-ng-primitives-tooltip.d.ts +26 -19
@@ -1,19 +1,19 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, model, input, booleanAttribute, computed, signal, Directive, InjectionToken, ElementRef, afterNextRender, effect, ContentChild, linkedSignal, DestroyRef, numberAttribute, forwardRef } from '@angular/core';
3
- import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
4
- import * as i3 from '@radix-ng/primitives/collection';
2
+ import { inject, model, input, booleanAttribute, output, computed, signal, untracked, effect, Directive, InjectionToken, ElementRef, Injector, DestroyRef, afterNextRender, linkedSignal, isDevMode, numberAttribute, forwardRef } from '@angular/core';
3
+ import { isEqual, getActiveElement, createContext, useTransitionStatus, isNullish, injectId, useListHighlight, useScrollLock, handleAndDispatchCustomEvent } from '@radix-ng/primitives/core';
4
+ import * as i1 from '@radix-ng/primitives/popper';
5
+ import { RdxPopper, RdxPopperContent, RdxPopperContentWrapper, RdxPopperAnchor } from '@radix-ng/primitives/popper';
6
+ import * as i4 from '@radix-ng/primitives/collection';
5
7
  import { RdxCollectionProvider, RdxCollectionItem } from '@radix-ng/primitives/collection';
6
- import { isEqual, getActiveElement, createContext, isNullish, injectId, handleAndDispatchCustomEvent } from '@radix-ng/primitives/core';
7
- import * as i2 from '@radix-ng/primitives/dismissable-layer';
8
+ import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
9
+ import * as i3 from '@radix-ng/primitives/dismissable-layer';
8
10
  import { RdxDismissableLayer, provideRdxDismissableLayerConfig } from '@radix-ng/primitives/dismissable-layer';
9
- import * as i1$1 from '@radix-ng/primitives/focus-scope';
11
+ import * as i2 from '@radix-ng/primitives/focus-scope';
10
12
  import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
11
- import * as i1 from '@radix-ng/primitives/popper';
12
- import { RdxPopper, RdxPopperContent, RdxPopperContentWrapper, RdxPopperAnchor } from '@radix-ng/primitives/popper';
13
- import * as i1$2 from '@radix-ng/primitives/portal';
14
- import { RdxPortal } from '@radix-ng/primitives/portal';
15
- import * as i1$3 from '@radix-ng/primitives/presence';
16
- import { provideRdxPresenceContext, RdxPresenceDirective } from '@radix-ng/primitives/presence';
13
+ import * as i1$1 from '@radix-ng/primitives/portal';
14
+ import { RdxPortalPresence } from '@radix-ng/primitives/portal';
15
+ import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
16
+ import { injectFieldRootContext } from '@radix-ng/primitives/field';
17
17
 
18
18
  const OPEN_KEYS = [' ', 'Enter', 'ArrowUp', 'ArrowDown'];
19
19
  const SELECTION_KEYS = [' ', 'Enter'];
@@ -62,9 +62,14 @@ const context$2 = () => {
62
62
  dir: context.dir,
63
63
  value: context.value,
64
64
  multiple: context.multiple,
65
- by: context.by,
65
+ isItemEqualToValue: context.isItemEqualToValue,
66
+ itemToStringLabel: context.itemToStringLabel,
66
67
  open: context.open,
67
68
  disabled: context.disabled,
69
+ modal: context.modal,
70
+ isEmptyModelValue: context.isEmptyModelValue,
71
+ transitionStatus: context.transitionStatus,
72
+ registerTransitionElement: context.registerTransitionElement,
68
73
  optionsSet: context.optionsSet,
69
74
  onOptionAdd: (option) => {
70
75
  const existingOption = context.getOption(option());
@@ -91,23 +96,33 @@ const context$2 = () => {
91
96
  }
92
97
  };
93
98
  };
94
- const [injectSelectRootContext, provideSelectRootContext] = createContext('RdxSelectRootContext');
99
+ const [injectSelectRootContext, provideSelectRootContext] = createContext('RdxSelectRootContext', 'components/select');
95
100
  class RdxSelectRoot {
96
101
  constructor() {
97
102
  this.open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
98
103
  this.value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
99
104
  this.multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
100
105
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
106
+ /** Whether the popup is modal: locks page scroll and makes outside content inert while open. */
107
+ this.modal = input(true, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
101
108
  this.dir = input('ltr', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
102
- /** Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared. */
103
- this.by = input(...(ngDevMode ? [undefined, { debugName: "by" }] : /* istanbul ignore next */ []));
109
+ /** How item values are compared for equality a function `(a, b) => boolean` or an object key. */
110
+ this.isItemEqualToValue = input(...(ngDevMode ? [undefined, { debugName: "isItemEqualToValue" }] : /* istanbul ignore next */ []));
111
+ /** Converts a value to its display label (used by `RdxSelectValue`). */
112
+ this.itemToStringLabel = input(...(ngDevMode ? [undefined, { debugName: "itemToStringLabel" }] : /* istanbul ignore next */ []));
113
+ /** Emits after the open/close transition (including any exit animation) finishes. */
114
+ this.onOpenChangeComplete = output();
115
+ this.transition = useTransitionStatus((open) => this.onOpenChangeComplete.emit(open));
116
+ /** Open/close transition phase, for `data-starting-style` / `data-ending-style`. */
117
+ this.transitionStatus = this.transition.status;
118
+ /** Registers the popup element whose animation determines transition completion. */
119
+ this.registerTransitionElement = this.transition.registerElement;
104
120
  this.isEmptyModelValue = computed(() => {
105
- if (this.multiple() && Array.isArray(this.value())) {
106
- return Array(this.value())?.length === 0;
107
- }
108
- else {
109
- return isNullish(this.value());
121
+ const value = this.value();
122
+ if (this.multiple() && Array.isArray(value)) {
123
+ return value.length === 0;
110
124
  }
125
+ return isNullish(value);
111
126
  }, ...(ngDevMode ? [{ debugName: "isEmptyModelValue" }] : /* istanbul ignore next */ []));
112
127
  this.optionsSet = signal(new Set(), ...(ngDevMode ? [{ debugName: "optionsSet" }] : /* istanbul ignore next */ []));
113
128
  // The native `select` only associates the correct default value if the corresponding
@@ -126,14 +141,24 @@ class RdxSelectRoot {
126
141
  x: 0,
127
142
  y: 0
128
143
  }, ...(ngDevMode ? [{ debugName: "triggerPointerDownPosRef" }] : /* istanbul ignore next */ []));
144
+ let previousOpen = untracked(this.open);
145
+ effect(() => {
146
+ const open = this.open();
147
+ if (open === previousOpen) {
148
+ return;
149
+ }
150
+ previousOpen = open;
151
+ untracked(() => this.transition.start(open));
152
+ });
129
153
  }
130
154
  getOption(value) {
131
- return Array.from(this.optionsSet()).find((option) => valueComparator(value, option.value, this.by()));
155
+ return Array.from(this.optionsSet()).find((option) => valueComparator(value, option.value, this.isItemEqualToValue()));
132
156
  }
133
157
  handleValueChange(value) {
134
158
  if (this.multiple()) {
135
- const array = Array.isArray(this.value()) ? [...Array(this.value())] : [];
136
- const index = array.findIndex((i) => compare(i, value, this.by()));
159
+ const current = this.value();
160
+ const array = Array.isArray(current) ? [...current] : [];
161
+ const index = array.findIndex((i) => compare(i, value, this.isItemEqualToValue()));
137
162
  index === -1 ? array.push(value) : array.splice(index, 1);
138
163
  this.value.set([...array]);
139
164
  }
@@ -142,7 +167,7 @@ class RdxSelectRoot {
142
167
  }
143
168
  }
144
169
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
145
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectRoot, isStandalone: true, selector: "[rdxSelectRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, by: { classPropertyName: "by", publicName: "by", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange" }, providers: [provideSelectRootContext(context$2)], exportAs: ["rdxSelectRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
170
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectRoot, isStandalone: true, selector: "[rdxSelectRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringLabel: { classPropertyName: "itemToStringLabel", publicName: "itemToStringLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [provideSelectRootContext(context$2)], exportAs: ["rdxSelectRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
146
171
  }
147
172
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectRoot, decorators: [{
148
173
  type: Directive,
@@ -152,25 +177,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
152
177
  providers: [provideSelectRootContext(context$2)],
153
178
  hostDirectives: [RdxPopper]
154
179
  }]
155
- }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], by: [{ type: i0.Input, args: [{ isSignal: true, alias: "by", required: false }] }] } });
180
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringLabel", required: false }] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
181
+
182
+ /**
183
+ * An overlay rendered beneath the popup in `modal` mode. Place it inside the portal/presence; style
184
+ * it `position: fixed; inset: 0`. Exposes `data-open` / `data-closed` for animation.
185
+ *
186
+ * @group Components
187
+ */
188
+ class RdxSelectBackdrop {
189
+ constructor() {
190
+ this.rootContext = injectSelectRootContext();
191
+ }
192
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
193
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectBackdrop, isStandalone: true, selector: "[rdxSelectBackdrop]", host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-open": "rootContext.open() ? \"\" : undefined", "attr.data-closed": "rootContext.open() ? undefined : \"\"" } }, exportAs: ["rdxSelectBackdrop"], ngImport: i0 }); }
194
+ }
195
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectBackdrop, decorators: [{
196
+ type: Directive,
197
+ args: [{
198
+ selector: '[rdxSelectBackdrop]',
199
+ exportAs: 'rdxSelectBackdrop',
200
+ host: {
201
+ 'aria-hidden': 'true',
202
+ '[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
203
+ '[attr.data-open]': 'rootContext.open() ? "" : undefined',
204
+ '[attr.data-closed]': 'rootContext.open() ? undefined : ""'
205
+ }
206
+ }]
207
+ }] });
208
+
209
+ class RdxSelectGroup {
210
+ constructor() {
211
+ this.id = injectId('rdx-select-group-');
212
+ }
213
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
214
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectGroup, isStandalone: true, selector: "[rdxSelectGroup]", host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "id" } }, exportAs: ["rdxSelectGroup"], ngImport: i0 }); }
215
+ }
216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroup, decorators: [{
217
+ type: Directive,
218
+ args: [{
219
+ selector: '[rdxSelectGroup]',
220
+ exportAs: 'rdxSelectGroup',
221
+ host: {
222
+ role: 'group',
223
+ '[attr.aria-labelledby]': 'id'
224
+ }
225
+ }]
226
+ }] });
227
+
228
+ class RdxSelectGroupLabel {
229
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroupLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
230
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectGroupLabel, isStandalone: true, selector: "[rdxSelectGroupLabel]", ngImport: i0 }); }
231
+ }
232
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroupLabel, decorators: [{
233
+ type: Directive,
234
+ args: [{
235
+ selector: '[rdxSelectGroupLabel]',
236
+ host: {}
237
+ }]
238
+ }] });
239
+
240
+ /**
241
+ * Decorative icon inside the trigger (e.g. a chevron). Hidden from assistive technology.
242
+ *
243
+ * @group Components
244
+ */
245
+ class RdxSelectIcon {
246
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
247
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectIcon, isStandalone: true, selector: "[rdxSelectIcon]", host: { attributes: { "aria-hidden": "true" } }, exportAs: ["rdxSelectIcon"], ngImport: i0 }); }
248
+ }
249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectIcon, decorators: [{
250
+ type: Directive,
251
+ args: [{
252
+ selector: '[rdxSelectIcon]',
253
+ exportAs: 'rdxSelectIcon',
254
+ host: {
255
+ 'aria-hidden': 'true'
256
+ }
257
+ }]
258
+ }] });
156
259
 
157
260
  const context$1 = () => {
158
- const context = inject(RdxSelectContent);
261
+ const context = inject(RdxSelectPopup);
159
262
  return {
160
263
  content: context.content,
161
264
  viewport: context.viewport,
162
265
  isPositioned: context.isPositioned,
163
266
  selectedItem: context.selectedItem,
164
267
  selectedItemText: context.selectedItemText,
268
+ highlightedItem: context.highlight.highlightedItem,
269
+ isHighlighted: (item) => context.highlight.highlightedItem() === item,
270
+ highlightItem: (item) => context.highlight.set(item),
271
+ isKeyboardActive: () => context.isKeyboardActive(),
272
+ setKeyboardActive: (value) => context.setKeyboardActive(value),
165
273
  onViewportChange: (node) => {
166
274
  context.viewport.set(node);
167
275
  },
168
276
  onItemLeave: () => {
169
- context.content()?.focus();
277
+ context.highlight.clear();
170
278
  },
171
279
  itemRefCallback: (node, value, disabled) => {
172
280
  const isFirstValidItem = !context.firstValidItemFoundRef() && !disabled;
173
- const isSelectedItem = valueComparator(context.rootContext.value(), value, context.rootContext.by());
281
+ const isSelectedItem = valueComparator(context.rootContext.value(), value, context.rootContext.isItemEqualToValue());
174
282
  if (isSelectedItem || isFirstValidItem) {
175
283
  context.selectedItem.set(node);
176
284
  }
@@ -180,34 +288,61 @@ const context$1 = () => {
180
288
  },
181
289
  itemTextRefCallback: (node, value, disabled) => {
182
290
  const isFirstValidItem = !context.firstValidItemFoundRef() && !disabled;
183
- const isSelectedItem = valueComparator(context.rootContext.value(), value, context.rootContext.by());
291
+ const isSelectedItem = valueComparator(context.rootContext.value(), value, context.rootContext.isItemEqualToValue());
184
292
  if (isSelectedItem || isFirstValidItem) {
185
293
  context.selectedItemText.set(node);
186
294
  }
187
295
  }
188
296
  };
189
297
  };
190
- const [injectSelectContentContext, provideSelectContentContext] = createContext('RdxSelectContent');
298
+ const [injectSelectPopupContext, provideSelectPopupContext] = createContext('RdxSelectPopup', 'components/select');
191
299
  const RDX_SELECT_POSITIONER_TOKEN = new InjectionToken('RDX_SELECT_POSITIONER_TOKEN');
192
- class RdxSelectContent {
193
- set positioner(port) {
194
- if (port) {
195
- port.placed.subscribe(() => {
196
- this.focusSelectedItem();
197
- this.isPositioned.set(true);
198
- });
199
- }
200
- }
300
+ /**
301
+ * The popup listbox. Holds DOM focus while open and navigates with the highlight model
302
+ * (`aria-activedescendant`) — items are not individually focusable.
303
+ *
304
+ * Since ADR 0010 §6 the popup is the **inner** element (the positioner is its ancestor): it carries
305
+ * `role="listbox"`, the `contentId`, and — via the composed {@link RdxPopperContent} — the
306
+ * `data-side` / `data-align` attributes and the until-positioned animation guard previously held by
307
+ * the deleted `rdxSelectPositionerContent`. `RdxPopperContent` also makes the popup the element the
308
+ * `RdxPopperContentWrapper` ancestor reads its content z-index from. In item-aligned mode there is no
309
+ * wrapper, so `RdxPopperContent` no-ops.
310
+ *
311
+ * @group Components
312
+ */
313
+ class RdxSelectPopup {
201
314
  constructor() {
202
315
  this.dismissableLayer = inject(RdxDismissableLayer);
203
316
  this.currentElement = inject(ElementRef);
204
317
  this.collection = inject(RdxCollectionProvider);
318
+ this.injector = inject(Injector);
205
319
  this.rootContext = injectSelectRootContext();
320
+ /**
321
+ * The collected items (DOM order). Exposed so the `item-aligned` positioner — now the popup's
322
+ * **ancestor** — can read them without injecting {@link RdxCollectionProvider} (which the popup
323
+ * provides as a descendant, so an upward `inject` would not find it).
324
+ */
325
+ this.items = this.collection.items;
326
+ /**
327
+ * Highlight-model navigation over the collected items (DOM order). `loop` is disabled so arrow
328
+ * navigation stops at the first / last item instead of wrapping around — matching native
329
+ * `<select>` behavior.
330
+ */
331
+ this.highlight = useListHighlight({
332
+ items: this.collection.items,
333
+ isNavigable: (item) => !item.disabled(),
334
+ getId: (item) => item.element.id,
335
+ loop: signal(false),
336
+ injector: this.injector
337
+ });
206
338
  this.selectedItem = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedItem" }] : /* istanbul ignore next */ []));
207
339
  this.selectedItemText = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedItemText" }] : /* istanbul ignore next */ []));
208
340
  this.firstValidItemFoundRef = signal(false, ...(ngDevMode ? [{ debugName: "firstValidItemFoundRef" }] : /* istanbul ignore next */ []));
209
341
  this.viewport = signal(undefined, ...(ngDevMode ? [{ debugName: "viewport" }] : /* istanbul ignore next */ []));
210
342
  this.isPositioned = signal(false, ...(ngDevMode ? [{ debugName: "isPositioned" }] : /* istanbul ignore next */ []));
343
+ // Tracks whether the last interaction was the keyboard, so the highlight doesn't jump to an item
344
+ // the cursor happens to rest on when arrow-key navigation scrolls the list.
345
+ this.keyboardActive = false;
211
346
  /**
212
347
  * Event handler called when the escape key is down.
213
348
  * Can be prevented.
@@ -219,6 +354,40 @@ class RdxSelectContent {
219
354
  */
220
355
  this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
221
356
  this.content = signal(null, ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
357
+ /**
358
+ * The positioner — now an **ancestor** element — provides {@link RDX_SELECT_POSITIONER_TOKEN}
359
+ * (Popper or item-aligned). We react to its `placed` to highlight and scroll the selected item
360
+ * into view and flag the popup as positioned.
361
+ */
362
+ this.positioner = inject(RDX_SELECT_POSITIONER_TOKEN, { optional: true });
363
+ this.positioner?.placed.subscribe(() => {
364
+ this.highlightSelectedItem();
365
+ this.scrollSelectedIntoView();
366
+ this.isPositioned.set(true);
367
+ // In Popper mode the popup lives inside the positioner, which stays `visibility: hidden`
368
+ // until it is placed — so the mount-time `mountAutoFocus` call no-ops on the hidden
369
+ // listbox and keyboard navigation never starts. Focus it now that it is visible (skip if
370
+ // focus already moved inside, e.g. item-aligned mode or a re-placement).
371
+ const popup = this.content();
372
+ if (popup && !popup.contains(document.activeElement)) {
373
+ popup.focus({ preventScroll: true });
374
+ }
375
+ });
376
+ // Keep the highlighted item in view during keyboard navigation. The highlight model is pure
377
+ // state (it never moves DOM focus or scrolls), so without this the highlight can move past the
378
+ // visible viewport — behind the scroll buttons. Only keyboard moves scroll; hover highlights
379
+ // must not (the cursor is already over a visible item).
380
+ effect(() => {
381
+ const item = this.highlight.highlightedItem();
382
+ if (item && this.keyboardActive) {
383
+ item.element.scrollIntoView?.({ block: 'nearest' });
384
+ }
385
+ });
386
+ // Lock page scroll while a modal popup is open (content mounts only while open).
387
+ useScrollLock(this.rootContext.modal);
388
+ // The popup's animation determines when the open/close transition (onOpenChangeComplete) is done.
389
+ const unregisterTransition = this.rootContext.registerTransitionElement(this.currentElement.nativeElement);
390
+ inject(DestroyRef).onDestroy(unregisterTransition);
222
391
  this.dismissableLayer.focusOutside.subscribe((e) => e.preventDefault());
223
392
  this.dismissableLayer.dismiss.subscribe(() => this.rootContext.onOpenChange(false));
224
393
  const focusScope = inject(RdxFocusScope);
@@ -229,10 +398,14 @@ class RdxSelectContent {
229
398
  this.rootContext.triggerElement()?.focus({ preventScroll: true });
230
399
  event.preventDefault();
231
400
  });
401
+ // Focus the popup itself (not an item) — the listbox is the focus owner; items are
402
+ // navigated virtually via aria-activedescendant.
232
403
  focusScope.mountAutoFocus.subscribe((event) => {
233
404
  event.preventDefault();
405
+ this.content()?.focus({ preventScroll: true });
234
406
  });
235
- this.content.set(this.currentElement.nativeElement.firstElementChild);
407
+ // The popup is now the listbox host itself (no longer the positioner's first child).
408
+ this.content.set(this.currentElement.nativeElement);
236
409
  });
237
410
  effect((onCleanup) => {
238
411
  if (!this.content())
@@ -275,56 +448,98 @@ class RdxSelectContent {
275
448
  });
276
449
  });
277
450
  }
278
- focusSelectedItem() {
279
- if (this.selectedItem() && this.content()) {
280
- focusFirst([this.selectedItem(), this.content()]);
451
+ /** Highlights the selected item (or the first enabled one) when the popup opens. */
452
+ highlightSelectedItem() {
453
+ const items = this.collection.items();
454
+ const selected = items.find((item) => valueComparator(this.rootContext.value(), item.value(), this.rootContext.isItemEqualToValue()));
455
+ if (selected) {
456
+ this.highlight.set(selected);
281
457
  }
458
+ else {
459
+ this.highlight.first();
460
+ }
461
+ }
462
+ scrollSelectedIntoView() {
463
+ this.selectedItem()?.scrollIntoView?.({ block: 'nearest' });
464
+ }
465
+ setKeyboardActive(value) {
466
+ this.keyboardActive = value;
467
+ }
468
+ isKeyboardActive() {
469
+ return this.keyboardActive;
282
470
  }
283
471
  handleKeyDown(event) {
284
472
  const keyEvent = event;
473
+ if (keyEvent.isComposing)
474
+ return;
285
475
  // select should not be navigated using tab key so we prevent it
286
- if (keyEvent.key === 'Tab')
476
+ if (keyEvent.key === 'Tab') {
287
477
  event.preventDefault();
288
- if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(keyEvent.key)) {
289
- const collectionItems = this.collection.items().map((i) => i.element);
290
- let candidateNodes = [...collectionItems];
291
- if (['ArrowUp', 'End'].includes(keyEvent.key))
292
- candidateNodes = candidateNodes.slice().reverse();
293
- if (['ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
294
- const currentElement = event.target;
295
- const currentIndex = candidateNodes.indexOf(currentElement);
296
- candidateNodes = candidateNodes.slice(currentIndex + 1);
478
+ return;
479
+ }
480
+ if (SELECTION_KEYS.includes(keyEvent.key)) {
481
+ event.preventDefault();
482
+ const item = this.highlight.highlightedItem();
483
+ if (item && !item.disabled()) {
484
+ this.rootContext.onValueChange(item.value());
485
+ if (!this.rootContext.multiple())
486
+ this.rootContext.onOpenChange(false);
297
487
  }
298
- setTimeout(() => focusFirst(candidateNodes));
488
+ return;
489
+ }
490
+ if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(keyEvent.key)) {
299
491
  event.preventDefault();
492
+ this.keyboardActive = true;
493
+ switch (keyEvent.key) {
494
+ case 'ArrowDown':
495
+ this.highlight.next();
496
+ break;
497
+ case 'ArrowUp':
498
+ this.highlight.previous();
499
+ break;
500
+ case 'Home':
501
+ this.highlight.first();
502
+ break;
503
+ case 'End':
504
+ this.highlight.last();
505
+ break;
506
+ }
300
507
  }
301
508
  }
302
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
303
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectContent, isStandalone: true, selector: "[rdxSelectContent]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside" }, host: { attributes: { "role": "listbox" }, listeners: { "keydown": "handleKeyDown($event)" }, properties: { "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "dir": "rootContext.dir()", "style": "{\n display: 'flex',\n flexDirection: 'column',\n outline: 'none'\n }" } }, providers: [
304
- provideSelectContentContext(context$1),
509
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
510
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPopup, isStandalone: true, selector: "[rdxSelectPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside" }, host: { attributes: { "role": "listbox", "tabindex": "-1" }, listeners: { "keydown": "handleKeyDown($event)" }, properties: { "id": "rootContext.contentId", "attr.aria-activedescendant": "highlight.activeId()", "attr.aria-multiselectable": "rootContext.multiple() ? \"true\" : undefined", "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-open": "rootContext.open() ? \"\" : undefined", "attr.data-closed": "rootContext.open() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "dir": "rootContext.dir()", "style": "{\n display: 'flex',\n flexDirection: 'column',\n outline: 'none'\n }" } }, providers: [
511
+ provideSelectPopupContext(context$1),
305
512
  provideRdxDismissableLayerConfig(() => {
306
513
  return {
307
- disableOutsidePointerEvents: signal(true)
514
+ disableOutsidePointerEvents: injectSelectRootContext().modal
308
515
  };
309
516
  })
310
- ], queries: [{ propertyName: "positioner", first: true, predicate: RDX_SELECT_POSITIONER_TOKEN, descendants: true }], hostDirectives: [{ directive: i1$1.RdxFocusScope }, { directive: i2.RdxDismissableLayer }, { directive: i3.RdxCollectionProvider }], ngImport: i0 }); }
517
+ ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFocusScope }, { directive: i3.RdxDismissableLayer }, { directive: i4.RdxCollectionProvider }], ngImport: i0 }); }
311
518
  }
312
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectContent, decorators: [{
519
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopup, decorators: [{
313
520
  type: Directive,
314
521
  args: [{
315
- selector: '[rdxSelectContent]',
316
- hostDirectives: [RdxFocusScope, RdxDismissableLayer, RdxCollectionProvider],
522
+ selector: '[rdxSelectPopup]',
523
+ hostDirectives: [RdxPopperContent, RdxFocusScope, RdxDismissableLayer, RdxCollectionProvider],
317
524
  providers: [
318
- provideSelectContentContext(context$1),
525
+ provideSelectPopupContext(context$1),
319
526
  provideRdxDismissableLayerConfig(() => {
320
527
  return {
321
- disableOutsidePointerEvents: signal(true)
528
+ disableOutsidePointerEvents: injectSelectRootContext().modal
322
529
  };
323
530
  })
324
531
  ],
325
532
  host: {
326
533
  role: 'listbox',
534
+ tabindex: '-1',
535
+ '[id]': 'rootContext.contentId',
536
+ '[attr.aria-activedescendant]': 'highlight.activeId()',
537
+ '[attr.aria-multiselectable]': 'rootContext.multiple() ? "true" : undefined',
327
538
  '[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
539
+ '[attr.data-open]': 'rootContext.open() ? "" : undefined',
540
+ '[attr.data-closed]': 'rootContext.open() ? undefined : ""',
541
+ '[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
542
+ '[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
328
543
  '[dir]': 'rootContext.dir()',
329
544
  '(keydown)': 'handleKeyDown($event)',
330
545
  '[style]': `{
@@ -334,29 +549,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
334
549
  }`
335
550
  }
336
551
  }]
337
- }], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], positioner: [{
338
- type: ContentChild,
339
- args: [RDX_SELECT_POSITIONER_TOKEN, { descendants: true }]
340
- }] } });
341
-
342
- class RdxSelectGroup {
343
- constructor() {
344
- this.id = injectId('rdx-select-group-');
345
- }
346
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
347
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectGroup, isStandalone: true, selector: "[rdxSelectGroup]", host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "id" } }, exportAs: ["rdxSelectGroup"], ngImport: i0 }); }
348
- }
349
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectGroup, decorators: [{
350
- type: Directive,
351
- args: [{
352
- selector: '[rdxSelectGroup]',
353
- exportAs: 'rdxSelectGroup',
354
- host: {
355
- role: 'group',
356
- '[attr.aria-labelledby]': 'id'
357
- }
358
- }]
359
- }] });
552
+ }], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }] } });
360
553
 
361
554
  const context = () => {
362
555
  const context = inject(RdxSelectItem);
@@ -370,30 +563,33 @@ const context = () => {
370
563
  }
371
564
  };
372
565
  };
373
- const [injectSelectItemContext, provideSelectItemContext] = createContext('RdxSelectItemContext');
566
+ const [injectSelectItemContext, provideSelectItemContext] = createContext('RdxSelectItemContext', 'components/select');
374
567
  class RdxSelectItem {
375
568
  constructor() {
376
569
  this.rootContext = injectSelectRootContext();
377
- this.contentContext = injectSelectContentContext();
570
+ this.contentContext = injectSelectPopupContext();
571
+ this.collectionItem = inject(RdxCollectionItem);
378
572
  this.currentElement = inject(ElementRef);
379
573
  this.value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
380
574
  this.textValue = input('', ...(ngDevMode ? [{ debugName: "textValue" }] : /* istanbul ignore next */ []));
381
575
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
382
576
  this.textValue$ = linkedSignal(this.textValue, ...(ngDevMode ? [{ debugName: "textValue$" }] : /* istanbul ignore next */ []));
383
- this.isSelected = computed(() => valueComparator(this.rootContext.value(), this.value(), this.rootContext.by()), ...(ngDevMode ? [{ debugName: "isSelected" }] : /* istanbul ignore next */ []));
384
- this.isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
577
+ this.isSelected = computed(() => valueComparator(this.rootContext.value(), this.value(), this.rootContext.isItemEqualToValue()), ...(ngDevMode ? [{ debugName: "isSelected" }] : /* istanbul ignore next */ []));
578
+ /** Highlighted via the highlight model (keyboard / hover), not DOM focus. */
579
+ this.isHighlighted = computed(() => this.contentContext.isHighlighted(this.collectionItem), ...(ngDevMode ? [{ debugName: "isHighlighted" }] : /* istanbul ignore next */ []));
580
+ /** Item id, referenced by the popup's `aria-activedescendant`. */
581
+ this.id = injectId('rdx-select-item-');
385
582
  this.textId = injectId('rdx-select-item-text-');
386
583
  this.afterNextRender = afterNextRender(() => {
387
584
  this.contentContext.itemRefCallback(this.currentElement.nativeElement, this.value(), this.disabled());
388
585
  });
389
586
  this.SELECT_SELECT = 'select.select';
390
587
  }
391
- onPointerDown(event) {
392
- event.currentTarget.focus({ preventScroll: true });
393
- }
394
588
  onPointerUp(event) {
395
589
  if (event.defaultPrevented)
396
590
  return;
591
+ if (this.disabled())
592
+ return;
397
593
  const eventDetail = { originalEvent: event, value: this.value() };
398
594
  handleAndDispatchCustomEvent(this.SELECT_SELECT, async (event) => {
399
595
  if (event.defaultPrevented)
@@ -407,19 +603,18 @@ class RdxSelectItem {
407
603
  onPointerLeave(event) {
408
604
  if (event.defaultPrevented)
409
605
  return;
410
- if (event.currentTarget === getActiveElement())
411
- this.contentContext.onItemLeave?.();
606
+ this.contentContext.onItemLeave?.();
412
607
  }
413
608
  onPointerMove(event) {
414
609
  if (event.defaultPrevented)
415
610
  return;
416
- if (this.disabled()) {
417
- this.contentContext.onItemLeave?.();
611
+ // Ignore pointer events synthesized by keyboard-driven scrolling (don't steal the highlight).
612
+ if (this.contentContext.isKeyboardActive()) {
613
+ this.contentContext.setKeyboardActive(false);
614
+ return;
418
615
  }
419
- else {
420
- // even though safari doesn't support this option, it's acceptable
421
- // as it only means it might scroll a few pixels when using the pointer.
422
- event.currentTarget?.focus({ preventScroll: true });
616
+ if (!this.disabled()) {
617
+ this.contentContext.highlightItem(this.collectionItem);
423
618
  }
424
619
  }
425
620
  handleKeyDown(event) {
@@ -427,52 +622,35 @@ class RdxSelectItem {
427
622
  if (event.defaultPrevented)
428
623
  return;
429
624
  if (SELECTION_KEYS.includes(keyEvent.key))
430
- this.handleSelectCustomEvent(keyEvent);
431
- // prevent page scroll if using the space key to select an item
432
- if (keyEvent.key === ' ')
433
- event.preventDefault();
434
- }
435
- async handleSelectCustomEvent(event) {
436
- if (event.defaultPrevented)
437
- return;
438
- const eventDetail = { originalEvent: event, value: this.value() };
439
- handleAndDispatchCustomEvent(this.SELECT_SELECT, async (event) => {
440
- if (event.defaultPrevented)
441
- return;
442
- if (!this.disabled()) {
443
- this.rootContext.onValueChange(this.value());
444
- if (!this.rootContext.multiple())
445
- this.rootContext.onOpenChange(false);
446
- }
447
- }, eventDetail);
625
+ this.onPointerUp(keyEvent);
448
626
  }
449
627
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
450
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectItem, isStandalone: true, selector: "[rdxSelectItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, textValue: { classPropertyName: "textValue", publicName: "textValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "focus": "isFocused.set(true)", "blur": "isFocused.set(false)", "pointerdown": "onPointerDown($event)", "pointerup": "onPointerUp($event)", "pointerleave": "onPointerLeave($event)", "pointermove": "onPointerMove($event)", "keydown": "handleKeyDown($event)" }, properties: { "attr.tabindex": "disabled() ? undefined : -1", "attr.aria-selected": "isSelected() ? \"checked\" : \"unchecked\"", "attr.data-state": "isSelected() ? \"checked\" : \"unchecked\"", "attr.data-highlighted": "isFocused() ? \"\" : undefined" } }, providers: [provideSelectItemContext(context)], hostDirectives: [{ directive: i3.RdxCollectionItem, inputs: ["value", "value"] }], ngImport: i0 }); }
628
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectItem, isStandalone: true, selector: "[rdxSelectItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, textValue: { classPropertyName: "textValue", publicName: "textValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "pointerup": "onPointerUp($event)", "pointerleave": "onPointerLeave($event)", "pointermove": "onPointerMove($event)" }, properties: { "attr.id": "id", "attr.aria-selected": "isSelected()", "attr.aria-disabled": "disabled() ? \"true\" : undefined", "attr.data-state": "isSelected() ? \"checked\" : \"unchecked\"", "attr.data-selected": "isSelected() ? \"\" : undefined", "attr.data-highlighted": "isHighlighted() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined" } }, providers: [provideSelectItemContext(context)], exportAs: ["rdxSelectItem"], hostDirectives: [{ directive: i4.RdxCollectionItem, inputs: ["value", "value", "disabled", "disabled"] }], ngImport: i0 }); }
451
629
  }
452
630
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectItem, decorators: [{
453
631
  type: Directive,
454
632
  args: [{
455
633
  selector: '[rdxSelectItem]',
634
+ exportAs: 'rdxSelectItem',
456
635
  providers: [provideSelectItemContext(context)],
457
636
  hostDirectives: [
458
637
  {
459
638
  directive: RdxCollectionItem,
460
- inputs: ['value']
639
+ inputs: ['value', 'disabled']
461
640
  }
462
641
  ],
463
642
  host: {
464
643
  role: 'option',
465
- '[attr.tabindex]': 'disabled() ? undefined : -1',
466
- '[attr.aria-selected]': 'isSelected() ? "checked" : "unchecked"',
644
+ '[attr.id]': 'id',
645
+ '[attr.aria-selected]': 'isSelected()',
646
+ '[attr.aria-disabled]': 'disabled() ? "true" : undefined',
467
647
  '[attr.data-state]': 'isSelected() ? "checked" : "unchecked"',
468
- '[attr.data-highlighted]': 'isFocused() ? "" : undefined',
469
- '(focus)': 'isFocused.set(true)',
470
- '(blur)': 'isFocused.set(false)',
471
- '(pointerdown)': 'onPointerDown($event)',
648
+ '[attr.data-selected]': 'isSelected() ? "" : undefined',
649
+ '[attr.data-highlighted]': 'isHighlighted() ? "" : undefined',
650
+ '[attr.data-disabled]': 'disabled() ? "" : undefined',
472
651
  '(pointerup)': 'onPointerUp($event)',
473
652
  '(pointerleave)': 'onPointerLeave($event)',
474
- '(pointermove)': 'onPointerMove($event)',
475
- '(keydown)': 'handleKeyDown($event)'
653
+ '(pointermove)': 'onPointerMove($event)'
476
654
  }
477
655
  }]
478
656
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], textValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "textValue", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
@@ -499,7 +677,7 @@ class RdxSelectItemText {
499
677
  constructor() {
500
678
  this.elementRef = inject((ElementRef));
501
679
  this.rootContext = injectSelectRootContext();
502
- this.contentContext = injectSelectContentContext();
680
+ this.contentContext = injectSelectPopupContext();
503
681
  this.itemContext = injectSelectItemContext();
504
682
  this.optionProps = computed(() => {
505
683
  return {
@@ -531,38 +709,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
531
709
  }]
532
710
  }], ctorParameters: () => [] });
533
711
 
534
- class RdxSelectLabel {
535
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
536
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectLabel, isStandalone: true, selector: "[rdxSelectLabel]", ngImport: i0 }); }
712
+ class RdxSelectList {
713
+ constructor() {
714
+ this.contentContext = injectSelectPopupContext();
715
+ this.elementRef = inject((ElementRef));
716
+ this.prevScrollTopRef = signal(0, ...(ngDevMode ? [{ debugName: "prevScrollTopRef" }] : /* istanbul ignore next */ []));
717
+ afterNextRender(() => {
718
+ this.contentContext?.onViewportChange(this.elementRef.nativeElement);
719
+ });
720
+ }
721
+ handleScroll(event) {
722
+ const viewport = event.currentTarget;
723
+ this.prevScrollTopRef.set(viewport.scrollTop);
724
+ }
725
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
726
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectList, isStandalone: true, selector: "[rdxSelectList]", host: { attributes: { "role": "presentation" }, listeners: { "scroll": "handleScroll($event)" }, properties: { "attr.data-rdx-select-list": "\"\"", "style": "{\n position: 'relative',\n flex: 1,\n overflow: 'hidden auto',\n scrollbarWidth: 'none'\n }" } }, ngImport: i0 }); }
537
727
  }
538
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectLabel, decorators: [{
728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectList, decorators: [{
539
729
  type: Directive,
540
730
  args: [{
541
- selector: '[rdxSelectLabel]',
542
- host: {}
731
+ selector: '[rdxSelectList]',
732
+ host: {
733
+ role: 'presentation',
734
+ '[attr.data-rdx-select-list]': '""',
735
+ '[style]': `{
736
+ position: 'relative',
737
+ flex: 1,
738
+ overflow: 'hidden auto',
739
+ scrollbarWidth: 'none'
740
+ }`,
741
+ '(scroll)': 'handleScroll($event)'
742
+ }
543
743
  }]
544
- }] });
744
+ }], ctorParameters: () => [] });
545
745
 
546
- class RdxSelectPopperPositionContent {
746
+ /**
747
+ * Structural directive that teleports the select popup into a container (default `document.body`)
748
+ * while the select is open, and keeps it mounted until any CSS exit `@keyframes` finishes.
749
+ *
750
+ * Apply it with the `*` microsyntax on the popup — `<div *rdxSelectPortal rdxSelectPopup>` — or as an
751
+ * explicit `<ng-template rdxSelectPortal>`. For a custom container use the explicit form with
752
+ * `[container]`. Unlike the previous attribute portal it no longer parks an empty wrapper `<div>` in
753
+ * `document.body` while the select is closed.
754
+ */
755
+ class RdxSelectPortal {
756
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
757
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPortal, isStandalone: true, selector: "ng-template[rdxSelectPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectSelectRootContext().open }))], exportAs: ["rdxSelectPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
758
+ }
759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortal, decorators: [{
760
+ type: Directive,
761
+ args: [{
762
+ selector: 'ng-template[rdxSelectPortal]',
763
+ exportAs: 'rdxSelectPortal',
764
+ hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
765
+ providers: [provideRdxPresenceContext(() => ({ present: injectSelectRootContext().open }))]
766
+ }]
767
+ }] });
768
+ /**
769
+ * Dev-mode guard: `rdxSelectPortal` used to be an attribute directive on a `<div>`. It is now
770
+ * structural, so the old `<div rdxSelectPortal>` markup would silently stop portaling — fail loudly
771
+ * instead.
772
+ */
773
+ class RdxSelectPortalMisuseGuard {
547
774
  constructor() {
548
- this.rootContext = injectSelectRootContext();
775
+ if (isDevMode()) {
776
+ throw new Error('[rdxSelectPortal] is now a structural directive. ' +
777
+ 'Use `*rdxSelectPortal` on the popup element or `<ng-template rdxSelectPortal>`. ' +
778
+ 'rdxSelectPortalPresence has been removed. See https://radix-ng.com/components/select.md');
779
+ }
549
780
  }
550
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopperPositionContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
551
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPopperPositionContent, isStandalone: true, selector: "[rdxSelectPopperPositionContent]", host: { attributes: { "role": "listbox" }, properties: { "id": "rootContext.contentId" } }, hostDirectives: [{ directive: i1.RdxPopperContent }], ngImport: i0 }); }
781
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
782
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPortalMisuseGuard, isStandalone: true, selector: "[rdxSelectPortal]:not(ng-template)", ngImport: i0 }); }
552
783
  }
553
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopperPositionContent, decorators: [{
784
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortalMisuseGuard, decorators: [{
554
785
  type: Directive,
555
786
  args: [{
556
- selector: '[rdxSelectPopperPositionContent]',
557
- hostDirectives: [RdxPopperContent],
558
- host: {
559
- role: 'listbox',
560
- '[id]': 'rootContext.contentId'
561
- }
787
+ selector: '[rdxSelectPortal]:not(ng-template)'
562
788
  }]
563
- }] });
789
+ }], ctorParameters: () => [] });
564
790
 
565
- class RdxSelectPopperPositionWrapper {
791
+ class RdxSelectPositioner {
566
792
  constructor() {
567
793
  /**
568
794
  * The preferred side of the anchor to render against when open.
@@ -619,22 +845,22 @@ class RdxSelectPopperPositionWrapper {
619
845
  */
620
846
  this.placed = outputFromObservable(outputToObservable(inject(RdxPopperContentWrapper).placed));
621
847
  }
622
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopperPositionWrapper, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
623
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectPopperPositionWrapper, isStandalone: true, selector: "[rdxSelectPopperPositionWrapper]", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { placed: "placed" }, host: { properties: { "style": "{\n 'boxSizing': 'border-box',\n '--radix-tooltip-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-tooltip-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-tooltip-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-tooltip-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-tooltip-trigger-height': 'var(--radix-popper-anchor-height)',\n }" } }, providers: [
848
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
849
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectPositioner, isStandalone: true, selector: "[rdxSelectPositioner]", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { placed: "placed" }, host: { properties: { "style": "{\n 'boxSizing': 'border-box',\n '--radix-select-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-select-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-select-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-select-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-select-trigger-height': 'var(--radix-popper-anchor-height)',\n }" } }, providers: [
624
850
  {
625
851
  provide: RDX_SELECT_POSITIONER_TOKEN,
626
- useExisting: forwardRef(() => RdxSelectPopperPositionWrapper)
852
+ useExisting: forwardRef(() => RdxSelectPositioner)
627
853
  }
628
854
  ], hostDirectives: [{ directive: i1.RdxPopperContentWrapper, inputs: ["side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
629
855
  }
630
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopperPositionWrapper, decorators: [{
856
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPositioner, decorators: [{
631
857
  type: Directive,
632
858
  args: [{
633
- selector: '[rdxSelectPopperPositionWrapper]',
859
+ selector: '[rdxSelectPositioner]',
634
860
  providers: [
635
861
  {
636
862
  provide: RDX_SELECT_POSITIONER_TOKEN,
637
- useExisting: forwardRef(() => RdxSelectPopperPositionWrapper)
863
+ useExisting: forwardRef(() => RdxSelectPositioner)
638
864
  }
639
865
  ],
640
866
  hostDirectives: [
@@ -659,70 +885,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
659
885
  // re-namespace exposed content custom properties
660
886
  '[style]': `{
661
887
  'boxSizing': 'border-box',
662
- '--radix-tooltip-content-transform-origin': 'var(--radix-popper-transform-origin)',
663
- '--radix-tooltip-content-available-width': 'var(--radix-popper-available-width)',
664
- '--radix-tooltip-content-available-height': 'var(--radix-popper-available-height)',
665
- '--radix-tooltip-trigger-width': 'var(--radix-popper-anchor-width)',
666
- '--radix-tooltip-trigger-height': 'var(--radix-popper-anchor-height)',
888
+ '--radix-select-content-transform-origin': 'var(--radix-popper-transform-origin)',
889
+ '--radix-select-content-available-width': 'var(--radix-popper-available-width)',
890
+ '--radix-select-content-available-height': 'var(--radix-popper-available-height)',
891
+ '--radix-select-trigger-width': 'var(--radix-popper-anchor-width)',
892
+ '--radix-select-trigger-height': 'var(--radix-popper-anchor-height)',
667
893
  }`
668
894
  }
669
895
  }]
670
896
  }], propDecorators: { side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }], placed: [{ type: i0.Output, args: ["placed"] }] } });
671
897
 
672
- class RdxSelectPortal {
673
- constructor() {
674
- /**
675
- * Optional container to portal the content into. Defaults to `document.body` when not set.
676
- */
677
- this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
678
- }
679
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
680
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectPortal, isStandalone: true, selector: "[rdxSelectPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1$2.RdxPortal, inputs: ["container", "container"] }], ngImport: i0 }); }
681
- }
682
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortal, decorators: [{
683
- type: Directive,
684
- args: [{
685
- selector: '[rdxSelectPortal]',
686
- hostDirectives: [
687
- {
688
- directive: RdxPortal,
689
- inputs: ['container']
690
- }
691
- ]
692
- }]
693
- }], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
694
-
695
- class RdxSelectPortalPresence {
696
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortalPresence, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
697
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPortalPresence, isStandalone: true, selector: "ng-template[rdxSelectPortalPresence]", providers: [
698
- provideRdxPresenceContext(() => {
699
- const context = injectSelectRootContext();
700
- return { present: context.open };
701
- })
702
- ], hostDirectives: [{ directive: i1$3.RdxPresenceDirective }], ngImport: i0 }); }
898
+ /**
899
+ * A visual divider between groups of items.
900
+ *
901
+ * @group Components
902
+ */
903
+ class RdxSelectSeparator {
904
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectSeparator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
905
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectSeparator, isStandalone: true, selector: "[rdxSelectSeparator]", host: { attributes: { "role": "separator", "aria-hidden": "true" } }, exportAs: ["rdxSelectSeparator"], ngImport: i0 }); }
703
906
  }
704
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortalPresence, decorators: [{
907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectSeparator, decorators: [{
705
908
  type: Directive,
706
909
  args: [{
707
- selector: 'ng-template[rdxSelectPortalPresence]',
708
- hostDirectives: [RdxPresenceDirective],
709
- providers: [
710
- provideRdxPresenceContext(() => {
711
- const context = injectSelectRootContext();
712
- return { present: context.open };
713
- })
714
- ]
910
+ selector: '[rdxSelectSeparator]',
911
+ exportAs: 'rdxSelectSeparator',
912
+ host: {
913
+ role: 'separator',
914
+ 'aria-hidden': 'true'
915
+ }
715
916
  }]
716
917
  }] });
717
918
 
919
+ const attr = (value) => (value ? '' : undefined);
718
920
  class RdxSelectTrigger {
719
921
  constructor() {
720
922
  this.rootContext = injectSelectRootContext();
721
923
  this.elementRef = inject(ElementRef);
924
+ this.fieldRootContext = injectFieldRootContext(true);
925
+ /** The trigger id; Field labels and descriptions reference it for accessible relationships. */
926
+ this.id = input(injectId('rdx-select-trigger-'), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
722
927
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
723
- this.isDisabled = computed(() => this.rootContext.disabled() || this.disabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
928
+ this.isDisabled = computed(() => this.rootContext.disabled() || this.disabled() || Boolean(this.fieldRootContext?.disabledState()), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
929
+ this.invalidState = computed(() => Boolean(this.fieldRootContext?.invalidState()), ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
930
+ this.requiredState = computed(() => Boolean(this.fieldRootContext?.requiredState()), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
931
+ this.filledState = computed(() => !this.rootContext.isEmptyModelValue() || Boolean(this.fieldRootContext?.filledState()), ...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
932
+ this.focusedState = computed(() => Boolean(this.fieldRootContext?.focusedState()), ...(ngDevMode ? [{ debugName: "focusedState" }] : /* istanbul ignore next */ []));
933
+ this.describedBy = computed(() => {
934
+ if (!this.fieldRootContext) {
935
+ return undefined;
936
+ }
937
+ const ids = [
938
+ ...this.fieldRootContext.descriptionIds(),
939
+ ...(this.fieldRootContext.invalidState() ? this.fieldRootContext.errorIds() : [])
940
+ ];
941
+ return ids.length ? ids.join(' ') : undefined;
942
+ }, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
943
+ this.dataAttr = attr;
724
944
  afterNextRender(() => {
725
945
  this.rootContext.onTriggerChange(this.elementRef);
946
+ this.fieldRootContext?.setControlId(this.id());
947
+ this.fieldRootContext?.setFilled(!this.rootContext.isEmptyModelValue());
726
948
  });
727
949
  }
728
950
  handleOpen() {
@@ -778,8 +1000,15 @@ class RdxSelectTrigger {
778
1000
  event.preventDefault();
779
1001
  }
780
1002
  }
1003
+ onFocus() {
1004
+ this.fieldRootContext?.setFocused(true);
1005
+ }
1006
+ onBlur() {
1007
+ this.fieldRootContext?.setFocused(false);
1008
+ this.fieldRootContext?.setTouched(true);
1009
+ }
781
1010
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
782
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectTrigger, isStandalone: true, selector: "button[rdxSelectTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "type": "button" }, listeners: { "click": "onClickHandler($event)", "pointerdown": "onPointerDown($event)", "pointerup": "onPointerUp($event)", "keydown": "onKeydown($event)" }, properties: { "attr.disabled": "isDisabled() ? \"\" : undefined", "dir": "rootContext.dir()", "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-disabled": "isDisabled() ? \"\" : undefined" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
1011
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectTrigger, isStandalone: true, selector: "button[rdxSelectTrigger]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "type": "button" }, listeners: { "click": "onClickHandler($event)", "pointerdown": "onPointerDown($event)", "pointerup": "onPointerUp($event)", "keydown": "onKeydown($event)", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "invalidState() ? \"true\" : undefined", "attr.aria-required": "requiredState() ? \"true\" : undefined", "attr.disabled": "isDisabled() ? \"\" : undefined", "dir": "rootContext.dir()", "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-popup-open": "dataAttr(rootContext.open())", "attr.data-placeholder": "dataAttr(rootContext.isEmptyModelValue())", "attr.data-disabled": "dataAttr(isDisabled())", "attr.data-invalid": "dataAttr(invalidState())", "attr.data-valid": "dataAttr(!invalidState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
783
1012
  }
784
1013
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectTrigger, decorators: [{
785
1014
  type: Directive,
@@ -789,17 +1018,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
789
1018
  host: {
790
1019
  role: 'combobox',
791
1020
  type: 'button',
1021
+ '[attr.id]': 'id()',
1022
+ '[attr.aria-describedby]': 'describedBy()',
1023
+ '[attr.aria-invalid]': 'invalidState() ? "true" : undefined',
1024
+ '[attr.aria-required]': 'requiredState() ? "true" : undefined',
792
1025
  '[attr.disabled]': 'isDisabled() ? "" : undefined',
793
1026
  '[dir]': 'rootContext.dir()',
794
1027
  '[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
795
- '[attr.data-disabled]': 'isDisabled() ? "" : undefined',
1028
+ '[attr.data-popup-open]': 'dataAttr(rootContext.open())',
1029
+ '[attr.data-placeholder]': 'dataAttr(rootContext.isEmptyModelValue())',
1030
+ '[attr.data-disabled]': 'dataAttr(isDisabled())',
1031
+ '[attr.data-invalid]': 'dataAttr(invalidState())',
1032
+ '[attr.data-valid]': 'dataAttr(!invalidState())',
1033
+ '[attr.data-required]': 'dataAttr(requiredState())',
1034
+ '[attr.data-filled]': 'dataAttr(filledState())',
1035
+ '[attr.data-focused]': 'dataAttr(focusedState())',
796
1036
  '(click)': 'onClickHandler($event)',
797
1037
  '(pointerdown)': 'onPointerDown($event)',
798
1038
  '(pointerup)': 'onPointerUp($event)',
799
- '(keydown)': 'onKeydown($event)'
1039
+ '(keydown)': 'onKeydown($event)',
1040
+ '(focus)': 'onFocus()',
1041
+ '(blur)': 'onBlur()'
800
1042
  }
801
1043
  }]
802
- }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
1044
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
803
1045
 
804
1046
  class RdxSelectValue {
805
1047
  constructor() {
@@ -810,16 +1052,17 @@ class RdxSelectValue {
810
1052
  return this.selectedLabel().length ? this.selectedLabel().join(', ') : this.placeholder();
811
1053
  }, ...(ngDevMode ? [{ debugName: "slotText" }] : /* istanbul ignore next */ []));
812
1054
  this.selectedLabel = computed(() => {
813
- // eslint-disable-next-line no-useless-assignment
814
- let list = [];
815
1055
  const options = Array.from(this.rootContext.optionsSet());
816
- const getOption = (value) => options.find((option) => valueComparator(value, option.value, this.rootContext.by()));
817
- if (Array.isArray(this.rootContext.value())) {
818
- list = Array(this.rootContext.value()).map((value) => getOption(value)?.textContent ?? '');
819
- }
820
- else {
821
- list = [getOption(this.rootContext.value())?.textContent ?? ''];
822
- }
1056
+ const customLabel = this.rootContext.itemToStringLabel();
1057
+ const labelFor = (value) => {
1058
+ if (customLabel && value !== undefined && value !== null) {
1059
+ return customLabel(value);
1060
+ }
1061
+ const option = options.find((o) => valueComparator(value, o.value, this.rootContext.isItemEqualToValue()));
1062
+ return option?.textContent ?? '';
1063
+ };
1064
+ const value = this.rootContext.value();
1065
+ const list = Array.isArray(value) ? value.map((v) => labelFor(v)) : [labelFor(value)];
823
1066
  return list.filter(Boolean);
824
1067
  }, ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
825
1068
  afterNextRender(() => {
@@ -840,60 +1083,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
840
1083
  }]
841
1084
  }], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
842
1085
 
843
- class RdxSelectViewport {
844
- constructor() {
845
- this.contentContext = injectSelectContentContext();
846
- this.elementRef = inject((ElementRef));
847
- this.prevScrollTopRef = signal(0, ...(ngDevMode ? [{ debugName: "prevScrollTopRef" }] : /* istanbul ignore next */ []));
848
- afterNextRender(() => {
849
- this.contentContext?.onViewportChange(this.elementRef.nativeElement);
850
- });
851
- }
852
- handleScroll(event) {
853
- const viewport = event.currentTarget;
854
- this.prevScrollTopRef.set(viewport.scrollTop);
855
- }
856
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
857
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectViewport, isStandalone: true, selector: "[rdxSelectViewport]", host: { attributes: { "role": "presentation" }, listeners: { "scroll": "handleScroll($event)" }, properties: { "attr.data-rdx-select-viewport": "\"\"", "style": "{\n position: 'relative',\n flex: 1,\n overflow: 'hidden auto',\n scrollbarWidth: 'none'\n }" } }, ngImport: i0 }); }
858
- }
859
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectViewport, decorators: [{
860
- type: Directive,
861
- args: [{
862
- selector: '[rdxSelectViewport]',
863
- host: {
864
- role: 'presentation',
865
- '[attr.data-rdx-select-viewport]': '""',
866
- '[style]': `{
867
- position: 'relative',
868
- flex: 1,
869
- overflow: 'hidden auto',
870
- scrollbarWidth: 'none'
871
- }`,
872
- '(scroll)': 'handleScroll($event)'
873
- }
874
- }]
875
- }], ctorParameters: () => [] });
876
-
877
1086
  const _importsSelect = [
878
1087
  RdxSelectRoot,
879
1088
  RdxSelectPortal,
1089
+ RdxSelectPortalMisuseGuard,
880
1090
  RdxSelectTrigger,
881
1091
  RdxSelectValue,
882
- RdxSelectContent,
883
- RdxSelectViewport,
1092
+ RdxSelectPopup,
1093
+ RdxSelectList,
884
1094
  RdxSelectItemIndicator,
885
1095
  RdxSelectItem,
886
1096
  RdxSelectItemText,
887
- RdxSelectLabel,
1097
+ RdxSelectGroupLabel,
888
1098
  RdxSelectGroup,
889
- RdxSelectPopperPositionContent,
890
- RdxSelectPopperPositionWrapper,
891
- RdxSelectPortalPresence
1099
+ RdxSelectPositioner,
1100
+ RdxSelectIcon,
1101
+ RdxSelectSeparator,
1102
+ RdxSelectBackdrop
892
1103
  ];
893
1104
 
894
1105
  /**
895
1106
  * Generated bundle index. Do not edit.
896
1107
  */
897
1108
 
898
- export { CONTENT_MARGIN, OPEN_KEYS, RDX_SELECT_POSITIONER_TOKEN, RdxSelectContent, RdxSelectGroup, RdxSelectItem, RdxSelectItemIndicator, RdxSelectItemText, RdxSelectLabel, RdxSelectPopperPositionContent, RdxSelectPopperPositionWrapper, RdxSelectPortal, RdxSelectPortalPresence, RdxSelectRoot, RdxSelectTrigger, RdxSelectValue, RdxSelectViewport, SELECTION_KEYS, _importsSelect, compare, focusFirst, injectSelectContentContext, injectSelectItemContext, injectSelectRootContext, provideSelectContentContext, provideSelectItemContext, provideSelectRootContext, shouldShowPlaceholder, valueComparator };
1109
+ export { CONTENT_MARGIN, OPEN_KEYS, RDX_SELECT_POSITIONER_TOKEN, RdxSelectBackdrop, RdxSelectGroup, RdxSelectGroupLabel, RdxSelectIcon, RdxSelectItem, RdxSelectItemIndicator, RdxSelectItemText, RdxSelectList, RdxSelectPopup, RdxSelectPortal, RdxSelectPortalMisuseGuard, RdxSelectPositioner, RdxSelectRoot, RdxSelectSeparator, RdxSelectTrigger, RdxSelectValue, SELECTION_KEYS, _importsSelect, compare, focusFirst, injectSelectItemContext, injectSelectPopupContext, injectSelectRootContext, provideSelectItemContext, provideSelectPopupContext, provideSelectRootContext, shouldShowPlaceholder, valueComparator };
899
1110
  //# sourceMappingURL=radix-ng-primitives-select.mjs.map