diff --git a/packages/runtime-tests/integration/focus/focus-manager.test.tsx b/packages/runtime-tests/integration/focus/focus-manager.test.tsx index 3535901..d4fd322 100644 --- a/packages/runtime-tests/integration/focus/focus-manager.test.tsx +++ b/packages/runtime-tests/integration/focus/focus-manager.test.tsx @@ -41,6 +41,38 @@ test("useFocusManager().activeId tracks the currently focused component", async expect(activeId.value).toBe("a"); }); +test("useFocus autoFocus prop update focuses when no item is focused", async () => { + let activeId!: ReturnType["activeId"]; + const autoFocus = shallowRef(false); + + const Item = defineComponent({ + props: { + id: { type: String, required: true }, + autoFocus: Boolean, + }, + setup(props) { + const { isFocused } = useFocus(props); + return () => {isFocused.value ? "focused" : "unfocused"}; + }, + }); + + const App = defineComponent(() => { + activeId = useFocusManager().activeId; + return () => ; + }); + + const { lastFrame, waitUntilRenderFlush } = await render(App); + + expect(activeId.value).toBeNull(); + expect(lastFrame()).toContain("unfocused"); + + autoFocus.value = true; + await waitUntilRenderFlush(); + + expect(activeId.value).toBe("item"); + expect(lastFrame()).toContain("focused"); +}); + // Locks the vue API-surface sentinel: `activeId` is a ShallowRef whose EMPTY // value is `null` (Ink's equivalent is `undefined`). See ink-divergences.md // ("`useFocusManager().activeId` empty value is `null`, not `undefined`"). diff --git a/packages/runtime/src/composables/useFocus.ts b/packages/runtime/src/composables/useFocus.ts index b1dcd54..d2a620f 100644 --- a/packages/runtime/src/composables/useFocus.ts +++ b/packages/runtime/src/composables/useFocus.ts @@ -12,7 +12,7 @@ import { FocusContextKey, StdinContextKey } from "../context.ts"; let nextAutoId = 0; export interface UseFocusOptions { - autoFocus?: boolean; + autoFocus?: MaybeRefOrGetter; isActive?: MaybeRefOrGetter; id?: MaybeRefOrGetter; } @@ -67,11 +67,11 @@ export function useFocus(options: UseFocusOptions = {}): { currentId = undefined; }; - const register = (id: string) => { + const register = (id: string, autoFocus: boolean) => { unsubscribe = ctx.subscribe(id, (v) => { isFocused.value = v; }); - ctx.add(id, { autoFocus: options.autoFocus }); + ctx.add(id, { autoFocus }); currentId = id; // Apply the current active state to the freshly-registered id. if (toValue(isActive)) { @@ -83,11 +83,11 @@ export function useFocus(options: UseFocusOptions = {}): { }; watch( - () => toValue(options.id) ?? fallbackId, - (id) => { + () => [toValue(options.id) ?? fallbackId, toValue(options.autoFocus ?? false)] as const, + ([id, autoFocus]) => { unregister(); isFocused.value = false; - register(id); + register(id, autoFocus); }, { immediate: true, flush: "sync" }, );