From 1c4b56438ca53678014db39dd22d0300b0bd33f8 Mon Sep 17 00:00:00 2001 From: Mike Ragalie Date: Mon, 31 Dec 2018 10:33:11 -0500 Subject: [PATCH 1/5] Use render prop function pattern The function receives as arguments the cursor props, which can then be passed into the relevant downstream nodes. This approach makes it straightforward to implement TypeScript definitions and is in line with the approach taken by many react-based libraries. In fact, the react documentation on render props uses a mouse tracking component as its prototypical example: https://reactjs.org/docs/render-props.html --- README.md | 10 +++++--- src/ReactCursorPosition.js | 44 ++++++++++---------------------- test/ReactCursorPosition.spec.js | 14 ++++++---- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 02c8f9d9..0cae83ee 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,19 @@ import ReactCursorPosition from 'react-cursor-position'; ... - - + {(cursorProps) => ( +
+ + +
+ )}
``` react-cursor-position wraps its children in a div, which mouse and touch position are plotted relative to. -Each child component will receive the following props: +The function provided will receive the following object: ```JavaScript { diff --git a/src/ReactCursorPosition.js b/src/ReactCursorPosition.js index 24e3fed8..08f36933 100644 --- a/src/ReactCursorPosition.js +++ b/src/ReactCursorPosition.js @@ -1,4 +1,4 @@ -import React, { Children, cloneElement } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import objectAssign from 'object-assign'; import omit from 'object.omit'; @@ -72,7 +72,7 @@ export default class extends React.Component { INTERACTIONS.TAP, INTERACTIONS.TOUCH ]), - children: PropTypes.any, + children: PropTypes.func.isRequired, className: PropTypes.string, hoverDelayInMs: PropTypes.number, hoverOffDelayInMs: PropTypes.number, @@ -430,28 +430,6 @@ export default class extends React.Component { return e.touches[0]; } - getIsReactComponent(reactElement) { - return typeof reactElement.type === 'function'; - } - - shouldDecorateChild(child) { - return ( - !!child && - this.getIsReactComponent(child) && - this.props.shouldDecorateChildren - ); - } - - decorateChild(child, props) { - return cloneElement(child, props); - } - - decorateChildren(children, props) { - return Children.map(children, (child) => { - return this.shouldDecorateChild(child) ? this.decorateChild(child, props) : child; - }); - } - addEventListeners() { this.eventListeners.push( addEventListener(this.el, 'touchstart', this.onTouchStart, { passive: false }), @@ -477,12 +455,16 @@ export default class extends React.Component { } render() { - const { children, className, mapChildProps, style } = this.props; - const props = objectAssign( - {}, - mapChildProps(this.state), - this.getPassThroughProps() - ); + const { children, className, mapChildProps, shouldDecorateChildren, style } = this.props; + let props = {}; + + if (shouldDecorateChildren) { + props = objectAssign( + {}, + mapChildProps(this.state), + this.getPassThroughProps() + ); + } return (
- {this.decorateChildren(children, props)} + {children(props)}
); } diff --git a/test/ReactCursorPosition.spec.js b/test/ReactCursorPosition.spec.js index 4fed2f4e..8e17cd08 100644 --- a/test/ReactCursorPosition.spec.js +++ b/test/ReactCursorPosition.spec.js @@ -7,12 +7,12 @@ import * as adEventListener from '../src/utils/addEventListener'; import { INTERACTIONS } from '../src/constants'; describe('ReactCursorPosition', () => { - let positionObserver = shallow(, {disableLifecycleMethods: true}); + let positionObserver = shallow( {}}/>, {disableLifecycleMethods: true}); const touchEvent = getTouchEvent(); const mouseEvent = getMouseEvent(); beforeEach(() => { - positionObserver = shallow(, {disableLifecycleMethods: true}); + positionObserver = shallow( {}}/>, {disableLifecycleMethods: true}); }); it('has the display name ReactCursorPosition', () => { @@ -619,7 +619,7 @@ describe('ReactCursorPosition', () => { }); it('supports style', () => { - const tree = render(); + const tree = render( {}}/>); expect(tree.css('width')).toBe('100px'); }); @@ -1180,8 +1180,12 @@ describe('ReactCursorPosition', () => { function getMountedComponentTree(props = {}) { const mountedWrapper = mount( - -
+ {(cursorProps) => ( + + +
+
+ )}
); const { el } = mountedWrapper.instance(); From 2879dd99b14fb5a0677ea612845127a4d61ee3d7 Mon Sep 17 00:00:00 2001 From: Mike Ragalie Date: Mon, 31 Dec 2018 10:59:13 -0500 Subject: [PATCH 2/5] Use updated syntax in examples --- example/public/activation-click.html | 8 ++++++-- example/public/activation-hover.html | 8 ++++++-- example/public/activation-press.html | 8 ++++++-- example/public/activation-tap.html | 8 ++++++-- example/public/activation-touch.html | 8 ++++++-- example/public/class-name.html | 8 ++++++-- example/public/on-activation-changed.html | 4 +++- example/public/on-detected-environment-changed.html | 4 +++- example/public/on-position-changed.html | 4 +++- example/public/should-decorate-children.html | 8 ++++++-- example/public/style.html | 8 ++++++-- example/src/components/ActivationByClick.js | 13 ++++++++----- example/src/components/ActivationByHover.js | 12 ++++++++---- example/src/components/ActivationByPress.js | 12 ++++++++---- example/src/components/ActivationByTap.js | 12 ++++++++---- example/src/components/ActivationByTouch.js | 12 ++++++++---- example/src/components/ActivationChanged.js | 4 +++- example/src/components/BasicExample.js | 8 ++++++-- example/src/components/ClassName.js | 8 ++++++-- .../src/components/DetectedEnvironmentChanged.js | 4 +++- example/src/components/PositionChanged.js | 4 +++- example/src/components/Reset.js | 8 ++++++-- example/src/components/ShouldDecorateChildren.js | 8 ++++++-- example/src/components/Style.js | 8 ++++++-- 24 files changed, 136 insertions(+), 53 deletions(-) diff --git a/example/public/activation-click.html b/example/public/activation-click.html index 5761fb19..0d64a772 100644 --- a/example/public/activation-click.html +++ b/example/public/activation-click.html @@ -21,8 +21,12 @@ <ReactCursorPosition activationInteractionMouse={INTERACTIONS.CLICK} > - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/activation-hover.html b/example/public/activation-hover.html index e8d38757..e24d7216 100644 --- a/example/public/activation-hover.html +++ b/example/public/activation-hover.html @@ -23,8 +23,12 @@ hoverDelayInMs={250} //default: 0 hoverOffDelayInMs={150} //default: 0 > - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/activation-press.html b/example/public/activation-press.html index 6eb30525..4cc9216b 100644 --- a/example/public/activation-press.html +++ b/example/public/activation-press.html @@ -23,8 +23,12 @@ pressDurationInMs={500} //default pressMoveThreshold={5} //default > - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/activation-tap.html b/example/public/activation-tap.html index ea3d46ad..569663f2 100644 --- a/example/public/activation-tap.html +++ b/example/public/activation-tap.html @@ -21,8 +21,12 @@ <ReactCursorPosition activationInteractionTouch={INTERACTIONS.TAP} > - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/activation-touch.html b/example/public/activation-touch.html index 06a4d081..825804c6 100644 --- a/example/public/activation-touch.html +++ b/example/public/activation-touch.html @@ -21,8 +21,12 @@ <ReactCursorPosition activationInteractionTouch={INTERACTIONS.TOUCH} > - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/class-name.html b/example/public/class-name.html index 69e1152d..3b2b8111 100644 --- a/example/public/class-name.html +++ b/example/public/class-name.html @@ -19,8 +19,12 @@ export default () => ( <ReactCursorPosition className="example__target"> - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); diff --git a/example/public/on-activation-changed.html b/example/public/on-activation-changed.html index f180c541..e1e307b4 100644 --- a/example/public/on-activation-changed.html +++ b/example/public/on-activation-changed.html @@ -34,7 +34,9 @@ this.setState({ isActive }); } }}> - <InstructionsLabel /> + {() => ( + <InstructionsLabel /> + )} </ReactCursorPosition> <ActivationChangedLabel {...this.state} /> </div> diff --git a/example/public/on-detected-environment-changed.html b/example/public/on-detected-environment-changed.html index 16137ba3..2e91bfff 100644 --- a/example/public/on-detected-environment-changed.html +++ b/example/public/on-detected-environment-changed.html @@ -32,7 +32,9 @@ this.setState({ detectedEnvironment }); } }}> - <InstructionsLabel /> + {() => ( + <InstructionsLabel /> + )} </ReactCursorPosition> <DetectedEnvironmentChangeLabel {...this.state} /> </div> diff --git a/example/public/on-position-changed.html b/example/public/on-position-changed.html index 1a1fdc30..d82bb999 100644 --- a/example/public/on-position-changed.html +++ b/example/public/on-position-changed.html @@ -36,7 +36,9 @@ className: 'example__target', onPositionChanged: props => this.setState(props) }}> - <InstructionsLabel /> + {() => ( + <InstructionsLabel /> + )} </ReactCursorPosition> <PositionChangedLabel {...this.state} /> </div> diff --git a/example/public/should-decorate-children.html b/example/public/should-decorate-children.html index b34e56d6..6d749c5c 100644 --- a/example/public/should-decorate-children.html +++ b/example/public/should-decorate-children.html @@ -37,8 +37,12 @@ onPositionChanged: props => this.setState(props), shouldDecorateChildren: false }}> - <PositionLabel /> - <InstructionsLabel /> + {() => ( + <div> + <PositionLabel /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> <PositionLabel {...this.state} {...{ className: 'example__external-label', diff --git a/example/public/style.html b/example/public/style.html index 6c905383..6b79371e 100644 --- a/example/public/style.html +++ b/example/public/style.html @@ -29,8 +29,12 @@ return ( <ReactCursorPosition {...{ style }}> - <PositionLabel /> - <InstructionsLabel /> + {(cursorProps) => ( + <div> + <PositionLabel {...cursorProps} /> + <InstructionsLabel /> + </div> + )} </ReactCursorPosition> ); } diff --git a/example/src/components/ActivationByClick.js b/example/src/components/ActivationByClick.js index a4f91294..4518bf1a 100644 --- a/example/src/components/ActivationByClick.js +++ b/example/src/components/ActivationByClick.js @@ -7,10 +7,13 @@ export default () => ( className="example__target" activationInteractionMouse={INTERACTIONS.CLICK} > - -
- Click to activate. Hover. Then click again to deactivate. -
+ {(cursorProps) => ( +
+ +
+ Click to activate. Hover. Then click again to deactivate. +
+
+ )}
); - diff --git a/example/src/components/ActivationByHover.js b/example/src/components/ActivationByHover.js index 0251a121..02f5aea7 100644 --- a/example/src/components/ActivationByHover.js +++ b/example/src/components/ActivationByHover.js @@ -10,10 +10,14 @@ export default () => ( hoverDelayInMs={250} //default 0 hoverOffDelayInMs={150} //default 0 > - -
- -
+ {(cursorProps) => ( +
+ +
+ +
+
+ )}
); diff --git a/example/src/components/ActivationByPress.js b/example/src/components/ActivationByPress.js index f03e679e..ba5f3a28 100644 --- a/example/src/components/ActivationByPress.js +++ b/example/src/components/ActivationByPress.js @@ -9,10 +9,14 @@ export default () => ( pressDurationInMs={500} //default pressMoveThreshold={5} //default > - -
- Press and hold to activate, then drag -
+ {(cursorProps) => ( +
+ +
+ Press and hold to activate, then drag +
+
+ )}
); diff --git a/example/src/components/ActivationByTap.js b/example/src/components/ActivationByTap.js index 5dbdfaf3..9283043a 100644 --- a/example/src/components/ActivationByTap.js +++ b/example/src/components/ActivationByTap.js @@ -7,10 +7,14 @@ export default () => ( className="example__target" activationInteractionTouch={INTERACTIONS.TAP} > - -
- Tap to activate. Drag. Tap again to deactivate. -
+ {(cursorProps) => ( +
+ +
+ Tap to activate. Drag. Tap again to deactivate. +
+
+ )}
); diff --git a/example/src/components/ActivationByTouch.js b/example/src/components/ActivationByTouch.js index 4f7b0639..24a73bee 100644 --- a/example/src/components/ActivationByTouch.js +++ b/example/src/components/ActivationByTouch.js @@ -7,10 +7,14 @@ export default () => ( className="example__target" activationInteractionTouch={INTERACTIONS.TOUCH} > - -
- Touch and Drag -
+ {(cursorProps) => ( +
+ +
+ Touch and Drag +
+
+ )}
); diff --git a/example/src/components/ActivationChanged.js b/example/src/components/ActivationChanged.js index 39e04ff7..6ec7d68d 100644 --- a/example/src/components/ActivationChanged.js +++ b/example/src/components/ActivationChanged.js @@ -20,7 +20,9 @@ export default class extends React.Component { this.setState({ isActive }); } }}> - + {() => ( + + )}
diff --git a/example/src/components/BasicExample.js b/example/src/components/BasicExample.js index aede0506..bbb42277 100644 --- a/example/src/components/BasicExample.js +++ b/example/src/components/BasicExample.js @@ -10,8 +10,12 @@ export default class extends React.Component { - - + {(cursorProps) => ( +
+ + +
+ )}
); } diff --git a/example/src/components/ClassName.js b/example/src/components/ClassName.js index 4e0cc4c8..c089cddb 100644 --- a/example/src/components/ClassName.js +++ b/example/src/components/ClassName.js @@ -5,7 +5,11 @@ import InstructionsLabel from './InstructionsLabel'; export default () => ( - - + {(cursorProps) => ( +
+ + +
+ )}
); diff --git a/example/src/components/DetectedEnvironmentChanged.js b/example/src/components/DetectedEnvironmentChanged.js index d900e4ab..6c5ba9ef 100644 --- a/example/src/components/DetectedEnvironmentChanged.js +++ b/example/src/components/DetectedEnvironmentChanged.js @@ -20,7 +20,9 @@ export default class extends React.Component { this.setState({ detectedEnvironment }); } }}> - + {() => ( + + )} diff --git a/example/src/components/PositionChanged.js b/example/src/components/PositionChanged.js index 3426dde7..2b4a2844 100644 --- a/example/src/components/PositionChanged.js +++ b/example/src/components/PositionChanged.js @@ -26,7 +26,9 @@ export default class extends React.Component { className: 'example__target', onPositionChanged: props => this.setState(props) }}> - + {() => ( + + )} this.rcp = rcp } > -
- + {(cursorProps) => ( +
+
+ +
+ )}
); diff --git a/example/src/components/ShouldDecorateChildren.js b/example/src/components/ShouldDecorateChildren.js index ec9f56e4..781027c6 100644 --- a/example/src/components/ShouldDecorateChildren.js +++ b/example/src/components/ShouldDecorateChildren.js @@ -28,8 +28,12 @@ export default class extends React.Component { onPositionChanged: (props) => this.setState(props), shouldDecorateChildren: false }}> - - + {() => ( +
+ + +
+ )} - - + {(cursorProps) => ( +
+ + +
+ )} ); } From 6ea17025c3d5d5b50df1b9cd995b08a405bbed86 Mon Sep 17 00:00:00 2001 From: Mike Ragalie Date: Mon, 31 Dec 2018 12:04:52 -0500 Subject: [PATCH 3/5] Implement type definitions --- package.json | 2 ++ src/ReactCursorPosition.d.ts | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/ReactCursorPosition.d.ts diff --git a/package.json b/package.json index 914766e0..246a5684 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "test-ci": "npm run test && npm run coveralls", "coveralls": "npm run test-coverage && cat ./coverage/lcov.info | coveralls" }, + "typings": "./src/ReactCursorPosition.d.ts", "devDependencies": { "babel-cli": "^6.26.0", "babel-core": "^6.26.0", @@ -83,6 +84,7 @@ "react": "~0.14.9 || ^15.0.0 || ^16.0.0" }, "dependencies": { + "@types/react": "^16.7.18", "object-assign": "^4.1.1", "object.omit": "^3.0.0", "prop-types": "^15.6.0" diff --git a/src/ReactCursorPosition.d.ts b/src/ReactCursorPosition.d.ts new file mode 100644 index 00000000..06509e47 --- /dev/null +++ b/src/ReactCursorPosition.d.ts @@ -0,0 +1,58 @@ +import React, { ReactPropTypes } from 'react'; + +interface DetectedEnvironment { + isMouseDetected: boolean; + isTouchDetected: boolean; +} + +interface ElementDimensions { + width: number; + height: number; +} + +interface Position { + x: number; + y: number; +} + +interface CursorPositionGeneratedProps { + detectedEnvironment: DetectedEnvironment; + elementDimensions: ElementDimensions; + isActive: boolean; + isPositionOutside: boolean; + position: Position; +} + +interface CursorPositionOptionsProps { + // TODO: Make these more specific + activationInteractionMouse?: string; + activationInteractionTouch?: string; + children: (CursorPositionOutputProps) => React.ReactElement | null; + className?: string; + hoverDelayInMs?: number; + isEnabled?: boolean; + mapChildProps?: (CursorPositionGeneratedProps) => object; + onActivationChanged?: ({isActive: boolean}) => void; + onDetectedEnvironmentChanged?: (DetectedEnvironment) => void; + onPositionChanged?: (CursorPositionGeneratedProps) => void; + pressDurationInMs?: number; + pressMoveThreshold?: number; + shouldDecorateChildren?: boolean; + shouldStopTouchMovePropagation?: boolean; + style?: object; + tapDurationInMs?: number; + tapMoveThreshold?: number; +} + +export interface CursorPositionOutputProps extends CursorPositionGeneratedProps { + [property:string]: any; +} + +export interface CursorPositionInputProps extends CursorPositionOptionsProps { + [property:string]: any; +} + +declare class ReactCursorPosition extends React.Component { +} + +export default ReactCursorPosition; From 0dfcab49bd140f9c1130937576289f9226530688 Mon Sep 17 00:00:00 2001 From: Mike Ragalie Date: Mon, 31 Dec 2018 15:07:50 -0500 Subject: [PATCH 4/5] Remove `shouldDecorateChildren` option Unnecessary now that we're outputing to a function; just don't do anything with the arguments if you don't want to. --- README.md | 3 - example/public/should-decorate-children.html | 63 --------------- .../src/components/ShouldDecorateChildren.js | 48 ----------- example/src/pages/ShouldDecorateChildren.js | 79 ------------------- example/src/router.js | 2 - src/ReactCursorPosition.d.ts | 1 - src/ReactCursorPosition.js | 18 ++--- test/ReactCursorPosition.spec.js | 14 ---- .../ReactCursorPosition.spec.js.snap | 1 - 9 files changed, 6 insertions(+), 223 deletions(-) delete mode 100644 example/public/should-decorate-children.html delete mode 100644 example/src/components/ShouldDecorateChildren.js delete mode 100644 example/src/pages/ShouldDecorateChildren.js diff --git a/README.md b/README.md index 0cae83ee..3599b66d 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,6 @@ Function receives one parameter with the signature `{ isMouseDetected: Boolean, **pressMoveThreshold** : Number - Amount of movement, in pixels, allowed during press gesture detection. Defaults to 5. -**shouldDecorateChildren** : Boolean - Suppress decoration of child components by -setting this prop false. Defaults to true. - **shouldStopTouchMovePropagation** : Boolean - Stop touchmove event bubbling when react-cursor-position is active. Defaults to false. **style** : Object - Style to be applied to the div rendered by react-cursor-position. diff --git a/example/public/should-decorate-children.html b/example/public/should-decorate-children.html deleted file mode 100644 index 6d749c5c..00000000 --- a/example/public/should-decorate-children.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - Should Decorate Children | Code Example | React Cursor Position - - -
-
-import React from 'react';
-import ReactCursorPosition from 'react-cursor-position';
-
-import PositionLabel from './PositionLabel';
-import InstructionsLabel from './InstructionsLabel';
-
-export default class extends React.Component {
-    constructor(props) {
-         super(props);
-        this.state = {
-            isPositionOutside: true,
-            position: {
-                x: 0,
-                y: 0
-            }
-        }
-    }
-
-    render() {
-        return (
-            <div className="example">
-                <ReactCursorPosition  {...{
-                    className: 'example__target',
-                    onPositionChanged: props => this.setState(props),
-                    shouldDecorateChildren: false
-                }}>
-                    {() => (
-                        <div>
-                            <PositionLabel />
-                            <InstructionsLabel />
-                        </div>
-                    )}
-                </ReactCursorPosition>
-                <PositionLabel {...this.state} {...{
-                    className: 'example__external-label',
-                    shouldShowIsActive: false }
-                }/>
-            </div>
-        );
-    }
-}
-
-
- - - - - diff --git a/example/src/components/ShouldDecorateChildren.js b/example/src/components/ShouldDecorateChildren.js deleted file mode 100644 index 781027c6..00000000 --- a/example/src/components/ShouldDecorateChildren.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; -import PositionLabel from './PositionLabel'; -import InstructionsLabel from './InstructionsLabel'; - - -export default class extends React.Component { - constructor(props) { - super(props); - this.state = { - elementDimensions: { - width: 0, - height: 0 - }, - isPositionOutside: true, - position: { - x: 0, - y: 0 - } - } - } - - render() { - return ( -
- this.setState(props), - shouldDecorateChildren: false - }}> - {() => ( -
- - -
- )} -
- -
- ); - } -} diff --git a/example/src/pages/ShouldDecorateChildren.js b/example/src/pages/ShouldDecorateChildren.js deleted file mode 100644 index dd8216b3..00000000 --- a/example/src/pages/ShouldDecorateChildren.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { Component } from 'react'; -import { - Col, - Grid, - Jumbotron, - Row -} from 'react-bootstrap'; -import Helmet from 'react-helmet'; - -import ShouldDecorateChildren from '../components/ShouldDecorateChildren'; -import Header from '../components/Header'; - -import 'bootstrap/dist/css/bootstrap.css'; -import '../styles/app.css'; - -class ShouldDecorateChildrenPage extends Component { - render() { - return ( -
- -
- - - - -

Should Decorate Children - API Example

- -
- - -
    -
  • - Set shouldDecorateChildren false when you do not want child - components to receive cursor position props -
  • -
  • - Default value is true -
  • -
- - - - -
-
-
- - - - - - -