diff --git a/projects/demo/src/modules/components/card-large/examples/14/index.html b/projects/demo/src/modules/components/card-large/examples/14/index.html
new file mode 100644
index 000000000000..99a016446555
--- /dev/null
+++ b/projects/demo/src/modules/components/card-large/examples/14/index.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Leave your feedback
+ It will only take 3 minutes
+
+
+
+
+ Why so?
+
+ Leave a few words
+
+
+
+
+
+
diff --git a/projects/demo/src/modules/components/card-large/examples/14/index.less b/projects/demo/src/modules/components/card-large/examples/14/index.less
new file mode 100644
index 000000000000..cb83f2063e20
--- /dev/null
+++ b/projects/demo/src/modules/components/card-large/examples/14/index.less
@@ -0,0 +1,18 @@
+.popover {
+ position: fixed;
+ right: 1.5rem;
+ bottom: 1.5rem;
+ inline-size: 20rem;
+}
+
+.close {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.75rem;
+}
+
+.footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.5rem;
+}
diff --git a/projects/demo/src/modules/components/card-large/examples/14/index.ts b/projects/demo/src/modules/components/card-large/examples/14/index.ts
new file mode 100644
index 000000000000..712c7a3ec17b
--- /dev/null
+++ b/projects/demo/src/modules/components/card-large/examples/14/index.ts
@@ -0,0 +1,42 @@
+import {NgIf} from '@angular/common';
+import {Component, signal} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {changeDetection} from '@demo/emulate/change-detection';
+import {encapsulation} from '@demo/emulate/encapsulation';
+import {TuiAutoFocus} from '@taiga-ui/cdk';
+import {TuiAppearance, TuiButton, TuiPopup, TuiTitle} from '@taiga-ui/core';
+import {TuiRating} from '@taiga-ui/kit';
+import {TuiCardLarge} from '@taiga-ui/layout';
+import {TuiTextareaModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';
+
+@Component({
+ standalone: true,
+ imports: [
+ FormsModule,
+ NgIf,
+ TuiAppearance,
+ TuiAutoFocus,
+ TuiButton,
+ TuiCardLarge,
+ TuiPopup,
+ TuiRating,
+ TuiTextareaModule,
+ TuiTextfieldControllerModule,
+ TuiTitle,
+ ],
+ templateUrl: './index.html',
+ styleUrls: ['./index.less'],
+ encapsulation,
+ changeDetection,
+})
+export default class Example {
+ protected readonly step = signal(0);
+ protected rating = 0;
+ protected comment = '';
+
+ protected close(): void {
+ this.rating = 0;
+ this.comment = '';
+ this.step.set(0);
+ }
+}
diff --git a/projects/demo/src/modules/components/card-large/index.html b/projects/demo/src/modules/components/card-large/index.html
index 59ae6a8dea64..4f26feb158f3 100644
--- a/projects/demo/src/modules/components/card-large/index.html
+++ b/projects/demo/src/modules/components/card-large/index.html
@@ -8,8 +8,8 @@
diff --git a/projects/demo/src/modules/components/card-large/index.ts b/projects/demo/src/modules/components/card-large/index.ts
index 3609d242bb92..0ed0df9229f4 100644
--- a/projects/demo/src/modules/components/card-large/index.ts
+++ b/projects/demo/src/modules/components/card-large/index.ts
@@ -23,5 +23,6 @@ export default class Example {
'Image-dark',
'Paddings and radii',
'Map',
+ 'In portal',
];
}
diff --git a/projects/demo/src/modules/components/pulse/examples/2/index.html b/projects/demo/src/modules/components/pulse/examples/2/index.html
new file mode 100644
index 000000000000..362ac08803c8
--- /dev/null
+++ b/projects/demo/src/modules/components/pulse/examples/2/index.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+ You can have images!
+
Or any content really
+
+
+
+
+
+
+
diff --git a/projects/demo/src/modules/components/pulse/examples/2/index.less b/projects/demo/src/modules/components/pulse/examples/2/index.less
new file mode 100644
index 000000000000..632a48e6f81d
--- /dev/null
+++ b/projects/demo/src/modules/components/pulse/examples/2/index.less
@@ -0,0 +1,25 @@
+:host {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.step-1 {
+ position: absolute;
+ top: 20%;
+ right: 10%;
+}
+
+.step-2 {
+ position: absolute;
+ top: 25%;
+ right: 25%;
+}
+
+.avatar {
+ inline-size: 100%;
+ block-size: 12rem;
+ object-fit: cover;
+ object-position: top;
+}
diff --git a/projects/demo/src/modules/components/pulse/examples/2/index.ts b/projects/demo/src/modules/components/pulse/examples/2/index.ts
new file mode 100644
index 000000000000..b6a197eb77d1
--- /dev/null
+++ b/projects/demo/src/modules/components/pulse/examples/2/index.ts
@@ -0,0 +1,20 @@
+import {NgIf} from '@angular/common';
+import {Component, signal} from '@angular/core';
+import {changeDetection} from '@demo/emulate/change-detection';
+import {encapsulation} from '@demo/emulate/encapsulation';
+import {TuiButton, TuiHint, TuiTitle} from '@taiga-ui/core';
+import {tuiProvideExperimentalHint} from '@taiga-ui/experimental';
+import {TuiAvatar, TuiPulse} from '@taiga-ui/kit';
+
+@Component({
+ standalone: true,
+ imports: [NgIf, TuiAvatar, TuiButton, TuiHint, TuiPulse, TuiTitle],
+ templateUrl: './index.html',
+ styleUrls: ['./index.less'],
+ encapsulation,
+ changeDetection,
+ providers: [tuiProvideExperimentalHint()],
+})
+export default class Example {
+ protected readonly step = signal(0);
+}
diff --git a/projects/demo/src/modules/components/pulse/index.html b/projects/demo/src/modules/components/pulse/index.html
index 1999b2484986..f6070482f2a5 100644
--- a/projects/demo/src/modules/components/pulse/index.html
+++ b/projects/demo/src/modules/components/pulse/index.html
@@ -5,10 +5,11 @@
>
diff --git a/projects/demo/src/modules/components/pulse/index.ts b/projects/demo/src/modules/components/pulse/index.ts
index a6af1afa2f68..9a3726678d4a 100644
--- a/projects/demo/src/modules/components/pulse/index.ts
+++ b/projects/demo/src/modules/components/pulse/index.ts
@@ -8,4 +8,6 @@ import {TuiDemo} from '@demo/utils';
templateUrl: './index.html',
changeDetection,
})
-export default class Page {}
+export default class Page {
+ protected readonly examples = ['Basic', 'Popover'];
+}
diff --git a/projects/demo/src/modules/components/push/examples/3/index.ts b/projects/demo/src/modules/components/push/examples/3/index.ts
index 5c011e8e350f..a5065f00bf87 100644
--- a/projects/demo/src/modules/components/push/examples/3/index.ts
+++ b/projects/demo/src/modules/components/push/examples/3/index.ts
@@ -1,12 +1,12 @@
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
-import {TuiButton, TuiIcon} from '@taiga-ui/core';
-import {TuiPush, TuiPushDirective} from '@taiga-ui/kit';
+import {TuiButton, TuiIcon, TuiLink} from '@taiga-ui/core';
+import {TuiPush} from '@taiga-ui/kit';
@Component({
standalone: true,
- imports: [TuiButton, TuiIcon, TuiPush, TuiPushDirective],
+ imports: [TuiButton, TuiIcon, TuiLink, TuiPush],
templateUrl: './index.html',
encapsulation,
changeDetection,
diff --git a/projects/demo/src/modules/components/push/index.html b/projects/demo/src/modules/components/push/index.html
index a913aedcc0c0..19219fa90b1d 100644
--- a/projects/demo/src/modules/components/push/index.html
+++ b/projects/demo/src/modules/components/push/index.html
@@ -7,24 +7,11 @@
Notifications in style of native browser push
-
-
-
-
diff --git a/projects/demo/src/modules/components/push/index.ts b/projects/demo/src/modules/components/push/index.ts
index 20a720f87bd7..ebcde2176b73 100644
--- a/projects/demo/src/modules/components/push/index.ts
+++ b/projects/demo/src/modules/components/push/index.ts
@@ -14,6 +14,7 @@ import {TuiPush} from '@taiga-ui/kit';
changeDetection,
})
export default class Page {
+ protected readonly examples = ['Basic', 'Service', 'Directive'];
protected heading = '';
protected type = '';
diff --git a/projects/experimental/components/hint/hint.component.ts b/projects/experimental/components/hint/hint.component.ts
new file mode 100644
index 000000000000..c8d788546ebf
--- /dev/null
+++ b/projects/experimental/components/hint/hint.component.ts
@@ -0,0 +1,151 @@
+import type {Provider} from '@angular/core';
+import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {EMPTY_CLIENT_RECT} from '@taiga-ui/cdk/constants';
+import {TuiHoveredService} from '@taiga-ui/cdk/directives/hovered';
+import {TUI_IS_MOBILE} from '@taiga-ui/cdk/tokens';
+import type {TuiContext} from '@taiga-ui/cdk/types';
+import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
+import {tuiClamp} from '@taiga-ui/cdk/utils/math';
+import {tuiPure, tuiPx} from '@taiga-ui/cdk/utils/miscellaneous';
+import {tuiFadeIn, tuiScaleIn} from '@taiga-ui/core/animations';
+import {TuiRectAccessor} from '@taiga-ui/core/classes';
+import {tuiButtonOptionsProvider} from '@taiga-ui/core/components/button';
+import {TuiAppearance, tuiAppearance} from '@taiga-ui/core/directives/appearance';
+import type {TuiHintDirective} from '@taiga-ui/core/directives/hint';
+import {
+ TUI_HINT_COMPONENT,
+ TUI_HINT_PROVIDERS,
+ TuiHintHover,
+ TuiHintPointer,
+ TuiHintUnstyledComponent,
+} from '@taiga-ui/core/directives/hint';
+import {TuiPositionService, TuiVisualViewportService} from '@taiga-ui/core/services';
+import {TUI_ANIMATIONS_SPEED, TUI_VIEWPORT} from '@taiga-ui/core/tokens';
+import {tuiIsObscured, tuiToAnimationOptions} from '@taiga-ui/core/utils';
+import {injectContext, PolymorpheusOutlet} from '@taiga-ui/polymorpheus';
+import {map, takeWhile} from 'rxjs';
+
+const GAP = 8;
+
+@Component({
+ standalone: true,
+ selector: 'tui-hint',
+ imports: [PolymorpheusOutlet],
+ template: `
+
+
+ `,
+ styleUrls: ['./hint.style.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [TUI_HINT_PROVIDERS, tuiButtonOptionsProvider({size: 's'})],
+ animations: [tuiFadeIn, tuiScaleIn],
+ hostDirectives: [TuiAppearance],
+ host: {
+ '[@tuiScaleIn]': 'isMobile ? options : dummy',
+ '[@tuiFadeIn]': 'options',
+ '[class._untouchable]': 'pointer',
+ '[class._mobile]': 'isMobile',
+ '[attr.tuiTheme]': 'theme',
+ '(document:click)': 'onClick($event.target)',
+ },
+})
+export class TuiHintComponent {
+ private readonly el = tuiInjectElement();
+ private readonly hover = inject(TuiHintHover);
+ private readonly vvs = inject(TuiVisualViewportService);
+ private readonly viewport = inject(TUI_VIEWPORT);
+
+ protected readonly dummy = {value: '', params: {end: 1, start: 1}};
+ protected readonly options = tuiToAnimationOptions(
+ inject(TUI_ANIMATIONS_SPEED),
+ 'cubic-bezier(0.35, 1.3, 0.25, 1)',
+ );
+
+ protected readonly pointer = inject(TuiHintPointer, {optional: true});
+ protected readonly accessor = inject(TuiRectAccessor);
+ protected readonly hint = injectContext>>().$implicit;
+ protected readonly isMobile = inject(TUI_IS_MOBILE);
+
+ protected readonly content =
+ this.hint.component.component === TuiHintUnstyledComponent
+ ? signal('')
+ : this.hint.content;
+
+ protected readonly theme = this.hint.el
+ .closest('[tuiTheme]')
+ ?.getAttribute('tuiTheme');
+
+ protected readonly appearance = tuiAppearance(this.hint.appearance);
+
+ constructor() {
+ inject(TuiPositionService)
+ .pipe(
+ takeWhile(() => this.hint.el.isConnected),
+ map((point) => this.vvs.correct(point)),
+ takeUntilDestroyed(),
+ )
+ .subscribe({
+ next: ([top, left]) => this.update(top, left),
+ complete: () => this.hover.toggle(false),
+ });
+
+ inject(TuiHoveredService)
+ .pipe(takeUntilDestroyed())
+ .subscribe((hover) => this.hover.toggle(hover));
+ }
+
+ protected onClick(target: HTMLElement): void {
+ if (
+ (!target.closest('tui-hint') && !this.hint.el.contains(target)) ||
+ tuiIsObscured(this.hint.el)
+ ) {
+ this.hover.toggle(false);
+ }
+ }
+
+ @tuiPure
+ private apply(top: string, left: string, beakTop: number, beakLeft: number): void {
+ this.el.style.top = top;
+ this.el.style.left = left;
+ this.el.style.setProperty('--top', `${beakTop}%`);
+ this.el.style.setProperty('--left', `${beakLeft}%`);
+ this.el.style.setProperty(
+ '--rotate',
+ !beakLeft || Math.ceil(beakLeft) === 100 ? '90deg' : '0deg',
+ );
+ }
+
+ private update(top: number, left: number): void {
+ const {clientHeight, clientWidth} = this.el;
+ const rect = this.accessor.getClientRect();
+ const viewport = this.viewport.getClientRect();
+
+ if (rect === EMPTY_CLIENT_RECT || !clientHeight || !clientWidth) {
+ return;
+ }
+
+ const safeLeft = tuiClamp(left, GAP, viewport.width - clientWidth - GAP);
+ const [beakTop, beakLeft] = this.vvs.correct([
+ rect.top + rect.height / 2 - top,
+ rect.left + rect.width / 2 - safeLeft,
+ ]);
+
+ this.apply(
+ tuiPx(Math.round(top)),
+ tuiPx(Math.round(safeLeft)),
+ Math.round((tuiClamp(beakTop, 0, clientHeight) / clientHeight) * 100),
+ Math.round((tuiClamp(beakLeft, 0, clientWidth) / clientWidth) * 100),
+ );
+ }
+}
+
+export function tuiProvideExperimentalHint(): Provider {
+ return {
+ provide: TUI_HINT_COMPONENT,
+ useValue: TuiHintComponent,
+ };
+}
diff --git a/projects/experimental/components/hint/hint.style.less b/projects/experimental/components/hint/hint.style.less
new file mode 100644
index 000000000000..ec30b55d62c7
--- /dev/null
+++ b/projects/experimental/components/hint/hint.style.less
@@ -0,0 +1,77 @@
+@import '@taiga-ui/core/styles/taiga-ui-local';
+
+:host {
+ position: absolute;
+ max-inline-size: 20rem;
+ padding: 0.75rem 1rem;
+ background: var(--tui-background-accent-1);
+ border-radius: var(--tui-radius-l);
+ color: var(--tui-text-primary-on-accent-1);
+ box-sizing: border-box;
+ font: var(--tui-font-text-s);
+ white-space: pre-line;
+ overflow-wrap: break-word;
+ transform-origin: var(--left) var(--top);
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: var(--top);
+ left: var(--left);
+ inline-size: 0.75rem;
+ block-size: 0.5rem;
+ background: inherit;
+ mask-image: url('data:image/svg+xml,');
+ transform: translate(-50%, -50%) rotate(var(--rotate));
+ }
+
+ &._mobile {
+ font: var(--tui-font-text-m);
+
+ &::before {
+ inline-size: 1.5rem;
+ block-size: 1.125rem;
+ mask-image: url('data:image/svg+xml,');
+ }
+ }
+
+ &:not([style*='top']) {
+ visibility: hidden;
+ }
+
+ &._untouchable {
+ pointer-events: none;
+ }
+
+ & ::ng-deep > {
+ [tuiTitle] {
+ margin-block-end: 0.75rem;
+
+ [tuiSubtitle] {
+ color: var(--tui-text-secondary);
+ }
+
+ + footer {
+ margin-block-start: 0.75rem;
+ }
+ }
+
+ [tuiIconButton][data-appearance='icon'][data-size='xs'] {
+ float: inline-end;
+ margin-inline-end: -0.25rem;
+ }
+
+ img {
+ display: block;
+ border-radius: var(--tui-radius-m);
+ }
+
+ footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.5rem;
+ inline-size: 18rem;
+ margin: 1rem 0 0.25rem;
+ }
+ }
+}
diff --git a/projects/experimental/components/hint/index.ts b/projects/experimental/components/hint/index.ts
new file mode 100644
index 000000000000..ce0f5a011255
--- /dev/null
+++ b/projects/experimental/components/hint/index.ts
@@ -0,0 +1 @@
+export * from './hint.component';
diff --git a/projects/experimental/components/hint/ng-package.json b/projects/experimental/components/hint/ng-package.json
new file mode 100644
index 000000000000..bebf62dcb5e5
--- /dev/null
+++ b/projects/experimental/components/hint/ng-package.json
@@ -0,0 +1,5 @@
+{
+ "lib": {
+ "entryFile": "index.ts"
+ }
+}
diff --git a/projects/experimental/components/index.ts b/projects/experimental/components/index.ts
index 46e223f30f9f..85cf6ff4fb1b 100644
--- a/projects/experimental/components/index.ts
+++ b/projects/experimental/components/index.ts
@@ -1 +1,2 @@
+export * from '@taiga-ui/experimental/components/hint';
export * from '@taiga-ui/experimental/components/input-phone-international';
diff --git a/projects/experimental/components/input-phone-international/input-phone-international.component.ts b/projects/experimental/components/input-phone-international/input-phone-international.component.ts
index 5cda76322dce..961eb2e7d53f 100644
--- a/projects/experimental/components/input-phone-international/input-phone-international.component.ts
+++ b/projects/experimental/components/input-phone-international/input-phone-international.component.ts
@@ -100,7 +100,7 @@ const NOT_FORM_CONTROL_SYMBOLS = /[^+\d]/g;
'[disabled]': 'disabled()',
'[value]': 'masked()',
'(blur)': 'onTouched()',
- '(input)': 'onInput()',
+ '(input)': 'onChange(unmasked)',
'(click)': 'open.set(false)',
'(beforeinput.capture)': 'onPaste($event)',
},
@@ -184,12 +184,10 @@ export class TuiInputPhoneInternational extends TuiControl {
this.dropdown.set(template);
}
- protected onInput(): void {
+ protected get unmasked(): string {
const value = this.el.value.replaceAll(NOT_FORM_CONTROL_SYMBOLS, '');
- this.onChange(
- value === tuiGetCallingCode(this.code(), this.metadata()) ? '' : value,
- );
+ return value === tuiGetCallingCode(this.code(), this.metadata()) ? '' : value;
}
protected onPaste(event: Event): void {
@@ -212,12 +210,17 @@ export class TuiInputPhoneInternational extends TuiControl {
}
}
- protected onItemClick(isoCode: TuiCountryIsoCode): void {
+ protected onItemClick(code: TuiCountryIsoCode): void {
this.el.focus();
- this.el.value = this.el.value || tuiGetCallingCode(this.code(), this.metadata());
+ this.el.value = this.unmasked;
this.open.set(false);
- this.code.set(isoCode);
+ this.code.set(code);
this.search.set('');
+ this.el.value = maskitoTransform(
+ this.el.value || tuiGetCallingCode(code, this.metadata()),
+ this.mask() || MASKITO_DEFAULT_OPTIONS,
+ );
+ this.onChange(this.unmasked);
}
private computeMask(
diff --git a/projects/experimental/components/input-phone-international/test/input-phone-international.component.spec.ts b/projects/experimental/components/input-phone-international/test/input-phone-international.component.spec.ts
index 988b9d823da7..fa34d2581788 100644
--- a/projects/experimental/components/input-phone-international/test/input-phone-international.component.spec.ts
+++ b/projects/experimental/components/input-phone-international/test/input-phone-international.component.spec.ts
@@ -1,11 +1,11 @@
-import type {DebugElement} from '@angular/core';
import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
import type {ComponentFixture} from '@angular/core/testing';
import {TestBed} from '@angular/core/testing';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
-import {TuiRoot} from '@taiga-ui/core';
+import {TuiRoot, TuiTextfield} from '@taiga-ui/core';
import {NG_EVENT_PLUGINS} from '@taiga-ui/event-plugins';
+import {TuiInputPhoneInternational} from '@taiga-ui/experimental';
import type {TuiCountryIsoCode, TuiLanguage} from '@taiga-ui/i18n';
import {
TUI_ENGLISH_LANGUAGE,
@@ -13,10 +13,7 @@ import {
TUI_LANGUAGE,
TUI_RUSSIAN_LANGUAGE,
} from '@taiga-ui/i18n';
-import {
- TuiInputPhoneInternational,
- tuiInputPhoneInternationalOptionsProvider,
-} from '@taiga-ui/kit';
+import {tuiInputPhoneInternationalOptionsProvider} from '@taiga-ui/kit';
import {TuiNativeInputPO} from '@taiga-ui/testing';
import metadata from 'libphonenumber-js/max/metadata';
import {of} from 'rxjs';
@@ -24,15 +21,18 @@ import {of} from 'rxjs';
describe('InputPhoneInternational', () => {
@Component({
standalone: true,
- imports: [ReactiveFormsModule, TuiInputPhoneInternational, TuiRoot],
+ imports: [ReactiveFormsModule, TuiInputPhoneInternational, TuiRoot, TuiTextfield],
template: `
-
+
+
+
`,
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -90,7 +90,7 @@ describe('InputPhoneInternational', () => {
initializeTestModule();
it('should switch country calling code and keeps all rest digits', async () => {
- component.onItemClick('UA');
+ component['onItemClick']('UA');
fixture.detectChanges();
await fixture.whenStable();
@@ -108,7 +108,7 @@ describe('InputPhoneInternational', () => {
data,
});
- component.onPaste(event);
+ component['onPaste'](event);
fixture.detectChanges();
inputPO.sendText(data);
@@ -188,7 +188,7 @@ describe('InputPhoneInternational', () => {
initializeTestModule(TUI_RUSSIAN_LANGUAGE);
it('displays country names in Russian inside dropdown', () => {
- getCountrySelector().nativeElement.click();
+ clickCountrySelector();
fixture.detectChanges();
expect(getDropdownCountryNames()).toEqual([
@@ -206,7 +206,7 @@ describe('InputPhoneInternational', () => {
initializeTestModule(TUI_ENGLISH_LANGUAGE);
it('displays country names in English inside dropdown', () => {
- getCountrySelector().nativeElement.click();
+ clickCountrySelector();
fixture.detectChanges();
expect(getDropdownCountryNames()).toEqual([
@@ -237,14 +237,16 @@ describe('InputPhoneInternational', () => {
function getDropdownCountryNames(): string[] {
const countryNameContainers =
- fixture.debugElement.queryAll(By.css('.t-name')) || [];
+ fixture.debugElement.queryAll(By.css('[tuiTitle]')) || [];
return countryNameContainers.map((container) =>
container.nativeElement.textContent?.trim(),
);
}
- function getCountrySelector(): DebugElement {
- return fixture.debugElement.query(By.css('.t-select select'));
+ function clickCountrySelector(): void {
+ return fixture.debugElement
+ .query(By.css('.t-ipi-select'))
+ .nativeElement.dispatchEvent(new Event('mousedown'));
}
});
diff --git a/projects/kit/components/avatar/avatar.template.html b/projects/kit/components/avatar/avatar.template.html
index e84d7d420682..22d1a278fadd 100644
--- a/projects/kit/components/avatar/avatar.template.html
+++ b/projects/kit/components/avatar/avatar.template.html
@@ -10,7 +10,5 @@
[icon]="value.toString()"
/>
{{ value }}
-
-
-
+
diff --git a/projects/kit/components/pulse/pulse.component.ts b/projects/kit/components/pulse/pulse.component.ts
index 0a53a071a842..88f66decb2f6 100644
--- a/projects/kit/components/pulse/pulse.component.ts
+++ b/projects/kit/components/pulse/pulse.component.ts
@@ -1,5 +1,14 @@
-import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core';
+import {isPlatformBrowser} from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ inject,
+ Input,
+ PLATFORM_ID,
+} from '@angular/core';
+import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {tuiFadeIn, tuiScaleIn} from '@taiga-ui/core/animations';
+import {tuiAsRectAccessor, TuiRectAccessor} from '@taiga-ui/core/classes';
import {TUI_ANIMATIONS_SPEED} from '@taiga-ui/core/tokens';
import {tuiToAnimationOptions} from '@taiga-ui/core/utils/miscellaneous';
@@ -9,6 +18,7 @@ import {tuiToAnimationOptions} from '@taiga-ui/core/utils/miscellaneous';
template: '',
styleUrls: ['./pulse.style.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [tuiAsRectAccessor(TuiPulse)],
animations: [tuiFadeIn, tuiScaleIn],
host: {
'[@tuiFadeIn]': 'animation',
@@ -16,9 +26,22 @@ import {tuiToAnimationOptions} from '@taiga-ui/core/utils/miscellaneous';
'[style.--t-animation-state]': "playing ? 'running' : 'paused'",
},
})
-export class TuiPulse {
+export class TuiPulse extends TuiRectAccessor {
+ private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
+ private readonly el = tuiInjectElement();
+
protected readonly animation = tuiToAnimationOptions(inject(TUI_ANIMATIONS_SPEED));
@Input()
public playing = true;
+
+ public readonly type = 'hint';
+
+ public getClientRect(): DOMRect {
+ const rect = this.el.getBoundingClientRect();
+
+ return this.isBrowser
+ ? new DOMRect(rect.x - 4, rect.y - 4, rect.width + 8, rect.height + 8)
+ : rect;
+ }
}
diff --git a/projects/kit/components/pulse/pulse.style.less b/projects/kit/components/pulse/pulse.style.less
index d1ade1d66926..ed2a25fe94c4 100644
--- a/projects/kit/components/pulse/pulse.style.less
+++ b/projects/kit/components/pulse/pulse.style.less
@@ -1,47 +1,9 @@
-@keyframes tuiPulse {
- 0% {
- opacity: 0.3;
- transform: scale(1);
- }
-
- 20% {
- opacity: 0;
- transform: scale(0.5);
- }
-
- 25% {
- opacity: 0.3;
- transform: scale(1);
- }
-
- 45% {
- opacity: 0;
- transform: scale(0.5);
- }
-
- 50% {
- opacity: 0.3;
- transform: scale(1);
- }
-
- 70% {
- opacity: 0;
- transform: scale(0.5);
- }
-
- 75% {
- opacity: 0.3;
- transform: scale(1);
- }
+@import '@taiga-ui/core/styles/taiga-ui-local';
- 95% {
+@keyframes tuiPulse {
+ to {
opacity: 0;
- transform: scale(0.5);
- }
-
- 100% {
- opacity: 0.3;
- transform: scale(1);
+ transform: scale(2.5);
}
}
@@ -49,27 +11,25 @@
position: relative;
color: var(--tui-background-accent-1);
- &::before {
+ &::before,
+ &::after {
content: '';
position: absolute;
- top: -0.5rem;
- left: -0.5rem;
- inline-size: 1rem;
- block-size: 1rem;
+ inline-size: 0.5rem;
+ block-size: 0.5rem;
border-radius: 100%;
+ margin: -0.25rem;
background: currentColor;
- opacity: 0.3;
- animation: tuiPulse 3s ease-in-out infinite;
+ }
+
+ &::before {
+ opacity: 1;
+ transform: scale(0);
+ animation: tuiPulse 1s 1s ease-in-out infinite;
animation-play-state: var(--t-animation-state);
}
&::after {
- content: '';
- position: absolute;
- inline-size: 0.5rem;
- block-size: 0.5rem;
- border-radius: 100%;
- transform: translate(-50%, -50%);
- background: currentColor;
+ box-shadow: 0 0 0.5rem;
}
}