ng-primitives 0.60.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,23 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, TemplateRef, ViewContainerRef, Injector, input, numberAttribute, booleanAttribute, Directive } from '@angular/core';
3
- import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal';
4
- import { DOCUMENT } from '@angular/common';
2
+ import { InjectionToken, inject, ApplicationRef, RendererFactory2, Injector, signal, ViewContainerRef, computed, Injectable, afterNextRender, HostListener, Directive } from '@angular/core';
3
+ import { InteractivityChecker } from '@angular/cdk/a11y';
4
+ import { explicitEffect } from 'ng-primitives/internal';
5
+ import { injectDimensions } from 'ng-primitives/utils';
6
+ import { createPortal } from 'ng-primitives/portal';
5
7
 
6
8
  const defaultToastConfig = {
7
- gap: 16,
9
+ gap: 14,
8
10
  duration: 3000,
9
- gravity: 'top',
10
- position: 'end',
11
- stopOnHover: true,
11
+ width: 360,
12
+ offsetTop: 24,
13
+ offsetBottom: 24,
14
+ offsetLeft: 24,
15
+ offsetRight: 24,
16
+ swipeThreshold: 45,
17
+ swipeDirections: ['left', 'right', 'top', 'bottom'],
18
+ dismissible: true,
19
+ maxToasts: 3,
20
+ zIndex: 9999999,
12
21
  ariaLive: 'polite',
13
22
  };
14
23
  const NgpToastConfigToken = new InjectionToken('NgpToastConfigToken');
@@ -33,209 +42,445 @@ function injectToastConfig() {
33
42
  return inject(NgpToastConfigToken, { optional: true }) ?? defaultToastConfig;
34
43
  }
35
44
 
36
- class NgpToastRef {
37
- /** Get the toast height */
38
- get height() {
39
- return this.toastElement.offsetHeight;
40
- }
41
- constructor(
42
- /** Store the toast element */
43
- toastElement,
44
- /** Store the duration */
45
- duration,
46
- /** The position of the toast */
47
- position,
48
- /** The gravity of the toast */
49
- gravity,
50
- /** Whether we should stop on focus */
51
- stopOnHover,
52
- /** The aria live setting */
53
- ariaLive, onDismiss) {
54
- this.toastElement = toastElement;
55
- this.duration = duration;
56
- this.position = position;
57
- this.gravity = gravity;
58
- this.stopOnHover = stopOnHover;
59
- this.ariaLive = ariaLive;
60
- this.onDismiss = onDismiss;
61
- /** Store the current timeout */
62
- this.timeoutId = null;
63
- this.toastElement.setAttribute('data-toast', 'visible');
64
- this.setPosition(position);
65
- this.setGravity(gravity);
66
- this.setAriaLive(ariaLive);
67
- this.setupTimeouts();
68
- this.setupListeners();
45
+ const NgpToastContext = new InjectionToken('NgpToastContext');
46
+ function provideToastContext(context) {
47
+ return { provide: NgpToastContext, useValue: context };
48
+ }
49
+ function injectToastContext() {
50
+ return inject(NgpToastContext);
51
+ }
52
+
53
+ const NgpToastOptions = new InjectionToken('NgpToastOptions');
54
+ function provideToastOptions(context) {
55
+ return { provide: NgpToastOptions, useValue: context };
56
+ }
57
+ function injectToastOptions() {
58
+ return inject(NgpToastOptions);
59
+ }
60
+
61
+ class NgpToastManager {
62
+ constructor() {
63
+ this.config = injectToastConfig();
64
+ this.applicationRef = inject(ApplicationRef);
65
+ this.rendererFactory = inject(RendererFactory2);
66
+ this.renderer = this.rendererFactory.createRenderer(null, null);
67
+ this.injector = inject(Injector);
68
+ // Map to store containers by placement
69
+ this.containers = new Map();
70
+ this.toasts = signal([]);
71
+ /** Signal that tracks which placements are expanded */
72
+ this.expanded = signal([]);
69
73
  }
70
- dismiss() {
71
- // determine if there is a transition on the element
72
- const transitionDuration = parseFloat(getComputedStyle(this.toastElement).transitionDuration);
73
- // if there is no transition, dismiss immediately
74
- if (transitionDuration === 0) {
75
- this.removeElement();
76
- return;
74
+ /** Show a toast notification */
75
+ show(toast, options = {}) {
76
+ // services can't access the view container directly, so this is a workaround
77
+ const viewContainerRef = this.applicationRef.components[0].injector.get(ViewContainerRef);
78
+ let instance = null;
79
+ const placement = options.placement ?? 'top-end';
80
+ const container = this.getOrCreateContainer(placement);
81
+ const portal = createPortal(toast, viewContainerRef, Injector.create({
82
+ parent: this.injector,
83
+ providers: [
84
+ provideToastContext(options.context),
85
+ provideToastOptions({
86
+ placement,
87
+ duration: options.duration ?? 5000,
88
+ register: (toast) => (instance = toast),
89
+ expanded: computed(() => this.expanded().includes(placement)),
90
+ dismissible: options.dismissible ?? this.config.dismissible,
91
+ swipeDirections: options.swipeDirections ?? this.config.swipeDirections,
92
+ }),
93
+ ],
94
+ }), {
95
+ // Hide the toast when the dismiss method is called
96
+ dismiss: () => this.dismiss(instance),
97
+ context: options.context,
98
+ });
99
+ portal.attach(container);
100
+ // Add the toast to the list of toasts
101
+ if (!instance) {
102
+ throw new Error('A toast must have the NgpToast directive applied.');
77
103
  }
78
- // wait for the transition to end
79
- this.toastElement.addEventListener('transitionend', () => this.removeElement());
80
- this.toastElement.setAttribute('data-toast', 'hidden');
81
- }
82
- removeElement() {
83
- this.toastElement.parentNode?.removeChild(this.toastElement);
84
- this.onDismiss();
104
+ this.toasts.update(toasts => [{ instance: instance, portal }, ...toasts]);
105
+ return {
106
+ dismiss: () => this.dismiss(instance),
107
+ };
85
108
  }
86
- /** Setup duration timeouts */
87
- setupTimeouts() {
88
- // if the duration is 0 skip
89
- if (this.duration === 0) {
90
- return;
109
+ /** Hide a toast notification */
110
+ async dismiss(toast) {
111
+ const ref = this.toasts().find(t => t.instance === toast);
112
+ if (ref) {
113
+ // Detach the portal from the container
114
+ await ref.portal.detach();
115
+ // Remove the toast from the list of toasts
116
+ this.toasts.update(toasts => toasts.filter(t => t !== ref));
117
+ // if there are no more toasts, ensure the container is no longer considered expanded
118
+ if (this.toasts().length === 0) {
119
+ this.expanded.update(expanded => expanded.filter(p => p !== toast.context.placement));
120
+ }
91
121
  }
92
- this.timeoutId = window.setTimeout(() => this.dismiss(), this.duration);
93
122
  }
94
- setupListeners() {
95
- if (!this.stopOnHover) {
96
- return;
123
+ /**
124
+ * Lazily create or get a container for a given placement.
125
+ */
126
+ getOrCreateContainer(placement) {
127
+ if (this.containers.has(placement)) {
128
+ return this.containers.get(placement);
97
129
  }
98
- // setup event listeners if we should stop on focus
99
- this.toastElement.addEventListener('mouseover', () => {
100
- window.clearTimeout(this.timeoutId);
101
- this.timeoutId = null;
130
+ const container = this.createContainer(placement);
131
+ this.containers.set(placement, container);
132
+ return container;
133
+ }
134
+ /**
135
+ * Create a section in which toasts will be rendered for a specific placement.
136
+ */
137
+ createContainer(placement) {
138
+ const container = this.renderer.createElement('section');
139
+ this.renderer.setAttribute(container, 'aria-live', this.config.ariaLive);
140
+ this.renderer.setAttribute(container, 'aria-atomic', 'false');
141
+ this.renderer.setAttribute(container, 'tabindex', '-1');
142
+ this.renderer.setAttribute(container, 'data-ngp-toast-container', placement);
143
+ container.style.setProperty('position', 'fixed');
144
+ container.style.setProperty('z-index', `${this.config.zIndex}`);
145
+ container.style.setProperty('width', `${this.config.width}px`);
146
+ container.style.setProperty('--ngp-toast-offset-top', `${this.config.offsetTop}px`);
147
+ container.style.setProperty('--ngp-toast-offset-bottom', `${this.config.offsetBottom}px`);
148
+ container.style.setProperty('--ngp-toast-offset-left', `${this.config.offsetLeft}px`);
149
+ container.style.setProperty('--ngp-toast-offset-right', `${this.config.offsetRight}px`);
150
+ container.style.setProperty('--ngp-toast-gap', `${this.config.gap}px`);
151
+ container.style.setProperty('--ngp-toast-width', `${this.config.width}px`);
152
+ // mark the container as expanded
153
+ this.renderer.listen(container, 'mouseenter', () => this.expanded.update(expanded => [...expanded, placement]));
154
+ this.renderer.listen(container, 'mouseleave', () => {
155
+ this.expanded.update(expanded => expanded.filter(p => p !== placement));
102
156
  });
103
- this.toastElement.addEventListener('mouseleave', () => this.setupTimeouts());
157
+ // Set placement styles
158
+ switch (placement) {
159
+ case 'top-start':
160
+ container.style.setProperty('top', `${this.config.offsetTop}px`);
161
+ container.style.setProperty('left', `${this.config.offsetLeft}px`);
162
+ break;
163
+ case 'top-center':
164
+ container.style.setProperty('top', '0');
165
+ container.style.setProperty('left', '50%');
166
+ container.style.setProperty('transform', 'translateX(-50%)');
167
+ break;
168
+ case 'top-end':
169
+ container.style.setProperty('top', `${this.config.offsetTop}px`);
170
+ container.style.setProperty('right', `${this.config.offsetRight}px`);
171
+ break;
172
+ case 'bottom-start':
173
+ container.style.setProperty('bottom', `${this.config.offsetBottom}px`);
174
+ container.style.setProperty('left', `${this.config.offsetLeft}px`);
175
+ break;
176
+ case 'bottom-center':
177
+ container.style.setProperty('bottom', '0');
178
+ container.style.setProperty('left', '50%');
179
+ container.style.setProperty('transform', 'translateX(-50%)');
180
+ break;
181
+ case 'bottom-end':
182
+ container.style.setProperty('bottom', `${this.config.offsetBottom}px`);
183
+ container.style.setProperty('right', `${this.config.offsetRight}px`);
184
+ break;
185
+ default:
186
+ throw new Error(`Unknown toast placement: ${placement}`);
187
+ }
188
+ this.renderer.appendChild(document.body, container);
189
+ return container;
104
190
  }
105
- /** Set the position attribute */
106
- setPosition(position) {
107
- this.toastElement.setAttribute('data-position', position);
191
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpToastManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
192
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpToastManager, providedIn: 'root' }); }
193
+ }
194
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpToastManager, decorators: [{
195
+ type: Injectable,
196
+ args: [{
197
+ providedIn: 'root',
198
+ }]
199
+ }] });
200
+
201
+ class NgpToastTimer {
202
+ constructor(duration, callback) {
203
+ this.duration = duration;
204
+ this.callback = callback;
205
+ this.startTime = null;
206
+ this.timeoutId = null;
207
+ this.isRunning = false;
208
+ this.remaining = duration;
108
209
  }
109
- /** Set the gravity attribute */
110
- setGravity(gravity) {
111
- this.toastElement.setAttribute('data-gravity', gravity);
210
+ start() {
211
+ if (this.isRunning)
212
+ return;
213
+ this.isRunning = true;
214
+ this.startTime = Date.now();
215
+ this.timeoutId = setTimeout(() => {
216
+ this.isRunning = false;
217
+ this.callback();
218
+ }, this.remaining);
112
219
  }
113
- /** Set the aria live attribute */
114
- setAriaLive(ariaLive) {
115
- this.toastElement.setAttribute('aria-live', ariaLive);
220
+ pause() {
221
+ if (!this.isRunning || this.startTime === null)
222
+ return;
223
+ this.isRunning = false;
224
+ clearTimeout(this.timeoutId);
225
+ const elapsed = Date.now() - this.startTime;
226
+ this.remaining -= elapsed;
227
+ this.startTime = null;
228
+ this.timeoutId = null;
116
229
  }
117
- /**
118
- * @internal
119
- */
120
- setInset(property, value) {
121
- this.toastElement.style[property] = value;
230
+ stop() {
231
+ this.isRunning = false;
232
+ clearTimeout(this.timeoutId);
233
+ this.timeoutId = null;
234
+ this.startTime = null;
235
+ this.remaining = this.duration;
122
236
  }
123
237
  }
238
+ function toastTimer(duration, callback) {
239
+ return new NgpToastTimer(duration, callback);
240
+ }
124
241
 
125
242
  class NgpToast {
126
243
  constructor() {
127
- this.config = injectToastConfig();
128
- /** Access the ng-template */
129
- this.template = inject(TemplateRef);
130
- /** Access the view container */
131
- this.viewContainer = inject(ViewContainerRef);
132
- /** Access the injector */
244
+ this.manager = inject(NgpToastManager);
133
245
  this.injector = inject(Injector);
134
- /** Access the document */
135
- this.document = inject(DOCUMENT);
246
+ this.config = injectToastConfig();
247
+ /** @internal */
248
+ this.context = injectToastOptions();
249
+ this.interactivityChecker = inject(InteractivityChecker);
250
+ this.isInteracting = signal(false);
251
+ this.pointerStartRef = null;
252
+ this.dragStartTime = null;
253
+ this.swiping = signal(false);
254
+ this.swipeDirection = signal(null);
255
+ this.swipeAmount = signal({ x: 0, y: 0 });
256
+ this.swipeOutDirection = computed(() => {
257
+ const direction = this.swipeDirection();
258
+ if (direction === 'x') {
259
+ return this.swipeAmount().x > 0 ? 'right' : 'left';
260
+ }
261
+ else if (direction === 'y') {
262
+ return this.swipeAmount().y > 0 ? 'bottom' : 'top';
263
+ }
264
+ return null;
265
+ });
266
+ /**
267
+ * Get all toasts that are currently being displayed in the same position.
268
+ */
269
+ this.toasts = computed(() => this.manager
270
+ .toasts()
271
+ .filter(toast => toast.instance.context.placement === this.context.placement));
136
272
  /**
137
- * The duration the toast will display for before it is automatically dismissed in milliseconds.
138
- * @default 3000
273
+ * The number of toasts that are currently being displayed before this toast.
139
274
  */
140
- this.duration = input(this.config.duration, {
141
- alias: 'ngpToastDuration',
142
- transform: numberAttribute,
275
+ this.index = computed(() => {
276
+ return this.toasts().findIndex(toast => toast.instance === this);
143
277
  });
144
278
  /**
145
- * The direction the toast will appear from.
146
- * @default 'top'
279
+ * Determine the position of the toast in the list of toasts.
280
+ * This is the combination of the heights of all the toasts before this one, plus the gap between them.
147
281
  */
148
- this.gravity = input(this.config.gravity, {
149
- alias: 'ngpToastGravity',
282
+ this.offset = computed(() => {
283
+ const gap = this.config.gap;
284
+ return this.toasts()
285
+ .slice(0, this.index())
286
+ .reduce((acc, toast) => acc + toast.instance.dimensions().height + gap, 0);
150
287
  });
151
288
  /**
152
- * The position the toast will on the horizontal axis.
153
- * @default 'end'
289
+ * Determine if this toast is visible.
290
+ * Visible considers the maximum number of toasts that can be displayed at once, and the last x toasts that are currently being displayed.
154
291
  */
155
- this.position = input(this.config.position, {
156
- alias: 'ngpToastPosition',
292
+ this.visible = computed(() => {
293
+ const maxToasts = this.config.maxToasts;
294
+ // determine if this toast is within the maximum number of toasts that can be displayed
295
+ return this.index() < maxToasts || this.toasts().length <= maxToasts;
157
296
  });
158
297
  /**
159
- * Whether the automatic dismissal of the toast should be paused when the user hovers over it.
160
- * @default true
298
+ * Determine the height of the front toast.
299
+ * This is used to determine the height of the toast when it is not expanded.
161
300
  */
162
- this.stopOnHover = input(this.config.stopOnHover, {
163
- alias: 'ngpToastStopOnHover',
164
- transform: booleanAttribute,
301
+ this.frontToastHeight = computed(() => {
302
+ // get the first toast in the list with height - as when a new toast is added, it may not initially have dimensions
303
+ return (this.toasts()
304
+ .find(toast => toast.instance.dimensions().height)
305
+ ?.instance.dimensions().height || 0);
165
306
  });
166
307
  /**
167
- * Whether the toast should be announced to assistive technologies.
168
- * @default 'polite'
308
+ * Determine the z-index of the toast. This is the inverse of the index.
309
+ * The first toast will have the highest z-index, and the last toast will have the lowest z-index.
310
+ * This is used to ensure that the first toast is always on top of the others.
311
+ */
312
+ this.zIndex = computed(() => this.toasts().length - this.index());
313
+ /**
314
+ * The height of the toast in pixels.
315
+ */
316
+ this.dimensions = injectDimensions();
317
+ /**
318
+ * The x position of the toast.
169
319
  */
170
- this.ariaLive = input(this.config.ariaLive, {
171
- alias: 'ngpToastAriaLive',
320
+ this.x = this.context.placement.split('-')[1] || 'end';
321
+ /**
322
+ * The y position of the toast.
323
+ */
324
+ this.y = this.context.placement.split('-')[0] || 'top';
325
+ /**
326
+ * The toast timer instance.
327
+ */
328
+ this.timer = toastTimer(this.config.duration, () => this.manager.dismiss(this));
329
+ this.context.register(this);
330
+ // Start the timer when the toast is created
331
+ this.timer.start();
332
+ // Pause the timer when the toast is expanded or when the user is interacting with it
333
+ explicitEffect([this.context.expanded, this.isInteracting], ([expanded, interacting]) => {
334
+ // If the toast is expanded, or if the user is interacting with it, reset the timer
335
+ if (expanded || interacting) {
336
+ this.timer.pause();
337
+ }
338
+ else {
339
+ this.timer.start();
340
+ }
172
341
  });
173
342
  }
174
- /** Store the list of toasts */
175
- static { this.toasts = []; }
176
- /** Show the toast. */
177
- show() {
178
- this.createToast();
179
- this.reposition();
180
- }
181
- /** Build the toast */
182
- createToast() {
183
- const portal = new TemplatePortal(this.template, this.viewContainer, {
184
- dismiss: () => toastRef.dismiss(),
185
- }, this.injector);
186
- const domOutlet = new DomPortalOutlet(this.document.body, undefined, undefined, Injector.create({
187
- parent: this.injector,
188
- providers: [],
189
- }));
190
- const viewRef = domOutlet.attach(portal);
191
- viewRef.detectChanges();
192
- const toastElement = viewRef.rootNodes[0];
193
- const toastRef = new NgpToastRef(toastElement, this.duration(), this.position(), this.gravity(), this.stopOnHover(), this.ariaLive(), () => {
194
- NgpToast.toasts = NgpToast.toasts.filter(t => t !== toastRef);
195
- this.reposition();
196
- });
197
- NgpToast.toasts = [...NgpToast.toasts, toastRef];
343
+ onPointerDown(event) {
344
+ // right click should not trigger swipe and we check if the toast is dismissible
345
+ if (event.button === 2 || !this.context.dismissible) {
346
+ return;
347
+ }
348
+ this.isInteracting.set(true);
349
+ // we need to check if the pointer is on an interactive element, if so, we should not start swiping
350
+ if (this.interactivityChecker.isFocusable(event.target)) {
351
+ return;
352
+ }
353
+ this.dragStartTime = new Date();
354
+ // Ensure we maintain correct pointer capture even when going outside of the toast (e.g. when swiping)
355
+ event.target.setPointerCapture(event.pointerId);
356
+ this.swiping.set(true);
357
+ this.pointerStartRef = { x: event.clientX, y: event.clientY };
198
358
  }
199
- /** Position the toast on the DOM */
200
- reposition() {
201
- const topStartOffsetSize = {
202
- top: this.config.gap,
203
- bottom: this.config.gap,
204
- };
205
- const topEndOffsetSize = {
206
- top: this.config.gap,
207
- bottom: this.config.gap,
359
+ onPointerMove(event) {
360
+ if (!this.pointerStartRef || !this.context.dismissible) {
361
+ return;
362
+ }
363
+ const isHighlighted = window.getSelection()?.toString().length ?? 0 > 0;
364
+ if (isHighlighted) {
365
+ return;
366
+ }
367
+ const yDelta = event.clientY - this.pointerStartRef.y;
368
+ const xDelta = event.clientX - this.pointerStartRef.x;
369
+ const swipeDirections = this.context.swipeDirections;
370
+ // Determine swipe direction if not already locked
371
+ if (!this.swipeDirection() && (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1)) {
372
+ this.swipeDirection.set(Math.abs(xDelta) > Math.abs(yDelta) ? 'x' : 'y');
373
+ }
374
+ const swipeAmount = { x: 0, y: 0 };
375
+ const getDampening = (delta) => {
376
+ const factor = Math.abs(delta) / 20;
377
+ return 1 / (1.5 + factor);
208
378
  };
209
- let position;
210
- // update the position of the toasts
211
- for (const toast of NgpToast.toasts) {
212
- // Getting the applied gravity
213
- position = toast.gravity;
214
- const height = toast.height;
215
- if (toast.position === 'start') {
216
- toast.setInset(position, `${topStartOffsetSize[position]}px`);
217
- topStartOffsetSize[position] += height + this.config.gap;
379
+ // Only apply swipe in the locked direction
380
+ if (this.swipeDirection() === 'y') {
381
+ // Handle vertical swipes
382
+ if (swipeDirections.includes('top') || swipeDirections.includes('bottom')) {
383
+ if ((swipeDirections.includes('top') && yDelta < 0) ||
384
+ (swipeDirections.includes('bottom') && yDelta > 0)) {
385
+ swipeAmount.y = yDelta;
386
+ }
387
+ else {
388
+ // Smoothly transition to dampened movement
389
+ const dampenedDelta = yDelta * getDampening(yDelta);
390
+ // Ensure we don't jump when transitioning to dampened movement
391
+ swipeAmount.y = Math.abs(dampenedDelta) < Math.abs(yDelta) ? dampenedDelta : yDelta;
392
+ }
218
393
  }
219
- else {
220
- toast.setInset(position, `${topEndOffsetSize[position]}px`);
221
- topEndOffsetSize[position] += height + this.config.gap;
394
+ }
395
+ else if (this.swipeDirection() === 'x') {
396
+ // Handle horizontal swipes
397
+ if (swipeDirections.includes('left') || swipeDirections.includes('right')) {
398
+ if ((swipeDirections.includes('left') && xDelta < 0) ||
399
+ (swipeDirections.includes('right') && xDelta > 0)) {
400
+ swipeAmount.x = xDelta;
401
+ }
402
+ else {
403
+ // Smoothly transition to dampened movement
404
+ const dampenedDelta = xDelta * getDampening(xDelta);
405
+ // Ensure we don't jump when transitioning to dampened movement
406
+ swipeAmount.x = Math.abs(dampenedDelta) < Math.abs(xDelta) ? dampenedDelta : xDelta;
407
+ }
222
408
  }
223
409
  }
410
+ this.swipeAmount.set({ x: swipeAmount.x, y: swipeAmount.y });
411
+ if (Math.abs(swipeAmount.x) > 0 || Math.abs(swipeAmount.y) > 0) {
412
+ this.swiping.set(true);
413
+ }
414
+ }
415
+ onPointerUp() {
416
+ this.isInteracting.set(false);
417
+ if (!this.config.dismissible ||
418
+ !this.pointerStartRef ||
419
+ !this.swiping() ||
420
+ !this.dragStartTime) {
421
+ return;
422
+ }
423
+ this.pointerStartRef = null;
424
+ const swipeAmountX = this.swipeAmount().x;
425
+ const swipeAmountY = this.swipeAmount().y;
426
+ const timeTaken = new Date().getTime() - this.dragStartTime.getTime();
427
+ const swipeAmount = this.swipeDirection() === 'x' ? swipeAmountX : swipeAmountY;
428
+ const velocity = Math.abs(swipeAmount) / timeTaken;
429
+ if (Math.abs(swipeAmount) >= this.config.swipeThreshold || velocity > 0.11) {
430
+ afterNextRender({ write: () => this.manager.dismiss(this) }, { injector: this.injector });
431
+ return;
432
+ }
433
+ else {
434
+ this.swipeAmount.set({ x: 0, y: 0 });
435
+ }
436
+ // Reset swipe state
437
+ this.swipeDirection.set(null);
438
+ this.swiping.set(false);
224
439
  }
225
440
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpToast, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
226
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpToast, isStandalone: true, selector: "[ngpToast]", inputs: { duration: { classPropertyName: "duration", publicName: "ngpToastDuration", isSignal: true, isRequired: false, transformFunction: null }, gravity: { classPropertyName: "gravity", publicName: "ngpToastGravity", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "ngpToastPosition", isSignal: true, isRequired: false, transformFunction: null }, stopOnHover: { classPropertyName: "stopOnHover", publicName: "ngpToastStopOnHover", isSignal: true, isRequired: false, transformFunction: null }, ariaLive: { classPropertyName: "ariaLive", publicName: "ngpToastAriaLive", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["ngpToast"], ngImport: i0 }); }
441
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpToast, isStandalone: true, selector: "[ngpToast]", host: { listeners: { "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp()" }, properties: { "attr.data-position-x": "x", "attr.data-position-y": "y", "attr.data-visible": "visible()", "attr.data-front": "index() === 0", "attr.data-swiping": "swiping()", "attr.data-swipe-direction": "swipeOutDirection()", "attr.data-expanded": "context.expanded()", "style.--ngp-toast-gap.px": "config.gap", "style.--ngp-toast-z-index": "zIndex()", "style.--ngp-toasts-before": "index()", "style.--ngp-toast-index": "index() + 1", "style.--ngp-toast-width.px": "config.width", "style.--ngp-toast-height.px": "dimensions().height", "style.--ngp-toast-offset.px": "offset()", "style.--ngp-toast-front-height.px": "frontToastHeight()", "style.--ngp-toast-swipe-amount-x.px": "swipeAmount().x", "style.--ngp-toast-swipe-amount-y.px": "swipeAmount().y", "style.--ngp-toast-swipe-x": "swipeAmount().x", "style.--ngp-toast-swipe-y": "swipeAmount().y" } }, exportAs: ["ngpToast"], ngImport: i0 }); }
227
442
  }
228
443
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpToast, decorators: [{
229
444
  type: Directive,
230
445
  args: [{
231
446
  selector: '[ngpToast]',
232
447
  exportAs: 'ngpToast',
448
+ host: {
449
+ '[attr.data-position-x]': 'x',
450
+ '[attr.data-position-y]': 'y',
451
+ '[attr.data-visible]': 'visible()',
452
+ '[attr.data-front]': 'index() === 0',
453
+ '[attr.data-swiping]': 'swiping()',
454
+ '[attr.data-swipe-direction]': 'swipeOutDirection()',
455
+ '[attr.data-expanded]': 'context.expanded()',
456
+ '[style.--ngp-toast-gap.px]': 'config.gap',
457
+ '[style.--ngp-toast-z-index]': 'zIndex()',
458
+ '[style.--ngp-toasts-before]': 'index()',
459
+ '[style.--ngp-toast-index]': 'index() + 1',
460
+ '[style.--ngp-toast-width.px]': 'config.width',
461
+ '[style.--ngp-toast-height.px]': 'dimensions().height',
462
+ '[style.--ngp-toast-offset.px]': 'offset()',
463
+ '[style.--ngp-toast-front-height.px]': 'frontToastHeight()',
464
+ '[style.--ngp-toast-swipe-amount-x.px]': 'swipeAmount().x',
465
+ '[style.--ngp-toast-swipe-amount-y.px]': 'swipeAmount().y',
466
+ '[style.--ngp-toast-swipe-x]': 'swipeAmount().x',
467
+ '[style.--ngp-toast-swipe-y]': 'swipeAmount().y',
468
+ },
233
469
  }]
234
- }] });
470
+ }], ctorParameters: () => [], propDecorators: { onPointerDown: [{
471
+ type: HostListener,
472
+ args: ['pointerdown', ['$event']]
473
+ }], onPointerMove: [{
474
+ type: HostListener,
475
+ args: ['pointermove', ['$event']]
476
+ }], onPointerUp: [{
477
+ type: HostListener,
478
+ args: ['pointerup']
479
+ }] } });
235
480
 
236
481
  /**
237
482
  * Generated bundle index. Do not edit.
238
483
  */
239
484
 
240
- export { NgpToast, NgpToastRef, provideToastConfig };
485
+ export { NgpToast, NgpToastManager, injectToastContext, provideToastConfig };
241
486
  //# sourceMappingURL=ng-primitives-toast.mjs.map