diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json
index 5e76ac113b..3c4e329585 100644
--- a/packages/examples/packages/browserify-plugin/snap.manifest.json
+++ b/packages/examples/packages/browserify-plugin/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "HEAbfXBUqw5fNP+sJVyvUpXucZy6CwiCFXJi36CEfUw=",
+ "shasum": "4Waitn0bnIxi/w96Dqni+0tHJkuPURlZznHkrdp2cPc=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json
index be6a71ec6d..74bae4ffd7 100644
--- a/packages/examples/packages/browserify/snap.manifest.json
+++ b/packages/examples/packages/browserify/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "mCoDlMSdhDJAXd9zT74ST7jHysifHdQ8r0++b8uPbOs=",
+ "shasum": "27AQQByAFXL3KIpdFzj+ke9/98WSR8o8Fan72A4LuXg=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/snaps-sdk/src/jsx/components/form/Input.test.tsx b/packages/snaps-sdk/src/jsx/components/form/Input.test.tsx
index 9cc32e97af..f446081a89 100644
--- a/packages/snaps-sdk/src/jsx/components/form/Input.test.tsx
+++ b/packages/snaps-sdk/src/jsx/components/form/Input.test.tsx
@@ -28,13 +28,16 @@ describe('Input', () => {
});
it('renders a number input', () => {
- const result = ;
+ const result = ;
expect(result).toStrictEqual({
type: 'Input',
props: {
name: 'foo',
type: 'number',
+ min: 0,
+ max: 10,
+ step: 1,
},
key: null,
});
diff --git a/packages/snaps-sdk/src/jsx/components/form/Input.ts b/packages/snaps-sdk/src/jsx/components/form/Input.ts
index c68b44e830..814bac24f5 100644
--- a/packages/snaps-sdk/src/jsx/components/form/Input.ts
+++ b/packages/snaps-sdk/src/jsx/components/form/Input.ts
@@ -2,6 +2,23 @@ import { createSnapComponent } from '../../component';
// TODO: Add the `onChange` prop to the `InputProps` type.
+export type GenericInputProps = {
+ name: string;
+ value?: string | undefined;
+ placeholder?: string | undefined;
+};
+
+export type TextInputProps = { type: 'text' } & GenericInputProps;
+
+export type PasswordInputProps = { type: 'password' } & GenericInputProps;
+
+export type NumberInputProps = {
+ type: 'number';
+ min?: number;
+ max?: number;
+ step?: number;
+} & GenericInputProps;
+
/**
* The props of the {@link Input} component.
*
@@ -10,13 +27,18 @@ import { createSnapComponent } from '../../component';
* @property type - The type of the input field. Defaults to `text`.
* @property value - The value of the input field.
* @property placeholder - The placeholder text of the input field.
+ * @property min - The minimum value of the input field.
+ * Only applicable to the type `number` input.
+ * @property max - The maximum value of the input field.
+ * Only applicable to the type `number` input.
+ * @property step - The step value of the input field.
+ * Only applicable to the type `number` input.
*/
-export type InputProps = {
- name: string;
- type?: 'text' | 'password' | 'number' | undefined;
- value?: string | undefined;
- placeholder?: string | undefined;
-};
+export type InputProps =
+ | GenericInputProps
+ | TextInputProps
+ | PasswordInputProps
+ | NumberInputProps;
const TYPE = 'Input';
@@ -29,9 +51,17 @@ const TYPE = 'Input';
* @param props.type - The type of the input field.
* @param props.value - The value of the input field.
* @param props.placeholder - The placeholder text of the input field.
+ * @param props.min - The minimum value of the input field.
+ * Only applicable to the type `number` input.
+ * @param props.max - The maximum value of the input field.
+ * Only applicable to the type `number` input.
+ * @param props.step - The step value of the input field.
+ * Only applicable to the type `number` input.
* @returns An input element.
* @example
*
+ * @example
+ *
*/
export const Input = createSnapComponent(TYPE);
diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx
index f10db2cf26..9cf87ac8e3 100644
--- a/packages/snaps-sdk/src/jsx/validation.test.tsx
+++ b/packages/snaps-sdk/src/jsx/validation.test.tsx
@@ -207,6 +207,7 @@ describe('InputStruct', () => {
,
,
,
+ ,
])('validates an input element', (value) => {
expect(is(value, InputStruct)).toBe(true);
});
@@ -222,6 +223,10 @@ describe('InputStruct', () => {
,
// @ts-expect-error - Invalid props.
,
+ // @ts-expect-error - Invalid props.
+ ,
+ // @ts-expect-error - Invalid props.
+ ,
foo,
foo
diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts
index 8240a36ffc..07c1f5e6a7 100644
--- a/packages/snaps-sdk/src/jsx/validation.ts
+++ b/packages/snaps-sdk/src/jsx/validation.ts
@@ -18,6 +18,7 @@ import {
string,
tuple,
refine,
+ assign,
} from '@metamask/superstruct';
import {
CaipAccountIdStruct,
@@ -194,6 +195,24 @@ function element(
});
}
+/**
+ * A helper function for creating a struct for a JSX element with selective props.
+ *
+ * @param name - The name of the element.
+ * @param selector - The selector function choosing the struct to validate with.
+ * @returns The struct for the element.
+ */
+function elementWithSelectiveProps<
+ Name extends string,
+ Selector extends (value: any) => AnyStruct,
+>(name: Name, selector: Selector) {
+ return object({
+ type: literal(name) as unknown as Struct,
+ props: selectiveUnion(selector),
+ key: nullable(KeyStruct),
+ });
+}
+
/**
* A struct for the {@link ImageElement} type.
*/
@@ -240,17 +259,69 @@ export const CheckboxStruct: Describe = element('Checkbox', {
});
/**
- * A struct for the {@link InputElement} type.
+ * A struct for the generic input element props.
*/
-export const InputStruct: Describe = element('Input', {
+export const GenericInputPropsStruct = object({
name: string(),
- type: optional(
- nullUnion([literal('text'), literal('password'), literal('number')]),
- ),
value: optional(string()),
placeholder: optional(string()),
});
+/**
+ * A struct for the text type input props.
+ */
+export const TextInputPropsStruct = assign(
+ GenericInputPropsStruct,
+ object({
+ type: literal('text'),
+ }),
+);
+
+/**
+ * A struct for the password type input props.
+ */
+export const PasswordInputPropsStruct = assign(
+ GenericInputPropsStruct,
+ object({
+ type: literal('password'),
+ }),
+);
+
+/**
+ * A struct for the number type input props.
+ */
+export const NumberInputPropsStruct = assign(
+ GenericInputPropsStruct,
+ object({
+ type: literal('number'),
+ min: optional(number()),
+ max: optional(number()),
+ step: optional(number()),
+ }),
+);
+
+/**
+ * A struct for the {@link InputElement} type.
+ */
+export const InputStruct: Describe = elementWithSelectiveProps(
+ 'Input',
+ (value) => {
+ if (isPlainObject(value) && hasProperty(value, 'type')) {
+ switch (value.type) {
+ case 'text':
+ return TextInputPropsStruct;
+ case 'password':
+ return PasswordInputPropsStruct;
+ case 'number':
+ return NumberInputPropsStruct;
+ default:
+ return GenericInputPropsStruct;
+ }
+ }
+ return GenericInputPropsStruct;
+ },
+);
+
/**
* A struct for the {@link OptionElement} type.
*/