├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── common └── config │ └── rush │ └── npm-shrinkwrap.json ├── docs └── 2017-04 │ ├── 2017-04-08.md │ ├── 2017-04-14.md │ ├── 2017-04-26.md │ └── 2017-04-27.md ├── package.json ├── packages ├── react-forms-dom │ ├── .gitignore │ ├── README.md │ ├── __tests__ │ │ ├── components │ │ │ ├── clear.test.tsx │ │ │ ├── form.test.tsx │ │ │ ├── reset.test.tsx │ │ │ └── text.test.tsx │ │ └── tsconfig.json │ ├── package.json │ ├── src │ │ ├── abstractions.ts │ │ ├── abstractions │ │ │ ├── base-dom-field.tsx │ │ │ ├── base-errors-container.ts │ │ │ ├── base-form-button.tsx │ │ │ └── index.ts │ │ ├── components │ │ │ ├── checkbox.tsx │ │ │ ├── clear.tsx │ │ │ ├── email.tsx │ │ │ ├── errors-container.ts │ │ │ ├── fields-array.tsx │ │ │ ├── fields-group.tsx │ │ │ ├── form.tsx │ │ │ ├── hidden.tsx │ │ │ ├── number.tsx │ │ │ ├── password.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── radio.tsx │ │ │ ├── reset.tsx │ │ │ ├── search.tsx │ │ │ ├── select.tsx │ │ │ ├── submit.tsx │ │ │ ├── text.tsx │ │ │ └── textarea.tsx │ │ ├── contracts.ts │ │ ├── contracts │ │ │ ├── field.ts │ │ │ └── form.ts │ │ └── index.ts │ ├── tools │ │ ├── gulpfile.ts │ │ ├── tsconfig.gulp.json │ │ └── webpack.config.ts │ ├── tsconfig.json │ └── tslint.json ├── react-forms-validation │ ├── .gitignore │ ├── README.md │ ├── __tests__ │ │ ├── subscribers │ │ │ ├── form-store-subscriber.test.tsx │ │ │ └── form-stores-handler-subscriber.test.ts │ │ ├── tsconfig.json │ │ └── validation.test.tsx │ ├── package.json │ ├── src │ │ ├── abstractions.ts │ │ ├── abstractions │ │ │ ├── base-field-validator.ts │ │ │ ├── base-form-validator.ts │ │ │ ├── base-validator.ts │ │ │ └── index.ts │ │ ├── contracts.ts │ │ ├── index.ts │ │ ├── subscribers │ │ │ ├── form-store-subscriber.ts │ │ │ ├── form-stores-handler-subscriber.ts │ │ │ ├── index.ts │ │ │ └── subscriber.ts │ │ ├── utils.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── validation.ts │ │ └── validators │ │ │ ├── alpha.ts │ │ │ ├── alphanumeric.ts │ │ │ ├── ascii.ts │ │ │ ├── base64.ts │ │ │ ├── boolean.ts │ │ │ ├── byte-length.ts │ │ │ ├── contains.ts │ │ │ ├── credit-card.ts │ │ │ ├── currency.ts │ │ │ ├── equals.ts │ │ │ ├── index.ts │ │ │ └── required.ts │ ├── tools │ │ ├── gulpfile.ts │ │ ├── tsconfig.gulp.json │ │ └── webpack.config.ts │ ├── tsconfig.json │ └── tslint.json └── react-forms │ ├── .gitignore │ ├── README.md │ ├── __tests__ │ ├── abstractions │ │ ├── base-field.test.tsx │ │ ├── base-fields-group.test.tsx │ │ └── base-form.test.tsx │ ├── stores │ │ ├── form-store.test.ts │ │ └── form-stores-handler.test.ts │ ├── test-components │ │ ├── test-field.tsx │ │ ├── test-fields-group.tsx │ │ └── test-form.tsx │ └── tsconfig.json │ ├── package.json │ ├── src │ ├── abstractions │ │ ├── base-container.ts │ │ ├── base-field.ts │ │ ├── base-fields-array.ts │ │ ├── base-fields-group.ts │ │ ├── base-form.ts │ │ ├── core-field.ts │ │ └── index.ts │ ├── actions.ts │ ├── actions │ │ ├── form-store.ts │ │ ├── form-stores-handler.ts │ │ └── index.ts │ ├── contracts.ts │ ├── contracts │ │ ├── error.ts │ │ ├── field.ts │ │ ├── fields-array.ts │ │ ├── fields-group.ts │ │ ├── form-store.ts │ │ ├── form.ts │ │ ├── index.ts │ │ ├── validation.ts │ │ └── value.ts │ ├── index.ts │ ├── modifiers.ts │ ├── modifiers │ │ ├── base-modifier.ts │ │ ├── index.ts │ │ └── string-to-decimal.ts │ ├── normalizers.ts │ ├── normalizers │ │ ├── alphanumeric.ts │ │ ├── base-normalizer.ts │ │ ├── index.ts │ │ ├── lower-case.ts │ │ └── upper-case.ts │ ├── stores.ts │ ├── stores │ │ ├── form-store-helpers.ts │ │ ├── form-store.ts │ │ ├── form-stores-handler.ts │ │ └── index.ts │ ├── utils.ts │ └── utils │ │ ├── form-error-helpers.ts │ │ ├── index.ts │ │ └── value-helpers.ts │ ├── tools │ ├── gulpfile.ts │ ├── tsconfig.gulp.json │ └── webpack.config.ts │ ├── tsconfig.json │ └── tslint.json ├── rush.json ├── tools ├── mvdir │ ├── .gitignore │ ├── dist │ │ ├── arguments.d.ts │ │ ├── arguments.js │ │ ├── cli.d.ts │ │ ├── cli.js │ │ ├── contracts.d.ts │ │ ├── contracts.js │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ ├── src │ │ ├── arguments.ts │ │ ├── cli.ts │ │ ├── contracts.ts │ │ └── index.ts │ ├── tsconfig.json │ └── tslint.json └── rush-tools.ts └── tslint.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | notifications: 3 | email: 4 | on_success: never 5 | on_failure: change 6 | node_js: 7 | - stable 8 | script: 9 | - npm run generate 10 | - npm run tools-build 11 | - npm run source-build 12 | - npm test 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |

@simplr/react-forms

7 | Declarative forms for React. Includes declarative modifiers, normalizers and validators. 8 | 9 | ### Packages 10 | 11 | | Package | Version | Docs | Description | 12 | |---------|---------|------|-------------| 13 | | [`@simplr/react-forms`](/packages/react-forms) | [![npm](https://img.shields.io/npm/v/@simplr/react-forms.svg?style=flat-square)](https://www.npmjs.com/package/@simplr/react-forms) | TBA | The core | 14 | | [`@simplr/react-forms-dom`](/packages/react-forms-dom) | [![npm](https://img.shields.io/npm/v/@simplr/react-forms-dom.svg?style=flat-square)](https://www.npmjs.com/package/@simplr/react-forms-dom) | TBA | DOM components | 15 | | [`@simplr/react-forms-validation`](/packages/react-forms-validation) | [![npm](https://img.shields.io/npm/v/@simplr/react-forms-validation.svg?style=flat-square)](https://www.npmjs.com/package/@simplr/react-forms-dom) | TBA | Validation | 16 | 17 | 18 | ### Development 19 | 20 | Latest published version should be in the [master](https://github.com/SimplrJS/react-forms/tree/master) branch. 21 | Development is happening in the [dev](https://github.com/SimplrJS/react-forms/tree/dev) and feature branches. 22 | 23 |

Core Team

24 | 25 | 26 | 27 | 28 | 34 | 35 | 42 | 49 | 56 | 57 | 58 |
29 | 30 | 31 |
32 | Dovydas Navickas 33 |
36 | 37 | 38 |
39 | Martynas Žilinskas 40 |
41 |
43 | 44 | 45 |
46 | Giedrius Grabauskas 47 |
48 |
50 | 51 | 52 |
53 | Deividas Bakanas 54 |
55 |
59 | 60 | ### Notes 61 | 62 | Notes can be found in this repo and in Medium: 63 | 64 | * [Part 1: Why are we doing this?](https://medium.com/@DovydasNavickas/part-1-simplr-forms-declarative-forms-for-react-why-are-we-doing-this-293b9a9c45bd) 65 | * [Part 2: Core, validation, testing.](https://medium.com/@DovydasNavickas/part-2-simplr-forms-declarative-forms-for-react-core-validation-testing-fd9494304305) 66 | * [Part 3: First e2e flow, FormStore.ToObject()](https://medium.com/@DovydasNavickas/part-3-simplr-forms-declarative-forms-for-react-first-e2e-flow-formstore-toobject-ed81d930e14c) 67 | * [Part 4: Normalizers and Modifiers](https://medium.com/@DovydasNavickas/part-4-simplr-forms-declarative-forms-for-react-normalizers-and-modifiers-6cefbd0269c4) 68 | -------------------------------------------------------------------------------- /docs/2017-04/2017-04-14.md: -------------------------------------------------------------------------------- 1 | ## April 14 ([discuss](https://github.com/SimplrJS/simplr-forms/pull/4)) 2 | 3 | ### Attendees 4 | 5 | * [Dovydas](https://twitter.com/dovydasnav) (QuatroDev) 6 | * [Martynas](https://twitter.com/MartinZilinskas) (QuatroDev) 7 | * [Giedrius](https://twitter.com/giedrucis) (QuatroDev) 8 | * [Aurimas](https://twitter.com/waikys) (QuatroDev) 9 | 10 | ### simplr-forms-core is done for pre-alpha 11 | 12 | During the week, we worked on `simplr-forms-core` to have a solid foundation for `simplr-forms-dom` and `simplr-forms-native`. 13 | 14 | Most of the coding was done by Dovydas and Martynas, with valuable and fast reviews by Giedrius. 15 | Feels good to work with such an amazing team. 16 | 17 | Also, Aurimas joined our efforts by preparing react-native app for development of `simplr-forms-native`. Thanks, Aurimas! 18 | 19 | We will start working on both `dom` and `native` packages soon. 20 | 21 | ### So... What happened during the week? 22 | 23 | We can confidently say that we have a solid first appearances of `core`, which consists of: 24 | * [`form-stores-handler`][1] - a general manager for all forms data stores 25 | * [`form-store`][2] - a data store for one particular form 26 | * [`base-form`][3] - a base component for a form to register to store and provide context for children, register with `form-stores-handler`, etc. 27 | * [`base-field`][4] - a base component for field that is aware of the parent form, registers and registers to and from form store, incorporates React lifecycle events, etc. 28 | 29 | These 4 pretty much cover the base of the whole library. 30 | 31 | Of course, there is more to it. 32 | 33 | [`form-stores-handler`][1] and [`base-form`][3] extend [`ActionEmitter`][5] for you to be able to `addListener` 34 | for actions and know when the store is created, updates it's state or even more specific [actions][6], 35 | e.g. [value][7] or [props][8] are updated. 36 | 37 | This enables another amazing thing: now we can externalize validation! 38 | 39 | > Shoutout to Giedrius for publishing [`ActionEmitter`][5] right on time. 40 | > And for us all to debugging it in a single day :smile: 41 | 42 | ### Externalized validation 43 | 44 | We understand that most developers use whatever they get out-of-the-box, 45 | especially if what they get is an easy-to-consume and also an optimal solution. 46 | 47 | But sometimes usage of project-specific validation library is a must. 48 | 49 | For example, there are quite a few industries with very specific data structures and their validations. 50 | Medicine, aviation, insurance, finance... And that's just a few on the tip of the tongue. 51 | 52 | Therefore, externalizing validation is an amazing step forward to using `simplr-forms` 53 | no matter what data validation requirements you have. 54 | 55 | ### Testing 56 | 57 | A library without tests these days is a scary and unstable bomb with a timer that shows gibberish on it's timer, 58 | i.e. you don't know when it is gonna hit you. 59 | 60 | Therefore, we already have 44 tests in 4 test suites. 61 | 62 | And we will write as much more as we need to ensure everything will progress forward 63 | and this library becomes de-facto a standard, a no brainer when anyone thinks about React and forms. 64 | 65 | ### What's next? 66 | 67 | Releasing pre-alpha of `simplr-forms-core` and starting development of `simplr-forms-dom` and (maybe) `simplr-forms-native`. 68 | 69 | Also, `simplr-validation` is coming into the light as we evaluate all of the peaces for it. 70 | 71 | And now... It's coding time again! :tada: 72 | 73 | ------------ 74 | 75 | The feedback and questions are more than welcome! 76 | 77 | ------------ 78 | 79 | Please feel free to discuss these notes in the [corresponding pull request](https://github.com/SimplrJS/simplr-forms/pull/4). 80 | 81 | [1]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/stores/form-stores-handler.ts 82 | [2]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/stores/form-store.ts 83 | [3]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/abstractions/base-form.ts 84 | [4]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/abstractions/base-field.ts 85 | [5]: https://github.com/SimplrJS/action-emitter 86 | [6]: https://github.com/SimplrJS/simplr-forms/tree/dev/packages/simplr-forms-core/src/actions 87 | [7]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/actions/form-store-actions.ts#L3-L9 88 | [8]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/actions/form-store-actions.ts#L11-L17 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@simplr/react-forms-repo", 3 | "private": true, 4 | "scripts": { 5 | "generate": "rush generate", 6 | "tools-build": "npm run rush-tools -- run gulp-build -e @simplr/mvdir", 7 | "source-build": "npm run rush-tools -- run build -e @simplr/mvdir", 8 | "test": "npm run rush-tools -- run test -e @simplr/mvdir", 9 | "rush-tools": "ts-node ./tools/rush-tools.ts", 10 | "publish": "npm run rush-tools -- publish --access public" 11 | }, 12 | "devDependencies": { 13 | "@microsoft/rush": "^3.0.9", 14 | "@microsoft/rush-lib": "^3.0.9", 15 | "@types/shelljs": "^0.7.2", 16 | "@types/yargs": "^6.6.0", 17 | "mz": "^2.6.0", 18 | "shelljs": "^0.7.8", 19 | "ts-node": "^3.0.6", 20 | "typescript": "^2.3.4", 21 | "yargs": "^8.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-forms-dom/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | **/*d.ts 3 | -------------------------------------------------------------------------------- /packages/react-forms-dom/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |

7 | 8 | version 9 | 10 | 11 | license 12 | 13 |

14 | 15 |

@simplr/react-forms-dom

16 | 17 | DOM implementation for `@simplr/react-forms`. 18 | -------------------------------------------------------------------------------- /packages/react-forms-dom/__tests__/components/clear.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { mount } from "enzyme"; 3 | import * as Sinon from "sinon"; 4 | 5 | import { FormStore } from "@simplr/react-forms/stores"; 6 | 7 | import { Form } from "../../src/components/form"; 8 | import { Text } from "../../src/components/text"; 9 | import { Clear } from "../../src/components/clear"; 10 | 11 | describe("Clear button", () => { 12 | let sandbox: Sinon.SinonSandbox; 13 | 14 | beforeEach(() => { 15 | sandbox = Sinon.sandbox.create(); 16 | }); 17 | 18 | afterEach(() => { 19 | sandbox.restore(); 20 | }); 21 | 22 | it("clears all fields", () => { 23 | const callback = sandbox.stub(FormStore.prototype, "ClearFields"); 24 | 25 | const wrapper = mount(
26 | 27 | Clear form 28 | ); 29 | 30 | wrapper.find("button").simulate("click"); 31 | 32 | expect(callback.called).toBe(true); 33 | }); 34 | 35 | it("clears fields by fieldsIds", () => { 36 | const callback = sandbox.stub(FormStore.prototype, "ClearFields"); 37 | const fieldsIds: string[] = []; 38 | for (let i = 0; i < 5; i++) { 39 | fieldsIds.push(`text-${i}`); 40 | } 41 | const fields: JSX.Element[] = []; 42 | fieldsIds.forEach(value => { 43 | fields.push(); 44 | }); 45 | 46 | const wrapper = mount(
47 | {fields} 48 | Clear form 49 |
); 50 | 51 | wrapper.find("button").simulate("click"); 52 | 53 | expect(callback.called).toBe(true); 54 | // first click, first argument(fieldIds) 55 | expect(callback.args[0][0]).toBe(fieldsIds); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/react-forms-dom/__tests__/components/form.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { mount } from "enzyme"; 3 | import * as Sinon from "sinon"; 4 | 5 | import { FSHContainer, FormStoresHandler } from "@simplr/react-forms/stores"; 6 | 7 | import { Form } from "../../src/components/form"; 8 | 9 | describe("Form", () => { 10 | beforeEach(() => { 11 | FSHContainer.SetFormStoresHandler(new FormStoresHandler(), true); 12 | }); 13 | 14 | it("calls submit callback when submit button is clicked", () => { 15 | const submitCallback = Sinon.stub(); 16 | 17 | const wrapper = mount(
18 | 19 |
); 20 | 21 | wrapper.find("button").simulate("submit"); 22 | 23 | expect(submitCallback.called).toBe(true); 24 | }); 25 | 26 | it("calls submit callback when submit called from FormStore", () => { 27 | const formId = "form-id"; 28 | const submitCallback = Sinon.stub(); 29 | 30 | mount(
); 31 | 32 | const formStore = FSHContainer.FormStoresHandler.GetStore(formId); 33 | formStore.InitiateFormSubmit(); 34 | 35 | expect(submitCallback.called).toBe(true); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/react-forms-dom/__tests__/components/reset.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { mount } from "enzyme"; 3 | import * as Sinon from "sinon"; 4 | 5 | import { FormStore } from "@simplr/react-forms/stores"; 6 | 7 | import { Form } from "../../src/components/form"; 8 | import { Text } from "../../src/components/text"; 9 | import { Reset } from "../../src/components/reset"; 10 | 11 | describe("Reset button", () => { 12 | let sandbox: Sinon.SinonSandbox; 13 | 14 | beforeEach(() => { 15 | sandbox = Sinon.sandbox.create(); 16 | }); 17 | 18 | afterEach(() => { 19 | sandbox.restore(); 20 | }); 21 | 22 | it("reset all fields", () => { 23 | const callback = sandbox.spy(FormStore.prototype, "ResetFields"); 24 | 25 | const wrapper = mount(
26 | 27 | Clear form 28 | ); 29 | 30 | wrapper.find("button").simulate("click"); 31 | 32 | expect(callback.called).toBe(true); 33 | }); 34 | 35 | it("reset fields by fieldsIds", () => { 36 | const callback = sandbox.spy(FormStore.prototype, "ResetFields"); 37 | const fieldsIds: string[] = []; 38 | for (let i = 0; i < 5; i++) { 39 | fieldsIds.push(`text-${i}`); 40 | } 41 | const fields: JSX.Element[] = []; 42 | fieldsIds.forEach(value => { 43 | fields.push(); 44 | }); 45 | 46 | const wrapper = mount(
47 | {fields} 48 | Clear form 49 |
); 50 | 51 | wrapper.find("button").simulate("click"); 52 | 53 | expect(callback.called).toBe(true); 54 | // first click, first argument(fieldIds) 55 | expect(callback.args[0][0]).toBe(fieldsIds); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/react-forms-dom/__tests__/components/text.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { mount } from "enzyme"; 3 | import * as Sinon from "sinon"; 4 | 5 | import { FSHContainer, FormStoresHandler } from "@simplr/react-forms/stores"; 6 | import { Form } from "../../src/components/form"; 7 | import { Text } from "../../src/components/text"; 8 | 9 | describe("Text field", () => { 10 | let sandbox: Sinon.SinonSandbox; 11 | beforeEach(() => { 12 | FSHContainer.SetFormStoresHandler(new FormStoresHandler(), true); 13 | sandbox = Sinon.sandbox.create(); 14 | }); 15 | 16 | afterEach(() => { 17 | sandbox.restore(); 18 | }); 19 | 20 | it("change value from input", () => { 21 | const formId = "formId"; 22 | const fieldName = "field name"; 23 | const nextValue = "next value"; 24 | const wrapper = mount(
25 | 26 | ); 27 | 28 | const formStore = FSHContainer.FormStoresHandler.GetStore(formId); 29 | 30 | wrapper.find("input").simulate("change", { target: { value: nextValue } }); 31 | 32 | expect(wrapper.find("input").props().value).toBe(nextValue); 33 | expect(formStore.GetField(fieldName).Value).toBe(nextValue); 34 | }); 35 | 36 | it("change value triggers onChange callback", () => { 37 | const fieldName = "field name"; 38 | const nextValue = "next value"; 39 | const callback = Sinon.stub(); 40 | const wrapper = mount(
41 | 42 | ); 43 | 44 | wrapper.find("input").simulate("change", { target: { value: nextValue } }); 45 | 46 | expect(callback.called).toBe(true); 47 | }); 48 | 49 | it("render is defined", () => { 50 | const wrapper = mount(
51 | 52 | ); 53 | 54 | expect(wrapper.find(Text).getDOMNode()).toBeDefined(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/react-forms-dom/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "removeComments": false, 6 | "jsx": "react", 7 | "sourceMap": false, 8 | "skipDefaultLibCheck": true, 9 | "declaration": true, 10 | "declarationDir": "@types", 11 | "pretty": true, 12 | "strict": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "importHelpers": true, 16 | "lib": [ 17 | "dom", 18 | "dom.iterable", 19 | "es6" 20 | ], 21 | "typeRoots": [ 22 | "../node_modules/@types/" 23 | ] 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "dist", 28 | "@types", 29 | "__tests__" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-forms-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@simplr/react-forms-dom", 3 | "version": "4.1.0", 4 | "description": "DOM components for @simplr/react-forms.", 5 | "repository": "SimplrJS/react-forms", 6 | "homepage": "https://github.com/SimplrJS/react-forms", 7 | "main": "index.js", 8 | "types": "index.d.ts", 9 | "author": "simplrjs (https://github.com/simplrjs)", 10 | "scripts": { 11 | "build": "gulp", 12 | "watch": "webpack -w", 13 | "uglifyjs": "uglifyjs ./dist/react-forms-dom.js -o ./dist/react-forms-dom.min.js --compress --mangle", 14 | "release": "npm run build && npm run uglifyjs", 15 | "test": "jest && npm run test-tsc && npm run test-tslint", 16 | "test-tslint": "tslint --config ./tslint.json --project tsconfig.json && echo TsLint test successfully passed.", 17 | "test-tsc": "tsc -p . --noEmit", 18 | "test-watch": "jest --watchAll", 19 | "test-coverage": "npm test -- --coverage", 20 | "prepublishOnly": "npm run build", 21 | "gulp-build": "tsc -p ./tools/tsconfig.gulp.json", 22 | "gulp-watch": "npm run gulp-build -- -w" 23 | }, 24 | "license": "AGPL-3.0", 25 | "files": [ 26 | "**/*.md", 27 | "*.js", 28 | "**/*.d.ts", 29 | "!*.config.js", 30 | "!gulpfile.js", 31 | "!node_modules/**" 32 | ], 33 | "devDependencies": { 34 | "@types/chokidar": "^1.7.0", 35 | "@types/enzyme": "^2.8.0", 36 | "@types/gulp": "^4.0.4", 37 | "@types/jest": "^20.0.1", 38 | "@types/sinon": "^2.3.1", 39 | "@types/undertaker": "^1.1.2", 40 | "@types/vinyl-fs": "^2.4.5", 41 | "@types/webpack": "^2.2.16", 42 | "enzyme": "^2.8.2", 43 | "gulp": "github:gulpjs/gulp#4.0", 44 | "jest": "^20.0.4", 45 | "jest-enzyme": "^3.2.0", 46 | "react-test-renderer": "^15.6.1", 47 | "@simplr/mvdir": "0.0.2", 48 | "sinon": "^2.3.5", 49 | "source-map-loader": "^0.2.1", 50 | "ts-jest": "^20.0.6", 51 | "ts-loader": "^2.1.0", 52 | "tslint": "^5.4.3", 53 | "typescript": "^2.3.4", 54 | "uglify-js": "^3.0.18", 55 | "webpack": "^3.0.0" 56 | }, 57 | "dependencies": { 58 | "@types/prop-types": "^15.5.1", 59 | "@types/react": "^15.0.29", 60 | "immutable": "^3.8.1", 61 | "prop-types": "^15.5.10", 62 | "react": "^15.6.1", 63 | "react-dom": "^15.6.1", 64 | "@simplr/react-forms": "^4.0.0", 65 | "tslib": "^1.7.1", 66 | "typed-immutable-record": "^0.0.6" 67 | }, 68 | "jest": { 69 | "setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js", 70 | "transform": { 71 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" 72 | }, 73 | "testRegex": "/__tests__/.*\\.(test|spec).(ts|tsx|js)$", 74 | "moduleFileExtensions": [ 75 | "ts", 76 | "tsx", 77 | "js" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/abstractions.ts: -------------------------------------------------------------------------------- 1 | export * from "./abstractions/index"; 2 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/abstractions/base-dom-field.tsx: -------------------------------------------------------------------------------- 1 | import { BaseField, BaseFieldState } from "@simplr/react-forms"; 2 | import { 3 | DomFieldProps, 4 | DomFieldTemplateCallback, 5 | DomComponentData, 6 | DomFieldDetails 7 | } from "../contracts/field"; 8 | import { FormProps } from "../contracts/form"; 9 | 10 | export type BaseDomFieldState = BaseFieldState; 11 | 12 | export abstract class BaseDomField 13 | extends BaseField { 14 | public Element: TUnderlyingElement | null; 15 | 16 | protected OnFocus = (event: React.FocusEvent): void => { 17 | const props = this.props as DomFieldProps; 18 | if (props.onFocus != null) { 19 | props.onFocus(event); 20 | } 21 | this.Focus(); 22 | } 23 | 24 | protected OnBlur = (event: React.FocusEvent): void => { 25 | const props = this.props as DomFieldProps; 26 | if (props.onBlur != null) { 27 | props.onBlur(event); 28 | } 29 | 30 | this.Blur(); 31 | } 32 | 33 | protected get FieldTemplate(): DomFieldTemplateCallback | undefined { 34 | const formProps = this.FormStore.GetState().Form.Props as FormProps; 35 | if (this.props.template != null) { 36 | return this.props.template; 37 | } 38 | 39 | if (formProps.template) { 40 | return formProps.template; 41 | } 42 | } 43 | 44 | protected GetHTMLProps(props: DomFieldProps): {} { 45 | const { 46 | defaultValue, 47 | destroyOnUnmount, 48 | disabled, 49 | formatValue, 50 | initialValue, 51 | name, 52 | normalizeValue, 53 | onBlur, 54 | onFocus, 55 | parseValue, 56 | template, 57 | validationType, 58 | value, 59 | children, 60 | ...restProps 61 | } = props; 62 | 63 | return restProps; 64 | } 65 | 66 | protected SetElementRef = (element: TUnderlyingElement | null): void => { 67 | this.Element = element; 68 | } 69 | 70 | public abstract renderField(): JSX.Element | null; 71 | 72 | public render(): JSX.Element | null { 73 | if (this.FieldTemplate == null) { 74 | return this.renderField(); 75 | } 76 | return this.FieldTemplate( 77 | this.renderField.bind(this), 78 | { 79 | name: this.Name, 80 | fieldGroupId: this.FieldsGroupId, 81 | id: this.FieldId 82 | } as DomFieldDetails, 83 | this.FormStore, 84 | { 85 | props: this.props, 86 | state: this.FieldState 87 | } as DomComponentData); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/abstractions/base-errors-container.ts: -------------------------------------------------------------------------------- 1 | import * as Immutable from "immutable"; 2 | import { BaseContainer, BaseContainerParentContext } from "@simplr/react-forms"; 3 | import { FormStore } from "@simplr/react-forms/stores"; 4 | import { FormError, FieldValidationStatus } from "@simplr/react-forms/contracts"; 5 | 6 | export type BaseErrorsContainerProps = {}; 7 | 8 | export type FieldErrors = Immutable.Map; 9 | 10 | export interface BaseErrorsContainerState { 11 | FieldErrors: FieldErrors; 12 | FormError?: FormError; 13 | } 14 | 15 | export abstract class BaseErrorsContainer 16 | extends BaseContainer { 17 | public state: TState = { 18 | FieldErrors: Immutable.Map() 19 | } as TState; 20 | 21 | protected OnStoreUpdated(): void { 22 | const storeState = this.FormStore.GetState(); 23 | 24 | if (!storeState.HasError) { 25 | this.setState(state => { 26 | state.FieldErrors = Immutable.Map(); 27 | state.FormError = undefined; 28 | return state; 29 | }); 30 | return; 31 | } 32 | const formError = storeState.Form.Error; 33 | const fieldsWithError = storeState 34 | .FieldsValidationStatuses 35 | .filter(x => x != null && x === FieldValidationStatus.HasError) 36 | .keySeq() 37 | .toArray(); 38 | 39 | const fieldsErrors: { [id: string]: FormError } = {}; 40 | 41 | fieldsWithError.forEach(fieldId => { 42 | const fieldError = storeState.Fields.get(fieldId).Error; 43 | if (fieldError != null) { 44 | fieldsErrors[fieldId] = fieldError; 45 | } 46 | }); 47 | 48 | this.setState(state => { 49 | state.FormError = formError; 50 | state.FieldErrors = Immutable.Map(fieldsErrors); 51 | 52 | return state; 53 | }); 54 | } 55 | 56 | public abstract render(): JSX.Element | null; 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/abstractions/base-form-button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { recordify, TypedRecord } from "typed-immutable-record"; 3 | import { Iterable } from "immutable"; 4 | 5 | import { BaseContainer, BaseContainerProps } from "@simplr/react-forms"; 6 | 7 | export interface BaseFormButtonProps extends BaseContainerProps { 8 | disableOnError?: boolean; 9 | disableOnBusy?: boolean; 10 | disableOnPristine?: boolean; 11 | busy?: boolean; 12 | disabled?: boolean; 13 | busyClassName?: string; 14 | style?: React.CSSProperties; 15 | className?: string; 16 | } 17 | 18 | export interface BaseFormButtonState { 19 | HasError: boolean; 20 | Validating: boolean; 21 | Submitting: boolean; 22 | Pristine: boolean; 23 | Disabled: boolean; 24 | } 25 | 26 | export interface BaseFormButtonStateRecord extends TypedRecord, BaseFormButtonState { } 27 | 28 | export abstract class BaseFormButton 29 | extends BaseContainer { 30 | public static defaultProps: BaseFormButtonProps = { 31 | disableOnBusy: true, 32 | disableOnError: false, 33 | disableOnPristine: false, 34 | disabled: undefined, 35 | busyClassName: "busy" 36 | }; 37 | 38 | protected OnStoreUpdated(): void { 39 | const formStore = this.FormStore.GetState(); 40 | const newState: BaseFormButtonState = { 41 | HasError: formStore.HasError, 42 | Validating: formStore.Validating, 43 | Submitting: formStore.Submitting, 44 | Pristine: formStore.Pristine, 45 | Disabled: formStore.Form.Disabled 46 | }; 47 | 48 | const newStateRecord = recordify(newState); 49 | 50 | // Type 'Readonly' cannot be converted to type 'Iterable'. 51 | const stateIterable = this.state as any as Iterable; 52 | if (!newStateRecord.equals(stateIterable)) { 53 | // newStateRecord becomes an empty object after setState 54 | // This happens because of an underlying Immutable.Record 55 | // not enumerating properties in for..in 56 | this.setState(() => newState); 57 | } 58 | } 59 | 60 | protected get Disabled(): boolean { 61 | if (this.state == null) { 62 | return false; 63 | } 64 | 65 | if (this.state.Disabled === true) { 66 | return true; 67 | } 68 | 69 | const props = this.props as TProps; 70 | if (props.disabled != null) { 71 | return props.disabled; 72 | } 73 | 74 | if (props.disableOnError === true && 75 | this.state.HasError) { 76 | return true; 77 | } 78 | 79 | if (props.disableOnBusy === true && 80 | this.Busy) { 81 | return true; 82 | } 83 | 84 | if (props.disableOnPristine === true && 85 | this.state.Pristine === true) { 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | protected get Busy(): boolean { 92 | return this.props.busy === true || 93 | this.state != null && 94 | (this.state.Validating || this.state.Submitting); 95 | } 96 | 97 | protected get InlineStyles(): React.CSSProperties { 98 | let inlineStyles: React.CSSProperties = {}; 99 | const props = this.props as TProps; 100 | if (props.style != null) { 101 | inlineStyles = props.style; 102 | } 103 | 104 | if (this.Busy && !props.disabled) { 105 | inlineStyles.cursor = "wait"; 106 | } 107 | 108 | return inlineStyles; 109 | } 110 | 111 | protected get ClassName(): string | undefined { 112 | let className = ""; 113 | if (this.props.className != null) { 114 | className += `${this.props.className} `; 115 | } 116 | if (this.Busy) { 117 | className += this.props.busyClassName; 118 | } 119 | return className.length > 0 ? className : undefined; 120 | } 121 | 122 | protected GetHTMLProps(props: BaseFormButtonProps): {} { 123 | const { 124 | disableOnError, 125 | disableOnBusy, 126 | disableOnPristine, 127 | busy, 128 | busyClassName, 129 | formId, 130 | ...restProps 131 | } = props; 132 | 133 | return restProps; 134 | } 135 | 136 | public abstract render(): JSX.Element | null; 137 | } 138 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/abstractions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./base-dom-field"; 2 | export * from "./base-errors-container"; 3 | export * from "./base-form-button"; 4 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | 14 | export type CheckBoxOnChangeCallback = FieldOnChangeCallback; 15 | 16 | /** 17 | * Override the differences between extended interfaces. 18 | */ 19 | export interface CheckBoxProps extends DomFieldProps, HTMLElementProps { 20 | name: string; 21 | onFocus?: React.FocusEventHandler; 22 | onBlur?: React.FocusEventHandler; 23 | onChange?: CheckBoxOnChangeCallback; 24 | 25 | defaultValue?: boolean; 26 | initialValue?: boolean; 27 | value?: boolean; 28 | ref?: React.Ref; 29 | } 30 | 31 | export class CheckBox extends BaseDomField { 32 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue { 33 | return event.currentTarget.checked; 34 | } 35 | 36 | protected OnChangeHandler: React.ChangeEventHandler = event => { 37 | this.OnValueChange(this.GetValueFromEvent(event)); 38 | 39 | const newValue = this.FormStore.GetField(this.FieldId).Value; 40 | 41 | if (this.props.onChange != null) { 42 | event.persist(); 43 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 44 | } 45 | 46 | const formStoreState = this.FormStore.GetState(); 47 | const formProps = formStoreState.Form.Props as FormProps; 48 | if (formProps.onChange != null) { 49 | event.persist(); 50 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 51 | } 52 | } 53 | 54 | protected get RawDefaultValue(): boolean { 55 | if (this.props.defaultValue != null) { 56 | return this.props.defaultValue; 57 | } 58 | return false; 59 | } 60 | 61 | public renderField(): JSX.Element { 62 | return ; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/clear.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { 4 | BaseFormButton, 5 | BaseFormButtonProps, 6 | BaseFormButtonStateRecord 7 | } from "../abstractions/base-form-button"; 8 | import { HTMLElementProps } from "../contracts/field"; 9 | 10 | export interface ClearProps extends BaseFormButtonProps, HTMLElementProps { 11 | fieldIds?: string[]; 12 | 13 | ref?: React.Ref; 14 | } 15 | 16 | export class Clear extends BaseFormButton { 17 | protected OnButtonClick: React.MouseEventHandler = (event): void => { 18 | this.FormStore.ClearFields(this.props.fieldIds); 19 | 20 | if (this.props.onClick != null) { 21 | event.persist(); 22 | this.props.onClick(event); 23 | } 24 | } 25 | 26 | protected GetHTMLProps(props: ClearProps): {} { 27 | const filteredProps = super.GetHTMLProps(props) as ClearProps; 28 | const { 29 | fieldIds, 30 | ...restProps 31 | } = filteredProps; 32 | 33 | return restProps; 34 | } 35 | 36 | public render(): JSX.Element { 37 | return ; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/email.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | 14 | export type EmailOnChangeCallback = FieldOnChangeCallback; 15 | 16 | /** 17 | * Override the differences between extended interfaces. 18 | */ 19 | export interface EmailProps extends DomFieldProps, HTMLElementProps { 20 | name: string; 21 | onFocus?: React.FocusEventHandler; 22 | onBlur?: React.FocusEventHandler; 23 | onChange?: EmailOnChangeCallback; 24 | 25 | defaultValue?: FieldValue; 26 | value?: FieldValue; 27 | ref?: React.Ref; 28 | } 29 | 30 | export class Email extends BaseDomField { 31 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue { 32 | return event.currentTarget.value; 33 | } 34 | 35 | protected OnChangeHandler: React.ChangeEventHandler = event => { 36 | this.OnValueChange(this.GetValueFromEvent(event)); 37 | 38 | const newValue = this.FormStore.GetField(this.FieldId).Value; 39 | 40 | if (this.props.onChange != null) { 41 | event.persist(); 42 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 43 | } 44 | 45 | const formStoreState = this.FormStore.GetState(); 46 | const formProps = formStoreState.Form.Props as FormProps; 47 | if (formProps.onChange != null) { 48 | event.persist(); 49 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 50 | } 51 | } 52 | 53 | protected get RawDefaultValue(): string { 54 | if (this.props.defaultValue != null) { 55 | return this.props.defaultValue; 56 | } 57 | return ""; 58 | } 59 | 60 | public renderField(): JSX.Element { 61 | return ; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/errors-container.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FormStore } from "@simplr/react-forms/stores"; 3 | import { FormError, FieldValidationStatus } from "@simplr/react-forms/contracts"; 4 | 5 | import { 6 | BaseErrorsContainer, 7 | BaseErrorsContainerState, 8 | FieldErrors 9 | } from "../abstractions/base-errors-container"; 10 | 11 | export type ErrorsTemplate = (fieldErrors: FieldErrors, formError: FormError | undefined, formStore: FormStore) => JSX.Element | null; 12 | 13 | export interface ErrorsContainerProps { 14 | template: ErrorsTemplate; 15 | } 16 | 17 | export type ErrorsContainerState = BaseErrorsContainerState; 18 | 19 | export class ErrorsContainer extends BaseErrorsContainer { 20 | public render(): JSX.Element | null { 21 | return this.props.template(this.state.FieldErrors, this.state.FormError, this.FormStore); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/fields-array.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | FieldsArrayProps as CoreProps, 4 | FieldsArrayState as CoreState 5 | } from "@simplr/react-forms/contracts"; 6 | import { BaseFieldsArray } from "@simplr/react-forms"; 7 | import { HTMLElementProps } from "../contracts/field"; 8 | 9 | export interface FieldsArrayProps extends CoreProps, HTMLElementProps { 10 | name: string; 11 | 12 | ref?: React.Ref; 13 | } 14 | 15 | export type FieldsArrayState = CoreState; 16 | 17 | interface Dictionary { 18 | [key: string]: any; 19 | } 20 | 21 | export class FieldsArray extends BaseFieldsArray { 22 | public Element: HTMLDivElement | null; 23 | 24 | protected SetElementRef = (element: HTMLDivElement | null): void => { 25 | this.Element = element; 26 | } 27 | 28 | protected GetHTMLProps(props: FieldsArrayProps): {} { 29 | const { 30 | name, 31 | index, 32 | destroyOnUnmount, 33 | children, 34 | ...restProps } = this.props; 35 | return restProps; 36 | } 37 | 38 | public render(): JSX.Element { 39 | return
43 | {this.props.children} 44 |
; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/fields-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | FieldsGroupProps as CoreProps, 4 | FieldsGroupState as CoreState 5 | } from "@simplr/react-forms/contracts"; 6 | import { BaseFieldsGroup } from "@simplr/react-forms"; 7 | import { HTMLElementProps } from "../contracts/field"; 8 | 9 | export interface FieldsGroupProps extends CoreProps, HTMLElementProps { 10 | name: string; 11 | 12 | ref?: React.Ref; 13 | } 14 | 15 | export type FieldsGroupState = CoreState; 16 | 17 | interface Dictionary { 18 | [key: string]: any; 19 | } 20 | 21 | export class FieldsGroup extends BaseFieldsGroup { 22 | public Element: HTMLDivElement | null; 23 | 24 | protected SetElementRef = (element: HTMLDivElement | null): void => { 25 | this.Element = element; 26 | } 27 | 28 | protected GetHTMLProps(props: FieldsGroupProps): {} { 29 | const { 30 | name, 31 | destroyOnUnmount, 32 | children, 33 | ...restProps } = this.props; 34 | return restProps; 35 | } 36 | 37 | public render(): JSX.Element { 38 | return
42 | {this.props.children} 43 |
; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BaseForm } from "@simplr/react-forms"; 3 | 4 | import { FormProps } from "../contracts/form"; 5 | 6 | export class Form extends BaseForm { 7 | public Element: HTMLFormElement | null; 8 | 9 | protected SetElementRef = (element: HTMLFormElement | null) => { 10 | this.Element = element; 11 | if (this.FormStore != null && element != null) { 12 | this.FormStore.SetFormSubmitCallback(() => { 13 | element.dispatchEvent(new Event("submit")); 14 | }); 15 | } 16 | } 17 | 18 | public static defaultProps: FormProps = { 19 | ...BaseForm.defaultProps, 20 | preventSubmitDefaultAndPropagation: true 21 | }; 22 | 23 | protected FormSubmitHandler = (event: React.FormEvent): void => { 24 | if (this.props.preventSubmitDefaultAndPropagation) { 25 | event.preventDefault(); 26 | event.stopPropagation(); 27 | } 28 | if (!this.ShouldFormSubmit()) { 29 | return; 30 | } 31 | 32 | this.FormStore.TouchFields(); 33 | 34 | if (this.props.onSubmit == null) { 35 | return; 36 | } 37 | 38 | // Persist synthetic event, because it's passed into another method. 39 | event.persist(); 40 | 41 | // Pass onSubmit result to FormStore for further processing. 42 | const result = this.props.onSubmit(event, this.FormStore); 43 | this.FormStore.SubmitForm(result); 44 | } 45 | 46 | public render(): JSX.Element { 47 | return
51 | {this.props.children} 52 |
; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/hidden.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BaseField, BaseFieldState } from "@simplr/react-forms"; 3 | import { FieldProps, FieldValue } from "@simplr/react-forms/contracts"; 4 | 5 | export interface HiddenProps extends FieldProps { 6 | defaultValue: FieldValue; 7 | value: FieldValue; 8 | } 9 | 10 | export class Hidden extends BaseField { 11 | protected get IsControlled(): boolean { 12 | return true; 13 | } 14 | 15 | protected get RawDefaultValue(): FieldValue { 16 | return this.props.defaultValue; 17 | } 18 | 19 | public render(): null { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/number.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | import { StringToDecimalModifier } from "@simplr/react-forms/modifiers"; 14 | 15 | export type NumberOnChangeCallback = FieldOnChangeCallback; 16 | 17 | /** 18 | * Override the differences between extended interfaces. 19 | */ 20 | export interface NumberProps extends DomFieldProps, HTMLElementProps { 21 | name: string; 22 | onFocus?: React.EventHandler>; 23 | onBlur?: React.EventHandler>; 24 | onChange?: NumberOnChangeCallback; 25 | 26 | defaultValue?: FieldValue; 27 | value?: FieldValue; 28 | ref?: React.Ref; 29 | } 30 | 31 | export class Number extends BaseDomField { 32 | protected get DefaultModifiers(): JSX.Element[] { 33 | return []; 34 | } 35 | 36 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue { 37 | return event.currentTarget.value; 38 | } 39 | 40 | protected OnChangeHandler: React.ChangeEventHandler = event => { 41 | this.OnValueChange(this.GetValueFromEvent(event)); 42 | 43 | const newValue = this.FormStore.GetField(this.FieldId).Value; 44 | 45 | if (this.props.onChange != null) { 46 | event.persist(); 47 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 48 | } 49 | 50 | const formStoreState = this.FormStore.GetState(); 51 | const formProps = formStoreState.Form.Props as FormProps; 52 | if (formProps.onChange != null) { 53 | event.persist(); 54 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 55 | } 56 | } 57 | 58 | protected get RawDefaultValue(): string { 59 | if (this.props.defaultValue != null) { 60 | return this.props.defaultValue; 61 | } 62 | return ""; 63 | } 64 | 65 | public renderField(): JSX.Element | null { 66 | return ; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/password.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | 14 | export type PasswordOnChangeCallback = FieldOnChangeCallback; 15 | 16 | /** 17 | * Override the differences between extended interfaces. 18 | */ 19 | export interface PasswordProps extends DomFieldProps, HTMLElementProps { 20 | name: string; 21 | onFocus?: React.FocusEventHandler; 22 | onBlur?: React.FocusEventHandler; 23 | onChange?: PasswordOnChangeCallback; 24 | 25 | defaultValue?: FieldValue; 26 | value?: FieldValue; 27 | ref?: React.Ref; 28 | } 29 | 30 | export class Password extends BaseDomField { 31 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue { 32 | return event.currentTarget.value; 33 | } 34 | 35 | protected OnChangeHandler: React.ChangeEventHandler = event => { 36 | this.OnValueChange(this.GetValueFromEvent(event)); 37 | 38 | const newValue = this.FormStore.GetField(this.FieldId).Value; 39 | 40 | if (this.props.onChange != null) { 41 | event.persist(); 42 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 43 | } 44 | 45 | const formStoreState = this.FormStore.GetState(); 46 | const formProps = formStoreState.Form.Props as FormProps; 47 | if (formProps.onChange != null) { 48 | event.persist(); 49 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 50 | } 51 | } 52 | 53 | protected get RawDefaultValue(): string { 54 | if (this.props.defaultValue != null) { 55 | return this.props.defaultValue; 56 | } 57 | return ""; 58 | } 59 | 60 | public renderField(): JSX.Element { 61 | return ; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import { BaseField, BaseFieldState } from "@simplr/react-forms"; 4 | import { FieldProps, FieldChildContext, FieldValue } from "@simplr/react-forms/contracts"; 5 | import { HTMLElementProps, DomFieldTemplateCallback } from "../contracts/field"; 6 | import { FormProps } from "../contracts/form"; 7 | import { BaseDomField } from "../abstractions"; 8 | import { TypedRecord } from "typed-immutable-record"; 9 | 10 | export type RadioValue = string | number; 11 | 12 | export interface RadioGroupProps extends FieldProps, HTMLElementProps { 13 | name: string; 14 | radioTemplate?: DomFieldTemplateCallback; 15 | 16 | defaultValue?: RadioValue; 17 | initialValue?: RadioValue; 18 | value?: RadioValue; 19 | } 20 | 21 | export interface RadioGroupChildContext extends FieldChildContext { 22 | RadioGroupOnChangeHandler: RadioOnChangeHandler; 23 | RadioGroupOnFocus: React.FocusEventHandler; 24 | RadioGroupOnBlur: React.FocusEventHandler; 25 | } 26 | 27 | export type RadioOnChangeHandler = (event: React.ChangeEvent, value: string) => void; 28 | 29 | export class RadioGroup extends BaseDomField { 30 | public static childContextTypes: PropTypes.ValidationMap = { 31 | ...BaseDomField.childContextTypes, 32 | RadioGroupOnChangeHandler: PropTypes.func.isRequired, 33 | RadioGroupOnBlur: PropTypes.func.isRequired, 34 | RadioGroupOnFocus: PropTypes.func.isRequired 35 | }; 36 | 37 | public getChildContext(): RadioGroupChildContext { 38 | return { 39 | ...super.getChildContext(), 40 | FieldId: this.FieldId, 41 | RadioGroupOnChangeHandler: this.OnChangeHandler, 42 | RadioGroupOnBlur: this.OnBlur, 43 | RadioGroupOnFocus: this.OnFocus 44 | }; 45 | } 46 | 47 | protected get RawDefaultValue(): React.ReactText { 48 | if (this.props.defaultValue != null) { 49 | return this.props.defaultValue; 50 | } 51 | 52 | return ""; 53 | } 54 | 55 | protected OnChangeHandler: RadioOnChangeHandler = (event, value) => { 56 | this.OnValueChange(value); 57 | 58 | const newValue = this.FormStore.GetField(this.FieldId).Value; 59 | 60 | if (this.props.onChange != null) { 61 | event.persist(); 62 | this.props.onChange(event, newValue, this.FieldId, this.FormId); 63 | } 64 | 65 | const formStoreState = this.FormStore.GetState(); 66 | const formProps = formStoreState.Form.Props as FormProps; 67 | if (formProps.onChange != null) { 68 | event.persist(); 69 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 70 | } 71 | } 72 | 73 | protected GetHTMLProps(props: RadioGroupProps): {} { 74 | const cleanedProps = super.GetHTMLProps(props) as RadioGroupProps; 75 | const { radioTemplate, ...restProps } = cleanedProps; 76 | 77 | return restProps; 78 | } 79 | 80 | public renderField(): JSX.Element { 81 | return
85 | {this.props.children} 86 |
; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/reset.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { 4 | BaseFormButton, 5 | BaseFormButtonProps, 6 | BaseFormButtonStateRecord 7 | } from "../abstractions/base-form-button"; 8 | import { HTMLElementProps } from "../contracts/field"; 9 | 10 | export interface ResetProps extends BaseFormButtonProps, HTMLElementProps { 11 | fieldIds?: string[]; 12 | 13 | ref?: React.Ref; 14 | } 15 | 16 | export class Reset extends BaseFormButton { 17 | protected OnButtonClick: React.MouseEventHandler = (event): void => { 18 | this.FormStore.ResetFields(this.props.fieldIds); 19 | 20 | if (this.props.onClick != null) { 21 | event.persist(); 22 | this.props.onClick(event); 23 | } 24 | } 25 | 26 | protected GetHTMLProps(props: ResetProps): {} { 27 | const filteredProps = super.GetHTMLProps(props) as ResetProps; 28 | const { 29 | fieldIds, 30 | ...restProps 31 | } = filteredProps; 32 | 33 | return restProps; 34 | } 35 | 36 | public render(): JSX.Element { 37 | return ; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/search.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | 14 | export type SearchOnChangeCallback = FieldOnChangeCallback; 15 | 16 | /** 17 | * Override the differences between extended interfaces. 18 | */ 19 | export interface SearchProps extends DomFieldProps, HTMLElementProps { 20 | name: string; 21 | onFocus?: React.FocusEventHandler; 22 | onBlur?: React.FocusEventHandler; 23 | onChange?: SearchOnChangeCallback; 24 | 25 | defaultValue?: string; 26 | initialValue?: string; 27 | value?: string; 28 | ref?: React.Ref; 29 | } 30 | 31 | export class Search extends BaseDomField { 32 | protected GetValueFromEvent(event: React.ChangeEvent): string { 33 | return event.currentTarget.value; 34 | } 35 | 36 | protected OnChangeHandler: React.ChangeEventHandler = event => { 37 | this.OnValueChange(this.GetValueFromEvent(event)); 38 | 39 | const newValue = this.FormStore.GetField(this.FieldId).Value; 40 | 41 | if (this.props.onChange != null) { 42 | event.persist(); 43 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 44 | } 45 | 46 | const formStoreState = this.FormStore.GetState(); 47 | const formProps = formStoreState.Form.Props as FormProps; 48 | if (formProps.onChange != null) { 49 | event.persist(); 50 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 51 | } 52 | } 53 | 54 | protected get RawDefaultValue(): string { 55 | if (this.props.defaultValue != null) { 56 | return this.props.defaultValue; 57 | } 58 | return ""; 59 | } 60 | 61 | public renderField(): JSX.Element { 62 | return ; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field"; 6 | import { FieldOnChangeCallback } from "../contracts/field"; 7 | import { 8 | HTMLElementProps 9 | } from "../contracts/field"; 10 | import { 11 | FormProps 12 | } from "../contracts/form"; 13 | 14 | export type SelectValue = string | string[]; 15 | export type SelectOnChangeCallback = FieldOnChangeCallback; 16 | 17 | /** 18 | * Override the differences between extended interfaces. 19 | */ 20 | export interface SelectProps extends DomFieldProps, HTMLElementProps { 21 | name: string; 22 | onFocus?: React.FocusEventHandler; 23 | onBlur?: React.FocusEventHandler; 24 | onChange?: SelectOnChangeCallback; 25 | 26 | defaultValue?: SelectValue; 27 | initialValue?: SelectValue; 28 | value?: SelectValue; 29 | ref?: React.Ref 131 | {this.props.children} 132 | ; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/submit.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { recordify, TypedRecord } from "typed-immutable-record"; 3 | 4 | import { BaseContainer, BaseContainerProps } from "@simplr/react-forms"; 5 | import { FormError } from "@simplr/react-forms/contracts"; 6 | 7 | import { 8 | BaseFormButton, 9 | BaseFormButtonProps, 10 | BaseFormButtonStateRecord 11 | } from "../abstractions/base-form-button"; 12 | 13 | export type SubmitProps = BaseFormButtonProps; 14 | 15 | export class Submit extends BaseFormButton { 16 | public static defaultProps: BaseFormButtonProps = { 17 | ...BaseFormButton.defaultProps, 18 | disableOnError: true 19 | }; 20 | 21 | public render(): JSX.Element { 22 | return ; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DomFieldProps } from "../contracts/field"; 3 | 4 | import { 5 | BaseDomField, 6 | BaseDomFieldState 7 | } from "../abstractions/base-dom-field"; 8 | import { 9 | FieldOnChangeCallback, 10 | HTMLElementProps 11 | } from "../contracts/field"; 12 | import { 13 | FormProps 14 | } from "../contracts/form"; 15 | 16 | export type TextOnChangeCallback = FieldOnChangeCallback; 17 | 18 | /** 19 | * Override the differences between extended interfaces. 20 | */ 21 | export interface TextProps extends DomFieldProps, HTMLElementProps { 22 | name: string; 23 | onFocus?: React.EventHandler>; 24 | onBlur?: React.EventHandler>; 25 | onChange?: TextOnChangeCallback; 26 | 27 | defaultValue?: string; 28 | initialValue?: string; 29 | value?: string; 30 | ref?: React.Ref; 31 | } 32 | 33 | export class Text extends BaseDomField { 34 | protected GetValueFromEvent(event: React.ChangeEvent): string { 35 | return event.target.value; 36 | } 37 | 38 | protected OnChangeHandler: React.ChangeEventHandler = event => { 39 | let newValue: string | undefined; 40 | if (!this.IsControlled) { 41 | this.OnValueChange(this.GetValueFromEvent(event)); 42 | newValue = this.FormStore.GetField(this.FieldId).Value; 43 | } else { 44 | newValue = this.GetValueFromEvent(event); 45 | } 46 | 47 | if (this.props.onChange != null) { 48 | event.persist(); 49 | this.props.onChange(event, newValue, this.FieldId, this.FormStore); 50 | } 51 | 52 | const formStoreState = this.FormStore.GetState(); 53 | const formProps = formStoreState.Form.Props as FormProps; 54 | if (formProps.onChange != null) { 55 | event.persist(); 56 | formProps.onChange(event, newValue, this.FieldId, this.FormStore); 57 | } 58 | } 59 | 60 | protected get RawDefaultValue(): string { 61 | if (this.props.defaultValue != null) { 62 | return this.props.defaultValue; 63 | } 64 | 65 | return ""; 66 | } 67 | 68 | public renderField(): JSX.Element { 69 | return ; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/react-forms-dom/src/components/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValue } from "@simplr/react-forms/contracts"; 3 | import { DomFieldProps } from "../contracts/field"; 4 | 5 | import { 6 | BaseDomField, 7 | BaseDomFieldState 8 | } from "../abstractions/base-dom-field"; 9 | import { 10 | FieldOnChangeCallback, 11 | HTMLElementProps 12 | } from "../contracts/field"; 13 | import { 14 | FormProps 15 | } from "../contracts/form"; 16 | 17 | export type TextAreaOnChangeCallback = FieldOnChangeCallback; 18 | 19 | /** 20 | * Override the differences between extended interfaces. 21 | */ 22 | export interface TextAreaProps extends DomFieldProps, HTMLElementProps { 23 | name: string; 24 | onFocus?: React.EventHandler>; 25 | onBlur?: React.EventHandler>; 26 | onChange?: TextAreaOnChangeCallback; 27 | 28 | defaultValue?: string; 29 | initialValue?: string; 30 | value?: string; 31 | ref?: React.Ref