@humanspeak/svelte-motion 0.1.20 → 0.1.22

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.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024-2025 Humanspeak, Inc.
1
+ Copyright (c) 2024-2026 Humanspeak, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -19,7 +19,7 @@
19
19
  import { sleep } from '../utils/testing'
20
20
  import { animate, type AnimationOptions, type DOMKeyframesDefinition } from 'motion'
21
21
  import { isPlaywrightEnv, pwLog } from '../utils/log'
22
- import { type Snippet } from 'svelte'
22
+ import { onDestroy, type Snippet } from 'svelte'
23
23
  import { VOID_TAGS } from '../utils/constants'
24
24
  import { mergeTransitions, animateWithLifecycle } from '../utils/animation'
25
25
  import { attachWhileTap } from '../utils/interaction'
@@ -37,7 +37,6 @@
37
37
  import { mergeInlineStyles } from '../utils/style'
38
38
  import { isNativelyFocusable } from '../utils/a11y'
39
39
  import {
40
- usePresence,
41
40
  getAnimatePresenceContext,
42
41
  getPresenceDepth,
43
42
  setPresenceDepth
@@ -154,10 +153,20 @@
154
153
  )
155
154
  )
156
155
 
157
- // Register with AnimatePresence so onDestroy triggers exit cloning
156
+ // Register onDestroy at component level (guaranteed to work in Svelte 5)
157
+ // usePresence() cannot be called inside $effect because it uses getContext() and onDestroy(),
158
+ // which must be called during component initialization.
159
+ if (context) {
160
+ onDestroy(() => {
161
+ pwLog('[presence] onDestroy triggered', { key: presenceKey })
162
+ context.unregisterChild(presenceKey)
163
+ })
164
+ }
165
+
166
+ // Reactively update registration when element/exit/transition props change
158
167
  $effect(() => {
159
- if (element) {
160
- usePresence(
168
+ if (element && context && resolvedExit) {
169
+ context.registerChild(
161
170
  presenceKey,
162
171
  element,
163
172
  resolvedExit,
@@ -602,7 +611,7 @@
602
611
  }
603
612
  })
604
613
 
605
- // whileTap handling without relying on motion.press (fallback compatible)
614
+ // whileTap handling via motion-dom's press()
606
615
  $effect(() => {
607
616
  if (!(element && isLoaded === 'ready' && isNotEmpty(whileTapProp))) return
608
617
  return attachWhileTap(
@@ -15,14 +15,15 @@ export declare const buildTapResetRecord: (initial: Record<string, unknown>, ani
15
15
  /**
16
16
  * Attach whileTap interactions to an element.
17
17
  *
18
- * On `pointerdown`, animates to the whileTap definition. On `pointerup` or
19
- * `pointercancel`, restores overlapping keys back to the animate-or-initial
20
- * baseline.
18
+ * Uses motion-dom's `press()` for pointer and Enter-key handling (with
19
+ * primary-pointer filtering, drag interop, and global release listeners).
20
+ * Space-key support is added manually since `press()` only handles Enter.
21
21
  *
22
22
  * @param el Element to attach listeners to.
23
23
  * @param whileTap While-tap keyframe record.
24
24
  * @param initial Initial keyframe record.
25
25
  * @param animateDef Animate keyframe record.
26
+ * @param callbacks Optional lifecycle callbacks.
26
27
  * @return Cleanup function to remove listeners.
27
28
  */
28
29
  export declare const attachWhileTap: (el: HTMLElement, whileTap: Record<string, unknown> | undefined, initial?: Record<string, unknown>, animateDef?: Record<string, unknown>, callbacks?: {
@@ -1,7 +1,7 @@
1
1
  import { isHoverCapable, splitHoverDefinition } from './hover';
2
2
  import { pwLog } from './log';
3
- import { parseMatrixScale } from './transform';
4
3
  import { animate } from 'motion';
4
+ import { press } from 'motion-dom';
5
5
  /**
6
6
  * Build a reset record for whileTap on pointerup.
7
7
  *
@@ -81,122 +81,60 @@ export const buildTapResetRecord = (initial, animateDef, whileTap) => {
81
81
  /**
82
82
  * Attach whileTap interactions to an element.
83
83
  *
84
- * On `pointerdown`, animates to the whileTap definition. On `pointerup` or
85
- * `pointercancel`, restores overlapping keys back to the animate-or-initial
86
- * baseline.
84
+ * Uses motion-dom's `press()` for pointer and Enter-key handling (with
85
+ * primary-pointer filtering, drag interop, and global release listeners).
86
+ * Space-key support is added manually since `press()` only handles Enter.
87
87
  *
88
88
  * @param el Element to attach listeners to.
89
89
  * @param whileTap While-tap keyframe record.
90
90
  * @param initial Initial keyframe record.
91
91
  * @param animateDef Animate keyframe record.
92
+ * @param callbacks Optional lifecycle callbacks.
92
93
  * @return Cleanup function to remove listeners.
93
94
  */
94
95
  export const attachWhileTap = (el, whileTap, initial, animateDef, callbacks) => {
95
96
  if (!whileTap)
96
97
  return () => { };
97
- let keyboardActive = false;
98
- let activePointerId = null;
99
- let tapCtl = null;
100
- let resetCtl = null;
101
- let baselineTransform = null;
102
- const safetyGuards = typeof window !== 'undefined' &&
103
- window['SM_GESTURE_SAFE_GUARDS'] === true;
104
- const computeCanonicalBaseline = () => {
105
- // Prefer animateDef > initial for transform-relevant keys when present, else computed style
106
- const scaleKeys = ['scale', 'scaleX', 'scaleY'];
107
- const from = (def) => def && scaleKeys.some((k) => Object.prototype.hasOwnProperty.call(def, k));
108
- if (from(animateDef)) {
109
- // If animate has scale keys, prefer matrix from current style (already at animate) as baseline
110
- const t = getComputedStyle(el).transform;
111
- return t && t !== 'none' ? t : 'none';
98
+ pwLog('[tap] attached', { whileTap, initial, animateDef, hasHoverDef: !!callbacks?.hoverDef });
99
+ // Tween transitions prevent spring velocity accumulation during rapid
100
+ // press/release cycles. Cubic-bezier with slight overshoot mimics the
101
+ // reference spring feel (~275ms settle, ~7% overshoot).
102
+ const pressTransition = { duration: 0.25, ease: [0.22, 1.1, 0.36, 1] };
103
+ const releaseTransition = { duration: 0.3, ease: [0.22, 1.1, 0.36, 1] };
104
+ // Single control tracking whatever gesture animation is in-flight
105
+ // (tap, reset, or hover reapply). Every new gesture cancels the previous.
106
+ let gestureCtl = null;
107
+ const cancelGesture = () => {
108
+ if (gestureCtl) {
109
+ pwLog('[tap] cancel-gesture', {
110
+ currentTime: gestureCtl.currentTime,
111
+ transform: getComputedStyle(el).transform
112
+ });
112
113
  }
113
- if (from(initial)) {
114
- const t = getComputedStyle(el).transform;
115
- return t && t !== 'none' ? t : 'none';
114
+ try {
115
+ // Use stop() instead of cancel(). cancel() reverts to the
116
+ // pre-animation state (causing a visual snap), while stop()
117
+ // holds the element at its current interpolated position.
118
+ gestureCtl?.stop();
116
119
  }
117
- const t = getComputedStyle(el).transform;
118
- return t || 'none';
119
- };
120
- const handlePointerDown = (event) => {
121
- // Capture pointer so we receive up/cancel even if pointer leaves the element
122
- if (typeof event.pointerId === 'number') {
123
- try {
124
- if ('setPointerCapture' in el) {
125
- el.setPointerCapture(event.pointerId);
126
- }
127
- }
128
- catch {
129
- // noop if not supported
130
- }
131
- activePointerId = event.pointerId;
132
- // Attach global listeners to catch off-element releases (even if capture unsupported)
133
- window.addEventListener('pointerup', handlePointerUp);
134
- window.addEventListener('pointercancel', handlePointerCancel);
135
- document.addEventListener('pointerup', handlePointerUp);
136
- document.addEventListener('pointercancel', handlePointerCancel);
120
+ catch {
121
+ // ignore
137
122
  }
138
- const incomingScale = parseMatrixScale(getComputedStyle(el).transform) ?? 1;
139
- pwLog('[tap] pointerdown', {
123
+ gestureCtl = null;
124
+ };
125
+ const animateTap = () => {
126
+ pwLog('[tap] animate-tap', {
140
127
  w: el.getBoundingClientRect().width,
141
128
  h: el.getBoundingClientRect().height,
142
129
  transform: getComputedStyle(el).transform,
143
- incomingScale,
144
- resetCtlActive: resetCtl !== null
130
+ whileTap,
131
+ gestureActive: gestureCtl !== null
145
132
  });
146
- // Cancel any in-flight reset and rebase to canonical baseline
147
- try {
148
- resetCtl?.cancel();
149
- }
150
- catch {
151
- // ignore
152
- }
153
- resetCtl = null;
154
- if (!baselineTransform)
155
- baselineTransform = computeCanonicalBaseline();
156
- // Apply baseline immediately before whileTap
157
- if (baselineTransform && baselineTransform !== 'none')
158
- el.style.transform = baselineTransform;
159
- else
160
- el.style.removeProperty('transform');
161
- // Software limit: if incoming scale is runaway, skip whileTap this cycle and force settle
162
- const baselineScale = parseMatrixScale(baselineTransform) ?? 1;
163
- const scaleDiff = Math.abs(incomingScale - baselineScale);
164
- const tolerance = 0.02; // 2% threshold
165
- pwLog('[tap] scale-check', { incomingScale, baselineScale, scaleDiff, tolerance });
166
- if (scaleDiff > tolerance) {
167
- pwLog('[tap] runaway-detected-skip-whileTap', {
168
- incomingScale,
169
- baselineScale,
170
- scaleDiff
171
- });
172
- callbacks?.onTapStart?.();
173
- tapCtl = null;
174
- return;
175
- }
176
- pwLog('[tap] whileTap-def', whileTap);
133
+ cancelGesture();
177
134
  callbacks?.onTapStart?.();
178
- // Cancel any existing tap animation before starting a new one
179
- try {
180
- tapCtl?.cancel();
181
- }
182
- catch {
183
- // ignore cancellation errors
184
- }
185
- tapCtl = animate(el, whileTap);
186
- // Safety clamp for runaway scale
187
- if (safetyGuards) {
188
- const a = parseMatrixScale(getComputedStyle(el).transform) ??
189
- parseMatrixScale(el.style.transform);
190
- if (a !== null && Math.abs(a) > 8) {
191
- if (baselineTransform && baselineTransform !== 'none')
192
- el.style.transform = baselineTransform;
193
- else
194
- el.style.removeProperty('transform');
195
- pwLog('[tap] clamp-on-down', { a, restored: getComputedStyle(el).transform });
196
- }
197
- }
198
- Promise.resolve(tapCtl.finished)
199
- .then(() => pwLog('[tap] applied', {
135
+ gestureCtl = animate(el, whileTap, pressTransition);
136
+ Promise.resolve(gestureCtl?.finished)
137
+ .then(() => pwLog('[tap] tap-applied', {
200
138
  w: el.getBoundingClientRect().width,
201
139
  h: el.getBoundingClientRect().height,
202
140
  transform: getComputedStyle(el).transform
@@ -204,210 +142,130 @@ export const attachWhileTap = (el, whileTap, initial, animateDef, callbacks) =>
204
142
  .catch(() => { });
205
143
  };
206
144
  const reapplyHoverIfActive = () => {
207
- if (!callbacks?.hoverDef)
145
+ if (!callbacks?.hoverDef) {
146
+ pwLog('[tap] hover-reapply-skip', { reason: 'no hoverDef' });
208
147
  return false;
209
- if (!isHoverCapable())
148
+ }
149
+ if (!isHoverCapable()) {
150
+ pwLog('[tap] hover-reapply-skip', { reason: 'not hover-capable' });
210
151
  return false;
152
+ }
211
153
  try {
212
- if (!el.matches(':hover'))
154
+ if (!el.matches(':hover')) {
155
+ pwLog('[tap] hover-reapply-skip', { reason: 'not :hover' });
213
156
  return false;
157
+ }
214
158
  }
215
159
  catch {
160
+ pwLog('[tap] hover-reapply-skip', { reason: 'matches threw' });
216
161
  return false;
217
162
  }
218
- const { keyframes, transition } = splitHoverDefinition(callbacks.hoverDef);
219
- animate(el, keyframes, (transition ?? callbacks.hoverFallbackTransition));
163
+ const { keyframes } = splitHoverDefinition(callbacks.hoverDef);
164
+ pwLog('[tap] hover-reapply', {
165
+ keyframes,
166
+ transform: getComputedStyle(el).transform,
167
+ w: el.getBoundingClientRect().width
168
+ });
169
+ gestureCtl = animate(el, keyframes, releaseTransition);
170
+ Promise.resolve(gestureCtl?.finished)
171
+ .then(() => pwLog('[tap] hover-reapply-done', {
172
+ w: el.getBoundingClientRect().width,
173
+ transform: getComputedStyle(el).transform
174
+ }))
175
+ .catch(() => { });
220
176
  return true;
221
177
  };
222
- const handlePointerUp = (event) => {
223
- if (typeof event.pointerId === 'number' && activePointerId !== null) {
224
- if (event.pointerId !== activePointerId)
225
- return;
226
- try {
227
- if ('releasePointerCapture' in el)
228
- el.releasePointerCapture(event.pointerId);
229
- }
230
- catch {
231
- // noop
232
- }
233
- activePointerId = null;
234
- window.removeEventListener('pointerup', handlePointerUp);
235
- window.removeEventListener('pointercancel', handlePointerCancel);
236
- document.removeEventListener('pointerup', handlePointerUp);
237
- document.removeEventListener('pointercancel', handlePointerCancel);
238
- }
239
- callbacks?.onTap?.();
240
- pwLog('[tap] pointerup', {
178
+ const animateReset = (success) => {
179
+ pwLog('[tap] animate-reset', {
180
+ success,
241
181
  w: el.getBoundingClientRect().width,
242
182
  h: el.getBoundingClientRect().height,
243
- transform: getComputedStyle(el).transform
183
+ transform: getComputedStyle(el).transform,
184
+ gestureActive: gestureCtl !== null
244
185
  });
245
- if (!whileTap)
246
- return;
247
- // Ensure the whileTap animation can't finish and overwrite our reset
248
- try {
249
- tapCtl?.cancel();
250
- }
251
- catch {
252
- // ignore
253
- }
254
- tapCtl = null;
255
- const style = el.style;
256
- if (baselineTransform && baselineTransform !== 'none')
257
- style.transform = baselineTransform;
186
+ if (success)
187
+ callbacks?.onTap?.();
258
188
  else
259
- style.removeProperty('transform');
260
- pwLog('[tap] transform-restored', {
261
- baseline: baselineTransform,
262
- now: getComputedStyle(el).transform
263
- });
189
+ callbacks?.onTapCancel?.();
190
+ cancelGesture();
191
+ // If still hovering after a successful tap, animate to hover state
192
+ if (success && reapplyHoverIfActive())
193
+ return;
264
194
  const resetRecord = buildTapResetRecord(initial ?? {}, animateDef ?? {}, whileTap ?? {});
265
195
  pwLog('[tap] reset-record', resetRecord);
266
196
  if (Object.keys(resetRecord).length > 0) {
267
- // Use a very fast transition to minimize overshoot window for re-entrancy
268
- const resetTransition = {
269
- duration: 0.08
270
- };
271
- resetCtl = animate(el, resetRecord, resetTransition);
272
- Promise.resolve(resetCtl.finished)
273
- .then(() => pwLog('[tap] reset-finished', {
197
+ gestureCtl = animate(el, resetRecord, releaseTransition);
198
+ Promise.resolve(gestureCtl?.finished)
199
+ .then(() => pwLog('[tap] reset-done', {
274
200
  w: el.getBoundingClientRect().width,
275
201
  h: el.getBoundingClientRect().height,
276
202
  transform: getComputedStyle(el).transform
277
203
  }))
278
- .catch(() => { })
279
- .finally(() => {
280
- resetCtl = null;
281
- });
204
+ .catch(() => { });
282
205
  }
283
- // After baseline and reset committed, optionally reapply hover on next frame
284
- queueMicrotask(() => {
285
- reapplyHoverIfActive();
286
- });
287
206
  };
288
- const handlePointerCancel = (event) => {
289
- if (typeof event.pointerId === 'number' && activePointerId !== null) {
290
- if (event.pointerId !== activePointerId)
291
- return;
292
- try {
293
- if ('releasePointerCapture' in el)
294
- el.releasePointerCapture(event.pointerId);
295
- }
296
- catch {
297
- // noop
298
- }
299
- activePointerId = null;
300
- window.removeEventListener('pointerup', handlePointerUp);
301
- window.removeEventListener('pointercancel', handlePointerCancel);
302
- document.removeEventListener('pointerup', handlePointerUp);
303
- document.removeEventListener('pointercancel', handlePointerCancel);
304
- }
305
- callbacks?.onTapCancel?.();
306
- pwLog('[tap] cancel', {
207
+ // Use press() for pointer + Enter key handling
208
+ const cancelPress = press(el, () => {
209
+ pwLog('[tap] press-start', {
307
210
  w: el.getBoundingClientRect().width,
308
- h: el.getBoundingClientRect().height
211
+ transform: getComputedStyle(el).transform
309
212
  });
310
- // On cancel, also restore baseline if available
311
- if (initial || animateDef) {
312
- try {
313
- tapCtl?.cancel();
314
- }
315
- catch {
316
- // ignore
317
- }
318
- tapCtl = null;
319
- const resetRecord = buildTapResetRecord(initial ?? {}, animateDef ?? {}, whileTap ?? {});
320
- if (Object.keys(resetRecord).length > 0) {
321
- animate(el, resetRecord);
322
- }
323
- }
324
- };
325
- const handleKeyDown = (e) => {
326
- if (!(e.key === 'Enter' || e.key === ' ' || e.key === 'Space'))
213
+ animateTap();
214
+ return (_endEvent, { success }) => {
215
+ pwLog('[tap] press-end', {
216
+ success,
217
+ w: el.getBoundingClientRect().width,
218
+ transform: getComputedStyle(el).transform
219
+ });
220
+ animateReset(success);
221
+ };
222
+ });
223
+ // Add Space key support (press() only handles Enter)
224
+ let spaceActive = false;
225
+ const onKeyDown = (e) => {
226
+ if (e.key !== ' ' && e.key !== 'Space')
327
227
  return;
328
- // Prevent page scroll/activation for Space
329
- if (e.key === ' ' || e.key === 'Space')
330
- e.preventDefault?.();
331
- if (keyboardActive)
228
+ e.preventDefault();
229
+ if (spaceActive)
332
230
  return;
333
- keyboardActive = true;
334
- callbacks?.onTapStart?.();
335
- pwLog('[tap] keydown', {
336
- key: e.key,
231
+ spaceActive = true;
232
+ pwLog('[tap] space-down', {
337
233
  w: el.getBoundingClientRect().width,
338
- h: el.getBoundingClientRect().height
234
+ transform: getComputedStyle(el).transform
339
235
  });
340
- try {
341
- tapCtl?.cancel();
342
- }
343
- catch {
344
- // ignore
345
- }
346
- tapCtl = animate(el, whileTap);
236
+ animateTap();
347
237
  };
348
- const handleKeyUp = (e) => {
349
- if (!(e.key === 'Enter' || e.key === ' ' || e.key === 'Space'))
238
+ const onKeyUp = (e) => {
239
+ if (e.key !== ' ' && e.key !== 'Space')
350
240
  return;
351
- // Prevent page scroll/activation for Space
352
- if (e.key === ' ' || e.key === 'Space')
353
- e.preventDefault?.();
354
- if (!keyboardActive)
241
+ e.preventDefault();
242
+ if (!spaceActive)
355
243
  return;
356
- keyboardActive = false;
357
- callbacks?.onTap?.();
358
- pwLog('[tap] keyup', {
359
- key: e.key,
244
+ spaceActive = false;
245
+ pwLog('[tap] space-up', {
360
246
  w: el.getBoundingClientRect().width,
361
- h: el.getBoundingClientRect().height
247
+ transform: getComputedStyle(el).transform
362
248
  });
363
- if (reapplyHoverIfActive())
364
- return;
365
- if (initial || animateDef) {
366
- try {
367
- tapCtl?.cancel();
368
- }
369
- catch {
370
- // ignore
371
- }
372
- tapCtl = null;
373
- const resetRecord = buildTapResetRecord(initial ?? {}, animateDef ?? {}, whileTap ?? {});
374
- if (Object.keys(resetRecord).length > 0) {
375
- animate(el, resetRecord);
376
- }
377
- }
249
+ animateReset(true);
378
250
  };
379
- const handleBlur = () => {
380
- if (!keyboardActive)
251
+ const onBlur = () => {
252
+ if (!spaceActive)
381
253
  return;
382
- keyboardActive = false;
383
- callbacks?.onTapCancel?.();
254
+ spaceActive = false;
384
255
  pwLog('[tap] blur', {
385
256
  w: el.getBoundingClientRect().width,
386
- h: el.getBoundingClientRect().height
257
+ transform: getComputedStyle(el).transform
387
258
  });
388
- if (initial || animateDef) {
389
- const resetRecord = buildTapResetRecord(initial ?? {}, animateDef ?? {}, whileTap ?? {});
390
- if (Object.keys(resetRecord).length > 0) {
391
- animate(el, resetRecord);
392
- }
393
- }
259
+ animateReset(false);
394
260
  };
395
- el.addEventListener('pointerdown', handlePointerDown);
396
- el.addEventListener('pointerup', handlePointerUp);
397
- el.addEventListener('pointercancel', handlePointerCancel);
398
- el.addEventListener('keydown', handleKeyDown);
399
- el.addEventListener('keyup', handleKeyUp);
400
- el.addEventListener('blur', handleBlur);
261
+ el.addEventListener('keydown', onKeyDown);
262
+ el.addEventListener('keyup', onKeyUp);
263
+ el.addEventListener('blur', onBlur);
401
264
  return () => {
402
- el.removeEventListener('pointerdown', handlePointerDown);
403
- el.removeEventListener('pointerup', handlePointerUp);
404
- el.removeEventListener('pointercancel', handlePointerCancel);
405
- window.removeEventListener('pointerup', handlePointerUp);
406
- window.removeEventListener('pointercancel', handlePointerCancel);
407
- document.removeEventListener('pointerup', handlePointerUp);
408
- document.removeEventListener('pointercancel', handlePointerCancel);
409
- el.removeEventListener('keydown', handleKeyDown);
410
- el.removeEventListener('keyup', handleKeyUp);
411
- el.removeEventListener('blur', handleBlur);
265
+ pwLog('[tap] cleanup');
266
+ cancelPress();
267
+ el.removeEventListener('keydown', onKeyDown);
268
+ el.removeEventListener('keyup', onKeyUp);
269
+ el.removeEventListener('blur', onBlur);
412
270
  };
413
271
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "A lightweight animation library for Svelte 5 that provides smooth, hardware-accelerated animations. Features include spring physics, custom easing, and fluid transitions. Built on top of the motion library, it offers a simple API for creating complex animations with minimal code. Perfect for interactive UIs, micro-interactions, and engaging user experiences.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -53,58 +53,58 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "motion": "^12.33.2",
57
- "motion-dom": "^12.33.2"
56
+ "motion": "^12.34.2",
57
+ "motion-dom": "^12.34.2"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@changesets/cli": "^2.29.8",
61
61
  "@eslint/compat": "^2.0.2",
62
62
  "@eslint/js": "^10.0.1",
63
63
  "@playwright/test": "^1.58.2",
64
- "@sveltejs/adapter-auto": "^7.0.0",
65
- "@sveltejs/kit": "^2.50.2",
64
+ "@sveltejs/adapter-auto": "^7.0.1",
65
+ "@sveltejs/kit": "^2.52.2",
66
66
  "@sveltejs/package": "^2.5.7",
67
67
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
68
68
  "@tailwindcss/aspect-ratio": "^0.4.2",
69
69
  "@tailwindcss/container-queries": "^0.1.1",
70
70
  "@tailwindcss/forms": "^0.5.11",
71
- "@tailwindcss/postcss": "^4.1.18",
71
+ "@tailwindcss/postcss": "^4.2.0",
72
72
  "@tailwindcss/typography": "^0.5.19",
73
73
  "@testing-library/jest-dom": "^6.9.1",
74
74
  "@testing-library/svelte": "^5.3.1",
75
- "@types/node": "^25.2.2",
75
+ "@types/node": "^25.3.0",
76
76
  "@vitest/coverage-v8": "^4.0.18",
77
77
  "concurrently": "^9.2.1",
78
- "eslint": "^9.39.2",
78
+ "eslint": "^10.0.0",
79
79
  "eslint-config-prettier": "10.1.8",
80
80
  "eslint-plugin-import": "2.32.0",
81
- "eslint-plugin-svelte": "3.14.0",
81
+ "eslint-plugin-svelte": "3.15.0",
82
82
  "eslint-plugin-unused-imports": "4.4.1",
83
83
  "esm-env": "^1.2.2",
84
84
  "globals": "^17.3.0",
85
85
  "html-tags": "^5.1.0",
86
86
  "html-void-elements": "^3.0.0",
87
87
  "husky": "^9.1.7",
88
- "jsdom": "^28.0.0",
88
+ "jsdom": "^28.1.0",
89
89
  "prettier": "^3.8.1",
90
90
  "prettier-plugin-organize-imports": "^4.3.0",
91
91
  "prettier-plugin-sort-json": "^4.2.0",
92
- "prettier-plugin-svelte": "^3.4.1",
92
+ "prettier-plugin-svelte": "^3.5.0",
93
93
  "prettier-plugin-tailwindcss": "^0.7.2",
94
94
  "publint": "^0.3.17",
95
95
  "runed": "0.37.1",
96
- "svelte": "^5.50.0",
97
- "svelte-check": "^4.3.6",
96
+ "svelte": "^5.53.0",
97
+ "svelte-check": "^4.4.1",
98
98
  "svg-tags": "^1.0.0",
99
- "tailwind-merge": "^3.4.0",
99
+ "tailwind-merge": "^3.5.0",
100
100
  "tailwind-variants": "^3.2.2",
101
- "tailwindcss": "^4.1.18",
101
+ "tailwindcss": "^4.2.0",
102
102
  "tailwindcss-animate": "^1.0.7",
103
103
  "tsx": "^4.21.0",
104
104
  "typescript": "^5.9.3",
105
- "typescript-eslint": "^8.54.0",
105
+ "typescript-eslint": "^8.56.0",
106
106
  "vite": "^7.3.1",
107
- "vite-tsconfig-paths": "^6.1.0",
107
+ "vite-tsconfig-paths": "^6.1.1",
108
108
  "vitest": "^4.0.18"
109
109
  },
110
110
  "peerDependencies": {