Skip to content

Commit

Permalink
Change types to allow regular array types as children (#2470)
Browse files Browse the repository at this point in the history
In #2466 we changed the types and validation of JSX components to use
`NonEmptyArray<Type>` as children. This causes issues however when using
utility functions like `.map()` and `.filter()`, since they return a
regular `Type[]` type. This is not great for devex, since it would
require type assertions everywhere.

I've changed the types to accept `Type[]`, though we technically still
validate `NonEmptyArray<Type>`.
  • Loading branch information
Mrtenz authored Jun 7, 2024
1 parent 3866525 commit 4c8ee13
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 33 deletions.
4 changes: 2 additions & 2 deletions packages/snaps-sdk/src/jsx/component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Json, NonEmptyArray } from '@metamask/utils';
import type { Json } from '@metamask/utils';

/**
* A key, which can be a string or a number.
Expand Down Expand Up @@ -48,7 +48,7 @@ export type SnapElement<
* const maybeArrayString: MaybeArrayString = 'hello';
* const maybeArrayStringArray: MaybeArrayString = ['hello', 'world'];
*/
export type MaybeArray<Type> = Type | NonEmptyArray<Type>;
export type MaybeArray<Type> = Type | Type[];

/**
* A JSX node, which can be an element, a string, null, or an array of nodes.
Expand Down
2 changes: 0 additions & 2 deletions packages/snaps-sdk/src/jsx/validation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ describe('BoxStruct', () => {
[],
// @ts-expect-error - Invalid props.
<Box />,
// @ts-expect-error - Invalid props.
<Box children={[]} />,
<Text>foo</Text>,
<Row label="label">
Expand Down Expand Up @@ -765,7 +764,6 @@ describe('DropdownStruct', () => {
[],
// @ts-expect-error - Invalid props.
<Dropdown name="foo" />,
// @ts-expect-error - Invalid props.
<Dropdown name="foo" children={[]} />,
// @ts-expect-error - Invalid props.
<Spinner>foo</Spinner>,
Expand Down
8 changes: 2 additions & 6 deletions packages/snaps-sdk/src/jsx/validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { NonEmptyArray } from '@metamask/utils';
import {
hasProperty,
HexChecksumAddressStruct,
Expand Down Expand Up @@ -86,11 +85,8 @@ export const ElementStruct: Describe<GenericSnapElement> = object({
*/
function nonEmptyArray<Type, Schema>(
struct: Struct<Type, Schema>,
): Struct<NonEmptyArray<Type>, any> {
return nonempty(array(struct)) as unknown as Struct<
NonEmptyArray<Type>,
Schema
>;
): Struct<Type[], Struct<Type, Schema>> {
return nonempty(array(struct));
}

/**
Expand Down
35 changes: 12 additions & 23 deletions packages/snaps-utils/src/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Component } from '@metamask/snaps-sdk';
import { NodeType } from '@metamask/snaps-sdk';
import type {
BoldChildren,
FieldElement,
ItalicChildren,
JSXElement,
LinkElement,
Expand All @@ -29,7 +28,6 @@ import {
Button,
Address,
} from '@metamask/snaps-sdk/jsx';
import type { NonEmptyArray } from '@metamask/utils';
import {
assert,
assertExhaustive,
Expand Down Expand Up @@ -67,7 +65,7 @@ function getButtonVariant(variant?: 'primary' | 'secondary' | undefined) {
* @param elements - The JSX elements.
* @returns The child or children.
*/
function getChildren<Type>(elements: NonEmptyArray<Type>) {
function getChildren<Type>(elements: Type[]) {
if (elements.length === 1) {
return elements[0];
}
Expand All @@ -83,11 +81,7 @@ function getChildren<Type>(elements: NonEmptyArray<Type>) {
*/
function getLinkText(token: Tokens.Link | Tokens.Generic) {
if (token.tokens && token.tokens.length > 0) {
return getChildren(
token.tokens.flatMap(
getTextChildFromToken,
) as NonEmptyArray<TextChildren>,
);
return getChildren(token.tokens.flatMap(getTextChildFromToken));
}

return token.href;
Expand All @@ -100,9 +94,7 @@ function getLinkText(token: Tokens.Link | Tokens.Generic) {
* @returns The text child.
*/
function getTextChildFromTokens(tokens: Token[]) {
return getChildren(
tokens.flatMap(getTextChildFromToken) as NonEmptyArray<TextChildren>,
);
return getChildren(tokens.flatMap(getTextChildFromToken));
}

/**
Expand Down Expand Up @@ -161,7 +153,7 @@ function getTextChildFromToken(token: Token): TextChildren {
*/
export function getTextChildren(
value: string,
): NonEmptyArray<string | StandardFormattingElement | LinkElement> {
): (string | StandardFormattingElement | LinkElement)[] {
const rootTokens = lexer(value, { gfm: false });
const children: (string | StandardFormattingElement | LinkElement | null)[] =
[];
Expand All @@ -177,9 +169,12 @@ export function getTextChildren(
}
});

return children.filter((child) => child !== null) as NonEmptyArray<
string | StandardFormattingElement | LinkElement
>;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return children.filter((child) => child !== null) as (
| string
| StandardFormattingElement
| LinkElement
)[];
}

/**
Expand Down Expand Up @@ -249,9 +244,7 @@ export function getJsxElementFromComponent(
case NodeType.Form:
return (
<Form name={component.name}>
{getChildren(
component.children.map(getElement) as NonEmptyArray<FieldElement>,
)}
{getChildren(component.children.map(getElement))}
</Form>
);

Expand All @@ -277,11 +270,7 @@ export function getJsxElementFromComponent(
case NodeType.Panel:
// `Panel` is renamed to `Box` in JSX.
return (
<Box
children={getChildren(
component.children.map(getElement) as NonEmptyArray<JSXElement>,
)}
/>
<Box children={getChildren(component.children.map(getElement))} />
);

case NodeType.Row:
Expand Down

0 comments on commit 4c8ee13

Please sign in to comment.