├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── package-lock.json ├── package.json ├── src └── submittable.js └── test └── submittable.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | coverage 4 | dist 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | cache: 5 | directories: 6 | - node_modules 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.1 4 | - Upgrade dependencies 5 | 6 | ## 3.0.0 7 | - Change the package name to @mapbox/react-submittable 8 | - Add react@18 to the allowed peer dependencies 9 | 10 | ## 2.0.0 11 | 12 | - react and react-dom are now peer dependencies, instead of regular dependencies. 13 | - `dist/` no longer included in Git repo. 14 | - Switch `prepublish` build to `prepublishOnly`. 15 | 16 | ## 1.0.4 17 | 18 | - Babel fanciness to appease React 15.5. 19 | 20 | ## 1.0.3 21 | 22 | - Fixed: no longer passes `ReactSubmittable`'s own props to the child `
` element. 23 | 24 | ## 1.0.2 25 | 26 | - Start this log 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | ISC License 3 | 4 | Copyright (c) 2017, Mapbox 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-submittable 2 | 3 | [![Build Status](https://travis-ci.com/mapbox/react-submittable.svg?branch=main)](https://travis-ci.com/mapbox/react-submittable) 4 | [![Coverage Status](https://coveralls.io/repos/mapbox/react-submittable/badge.svg?branch=main&service=github)](https://coveralls.io/github/mapbox/react-submittable?branch=master) 5 | 6 | Stop using `preventDefault` to get form behavior in React. 7 | 8 | ```js 9 | 12 | 13 | 14 | ``` 15 | 16 | The `Submittable` component simulates the same enter & escape behaviors 17 | you would get with the `submit` event of an HTML form, without having to 18 | shut down the default behavior. It also catches the escape key and calls 19 | an `onCancel` event when it sees it. 20 | 21 | ## install 22 | 23 | npm install --save @mapbox/react-submittable 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/react-submittable", 3 | "version": "3.0.1", 4 | "description": "a replacement for preventing the default behavior of forms: allows submission on enter", 5 | "main": "dist/submittable.js", 6 | "scripts": { 7 | "format": "prettier --write src/*.js", 8 | "build": "babel src -d dist", 9 | "jest": "jest", 10 | "pretest": "npm run build", 11 | "prepublishOnly": "npm run build", 12 | "test": "npm run jest" 13 | }, 14 | "keywords": [ 15 | "submit", 16 | "form", 17 | "element", 18 | "react" 19 | ], 20 | "files": [ 21 | "dist", 22 | "src" 23 | ], 24 | "author": "Mapbox", 25 | "license": "ISC", 26 | "devDependencies": { 27 | "@babel/cli": "^7.24.1", 28 | "@babel/core": "^7.24.3", 29 | "@babel/plugin-transform-class-properties": "^7.24.1", 30 | "@babel/preset-env": "^7.24.3", 31 | "@babel/preset-react": "^7.24.1", 32 | "@testing-library/react": "^14.2.2", 33 | "@testing-library/user-event": "^14.5.2", 34 | "jest": "^29.7.0", 35 | "jest-environment-jsdom": "^29.7.0", 36 | "prettier": "^3.2.5", 37 | "react": "^18.2.0", 38 | "react-dom": "^18.2.0" 39 | }, 40 | "prettier": { 41 | "singleQuote": true 42 | }, 43 | "babel": { 44 | "presets": [ 45 | "@babel/preset-react", 46 | "@babel/preset-env" 47 | ], 48 | "plugins": [ 49 | "@babel/plugin-transform-class-properties" 50 | ] 51 | }, 52 | "peerDependencies": { 53 | "react": "^15.5.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", 54 | "react-dom": "^15.5.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" 55 | }, 56 | "repository": { 57 | "type": "git", 58 | "url": "git@github.com:mapbox/react-submittable.git" 59 | }, 60 | "jest": { 61 | "clearMocks": true, 62 | "testEnvironment": "jsdom" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/submittable.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const propNames = ['onEnter', 'onCancel']; 4 | 5 | /** 6 | * The submittable component is a replacement for form inputs that 7 | * replicates their onSubmit handler - pressing 'Enter' with the cursor 8 | * focused on an input element calls an onEvent property. 9 | */ 10 | class Submittable extends React.Component { 11 | onKeyDown = (event) => { 12 | if (event.target.tagName === 'INPUT') { 13 | if (event.code === 'Enter') { 14 | this.props.onEnter(event); 15 | } else if (event.code === 'Escape' && this.props.onCancel) { 16 | this.props.onCancel(event); 17 | } 18 | } 19 | }; 20 | 21 | onSubmit = (event) => { 22 | event.preventDefault(); 23 | }; 24 | 25 | render() { 26 | const formProps = { 27 | onKeyDown: this.onKeyDown, 28 | onSubmit: this.onSubmit, 29 | }; 30 | Object.keys(this.props).forEach((key) => { 31 | if (propNames.indexOf(key) !== -1) return; 32 | formProps[key] = this.props[key]; 33 | }); 34 | return React.createElement('form', formProps, this.props.children); 35 | } 36 | } 37 | 38 | module.exports = Submittable; 39 | -------------------------------------------------------------------------------- /test/submittable.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import userEvent from "@testing-library/user-event"; 3 | import Submittable from '../src/submittable.js'; 4 | import React from 'react'; 5 | 6 | describe('Submittable', function() { 7 | it('calls onEnter when it gets an enter event', async function() { 8 | const onEnter = jest.fn(); 9 | render( 10 | 11 | 12 | ); 13 | 14 | await userEvent.click(screen.getByRole('textbox')); 15 | await userEvent.keyboard('[Enter]'); 16 | 17 | await expect(onEnter).toHaveBeenCalledTimes(1) 18 | }); 19 | 20 | it('calls onCancel when it gets an exit event and has a binding', async function() { 21 | const onCancel = jest.fn(); 22 | render( 23 | 24 | 25 | ); 26 | 27 | await userEvent.click(screen.getByRole('textbox')); 28 | await userEvent.keyboard('[Escape]'); 29 | 30 | await expect(onCancel).toHaveBeenCalledTimes(1) 31 | }); 32 | 33 | it('does not pass Submittable-specific props to the element', async function() { 34 | render( 35 | 40 | 41 | ); 42 | 43 | const form = screen.getByTestId('foobar'); 44 | expect(form.getAttribute('data-testid')).toBe('foobar'); 45 | expect(form.hasAttribute('disabled')).toBe(true); 46 | expect(form.hasAttribute('onCancel')).toBe(false); 47 | expect(form.hasAttribute('onEnter')).toBe(false); 48 | }); 49 | }); 50 | --------------------------------------------------------------------------------