diff --git a/CHANGELOG.md b/CHANGELOG.md
index 826c28e..984e444 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+## [0.4.0](https://github.com/tutkli/ngx-sonner/compare/v0.3.4...v0.4.0) (2024-04-01)
+
+
+### Features
+
+* use new signal queries ([#4](https://github.com/tutkli/ngx-sonner/issues/4)) ([bde803e](https://github.com/tutkli/ngx-sonner/commit/bde803efb543fbbbc4a812d6ebc19d4c595cd04a))
+
+
+### Bug Fixes
+
+* blurry toasts ([#6](https://github.com/tutkli/ngx-sonner/issues/6)) ([5ba650c](https://github.com/tutkli/ngx-sonner/commit/5ba650c58ecf1fa1f839c85fe0ebe5e825a72f65))
+* preserve heights order when adding new toasts ([#8](https://github.com/tutkli/ngx-sonner/issues/8)) ([c7775a2](https://github.com/tutkli/ngx-sonner/commit/c7775a20f165bc39575afd599004e6cb3787859a))
+
+
+### Chores
+
+* remove nx cloud ([fe4a4d1](https://github.com/tutkli/ngx-sonner/commit/fe4a4d19c9b9d97f4af096e5bb909f04b0b0a777))
+* sync package-lock.json ([ead7f75](https://github.com/tutkli/ngx-sonner/commit/ead7f7597326b1f2ea166fab1fa4efe1821831f8))
+* sync package-lock.json ([bcf2f46](https://github.com/tutkli/ngx-sonner/commit/bcf2f468815d1974e621d4fd9e8918a39a8a0334))
+* update nx ([ddc83fa](https://github.com/tutkli/ngx-sonner/commit/ddc83fa76da51f9d85dd4c2aa7eeb1cf8c1f029f))
+
## [0.3.4](https://github.com/tutkli/ngx-sonner/compare/v0.3.3...v0.3.4) (2024-03-07)
diff --git a/README.md b/README.md
index ac20812..77cd8c3 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Based on [emilkowalski](https://github.com/emilkowalski)'s React [implementation
## Angular compatibility
-Make sure you are using Angular v17.2.0 or greater
+Make sure you are using Angular v17.3.0 or greater
## Quick start
diff --git a/apps/docs/src/app/app.component.ts b/apps/docs/src/app/app.component.ts
index 9d36a8e..59acb6a 100644
--- a/apps/docs/src/app/app.component.ts
+++ b/apps/docs/src/app/app.component.ts
@@ -36,15 +36,9 @@ import { UsageComponent } from './components/usage.component';
-
-
-
+
+
+
diff --git a/apps/docs/src/app/components/code-block.component.ts b/apps/docs/src/app/components/code-block.component.ts
index f637b44..b57fa22 100644
--- a/apps/docs/src/app/components/code-block.component.ts
+++ b/apps/docs/src/app/components/code-block.component.ts
@@ -8,7 +8,7 @@ import {
inject,
input,
signal,
- ViewChild,
+ viewChild,
} from '@angular/core';
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
@@ -138,7 +138,7 @@ export class CodeBlockComponent {
code = input.required();
_class = input('', { alias: 'class' });
- @ViewChild('codeElement') codeElement!: ElementRef;
+ codeElement = viewChild.required>('codeElement');
copying = signal(false);
cannotDetectLanguage = computed(
@@ -168,9 +168,7 @@ export class CodeBlockComponent {
constructor() {
effect(() => {
- if (this.codeElement?.nativeElement) {
- this.codeElement.nativeElement.innerHTML = this.highlightedCode();
- }
+ this.codeElement().nativeElement.innerHTML = this.highlightedCode();
});
}
diff --git a/apps/docs/src/app/components/expand.component.ts b/apps/docs/src/app/components/expand.component.ts
index 8f91712..bf67e70 100644
--- a/apps/docs/src/app/components/expand.component.ts
+++ b/apps/docs/src/app/components/expand.component.ts
@@ -2,9 +2,7 @@ import {
ChangeDetectionStrategy,
Component,
computed,
- EventEmitter,
- input,
- Output,
+ model,
} from '@angular/core';
import { toast } from 'ngx-sonner';
import { CodeBlockComponent } from './code-block.component';
@@ -41,8 +39,7 @@ import { CodeBlockComponent } from './code-block.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandComponent {
- expand = input.required();
- @Output() expandChange = new EventEmitter();
+ expand = model.required();
expandSnippet = computed(
() => ``
@@ -52,13 +49,13 @@ export class ExpandComponent {
toast('Event has been created', {
description: 'Monday, January 3rd at 6:00pm',
});
- this.expandChange.emit(true);
+ this.expand.set(true);
}
collapseToasts() {
toast('Event has been created', {
description: 'Monday, January 3rd at 6:00pm',
});
- this.expandChange.emit(false);
+ this.expand.set(false);
}
}
diff --git a/apps/docs/src/app/components/other.component.ts b/apps/docs/src/app/components/other.component.ts
index 15ea4ba..a3e4c1b 100644
--- a/apps/docs/src/app/components/other.component.ts
+++ b/apps/docs/src/app/components/other.component.ts
@@ -2,9 +2,7 @@ import {
ChangeDetectionStrategy,
Component,
computed,
- EventEmitter,
- input,
- Output,
+ model,
signal,
} from '@angular/core';
import { toast } from 'ngx-sonner';
@@ -39,11 +37,8 @@ import { TestComponent } from './test.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OtherComponent {
- closeButton = input.required();
- @Output() closeButtonChange = new EventEmitter();
-
- richColors = input.required();
- @Output() richColorsChange = new EventEmitter();
+ closeButton = model.required();
+ richColors = model.required();
allTypes = [
{
@@ -51,7 +46,7 @@ export class OtherComponent {
snippet: "toast.success('Event has been created')",
action: () => {
toast.success('Event has been created');
- this.richColorsChange.emit(true);
+ this.richColors.set(true);
},
},
{
@@ -59,7 +54,7 @@ export class OtherComponent {
snippet: "toast.error('Event has not been created')",
action: () => {
toast.error('Event has not been created');
- this.richColorsChange.emit(true);
+ this.richColors.set(true);
},
},
{
@@ -67,7 +62,7 @@ export class OtherComponent {
snippet: "toast.info('Info')",
action: () => {
toast.info('Be at the area 10 minutes before the event time');
- this.richColorsChange.emit(true);
+ this.richColors.set(true);
},
},
{
@@ -75,7 +70,7 @@ export class OtherComponent {
snippet: "toast.warning('Warning')",
action: () => {
toast.warning('Event start time cannot be earlier than 8am');
- this.richColorsChange.emit(true);
+ this.richColors.set(true);
},
},
{
@@ -87,7 +82,7 @@ export class OtherComponent {
toast('Event has been created', {
description: 'Monday, January 3rd at 6:00pm',
});
- this.closeButtonChange.emit(!this.closeButton());
+ this.closeButton.set(!this.closeButton());
},
},
{
diff --git a/apps/docs/src/app/components/position.component.ts b/apps/docs/src/app/components/position.component.ts
index 7ba1054..2d48bf1 100644
--- a/apps/docs/src/app/components/position.component.ts
+++ b/apps/docs/src/app/components/position.component.ts
@@ -2,13 +2,22 @@ import {
ChangeDetectionStrategy,
Component,
computed,
- EventEmitter,
- input,
- Output,
+ model,
} from '@angular/core';
import { toast } from 'ngx-sonner';
import { CodeBlockComponent } from './code-block.component';
+const positions = [
+ 'top-left',
+ 'top-center',
+ 'top-right',
+ 'bottom-left',
+ 'bottom-center',
+ 'bottom-right',
+] as const;
+
+type Position = (typeof positions)[number];
+
@Component({
selector: 'docs-position',
standalone: true,
@@ -33,36 +42,26 @@ import { CodeBlockComponent } from './code-block.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PositionComponent {
- positions = [
- 'top-left',
- 'top-center',
- 'top-right',
- 'bottom-left',
- 'bottom-center',
- 'bottom-right',
- ] as const;
-
- position = input.required<(typeof this.positions)[number]>();
+ protected positions = positions;
- @Output() positionChange = new EventEmitter<
- (typeof this.positions)[number]
- >();
+ position = model.required();
positionSnippet = computed(
() => ``
);
- showToast(position: (typeof this.positions)[number]) {
+ showToast(position: Position) {
const toastsAmount = document.querySelectorAll(
'[data-sonner-toast]'
).length;
- this.positionChange.emit(position);
// No need to show a toast when there is already one
- if (toastsAmount > 0 && position !== this.position()) return;
+ if (toastsAmount === 0 || position === this.position()) {
+ toast('Event has been created', {
+ description: 'Monday, January 3rd at 6:00pm',
+ });
+ }
- toast('Event has been created', {
- description: 'Monday, January 3rd at 6:00pm',
- });
+ this.position.set(position);
}
}
diff --git a/apps/docs/src/app/components/test.component.ts b/apps/docs/src/app/components/test.component.ts
index fdc3453..3f302e1 100644
--- a/apps/docs/src/app/components/test.component.ts
+++ b/apps/docs/src/app/components/test.component.ts
@@ -1,9 +1,8 @@
import {
ChangeDetectionStrategy,
Component,
- EventEmitter,
input,
- Output,
+ output,
} from '@angular/core';
@Component({
@@ -75,5 +74,5 @@ import {
})
export class TestComponent {
eventName = input.required();
- @Output() closeToast = new EventEmitter();
+ closeToast = output();
}
diff --git a/libs/ngx-sonner/README.md b/libs/ngx-sonner/README.md
index 8cb9d96..390589f 100644
--- a/libs/ngx-sonner/README.md
+++ b/libs/ngx-sonner/README.md
@@ -6,7 +6,7 @@ Based on [emilkowalski](https://github.com/emilkowalski)'s React [implementation
## Angular compatibility
-Make sure you are using Angular v17.2.0 or greater
+Make sure you are using Angular v17.3.0 or greater
## Quick start
diff --git a/libs/ngx-sonner/package.json b/libs/ngx-sonner/package.json
index b311e4f..d564583 100644
--- a/libs/ngx-sonner/package.json
+++ b/libs/ngx-sonner/package.json
@@ -1,7 +1,7 @@
{
"name": "ngx-sonner",
"description": "An opinionated toast component for Angular.",
- "version": "0.3.0",
+ "version": "0.0.0",
"bugs": {
"url": "https://github.com/tutkli/ngx-sonner/issues"
},
@@ -23,8 +23,8 @@
},
"license": "MIT",
"peerDependencies": {
- "@angular/common": ">=17.1.0",
- "@angular/core": ">=17.1.0"
+ "@angular/common": ">=17.3.0",
+ "@angular/core": ">=17.3.0"
},
"dependencies": {
"tslib": "^2.3.0"
diff --git a/libs/ngx-sonner/src/lib/pipes/toast-position.pipe.ts b/libs/ngx-sonner/src/lib/pipes/toast-filter.pipe.ts
similarity index 72%
rename from libs/ngx-sonner/src/lib/pipes/toast-position.pipe.ts
rename to libs/ngx-sonner/src/lib/pipes/toast-filter.pipe.ts
index 51fac20..19be155 100644
--- a/libs/ngx-sonner/src/lib/pipes/toast-position.pipe.ts
+++ b/libs/ngx-sonner/src/lib/pipes/toast-filter.pipe.ts
@@ -1,8 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core';
import { Position, ToastT } from '../types';
-@Pipe({ name: 'toastPosition', standalone: true })
-export class ToastPositionPipe implements PipeTransform {
+@Pipe({ name: 'toastFilter', standalone: true })
+export class ToastFilterPipe implements PipeTransform {
transform(toasts: ToastT[], index: number, position: Position): ToastT[] {
return toasts.filter(
toast => (!toast.position && index === 0) || toast.position === position
diff --git a/libs/ngx-sonner/src/lib/state.ts b/libs/ngx-sonner/src/lib/state.ts
index 6878506..5c9c08a 100644
--- a/libs/ngx-sonner/src/lib/state.ts
+++ b/libs/ngx-sonner/src/lib/state.ts
@@ -169,9 +169,13 @@ function createToastState() {
}
function addHeight(height: HeightT) {
- heights.update(prev => [height, ...prev]);
+ heights.update(prev => [height, ...prev].sort(sortHeights));
}
+ const sortHeights = (a: HeightT, b: HeightT) =>
+ toasts().findIndex(t => t.id === a.toastId) -
+ toasts().findIndex(t => t.id === b.toastId);
+
function reset() {
toasts.set([]);
heights.set([]);
diff --git a/libs/ngx-sonner/src/lib/toast.component.ts b/libs/ngx-sonner/src/lib/toast.component.ts
index 599660f..181e115 100644
--- a/libs/ngx-sonner/src/lib/toast.component.ts
+++ b/libs/ngx-sonner/src/lib/toast.component.ts
@@ -10,7 +10,7 @@ import {
OnDestroy,
signal,
untracked,
- ViewChild,
+ viewChild,
} from '@angular/core';
import { cn } from './internal/cn';
import { AsComponentPipe } from './pipes/as-component.pipe';
@@ -236,8 +236,7 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
offsetBeforeRemove = signal(0);
initialHeight = signal(0);
- // viewChild.required>('toastRef')
- @ViewChild('toastRef') toastRef!: ElementRef;
+ toastRef = viewChild.required>('toastRef');
classes: any = computed(() => ({
...defaultClasses,
@@ -304,7 +303,9 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
effect(() => {
const heightIndex = this.heightIndex();
const toastsHeightBefore = this.toastsHeightBefore();
- untracked(() => this.offset.set(heightIndex * GAP + toastsHeightBefore));
+ untracked(() =>
+ this.offset.set(Math.round(heightIndex * GAP + toastsHeightBefore))
+ );
});
effect(() => {
@@ -342,7 +343,7 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
this.remainingTime =
this.toast().duration ?? this.duration() ?? TOAST_LIFETIME;
this.mounted.set(true);
- const height = this.toastRef.nativeElement.getBoundingClientRect().height;
+ const height = this.toastRef().nativeElement.getBoundingClientRect().height;
this.initialHeight.set(height);
this.addHeight({ toastId: this.toast().id, height });
}
@@ -406,8 +407,8 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
this.pointerStartRef = null;
const swipeAmount = Number(
- this.toastRef.nativeElement.style
- .getPropertyValue('--swipe-amount')
+ this.toastRef()
+ .nativeElement.style.getPropertyValue('--swipe-amount')
.replace('px', '') || 0
);
@@ -420,7 +421,7 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
return;
}
- this.toastRef.nativeElement.style.setProperty('--swipe-amount', '0px');
+ this.toastRef().nativeElement.style.setProperty('--swipe-amount', '0px');
this.swiping.set(false);
}
@@ -436,7 +437,7 @@ export class ToastComponent implements AfterViewInit, OnDestroy {
const isAllowedToSwipe = Math.abs(clampedY) > swipeStartThreshold;
if (isAllowedToSwipe) {
- this.toastRef.nativeElement.style.setProperty(
+ this.toastRef().nativeElement.style.setProperty(
'--swipe-amount',
`${yPosition}px`
);
diff --git a/libs/ngx-sonner/src/lib/toaster.component.ts b/libs/ngx-sonner/src/lib/toaster.component.ts
index 5a7a577..cf4591e 100644
--- a/libs/ngx-sonner/src/lib/toaster.component.ts
+++ b/libs/ngx-sonner/src/lib/toaster.component.ts
@@ -13,12 +13,12 @@ import {
PLATFORM_ID,
signal,
untracked,
- ViewChild,
+ viewChild,
ViewEncapsulation,
} from '@angular/core';
import { IconComponent } from './icon.component';
import { LoaderComponent } from './loader.component';
-import { ToastPositionPipe } from './pipes/toast-position.pipe';
+import { ToastFilterPipe } from './pipes/toast-filter.pipe';
import { toastState } from './state';
import { ToastComponent } from './toast.component';
import { Position, Theme, ToasterProps } from './types';
@@ -41,7 +41,7 @@ const GAP = 14;
@Component({
selector: 'ngx-sonner-toaster',
standalone: true,
- imports: [ToastComponent, ToastPositionPipe, IconComponent, LoaderComponent],
+ imports: [ToastComponent, ToastFilterPipe, IconComponent, LoaderComponent],
template: `
@if (toasts().length > 0) {
@for (
- toast of toasts() | toastPosition: $index : pos;
+ toast of toasts() | toastFilter: $index : pos;
track toast.id
) {
('listRef');
- @ViewChild('listRef') listRef!: ElementRef;
+ listRef = viewChild>('listRef');
lastFocusedElementRef = signal(null);
isFocusWithinRef = signal(false);
@@ -239,7 +238,8 @@ export class NgxSonnerToaster implements OnDestroy {
}
private handleKeydown = (event: KeyboardEvent) => {
- if (!this.listRef?.nativeElement) return;
+ const listRef = this.listRef()?.nativeElement;
+ if (!listRef) return;
const isHotkeyPressed = this.hotKey().every(
key => (event as never)[key] || event.code === key
@@ -247,13 +247,13 @@ export class NgxSonnerToaster implements OnDestroy {
if (isHotkeyPressed) {
this.expanded.set(true);
- this.listRef.nativeElement?.focus();
+ listRef.focus();
}
if (
event.code === 'Escape' &&
- (document.activeElement === this.listRef.nativeElement ||
- this.listRef.nativeElement?.contains(document.activeElement))
+ (document.activeElement === listRef ||
+ listRef.contains(document.activeElement))
) {
this.expanded.set(false);
}
diff --git a/libs/ngx-sonner/src/tests/toaster.spec.ts b/libs/ngx-sonner/src/tests/toaster.spec.ts
index b285b47..3464d67 100644
--- a/libs/ngx-sonner/src/tests/toaster.spec.ts
+++ b/libs/ngx-sonner/src/tests/toaster.spec.ts
@@ -44,17 +44,17 @@ describe('Toaster', () => {
it('should show a toast with custom duration', async () => {
const { user, trigger, queryByText, detectChanges } = await setup({
- cb: toast => toast('Hello world', { duration: 300 }),
+ cb: toast => toast('Custom duration', { duration: 300 }),
});
- expect(queryByText('Hello world')).toBeNull();
+ expect(queryByText('Custom duration')).toBeNull();
await user.click(trigger);
- expect(queryByText('Hello world')).not.toBeNull();
+ expect(queryByText('Custom duration')).not.toBeNull();
await sleep(500);
detectChanges();
- expect(queryByText('Hello world')).toBeNull();
+ expect(queryByText('Custom duration')).toBeNull();
});
it('should reset duration on a toast update', async () => {
diff --git a/package.json b/package.json
index 0622e3b..04033e2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@ngx-sonner/source",
- "version": "0.3.4",
+ "version": "0.4.0",
"license": "MIT",
"scripts": {
"start:docs": "nx serve docs",