react-hint
Advanced tools
Comparing version 1.3.1 to 2.0.0
{ | ||
"name": "react-hint", | ||
"version": "1.3.1", | ||
"version": "2.0.0", | ||
"license": "MIT", | ||
"description": "Tooltip component for React, Preact, Inferno", | ||
"author": "Vladimir Simonov <slmgc@ya.ru>", | ||
"homepage": "https://github.com/slmgc/react-hint", | ||
"homepage": "https://react-hint.js.org", | ||
"repository": "slmgc/react-hint", | ||
"license": "MIT", | ||
"main": "lib", | ||
"jsnext:main": "src", | ||
"style": "css/index.css", | ||
"main": "lib/index.js", | ||
"module": "src/index.js", | ||
"jsnext:main": "src/index.js", | ||
"scripts": { | ||
"build": "npm run build:css && npm run build:js", | ||
"build:css": "postcss --use autoprefixer src/css/*.css -d css", | ||
"build:js": "nwb build-react-component", | ||
"start": "nwb serve-react-demo --port 8080" | ||
"build": "nwb build", | ||
"clean": "nwb clean", | ||
"start": "nwb serve --no-clear --port 8080 --reload" | ||
}, | ||
"devDependencies": { | ||
"autoprefixer": "6.x", | ||
"nwb": "0.12.x", | ||
"postcss-cli": "2.x", | ||
"primer-buttons": "*", | ||
"react": ">=0.14", | ||
"react-dom": ">=0.14" | ||
"nwb": "0.18.x", | ||
"react": "15.x", | ||
"react-dom": "15.x" | ||
}, | ||
@@ -33,9 +29,11 @@ "files": [ | ||
"keywords": [ | ||
"tooltip", | ||
"react", | ||
"react-tooltip", | ||
"react-component", | ||
"preact", | ||
"inferno" | ||
"hint", | ||
"inferno", | ||
"preact", | ||
"react", | ||
"react-component", | ||
"react-hint", | ||
"react-tooltip", | ||
"tooltip" | ||
] | ||
} |
React-hint | ||
========== | ||
**React-hint** is a small tooltip component for [React](https://github.com/facebook/react) which is developed with simplicity and performance in mind. It also plays nicely with [Preact](https://github.com/developit/preact) and [Inferno](https://github.com/trueadm/inferno). There is a [demo page](https://slmgc.github.io/react-hint/). | ||
[![npm package][npm-badge]][npm] | ||
**React-hint** is a small tooltip component for [React](https://github.com/facebook/react) which is developed with simplicity and performance in mind. It also plays nicely with [Preact](https://github.com/developit/preact) and [Inferno](https://github.com/trueadm/inferno). | ||
![react-hint tooltip](https://raw.githubusercontent.com/slmgc/react-hint/master/demo/react-hint.gif) | ||
@@ -17,57 +20,69 @@ | ||
---------- | ||
Property/Attribute|Type|Default Value|Description | ||
ReactHint Property|Type|Default Value|Description | ||
:---|:---|:---|:--- | ||
className|String|react-hint|`<ReactHint />` is a singleton component. You can completely override the default tooltip style by passing `className` property with a new base class name. | ||
attribute|String|"data-rh"|Allows to set a custom tooltip attribute instead of a default `data-rh`. | ||
className|String|"react-hint"|You can completely override a tooltip style by passing a `className` property. | ||
delay|Number|0|The default delay before showing a tooltip. | ||
events|Boolean|false|Enables/disables `mouseOver` events. Disabling events is useful in case you want to trigger a tooltip programmatically. | ||
hover|Boolean|false|Enables to hover a mouse cursor over a tooltip. | ||
position|"top", "left", "right", "bottom"|"top"|Allows to customize a default placement of tooltips. | ||
ref|function||You can get a reference to an instance by passing a function which will set it for you, e.g. `<ReactHint ref={(ref) => this.instance = ref} />`. This might be needed to programmatically trigger a tooltip by calling `this.instance.setState({target})` or update it's content by calling `this.instance.forceUpdate()`. | ||
DOM Element Attribute|Type|Default Value|Description | ||
:---|:---|:---|:--- | ||
data-rh|String or #element-id||To show a tooltip on any DOM element and its children add `data-rh` attribute with a tooltip text to the element. Pass `#element-id` instead of a text to show the element's HTML content. | ||
data-rh-at|top, left, right, bottom|top|The default placement of a tooltip is at the top, but you can add `data-rh-at` attribute to change the placement. | ||
data-rh-cls|String||To customize a single tooltip add `data-rh-cls` with a class name which will be added to the tooltip. | ||
data-rh-at|"top", "left", "right", "bottom"|"top"|Allows overriding the default tooltip placement. | ||
```jsx | ||
import React from 'react' | ||
import {render} from 'react-dom' | ||
import ReactHint from 'react-hint' | ||
import {ReactHintFactory} from 'react-hint' | ||
import 'react-hint/css/index.css' | ||
class Demo extends React.Component { | ||
state = {count: 0} | ||
// You can pass any object which contains | ||
// `createElement` & `Component` properties. | ||
// This allows you to pass Inferno/Preact in | ||
// compatibility mode. | ||
const ReactHint = ReactHintFactory(React) | ||
componentDidMount() { | ||
setInterval(() => { | ||
this.setState({count: this.state.count + 1}) | ||
ReactHint.instance.forceUpdate() | ||
}, 1000) | ||
class App extends Component { | ||
toggleCustomHint = ({target}) => { | ||
if (this.instance.state.target) target = null | ||
this.instance.setState({target}) | ||
} | ||
render() { | ||
const {count} = this.state | ||
return ( | ||
<div> | ||
<button data-rh="Default">Default</button> | ||
<button data-rh="Left" data-rh-at="left">Left</button> | ||
<button data-rh="Top" data-rh-at="top">Top</button> | ||
<button data-rh="Bottom" data-rh-at="bottom">Bottom</button> | ||
<button data-rh="Right" data-rh-at="right">Right</button> | ||
<button data-rh={`Count: ${count}`}>Count: {count}</button> | ||
<button data-rh="#custom" data-rh-cls="react-hint--custom">Custom</button> | ||
<ReactHint /> | ||
return <div> | ||
<ReactHint events delay={100} /> | ||
<ReactHint attribute="data-custom" className="custom-hint" | ||
ref={(ref) => this.instance = ref} /> | ||
<div style={{display: 'none'}} id="custom"> | ||
Here goes a custom tooltip.<br /> | ||
You can show <b>HTML</b> content in tooltips. | ||
<img src="//placekitten.com/260/100" /> | ||
</div> | ||
<button data-rh="Default">Default</button> | ||
<button data-rh="Top" data-rh-at="top">Top</button> | ||
<button data-rh="Right" data-rh-at="right">Right</button> | ||
<button data-rh="Bottom" data-rh-at="bottom">Bottom</button> | ||
<button data-rh="Left" data-rh-at="left">Left</button> | ||
<button data-custom="#content" data-custom-at="bottom" | ||
onClick={this.toggleCustomHint}>Click Me</button> | ||
<div id="content" style={{display: 'none'}}> | ||
Here goes a custom tooltip.<br /> | ||
You can show <b>HTML</b> content in tooltips.<br /> | ||
<img data-rh="Cat" data-rh-at="bottom" | ||
src="https://images.pexels.com/photos/20787/pexels-photo.jpg?w=240" /> | ||
</div> | ||
) | ||
</div> | ||
} | ||
} | ||
render(<Demo />, document.getElementById('demo')) | ||
render(<App />, demo) | ||
``` | ||
How to rerender | ||
--------------- | ||
**React-hint** uses [shouldComponentUpdate](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) under the hood to decide if it needs to be updated. You can use `ReactHint.instance.forceUpdate()` in case you want to force an update. | ||
License | ||
------- | ||
MIT | ||
MIT | ||
[npm-badge]: https://img.shields.io/npm/v/react-hint.png | ||
[npm]: https://www.npmjs.org/package/react-hint |
256
src/index.js
@@ -1,162 +0,150 @@ | ||
import React from 'react' | ||
export const ReactHintFactory = ({Component, createElement}) => { | ||
class ReactHint extends Component { | ||
constructor(...args) { | ||
super(...args) | ||
this.state = {target: null} | ||
export default class ReactHint extends React.Component { | ||
static _instance = null | ||
this.shouldComponentUpdate = this.shouldComponentUpdate.bind(this) | ||
this.shallowEqual = this.shallowEqual.bind(this) | ||
this.findHint = this.findHint.bind(this) | ||
this.getHintData = this.getHintData.bind(this) | ||
this.mouseOver = this.mouseOver.bind(this) | ||
this.renderContent = this.renderContent.bind(this) | ||
} | ||
static get instance() { | ||
return ReactHint._instance | ||
} | ||
componentDidMount() { | ||
document.addEventListener('mouseover', this.mouseOver) | ||
} | ||
static set instance(instance) { | ||
if (instance) { | ||
document.addEventListener('mouseover', instance.onHover) | ||
} else { | ||
document.removeEventListener('mouseover', ReactHint.instance.onHover) | ||
componentWillUnmount() { | ||
document.removeEventListener('mouseover', this.mouseOver) | ||
clearTimeout(this._timeout) | ||
} | ||
ReactHint._instance = instance | ||
} | ||
shouldComponentUpdate(props, state) { | ||
return !this.shallowEqual(state, this.state) || | ||
!this.shallowEqual(props, this.props) | ||
} | ||
static defaultProps = { | ||
className: 'react-hint' | ||
} | ||
shallowEqual(a, b) { | ||
const keys = Object.keys(a) | ||
return keys.length === Object.keys(b).length && | ||
keys.reduce((result, key) => result && | ||
(typeof a[key] === 'function' || a[key] === b[key]), true) | ||
} | ||
state = { | ||
target: null, | ||
content: null, | ||
cls: null, | ||
at: 'top', | ||
top: 0, | ||
left: 0 | ||
} | ||
componentDidUpdate() { | ||
if (this.state.target) this.setState(this.getHintData) | ||
} | ||
shouldComponentUpdate({className}, {target, content, cls, at, top, left}) { | ||
const {props, state} = this | ||
return target !== state.target | ||
|| content !== state.content | ||
|| cls !== state.cls | ||
|| at !== state.at | ||
|| top !== state.top | ||
|| left !== state.left | ||
|| className !== props.className | ||
} | ||
findHint(el) { | ||
const {attribute, hover} = this.props | ||
const {target} = this.state | ||
componentDidMount() { | ||
if (ReactHint.instance) ReactHint.instance = null | ||
ReactHint.instance = this | ||
} | ||
while (el) { | ||
if (el === document) break | ||
if (hover && el === this._hint) return target | ||
if (el.hasAttribute(attribute)) return el | ||
el = el.parentNode | ||
} return null | ||
} | ||
componentDidUpdate() { | ||
const {target} = this.state | ||
if (!target) return | ||
getHintData({target}, {attribute, position}) { | ||
const content = target.getAttribute(attribute) || '' | ||
const at = target.getAttribute(`${attribute}-at`) || position | ||
const {top, left, width, height} = target.getBoundingClientRect() | ||
if (!(top || left || width || height)) return | ||
const { | ||
top: containerTop, | ||
left: containerLeft | ||
} = this._container.getBoundingClientRect() | ||
this.setState(this.getHintData(target)) | ||
} | ||
const { | ||
width: hintWidth, | ||
height: hintHeight | ||
} = this._hint.getBoundingClientRect() | ||
componentWillUnmount() { | ||
ReactHint.instance = null | ||
} | ||
const { | ||
top: targetTop, | ||
left: targetLeft, | ||
width: targetWidth, | ||
height: targetHeight | ||
} = target.getBoundingClientRect() | ||
findHint = (el) => { | ||
while (el) { | ||
if (el === document) break | ||
if (el.hasAttribute('data-rh')) return el | ||
if (el === this._hint) return this.state.target | ||
el = el.parentNode | ||
} return null | ||
} | ||
let top, left | ||
switch (at) { | ||
case 'left': | ||
top = targetHeight - hintHeight >> 1 | ||
left = -hintWidth | ||
break | ||
getHintData = (target) => { | ||
const {_container, _hint} = this | ||
const content = target.getAttribute('data-rh') | ||
const cls = target.getAttribute('data-rh-cls') | ||
const at = target.getAttribute('data-rh-at') || 'top' | ||
case 'right': | ||
top = targetHeight - hintHeight >> 1 | ||
left = targetWidth | ||
break | ||
const { | ||
top: container_top, | ||
left: container_left, | ||
} = _container.getBoundingClientRect() | ||
case 'bottom': | ||
top = targetHeight | ||
left = targetWidth - hintWidth >> 1 | ||
break | ||
const { | ||
width: hint_width, | ||
height: hint_height, | ||
} = _hint.getBoundingClientRect() | ||
case 'top': | ||
default: | ||
top = -hintHeight | ||
left = targetWidth - hintWidth >> 1 | ||
} | ||
const { | ||
top: target_top, | ||
left: target_left, | ||
width: target_width, | ||
height: target_height, | ||
} = target.getBoundingClientRect() | ||
return { | ||
content, at, | ||
top: top + targetTop - containerTop, | ||
left: left + targetLeft - containerLeft | ||
} | ||
} | ||
let top, left | ||
switch (at) { | ||
case 'left': | ||
top = target_height - hint_height >> 1 | ||
left = -hint_width | ||
break | ||
mouseOver({target}) { | ||
const {delay, events} = this.props | ||
if (!events) return | ||
case 'right': | ||
top = target_height - hint_height >> 1 | ||
left = target_width | ||
break | ||
clearTimeout(this._timeout) | ||
this._timeout = setTimeout(() => this.setState(() => ({ | ||
target: this.findHint(target) | ||
})), delay) | ||
} | ||
case 'bottom': | ||
top = target_height | ||
left = target_width - hint_width >> 1 | ||
break | ||
case 'top': | ||
default: | ||
top = -hint_height | ||
left = target_width - hint_width >> 1 | ||
renderContent(content) { | ||
if (String(content)[0] === '#') { | ||
const el = document.getElementById(content.slice(1)) | ||
if (el) return createElement('div', { | ||
dangerouslySetInnerHTML: {__html: el.innerHTML} | ||
}) | ||
} return content | ||
} | ||
return { | ||
content, cls, at, | ||
top: top + target_top - container_top, | ||
left: left + target_left - container_left | ||
render() { | ||
const {className} = this.props | ||
const {target, content, at, top, left} = this.state | ||
return createElement('div', { | ||
ref: (ref) => this._container = ref, | ||
style: {position: 'relative'}, | ||
}, target && createElement('div', { | ||
className: `${className} ${className}--${at}`, | ||
ref: (ref) => this._hint = ref, | ||
style: {top, left} | ||
}, createElement('div', { | ||
className: `${className}__content` | ||
}, this.renderContent(content))) | ||
) | ||
} | ||
} | ||
onHover = ({target}) => { | ||
clearTimeout(this.timeout) | ||
this.timeout = setTimeout(() => { | ||
target = this.findHint(target) | ||
this.setState({target}) | ||
}, 100) | ||
ReactHint.defaultProps = { | ||
attribute: 'data-rh', | ||
className: 'react-hint', | ||
delay: 0, | ||
events: false, | ||
hover: false, | ||
position: 'top' | ||
} | ||
setRef = (name, ref) => | ||
this[name] = ref | ||
renderContent = (content) => { | ||
if (content && content[0] === '#') { | ||
const el = document.getElementById(content.slice(1)) | ||
if (el) return <span dangerouslySetInnerHTML={{__html: el.innerHTML}} /> | ||
} return content | ||
} | ||
render() { | ||
const {className} = this.props | ||
const {target, content, cls, at, top, left} = this.state | ||
return ( | ||
<div style={{position: 'relative'}} | ||
ref={this.setRef.bind(this, '_container')}> | ||
{target && | ||
<div className={`${className} ${className}--${at} ${cls}`} | ||
ref={this.setRef.bind(this, '_hint')} | ||
style={{top, left}}> | ||
<div className={`${className}__content`}> | ||
{this.renderContent(content)} | ||
</div> | ||
</div> | ||
} | ||
</div> | ||
) | ||
} | ||
} | ||
return ReactHint | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
3
0
88
9837
5
180
1