├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── extras
├── MaterialUiFormikUndoControlBar.tsx
└── formik-undo.gif
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── CheckpointPicker.ts
├── EditedFieldChangedCheckpointPicker.ts
├── FormikUndo.tsx
├── FormikUndoControlBar.tsx
├── FormikUndoProvider.tsx
├── WordEditingCheckpointPicker.ts
├── getFormikValuesDiff.ts
├── hooks.ts
├── index.ts
├── types.ts
└── useFormikUndoAutoSave.tsx
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true,
6 | },
7 | parserOptions: {
8 | ecmaVersion: 2017,
9 | sourceType: 'module',
10 | ecmaFeatures: {
11 | jsx: true,
12 | }
13 | },
14 | plugins: [
15 | '@typescript-eslint',
16 | "react",
17 | 'react-hooks',
18 | ],
19 | extends: [
20 | 'eslint:recommended',
21 | 'plugin:@typescript-eslint/eslint-recommended',
22 | 'plugin:@typescript-eslint/recommended',
23 | 'react-app',
24 | 'plugin:react/recommended'
25 | ],
26 | rules: {
27 | '@typescript-eslint/explicit-function-return-type': 'off',
28 | '@typescript-eslint/no-empty-interface': 'off',
29 | '@typescript-eslint/camelcase': 'warn',
30 | '@typescript-eslint/member-delimiter-style': 'warn',
31 | '@typescript-eslint/no-inferrable-types': 'warn',
32 | '@typescript-eslint/no-non-null-assertion': 'off',
33 | '@typescript-eslint/no-empty-function': 'warn',
34 | '@typescript-eslint/semi': ['warn', 'always'],
35 | '@typescript-eslint/type-annotation-spacing': 'warn',
36 | '@typescript-eslint/interface-name-prefix': 'off',
37 | '@typescript-eslint/no-unused-vars': ['warn', { 'argsIgnorePattern': '(next)|(returns)|(of)|(type)|(_\\w)', ignoreRestSiblings: true }],
38 | '@typescript-eslint/no-explicit-any': 'off',
39 | 'array-bracket-spacing': ['warn', 'never'],
40 | 'arrow-parens': 'off',
41 | 'comma-spacing': ['warn', {"before": false, "after": true}],
42 | "indent": ['warn', 2, { SwitchCase: 1 }],
43 | 'interface-name': 'off',
44 | 'interface-over-type-literal': 'off',
45 | 'jsx-no-multiline-js': 'off',
46 | 'keyword-spacing': ['warn', { before: true, after: true }],
47 | 'max-classes-per-file': 'off',
48 | 'max-len': ['warn', { 'code': 120, ignoreRegExpLiterals: true, ignoreUrls: true, ignorePattern: '^(import|export)\\s.+\\sfrom\\s.+;?$' }],
49 | 'member-ordering': 'off',
50 | 'new-parens': 'warn',
51 | 'no-angle-bracket-type-assertion': 'off',
52 | 'no-bitwise': 'warn',
53 | 'no-consecutive-blank-lines': 'off',
54 | 'no-console': 'off',
55 | 'no-multi-spaces': ['warn', { exceptions: { 'Property': false } }],
56 | 'no-multiple-empty-lines': ['warn', { max: 2, maxBOF: 0, maxEOF: 0 }],
57 | 'no-empty': 'warn',
58 | 'no-extra-semi': 'off',
59 | 'no-return-await': 'off',
60 | 'no-shadowed-variable': 'off',
61 | 'no-trailing-whitespace': 'off',
62 | 'no-useless-escape': 'warn',
63 | 'object-curly-spacing': ['warn', 'always'],
64 | 'object-literal-sort-keys': 'off',
65 | 'ordered-imports': 'off',
66 | 'prefer-const': 'warn',
67 | 'quotemark': 'off',
68 | 'semi': 'off', // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md
69 | 'space-infix-ops': ['warn', { 'int32Hint': false }],
70 | 'trailing-comma': 'off',
71 | 'variable-name': 'off',
72 | },
73 | ignorePatterns: [
74 | '**/node_modules/*',
75 | 'dist',
76 | 'extras',
77 | ]
78 | };
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | build
3 | .vscode
4 | dist
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Pascal Heitz
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Formik Undo
2 |
3 | Provide the ability to undo / redo modifications in a Formik form.
4 |
5 | Uses Typescript and React hooks.
6 |
7 |
8 | 
9 |
10 |
11 | Online Demo: https://codesandbox.io/s/github/OoDeLally/formik-undo-demo
12 |
13 |
14 | ## Setup
15 |
16 | ```bash
17 | npm install --save formik-undo
18 | ```
19 |
20 |
21 | ```tsx
22 | import { FormikUndo } from 'formik-undo';
23 |
24 | const autoSaveOptions = {
25 | throttleDelay: 10000,
26 | };
27 |
28 | const App = () => {
29 | return (
30 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 | ```
41 |
42 | Provider's props are as follow:
43 |
44 | | Name | Type | Default | Description |
45 | | -----------------------------|--------------------------------|---------|--------------------------------------------------------------------------------|
46 | | `autoSave` | `boolean \| { ...options }` | `true` | If `false`, does not autosave.
If `true`, autosave with the default options.
If `object` autosave with the provided options. |
47 | | `autoSave.throttleDelay` | `number` | 2000 | Frequency of autosaving in millisecond.
If `0`, save at every modification. |
48 | | `autoSave.saveOnFieldChange` | `boolean` | `true` | If ``true``, save a checkpoint everytime the modified field is different from the previously modified. This is useful to save the final value of a input after the user moves to another input.
If `false`, only the whole formik `values` object is considered and different fields changes may be aggregated from one checkpoint to another. |
49 | | `autoSave.preventWordCutting`| `boolean` | `true` | If ``true``, when editing a string value, don't save in the middle of a word (experimental). |
50 |
51 | If you need a finer save trigger, write your own hook using the methods provided by `useFormikContext()` and `useFormikUndo()`.
52 |
53 |
54 |
55 | ## Usage
56 |
57 | ```tsx
58 | import { useFormikUndo } from 'formik-undo';
59 |
60 | const MyComponent = () => {
61 | const { reset, undo, redo, saveCheckpoint } = useFormikUndo();
62 | // Do stuff
63 | return (
64 |
65 | ...
66 |
67 | );
68 | };
69 | ```
70 |
71 | | Name | Type | Description |
72 | | --------------------------- |-------------------------------|----------------------------------------------------------------|
73 | | `reset` | `() => void` | Reset the form to the initial values. |
74 | | `undo` | `() => void` | Undo to previous checkpoint. |
75 | | `redo` | `() => void` | Redo to next checkpoint. |
76 | | `saveCheckpoint` | `() => void` | Manually save a checkpoint to the history. |
77 | | `addCheckpointEquivalent` | `(targetValue: Values, equivalentValue: Values) => void` | Declare that a certain value is equivalent to another, and therefore does not constitute a change worth saving (advanced). |
78 | | `undoableCount` | `number` | Number of possible undo actions. |
79 | | `redoableCount` | `number` | Number of possible redo actions. |
80 | | `didCreateCurrentValues` | `boolean` | Whether the latest form's values were set by us (advanced). |
81 |
82 |
83 |
84 | ## Control bar
85 |
86 | A control bar with default buttons is provided (as seen on the screenshot above).
87 |
88 | ```tsx
89 | import { FormikUndoControlBar } from 'formik-undo';
90 |
91 | const MyForm = () => {
92 | return (
93 |
98 | )
99 | };
100 | ```
101 |
102 | `` accepts props:
103 |
104 | | Name | Type | Default | Description |
105 | | ---------------------------|---------------------------------------------------------|-------------------------------|-----------------------------------------------|
106 | | `showReset` | `boolean` | `true` | Show the `reset` button. |
107 | | `showRedo` | `boolean` | `true` | Show the `redo` button. |
108 | | `disabled` | `boolean` | `false` | Disable every control. |
109 | | `className` | `string` | | Add extra classes to the control container. |
110 | | `buttonTitles` | `Record<('reset' \| 'undo' \| 'redo'), string \| null>` | `"Reset"`, `"Undo"`, `"Redo"` | Override the `title` attribute of the button. |
111 | | `buttonClasses` | `Record<('reset' \| 'undo' \| 'redo'), string>` | `{}` | Add extra classes to some of the buttons. |
112 | | `buttonIcons` | `Record<('reset' \| 'undo' \| 'redo'), ComponentType>` | `"↺"`, `"↶"`, `"↷"` | Override the buttons' content. |
113 | | `buttonComponent` | `ComponentType` |