Skip to content

Commit 531a90b

Browse files
evinmaBuptStEve
authored andcommitted
refactor: use typescript (#25)
* refactor: ts refactor * refactor: remove src/index.d.ts, remove @ts-ignore * refactor: add utils.ts * feat: jest test utils.ts
1 parent bc99727 commit 531a90b

10 files changed

+243
-66
lines changed

.eslintrc.js

+33-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
module.exports = {
2-
extends: 'standard',
3-
parser: 'babel-eslint',
2+
root: true,
3+
parserOptions: {
4+
parser: '@typescript-eslint/parser',
5+
sourceType: 'module',
6+
ecmaFeatures: {
7+
legacyDecorators: true,
8+
},
9+
},
10+
env: {
11+
es6: true,
12+
node: true,
13+
jest: true,
14+
browser: true,
15+
},
16+
extends: [
17+
'standard',
18+
'plugin:@typescript-eslint/recommended',
19+
],
20+
plugins: [
21+
'@typescript-eslint',
22+
],
423
rules: {
5-
'indent': [2, 4],
6-
'promise/param-names': 0,
7-
'comma-dangle': [2, 'always-multiline'],
24+
'semi': 'off',
25+
'indent': ['error', 4],
26+
'promise/param-names': 'off',
27+
'comma-dangle': ['error', 'always-multiline'],
28+
'@typescript-eslint/semi': ['error', 'never'],
29+
'@typescript-eslint/no-var-requires': 'off',
30+
'@typescript-eslint/explicit-function-return-type': 'off',
31+
'@typescript-eslint/explicit-member-accessibility': 'off',
32+
'@typescript-eslint/member-delimiter-style': ['error', {
33+
'multiline': { 'delimiter': 'none' },
34+
'singleline': { 'delimiter': 'comma' },
35+
}],
836
},
937
}

babel.config.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
module.exports = {
2-
presets: [
3-
[
4-
'@babel/preset-env',
5-
{ 'modules': false },
6-
],
7-
],
2+
env: {
3+
test: {
4+
presets: [
5+
[
6+
'@babel/preset-env',
7+
{ targets: { node: 'current' } },
8+
],
9+
'@babel/typescript',
10+
],
11+
},
12+
production: {
13+
presets: [
14+
[
15+
'@babel/preset-env',
16+
{ modules: false },
17+
],
18+
'@babel/typescript',
19+
],
20+
},
21+
},
822
}

jest.config.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
verbose: false,
3+
testURL: 'http://localhost',
4+
collectCoverage: true,
5+
collectCoverageFrom: ['src/**'],
6+
transform: {
7+
'^.+\\.[j|t]sx?$': 'babel-jest',
8+
},
9+
transformIgnorePatterns: ['node_modules'],
10+
moduleNameMapper: {
11+
'@/(.*)$': '<rootDir>/src/$1',
12+
},
13+
moduleFileExtensions: ['js', 'ts', 'tsx'],
14+
snapshotSerializers: [],
15+
}

package.json

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
{
22
"name": "tua-body-scroll-lock",
3-
"version": "0.2.2",
3+
"version": "0.3.0",
44
"description": "🔐Body scroll locking that just works with everything",
55
"main": "dist/tua-bsl.umd.js",
66
"module": "dist/tua-bsl.esm.js",
77
"unpkg": "dist/tua-bsl.umd.js",
88
"jsdelivr": "dist/tua-bsl.umd.js",
9-
"typings": "src/index.d.ts",
9+
"typings": "dist/index.d.ts",
1010
"scripts": {
11+
"cov": "open coverage/lcov-report/index.html",
12+
"type-check": "tsc --noEmit",
13+
"type-check:watch": "npm run type-check -- --watch",
1114
"lint": "eslint --fix ./",
1215
"start": "rollup -c -w",
16+
"test": "npm run type-check && cross-env NODE_ENV=test jest",
17+
"test:tdd": "cross-env NODE_ENV=test jest --watch",
1318
"build": "npm run lint && rollup -c && cp index.html dist/index.html",
1419
"next": "npm --no-git-tag-version version prerelease",
1520
"next:m": "npm --no-git-tag-version version preminor",
@@ -40,24 +45,34 @@
4045
"devDependencies": {
4146
"@babel/core": "^7.3.3",
4247
"@babel/preset-env": "^7.3.1",
48+
"@babel/preset-typescript": "^7.3.3",
4349
"@commitlint/cli": "^7.5.2",
4450
"@commitlint/config-conventional": "^7.5.0",
51+
"@types/jest": "^24.0.17",
52+
"@typescript-eslint/eslint-plugin": "^1.13.0",
53+
"@typescript-eslint/parser": "^1.13.0",
4554
"all-contributors-cli": "^6.3.0",
4655
"babel-eslint": "^10.0.1",
56+
"babel-jest": "^24.8.0",
57+
"cross-env": "^5.2.0",
4758
"eslint-config-standard": "^12.0.0",
4859
"eslint-plugin-import": "^2.16.0",
4960
"eslint-plugin-node": "^8.0.1",
5061
"eslint-plugin-promise": "^4.0.1",
5162
"eslint-plugin-standard": "^4.0.0",
5263
"gh-pages": "^2.0.1",
5364
"husky": "^1.3.1",
65+
"jest": "^24.8.0",
66+
"jest-environment-jsdom-thirteen": "^1.0.1",
5467
"lint-staged": "^8.1.4",
5568
"rollup": "^1.2.2",
5669
"rollup-plugin-babel": "^4.3.2",
5770
"rollup-plugin-eslint": "^5.0.0",
5871
"rollup-plugin-json": "^3.1.0",
5972
"rollup-plugin-replace": "^2.1.0",
60-
"rollup-plugin-terser": "^5.0.0"
73+
"rollup-plugin-terser": "^5.0.0",
74+
"rollup-plugin-typescript2": "^0.22.1",
75+
"typescript": "^3.5.3"
6176
},
6277
"repository": {
6378
"type": "git",

rollup.config.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import replace from 'rollup-plugin-replace'
33
import { eslint } from 'rollup-plugin-eslint'
44
import { terser } from 'rollup-plugin-terser'
55
import { DEFAULT_EXTENSIONS } from '@babel/core'
6+
import typescript from 'rollup-plugin-typescript2'
67

78
const pkg = require('./package.json')
89

@@ -48,8 +49,14 @@ const genConfig = (opts) => {
4849
const isProd = /min\.js$/.test(opts.file)
4950

5051
const config = {
51-
input: 'src/index.js',
52-
plugins: [eslint({ include: '**/*.js' })],
52+
input: 'src/index.ts',
53+
plugins: [
54+
typescript({
55+
cacheRoot: `${require('os').tmpdir()}/.rpt2_cache`,
56+
useTsconfigDeclarationDir: true,
57+
}),
58+
eslint({ include: '**/*.js' }),
59+
],
5360
output: {
5461
file: opts.file,
5562
name: 'bodyScrollLock',

src/index.d.ts

-2
This file was deleted.

src/index.js src/index.ts

+38-46
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,20 @@
1+
import {
2+
$,
3+
isServer,
4+
detectOS,
5+
getEventListenerOptions,
6+
} from './utils'
7+
8+
type OverflowHiddenPcStyleType = 'overflow' | 'boxSizing' | 'paddingRight'
9+
type OverflowHiddenMobileStyleType = 'top' | 'width' | 'height' | 'overflow' | 'position'
10+
111
let lockedNum = 0
212
let initialClientY = 0
3-
let unLockCallback = null
13+
let unLockCallback: any = null
414
let documentListenerAdded = false
515

6-
const isServer = typeof window === 'undefined'
7-
const lockedElements = []
8-
9-
const $ = !isServer && document.querySelector.bind(document)
10-
11-
let eventListenerOptions
12-
if (!isServer) {
13-
const testEvent = '__TUA_BSL_TEST_PASSIVE__'
14-
const passiveTestOptions = {
15-
get passive () {
16-
eventListenerOptions = { passive: false }
17-
},
18-
}
19-
window.addEventListener(testEvent, null, passiveTestOptions)
20-
window.removeEventListener(testEvent, null, passiveTestOptions)
21-
}
22-
23-
const detectOS = () => {
24-
const ua = navigator.userAgent
25-
const ipad = /(iPad).*OS\s([\d_]+)/.test(ua)
26-
const iphone = !ipad && /(iPhone\sOS)\s([\d_]+)/.test(ua)
27-
const android = /(Android);?[\s/]+([\d.]+)?/.test(ua)
28-
29-
const os = android ? 'android' : 'ios'
30-
const ios = iphone || ipad
31-
32-
return { os, ios, ipad, iphone, android }
33-
}
16+
const lockedElements: HTMLElement[] = []
17+
const eventListenerOptions = getEventListenerOptions({ passive: false })
3418

3519
const setOverflowHiddenPc = () => {
3620
const $body = $('body')
@@ -42,7 +26,7 @@ const setOverflowHiddenPc = () => {
4226
$body.style.paddingRight = `${scrollBarWidth}px`
4327

4428
return () => {
45-
;['overflow', 'boxSizing', 'paddingRight'].forEach((x) => {
29+
;['overflow', 'boxSizing', 'paddingRight'].forEach((x: OverflowHiddenPcStyleType) => {
4630
$body.style[x] = bodyStyle[x] || ''
4731
})
4832
}
@@ -68,21 +52,21 @@ const setOverflowHiddenMobile = () => {
6852
$html.style.height = htmlStyle.height || ''
6953
$html.style.overflow = htmlStyle.overflow || ''
7054

71-
;['top', 'width', 'height', 'overflow', 'position'].forEach((x) => {
55+
;['top', 'width', 'height', 'overflow', 'position'].forEach((x: OverflowHiddenMobileStyleType) => {
7256
$body.style[x] = bodyStyle[x] || ''
7357
})
7458

7559
window.scrollTo(0, scrollTop)
7660
}
7761
}
7862

79-
const preventDefault = (event) => {
63+
const preventDefault = (event: TouchEvent) => {
8064
if (!event.cancelable) return
8165

8266
event.preventDefault()
8367
}
8468

85-
const handleScroll = (event, targetElement) => {
69+
const handleScroll = (event: TouchEvent, targetElement: HTMLElement) => {
8670
const clientY = event.targetTouches[0].clientY - initialClientY
8771

8872
if (targetElement) {
@@ -99,7 +83,7 @@ const handleScroll = (event, targetElement) => {
9983
return true
10084
}
10185

102-
const checkTargetElement = (targetElement) => {
86+
const checkTargetElement = (targetElement?: HTMLElement) => {
10387
if (targetElement) return
10488
if (targetElement === null) return
10589
if (process.env.NODE_ENV === 'production') return
@@ -110,8 +94,8 @@ const checkTargetElement = (targetElement) => {
11094
)
11195
}
11296

113-
const lock = (targetElement) => {
114-
if (isServer) return
97+
const lock = (targetElement?: HTMLElement) => {
98+
if (isServer()) return
11599

116100
checkTargetElement(targetElement)
117101

@@ -136,30 +120,38 @@ const lock = (targetElement) => {
136120
documentListenerAdded = true
137121
}
138122
} else if (lockedNum <= 0) {
139-
unLockCallback = detectOS().android ? setOverflowHiddenMobile() : setOverflowHiddenPc()
123+
unLockCallback = detectOS().android
124+
? setOverflowHiddenMobile()
125+
: setOverflowHiddenPc()
140126
}
141127

142128
lockedNum += 1
143129
}
144130

145-
const unlock = (targetElement) => {
146-
if (isServer) return
131+
const unlock = (targetElement?: HTMLElement) => {
132+
if (isServer()) return
147133

148134
checkTargetElement(targetElement)
149135
lockedNum -= 1
150136

151137
if (lockedNum > 0) return
152-
if (!detectOS().ios) {
153-
lockedNum <= 0 && typeof unLockCallback === 'function' && unLockCallback()
138+
if (
139+
!detectOS().ios &&
140+
typeof unLockCallback === 'function'
141+
) {
142+
unLockCallback()
154143
return
155144
}
156145

157146
// iOS
158-
const index = lockedElements.indexOf(targetElement)
159-
if (index !== -1) {
160-
targetElement.ontouchmove = null
161-
targetElement.ontouchstart = null
162-
lockedElements.splice(index, 1)
147+
if (targetElement) {
148+
const index = lockedElements.indexOf(targetElement)
149+
150+
if (index !== -1) {
151+
targetElement.ontouchmove = null
152+
targetElement.ontouchstart = null
153+
lockedElements.splice(index, 1)
154+
}
163155
}
164156

165157
if (documentListenerAdded) {

src/utils.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export const isServer = () => typeof window === 'undefined'
2+
3+
export const $ = (selector: string) => <HTMLElement>document.querySelector(selector)
4+
5+
export interface DetectOSResult { ios: boolean, android: boolean }
6+
export const detectOS = (ua?: string): DetectOSResult => {
7+
ua = ua || navigator.userAgent
8+
const ipad = /(iPad).*OS\s([\d_]+)/.test(ua)
9+
const iphone = !ipad && /(iPhone\sOS)\s([\d_]+)/.test(ua)
10+
const android = /(Android);?[\s/]+([\d.]+)?/.test(ua)
11+
const ios = iphone || ipad
12+
13+
return { ios, android }
14+
}
15+
16+
export function getEventListenerOptions (options: AddEventListenerOptions): AddEventListenerOptions | boolean {
17+
/* istanbul ignore if */
18+
if (isServer()) return false
19+
20+
if (!options) {
21+
throw new Error('options must be provided')
22+
}
23+
let isSupportOptions = false
24+
const listenerOptions = <AddEventListenerOptions>{
25+
get passive () {
26+
isSupportOptions = true
27+
return
28+
},
29+
}
30+
31+
/* istanbul ignore next */
32+
const noop = () => {}
33+
const testEvent = '__TUA_BSL_TEST_PASSIVE__'
34+
window.addEventListener(testEvent, noop, listenerOptions)
35+
window.removeEventListener(testEvent, noop, listenerOptions)
36+
37+
const { capture } = options
38+
39+
/* istanbul ignore next */
40+
return isSupportOptions
41+
? options
42+
: typeof capture !== 'undefined'
43+
? capture
44+
: false
45+
}

0 commit comments

Comments
 (0)