Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…React into feat/visibility-direction

# Conflicts:
#	src/behaviors/Visibility/Visibility.js
  • Loading branch information
Alexander Fedyashov committed Sep 22, 2017
2 parents 0adab10 + e2fc8f5 commit 6a6c8bd
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 180 deletions.
170 changes: 96 additions & 74 deletions src/behaviors/Visibility/Visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,27 @@ export default class Visibility extends Component {
}

calculations = {
topPassed: false,
bottomPassed: false,
topVisible: false,
bottomVisible: false,
fits: false,
passing: false,
onScreen: false,
offScreen: false,
onScreen: false,
topPassed: false,
topVisible: false,
}

firedCallbacks = []

// ----------------------------------------
// Lifecycle
// ----------------------------------------

componentWillReceiveProps({ continuous, once }) {
const cleanOut = continuous !== this.props.continuous || once !== this.props.once
if (cleanOut) this.firedCallbacks = []
const cleanHappened = continuous !== this.props.continuous || once !== this.props.once

// Heads up! We should clean up array of happened callbacks, if values of these props are changed
if (cleanHappened) this.firedCallbacks = []
}

componentDidMount() {
Expand All @@ -195,7 +201,8 @@ export default class Visibility extends Component {

this.pageYOffset = window.pageYOffset
context.addEventListener('scroll', this.handleScroll)
if (fireOnMount) this.handleUpdate()

if (fireOnMount) this.update()
}

componentWillUnmount() {
Expand All @@ -205,75 +212,37 @@ export default class Visibility extends Component {
context.removeEventListener('scroll', this.handleScroll)
}

execute = (callback, name) => {
const { continuous, once } = this.props
// ----------------------------------------
// Callback handling
// ----------------------------------------

execute(callback, name) {
const { continuous } = this.props
if (!callback) return
// Reverse callbacks aren't fired continuously
if (this.calculations[name] === false) return

// Always fire callback if continuous = true
if (continuous) {
callback(null, { ...this.props, calculations: this.calculations })
return
}

// If once = true, fire callback only if it wasn't fired before
if (once) {
if (!_.includes(this.firedCallbacks, name)) {
this.firedCallbacks.push(name)
callback(null, { ...this.props, calculations: this.calculations })
}

return
}
// Heads up! When `continuous` is true, callback will be fired always
if (!continuous && _.includes(this.firedCallbacks, name)) return

// Fire callback only if the value changed
if (this.calculations[name] !== this.oldCalculations[name]) {
callback(null, { ...this.props, calculations: this.calculations })
}
callback(null, { ...this.props, calculations: this.calculations })
this.firedCallbacks.push(name)
}

fireCallbacks() {
const {
onBottomPassed,
onBottomPassedReverse,
onBottomVisible,
onBottomVisibleReverse,
onPassing,
onPassingReverse,
onTopPassed,
onTopPassedReverse,
onTopVisible,
onTopVisibleReverse,
onOffScreen,
onOnScreen,
} = this.props
const callbacks = {
bottomPassed: onBottomPassed,
bottomVisible: onBottomVisible,
passing: onPassing,
offScreen: onOffScreen,
onScreen: onOnScreen,
topPassed: onTopPassed,
topVisible: onTopVisible,
}
const reverse = {
bottomPassed: onBottomPassedReverse,
bottomVisible: onBottomVisibleReverse,
passing: onPassingReverse,
topPassed: onTopPassedReverse,
topVisible: onTopVisibleReverse,
}
fire = ({ callback, name }, value, reverse = false) => {
const { continuous, once } = this.props

_.invoke(this.props, 'onUpdate', null, { ...this.props, calculations: this.calculations })
this.fireOnPassed()
// Heads up! For the execution is required:
// - current value correspond to the fired direction
// - `continuous` is true or calculation values are different
const matchesDirection = this.calculations[value] !== reverse
const executionPossible = continuous || this.calculations[value] !== this.oldCalculations[value]

if (matchesDirection && executionPossible) this.execute(callback, name)

_.forEach(callbacks, (callback, name) => this.execute(callback, name))
_.forEach(reverse, (callback, name) => this.execute(callback, name))
// Heads up! We should remove callback from the happened when it's not `once`
if (!once) this.firedCallbacks = _.without(this.firedCallbacks, name)
}

fireOnPassed = () => {
fireOnPassed() {
const { percentagePassed, pixelsPassed } = this.calculations
const { onPassed } = this.props

Expand All @@ -297,14 +266,61 @@ export default class Visibility extends Component {
if (this.ticking) return

this.ticking = true
requestAnimationFrame(this.handleUpdate)
requestAnimationFrame(this.update)
}

handleRef = c => (this.ref = c)

handleUpdate = () => {
update = () => {
this.ticking = false

this.oldCalculations = this.calculations
this.calculations = this.computeCalculations()
this.pageYOffset = window.pageYOffset

const {
onBottomPassed,
onBottomPassedReverse,
onBottomVisible,
onBottomVisibleReverse,
onPassing,
onPassingReverse,
onTopPassed,
onTopPassedReverse,
onTopVisible,
onTopVisibleReverse,
onOffScreen,
onOnScreen,
} = this.props
const forward = {
bottomPassed: { callback: onBottomPassed, name: 'onBottomPassed' },
bottomVisible: { callback: onBottomVisible, name: 'onBottomVisible' },
passing: { callback: onPassing, name: 'onPassing' },
offScreen: { callback: onOffScreen, name: 'onOffScreen' },
onScreen: { callback: onOnScreen, name: 'onOnScreen' },
topPassed: { callback: onTopPassed, name: 'onTopPassed' },
topVisible: { callback: onTopVisible, name: 'onTopVisible' },
}

const reverse = {
bottomPassed: { callback: onBottomPassedReverse, name: 'onBottomPassedReverse' },
bottomVisible: { callback: onBottomVisibleReverse, name: 'onBottomVisibleReverse' },
passing: { callback: onPassingReverse, name: 'onPassingReverse' },
topPassed: { callback: onTopPassedReverse, name: 'onTopPassedReverse' },
topVisible: { callback: onTopVisibleReverse, name: 'onTopVisibleReverse' },
}

_.invoke(this.props, 'onUpdate', null, { ...this.props, calculations: this.calculations })
this.fireOnPassed()

// Heads up! Reverse callbacks should be fired first
_.forEach(reverse, (data, value) => this.fire(data, value, true))
_.forEach(forward, (data, value) => this.fire(data, value))
}

// ----------------------------------------
// Helpers
// ----------------------------------------

computeCalculations() {
const { offset } = this.props
const { bottom, height, top, width } = this.ref.getBoundingClientRect()
const [topOffset, bottomOffset] = normalizeOffset(offset)
Expand All @@ -325,8 +341,7 @@ export default class Visibility extends Component {
const onScreen = (topVisible || topPassed) && !bottomPassed
const offScreen = !onScreen

this.oldCalculations = this.calculations
this.calculations = {
return {
bottomPassed,
bottomVisible,
direction,
Expand All @@ -341,11 +356,18 @@ export default class Visibility extends Component {
topVisible,
width,
}
this.pageYOffset = window.pageYOffset

this.fireCallbacks()
}

// ----------------------------------------
// Refs
// ----------------------------------------

handleRef = c => (this.ref = c)

// ----------------------------------------
// Render
// ----------------------------------------

render() {
const { children } = this.props
const ElementType = getElementType(Visibility, this.props)
Expand Down
2 changes: 1 addition & 1 deletion src/elements/Icon/Icon.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface IconProps {
}

declare class Icon extends React.Component<IconProps, {}> {
Group: typeof IconGroup;
static Group: typeof IconGroup;
}

export default Icon;
Loading

0 comments on commit 6a6c8bd

Please sign in to comment.