diff --git a/packages/examples/packages/bip32/snap.manifest.json b/packages/examples/packages/bip32/snap.manifest.json index dced6abb97..35a97a1475 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": "Myfiv00eKUdOQh/6VURJt/NLEHvF7t8p+arvI8ZEy2E=", + "shasum": "7AJkQic0kErNfOuBozGOUaJ5U5Z24yOV28zHL+WEPz0=", "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 0c1f0b6412..ed5acb595d 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": "kEd5v0Da+ONo/Pemo/W/+Rj+DCBcMlq8UoDkR3j+7hg=", + "shasum": "R/jtwKJg/6gTCuMrphuKHZkTtUlggfwnNgKiOXx11GU=", "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 1029c477be..d461c74fa0 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": "AtCtWmMVDhVL7nTsaqqCnm+M9tNQpPsp+i/w3G+dvK0=", + "shasum": "u7x/swxx6XHT7G0BDY8mJkwcY/mP/i4YmdYaRQ/QKZY=", "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 df0aaa0662..e246e23315 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": "vMjY250Ko3ULV8mMenZFFKbTfHYY8HLQizQJkOK/Ee0=", + "shasum": "105XsceecyaQZ+reC6ZNusiOJVUMBoNSYK4PHvREPQw=", "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 e227bb5819..5aef27eb43 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": "uo2UGVZKuD4tO104OeC7Y2oBkpLPrvJWWyGPJTWPVD4=", + "shasum": "9Ttl+TbR7gufQzhQx4o/keKeINCjio65rjLWOlP36NM=", "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 642b38627e..ff794a8fe1 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": "NuMOVeN5RPc/tuAINdZLD7Q0wccvhS9w0YLCq7JXs8M=", + "shasum": "0NNqpKgq/+zNokLs67+6vrA+kZxvDnDe9PWrX6X8Goc=", "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 f0ba60b41c..a2a3144243 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": "7Zm7WDQ4Dvg8YnbqTacXu0oC3C25O1oMJkPnLW+r5co=", + "shasum": "tDbsxyvQUz2VoKmIzIXbLr3mtJfNewiHzoEjW7X6c8k=", "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 ab6a9fb35a..79ee74c52d 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": "k0CibdCt3wsBIq47PPqzR193o2VjuddFrcOribmHHmc=", + "shasum": "8/P91GreWpqGOWLGAntmd3Kyu6A3WMelRIWUuxKcsGI=", "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 0cd225ecbf..afb4738193 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": "pFmqP6z84qAB39wGZcxFQifiNlDOKT7QBPMeKDKNRwg=", + "shasum": "1bLwZCxQusG/VCAswAJ5ohHF6fBgiQtTLhQG1Mby4HI=", "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 5b0ca54a5b..ff9b4d4c18 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": "OK/QqfPZc/L7VitrdRypHggyR4/4PnAG/j9erpJfJww=", + "shasum": "9m0//6LmoFrxb/Mpkjlr7eNRe9HK92iKJgtqtijlkFw=", "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 7321643b24..764d362693 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": "lFmdEJF/RldpN7/U6roJJm9BqSI5Bb/Whzh6dockQAw=", + "shasum": "sKg86tSHApZz49OdruONj63Yhts7zDlyLF2lpf6sguw=", "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 024f13679b..ba9cd98a97 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": "oX0FZa9+kdiEHrtU1sMbUCJMoXpiM9ur2W7WG8s/OW8=", + "shasum": "0vuiA3xbMMPYDMcE7ytDmXM0B4wwc+b90Ncy114JU24=", "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 1b4e69090f..34e5273135 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": "YV8OblLCDm1crxxmNDvWA3W8FL5Im3754++56NBSbUY=", + "shasum": "I2MgHF7MuMKoGGKMSJTB2TAdRNt3ThuAs7N5QLqsFwc=", "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 88d724b5d4..9de489cf9c 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": "wquuFPu3DiUvPAXvUsSwnYN6l8SltYivBAlMdfHXLS4=", + "shasum": "F+ZTNm0I7LeuhiRfFLz34sEn1nQrHEmZ4vbl4V1buiU=", "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 1f51775aa1..d0b3a94d16 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": "AYkzHZhTfVEuyZbMIOrbwX+fTYIm97M2rs/iwXEG954=", + "shasum": "LSk+GmGMP8byzHX013+Fnj8hPWxFD1ctKE7FwmXwUZ4=", "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 ec3d881736..951f2c349d 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": "7mRu1KFdFY6XxJCVdUBj+vHOgiuEtdbuQ7pNXrwVZdw=", + "shasum": "gRoujvXbGzclQmIEgWQQvuKxeeTXt4jt8Kv+SggT5I8=", "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 290cd9dde2..cf2c2e4664 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": "DMgBdNXhzY5eanbB+zfrshV+NfhYhwcf3N7+OpXOfVg=", + "shasum": "pgKvkVBqtJ4sPb6shDMNXiDc1C8apmV4+WfN6ciavKg=", "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 a4ff828cc7..5e624673b7 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": "y06YrfQGNwFwo/kH3HfwJaWNYQu5C6UAKuZlafLWYHk=", + "shasum": "MDjrpUYVTAW1NxWp544Gn4DcF0t6GoDBQIR3F+zeqtI=", "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 1da343a63e..d188a8a74a 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": "6qUXiZopZ2r/V/Hd6v8v4OjP2IKpBwMu5WAZjO4Ikgs=", + "shasum": "G9fNyPGqUsjlqzMskFKEkdy04ny6eZDBBlXSajmQcN8=", "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 8f374bf06b..ec8df0fe47 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": "XKLkCOhCgU+yQ2Z+qnX7mSrlduxJeqMqBd5nqKKdz6Q=", + "shasum": "1Vo2b7dgicVZYWx6DMTlnkDnK6UBbk4fmilSsLKwSDg=", "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 05c2117df1..10ca7dc40f 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": "ZFIL9SrOEbTWaUDGIJZXZJPJFGraq3sPiWM8bC2tLy4=", + "shasum": "czQq93Uay+jE8HZvwSViZfMdDQTPn0ClMJaH34VN0dc=", "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 21adc7e4f1..230a3667b3 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": "lvCLamKc9tutH9zT6ECKjMoVgKxaueZxEm2Soy+GIX0=", + "shasum": "Vr0QhrtHYgg7DFczqQc4KEkAkKrOCOAc3ZnNesEVZ+w=", "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 f6dbf4f567..dc4a0e6564 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": "osTfC/2Y6+aDcYtRj6N7YxVRLfVgNEHAmL7Km8nkwI4=", + "shasum": "6btrG6bcFAWosjSF/ldUuTd3sXNlYxVsRm0gcTJRE2M=", "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 c501cb588b..b26add1a30 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": "xM6Q/SRD4ivSYzwqD2NhmlVQ5v2RHLnEN8ohxWtWcBU=", + "shasum": "khCrwAziXNhRJrV8cHp3hfRyudXVBYVB3Cew7YpWDG0=", "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 6ac46703ac..03ac09d8de 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": "9DHCPiYk3FZZqg0rdsH0YIJFL3x8BfUH3jcZAD2gqA4=", + "shasum": "7wOe/70O/A4BoUF5+CabV0Rjivnnv2D/xcQ1AlAZdw0=", "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 bc64d3110a..d0e13487f4 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": "SBw2f5d1MMrve92+z+rGmPpJ5hJmyuMi1N6Pza/9nXU=", + "shasum": "5dDSK6C4XDIT1ukZh1nEimci2a+z0hnqUNEqi7qBNAk=", "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 8a6d8cdab6..3b2e3be783 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": "FBzAQuGVTVij6SLPkzro0OmcEPNXKT1NLHjiY3rUcxQ=", + "shasum": "8kKJRiaM18IxSr89svPvmzGIjRszgJvmfLkJxJLyFA4=", "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 686dd749ab..eb730f138c 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": "xEkfElvZ0ndhl1PcdkqDL6gicxdKxae/aKDl011Tgs4=", + "shasum": "DGgaRZmS8KSVnUzCq/V7f9TauAelSCFe9sg3myXrWks=", "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 f6bc3fabda..54f4234e1c 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": "+x5V9w2ybgWUj2j/DvhKFRI3yo0ng8zhQ/BFN55F7yw=", + "shasum": "U4Zhw9ZpNVXJHoYfuYJ4a6j/ynAaFXxZYUYo0DSESVs=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index 263bcd6345..94c92cee99 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { - "branches": 91.98, - "functions": 96.73, + "branches": 92.08, + "functions": 96.74, "lines": 97.95, "statements": 97.63 } diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx index 59f41f9ae9..6cf808a508 100644 --- a/packages/snaps-controllers/src/interface/utils.test.tsx +++ b/packages/snaps-controllers/src/interface/utils.test.tsx @@ -8,6 +8,7 @@ import { Input, Text, FileInput, + Checkbox, } from '@metamask/snaps-sdk/jsx'; import { assertNameIsUnique, constructState } from './utils'; @@ -305,6 +306,53 @@ describe('constructState', () => { }); }); + it('supports root level checkboxes in forms', () => { + const element = ( + + + + ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + foo: true, + }); + }); + + it('sets default value for checkbox in forms', () => { + const element = ( + +
+ + + +
+
+ ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + form: { foo: false }, + }); + }); + + it('supports checkboxes in forms', () => { + const element = ( + +
+ + + +
+
+ ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + form: { foo: true }, + }); + }); + 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 f1965bbd08..d7ce6f1d4d 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -12,6 +12,7 @@ import type { JSXElement, OptionElement, FileInputElement, + CheckboxElement, } from '@metamask/snaps-sdk/jsx'; import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx'; import { @@ -60,14 +61,41 @@ export function assertNameIsUnique(state: InterfaceState, name: string) { * @returns The default state for the specific component, if any. */ function constructComponentSpecificDefaultState( - element: InputElement | DropdownElement, + element: InputElement | DropdownElement | CheckboxElement, ) { - if (element.type === 'Dropdown') { - const children = getJsxChildren(element) as OptionElement[]; - return children[0]?.props.value; + switch (element.type) { + case 'Dropdown': { + const children = getJsxChildren(element) as OptionElement[]; + return children[0]?.props.value; + } + + case 'Checkbox': + return false; + + default: + return null; } +} + +/** + * Get the state value for a stateful component. + * + * Most components store the state value as a `value` prop. + * This function exists to account for components where that isn't the case. + * + * @param element - The input element. + * @returns The state value for a given component. + */ +function getComponentStateValue( + element: InputElement | DropdownElement | CheckboxElement, +) { + switch (element.type) { + case 'Checkbox': + return element.props.checked; - return null; + default: + return element.props.value; + } } /** @@ -80,7 +108,7 @@ function constructComponentSpecificDefaultState( */ function constructInputState( oldState: InterfaceState, - element: InputElement | DropdownElement | FileInputElement, + element: InputElement | DropdownElement | FileInputElement | CheckboxElement, form?: string, ) { const oldStateUnwrapped = form ? (oldState[form] as FormState) : oldState; @@ -91,7 +119,7 @@ function constructInputState( } return ( - element.props.value ?? + getComponentStateValue(element) ?? oldInputState ?? constructComponentSpecificDefaultState(element) ?? null @@ -135,7 +163,8 @@ export function constructState( currentForm && (component.type === 'Input' || component.type === 'Dropdown' || - component.type === 'FileInput') + component.type === 'FileInput' || + component.type === 'Checkbox') ) { const formState = newState[currentForm.name] as FormState; assertNameIsUnique(formState, component.props.name); @@ -151,7 +180,8 @@ export function constructState( if ( component.type === 'Input' || component.type === 'Dropdown' || - component.type === 'FileInput' + component.type === 'FileInput' || + component.type === 'Checkbox' ) { assertNameIsUnique(newState, component.props.name); newState[component.props.name] = constructInputState(oldState, component); diff --git a/packages/snaps-sdk/src/jsx/components/form/Checkbox.test.tsx b/packages/snaps-sdk/src/jsx/components/form/Checkbox.test.tsx new file mode 100644 index 0000000000..7ac2a7557a --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/Checkbox.test.tsx @@ -0,0 +1,33 @@ +import { Checkbox } from './Checkbox'; + +describe('Checkbox', () => { + it('renders a checkbox', () => { + const result = ; + + expect(result).toStrictEqual({ + type: 'Checkbox', + props: { + name: 'foo', + checked: true, + }, + key: null, + }); + }); + + it('renders a checkbox with a variant and a label', () => { + const result = ( + + ); + + expect(result).toStrictEqual({ + type: 'Checkbox', + props: { + name: 'foo', + checked: true, + variant: 'toggle', + label: 'Foo', + }, + key: null, + }); + }); +}); diff --git a/packages/snaps-sdk/src/jsx/components/form/Checkbox.ts b/packages/snaps-sdk/src/jsx/components/form/Checkbox.ts new file mode 100644 index 0000000000..ad2dd799fc --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/Checkbox.ts @@ -0,0 +1,41 @@ +import { createSnapComponent } from '../../component'; + +/** + * The props of the {@link Checkbox} component. + * + * @property name - The name of the checkbox. This is used to identify the + * state in the form data. + * @property checked - Whether the checkbox is checked or not. + * @property label - An optional label for the checkbox. + * @property variant - An optional variant for the checkbox. + */ +export type CheckboxProps = { + name: string; + checked?: boolean | undefined; + label?: string | undefined; + variant?: 'default' | 'toggle' | undefined; +}; + +const TYPE = 'Checkbox'; + +/** + * A checkbox component, which is used to create a checkbox. + * + * @param props - The props of the component. + * @param props.name - The name of the checkbox. This is used to identify the + * state in the form data. + * @param props.checked - Whether the checkbox is checked or not. + * @param props.label - An optional label for the checkbox. + * @param props.variant - An optional variant for the checkbox. + * @returns A checkbox element. + * @example + * + */ +export const Checkbox = createSnapComponent(TYPE); + +/** + * A checkbox element. + * + * @see Checkbox + */ +export type CheckboxElement = ReturnType; diff --git a/packages/snaps-sdk/src/jsx/components/form/Field.ts b/packages/snaps-sdk/src/jsx/components/form/Field.ts index 9ba260eed2..d2f87f38ba 100644 --- a/packages/snaps-sdk/src/jsx/components/form/Field.ts +++ b/packages/snaps-sdk/src/jsx/components/form/Field.ts @@ -1,5 +1,6 @@ import { createSnapComponent } from '../../component'; import type { ButtonElement } from './Button'; +import type { CheckboxElement } from './Checkbox'; import type { DropdownElement } from './Dropdown'; import type { FileInputElement } from './FileInput'; import type { InputElement } from './Input'; @@ -18,7 +19,8 @@ export type FieldProps = { | [InputElement, ButtonElement] | DropdownElement | FileInputElement - | InputElement; + | InputElement + | CheckboxElement; }; const TYPE = 'Field'; diff --git a/packages/snaps-sdk/src/jsx/components/form/index.ts b/packages/snaps-sdk/src/jsx/components/form/index.ts index af7ee70959..a2917bfaa5 100644 --- a/packages/snaps-sdk/src/jsx/components/form/index.ts +++ b/packages/snaps-sdk/src/jsx/components/form/index.ts @@ -1,4 +1,5 @@ import type { ButtonElement } from './Button'; +import type { CheckboxElement } from './Checkbox'; import type { DropdownElement } from './Dropdown'; import type { FieldElement } from './Field'; import type { FileInputElement } from './FileInput'; @@ -7,6 +8,7 @@ import type { InputElement } from './Input'; import type { OptionElement } from './Option'; export * from './Button'; +export * from './Checkbox'; export * from './Dropdown'; export * from './Option'; export * from './Field'; @@ -16,6 +18,7 @@ export * from './Input'; export type StandardFormElement = | ButtonElement + | CheckboxElement | FormElement | FieldElement | FileInputElement diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index eb6a2a0efa..98ccfff126 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -22,6 +22,7 @@ import { Tooltip, Value, FileInput, + Checkbox, } from './components'; import { AddressStruct, @@ -29,6 +30,7 @@ import { BoldStruct, BoxStruct, ButtonStruct, + CheckboxStruct, CopyableStruct, DividerStruct, DropdownStruct, @@ -220,6 +222,9 @@ describe('FieldStruct', () => { , + + + , ])('validates a field element', (value) => { expect(is(value, FieldStruct)).toBe(true); }); @@ -466,6 +471,36 @@ describe('BoxStruct', () => { }); }); +describe('CheckboxStruct', () => { + it.each([ + , + , + , + ])('validates a dropdown element', (value) => { + expect(is(value, CheckboxStruct)).toBe(true); + }); + + it.each([ + 'foo', + 42, + null, + undefined, + {}, + [], + // @ts-expect-error - Invalid props. + foo, + foo, + + foo + , + + alt + , + ])('does not validate "%p"', (value) => { + expect(is(value, CheckboxStruct)).toBe(false); + }); +}); + describe('CopyableStruct', () => { it.each([ , @@ -541,9 +576,6 @@ describe('DropdownStruct', () => { {}, [], // @ts-expect-error - Invalid props. - , - , - // @ts-expect-error - Invalid props. foo, foo, @@ -553,7 +585,7 @@ describe('DropdownStruct', () => { alt , ])('does not validate "%p"', (value) => { - expect(is(value, ValueStruct)).toBe(false); + expect(is(value, DropdownStruct)).toBe(false); }); }); diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index 703cce5d77..1c6a2ccc7a 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -37,6 +37,7 @@ import type { BoldElement, BoxElement, ButtonElement, + CheckboxElement, CopyableElement, DividerElement, DropdownElement, @@ -135,6 +136,16 @@ export const ButtonStruct: Describe = element('Button', { disabled: optional(boolean()), }); +/** + * A struct for the {@link CheckboxElement} type. + */ +export const CheckboxStruct: Describe = element('Checkbox', { + name: string(), + checked: optional(boolean()), + label: optional(string()), + variant: optional(nullUnion([literal('default'), literal('toggle')])), +}); + /** * A struct for the {@link InputElement} type. */ @@ -187,6 +198,7 @@ export const FieldStruct: Describe = element('Field', { DropdownStruct, FileInputStruct, InputStruct, + CheckboxStruct, ]), }); @@ -396,6 +408,7 @@ export const BoxChildStruct = nullUnion([ SpinnerStruct, TextStruct, TooltipStruct, + CheckboxStruct, ]); /** @@ -429,6 +442,7 @@ export const JSXElementStruct: Describe = nullUnion([ OptionStruct, ValueStruct, TooltipStruct, + CheckboxStruct, ]); /** diff --git a/packages/snaps-sdk/src/types/handlers/user-input.test.ts b/packages/snaps-sdk/src/types/handlers/user-input.test.ts index d9d914cd62..591b827f26 100644 --- a/packages/snaps-sdk/src/types/handlers/user-input.test.ts +++ b/packages/snaps-sdk/src/types/handlers/user-input.test.ts @@ -1,6 +1,10 @@ import { is } from 'superstruct'; -import { FormSubmitEventStruct, UserInputEventType } from './user-input'; +import { + FormSubmitEventStruct, + InputChangeEventStruct, + UserInputEventType, +} from './user-input'; describe('UserInputEventType', () => { it('has the correct values', () => { @@ -13,7 +17,7 @@ describe('UserInputEventType', () => { }); describe('FormSubmitEventStruct', () => { - it('accepts string values and files', () => { + it('accepts strings, booleans, and files as value', () => { expect( is( { @@ -27,6 +31,7 @@ describe('FormSubmitEventStruct', () => { contents: '...', }, string: 'bar', + bool: true, }, }, FormSubmitEventStruct, @@ -34,3 +39,31 @@ describe('FormSubmitEventStruct', () => { ).toBe(true); }); }); + +describe('InputChangeEventStruct', () => { + it('accepts string values', () => { + expect( + is( + { + type: 'InputChangeEvent', + name: 'foo', + value: 'bar', + }, + InputChangeEventStruct, + ), + ).toBe(true); + }); + + it('accepts boolean values', () => { + expect( + is( + { + type: 'InputChangeEvent', + name: 'foo', + value: true, + }, + InputChangeEventStruct, + ), + ).toBe(true); + }); +}); diff --git a/packages/snaps-sdk/src/types/handlers/user-input.ts b/packages/snaps-sdk/src/types/handlers/user-input.ts index 03db7c6704..126604d70f 100644 --- a/packages/snaps-sdk/src/types/handlers/user-input.ts +++ b/packages/snaps-sdk/src/types/handlers/user-input.ts @@ -9,6 +9,7 @@ import { record, string, union, + boolean, } from 'superstruct'; import type { InterfaceContext } from '../interface'; @@ -74,7 +75,7 @@ export const FormSubmitEventStruct = assign( GenericEventStruct, object({ type: literal(UserInputEventType.FormSubmitEvent), - value: record(string(), nullable(union([string(), FileStruct]))), + value: record(string(), nullable(union([string(), FileStruct, boolean()]))), name: string(), }), ); @@ -99,7 +100,7 @@ export const InputChangeEventStruct = assign( object({ type: literal(UserInputEventType.InputChangeEvent), name: string(), - value: string(), + value: union([string(), boolean()]), }), ); diff --git a/packages/snaps-sdk/src/types/interface.ts b/packages/snaps-sdk/src/types/interface.ts index b144c74601..624bc06b1a 100644 --- a/packages/snaps-sdk/src/types/interface.ts +++ b/packages/snaps-sdk/src/types/interface.ts @@ -1,6 +1,6 @@ import { JsonStruct } from '@metamask/utils'; import type { Infer } from 'superstruct'; -import { nullable, record, string, union } from 'superstruct'; +import { boolean, nullable, record, string, union } from 'superstruct'; import type { JSXElement } from '../jsx'; import { RootJSXElementStruct } from '../jsx'; @@ -15,7 +15,7 @@ import { FileStruct } from './handlers'; * either the value of an input or a sub-state of a form. */ -export const StateStruct = union([FileStruct, string()]); +export const StateStruct = union([FileStruct, string(), boolean()]); export const FormStateStruct = record(string(), nullable(StateStruct));