@taiga-ui/core 5.25.0 → 5.26.0-canary.eb5f53f

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 (61) hide show
  1. package/classes/accessors.d.ts +3 -0
  2. package/components/scrollbar/scrollbar.component.d.ts +2 -2
  3. package/fesm2022/taiga-ui-core-classes.mjs +1 -0
  4. package/fesm2022/taiga-ui-core-classes.mjs.map +1 -1
  5. package/fesm2022/taiga-ui-core-components-button.mjs +2 -2
  6. package/fesm2022/taiga-ui-core-components-button.mjs.map +1 -1
  7. package/fesm2022/taiga-ui-core-components-cell.mjs +3 -3
  8. package/fesm2022/taiga-ui-core-components-cell.mjs.map +1 -1
  9. package/fesm2022/taiga-ui-core-components-checkbox.mjs +2 -2
  10. package/fesm2022/taiga-ui-core-components-checkbox.mjs.map +1 -1
  11. package/fesm2022/taiga-ui-core-components-data-list.mjs +4 -4
  12. package/fesm2022/taiga-ui-core-components-data-list.mjs.map +1 -1
  13. package/fesm2022/taiga-ui-core-components-icon.mjs +2 -2
  14. package/fesm2022/taiga-ui-core-components-icon.mjs.map +1 -1
  15. package/fesm2022/taiga-ui-core-components-label.mjs +3 -3
  16. package/fesm2022/taiga-ui-core-components-label.mjs.map +1 -1
  17. package/fesm2022/taiga-ui-core-components-link.mjs +2 -2
  18. package/fesm2022/taiga-ui-core-components-link.mjs.map +1 -1
  19. package/fesm2022/taiga-ui-core-components-notification.mjs +2 -2
  20. package/fesm2022/taiga-ui-core-components-notification.mjs.map +1 -1
  21. package/fesm2022/taiga-ui-core-components-radio.mjs +2 -2
  22. package/fesm2022/taiga-ui-core-components-radio.mjs.map +1 -1
  23. package/fesm2022/taiga-ui-core-components-root.mjs +1 -1
  24. package/fesm2022/taiga-ui-core-components-root.mjs.map +1 -1
  25. package/fesm2022/taiga-ui-core-components-scrollbar.mjs +13 -12
  26. package/fesm2022/taiga-ui-core-components-scrollbar.mjs.map +1 -1
  27. package/fesm2022/taiga-ui-core-components-textfield.mjs +4 -4
  28. package/fesm2022/taiga-ui-core-components-textfield.mjs.map +1 -1
  29. package/fesm2022/taiga-ui-core-components-title.mjs +3 -3
  30. package/fesm2022/taiga-ui-core-components-title.mjs.map +1 -1
  31. package/fesm2022/taiga-ui-core-directives-appearance.mjs +3 -3
  32. package/fesm2022/taiga-ui-core-directives-appearance.mjs.map +1 -1
  33. package/fesm2022/taiga-ui-core-directives-group.mjs +3 -3
  34. package/fesm2022/taiga-ui-core-directives-group.mjs.map +1 -1
  35. package/fesm2022/taiga-ui-core-directives-icons.mjs +3 -3
  36. package/fesm2022/taiga-ui-core-directives-icons.mjs.map +1 -1
  37. package/fesm2022/taiga-ui-core-portals-alert.mjs +3 -3
  38. package/fesm2022/taiga-ui-core-portals-alert.mjs.map +1 -1
  39. package/fesm2022/taiga-ui-core-portals-dialog.mjs +2 -2
  40. package/fesm2022/taiga-ui-core-portals-dialog.mjs.map +1 -1
  41. package/fesm2022/taiga-ui-core-portals-dropdown.mjs +270 -163
  42. package/fesm2022/taiga-ui-core-portals-dropdown.mjs.map +1 -1
  43. package/fesm2022/taiga-ui-core-portals-hint.mjs +7 -21
  44. package/fesm2022/taiga-ui-core-portals-hint.mjs.map +1 -1
  45. package/fesm2022/taiga-ui-core-portals-modal.mjs +3 -3
  46. package/fesm2022/taiga-ui-core-portals-modal.mjs.map +1 -1
  47. package/fesm2022/taiga-ui-core-tokens.mjs +1 -1
  48. package/fesm2022/taiga-ui-core-tokens.mjs.map +1 -1
  49. package/fesm2022/taiga-ui-core-utils-dom.mjs +21 -1
  50. package/fesm2022/taiga-ui-core-utils-dom.mjs.map +1 -1
  51. package/package.json +19 -19
  52. package/portals/dropdown/dropdown-anchored.directive.d.ts +4 -1
  53. package/portals/dropdown/dropdown-context.directive.d.ts +3 -4
  54. package/portals/dropdown/dropdown-position-sided.directive.d.ts +2 -0
  55. package/portals/dropdown/dropdown-position.directive.d.ts +2 -0
  56. package/portals/dropdown/dropdown-selection.directive.d.ts +1 -1
  57. package/portals/dropdown/dropdown.component.d.ts +10 -8
  58. package/portals/dropdown/dropdown.providers.d.ts +2 -3
  59. package/portals/hint/hint-pointer.directive.d.ts +1 -3
  60. package/utils/dom/anchor-delegate.d.ts +1 -0
  61. package/utils/dom/index.d.ts +1 -0
@@ -1,21 +1,21 @@
1
1
  import { tuiProvide, tuiDirectiveBinding, tuiCreateOptions } from '@taiga-ui/cdk/utils/di';
2
2
  import { coerceArray } from '@angular/cdk/coercion';
3
3
  import * as i0 from '@angular/core';
4
- import { Injectable, Directive, InjectionToken, Optional, Self, SkipSelf, inject, computed, ChangeDetectionStrategy, Component, input, effect, ElementRef, INJECTOR, ChangeDetectorRef, signal, TemplateRef, contentChild, model, PLATFORM_ID, ViewContainerRef, afterNextRender } from '@angular/core';
4
+ import { InjectionToken, Optional, Self, SkipSelf, inject, Directive, computed, ChangeDetectionStrategy, Component, Injectable, input, effect, ElementRef, INJECTOR, ChangeDetectorRef, signal, TemplateRef, contentChild, model, PLATFORM_ID, ViewContainerRef } from '@angular/core';
5
5
  import { takeUntilDestroyed, outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
6
6
  import { tuiZonefreeScheduler, tuiIfMap, tuiCloseWatcher, tuiTypedFromEvent, tuiStopPropagation, tuiZoneOptimized } from '@taiga-ui/cdk/observables';
7
7
  import { tuiInjectElement, tuiGetActualTarget, tuiIsElement, tuiIsElementEditable, tuiIsHTMLElement, tuiPointToClientRect, tuiIsTextNode, tuiIsTextfield } from '@taiga-ui/cdk/utils/dom';
8
- import { TuiDriverDirective, TuiRectAccessor, tuiProvideAccessor, TuiPositionAccessor, tuiAsRectAccessor, tuiInjectAccessor, tuiAsPositionAccessor, tuiAsVehicle, tuiAsDriver, TuiDriver } from '@taiga-ui/core/classes';
8
+ import { TuiRectAccessor, TuiPositionAccessor, tuiProvideAccessor, tuiInjectAccessor, tuiAsPositionAccessor, TuiDriverDirective, tuiAsRectAccessor, tuiAsVehicle, tuiAsDriver, TuiDriver } from '@taiga-ui/core/classes';
9
9
  import { TuiPopupService } from '@taiga-ui/core/portals/popup';
10
- import { tuiCheckFixedPosition, tuiGetWordRange } from '@taiga-ui/core/utils/dom';
10
+ import { tuiCheckFixedPosition, tuiAnchorDelegate, tuiGetWordRange } from '@taiga-ui/core/utils/dom';
11
11
  import { PolymorpheusOutlet, PolymorpheusComponent, PolymorpheusTemplate } from '@taiga-ui/polymorpheus';
12
- import { BehaviorSubject, takeWhile, map, Subject, distinctUntilChanged, throttleTime, merge, filter, EMPTY, switchMap, delay, startWith, takeUntil, fromEvent, of, tap, share, combineLatest } from 'rxjs';
12
+ import { takeWhile, map, Subject, distinctUntilChanged, BehaviorSubject, throttleTime, merge, filter, EMPTY, switchMap, delay, startWith, takeUntil, fromEvent, of, tap, share, combineLatest } from 'rxjs';
13
13
  import * as i1 from '@taiga-ui/cdk/directives/active-zone';
14
14
  import { TuiActiveZone } from '@taiga-ui/cdk/directives/active-zone';
15
15
  import * as i2 from '@taiga-ui/cdk/directives/animated';
16
16
  import { TuiAnimated } from '@taiga-ui/cdk/directives/animated';
17
- import { TuiScrollbar } from '@taiga-ui/core/components/scrollbar';
18
- import { TUI_VIEWPORT, TUI_DARK_MODE, TUI_SELECTION_STREAM } from '@taiga-ui/core/tokens';
17
+ import { TuiScrollbar, TuiScrollControls, TUI_SCROLL_REF } from '@taiga-ui/core/components/scrollbar';
18
+ import { TUI_VIEWPORT, TUI_ANCHOR_SUPPORT, TUI_DARK_MODE, TUI_SELECTION_STREAM } from '@taiga-ui/core/tokens';
19
19
  import { EMPTY_CLIENT_RECT, TUI_TRUE_HANDLER, CHAR_ZERO_WIDTH_SPACE, CHAR_NO_BREAK_SPACE } from '@taiga-ui/cdk/constants';
20
20
  import { tuiClamp } from '@taiga-ui/cdk/utils/math';
21
21
  import { tuiPx, tuiGenerateId, tuiSetSignal, tuiIsString } from '@taiga-ui/cdk/utils/miscellaneous';
@@ -27,28 +27,12 @@ import { TuiObscured } from '@taiga-ui/cdk/directives/obscured';
27
27
  import { tuiIsFocusable, tuiGetClosestFocusable, tuiIsFocusedIn, tuiGetFocused } from '@taiga-ui/cdk/utils/focus';
28
28
  import { WA_IS_TOUCH } from '@ng-web-apis/platform';
29
29
 
30
- class TuiDropdownDriver extends BehaviorSubject {
31
- constructor() {
32
- super(false);
33
- this.type = 'dropdown';
34
- }
35
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
36
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver }); }
37
- }
38
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver, decorators: [{
39
- type: Injectable
40
- }], ctorParameters: () => [] });
41
- class TuiDropdownDriverDirective extends TuiDriverDirective {
42
- constructor() {
43
- super(...arguments);
44
- this.type = 'dropdown';
45
- }
46
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriverDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
47
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownDriverDirective, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
48
- }
49
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriverDirective, decorators: [{
50
- type: Directive
51
- }] });
30
+ /**
31
+ * @deprecated: remove in v6
32
+ */
33
+ const TUI_DROPDOWN_CONTEXT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_CONTEXT' : '');
34
+ const TUI_DROPDOWN_HOST = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_HOST' : '');
35
+ const TUI_DROPDOWN_ANCHOR = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_ANCHOR' : '');
52
36
 
53
37
  const TUI_DROPDOWN_DEFAULT_OPTIONS = {
54
38
  align: 'start',
@@ -104,25 +88,45 @@ class TuiDropdownAnchored {
104
88
  constructor() {
105
89
  this.el = tuiInjectElement();
106
90
  this.directive = inject(TuiDropdownDirective);
107
- this.accessor = inject(TuiRectAccessor);
91
+ this.rect = inject(TuiRectAccessor);
108
92
  this.viewport = inject(TUI_VIEWPORT);
109
93
  this.vvs = inject(TuiVisualViewportService);
110
94
  this.options = inject(TUI_DROPDOWN_OPTIONS);
111
95
  this.position = this.directive.position;
96
+ this.accessor = inject(TuiPositionAccessor);
112
97
  this.styles$ = inject(TuiPositionService).pipe(takeWhile(() => this.directive.el.isConnected &&
113
98
  this.directive.el.getClientRects().length > 0), map((v) => (this.position === 'fixed' ? this.vvs.correct(v) : v)), map((point) => this.getStyles(...point)), takeUntilDestroyed());
99
+ this.anchored = inject(TUI_ANCHOR_SUPPORT) &&
100
+ 'position' in this.accessor &&
101
+ this.viewport.type === 'window';
114
102
  }
115
103
  ngAfterViewInit() {
116
- this.styles$.subscribe({
117
- next: (styles) => Object.assign(this.el.style, styles),
118
- complete: () => this.directive.toggle(false),
119
- });
104
+ if (this.anchored) {
105
+ this.accessor.position?.(this.el);
106
+ }
107
+ else {
108
+ this.styles$.subscribe({
109
+ next: (styles) => Object.assign(this.el.style, styles),
110
+ complete: () => this.directive.toggle(false),
111
+ });
112
+ }
113
+ }
114
+ // https://github.com/w3c/csswg-drafts/issues/14112
115
+ retrigger() {
116
+ if (!this.anchored ||
117
+ this.el.scrollHeight === this.el.clientHeight ||
118
+ this.el.clientHeight === this.options.maxHeight) {
119
+ return;
120
+ }
121
+ this.el.style.position = 'static';
122
+ void this.el.offsetHeight;
123
+ this.el.style.position = 'fixed';
120
124
  }
121
125
  getStyles(x, y) {
122
126
  const { maxHeight, minHeight, offset, limitWidth } = this.options;
123
127
  const parent = this.el.offsetParent?.getBoundingClientRect() || EMPTY_CLIENT_RECT;
124
128
  const { left = 0, top = 0 } = this.position === 'fixed' ? {} : parent;
125
- const rect = this.accessor.getClientRect();
129
+ const rect = this.rect.getClientRect();
126
130
  const viewport = this.viewport.getClientRect();
127
131
  const zoom = this.directive.el.currentCSSZoom || 1;
128
132
  const above = rect.top - viewport.top - 2 * offset;
@@ -144,7 +148,7 @@ class TuiDropdownAnchored {
144
148
  };
145
149
  }
146
150
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownAnchored, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
147
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownAnchored, isStandalone: true, providers: [
151
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownAnchored, isStandalone: true, host: { listeners: { "window:scrollend.zoneless.capture": "retrigger()" }, properties: { "class._anchored": "anchored" } }, providers: [
148
152
  TuiPositionService,
149
153
  tuiProvideAccessor(TuiPositionAccessor, 'dropdown'),
150
154
  tuiProvideAccessor(TuiRectAccessor, 'dropdown'),
@@ -158,41 +162,224 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
158
162
  tuiProvideAccessor(TuiPositionAccessor, 'dropdown'),
159
163
  tuiProvideAccessor(TuiRectAccessor, 'dropdown'),
160
164
  ],
165
+ host: {
166
+ '[class._anchored]': 'anchored',
167
+ '(window:scrollend.zoneless.capture)': 'retrigger()',
168
+ },
161
169
  }]
162
170
  }] });
163
171
 
172
+ class TuiDropdownPosition extends TuiPositionAccessor {
173
+ constructor() {
174
+ super(...arguments);
175
+ this.el = tuiInjectElement();
176
+ this.options = inject(TUI_DROPDOWN_OPTIONS);
177
+ this.viewport = inject(TUI_VIEWPORT);
178
+ this.anchor = inject(TUI_DROPDOWN_ANCHOR);
179
+ this.direction = new Subject();
180
+ this.type = 'dropdown';
181
+ this.accessor = tuiInjectAccessor(TuiRectAccessor, 'dropdown');
182
+ this.tuiDropdownDirectionChange = outputFromObservable(this.direction.pipe(distinctUntilChanged()));
183
+ }
184
+ position(element) {
185
+ const { direction, align, offset, limitWidth, minHeight, maxHeight } = this.options;
186
+ const rect = this.anchor.nativeElement.getBoundingClientRect();
187
+ const viewport = this.viewport.getClientRect();
188
+ const horizontal = align === 'center' ? '' : `span-x-${invert(align)}`;
189
+ const height = Math.min(element.clientHeight, maxHeight) + offset * 2;
190
+ const vertical = direction || 'bottom';
191
+ const top = rect.top - viewport.top;
192
+ const bottom = viewport.bottom - rect.bottom;
193
+ const available = vertical === 'top' ? top : bottom;
194
+ const max = Math.max(top, bottom);
195
+ Object.assign(element.style, {
196
+ position: 'fixed',
197
+ visibility: 'visible',
198
+ positionAnchor: this.anchor.nativeElement.dataset.tuiAnchor,
199
+ positionArea: `${available < height && max !== available ? flip(vertical) : vertical} ${horizontal}`,
200
+ marginBlock: `${offset}px`,
201
+ minBlockSize: `calc-size(fit-content, min(size, ${minHeight}px))`,
202
+ maxBlockSize: `calc-size(fit-content, min(size, ${maxHeight}px))`,
203
+ minInlineSize: limitWidth === 'min' ? 'anchor-size(inline)' : '',
204
+ inlineSize: limitWidth === 'fixed' ? 'anchor-size(inline)' : '',
205
+ blockSize: `calc-size(fit-content, calc(100% - ${2 * offset}px))`,
206
+ });
207
+ }
208
+ getPosition({ width, height }) {
209
+ if (!width && !height) {
210
+ this.previous = undefined;
211
+ }
212
+ const hostRect = this.accessor.getClientRect();
213
+ const viewportRect = this.viewport.getClientRect();
214
+ const { minHeight, direction, offset, limitWidth } = this.options;
215
+ const align = this.getAlign(this.options.align);
216
+ const previous = this.previous || direction || 'bottom';
217
+ const viewport = {
218
+ top: viewportRect.top - offset,
219
+ bottom: viewportRect.bottom + offset,
220
+ right: viewportRect.right - offset,
221
+ left: viewportRect.left + offset,
222
+ };
223
+ const available = {
224
+ top: hostRect.top - 2 * offset - viewport.top,
225
+ bottom: viewport.bottom - hostRect.bottom - 2 * offset,
226
+ };
227
+ const rectWidth = limitWidth === 'fixed' ? hostRect.width : width;
228
+ const right = Math.max(hostRect.right - rectWidth, offset);
229
+ const left = hostRect.left + width < viewport.right ? hostRect.left : right;
230
+ const better = available.top > available.bottom ? 'top' : 'bottom';
231
+ const position = {
232
+ top: hostRect.top - offset - height,
233
+ bottom: hostRect.bottom + offset,
234
+ right: Math.max(viewport.left, right),
235
+ center: hostRect.left + hostRect.width / 2 + width / 2 < viewport.right
236
+ ? hostRect.left + hostRect.width / 2 - width / 2
237
+ : right,
238
+ left: Math.max(viewport.left, left),
239
+ };
240
+ if ((available[previous] > minHeight && direction) ||
241
+ available[previous] > height) {
242
+ this.direction.next(previous);
243
+ return [position[align], position[previous]];
244
+ }
245
+ this.previous = better;
246
+ this.direction.next(better);
247
+ return [position[align], position[better]];
248
+ }
249
+ getAlign(align) {
250
+ const rtl = this.el.matches('[dir="rtl"] :scope');
251
+ if (rtl && align === 'start') {
252
+ return 'right';
253
+ }
254
+ if (rtl && align === 'end') {
255
+ return 'left';
256
+ }
257
+ if (align === 'center') {
258
+ return 'center';
259
+ }
260
+ return align === 'end' ? 'right' : 'left';
261
+ }
262
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
263
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownPosition, isStandalone: true, outputs: { tuiDropdownDirectionChange: "tuiDropdownDirectionChange" }, providers: [tuiAsPositionAccessor(TuiDropdownPosition)], usesInheritance: true, ngImport: i0 }); }
264
+ }
265
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, decorators: [{
266
+ type: Directive,
267
+ args: [{ providers: [tuiAsPositionAccessor(TuiDropdownPosition)] }]
268
+ }] });
269
+ // TODO: Review in v6 to possible sync alignment with native anchors
270
+ function invert(align) {
271
+ return align === 'start' ? 'end' : 'start';
272
+ }
273
+ function flip(direction) {
274
+ return direction === 'top' ? 'bottom' : 'top';
275
+ }
276
+
277
+ const TUI_DROPDOWN_COMPONENT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_COMPONENT' : '', { factory: () => TuiDropdownComponent });
164
278
  /**
165
- * @description:
166
- * This component is used to show template in a portal
167
- * using default style of white rounded box with a shadow
279
+ * TODO: Remove extends TuiScrollbar in v6 when TuiScrollable is dropped
168
280
  */
169
- class TuiDropdownComponent {
281
+ class TuiDropdownComponent extends TuiScrollbar {
170
282
  constructor() {
283
+ super(...arguments);
284
+ this.anchor = inject(TUI_DROPDOWN_ANCHOR);
285
+ this.position = inject(TuiDropdownPosition);
286
+ this.darkMode = inject(TUI_DARK_MODE);
171
287
  this.options = inject(TUI_DROPDOWN_OPTIONS);
172
288
  this.directive = inject(TuiDropdownDirective);
173
- this.context = inject(TUI_DROPDOWN_CONTEXT, { optional: true });
174
- this.darkMode = inject(TUI_DARK_MODE);
175
289
  this.theme = computed((_ = this.darkMode()) => this.directive.el.closest('[tuiTheme]')?.getAttribute('tuiTheme'));
176
290
  this.close = () => this.directive.toggle(false);
177
291
  }
178
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
179
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownComponent, isStandalone: true, selector: "tui-dropdown", host: { properties: { "attr.data-appearance": "options.appearance", "attr.tuiTheme": "theme()" } }, hostDirectives: [{ directive: i1.TuiActiveZone }, { directive: i2.TuiAnimated }, { directive: TuiDropdownAnchored }], ngImport: i0, template: "<tui-scrollbar class=\"t-scroll\">\n <div\n *polymorpheusOutlet=\"directive.content() as text; context: {$implicit: close}\"\n class=\"t-primitive\"\n >\n {{ text }}\n </div>\n</tui-scrollbar>\n", styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-1);border-radius:var(--tui-radius-m);overflow:hidden;isolation:isolate;pointer-events:auto;outline-offset:-1px;outline:1px solid color-mix(in srgb,rgb(from var(--tui-border-normal) r g b / 1) 12%,var(--tui-background-elevation-1));--tui-from: translateY(-1rem)}:host:has(tui-data-list[data-size=l]){border-radius:var(--tui-radius-l)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide;animation-duration:calc(var(--tui-duration) / 2);pointer-events:none}:host:not([style*=top]){visibility:hidden}.t-scroll{z-index:-1;flex-grow:1;max-inline-size:100%;inline-size:max-content;overscroll-behavior:none}.t-primitive{padding:1rem}\n"], dependencies: [{ kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "component", type: TuiScrollbar, selector: "tui-scrollbar" }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
292
+ ngOnDestroy() {
293
+ if (!this.anchor.nativeElement.isConnected) {
294
+ this.el.style.setProperty('visibility', 'hidden');
295
+ }
296
+ }
297
+ onResize({ target, contentRect }) {
298
+ if (!target.checkVisibility({ opacityProperty: true })) {
299
+ return;
300
+ }
301
+ if (contentRect.width) {
302
+ this.position.direction.next(contentRect.height ? 'bottom' : 'top');
303
+ this.el.style.setProperty('visibility', 'visible');
304
+ }
305
+ else {
306
+ this.el.style.setProperty('visibility', 'hidden');
307
+ }
308
+ }
309
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
310
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownComponent, isStandalone: true, selector: "tui-dropdown", host: { properties: { "attr.data-appearance": "options.appearance", "attr.tuiTheme": "theme()", "style.max-block-size.px": "options.maxHeight" } }, providers: [
311
+ {
312
+ provide: TUI_SCROLL_REF,
313
+ useFactory: () => inject(TuiDropdownComponent).browserScrollRef,
314
+ },
315
+ ], usesInheritance: true, hostDirectives: [{ directive: i1.TuiActiveZone }, { directive: i2.TuiAnimated }, { directive: TuiDropdownAnchored }], ngImport: i0, template: `
316
+ <tui-scroll-controls class="t-scrollbar" />
317
+ <div class="t-wrapper">
318
+ <div
319
+ *polymorpheusOutlet="
320
+ directive.content() as text;
321
+ context: {$implicit: close}
322
+ "
323
+ [innerHTML]="text"
324
+ ></div>
325
+ </div>
326
+ <div
327
+ class="t-detector"
328
+ (resize)="onResize($any($event)[0])"
329
+ ></div>
330
+ `, isInline: true, styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-1);border-radius:var(--tui-radius-m);overflow:auto;isolation:isolate;pointer-events:auto;outline-offset:-1px;outline:1px solid color-mix(in srgb,rgb(from var(--tui-border-normal) r g b / 1) 12%,var(--tui-background-elevation-1));overscroll-behavior:none;position-visibility:anchors-valid;position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;--tui-from: translateY(-1rem)}:host:has(tui-data-list[data-size=l]){border-radius:var(--tui-radius-l)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide;animation-duration:calc(var(--tui-duration) / 2);pointer-events:none}:host:not([style*=top]){visibility:hidden}:host._delegated{scroll-timeline:none}:host:not(._anchored) .t-detector{display:none}.t-wrapper{z-index:-1;flex:1}.t-wrapper>*{padding:1rem}.t-detector{position:inherit;inset:anchor(top) 1rem calc(anchor(--t-scrollbar top) - 1rem);pointer-events:none;inline-size:calc(anchor-size(inline,-1px) + 1px);position-anchor:inherit;position-visibility:always}@supports (anchor-name: --t-scrollbar){.t-scrollbar{position:fixed;inset:anchor(top) anchor(right) anchor(bottom) anchor(left);position-anchor:--t-scrollbar;min-inline-size:0;min-block-size:0;margin:0}}\n"], dependencies: [{ kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "component", type: TuiScrollControls, selector: "tui-scroll-controls" }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
180
331
  }
181
332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownComponent, decorators: [{
182
333
  type: Component,
183
- args: [{ selector: 'tui-dropdown', imports: [PolymorpheusOutlet, TuiScrollbar], changeDetection: ChangeDetectionStrategy.Default, hostDirectives: [TuiActiveZone, TuiAnimated, TuiDropdownAnchored], host: {
334
+ args: [{ selector: 'tui-dropdown', imports: [PolymorpheusOutlet, TuiScrollControls], template: `
335
+ <tui-scroll-controls class="t-scrollbar" />
336
+ <div class="t-wrapper">
337
+ <div
338
+ *polymorpheusOutlet="
339
+ directive.content() as text;
340
+ context: {$implicit: close}
341
+ "
342
+ [innerHTML]="text"
343
+ ></div>
344
+ </div>
345
+ <div
346
+ class="t-detector"
347
+ (resize)="onResize($any($event)[0])"
348
+ ></div>
349
+ `, changeDetection: ChangeDetectionStrategy.Default, providers: [
350
+ {
351
+ provide: TUI_SCROLL_REF,
352
+ useFactory: () => inject(TuiDropdownComponent).browserScrollRef,
353
+ },
354
+ ], hostDirectives: [TuiActiveZone, TuiAnimated, TuiDropdownAnchored], host: {
184
355
  '[attr.data-appearance]': 'options.appearance',
185
356
  '[attr.tuiTheme]': 'theme()',
186
- }, template: "<tui-scrollbar class=\"t-scroll\">\n <div\n *polymorpheusOutlet=\"directive.content() as text; context: {$implicit: close}\"\n class=\"t-primitive\"\n >\n {{ text }}\n </div>\n</tui-scrollbar>\n", styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-1);border-radius:var(--tui-radius-m);overflow:hidden;isolation:isolate;pointer-events:auto;outline-offset:-1px;outline:1px solid color-mix(in srgb,rgb(from var(--tui-border-normal) r g b / 1) 12%,var(--tui-background-elevation-1));--tui-from: translateY(-1rem)}:host:has(tui-data-list[data-size=l]){border-radius:var(--tui-radius-l)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide;animation-duration:calc(var(--tui-duration) / 2);pointer-events:none}:host:not([style*=top]){visibility:hidden}.t-scroll{z-index:-1;flex-grow:1;max-inline-size:100%;inline-size:max-content;overscroll-behavior:none}.t-primitive{padding:1rem}\n"] }]
357
+ '[style.max-block-size.px]': 'options.maxHeight',
358
+ }, styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-1);border-radius:var(--tui-radius-m);overflow:auto;isolation:isolate;pointer-events:auto;outline-offset:-1px;outline:1px solid color-mix(in srgb,rgb(from var(--tui-border-normal) r g b / 1) 12%,var(--tui-background-elevation-1));overscroll-behavior:none;position-visibility:anchors-valid;position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline;--tui-from: translateY(-1rem)}:host:has(tui-data-list[data-size=l]){border-radius:var(--tui-radius-l)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide;animation-duration:calc(var(--tui-duration) / 2);pointer-events:none}:host:not([style*=top]){visibility:hidden}:host._delegated{scroll-timeline:none}:host:not(._anchored) .t-detector{display:none}.t-wrapper{z-index:-1;flex:1}.t-wrapper>*{padding:1rem}.t-detector{position:inherit;inset:anchor(top) 1rem calc(anchor(--t-scrollbar top) - 1rem);pointer-events:none;inline-size:calc(anchor-size(inline,-1px) + 1px);position-anchor:inherit;position-visibility:always}@supports (anchor-name: --t-scrollbar){.t-scrollbar{position:fixed;inset:anchor(top) anchor(right) anchor(bottom) anchor(left);position-anchor:--t-scrollbar;min-inline-size:0;min-block-size:0;margin:0}}\n"] }]
187
359
  }] });
188
360
 
189
- /**
190
- * A component to display a dropdown
191
- */
192
- const TUI_DROPDOWN_COMPONENT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_COMPONENT' : '', { factory: () => TuiDropdownComponent });
193
- const TUI_DROPDOWN_CONTEXT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_CONTEXT' : '');
194
- const TUI_DROPDOWN_HOST = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_HOST' : '');
195
- const TUI_DROPDOWN_ANCHOR = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_ANCHOR' : '');
361
+ class TuiDropdownDriver extends BehaviorSubject {
362
+ constructor() {
363
+ super(false);
364
+ this.type = 'dropdown';
365
+ }
366
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
367
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver }); }
368
+ }
369
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriver, decorators: [{
370
+ type: Injectable
371
+ }], ctorParameters: () => [] });
372
+ class TuiDropdownDriverDirective extends TuiDriverDirective {
373
+ constructor() {
374
+ super(...arguments);
375
+ this.type = 'dropdown';
376
+ }
377
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriverDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
378
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownDriverDirective, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
379
+ }
380
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownDriverDirective, decorators: [{
381
+ type: Directive
382
+ }] });
196
383
 
197
384
  class TuiDropdownA11y {
198
385
  constructor() {
@@ -257,79 +444,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
257
444
  }]
258
445
  }] });
259
446
 
260
- class TuiDropdownPosition extends TuiPositionAccessor {
261
- constructor() {
262
- super(...arguments);
263
- this.el = tuiInjectElement();
264
- this.options = inject(TUI_DROPDOWN_OPTIONS);
265
- this.viewport = inject(TUI_VIEWPORT);
266
- this.direction = new Subject();
267
- this.type = 'dropdown';
268
- this.accessor = tuiInjectAccessor(TuiRectAccessor, 'dropdown');
269
- this.tuiDropdownDirectionChange = outputFromObservable(this.direction.pipe(distinctUntilChanged()));
270
- }
271
- getPosition({ width, height }) {
272
- if (!width && !height) {
273
- this.previous = undefined;
274
- }
275
- const hostRect = this.accessor.getClientRect();
276
- const viewportRect = this.viewport.getClientRect();
277
- const { minHeight, direction, offset, limitWidth } = this.options;
278
- const align = this.getAlign(this.options.align);
279
- const viewport = {
280
- top: viewportRect.top - offset,
281
- bottom: viewportRect.bottom + offset,
282
- right: viewportRect.right - offset,
283
- left: viewportRect.left + offset,
284
- };
285
- const previous = this.previous || direction || 'bottom';
286
- const available = {
287
- top: hostRect.top - 2 * offset - viewport.top,
288
- bottom: viewport.bottom - hostRect.bottom - 2 * offset,
289
- };
290
- const rectWidth = limitWidth === 'fixed' ? hostRect.width : width;
291
- const right = Math.max(hostRect.right - rectWidth, offset);
292
- const left = hostRect.left + width < viewport.right ? hostRect.left : right;
293
- const position = {
294
- top: hostRect.top - offset - height,
295
- bottom: hostRect.bottom + offset,
296
- right: Math.max(viewport.left, right),
297
- center: hostRect.left + hostRect.width / 2 + width / 2 < viewport.right
298
- ? hostRect.left + hostRect.width / 2 - width / 2
299
- : right,
300
- left: Math.max(viewport.left, left),
301
- };
302
- const better = available.top > available.bottom ? 'top' : 'bottom';
303
- if ((available[previous] > minHeight && direction) ||
304
- available[previous] > height) {
305
- this.direction.next(previous);
306
- return [position[align], position[previous]];
307
- }
308
- this.previous = better;
309
- this.direction.next(better);
310
- return [position[align], position[better]];
311
- }
312
- getAlign(align) {
313
- const rtl = this.el.matches('[dir="rtl"] :scope');
314
- if (rtl && align === 'start') {
315
- return 'right';
316
- }
317
- if (rtl && align === 'end') {
318
- return 'left';
319
- }
320
- if (align === 'center') {
321
- return 'center';
322
- }
323
- return align === 'end' ? 'right' : 'left';
324
- }
325
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
326
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: TuiDropdownPosition, isStandalone: true, outputs: { tuiDropdownDirectionChange: "tuiDropdownDirectionChange" }, providers: [tuiAsPositionAccessor(TuiDropdownPosition)], usesInheritance: true, ngImport: i0 }); }
327
- }
328
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, decorators: [{
329
- type: Directive,
330
- args: [{ providers: [tuiAsPositionAccessor(TuiDropdownPosition)] }]
331
- }] });
332
-
333
447
  class TuiDropdownDirective {
334
448
  constructor() {
335
449
  this.injector = inject(INJECTOR);
@@ -404,10 +518,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
404
518
  TuiDropdownAnchor,
405
519
  TuiDropdownDriverDirective,
406
520
  { directive: TuiDropdownA11y, inputs: ['tuiDropdownRole'] },
407
- {
408
- directive: TuiDropdownPosition,
409
- outputs: ['tuiDropdownDirectionChange'],
410
- },
521
+ { directive: TuiDropdownPosition, outputs: ['tuiDropdownDirectionChange'] },
411
522
  ],
412
523
  host: { '[class.tui-dropdown-open]': 'ref()' },
413
524
  }]
@@ -588,12 +699,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
588
699
  args: [{ selector: 'ng-template[tuiDropdown]' }]
589
700
  }], ctorParameters: () => [] });
590
701
 
591
- const STYLE = {
592
- position: 'fixed',
593
- blockSize: '1px',
594
- inlineSize: '1px',
595
- pointerEvents: 'none',
596
- };
597
702
  class TuiDropdownContext extends TuiRectAccessor {
598
703
  constructor() {
599
704
  super(...arguments);
@@ -616,14 +721,10 @@ class TuiDropdownContext extends TuiRectAccessor {
616
721
  this.currentRect = EMPTY_CLIENT_RECT;
617
722
  });
618
723
  this.type = 'dropdown';
619
- this.nativeElement = this.doc.createElement('div');
620
- }
621
- ngAfterViewInit() {
622
- const anchorName = `--${tuiGenerateId()}`;
623
- const positionAnchor = this.el.getAttribute('data-tui-anchor');
624
- const style = { ...STYLE, positionAnchor, anchorName };
625
- Object.assign(this.nativeElement.style, style);
626
- this.nativeElement.setAttribute('data-tui-anchor', anchorName);
724
+ this.nativeElement = tuiAnchorDelegate({
725
+ top: 'calc(anchor(top)',
726
+ left: 'calc(anchor(left)',
727
+ });
627
728
  }
628
729
  ngOnDestroy() {
629
730
  this.nativeElement.parentNode?.removeChild(this.nativeElement);
@@ -635,8 +736,7 @@ class TuiDropdownContext extends TuiRectAccessor {
635
736
  const { top, left } = this.el.getBoundingClientRect();
636
737
  this.currentRect = tuiPointToClientRect(x, y);
637
738
  this.nativeElement.style.top = `calc(anchor(top) + ${y - top}px)`;
638
- this.nativeElement.style.left = `calc(anchor(left) + ${x - left}px)`;
639
- this.doc.body.appendChild(this.nativeElement);
739
+ this.nativeElement.style.left = `calc(anchor(left, -100000px) + ${x - left}px)`;
640
740
  this.driver.next(true);
641
741
  }
642
742
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownContext, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
@@ -778,6 +878,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
778
878
  class TuiDropdownPositionSided extends TuiPositionAccessor {
779
879
  constructor() {
780
880
  super(...arguments);
881
+ this.anchor = inject(TUI_DROPDOWN_ANCHOR, { optional: true });
781
882
  this.options = inject(TUI_DROPDOWN_OPTIONS);
782
883
  this.viewport = inject(TUI_VIEWPORT);
783
884
  this.vertical = inject(TuiDropdownPosition, { optional: true });
@@ -786,6 +887,23 @@ class TuiDropdownPositionSided extends TuiPositionAccessor {
786
887
  this.tuiDropdownSidedOffset = input(4);
787
888
  this.type = 'dropdown';
788
889
  }
890
+ position(element) {
891
+ if (this.tuiDropdownSided() === false) {
892
+ this.vertical?.position(element);
893
+ return;
894
+ }
895
+ const { direction, align, offset, minHeight, maxHeight } = this.options;
896
+ const horizontal = align === 'center' ? 'end' : align;
897
+ Object.assign(element.style, {
898
+ position: 'fixed',
899
+ visibility: 'visible',
900
+ positionAnchor: this.anchor?.nativeElement.dataset.tuiAnchor,
901
+ positionArea: `x-${horizontal} span-${direction || 'bottom'}`,
902
+ minBlockSize: `calc-size(fit-content, min(size, ${minHeight}px))`,
903
+ maxBlockSize: `min(calc-size(fit-content, size), ${maxHeight}px)`,
904
+ margin: `${-offset}px ${offset}px`,
905
+ });
906
+ }
789
907
  getPosition(rect) {
790
908
  if (this.tuiDropdownSided() === false && this.vertical) {
791
909
  return this.vertical.getPosition(rect);
@@ -857,27 +975,17 @@ class TuiDropdownSelection extends TuiDriver {
857
975
  const visible = valid || this.inDropdown(range);
858
976
  const focus = tuiGetFocused(this.doc);
859
977
  const textfield = focus && tuiIsTextfield(focus) && this.el.contains(focus);
978
+ const show = visible && textfield ? this.isCaretVisible(this.range) : visible;
860
979
  this.updateAnchor();
861
- return visible && textfield ? this.isCaretVisible(this.range) : visible;
980
+ return show;
862
981
  }));
863
982
  this.range = isPlatformBrowser(inject(PLATFORM_ID))
864
983
  ? new Range()
865
984
  : {};
866
985
  this.type = 'dropdown';
867
- this.nativeElement = this.doc.createElement('div');
986
+ this.nativeElement = tuiAnchorDelegate();
868
987
  this.tuiDropdownSelection = input('');
869
988
  this.tuiDropdownSelectionPosition = input('selection');
870
- afterNextRender(() => {
871
- const anchorName = `--${tuiGenerateId()}`;
872
- Object.assign(this.nativeElement.style, {
873
- position: 'fixed',
874
- pointerEvents: 'none',
875
- positionAnchor: this.el.dataset.tuiAnchor,
876
- anchorName,
877
- });
878
- this.nativeElement.dataset.tuiAnchor = anchorName;
879
- this.doc.body.appendChild(this.nativeElement);
880
- });
881
989
  }
882
990
  ngOnDestroy() {
883
991
  this.nativeElement.parentNode?.removeChild(this.nativeElement);
@@ -939,12 +1047,11 @@ class TuiDropdownSelection extends TuiDriver {
939
1047
  }
940
1048
  veryVerySadInputFix(element) {
941
1049
  const { ghost = this.initGhost(this.ghostHost) } = this;
942
- const { top, left, width, height } = this.ghostHost.getBoundingClientRect();
1050
+ const { width, height } = this.ghostHost.getBoundingClientRect();
943
1051
  const { selectionStart, selectionEnd, value } = element;
944
1052
  const range = this.doc.createRange();
945
- const hostRect = this.ghostHost.getBoundingClientRect();
946
- ghost.style.top = tuiPx(top - hostRect.top);
947
- ghost.style.left = tuiPx(left - hostRect.left);
1053
+ ghost.style.top = '0px';
1054
+ ghost.style.left = '0px';
948
1055
  ghost.style.width = tuiPx(width);
949
1056
  ghost.style.height = tuiPx(height);
950
1057
  ghost.textContent = `${CHAR_ZERO_WIDTH_SPACE}${value}${CHAR_NO_BREAK_SPACE}`;
@@ -989,7 +1096,7 @@ class TuiDropdownSelection extends TuiDriver {
989
1096
  const { top, left } = this.el.getBoundingClientRect();
990
1097
  Object.assign(this.nativeElement.style, {
991
1098
  top: `calc(anchor(top) + ${rect.top - top}px)`,
992
- left: `calc(anchor(left) + ${rect.left - left}px)`,
1099
+ left: `calc(anchor(left, -100000px) + ${rect.left - left}px)`,
993
1100
  blockSize: tuiPx(rect.height),
994
1101
  inlineSize: tuiPx(rect.width),
995
1102
  });