@sveltia/ui 0.37.1 → 0.37.2

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.
@@ -65,9 +65,15 @@
65
65
 
66
66
  $effect(() => {
67
67
  if (open && content) {
68
+ let cancelled = false;
69
+
68
70
  (async () => {
69
71
  await sleep(50);
70
72
 
73
+ if (cancelled) {
74
+ return;
75
+ }
76
+
71
77
  if (focusInput) {
72
78
  /** @type {HTMLInputElement | HTMLButtonElement} */ (
73
79
  content?.querySelector('input, button.primary')
@@ -77,7 +83,13 @@
77
83
  modal?.focus();
78
84
  }
79
85
  })();
86
+
87
+ return () => {
88
+ cancelled = true;
89
+ };
80
90
  }
91
+
92
+ return undefined;
81
93
  });
82
94
  </script>
83
95
 
@@ -83,19 +83,25 @@
83
83
  });
84
84
 
85
85
  onMount(() => {
86
- if (position === 'auto') {
87
- const mql = globalThis.matchMedia('(width < 1024px)');
88
-
89
- // eslint-disable-next-line jsdoc/require-jsdoc
90
- const setMode = () => {
91
- position = mql.matches
92
- ? 'bottom-center'
93
- : `bottom-${document.dir === 'rtl' ? 'left' : 'right'}`;
94
- };
95
-
96
- setMode();
97
- mql.addEventListener('change', setMode);
86
+ if (position !== 'auto') {
87
+ return undefined;
98
88
  }
89
+
90
+ const mql = globalThis.matchMedia('(width < 1024px)');
91
+
92
+ // eslint-disable-next-line jsdoc/require-jsdoc
93
+ const setMode = () => {
94
+ position = mql.matches
95
+ ? 'bottom-center'
96
+ : `bottom-${document.dir === 'rtl' ? 'left' : 'right'}`;
97
+ };
98
+
99
+ setMode();
100
+ mql.addEventListener('change', setMode);
101
+
102
+ return () => {
103
+ mql.removeEventListener('change', setMode);
104
+ };
99
105
  });
100
106
 
101
107
  $effect(() => {
@@ -52,15 +52,16 @@
52
52
  };
53
53
 
54
54
  applyTheme();
55
+ mediaQuery.addEventListener('change', applyTheme);
55
56
 
56
- // eslint-disable-next-line jsdoc/require-jsdoc
57
- mediaQuery.onchange = () => {
58
- applyTheme();
59
- };
60
-
61
- globalThis.setTimeout(() => {
57
+ const fontTimer = globalThis.setTimeout(() => {
62
58
  fontLoaded = true;
63
59
  }, 1000);
60
+
61
+ return () => {
62
+ mediaQuery.removeEventListener('change', applyTheme);
63
+ globalThis.clearTimeout(fontTimer);
64
+ };
64
65
  });
65
66
  </script>
66
67
 
@@ -78,7 +78,7 @@
78
78
  /**
79
79
  * @type {{ style: { inset: string | undefined, zIndex: number | undefined, minWidth: string |
80
80
  * undefined, maxWidth: string | undefined, height: string | undefined }, open: boolean,
81
- * checkPosition: () => void } | undefined}
81
+ * checkPosition: () => void, destroy: () => void } | undefined}
82
82
  */
83
83
  let popupInstance = $state();
84
84
  let hoveredTimeout = 0;
@@ -122,6 +122,11 @@
122
122
 
123
123
  onMount(() => {
124
124
  touchEnabled = globalThis.matchMedia('(pointer: coarse)').matches;
125
+
126
+ return () => {
127
+ popupInstance?.destroy?.();
128
+ globalThis.clearTimeout(hoveredTimeout);
129
+ };
125
130
  });
126
131
  </script>
127
132
 
@@ -38,6 +38,8 @@ declare class Popup {
38
38
  position: PopupPosition;
39
39
  positionBaseElement: HTMLElement;
40
40
  id: string;
41
+ intersectionObserver: IntersectionObserver;
42
+ resizeObserver: ResizeObserver;
41
43
  _rafId: number;
42
44
  /**
43
45
  * Whether the anchor element is disabled.
@@ -57,6 +59,10 @@ declare class Popup {
57
59
  * Hide the popup immediately (when the anchor is being hidden).
58
60
  */
59
61
  hideImmediately(): Promise<void>;
62
+ /**
63
+ * Dispose of the popup, disconnecting observers and canceling pending work.
64
+ */
65
+ destroy(): void;
60
66
  #private;
61
67
  }
62
68
  import type { PopupPosition } from '../typedefs';
@@ -193,11 +193,12 @@ class Popup {
193
193
  }
194
194
  });
195
195
 
196
- new IntersectionObserver(([entry]) => {
196
+ this.intersectionObserver = new IntersectionObserver(([entry]) => {
197
197
  if (!entry.isIntersecting && this.open) {
198
198
  this.hideImmediately();
199
199
  }
200
- }).observe(this.anchorElement);
200
+ });
201
+ this.intersectionObserver.observe(this.anchorElement);
201
202
 
202
203
  // Close the popup when the backdrop, a menu item or an option is clicked
203
204
  on(this.popupElement, 'click', (event) => {
@@ -226,10 +227,11 @@ class Popup {
226
227
  });
227
228
 
228
229
  // Update the popup width when the base element is resized
229
- new ResizeObserver(() => {
230
+ this.resizeObserver = new ResizeObserver(() => {
230
231
  cancelAnimationFrame(this._rafId);
231
232
  this._rafId = requestAnimationFrame(() => this.checkPosition());
232
- }).observe(this.positionBaseElement);
233
+ });
234
+ this.resizeObserver.observe(this.positionBaseElement);
233
235
  }
234
236
 
235
237
  /**
@@ -265,6 +267,19 @@ class Popup {
265
267
  await sleep(50);
266
268
  this.popupElement.hidden = false;
267
269
  }
270
+
271
+ /**
272
+ * Dispose of the popup, disconnecting observers and canceling pending work.
273
+ */
274
+ destroy() {
275
+ this.intersectionObserver?.disconnect();
276
+ this.resizeObserver?.disconnect();
277
+ this.observer?.disconnect();
278
+
279
+ if (this._rafId) {
280
+ cancelAnimationFrame(this._rafId);
281
+ }
282
+ }
268
283
  }
269
284
 
270
285
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.37.1",
3
+ "version": "0.37.2",
4
4
  "description": "A collection of Svelte components and utilities for building user interfaces.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,18 +45,18 @@
45
45
  "@lexical/selection": "^0.43.0",
46
46
  "@lexical/table": "^0.43.0",
47
47
  "@lexical/utils": "^0.43.0",
48
- "@sveltia/i18n": "^1.0.2",
49
- "@sveltia/utils": "^0.10.0",
48
+ "@sveltia/i18n": "^1.0.5",
49
+ "@sveltia/utils": "^0.10.6",
50
50
  "lexical": "^0.43.0",
51
51
  "prismjs": "^1.30.0",
52
52
  "yaml": "^2.8.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@sveltejs/adapter-auto": "^7.0.1",
56
- "@sveltejs/kit": "^2.57.0",
56
+ "@sveltejs/kit": "^2.57.1",
57
57
  "@sveltejs/package": "^2.5.7",
58
58
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
59
- "@vitest/coverage-v8": "^4.1.3",
59
+ "@vitest/coverage-v8": "^4.1.5",
60
60
  "cspell": "^10.0.0",
61
61
  "eslint": "^9.39.4",
62
62
  "eslint-config-airbnb-extended": "^3.1.0",
@@ -64,24 +64,24 @@
64
64
  "eslint-plugin-import": "^2.32.0",
65
65
  "eslint-plugin-jsdoc": "^62.9.0",
66
66
  "eslint-plugin-package-json": "^0.91.1",
67
- "eslint-plugin-svelte": "^3.17.0",
68
- "globals": "^17.4.0",
69
- "happy-dom": "^20.8.9",
70
- "oxlint": "^1.59.0",
71
- "postcss": "^8.5.9",
67
+ "eslint-plugin-svelte": "^3.17.1",
68
+ "globals": "^17.5.0",
69
+ "happy-dom": "^20.9.0",
70
+ "oxlint": "^1.61.0",
71
+ "postcss": "^8.5.10",
72
72
  "postcss-html": "^1.8.1",
73
- "prettier": "^3.8.1",
73
+ "prettier": "^3.8.3",
74
74
  "prettier-plugin-svelte": "^3.5.1",
75
75
  "sass": "^1.99.0",
76
- "stylelint": "^17.6.0",
76
+ "stylelint": "^17.8.0",
77
77
  "stylelint-config-recommended-scss": "^17.0.1",
78
78
  "stylelint-scss": "^7.0.0",
79
- "svelte": "^5.55.2",
79
+ "svelte": "^5.55.4",
80
80
  "svelte-check": "^4.4.6",
81
81
  "svelte-preprocess": "^6.0.3",
82
82
  "tslib": "^2.8.1",
83
- "vite": "^8.0.7",
84
- "vitest": "^4.1.3"
83
+ "vite": "^8.0.9",
84
+ "vitest": "^4.1.5"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "svelte": "^5.0.0"