fix(runtime): type Static scoped slots

This commit is contained in:
Yunfei He
2026-06-13 23:08:24 +08:00
parent 8f2eabfa8d
commit dab5125c90
7 changed files with 161 additions and 13 deletions
@@ -11,7 +11,7 @@
* - children rejected -> the `accepted` cases error
* - prop validation lost -> the `@ts-expect-error` directives become unused
*/
import { Box, Static, Text, Transform } from "@vue-tui/runtime";
import { Box, Newline, Spacer, Static, Text, Transform } from "@vue-tui/runtime";
// Children are accepted (the shim's whole purpose under the automatic runtime).
export const accepted = [
@@ -20,13 +20,27 @@ export const accepted = [
<Text>nested child</Text>
</Box>,
<Static items={[1, 2, 3]}>
<Text>static child</Text>
{({ item, index }) => {
const renderedItem = item.toFixed(0);
const renderedIndex = index.toFixed(0);
return (
<Text>
{renderedIndex}:{renderedItem}
</Text>
);
}}
</Static>,
<Transform transform={(line) => line}>
<Text>transformed child</Text>
</Transform>,
<Newline />,
<Spacer />,
];
const invalidScopedTransformSlot = {
default: ({ item }: { item: string }) => <Text>{item}</Text>,
};
// Declared props stay validated — children must not widen the prop bag.
export const rejected = [
// @ts-expect-error `display` accepts "flex" | "none", not a number
@@ -39,4 +53,20 @@ export const rejected = [
<Transform>x</Transform>,
// @ts-expect-error `items` is required
<Static>x</Static>,
// @ts-expect-error `<Text>` has an ordinary default slot, not a scoped slot
<Text>{({ item }: { item: string }) => item}</Text>,
// @ts-expect-error `<Box>` has no named `foo` slot
<Box>{{ foo: () => <Text>x</Text> }}</Box>,
// @ts-expect-error `<Transform>` has an ordinary default slot, not a scoped slot
<Transform transform={(line) => line}>{invalidScopedTransformSlot}</Transform>,
// @ts-expect-error `<Newline>` does not accept children
<Newline>x</Newline>,
// @ts-expect-error `<Spacer>` does not accept children
<Spacer>x</Spacer>,
// @ts-expect-error `<Static>` children are Vue scoped slots: one `{ item, index }` object
<Static items={[1]}>{(item: number, index: number) => <Text>{item + index}</Text>}</Static>,
// @ts-expect-error `style` is the supported layout style surface, not an arbitrary object
<Static items={[1]} style={{ flexDirektion: "row" }}>
{({ item }) => <Text>{item}</Text>}
</Static>,
];
@@ -16,10 +16,16 @@ import { shallowRef } from "vue";
import { useApp, useInput, usePaste, useStdin, useStdout, useStderr } from "@vue-tui/runtime";
import type {
BoxProps,
BoxLayoutStyle,
TextProps,
StaticChildren,
StaticProps,
StaticSlot,
StaticSlotProps,
StaticStyle,
TransformProps,
NewlineProps,
SpacerProps,
Key,
WindowSize,
CursorPosition,
@@ -41,10 +47,25 @@ expectTypeOf<BoxProps["backgroundColor"]>().toEqualTypeOf<string | undefined>();
expectTypeOf<BoxProps["borderColor"]>().toEqualTypeOf<string | undefined>();
expectTypeOf<BoxProps["borderBackgroundColor"]>().toEqualTypeOf<string | undefined>();
expectTypeOf<StaticProps["items"]>().toEqualTypeOf<unknown[]>();
expectTypeOf<StaticProps<string>["items"]>().toEqualTypeOf<string[]>();
expectTypeOf<StaticProps["style"]>().toEqualTypeOf<StaticStyle | undefined>();
expectTypeOf<StaticStyle>().toEqualTypeOf<BoxLayoutStyle>();
expectTypeOf<StaticStyle["flexDirection"]>().toEqualTypeOf<BoxProps["flexDirection"]>();
expectTypeOf<StaticSlotProps<string>>().toEqualTypeOf<{ item: string; index: number }>();
expectTypeOf<StaticSlot<string>>().toEqualTypeOf<
(props: StaticSlotProps<string>) => import("vue").VNodeChild
>();
expectTypeOf<StaticChildren<string>>().toEqualTypeOf<
| StaticSlot<string>
| {
default: StaticSlot<string>;
}
>();
expectTypeOf<TransformProps["transform"]>().toEqualTypeOf<
(line: string, lineIndex: number) => string
>();
expectTypeOf<NewlineProps["count"]>().toEqualTypeOf<number | undefined>();
expectTypeOf<keyof SpacerProps>().toEqualTypeOf<never>();
// Framework-neutral data shapes, mirrored from Ink exactly.
expectTypeOf<WindowSize>().toEqualTypeOf<{ readonly columns: number; readonly rows: number }>();