fix(runtime): type Static scoped slots
This commit is contained in:
@@ -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 }>();
|
||||
|
||||
@@ -38,6 +38,52 @@ type BorderStyle =
|
||||
// Exported so consumers can type their custom border objects (Ink parity, G13).
|
||||
export type BoxStyle = (typeof cliBoxes)[keyof cliBoxes.Boxes];
|
||||
|
||||
export type BoxLayoutStyle = Pick<
|
||||
BoxProps,
|
||||
| "flexDirection"
|
||||
| "flexGrow"
|
||||
| "flexShrink"
|
||||
| "flexBasis"
|
||||
| "flexWrap"
|
||||
| "alignItems"
|
||||
| "alignSelf"
|
||||
| "justifyContent"
|
||||
| "gap"
|
||||
| "columnGap"
|
||||
| "rowGap"
|
||||
| "width"
|
||||
| "height"
|
||||
| "minWidth"
|
||||
| "minHeight"
|
||||
| "maxWidth"
|
||||
| "maxHeight"
|
||||
| "aspectRatio"
|
||||
| "alignContent"
|
||||
| "position"
|
||||
| "top"
|
||||
| "right"
|
||||
| "bottom"
|
||||
| "left"
|
||||
| "margin"
|
||||
| "marginX"
|
||||
| "marginY"
|
||||
| "marginTop"
|
||||
| "marginBottom"
|
||||
| "marginLeft"
|
||||
| "marginRight"
|
||||
| "padding"
|
||||
| "paddingX"
|
||||
| "paddingY"
|
||||
| "paddingTop"
|
||||
| "paddingBottom"
|
||||
| "paddingLeft"
|
||||
| "paddingRight"
|
||||
| "overflow"
|
||||
| "overflowX"
|
||||
| "overflowY"
|
||||
| "display"
|
||||
>;
|
||||
|
||||
export type AriaRole =
|
||||
| "button"
|
||||
| "checkbox"
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { defineComponent, h } from "vue";
|
||||
import { defineComponent, h, type ExtractPublicPropTypes } from "vue";
|
||||
|
||||
export const Spacer = defineComponent({
|
||||
const spacerProps = {};
|
||||
|
||||
const SpacerImpl = defineComponent({
|
||||
name: "Spacer",
|
||||
props: spacerProps,
|
||||
setup() {
|
||||
return () => h("box", { flexGrow: 1, flexShrink: 1 });
|
||||
},
|
||||
});
|
||||
|
||||
/** Props accepted by `<Spacer>` — the vue-tui analogue of Ink's `SpacerProps`. */
|
||||
export type SpacerProps = ExtractPublicPropTypes<typeof spacerProps>;
|
||||
|
||||
export const Spacer = SpacerImpl as typeof SpacerImpl & {
|
||||
new (): { $props: SpacerProps & { children?: never } };
|
||||
};
|
||||
|
||||
@@ -5,8 +5,21 @@ import {
|
||||
watch,
|
||||
type ExtractPublicPropTypes,
|
||||
type PropType,
|
||||
type SlotsType,
|
||||
type VNodeChild,
|
||||
} from "vue";
|
||||
import type { WithChildren } from "./with-children.ts";
|
||||
import type { BoxLayoutStyle } from "./box.ts";
|
||||
|
||||
export interface StaticSlotProps<T = unknown> {
|
||||
item: T;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export type StaticSlot<T = unknown> = (props: StaticSlotProps<T>) => VNodeChild;
|
||||
|
||||
export type StaticChildren<T = unknown> = StaticSlot<T> | { default: StaticSlot<T> };
|
||||
|
||||
export type StaticStyle = BoxLayoutStyle;
|
||||
|
||||
const staticProps = {
|
||||
// `required: true as const` (not bare `true`): a standalone `const` widens
|
||||
@@ -14,14 +27,17 @@ const staticProps = {
|
||||
// required keys (and from the component's own `props.items` typing). The
|
||||
// literal keeps `items` required, matching Ink's `StaticProps`.
|
||||
items: { type: Array as PropType<unknown[]>, required: true as const },
|
||||
style: { type: Object as PropType<Record<string, unknown>>, default: undefined },
|
||||
style: { type: Object as PropType<StaticStyle>, default: undefined },
|
||||
};
|
||||
|
||||
const StaticImpl = defineComponent({
|
||||
name: "Static",
|
||||
props: staticProps,
|
||||
slots: Object as SlotsType<{
|
||||
default: StaticSlot;
|
||||
}>,
|
||||
setup(props, { slots }) {
|
||||
const defaultStyle: Record<string, unknown> = {
|
||||
const defaultStyle: StaticStyle = {
|
||||
position: "absolute",
|
||||
flexDirection: "column",
|
||||
};
|
||||
@@ -76,7 +92,18 @@ const StaticImpl = defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
export const Static = StaticImpl as WithChildren<typeof StaticImpl>;
|
||||
|
||||
/** Props accepted by `<Static>` — the vue-tui analogue of Ink's `StaticProps`. */
|
||||
export type StaticProps = ExtractPublicPropTypes<typeof staticProps>;
|
||||
type StaticBaseProps = ExtractPublicPropTypes<typeof staticProps>;
|
||||
|
||||
export type StaticProps<T = unknown> = Omit<StaticBaseProps, "items"> & {
|
||||
items: T[];
|
||||
};
|
||||
|
||||
export const Static = StaticImpl as typeof StaticImpl & {
|
||||
new <T = unknown>(): {
|
||||
$props: StaticProps<T> & { children?: StaticChildren<T> };
|
||||
$slots: {
|
||||
default?: StaticSlot<T>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import type { VNodeChild } from "vue";
|
||||
|
||||
export type DefaultSlot = () => VNodeChild;
|
||||
|
||||
export type DefaultChildren = VNodeChild | DefaultSlot | { default: DefaultSlot };
|
||||
|
||||
/**
|
||||
* Adds an optional `children` prop to a component's JSX `$props` so that TSX
|
||||
* written under the automatic runtime (`jsx: "react-jsx"` + `jsxImportSource:
|
||||
@@ -7,6 +13,6 @@
|
||||
* that prop to the default slot at runtime, so this is a type-only shim with no
|
||||
* runtime effect.
|
||||
*/
|
||||
export type WithChildren<C, T = unknown> = C & {
|
||||
export type WithChildren<C, T = DefaultChildren> = C & {
|
||||
new (): { $props: { children?: T } };
|
||||
};
|
||||
|
||||
@@ -5,13 +5,21 @@ export {
|
||||
Box,
|
||||
type AriaRole,
|
||||
type AriaState,
|
||||
type BoxLayoutStyle,
|
||||
type BoxStyle,
|
||||
type BoxProps,
|
||||
} from "./components/box.ts";
|
||||
export { Text, type TextProps } from "./components/text.ts";
|
||||
export { Newline, type NewlineProps } from "./components/newline.ts";
|
||||
export { Spacer } from "./components/spacer.ts";
|
||||
export { Static, type StaticProps } from "./components/static.ts";
|
||||
export { Spacer, type SpacerProps } from "./components/spacer.ts";
|
||||
export {
|
||||
Static,
|
||||
type StaticChildren,
|
||||
type StaticProps,
|
||||
type StaticSlot,
|
||||
type StaticSlotProps,
|
||||
type StaticStyle,
|
||||
} from "./components/static.ts";
|
||||
export { Transform, type TransformProps } from "./components/transform.ts";
|
||||
|
||||
export { useApp, type UseAppReturn } from "./composables/useApp.ts";
|
||||
|
||||
Reference in New Issue
Block a user