Giter VIP home page Giter VIP logo

react-tether's Introduction

React Tether

CI Status Sauce Test Status

Cross-browser Testing Platform and Open Source <3 Provided by Sauce Labs.


React wrapper around Tether, a positioning engine to make overlays, tooltips and dropdowns better

React Tether

Install

npm install react-tether

As of version 2, a minimum of React 16.3 is required. If you need support for React < 16.3 please use the 1.x branch.

Example Usage

import TetherComponent from "react-tether";

class SimpleDemo extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			isOpen: false,
		};
	}

	render() {
		const { isOpen } = this.state;

		return (
			<TetherComponent
				attachment="top center"
				constraints={[
					{
						to: "scrollParent",
						attachment: "together",
					},
				]}
				/* renderTarget: This is what the item will be tethered to, make sure to attach the ref */
				renderTarget={(ref) => (
					<button
						ref={ref}
						onClick={() => {
							this.setState({ isOpen: !isOpen });
						}}
					>
						Toggle Tethered Content
					</button>
				)}
				/* renderElement: If present, this item will be tethered to the the component returned by renderTarget */
				renderElement={(ref) =>
					isOpen && (
						<div ref={ref}>
							<h2>Tethered Content</h2>
							<p>A paragraph to accompany the title.</p>
						</div>
					)
				}
			/>
		);
	}
}

Props

renderTarget: PropTypes.func

This is a render prop, the component returned from this function will be Tether's target. One argument, ref, is passed into this function. This is a ref that must be attached to the highest possible DOM node in the tree. If this is not done the element will not render.

renderElement: PropTypes.func

This is a render prop, the component returned from this function will be Tether's element, that will be moved. If no component is returned, the target will still render, but with no element tethered. One argument, ref, is passed into this function. This is a ref that must be attached to the highest possible DOM node in the tree. If this is not done the element will not render.

renderElementTag: PropTypes.string

The tag that is used to render the Tether element, defaults to div.

renderElementTo: PropTypes.string

Where in the DOM the Tether element is appended. Passes in any valid selector to document.querySelector. Defaults to document.body.

Tether requires this element to be position: static;, otherwise it will default to document.body. See this example for more information.

Tether Options:

Any valid Tether options.

children:

Previous versions of react-tether used children to render the target and component, using children will now throw an error. Please use renderTarget and renderElement instead

Imperative API

The following methods are exposed on the component instance:

  • getTetherInstance(): Tether
  • disable(): void
  • enable(): void
  • on(event: string, handler: function, ctx: any): void
  • once(event: string, handler: function, ctx: any): void
  • off(event: string, handler: function): void
  • position(): void

Example usage:

<TetherComponent
	ref={(tether) => (this.tether = tether)}
	renderTarget={(ref) => <Target ref={ref} />}
	renderElement={(ref) => (
		<Element ref={ref} onResize={() => this.tether && this.tether.position()} />
	)}
/>

Run Example

clone repo

git clone [email protected]:danreeves/react-tether.git

move into folder

cd ~/react-tether

install dependencies

npm install

run dev mode

npm run demo

open your browser and visit: http://localhost:1234/

react-tether's People

Contributors

anomen avatar basarat avatar corinchappy avatar cseas avatar danreeves avatar doronbrikman avatar dylan-baskind avatar eaglus avatar earthtone0ne avatar flacerdk avatar holmari avatar ilyamkin avatar j3tan avatar jabbypanda avatar jrmyio avatar leabaertschi avatar m7kvqbe1 avatar maxkostow avatar minznerjosh avatar nilset avatar rafeememon avatar reidmit avatar reintroducing avatar renovate[bot] avatar ryprice avatar shw6rn avatar slorber avatar souporserious avatar yasyf avatar zyml 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

react-tether's Issues

Setting `attachment: "top right"` in IE 11 results in `left: 0px`

When using the attachment prop and setting it to top right in Safari and Chrome, this results in the tethered component having css property, right: 0px.

However, in IE 11, this results in left: 0px.

We're having to detect the user's browser and pass in top left in the case of IE 11 in order to get the correct functionality.

renderElementTo not working when specified element is not directly under body

I find that if the render-to-element is a direct child of 'body', it works or else it defaults to the body itself.

     // console.log("tool-tip-container: " + jQuery('#tool-tip-container').length);
        var ElementToolTips = React.createElement(TetherComponent, {
            attachment: "top left",
            targetAttachment: 'top right',
            renderElementTo: document.getElementById('tool-tip-container'),
            key: fakeKey,
            className: fakeKey,
        }, rootAtom, toolTipContainer);

Anyone else has this issue?

Imperative API: expose tether node and tether instance

It would be nice to expose tether instance through the imperative API.

It looks like the imperative api (undocumented) is the non-react methods that don't start with _ , so these are currently not "officially exposed" and it seems bad to use this._reactTetherInstance._tether directly as it does look it could break in the future.

So I think it would be useful to add some additional methods so that we can retrieve these values safely:

  _targetNode = null
  _elementParentNode = null
  _tether = false

My usecase is that I build an onboarding system with hotspots and tooltips. (It's built on react-tether and I'm thinking of opensourcing it)

image

Both the hotspots and the tooltips are tether elements (because they need to have different constraints). The problem is that when the user scroll, the hotspot can become hidden under the top menu. It disappears thanks to the "out-of-bound" class, but the tooltip remains visible which leads to that:

image

So, I need a way to detect that when my hotspot become out of bound (and thus disappears), the tooltip should also hide itself).

This requires both the event-system described here: #28 and some more things exposed described in this issue.

In pseudo-code this would look like that:

handleHotspotReposition() {
   this._tooltip.position();
   if ( this._hotspot.isOutOfBound() ) {
      this._tooltip.hide();
   } 
   else {
      this._tooltip.show();
   } 
}

render() {
  return (
     <TetherComponent
        {...otherAttributes}
        className="hotspot"
        ref={c => this._hotspot = c}
        on={[
          {
            event: "repositioned",
            fn: this.handleHotspotReposition
          }
        ]}
      >
         {target}
         <div className="hotspot"/>
      </TetherComponent>
  )
}

Currently there is no easy way to implement this this._hotspot.isOutOfBound() method. The only thing I can do is something like $(this._hotspotRef).parent().hasClass("tether-element-out-of-bounds"); (it requires addind a ref to my hotspot div, so that I can access the container that has the class, which seems a bit unnatural)
There is also no easy way to add a class to the tooltip. I need the class to be added to the container so I need again the same trick of using ref on the child to access the parent.

Finally, I'd say it could be useful to have high-level methods like "isOutOfBound" or "isPinned" directly in the component imperative API, like in my pseudo code :)

Also, exposing tether instance would have permitted me to use event system of tether even if it's not implemented by component imperative API. It could be useful for any feature that tether expose and the component doesn't (yet).

I'd be interested to implement this, tell me if you would accept a PR and your ideas.

renderElementTo not working?

I've tried a bunch of alternatives but none of them seem to work...

renderElementTo={"#root"}, renderElementTo={document.querySelector('#root')}, renderElementTo={document.getElementById('root')}, ...

The #root element exists during rendering but react-tether is still just putting it in the body. Any suggestions?

Problem with `renderElementTo`

Hello. Can you provide sample props for renderElementTo.
this.refs.calendar, ReactDOM.findDOMNode(this.refs.calendar) not affected to render element?

const renderTo = renderElementTo || document.body
renderTo.appendChild(this._elementParentNode)

I'm using component which use react-tether
Hacker0x01/react-datepicker#434 (comment)

Can you provide simple example whicj works ?

Tether upgrade breaks UglifyJS and other minifiers

This isn't really an issue with react-tether, but it is useful to track potential issues with version 1.0.4+.

The dependency upgrade to the latest tether introduces a problem with running UglifyJS during webpack prod builds (shipshapecode/tether#291). As stated in the related issue, it is actually a bug with the minifiers and not tetheritself, but that does prevent users from adopting the latest version of react-tether. I suggest leaving this issue open for visibility or revert tether to an older version (less ideal).

Tether element's position is error when render in position:absolute div

Hi,
After I upgrade my package to lastest, the position of tether element which append to body is error when I use it in a postion:absolute div(for example, modal)
image

Can you help me please? Thanks.

My package.json

{
  "name": "hrone-react",
  "version": "6.0.0",
  "description": "hrone react",
  "main": "./src/index.js",
  "scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --hot --inline --progress --colors | npm run start:css",
    "start:css": "gulp watch --env=develop",
    "clean": "rimraf ./lib ./coverage",
    "build": "gulp --env=production & npm run clean & babel ./src --out-dir ./lib",
    "check": "npm-check -u",
    "eslint": "eslint ./src/components ./src/extends ./src/stores ./test",
    "build:example": "rimraf ./dist && webpack",
    "test": "cross-env NODE_ENV=test mocha --timeout 20000 --recursive -r test/_setup.js -r babel-core/register -r ignore-styles -r jsdom-global/register",
    "test:watch": "npm test -- --watch",
    "coverage": "nyc npm test && nyc report --reporter=lcov",
    "precommit": "npm run eslint"
  },
  "repository": {
    "type": "git",
    "url": "[email protected]:szgaiaworkscn/hrone-react.git"
  },
  "keywords": [
    "hrone",
    "style",
    "react"
  ],
  "author": "hrone style ui team",
  "bugs": {
    "url": "https://github.com/szgaiaworkscn/hrone-react/issues"
  },
  "homepage": "",
  "nyc": {
    "exclude": [
      "src/lib",
      "test",
      "example",
      "src/js/dragula/index.js"
    ]
  },
  "devDependencies": {
    "autoprefixer": "7.1.6",
    "babel-core": "6.26.0",
    "babel-eslint": "8.0.2",
    "babel-loader": "7.1.2",
    "babel-plugin-transform-decorators-legacy": "1.3.4",
    "babel-plugin-transform-runtime": "6.23.0",
    "babel-plugin-webpack-alias": "2.1.2",
    "babel-polyfill": "6.26.0",
    "babel-preset-es2015": "6.24.1",
    "babel-preset-react": "6.24.1",
    "babel-preset-stage-0": "6.24.1",
    "babel-preset-stage-1": "6.24.1",
    "babel-preset-stage-2": "6.24.1",
    "babel-preset-stage-3": "6.24.1",
    "babel-runtime": "6.26.0",
    "cross-env": "5.1.1",
    "css-loader": "0.28.7",
    "css-mqpacker": "6.0.1",
    "cssgrace": "3.0.0",
    "cssnano": "3.10.0",
    "del": "3.0.0",
    "enzyme": "3.2.0",
    "eslint": "4.11.0",
    "eslint-plugin-jsx-control-statements": "2.2.0",
    "eslint-plugin-react": "7.5.1",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "1.1.5",
    "gulp": "3.9.1",
    "gulp-clean-css": "3.9.0",
    "gulp-connect": "5.0.0",
    "gulp-consolidate": "0.2.0",
    "gulp-iconfont": "9.0.2",
    "gulp-open": "2.0.0",
    "gulp-postcss": "7.0.0",
    "gulp-rename": "1.2.2",
    "gulp-replace": "0.6.1",
    "gulp-rev": "8.1.0",
    "gulp-shell": "0.6.3",
    "gulp-static-hash": "0.1.4",
    "gulp-watch": "4.3.11",
    "html-webpack-plugin": "2.30.1",
    "husky": "0.14.3",
    "ignore-styles": "5.0.1",
    "istanbul": "0.4.5",
    "jsdom": "11.4.0",
    "jsdom-global": "3.0.2",
    "jsx-control-statements": "3.2.8",
    "mobx-react-devtools": "4.2.15",
    "mocha": "4.0.1",
    "node-args": "2.1.8",
    "normalize.css": "7.0.0",
    "npm-install-webpack-plugin": "4.0.5",
    "nyc": "11.3.0",
    "open-browser-webpack-plugin": "0.0.5",
    "postcss-assets": "5.0.0",
    "postcss-calc": "5.0.0",
    "postcss-color-rgba-fallback": "3.0.0",
    "postcss-css-variables": "0.8.0",
    "postcss-custom-media": "6.0.0",
    "postcss-custom-properties": "6.2.0",
    "postcss-custom-selectors": "4.0.1",
    "postcss-each": "0.10.0",
    "postcss-extend": "1.0.5",
    "postcss-for": "2.1.1",
    "postcss-import": "11.0.0",
    "postcss-mixins": "6.2.0",
    "postcss-nested": "2.1.2",
    "postcss-opacity": "5.0.0",
    "postcss-selector-matches": "3.0.1",
    "postcss-selector-not": "3.0.1",
    "postcss-simple-vars": "4.1.0",
    "postcss-urlrev": "2.0.0",
    "react": "16.1.1",
    "react-dom": "16.1.1",
    "rewire": "3.0.2",
    "rimraf": "2.6.2",
    "run-sequence": "2.2.0",
    "simulate-event": "1.4.0",
    "sinon": "4.1.2",
    "style-loader": "0.19.0",
    "stylelint": "8.2.0",
    "uglify-loader": "2.0.0",
    "url-loader": "0.6.2",
    "webpack": "3.8.1",
    "webpack-dev-server": "2.9.4"
  },
  "dependencies": {
    "classlist-polyfill": "1.2.0",
    "classnames": "2.2.5",
    "cleave.js": "1.0.7",
    "contra": "1.9.4",
    "crossvent": "1.5.5",
    "i18next": "10.1.0",
    "jquery": "3.2.1",
    "lodash": "4.17.4",
    "mobx": "3.3.2",
    "mobx-react": "4.3.5",
    "moment": "2.19.2",
    "prop-types": "15.6.0",
    "pubsub-js": "1.5.7",
    "react-addons-test-utils": "15.6.2",
    "react-draggable": "3.0.3",
    "react-highlight-words": "0.10.0",
    "react-i18next": "7.0.0",
    "react-lines-ellipsis": "0.8.1",
    "react-mixin": "4.0.0",
    "react-onclickoutside": "6.7.0",
    "react-spinkit": "3.0.0",
    "react-tether": "0.6.0",
    "react-timeout": "1.0.1",
    "seamless-immutable": "7.1.2",
    "shallow-compare": "1.2.2",
    "uuid": "3.1.0"
  }
}

Thanks for this

Just wanted to say this component made a huge difference for my app. I was having a lot of trouble hooking up top level dropdowns and tooltips in my Redux app (I was passing around a bunch of bounding rects to properly position panels). Using Tether has really cleaned this up by reducing the amount of global knowledge being passed around (target and element being colocated in the code, even if they aren't colocated in the DOM tree).

module export is TetherComponent, not {default: TetherComponent}

It seems that, at least in my app, import TetherComponent from 'react-tether'; doesn't work, (sets TetherComponent to undefined) while import * as TetherComponet from 'react-tether'; works as expected (sets TetherComponent to the TetherComponent react class).

What seems to be happening is that react-tether.js compiles to something like this:

Current behavior

export default from './TetherComponent';
// compiles to this:
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = undefined;
var _TetherComponent = require('./TetherComponent');
var _TetherComponent2 = _interopRequireDefault(_TetherComponent);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = _TetherComponent2.default;
module.exports = exports['default'];

Expected behavior

// Proposed change:
export {default as TetherComponent} from './TetherComponent';
export default from './TetherComponent';
// Compiles to this:
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.TetherComponent = undefined;
var _TetherComponent = require('./TetherComponent');
Object.defineProperty(exports, 'TetherComponent', {
  enumerable: true,
  get: function get() {
    return _interopRequireDefault(_TetherComponent).default;
  }
});
var _TetherComponent2 = _interopRequireDefault(_TetherComponent);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = _TetherComponent2.default;

I'm not sure why, but as you can see, the current code directly exports TetherComponent instead of {default: TetherComponent} which makes it a bit obtuse to import correctly. I think it will be a breaking change though.

targetNode doesn't update when it changes

For example, with a key:

class SimpleDemo extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isOpen: false
    }
  }

  render() {
    const { isOpen } = this.state

    return(
      <TetherComponent
        attachment="top center"
        constraints={[{
          to: 'scrollParent',
          attachment: 'together'
        }]}
      >
        <button key={this.props.k} onClick={() => {this.setState({isOpen: !isOpen})}}>
          Toggle Tethered Content
        </button>
        {
          isOpen &&
          <div>
            <h2>Tethered Content</h2>
            <p>A paragraph to accompany the title.</p>
          </div>
        }
      </TetherComponent>
    )
  }
}

ReactDOM.render(<SimpleDemo k="1"/>, el)
ReactDOM.render(<SimpleDemo k="2"/>, el)

I think _targetNode https://github.com/souporserious/react-tether/blob/master/src/TetherComponent.jsx#L70 should also be assigned in componentDidUpdate

Reapplying tether classes causes CSS animations to restart

I'll preface this by saying that it is not a good pattern for consumers of react-tether to create CSS selectors on library-controlled classes but it sometimes is necessary to apply "directional-specific" classes to bind "caret" css for tooltips and to use custom "expand/collapse" animations for the different positions.

Example:

.tether-element-attached-top .contents {
  animation-name: expand-down;
  animation-duration: 0.5s;
}
.tether-element-attached-bottom .contents {
  animation-name: expand-up;
  animation-duration: 0.5s;
}

It would be preferable if react-tether did not remove tether classes while doing an update so that CSS animations do not restart when the position of the tether has not changed. Currently, the code removes all tether-classes in TetherComponent, see latest code from #189

var classStr = className || '';
if (this._elementParentNode.className !== classStr) {
    this._elementParentNode.className = classStr;
}

Then re-applies the same classes in tether.position() which causes CSS animations to fire again. This behavior was not present (perhaps overlooked) before when a className wasn't passed to the TetherComponent but I suspect this is done to compute the correct bounding boxes for tether positioning.

Old Code:

if (className) {
    this._elementParentNode.className = className;
}

Related CodePen ([email protected]): https://codepen.io/j3tan/pen/exGRPw
Related CodePen ([email protected]): https://codepen.io/j3tan/pen/JjPMxQx

Ideally, tether classes should only be removed/modified if they have changed during the update.

Doesn't work on dynamically added elements

Hello,

First of all, thanks for this library, it's pretty sweet :)

I came across a bug when using TetherElement on dynamically added content. Here is a sample app I've came up with to demonstrate this - https://github.com/Szeliga/tether-react-bug-test-app

The path is like this:

  • click add button
  • a new element appears (1) with no tethered content visible (an empty div is added to body)
  • click add buton again
  • another element appears (2) with no tethered content and for element 1, the tethered content appears
  • and so on

I would like to fix this and submit a PR, but my React knowledge is very slim and I have no idea where to luck for this bug. If you don't have time for fixing this yourself, could you point me in the right direction at least? :)

Best regards,
Szymon

Element is positioned incorrectly with attachment: "together" constraint

Problem
When using the constraint: [{attachment: 'together'}] option, the tether element is positioned incorrectly (on the x-axis). This happens because the elementParentNode created by TetherComponent takes up the whole width of the renderElementTo element and therefore always runs out of room in its parent. The elementParentNode should only be as wide as the element put inside of it.

Suggested Solution
I believe this can be fixed by setting the display on elementParentNode to inline-block

Integration with tether event system

Hi,

Tether has an event system that permits to attach listeners to events. It is however not documented.

It would be useful for me if we could implement this in react-tether. The tether object expose some methods through inheriting an Evented class.

I've hacked a bit the lib and it works fine if we register listeners on mount, like:

<TetherComponent
        attachment={this.props.hotspotPosition}
        targetAttachment={this.props.hotspotPosition}
        className={"onboarding-hotspot-tethered " + this.props.name}
        offset={this.props.hotspotOffset}
        constraints={[{
          to: 'scrollParent',
          attachment: 'none'
        }]}
        on={[
          {
            event: "repositioned",
            fn: this.repositionTooltip
          }
        ]}
      >

I simply added this on componentDidMount:

      (this.props.on || []).forEach(function(onItem) {
        this._tether.on(onItem.event,onItem.fn,onItem.ctx);
      }.bind(this));

Also would be interested to update tether to 1.3.4 that's going to be release soon and introduces a new "repositioned" event that I need.

For context, see:

I'm interested to contribute to this repo as we actively use tether for a long time. This issue is mostly for discussions about the API that we'd like for this feature and I'll find time to make a PR.

I think the simplest is to allow the user to register listeners on mount like I did, but to also expose tether event methods through component imperative API

Reposition the tether component

Hello guys,

I would like to know if there is any plan to allow the re-positioning of the component when the target is moving (without a user action - imagine an animation) ?
I think one way to do it would be to be able to call Tether position function from my react-tether component.

Thanks,
Luc

Suggestion to fix events in tethered element

If, for example you add a button to a tethered element, and click the button, the react event won't be handled because Tether has moved the node out of the React-managed hierarchy to document.body.

I think this can be fixed by:

  • Forget about creating _elementParentNode
  • TetherComponent renders and empty <span/>
  • _elementNode = findDOMNode(this)
  • initialise Tether on _elementNode
  • renderSubtreeIntoContainer into _elementNode

Seems to work in my project where I've added a similar component.

I may have time for a PR if there's interest?

Cheers

body as target

How can I define document.body as the target element? I want to do something like this (http://tether.io/examples/viewport/), but couldn't figure out how to do it other than passing a div as first child and laying it over the whole page.

npm install installs source

I'm still new to npm, but I think that when you install via npm, the src shouldn't be included. It's an easy fix to package.json. Or better yet, leave that as is, but make the main point into the lib file.

Error on unmount if never opened

If the component is rendered into the DOM but never opened, the following error occurs because of calling ReactDOM.unmountComponentAtNode with a null node:

Uncaught Invariant Violation: unmountComponentAtNode(...): Target container is not a DOM element.

Cut a release (0.3.2?)

There are a couple fixes that we'd like to include in react-datepicker, specifically #5, #7 and #9. Please make a new release so we can consume them.

Thanks!

Release 0.5.4 is broken on npm

I'm getting Module not found errors in our build for the react-tether module.
Looking in the package.json, the main entry is located at lib/react-tether.js but the lib folder seems to be missing in the latest release on NPM
@souporserious

Support hot reloading

Hi,

It would be nice if the component could support hot reloading: I change tether offset in my code and after hot reloading I'd like the tooltip/dropdown/hotspot adjusted on the screen.

Ie on hot reload the component could do something:

this._tether.setOptions(tetherOptions)
this._tether.position()

Not sure how this could be done but this may work

Dependency deprecation warning: babel-preset-es2015 (npm)

On registry https://registry.npmjs.org/, the "latest" version (v6.24.1) of dependency babel-preset-es2015 has the following deprecation notice:

๐Ÿ™Œ Thanks for using Babel: we recommend using babel-preset-env now: please read babeljs.io/env to update!

Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.

Please take the actions necessary to rename or substitute this deprecated package and commit to your base branch. If you wish to ignore this deprecation warning and continue using babel-preset-es2015 as-is, please add it to your ignoreDeps array in Renovate config before closing this issue, otherwise another issue will be recreated the next time Renovate runs.

Uncaught TypeError: Cannot read property 'appendChild' of null

We're having an issue in react-datepicker where document.body in the renderElementTo default prop seems to be giving null. I believe this manifests when scripts are declared in the <head> portion of the page where body isn't yet defined, but the null is getting saved to the TetherComponent's props and leads to a Uncaught TypeError: Cannot read property 'appendChild' of null.

This contrasts with https://github.com/souporserious/react-tether/blob/0.1.2/src/Tether.jsx#L18, where document.body is evaluated only when the component is mounted.

Re: Hacker0x01/react-datepicker#354

Make portal usage configurable

Hi, we would love to update the react-tether package in our solution to the latest version, and we love it leveraging portals, but we are facing an issue in specific cases.

In our case, we use tether for extra UI (such as popups for editing links) within the rich text editor powered by https://github.com/facebook/draft-js

It works well with the old version (0.6.1) because the tethered content actually lies outside the editor and its contenteditable in the DOM, and therefore does not interfere with the editor by either native or React events. Thanks to the same fact we are able to "nest" contenteditables within the editor even when the editor doesn't natively support it.

However when a portal is in place, it suddenly let's the Portal events through, making the editor react to events it isn't supposed to react to.

We would ideally like to keep everything outside the editor running by default on portal, but fallback to the old version for anything inside the editor. They don't plan to make event bubbling optional within portal feature facebook/react#11387 and simply do not use portals in case where you don't want it, so the suggestion is that whether portal is used or not could be configurable in react-tether. It would be a fairly easy change making the package backwards compatible for complex scenarios.

I can imagine that the same problem may be with other React-based editors leveraging contenteditable and React events.

Element is positioned incorrectly when flipped horizontally and the body has a scrollbar

When the element needs to flip because it would go off the edge of the screen and the body has a scrollbar, it is positioned incorrectly (it is off by the width of the scrollbar)

withscrollbar

Use the following React component to reproduce:

import React from 'react';
import TetherComponent from 'react-tether';

document.body.style['overflow-y'] = 'scroll';

const TetheredThing = React.createClass({
  getInitialState() {
    return {
      open: false
    };
  },
  onClick() {
    this.setState({open: !this.state.open});
  },
  render() {
    const buttonStyle = {
      position: 'absolute',
      right: '50px',
      height: '100px',
      width: '100px',
      backgroundColor: 'red'
    };

    return (
      <TetherComponent
        attachment="top left"
        targetAttachment="bottom right"
        constraints={[
          {
            to: 'window',
            attachment: 'together'
          }
        ]}>
        <button style={buttonStyle} onClick={this.onClick}>
          {'Click on me'}
        </button>
        {this.state.open ? <div style={{height: '100px', width: '100px', backgroundColor: 'green'}}></div> : undefined}
      </TetherComponent>
    );
  }
});

export default TetheredThing;

When you do not apply the scrollbar to the body, the element is positioned correctly.
withoutscrollbar

Additionally, this problem only happens the first time the element renders. I've modified the component above to demonstrate:

import React from 'react';
import TetherComponent from 'react-tether';

document.body.style['overflow-y'] = 'scroll';

const TetheredThing = React.createClass({
  onClick() {
    this.forceUpdate();
  },
  render() {
    const buttonStyle = {
      position: 'absolute',
      right: '50px',
      height: '100px',
      width: '100px',
      backgroundColor: 'red'
    };

    return (
      <TetherComponent
        attachment="top left"
        targetAttachment="bottom right"
        constraints={[
          {
            to: 'window',
            attachment: 'together'
          }
        ]}>
        <button style={buttonStyle} onClick={this.onClick}>
          {'Click to force update'}
        </button>
        <div style={{height: '100px', width: '100px', backgroundColor: 'green'}}></div>
      </TetherComponent>
    );
  }
});

export default TetheredThing;

New maintainer

I've been pretty busy in other efforts and haven't been able to give this project the time it deserves. Please feel free to comment here if you would like to take the project over. First dibs to @rafeememon if you want it.

react-tether 1.0

  • Tests!
  • Use react portals
  • Support ES Modules & Typescript (with examples/tests)
  • Investigate renderElementTo not working
  • Typescript definitions
  • Prettier
  • Linting (xo)
  • Upgrade all deps!
  • Code coverage report
  • Add renovateapp/greenkeeper
  • Audit files published to npm
  • Drop bower support
  • Flow types + figure out how to distribute them
  • Changelog

Add Flow types

We have TypeScript definitions so this shouldn't be hard to port over.

Merge with react-tether2

Hello,

I've tried using this library but was limited by how the target needs to be a children of the source and also by how the react component is moved in the DOM after rendering.

This led me to write my own wrapper around tether (https://github.com/gabrielbull/react-tether2). If you are interested in merging the two libraries let me know.

Regards,
Gabriel Bull

Example project broken

None of the elements are rendering properly.
Also, can I confirm that the renderElementTo prop also no longer works, and we must instead give TetherComponent two children?
Thanks!

Using the `renderElementTo` prop

Hi there!

I'm using react-tether in react-dates and having some trouble trying to change the location the tether-element renders to. Basically, I'm trying to set up get the tethered version of the datepicker working inside of a portal or modal-like object.

My example set-up is as follows:

const button = <button type="button">Open Portal</button>
return (
  <Portal
    style={{ marginTop: '2px'}}
    openByClickOn={button}
    closeOnOutsideClick
  >
    <div
      className="tetherParent"
      style={{
        background: '#fff',
        padding: '24px',
        margin: 'auto',
      }}
    >
      This portal closes on outside click.
      <DateRangePickerWrapper
        tetherParentSelector=".tetherParent"
      />
    </div>
  </Portal>
);

And then the TetherComponent set-up is here:

<TetherComponent
  classPrefix="DateRangePicker__tether"
  renderElementTo={document.querySelector(tetherParentSelector)}
  attachment={`top ${anchorDirection}`}
  targetAttachment={`bottom ${anchorDirection}`}
  offset="-23px 0"
  constraints={[{
    to: 'scrollParent',
    attachment: 'none',
    pin: [tetherPinDirection],
  }]}
>
  ...
</TetherComponent>

I tried using refs initially, but because those don't exist at the time of render, that's not really possible. I even tried changing the to value of my constraint to document.querySelector(tetherParentSelector), which kind of worked... but didn't actually change the DOM as expected and broke the portal somehow. Do you have any advice?

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.