diff --git a/packages/examples/packages/bip32/snap.manifest.json b/packages/examples/packages/bip32/snap.manifest.json
index abdb7ebda0..2acc3430e6 100644
--- a/packages/examples/packages/bip32/snap.manifest.json
+++ b/packages/examples/packages/bip32/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "ik7uBPZA2o4lF5QljJZQSa2RdCng8nYtKsEulOWXZU4=",
+ "shasum": "meplfIhDKAdAYfRjk1slGTks3rI6+uPewth+sL7o/Go=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/bip44/snap.manifest.json b/packages/examples/packages/bip44/snap.manifest.json
index f948ab344d..b21a1db41a 100644
--- a/packages/examples/packages/bip44/snap.manifest.json
+++ b/packages/examples/packages/bip44/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "vpc0ie9a1H8KWv8UMpOBaPZyGJTjESuvjv1ERUMttaM=",
+ "shasum": "hM4uyKv1VEidiHratstws0S/LdBzPWjOQncUkHBNDP0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json
index ef8b7f456f..9ca1082f37 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": "HRGY/ewBpqh8U7fsF/3QgRrpKqn0uDNTdpi83ZCqPQ0=",
+ "shasum": "uFeB4RsFZsV7ZYjLPEp8R+R96iuN9gpHjYB1MKUy0bw=",
"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 d59b9a9553..e4f36d7c03 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": "x1+/hSsNK4ymlrifMWvkwpibVxNSUekwvYdIwmw7T2k=",
+ "shasum": "9RcL8rtVPwRiXki5Vs+pEA6lO1CthaofVv2UQF1COlY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/client-status/snap.manifest.json b/packages/examples/packages/client-status/snap.manifest.json
index f2a1e5f211..3c27f9032c 100644
--- a/packages/examples/packages/client-status/snap.manifest.json
+++ b/packages/examples/packages/client-status/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "YaCBzI0O1i5L5l5gFfzmNhfwuvnnxbkfmsNjLkwFhfY=",
+ "shasum": "BmrBVhdD2ZmEdDjNVdEiVSzd3uS8PiGgmZcvYmHnHxw=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/cronjobs/snap.manifest.json b/packages/examples/packages/cronjobs/snap.manifest.json
index 22b484c404..6beb76defc 100644
--- a/packages/examples/packages/cronjobs/snap.manifest.json
+++ b/packages/examples/packages/cronjobs/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "dxp0O4Nhq7QOU1k6S5FJvaxvuc5wGnLsInCZ184N6gM=",
+ "shasum": "oZbPamYypPK6BHoVJ/Mx0l7pLIBjQRBtbZNZWyDMPGs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/dialogs/snap.manifest.json b/packages/examples/packages/dialogs/snap.manifest.json
index 7536f130ac..d56d58df82 100644
--- a/packages/examples/packages/dialogs/snap.manifest.json
+++ b/packages/examples/packages/dialogs/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "AP2i5PiuYC9Dr9chFNMP1FMDJmPwpsFBr34+OKEfo/4=",
+ "shasum": "prxccwZ+383T+7jjMrSabwO74aBRyoKEnnP1o7r1Aac=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/ethereum-provider/snap.manifest.json b/packages/examples/packages/ethereum-provider/snap.manifest.json
index 205345a143..2e06422fbb 100644
--- a/packages/examples/packages/ethereum-provider/snap.manifest.json
+++ b/packages/examples/packages/ethereum-provider/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "6NIpWbenThi4e3LAiAAB0MP2PQcAcdadhcKAccM66yo=",
+ "shasum": "GSpRcUSfCYEWViNqYyG3NdTa4/nO8EGukSHvGBVXtT8=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/ethers-js/snap.manifest.json b/packages/examples/packages/ethers-js/snap.manifest.json
index 8cd9af62a3..1561e38dc2 100644
--- a/packages/examples/packages/ethers-js/snap.manifest.json
+++ b/packages/examples/packages/ethers-js/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "4Nt428/mGU2ozcXM+X28gZjmKbyI1BQxuDntChDFl0M=",
+ "shasum": "bG6octHXbG5c1Keftfj2kCbHcATOJMnVe5oy4ENLGYM=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/file-upload/snap.manifest.json b/packages/examples/packages/file-upload/snap.manifest.json
index 72aa5cbb98..001a5d998c 100644
--- a/packages/examples/packages/file-upload/snap.manifest.json
+++ b/packages/examples/packages/file-upload/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "YNic7/VXJF32HBUr5npNpXopQB7wwY50d9ejLpVclKI=",
+ "shasum": "dRJ64XJAM36aNfsZBN7FC8fGmh/4b3nd0V0wJZQfmzk=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/get-entropy/snap.manifest.json b/packages/examples/packages/get-entropy/snap.manifest.json
index 9f8aba5e20..d6b20a49c1 100644
--- a/packages/examples/packages/get-entropy/snap.manifest.json
+++ b/packages/examples/packages/get-entropy/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "0vquB1L/2Ftqhkth3a2hhU9P8Ij6QAh0VFDhvFi60sw=",
+ "shasum": "z/xO4vaYBErIumDuzkS1JuWD0w3M/JQzrkl7QdH+Rsw=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/get-file/snap.manifest.json b/packages/examples/packages/get-file/snap.manifest.json
index 215968be2f..f8010e3d09 100644
--- a/packages/examples/packages/get-file/snap.manifest.json
+++ b/packages/examples/packages/get-file/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "FZAIkcXgzle5cFsso165dzSTwerIFAzASA8rptjezmc=",
+ "shasum": "RY6WMOfBdTEWYMUX7zIzNy7ZmMjZdxEZUbsW4d7bAWc=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/home-page/snap.manifest.json b/packages/examples/packages/home-page/snap.manifest.json
index 9e16069e29..8c1a57c967 100644
--- a/packages/examples/packages/home-page/snap.manifest.json
+++ b/packages/examples/packages/home-page/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "B2lQ3m9r4PRZ2vRrrFgenZvRSomQKIs8sJOSM5U9hDQ=",
+ "shasum": "7w5lE7ZVBqS66ghFwA5z3USgqOxh6vU2d71JBi5dx+s=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/images/snap.manifest.json b/packages/examples/packages/images/snap.manifest.json
index f34ddbc021..2ebb259b78 100644
--- a/packages/examples/packages/images/snap.manifest.json
+++ b/packages/examples/packages/images/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "kA505849kj9G8hG1R92ZD0qGYEoUr4TXXk8PxGBG5nQ=",
+ "shasum": "xJUmec7F6/It7kfSljqHWM76MSJSmytZlPvlgyLxxXI=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/interactive-ui/snap.manifest.json b/packages/examples/packages/interactive-ui/snap.manifest.json
index c7c91722ae..23c09c39b3 100644
--- a/packages/examples/packages/interactive-ui/snap.manifest.json
+++ b/packages/examples/packages/interactive-ui/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "9RyN2IznzsqwJO/7mhIKxp4DVQd0ongnYbN9MeFowI0=",
+ "shasum": "rvE1wmZmbTWiskg2VPVLbn8KT2PFkFQ5v3tGFqybsRE=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json
index 1cbf6a1e30..b6219626cd 100644
--- a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json
+++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "IPgrx/srJ0pnYXbMpymuHMWWHMb9MZfyekCiiH8IDjQ=",
+ "shasum": "dsc+T0qqSAe1LoKYI/8HPVk3yBktRoaRSOvRMjlF5B8=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json
index e3272dd40a..d538a916e6 100644
--- a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json
+++ b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "WFtsYTJglOJChmrR8AtwRRamhFXzrqmtyCizcg1zIK4=",
+ "shasum": "XZ4Pt2LJwOMXBm1wQXtJP3l/qZgC218Z/n3xYE8BGjM=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/json-rpc/snap.manifest.json b/packages/examples/packages/json-rpc/snap.manifest.json
index 64b1a9718d..1f15ba024d 100644
--- a/packages/examples/packages/json-rpc/snap.manifest.json
+++ b/packages/examples/packages/json-rpc/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "YDat1yOn2nLtxCIls+5lbNYnahRdV9EfASQI/b/9lhg=",
+ "shasum": "wokl2HTmrbZObmcc1MH9a7so9xrZkXNhRcCqtfepGA0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/jsx/snap.manifest.json b/packages/examples/packages/jsx/snap.manifest.json
index e0c590f6f3..bb82c77ab6 100644
--- a/packages/examples/packages/jsx/snap.manifest.json
+++ b/packages/examples/packages/jsx/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "+Nb1THmpw/hGhx5AmYY8lJ8sdELuLKHv/n4ndO1WTCo=",
+ "shasum": "v2qKGCM4FTPM6wUB4lbKO556tDV00aYZDjd3RxvMcsI=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/lifecycle-hooks/snap.manifest.json b/packages/examples/packages/lifecycle-hooks/snap.manifest.json
index 2309fb924a..88dc983401 100644
--- a/packages/examples/packages/lifecycle-hooks/snap.manifest.json
+++ b/packages/examples/packages/lifecycle-hooks/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "Jrk/yg039/Qf51OfijNmqn+jA4+jS+DdbDTTXGK52t8=",
+ "shasum": "iyRAkal8Vql5iD1XJwIp/9KNi/kk3HjlfdfklQf3UW0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/localization/snap.manifest.json b/packages/examples/packages/localization/snap.manifest.json
index 0bda07313f..2330e551b1 100644
--- a/packages/examples/packages/localization/snap.manifest.json
+++ b/packages/examples/packages/localization/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "iwYePZv3WxiRNwYWLJMYFVezr4FVNt9vKENR+l5cUv4=",
+ "shasum": "LofJCTQeoNFWE+F1S1OTGzcUyJPJiTskgK9aIRrt1NM=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/manage-state/snap.manifest.json b/packages/examples/packages/manage-state/snap.manifest.json
index 22b8f89220..416a85065a 100644
--- a/packages/examples/packages/manage-state/snap.manifest.json
+++ b/packages/examples/packages/manage-state/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "yfuwl/IJSpXoW950VQsJYOt5wugF4zqrwEJmHpfXdH0=",
+ "shasum": "f6IlmDjDvF8G3hEaIF8mrBl5irEpYgg6WPqpdk7P+is=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/network-access/snap.manifest.json b/packages/examples/packages/network-access/snap.manifest.json
index a137982e73..6af024bf18 100644
--- a/packages/examples/packages/network-access/snap.manifest.json
+++ b/packages/examples/packages/network-access/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "W6MFWW9qbN2L2Jrx49JjLzAvoiQA9uUcnWPTkNUFvE4=",
+ "shasum": "JtcrsUNAGdf3v4dr4uo1XQgom71wdtifM/rh54HgnEs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/notifications/snap.manifest.json b/packages/examples/packages/notifications/snap.manifest.json
index 748b123e3d..d9db789b5b 100644
--- a/packages/examples/packages/notifications/snap.manifest.json
+++ b/packages/examples/packages/notifications/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "MZSLIUmv+W4OVk8BdXpbCsrkpKZ2DcXbyWCbje1bZjA=",
+ "shasum": "Nm2UUq7WZAa+oFy8y08o0bDHwLrVKpP00WW+F/UVw4o=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/rollup-plugin/snap.manifest.json b/packages/examples/packages/rollup-plugin/snap.manifest.json
index 7da1f6816e..689f5119d6 100644
--- a/packages/examples/packages/rollup-plugin/snap.manifest.json
+++ b/packages/examples/packages/rollup-plugin/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "CeDhOKcmnMXKwDdC2HozIABPQ+q/oVUFxrhiNNIclDc=",
+ "shasum": "DFfEzW8wLRohClvMLn+mN/pN+YmB39HgrDcQavUDISg=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/signature-insights/snap.manifest.json b/packages/examples/packages/signature-insights/snap.manifest.json
index f390e3465c..a85625b34d 100644
--- a/packages/examples/packages/signature-insights/snap.manifest.json
+++ b/packages/examples/packages/signature-insights/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "0eVnlZsCe7wuh964O9xM/hGczrpDZF7yekn9DZKgXNM=",
+ "shasum": "hsCvm1U9gV/4FzppkTRoVd8kjAmDHc1ar2KaV1SLUcM=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/transaction-insights/snap.manifest.json b/packages/examples/packages/transaction-insights/snap.manifest.json
index 69e614b8ca..fdc321a4b3 100644
--- a/packages/examples/packages/transaction-insights/snap.manifest.json
+++ b/packages/examples/packages/transaction-insights/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "sq30Pjd883FkDKbg1hAvud3vc0XD3MOsHT6vqpNh5kE=",
+ "shasum": "Vizjf/UbRArRlUspGuMRFkCZXEMwK20c/8wTCJhNSc0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/wasm/snap.manifest.json b/packages/examples/packages/wasm/snap.manifest.json
index 470a80a9ef..7abc742414 100644
--- a/packages/examples/packages/wasm/snap.manifest.json
+++ b/packages/examples/packages/wasm/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "726OomzJecbcMjZqZEFPBdM+Zn49gOljb6pFJNOmSW0=",
+ "shasum": "I5yXL+XH56rFZiJfGc6Pq4v4lcXwuUIhkk/vbMbS5O4=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/webpack-plugin/snap.manifest.json b/packages/examples/packages/webpack-plugin/snap.manifest.json
index 25c530dc63..7c5ba582e7 100644
--- a/packages/examples/packages/webpack-plugin/snap.manifest.json
+++ b/packages/examples/packages/webpack-plugin/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "LuxkyGh4b6uksSaom6Yp4Uzezibjg9oskNh4L62vvMA=",
+ "shasum": "5hX1yMdOi/HxZesj7T4qXvR/Cu4GVpQHnfbavDPS45U=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json
index d03ef020c3..aa297f891c 100644
--- a/packages/snaps-controllers/coverage.json
+++ b/packages/snaps-controllers/coverage.json
@@ -1,5 +1,5 @@
{
- "branches": 92.54,
+ "branches": 92.59,
"functions": 96.91,
"lines": 98.01,
"statements": 97.71
diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx
index a8a698765d..10403584d5 100644
--- a/packages/snaps-controllers/src/interface/utils.test.tsx
+++ b/packages/snaps-controllers/src/interface/utils.test.tsx
@@ -12,6 +12,9 @@ import {
Checkbox,
RadioGroup,
Radio,
+ Selector,
+ Card,
+ SelectorOption,
} from '@metamask/snaps-sdk/jsx';
import { assertNameIsUnique, constructState, getJsxInterface } from './utils';
@@ -456,6 +459,94 @@ describe('constructState', () => {
});
});
+ it('sets default value for root level Selector', () => {
+ const element = (
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const result = constructState({}, element);
+ expect(result).toStrictEqual({
+ foo: 'option1',
+ });
+ });
+
+ it('supports root level Selector', () => {
+ const element = (
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const result = constructState({}, element);
+ expect(result).toStrictEqual({
+ foo: 'option2',
+ });
+ });
+
+ it('sets default value for Selector in form', () => {
+ const element = (
+
+
+
+ );
+
+ const result = constructState({}, element);
+ expect(result).toStrictEqual({
+ form: { foo: 'option1' },
+ });
+ });
+
+ it('supports Selector in form', () => {
+ const element = (
+
+
+
+ );
+
+ const result = constructState({}, element);
+ expect(result).toStrictEqual({
+ form: { foo: 'option2' },
+ });
+ });
+
it('supports nested fields', () => {
const element = (
diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts
index 1701b91f8f..24923138a4 100644
--- a/packages/snaps-controllers/src/interface/utils.ts
+++ b/packages/snaps-controllers/src/interface/utils.ts
@@ -15,6 +15,8 @@ import type {
CheckboxElement,
RadioGroupElement,
RadioElement,
+ SelectorElement,
+ SelectorOptionElement,
} from '@metamask/snaps-sdk/jsx';
import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx';
import {
@@ -63,7 +65,12 @@ export function assertNameIsUnique(state: InterfaceState, name: string) {
* @returns The default state for the specific component, if any.
*/
function constructComponentSpecificDefaultState(
- element: InputElement | DropdownElement | RadioGroupElement | CheckboxElement,
+ element:
+ | InputElement
+ | DropdownElement
+ | RadioGroupElement
+ | CheckboxElement
+ | SelectorElement,
) {
switch (element.type) {
case 'Dropdown': {
@@ -76,6 +83,11 @@ function constructComponentSpecificDefaultState(
return children[0]?.props.value;
}
+ case 'Selector': {
+ const children = getJsxChildren(element) as SelectorOptionElement[];
+ return children[0]?.props.value;
+ }
+
case 'Checkbox':
return false;
@@ -94,7 +106,12 @@ function constructComponentSpecificDefaultState(
* @returns The state value for a given component.
*/
function getComponentStateValue(
- element: InputElement | DropdownElement | RadioGroupElement | CheckboxElement,
+ element:
+ | InputElement
+ | DropdownElement
+ | RadioGroupElement
+ | CheckboxElement
+ | SelectorElement,
) {
switch (element.type) {
case 'Checkbox':
@@ -120,7 +137,8 @@ function constructInputState(
| DropdownElement
| RadioGroupElement
| FileInputElement
- | CheckboxElement,
+ | CheckboxElement
+ | SelectorElement,
form?: string,
) {
const oldStateUnwrapped = form ? (oldState[form] as FormState) : oldState;
@@ -177,7 +195,8 @@ export function constructState(
component.type === 'Dropdown' ||
component.type === 'RadioGroup' ||
component.type === 'FileInput' ||
- component.type === 'Checkbox')
+ component.type === 'Checkbox' ||
+ component.type === 'Selector')
) {
const formState = newState[currentForm.name] as FormState;
assertNameIsUnique(formState, component.props.name);
@@ -195,7 +214,8 @@ export function constructState(
component.type === 'Dropdown' ||
component.type === 'RadioGroup' ||
component.type === 'FileInput' ||
- component.type === 'Checkbox'
+ component.type === 'Checkbox' ||
+ component.type === 'Selector'
) {
assertNameIsUnique(newState, component.props.name);
newState[component.props.name] = constructInputState(oldState, component);
diff --git a/packages/snaps-sdk/src/jsx/components/form/Field.ts b/packages/snaps-sdk/src/jsx/components/form/Field.ts
index b36a8e609d..933e27d0cb 100644
--- a/packages/snaps-sdk/src/jsx/components/form/Field.ts
+++ b/packages/snaps-sdk/src/jsx/components/form/Field.ts
@@ -5,6 +5,7 @@ import type { DropdownElement } from './Dropdown';
import type { FileInputElement } from './FileInput';
import type { InputElement } from './Input';
import type { RadioGroupElement } from './RadioGroup';
+import type { SelectorElement } from './Selector';
/**
* The props of the {@link Field} component.
@@ -22,7 +23,8 @@ export type FieldProps = {
| RadioGroupElement
| FileInputElement
| InputElement
- | CheckboxElement;
+ | CheckboxElement
+ | SelectorElement;
};
const TYPE = 'Field';
diff --git a/packages/snaps-sdk/src/jsx/components/form/Selector.test.tsx b/packages/snaps-sdk/src/jsx/components/form/Selector.test.tsx
new file mode 100644
index 0000000000..843025d3d0
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/form/Selector.test.tsx
@@ -0,0 +1,103 @@
+import { Card } from '../Card';
+import { Selector } from './Selector';
+import { SelectorOption } from './SelectorOption';
+
+describe('Selector', () => {
+ it('renders a selector with options', () => {
+ const result = (
+
+
+
+
+
+
+
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'Selector',
+ props: {
+ name: 'selector',
+ value: 'foo',
+ title: 'Choose an option',
+ children: [
+ {
+ type: 'SelectorOption',
+ props: {
+ value: 'foo',
+ children: {
+ type: 'Card',
+ props: {
+ title: 'Foo',
+ value: '$1',
+ },
+ key: null,
+ },
+ },
+ key: null,
+ },
+ {
+ type: 'SelectorOption',
+ props: {
+ value: 'bar',
+ children: {
+ type: 'Card',
+ props: {
+ title: 'Bar',
+ value: '$1',
+ },
+ key: null,
+ },
+ },
+ key: null,
+ },
+ ],
+ },
+ key: null,
+ });
+ });
+
+ it('renders a selector with a conditional option', () => {
+ const result = (
+
+
+
+
+ {false && (
+
+
+
+ )}
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'Selector',
+ props: {
+ name: 'selector',
+ value: 'foo',
+ title: 'Choose an option',
+ children: [
+ {
+ type: 'SelectorOption',
+ props: {
+ value: 'foo',
+ children: {
+ type: 'Card',
+ props: {
+ title: 'Foo',
+ value: '$1',
+ },
+ key: null,
+ },
+ },
+ key: null,
+ },
+ false,
+ ],
+ },
+ key: null,
+ });
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/components/form/Selector.ts b/packages/snaps-sdk/src/jsx/components/form/Selector.ts
new file mode 100644
index 0000000000..67e8adb9c8
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/form/Selector.ts
@@ -0,0 +1,47 @@
+import type { SnapsChildren } from '../../component';
+import { createSnapComponent } from '../../component';
+import type { SelectorOptionElement } from './SelectorOption';
+
+/**
+ * The props of the {@link Selector} component.
+ *
+ * @property name - The name of the selector. This is used to identify the
+ * state in the form data.
+ * @property title - The title of the selector. This is displayed in the UI.
+ * @property value - The selected value of the selector.
+ * @property children - The children of the selector.
+ */
+export type SelectorProps = {
+ name: string;
+ title: string;
+ value?: string | undefined;
+ children: SnapsChildren;
+};
+
+const TYPE = 'Selector';
+
+/**
+ * A selector component, which is used to create a selector.
+ *
+ * @param props - The props of the component.
+ * @param props.name - The name of the selector field. This is used to identify the
+ * state in the form data.
+ * @param props.title - The title of the selector field. This is displayed in the UI.
+ * @param props.value - The selected value of the selector.
+ * @param props.children - The children of the selector.
+ * @returns A selector element.
+ * @example
+ *
+ *
+ *
+ *
+ *
+ */
+export const Selector = createSnapComponent(TYPE);
+
+/**
+ * A selector element.
+ *
+ * @see Selector
+ */
+export type SelectorElement = ReturnType;
diff --git a/packages/snaps-sdk/src/jsx/components/form/SelectorOption.test.tsx b/packages/snaps-sdk/src/jsx/components/form/SelectorOption.test.tsx
new file mode 100644
index 0000000000..65dd9aeb0a
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/form/SelectorOption.test.tsx
@@ -0,0 +1,28 @@
+import { Card } from '../Card';
+import { SelectorOption } from './SelectorOption';
+
+describe('Option', () => {
+ it('renders a selector option', () => {
+ const result = (
+
+
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'SelectorOption',
+ props: {
+ value: 'foo',
+ children: {
+ type: 'Card',
+ props: {
+ title: 'Foo',
+ value: 'Bar',
+ },
+ key: null,
+ },
+ },
+ key: null,
+ });
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/components/form/SelectorOption.ts b/packages/snaps-sdk/src/jsx/components/form/SelectorOption.ts
new file mode 100644
index 0000000000..67734f5716
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/form/SelectorOption.ts
@@ -0,0 +1,44 @@
+import { createSnapComponent } from '../../component';
+import type { CardElement } from '../Card';
+
+/**
+ * The props of the {@link SelectorOption} component.
+ *
+ * @property value - The value of the selector option. This is used to populate the
+ * state in the form data.
+ * @property children - The component to display.
+ */
+export type SelectorOptionProps = {
+ value: string;
+ children: CardElement;
+};
+
+const TYPE = 'SelectorOption';
+
+/**
+ * A selector option component, which is used to create a selector option. This component
+ * can only be used as a child of the {@link Selector} component.
+ *
+ * @param props - The props of the component.
+ * @param props.value - The value of the selector option. This is used to populate the
+ * state in the form data.
+ * @param props.children - The component to display.
+ * @returns A selector option element.
+ * @example
+ *
+ *
+ *
+ *
+ *
+ */
+export const SelectorOption = createSnapComponent<
+ SelectorOptionProps,
+ typeof TYPE
+>(TYPE);
+
+/**
+ * A selector option element.
+ *
+ * @see SelectorOption
+ */
+export type SelectorOptionElement = ReturnType;
diff --git a/packages/snaps-sdk/src/jsx/components/form/index.ts b/packages/snaps-sdk/src/jsx/components/form/index.ts
index 8078353702..3dc4221376 100644
--- a/packages/snaps-sdk/src/jsx/components/form/index.ts
+++ b/packages/snaps-sdk/src/jsx/components/form/index.ts
@@ -8,6 +8,8 @@ import type { InputElement } from './Input';
import type { OptionElement } from './Option';
import type { RadioElement } from './Radio';
import type { RadioGroupElement } from './RadioGroup';
+import type { SelectorElement } from './Selector';
+import type { SelectorOptionElement } from './SelectorOption';
export * from './Button';
export * from './Checkbox';
@@ -19,6 +21,8 @@ export * from './Field';
export * from './FileInput';
export * from './Form';
export * from './Input';
+export * from './Selector';
+export * from './SelectorOption';
export type StandardFormElement =
| ButtonElement
@@ -30,4 +34,6 @@ export type StandardFormElement =
| DropdownElement
| OptionElement
| RadioElement
- | RadioGroupElement;
+ | RadioGroupElement
+ | SelectorElement
+ | SelectorOptionElement;
diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx
index 713b5b1578..c9a6064536 100644
--- a/packages/snaps-sdk/src/jsx/validation.test.tsx
+++ b/packages/snaps-sdk/src/jsx/validation.test.tsx
@@ -29,6 +29,8 @@ import {
Container,
Card,
Icon,
+ Selector,
+ SelectorOption,
} from './components';
import {
AddressStruct,
@@ -63,6 +65,7 @@ import {
TooltipStruct,
ValueStruct,
IconStruct,
+ SelectorStruct,
} from './validation';
describe('KeyStruct', () => {
@@ -248,6 +251,16 @@ describe('FieldStruct', () => {
,
+
+
+
+
+
+
+
+
+
+ ,
])('validates a field element', (value) => {
expect(is(value, FieldStruct)).toBe(true);
});
@@ -794,6 +807,59 @@ describe('FileInputStruct', () => {
});
});
+describe('SelectorStruct', () => {
+ it.each([
+
+
+
+
+
+
+
+ ,
+ ])('validates a selector element', (value) => {
+ expect(is(value, SelectorStruct)).toBe(true);
+ });
+
+ it.each([
+ 'foo',
+ 42,
+ null,
+ undefined,
+ {},
+ [],
+ // @ts-expect-error - Invalid props.
+ foo,
+ // @ts-expect-error - Invalid props.
+
+
+
+
+ ,
+ // @ts-expect-error - Invalid props.
+
+
+
+
+ ,
+ // @ts-expect-error - Invalid props.
+
+
+
+
+ ,
+ foo,
+
+ foo
+ ,
+
+
+
,
+ ])('does not validate "%p"', (value) => {
+ expect(is(value, SelectorStruct)).toBe(false);
+ });
+});
+
describe('HeadingStruct', () => {
it.each([Hello])(
'validates a heading element',
diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts
index cd83d62173..dc03e09602 100644
--- a/packages/snaps-sdk/src/jsx/validation.ts
+++ b/packages/snaps-sdk/src/jsx/validation.ts
@@ -68,6 +68,8 @@ import {
type ContainerElement,
type FooterElement,
type IconElement,
+ type SelectorElement,
+ type SelectorOptionElement,
IconName,
} from './components';
@@ -204,7 +206,7 @@ export const InputStruct: Describe = element('Input', {
*/
export const OptionStruct: Describe = element('Option', {
value: string(),
- children: string(),
+ children: nullUnion([string()]),
});
/**
@@ -216,6 +218,38 @@ export const DropdownStruct: Describe = element('Dropdown', {
children: children([OptionStruct]),
});
+/**
+ * A struct for the {@link CardElement} type.
+ */
+export const CardStruct: Describe = element('Card', {
+ image: optional(string()),
+ title: string(),
+ description: optional(string()),
+ value: string(),
+ extra: optional(string()),
+});
+
+/**
+ * A struct for the {@link SelectorOptionElement} type.
+ */
+export const SelectorOptionStruct: Describe = element(
+ 'SelectorOption',
+ {
+ value: string(),
+ children: CardStruct,
+ },
+);
+
+/**
+ * A struct for the {@link SelectorElement} type.
+ */
+export const SelectorStruct: Describe = element('Selector', {
+ name: string(),
+ title: string(),
+ value: optional(string()),
+ children: children([SelectorOptionStruct]),
+});
+
/**
* A struct for the {@link RadioElement} type.
*/
@@ -265,12 +299,14 @@ const FIELD_CHILDREN_ARRAY = [
RadioGroupStruct,
FileInputStruct,
CheckboxStruct,
+ SelectorStruct,
] as [
typeof InputStruct,
typeof DropdownStruct,
typeof RadioGroupStruct,
typeof FileInputStruct,
typeof CheckboxStruct,
+ typeof SelectorStruct,
];
/**
@@ -431,17 +467,6 @@ export const ValueStruct: Describe = element('Value', {
extra: string(),
});
-/**
- * A struct for the {@link CardElement} type.
- */
-export const CardStruct: Describe = element('Card', {
- image: optional(string()),
- title: string(),
- description: optional(string()),
- value: string(),
- extra: optional(string()),
-});
-
/**
* A struct for the {@link HeadingElement} type.
*/
@@ -563,6 +588,7 @@ export const BoxChildStruct = typedUnion([
CheckboxStruct,
CardStruct,
IconStruct,
+ SelectorStruct,
]);
/**
@@ -606,6 +632,8 @@ export const JSXElementStruct: Describe = typedUnion([
ContainerStruct,
CardStruct,
IconStruct,
+ SelectorStruct,
+ SelectorOptionStruct,
]);
/**