56 | }>
57 | {this.props.children}
58 |
59 | );
60 | }
61 | }
62 | ```
63 |
64 | ## Respond to scroll events
65 |
66 | If you want to change the appearance in respond to the scrolling position, you could do that like:
67 |
68 | ```javascript
69 | import { Scrollbars } from 'preact-custom-scrollbars';
70 | class CustomScrollbars extends Component {
71 | constructor(props, context) {
72 | super(props, context)
73 | this.state = { top: 0 };
74 | this.handleScrollFrame = this.handleScrollFrame.bind(this);
75 | this.renderView = this.renderView.bind(this);
76 | }
77 |
78 | handleScrollFrame(values) {
79 | const { top } = values;
80 | this.setState({ top });
81 | }
82 |
83 | renderView({ style, ...props }) {
84 | const { top } = this.state;
85 | const color = top * 255;
86 | const customStyle = {
87 | backgroundColor: `rgb(${color}, ${color}, ${color})`
88 | };
89 | return (
90 |
91 | );
92 | }
93 |
94 | render() {
95 | return (
96 |
100 | );
101 | }
102 | }
103 | ```
104 |
105 | Check out these examples for some inspiration:
106 | * [ColoredScrollbars](https://github.com/malte-wessel/preact-custom-scrollbars/tree/master/examples/simple/components/ColoredScrollbars)
107 | * [ShadowScrollbars](https://github.com/malte-wessel/preact-custom-scrollbars/tree/master/examples/simple/components/ShadowScrollbars)
108 |
--------------------------------------------------------------------------------
/docs/upgrade-guide-v2-v3.md:
--------------------------------------------------------------------------------
1 | # Upgrade guide from 2.x to 3.x
2 |
3 | ## Render functions
4 |
5 | ```javascript
6 | // v2.x
7 |
}
9 | renderScrollbarVertical={props =>
}>
10 | {/* */}
11 |
12 |
13 | // v3.x
14 |
}
16 | renderTrackVertical={props =>
}>
17 | {/* */}
18 |
19 | ```
20 |
21 | ## onScroll handler
22 |
23 | ```javascript
24 | // v2.x
25 |
{
27 | // do something with event
28 | // do something with values, animate
29 | }}>
30 | {/* */}
31 |
32 |
33 | // v3.x
34 |
{
36 | // do something with event
37 | }}
38 | onScrollFrame={values => {
39 | // do something with values, animate
40 | // runs inside animation frame
41 | }}>
42 | {/* */}
43 |
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ## Default Scrollbars
4 |
5 | The `
` component works out of the box with some default styles. The only thing you need to care about is that the component has a `width` and `height`:
6 |
7 | ```javascript
8 | import { Scrollbars } from 'preact-custom-scrollbars';
9 |
10 | class App extends Component {
11 | render() {
12 | return (
13 |
14 | Some great content...
15 |
16 | );
17 | }
18 | }
19 | ```
20 |
21 | Also don't forget to set the `viewport` meta tag, if you want to **support mobile devices**
22 |
23 | ```html
24 |
27 | ```
28 |
29 | ## Events
30 |
31 | There are several events you can listen to:
32 |
33 | ```javascript
34 | import { Scrollbars } from 'preact-custom-scrollbars';
35 |
36 | class App extends Component {
37 | render() {
38 | return (
39 |
48 | // Called when ever the component is updated. Runs inside the animation frame
49 | onUpdate={this.handleUpdate}
50 | Some great content...
51 |
52 | );
53 | }
54 | }
55 | ```
56 |
57 |
58 | ## Auto-hide
59 |
60 | You can activate auto-hide by setting the `autoHide` property.
61 |
62 | ```javascript
63 | import { Scrollbars } from 'preact-custom-scrollbars';
64 |
65 | class App extends Component {
66 | render() {
67 | return (
68 |
75 | Some great content...
76 |
77 | );
78 | }
79 | }
80 | ```
81 |
82 | ## Auto-height
83 |
84 | You can active auto-height by setting the `autoHeight` property.
85 | ```javascript
86 | import { Scrollbars } from 'preact-custom-scrollbars';
87 |
88 | class App extends Component {
89 | render() {
90 | return (
91 |
96 | Some great content...
97 |
98 | );
99 | }
100 | }
101 | ```
102 |
103 | ## Universal rendering
104 |
105 | If your app runs on both client and server, activate the `universal` mode. This will ensure that the initial markup on client and server are the same:
106 |
107 | ```javascript
108 | import { Scrollbars } from 'preact-custom-scrollbars';
109 |
110 | class App extends Component {
111 | render() {
112 | return (
113 | // This will activate universal mode
114 |
115 | Some great content...
116 |
117 | );
118 | }
119 | }
120 | ```
121 |
--------------------------------------------------------------------------------
/docs/v2-documentation.md:
--------------------------------------------------------------------------------
1 | # v2.x Documentation
2 | ## Table of Contents
3 |
4 | - [Customization](#customization)
5 | - [API](#api)
6 |
7 | ## Customization
8 | ```javascript
9 | import { Scrollbars } from 'preact-custom-scrollbars';
10 |
11 | class CustomScrollbars extends Component {
12 | render() {
13 | return (
14 |
}
17 | renderScrollbarVertical={props =>
}
18 | renderThumbHorizontal={props =>
}
19 | renderThumbVertical={props =>
}
20 | renderView={props =>
}>
21 | {this.props.children}
22 |
23 | );
24 | }
25 | }
26 |
27 | class App extends Component {
28 | render() {
29 | return (
30 |
31 | Some great content...
32 |
33 | );
34 | }
35 | }
36 | ```
37 |
38 | **NOTE**: If you use `renderScrollbarHorizontal`, **make sure that you define a height value** with css or inline styles. If you use `renderScrollbarVertical`, **make sure that you define a width value with** css or inline styles.
39 |
40 | ## API
41 |
42 | ### ``
43 |
44 | #### Props
45 |
46 | * `renderScrollbarHorizontal`: (Function) Horizontal scrollbar element
47 | * `renderScrollbarVertical`: (Function) Vertical scrollbar element
48 | * `renderThumbHorizontal`: (Function) Horizontal thumb element
49 | * `renderThumbVertical`: (Function) Vertical thumb element
50 | * `renderView`: (Function) The element your content will be rendered in
51 | * `onScroll`: (Function) Event handler. Will be called with the native scroll event and some handy values about the current position.
52 | * **Signature**: `onScroll(event, values)`
53 | * `event`: (Event) Native onScroll event
54 | * `values`: (Object) Values about the current position
55 | * `values.top`: (Number) scrollTop progess, from 0 to 1
56 | * `values.left`: (Number) scrollLeft progess, from 0 to 1
57 | * `values.clientWidth`: (Number) width of the view
58 | * `values.clientHeight`: (Number) height of the view
59 | * `values.scrollWidth`: (Number) native scrollWidth
60 | * `values.scrollHeight`: (Number) native scrollHeight
61 | * `values.scrollLeft`: (Number) native scrollLeft
62 | * `values.scrollTop`: (Number) native scrollTop
63 |
64 | **Don't forget to pass the received props to your custom element. Example:**
65 |
66 | **NOTE**: If you use `renderScrollbarHorizontal`, **make sure that you define a height value** with css or inline styles. If you use `renderScrollbarVertical`, **make sure that you define a width value with** css or inline styles.
67 |
68 | ```javascript
69 | import { Scrollbars } from 'preact-custom-scrollbars';
70 |
71 | class CustomScrollbars extends Component {
72 | render() {
73 | return (
74 |
}
77 | // Customize inline styles
78 | renderScrollbarVertical={({ style, ...props}) => {
79 | return
;
80 | }}>
81 | {this.props.children}
82 |
83 | );
84 | }
85 | }
86 | ```
87 |
88 | #### Methods
89 |
90 | * `scrollTop(top)`: scroll to the top value
91 | * `scrollLeft(left)`: scroll to the left value
92 | * `scrollToTop()`: scroll to top
93 | * `scrollToBottom()`: scroll to bottom
94 | * `scrollToLeft()`: scroll to left
95 | * `scrollToRight()`: scroll to right
96 | * `getScrollLeft`: get scrollLeft value
97 | * `getScrollTop`: get scrollTop value
98 | * `getScrollWidth`: get scrollWidth value
99 | * `getScrollHeight`: get scrollHeight value
100 | * `getWidth`: get view client width
101 | * `getHeight`: get view client height
102 | * `getValues`: get an object with values about the current position.
103 | * `left`, `top`, `scrollLeft`, `scrollTop`, `scrollWidth`, `scrollHeight`, `clientWidth`, `clientHeight`
104 |
105 | ```javascript
106 | import { Scrollbars } from 'preact-custom-scrollbars';
107 |
108 | class App extends Component {
109 | handleClick() {
110 | this.refs.scrollbars.scrollToTop()
111 | },
112 | render() {
113 | return (
114 |
115 |
118 | {/* your content */}
119 |
120 |
121 | Scroll to top
122 |
123 |
124 | );
125 | }
126 | }
127 | ```
128 |
129 | ### Receive values about the current position
130 |
131 | ```javascript
132 | class CustomScrollbars extends Component {
133 | handleScroll(event, values) {
134 | console.log(values);
135 | /*
136 | {
137 | left: 0,
138 | top: 0.21513353115727002
139 | clientWidth: 952
140 | clientHeight: 300
141 | scrollWidth: 952
142 | scrollHeight: 1648
143 | scrollLeft: 0
144 | scrollTop: 290
145 | }
146 | */
147 | }
148 | render() {
149 | return (
150 |
151 | {this.props.children}
152 |
153 | );
154 | }
155 | }
156 | ```
157 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /* eslint no-var: 0, no-unused-vars: 0 */
2 | var path = require('path')
3 | var webpack = require('webpack')
4 | var runCoverage = process.env.COVERAGE === 'true'
5 |
6 | var coverageLoaders = []
7 | var coverageReporters = []
8 |
9 | if (runCoverage) {
10 | coverageLoaders.push({
11 | test: /\.js$/,
12 | include: path.resolve('src/'),
13 | loader: 'isparta',
14 | })
15 | coverageReporters.push('coverage')
16 | }
17 |
18 | module.exports = function karmaConfig(config) {
19 | config.set({
20 | browsers: ['Chrome'],
21 | singleRun: true,
22 | frameworks: ['mocha'],
23 | files: ['./test.js'],
24 | preprocessors: {
25 | './test.js': ['webpack', 'sourcemap'],
26 | },
27 | reporters: ['mocha'].concat(coverageReporters),
28 | webpack: {
29 | devtool: 'inline-source-map',
30 | resolve: {
31 | alias: {
32 | 'preact-custom-scrollbars': path.resolve(__dirname, './src'),
33 | },
34 | },
35 | module: {
36 | loaders: [{
37 | test: /\.js$/,
38 | loader: 'babel',
39 | exclude: /(node_modules)/,
40 | }].concat(coverageLoaders),
41 | },
42 | },
43 | coverageReporter: {
44 | dir: 'coverage/',
45 | reporters: [
46 | { type: 'html', subdir: 'report-html' },
47 | { type: 'text', subdir: '.', file: 'text.txt' },
48 | { type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
49 | ],
50 | },
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preact-custom-scrollbars",
3 | "version": "4.0.2",
4 | "description": "Preact scrollbars component",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "clean": "rimraf lib dist",
8 | "build": "babel src --out-dir lib",
9 | "build:umd": "NODE_ENV=development webpack src/index.js dist/preact-custom-scrollbars.js",
10 | "build:umd:min": "NODE_ENV=production webpack src/index.js dist/preact-custom-scrollbars.min.js",
11 | "lint": "eslint src test examples",
12 | "test": "NODE_ENV=test karma start",
13 | "test:watch": "NODE_ENV=test karma start --auto-watch --no-single-run",
14 | "test:cov": "NODE_ENV=test COVERAGE=true karma start --single-run",
15 | "prepublish": "npm run clean && npm run build && npm run build:umd && npm run build:umd:min && node ./prepublish"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/lucafalasco/preact-custom-scrollbars.git"
20 | },
21 | "keywords": [
22 | "scroll",
23 | "scroller",
24 | "scrollbars",
25 | "preact-component",
26 | "preact",
27 | "custom"
28 | ],
29 | "author": "Luca Falasco",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/lucafalasco/preact-custom-scrollbars/issues"
33 | },
34 | "homepage": "https://github.com/lucafalasco/preact-custom-scrollbars",
35 | "devDependencies": {
36 | "babel-cli": "^6.2.0",
37 | "babel-core": "^6.2.1",
38 | "babel-eslint": "^6.1.2",
39 | "babel-loader": "^6.2.0",
40 | "babel-plugin-transform-react-jsx": "^6.23.0",
41 | "babel-preset-es2015": "^6.1.18",
42 | "babel-preset-latest": "^6.22.0",
43 | "babel-preset-stage-0": "^6.22.0",
44 | "babel-register": "^6.3.13",
45 | "babel-runtime": "^6.3.19",
46 | "es3ify": "^0.2.1",
47 | "eslint": "^2.9.0",
48 | "eslint-config-standard": "^5.3.1",
49 | "eslint-plugin-promise": "^1.3.2",
50 | "eslint-plugin-react": "^6.9.0",
51 | "eslint-plugin-standard": "^1.3.2",
52 | "expect": "^1.6.0",
53 | "glob": "^7.0.0",
54 | "isparta-loader": "^2.0.0",
55 | "karma": "^1.1.1",
56 | "karma-chrome-launcher": "^1.0.1",
57 | "karma-cli": "^1.0.1",
58 | "karma-coverage": "^1.1.0",
59 | "karma-mocha": "^0.2.0",
60 | "karma-mocha-reporter": "^2.0.4",
61 | "karma-sourcemap-loader": "^0.3.6",
62 | "karma-webpack": "^1.6.0",
63 | "mocha": "^2.2.5",
64 | "preact": "^7.1.0",
65 | "preact-dom": "^1.0.1",
66 | "rimraf": "^2.3.4",
67 | "simulant": "^0.2.2",
68 | "webpack": "^1.9.6",
69 | "webpack-dev-server": "^1.8.2"
70 | },
71 | "peerDependencies": {
72 | "preact": "*"
73 | },
74 | "dependencies": {
75 | "dom-css": "^2.0.0",
76 | "raf": "^3.1.0"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/prepublish.js:
--------------------------------------------------------------------------------
1 | var glob = require('glob');
2 | var fs = require('fs');
3 | var es3ify = require('es3ify');
4 |
5 | glob('./@(lib|dist)/**/*.js', function (err, files) {
6 | if (err) throw err;
7 |
8 | files.forEach(function(file) {
9 | fs.readFile(file, 'utf8', function (err, data) {
10 | if (err) throw err;
11 | fs.writeFile(file, es3ify.transform(data), function (err) {
12 | if (err) throw err
13 | console.log('es3ified ' + file);
14 | })
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/Scrollbars/defaultRenderElements.js:
--------------------------------------------------------------------------------
1 | import { h } from 'preact'
2 |
3 | export function renderViewDefault(props) {
4 | return
5 | }
6 |
7 | export function renderTrackHorizontalDefault({ style, ...props }) {
8 | const finalStyle = {
9 | ...style,
10 | right: 2,
11 | bottom: 2,
12 | left: 2,
13 | borderRadius: 3,
14 | }
15 | return
16 | }
17 |
18 | export function renderTrackVerticalDefault({ style, ...props }) {
19 | const finalStyle = {
20 | ...style,
21 | right: 2,
22 | bottom: 2,
23 | top: 2,
24 | borderRadius: 3,
25 | }
26 | return
27 | }
28 |
29 | export function renderThumbHorizontalDefault({ style, ...props }) {
30 | const finalStyle = {
31 | ...style,
32 | cursor: 'pointer',
33 | borderRadius: 'inherit',
34 | backgroundColor: 'rgba(0,0,0,.2)',
35 | }
36 | return
37 | }
38 |
39 | export function renderThumbVerticalDefault({ style, ...props }) {
40 | const finalStyle = {
41 | ...style,
42 | cursor: 'pointer',
43 | borderRadius: 'inherit',
44 | backgroundColor: 'rgba(0,0,0,.2)',
45 | }
46 | return
47 | }
48 |
--------------------------------------------------------------------------------
/src/Scrollbars/index.js:
--------------------------------------------------------------------------------
1 | import raf, { cancel as caf } from 'raf'
2 | import css from 'dom-css'
3 | import { Component, h, cloneElement } from 'preact'
4 | import isString from '../utils/isString'
5 | import getScrollbarWidth from '../utils/getScrollbarWidth'
6 | import returnFalse from '../utils/returnFalse'
7 | import getInnerWidth from '../utils/getInnerWidth'
8 | import getInnerHeight from '../utils/getInnerHeight'
9 |
10 | import {
11 | containerStyleDefault,
12 | containerStyleAutoHeight,
13 | viewStyleDefault,
14 | viewStyleAutoHeight,
15 | viewStyleUniversalInitial,
16 | trackHorizontalStyleDefault,
17 | trackVerticalStyleDefault,
18 | thumbHorizontalStyleDefault,
19 | thumbVerticalStyleDefault,
20 | disableSelectStyle,
21 | disableSelectStyleReset
22 | } from './styles'
23 |
24 | import {
25 | renderViewDefault,
26 | renderTrackHorizontalDefault,
27 | renderTrackVerticalDefault,
28 | renderThumbHorizontalDefault,
29 | renderThumbVerticalDefault
30 | } from './defaultRenderElements'
31 |
32 | export default class Scrollbars extends Component {
33 | static defaultProps = {
34 | renderView: renderViewDefault,
35 | renderTrackHorizontal: renderTrackHorizontalDefault,
36 | renderTrackVertical: renderTrackVerticalDefault,
37 | renderThumbHorizontal: renderThumbHorizontalDefault,
38 | renderThumbVertical: renderThumbVerticalDefault,
39 | tagName: 'div',
40 | thumbMinSize: 30,
41 | hideTracksWhenNotNeeded: false,
42 | autoHide: false,
43 | autoHideTimeout: 1000,
44 | autoHideDuration: 200,
45 | autoHeight: false,
46 | autoHeightMin: 0,
47 | autoHeightMax: 200,
48 | universal: false
49 | }
50 |
51 | refs = {}
52 |
53 | state = {
54 | didMountUniversal: false
55 | }
56 |
57 | componentDidMount = () => {
58 | this.addListeners()
59 | this.update()
60 | this.componentDidMountUniversal()
61 | }
62 |
63 | componentDidMountUniversal () { // eslint-disable-line react/sort-comp
64 | const { universal } = this.props
65 | if (!universal) return
66 | this.setState({ didMountUniversal: true })
67 | }
68 |
69 | componentDidUpdate = () => {
70 | this.update()
71 | }
72 |
73 | componentWillUnmount = () => {
74 | this.unsetDomStyles()
75 | this.removeListeners()
76 | caf(this.requestFrame)
77 | clearTimeout(this.hideTracksTimeout)
78 | clearInterval(this.detectScrollingInterval)
79 | }
80 |
81 | unsetDomStyles () {
82 | const { thumbHorizontal, thumbVertical, trackHorizontal, trackVertical } = this.refs
83 | const stylesReset = {
84 | width: '',
85 | height: '',
86 | transform: '',
87 | opacity: '',
88 | visibility: ''
89 | }
90 | css(thumbVertical, stylesReset)
91 | css(thumbHorizontal, stylesReset)
92 | css(trackVertical, stylesReset)
93 | css(trackHorizontal, stylesReset)
94 | }
95 |
96 | getScrollLeft = () => {
97 | const { view } = this.refs
98 | return view.scrollLeft
99 | }
100 |
101 | getScrollTop = () => {
102 | const { view } = this.refs
103 | return view.scrollTop
104 | }
105 |
106 | getScrollWidth = () => {
107 | const { view } = this.refs
108 | return view.scrollWidth
109 | }
110 |
111 | getScrollHeight = () => {
112 | const { view } = this.refs
113 | return view.scrollHeight
114 | }
115 |
116 | getClientWidth = () => {
117 | const { view } = this.refs
118 | return view.clientWidth
119 | }
120 |
121 | getClientHeight = () => {
122 | const { view } = this.refs
123 | return view.clientHeight
124 | }
125 |
126 | getValues = () => {
127 | const { view } = this.refs
128 | const {
129 | scrollLeft,
130 | scrollTop,
131 | scrollWidth,
132 | scrollHeight,
133 | clientWidth,
134 | clientHeight
135 | } = view
136 |
137 | return {
138 | left: (scrollLeft / (scrollWidth - clientWidth)) || 0,
139 | top: (scrollTop / (scrollHeight - clientHeight)) || 0,
140 | scrollLeft,
141 | scrollTop,
142 | scrollWidth,
143 | scrollHeight,
144 | clientWidth,
145 | clientHeight
146 | }
147 | }
148 |
149 | getThumbHorizontalWidth = () => {
150 | const { thumbSize, thumbMinSize } = this.props
151 | const { view, trackHorizontal } = this.refs
152 | const { scrollWidth, clientWidth } = view
153 | const trackWidth = getInnerWidth(trackHorizontal)
154 | const width = Math.ceil(clientWidth / scrollWidth * trackWidth)
155 | if (trackWidth === width) return 0
156 | if (thumbSize) return thumbSize
157 | return Math.max(width, thumbMinSize)
158 | }
159 |
160 | getThumbVerticalHeight = () => {
161 | const { thumbSize, thumbMinSize } = this.props
162 | const { view, trackVertical } = this.refs
163 | const { scrollHeight, clientHeight } = view
164 | const trackHeight = getInnerHeight(trackVertical)
165 | const height = Math.ceil(clientHeight / scrollHeight * trackHeight)
166 | if (trackHeight === height) return 0
167 | if (thumbSize) return thumbSize
168 | return Math.max(height, thumbMinSize)
169 | }
170 |
171 | getScrollLeftForOffset = (offset) => {
172 | const { view, trackHorizontal } = this.refs
173 | const { scrollWidth, clientWidth } = view
174 | const trackWidth = getInnerWidth(trackHorizontal)
175 | const thumbWidth = this.getThumbHorizontalWidth()
176 | return offset / (trackWidth - thumbWidth) * (scrollWidth - clientWidth)
177 | }
178 |
179 | getScrollTopForOffset = (offset) => {
180 | const { view, trackVertical } = this.refs
181 | const { scrollHeight, clientHeight } = view
182 | const trackHeight = getInnerHeight(trackVertical)
183 | const thumbHeight = this.getThumbVerticalHeight()
184 | return offset / (trackHeight - thumbHeight) * (scrollHeight - clientHeight)
185 | }
186 |
187 | scrollLeft = (left = 0) => {
188 | const { view } = this.refs
189 | view.scrollLeft = left
190 | }
191 |
192 | scrollTop = (top = 0) => {
193 | const { view } = this.refs
194 | view.scrollTop = top
195 | }
196 |
197 | scrollToLeft = () => {
198 | const { view } = this.refs
199 | view.scrollLeft = 0
200 | }
201 |
202 | scrollToTop = () => {
203 | const { view } = this.refs
204 | view.scrollTop = 0
205 | }
206 |
207 | scrollToRight = () => {
208 | const { view } = this.refs
209 | view.scrollLeft = view.scrollWidth
210 | }
211 |
212 | scrollToBottom = () => {
213 | const { view } = this.refs
214 | view.scrollTop = view.scrollHeight
215 | }
216 |
217 | addListeners = () => {
218 | /* istanbul ignore if */
219 | if (typeof document === 'undefined') return
220 | const { view, trackHorizontal, trackVertical, thumbHorizontal, thumbVertical } = this.refs
221 | view.addEventListener('scroll', this.handleScroll)
222 | if (!getScrollbarWidth()) return
223 | trackHorizontal.addEventListener('mouseenter', this.handleTrackMouseEnter)
224 | trackHorizontal.addEventListener('mouseleave', this.handleTrackMouseLeave)
225 | trackHorizontal.addEventListener('mousedown', this.handleHorizontalTrackMouseDown)
226 | trackVertical.addEventListener('mouseenter', this.handleTrackMouseEnter)
227 | trackVertical.addEventListener('mouseleave', this.handleTrackMouseLeave)
228 | trackVertical.addEventListener('mousedown', this.handleVerticalTrackMouseDown)
229 | thumbHorizontal.addEventListener('mousedown', this.handleHorizontalThumbMouseDown)
230 | thumbVertical.addEventListener('mousedown', this.handleVerticalThumbMouseDown)
231 | window.addEventListener('resize', this.handleWindowResize)
232 | }
233 |
234 | removeListeners = () => {
235 | /* istanbul ignore if */
236 | if (typeof document === 'undefined') return
237 | const { view, trackHorizontal, trackVertical, thumbHorizontal, thumbVertical } = this.refs
238 | view.removeEventListener('scroll', this.handleScroll)
239 | if (!getScrollbarWidth()) return
240 | trackHorizontal.removeEventListener('mouseenter', this.handleTrackMouseEnter)
241 | trackHorizontal.removeEventListener('mouseleave', this.handleTrackMouseLeave)
242 | trackHorizontal.removeEventListener('mousedown', this.handleHorizontalTrackMouseDown)
243 | trackVertical.removeEventListener('mouseenter', this.handleTrackMouseEnter)
244 | trackVertical.removeEventListener('mouseleave', this.handleTrackMouseLeave)
245 | trackVertical.removeEventListener('mousedown', this.handleVerticalTrackMouseDown)
246 | thumbHorizontal.removeEventListener('mousedown', this.handleHorizontalThumbMouseDown)
247 | thumbVertical.removeEventListener('mousedown', this.handleVerticalThumbMouseDown)
248 | window.removeEventListener('resize', this.handleWindowResize)
249 | // Possibly setup by `handleDragStart`
250 | this.teardownDragging()
251 | }
252 |
253 | handleScroll = (event) => {
254 | const { onScroll, onScrollFrame } = this.props
255 | if (onScroll) onScroll(event)
256 | this.update(values => {
257 | const { scrollLeft, scrollTop } = values
258 | this.viewScrollLeft = scrollLeft
259 | this.viewScrollTop = scrollTop
260 | if (onScrollFrame) onScrollFrame(values)
261 | })
262 | this.detectScrolling()
263 | }
264 |
265 | handleScrollStart = () => {
266 | const { onScrollStart } = this.props
267 | if (onScrollStart) onScrollStart()
268 | this.handleScrollStartAutoHide()
269 | }
270 |
271 | handleScrollStartAutoHide = () => {
272 | const { autoHide } = this.props
273 | if (!autoHide) return
274 | this.showTracks()
275 | }
276 |
277 | handleScrollStop = () => {
278 | const { onScrollStop } = this.props
279 | if (onScrollStop) onScrollStop()
280 | this.handleScrollStopAutoHide()
281 | }
282 |
283 | handleScrollStopAutoHide = () => {
284 | const { autoHide } = this.props
285 | if (!autoHide) return
286 | this.hideTracks()
287 | }
288 |
289 | handleWindowResize = () => {
290 | this.update()
291 | }
292 |
293 | handleHorizontalTrackMouseDown = (event) => {
294 | event.preventDefault()
295 | const { view } = this.refs
296 | const { target, clientX } = event
297 | const { left: targetLeft } = target.getBoundingClientRect()
298 | const thumbWidth = this.getThumbHorizontalWidth()
299 | const offset = Math.abs(targetLeft - clientX) - thumbWidth / 2
300 | view.scrollLeft = this.getScrollLeftForOffset(offset)
301 | }
302 |
303 | handleVerticalTrackMouseDown = (event) => {
304 | event.preventDefault()
305 | const { view } = this.refs
306 | const { target, clientY } = event
307 | const { top: targetTop } = target.getBoundingClientRect()
308 | const thumbHeight = this.getThumbVerticalHeight()
309 | const offset = Math.abs(targetTop - clientY) - thumbHeight / 2
310 | view.scrollTop = this.getScrollTopForOffset(offset)
311 | }
312 |
313 | handleHorizontalThumbMouseDown = (event) => {
314 | event.preventDefault()
315 | this.handleDragStart(event)
316 | const { target, clientX } = event
317 | const { offsetWidth } = target
318 | const { left } = target.getBoundingClientRect()
319 | this.prevPageX = offsetWidth - (clientX - left)
320 | }
321 |
322 | handleVerticalThumbMouseDown = (event) => {
323 | event.preventDefault()
324 | this.handleDragStart(event)
325 | const { target, clientY } = event
326 | const { offsetHeight } = target
327 | const { top } = target.getBoundingClientRect()
328 | this.prevPageY = offsetHeight - (clientY - top)
329 | }
330 |
331 | setupDragging = () => {
332 | css(document.body, disableSelectStyle)
333 | document.addEventListener('mousemove', this.handleDrag)
334 | document.addEventListener('mouseup', this.handleDragEnd)
335 | document.onselectstart = returnFalse
336 | }
337 |
338 | teardownDragging = () => {
339 | css(document.body, disableSelectStyleReset)
340 | document.removeEventListener('mousemove', this.handleDrag)
341 | document.removeEventListener('mouseup', this.handleDragEnd)
342 | document.onselectstart = undefined
343 | }
344 |
345 | handleDragStart = (event) => {
346 | this.dragging = true
347 | event.stopImmediatePropagation()
348 | this.setupDragging()
349 | }
350 |
351 | handleDrag = (event) => {
352 | if (this.prevPageX) {
353 | const { clientX } = event
354 | const { view, trackHorizontal } = this.refs
355 | const { left: trackLeft } = trackHorizontal.getBoundingClientRect()
356 | const thumbWidth = this.getThumbHorizontalWidth()
357 | const clickPosition = thumbWidth - this.prevPageX
358 | const offset = -trackLeft + clientX - clickPosition
359 | view.scrollLeft = this.getScrollLeftForOffset(offset)
360 | }
361 | if (this.prevPageY) {
362 | const { clientY } = event
363 | const { view, trackVertical } = this.refs
364 | const { top: trackTop } = trackVertical.getBoundingClientRect()
365 | const thumbHeight = this.getThumbVerticalHeight()
366 | const clickPosition = thumbHeight - this.prevPageY
367 | const offset = -trackTop + clientY - clickPosition
368 | view.scrollTop = this.getScrollTopForOffset(offset)
369 | }
370 | return false
371 | }
372 |
373 | handleDragEnd = () => {
374 | this.dragging = false
375 | this.prevPageX = this.prevPageY = 0
376 | this.teardownDragging()
377 | this.handleDragEndAutoHide()
378 | }
379 |
380 | handleDragEndAutoHide = () => {
381 | const { autoHide } = this.props
382 | if (!autoHide) return
383 | this.hideTracks()
384 | }
385 |
386 | handleTrackMouseEnter = () => {
387 | this.trackMouseOver = true
388 | this.handleTrackMouseEnterAutoHide()
389 | }
390 |
391 | handleTrackMouseEnterAutoHide = () => {
392 | const { autoHide } = this.props
393 | if (!autoHide) return
394 | this.showTracks()
395 | }
396 |
397 | handleTrackMouseLeave = () => {
398 | this.trackMouseOver = false
399 | this.handleTrackMouseLeaveAutoHide()
400 | }
401 |
402 | handleTrackMouseLeaveAutoHide = () => {
403 | const { autoHide } = this.props
404 | if (!autoHide) return
405 | this.hideTracks()
406 | }
407 |
408 | showTracks = () => {
409 | const { trackHorizontal, trackVertical } = this.refs
410 | clearTimeout(this.hideTracksTimeout)
411 | css(trackHorizontal, { opacity: 1 })
412 | css(trackVertical, { opacity: 1 })
413 | }
414 |
415 | hideTracks = () => {
416 | if (this.dragging) return
417 | if (this.scrolling) return
418 | if (this.trackMouseOver) return
419 | const { autoHideTimeout } = this.props
420 | const { trackHorizontal, trackVertical } = this.refs
421 | clearTimeout(this.hideTracksTimeout)
422 | this.hideTracksTimeout = setTimeout(() => {
423 | css(trackHorizontal, { opacity: 0 })
424 | css(trackVertical, { opacity: 0 })
425 | }, autoHideTimeout)
426 | }
427 |
428 | detectScrolling = () => {
429 | if (this.scrolling) return
430 | this.scrolling = true
431 | this.handleScrollStart()
432 | this.detectScrollingInterval = setInterval(() => {
433 | if (this.lastViewScrollLeft === this.viewScrollLeft &&
434 | this.lastViewScrollTop === this.viewScrollTop) {
435 | clearInterval(this.detectScrollingInterval)
436 | this.scrolling = false
437 | this.handleScrollStop()
438 | }
439 | this.lastViewScrollLeft = this.viewScrollLeft
440 | this.lastViewScrollTop = this.viewScrollTop
441 | }, 100)
442 | }
443 |
444 | raf = (callback) => {
445 | if (this.requestFrame) raf.cancel(this.requestFrame)
446 | this.requestFrame = raf(() => {
447 | this.requestFrame = undefined
448 | callback()
449 | })
450 | }
451 |
452 | update = (callback) => {
453 | this.raf(() => this._update(callback))
454 | }
455 |
456 | _update = (callback) => {
457 | const { onUpdate, hideTracksWhenNotNeeded } = this.props
458 | const values = this.getValues()
459 | if (getScrollbarWidth()) {
460 | const { thumbHorizontal, thumbVertical, trackHorizontal, trackVertical } = this.refs
461 | const { scrollLeft, clientWidth, scrollWidth } = values
462 | const trackHorizontalWidth = getInnerWidth(trackHorizontal)
463 | const thumbHorizontalWidth = this.getThumbHorizontalWidth()
464 | const thumbHorizontalX = scrollLeft / (scrollWidth - clientWidth) * (trackHorizontalWidth - thumbHorizontalWidth)
465 | const thumbHorizontalStyle = {
466 | width: thumbHorizontalWidth,
467 | transform: `translateX(${thumbHorizontalX}px)`
468 | }
469 | const { scrollTop, clientHeight, scrollHeight } = values
470 | const trackVerticalHeight = getInnerHeight(trackVertical)
471 | const thumbVerticalHeight = this.getThumbVerticalHeight()
472 | const thumbVerticalY = scrollTop / (scrollHeight - clientHeight) * (trackVerticalHeight - thumbVerticalHeight)
473 | const thumbVerticalStyle = {
474 | height: thumbVerticalHeight,
475 | transform: `translateY(${thumbVerticalY}px)`
476 | }
477 | if (hideTracksWhenNotNeeded) {
478 | const trackHorizontalStyle = {
479 | visibility: scrollWidth > clientWidth ? 'visible' : 'hidden'
480 | }
481 | const trackVerticalStyle = {
482 | visibility: scrollHeight > clientHeight ? 'visible' : 'hidden'
483 | }
484 | css(trackHorizontal, trackHorizontalStyle)
485 | css(trackVertical, trackVerticalStyle)
486 | }
487 | css(thumbHorizontal, thumbHorizontalStyle)
488 | css(thumbVertical, thumbVerticalStyle)
489 | }
490 | if (onUpdate) onUpdate(values)
491 | if (typeof callback !== 'function') return
492 | callback(values)
493 | }
494 |
495 | render = () => {
496 | const scrollbarWidth = getScrollbarWidth()
497 | /* eslint-disable no-unused-vars */
498 | const {
499 | onScroll,
500 | onScrollFrame,
501 | onScrollStart,
502 | onScrollStop,
503 | onUpdate,
504 | renderView,
505 | renderTrackHorizontal,
506 | renderTrackVertical,
507 | renderThumbHorizontal,
508 | renderThumbVertical,
509 | tagName,
510 | hideTracksWhenNotNeeded,
511 | autoHide,
512 | autoHideTimeout,
513 | autoHideDuration,
514 | thumbSize,
515 | thumbMinSize,
516 | universal,
517 | autoHeight,
518 | autoHeightMin,
519 | autoHeightMax,
520 | style,
521 | children,
522 | ...props
523 | } = this.props
524 | /* eslint-enable no-unused-vars */
525 |
526 | const { didMountUniversal } = this.state
527 |
528 | const containerStyle = {
529 | ...containerStyleDefault,
530 | ...(autoHeight && {
531 | ...containerStyleAutoHeight,
532 | minHeight: autoHeightMin,
533 | maxHeight: autoHeightMax
534 | }),
535 | ...style
536 | }
537 |
538 | const viewStyle = {
539 | ...viewStyleDefault,
540 | // Hide scrollbars by setting a negative margin
541 | marginRight: scrollbarWidth ? -scrollbarWidth : 0,
542 | marginBottom: scrollbarWidth ? -scrollbarWidth : 0,
543 | ...(autoHeight && {
544 | ...viewStyleAutoHeight,
545 | // Add scrollbarWidth to autoHeight in order to compensate negative margins
546 | minHeight: isString(autoHeightMin)
547 | ? `calc(${autoHeightMin} + ${scrollbarWidth}px)`
548 | : autoHeightMin + scrollbarWidth,
549 | maxHeight: isString(autoHeightMax)
550 | ? `calc(${autoHeightMax} + ${scrollbarWidth}px)`
551 | : autoHeightMax + scrollbarWidth
552 | }),
553 | // Override min/max height for initial universal rendering
554 | ...((autoHeight && universal && !didMountUniversal) && {
555 | minHeight: autoHeightMin,
556 | maxHeight: autoHeightMax
557 | }),
558 | // Override
559 | ...((universal && !didMountUniversal) && viewStyleUniversalInitial)
560 | }
561 |
562 | const trackAutoHeightStyle = {
563 | transition: `opacity ${autoHideDuration}ms`,
564 | opacity: 0
565 | }
566 |
567 | const trackHorizontalStyle = {
568 | ...trackHorizontalStyleDefault,
569 | ...(autoHide && trackAutoHeightStyle),
570 | ...((!scrollbarWidth || (universal && !didMountUniversal)) && {
571 | display: 'none'
572 | })
573 | }
574 |
575 | const trackVerticalStyle = {
576 | ...trackVerticalStyleDefault,
577 | ...(autoHide && trackAutoHeightStyle),
578 | ...((!scrollbarWidth || (universal && !didMountUniversal)) && {
579 | display: 'none'
580 | })
581 | }
582 |
583 | return h(tagName, { ...props, style: containerStyle, ref: (r) => { this.refs.container = r } }, [
584 | cloneElement(
585 | renderView({ style: viewStyle }),
586 | { key: 'view', ref: (r) => { this.refs.view = r } },
587 | children
588 | ),
589 | cloneElement(
590 | renderTrackHorizontal({ style: trackHorizontalStyle }),
591 | { key: 'trackHorizontal', ref: (r) => { this.refs.trackHorizontal = r } },
592 | cloneElement(renderThumbHorizontal({ style: thumbHorizontalStyleDefault }), { ref: (r) => { this.refs.thumbHorizontal = r } })
593 | ),
594 | cloneElement(
595 | renderTrackVertical({ style: trackVerticalStyle }),
596 | { key: 'trackVertical', ref: (r) => { this.refs.trackVertical = r } },
597 | cloneElement(renderThumbVertical({ style: thumbVerticalStyleDefault }), { ref: (r) => { this.refs.thumbVertical = r } })
598 | )
599 | ])
600 | }
601 | }
602 |
--------------------------------------------------------------------------------
/src/Scrollbars/styles.js:
--------------------------------------------------------------------------------
1 | export const containerStyleDefault = {
2 | position: 'relative',
3 | overflow: 'hidden',
4 | width: '100%',
5 | height: '100%',
6 | }
7 |
8 | // Overrides containerStyleDefault properties
9 | export const containerStyleAutoHeight = {
10 | height: 'auto',
11 | }
12 |
13 | export const viewStyleDefault = {
14 | position: 'absolute',
15 | top: 0,
16 | left: 0,
17 | right: 0,
18 | bottom: 0,
19 | overflow: 'scroll',
20 | WebkitOverflowScrolling: 'touch',
21 | }
22 |
23 | // Overrides viewStyleDefault properties
24 | export const viewStyleAutoHeight = {
25 | position: 'relative',
26 | top: undefined,
27 | left: undefined,
28 | right: undefined,
29 | bottom: undefined,
30 | }
31 |
32 | export const viewStyleUniversalInitial = {
33 | overflow: 'hidden',
34 | marginRight: 0,
35 | marginBottom: 0,
36 | }
37 |
38 | export const trackHorizontalStyleDefault = {
39 | position: 'absolute',
40 | height: 6,
41 | }
42 |
43 | export const trackVerticalStyleDefault = {
44 | position: 'absolute',
45 | width: 6,
46 | }
47 |
48 | export const thumbHorizontalStyleDefault = {
49 | position: 'relative',
50 | display: 'block',
51 | height: '100%',
52 | }
53 |
54 | export const thumbVerticalStyleDefault = {
55 | position: 'relative',
56 | display: 'block',
57 | width: '100%',
58 | }
59 |
60 | export const disableSelectStyle = {
61 | userSelect: 'none',
62 | }
63 |
64 | export const disableSelectStyleReset = {
65 | userSelect: '',
66 | }
67 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Scrollbars from './Scrollbars'
2 | export default Scrollbars
3 | export { Scrollbars }
4 |
--------------------------------------------------------------------------------
/src/utils/getInnerHeight.js:
--------------------------------------------------------------------------------
1 | export default function getInnerHeight(el) {
2 | const { clientHeight } = el
3 | const { paddingTop, paddingBottom } = window.getComputedStyle(el)
4 | return clientHeight - parseFloat(paddingTop) - parseFloat(paddingBottom)
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/getInnerWidth.js:
--------------------------------------------------------------------------------
1 | export default function getInnerWidth(el) {
2 | const { clientWidth } = el
3 | const { paddingLeft, paddingRight } = window.getComputedStyle(el)
4 | return clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight)
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/getScrollbarWidth.js:
--------------------------------------------------------------------------------
1 | import css from 'dom-css'
2 | let scrollbarWidth = false
3 |
4 | export default function getScrollbarWidth() {
5 | if (scrollbarWidth !== false) return scrollbarWidth
6 | /* istanbul ignore else */
7 | if (typeof document !== 'undefined') {
8 | const div = document.createElement('div')
9 | css(div, {
10 | width: 100,
11 | height: 100,
12 | position: 'absolute',
13 | top: -9999,
14 | overflow: 'scroll',
15 | MsOverflowStyle: 'scrollbar',
16 | })
17 | document.body.appendChild(div)
18 | scrollbarWidth = (div.offsetWidth - div.clientWidth)
19 | document.body.removeChild(div)
20 | } else {
21 | scrollbarWidth = 0
22 | }
23 | return scrollbarWidth || 0
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/isString.js:
--------------------------------------------------------------------------------
1 | export default function isString(maybe) {
2 | return typeof maybe === 'string'
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/returnFalse.js:
--------------------------------------------------------------------------------
1 | export default function returnFalse() {
2 | return false
3 | }
4 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect'
2 | window.expect = expect
3 | window.createSpy = expect.createSpy
4 | window.spyOn = expect.spyOn
5 | window.isSpy = expect.isSpy
6 |
7 | const context = require.context('./test', true, /\.spec\.js$/)
8 | context.keys().forEach(context)
9 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "describe": true,
4 | "it": true,
5 | "expect": true,
6 | "before": true,
7 | "beforeEach": true,
8 | "after": true,
9 | "afterEach": true,
10 | "createSpy": true,
11 | "spyOn": true,
12 | "isSpy": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/Scrollbars/autoHeight.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode, findDOMNode } from 'preact-dom'
3 | import Preact, { createClass } from 'preact'
4 |
5 | export default function createTests(scrollbarWidth, envScrollbarWidth) {
6 | describe('autoHeight', () => {
7 | let node
8 | beforeEach(() => {
9 | node = document.createElement('div')
10 | document.body.appendChild(node)
11 | })
12 | afterEach(() => {
13 | unmountComponentAtNode(node)
14 | document.body.removeChild(node)
15 | })
16 |
17 | describe('when rendered', () => {
18 | it('should have min-height and max-height', done => {
19 | render((
20 |
24 |
25 |
26 | ), node, function callback() {
27 | const scrollbars = findDOMNode(this)
28 | const view = this.refs.view
29 | expect(scrollbars.style.position).toEqual('relative')
30 | expect(scrollbars.style.minHeight).toEqual('0px')
31 | expect(scrollbars.style.maxHeight).toEqual('100px')
32 | expect(view.style.position).toEqual('relative')
33 | expect(view.style.minHeight).toEqual(`${scrollbarWidth}px`)
34 | expect(view.style.maxHeight).toEqual(`${100 + scrollbarWidth}px`)
35 | done()
36 | })
37 | })
38 | })
39 |
40 | describe('when native scrollbars have a width', () => {
41 | if (!scrollbarWidth) return
42 | it('hides native scrollbars', done => {
43 | render((
44 |
47 |
48 |
49 | ), node, function callback() {
50 | const width = `-${scrollbarWidth}px`
51 | expect(this.refs.view.style.marginRight).toEqual(width)
52 | expect(this.refs.view.style.marginBottom).toEqual(width)
53 | done()
54 | })
55 | })
56 | })
57 |
58 | describe('when native scrollbars have no width', () => {
59 | if (scrollbarWidth) return
60 | it('hides bars', done => {
61 | render((
62 |
65 |
66 |
67 | ), node, function callback() {
68 | setTimeout(() => {
69 | expect(this.refs.trackVertical.style.display).toEqual('none')
70 | expect(this.refs.trackHorizontal.style.display).toEqual('none')
71 | done()
72 | }, 100)
73 | })
74 | })
75 | })
76 |
77 | describe('when content is smaller than maxHeight', () => {
78 | it('should have the content\'s height', done => {
79 | render((
80 |
83 |
84 |
85 | ), node, function callback() {
86 | setTimeout(() => {
87 | const scrollbars = findDOMNode(this)
88 | const view = this.refs.view
89 | const thumbVertical = this.refs.thumbVertical
90 | expect(scrollbars.clientHeight).toEqual(50 + (envScrollbarWidth - scrollbarWidth))
91 | expect(view.clientHeight).toEqual(50)
92 | expect(view.scrollHeight).toEqual(50)
93 | expect(thumbVertical.clientHeight).toEqual(0)
94 | done()
95 | }, 100)
96 | })
97 | })
98 | })
99 |
100 | describe('when content is larger than maxHeight', () => {
101 | it('should show scrollbars', done => {
102 | render((
103 |
106 |
107 |
108 | ), node, function callback() {
109 | setTimeout(() => {
110 | const scrollbars = findDOMNode(this)
111 | const view = this.refs.view
112 | const thumbVertical = this.refs.thumbVertical
113 | expect(scrollbars.clientHeight).toEqual(100)
114 | expect(view.clientHeight).toEqual(100 - (envScrollbarWidth - scrollbarWidth))
115 | expect(view.scrollHeight).toEqual(200)
116 | if (scrollbarWidth) {
117 | // 100 / 200 * 96 = 48
118 | expect(thumbVertical.clientHeight).toEqual(48)
119 | }
120 | done()
121 | }, 100)
122 | })
123 | })
124 | })
125 |
126 | describe('when minHeight is greater than 0', () => {
127 | it('should have height greater than 0', done => {
128 | render((
129 |
133 |
134 |
135 | ), node, function callback() {
136 | setTimeout(() => {
137 | const scrollbars = findDOMNode(this)
138 | const view = this.refs.view
139 | const thumbVertical = this.refs.thumbVertical
140 | expect(scrollbars.clientHeight).toEqual(100)
141 | expect(view.clientHeight).toEqual(100 - (envScrollbarWidth - scrollbarWidth))
142 | expect(thumbVertical.clientHeight).toEqual(0)
143 | done()
144 | }, 100)
145 | })
146 | })
147 | })
148 |
149 | describe('when using perecentages', () => {
150 | it('should use calc', done => {
151 | const Root = createClass({
152 | render() {
153 | return (
154 |
163 | )
164 | },
165 | })
166 | render( , node, function callback() {
167 | setTimeout(() => {
168 | const { scrollbars } = this.refs
169 | const $scrollbars = findDOMNode(scrollbars)
170 | const view = scrollbars.refs.view
171 | expect($scrollbars.clientWidth).toEqual(500)
172 | expect($scrollbars.clientHeight).toEqual(250)
173 | expect($scrollbars.style.position).toEqual('relative')
174 | expect($scrollbars.style.minHeight).toEqual('50%')
175 | expect($scrollbars.style.maxHeight).toEqual('100%')
176 | expect(view.style.position).toEqual('relative')
177 | expect(view.style.minHeight).toEqual(`calc(50% + ${scrollbarWidth}px)`)
178 | expect(view.style.maxHeight).toEqual(`calc(100% + ${scrollbarWidth}px)`)
179 | done()
180 | }, 100)
181 | })
182 | })
183 | })
184 |
185 | describe('when using other units', () => {
186 | it('should use calc', done => {
187 | render((
188 |
192 |
193 |
194 | ), node, function callback() {
195 | const scrollbars = findDOMNode(this)
196 | const view = this.refs.view
197 | expect(scrollbars.style.position).toEqual('relative')
198 | expect(scrollbars.style.minHeight).toEqual('10em')
199 | expect(scrollbars.style.maxHeight).toEqual('100em')
200 | expect(view.style.position).toEqual('relative')
201 | expect(view.style.minHeight).toEqual(`calc(10em + ${scrollbarWidth}px)`)
202 | expect(view.style.maxHeight).toEqual(`calc(100em + ${scrollbarWidth}px)`)
203 | done()
204 | })
205 | })
206 | })
207 | })
208 | }
209 |
--------------------------------------------------------------------------------
/test/Scrollbars/autoHide.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 | import simulant from 'simulant'
5 |
6 | export default function createTests(scrollbarWidth) {
7 | // Not for mobile environment
8 | if (!scrollbarWidth) return
9 |
10 | let node
11 | beforeEach(() => {
12 | node = document.createElement('div')
13 | document.body.appendChild(node)
14 | })
15 | afterEach(() => {
16 | unmountComponentAtNode(node)
17 | document.body.removeChild(node)
18 | })
19 |
20 | describe('autoHide', () => {
21 | describe('when Scrollbars are rendered', () => {
22 | it('should hide tracks', done => {
23 | render((
24 |
25 |
26 |
27 | ), node, function callback() {
28 | const { trackHorizontal, trackVertical } = this.refs
29 | expect(trackHorizontal.style.opacity).toEqual('0')
30 | expect(trackVertical.style.opacity).toEqual('0')
31 | done()
32 | })
33 | })
34 | })
35 | describe('enter/leave track', () => {
36 | describe('when entering horizontal track', () => {
37 | it('should show tracks', done => {
38 | render((
39 |
40 |
41 |
42 | ), node, function callback() {
43 | const { trackHorizontal: track } = this.refs
44 | simulant.fire(track, 'mouseenter')
45 | expect(track.style.opacity).toEqual('1')
46 | done()
47 | })
48 | })
49 | it('should not hide tracks', done => {
50 | render((
51 |
55 |
56 |
57 | ), node, function callback() {
58 | const { trackHorizontal: track } = this.refs
59 | simulant.fire(track, 'mouseenter')
60 | setTimeout(() => this.hideTracks(), 10)
61 | setTimeout(() => {
62 | expect(track.style.opacity).toEqual('1')
63 | }, 100)
64 | done()
65 | })
66 | })
67 | })
68 | describe('when leaving horizontal track', () => {
69 | it('should hide tracks', done => {
70 | render((
71 |
76 |
77 |
78 | ), node, function callback() {
79 | const { trackHorizontal: track } = this.refs
80 | simulant.fire(track, 'mouseenter')
81 | simulant.fire(track, 'mouseleave')
82 | setTimeout(() => {
83 | expect(track.style.opacity).toEqual('0')
84 | done()
85 | }, 100)
86 | })
87 | })
88 | })
89 | describe('when entering vertical track', () => {
90 | it('should show tracks', done => {
91 | render((
92 |
93 |
94 |
95 | ), node, function callback() {
96 | const { trackVertical: track } = this.refs
97 | simulant.fire(track, 'mouseenter')
98 | expect(track.style.opacity).toEqual('1')
99 | done()
100 | })
101 | })
102 | it('should not hide tracks', done => {
103 | render((
104 |
108 |
109 |
110 | ), node, function callback() {
111 | const { trackVertical: track } = this.refs
112 | simulant.fire(track, 'mouseenter')
113 | setTimeout(() => this.hideTracks(), 10)
114 | setTimeout(() => {
115 | expect(track.style.opacity).toEqual('1')
116 | }, 100)
117 | done()
118 | })
119 | })
120 | })
121 | describe('when leaving vertical track', () => {
122 | it('should hide tracks', done => {
123 | render((
124 |
129 |
130 |
131 | ), node, function callback() {
132 | const { trackVertical: track } = this.refs
133 | simulant.fire(track, 'mouseenter')
134 | simulant.fire(track, 'mouseleave')
135 | setTimeout(() => {
136 | expect(track.style.opacity).toEqual('0')
137 | done()
138 | }, 100)
139 | })
140 | })
141 | })
142 | })
143 | describe('when scrolling', () => {
144 | it('should show tracks', done => {
145 | render((
146 |
147 |
148 |
149 | ), node, function callback() {
150 | this.scrollTop(50)
151 | setTimeout(() => {
152 | const { trackHorizontal, trackVertical } = this.refs
153 | expect(trackHorizontal.style.opacity).toEqual('1')
154 | expect(trackVertical.style.opacity).toEqual('1')
155 | done()
156 | }, 100)
157 | })
158 | })
159 | it('should hide tracks after scrolling', done => {
160 | render((
161 |
166 |
167 |
168 | ), node, function callback() {
169 | this.scrollTop(50)
170 | setTimeout(() => {
171 | const { trackHorizontal, trackVertical } = this.refs
172 | expect(trackHorizontal.style.opacity).toEqual('0')
173 | expect(trackVertical.style.opacity).toEqual('0')
174 | done()
175 | }, 300)
176 | })
177 | })
178 | it('should not hide tracks', done => {
179 | render((
180 |
184 |
185 |
186 | ), node, function callback() {
187 | this.scrollTop(50)
188 | setTimeout(() => this.hideTracks())
189 | setTimeout(() => {
190 | const { trackHorizontal, trackVertical } = this.refs
191 | expect(trackHorizontal.style.opacity).toEqual('1')
192 | expect(trackVertical.style.opacity).toEqual('1')
193 | done()
194 | }, 50)
195 | })
196 | })
197 | })
198 | describe('when dragging x-axis', () => {
199 | it('should show tracks', done => {
200 | render((
201 |
206 |
207 |
208 | ), node, function callback() {
209 | const { thumbHorizontal: thumb, trackHorizontal: track } = this.refs
210 | const { left } = thumb.getBoundingClientRect()
211 | simulant.fire(thumb, 'mousedown', {
212 | target: thumb,
213 | clientX: left + 1,
214 | })
215 | simulant.fire(document, 'mousemove', {
216 | clientX: left + 100,
217 | })
218 | setTimeout(() => {
219 | expect(track.style.opacity).toEqual('1')
220 | done()
221 | }, 100)
222 | })
223 | })
224 |
225 | it('should hide tracks on end', done => {
226 | render((
227 |
232 |
233 |
234 | ), node, function callback() {
235 | const { thumbHorizontal: thumb, trackHorizontal: track } = this.refs
236 | const { left } = thumb.getBoundingClientRect()
237 | simulant.fire(thumb, 'mousedown', {
238 | target: thumb,
239 | clientX: left + 1,
240 | })
241 | simulant.fire(document, 'mouseup')
242 | setTimeout(() => {
243 | expect(track.style.opacity).toEqual('0')
244 | done()
245 | }, 100)
246 | })
247 | })
248 |
249 | describe('and leaving track', () => {
250 | it('should not hide tracks', done => {
251 | render((
252 |
257 |
258 |
259 | ), node, function callback() {
260 | setTimeout(() => {
261 | const { thumbHorizontal: thumb, trackHorizontal: track } = this.refs
262 | const { left } = thumb.getBoundingClientRect()
263 | simulant.fire(thumb, 'mousedown', {
264 | target: thumb,
265 | clientX: left + 1,
266 | })
267 | simulant.fire(document, 'mousemove', {
268 | clientX: left + 100,
269 | })
270 | simulant.fire(track, 'mouseleave')
271 | setTimeout(() => {
272 | expect(track.style.opacity).toEqual('1')
273 | done()
274 | }, 200)
275 | }, 100)
276 | })
277 | })
278 | })
279 | })
280 | describe('when dragging y-axis', () => {
281 | it('should show tracks', done => {
282 | render((
283 |
288 |
289 |
290 | ), node, function callback() {
291 | const { thumbVertical: thumb, trackVertical: track } = this.refs
292 | const { top } = thumb.getBoundingClientRect()
293 | simulant.fire(thumb, 'mousedown', {
294 | target: thumb,
295 | clientY: top + 1,
296 | })
297 | simulant.fire(document, 'mousemove', {
298 | clientY: top + 100,
299 | })
300 | setTimeout(() => {
301 | expect(track.style.opacity).toEqual('1')
302 | done()
303 | }, 100)
304 | })
305 | })
306 | it('should hide tracks on end', done => {
307 | render((
308 |
313 |
314 |
315 | ), node, function callback() {
316 | const { thumbVertical: thumb, trackVertical: track } = this.refs
317 | const { top } = thumb.getBoundingClientRect()
318 | simulant.fire(thumb, 'mousedown', {
319 | target: thumb,
320 | clientY: top + 1,
321 | })
322 | simulant.fire(document, 'mouseup')
323 | setTimeout(() => {
324 | expect(track.style.opacity).toEqual('0')
325 | done()
326 | }, 100)
327 | })
328 | })
329 | describe('and leaving track', () => {
330 | it('should not hide tracks', done => {
331 | render((
332 |
337 |
338 |
339 | ), node, function callback() {
340 | setTimeout(() => {
341 | const { thumbVertical: thumb, trackVertical: track } = this.refs
342 | const { top } = thumb.getBoundingClientRect()
343 | simulant.fire(thumb, 'mousedown', {
344 | target: thumb,
345 | clientY: top + 1,
346 | })
347 | simulant.fire(document, 'mousemove', {
348 | clientY: top + 100,
349 | })
350 | simulant.fire(track, 'mouseleave')
351 | setTimeout(() => {
352 | expect(track.style.opacity).toEqual('1')
353 | done()
354 | }, 200)
355 | }, 100)
356 | })
357 | })
358 | })
359 | })
360 | })
361 |
362 | describe('when autoHide is disabed', () => {
363 | describe('enter/leave track', () => {
364 | describe('when entering horizontal track', () => {
365 | it('should not call `showTracks`', done => {
366 | render((
367 |
368 |
369 |
370 | ), node, function callback() {
371 | const spy = spyOn(this, 'showTracks')
372 | const { trackHorizontal: track } = this.refs
373 | simulant.fire(track, 'mouseenter')
374 | expect(spy.calls.length).toEqual(0)
375 | done()
376 | })
377 | })
378 | })
379 | describe('when leaving horizontal track', () => {
380 | it('should not call `hideTracks`', done => {
381 | render((
382 |
383 |
384 |
385 | ), node, function callback() {
386 | const spy = spyOn(this, 'hideTracks')
387 | const { trackHorizontal: track } = this.refs
388 | simulant.fire(track, 'mouseenter')
389 | simulant.fire(track, 'mouseleave')
390 | setTimeout(() => {
391 | expect(spy.calls.length).toEqual(0)
392 | done()
393 | }, 100)
394 | })
395 | })
396 | })
397 | describe('when entering vertical track', () => {
398 | it('should not call `showTracks`', done => {
399 | render((
400 |
401 |
402 |
403 | ), node, function callback() {
404 | const spy = spyOn(this, 'showTracks')
405 | const { trackVertical: track } = this.refs
406 | simulant.fire(track, 'mouseenter')
407 | expect(spy.calls.length).toEqual(0)
408 | done()
409 | })
410 | })
411 | })
412 | describe('when leaving vertical track', () => {
413 | it('should not call `hideTracks`', done => {
414 | render((
415 |
416 |
417 |
418 | ), node, function callback() {
419 | const spy = spyOn(this, 'hideTracks')
420 | const { trackVertical: track } = this.refs
421 | simulant.fire(track, 'mouseenter')
422 | simulant.fire(track, 'mouseleave')
423 | setTimeout(() => {
424 | expect(spy.calls.length).toEqual(0)
425 | done()
426 | }, 100)
427 | })
428 | })
429 | })
430 | })
431 | })
432 | }
433 |
--------------------------------------------------------------------------------
/test/Scrollbars/clickTrack.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 | import simulant from 'simulant'
5 |
6 | export default function createTests(scrollbarWidth) {
7 | // Not for mobile environment
8 | if (!scrollbarWidth) return
9 |
10 | let node
11 | beforeEach(() => {
12 | node = document.createElement('div')
13 | document.body.appendChild(node)
14 | })
15 | afterEach(() => {
16 | unmountComponentAtNode(node)
17 | document.body.removeChild(node)
18 | })
19 |
20 | describe('when clicking on horizontal track', () => {
21 | it('should scroll to the respective position', done => {
22 | render((
23 |
24 |
25 |
26 | ), node, function callback() {
27 | setTimeout(() => {
28 | const { view, trackHorizontal: bar } = this.refs
29 | const { left, width } = bar.getBoundingClientRect()
30 | simulant.fire(bar, 'mousedown', {
31 | target: bar,
32 | clientX: left + (width / 2),
33 | })
34 | expect(view.scrollLeft).toEqual(50)
35 | done()
36 | }, 100)
37 | })
38 | })
39 | })
40 |
41 | describe('when clicking on vertical track', () => {
42 | it('should scroll to the respective position', done => {
43 | render((
44 |
45 |
46 |
47 | ), node, function callback() {
48 | setTimeout(() => {
49 | const { view, trackVertical: bar } = this.refs
50 | const { top, height } = bar.getBoundingClientRect()
51 | simulant.fire(bar, 'mousedown', {
52 | target: bar,
53 | clientY: top + (height / 2),
54 | })
55 | expect(view.scrollTop).toEqual(50)
56 | done()
57 | }, 100)
58 | })
59 | })
60 | })
61 | }
62 |
--------------------------------------------------------------------------------
/test/Scrollbars/dragThumb.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 | import simulant from 'simulant'
5 |
6 | export default function createTests(scrollbarWidth) {
7 | // Not for mobile environment
8 | if (!scrollbarWidth) return
9 |
10 | let node
11 | beforeEach(() => {
12 | node = document.createElement('div')
13 | document.body.appendChild(node)
14 | })
15 | afterEach(() => {
16 | unmountComponentAtNode(node)
17 | document.body.removeChild(node)
18 | })
19 | describe('when dragging horizontal thumb', () => {
20 | it('should scroll to the respective position', done => {
21 | render((
22 |
23 |
24 |
25 | ), node, function callback() {
26 | setTimeout(() => {
27 | const { view, thumbHorizontal: thumb } = this.refs
28 | const { left } = thumb.getBoundingClientRect()
29 | simulant.fire(thumb, 'mousedown', {
30 | target: thumb,
31 | clientX: left + 1,
32 | })
33 | simulant.fire(document, 'mousemove', {
34 | clientX: left + 100,
35 | })
36 | simulant.fire(document, 'mouseup')
37 | expect(view.scrollLeft).toEqual(100)
38 | done()
39 | }, 100)
40 | })
41 | })
42 |
43 | it('should disable selection', done => {
44 | render((
45 |
46 |
47 |
48 | ), node, function callback() {
49 | setTimeout(() => {
50 | const { thumbHorizontal: thumb } = this.refs
51 | const { left } = thumb.getBoundingClientRect()
52 | simulant.fire(thumb, 'mousedown', {
53 | target: thumb,
54 | clientX: left + 1,
55 | })
56 | expect(document.body.style.webkitUserSelect).toEqual('none')
57 | simulant.fire(document, 'mouseup')
58 | expect(document.body.style.webkitUserSelect).toEqual('')
59 | done()
60 | }, 100)
61 | })
62 | })
63 | })
64 |
65 | describe('when dragging vertical thumb', () => {
66 | it('should scroll to the respective position', done => {
67 | render((
68 |
69 |
70 |
71 | ), node, function callback() {
72 | setTimeout(() => {
73 | const { view, thumbVertical: thumb } = this.refs
74 | const { top } = thumb.getBoundingClientRect()
75 | simulant.fire(thumb, 'mousedown', {
76 | target: thumb,
77 | clientY: top + 1,
78 | })
79 | simulant.fire(document, 'mousemove', {
80 | clientY: top + 100,
81 | })
82 | simulant.fire(document, 'mouseup')
83 | expect(view.scrollTop).toEqual(100)
84 | done()
85 | }, 100)
86 | })
87 | })
88 |
89 | it('should disable selection', done => {
90 | render((
91 |
92 |
93 |
94 | ), node, function callback() {
95 | setTimeout(() => {
96 | const { thumbVertical: thumb } = this.refs
97 | const { top } = thumb.getBoundingClientRect()
98 | simulant.fire(thumb, 'mousedown', {
99 | target: thumb,
100 | clientY: top + 1,
101 | })
102 | expect(document.body.style.webkitUserSelect).toEqual('none')
103 | simulant.fire(document, 'mouseup')
104 | expect(document.body.style.webkitUserSelect).toEqual('')
105 | done()
106 | }, 100)
107 | })
108 | })
109 | })
110 | }
111 |
--------------------------------------------------------------------------------
/test/Scrollbars/flexbox.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode, findDOMNode } from 'preact-dom'
3 | import Preact, { createClass } from 'preact'
4 |
5 | export default function createTests() {
6 | let node
7 | beforeEach(() => {
8 | node = document.createElement('div')
9 | document.body.appendChild(node)
10 | })
11 | afterEach(() => {
12 | unmountComponentAtNode(node)
13 | document.body.removeChild(node)
14 | })
15 | describe('when scrollbars are in flexbox environment', () => {
16 | it('should still work', done => {
17 | const Root = createClass({
18 | render() {
19 | return (
20 |
25 | )
26 | },
27 | })
28 | render( , node, function callback() {
29 | setTimeout(() => {
30 | const { scrollbars } = this.refs
31 | const $scrollbars = findDOMNode(scrollbars)
32 | const $view = scrollbars.refs.view
33 | expect($scrollbars.clientHeight).toBeGreaterThan(0)
34 | expect($view.clientHeight).toBeGreaterThan(0)
35 | done()
36 | }, 100)
37 | })
38 | })
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/test/Scrollbars/gettersSetters.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests(scrollbarWidth, envScrollbarWidth) {
6 | let node
7 | beforeEach(() => {
8 | node = document.createElement('div')
9 | document.body.appendChild(node)
10 | })
11 | afterEach(() => {
12 | unmountComponentAtNode(node)
13 | document.body.removeChild(node)
14 | })
15 |
16 | describe('getters', () => {
17 | function renderScrollbars(callback) {
18 | render((
19 |
20 |
21 |
22 | ), node, callback)
23 | }
24 | describe('getScrollLeft', () => {
25 | it('should return scrollLeft', done => {
26 | renderScrollbars(function callback() {
27 | this.scrollLeft(50)
28 | expect(this.getScrollLeft()).toEqual(50)
29 | done()
30 | })
31 | })
32 | })
33 | describe('getScrollTop', () => {
34 | it('should return scrollTop', done => {
35 | renderScrollbars(function callback() {
36 | this.scrollTop(50)
37 | expect(this.getScrollTop()).toEqual(50)
38 | done()
39 | })
40 | })
41 | })
42 | describe('getScrollWidth', () => {
43 | it('should return scrollWidth', done => {
44 | renderScrollbars(function callback() {
45 | expect(this.getScrollWidth()).toEqual(200)
46 | done()
47 | })
48 | })
49 | })
50 | describe('getScrollHeight', () => {
51 | it('should return scrollHeight', done => {
52 | renderScrollbars(function callback() {
53 | expect(this.getScrollHeight()).toEqual(200)
54 | done()
55 | })
56 | })
57 | })
58 | describe('getClientWidth', () => {
59 | it('should return scrollWidth', done => {
60 | renderScrollbars(function callback() {
61 | expect(this.getClientWidth()).toEqual(100 + (scrollbarWidth - envScrollbarWidth))
62 | done()
63 | })
64 | })
65 | })
66 | describe('getClientHeight', () => {
67 | it('should return scrollHeight', done => {
68 | renderScrollbars(function callback() {
69 | expect(this.getClientHeight()).toEqual(100 + (scrollbarWidth - envScrollbarWidth))
70 | done()
71 | })
72 | })
73 | })
74 | })
75 |
76 | describe('setters', () => {
77 | function renderScrollbars(callback) {
78 | render((
79 |
80 |
81 |
82 | ), node, callback)
83 | }
84 | describe('scrollLeft/scrollToLeft', () => {
85 | it('should scroll to given left value', done => {
86 | renderScrollbars(function callback() {
87 | this.scrollLeft(50)
88 | expect(this.getScrollLeft()).toEqual(50)
89 | this.scrollToLeft()
90 | expect(this.getScrollLeft()).toEqual(0)
91 | this.scrollLeft(50)
92 | this.scrollLeft()
93 | expect(this.getScrollLeft()).toEqual(0)
94 | done()
95 | })
96 | })
97 | })
98 | describe('scrollTop/scrollToTop', () => {
99 | it('should scroll to given top value', done => {
100 | renderScrollbars(function callback() {
101 | this.scrollTop(50)
102 | expect(this.getScrollTop()).toEqual(50)
103 | this.scrollToTop()
104 | expect(this.getScrollTop()).toEqual(0)
105 | this.scrollTop(50)
106 | this.scrollTop()
107 | expect(this.getScrollTop()).toEqual(0)
108 | done()
109 | })
110 | })
111 | })
112 | describe('scrollToRight', () => {
113 | it('should scroll to right', done => {
114 | renderScrollbars(function callback() {
115 | this.scrollToRight()
116 | expect(this.getScrollLeft()).toEqual(100 + (envScrollbarWidth - scrollbarWidth))
117 | done()
118 | })
119 | })
120 | })
121 | describe('scrollToBottom', () => {
122 | it('should scroll to bottom', done => {
123 | renderScrollbars(function callback() {
124 | this.scrollToBottom()
125 | expect(this.getScrollTop()).toEqual(100 + (envScrollbarWidth - scrollbarWidth))
126 | done()
127 | })
128 | })
129 | })
130 | })
131 | }
132 |
--------------------------------------------------------------------------------
/test/Scrollbars/hideTracks.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests(scrollbarWidth) {
6 | describe('hide tracks', () => {
7 | let node
8 | beforeEach(() => {
9 | node = document.createElement('div')
10 | document.body.appendChild(node)
11 | })
12 | afterEach(() => {
13 | unmountComponentAtNode(node)
14 | document.body.removeChild(node)
15 | })
16 |
17 | describe('when native scrollbars have a width', () => {
18 | if (!scrollbarWidth) return
19 | describe('when content is greater than wrapper', () => {
20 | it('should show tracks', done => {
21 | render((
22 |
25 |
26 |
27 | ), node, function callback() {
28 | setTimeout(() => {
29 | const { trackHorizontal, trackVertical } = this.refs
30 | expect(trackHorizontal.style.visibility).toEqual('visible')
31 | expect(trackVertical.style.visibility).toEqual('visible')
32 | done()
33 | }, 100)
34 | })
35 | })
36 | })
37 | describe('when content is smaller than wrapper', () => {
38 | it('should hide tracks', done => {
39 | render((
40 |
43 |
44 |
45 | ), node, function callback() {
46 | setTimeout(() => {
47 | const { trackHorizontal, trackVertical } = this.refs
48 | expect(trackHorizontal.style.visibility).toEqual('hidden')
49 | expect(trackVertical.style.visibility).toEqual('hidden')
50 | done()
51 | }, 100)
52 | })
53 | })
54 | })
55 | })
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/test/Scrollbars/index.js:
--------------------------------------------------------------------------------
1 | import rendering from './rendering'
2 | import gettersSetters from './gettersSetters'
3 | import scrolling from './scrolling'
4 | import resizing from './resizing'
5 | import clickTrack from './clickTrack'
6 | import dragThumb from './dragThumb'
7 | import flexbox from './flexbox'
8 | import autoHide from './autoHide'
9 | import autoHeight from './autoHeight'
10 | import hideTracks from './hideTracks'
11 | import universal from './universal'
12 | import onUpdate from './onUpdate'
13 |
14 | export default function createTests(scrollbarWidth, envScrollbarWidth) {
15 | rendering(scrollbarWidth, envScrollbarWidth)
16 | gettersSetters(scrollbarWidth, envScrollbarWidth)
17 | scrolling(scrollbarWidth, envScrollbarWidth)
18 | resizing(scrollbarWidth, envScrollbarWidth)
19 | clickTrack(scrollbarWidth, envScrollbarWidth)
20 | dragThumb(scrollbarWidth, envScrollbarWidth)
21 | flexbox(scrollbarWidth, envScrollbarWidth)
22 | autoHide(scrollbarWidth, envScrollbarWidth)
23 | autoHeight(scrollbarWidth, envScrollbarWidth)
24 | hideTracks(scrollbarWidth, envScrollbarWidth)
25 | universal(scrollbarWidth, envScrollbarWidth)
26 | onUpdate(scrollbarWidth, envScrollbarWidth)
27 | }
28 |
--------------------------------------------------------------------------------
/test/Scrollbars/onUpdate.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests() {
6 | let node
7 | beforeEach(() => {
8 | node = document.createElement('div')
9 | document.body.appendChild(node)
10 | })
11 | afterEach(() => {
12 | unmountComponentAtNode(node)
13 | document.body.removeChild(node)
14 | })
15 |
16 | describe('onUpdate', () => {
17 | describe('when scrolling x-axis', () => {
18 | it('should call `onUpdate`', done => {
19 | const spy = createSpy()
20 | render((
21 |
22 |
23 |
24 | ), node, function callback() {
25 | this.scrollLeft(50)
26 | setTimeout(() => {
27 | expect(spy.calls.length).toEqual(1)
28 | done()
29 | }, 100)
30 | })
31 | })
32 | })
33 | describe('when scrolling y-axis', () => {
34 | it('should call `onUpdate`', done => {
35 | const spy = createSpy()
36 | render((
37 |
38 |
39 |
40 | ), node, function callback() {
41 | this.scrollTop(50)
42 | setTimeout(() => {
43 | expect(spy.calls.length).toEqual(1)
44 | done()
45 | }, 100)
46 | })
47 | })
48 | })
49 |
50 | describe('when resizing window', () => {
51 | it('should call onUpdate', done => {
52 | const spy = createSpy()
53 | render((
54 |
55 |
56 |
57 | ), node, function callback() {
58 | setTimeout(() => {
59 | expect(spy.calls.length).toEqual(1)
60 | done()
61 | }, 100)
62 | })
63 | })
64 | })
65 | })
66 | }
67 |
--------------------------------------------------------------------------------
/test/Scrollbars/rendering.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode, findDOMNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests(scrollbarWidth) {
6 | describe('rendering', () => {
7 | let node
8 | beforeEach(() => {
9 | node = document.createElement('div')
10 | document.body.appendChild(node)
11 | })
12 | afterEach(() => {
13 | unmountComponentAtNode(node)
14 | document.body.removeChild(node)
15 | })
16 |
17 | describe('when Scrollbars are rendered', () => {
18 | it('takes className', done => {
19 | render((
20 |
21 |
22 |
23 | ), node, function callback() {
24 | expect(findDOMNode(this).className).toEqual('foo')
25 | done()
26 | })
27 | })
28 |
29 | it('takes styles', done => {
30 | render((
31 |
32 |
33 |
34 | ), node, function callback() {
35 | expect(findDOMNode(this).style.width).toEqual('100px')
36 | expect(findDOMNode(this).style.height).toEqual('100px')
37 | expect(findDOMNode(this).style.overflow).toEqual('hidden')
38 | done()
39 | })
40 | })
41 |
42 | it('renders view', done => {
43 | render((
44 |
45 |
46 |
47 | ), node, function callback() {
48 | expect(this.refs.view).toBeA(Node)
49 | done()
50 | })
51 | })
52 |
53 | describe('when using custom tagName', () => {
54 | it('should use the defined tagName', done => {
55 | render((
56 |
59 |
60 |
61 | ), node, function callback() {
62 | const el = findDOMNode(this)
63 | expect(el.tagName.toLowerCase()).toEqual('nav')
64 | done()
65 | })
66 | })
67 | })
68 |
69 | describe('when custom `renderView` is passed', () => {
70 | it('should render custom element', done => {
71 | render((
72 | }>
75 |
76 |
77 | ), node, function callback() {
78 | expect(this.refs.view.tagName).toEqual('SECTION')
79 | expect(this.refs.view.style.color).toEqual('red')
80 | expect(this.refs.view.style.position).toEqual('absolute')
81 | done()
82 | })
83 | })
84 | })
85 |
86 | describe('when native scrollbars have a width', () => {
87 | if (!scrollbarWidth) return
88 |
89 | it('hides native scrollbars', done => {
90 | render((
91 |
92 |
93 |
94 | ), node, function callback() {
95 | const width = `-${scrollbarWidth}px`
96 | expect(this.refs.view.style.marginRight).toEqual(width)
97 | expect(this.refs.view.style.marginBottom).toEqual(width)
98 | done()
99 | })
100 | })
101 |
102 | it('renders bars', done => {
103 | render((
104 |
105 |
106 |
107 | ), node, function callback() {
108 | expect(this.refs.trackHorizontal).toBeA(Node)
109 | expect(this.refs.trackVertical).toBeA(Node)
110 | done()
111 | })
112 | })
113 |
114 | it('renders thumbs', done => {
115 | render((
116 |
117 |
118 |
119 | ), node, function callback() {
120 | expect(this.refs.thumbHorizontal).toBeA(Node)
121 | expect(this.refs.thumbVertical).toBeA(Node)
122 | done()
123 | })
124 | })
125 |
126 | it('renders thumbs with correct size', done => {
127 | render((
128 |
129 |
130 |
131 | ), node, function callback() {
132 | setTimeout(() => {
133 | // 100 / 200 * 96 = 48
134 | expect(this.refs.thumbVertical.style.height).toEqual('48px')
135 | expect(this.refs.thumbHorizontal.style.width).toEqual('48px')
136 | done()
137 | }, 100)
138 | })
139 | })
140 |
141 | it('the thumbs size should not be less than the given `thumbMinSize`', done => {
142 | render((
143 |
144 |
145 |
146 | ), node, function callback() {
147 | setTimeout(() => {
148 | // 100 / 200 * 96 = 48
149 | expect(this.refs.thumbVertical.style.height).toEqual('30px')
150 | expect(this.refs.thumbHorizontal.style.width).toEqual('30px')
151 | done()
152 | }, 100)
153 | })
154 | })
155 |
156 | describe('when thumbs have a fixed size', () => {
157 | it('thumbs should have the given fixed size', done => {
158 | render((
159 |
160 |
161 |
162 | ), node, function callback() {
163 | setTimeout(() => {
164 | // 100 / 200 * 96 = 48
165 | expect(this.refs.thumbVertical.style.height).toEqual('50px')
166 | expect(this.refs.thumbHorizontal.style.width).toEqual('50px')
167 | done()
168 | }, 100)
169 | })
170 | })
171 | })
172 |
173 | describe('when custom `renderTrackHorizontal` is passed', () => {
174 | it('should render custom element', done => {
175 | render((
176 | }>
179 |
180 |
181 | ), node, function callback() {
182 | expect(this.refs.trackHorizontal.tagName).toEqual('SECTION')
183 | expect(this.refs.trackHorizontal.style.position).toEqual('absolute')
184 | expect(this.refs.trackHorizontal.style.color).toEqual('red')
185 | done()
186 | })
187 | })
188 | })
189 |
190 | describe('when custom `renderTrackVertical` is passed', () => {
191 | it('should render custom element', done => {
192 | render((
193 | }>
196 |
197 |
198 | ), node, function callback() {
199 | expect(this.refs.trackVertical.tagName).toEqual('SECTION')
200 | expect(this.refs.trackVertical.style.position).toEqual('absolute')
201 | expect(this.refs.trackVertical.style.color).toEqual('red')
202 | done()
203 | })
204 | })
205 | })
206 |
207 | describe('when custom `renderThumbHorizontal` is passed', () => {
208 | it('should render custom element', done => {
209 | render((
210 | }>
213 |
214 |
215 | ), node, function callback() {
216 | expect(this.refs.thumbHorizontal.tagName).toEqual('SECTION')
217 | expect(this.refs.thumbHorizontal.style.position).toEqual('relative')
218 | expect(this.refs.thumbHorizontal.style.color).toEqual('red')
219 | done()
220 | })
221 | })
222 | })
223 |
224 | describe('when custom `renderThumbVertical` is passed', () => {
225 | it('should render custom element', done => {
226 | render((
227 | }>
230 |
231 |
232 | ), node, function callback() {
233 | expect(this.refs.thumbVertical.tagName).toEqual('SECTION')
234 | expect(this.refs.thumbVertical.style.position).toEqual('relative')
235 | expect(this.refs.thumbVertical.style.color).toEqual('red')
236 | done()
237 | })
238 | })
239 | })
240 |
241 | it('positions view absolute', done => {
242 | render((
243 |
244 |
245 |
246 | ), node, function callback() {
247 | expect(this.refs.view.style.position).toEqual('absolute')
248 | expect(this.refs.view.style.top).toEqual('0px')
249 | expect(this.refs.view.style.left).toEqual('0px')
250 | done()
251 | })
252 | })
253 |
254 | it('should not override the scrollbars width/height values', done => {
255 | render((
256 |
259 |
}
260 | renderTrackVertical={({ style, ...props }) =>
261 |
}>
262 |
263 |
264 | ), node, function callback() {
265 | setTimeout(() => {
266 | expect(this.refs.trackHorizontal.style.height).toEqual('10px')
267 | expect(this.refs.trackVertical.style.width).toEqual('10px')
268 | done()
269 | }, 100)
270 | })
271 | })
272 |
273 | describe('when view does not overflow container', () => {
274 | it('should hide scrollbars', done => {
275 | render((
276 |
279 |
}
280 | renderTrackVertical={({ style, ...props }) =>
281 |
}>
282 |
283 |
284 | ), node, function callback() {
285 | setTimeout(() => {
286 | expect(this.refs.thumbHorizontal.style.width).toEqual('0px')
287 | expect(this.refs.thumbVertical.style.height).toEqual('0px')
288 | done()
289 | }, 100)
290 | })
291 | })
292 | })
293 | })
294 |
295 | describe('when native scrollbars have no width', () => {
296 | if (scrollbarWidth) return
297 |
298 | it('hides bars', done => {
299 | render((
300 |
301 |
302 |
303 | ), node, function callback() {
304 | setTimeout(() => {
305 | expect(this.refs.trackVertical.style.display).toEqual('none')
306 | expect(this.refs.trackHorizontal.style.display).toEqual('none')
307 | done()
308 | }, 100)
309 | })
310 | })
311 | })
312 | })
313 |
314 | describe('when rerendering Scrollbars', () => {
315 | function renderScrollbars(callback) {
316 | render((
317 |
318 |
319 |
320 | ), node, callback)
321 | }
322 | it('should update scrollbars', done => {
323 | renderScrollbars(function callback() {
324 | const spy = spyOn(this, 'update').andCallThrough()
325 | renderScrollbars(function rerenderCallback() {
326 | expect(spy.calls.length).toEqual(1)
327 | spy.restore()
328 | done()
329 | })
330 | })
331 | })
332 | })
333 | })
334 | }
335 |
--------------------------------------------------------------------------------
/test/Scrollbars/resizing.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 | import simulant from 'simulant'
5 |
6 | export default function createTests(scrollbarWidth) {
7 | // Not for mobile environment
8 | if (!scrollbarWidth) return
9 |
10 | let node
11 | beforeEach(() => {
12 | node = document.createElement('div')
13 | document.body.appendChild(node)
14 | })
15 | afterEach(() => {
16 | unmountComponentAtNode(node)
17 | document.body.removeChild(node)
18 | })
19 |
20 | describe('when resizing window', () => {
21 | it('should update scrollbars', done => {
22 | render((
23 |
24 |
25 |
26 | ), node, function callback() {
27 | setTimeout(() => {
28 | const spy = spyOn(this, 'update')
29 | simulant.fire(window, 'resize')
30 | expect(spy.calls.length).toEqual(1)
31 | done()
32 | }, 100)
33 | })
34 | })
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/test/Scrollbars/scrolling.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests(scrollbarWidth, envScrollbarWidth) {
6 | let node
7 | beforeEach(() => {
8 | node = document.createElement('div')
9 | document.body.appendChild(node)
10 | })
11 | afterEach(() => {
12 | unmountComponentAtNode(node)
13 | document.body.removeChild(node)
14 | })
15 |
16 | describe('when scrolling', () => {
17 | describe('when native scrollbars have a width', () => {
18 | if (!scrollbarWidth) return
19 | it('should update thumbs position', done => {
20 | render((
21 |
22 |
23 |
24 | ), node, function callback() {
25 | this.scrollTop(50)
26 | this.scrollLeft(50)
27 | setTimeout(() => {
28 | if (scrollbarWidth) {
29 | // 50 / (200 - 100) * (96 - 48) = 24
30 | expect(this.refs.thumbVertical.style.transform).toEqual('translateY(24px)')
31 | expect(this.refs.thumbHorizontal.style.transform).toEqual('translateX(24px)')
32 | } else {
33 | expect(this.refs.thumbVertical.style.transform).toEqual('')
34 | expect(this.refs.thumbHorizontal.style.transform).toEqual('')
35 | }
36 | done()
37 | }, 100)
38 | })
39 | })
40 | })
41 |
42 | it('should not trigger a rerender', () => {
43 | render((
44 |
45 |
46 |
47 | ), node, function callback() {
48 | const spy = spyOn(this, 'render').andCallThrough()
49 | this.scrollTop(50)
50 | expect(spy.calls.length).toEqual(0)
51 | spy.restore()
52 | })
53 | })
54 |
55 | describe('when scrolling x-axis', () => {
56 | it('should call `onScroll`', done => {
57 | const spy = createSpy()
58 | render((
59 |
60 |
61 |
62 | ), node, function callback() {
63 | this.scrollLeft(50)
64 | setTimeout(() => {
65 | expect(spy.calls.length).toEqual(1)
66 | const args = spy.calls[0].arguments
67 | const event = args[0]
68 | expect(event).toBeA(Event)
69 | done()
70 | }, 100)
71 | })
72 | })
73 | it('should call `onScrollFrame`', done => {
74 | const spy = createSpy()
75 | render((
76 |
77 |
78 |
79 | ), node, function callback() {
80 | this.scrollLeft(50)
81 | setTimeout(() => {
82 | expect(spy.calls.length).toEqual(1)
83 | const args = spy.calls[0].arguments
84 | const values = args[0]
85 | expect(values).toBeA(Object)
86 |
87 | if (scrollbarWidth) {
88 | expect(values).toEqual({
89 | left: 0.5,
90 | top: 0,
91 | scrollLeft: 50,
92 | scrollTop: 0,
93 | scrollWidth: 200,
94 | scrollHeight: 200,
95 | clientWidth: 100,
96 | clientHeight: 100,
97 | })
98 | } else {
99 | expect(values).toEqual({
100 | left: values.scrollLeft / (values.scrollWidth - (values.clientWidth)),
101 | top: 0,
102 | scrollLeft: 50,
103 | scrollTop: 0,
104 | scrollWidth: 200,
105 | scrollHeight: 200,
106 | clientWidth: 100 - envScrollbarWidth,
107 | clientHeight: 100 - envScrollbarWidth,
108 | })
109 | }
110 | done()
111 | }, 100)
112 | })
113 | })
114 | it('should call `onScrollStart` once', done => {
115 | const spy = createSpy()
116 | render((
117 |
118 |
119 |
120 | ), node, function callback() {
121 | let left = 0
122 | const interval = setInterval(() => {
123 | this.scrollLeft(++left)
124 | if (left >= 50) {
125 | clearInterval(interval)
126 | expect(spy.calls.length).toEqual(1)
127 | done()
128 | }
129 | }, 10)
130 | })
131 | })
132 | it('should call `onScrollStop` once when scrolling stops', done => {
133 | const spy = createSpy()
134 | render((
135 |
136 |
137 |
138 | ), node, function callback() {
139 | let left = 0
140 | const interval = setInterval(() => {
141 | this.scrollLeft(++left)
142 | if (left >= 50) {
143 | clearInterval(interval)
144 | setTimeout(() => {
145 | expect(spy.calls.length).toEqual(1)
146 | done()
147 | }, 300)
148 | }
149 | }, 10)
150 | })
151 | })
152 | })
153 |
154 | describe('when scrolling y-axis', () => {
155 | it('should call `onScroll`', done => {
156 | const spy = createSpy()
157 | render((
158 |
159 |
160 |
161 | ), node, function callback() {
162 | this.scrollTop(50)
163 | setTimeout(() => {
164 | expect(spy.calls.length).toEqual(1)
165 | const args = spy.calls[0].arguments
166 | const event = args[0]
167 | expect(event).toBeA(Event)
168 | done()
169 | }, 100)
170 | })
171 | })
172 | it('should call `onScrollFrame`', done => {
173 | const spy = createSpy()
174 | render((
175 |
176 |
177 |
178 | ), node, function callback() {
179 | this.scrollTop(50)
180 | setTimeout(() => {
181 | expect(spy.calls.length).toEqual(1)
182 | const args = spy.calls[0].arguments
183 | const values = args[0]
184 | expect(values).toBeA(Object)
185 |
186 | if (scrollbarWidth) {
187 | expect(values).toEqual({
188 | left: 0,
189 | top: 0.5,
190 | scrollLeft: 0,
191 | scrollTop: 50,
192 | scrollWidth: 200,
193 | scrollHeight: 200,
194 | clientWidth: 100,
195 | clientHeight: 100,
196 | })
197 | } else {
198 | expect(values).toEqual({
199 | left: 0,
200 | top: values.scrollTop / (values.scrollHeight - (values.clientHeight)),
201 | scrollLeft: 0,
202 | scrollTop: 50,
203 | scrollWidth: 200,
204 | scrollHeight: 200,
205 | clientWidth: 100 - envScrollbarWidth,
206 | clientHeight: 100 - envScrollbarWidth,
207 | })
208 | }
209 | done()
210 | }, 100)
211 | })
212 | })
213 | it('should call `onScrollStart` once', done => {
214 | const spy = createSpy()
215 | render((
216 |
217 |
218 |
219 | ), node, function callback() {
220 | let top = 0
221 | const interval = setInterval(() => {
222 | this.scrollTop(++top)
223 | if (top >= 50) {
224 | clearInterval(interval)
225 | expect(spy.calls.length).toEqual(1)
226 | done()
227 | }
228 | }, 10)
229 | })
230 | })
231 | it('should call `onScrollStop` once when scrolling stops', done => {
232 | const spy = createSpy()
233 | render((
234 |
235 |
236 |
237 | ), node, function callback() {
238 | let top = 0
239 | const interval = setInterval(() => {
240 | this.scrollTop(++top)
241 | if (top >= 50) {
242 | clearInterval(interval)
243 | setTimeout(() => {
244 | expect(spy.calls.length).toEqual(1)
245 | done()
246 | }, 300)
247 | }
248 | }, 10)
249 | })
250 | })
251 | })
252 | })
253 | }
254 |
--------------------------------------------------------------------------------
/test/Scrollbars/universal.js:
--------------------------------------------------------------------------------
1 | import { Scrollbars } from 'preact-custom-scrollbars'
2 | import { render, unmountComponentAtNode } from 'preact-dom'
3 | import Preact from 'preact'
4 |
5 | export default function createTests(scrollbarWidth) {
6 | let node
7 | beforeEach(() => {
8 | node = document.createElement('div')
9 | document.body.appendChild(node)
10 | })
11 | afterEach(() => {
12 | unmountComponentAtNode(node)
13 | document.body.removeChild(node)
14 | })
15 |
16 | describe('universal', () => {
17 | describe('default', () => {
18 | describe('when rendered', () => {
19 | it('should hide overflow', done => {
20 | class ScrollbarsTest extends Scrollbars {
21 | // Override componentDidMount, so we can check, how the markup
22 | // looks like on the first rendering
23 | componentDidMount() {}
24 | }
25 | render((
26 |
27 |
28 |
29 | ), node, function callback() {
30 | const { view, trackHorizontal, trackVertical } = this.refs
31 | expect(view.style.position).toEqual('absolute')
32 | expect(view.style.overflow).toEqual('hidden')
33 | expect(view.style.top).toEqual('0px')
34 | expect(view.style.bottom).toEqual('0px')
35 | expect(view.style.left).toEqual('0px')
36 | expect(view.style.right).toEqual('0px')
37 | expect(view.style.marginBottom).toEqual('0px')
38 | expect(view.style.marginRight).toEqual('0px')
39 | expect(trackHorizontal.style.display).toEqual('none')
40 | expect(trackVertical.style.display).toEqual('none')
41 | done()
42 | })
43 | })
44 | })
45 | describe('when componentDidMount', () => {
46 | it('should rerender', done => {
47 | render((
48 |
49 |
50 |
51 | ), node, function callback() {
52 | const { view } = this.refs
53 | expect(view.style.overflow).toEqual('scroll')
54 | expect(view.style.marginBottom).toEqual(`${-scrollbarWidth}px`)
55 | expect(view.style.marginRight).toEqual(`${-scrollbarWidth}px`)
56 | done()
57 | })
58 | })
59 | })
60 | })
61 | describe('when using autoHeight', () => {
62 | describe('when rendered', () => {
63 | it('should hide overflow', done => {
64 | class ScrollbarsTest extends Scrollbars {
65 | // Override componentDidMount, so we can check, how the markup
66 | // looks like on the first rendering
67 | componentDidMount() {}
68 | }
69 | render((
70 |
71 |
72 |
73 | ), node, function callback() {
74 | const { view, trackHorizontal, trackVertical } = this.refs
75 | expect(view.style.position).toEqual('relative')
76 | expect(view.style.overflow).toEqual('hidden')
77 | expect(view.style.marginBottom).toEqual('0px')
78 | expect(view.style.marginRight).toEqual('0px')
79 | expect(view.style.minHeight).toEqual('0px')
80 | expect(view.style.maxHeight).toEqual('100px')
81 | expect(trackHorizontal.style.display).toEqual('none')
82 | expect(trackVertical.style.display).toEqual('none')
83 | done()
84 | })
85 | })
86 | })
87 | describe('when componentDidMount', () => {
88 | it('should rerender', done => {
89 | render((
90 |
91 |
92 |
93 | ), node, function callback() {
94 | const { view } = this.refs
95 | expect(view.style.overflow).toEqual('scroll')
96 | expect(view.style.marginBottom).toEqual(`${-scrollbarWidth}px`)
97 | expect(view.style.marginRight).toEqual(`${-scrollbarWidth}px`)
98 | expect(view.style.minHeight).toEqual(`${scrollbarWidth}px`)
99 | expect(view.style.maxHeight).toEqual(`${100 + scrollbarWidth}px`)
100 | done()
101 | })
102 | })
103 | })
104 | })
105 | })
106 | }
107 |
--------------------------------------------------------------------------------
/test/browser.spec.js:
--------------------------------------------------------------------------------
1 | import getScrollbarWidth from '../src/utils/getScrollbarWidth'
2 | import createTests from './Scrollbars'
3 |
4 | describe('Scrollbars (browser)', () => {
5 | createTests(getScrollbarWidth(), getScrollbarWidth())
6 | })
7 |
--------------------------------------------------------------------------------
/test/mobile.spec.js:
--------------------------------------------------------------------------------
1 | const getScrollbarWidthModule = require('../src/utils/getScrollbarWidth')
2 | const envScrollbarWidth = getScrollbarWidthModule.default()
3 | import createTests from './Scrollbars'
4 |
5 | describe('Scrollbars (mobile)', () => {
6 | const mobileScrollbarsWidth = 0
7 | let getScrollbarWidthSpy
8 |
9 | before(() => {
10 | getScrollbarWidthSpy = spyOn(getScrollbarWidthModule, 'default')
11 | getScrollbarWidthSpy.andReturn(mobileScrollbarsWidth)
12 | })
13 |
14 | after(() => {
15 | getScrollbarWidthSpy.restore()
16 | })
17 |
18 | createTests(mobileScrollbarsWidth, envScrollbarWidth)
19 | })
20 |
--------------------------------------------------------------------------------
/test/utils.spec.js:
--------------------------------------------------------------------------------
1 | import returnFalse from '../src/utils/returnFalse'
2 | describe('utils', () => {
3 | describe('returnFalse', () => {
4 | it('should return false', done => {
5 | expect(returnFalse()).toEqual(false)
6 | done()
7 | })
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 |
5 | var plugins = [
6 | new webpack.optimize.OccurenceOrderPlugin(),
7 | new webpack.DefinePlugin({
8 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
9 | })
10 | ];
11 |
12 | if (process.env.NODE_ENV === 'production') {
13 | plugins.push(
14 | new webpack.optimize.UglifyJsPlugin({
15 | compressor: {
16 | screw_ie8: true,
17 | warnings: false
18 | }
19 | })
20 | );
21 | }
22 |
23 | module.exports = {
24 | externals: {
25 | preact: {
26 | root: 'Preact',
27 | commonjs2: 'preact',
28 | commonjs: 'preact',
29 | amd: 'preact'
30 | }
31 | },
32 | module: {
33 | loaders: [{
34 | test: /\.js$/,
35 | loaders: ['babel-loader'],
36 | exclude: /node_modules/
37 | }]
38 | },
39 | output: {
40 | library: 'PreactCustomScrollbars',
41 | libraryTarget: 'umd'
42 | },
43 | plugins: plugins,
44 | resolve: {
45 | extensions: ['', '.js']
46 | }
47 | };
48 |
--------------------------------------------------------------------------------