├── .eslintrc.js ├── .github └── images │ └── screen.png ├── .gitignore ├── .prettierrc.js ├── README.md ├── assets ├── 1280.png ├── 1400.png ├── 440.png └── icon.png ├── package-lock.json ├── package.json ├── package ├── .gitignore ├── README.md ├── index.js ├── index.ts ├── package-lock.json ├── package.json ├── tsconfig-cjs.json └── tsconfig.json ├── public ├── 128.png ├── 16.png ├── 48.png ├── devtools.html ├── favicon.ico ├── icons │ ├── arrow-down.png │ └── arrow-right.png ├── index.html ├── manifest.json ├── robots.txt └── scripts │ ├── background.js │ ├── content.js │ ├── devtools.js │ └── war.js ├── src ├── __mock │ └── messagesMock.ts ├── browser-api │ └── browser.ts ├── components │ ├── FormikStateValues │ │ ├── FormikStateValues.tsx │ │ ├── index.ts │ │ └── style.module.scss │ ├── FormikStateValuesList │ │ ├── FormikStateValuesList.tsx │ │ ├── index.ts │ │ └── style.module.scss │ ├── StatesList │ │ ├── StatesList.tsx │ │ ├── index.ts │ │ └── style.module.scss │ ├── StatusPanel │ │ ├── StatusPanel.tsx │ │ ├── index.ts │ │ └── style.module.scss │ └── index.ts ├── containers │ ├── FormikDevtools.tsx │ ├── index.ts │ └── style.module.scss ├── examples │ ├── ExtensionLiveExample │ │ ├── ExtensionLiveExample.tsx │ │ └── style.module.scss │ ├── FormikForm │ │ └── FormikForm.tsx │ └── index.tsx ├── helpers │ ├── renderValue.tsx │ ├── utils.tsx │ └── validate.tsx ├── hooks │ └── useMessageLoad.ts ├── index.tsx ├── interfaces │ ├── formikState.ts │ ├── message.ts │ └── values.ts ├── parsers │ ├── changesParsers.ts │ ├── parseHelpers.ts │ └── valuesParsers.ts ├── react-app-env.d.ts ├── styles │ ├── colorVariables.scss │ └── resetStyles.scss └── validation │ └── messageValidation.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 2020, 5 | sourceType: 'module', 6 | }, 7 | settings: { 8 | react: { 9 | version: 'detect', 10 | }, 11 | }, 12 | extends: [ 13 | 'plugin:react/recommended', 14 | 'plugin:@typescript-eslint/recommended', 15 | 'prettier/@typescript-eslint', 16 | 'plugin:prettier/recommended', 17 | ], 18 | rules: { 19 | 'react/prop-types': 'off', 20 | '@typescript-eslint/explicit-module-boundary-types': 'off', 21 | '@typescript-eslint/no-var-requires': 'off', 22 | '@typescript-eslint/no-use-before-define': 0, 23 | }, 24 | globals: { 25 | chrome: 'readonly', 26 | browser: 'readonly', 27 | document: 'readonly', 28 | window: 'readonly', 29 | console: 'readonly', 30 | CustomEvent: 'readonly', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/.github/images/screen.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /build_zip -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formik Devtools 2 | 3 | [![npm version](https://badge.fury.io/js/formik-devtools-extension.svg)](https://badge.fury.io/js/formik-devtools-extension) 4 | [![downloads](https://img.shields.io/npm/dw/formik-devtools-extension)](https://img.shields.io/npm/dw/formik-devtools-extension) 5 | 6 | ## Browser extension for debugging [Formik](https://github.com/formium/formik) state. 7 | 8 | ## Check Demo [here](https://petrenkovitaliy.github.io/) 9 | 10 |

11 | Devtools preview 12 |

13 | 14 | ## 1. Installation: 15 | 16 | ### 1.1 install [Chrome extension](https://chrome.google.com/webstore/detail/formik-devtools/dadeefbkfcpaeacnafgceahcpjlfmmjj?hl=en) or [Firefox addon](https://addons.mozilla.org/en-GB/firefox/addon/formik-devtools/) 17 | 18 | ### 1.2 install package with [npm](https://www.npmjs.com/package/formik-devtools-extension): 19 | 20 | ```bash 21 | npm i formik-devtools-extension 22 | ``` 23 | 24 | ## 2. Quick Start: 25 | 26 | ### 2.1 inside your component containing `` use: 27 | 28 | ```tsx 29 | import { withFormikDevtools } from "formik-devtools-extension"; 30 | 31 | /* ... */ 32 | 33 | 34 | {(formikProps) => { 35 | withFormikDevtools(formikProps); 36 | return 37 | } 38 | 39 | ``` 40 | 41 | OR _(both methods are equivalent)_ : 42 | 43 | ```jsx 44 | // pass props with ReactElements 45 | 46 | 47 | {(formikProps) => 48 | withFormikDevtools(formikProps, 49 |
50 | 51 |
) 52 | } 53 |
54 | ``` 55 | 56 | ### 2.2 open page you want to monitor in browser 57 | 58 | ### 2.3 open browser devtools (F12) with **"Formik tab"** 59 | 60 | ## 3. API: 61 | 62 | - _withFormikDevtools_ passes Formik props on every update and sends values to extension. 63 | 64 | ```ts 65 | withFormikDevtools(formikProps: FormikProps, children?: any): children | undefined 66 | ``` 67 | 68 | - If you have more than one Formik component, you should name them. _getFormikDevtools_ returns _withFormikDevtools_ entity with binded name. 69 | 70 | ```ts 71 | getFormikDevtools(formName: string): withFormikDevtools 72 | ``` 73 | -------------------------------------------------------------------------------- /assets/1280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/assets/1280.png -------------------------------------------------------------------------------- /assets/1400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/assets/1400.png -------------------------------------------------------------------------------- /assets/440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/assets/440.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/assets/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-devtools", 3 | "version": "0.3.1", 4 | "description": "Formik Developer Tools for debugging React form components", 5 | "private": true, 6 | "author": { 7 | "email": "vitaliy.ptt@gmail.com", 8 | "url": "https://github.com/petrenkoVitaliy", 9 | "name": "Vitaliy Petrenko" 10 | }, 11 | "license": "MIT", 12 | "dependencies": { 13 | "classnames": "^2.3.1", 14 | "formik": "^2.2.3", 15 | "formik-devtools-extension": "^0.1.4", 16 | "node-sass": "^4.14.1", 17 | "prism-react-renderer": "^1.1.1", 18 | "react": "^16.13.1", 19 | "react-dom": "^16.13.1", 20 | "react-live": "^2.2.3", 21 | "react-scripts": "^3.4.3", 22 | "yup": "^0.29.3" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "start-examples": "REACT_APP_ENV=test react-scripts start", 27 | "build": "react-scripts build", 28 | "build-examples": "REACT_APP_ENV=test react-scripts build", 29 | "eject": "react-scripts eject", 30 | "lint": "eslint \"src/**/*.{js,ts,tsx}\" --fix", 31 | "prettier": "prettier --write --print-width 120 --trailing-comma all \"**/*.+(ts|tsx|js|jsx)\"" 32 | }, 33 | "pre-commit": [ 34 | "prettier", 35 | "lint" 36 | ], 37 | "eslintConfig": { 38 | "extends": "react-app" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "devDependencies": { 53 | "@types/chrome": "0.0.125", 54 | "@types/classnames": "^2.2.10", 55 | "@types/firefox-webext-browser": "^82.0.0", 56 | "@types/node": "^12.12.62", 57 | "@types/react": "^16.9.49", 58 | "@types/react-dom": "^16.9.8", 59 | "@types/yup": "^0.29.8", 60 | "@typescript-eslint/eslint-plugin": "^4.0.0", 61 | "eslint-config-prettier": "^6.12.0", 62 | "eslint-plugin-prettier": "^3.1.4", 63 | "pre-commit": "^1.2.2", 64 | "prettier": "^2.1.2", 65 | "typescript": "^3.7.5" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /dist 4 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # Formik Devtools 2 | 3 | [![NPM version](https://badgen.net/npm/v/formik-devtools-extension)](https://www.npmjs.com/package/formik-devtools-extension) 4 | 5 | ## Browser extension for debugging [Formik](https://github.com/formium/formik) state. 6 | 7 | ## Check Demo [here](https://petrenkovitaliy.github.io/) 8 | 9 |

10 | Devtools preview 11 |

12 | 13 | ## 1. Installation: 14 | 15 | ### 1.1 install [Chrome extension](https://chrome.google.com/webstore/detail/formik-devtools/dadeefbkfcpaeacnafgceahcpjlfmmjj?hl=en) or [Firefox addon](https://addons.mozilla.org/en-GB/firefox/addon/formik-devtools/) 16 | 17 | ### 1.2 install package with [npm](https://www.npmjs.com/package/formik-devtools-extension): 18 | 19 | ```bash 20 | npm i formik-devtools-extension 21 | ``` 22 | 23 | ## 2. Quick Start: 24 | 25 | ### 2.1 inside your component containing `` use: 26 | 27 | ```tsx 28 | import { withFormikDevtools } from "formik-devtools-extension"; 29 | 30 | /* ... */ 31 | 32 | // pass just props 33 | 34 | {(formikProps) => { 35 | withFormikDevtools(formikProps); 36 | return 37 | } 38 | 39 | ``` 40 | 41 | OR _(both methods are equivalent)_ : 42 | 43 | ```jsx 44 | // pass props with ReactElements 45 | 46 | 47 | {(formikProps) => 48 | withFormikDevtools(formikProps, 49 |
50 | 51 |
) 52 | } 53 |
54 | ``` 55 | 56 | you can also use it in functional components 57 | 58 | ```jsx 59 | import { useFormik } from 'formik'; 60 | import { withFormikDevtools } from 'formik-devtools-extension'; 61 | 62 | export const FunctionalComponent = () => { 63 | // initializing a form with a hook 64 | const formikForm = useFormik({ 65 | initialValues: { 66 | firstFormValue: '', 67 | secondFormValue: {}, 68 | }, 69 | onSubmit, 70 | }); 71 | 72 | // call it at each render 73 | withFormikDevtools(formikForm); 74 | 75 | 76 | return ( 77 | // ..your form implementation 78 | ) 79 | } 80 | 81 | ``` 82 | 83 | ### 2.2 open page you want to monitor in browser 84 | 85 | ### 2.3 open browser devtools (F12) with **"Formik tab"** 86 | 87 | ## 3. API: 88 | 89 | - _withFormikDevtools_ passes Formik props on every update and sends values to extension. 90 | 91 | ```ts 92 | withFormikDevtools(formikProps: FormikProps, children?: any): children | undefined 93 | ``` 94 | 95 | - If you have more than one Formik component, you should name them. _getFormikDevtools_ returns _withFormikDevtools_ entity with binded name. 96 | 97 | ```ts 98 | getFormikDevtools(formName: string): withFormikDevtools 99 | ``` 100 | -------------------------------------------------------------------------------- /package/index.js: -------------------------------------------------------------------------------- 1 | export * from './dist/cjs'; 2 | export * from './dist/esm'; 3 | -------------------------------------------------------------------------------- /package/index.ts: -------------------------------------------------------------------------------- 1 | interface ExtendedWindow extends Window { 2 | FORMIK_DEVTOOLS?: (formikProps: any) => void; 3 | } 4 | const extendedWindow: ExtendedWindow | null = typeof window === 'undefined' ? null : window; 5 | 6 | const FormikDevtools = () => { 7 | let isInitialRender = true; 8 | return (formikProps: T, children?: M) => { 9 | if (extendedWindow?.FORMIK_DEVTOOLS) { 10 | extendedWindow.FORMIK_DEVTOOLS({ ...formikProps, __init: isInitialRender }); 11 | if (isInitialRender) { 12 | isInitialRender = false; 13 | } 14 | } 15 | return children; 16 | }; 17 | }; 18 | 19 | export const withFormikDevtools = FormikDevtools(); 20 | 21 | const getNamedFormikDevtools = (formName: string) => { 22 | let isInitialRender = true; 23 | return (formikProps: T, children?: M) => { 24 | if (extendedWindow?.FORMIK_DEVTOOLS) { 25 | extendedWindow.FORMIK_DEVTOOLS({ ...formikProps, __init: isInitialRender, __formName: formName }); 26 | if (isInitialRender) { 27 | isInitialRender = false; 28 | } 29 | } 30 | return children; 31 | }; 32 | }; 33 | 34 | export const getFormikDevtools = getNamedFormikDevtools; 35 | -------------------------------------------------------------------------------- /package/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-devtools-extension", 3 | "version": "0.1.8", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "formik-devtools-extension", 9 | "version": "0.1.8", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "typescript": "^4.0.5" 13 | } 14 | }, 15 | "node_modules/typescript": { 16 | "version": "4.0.5", 17 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", 18 | "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", 19 | "dev": true, 20 | "bin": { 21 | "tsc": "bin/tsc", 22 | "tsserver": "bin/tsserver" 23 | }, 24 | "engines": { 25 | "node": ">=4.2.0" 26 | } 27 | } 28 | }, 29 | "dependencies": { 30 | "typescript": { 31 | "version": "4.0.5", 32 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", 33 | "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", 34 | "dev": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-devtools-extension", 3 | "version": "0.1.8", 4 | "description": "Formik Developer Tools for debugging React form components", 5 | "author": { 6 | "email": "vitaliy.ptt@gmail.com", 7 | "url": "https://github.com/petrenkoVitaliy", 8 | "name": "Vitaliy Petrenko" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/petrenkoVitaliy/formik-devtools" 14 | }, 15 | "main": "dist/cjs/index.js", 16 | "module": "dist/esm/index.js", 17 | "types": "dist/esm/index.d.ts", 18 | "files": [ 19 | "dist/" 20 | ], 21 | "scripts": { 22 | "build": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json" 23 | }, 24 | "devDependencies": { 25 | "typescript": "^4.0.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package/tsconfig-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "./dist/cjs" 6 | }, 7 | } -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2017", "es7", "es6", "dom"], 6 | "declaration": true, 7 | "outDir": "./dist/esm", 8 | "strict": true, 9 | "esModuleInterop": true 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "dist" 14 | ] 15 | } -------------------------------------------------------------------------------- /public/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/128.png -------------------------------------------------------------------------------- /public/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/16.png -------------------------------------------------------------------------------- /public/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/48.png -------------------------------------------------------------------------------- /public/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/icons/arrow-down.png -------------------------------------------------------------------------------- /public/icons/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petrenkoVitaliy/formik-devtools/fcf130300e186b249ce0c7ea3f8f24dc7c21e65f/public/icons/arrow-right.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Formik Devtools 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Formik Devtools", 4 | "description": "Formik Developer Tools for debugging React form components", 5 | "version": "0.3.1", 6 | "devtools_page": "devtools.html", 7 | "icons": { 8 | "16": "16.png", 9 | "48": "48.png", 10 | "128": "128.png" 11 | }, 12 | "content_scripts": [ 13 | { 14 | "matches": [""], 15 | "js": ["scripts/content.js"], 16 | "run_at": "document_start", 17 | "all_frames": true 18 | } 19 | ], 20 | "background": { 21 | "scripts": ["scripts/background.js"] 22 | }, 23 | "web_accessible_resources": ["scripts/war.js"], 24 | "content_security_policy": "script-src 'self' 'unsafe-eval' 'sha256-B+54yc4gCSgqrGNlJwxG6UsPq7pv1Is8KouyCs7rpXg='; object-src 'self'" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/scripts/background.js: -------------------------------------------------------------------------------- 1 | const BROWSER = chrome || browser; 2 | 3 | let portToDevtoolsScript = null; 4 | let portToContentScript = null; 5 | 6 | BROWSER.runtime.onConnect.addListener((port) => { 7 | if (port.name === 'devtools') { 8 | portToDevtoolsScript = port; 9 | 10 | portToDevtoolsScript.onMessage.addListener((request) => { 11 | if (request.tabId) { 12 | BROWSER.tabs.sendMessage(request.tabId, { message: 'start' }); 13 | } 14 | }); 15 | } 16 | 17 | if (portToDevtoolsScript && port.name === 'content') { 18 | portToContentScript = port; 19 | 20 | portToContentScript.onMessage.addListener((request) => { 21 | if (request.formikProps) { 22 | portToDevtoolsScript.postMessage(request); 23 | } 24 | }); 25 | } 26 | }); 27 | 28 | BROWSER.tabs.onUpdated.addListener((tabId, changeInfo) => { 29 | if (portToDevtoolsScript && changeInfo.status === 'complete') { 30 | portToDevtoolsScript.postMessage({ action: 'restart' }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /public/scripts/content.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const BROWSER = chrome || browser; 3 | 4 | function injectScript(file) { 5 | var s = document.createElement('script'); 6 | s.setAttribute('type', 'text/javascript'); 7 | s.setAttribute('src', file); 8 | document.documentElement.appendChild(s); 9 | } 10 | injectScript(BROWSER.extension.getURL('/scripts/war.js')); 11 | 12 | let messagesList = []; 13 | let port = undefined; 14 | 15 | function tryToConnect() { 16 | if (port) { 17 | sendMessages(); 18 | } 19 | } 20 | 21 | function sendMessages() { 22 | messagesList.forEach((message) => { 23 | port.postMessage(message); 24 | }); 25 | messagesList = []; 26 | } 27 | 28 | document.addEventListener('FORMIK_DEVTOOLS_EVENT', ({ detail: formikProps }) => { 29 | messagesList.push({ formikProps }); 30 | tryToConnect(); 31 | }); 32 | 33 | BROWSER.runtime.onMessage.addListener(function (request) { 34 | if (request.message === 'start') { 35 | port = BROWSER.runtime.connect(BROWSER.runtime.id, { name: 'content' }); 36 | 37 | sendMessages(); 38 | 39 | console.info('[FORMIK:DEVTOOLS] connected to extension'); 40 | } 41 | }); 42 | })(); 43 | -------------------------------------------------------------------------------- /public/scripts/devtools.js: -------------------------------------------------------------------------------- 1 | const BROWSER = chrome || browser; 2 | 3 | if (BROWSER && BROWSER.devtools) { 4 | BROWSER.devtools.panels.create('Formik', '16.png', 'index.html'); 5 | } 6 | -------------------------------------------------------------------------------- /public/scripts/war.js: -------------------------------------------------------------------------------- 1 | const sendFormikDevtoolsMessage = function (props) { 2 | try { 3 | if (props) { 4 | document.dispatchEvent( 5 | new CustomEvent('FORMIK_DEVTOOLS_EVENT', { 6 | detail: JSON.stringify(props), 7 | }), 8 | ); 9 | } 10 | } catch (ex) { 11 | console.log(ex); 12 | } 13 | }; 14 | 15 | window.FORMIK_DEVTOOLS = sendFormikDevtoolsMessage; 16 | -------------------------------------------------------------------------------- /src/__mock/messagesMock.ts: -------------------------------------------------------------------------------- 1 | const mockMessages = [ 2 | JSON.stringify({ 3 | values: { 4 | form1: 'form1', 5 | }, 6 | initialValues: { form1: 'form1' }, 7 | errors: {}, 8 | touched: {}, 9 | dirty: false, 10 | __init: true, 11 | __formName: 'form1', 12 | }), 13 | JSON.stringify({ 14 | values: { 15 | form1: 'form2', 16 | }, 17 | initialValues: { form1: 'form2' }, 18 | errors: {}, 19 | touched: {}, 20 | dirty: false, 21 | __init: true, 22 | __formName: 'form2', 23 | }), 24 | JSON.stringify({ 25 | values: { 26 | form1: 'unn', 27 | }, 28 | initialValues: { form1: 'unn' }, 29 | errors: {}, 30 | touched: {}, 31 | __init: true, 32 | dirty: false, 33 | }), 34 | ]; 35 | export { mockMessages }; 36 | -------------------------------------------------------------------------------- /src/browser-api/browser.ts: -------------------------------------------------------------------------------- 1 | const BROWSER = chrome || browser; 2 | 3 | const sendMessageInTabs = (portToBackgroundPage: browser.runtime.Port | chrome.runtime.Port) => { 4 | portToBackgroundPage.postMessage({ 5 | tabId: BROWSER.devtools.inspectedWindow.tabId, 6 | }); 7 | }; 8 | 9 | export const addListenerToMessages = (callBack: (arg: any) => void) => { 10 | const portToBackgroundPage = BROWSER.runtime.connect(BROWSER.runtime.id, { name: 'devtools' }); 11 | 12 | portToBackgroundPage.onMessage.addListener((msg: any) => { 13 | if (msg) { 14 | if (msg.action && msg.action === 'restart') { 15 | sendMessageInTabs(portToBackgroundPage); 16 | } else { 17 | callBack(msg); 18 | } 19 | } 20 | }); 21 | 22 | sendMessageInTabs(portToBackgroundPage); 23 | }; 24 | 25 | export const addListenerToExampleMessages = (callBack: (arg: any) => void) => { 26 | document.addEventListener('FORMIK_EXAMPLE_DEVTOOLS_EVENT', ({ detail: formikProps }: any) => { 27 | callBack({ formikProps }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/FormikStateValues/FormikStateValues.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | import { IFormikValue } from '../../interfaces/formikState'; 5 | import classNames from './style.module.scss'; 6 | import { renderValue } from '../../helpers/renderValue'; 7 | import { ValueType } from '../../interfaces/values'; 8 | 9 | interface FormikStateValuesProps { 10 | formikStateValues: IFormikValue; 11 | fieldName: string; 12 | } 13 | 14 | export const FormikStateValues: React.FunctionComponent = (props) => { 15 | const [isCollapsed, setIsCollapsed] = useState(true); 16 | 17 | const { formikStateValues, fieldName } = props; 18 | 19 | const toggleCollapse = () => { 20 | setIsCollapsed(!isCollapsed); 21 | }; 22 | 23 | const getColorClassName = (type: ValueType) => { 24 | switch (type) { 25 | case 'string': 26 | return classNames.stringValueColor; 27 | case 'number': 28 | return classNames.numberValueColor; 29 | case 'boolean': 30 | return classNames.booleanValueColor; 31 | case 'object': 32 | return classNames.objectValueColor; 33 | case 'undefined': 34 | return classNames.undefinedValueColor; 35 | case 'null': 36 | return classNames.nullValueColor; 37 | } 38 | }; 39 | 40 | return ( 41 |
47 |
48 |
49 | {isCollapsed ? ( 50 | collapse 51 | ) : ( 52 | collapse 53 | )} 54 | {fieldName}: 55 |
56 | {isCollapsed && ( 57 |
58 | {formikStateValues.collapsedValue} 59 |
60 | )} 61 |
62 | {!isCollapsed && ( 63 |
64 |
{renderValue(formikStateValues.value, classNames)}
65 |
66 | )} 67 | 68 | {formikStateValues.error &&
Error: {formikStateValues.error}
} 69 | {formikStateValues.touched &&
Touched
} 70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/FormikStateValues/index.ts: -------------------------------------------------------------------------------- 1 | export { FormikStateValues } from './FormikStateValues'; 2 | -------------------------------------------------------------------------------- /src/components/FormikStateValues/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colorVariables.scss'; 2 | 3 | .stateWrapper { 4 | width: 100%; 5 | font-family: 'Inconsolata', monospace; 6 | line-height: 17px; 7 | font-size: 14px; 8 | padding: 10px; 9 | 10 | border: 2px solid $backgroundColor; 11 | border-bottom: 10px solid $backgroundColor; 12 | border-left: 10px solid $backgroundColor; 13 | 14 | &.error { 15 | border: 2px solid $errorColor; 16 | border-bottom: 10px solid $errorColor; 17 | } 18 | 19 | &.touched { 20 | border-left: 10px solid $touchedColor; 21 | } 22 | 23 | .collapsedWrap { 24 | font-family: inherit; 25 | display: flex; 26 | justify-content: flex-start; 27 | align-items: flex-start; 28 | cursor: pointer; 29 | 30 | .name { 31 | font-family: inherit; 32 | color: $fieldName; 33 | display: flex; 34 | align-items: center; 35 | cursor: pointer; 36 | 37 | border-bottom: 1px solid $backgroundColor; 38 | 39 | &:hover { 40 | border-bottom: 1px solid $fieldName; 41 | } 42 | 43 | > img { 44 | width: 10px; 45 | height: 10px; 46 | margin-right: 10px; 47 | background: $fieldName; 48 | padding: 2px; 49 | border-radius: 50%; 50 | box-sizing: content-box; 51 | } 52 | 53 | font-weight: bold; 54 | } 55 | 56 | .collapsedValue { 57 | font-family: inherit; 58 | margin-left: 10px; 59 | color: $collapsedColor; 60 | 61 | &.stringValueColor { 62 | color: $stringValueColor; 63 | } 64 | &.numberValueColor { 65 | color: $numberValueColor; 66 | } 67 | &.booleanValueColor { 68 | color: $booleanValueColor; 69 | } 70 | &.objectValueColor { 71 | color: $objectValueColor; 72 | } 73 | &.undefinedValueColor { 74 | color: $defaultValueColor; 75 | } 76 | &.nullValueColor { 77 | color: $nullValueColor; 78 | } 79 | } 80 | } 81 | 82 | .datailedValue { 83 | .value { 84 | color: $defaultValueColor; 85 | 86 | margin-left: 40px; 87 | 88 | .string { 89 | color: $stringValueColor; 90 | } 91 | .number { 92 | color: $numberValueColor; 93 | } 94 | .boolean { 95 | color: $booleanValueColor; 96 | } 97 | .null { 98 | color: $nullValueColor; 99 | } 100 | 101 | .objectProp { 102 | color: $objectValueColor; 103 | } 104 | 105 | .inline { 106 | display: flex; 107 | justify-content: flex-start; 108 | } 109 | 110 | span { 111 | color: $symbolColor; 112 | } 113 | } 114 | } 115 | 116 | .errorText { 117 | margin-top: 10px; 118 | color: $errorColor; 119 | margin-left: 0px; 120 | font-weight: bold; 121 | font-size: 14px; 122 | } 123 | 124 | .touchedText { 125 | margin-top: 10px; 126 | color: $touchedColor; 127 | margin-left: 0px; 128 | font-size: 14px; 129 | font-weight: bold; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/components/FormikStateValuesList/FormikStateValuesList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { IFormikValues } from '../../interfaces/formikState'; 4 | import { FormikStateValues } from '../FormikStateValues'; 5 | import classNames from './style.module.scss'; 6 | 7 | interface FormikStateValuesListProps { 8 | formikState: IFormikValues; 9 | } 10 | 11 | export const FormikStateValuesList: React.FunctionComponent = (props) => { 12 | const { formikState } = props; 13 | 14 | return ( 15 |
16 | {Object.entries(formikState).map(([fieldName, state]) => ( 17 | 18 | ))} 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/FormikStateValuesList/index.ts: -------------------------------------------------------------------------------- 1 | export { FormikStateValuesList } from './FormikStateValuesList'; 2 | -------------------------------------------------------------------------------- /src/components/FormikStateValuesList/style.module.scss: -------------------------------------------------------------------------------- 1 | .statesList { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/StatesList/StatesList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { IFormikDetailedState } from '../../interfaces/formikState'; 3 | import classnames from 'classnames'; 4 | import classNames from './style.module.scss'; 5 | 6 | interface StatesListProps { 7 | formikStates: IFormikDetailedState[]; 8 | currentStep: number; 9 | isFormsList: boolean; 10 | selectStep: (step: number) => void; 11 | } 12 | 13 | export const StatesList: React.FunctionComponent = (props) => { 14 | const { formikStates, currentStep, selectStep, isFormsList } = props; 15 | 16 | const handleSelectStep = (step: number) => () => selectStep(step); 17 | 18 | const reversedStates = useMemo(() => [...formikStates].reverse(), [formikStates]); 19 | 20 | return ( 21 |
22 | {reversedStates.map((state, index) => ( 23 |
30 |
{formikStates.length - index}:
31 |
32 | {formikStates.length - index === 1 ? '' : state.changed.composedChangedString} 33 |
34 |
35 | ))} 36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/StatesList/index.ts: -------------------------------------------------------------------------------- 1 | export { StatesList } from './StatesList'; 2 | -------------------------------------------------------------------------------- /src/components/StatesList/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colorVariables.scss'; 2 | 3 | .statesList { 4 | font-family: 'Inconsolata', monospace; 5 | width: 100%; 6 | height: calc(100% - 300px); 7 | overflow-y: auto; 8 | 9 | &.withSelect { 10 | height: calc(100% - 330px); 11 | } 12 | 13 | .stateItem { 14 | font-size: 16px; 15 | user-select: none; 16 | &.selected { 17 | background: $selectedStateItem; 18 | } 19 | 20 | height: 40px; 21 | display: flex; 22 | align-items: center; 23 | padding: 0 10px; 24 | 25 | border-top: 2px solid $stateItemBorder; 26 | color: $stateItemText; 27 | background: $stateItem; 28 | 29 | .step { 30 | color: $stateItemStepText; 31 | width: 20px; 32 | text-align: right; 33 | margin-right: 10px; 34 | font-weight: bold; 35 | } 36 | 37 | .composedChanges { 38 | white-space: nowrap; 39 | overflow: hidden; 40 | text-overflow: ellipsis; 41 | } 42 | 43 | cursor: pointer; 44 | &:hover { 45 | background: $hoveredStateItem; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/StatusPanel/StatusPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | 4 | import { IFormikDetailedState } from '../../interfaces/formikState'; 5 | 6 | import classNames from './style.module.scss'; 7 | 8 | interface StatusPanelProps { 9 | currentStep: number; 10 | steps: number; 11 | formsList: string[]; 12 | selectedForm: string; 13 | currentState?: IFormikDetailedState; 14 | 15 | handleSelectForm: (formName: string) => void; 16 | setNextStep: () => void; 17 | setPreviousStep: () => void; 18 | } 19 | 20 | export const StatusPanel: React.FunctionComponent = (props) => { 21 | const { 22 | currentStep, 23 | steps, 24 | setNextStep, 25 | setPreviousStep, 26 | currentState, 27 | formsList, 28 | selectedForm, 29 | handleSelectForm, 30 | } = props; 31 | 32 | const handleFormChange = (event: React.ChangeEvent) => { 33 | handleSelectForm(event.target.value); 34 | }; 35 | 36 | return ( 37 |
1 })}> 38 | {formsList.length > 1 && ( 39 |
40 |
Current Form:
41 | 48 |
49 | )} 50 |
51 |
52 | Previous 53 |
54 |
55 | {currentStep} / {steps} 56 |
57 |
58 | Next 59 |
60 |
61 | 62 | {currentState && ( 63 |
64 | {currentState.changed.composedChangedString &&
Changed:
} 65 | 66 | {currentState.changed.changedProperties && ( 67 |
68 |
69 | properties: {currentState.changed.changedProperties} 70 |
71 |
72 | )} 73 | {currentState.changed.changedValues && ( 74 |
75 |
76 | values: {currentState.changed.changedValues} 77 |
78 |
79 | )} 80 | {currentState.changed.changedErrors && ( 81 |
82 |
83 | errors: {currentState.changed.changedErrors} 84 |
85 |
86 | )} 87 | {currentState.changed.changedTouches && ( 88 |
89 |
90 | touched: {currentState.changed.changedTouches} 91 |
92 |
93 | )} 94 |
95 | )} 96 |
97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /src/components/StatusPanel/index.ts: -------------------------------------------------------------------------------- 1 | export { StatusPanel } from './StatusPanel'; 2 | -------------------------------------------------------------------------------- /src/components/StatusPanel/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colorVariables.scss'; 2 | 3 | .statusPanel { 4 | font-family: 'Inconsolata', monospace; 5 | color: $statusPanelText; 6 | background: $statusPanel; 7 | height: 300px; 8 | width: 100%; 9 | padding: 5px 10px; 10 | 11 | &.withSelect { 12 | height: 330px; 13 | } 14 | 15 | .selectWrapper { 16 | width: 100%; 17 | height: 30px; 18 | 19 | display: flex; 20 | justify-content: flex-start; 21 | align-items: center; 22 | 23 | .selectLabel { 24 | width: 120px; 25 | } 26 | 27 | > select { 28 | -moz-appearance: none; 29 | flex-grow: 1; 30 | 31 | height: 100%; 32 | 33 | font-family: 'Inconsolata', monospace; 34 | font-size: 16px; 35 | cursor: pointer; 36 | border-radius: 10px; 37 | background: $statusPanelButton; 38 | color: $statusPanelText; 39 | border: 2px solid $statusPanelBorder; 40 | 41 | &:focus { 42 | outline: none; 43 | } 44 | } 45 | } 46 | 47 | .controls { 48 | display: flex; 49 | justify-content: space-between; 50 | align-items: center; 51 | width: 100%; 52 | height: 50px; 53 | 54 | .button { 55 | font-size: 16px; 56 | user-select: none; 57 | height: 30px; 58 | 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | 63 | padding: 10px; 64 | box-sizing: border-box; 65 | border: 2px solid $statusPanelBorder; 66 | border-radius: 10px; 67 | cursor: pointer; 68 | 69 | background: $statusPanelButton; 70 | color: $statusPanelButtonText; 71 | 72 | &:hover { 73 | background: $statusPanelButtonHovered; 74 | } 75 | } 76 | 77 | .steps { 78 | user-select: none; 79 | font-size: 20px; 80 | font-weight: bold; 81 | } 82 | } 83 | 84 | .changedStatusesList { 85 | margin-top: 20px; 86 | width: 100%; 87 | height: 210px; 88 | overflow-y: auto; 89 | 90 | .title { 91 | font-size: 16px; 92 | font-weight: bold; 93 | margin-bottom: 10px; 94 | } 95 | 96 | .changedStatus { 97 | margin-bottom: 10px; 98 | 99 | display: flex; 100 | justify-content: space-between; 101 | align-items: center; 102 | 103 | .statusAttr { 104 | font-size: 15px; 105 | 106 | > span { 107 | font-weight: bold; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './StatusPanel'; 2 | export * from './FormikStateValuesList'; 3 | export * from './FormikStateValues'; 4 | -------------------------------------------------------------------------------- /src/containers/FormikDevtools.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useState } from 'react'; 2 | 3 | import { useMessageLoad } from '../hooks/useMessageLoad'; 4 | // import { useMessageLoadMOCK } from '../hooks/useMessageLoad'; 5 | 6 | import { FormikStateValuesList, StatusPanel } from '../components'; 7 | import { ChangedState, IFormikDetailedState } from '../interfaces/formikState'; 8 | 9 | import { getChangedInitialProps, getChangedProps } from '../parsers/changesParsers'; 10 | import { parseValues } from '../parsers/valuesParsers'; 11 | import classNames from './style.module.scss'; 12 | import { StatesList } from '../components/StatesList/StatesList'; 13 | 14 | interface FormikDevtoolsProps { 15 | example?: boolean; 16 | } 17 | 18 | interface FormikFormsStates { 19 | [key: string]: IFormikDetailedState[]; 20 | } 21 | 22 | const EMPTY_FORM_NAME = 'unnamed_form'; 23 | 24 | const FormikDevtools: React.FunctionComponent = ({ example }) => { 25 | const [formikStates, setFormikStates] = useState({ [EMPTY_FORM_NAME]: [] }); 26 | const [currentForm, setCurrentForm] = useState(EMPTY_FORM_NAME); 27 | 28 | const [currentStep, setCurrentStep] = useState(-1); 29 | const { message: newMessage } = useMessageLoad(); 30 | 31 | useEffect(() => { 32 | if (!example) { 33 | console.log(formikStates); 34 | } 35 | }, [formikStates, example]); 36 | 37 | useEffect(() => { 38 | if (newMessage) { 39 | const formName = newMessage.__formName || EMPTY_FORM_NAME; 40 | const { state, formProps } = parseValues(newMessage); 41 | 42 | let savedStates = formikStates[formName] || []; 43 | 44 | if (state) { 45 | let changed: ChangedState | null = null; 46 | 47 | if (formProps) { 48 | savedStates = []; 49 | changed = getChangedInitialProps(formProps, state) || {}; 50 | } else { 51 | if (savedStates[savedStates.length - 1]) { 52 | changed = getChangedProps(state, savedStates[savedStates.length - 1].state); 53 | } 54 | } 55 | 56 | if (changed) { 57 | const newFormikStates = { ...formikStates, [formName]: [...savedStates, { state, changed }] }; 58 | setFormikStates(newFormikStates); 59 | if (currentForm === formName) { 60 | setCurrentStep(savedStates.length); 61 | } 62 | if (currentForm === EMPTY_FORM_NAME && formName !== EMPTY_FORM_NAME) { 63 | setCurrentForm(formName); 64 | setCurrentStep(savedStates.length); 65 | } 66 | } 67 | } 68 | } 69 | // eslint-disable-next-line 70 | }, [newMessage]); 71 | 72 | const formsList = useMemo(() => Object.keys(formikStates).filter((formName) => !!formikStates[formName].length), [ 73 | formikStates, 74 | ]); 75 | 76 | const setNextStep = () => { 77 | if (currentStep < formikStates[currentForm].length - 1 && currentStep > -1) { 78 | setCurrentStep(currentStep + 1); 79 | } 80 | }; 81 | 82 | const setPreviousStep = () => { 83 | if (currentStep > 0) { 84 | setCurrentStep(currentStep - 1); 85 | } 86 | }; 87 | 88 | const setStep = (step: number) => { 89 | if (step >= 0 && step < formikStates[currentForm].length) { 90 | setCurrentStep(step); 91 | } 92 | }; 93 | 94 | const handleSelectForm = (formName: string) => { 95 | if (formikStates[formName]) { 96 | setCurrentForm(formName); 97 | setCurrentStep(formikStates[formName].length - 1); 98 | } 99 | }; 100 | 101 | return ( 102 |
103 |
104 | {currentStep > -1 && formikStates[currentForm][currentStep] && ( 105 | 106 | )} 107 |
108 |
109 | -1 && formikStates[currentForm][currentStep] 119 | ? formikStates[currentForm][currentStep] 120 | : undefined 121 | } 122 | /> 123 | 1} 125 | formikStates={formikStates[currentForm]} 126 | currentStep={currentStep} 127 | selectStep={setStep} 128 | /> 129 |
130 |
131 | ); 132 | }; 133 | 134 | export default FormikDevtools; 135 | -------------------------------------------------------------------------------- /src/containers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FormikDevtools } from './FormikDevtools'; 2 | -------------------------------------------------------------------------------- /src/containers/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/colorVariables.scss'; 2 | 3 | .devtoolsWrapper { 4 | display: flex; 5 | justify-content: space-between; 6 | width: 100%; 7 | height: 100vh; 8 | background: $backgroundColor; 9 | 10 | .body { 11 | width: 60%; 12 | overflow: auto; 13 | } 14 | 15 | .sidePanel { 16 | min-width: 250px; 17 | width: 40%; 18 | background: $sidePanel; 19 | border-left: 1px solid $statusPanelBorder; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/ExtensionLiveExample/ExtensionLiveExample.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import dracula from 'prism-react-renderer/themes/dracula'; 3 | 4 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 5 | 6 | import { FormikDevtools } from '../../containers'; 7 | import { FormikForm, FormikFormString, FormikFormStringScope } from '../FormikForm/FormikForm'; 8 | 9 | import styles from './style.module.scss'; 10 | 11 | const LOAD_TIMEOUT = 1; 12 | const IS_LIVE_RENDER = true; 13 | 14 | export const ExtensionLiveExample: React.FunctionComponent = () => { 15 | const [isLoaded, setIsLoaded] = useState(false); 16 | 17 | useEffect(() => { 18 | setTimeout(() => setIsLoaded(true), LOAD_TIMEOUT); // Formik renders before FormikDevtools... 19 | }); 20 | 21 | return ( 22 |
23 |
24 |

Formik Devtools live example

25 |

26 | Change the values ​​in the form and track them with formik-devtools preview 27 |

28 |

*Right panel is imitating extension that could be downloaded from chrome's market

29 | 30 | 49 | 50 | {isLoaded ? ( 51 | <> 52 |

Change values:

53 | {IS_LIVE_RENDER ? ( 54 | 55 | 56 | 57 |
58 | 59 |
60 |
61 | ) : ( 62 | 63 | )} 64 | 65 | ) : null} 66 |
67 |
68 | 69 |
70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/examples/ExtensionLiveExample/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colorVariables.scss'; 2 | 3 | .exampleDevtoolsWrapper { 4 | display: flex; 5 | justify-content: space-between; 6 | width: 100%; 7 | height: 100vh; 8 | background: $statusPanelText; 9 | 10 | .extensionWrapper { 11 | width: 60%; 12 | } 13 | 14 | .formWrapper { 15 | height: 100%; 16 | overflow: scroll; 17 | font-family: 'Inconsolata', monospace; 18 | width: 40%; 19 | display: flex; 20 | flex-direction: column; 21 | padding: 20px; 22 | 23 | .links { 24 | display: flex; 25 | flex-direction: column; 26 | margin: 10px; 27 | border: 1px dotted $statusPanel; 28 | width: 167px; 29 | padding: 5px; 30 | } 31 | 32 | .editorWrapper { 33 | min-height: 400px; 34 | overflow: auto; 35 | } 36 | 37 | .fieldBlock { 38 | margin: 20px 0; 39 | user-select: none; 40 | 41 | input { 42 | margin-right: 10px; 43 | } 44 | > label { 45 | cursor: pointer; 46 | } 47 | > select { 48 | cursor: pointer; 49 | } 50 | } 51 | 52 | .subtitle { 53 | margin-bottom: 5px; 54 | } 55 | 56 | .error { 57 | color: $errorColor; 58 | font-weight: bold; 59 | margin-top: 10px; 60 | font-size: 14px; 61 | } 62 | 63 | button { 64 | font-size: 16px; 65 | user-select: none; 66 | height: 30px; 67 | 68 | display: flex; 69 | justify-content: center; 70 | align-items: center; 71 | 72 | padding: 10px; 73 | box-sizing: border-box; 74 | border: 2px solid $statusPanelBorder; 75 | border-radius: 10px; 76 | cursor: pointer; 77 | 78 | background: $statusPanelButton; 79 | color: $statusPanelButtonText; 80 | 81 | &:hover { 82 | background: $statusPanelButtonHovered; 83 | } 84 | margin-bottom: 20px; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/examples/FormikForm/FormikForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as Yup from 'yup'; 3 | import { Formik, Field, ErrorMessage } from 'formik'; 4 | import { withFormikDevtools } from 'formik-devtools-extension'; 5 | 6 | import styles from '../ExtensionLiveExample/style.module.scss'; 7 | 8 | const options = [ 9 | { id: 'NY', label: 'New York' }, 10 | { id: 'SF', label: 'San Francisco' }, 11 | { id: 'BA', label: 'Baltimore' }, 12 | { id: 'OTHER', label: 'another...' }, 13 | ]; 14 | 15 | const validationSchema = Yup.object().shape({ 16 | terms: Yup.boolean(), 17 | jobType: Yup.array(), 18 | newsletter: Yup.boolean(), 19 | location: Yup.array().min(1, 'At least 1 option'), 20 | password: Yup.string().min(2, 'Too Short!').max(8, 'Too Long!').required('Required'), 21 | email: Yup.string().email('Invalid email').required('Required'), 22 | }); 23 | 24 | export const FormikForm: React.FunctionComponent = () => { 25 | return ( 26 | { 41 | setTimeout(() => { 42 | alert(JSON.stringify(values, null, 2)); 43 | setSubmitting(false); 44 | }, 400); 45 | }} 46 | > 47 | {(formikProps) => { 48 | withFormikDevtools(formikProps); 49 | 50 | const { handleSubmit, handleChange, handleBlur, values, isSubmitting } = formikProps; 51 | 52 | return ( 53 |
54 |
55 |
Please enter your email
56 | 63 |
{msg}
} /> 64 |
65 | 66 |
67 |
Please enter your password
68 | 69 | 76 |
{msg}
} /> 77 |
78 | 79 |
Who are you? (check all that apply)
80 | 81 |
82 | 86 |
87 | 88 |
89 | 93 |
94 | 95 |
96 | 100 |
101 | 102 |
103 | 114 |
115 | 116 |
117 |
118 | Where do you work? (multiple with ctrl) 119 |
120 | 121 | {options.map(({ id, label }) => ( 122 | 125 | ))} 126 | 127 |
{msg}
} /> 128 |
129 | 130 |
131 | 134 |
135 | 136 | {!!values.terms ? ( 137 |
138 | 142 |
143 | ) : null} 144 | 145 |
146 |
Please enter your facebook url
147 | 148 |
149 |
150 |
Please enter your twitter url
151 | 152 |
153 | 154 | 157 |
158 | ); 159 | }} 160 |
161 | ); 162 | }; 163 | 164 | export const FormikFormStringScope = { 165 | Formik, 166 | withFormikDevtools, 167 | validationSchema, 168 | styles, 169 | ErrorMessage, 170 | Field, 171 | options, 172 | }; 173 | 174 | export const FormikFormString = ` 175 | /* 176 | import { withFormikDevtools } from 'formik-devtools-extension'; 177 | ... 178 | */ 179 | 180 | { 195 | setTimeout(() => { 196 | alert(JSON.stringify(values, null, 2)); 197 | setSubmitting(false); 198 | }, 400); 199 | }} 200 | > 201 | {(formikProps) => { 202 | withFormikDevtools(formikProps); // use this callback to pass props to extension 203 | 204 | const { 205 | handleSubmit, 206 | handleChange, 207 | handleBlur, 208 | values, 209 | isSubmitting, 210 | } = formikProps; 211 | 212 | return ( 213 |
214 |
215 |
Please enter your email
216 | 223 |
{msg}
} 226 | /> 227 |
228 | 229 |
230 |
Please enter your password
231 | 232 | 239 |
{msg}
} 242 | /> 243 |
244 | 245 |
246 | Who are you? (check all that apply) 247 |
248 | 249 |
250 | 254 |
255 | 256 |
257 | 261 |
262 | 263 |
264 | 268 |
269 | 270 |
271 | 282 |
283 | 284 |
285 |
286 | Where do you work? (multiple with ctrl) 287 |
288 | 294 | {options.map(({ id, label }) => ( 295 | 298 | ))} 299 | 300 |
{msg}
} 303 | /> 304 |
305 | 306 |
307 | 311 |
312 | 313 | {!!values.terms ? ( 314 |
315 | 319 |
320 | ) : null} 321 | 322 |
323 |
Please enter your facebook url
324 | 325 |
326 |
327 |
Please enter your twitter url
328 | 329 |
330 | 331 | 334 |
335 | ); 336 | }} 337 |
; 338 | 339 | `; 340 | -------------------------------------------------------------------------------- /src/examples/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ExtensionLiveExample } from './ExtensionLiveExample/ExtensionLiveExample'; 3 | 4 | interface ExtendedWindow extends Window { 5 | FORMIK_DEVTOOLS?: (formikProps: any) => void; 6 | } 7 | const extendedWindow = window as ExtendedWindow; 8 | extendedWindow.FORMIK_DEVTOOLS = (formikProps: any) => { 9 | document.dispatchEvent( 10 | new CustomEvent('FORMIK_EXAMPLE_DEVTOOLS_EVENT', { 11 | detail: JSON.stringify(formikProps), 12 | }), 13 | ); 14 | }; 15 | 16 | const Examples = () => { 17 | return ; 18 | }; 19 | 20 | export default Examples; 21 | -------------------------------------------------------------------------------- /src/helpers/renderValue.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const OBJECT_MARGIN = 5; 4 | 5 | interface ClassNamesMap { 6 | [key: string]: string; 7 | } 8 | 9 | const renderString = (value: string, classname: string, margin = 0, key = 0) => { 10 | return ( 11 |
12 | {`'${value}'`} 13 |
14 | ); 15 | }; 16 | 17 | const renderNumber = (value: number, classname: string, margin = 0, key = 0) => { 18 | return ( 19 |
20 | {`${value}`} 21 |
22 | ); 23 | }; 24 | 25 | const renderBoolean = (value: boolean, classname: string, margin = 0, key = 0) => { 26 | return ( 27 |
28 | {`${value}`} 29 |
30 | ); 31 | }; 32 | 33 | const renderUndefined = (classname: string, margin = 0, key = 0) => { 34 | return ( 35 |
36 | undefined 37 |
38 | ); 39 | }; 40 | 41 | const renderNull = (classname: string, margin = 0, key = 0) => { 42 | return ( 43 |
44 | null 45 |
46 | ); 47 | }; 48 | 49 | const renderArray = (valuesArray: any[], classNamesMap: ClassNamesMap, margin: number, key = 0) => { 50 | return ( 51 |
52 |
[
53 |
54 | {valuesArray.map((value, index) => renderValue(value, classNamesMap, margin + OBJECT_MARGIN, index))} 55 |
56 |
]
57 |
58 | ); 59 | }; 60 | 61 | const renderObject = (valuesObject: { [key: string]: any }, classNamesMap: ClassNamesMap, margin: number, key = 0) => { 62 | return ( 63 |
64 |
{'{'}
65 |
66 | {Object.entries(valuesObject).map(([key, valueProp]) => { 67 | return ( 68 |
69 |
{key}:
70 |
{renderValue(valueProp, classNamesMap, OBJECT_MARGIN)}
71 |
72 | ); 73 | })} 74 |
75 |
{'}'}
76 |
77 | ); 78 | }; 79 | 80 | export const renderValue = (value: any, classNamesMap: ClassNamesMap, margin = 0, index = 0) => { 81 | if (typeof value === 'string') { 82 | return renderString(value, classNamesMap.string, margin, index); 83 | } 84 | if (typeof value === 'number') { 85 | return renderNumber(value, classNamesMap.number, margin, index); 86 | } 87 | if (typeof value === 'boolean') { 88 | return renderBoolean(value, classNamesMap.boolean, margin, index); 89 | } 90 | if (value === null) { 91 | return renderNull(classNamesMap.null, margin, index); 92 | } 93 | if (value === undefined) { 94 | return renderUndefined(classNamesMap.undefined, margin, index); 95 | } 96 | if (value.length !== undefined) { 97 | return renderArray(value, classNamesMap, margin, index); 98 | } 99 | if (typeof value === 'object') { 100 | return renderObject(value, classNamesMap, margin, index); 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /src/helpers/utils.tsx: -------------------------------------------------------------------------------- 1 | export const notEmpty = (value: TValue | null | undefined): value is TValue => { 2 | return value !== null && value !== undefined; 3 | }; 4 | -------------------------------------------------------------------------------- /src/helpers/validate.tsx: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | 3 | export const validateValue = async ( 4 | value: any | null | undefined, 5 | validation: yup.ObjectSchema, 6 | ): Promise => { 7 | if (value) { 8 | const validatedValue = await validation 9 | .validate(value) 10 | .then((value) => { 11 | return value; 12 | }) 13 | .catch((value) => { 14 | console.error(value.errors); 15 | console.error(value.value); 16 | return undefined; 17 | }); 18 | 19 | return validatedValue as TValue | undefined; 20 | } 21 | 22 | return undefined; 23 | }; 24 | -------------------------------------------------------------------------------- /src/hooks/useMessageLoad.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { addListenerToMessages, addListenerToExampleMessages } from '../browser-api/browser'; 3 | import { validateValue } from '../helpers/validate'; 4 | import { IMessage } from '../interfaces/message'; 5 | import { messageValidation } from '../validation/messageValidation'; 6 | import { mockMessages } from '../__mock/messagesMock'; 7 | 8 | export const useMessageLoad = (isExample?: boolean) => { 9 | const [message, setMessage] = useState(undefined); 10 | 11 | useEffect(() => { 12 | if (isExample) { 13 | addListenerToExampleMessages(onMessage); 14 | } else { 15 | addListenerToMessages(onMessage); 16 | } 17 | // eslint-disable-next-line 18 | }, []); 19 | 20 | const onMessage = async (message: { formikProps: string }) => { 21 | try { 22 | if (message) { 23 | const validatedMessage = await validateValue(message.formikProps, messageValidation); 24 | 25 | if (validatedMessage) { 26 | setMessage(validatedMessage); 27 | } 28 | } 29 | } catch (ex) { 30 | console.log(ex); 31 | console.log(message); 32 | } 33 | }; 34 | 35 | return { message }; 36 | }; 37 | 38 | export const useMessageLoadMOCK = () => { 39 | const [message, setMessage] = useState(undefined); 40 | const DELAY = 100; 41 | 42 | useEffect(() => { 43 | sendMessage(mockMessages, 0); 44 | // eslint-disable-next-line 45 | }, []); 46 | 47 | const sendMessage = async (mockMessagesList: string[], index: number) => { 48 | if (mockMessagesList[index]) { 49 | const validatedMessage = await validateValue(mockMessagesList[index], messageValidation); 50 | 51 | if (validatedMessage) { 52 | setMessage(validatedMessage); 53 | } 54 | setTimeout(() => sendMessage(mockMessagesList, index + 1), DELAY); 55 | } 56 | }; 57 | 58 | return { message }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import { FormikDevtools } from './containers'; 5 | 6 | import Examples from './examples'; 7 | 8 | import './styles/resetStyles.scss'; 9 | 10 | ReactDOM.render( 11 | {process.env.REACT_APP_ENV === 'test' ? : }, 12 | document.getElementById('root'), 13 | ); 14 | -------------------------------------------------------------------------------- /src/interfaces/formikState.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from './values'; 2 | 3 | export interface ChangedState { 4 | changedProperties?: string; 5 | changedValues?: string; 6 | changedErrors?: string; 7 | changedTouches?: string; 8 | composedChangedString?: string; 9 | } 10 | 11 | export interface IFormikDetailedState { 12 | state: IFormikState; 13 | changed: ChangedState; 14 | } 15 | 16 | export interface IFormikState { 17 | dirty: boolean; 18 | values: IFormikValues; 19 | } 20 | 21 | export interface InitialProperties { 22 | name: string; 23 | initialValues: { 24 | [key: string]: IFormikValue; 25 | }; 26 | } 27 | 28 | export interface IFormikValues { 29 | [key: string]: IFormikValue; 30 | } 31 | 32 | export interface IFormikValue { 33 | value: any; 34 | collapsedValue: string | number | boolean | undefined | null; 35 | type: ValueType; 36 | error: string | undefined; 37 | touched: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /src/interfaces/message.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from './values'; 2 | 3 | export interface IMessage { 4 | values: { 5 | [key: string]: ValueType; 6 | }; 7 | initialValues: { 8 | [key: string]: ValueType; 9 | }; 10 | initialErrors: { 11 | [key: string]: ValueType | ValueType[]; 12 | }; 13 | initialTouched: { 14 | [key: string]: ValueType | ValueType[]; 15 | }; 16 | errors: { 17 | [key: string]: ValueType | ValueType[]; 18 | }; 19 | touched: { 20 | [key: string]: ValueType | ValueType[]; 21 | }; 22 | dirty: boolean; 23 | __init: boolean; 24 | __formName?: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/interfaces/values.ts: -------------------------------------------------------------------------------- 1 | export type ValueType = 'string' | 'number' | 'boolean' | 'object' | 'undefined' | 'null'; 2 | -------------------------------------------------------------------------------- /src/parsers/changesParsers.ts: -------------------------------------------------------------------------------- 1 | import { notEmpty } from '../helpers/utils'; 2 | import { ChangedState, IFormikState, IFormikValues, InitialProperties } from '../interfaces/formikState'; 3 | 4 | export const getChangedProps = (prevState: IFormikState, currentState: IFormikState): ChangedState | null => { 5 | const composedChangesSet = new Set(); 6 | 7 | const changedProperties = prevState.dirty !== currentState.dirty ? 'dirty' : ''; 8 | changedProperties && composedChangesSet.add(changedProperties); 9 | 10 | const changedValuesSet = new Set(); 11 | [ 12 | ...getChangedValues(prevState.values, currentState.values), 13 | ...getChangedValues(currentState.values, prevState.values), 14 | ].forEach((changedValue) => { 15 | changedValuesSet.add(changedValue); 16 | composedChangesSet.add(changedValue); 17 | }); 18 | const changedValues = [...changedValuesSet].join(', '); 19 | 20 | const changedErrorsSet = new Set(); 21 | [ 22 | ...getChangedErrors(prevState.values, currentState.values), 23 | ...getChangedErrors(currentState.values, prevState.values), 24 | ].forEach((changedValue) => { 25 | changedErrorsSet.add(changedValue); 26 | composedChangesSet.add(changedValue); 27 | }); 28 | const changedErrors = [...changedErrorsSet].join(', '); 29 | 30 | const changedTouchedSet = new Set(); 31 | [ 32 | ...getChangedTouched(prevState.values, currentState.values), 33 | ...getChangedTouched(currentState.values, prevState.values), 34 | ].forEach((changedValue) => { 35 | changedTouchedSet.add(changedValue); 36 | composedChangesSet.add(changedValue); 37 | }); 38 | const changedTouches = [...changedTouchedSet].join(', '); 39 | const composedChangedString = [...composedChangesSet].join(', '); 40 | 41 | const isSomethingHasChanged = Boolean(changedErrors || changedProperties || changedTouches || changedValues); 42 | 43 | return isSomethingHasChanged 44 | ? { 45 | changedErrors, 46 | changedProperties, 47 | changedTouches, 48 | changedValues, 49 | composedChangedString, 50 | } 51 | : null; 52 | }; 53 | 54 | export const getChangedInitialProps = ( 55 | prevState: InitialProperties, 56 | currentState: IFormikState, 57 | ): ChangedState | null => { 58 | const changedValuesSet = new Set(); 59 | [ 60 | ...getChangedValues(prevState.initialValues, currentState.values), 61 | ...getChangedValues(currentState.values, prevState.initialValues), 62 | ].forEach((changedValue) => changedValuesSet.add(changedValue)); 63 | 64 | const changedValues = [...changedValuesSet].join(', '); 65 | 66 | return changedValues ? { changedValues } : null; 67 | }; 68 | 69 | const getChangedValues = (prevFormikValues: IFormikValues, currentFormikValues: IFormikValues): string[] => { 70 | return Object.entries(prevFormikValues) 71 | .map(([fieldName, formikValue]) => { 72 | if (formikValue.value === undefined && !currentFormikValues[fieldName]?.type) { 73 | return fieldName; 74 | } 75 | if (isValuesChangedCheck(formikValue.value, currentFormikValues[fieldName]?.value)) { 76 | return fieldName; 77 | } 78 | return undefined; 79 | }) 80 | .filter(notEmpty); 81 | }; 82 | 83 | const getChangedErrors = (prevFormikValues: IFormikValues, currentFormikValues: IFormikValues): string[] => { 84 | return Object.entries(prevFormikValues) 85 | .map(([fieldName, formikValue]) => { 86 | if (isValuesChangedCheck(formikValue.error, currentFormikValues[fieldName]?.error)) { 87 | return fieldName; 88 | } 89 | return undefined; 90 | }) 91 | .filter(notEmpty); 92 | }; 93 | 94 | const getChangedTouched = (prevFormikValues: IFormikValues, currentFormikValues: IFormikValues): string[] => { 95 | return Object.entries(prevFormikValues) 96 | .map(([fieldName, formikValue]) => { 97 | if (isValuesChangedCheck(formikValue.touched, currentFormikValues[fieldName]?.touched)) { 98 | return fieldName; 99 | } 100 | return undefined; 101 | }) 102 | .filter(notEmpty); 103 | }; 104 | 105 | const isValuesChangedCheck = (prevValue: any, currentValue: any): boolean => { 106 | if (typeof prevValue === 'object') { 107 | if (prevValue === null) { 108 | return prevValue !== currentValue; 109 | } 110 | if (prevValue.length !== undefined) { 111 | return ( 112 | !currentValue || 113 | (prevValue as any[]) 114 | .map((value, index) => isValuesChangedCheck(value, currentValue[index])) 115 | .some(Boolean) 116 | ); 117 | } else { 118 | return ( 119 | !currentValue || 120 | Object.entries(prevValue as { [key: string]: any }) 121 | .map(([key, value]) => { 122 | return isValuesChangedCheck(value, currentValue[key]); 123 | }) 124 | .some(Boolean) 125 | ); 126 | } 127 | } 128 | return prevValue !== currentValue; 129 | }; 130 | -------------------------------------------------------------------------------- /src/parsers/parseHelpers.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from '../interfaces/values'; 2 | 3 | export const isValueTouchedCheck = ( 4 | key: string, 5 | touchedValues: { [key: string]: ValueType | ValueType[] } = {}, 6 | ): boolean => { 7 | return !!touchedValues[key]; 8 | }; 9 | 10 | export const getError = ( 11 | key: string, 12 | errors: { 13 | [key: string]: ValueType | ValueType[]; 14 | } = {}, 15 | ): string | undefined => { 16 | const error = errors[key]; 17 | switch (typeof error) { 18 | case 'object': 19 | return error === null ? undefined : JSON.stringify(error); 20 | case 'undefined': 21 | return undefined; 22 | default: 23 | return String(error); 24 | } 25 | }; 26 | 27 | export const getCollapsedValue = (value: any): string | number | boolean | undefined | null => { 28 | switch (typeof value) { 29 | case 'object': 30 | return value === null ? null : JSON.stringify(value); 31 | case 'string': 32 | return `'${value}'`; 33 | case 'number': 34 | return value; 35 | case 'boolean': 36 | return value.toString(); 37 | case 'undefined': 38 | return undefined; 39 | default: 40 | return undefined; 41 | } 42 | }; 43 | 44 | export const getValueType = (value: any): ValueType => { 45 | switch (typeof value) { 46 | case 'object': 47 | return value === null ? 'null' : 'object'; 48 | case 'string': 49 | return 'string'; 50 | case 'number': 51 | return 'number'; 52 | case 'boolean': 53 | return 'boolean'; 54 | case 'undefined': 55 | return 'undefined'; 56 | default: 57 | return 'undefined'; 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /src/parsers/valuesParsers.ts: -------------------------------------------------------------------------------- 1 | import { IFormikState, InitialProperties } from '../interfaces/formikState'; 2 | import { IMessage } from '../interfaces/message'; 3 | import { getCollapsedValue, getError, getValueType, isValueTouchedCheck } from './parseHelpers'; 4 | 5 | export const parseValues = ( 6 | formikState: IMessage, 7 | ): { state: IFormikState | null; formProps: InitialProperties | null } => { 8 | if (formikState) { 9 | const formProps = parseInitialValues(formikState); 10 | const formikValuesShape: IFormikState = { values: {}, dirty: !!formikState.dirty }; 11 | 12 | Object.entries(formikState.values).forEach(([valueName, value]) => { 13 | const type = getValueType(value); 14 | const error = getError(valueName, formikState.errors); 15 | const touched = isValueTouchedCheck(valueName, formikState.touched); 16 | const collapsedValue = getCollapsedValue(value); 17 | 18 | formikValuesShape.values[valueName] = { value, collapsedValue, error, touched, type }; 19 | }); 20 | 21 | const touchedAndErrorsValuesSet = new Set(); 22 | [...Object.keys(formikState.touched || {}), ...Object.keys(formikState.errors)].forEach((key) => 23 | touchedAndErrorsValuesSet.add(key), 24 | ); 25 | [...touchedAndErrorsValuesSet].forEach((key) => { 26 | if (!formikValuesShape.values[key]) { 27 | const error = getError(key, formikState.errors); 28 | const touched = isValueTouchedCheck(key, formikState.touched); 29 | 30 | formikValuesShape.values[key] = { 31 | value: undefined, 32 | collapsedValue: 'undefined', 33 | error, 34 | touched, 35 | type: 'undefined', 36 | }; 37 | } 38 | }); 39 | 40 | return { state: formikValuesShape, formProps }; 41 | } 42 | 43 | return { state: null, formProps: null }; 44 | }; 45 | 46 | const parseInitialValues = (formikState: IMessage): InitialProperties | null => { 47 | if (!formikState.__init) { 48 | return null; 49 | } 50 | 51 | const formProps: InitialProperties = { 52 | name: 'Form', 53 | initialValues: {}, 54 | }; 55 | 56 | Object.entries(formikState.initialValues).forEach(([valueName, value]) => { 57 | const type = getValueType(value); 58 | const error = getError(valueName, formikState.initialErrors); 59 | const touched = isValueTouchedCheck(valueName, formikState.initialTouched); 60 | const collapsedValue = getCollapsedValue(value); 61 | 62 | formProps.initialValues[valueName] = { value, collapsedValue, error, touched, type }; 63 | }); 64 | 65 | return formProps; 66 | }; 67 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/styles/colorVariables.scss: -------------------------------------------------------------------------------- 1 | $backgroundColor: #282a36; 2 | $sidePanel: #282a36; 3 | 4 | $statusPanel: #282a36; 5 | $statusPanelText: #f8f8f2; 6 | 7 | $statusPanelButton: #353746; 8 | $statusPanelButtonText: #f8f8f2; 9 | $statusPanelButtonHovered: #424450; 10 | $statusPanelBorder: #424450; 11 | 12 | $stateItem: #282a36; 13 | $stateItemBorder: #424450; 14 | $selectedStateItem: #6272a4; 15 | $hoveredStateItem: #6272a4; 16 | $stateItemText: #f8f8f2; 17 | $stateItemStepText: #e66fb4; 18 | 19 | $fieldName: #e66fb4; 20 | $defaultValueColor: #f8f8f2; 21 | $stringValueColor: #e0a464; 22 | $numberValueColor: #00afff; 23 | $booleanValueColor: #bd93f9; 24 | $nullValueColor: #50fa7b; 25 | $objectValueColor: #f1fa8c; 26 | $symbolColor: #f8f8f2; 27 | 28 | $errorColor: #ff5555; 29 | $touchedColor: #8be9fd; 30 | $collapsedColor: #f8f8f2; 31 | -------------------------------------------------------------------------------- /src/styles/resetStyles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inconsolata&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | 8 | ::-webkit-scrollbar { 9 | width: 7px; 10 | } 11 | 12 | ::-webkit-scrollbar-track { 13 | background: #282a36; 14 | } 15 | 16 | ::-webkit-scrollbar-thumb { 17 | background: #424450; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb:hover { 21 | background: #353746; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/validation/messageValidation.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | 3 | import { IMessage } from '../interfaces/message'; 4 | 5 | export const messageValidation = yup.object().shape({ 6 | values: yup.object().required(), 7 | initialValues: yup.object().required(), 8 | errors: yup.object().required(), 9 | touched: yup.object().required(), 10 | initialErrors: yup.object(), 11 | initialTouched: yup.object(), 12 | 13 | dirty: yup.boolean().required(), 14 | __init: yup.boolean(), 15 | __formName: yup.string(), 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "experimentalDecorators": true, 6 | "downlevelIteration": true, 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react", 19 | "typeRoots": ["node_modules/@types"] 20 | }, 21 | "include": ["src"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------