Giter Club home page Giter Club logo

react-tappable's Introduction

React-Tappable

Tappable component for React. Abstracts touch events to implement onTap, onPress, and pinch events.

The events mimic their native equivalents as closely as possible:

  • the baseClass (default: Tappable) has -active or -inactive added to it to enable pressed-state styling
  • the pressed state is visually cancelled if the touch moves too far away from the element, but resumes if the touch comes back again
  • when you start scrolling a parent element, the touch event is cancelled
  • if the onPress property is set, it will cancel the touch event after the press happens

When touch events are not supported, it will fall back to mouse events. Keyboard events are also supported, emulating the behaviour of native button controls.

Demo & Examples

Live demo: jedwatson.github.io/react-tappable

To build the examples locally, run:

npm install
gulp dev

Then open localhost:8000 in a browser.

Installation

The easiest way to use React-tappable is to install it from npm.

npm install react-tappable --save

Ensure to include it in your own React build process (using Browserify, etc).

You could also use the standalone build by including dist/react-tappable.js in your page; but, if you do this, make sure you have already included React, and that it is available globally.

Usage

React-tappable generates a React component (defaults to <span>) and binds touch events to it.

To disable default event handling (e.g. scrolling) set the preventDefault prop.

import Tappable from 'react-tappable';

<Tappable onTap={this.handleTapEvent}>Tap me</Tappable>

For a lighter component, you can opt-in to just the features you need:

import Tappable from 'react-tappable/lib/Tappable';
import Pinchable from 'react-tappable/lib/Pinchable';
import TapAndPinchable from 'react-tappable/lib/TapAndPinchable';

<Tappable onTap={this.handleTapEvent}>I respond to Tap events</Tappable>
<Pinchable onPinch={this.handlePinch}>I respond to Pinch events</Pinchable>
<TapAndPinchable onTap={this.handleTapEvent} onPinch={this.handlePinch}>In respond to both!</TapAndPinchable>

The TapAndPinchable component is the default one you get when you just import react-tappable.

Properties

  • activeDelay ms delay before the -active class is added, defaults to 0
  • component component to render, defaults to 'span'
  • classes optional object containing active and inactive class names to apply to the component; useful with css-modules
  • classBase base to use for the active/inactive classes
  • className optional class name for the component
  • moveThreshold px to allow movement before cancelling a tap; defaults to 100
  • pressDelay ms delay before a press event is detected, defaults to 1000
  • pressMoveThreshold px to allow movement before ignoring long presses; defaults to 5
  • preventDefault (boolean) automatically call preventDefault on all events
  • stopPropagation (boolean) automatically call stopPropagation on all events
  • style (object) styles to apply to the component

Special Events

These are the special events implemented by Tappable.

  • onTap fired when touchStart or mouseDown is followed by touchEnd or mouseUp within the moveThreshold
  • onPress fired when a touch is held for the specified ms
  • onPinchStart fired when two fingers land on the screen
  • onPinchMove fired on any movement while two fingers are on screen
  • onPinchEnd fired when less than two fingers are left on the screen, onTouchStart is triggerred, if one touch remains

Pinch Events

Pinch events come with a special object with additional data to actually be more useful than the native events:

  • touches: Array of Objects - {identifier, pageX, pageY} - raw data from the event
  • center: Object - {x, y} - Calculated center between the two touch points
  • angle: Degrees - angle of the line connecting the two touch points to the X-axis
  • distance: Number of pixels - beween the two touch points
  • displacement: Object {x, y} - offset of the center since the pinch began
  • displacementVelocity: Object {x, y} : Pixels/ms - Immediate velocity of the displacement
  • rotation: degrees - delta rotation since the beginning of the gesture
  • rotationVelocity: degrees/millisecond - immediate rotational velocity
  • zoom: Number - Zoom factor - ratio between distance between the two touch points now over initial
  • zoomVelocity: zoomFactor/millisecond - immediate velocity of zooming
  • time: milliseconds since epoch - Timestamp

Known Issues

  • The pinch implementation has not been thoroughly tested
  • Any touch event with 3 three or more touches is completely ignored.

Native Events

The following native event handlers can also be specified.

  • onKeyDown
  • onKeyUp
  • onTouchStart
  • onTouchMove
  • onTouchEnd
  • onMouseDown
  • onMouseUp
  • onMouseMove
  • onMouseOut

Returning false from onKeyDown, onMouseDown, or onTouchStart handlers will prevent Tappable from handling the event.

Changelog

See CHANGES.md

License

Copyright (c) 2017 Jed Watson. MIT

react-tappable's People

Contributors

andorbal avatar andrewsouthpaw avatar dcousens avatar harborhoffer avatar ibirrer avatar jedwatson avatar jossmac avatar keks0r avatar matthieuprat avatar nielsvanmidden avatar nmn avatar nomemoryerror avatar oliviertassinari avatar rudin avatar slorber avatar stuk avatar tomhicks-bsf avatar wbinnssmith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-tappable's Issues

Ability to cancel onTap event when responding with onPress event

In my app, i am listening to both onTap and and onPress event and wants to react differently for both of them. Wondering if there is a way to cancel onTap event when i have already responded on onPress event.

So for eg. when users presses button for a longer time, we wanted to show different options vs when user taps the button, we just respond on the default options.

Currently it seems like it always dispatches onTap event regardless of onPress event.

Key events don't work on MacBook (Safari or Firefox)

I'm sorry if this is a well-known shortcoming of Macs, but I can't find it mentioned in other issues so here goes.

I do not see keydown (or keyup) events on a MacBook Air in Safari or Firefox, even with the online demo at http://jedwatson.github.io/react-tappable/ . I do see key events in Chrome on the same machine, however. (The key events all work fine in Chrome, FF, or IE on Windows of course.)

I do see these events from the Javascript Key Event Tester at http://unixpapa.com/js/testkey.html in both browsers however.

Am I missing something or is this a known issue that I overlooked or bug?

1.0.0

@JedWatson I think we should this by now.
This module is basically stable and in enough usage that we should already be using SEMVER properly.

Lets do it 👍

Mouse events are not effectively blocked

The Tappable component blocks the immediate mouse events that follow the touch events to prevent firing the onTap action twice while still supporting both touch and mouse input types.

However, if the onTap event fires a UI change that immediately places a different tappable component in the place of the original, the mouse events are not correctly blocked on the second tappable causing two onTap events to fire from a single touch.

The blocking method needs to be global to the page to prevent this behaviour.

Touch-to-stop-scroll also fires the tap event

In native mobile apps, touching a scrollable container while momentum scrolling is in effect will immediately stop the scroll momentum, and prevent any tap events from firing on the elements inside the container.

(by scrollable container, I mean an element with -webkit-overflow-scrolling:touch applied)

Currently, react-tappable doesn't know that a momentum scroll is in progress, so it will treat the interaction like a normal tap event (which leads to a really broken user experience).

I'm currently investigating whether we can detect scroll events in any way, and block the tap correctly. No idea (yet) how I'm actually going to fix this though.

Make difference between taps and holds

Tap event fires after release hold (holded for more than 2-5 secs). Is there a way to prevent onTap firing after releasing holds? So adding some time span that determines what should be considered tap vs hold (time between touch start and end).

Thanks a lot!

Question: onTap vs onClick. How to support both correctly?

Hello

It seems that mobile browsers are able to handle click events.
(However, iOS does some strange things see facebook/react#2055)

So what I don't understand is what's the advantage of tap over click exactly?

In an application that should handle both mouse and touch events (responsive websites, or websites for laptops with touch screens), what should I do? use both onClick and onTap at the same time?

browser issue

any events like long press is not working on safari

How to add properties to a Tappable component?

Hi,

Tappable is awesome, really nice out of the box :)

I'm sure this is a ridiculously stupid question, but how do I add properties to a Tappable instance?

In my code I can do something like

<Tappable onPress={this.example} onTap={this.anotherexample} ...etc

and it works perfectly. But I can't work out from the code here on the repository how to add a property - for example; how to set pressDelay to 400ms.

If you (or anyone that views this) can help a noob out that would be very welcome ;)

I'm using React 0.14 with JSX if that helps (and haven't converted to ES6 yet).

Cheers,

Jake

TypeError: ReactDOM.findDOMNode is not a function

I run a simple example of tabbable together with react 0.14 and get this error in the console on tap:

TypeError: ReactDOM.findDOMNode is not a function.
(In 'ReactDOM.findDOMNode(this)', 'ReactDOM.findDOMNode' is undefined)

(initScrollDetection)

I get this if I use and iPhone 6 and safari and connect the phone to my computer and open up Web Inspector and look in the console.

I don't get this error if I'm running the code on my laptop and look in the console.

image

Simple example code (coffee-script):

{map, range} = require 'ramda'
AppView = React.createClass
    displayName: 'AppView'

    render: ->
        div {},
            map @renderItem, range(0, 20)

    renderItem: (i) ->
        Tappable {onTap: @onTap, key: i}, 
            span {}, "Item number #{i}"

    onTap: (e) ->
        console.log 'tap'

Proposal: onTapOutside event

Hi,

I want to implement a touch-friendly dropdown button which opens a submenu on a tap event and should close the submenu if a tap event is triggered somewhere outside of the dropdown button.

Similar modules:

[ASK] Passing data when event onPress Tappable triggered

Hello, i'm trying to pass data from my component to event onPress inside Tappable tag.

renderList = (list.queue.map((data, i) =>
        (<Tappable pressDelay={delay} onPress={this.handleTapEvent.bind(this, data)} key={i}>
          <Link to={`/list/${value.listId}`} style={{ textDecoration: 'none' }}>value.name</Link>
        </Tappable>)
));

but i got warning message from eslint. it says "jsx props should not use .bind"
so how do i bind data to this event onPress? thank you for your attention.

Add optional "style" prop

Tappable is excellent. Thanks for this. Some of the elements I am making tappable need some inline styling. Would be great if you could pass in a style prop that gets merged with the inline styles Tappable is currently injecting. Should I start a pull request?

Consider opt-into inline styles

These should be declared once in the consumer's stylesheet:

body {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.Tappable {
  -webkit-tap-highlight-color: 'rgba(0,0,0,0)';
  cursor: 'pointer';
}

Apart from weighing down the page with inline-styles it makes visually traversing the DOM that much more difficult

Uncaught TypeError: Can't add property context, object is not extensible

Just tried this in the reapp demo, and when I attempt to use the tag I get this. Figured I'd open this to see if you had any idea before I dug in.

I get this super long stack trace (and slowdown in the devtools):

Uncaught TypeError: Can't add property context, object is not extensibleReactCompositeComponent.js:136 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMultiChild.js:187 ReactMultiChild.Mixin.mountChildrenReactDOMComponent.js:251 ReactDOMComponent.Mixin._createContentMarkupReactDOMComponent.js:167 ReactDOMComponent.Mixin.mountComponentReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactCompositeComponent.js:187 ReactCompositeComponentMixin.mountComponentReactPerf.js:66 ReactPerf.measure.wrapperReactReconciler.js:38 ReactReconciler.mountComponentReactMount.js:232 mountComponentIntoNode

"-active" not set if tapping fast on android

I'm using the example at http://jedwatson.github.io/react-tappable/

and when toggling scrolling to ON and clicking the area saying "Touch me" the events getting logged are touchStart, touchEnd, and tap but the active state never seems to be se and the area never turns blue.

If holding the finger there a bit longer, the active state is set after what feels like 300-ish millisecons.

I'm using a HTC one A9 with Andoid 6.0.0 and Chrome 48.0...

  • If toggling scrolling to OFF, I can't reproduce the problem
  • On an iPhone 6 in Safari I can't reproduce the problem

Anyone else noticing this?

Better solution to block click event firing

@jossmac and I have isolated some issues with mobile UIs behaving unexpectedly (focus stolen after tap event, and potentially weird jumping effects with CSSTransitionGroups in WKWebViews) to Tappable, we're pretty sure it is because mobile Safari is still emulating the click event 300ms after the tap event and if there's a potential handler (focus target, etc) under the coordinates that were tapped, it fires.

Tappable currently works around this by preventing double fires but we now have reason to suspect that preventing default in the onTouchEnd method is a better solution, and globally prevents the weirdness.

I'm going to turn that on there and see if we can establish it resolves the issue for good, without introducing any other side effects (this is really hard to reproduce except in real-world scenarios in an app on an iPhone)

Cannot find module 'react-dom'

I updated one of my project with the newest react 0.14.2 and got Error: Cannot find module 'react-dom'.

Here's my gulpfile file:

var gulp = require('gulp'),
    react = require('gulp-react'),
    concat = require('gulp-concat');

function compileJS() {
    gulp.src([
        'node_modules/react/dist/react.js',
        'node_modules/react-dom/dist/react-dom.js',
        'node_modules/react-tappable/dist/react-tappable.min.js',
    ])
    .pipe(concat('app.js'))
    .pipe(gulp.dest('dist'));
}

gulp.task('default', ['js']);

gulp.task('js', function() {
    compileJS();
});

package file:

{
  "name": "tappable issue",
  "devDependencies": {
    "gulp-concat": "^2.6.0",
    "gulp-react": "^3.1.0",
    "react": "^0.14.2",
    "react-dom": "^0.14.2",
    "react-tappable": "^0.7.1"
  }
}

and html file:

<html>
<head>
<script src="dist/app.js"></script>
</head>
<body>
    Tappable test...
</body>
</html>

React and ReactDOM are both available in the browser console.

moveThreshold doesn't take effect on Browser with onMouseUp

Hi,
I found 'moveThreshold' property is only used within touchable devices, when working on desktop browsers with a mouse, 'moveThreshold' doesn't take effect. And when I try to scroll a list with many tappables, onTap tends to be triggered.

onTap should not be triggered when tap occurs to stop momentum scrolling

When an user is scrolling on a mobile there's "momentum scrolling" effect that makes the content continue to scroll even after the touchend event, according to the movement velocity (also called "fling scroll")

Often, the user may want to stop that momentum scrolling by tapping the screen. He does not care what he taps on, he just want to stop scrolling. But unfortunatly when the scrollable content has a lot of onTap listeners, the probability to trigger one of them is high and the user ends up opening unintentionally new popups / menus / content...

Other references on this issue here:
http://stackoverflow.com/questions/27040241/detect-if-a-touchstart-touchend-has-cancelled-a-scroll-momentum-scroll
http://stackoverflow.com/questions/10573080/mobile-safari-vertical-scrolling-how-to-detect-when-the-window-has-stopped-mov

It seems a workaroud has been implemented for FastClick#42
and the commit is here:
ftlabs/fastclick@2a31072

I may have time to work on this as we need it at at work some point, just tell me if you agree with the idea

Does it makes sense to use tappable to implement a button?

I was wondering if this makes any sense to you

     <Tappable
        className={buttonClasses}
        onFocus={::this.handleFocus}
        onBlur={::this.handleBlur}
        onMouseEnter={::this.handleMouseEnter}
        onMouseLeave={::this.handleMouseLeave}
        onTap={::this.handleTap}
        component={asLink ? 'a' : 'button' }
      >
        <span className={styles.content}>
          {label}
        </span>
      </Tappable>

I am basically trying to use the tappable behavior for links and buttons. The problem I'm facing here is that if I set a property like "disabled" on my component I somehow needs to be passed down to the Tappable component. So I'm starting to wonder if this even makes sense.

Should I just use react-tap-event-plugin + a regular link/button? Are there any traps I should be aware of if I do that? I thought react-tappable soved a few issues I would have to solve manually for iOS if I use regular buttons/links.

Make into a mixin?

This thought crossed my mind the other day. I love the fact that as a component it's incredibly flexible in it's use, but building a UI library for external users it would almost be nice to mix it into every component.

I understand I could use a decorator of sorts, but just wondering your thoughts on this.

Also, I am starting to look into overlapping things. Like what happens if I have a draggable element above a tappable one, sometimes I see the tappable one get the event after the draggable one is finished.

Anyway, just thinking out loud. Wonder your thoughts here.

Blur event does't trigger

In case we focus an input field and then tap on tappable element, blur event doesn't trigger. As result input is still focused.

Any way to simulate tap?

I've been trying my custom component based on react-tappable, but simulating tap is quite a PITA. What would you do for simulating tap in unit test?

(Actually, I've read react-tappable and tappable both source and finding it hard to test, by given nature of tappable lib)

EDIT: Sorry, I've just find out both libs have nothing to do with each other ;) But still wondering about testing method tho.

PS: Would you accept unit test PR?

Combination with Swiper

Hi,

I am using touchstone in combination with swiper.js for some swipable subnavigation. I have experienced the problem that on one screen with Tochstone.Link and a swiper screen the combination is causing issues. If I want to initiate a swipe, but start it on a link, I can finish the swipe but the "tap" event is still triggered and I am navigated to the Link target, instead of the swipe target. I know that the tap is aborted when I scroll, but the swipe is not interrupting the tap. I am not sure if this issue can be addressed in react-tappable or If I need to manually write something.

Why is `return false` required?

Per https://github.com/JedWatson/react-tappable#native-events, it seems like you're recommending using return false from an event handler to prevent Tappable from handling the event.

However, it's a common best practice (and one my company enforces in its own codebase) to never return false from an event handler, and to always only explicitly call preventDefault/stopPropagation/stopImmediatePropagation on the event object.

Would it be possible to make Tappable respect preventDefault on the event object such that return false isn't required?

Props not removed from newComponentProps in lib code

In src/getComponent, the properties activeDelay, classBase, and handlers are deleted from the newComponentProps object:
https://github.com/JedWatson/react-tappable/blob/master/src/getComponent.js#L52-L55

However in lib/getComponent, those lines are missing and the 3 properties are left on newComponentProps:
https://github.com/JedWatson/react-tappable/blob/master/lib/getComponent.js#L56-L66

We just updated to React version 15.3.0 and are getting the following warning because those properties aren't removed:
bundle.js:1072 Warning: Unknown props activeDelay, classBase, handlers on <span> tag. Remove these props from the element. For details, see https://fb.me/react-unknown-prop in span (created by Tappable) in Tappable (created by Hotspots)

Tappable Test

Related to #15 -- I was writing a unit test for another component that uses react-tappable, with the following behavior (simplified to isolate react-tappable):

// __tests__/Tappable-test.js

"use strict";

jest.dontMock('react-tappable');
describe('Button', function() {
  it('fires callback when tapped', function() {
    var React = require('react/addons');
    var Tappable = require('react-tappable');
    var TestUtils = React.addons.TestUtils;

    // mock callback
    var callback = jest.genMockFunction();

    // Render a button with the mock callback
    var button = TestUtils.renderIntoDocument(
      <Tappable onTap={callback}></Tappable>
    );

    // Get the node
    var node = TestUtils.findRenderedDOMComponentWithTag(button, 'span').getDOMNode();

    // Fire the touch events
    TestUtils.Simulate.touchStart(node, TestUtils.nativeTouchData(0, 0));
    TestUtils.Simulate.touchEnd(node, TestUtils.nativeTouchData(0, 0));

    expect(callback).toBeCalled();

  });
});

The test case fails with the following trace:

  - Expected Function to be called.
        at Spec.<anonymous> (/vagrant/modules/javascript/www/components/Button/__tests__/Tappable-test.js:23:22)
        at Timer.listOnTimeout [as ontimeout] (timers.js:112:15) 

What am I missing here about why the callback does not fire, specific to touch events? A similar test case with a mouseDown and mouseUp simulated events does pass:

// __tests__/Tappable-click-test.js

"use strict";

jest.dontMock('react-tappable');
describe('Button', function() {
  it('fires callback when tapped', function() {
    var React = require('react/addons');
    var Tappable = require('react-tappable');
    var TestUtils = React.addons.TestUtils;

    // mock callback
    var callback = jest.genMockFunction();

    // Render a button with the mock callback
    var button = TestUtils.renderIntoDocument(
      <Tappable onTap={callback}></Tappable>
    );

    // Get the node
    var node = TestUtils.findRenderedDOMComponentWithTag(button, 'span').getDOMNode();

    // Fire the touch events
    TestUtils.Simulate.mouseDown(node);
    TestUtils.Simulate.mouseUp(node);

    expect(callback).toBeCalled();

  });
});

(I'd be happy to contribute tests in a PR, but want to make sure I understand intended behavior with touch events)

onTap is fired with mouseup event

    onMouseUp: function onMouseUp(event) {
        if (window._blockMouseEvents || !this._mouseDown) return;
        this.processEvent(event);
        this.props.onMouseUp && this.props.onMouseUp(event);
        this.props.onTap && this.props.onTap(event);
        this.endMouseEvent();
    },

onTap fired when touchStart or mouseDown is followed by touchEnd or mouseUp within the moveThreshold

This may be on purpose, but I kinda think it's weird and unexpected.

Couldn't we simply have a onClick instead? What if I want different behavior onTap if it's a touch or mouse event?

I don't expect any tap event to fire at all when the user is using a mouse.

onMouseOut doesn't fire

source:

    onMouseOut: function (event) {
        if (window._blockMouseEvents || !this._mouseDown) return;
        this.processEvent(event);
        this.props.onMouseOut && this.props.onMouseOut(event);
        this.endMouseEvent();
    },

If I have <Tappable otherThings... onMouseOut={callback}/>, the callback will never be fired on mouse out, because !this._mouseDown is false. I think this is unexpected no?

Does is make sense to add pinch controls to the same component?

I'm glad I found react-tappable, as I was gonna write the same thing for use in my project. The common touch controls are handled here.

I have one issue, and one question

  • You don't check for e.touches.length - so technically, a tap would fire even with two fingers on the screen
  • Do you think multi-touch events such as for pinching belong in the same component?
    I think it would make sense to add with the following custom callback events:
  • onPinchStart
  • onPinchMove
  • onPinchEnd

The callback can receive an object with

{
    originalEvent: originalTouchEvent
    zoomRatio: Number as ratio of original size (for example 2 or 0.5)
    displacement: Movement in pixels of the center of the pinch
    rotation: Degrees
}

The rest of the smarts can be handled outside the element, so this should be enough.

Warnings with react v0.15.2

I'm seeing the following warnings when upgrading to React v0.15.2

-Warning: Unknown props `activeDelay`, `classBase`, `handlers` on <div> tag.
-Remove this prop from the element.
-For details, see https://fb.me/react-unknown-prop

Stack trace

-    in div (created by Tappable)
-    in Tappable (created by Menu)
-    in ReactCSSTransitionGroupChild (created by ReactTransitionGroup)
-    in div (created by ReactTransitionGroup)
-    in ReactTransitionGroup (created by ReactCSSTransitionGroup)
-    in ReactCSSTransitionGroup (created by ViewContainer)
-    in ViewContainer (created by Menu)
-    in Menu (created by UserMenu)
-    in UserMenu (created by pure(UserMenu))
-    in pure(UserMenu) (created by connect(pure(UserMenu)))
-    in connect(pure(UserMenu)) (created by App)
-    in div (created by App)
-    in div (created by App)
-    in App (created by provide(App))
-    in provide(App) (created by Transition)
-    in Transition (created by RouterContext)
-    in RouterContext (created by Router)
-    in TransitionContext (created by Router)
-    in Router (created by Routes)
-    in Routes (created by connect(Routes))
-    in connect(Routes) (created by provide(connect(Routes)))
-    in provide(connect(Routes)) (created by withContext(provide(connect(Routes))))
-    in withContext(provide(connect(Routes)))

Impossible to stop propagation between nested Tappable elements

As far as I'm aware, it is impossible to stop propagation to a parent component when there is a child component within it, even if stopPropagation is set to true and you try to manually stop propagation on the event.

Example:

<Tappable onTap={parentCallback}>
    <Tappable onTap={childCallback} stopPropagation={true}>
    <Tappable>
</Tappable>

When clicking on the child Tappable element, both the parent and the child functions are called.

Bug while calling e.preventDefault

I have this: <Tappable onTap={this.onTap}>{element}</Tappable>;

When my listener is:

    onTap: function(e) {
        console.error("tap!",e);
        e.preventDefault();
        this.props.onTap(e);
    },

I get an error:

tap! SyntheticTouchEvent {dispatchConfig: null, dispatchMarker: null, nativeEvent: null, type: null, target: null…} touchable.jsx:22
Uncaught TypeError: Cannot read property 'preventDefault' of null 

It is not the e that is null, but calling e.preventDefault(); calls this internal SyntheticTouchEvent function:

  preventDefault: function() {
    this.defaultPrevented = true;
    var event = this.nativeEvent;
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
    this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
  },

This is related to how React use pooling on Synthetic events and because onTap is called asynchronously un the setState callback

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.