fix(runtime): track useFocus autoFocus updates
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,38 @@ test("useFocusManager().activeId tracks the currently focused component", async
|
|||||||
expect(activeId.value).toBe("a");
|
expect(activeId.value).toBe("a");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("useFocus autoFocus prop update focuses when no item is focused", async () => {
|
||||||
|
let activeId!: ReturnType<typeof useFocusManager>["activeId"];
|
||||||
|
const autoFocus = shallowRef(false);
|
||||||
|
|
||||||
|
const Item = defineComponent({
|
||||||
|
props: {
|
||||||
|
id: { type: String, required: true },
|
||||||
|
autoFocus: Boolean,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { isFocused } = useFocus(props);
|
||||||
|
return () => <Text>{isFocused.value ? "focused" : "unfocused"}</Text>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = defineComponent(() => {
|
||||||
|
activeId = useFocusManager().activeId;
|
||||||
|
return () => <Item id="item" autoFocus={autoFocus.value} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
// Locks the vue API-surface sentinel: `activeId` is a ShallowRef whose EMPTY
|
||||||
// value is `null` (Ink's equivalent is `undefined`). See ink-divergences.md
|
// value is `null` (Ink's equivalent is `undefined`). See ink-divergences.md
|
||||||
// ("`useFocusManager().activeId` empty value is `null`, not `undefined`").
|
// ("`useFocusManager().activeId` empty value is `null`, not `undefined`").
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { FocusContextKey, StdinContextKey } from "../context.ts";
|
|||||||
let nextAutoId = 0;
|
let nextAutoId = 0;
|
||||||
|
|
||||||
export interface UseFocusOptions {
|
export interface UseFocusOptions {
|
||||||
autoFocus?: boolean;
|
autoFocus?: MaybeRefOrGetter<boolean>;
|
||||||
isActive?: MaybeRefOrGetter<boolean>;
|
isActive?: MaybeRefOrGetter<boolean>;
|
||||||
id?: MaybeRefOrGetter<string>;
|
id?: MaybeRefOrGetter<string>;
|
||||||
}
|
}
|
||||||
@@ -67,11 +67,11 @@ export function useFocus(options: UseFocusOptions = {}): {
|
|||||||
currentId = undefined;
|
currentId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = (id: string) => {
|
const register = (id: string, autoFocus: boolean) => {
|
||||||
unsubscribe = ctx.subscribe(id, (v) => {
|
unsubscribe = ctx.subscribe(id, (v) => {
|
||||||
isFocused.value = v;
|
isFocused.value = v;
|
||||||
});
|
});
|
||||||
ctx.add(id, { autoFocus: options.autoFocus });
|
ctx.add(id, { autoFocus });
|
||||||
currentId = id;
|
currentId = id;
|
||||||
// Apply the current active state to the freshly-registered id.
|
// Apply the current active state to the freshly-registered id.
|
||||||
if (toValue(isActive)) {
|
if (toValue(isActive)) {
|
||||||
@@ -83,11 +83,11 @@ export function useFocus(options: UseFocusOptions = {}): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => toValue(options.id) ?? fallbackId,
|
() => [toValue(options.id) ?? fallbackId, toValue(options.autoFocus ?? false)] as const,
|
||||||
(id) => {
|
([id, autoFocus]) => {
|
||||||
unregister();
|
unregister();
|
||||||
isFocused.value = false;
|
isFocused.value = false;
|
||||||
register(id);
|
register(id, autoFocus);
|
||||||
},
|
},
|
||||||
{ immediate: true, flush: "sync" },
|
{ immediate: true, flush: "sync" },
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user