diff --git a/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts b/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts index b00714550..109cf4d29 100644 --- a/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts +++ b/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts @@ -11,6 +11,7 @@ import type { GridListSelectionMode } from './types'; import { gridListMorfo } from '../../../morfo/components/grid-list'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; +import type { TimerHandle } from '$timer'; const attrs = createAttrs(gridListMorfo); @@ -55,7 +56,7 @@ export class GridListProvider { readonly field = FieldProvider.get(); private typeaheadBuffer = ''; - private typeaheadTimer: ReturnType | null = null; + private typeaheadTimer: TimerHandle | null = null; /** Anchor for Shift+Click range selection. */ private anchor: string | null = null; @@ -71,9 +72,7 @@ export class GridListProvider { }); $effect(() => { - return () => { - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - }; + return () => this.clearTypeaheadTimer(); }); } @@ -367,10 +366,16 @@ export class GridListProvider { private handleTypeahead(char: string, fromIndex: number) { this.typeaheadBuffer += char.toLowerCase(); - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - this.typeaheadTimer = setTimeout(() => { - this.typeaheadBuffer = ''; - }, this.opts.typeaheadTimeout.current); + this.clearTypeaheadTimer(); + this.typeaheadTimer = this.soma.uix.timers.schedule( + this.typeaheadTimerKey, + this.opts.typeaheadTimeout.current, + () => { + this.typeaheadTimer = null; + this.typeaheadBuffer = ''; + }, + { replace: true, meta: { component: 'grid-list', action: 'typeahead-reset' } } + ); const rows = this.getRows(); if (rows.length === 0) return; @@ -390,6 +395,17 @@ export class GridListProvider { } } + private clearTypeaheadTimer() { + if (this.typeaheadTimer) { + this.typeaheadTimer.cancel(); + this.typeaheadTimer = null; + } + } + + private get typeaheadTimerKey() { + return `soma:grid-list:${this.opts.id.current}:typeahead`; + } + handleRowClick(value: string, e: SomaMouseEvent) { if (this.isDisabled || this.isReadonly) return; const mode = diff --git a/src/uix/soma/components/listbox/listbox-provider.svelte.ts b/src/uix/soma/components/listbox/listbox-provider.svelte.ts index 5f1175ff5..670be2add 100644 --- a/src/uix/soma/components/listbox/listbox-provider.svelte.ts +++ b/src/uix/soma/components/listbox/listbox-provider.svelte.ts @@ -17,6 +17,7 @@ import type { ListboxSelectionMode } from './types'; import { listboxMorfo } from '../../../morfo/components/listbox'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; +import type { TimerHandle } from '$timer'; const attrs = createAttrs(listboxMorfo); @@ -64,7 +65,7 @@ export class ListboxProvider { focused = $state(false); private typeaheadBuffer = ''; - private typeaheadTimer: ReturnType | null = null; + private typeaheadTimer: TimerHandle | null = null; private constructor(opts: ListboxOpts) { this.opts = opts; @@ -78,9 +79,7 @@ export class ListboxProvider { }); $effect(() => { - return () => { - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - }; + return () => this.clearTypeaheadTimer(); }); } @@ -261,10 +260,16 @@ export class ListboxProvider { private handleTypeahead(char: string, fromIndex: number) { this.typeaheadBuffer += char.toLowerCase(); - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - this.typeaheadTimer = setTimeout(() => { - this.typeaheadBuffer = ''; - }, this.opts.typeaheadTimeout.current); + this.clearTypeaheadTimer(); + this.typeaheadTimer = this.soma.uix.timers.schedule( + this.typeaheadTimerKey, + this.opts.typeaheadTimeout.current, + () => { + this.typeaheadTimer = null; + this.typeaheadBuffer = ''; + }, + { replace: true, meta: { component: 'listbox', action: 'typeahead-reset' } } + ); const items = this.getItems(); if (items.length === 0) return; @@ -284,6 +289,17 @@ export class ListboxProvider { } } + private clearTypeaheadTimer() { + if (this.typeaheadTimer) { + this.typeaheadTimer.cancel(); + this.typeaheadTimer = null; + } + } + + private get typeaheadTimerKey() { + return `soma:listbox:${this.opts.id.current}:typeahead`; + } + handleItemClick(value: string, e: SomaMouseEvent) { if (this.isDisabled || this.isReadonly) return; if (this.opts.selectionMode.current === 'multiple') { diff --git a/src/uix/soma/components/tree-grid/tree-grid-provider.svelte.ts b/src/uix/soma/components/tree-grid/tree-grid-provider.svelte.ts index 1660a8cb9..c3214fdf3 100644 --- a/src/uix/soma/components/tree-grid/tree-grid-provider.svelte.ts +++ b/src/uix/soma/components/tree-grid/tree-grid-provider.svelte.ts @@ -16,6 +16,7 @@ import type { TreeGridSelectionMode } from './types'; import { treeGridMorfo } from '../../../morfo/components/tree-grid'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; +import type { TimerHandle } from '$timer'; const attrs = createAttrs(treeGridMorfo); @@ -59,7 +60,7 @@ export class TreeGridProvider { } private typeaheadBuffer = ''; - private typeaheadTimer: ReturnType | null = null; + private typeaheadTimer: TimerHandle | null = null; private anchor: string | null = null; private constructor(opts: TreeGridOpts) { @@ -73,9 +74,7 @@ export class TreeGridProvider { context: TreeGridProvider.ctx }); $effect(() => { - return () => { - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - }; + return () => this.clearTypeaheadTimer(); }); } @@ -380,10 +379,16 @@ export class TreeGridProvider { private handleTypeahead(char: string, fromIndex: number) { this.typeaheadBuffer += char.toLowerCase(); - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - this.typeaheadTimer = setTimeout(() => { - this.typeaheadBuffer = ''; - }, this.opts.typeaheadTimeout.current); + this.clearTypeaheadTimer(); + this.typeaheadTimer = this.soma.uix.timers.schedule( + this.typeaheadTimerKey, + this.opts.typeaheadTimeout.current, + () => { + this.typeaheadTimer = null; + this.typeaheadBuffer = ''; + }, + { replace: true, meta: { component: 'tree-grid', action: 'typeahead-reset' } } + ); const rows = this.getVisibleRows(); if (rows.length === 0) return; @@ -403,6 +408,17 @@ export class TreeGridProvider { } } + private clearTypeaheadTimer() { + if (this.typeaheadTimer) { + this.typeaheadTimer.cancel(); + this.typeaheadTimer = null; + } + } + + private get typeaheadTimerKey() { + return `soma:tree-grid:${this.opts.id.current}:typeahead`; + } + handleRowClick(value: string, e: SomaMouseEvent) { if (this.opts.disabled.current || this.opts.readonly.current) return; const mode = diff --git a/src/uix/soma/components/tree-view/tree-view-provider.svelte.ts b/src/uix/soma/components/tree-view/tree-view-provider.svelte.ts index f21d6d59c..a6b6a43d3 100644 --- a/src/uix/soma/components/tree-view/tree-view-provider.svelte.ts +++ b/src/uix/soma/components/tree-view/tree-view-provider.svelte.ts @@ -9,6 +9,7 @@ import { TREE_VIEW_LANGS } from './langs'; import { treeViewMorfo } from '../../../morfo/components/tree-view'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; +import type { TimerHandle } from '$timer'; const attrs = createAttrs(treeViewMorfo); @@ -68,7 +69,7 @@ export class TreeViewProvider { // Typeahead private typeaheadBuffer = ''; - private typeaheadTimer: ReturnType | null = null; + private typeaheadTimer: TimerHandle | null = null; private constructor(opts: TreeViewOpts) { this.opts = opts; @@ -83,12 +84,7 @@ export class TreeViewProvider { // Cleanup typeahead timer on unmount $effect(() => { - return () => { - if (this.typeaheadTimer) { - clearTimeout(this.typeaheadTimer); - this.typeaheadTimer = null; - } - }; + return () => this.clearTypeaheadTimer(); }); } @@ -170,10 +166,16 @@ export class TreeViewProvider { if (!this.opts.typeahead.current) return; this.typeaheadBuffer += char.toLowerCase(); - if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer); - this.typeaheadTimer = setTimeout(() => { - this.typeaheadBuffer = ''; - }, 500); + this.clearTypeaheadTimer(); + this.typeaheadTimer = this.soma.uix.timers.schedule( + this.typeaheadTimerKey, + 500, + () => { + this.typeaheadTimer = null; + this.typeaheadBuffer = ''; + }, + { replace: true, meta: { component: 'tree-view', action: 'typeahead-reset' } } + ); const items = this.getVisibleItems(); const match = items.find((el) => { @@ -183,6 +185,17 @@ export class TreeViewProvider { if (match) this.focusItem(match); } + private clearTypeaheadTimer() { + if (this.typeaheadTimer) { + this.typeaheadTimer.cancel(); + this.typeaheadTimer = null; + } + } + + private get typeaheadTimerKey() { + return `soma:tree-view:${this.opts.id.current}:typeahead`; + } + // ── Keyboard (root-level) ──────────────────────────────────────────────── private handleExpandKey(activeEl: HTMLElement, items: HTMLElement[], currentIndex: number) {