├── .editorconfig ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .yarnrc.yml ├── CHANGES.md ├── CREDITS.md ├── LICENSE ├── README.md ├── demo ├── component.tsx ├── div.html ├── index.tsx └── tsconfig.json ├── eslint.config.mjs ├── package.json ├── src ├── accessor-base.ts ├── accessor.ts ├── backend.ts ├── changeTracker.ts ├── controlled.ts ├── converter.ts ├── converters.ts ├── decimal-type.ts ├── decimalParser.ts ├── dynamic-converter.ts ├── field-accessor.ts ├── form-accessor-base.ts ├── form.ts ├── group-accessor.ts ├── index.ts ├── interfaces.ts ├── references.ts ├── repeating-form-accessor.ts ├── repeating-form-indexed-accessor.ts ├── source.ts ├── state.ts ├── sub-form-accessor.ts ├── utils.ts ├── validate-options.ts ├── validation-props.ts └── validationMessages.ts ├── test ├── accessor.test.ts ├── backend.test.ts ├── changeTracker.test.ts ├── changehook.test.ts ├── context.test.ts ├── controlled.test.ts ├── converter.test.ts ├── converters.test.ts ├── decimal-type.test.ts ├── decimalParser.test.ts ├── derived.test.ts ├── dynamic.test.ts ├── fieldref.test.ts ├── form.test.ts ├── groups.test.ts ├── ignore.test.ts ├── multiple-conversion-errors.test.ts ├── navigate.test.ts ├── save.test.ts ├── source.test.ts ├── subform.test.ts ├── utils.ts ├── viewhook.test.ts └── warning.test.ts ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{js,jsx,ts,tsx}] 12 | indent_size = 2 13 | 14 | [*.yml] 15 | indent_size = 2 16 | 17 | [**.min.js] 18 | indent_style = ignore 19 | insert_final_newline = ignore 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | lib 4 | dist 5 | test-lib/**/*.js 6 | coverage 7 | .nyc_output 8 | .idea 9 | package-lock.json 10 | .vscode 11 | .DS_STORE 12 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | 3 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableImmutableInstalls: false 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | mstform was developed by ISProjects. 2 | 3 | Contributors: 4 | 5 | - Martijn Faassen 6 | - Gerjon Mensinga 7 | - Subhi Dweik 8 | - Koen de Leijer 9 | - Randy Wanga 10 | - Rick Lucassen 11 | - Mark den Toom 12 | - Ruben van de Kerkhof 13 | - Guus Biemans 14 | - Robin van Grinsven 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ISProjects 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/component.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, ReactNode } from "react"; 2 | import { observer } from "mobx-react"; 3 | import { Instance, types, getSnapshot } from "mobx-state-tree"; 4 | import { 5 | Field, 6 | Form, 7 | converters, 8 | FieldAccessor, 9 | RepeatingForm, 10 | } from "../src/index"; 11 | 12 | // we have a MST model with a string field foo, 13 | // and a few number fields 14 | const N = types 15 | .model("N", { 16 | foo: types.string, 17 | a: types.number, 18 | b: types.number, 19 | derived: types.number, 20 | textarea: types.array(types.string), 21 | }) 22 | .views((self) => ({ 23 | get calculated() { 24 | return self.a + self.b; 25 | }, 26 | })); 27 | 28 | const newN = (count: number) => { 29 | return N.create({ 30 | foo: `Some string ${count}`, 31 | a: 1, 32 | b: 2, 33 | derived: 3, 34 | textarea: ["value"], 35 | }); 36 | }; 37 | 38 | const M = types 39 | .model("M", { 40 | foo: types.string, 41 | a: types.number, 42 | b: types.number, 43 | derived: types.number, 44 | textarea: types.array(types.string), 45 | repeated: types.array(N), 46 | }) 47 | .views((self) => ({ 48 | get calculated() { 49 | return self.a + self.b; 50 | }, 51 | })); 52 | 53 | // we create an instance of the model 54 | const o = M.create({ 55 | foo: "FOO", 56 | a: 1, 57 | b: 3, 58 | derived: 4, 59 | textarea: [], 60 | repeated: [newN(1), newN(2), newN(3)], 61 | }); 62 | 63 | // we expose this field in our form 64 | const form = new Form(M, { 65 | foo: new Field(converters.string, { 66 | validators: [(value) => (value !== "correct" ? "Wrong" : false)], 67 | }), 68 | a: new Field(converters.number), 69 | b: new Field(converters.number), 70 | derived: new Field(converters.number, { 71 | derived: (node) => node.calculated, 72 | }), 73 | textarea: new Field(converters.textStringArray), 74 | repeated: new RepeatingForm({ 75 | foo: new Field(converters.string, { 76 | validators: [(value) => (value !== "correct" ? "Wrong" : false)], 77 | }), 78 | a: new Field(converters.number), 79 | b: new Field(converters.number), 80 | derived: new Field(converters.number, { 81 | derived: (node) => node.calculated, 82 | }), 83 | textarea: new Field(converters.textStringArray), 84 | }), 85 | }); 86 | 87 | const InlineError: React.FunctionComponent<{ 88 | field?: FieldAccessor; 89 | children: ReactNode; 90 | }> = observer((props) => { 91 | const { field, children } = props; 92 | return ( 93 |
94 | {children} 95 | {field && {field.error}} 96 |
97 | ); 98 | }); 99 | 100 | const MyInput: React.FunctionComponent<{ 101 | type: string; 102 | field: FieldAccessor; 103 | }> = observer((props) => { 104 | const { type, field } = props; 105 | return ; 106 | }); 107 | 108 | const MyTextArea: React.FunctionComponent<{ 109 | field: FieldAccessor; 110 | }> = observer((props) =>