diff --git a/projects/addon-doc/components/main/main.template.html b/projects/addon-doc/components/main/main.template.html index 6ebf1410edcb..460dc57508e3 100644 --- a/projects/addon-doc/components/main/main.template.html +++ b/projects/addon-doc/components/main/main.template.html @@ -31,8 +31,8 @@ - - + + diff --git a/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.component.ts b/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.component.ts index d965bacc38b7..af9005bca7c5 100644 --- a/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.component.ts +++ b/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.component.ts @@ -10,7 +10,6 @@ import { } from '@angular/core'; import { TUI_IS_IOS, - TUI_SCROLL_REF, TuiContext, TuiDestroyService, TuiHandler, @@ -18,6 +17,7 @@ import { tuiScrollFrom, tuiZonefree, } from '@taiga-ui/cdk'; +import {TUI_SCROLL_REF} from '@taiga-ui/core'; import {PolymorpheusContent} from '@tinkoff/ng-polymorpheus'; import {distinctUntilChanged, filter, map, Observable, startWith, takeUntil} from 'rxjs'; diff --git a/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.service.ts b/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.service.ts index 18ce6dcc8423..a3a32a28dce3 100644 --- a/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.service.ts +++ b/projects/addon-mobile/components/pull-to-refresh/pull-to-refresh.service.ts @@ -1,5 +1,6 @@ import {ElementRef, Inject, Injectable} from '@angular/core'; -import {TUI_SCROLL_REF, tuiScrollFrom, tuiTypedFromEvent} from '@taiga-ui/cdk'; +import {tuiScrollFrom, tuiTypedFromEvent} from '@taiga-ui/cdk'; +import {TUI_SCROLL_REF} from '@taiga-ui/core'; import { distinctUntilChanged, EMPTY, diff --git a/projects/addon-mobile/components/sheet/components/sheet/sheet.providers.ts b/projects/addon-mobile/components/sheet/components/sheet/sheet.providers.ts index 1d02c208a1d7..4028df03da0b 100644 --- a/projects/addon-mobile/components/sheet/components/sheet/sheet.providers.ts +++ b/projects/addon-mobile/components/sheet/components/sheet/sheet.providers.ts @@ -4,10 +4,10 @@ import { ALWAYS_FALSE_HANDLER, ALWAYS_TRUE_HANDLER, TUI_IS_IOS, - TUI_SCROLL_REF, tuiTypedFromEvent, tuiZonefree, } from '@taiga-ui/cdk'; +import {TUI_SCROLL_REF} from '@taiga-ui/core'; import {map, merge, Observable, share} from 'rxjs'; import {iosScrollFactory} from '../../ios.hacks'; diff --git a/projects/addon-mobile/components/sheet/directives/sheet-stop/sheet-stop.directive.ts b/projects/addon-mobile/components/sheet/directives/sheet-stop/sheet-stop.directive.ts index 7d80576fffa4..5d729d045309 100644 --- a/projects/addon-mobile/components/sheet/directives/sheet-stop/sheet-stop.directive.ts +++ b/projects/addon-mobile/components/sheet/directives/sheet-stop/sheet-stop.directive.ts @@ -1,5 +1,6 @@ import {Directive, ElementRef, Inject, Self} from '@angular/core'; -import {TUI_SCROLL_REF, TuiDestroyService} from '@taiga-ui/cdk'; +import {TuiDestroyService} from '@taiga-ui/cdk'; +import {TUI_SCROLL_REF} from '@taiga-ui/core'; import { distinctUntilChanged, filter, diff --git a/projects/addon-mobile/directives/elastic-sticky/elastic-sticky.service.ts b/projects/addon-mobile/directives/elastic-sticky/elastic-sticky.service.ts index 9317ac0fe135..dc0807c09204 100644 --- a/projects/addon-mobile/directives/elastic-sticky/elastic-sticky.service.ts +++ b/projects/addon-mobile/directives/elastic-sticky/elastic-sticky.service.ts @@ -1,12 +1,11 @@ import {ElementRef, Inject, Injectable, NgZone, Self} from '@angular/core'; import { - SCROLL_REF_SELECTOR, - TUI_SCROLL_REF, TuiDestroyService, tuiGetElementOffset, tuiScrollFrom, tuiZoneOptimized, } from '@taiga-ui/cdk'; +import {SCROLL_REF_SELECTOR, TUI_SCROLL_REF} from '@taiga-ui/core'; import { distinctUntilChanged, map, diff --git a/projects/addon-mobile/directives/sidebar/sidebar.directive.ts b/projects/addon-mobile/directives/sidebar/sidebar.directive.ts index f0bd595e1250..238d3a166e99 100644 --- a/projects/addon-mobile/directives/sidebar/sidebar.directive.ts +++ b/projects/addon-mobile/directives/sidebar/sidebar.directive.ts @@ -8,8 +8,7 @@ import { OnDestroy, TemplateRef, } from '@angular/core'; -import {TuiDropdownPortalService} from '@taiga-ui/cdk'; -import {TuiHorizontalDirection} from '@taiga-ui/core'; +import {TuiDropdownService, TuiHorizontalDirection} from '@taiga-ui/core'; import {PolymorpheusComponent, PolymorpheusTemplate} from '@tinkoff/ng-polymorpheus'; import {TuiSidebarComponent} from './sidebar.component'; @@ -46,8 +45,8 @@ export class TuiSidebarDirective> constructor( @Inject(TemplateRef) readonly content: TemplateRef, @Inject(Injector) private readonly injector: Injector, - @Inject(TuiDropdownPortalService) - private readonly portalService: TuiDropdownPortalService, + @Inject(TuiDropdownService) + private readonly portalService: TuiDropdownService, @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef, ) { super(content, cdr); diff --git a/projects/cdk/abstract/index.ts b/projects/cdk/abstract/index.ts index 30cada44edb1..a75c844a5524 100644 --- a/projects/cdk/abstract/index.ts +++ b/projects/cdk/abstract/index.ts @@ -3,7 +3,6 @@ export * from './controller'; export * from './interactive'; export * from './multiple-control'; export * from './nullable-control'; -export * from './portal-host'; -export * from './portal-service'; +export * from './portals'; export * from './theme-switcher'; export * from './value-transformer'; diff --git a/projects/cdk/abstract/portal-host.ts b/projects/cdk/abstract/portal-host.ts deleted file mode 100644 index e6ece5a62d14..000000000000 --- a/projects/cdk/abstract/portal-host.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - ComponentFactoryResolver, - ComponentRef, - Directive, - ElementRef, - EmbeddedViewRef, - Inject, - INJECTOR, - Injector, - TemplateRef, - ViewChild, - ViewContainerRef, -} from '@angular/core'; -import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; - -import {AbstractTuiPortalService} from './portal-service'; - -/** - * Abstract class for host element for dynamically created portals. - */ -@Directive() -export abstract class AbstractTuiPortalHostComponent { - @ViewChild('viewContainer', {read: ViewContainerRef}) - private readonly vcr!: ViewContainerRef; - - constructor( - @Inject(INJECTOR) private readonly injector: Injector, - @Inject(ElementRef) protected readonly el: ElementRef, - @Inject(AbstractTuiPortalService) portalService: AbstractTuiPortalService, - ) { - portalService.attach(this); - } - - /** @deprecated unused, will be removed in 4.0 **/ - get clientRect(): ClientRect { - return this.el.nativeElement.getBoundingClientRect(); - } - - addComponentChild(component: PolymorpheusComponent): ComponentRef { - const parent = component.createInjector(this.injector); - const resolver = parent.get(ComponentFactoryResolver); - const factory = resolver.resolveComponentFactory(component.component); - // TODO: Remove in 4.0 - const providers = [{provide: AbstractTuiPortalHostComponent, useValue: this}]; - const injector = Injector.create({parent, providers}); - const ref = this.vcr.createComponent(factory, undefined, injector); - - ref.changeDetectorRef.detectChanges(); - - return ref; - } - - addTemplateChild(templateRef: TemplateRef, context?: C): EmbeddedViewRef { - return this.vcr.createEmbeddedView(templateRef, context); - } -} diff --git a/projects/cdk/abstract/portal-service.ts b/projects/cdk/abstract/portal-service.ts deleted file mode 100644 index fe13f73d5c3e..000000000000 --- a/projects/cdk/abstract/portal-service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {ComponentRef, EmbeddedViewRef, Injectable, TemplateRef} from '@angular/core'; -import {TuiNoHostException} from '@taiga-ui/cdk/exceptions'; -import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; - -import {AbstractTuiPortalHostComponent} from './portal-host'; - -/** - * Abstract service for displaying portals - */ -@Injectable() -export abstract class AbstractTuiPortalService { - protected host?: AbstractTuiPortalHostComponent; - - protected get safeHost(): AbstractTuiPortalHostComponent { - if (!this.host) { - throw new TuiNoHostException(); - } - - return this.host; - } - - attach(host: AbstractTuiPortalHostComponent): void { - this.host = host; - } - - add(component: PolymorpheusComponent): ComponentRef { - return this.safeHost.addComponentChild(component); - } - - remove({hostView}: ComponentRef): void { - if (!hostView.destroyed) { - hostView.destroy(); - } - } - - addTemplate(templateRef: TemplateRef, context?: C): EmbeddedViewRef { - return this.safeHost.addTemplateChild(templateRef, context); - } - - removeTemplate(viewRef: EmbeddedViewRef): void { - if (!viewRef.destroyed) { - viewRef.destroy(); - } - } -} diff --git a/projects/cdk/abstract/portals.ts b/projects/cdk/abstract/portals.ts new file mode 100644 index 000000000000..5128e2f9ac72 --- /dev/null +++ b/projects/cdk/abstract/portals.ts @@ -0,0 +1,87 @@ +import { + ComponentRef, + Directive, + EmbeddedViewRef, + inject, + Injectable, + INJECTOR, + Provider, + TemplateRef, + ViewChild, + ViewContainerRef, +} from '@angular/core'; +import {TuiNoHostException} from '@taiga-ui/cdk/exceptions'; +import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; + +/** + * Abstract class for host element for dynamically created portals. + */ +@Directive() +export abstract class TuiPortalsComponent { + @ViewChild('viewContainer', {read: ViewContainerRef}) + private readonly vcr!: ViewContainerRef; + + private readonly injector = inject(INJECTOR); + + protected readonly nothing = inject(TuiPortalService).attach(this); + + addComponentChild(component: PolymorpheusComponent): ComponentRef { + const injector = component.createInjector(this.injector); + const ref = this.vcr.createComponent(component.component, {injector}); + + ref.changeDetectorRef.detectChanges(); + + return ref; + } + + addTemplateChild(templateRef: TemplateRef, context?: C): EmbeddedViewRef { + return this.vcr.createEmbeddedView(templateRef, context); + } +} + +/** + * Abstract service for displaying portals + */ +@Injectable() +export abstract class TuiPortalService { + protected host?: TuiPortalsComponent; + + protected get safeHost(): TuiPortalsComponent { + if (!this.host) { + throw new TuiNoHostException(); + } + + return this.host; + } + + attach(host: TuiPortalsComponent): void { + this.host = host; + } + + add(component: PolymorpheusComponent): ComponentRef { + return this.safeHost.addComponentChild(component); + } + + remove({hostView}: ComponentRef): void { + if (!hostView.destroyed) { + hostView.destroy(); + } + } + + addTemplate(templateRef: TemplateRef, context?: C): EmbeddedViewRef { + return this.safeHost.addTemplateChild(templateRef, context); + } + + removeTemplate(viewRef: EmbeddedViewRef): void { + if (!viewRef.destroyed) { + viewRef.destroy(); + } + } +} + +export function tuiAsPortal(useExisting: typeof TuiPortalService): Provider { + return { + provide: TuiPortalService, + useExisting, + }; +} diff --git a/projects/cdk/components/dropdown-host/dropdown-host.component.ts b/projects/cdk/components/dropdown-host/dropdown-host.component.ts deleted file mode 100644 index ae4b4efcb1bb..000000000000 --- a/projects/cdk/components/dropdown-host/dropdown-host.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import { - AbstractTuiPortalHostComponent, - AbstractTuiPortalService, -} from '@taiga-ui/cdk/abstract'; - -import {TuiDropdownPortalService} from './dropdown-portal.service'; - -/** - * Host element for dynamically created portals, for example using {@link TuiDropdownDirective}. - */ -@Component({ - selector: 'tui-dropdown-host', - templateUrl: './dropdown-host.template.html', - styleUrls: ['./dropdown-host.style.less'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - {provide: AbstractTuiPortalService, useExisting: TuiDropdownPortalService}, - // TODO: Remove in 4.0 - {provide: AbstractTuiPortalHostComponent, useExisting: TuiDropdownHostComponent}, - ], -}) -export class TuiDropdownHostComponent extends AbstractTuiPortalHostComponent {} diff --git a/projects/cdk/components/dropdown-host/dropdown-host.module.ts b/projects/cdk/components/dropdown-host/dropdown-host.module.ts deleted file mode 100644 index a02e0cad7bbc..000000000000 --- a/projects/cdk/components/dropdown-host/dropdown-host.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {NgModule} from '@angular/core'; - -import {TuiDropdownHostComponent} from './dropdown-host.component'; - -@NgModule({ - declarations: [TuiDropdownHostComponent], - exports: [TuiDropdownHostComponent], -}) -export class TuiDropdownHostModule {} diff --git a/projects/cdk/components/dropdown-host/dropdown-host.style.less b/projects/cdk/components/dropdown-host/dropdown-host.style.less deleted file mode 100644 index a712217f84fc..000000000000 --- a/projects/cdk/components/dropdown-host/dropdown-host.style.less +++ /dev/null @@ -1,22 +0,0 @@ -:host { - position: relative; - z-index: 0; - display: block; - height: 100%; - - &:before { - content: ''; - display: block; - overflow: hidden; - } -} - -.t-position-fixed-offset { - position: fixed; - left: 0; - top: 0; - pointer-events: none; - visibility: hidden; - width: 100%; - height: 100%; -} diff --git a/projects/cdk/components/dropdown-host/dropdown-host.template.html b/projects/cdk/components/dropdown-host/dropdown-host.template.html deleted file mode 100644 index 84776d85b4b6..000000000000 --- a/projects/cdk/components/dropdown-host/dropdown-host.template.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/projects/cdk/components/dropdown-host/dropdown-portal.service.ts b/projects/cdk/components/dropdown-host/dropdown-portal.service.ts deleted file mode 100644 index 82ed89ce95c6..000000000000 --- a/projects/cdk/components/dropdown-host/dropdown-portal.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Injectable} from '@angular/core'; -import {AbstractTuiPortalService} from '@taiga-ui/cdk/abstract'; - -/** - * Service for displaying dropdown portals - */ -@Injectable({ - providedIn: 'root', -}) -export class TuiDropdownPortalService extends AbstractTuiPortalService {} diff --git a/projects/cdk/components/dropdown-host/index.ts b/projects/cdk/components/dropdown-host/index.ts deleted file mode 100644 index 0016886321a6..000000000000 --- a/projects/cdk/components/dropdown-host/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './dropdown-host.component'; -export * from './dropdown-host.module'; -export * from './dropdown-portal.service'; diff --git a/projects/cdk/components/dropdown-host/ng-package.json b/projects/cdk/components/dropdown-host/ng-package.json deleted file mode 100644 index bebf62dcb5e5..000000000000 --- a/projects/cdk/components/dropdown-host/ng-package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lib": { - "entryFile": "index.ts" - } -} diff --git a/projects/cdk/components/dropdown-host/test/dropdown-portal.service.spec.ts b/projects/cdk/components/dropdown-host/test/dropdown-portal.service.spec.ts deleted file mode 100644 index 2a4579c4c04a..000000000000 --- a/projects/cdk/components/dropdown-host/test/dropdown-portal.service.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import {ComponentRef, EmbeddedViewRef, TemplateRef} from '@angular/core'; -import {AbstractTuiPortalHostComponent} from '@taiga-ui/cdk'; -import {tuiSwitchNgDevMode} from '@taiga-ui/testing'; -import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; - -import {TuiDropdownPortalService} from '../dropdown-portal.service'; - -describe('PortalService', () => { - let service: TuiDropdownPortalService; - - beforeEach(() => { - service = new TuiDropdownPortalService(); - }); - - it('Template removing', () => { - let called = 0; - const viewRefStub: EmbeddedViewRef = { - destroy: () => called++, - } as unknown as EmbeddedViewRef; - - service.removeTemplate(viewRefStub); - expect(called).toBe(1); - }); - - it('HostView removing', () => { - let called = 0; - const componentRefStub: ComponentRef = { - hostView: {destroy: () => called++}, - } as unknown as ComponentRef; - - service.remove(componentRefStub); - expect(called).toBe(1); - }); - - describe('production mode', () => { - it('throws an error with no host', () => { - let actual = ''; - const a = null as unknown as PolymorpheusComponent; - - try { - service.add(a); - } catch (err) { - actual = err.message; - } - - expect(actual).toBe(''); - }); - }); - - describe('dev mode', () => { - beforeEach(() => tuiSwitchNgDevMode(true)); - - it('throws an error with no host', () => { - let actual = ''; - const a = null as unknown as PolymorpheusComponent; - - try { - service.add(a); - } catch (err) { - actual = err.message; - } - - expect(actual).toBe('Portals cannot be used without TuiPortalHostComponent'); - }); - - afterEach(() => tuiSwitchNgDevMode(false)); - }); - - it('addTemplateChild with host attached', () => { - const a: TemplateRef = null as unknown as TemplateRef; - const result: EmbeddedViewRef = - {} as unknown as EmbeddedViewRef; - const componentPortalStub: AbstractTuiPortalHostComponent = { - addTemplateChild: () => result, - } as unknown as AbstractTuiPortalHostComponent; - - service.attach(componentPortalStub); - - expect(service.addTemplate(a)).toBe(result); - }); -}); diff --git a/projects/cdk/components/index.ts b/projects/cdk/components/index.ts deleted file mode 100644 index 4eb1758c8ec3..000000000000 --- a/projects/cdk/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from '@taiga-ui/cdk/components/dropdown-host'; -export * from '@taiga-ui/cdk/components/scroll-controls'; diff --git a/projects/cdk/components/ng-package.json b/projects/cdk/components/ng-package.json deleted file mode 100644 index bebf62dcb5e5..000000000000 --- a/projects/cdk/components/ng-package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lib": { - "entryFile": "index.ts" - } -} diff --git a/projects/cdk/components/scroll-controls/index.ts b/projects/cdk/components/scroll-controls/index.ts deleted file mode 100644 index 450b5423857f..000000000000 --- a/projects/cdk/components/scroll-controls/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './scroll-controls.component'; -export * from './scroll-controls.module'; -export * from './scroll-ref.directive'; -export * from './scrollbar.directive'; diff --git a/projects/cdk/components/scroll-controls/ng-package.json b/projects/cdk/components/scroll-controls/ng-package.json deleted file mode 100644 index bebf62dcb5e5..000000000000 --- a/projects/cdk/components/scroll-controls/ng-package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lib": { - "entryFile": "index.ts" - } -} diff --git a/projects/cdk/components/scroll-controls/scroll-controls.component.ts b/projects/cdk/components/scroll-controls/scroll-controls.component.ts deleted file mode 100644 index e83b0ebfb95d..000000000000 --- a/projects/cdk/components/scroll-controls/scroll-controls.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Inject, - NgZone, -} from '@angular/core'; -import {ANIMATION_FRAME} from '@ng-web-apis/common'; -import {tuiZoneOptimized} from '@taiga-ui/cdk/observables'; -import {TUI_SCROLL_REF} from '@taiga-ui/cdk/tokens'; -import {distinctUntilChanged, map, Observable, startWith, throttleTime} from 'rxjs'; - -@Component({ - selector: 'tui-scroll-controls', - templateUrl: './scroll-controls.template.html', - styleUrls: ['./scroll-controls.style.less'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TuiScrollControlsComponent { - readonly refresh$ = this.animationFrame$.pipe( - throttleTime(300), - map(() => this.scrollbars), - startWith([false, false]), - distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1]), - tuiZoneOptimized(this.zone), - ); - - constructor( - @Inject(NgZone) private readonly zone: NgZone, - @Inject(TUI_SCROLL_REF) private readonly scrollRef: ElementRef, - @Inject(ANIMATION_FRAME) private readonly animationFrame$: Observable, - ) {} - - private get scrollbars(): [boolean, boolean] { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = - this.scrollRef.nativeElement; - - return [ - Math.ceil((clientHeight / scrollHeight) * 100) < 100, - Math.ceil((clientWidth / scrollWidth) * 100) < 100, - ]; - } -} diff --git a/projects/cdk/components/scroll-controls/scroll-controls.module.ts b/projects/cdk/components/scroll-controls/scroll-controls.module.ts deleted file mode 100644 index bb6725b23c67..000000000000 --- a/projects/cdk/components/scroll-controls/scroll-controls.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {TuiScrollControlsComponent} from './scroll-controls.component'; -import {TuiScrollRefDirective} from './scroll-ref.directive'; -import {TuiScrollbarDirective} from './scrollbar.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [ - TuiScrollbarDirective, - TuiScrollControlsComponent, - TuiScrollRefDirective, - ], - exports: [TuiScrollControlsComponent, TuiScrollRefDirective], -}) -export class TuiScrollControlsModule {} diff --git a/projects/cdk/components/scroll-controls/scroll-controls.style.less b/projects/cdk/components/scroll-controls/scroll-controls.style.less deleted file mode 100644 index 576ff7ebc95d..000000000000 --- a/projects/cdk/components/scroll-controls/scroll-controls.style.less +++ /dev/null @@ -1,93 +0,0 @@ -@scroll-width: 0.75rem; -@scroll-width-large: 0.875rem; -@scroll-min-size: 1.25rem; - -:host { - position: sticky; - top: 0; - left: 0; - z-index: 1; - display: none; - min-width: calc(100% - 1px); - min-height: calc(100% - 1px); - max-width: calc(100% - 1px); - max-height: calc(100% - 1px); - float: left; - margin-inline-end: calc(-100% + 1px); - pointer-events: none; - - &:host-context([data-tui-theme]) { - display: block; - } -} - -.t-bar { - position: absolute; - right: 0; - bottom: 0; - pointer-events: auto; - animation: tuiFadeIn var(--tui-duration, 300ms) ease-in-out; - - &_vertical { - top: 0; - width: @scroll-width-large; - } - - &_horizontal { - left: 0; - height: @scroll-width-large; - } - - &_has-horizontal { - bottom: @scroll-width - 0.25rem; - } - - &_has-vertical { - right: @scroll-width - 0.25rem; - } -} - -.t-thumb { - position: absolute; - border-radius: 6.25rem; - border: 0.25rem solid transparent; - cursor: pointer; - pointer-events: auto; - user-select: none; - background: currentColor; - background-clip: content-box; - box-sizing: border-box; - transition: all var(--tui-duration, 300ms) ease-in-out; - transition-property: width, height, opacity; - opacity: 0.2; - - &:hover { - opacity: 0.24; - } - - &:active { - opacity: 0.48; - } - - .t-bar_vertical & { - right: 0; - width: @scroll-width; - min-height: @scroll-min-size; - } - - .t-bar_vertical:hover &, - .t-bar_vertical &:active { - width: @scroll-width-large; - } - - .t-bar_horizontal & { - bottom: 0; - height: @scroll-width; - min-width: @scroll-min-size; - } - - .t-bar_horizontal:hover &, - .t-bar_horizontal &:active { - height: @scroll-width-large; - } -} diff --git a/projects/cdk/components/scroll-controls/scroll-controls.template.html b/projects/cdk/components/scroll-controls/scroll-controls.template.html deleted file mode 100644 index 33c08437c4f1..000000000000 --- a/projects/cdk/components/scroll-controls/scroll-controls.template.html +++ /dev/null @@ -1,22 +0,0 @@ - -
-
-
-
-
-
-
diff --git a/projects/cdk/components/scroll-controls/scrollbar.directive.ts b/projects/cdk/components/scroll-controls/scrollbar.directive.ts deleted file mode 100644 index 018996a5885c..000000000000 --- a/projects/cdk/components/scroll-controls/scrollbar.directive.ts +++ /dev/null @@ -1,159 +0,0 @@ -import {DOCUMENT} from '@angular/common'; -import {Directive, ElementRef, Inject, Input, NgZone, Self} from '@angular/core'; -import {ANIMATION_FRAME} from '@ng-web-apis/common'; -import {POLLING_TIME} from '@taiga-ui/cdk/constants'; -import { - tuiScrollFrom, - tuiStopPropagation, - tuiTypedFromEvent, - tuiZonefree, -} from '@taiga-ui/cdk/observables'; -import {TuiDestroyService} from '@taiga-ui/cdk/services'; -import {TUI_SCROLL_REF} from '@taiga-ui/cdk/tokens'; -import {map, merge, Observable, switchMap, takeUntil, throttleTime} from 'rxjs'; - -const MIN_WIDTH = 24; - -function getOffsetVertical({clientY}: MouseEvent, {top, height}: ClientRect): number { - return (clientY - top) / height; -} - -function getOffsetHorizontal({clientX}: MouseEvent, {left, width}: ClientRect): number { - return (clientX - left) / width; -} - -@Directive({ - selector: '[tuiScrollbar]', - providers: [TuiDestroyService], -}) -export class TuiScrollbarDirective { - @Input() - tuiScrollbar: 'horizontal' | 'vertical' = 'vertical'; - - constructor( - @Inject(NgZone) zone: NgZone, - @Self() @Inject(TuiDestroyService) destroy$: Observable, - @Inject(ANIMATION_FRAME) animationFrame$: Observable, - @Inject(TUI_SCROLL_REF) private readonly container: ElementRef, - @Inject(DOCUMENT) private readonly doc: Document, - @Inject(ElementRef) private readonly el: ElementRef, - ) { - const {nativeElement} = this.el; - const mousedown$ = tuiTypedFromEvent(nativeElement, 'mousedown'); - const mousemove$ = tuiTypedFromEvent(this.doc, 'mousemove'); - const mouseup$ = tuiTypedFromEvent(this.doc, 'mouseup'); - const mousedownWrapper$ = tuiTypedFromEvent(this.wrapper, 'mousedown'); - - merge( - mousedownWrapper$.pipe(map(event => this.getScrolled(event, 0.5, 0.5))), - mousedown$.pipe( - tuiStopPropagation(), - switchMap(event => { - const rect = nativeElement.getBoundingClientRect(); - const vertical = getOffsetVertical(event, rect); - const horizontal = getOffsetHorizontal(event, rect); - - return mousemove$.pipe( - map(event => this.getScrolled(event, vertical, horizontal)), - takeUntil(mouseup$), - ); - }), - ), - ) - .pipe(tuiZonefree(zone), takeUntil(destroy$)) - .subscribe(([scrollTop, scrollLeft]) => { - if (this.tuiScrollbar === 'vertical') { - this.element.scrollTop = scrollTop; - } else { - this.element.scrollLeft = scrollLeft; - } - }); - - merge( - animationFrame$.pipe(throttleTime(POLLING_TIME)), - tuiScrollFrom(this.element), - ) - .pipe(tuiZonefree(zone), takeUntil(destroy$)) - .subscribe(() => { - if (this.tuiScrollbar === 'vertical') { - nativeElement.style.top = `${this.thumb * 100}%`; - nativeElement.style.height = `${this.view * 100}%`; - } else { - nativeElement.style.left = `${this.thumb * 100}%`; - nativeElement.style.width = `${this.view * 100}%`; - } - }); - } - - private get wrapper(): Element { - return this.el.nativeElement.parentElement!; - } - - private get scrolled(): number { - const { - scrollTop, - scrollHeight, - clientHeight, - scrollLeft, - scrollWidth, - clientWidth, - } = this.element; - - return this.tuiScrollbar === 'vertical' - ? scrollTop / (scrollHeight - clientHeight) - : scrollLeft / (scrollWidth - clientWidth); - } - - private get compensation(): number { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.element; - - if ( - ((clientHeight * clientHeight) / scrollHeight > MIN_WIDTH && - this.tuiScrollbar === 'vertical') || - ((clientWidth * clientWidth) / scrollWidth > MIN_WIDTH && - this.tuiScrollbar === 'horizontal') - ) { - return 0; - } - - return this.tuiScrollbar === 'vertical' - ? MIN_WIDTH / clientHeight - : MIN_WIDTH / clientWidth; - } - - private get thumb(): number { - const compensation = this.compensation || this.view; - - return this.scrolled * (1 - compensation); - } - - private get view(): number { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.element; - - return this.tuiScrollbar === 'vertical' - ? Math.ceil((clientHeight / scrollHeight) * 100) / 100 - : Math.ceil((clientWidth / scrollWidth) * 100) / 100; - } - - private get element(): Element { - return this.container.nativeElement; - } - - private getScrolled( - {clientY, clientX}: MouseEvent, - offsetVertical: number, - offsetHorizontal: number, - ): [number, number] { - const {offsetHeight, offsetWidth} = this.el.nativeElement; - const {top, left, width, height} = this.wrapper.getBoundingClientRect(); - - const maxTop = this.element.scrollHeight - height; - const maxLeft = this.element.scrollWidth - width; - const scrolledTop = - (clientY - top - offsetHeight * offsetVertical) / (height - offsetHeight); - const scrolledLeft = - (clientX - left - offsetWidth * offsetHorizontal) / (width - offsetWidth); - - return [maxTop * scrolledTop, maxLeft * scrolledLeft]; - } -} diff --git a/projects/cdk/decorators/debounce.ts b/projects/cdk/decorators/debounce.ts deleted file mode 100644 index c33697c5db1c..000000000000 --- a/projects/cdk/decorators/debounce.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @deprecated: - * not compatible with TypeScript 5 - */ -export function tuiDebounce(timeout: number): MethodDecorator { - let timeoutRef: ReturnType | undefined; - - return function (_target, _key: string | symbol, descriptor: PropertyDescriptor) { - const {value} = descriptor; - - descriptor.value = function (...args: unknown[]) { - clearTimeout(timeoutRef); - timeoutRef = setTimeout(() => value.apply(this, args), timeout); - }; - - return descriptor; - }; -} diff --git a/projects/cdk/decorators/default-prop.ts b/projects/cdk/decorators/default-prop.ts deleted file mode 100644 index 678cd4cc1f17..000000000000 --- a/projects/cdk/decorators/default-prop.ts +++ /dev/null @@ -1,95 +0,0 @@ -/// -import {tuiAssert} from '@taiga-ui/cdk/classes'; -import {TuiBooleanHandler} from '@taiga-ui/cdk/types'; - -function errorGetDefault(key: string | symbol, component: string): string { - return `Default value for ${String( - key, - )} was not provided in ${component}, error in Taiga UI Angular Kit`; -} - -function errorSetDefault(key: string | symbol, component: string): string { - return `Undefined was passed as ${String( - key, - )} to ${component}, which is invalid input, using default value:`; -} - -function errorSetDefaultInitial(key: string | symbol, component: string): string { - return `Undefined was passed as default value for ${String( - key, - )} to ${component}, error in Taiga UI Angular Kit`; -} - -/** - * @deprecated: - * not compatible with TypeScript 5 - * - * Decorator for checking input values for undefined. You can also pass - * optional assertion to check input against. - * - * CAUTION: This decorator overwrites other getters and setters. - */ -export function tuiDefaultProp, K extends keyof T>( - assertion?: TuiBooleanHandler, - ...args: unknown[] -): PropertyDecorator { - return (target, key) => { - const {name} = target.constructor; - const errorGetDefaultMessage = ngDevMode && errorGetDefault(key, name); - const errorSetDefaultMessage = ngDevMode && errorSetDefault(key, name); - - Object.defineProperty(target, key, { - configurable: true, - get(): undefined { - ngDevMode && tuiAssert.assert(false, errorGetDefaultMessage); - - return undefined; - }, - set(this: T, initialValue: T[K]) { - const isValid = initialValue !== undefined; - const errorMessage = ngDevMode && errorSetDefaultInitial(key, name); - let currentValue = initialValue; - - ngDevMode && tuiAssert.assert(isValid, errorMessage); - - if (ngDevMode && isValid && assertion && tuiAssert) { - tuiAssert.assert( - assertion.call(this, initialValue), - `${String(key)} in ${name} received:`, - initialValue, - ...args, - ); - } - - Object.defineProperty(this, key, { - configurable: true, - get(): T[K] { - return currentValue; - }, - set(this: T, value: T[K]) { - const isValid = value !== undefined; - const backupValue = initialValue; - - ngDevMode && - tuiAssert.assert( - isValid, - errorSetDefaultMessage, - String(backupValue), - ); - - if (ngDevMode && isValid && assertion && tuiAssert) { - tuiAssert.assert( - assertion.call(this, value), - `${String(key)} in ${name} received:`, - value, - ...args, - ); - } - - currentValue = isValid ? value : backupValue; - }, - }); - }, - }); - }; -} diff --git a/projects/cdk/decorators/index.ts b/projects/cdk/decorators/index.ts index 9b7bfcd84c19..d18396b9a017 100644 --- a/projects/cdk/decorators/index.ts +++ b/projects/cdk/decorators/index.ts @@ -1,4 +1 @@ -export * from './debounce'; -export * from './default-prop'; export * from './pure'; -export * from './required-setter'; diff --git a/projects/cdk/decorators/required-setter.ts b/projects/cdk/decorators/required-setter.ts deleted file mode 100644 index 21a05fe7e4c2..000000000000 --- a/projects/cdk/decorators/required-setter.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {tuiAssert} from '@taiga-ui/cdk/classes'; -import {TuiBooleanHandler} from '@taiga-ui/cdk/types'; - -function errorSet(key: string | symbol, component: string): string { - return `Undefined was passed as ${String( - key, - )} to ${component}, setter will not be called`; -} - -/** - * @deprecated: - * not compatible with TypeScript 5 - * - * Decorator for checking input setter values against a custom assertion which - * takes value passed to input setter and component instance as arguments. - * It specifically checks for undefined values and prevents calls to the - * original setter in this case. - */ -export function tuiRequiredSetter, K extends keyof T>( - assertion?: TuiBooleanHandler, - ...args: any[] -): MethodDecorator { - return ( - target: Record, - key, - {configurable, enumerable, get, set}: PropertyDescriptor, - ): PropertyDescriptor => { - const {name} = target.constructor; - - return { - configurable, - enumerable, - get, - set(this: T, value: T[K]) { - if (ngDevMode && value !== undefined && assertion && tuiAssert) { - tuiAssert.assert( - assertion.call(this, value), - `${String(key)} in ${name} received:`, - value, - ...args, - ); - } - - if (!set || value === undefined) { - ngDevMode && - tuiAssert.assert(value !== undefined, errorSet(key, name)); - - return; - } - - set.call(this, value); - }, - }; - }; -} diff --git a/projects/cdk/decorators/test/debounce.spec.ts b/projects/cdk/decorators/test/debounce.spec.ts deleted file mode 100644 index 110a35a4156d..000000000000 --- a/projects/cdk/decorators/test/debounce.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {tuiDebounce} from '@taiga-ui/cdk'; - -describe('tuiDebounce', () => { - it('should debounce method calls', done => { - class MyClass { - count = 0; - - @tuiDebounce(100) - method(): void { - this.count++; - } - } - - const instance = new MyClass(); - - expect(instance.count).toBe(0); - - instance.method(); - instance.method(); - instance.method(); - - expect(instance.count).toBe(0); - - setTimeout(() => instance.method(), 50); - - expect(instance.count).toBe(0); - - setTimeout(() => { - expect(instance.count).toBe(1); - - done(); - }, 200); - }); -}); diff --git a/projects/cdk/decorators/test/default-prop-and-pure.spec.ts b/projects/cdk/decorators/test/default-prop-and-pure.spec.ts deleted file mode 100644 index fdaf0629993e..000000000000 --- a/projects/cdk/decorators/test/default-prop-and-pure.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {tuiDefaultProp, tuiPure} from '@taiga-ui/cdk/decorators'; - -describe('@tuiDefaultProp and @tuiPure', () => { - it('Support redefine properties for parent class', () => { - class C { - @tuiDefaultProp() - limitWidth = '__C'; - - constructor(limitWidth: string) { - this.limitWidth = limitWidth; - } - - @tuiPure - get width(): string { - return this.limitWidth; - } - } - - class B extends C { - @tuiDefaultProp() - override limitWidth = '__B'; - - @tuiPure - update(val: string): this { - super.limitWidth = val; // the target is super(parent) class - - return this; - } - - @tuiPure - override get width(): string { - return super.width; // the target is super(parent) class - } - } - - class A extends B { - @tuiPure - override get width(): string { - return super.width; // the target is super(parent) class - } - - @tuiPure - override update(val: string): this { - super.update(val); // the target is super(parent) class - - return this; - } - } - - expect(new A('_A_').limitWidth).toBe('__B'); - expect(new B('_B_').limitWidth).toBe('__B'); - expect(new C('_C_').limitWidth).toBe('_C_'); - - expect(new A('_A_').update('A').width).toBe('A'); - expect(new B('_B_').update('B').width).toBe('B'); - expect(new C('_C_').width).toBe('_C_'); - }); -}); diff --git a/projects/cdk/directives/element/test/element.directive.spec.ts b/projects/cdk/directives/element/test/element.directive.spec.ts deleted file mode 100644 index 2692ca6d04d1..000000000000 --- a/projects/cdk/directives/element/test/element.directive.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {ChangeDetectionStrategy, Component, ElementRef} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {TuiElementModule} from '@taiga-ui/cdk'; -import { - TuiDropdownHostComponent, - TuiDropdownHostModule, -} from '@taiga-ui/cdk/components/dropdown-host'; - -describe('TuiElement directive', () => { - @Component({ - template: ` - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - }) - class TestComponent { - component?: TuiDropdownHostComponent; - element?: ElementRef; - - storeRefs( - component: TuiDropdownHostComponent, - element: ElementRef, - ): void { - this.component = component; - this.element = element; - } - } - - let fixture: ComponentFixture; - let testComponent: TestComponent; - - beforeEach(async () => { - TestBed.configureTestingModule({ - imports: [TuiDropdownHostModule, TuiElementModule], - declarations: [TestComponent], - }); - await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestComponent); - testComponent = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('gets native element behind component', () => { - expect(testComponent.component instanceof TuiDropdownHostComponent).toBe(true); - expect(testComponent.element?.nativeElement instanceof HTMLElement).toBe(true); - expect(testComponent.element?.nativeElement.tagName.toLowerCase()).toBe( - 'tui-dropdown-host', - ); - }); -}); diff --git a/projects/cdk/directives/index.ts b/projects/cdk/directives/index.ts index 1fba4e8d59e6..e468b6f1b375 100644 --- a/projects/cdk/directives/index.ts +++ b/projects/cdk/directives/index.ts @@ -23,7 +23,6 @@ export * from '@taiga-ui/cdk/directives/overscroll'; export * from '@taiga-ui/cdk/directives/pan'; export * from '@taiga-ui/cdk/directives/platform'; export * from '@taiga-ui/cdk/directives/popover'; -export * from '@taiga-ui/cdk/directives/portal'; export * from '@taiga-ui/cdk/directives/pressed'; export * from '@taiga-ui/cdk/directives/prevent-default'; export * from '@taiga-ui/cdk/directives/repeat-times'; diff --git a/projects/cdk/directives/portal/index.ts b/projects/cdk/directives/portal/index.ts deleted file mode 100644 index 0fd0baad8a8f..000000000000 --- a/projects/cdk/directives/portal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './portal.directive'; -export * from './portal.module'; diff --git a/projects/cdk/directives/portal/ng-package.json b/projects/cdk/directives/portal/ng-package.json deleted file mode 100644 index bebf62dcb5e5..000000000000 --- a/projects/cdk/directives/portal/ng-package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lib": { - "entryFile": "index.ts" - } -} diff --git a/projects/cdk/directives/portal/portal.directive.ts b/projects/cdk/directives/portal/portal.directive.ts deleted file mode 100644 index 64eaf451fe99..000000000000 --- a/projects/cdk/directives/portal/portal.directive.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - Directive, - EmbeddedViewRef, - Inject, - Input, - OnDestroy, - TemplateRef, -} from '@angular/core'; -import {TuiDropdownPortalService} from '@taiga-ui/cdk/components/dropdown-host'; - -@Directive({ - selector: '[tuiPortal]', -}) -export class TuiPortalDirective implements OnDestroy { - private viewRef?: EmbeddedViewRef; - - @Input() - set tuiPortal(show: boolean) { - this.viewRef?.destroy(); - - if (show) { - this.viewRef = this.portalService.addTemplate(this.templateRef); - } - } - - constructor( - @Inject(TemplateRef) - private readonly templateRef: TemplateRef>, - @Inject(TuiDropdownPortalService) - private readonly portalService: TuiDropdownPortalService, - ) {} - - ngOnDestroy(): void { - this.viewRef?.destroy(); - } -} diff --git a/projects/cdk/directives/portal/portal.module.ts b/projects/cdk/directives/portal/portal.module.ts deleted file mode 100644 index ea5310884e81..000000000000 --- a/projects/cdk/directives/portal/portal.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {NgModule} from '@angular/core'; - -import {TuiPortalDirective} from './portal.directive'; - -@NgModule({ - declarations: [TuiPortalDirective], - exports: [TuiPortalDirective], -}) -export class TuiPortalModule {} diff --git a/projects/cdk/directives/portal/test/portal.directive.spec.ts b/projects/cdk/directives/portal/test/portal.directive.spec.ts deleted file mode 100644 index 087a230e2298..000000000000 --- a/projects/cdk/directives/portal/test/portal.directive.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {Component, DebugElement} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {By} from '@angular/platform-browser'; -import {TuiDropdownHostModule, TuiPortalModule} from '@taiga-ui/cdk'; - -describe('Portal directive', () => { - @Component({ - template: ` - - -
- Hi -
-
- `, - }) - class TestComponent { - present = true; - show = false; - } - - let fixture: ComponentFixture; - let testComponent: TestComponent; - - beforeEach(async () => { - TestBed.configureTestingModule({ - imports: [CommonModule, TuiPortalModule, TuiDropdownHostModule], - declarations: [TestComponent], - }); - await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestComponent); - testComponent = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("doesn't show template initially", () => { - expect(getPortal()).toBeNull(); - }); - - it('shows template when true is passes', () => { - testComponent.show = true; - fixture.detectChanges(); - - expect(getPortal()).not.toBeNull(); - }); - - it('hides template when directive is destroyed', () => { - testComponent.show = true; - fixture.detectChanges(); - testComponent.present = false; - fixture.detectChanges(); - - expect(getPortal()).toBeNull(); - }); - - function getPortal(): DebugElement | null { - return fixture.debugElement.query(By.css('#test')); - } -}); diff --git a/projects/cdk/index.ts b/projects/cdk/index.ts index be718b16cc2a..0689ae971ba3 100644 --- a/projects/cdk/index.ts +++ b/projects/cdk/index.ts @@ -1,7 +1,6 @@ export * from '@taiga-ui/cdk/abstract'; export * from '@taiga-ui/cdk/classes'; export * from '@taiga-ui/cdk/coercion'; -export * from '@taiga-ui/cdk/components'; export * from '@taiga-ui/cdk/constants'; export * from '@taiga-ui/cdk/date-time'; export * from '@taiga-ui/cdk/decorators'; diff --git a/projects/cdk/schematics/ng-update/v4/steps/constants/identifiers-to-replace.ts b/projects/cdk/schematics/ng-update/v4/steps/constants/identifiers-to-replace.ts index 566c17176fc7..3289102c08dc 100644 --- a/projects/cdk/schematics/ng-update/v4/steps/constants/identifiers-to-replace.ts +++ b/projects/cdk/schematics/ng-update/v4/steps/constants/identifiers-to-replace.ts @@ -98,4 +98,36 @@ export const IDENTIFIERS_TO_REPLACE: ReplacementIdentifier[] = [ moduleSpecifier: '@taiga-ui/cdk', }, }, + { + from: {name: 'TUI_SCROLL_REF', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TUI_SCROLL_REF', moduleSpecifier: '@taiga-ui/core'}, + }, + { + from: {name: 'AbstractTuiPortalHostComponent', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiPortalsComponent', moduleSpecifier: '@taiga-ui/cdk'}, + }, + { + from: {name: 'AbstractTuiPortalService', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiPortalService', moduleSpecifier: '@taiga-ui/cdk'}, + }, + { + from: {name: 'AbstractTuiDialogService', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiPopoverService', moduleSpecifier: '@taiga-ui/cdk'}, + }, + { + from: {name: 'TuiDropdownHostComponent', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiDropdownsComponent', moduleSpecifier: '@taiga-ui/core'}, + }, + { + from: {name: 'TuiDropdownPortalService', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiDropdownService', moduleSpecifier: '@taiga-ui/core'}, + }, + { + from: {name: 'TuiPortalDirective', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiDropdownPortalDirective', moduleSpecifier: '@taiga-ui/core'}, + }, + { + from: {name: 'TuiPortalModule', moduleSpecifier: '@taiga-ui/cdk'}, + to: {name: 'TuiDropdownPortalDirective', moduleSpecifier: '@taiga-ui/core'}, + }, ]; diff --git a/projects/cdk/tokens/index.ts b/projects/cdk/tokens/index.ts index d11599b58df8..98c3bfcf72e5 100644 --- a/projects/cdk/tokens/index.ts +++ b/projects/cdk/tokens/index.ts @@ -18,7 +18,6 @@ export * from './is-webkit'; export * from './platform'; export * from './range'; export * from './removed-element'; -export * from './scroll-ref'; export * from './swipe-options'; export * from './take-only-trusted-events'; export * from './touch-supported'; diff --git a/projects/cdk/tokens/scroll-ref.ts b/projects/cdk/tokens/scroll-ref.ts deleted file mode 100644 index 0c017a2fe044..000000000000 --- a/projects/cdk/tokens/scroll-ref.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {DOCUMENT} from '@angular/common'; -import {ElementRef, inject} from '@angular/core'; -import {tuiCreateTokenFromFactory} from '@taiga-ui/cdk/utils'; - -/** - * Scrollable container - */ -export const TUI_SCROLL_REF = tuiCreateTokenFromFactory( - () => new ElementRef(inject(DOCUMENT).documentElement), -); diff --git a/projects/core/components/dialog/dialogs.component.ts b/projects/core/components/dialog/dialogs.component.ts index 386f5c2ce6a6..fbe8e5b590d3 100644 --- a/projects/core/components/dialog/dialogs.component.ts +++ b/projects/core/components/dialog/dialogs.component.ts @@ -1,11 +1,8 @@ import {CommonModule, DOCUMENT} from '@angular/common'; import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; -import { - TuiFocusTrapModule, - TuiOverscrollModule, - TuiScrollControlsModule, -} from '@taiga-ui/cdk'; +import {TuiFocusTrapModule, TuiOverscrollModule} from '@taiga-ui/cdk'; import {tuiHost} from '@taiga-ui/core/animations'; +import {TuiScrollControlsComponent} from '@taiga-ui/core/components/scroll-controls'; import {PolymorpheusModule} from '@tinkoff/ng-polymorpheus'; import {tap} from 'rxjs'; @@ -17,7 +14,7 @@ import {TUI_DIALOGS} from './dialog.tokens'; imports: [ CommonModule, PolymorpheusModule, - TuiScrollControlsModule, + TuiScrollControlsComponent, TuiFocusTrapModule, TuiOverscrollModule, ], diff --git a/projects/core/components/root/root.module.ts b/projects/core/components/root/root.module.ts index 360ada7a9ad3..0d431487a01a 100644 --- a/projects/core/components/root/root.module.ts +++ b/projects/core/components/root/root.module.ts @@ -1,9 +1,10 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {TuiDropdownHostModule, TuiScrollControlsModule} from '@taiga-ui/cdk'; import {TuiAlertsComponent} from '@taiga-ui/core/components/alert'; import {TuiDialogsComponent} from '@taiga-ui/core/components/dialog'; import {TuiHintsHostModule} from '@taiga-ui/core/components/hints-host'; +import {TuiScrollControlsComponent} from '@taiga-ui/core/components/scroll-controls'; +import {TuiDropdownsComponent} from '@taiga-ui/core/directives/dropdown'; import {TuiSvgDefsHostModule} from '@taiga-ui/core/internal/svg-defs-host'; import {EventPluginsModule} from '@tinkoff/ng-event-plugins'; @@ -13,10 +14,10 @@ import {TuiRootComponent} from './root.component'; imports: [ CommonModule, EventPluginsModule, - TuiDropdownHostModule, + TuiDropdownsComponent, TuiSvgDefsHostModule, TuiHintsHostModule, - TuiScrollControlsModule, + TuiScrollControlsComponent, TuiDialogsComponent, TuiAlertsComponent, ], diff --git a/projects/core/components/root/root.template.html b/projects/core/components/root/root.template.html index d71fe1dff479..68dc63648235 100644 --- a/projects/core/components/root/root.template.html +++ b/projects/core/components/root/root.template.html @@ -3,7 +3,7 @@ class="t-root-scrollbar" > - +
@@ -12,8 +12,7 @@ -
- - + + diff --git a/projects/core/components/scroll-controls/index.ts b/projects/core/components/scroll-controls/index.ts index 1d649b6d68ca..6ff08fb5ba55 100644 --- a/projects/core/components/scroll-controls/index.ts +++ b/projects/core/components/scroll-controls/index.ts @@ -1,3 +1,4 @@ export * from './scroll-controls.component'; -export * from './scroll-controls.module'; +export * from './scroll-ref.directive'; export * from './scrollbar.directive'; +export * from './scrollbar.service'; diff --git a/projects/core/components/scroll-controls/scroll-controls.component.ts b/projects/core/components/scroll-controls/scroll-controls.component.ts index b416f677d1f7..a9d20de34c61 100644 --- a/projects/core/components/scroll-controls/scroll-controls.component.ts +++ b/projects/core/components/scroll-controls/scroll-controls.component.ts @@ -1,51 +1,37 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Inject, - inject, - NgZone, -} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject, NgZone} from '@angular/core'; import {ANIMATION_FRAME} from '@ng-web-apis/common'; -import {TUI_SCROLL_REF, tuiZoneOptimized} from '@taiga-ui/cdk'; +import {tuiZoneOptimized} from '@taiga-ui/cdk'; import {tuiFadeIn} from '@taiga-ui/core/animations'; -import {MODE_PROVIDER} from '@taiga-ui/core/providers'; -import {TUI_ANIMATIONS_SPEED, TUI_MODE} from '@taiga-ui/core/tokens'; -import {TuiBrightness} from '@taiga-ui/core/types'; +import {TUI_ANIMATIONS_SPEED, TUI_SCROLL_REF} from '@taiga-ui/core/tokens'; import {tuiToAnimationOptions} from '@taiga-ui/core/utils'; -import {distinctUntilChanged, map, Observable, startWith, throttleTime} from 'rxjs'; +import {distinctUntilChanged, map, startWith, throttleTime} from 'rxjs'; + +import {TuiScrollbarDirective} from './scrollbar.directive'; @Component({ + standalone: true, selector: 'tui-scroll-controls', + imports: [CommonModule, TuiScrollbarDirective], templateUrl: './scroll-controls.template.html', styleUrls: ['./scroll-controls.style.less'], changeDetection: ChangeDetectionStrategy.OnPush, - providers: [MODE_PROVIDER], - host: { - '($.data-mode.attr)': 'mode$', - }, animations: [tuiFadeIn], }) export class TuiScrollControlsComponent { + private readonly scrollRef = inject(TUI_SCROLL_REF).nativeElement; + readonly options = tuiToAnimationOptions(inject(TUI_ANIMATIONS_SPEED)); - readonly refresh$ = this.animationFrame$.pipe( + readonly refresh$ = inject(ANIMATION_FRAME).pipe( throttleTime(300), map(() => this.scrollbars), startWith([false, false]), distinctUntilChanged((a, b) => a[0] === b[0] && a[1] === b[1]), - tuiZoneOptimized(this.zone), + tuiZoneOptimized(inject(NgZone)), ); - constructor( - @Inject(NgZone) private readonly zone: NgZone, - @Inject(TUI_SCROLL_REF) private readonly scrollRef: ElementRef, - @Inject(ANIMATION_FRAME) private readonly animationFrame$: Observable, - @Inject(TUI_MODE) readonly mode$: Observable, - ) {} - private get scrollbars(): [boolean, boolean] { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = - this.scrollRef.nativeElement; + const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.scrollRef; return [ Math.ceil((clientHeight / scrollHeight) * 100) < 100, diff --git a/projects/core/components/scroll-controls/scroll-controls.module.ts b/projects/core/components/scroll-controls/scroll-controls.module.ts deleted file mode 100644 index 0dcd3164e092..000000000000 --- a/projects/core/components/scroll-controls/scroll-controls.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {TuiLetModule} from '@taiga-ui/cdk'; - -import {TuiScrollControlsComponent} from './scroll-controls.component'; -import {TuiScrollbarDirective} from './scrollbar.directive'; -import {TuiScrollbarWrapperDirective} from './scrollbar-wrapper.directive'; - -/** @deprecated import from `@taiga-ui/cdk` instead */ -@NgModule({ - imports: [CommonModule, TuiLetModule], - declarations: [ - TuiScrollbarDirective, - TuiScrollbarWrapperDirective, - TuiScrollControlsComponent, - ], - exports: [TuiScrollControlsComponent], -}) -export class TuiScrollControlsModule {} diff --git a/projects/core/components/scroll-controls/scroll-controls.style.less b/projects/core/components/scroll-controls/scroll-controls.style.less index 1745d58c5d8c..b210891b1e83 100644 --- a/projects/core/components/scroll-controls/scroll-controls.style.less +++ b/projects/core/components/scroll-controls/scroll-controls.style.less @@ -35,11 +35,11 @@ } &_has-horizontal { - bottom: @scroll-width - @space; + bottom: @scroll-width - 0.25rem; } &_has-vertical { - right: @scroll-width - @space; + right: @scroll-width - 0.25rem; } } @@ -48,14 +48,14 @@ position: absolute; border-radius: 6.25rem; - border: @space solid transparent; + border: 0.25rem solid transparent; cursor: pointer; pointer-events: auto; user-select: none; - background: var(--tui-text-01); + background: currentColor; background-clip: content-box; box-sizing: border-box; - transition-property: width, height; + transition-property: width, height, opacity; opacity: 0.2; &:hover { @@ -66,10 +66,6 @@ opacity: 0.48; } - :host[data-mode='onDark'] & { - background-color: var(--tui-text-01-night); - } - .t-bar_vertical & { right: 0; width: @scroll-width; diff --git a/projects/core/components/scroll-controls/scroll-controls.template.html b/projects/core/components/scroll-controls/scroll-controls.template.html index bb3e20a6f45c..05ebf7df3e9b 100644 --- a/projects/core/components/scroll-controls/scroll-controls.template.html +++ b/projects/core/components/scroll-controls/scroll-controls.template.html @@ -1,7 +1,6 @@
, - @Inject(ANIMATION_FRAME) animationFrame$: Observable, - @Inject(TUI_ELEMENT_REF) private readonly wrapper: ElementRef, - @Inject(TUI_SCROLL_REF) private readonly container: ElementRef, - @Inject(DOCUMENT) private readonly doc: Document, - @Inject(ElementRef) private readonly el: ElementRef, - ) { - const {nativeElement} = this.el; - const mousedown$ = tuiTypedFromEvent(nativeElement, 'mousedown'); - const mousemove$ = tuiTypedFromEvent(this.doc, 'mousemove'); - const mouseup$ = tuiTypedFromEvent(this.doc, 'mouseup'); - const mousedownWrapper$ = tuiTypedFromEvent( - this.wrapper.nativeElement, - 'mousedown', - ); - - merge( - mousedownWrapper$.pipe(map(event => this.getScrolled(event, 0.5, 0.5))), - mousedown$.pipe( - tuiStopPropagation(), - switchMap(event => { - const rect = nativeElement.getBoundingClientRect(); - const vertical = getOffsetVertical(event, rect); - const horizontal = getOffsetHorizontal(event, rect); - - return mousemove$.pipe( - map(event => this.getScrolled(event, vertical, horizontal)), - takeUntil(mouseup$), - ); - }), - ), - ) - .pipe(tuiZonefree(zone), takeUntil(destroy$)) - .subscribe(([scrollTop, scrollLeft]) => { - if (this.tuiScrollbar === 'vertical') { - renderer.setProperty(this.element, 'scrollTop', scrollTop); - } else { - renderer.setProperty(this.element, 'scrollLeft', scrollLeft); - } - }); + private readonly el = inject(TUI_SCROLL_REF).nativeElement; + private readonly style: CSSStyleDeclaration = inject(ElementRef).nativeElement.style; + + protected readonly scrollSub = inject(TuiScrollbarService) + .pipe(takeUntil(inject(TuiDestroyService))) + .subscribe(([top, left]) => { + this.el.style.scrollBehavior = 'auto'; + this.el.scrollTo({top, left}); + this.el.style.scrollBehavior = ''; + }); + + protected readonly styleSub = merge( + inject(ANIMATION_FRAME).pipe(throttleTime(POLLING_TIME)), + tuiScrollFrom(this.el), + ) + .pipe(tuiZonefree(inject(NgZone)), takeUntil(inject(TuiDestroyService))) + .subscribe(() => { + if (this.tuiScrollbar === 'vertical') { + this.style.top = `${this.thumb * 100}%`; + this.style.height = `${this.view * 100}%`; + } else { + this.style.left = `${this.thumb * 100}%`; + this.style.width = `${this.view * 100}%`; + } + }); - merge( - animationFrame$.pipe(throttleTime(POLLING_TIME)), - tuiScrollFrom(this.element), - ) - .pipe(tuiZonefree(zone), takeUntil(destroy$)) - .subscribe(() => { - if (this.tuiScrollbar === 'vertical') { - renderer.setStyle(nativeElement, 'top', `${this.thumb * 100}%`); - renderer.setStyle(nativeElement, 'height', `${this.view * 100}%`); - } else { - renderer.setStyle(nativeElement, 'left', `${this.thumb * 100}%`); - renderer.setStyle(nativeElement, 'width', `${this.view * 100}%`); - } - }); - } + @Input() + tuiScrollbar: 'horizontal' | 'vertical' = 'vertical'; private get scrolled(): number { const { @@ -108,7 +51,7 @@ export class TuiScrollbarDirective { scrollLeft, scrollWidth, clientWidth, - } = this.element; + } = this.el; return this.tuiScrollbar === 'vertical' ? scrollTop / (scrollHeight - clientHeight) @@ -116,7 +59,7 @@ export class TuiScrollbarDirective { } private get compensation(): number { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.element; + const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.el; if ( ((clientHeight * clientHeight) / scrollHeight > MIN_WIDTH && @@ -139,33 +82,10 @@ export class TuiScrollbarDirective { } private get view(): number { - const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.element; + const {clientHeight, scrollHeight, clientWidth, scrollWidth} = this.el; return this.tuiScrollbar === 'vertical' ? Math.ceil((clientHeight / scrollHeight) * 100) / 100 : Math.ceil((clientWidth / scrollWidth) * 100) / 100; } - - private get element(): Element { - return this.container.nativeElement; - } - - private getScrolled( - {clientY, clientX}: MouseEvent, - offsetVertical: number, - offsetHorizontal: number, - ): [number, number] { - const {offsetHeight, offsetWidth} = this.el.nativeElement; - const {top, left, width, height} = - this.wrapper.nativeElement.getBoundingClientRect(); - - const maxTop = this.element.scrollHeight - height; - const maxLeft = this.element.scrollWidth - width; - const scrolledTop = - (clientY - top - offsetHeight * offsetVertical) / (height - offsetHeight); - const scrolledLeft = - (clientX - left - offsetWidth * offsetHorizontal) / (width - offsetWidth); - - return [maxTop * scrolledTop, maxLeft * scrolledLeft]; - } } diff --git a/projects/core/components/scroll-controls/scrollbar.service.ts b/projects/core/components/scroll-controls/scrollbar.service.ts new file mode 100644 index 000000000000..f27cde8a9d77 --- /dev/null +++ b/projects/core/components/scroll-controls/scrollbar.service.ts @@ -0,0 +1,60 @@ +import {ElementRef, inject, Injectable, NgZone} from '@angular/core'; +import {tuiStopPropagation, tuiTypedFromEvent, tuiZonefree} from '@taiga-ui/cdk'; +import {TUI_SCROLL_REF} from '@taiga-ui/core/tokens'; +import {map, merge, Observable, switchMap, takeUntil} from 'rxjs'; + +@Injectable() +export class TuiScrollbarService extends Observable<[number, number]> { + private readonly el: HTMLElement = inject(ElementRef).nativeElement; + private readonly element = inject(TUI_SCROLL_REF).nativeElement; + private readonly scroll$ = merge( + tuiTypedFromEvent(this.el.parentElement!, 'mousedown').pipe( + map(event => this.getScrolled(event, 0.5, 0.5)), + ), + tuiTypedFromEvent(this.el, 'mousedown').pipe( + tuiStopPropagation(), + tuiZonefree(inject(NgZone)), + switchMap(event => { + const {ownerDocument} = this.el; + const rect = this.el.getBoundingClientRect(); + const vertical = getOffsetVertical(event, rect); + const horizontal = getOffsetHorizontal(event, rect); + + return tuiTypedFromEvent(ownerDocument, 'mousemove').pipe( + map(event => this.getScrolled(event, vertical, horizontal)), + takeUntil(tuiTypedFromEvent(ownerDocument, 'mouseup')), + ); + }), + ), + ); + + constructor() { + super(subscriber => this.scroll$.subscribe(subscriber)); + } + + private getScrolled( + {clientY, clientX}: MouseEvent, + offsetY: number, + offsetX: number, + ): [number, number] { + const {offsetHeight, offsetWidth} = this.el; + const {top, left, width, height} = this.el.parentElement!.getBoundingClientRect(); + + const maxTop = this.element.scrollHeight - height; + const maxLeft = this.element.scrollWidth - width; + const scrolledTop = + (clientY - top - offsetHeight * offsetY) / (height - offsetHeight); + const scrolledLeft = + (clientX - left - offsetWidth * offsetX) / (width - offsetWidth); + + return [maxTop * scrolledTop, maxLeft * scrolledLeft]; + } +} + +function getOffsetVertical({clientY}: MouseEvent, {top, height}: DOMRect): number { + return (clientY - top) / height; +} + +function getOffsetHorizontal({clientX}: MouseEvent, {left, width}: DOMRect): number { + return (clientX - left) / width; +} diff --git a/projects/core/components/scrollbar/index.ts b/projects/core/components/scrollbar/index.ts index 01466b797884..6f2e27d38bc3 100644 --- a/projects/core/components/scrollbar/index.ts +++ b/projects/core/components/scrollbar/index.ts @@ -1,4 +1,3 @@ -export * from './scroll-ref.directive'; export * from './scrollable.directive'; export * from './scrollbar.component'; export * from './scrollbar.module'; diff --git a/projects/core/components/scrollbar/scroll-ref.directive.ts b/projects/core/components/scrollbar/scroll-ref.directive.ts deleted file mode 100644 index c2757c5ffd1a..000000000000 --- a/projects/core/components/scrollbar/scroll-ref.directive.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Directive, ElementRef} from '@angular/core'; -import {TUI_SCROLL_REF} from '@taiga-ui/cdk'; - -/** @deprecated import from `@taiga-ui/cdk` instead */ -export const SCROLL_REF_SELECTOR = '[tuiScrollRef]'; - -/** @deprecated import from `@taiga-ui/cdk` instead */ -@Directive({ - selector: SCROLL_REF_SELECTOR, - providers: [ - { - provide: TUI_SCROLL_REF, - useExisting: ElementRef, - }, - ], -}) -export class TuiScrollRefDirective {} diff --git a/projects/core/components/scrollbar/scrollbar.component.ts b/projects/core/components/scrollbar/scrollbar.component.ts index 1dd8bedc74c6..45376adeb27c 100644 --- a/projects/core/components/scrollbar/scrollbar.component.ts +++ b/projects/core/components/scrollbar/scrollbar.component.ts @@ -10,12 +10,12 @@ import { import {CSS as CSS_TOKEN, USER_AGENT} from '@ng-web-apis/common'; import { TUI_IS_IOS, - TUI_SCROLL_REF, tuiGetElementOffset, TuiInjectionTokenType, tuiIsFirefox, } from '@taiga-ui/cdk'; import {TUI_SCROLL_INTO_VIEW, TUI_SCROLLABLE} from '@taiga-ui/core/constants'; +import {TUI_SCROLL_REF} from '@taiga-ui/core/tokens'; // TODO: Remove all legacy code in 4.0 @Component({ diff --git a/projects/core/components/scrollbar/scrollbar.module.ts b/projects/core/components/scrollbar/scrollbar.module.ts index 4ea24c1ddb53..0663296ccc6a 100644 --- a/projects/core/components/scrollbar/scrollbar.module.ts +++ b/projects/core/components/scrollbar/scrollbar.module.ts @@ -1,14 +1,13 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {TuiScrollControlsModule} from '@taiga-ui/cdk'; +import {TuiScrollControlsComponent} from '@taiga-ui/core/components/scroll-controls'; -import {TuiScrollRefDirective} from './scroll-ref.directive'; import {TuiScrollableDirective} from './scrollable.directive'; import {TuiScrollbarComponent} from './scrollbar.component'; @NgModule({ - imports: [CommonModule, TuiScrollControlsModule], - declarations: [TuiScrollbarComponent, TuiScrollRefDirective, TuiScrollableDirective], - exports: [TuiScrollbarComponent, TuiScrollRefDirective, TuiScrollableDirective], + imports: [CommonModule, TuiScrollControlsComponent], + declarations: [TuiScrollbarComponent, TuiScrollableDirective], + exports: [TuiScrollbarComponent, TuiScrollableDirective], }) export class TuiScrollbarModule {} diff --git a/projects/core/directives/dropdown/dropdown-portal.directive.ts b/projects/core/directives/dropdown/dropdown-portal.directive.ts new file mode 100644 index 000000000000..c0dd70c8d8f9 --- /dev/null +++ b/projects/core/directives/dropdown/dropdown-portal.directive.ts @@ -0,0 +1,34 @@ +import { + Directive, + EmbeddedViewRef, + inject, + Input, + OnDestroy, + TemplateRef, +} from '@angular/core'; + +import {TuiDropdownService} from './dropdown.service'; + +@Directive({ + standalone: true, + selector: 'ng-template[tuiDropdown]', +}) +export class TuiDropdownPortalDirective implements OnDestroy { + private readonly template = inject(TemplateRef); + private readonly service = inject(TuiDropdownService); + + private viewRef?: EmbeddedViewRef; + + @Input() + set tuiDropdown(show: boolean) { + this.viewRef?.destroy(); + + if (show) { + this.viewRef = this.service.addTemplate(this.template); + } + } + + ngOnDestroy(): void { + this.viewRef?.destroy(); + } +} diff --git a/projects/core/directives/dropdown/dropdown.directive.ts b/projects/core/directives/dropdown/dropdown.directive.ts index d1230f033217..b78853e8c9d0 100644 --- a/projects/core/directives/dropdown/dropdown.directive.ts +++ b/projects/core/directives/dropdown/dropdown.directive.ts @@ -15,7 +15,6 @@ import { TuiActiveZoneDirective, TuiContext, TuiDestroyService, - TuiDropdownPortalService, tuiPure, } from '@taiga-ui/cdk'; import { @@ -35,6 +34,7 @@ import {Subject, takeUntil, throttleTime} from 'rxjs'; import {TuiDropdownDriverDirective} from './dropdown.driver'; import {TUI_DROPDOWN_COMPONENT} from './dropdown.providers'; +import {TuiDropdownService} from './dropdown.service'; import {TuiDropdownPositionDirective} from './dropdown-position.directive'; @Directive({ @@ -58,7 +58,7 @@ export class TuiDropdownDirective TuiVehicle { private readonly refresh$ = new Subject(); - private readonly service = inject(TuiDropdownPortalService); + private readonly service = inject(TuiDropdownService); private readonly cdr = inject(ChangeDetectorRef); @Input() diff --git a/projects/core/directives/dropdown/dropdown.module.ts b/projects/core/directives/dropdown/dropdown.module.ts index ce9a2b554c67..9d79c241746a 100644 --- a/projects/core/directives/dropdown/dropdown.module.ts +++ b/projects/core/directives/dropdown/dropdown.module.ts @@ -8,6 +8,7 @@ import {TuiDropdownHoverDirective} from './dropdown-hover.directive'; import {TuiDropdownManualDirective} from './dropdown-manual.directive'; import {TuiDropdownOpenDirective} from './dropdown-open.directive'; import {TuiDropdownOptionsDirective} from './dropdown-options.directive'; +import {TuiDropdownPortalDirective} from './dropdown-portal.directive'; import {TuiDropdownPositionDirective} from './dropdown-position.directive'; import {TuiDropdownPositionSidedDirective} from './dropdown-position-sided.directive'; import {TuiDropdownSelectionDirective} from './dropdown-selection.directive'; @@ -18,6 +19,7 @@ import {TuiDropdownSelectionDirective} from './dropdown-selection.directive'; TuiDropdownComponent, TuiDropdownOpenDirective, TuiDropdownOptionsDirective, + TuiDropdownPortalDirective, TuiDropdownDriverDirective, TuiDropdownManualDirective, TuiDropdownHoverDirective, @@ -31,6 +33,7 @@ import {TuiDropdownSelectionDirective} from './dropdown-selection.directive'; TuiDropdownComponent, TuiDropdownOpenDirective, TuiDropdownOptionsDirective, + TuiDropdownPortalDirective, TuiDropdownDriverDirective, TuiDropdownManualDirective, TuiDropdownHoverDirective, diff --git a/projects/core/directives/dropdown/dropdown.service.ts b/projects/core/directives/dropdown/dropdown.service.ts new file mode 100644 index 000000000000..97089e9d402f --- /dev/null +++ b/projects/core/directives/dropdown/dropdown.service.ts @@ -0,0 +1,5 @@ +import {Injectable} from '@angular/core'; +import {TuiPortalService} from '@taiga-ui/cdk'; + +@Injectable({providedIn: 'root'}) +export class TuiDropdownService extends TuiPortalService {} diff --git a/projects/core/directives/dropdown/dropdowns.component.ts b/projects/core/directives/dropdown/dropdowns.component.ts new file mode 100644 index 000000000000..1c65078a0454 --- /dev/null +++ b/projects/core/directives/dropdown/dropdowns.component.ts @@ -0,0 +1,28 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {tuiAsPortal, TuiPortalsComponent} from '@taiga-ui/cdk'; + +import {TuiDropdownService} from './dropdown.service'; + +/** + * Host element for dynamically created portals, for example using {@link TuiDropdownDirective}. + */ +@Component({ + standalone: true, + selector: 'tui-dropdowns', + template: ` + + + `, + styles: [ + ` + :host { + display: block; + height: 100%; + isolation: isolate; + } + `, + ], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [tuiAsPortal(TuiDropdownService)], +}) +export class TuiDropdownsComponent extends TuiPortalsComponent {} diff --git a/projects/core/directives/dropdown/index.ts b/projects/core/directives/dropdown/index.ts index 8dd12ede2f1f..ffe722118e13 100644 --- a/projects/core/directives/dropdown/index.ts +++ b/projects/core/directives/dropdown/index.ts @@ -3,12 +3,15 @@ export * from './dropdown.directive'; export * from './dropdown.driver'; export * from './dropdown.module'; export * from './dropdown.providers'; +export * from './dropdown.service'; export * from './dropdown-context.directive'; export * from './dropdown-hover.directive'; export * from './dropdown-hover.options'; export * from './dropdown-manual.directive'; export * from './dropdown-open.directive'; export * from './dropdown-options.directive'; +export * from './dropdown-portal.directive'; export * from './dropdown-position.directive'; export * from './dropdown-position-sided.directive'; export * from './dropdown-selection.directive'; +export * from './dropdowns.component'; diff --git a/projects/core/tokens/scroll-ref.ts b/projects/core/tokens/scroll-ref.ts index e1b10093e364..8885c3bcc6cb 100644 --- a/projects/core/tokens/scroll-ref.ts +++ b/projects/core/tokens/scroll-ref.ts @@ -1,6 +1,7 @@ -import {TUI_SCROLL_REF as TOKEN} from '@taiga-ui/cdk'; +import {DOCUMENT} from '@angular/common'; +import {ElementRef, inject} from '@angular/core'; +import {tuiCreateTokenFromFactory} from '@taiga-ui/cdk'; -/** - * @deprecated import from `@taiga-ui/cdk` instead - */ -export const TUI_SCROLL_REF = TOKEN; +export const TUI_SCROLL_REF = tuiCreateTokenFromFactory( + () => new ElementRef(inject(DOCUMENT).documentElement), +); diff --git a/projects/demo/src/modules/app/app.routes.ts b/projects/demo/src/modules/app/app.routes.ts index 64763dcdcbab..d5b2bf79f337 100644 --- a/projects/demo/src/modules/app/app.routes.ts +++ b/projects/demo/src/modules/app/app.routes.ts @@ -1570,14 +1570,6 @@ export const ROUTES: Routes = [ title: 'Pan', }, }, - { - path: 'directives/portal', - loadChildren: async () => - (await import('../directives/portal/portal.module')).ExampleTuiPortalModule, - data: { - title: 'Portal', - }, - }, { path: 'directives/resizer', loadChildren: async () => diff --git a/projects/demo/src/modules/app/home/examples/app-template.md b/projects/demo/src/modules/app/home/examples/app-template.md index ee406acb4dbf..6d8cadc8df96 100644 --- a/projects/demo/src/modules/app/home/examples/app-template.md +++ b/projects/demo/src/modules/app/home/examples/app-template.md @@ -14,7 +14,7 @@ - + diff --git a/projects/demo/src/modules/app/pages.ts b/projects/demo/src/modules/app/pages.ts index 1f959bdd38a6..11b27a864164 100644 --- a/projects/demo/src/modules/app/pages.ts +++ b/projects/demo/src/modules/app/pages.ts @@ -1340,12 +1340,6 @@ export const pages: TuiDocPages = [ keywords: 'pan, panning, панарамирование, пан', route: '/directives/pan', }, - { - section: 'Tools', - title: 'Portal', - keywords: 'template, шаблон, портал', - route: '/directives/portal', - }, { section: 'Tools', title: 'Present', diff --git a/projects/demo/src/modules/components/app-bar/app-bar.module.ts b/projects/demo/src/modules/components/app-bar/app-bar.module.ts index 3ac70ec9d360..f42efe272e92 100644 --- a/projects/demo/src/modules/components/app-bar/app-bar.module.ts +++ b/projects/demo/src/modules/components/app-bar/app-bar.module.ts @@ -3,7 +3,6 @@ import {NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; import {TuiAppBarModule} from '@taiga-ui/addon-mobile'; -import {TuiPortalModule} from '@taiga-ui/cdk'; import {TuiButtonModule, TuiLabelModule, TuiNotificationModule} from '@taiga-ui/core'; import {TuiProgressModule} from '@taiga-ui/kit'; @@ -18,7 +17,6 @@ import {TuiAppBarExample1} from './examples/1'; TuiLabelModule, TuiNotificationModule, TuiAppBarModule, - TuiPortalModule, TuiAddonDocModule, RouterModule.forChild(tuiGenerateRoutes(ExampleTuiAppBarComponent)), ], diff --git a/projects/demo/src/modules/components/dialog/examples/4/index.ts b/projects/demo/src/modules/components/dialog/examples/4/index.ts index e279a529027b..35a0d7d22095 100644 --- a/projects/demo/src/modules/components/dialog/examples/4/index.ts +++ b/projects/demo/src/modules/components/dialog/examples/4/index.ts @@ -1,8 +1,8 @@ import {Component, Inject, TemplateRef} from '@angular/core'; import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; -import {tuiClamp, TuiDropdownPortalService} from '@taiga-ui/cdk'; -import {TuiDialogService} from '@taiga-ui/core'; +import {tuiClamp} from '@taiga-ui/cdk'; +import {TuiDialogService, TuiDropdownService} from '@taiga-ui/core'; import {PolymorpheusContent} from '@tinkoff/ng-polymorpheus'; @Component({ @@ -19,8 +19,7 @@ export class TuiDialogExampleComponent4 { constructor( @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, - @Inject(TuiDropdownPortalService) - private readonly portalService: TuiDropdownPortalService, + @Inject(TuiDropdownService) private readonly dropdowns: TuiDropdownService, ) {} get transform(): string { @@ -48,11 +47,11 @@ export class TuiDialogExampleComponent4 { content: PolymorpheusContent, button: TemplateRef>, ): void { - const templateRef = this.portalService.addTemplate(button); + const templateRef = this.dropdowns.addTemplate(button); this.dialogs.open(content).subscribe({ complete: () => { - this.portalService.removeTemplate(templateRef); + this.dropdowns.removeTemplate(templateRef); }, }); } diff --git a/projects/demo/src/modules/components/tab-bar/tab-bar.module.ts b/projects/demo/src/modules/components/tab-bar/tab-bar.module.ts index 5b2d2a327381..adb3a1422bf0 100644 --- a/projects/demo/src/modules/components/tab-bar/tab-bar.module.ts +++ b/projects/demo/src/modules/components/tab-bar/tab-bar.module.ts @@ -4,8 +4,7 @@ import {FormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; import {TuiTabBarModule} from '@taiga-ui/addon-mobile'; -import {TuiPortalModule} from '@taiga-ui/cdk'; -import {TuiButtonModule, TuiNotificationModule} from '@taiga-ui/core'; +import {TuiButtonModule, TuiDropdownModule, TuiNotificationModule} from '@taiga-ui/core'; import {TuiCheckboxLabeledModule} from '@taiga-ui/kit'; import {TuiTabBarExample1} from './examples/1'; @@ -18,11 +17,11 @@ import {ExampleTuiTabBarComponent} from './tab-bar.component'; imports: [ CommonModule, FormsModule, + TuiDropdownModule, TuiButtonModule, TuiNotificationModule, TuiCheckboxLabeledModule, TuiTabBarModule, - TuiPortalModule, TuiAddonDocModule, RouterModule.forChild(tuiGenerateRoutes(ExampleTuiTabBarComponent)), ], diff --git a/projects/demo/src/modules/components/tab-bar/tab-bar.template.html b/projects/demo/src/modules/components/tab-bar/tab-bar.template.html index 267e06b9f30b..730f88029215 100644 --- a/projects/demo/src/modules/components/tab-bar/tab-bar.template.html +++ b/projects/demo/src/modules/components/tab-bar/tab-bar.template.html @@ -22,9 +22,10 @@ *ngIf="!fixed" class="bar" > - - - +

Create your own portal service by extending - AbstractTuiPortalService + TuiPortalService

+ + + + diff --git a/projects/demo/src/modules/directives/dropdown/examples/2/index.ts b/projects/demo/src/modules/directives/dropdown/examples/2/index.ts index bab803340f3d..0abd045cc699 100644 --- a/projects/demo/src/modules/directives/dropdown/examples/2/index.ts +++ b/projects/demo/src/modules/directives/dropdown/examples/2/index.ts @@ -12,14 +12,5 @@ import {assets} from '@demo/utils'; }) export class TuiDropdownExample2 { open = false; - avatarUrl = assets`/images/avatar.jpg`; - - onMouseEnter(): void { - this.open = true; - } - - onMouseLeave(): void { - this.open = false; - } } diff --git a/projects/demo/src/modules/directives/dropdown/examples/5/index.html b/projects/demo/src/modules/directives/dropdown/examples/5/index.html new file mode 100644 index 000000000000..c2e4c61772a3 --- /dev/null +++ b/projects/demo/src/modules/directives/dropdown/examples/5/index.html @@ -0,0 +1,16 @@ + + diff --git a/projects/demo/src/modules/directives/dropdown/examples/5/index.less b/projects/demo/src/modules/directives/dropdown/examples/5/index.less new file mode 100644 index 000000000000..d43e7c98d03f --- /dev/null +++ b/projects/demo/src/modules/directives/dropdown/examples/5/index.less @@ -0,0 +1,6 @@ +@import '@taiga-ui/core/styles/taiga-ui-local'; + +.dropdown { + .center-all(); + position: fixed; +} diff --git a/projects/demo/src/modules/directives/portal/examples/1/index.ts b/projects/demo/src/modules/directives/dropdown/examples/5/index.ts similarity index 75% rename from projects/demo/src/modules/directives/portal/examples/1/index.ts rename to projects/demo/src/modules/directives/dropdown/examples/5/index.ts index 6cd627fda608..5b679564b1c4 100644 --- a/projects/demo/src/modules/directives/portal/examples/1/index.ts +++ b/projects/demo/src/modules/directives/dropdown/examples/5/index.ts @@ -3,12 +3,12 @@ import {changeDetection} from '@demo/emulate/change-detection'; import {encapsulation} from '@demo/emulate/encapsulation'; @Component({ - selector: 'tui-portal-example-1', + selector: 'tui-dropdown-example-5', templateUrl: './index.html', styleUrls: ['./index.less'], encapsulation, changeDetection, }) -export class TuiPortalExample1 { - show = false; +export class TuiDropdownExample5 { + open = false; } diff --git a/projects/demo/src/modules/directives/portal/examples/1/index.html b/projects/demo/src/modules/directives/portal/examples/1/index.html deleted file mode 100644 index 8d00ff777243..000000000000 --- a/projects/demo/src/modules/directives/portal/examples/1/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - -
- Hey, Joe -
diff --git a/projects/demo/src/modules/directives/portal/examples/1/index.less b/projects/demo/src/modules/directives/portal/examples/1/index.less deleted file mode 100644 index 20b26ef88f95..000000000000 --- a/projects/demo/src/modules/directives/portal/examples/1/index.less +++ /dev/null @@ -1,10 +0,0 @@ -@import '@taiga-ui/core/styles/taiga-ui-local'; - -.portal { - box-shadow: var(--tui-shadow-dropdown); - .center-all(); - position: fixed; - padding: 1.5rem; - border-radius: var(--tui-radius-l); - background: var(--tui-base-01); -} diff --git a/projects/demo/src/modules/directives/portal/examples/import/import-module.md b/projects/demo/src/modules/directives/portal/examples/import/import-module.md deleted file mode 100644 index 32690613da38..000000000000 --- a/projects/demo/src/modules/directives/portal/examples/import/import-module.md +++ /dev/null @@ -1,12 +0,0 @@ -```ts -import {TuiPortalModule} from '@taiga-ui/cdk'; - -@NgModule({ - imports: [ - // ... - TuiPortalModule, - ], - // ... -}) -export class MyModule {} -``` diff --git a/projects/demo/src/modules/directives/portal/examples/import/insert-template.md b/projects/demo/src/modules/directives/portal/examples/import/insert-template.md deleted file mode 100644 index 2fcf27457e10..000000000000 --- a/projects/demo/src/modules/directives/portal/examples/import/insert-template.md +++ /dev/null @@ -1,7 +0,0 @@ -```html -
- I will be shown above all content when - show - is set to true -
-``` diff --git a/projects/demo/src/modules/directives/portal/portal.component.ts b/projects/demo/src/modules/directives/portal/portal.component.ts deleted file mode 100644 index bf8772597214..000000000000 --- a/projects/demo/src/modules/directives/portal/portal.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Component} from '@angular/core'; -import {changeDetection} from '@demo/emulate/change-detection'; -import {TuiDocExample} from '@taiga-ui/addon-doc'; - -@Component({ - selector: 'example-tui-portal', - templateUrl: './portal.template.html', - changeDetection, -}) -export class ExampleTuiPortalComponent { - readonly example1: TuiDocExample = { - TypeScript: import('./examples/1/index.ts?raw'), - HTML: import('./examples/1/index.html?raw'), - LESS: import('./examples/1/index.less?raw'), - }; - - readonly exampleModule = import('./examples/import/import-module.md?raw'); - readonly exampleHtml = import('./examples/import/insert-template.md?raw'); -} diff --git a/projects/demo/src/modules/directives/portal/portal.module.ts b/projects/demo/src/modules/directives/portal/portal.module.ts deleted file mode 100644 index 18201a3b691f..000000000000 --- a/projects/demo/src/modules/directives/portal/portal.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; -import {TuiPortalModule} from '@taiga-ui/cdk'; -import {TuiToggleModule} from '@taiga-ui/kit'; - -import {TuiPortalExample1} from './examples/1'; -import {ExampleTuiPortalComponent} from './portal.component'; - -@NgModule({ - imports: [ - FormsModule, - TuiToggleModule, - TuiPortalModule, - TuiAddonDocModule, - RouterModule.forChild(tuiGenerateRoutes(ExampleTuiPortalComponent)), - ], - declarations: [ExampleTuiPortalComponent, TuiPortalExample1], - exports: [ExampleTuiPortalComponent], -}) -export class ExampleTuiPortalModule {} diff --git a/projects/demo/src/modules/directives/portal/portal.template.html b/projects/demo/src/modules/directives/portal/portal.template.html deleted file mode 100644 index f860ab92df41..000000000000 --- a/projects/demo/src/modules/directives/portal/portal.template.html +++ /dev/null @@ -1,43 +0,0 @@ - - -

Directive to show a template in a portal host above all content, similar to dropdowns.

- - - - -
- - -
    -
  1. -

    - Import - TuiPortalModule - into a module where you want to use our component -

    - - -
  2. - -
  3. -

    Add to the template:

    - - -
  4. -
-
-