@taiga-ui/core 5.25.0-canary.6f46c3c → 5.26.0

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 (59) hide show
  1. package/classes/accessors.d.ts +3 -0
  2. package/components/scrollbar/scrollbar.component.d.ts +1 -1
  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 +3 -3
  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 +229 -154
  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-utils-dom.mjs +21 -1
  48. package/fesm2022/taiga-ui-core-utils-dom.mjs.map +1 -1
  49. package/package.json +34 -34
  50. package/portals/dropdown/dropdown-anchored.directive.d.ts +3 -0
  51. package/portals/dropdown/dropdown-context.directive.d.ts +3 -4
  52. package/portals/dropdown/dropdown-position-sided.directive.d.ts +2 -0
  53. package/portals/dropdown/dropdown-position.directive.d.ts +2 -0
  54. package/portals/dropdown/dropdown-selection.directive.d.ts +1 -1
  55. package/portals/dropdown/dropdown.component.d.ts +8 -5
  56. package/portals/dropdown/dropdown.providers.d.ts +2 -3
  57. package/portals/hint/hint-pointer.directive.d.ts +1 -3
  58. package/utils/dom/anchor-delegate.d.ts +1 -0
  59. 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
17
  import { TuiScrollbar, TuiScrollControls, TUI_SCROLL_REF } from '@taiga-ui/core/components/scrollbar';
18
- import { TUI_VIEWPORT, TUI_DARK_MODE, TUI_SELECTION_STREAM } from '@taiga-ui/core/tokens';
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',
@@ -109,14 +93,34 @@ class TuiDropdownAnchored {
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;
@@ -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,22 +162,150 @@ 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
279
  * TODO: Remove extends TuiScrollbar in v6 when TuiScrollable is dropped
166
280
  */
167
281
  class TuiDropdownComponent extends TuiScrollbar {
168
282
  constructor() {
169
283
  super(...arguments);
284
+ this.anchor = inject(TUI_DROPDOWN_ANCHOR);
285
+ this.position = inject(TuiDropdownPosition);
286
+ this.darkMode = inject(TUI_DARK_MODE);
170
287
  this.options = inject(TUI_DROPDOWN_OPTIONS);
171
288
  this.directive = inject(TuiDropdownDirective);
172
- this.context = inject(TUI_DROPDOWN_CONTEXT, { optional: true });
173
- this.darkMode = inject(TUI_DARK_MODE);
174
289
  this.theme = computed((_ = this.darkMode()) => this.directive.el.closest('[tuiTheme]')?.getAttribute('tuiTheme'));
175
290
  this.close = () => this.directive.toggle(false);
176
291
  }
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
+ }
177
309
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
178
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: [
179
311
  {
@@ -191,7 +323,11 @@ class TuiDropdownComponent extends TuiScrollbar {
191
323
  [innerHTML]="text"
192
324
  ></div>
193
325
  </div>
194
- `, 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-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}.t-wrapper{z-index:-1;flex:1}.t-wrapper>*{padding:1rem}@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 }); }
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 }); }
195
331
  }
196
332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownComponent, decorators: [{
197
333
  type: Component,
@@ -206,6 +342,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
206
342
  [innerHTML]="text"
207
343
  ></div>
208
344
  </div>
345
+ <div
346
+ class="t-detector"
347
+ (resize)="onResize($any($event)[0])"
348
+ ></div>
209
349
  `, changeDetection: ChangeDetectionStrategy.Default, providers: [
210
350
  {
211
351
  provide: TUI_SCROLL_REF,
@@ -215,16 +355,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
215
355
  '[attr.data-appearance]': 'options.appearance',
216
356
  '[attr.tuiTheme]': 'theme()',
217
357
  '[style.max-block-size.px]': 'options.maxHeight',
218
- }, 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-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}.t-wrapper{z-index:-1;flex:1}.t-wrapper>*{padding:1rem}@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"] }]
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"] }]
219
359
  }] });
220
360
 
221
- /**
222
- * A component to display a dropdown
223
- */
224
- const TUI_DROPDOWN_COMPONENT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_COMPONENT' : '', { factory: () => TuiDropdownComponent });
225
- const TUI_DROPDOWN_CONTEXT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_CONTEXT' : '');
226
- const TUI_DROPDOWN_HOST = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_HOST' : '');
227
- 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
+ }] });
228
383
 
229
384
  class TuiDropdownA11y {
230
385
  constructor() {
@@ -289,79 +444,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
289
444
  }]
290
445
  }] });
291
446
 
292
- class TuiDropdownPosition extends TuiPositionAccessor {
293
- constructor() {
294
- super(...arguments);
295
- this.el = tuiInjectElement();
296
- this.options = inject(TUI_DROPDOWN_OPTIONS);
297
- this.viewport = inject(TUI_VIEWPORT);
298
- this.direction = new Subject();
299
- this.type = 'dropdown';
300
- this.accessor = tuiInjectAccessor(TuiRectAccessor, 'dropdown');
301
- this.tuiDropdownDirectionChange = outputFromObservable(this.direction.pipe(distinctUntilChanged()));
302
- }
303
- getPosition({ width, height }) {
304
- if (!width && !height) {
305
- this.previous = undefined;
306
- }
307
- const hostRect = this.accessor.getClientRect();
308
- const viewportRect = this.viewport.getClientRect();
309
- const { minHeight, direction, offset, limitWidth } = this.options;
310
- const align = this.getAlign(this.options.align);
311
- const previous = this.previous || direction || 'bottom';
312
- const viewport = {
313
- top: viewportRect.top - offset,
314
- bottom: viewportRect.bottom + offset,
315
- right: viewportRect.right - offset,
316
- left: viewportRect.left + offset,
317
- };
318
- const available = {
319
- top: hostRect.top - 2 * offset - viewport.top,
320
- bottom: viewport.bottom - hostRect.bottom - 2 * offset,
321
- };
322
- const rectWidth = limitWidth === 'fixed' ? hostRect.width : width;
323
- const right = Math.max(hostRect.right - rectWidth, offset);
324
- const left = hostRect.left + width < viewport.right ? hostRect.left : right;
325
- const better = available.top > available.bottom ? 'top' : 'bottom';
326
- const position = {
327
- top: hostRect.top - offset - height,
328
- bottom: hostRect.bottom + offset,
329
- right: Math.max(viewport.left, right),
330
- center: hostRect.left + hostRect.width / 2 + width / 2 < viewport.right
331
- ? hostRect.left + hostRect.width / 2 - width / 2
332
- : right,
333
- left: Math.max(viewport.left, left),
334
- };
335
- if ((available[previous] > minHeight && direction) ||
336
- available[previous] > height) {
337
- this.direction.next(previous);
338
- return [position[align], position[previous]];
339
- }
340
- this.previous = better;
341
- this.direction.next(better);
342
- return [position[align], position[better]];
343
- }
344
- getAlign(align) {
345
- const rtl = this.el.matches('[dir="rtl"] :scope');
346
- if (rtl && align === 'start') {
347
- return 'right';
348
- }
349
- if (rtl && align === 'end') {
350
- return 'left';
351
- }
352
- if (align === 'center') {
353
- return 'center';
354
- }
355
- return align === 'end' ? 'right' : 'left';
356
- }
357
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
358
- 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 }); }
359
- }
360
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownPosition, decorators: [{
361
- type: Directive,
362
- args: [{ providers: [tuiAsPositionAccessor(TuiDropdownPosition)] }]
363
- }] });
364
-
365
447
  class TuiDropdownDirective {
366
448
  constructor() {
367
449
  this.injector = inject(INJECTOR);
@@ -436,10 +518,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
436
518
  TuiDropdownAnchor,
437
519
  TuiDropdownDriverDirective,
438
520
  { directive: TuiDropdownA11y, inputs: ['tuiDropdownRole'] },
439
- {
440
- directive: TuiDropdownPosition,
441
- outputs: ['tuiDropdownDirectionChange'],
442
- },
521
+ { directive: TuiDropdownPosition, outputs: ['tuiDropdownDirectionChange'] },
443
522
  ],
444
523
  host: { '[class.tui-dropdown-open]': 'ref()' },
445
524
  }]
@@ -620,12 +699,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
620
699
  args: [{ selector: 'ng-template[tuiDropdown]' }]
621
700
  }], ctorParameters: () => [] });
622
701
 
623
- const STYLE = {
624
- position: 'fixed',
625
- blockSize: '1px',
626
- inlineSize: '1px',
627
- pointerEvents: 'none',
628
- };
629
702
  class TuiDropdownContext extends TuiRectAccessor {
630
703
  constructor() {
631
704
  super(...arguments);
@@ -648,14 +721,10 @@ class TuiDropdownContext extends TuiRectAccessor {
648
721
  this.currentRect = EMPTY_CLIENT_RECT;
649
722
  });
650
723
  this.type = 'dropdown';
651
- this.nativeElement = this.doc.createElement('div');
652
- }
653
- ngAfterViewInit() {
654
- const anchorName = `--${tuiGenerateId()}`;
655
- const positionAnchor = this.el.getAttribute('data-tui-anchor');
656
- const style = { ...STYLE, positionAnchor, anchorName };
657
- Object.assign(this.nativeElement.style, style);
658
- this.nativeElement.setAttribute('data-tui-anchor', anchorName);
724
+ this.nativeElement = tuiAnchorDelegate({
725
+ top: 'calc(anchor(top)',
726
+ left: 'calc(anchor(left)',
727
+ });
659
728
  }
660
729
  ngOnDestroy() {
661
730
  this.nativeElement.parentNode?.removeChild(this.nativeElement);
@@ -667,8 +736,7 @@ class TuiDropdownContext extends TuiRectAccessor {
667
736
  const { top, left } = this.el.getBoundingClientRect();
668
737
  this.currentRect = tuiPointToClientRect(x, y);
669
738
  this.nativeElement.style.top = `calc(anchor(top) + ${y - top}px)`;
670
- this.nativeElement.style.left = `calc(anchor(left) + ${x - left}px)`;
671
- this.doc.body.appendChild(this.nativeElement);
739
+ this.nativeElement.style.left = `calc(anchor(left, -100000px) + ${x - left}px)`;
672
740
  this.driver.next(true);
673
741
  }
674
742
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: TuiDropdownContext, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
@@ -810,6 +878,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
810
878
  class TuiDropdownPositionSided extends TuiPositionAccessor {
811
879
  constructor() {
812
880
  super(...arguments);
881
+ this.anchor = inject(TUI_DROPDOWN_ANCHOR, { optional: true });
813
882
  this.options = inject(TUI_DROPDOWN_OPTIONS);
814
883
  this.viewport = inject(TUI_VIEWPORT);
815
884
  this.vertical = inject(TuiDropdownPosition, { optional: true });
@@ -818,6 +887,23 @@ class TuiDropdownPositionSided extends TuiPositionAccessor {
818
887
  this.tuiDropdownSidedOffset = input(4);
819
888
  this.type = 'dropdown';
820
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
+ }
821
907
  getPosition(rect) {
822
908
  if (this.tuiDropdownSided() === false && this.vertical) {
823
909
  return this.vertical.getPosition(rect);
@@ -889,27 +975,17 @@ class TuiDropdownSelection extends TuiDriver {
889
975
  const visible = valid || this.inDropdown(range);
890
976
  const focus = tuiGetFocused(this.doc);
891
977
  const textfield = focus && tuiIsTextfield(focus) && this.el.contains(focus);
978
+ const show = visible && textfield ? this.isCaretVisible(this.range) : visible;
892
979
  this.updateAnchor();
893
- return visible && textfield ? this.isCaretVisible(this.range) : visible;
980
+ return show;
894
981
  }));
895
982
  this.range = isPlatformBrowser(inject(PLATFORM_ID))
896
983
  ? new Range()
897
984
  : {};
898
985
  this.type = 'dropdown';
899
- this.nativeElement = this.doc.createElement('div');
986
+ this.nativeElement = tuiAnchorDelegate();
900
987
  this.tuiDropdownSelection = input('');
901
988
  this.tuiDropdownSelectionPosition = input('selection');
902
- afterNextRender(() => {
903
- const anchorName = `--${tuiGenerateId()}`;
904
- Object.assign(this.nativeElement.style, {
905
- position: 'fixed',
906
- pointerEvents: 'none',
907
- positionAnchor: this.el.dataset.tuiAnchor,
908
- anchorName,
909
- });
910
- this.nativeElement.dataset.tuiAnchor = anchorName;
911
- this.doc.body.appendChild(this.nativeElement);
912
- });
913
989
  }
914
990
  ngOnDestroy() {
915
991
  this.nativeElement.parentNode?.removeChild(this.nativeElement);
@@ -971,12 +1047,11 @@ class TuiDropdownSelection extends TuiDriver {
971
1047
  }
972
1048
  veryVerySadInputFix(element) {
973
1049
  const { ghost = this.initGhost(this.ghostHost) } = this;
974
- const { top, left, width, height } = this.ghostHost.getBoundingClientRect();
1050
+ const { width, height } = this.ghostHost.getBoundingClientRect();
975
1051
  const { selectionStart, selectionEnd, value } = element;
976
1052
  const range = this.doc.createRange();
977
- const hostRect = this.ghostHost.getBoundingClientRect();
978
- ghost.style.top = tuiPx(top - hostRect.top);
979
- ghost.style.left = tuiPx(left - hostRect.left);
1053
+ ghost.style.top = '0px';
1054
+ ghost.style.left = '0px';
980
1055
  ghost.style.width = tuiPx(width);
981
1056
  ghost.style.height = tuiPx(height);
982
1057
  ghost.textContent = `${CHAR_ZERO_WIDTH_SPACE}${value}${CHAR_NO_BREAK_SPACE}`;
@@ -1021,7 +1096,7 @@ class TuiDropdownSelection extends TuiDriver {
1021
1096
  const { top, left } = this.el.getBoundingClientRect();
1022
1097
  Object.assign(this.nativeElement.style, {
1023
1098
  top: `calc(anchor(top) + ${rect.top - top}px)`,
1024
- left: `calc(anchor(left) + ${rect.left - left}px)`,
1099
+ left: `calc(anchor(left, -100000px) + ${rect.left - left}px)`,
1025
1100
  blockSize: tuiPx(rect.height),
1026
1101
  inlineSize: tuiPx(rect.width),
1027
1102
  });