Commit 72efb4e7 authored by catamphetamine's avatar catamphetamine

Initial commit

parents
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
["@babel/plugin-transform-for-of", { loose: true }],
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties"
],
"env": {
"es6": {
"presets": [
["@babel/preset-env", { modules: false }]
]
}
}
}
\ No newline at end of file
# testing package
/on-scroll-to-*.tgz
# test coverage folder
/coverage/
/.nyc_output/
# npm modules
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
/node_modules/
# npm errors
npm-debug.log
# for OS X users
.DS_Store
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# webpack build target folder
/modules/
/commonjs/
# browser builds
/bundle/
\ No newline at end of file
# git
.gitignore
.gitattributes
# Babel
.babelrc
# testing package
/on-scroll-to-*.tgz
# Travis CI
.travis.yml
# test coverage folder
/coverage/
/.nyc_output/
.nycrc
# npm errors
npm-debug.log
# for OS X users
.DS_Store
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
*.sublime-project
# Rollup is used for bundling
/rollup.config.js
# tests aren't needed for npm
/test/
# Demo.
/bundle/index.html
/bundle/index-react.html
/bundle/style.css
\ No newline at end of file
(The MIT License)
Copyright (c) 2019 @catamphetamine <purecatamphetamine@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# OnScrollTo
A component that triggers an action when it's scrolled to.
Can be used to render a "Load more items on scroll down" component for "infinite scroll" lists.
## Demo
* [DOM](https://catamphetamine.github.io/on-scroll-to/index-dom.html)
* [React](https://catamphetamine.github.io/on-scroll-to)
## Install
```
npm install on-scroll-to --save
```
If you're not using a bundler then use a [standalone version from a CDN](#cdn).
## Use
The default export is the `OnScrollTo` class. It implements the core logic and can be used for building an `OnScrollTo` component for any UI framework. [`on-scroll-to/dom`](#dom) and [`on-scroll-to/react`](#react) are both built upon it so this `OnScrollTo` utility class is a low-level core and is meant to be used by UI framework library authors and not by the end users — the end users should use high-level components like [`on-scroll-to/dom`](#dom) (for pure JS) and [`on-scroll-to/react`](#react) (for React).
```js
import OnScrollTo from 'on-scroll-to'
new OnScrollTo(
getElement,
onScrollTo,
onStateChange,
options
)
```
* `getElement()` function should return the DOM Element which will get the `onScrollTo()` action triggered when it's scrolled to.
* `onScrollTo()` function is the action that will get triggered when the DOM Element is scrolled to.
* `onStateChange(state)` function gets called whenever `OnScrollTo` instance's state changes.
* `options` is an optional argument.
Available `options`:
* `distance` — Is a `number` (in pixels) and by default is equal to screen height meaning that `onScrollTo()` function will get called as soon as the user scrolls down to the component's Y position minus screen height. Can be used to perform some action ahead of time before the user scrolls down to the actual DOM Element. For example, to load next list items before the user scrolls down to the end of the currently shown list items.
When `OnScrollTo` DOM Element is scrolled to the `OnScrollTo` component is "deactivated" and `onScrollTo()` function is called. The `onScrollTo()` function must return either `true` (or a `Promise` resolving to `true`) to re-activate the `OnScrollTo` component or `false` (or a `Promise` resolving to `false`) if the `OnScrollTo` component is no longer needed on the page in which case it disappears (returns nothing when rendered).
The `onScrollTo()` function could look like this:
```js
let page = 0
let list = []
function onScrollTo() {
return http.get('/items', { skip: page * 10, limit: 10 })
.then(({ items, hasMore }) => {
list = list.concat(items)
page++
// If there're more items to load
// then re-enable the `OnScrollTo` component.
// Otherwise, don't render the `OnScrollTo` DOM Element.
return hasMore
})
}
```
The `OnScrollTo` class instance provides methods:
* `onMount()` — Should be called when the `OnScrollTo` component is "mounted" (rendered) on a page.
* `onUnmount()` — Should be called when the `OnScrollTo` component is "unmounted" (removed) from the page.
* `getState()` — Returns `OnScrollTo` instance state.
* `retry()` — Can be used for manually re-activating the `OnScrollTo` component if an `error` happened.
`OnScrollTo` state provides properties:
* `hidden` — Is set to `true` when the `OnScrollTo` component should no longer be rendered.
* `loading` — Is set to `true` when the `OnScrollTo` component is in "disabled" state (`onScrollTo()` returned a `Promise` which hasn't been resolved or rejected yet).
* `error` — Is present if `onScrollTo()` threw an `error` (or returned a `Promise` that rejected with an `error`).
### DOM
This is an example of using `on-scroll-to/dom` component. It's the source code of the [DOM demo](https://catamphetamine.github.io/on-scroll-to/index-dom.html).
```js
import { OnScrollTo } from 'on-scroll-to/dom'
// Renders the "Loading more..." DOM Element.
function render(state) {
// Create `element`.
const element = document.createElement('div')
element.classList.add('load-more')
// Clear container element.
const container = document.getElementById('load-more-items')
while (container.firstChild) {
container.removeChild(container.firstChild)
}
// If there was an error then show it.
if (state.error) {
element.classList.add('load-more--error')
element.textContent = 'Error while loading more items'
}
// If there're no more items then
// `element` is removed from `document`.
else if (state.hidden) {
return
}
// Create `element`.
else {
if (state.loading) {
// May render a spinner animation.
element.textContent = 'Loading more...'
} else {
element.textContent = 'Load more'
}
}
// Insert `element` into `document`.
container.appendChild(element)
// `render()` function must return the DOM Element.
return element
}
const onScrollToComponent = new OnScrollTo(onScrollTo, render)
// Call `.mount()` for the initial render.
// All subsequent renders will be automatic.
onScrollToComponent.mount()
// For "Single Page Apps":
// router.onPageUnload(onScrollToComponent.onUnmount)
```
`OnScrollTo` class constructor receives arguments:
* `onScrollTo()` function is the action that will get triggered when the DOM Element is scrolled to.
* `render(state)` function renders the DOM Element for the `OnScrollTo` component.
* `options` is an optional argument. These are the options for the core `OnScrollTo` class constructor.
`OnScrollTo` instance provides methods:
* `retry()` — Can be used for manually re-activating the `OnScrollTo` component if an `error` happened.
### React
This is an example of using the React `OnScrollTo` component. It's the source code of the [React demo](https://catamphetamine.github.io/on-scroll-to).
```js
import React from 'react'
import PropTypes from 'prop-types'
import OnScrollTo from 'on-scroll-to/react'
function Example() {
return (
<OnScrollTo
onScrollTo={onScrollTo}
component={LoadMoreItemsOnScroll}/>
)
}
Example.propTypes = {
onScrollTo: PropTypes.func.isRequired
}
function LoadMoreItemsOnScroll({
setDOMNode,
loading,
error,
retry
}) {
// May render a spinner animation.
return (
<div ref={setDOMNode} className="load-more">
{error ? 'Error while loading more items' : (loading ? 'Loading more...' : 'Load more')}
</div>
)
}
LoadMoreItemsOnScroll.propTypes = {
setDOMNode: PropTypes.func.isRequired,
loading: PropTypes.bool,
error: PropTypes.any,
retry: PropTypes.func.isRequired
}
ReactDOM.render(
<Example/>,
document.getElementById('root')
)
```
`<OnScrollTo/>` component receives properties:
* `onScrollTo()` — The function that gets called when the component is scrolled to.
* `component` — A React component for the `OnScrollTo` DOM Element.
* `distance` — (optional) The `distance` option for the core `OnScrollTo` class constructor.
`component` receives properties:
* `setDOMNode()` — Should be passed as `ref` to the root DOM Element of the component.
* `loading` — (optional) Will be `true` if `onScrollTo()` returned a `Promise` which hasn't been resolved or rejected yet.
* `error` — (optional) If `onScrollTo()` throws an `error` (or rejects with an `error`) then the `error` property will be passed.
* `retry()` — Can be used for manually re-activating the `OnScrollTo` component if an `error` happened.
## Debug
Set `window.OnScrollToDebug` to `true` to output debug messages to `console`.
## CDN
One can use any npm CDN service, e.g. [unpkg.com](https://unpkg.com) or [jsdelivr.net](https://jsdelivr.net)
```html
<!-- Core. -->
<script src="https://unpkg.com/on-scroll-to@1.x/bundle/on-scroll-to.js"></script>
<script>
new OnScrollTo(...)
</script>
<!-- DOM component. -->
<script src="https://unpkg.com/on-scroll-to@1.x/bundle/on-scroll-to-dom.js"></script>
<script>
new OnScrollTo(...)
</script>
<!-- React component. -->
<script src="https://unpkg.com/on-scroll-to@1.x/bundle/on-scroll-to-react.js"></script>
<script>
<OnScrollTo .../>
</script>
```
## License
[MIT](LICENSE)
\ No newline at end of file
'use strict'
exports = module.exports = require('../commonjs/DOMOnScrollTo').default
exports['default'] = require('../commonjs/DOMOnScrollTo').default
\ No newline at end of file
export { default } from '../modules/DOMOnScrollTo'
{
"private": true,
"name": "on-scroll-to-dom",
"version": "1.0.0",
"main": "index.commonjs.js",
"module": "index.js",
"sideEffects": [
"*.css"
]
}
\ No newline at end of file
'use strict'
exports = module.exports = require('./commonjs/OnScrollTo').default
exports['default'] = require('./commonjs/OnScrollTo').default
\ No newline at end of file
export { default } from './modules/OnScrollTo'
This diff is collapsed.
{
"name": "on-scroll-to",
"version": "1.0.0",
"description": "A DOM Element that triggers an action when it's scrolled to",
"main": "index.commonjs.js",
"module": "index.js",
"sideEffects": [
"*.css"
],
"dependencies": {},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.0.0",
"@babel/node": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
"@babel/plugin-transform-destructuring": "^7.1.2",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"autoprefixer": "^9.5.0",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.1",
"babel-plugin-istanbul": "^5.1.1",
"cross-env": "^5.1.4",
"npm-run-all": "^1.4.0",
"postcss": "^7.0.14",
"postcss-custom-properties": "^8.0.10",
"rimraf": "^2.5.0",
"rollup": "^1.9.0",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.3",
"rollup-plugin-terser": "^4.0.4"
},
"scripts": {
"test": "echo \"Error: no test specified\"",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"build:browser": "rollup --config rollup.config.js",
"build:clean": "rimraf ./commonjs/**/* ./modules/**/*",
"build:commonjs": "cross-env babel ./source --out-dir ./commonjs --source-maps --ignore *.test.js",
"build:es6": "cross-env BABEL_ENV=es6 babel ./source --out-dir ./modules --source-maps --ignore *.test.js",
"build": "npm-run-all build:clean build:commonjs build:es6 build:browser",
"prepublish": "npm-run-all build test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/catamphetamine/on-scroll-to.git"
},
"keywords": [
"on-scroll-to",
"virtualscroller",
"virtual",
"scroller",
"infinite",
"scroll"
],
"author": "catamphetamine <purecatamphetamine@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/catamphetamine/on-scroll-to/issues"
},
"homepage": "https://github.com/catamphetamine/on-scroll-to#readme"
}
{
"folders":
[
{
"follow_symlinks": true,
"path": ".",
"file_exclude_patterns": ["npm-debug.log", "*.js.map", "package-lock.json"],
"folder_exclude_patterns": ["node_modules", "commonjs", "modules", "bundle"]
}
],
"settings":
{
"tab_size": 2,
"translate_tabs_to_spaces": false,
"ensure_newline_at_eof_on_save": false,
"trim_trailing_white_space_on_save": true
}
}
\ No newline at end of file
'use strict'
exports = module.exports = require('../commonjs/ReactOnScrollTo').default
exports['default'] = require('../commonjs/ReactOnScrollTo').default
\ No newline at end of file
export { default } from '../modules/ReactOnScrollTo'
{
"private": true,
"name": "on-scroll-to-react",
"version": "1.0.0",
"main": "index.commonjs.js",
"module": "index.js",
"sideEffects": [
"*.css"
]
}
\ No newline at end of file
// import json from 'rollup-plugin-json'
// import commonjs from 'rollup-plugin-commonjs'
// import resolve from 'rollup-plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
// const resolveModules = resolve({
// only: ['lodash']
// })
export default [
{
input: 'index.js',
plugins: [
// resolveModules,
// commonjs(),
// json(),
terser()
],
output: {
format: 'umd',
name: 'OnScrollTo',
file: 'bundle/on-scroll-to.js',
sourcemap: true
}
},
{
input: 'dom/index.js',
plugins: [
// resolveModules,
// commonjs(),
// json(),
terser()
],
output: {
format: 'umd',
name: 'OnScrollTo',
file: 'bundle/on-scroll-to-dom.js',
sourcemap: true
}
},
{
input: 'react/index.js',
plugins: [
// resolveModules,
// commonjs(),
// json(),
terser()
],
external: [
'react',
'prop-types'
],
output: {
format: 'umd',
name: 'OnScrollTo',
file: 'bundle/on-scroll-to-react.js',
sourcemap: true,
globals: {
'react': 'React',
'prop-types': 'PropTypes'
}
}
}
]
export function getScrollY() {
// `window.scrollY` is not supported by Internet Explorer.
return window.pageYOffset
}
export function getScreenBounds() {
const height = window.innerHeight
return {
// The first pixel of the viewport.
top: getScrollY(),
// The pixel after the last pixel of the viewport.
bottom: getScrollY() + height,
height
}
}
\ No newline at end of file
import OnScrollTo from './OnScrollTo'
export default class DOMOnScrollTo {
constructor(onTrigger, render, options) {
this._render = render
this.onScrollTo = new OnScrollTo(() => this.element, onTrigger, this.onStateChange, options)
}
retry = () => this.onScrollTo.retry()
onStateChange = (state) => this.render(state)
render(state) {
this.element = this._render(state)
}
mount() {
this.render(this.onScrollTo.getState())
this.onScrollTo.onMount()
}
onUnmount = () => {
this.onScrollTo.onUnmount()
}
}
\ No newline at end of file
import { getScrollY, getScreenBounds } from './DOM'
import log from './log'
export default class OnScrollTo {
constructor(getElement, onScrollTo, onStateChange, options) {
this.getElement = getElement
this._onScrollTo = onScrollTo
this.onStateChange = onStateChange
this.options = { ...options }
if (this.options.distance === undefined) {
// The default trigger distance is the screen height.
this.options.distance = typeof window === 'undefined' ? 0 : window.innerHeight
}
this.state = {}
log('~ Initialize ~')
log('Distance', this.options.distance)
}
onMount() {
this.isMounted = true