varsel 0.5.2 → 0.5.4

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.
@@ -290,9 +290,12 @@ $effect(() => {
290
290
  });
291
291
 
292
292
  $effect(() => {
293
- const handleMouseMove = (event: MouseEvent) => {
293
+ let hoverFrameId: number | null = null;
294
+ let pointerX = 0;
295
+ let pointerY = 0;
296
+
297
+ const updateHoverState = (x: number, y: number) => {
294
298
  if (latestPositionEntries.length === 0) return;
295
- const { clientX: x, clientY: y } = event;
296
299
  const next: Record<ToastPosition, boolean> = {
297
300
  ...latestHovered,
298
301
  };
@@ -333,6 +336,19 @@ $effect(() => {
333
336
  }
334
337
  };
335
338
 
339
+ const flushHoverUpdate = () => {
340
+ hoverFrameId = null;
341
+ updateHoverState(pointerX, pointerY);
342
+ };
343
+
344
+ const handleMouseMove = (event: MouseEvent) => {
345
+ pointerX = event.clientX;
346
+ pointerY = event.clientY;
347
+ if (hoverFrameId == null) {
348
+ hoverFrameId = requestAnimationFrame(flushHoverUpdate);
349
+ }
350
+ };
351
+
336
352
  const handleKeyDown = (event: KeyboardEvent) => {
337
353
  if (event.key !== "Escape") return;
338
354
  for (const [, group] of latestPositionEntries) {
@@ -358,6 +374,10 @@ $effect(() => {
358
374
  document.addEventListener("mousemove", handleMouseMove);
359
375
  document.addEventListener("keydown", handleKeyDown);
360
376
  return () => {
377
+ if (hoverFrameId != null) {
378
+ cancelAnimationFrame(hoverFrameId);
379
+ hoverFrameId = null;
380
+ }
361
381
  document.removeEventListener("mousemove", handleMouseMove);
362
382
  document.removeEventListener("keydown", handleKeyDown);
363
383
  };
@@ -428,4 +448,4 @@ const handleHeightChange = (id: string, height: number) => {
428
448
  {/each}
429
449
  {/each}
430
450
  </div>
431
- {/if}
451
+ {/if}
@@ -1 +1 @@
1
- {"version":3,"file":"VarselManager.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VarselManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,EAGN,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAEpB,KAAK,gBAAgB,GAAI;IACzB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAC7B,CAAC;AAuYF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"VarselManager.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VarselManager.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,EAGN,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAEpB,KAAK,gBAAgB,GAAI;IACzB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAC7B,CAAC;AA4ZF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -18,8 +18,9 @@ export {
18
18
  * It subscribes to the global toast state and renders the `VarselManager`
19
19
  * which handles the positioning and layout of individual toasts.
20
20
  *
21
- * Place this component once in your application's root layout (e.g., `+layout.svelte`).
22
- */
21
+ * Place this component once in your application's root layout (e.g., `+layout.svelte`).
22
+ */
23
+ import { onMount } from 'svelte';
23
24
  import VarselManager from './VarselManager.svelte';
24
25
  import {
25
26
  toastState,
@@ -63,25 +64,30 @@ export {
63
64
  } = $props();
64
65
 
65
66
  let toasts = $state<ToastData[]>([]);
66
- const instanceId = toasterInstanceManager.registerInstance();
67
+ let instanceId = $state<string | null>(null);
67
68
 
68
69
  const handleRemove = (id: string) => {
69
70
  toastState.remove(id);
70
71
  };
71
72
 
72
- $effect(() => {
73
+ onMount(() => {
74
+ const registeredInstanceId = toasterInstanceManager.registerInstance();
75
+ instanceId = registeredInstanceId;
73
76
  toasts = toastState.getToasts();
74
77
  const unsubscribe = toastState.subscribe((value) => {
75
78
  toasts = value;
76
79
  });
77
80
  return () => {
78
81
  unsubscribe();
79
- toasterInstanceManager.unregisterInstance(instanceId);
82
+ toasterInstanceManager.unregisterInstance(registeredInstanceId);
83
+ if (instanceId === registeredInstanceId) {
84
+ instanceId = null;
85
+ }
80
86
  };
81
87
  });
82
88
  </script>
83
89
 
84
- {#if toasterInstanceManager.isActiveInstance(instanceId)}
90
+ {#if instanceId && toasterInstanceManager.isActiveInstance(instanceId)}
85
91
  <VarselManager
86
92
  {toasts}
87
93
  onRemove={handleRemove}
@@ -1 +1 @@
1
- {"version":3,"file":"VarselToaster.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VarselToaster.svelte.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,OAAO,EACN,KAAK,EACL,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,aAAa,GAClB,MAAM,aAAa,CAAC;AAarB,OAAO,EAIL,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAErB,KAAK,gBAAgB,GAAI;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,kCAAkC;IAClC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAC7B,CAAC;AA6CH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"VarselToaster.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VarselToaster.svelte.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,OAAO,EACN,KAAK,EACL,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,aAAa,GAClB,MAAM,aAAa,CAAC;AAcrB,OAAO,EAIL,KAAK,aAAa,EAClB,MAAM,aAAa,CAAC;AAErB,KAAK,gBAAgB,GAAI;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,kCAAkC;IAClC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAC7B,CAAC;AAmDH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"toast-factory.d.ts","sourceRoot":"","sources":["../../src/lib/core/toast-factory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGX,YAAY,EAEZ,MAAM,SAAS,CAAC;AAgKjB,eAAO,MAAM,KAAK,cAAc,CAAC"}
1
+ {"version":3,"file":"toast-factory.d.ts","sourceRoot":"","sources":["../../src/lib/core/toast-factory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGX,YAAY,EAEZ,MAAM,SAAS,CAAC;AAuLjB,eAAO,MAAM,KAAK,cAAc,CAAC"}
@@ -15,6 +15,15 @@ const resolvePromiseState = async (value, state) => {
15
15
  const resolvedValue = typeof state === "function" ? await state(value) : await state;
16
16
  return normalizeToastData(resolvedValue);
17
17
  };
18
+ const getPromiseFallbackDescription = (error) => {
19
+ if (error instanceof Error) {
20
+ return error.message;
21
+ }
22
+ if (typeof error === "string") {
23
+ return error;
24
+ }
25
+ return "An unknown error occurred while updating the notification.";
26
+ };
18
27
  /**
19
28
  * The main entry point for creating toasts.
20
29
  * @param data - Toast configuration object or description string.
@@ -86,7 +95,7 @@ createToast.promise = (promise, options) => {
86
95
  ...loadingData,
87
96
  duration: loadingData.duration ?? 0,
88
97
  isLoading: true,
89
- showClose: loadingData.showClose ?? false,
98
+ showClose: loadingData.showClose ?? true,
90
99
  });
91
100
  const handleResult = async (state, value, defaultVariant) => {
92
101
  const payload = await resolvePromiseState(value, state);
@@ -95,16 +104,28 @@ createToast.promise = (promise, options) => {
95
104
  isLoading: false,
96
105
  duration: payload.duration,
97
106
  variant: payload.variant ?? defaultVariant,
98
- showClose: payload.showClose ?? false,
107
+ showClose: payload.showClose ?? true,
108
+ });
109
+ };
110
+ const applyPromiseFallback = (error) => {
111
+ toastState.update(toastId, {
112
+ title: "Operation failed",
113
+ description: getPromiseFallbackDescription(error),
114
+ variant: "destructive",
115
+ isLoading: false,
116
+ showClose: true,
117
+ duration: 5000,
99
118
  });
100
119
  };
101
- Promise.resolve(promise)
102
- .then(async (value) => {
103
- await handleResult(options.success, value, "success");
104
- return value;
120
+ void Promise.resolve(promise)
121
+ .then((value) => {
122
+ return handleResult(options.success, value, "success");
123
+ })
124
+ .catch((error) => {
125
+ return handleResult(options.error, error, "destructive");
105
126
  })
106
- .catch(async (error) => {
107
- await handleResult(options.error, error, "destructive");
127
+ .catch((error) => {
128
+ applyPromiseFallback(error);
108
129
  });
109
130
  return toastId;
110
131
  };
@@ -5,6 +5,7 @@
5
5
  declare class ToasterInstanceManager {
6
6
  private activeInstanceId;
7
7
  private instanceCounter;
8
+ private instanceIds;
8
9
  /**
9
10
  * Registers a new toaster instance.
10
11
  * @returns A unique instance ID.
@@ -1 +1 @@
1
- {"version":3,"file":"toaster-instances.d.ts","sourceRoot":"","sources":["../../src/lib/core/toaster-instances.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAM,sBAAsB;IAC3B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAK;IAE5B;;;OAGG;IACH,gBAAgB,IAAI,MAAM;IAQ1B;;;OAGG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAM5C;;;;OAIG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAG7C;AAED,eAAO,MAAM,sBAAsB,wBAA+B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
1
+ {"version":3,"file":"toaster-instances.d.ts","sourceRoot":"","sources":["../../src/lib/core/toaster-instances.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAM,sBAAsB;IAC3B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;OAGG;IACH,gBAAgB,IAAI,MAAM;IAY1B;;;OAGG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY5C;;;;OAIG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAG7C;AAED,eAAO,MAAM,sBAAsB,wBAA+B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -5,13 +5,16 @@
5
5
  class ToasterInstanceManager {
6
6
  activeInstanceId = null;
7
7
  instanceCounter = 0;
8
+ instanceIds = [];
8
9
  /**
9
10
  * Registers a new toaster instance.
10
11
  * @returns A unique instance ID.
11
12
  */
12
13
  registerInstance() {
13
14
  const instanceId = `toaster-${++this.instanceCounter}`;
14
- if (!this.activeInstanceId) {
15
+ this.instanceIds.push(instanceId);
16
+ if (!this.activeInstanceId ||
17
+ !this.instanceIds.includes(this.activeInstanceId)) {
15
18
  this.activeInstanceId = instanceId;
16
19
  }
17
20
  return instanceId;
@@ -21,8 +24,13 @@ class ToasterInstanceManager {
21
24
  * @param instanceId - The ID of the instance to unregister.
22
25
  */
23
26
  unregisterInstance(instanceId) {
27
+ this.instanceIds = this.instanceIds.filter((id) => id !== instanceId);
24
28
  if (this.activeInstanceId === instanceId) {
25
- this.activeInstanceId = null;
29
+ this.activeInstanceId = this.instanceIds[0] ?? null;
30
+ }
31
+ else if (this.activeInstanceId &&
32
+ !this.instanceIds.includes(this.activeInstanceId)) {
33
+ this.activeInstanceId = this.instanceIds[0] ?? null;
26
34
  }
27
35
  }
28
36
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "varsel",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Headless yet opinionated toast notifications for Svelte apps.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  "build": "bun run build:css && svelte-package",
42
42
  "check": "svelte-check --tsconfig ./tsconfig.json",
43
43
  "dev": "bun run build:css:watch & svelte-package --watch",
44
+ "test": "vitest run",
44
45
  "lint": "tsc -p tsconfig.json --noEmit",
45
46
  "prepublishOnly": "bun run build"
46
47
  },