From 2e60d5b98fbd4d1cee326031835d4dad65b0b9b4 Mon Sep 17 00:00:00 2001 From: Ethan Selzer Date: Sun, 23 Jul 2017 21:18:18 -0700 Subject: [PATCH] V2.2.0: Support Hover Intent + Environment Detection --- .eslintrc | 20 +- README.md | 38 +- example/package.json | 19 +- example/public/hover-delay.html | 38 + .../on-detected-environment-changed.html | 50 + example/src/components/ActivationChanged.js | 4 +- example/src/components/BasicExample.js | 16 +- example/src/components/ClassName.js | 2 +- .../components/DetectedEnvironmentChanged.js | 29 + .../DetectedEnvironmentChangedLabel.js | 16 + example/src/components/Header.js | 12 +- example/src/components/HoverDelay.js | 18 + example/src/components/IsActivatedOnTouch.js | 2 +- example/src/components/MapProps.js | 2 +- example/src/components/PositionChanged.js | 4 +- example/src/components/PositionLabel.js | 12 +- example/src/components/PressDuration.js | 2 +- example/src/components/PressMoveThreshold.js | 2 +- .../src/components/ShouldDecorateChildren.js | 2 +- example/src/components/Style.js | 5 +- example/{ => src}/images/github-logo.png | Bin example/{ => src}/images/large-a.jpg | Bin example/{ => src}/images/npm-logo.png | Bin example/src/pages/ActivationChanged.js | 4 +- example/src/pages/ClassName.js | 6 +- .../src/pages/DetectedEnvironmentChanged.js | 76 + example/src/pages/Home.js | 4 +- example/src/pages/HoverDelay.js | 78 + example/src/pages/HoverOffDelay.js | 73 + example/src/pages/ImageMagnify.js | 4 +- example/src/pages/IsActivatedOnTouch.js | 4 +- example/src/pages/MapProps.js | 4 +- example/src/pages/PositionChanged.js | 4 +- example/src/pages/PressDuration.js | 4 +- example/src/pages/PressMoveThreshold.js | 4 +- example/src/pages/ShouldDecorateChildren.js | 4 +- example/src/pages/Style.js | 4 +- example/src/pages/Support.js | 2 +- example/src/pkg-lnk | 1 + example/src/router.js | 8 +- example/{ => src}/styles/app.css | 23 +- example/{ => src}/styles/header.css | 0 example/yarn.lock | 3539 ++++++++++++----- package.json | 40 +- src/ReactCursorPosition.js | 205 +- src/constants.js | 4 + src/utils/noop.js | 1 + test/ReactCursorPosition.spec.js | 326 +- yarn.lock | 1031 +++-- 49 files changed, 4140 insertions(+), 1606 deletions(-) create mode 100644 example/public/hover-delay.html create mode 100644 example/public/on-detected-environment-changed.html create mode 100644 example/src/components/DetectedEnvironmentChanged.js create mode 100644 example/src/components/DetectedEnvironmentChangedLabel.js create mode 100644 example/src/components/HoverDelay.js rename example/{ => src}/images/github-logo.png (100%) rename example/{ => src}/images/large-a.jpg (100%) rename example/{ => src}/images/npm-logo.png (100%) create mode 100644 example/src/pages/DetectedEnvironmentChanged.js create mode 100644 example/src/pages/HoverDelay.js create mode 100644 example/src/pages/HoverOffDelay.js create mode 120000 example/src/pkg-lnk rename example/{ => src}/styles/app.css (80%) rename example/{ => src}/styles/header.css (100%) create mode 100644 src/constants.js create mode 100644 src/utils/noop.js diff --git a/.eslintrc b/.eslintrc index bdc2c96b..7945674a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,16 +8,16 @@ "rules": { "no-unused-expressions": 0, "chai-friendly/no-unused-expressions": 2, - "import/default": "warn", - "import/export": "warn", - "import/named": "warn", - "import/namespace": "warn", - "import/no-amd": "warn", - "import/no-duplicates": "warn", - "import/no-extraneous-dependencies": "warn", - "import/no-named-as-default": "warn", - "import/no-named-as-default-member": "warn", - "import/no-unresolved": ["warn", { "commonjs": true }], + "import/default": "error", + "import/export": "error", + "import/named": "error", + "import/namespace": "error", + "import/no-amd": "error", + "import/no-duplicates": "error", + "import/no-extraneous-dependencies": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-unresolved": ["error", { "commonjs": true }], "indent": ["error", 4] } } diff --git a/README.md b/README.md index c5d764a1..a2727113 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,6 @@ See the [react-cursor-position demo site](https://ethanselzer.github.io/react-cu Experiment with react-cursor-position [live on CodePen](http://codepen.io/ethanselzer/pen/ryayLK). -## Related Project - -For hover monitoring, please consider [react-hover-observer](https://www.npmjs.com/package/react-hover-observer). -It has a similar interface to react-cursor-position, and can be used in combination with it. - ## Installation ```sh @@ -45,13 +40,17 @@ import ReactCursorPosition from 'react-cursor-position'; ``` -react-cursor-position wraps its children in a div, which cursor and touch position +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: ```JavaScript { + detectedEnvironment: { + isMouseDetected: false, + isTouchDetected: false, + }, elementDimensions: { width: Number, height: Number @@ -66,19 +65,20 @@ Each child component will receive the following props: ``` This structure may be customized by implementing `mapChildProps` API feature. +The information in `detectedEnvironment` is acquired from interaction with this component and will be unset until the first interaction. + ## Props API All props are optional. **className** : String - CSS class name(s) to be applied to the div rendered by react-cursor-position. -**style** : Object - Style to be applied to the div rendered by react-cursor-position. +**hoverDelayInMs** : Number - Amount of time, in milliseconds, to delay hover interaction from activating. Defaults to 0. -**onActivationChanged** : Function - Called when the component is active. -Function receives one parameter with the signature `{ isActive: Boolean }`. +**hoverOffDelayInMs** : Number - Amount of time, in milliseconds, to delay hover off interaciton from deactivating. Defaults to 0. -**onPositionChanged** : Function - Called when cursor or touch position changes. -Function receives one parameter with the signature `{ elementDimensions: { width: Number, height: Number }, isPositionOutside: Boolean, position: { x: Number, y: Number } }`. +**isActivatedOnTouch** : Boolean - Activate immediately on touch. Scrolling may not be possible when scroll +gesture begins on target area. Recommended only when scrolling is not an expected use case. Defaults to false. **mapChildProps** : Function - Model child component props to your custom shape. Function receives one parameter with the signature @@ -86,16 +86,24 @@ Function receives one parameter with the signature It should return an object that is compatible with the props interface of your child components. See [example demo](https://ethanselzer.github.io/react-cursor-position/#/map-child-props). -**shouldDecorateChildren** : Boolean - Suppress decoration of child components by -setting this prop false. Defaults to true. +**onActivationChanged** : Function - Called when the component is active. +Function receives one parameter with the signature `{ isActive: Boolean }`. -**isActivatedOnTouch** : Boolean - Activate immediately on touch. Scrolling may not be possible when scroll -gesture begins on target area. Recommended only when scrolling is not an expected use case. Defaults to false. +**onPositionChanged** : Function - Called when cursor or touch position changes. +Function receives one parameter with the signature `{ elementDimensions: { width: Number, height: Number }, isPositionOutside: Boolean, position: { x: Number, y: Number } }`. + +**onDetectedEnvironmentChanged** : Function - Called when detected environment (mouse or touch) changes. +Function receives one parameter with the signature `{ isMouseDetected: Boolean, isTouchDetected: Boolean }`. **pressDuration** : Number - Milliseconds delay before press gesture is activated. Defaults to 500. **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. + +**style** : Object - Style to be applied to the div rendered by react-cursor-position. + See API Examples section of the [demo site](https://ethanselzer.github.io/react-cursor-position/#/) for more. ## Support diff --git a/example/package.json b/example/package.json index a420acfe..4a62cb26 100644 --- a/example/package.json +++ b/example/package.json @@ -1,21 +1,20 @@ { "name": "react-cursor-position-example", - "version": "0.1.0", + "version": "1.0.0", "homepage": "http://ethanselzer.github.io/react-cursor-position", "devDependencies": { - "react-scripts": "^0.9.5" + "react-scripts": "^1.0.10" }, "dependencies": { "bootstrap": "^3.3.7", - "highlightjs": "^9.10.0", - "prop-types": "^15.5.8", - "react": "^15.5.4", - "react-bootstrap": "^0.31.0", - "react-dom": "^15.5.4", - "react-helmet": "^5.0.3", - "react-image-magnify": "^1.4.8", + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-bootstrap": "^0.31.1", + "react-dom": "^15.6.1", + "react-helmet": "^5.1.3", + "react-image-magnify": "^1.8.0", "react-router": "3.0.5", - "react-syntax-highlighter": "^5.5.2" + "react-syntax-highlighter": "^5.6.2" }, "scripts": { "start": "react-scripts start", diff --git a/example/public/hover-delay.html b/example/public/hover-delay.html new file mode 100644 index 00000000..657eb77b --- /dev/null +++ b/example/public/hover-delay.html @@ -0,0 +1,38 @@ + + + + + + + + + Class Name | 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 () => (
+    <ReactCursorPosition
+        className="example__target"
+        hoverDelayInMs={1000}
+        hoverOffDelayInMs={500}
+    >
+        <PositionLabel />
+        <InstructionsLabel />
+    </ReactCursorPosition>
+);
+
+
+ + + + + diff --git a/example/public/on-detected-environment-changed.html b/example/public/on-detected-environment-changed.html new file mode 100644 index 00000000..16137ba3 --- /dev/null +++ b/example/public/on-detected-environment-changed.html @@ -0,0 +1,50 @@ + + + + + + + + + On Detected Environment Changed | Code Example | React Cursor Position + + +
+
+import React from 'react';
+import ReactCursorPosition from 'react-cursor-position';
+
+import DetectedEnvironmentChangeLabel from './DetectedEnvironmentChangeLabel';
+import InstructionsLabel from './InstructionsLabel';
+
+export default class extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = { detectedEnvironment: {} };
+    }
+
+    render() {
+        return (
+            <div className="example">
+                <ReactCursorPosition  {...{
+                    className: 'example__target',
+                    onDetectedEnvironmentChanged: (detectedEnvironment) => {
+                        this.setState({ detectedEnvironment });
+                    }
+                }}>
+                    <InstructionsLabel />
+                </ReactCursorPosition>
+                <DetectedEnvironmentChangeLabel {...this.state} />
+            </div>
+        );
+    }
+}
+
+
+ + + + + diff --git a/example/src/components/ActivationChanged.js b/example/src/components/ActivationChanged.js index 7ca6d0b1..39e04ff7 100644 --- a/example/src/components/ActivationChanged.js +++ b/example/src/components/ActivationChanged.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import ActivationChangedLabel from './ActivationChangedLabel'; import InstructionsLabel from './InstructionsLabel'; @@ -20,7 +20,7 @@ export default class extends React.Component { this.setState({ isActive }); } }}> - + diff --git a/example/src/components/BasicExample.js b/example/src/components/BasicExample.js index 46524e67..aede0506 100644 --- a/example/src/components/BasicExample.js +++ b/example/src/components/BasicExample.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; @@ -7,14 +7,12 @@ import InstructionsLabel from './InstructionsLabel'; export default class extends React.Component { render() { return ( -
- - - - -
+ + + + ); } } diff --git a/example/src/components/ClassName.js b/example/src/components/ClassName.js index 679ae54d..ad89c1d2 100644 --- a/example/src/components/ClassName.js +++ b/example/src/components/ClassName.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; diff --git a/example/src/components/DetectedEnvironmentChanged.js b/example/src/components/DetectedEnvironmentChanged.js new file mode 100644 index 00000000..d900e4ab --- /dev/null +++ b/example/src/components/DetectedEnvironmentChanged.js @@ -0,0 +1,29 @@ +import React from 'react'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; +import DetectedEnvironmentChangedLabel from './DetectedEnvironmentChangedLabel'; +import InstructionsLabel from './InstructionsLabel'; + +export default class extends React.Component { + constructor(props) { + super(props); + this.state = { + detectedEnvironment: {} + }; + } + + render() { + return ( +
+ { + this.setState({ detectedEnvironment }); + } + }}> + + + +
+ ); + } +} diff --git a/example/src/components/DetectedEnvironmentChangedLabel.js b/example/src/components/DetectedEnvironmentChangedLabel.js new file mode 100644 index 00000000..f39d3ea8 --- /dev/null +++ b/example/src/components/DetectedEnvironmentChangedLabel.js @@ -0,0 +1,16 @@ +import React from 'react'; + +export default (props) => { + const { + detectedEnvironment: { + isMouseDetected = false, + isTouchDetected = false + } = {} + } = props; + return ( +
+ {`isMouseDetected: ${isMouseDetected ? 'true' : 'false'}`}
+ {`isTouchDetected: ${isTouchDetected ? 'true' : 'false'}`} +
+ ); +} diff --git a/example/src/components/Header.js b/example/src/components/Header.js index e5a25a43..0c931874 100644 --- a/example/src/components/Header.js +++ b/example/src/components/Header.js @@ -1,10 +1,10 @@ import React from 'react'; import { MenuItem, Navbar, Nav, NavItem, NavDropdown } from 'react-bootstrap'; -import npmLogo from '../../images/npm-logo.png'; -import githubLogo from '../../images/github-logo.png'; +import npmLogo from '../images/npm-logo.png'; +import githubLogo from '../images/github-logo.png'; -import '../../styles/header.css'; +import '../styles/header.css'; class Navigation extends React.Component { constructor(props) { @@ -45,6 +45,10 @@ class Navigation extends React.Component { return 2.8; case '/on-activation-changed' : return 2.9; + case '/hover-delay' : + return 2.11; + case '/detected-environment-changed' : + return 2.12; case '/image-magnify' : return 3.1; case '/support' : @@ -70,8 +74,10 @@ class Navigation extends React.Component { Home Class Name + Hover Delay Is Activated On Touch Map Child Props + On Detected Environment Changed On Position Changed On Activation Changed Press Duration diff --git a/example/src/components/HoverDelay.js b/example/src/components/HoverDelay.js new file mode 100644 index 00000000..4499c02f --- /dev/null +++ b/example/src/components/HoverDelay.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; +import PositionLabel from './PositionLabel'; +import InstructionsLabel from './InstructionsLabel'; + +export default () => ( + + +
+ +
+
+); + diff --git a/example/src/components/IsActivatedOnTouch.js b/example/src/components/IsActivatedOnTouch.js index 62a0f9fe..aee5862e 100644 --- a/example/src/components/IsActivatedOnTouch.js +++ b/example/src/components/IsActivatedOnTouch.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; export default () => ( diff --git a/example/src/components/MapProps.js b/example/src/components/MapProps.js index cd7c6673..85e48e12 100644 --- a/example/src/components/MapProps.js +++ b/example/src/components/MapProps.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PointPositionLabel from './PointPositionLabel'; import InstructionsLabel from './InstructionsLabel'; diff --git a/example/src/components/PositionChanged.js b/example/src/components/PositionChanged.js index b9b52e42..3426dde7 100644 --- a/example/src/components/PositionChanged.js +++ b/example/src/components/PositionChanged.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import InstructionsLabel from './InstructionsLabel'; import PositionLabel from './PositionLabel'; @@ -26,7 +26,7 @@ export default class extends React.Component { className: 'example__target', onPositionChanged: props => this.setState(props) }}> - + { const { + detectedEnvironment: { + isMouseDetected = false, + isTouchDetected = false + } = {}, elementDimensions: { width = 0, height = 0 @@ -19,9 +23,11 @@ const PositionLabel = (props) => { {`x: ${x}`}
{`y: ${y}`}
{props.shouldShowIsActive && [`isActive: ${isActive}`,
]} - {`Element Width: ${width}`}
- {`Element Height: ${height}`}
- {`isOutside: ${isPositionOutside ? 'true' : 'false'}`} + {`width: ${width}`}
+ {`height: ${height}`}
+ {`isPositionOutside: ${isPositionOutside ? 'true' : 'false'}`}
+ {`isMouseDetected: ${isMouseDetected ? 'true' : 'false'}`}
+ {`isTouchDetected: ${isTouchDetected ? 'true' : 'false'}`} ); }; diff --git a/example/src/components/PressDuration.js b/example/src/components/PressDuration.js index fb6363bf..898e1343 100644 --- a/example/src/components/PressDuration.js +++ b/example/src/components/PressDuration.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; diff --git a/example/src/components/PressMoveThreshold.js b/example/src/components/PressMoveThreshold.js index 495830b3..e261d675 100644 --- a/example/src/components/PressMoveThreshold.js +++ b/example/src/components/PressMoveThreshold.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; diff --git a/example/src/components/ShouldDecorateChildren.js b/example/src/components/ShouldDecorateChildren.js index 73cb473f..ec9f56e4 100644 --- a/example/src/components/ShouldDecorateChildren.js +++ b/example/src/components/ShouldDecorateChildren.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; diff --git a/example/src/components/Style.js b/example/src/components/Style.js index 8b4b3c65..bf46769b 100644 --- a/example/src/components/Style.js +++ b/example/src/components/Style.js @@ -1,12 +1,13 @@ import React from 'react'; -import ReactCursorPosition from '../../../dist/ReactCursorPosition'; +import ReactCursorPosition from '../pkg-lnk/ReactCursorPosition'; import PositionLabel from './PositionLabel'; import InstructionsLabel from './InstructionsLabel'; export default class extends React.Component { render() { const style = { - height: '350px', + height: '250px', + paddingTop: '10px', position: 'relative', border: '1px solid #ccc', borderRadius: '4px', diff --git a/example/images/github-logo.png b/example/src/images/github-logo.png similarity index 100% rename from example/images/github-logo.png rename to example/src/images/github-logo.png diff --git a/example/images/large-a.jpg b/example/src/images/large-a.jpg similarity index 100% rename from example/images/large-a.jpg rename to example/src/images/large-a.jpg diff --git a/example/images/npm-logo.png b/example/src/images/npm-logo.png similarity index 100% rename from example/images/npm-logo.png rename to example/src/images/npm-logo.png diff --git a/example/src/pages/ActivationChanged.js b/example/src/pages/ActivationChanged.js index 4a64eefa..866fae1b 100644 --- a/example/src/pages/ActivationChanged.js +++ b/example/src/pages/ActivationChanged.js @@ -11,7 +11,7 @@ import ActivationChanged from '../components/ActivationChanged'; import Header from '../components/Header'; import 'bootstrap/dist/css/bootstrap.css'; -import '../../styles/app.css'; +import '../styles/app.css'; export default class extends Component { render() { @@ -62,7 +62,7 @@ export default class extends Component { className="example__source-container" style={{height: '490px' }} > -