From adf544ac50d253420f6c09765e8b05bd1db99e40 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Fri, 10 Aug 2018 14:27:06 -0600 Subject: [PATCH] Add calc fix & friendlier error message --- .eslintrc | 1 + packages/babel-plugin-bridge/index.js | 42 +++++++++++++++++++++++++++ src/ScrollAgent.js | 26 ++++++++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/babel-plugin-bridge/index.js diff --git a/.eslintrc b/.eslintrc index caa272d..acb98c5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ "rules": { "arrow-parens": 0, "import/no-extraneous-dependencies": 0, + "no-underscore-dangle": 0, "react/jsx-filename-extension": 0, "react/destructuring-assignment": 0 } diff --git a/packages/babel-plugin-bridge/index.js b/packages/babel-plugin-bridge/index.js new file mode 100644 index 0000000..de6e41b --- /dev/null +++ b/packages/babel-plugin-bridge/index.js @@ -0,0 +1,42 @@ +const { existsSync, mkdirSync } = require('fs'); +const { resolve } = require('path'); +const { ncp } = require('ncp'); +const { declare } = require('@babel/helper-plugin-utils'); + +module.exports = declare((api, opts) => { + api.assertVersion(7); + + const modules = opts.modules || 'node_modules'; + const out = opts.out || 'dist/modules'; + + return { + visitor: { + ImportDeclaration({ + node: { + source: { value }, + }, + }) { + const outputDir = resolve(process.cwd(), out); + if (!existsSync(outputDir)) mkdirSync(outputDir); + + const isLocalModule = value[0] === '.'; + if (isLocalModule) { + const pathToModule = resolve(__dirname, value); + const outputPath = resolve(process.cwd(), out, value); + console.log(pathToModule, outputPath); + if (!existsSync(outputPath)) { + ncp(pathToModule, outputPath, err => console.error(err)); + } + return; + } + + const pathToModule = resolve(process.cwd(), modules, value); + const outputPath = resolve(process.cwd(), out, value); + console.log(pathToModule, outputPath); + if (!existsSync(pathToModule)) { + ncp(pathToModule, outputPath, err => console.error(err)); + } + }, + }, + }; +}); diff --git a/src/ScrollAgent.js b/src/ScrollAgent.js index 063783e..faa5aa4 100644 --- a/src/ScrollAgent.js +++ b/src/ScrollAgent.js @@ -2,6 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import observeRect from '@reach/observe-rect'; +// Settings +const REFIRE = 500; // Time in ms to refire calc (takes care of reflow that happens after final animationFrame firing) + +// Values const TOP = 'top'; const CENTER = 'center'; const BOTTOM = 'bottom'; @@ -13,6 +17,9 @@ class ScrollAgent extends React.PureComponent { // Memoized container height, to prevent unnecessary recalcs _lastH = -1; + // setTimeout placeholder + _lastRecalc = undefined; + // Ref for scrollspy wrapper = React.createRef(); @@ -68,13 +75,30 @@ class ScrollAgent extends React.PureComponent { if (height > 0 && height !== this._lastH) { this.handleRecalc(); this._lastH = height; + + // After last recalculation, wait 500ms and re-fire. + // This fixes calc issues on longer pages when animationFrame skips. + clearTimeout(this._lastRecalc); + this._lastRecalc = setTimeout(() => this.handleRecalc(), REFIRE); } }; // Handle height recalculation handleRecalc = () => { + let elements = []; + + try { + elements = this.wrapper.current.querySelectorAll(this.props.selector); + } catch (err) { + console.error( + `⚠️ ReactScrollAgent: failed prop \`selector="${ + this.props.selector + }"\`. Must be a valid param for document.querySelectorAll(): https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll` + ); + console.error(err); + } this.setState({ - positions: [...this.wrapper.current.querySelectorAll(this.props.selector)] + positions: [...elements] .map(node => node.getBoundingClientRect().top + window.scrollY) .sort((a, b) => a - b), });