Skip to content

bug(svelte): useAskable.svelte.ts registers event listeners outside lifecycle — no cleanup #252

@vamgan

Description

@vamgan

Summary

In the Svelte 5 (runes) variant of useAskable, focus and clear event listeners are registered synchronously during module initialization — not inside a lifecycle hook. There is no corresponding cleanup, so if the component unmounts, listeners remain on the context indefinitely.

Root cause

packages/svelte/src/useAskable.svelte.ts:

let focus: AskableFocus | null = $state(null);

ctx.on('focus', (f) => { focus = f; });   // registered at init, never removed
ctx.on('clear', () => { focus = null; }); // registered at init, never removed

if (options?.observe !== false) {
  ctx.observe(document, observeOpts);
}

Unlike the store-based Svelte implementation which returns a destroy() function, the runes variant has no teardown path at all.

Expected behavior

Listeners should be registered in an effect with proper cleanup, or the function should return a destroy() handle the caller is responsible for calling.

Fix

Use $effect for registration and cleanup:

$effect(() => {
  const handleFocus = (f: AskableFocus) => { focus = f; };
  const handleClear = () => { focus = null; };
  ctx.on('focus', handleFocus);
  ctx.on('clear', handleClear);
  return () => {
    ctx.off('focus', handleFocus);
    ctx.off('clear', handleClear);
  };
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: svelteSvelte adapter workbugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions