Skip to content

Commit

Permalink
feat/migrate-utilities
Browse files Browse the repository at this point in the history
- [WIP] migrate utility methods.
  • Loading branch information
jw-foss committed Jul 29, 2020
1 parent a9b8c37 commit 5b98819
Show file tree
Hide file tree
Showing 12 changed files with 614 additions and 3 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@storybook/addon-storysource": "^5.3.19",
"@storybook/html": "^5.3.19",
"@types/jest": "^26.0.7",
"@types/lodash-es": "^4.17.3",
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"@vue/compiler-sfc": "^3.0.0-rc.1",
Expand All @@ -30,9 +31,9 @@
"eslint": "^7.5.0",
"eslint-plugin-vue": "^7.0.0-beta.0",
"husky": "^4.2.5",
"lint-staged": "^10.2.11",
"jest": "^24.1.0",
"lerna": "^3.22.1",
"lint-staged": "^10.2.11",
"ts-jest": "^26.1.3",
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
Expand Down Expand Up @@ -63,5 +64,8 @@
"description": "A Component Library for Vue3.0",
"main": "index.js",
"repository": "[email protected]:element-plus/element-plus.git",
"license": "MIT"
"license": "MIT",
"dependencies": {
"lodash-es": "^4.17.15"
}
}
83 changes: 83 additions & 0 deletions packages/utils/click-outside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {DirectiveBinding, VNode} from 'vue'
import isServer from './isServer'
import { on } from './dom'

const nodeList = []
const ctx = '@@clickoutsideContext'

let startClick
let seed = 0

!isServer && on(document, 'mousedown', e => (startClick = e))

!isServer &&
on(document, 'mouseup', (e: Event) => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick))
})

function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding, vnode: VNode) {
return function(mouseup = {}, mousedown = {}) {
if (
!vnode ||
!vnode.component ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.component.popperElm &&
(vnode.component.popperElm.contains(mouseup.target) ||
vnode.component.popperElm.contains(mousedown.target)))
)
return

if (
binding.value &&
el[ctx].methodName &&
vnode.component[el[ctx].methodName]
) {
vnode.component[el[ctx].methodName]()
} else {
el[ctx].bindingFn && el[ctx].bindingFn()
}
}
}

/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose">
* ```
*/
export default {
mounted(el: HTMLElement, binding: DirectiveBinding, vnode: VNode):void {
nodeList.push(el)
const id = seed++
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.arg,
bindingFn: binding.value,
}
},

updated(el: HTMLElement, binding: DirectiveBinding, vnode: VNode): void {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode)
el[ctx].methodName = binding.arg
el[ctx].bindingFn = binding.value
},

unmounted(el: HTMLElement): void {
const len = nodeList.length

for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1)
break
}
}
delete el[ctx]
},
}
223 changes: 223 additions & 0 deletions packages/utils/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import isServer from './isServer'

const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
const MOZ_HACK_REGEXP = /^moz([A-Z])/

/* istanbul ignore next */
const trim = function(s: string) {
return (s || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
}
/* istanbul ignore next */
const camelCase = function(name: string) {
return name
.replace(SPECIAL_CHARS_REGEXP, function(_, __, letter, offset) {
return offset ? letter.toUpperCase() : letter
})
.replace(MOZ_HACK_REGEXP, 'Moz$1')
}

/* istanbul ignore next */
export const on = (function() {
// Since Vue3 does not support < IE11, we don't need to support it as well.
if (!isServer) {
return function(
element: HTMLElement | Document,
event: string,
handler: EventListenerOrEventListenerObject,
) {
if (element && event && handler) {
element.addEventListener(event, handler, false)
}
}
}
})()

/* istanbul ignore next */
export const off = (function() {
if (!isServer) {
return function(
element: HTMLElement,
event: string,
handler: EventListenerOrEventListenerObject,
) {
if (element && event) {
element.removeEventListener(event, handler, false)
}
}
}
})()

/* istanbul ignore next */
export const once = function(
el: HTMLElement,
event: string,
fn: EventListener,
) {
var listener = function() {
if (fn) {
fn.apply(this, arguments)
}
off(el, event, listener)
}
on(el, event, listener)
}

/* istanbul ignore next */
export function hasClass(el: HTMLElement, cls: string) {
if (!el || !cls) return false
if (cls.indexOf(' ') !== -1)
throw new Error('className should not contain space.')
if (el.classList) {
return el.classList.contains(cls)
} else {
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1
}
}

/* istanbul ignore next */
export function addClass(el: HTMLElement, cls: string) {
if (!el) return
let curClass = el.className
const classes = (cls || '').split(' ')

for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) continue

if (el.classList) {
el.classList.add(clsName)
} else if (!hasClass(el, clsName)) {
curClass += ' ' + clsName
}
}
if (!el.classList) {
el.className = curClass
}
}

/* istanbul ignore next */
export function removeClass(el: HTMLElement, cls: string) {
if (!el || !cls) return
const classes = cls.split(' ')
let curClass = ' ' + el.className + ' '

for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) continue

if (el.classList) {
el.classList.remove(clsName)
} else if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ')
}
}
if (!el.classList) {
el.className = trim(curClass)
}
}

/* istanbul ignore next */
// Here I want to use the type CSSStyleDeclaration, but the definition for CSSStyleDeclaration
// has { [index: number]: string } in its type annotation, which does not satisfiy the method
// camelCase(s: string)
// Same as the return type
export const getStyle = function(
element: HTMLElement,
styleName: string,
): string {
if (isServer) return
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'cssFloat'
}
try {
const computed = document.defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
}

/* istanbul ignore next */
export function setStyle(
element: HTMLElement,
styleName: CSSStyleDeclaration | string,
value: string,
) {
if (!element || !styleName) return

if (typeof styleName === 'object') {
for (const prop in styleName) {
if (styleName.hasOwnProperty(prop)) {
setStyle(element, prop, styleName[prop])
}
}
} else {
styleName = camelCase(styleName)

element.style[styleName] = value
}
}

export const isScroll = (el: HTMLElement, isVertical?: Nullable<boolean>) => {
if (isServer) return

const determinedDirection = isVertical !== null || isVertical !== undefined
const overflow = determinedDirection
? isVertical
? getStyle(el, 'overflow-y')
: getStyle(el, 'overflow-x')
: getStyle(el, 'overflow')

return overflow.match(/(scroll|auto)/)
}

export const getScrollContainer = (
el: HTMLElement,
isVertical?: Nullable<boolean>,
) => {
if (isServer) return
el.classList
let parent: HTMLElement = el
while (parent) {
if ([window, document, document.documentElement].includes(parent)) {
return window
}
if (isScroll(parent, isVertical)) {
return parent
}
parent = parent.parentNode as HTMLElement
}

return parent
}

export const isInContainer = (el: HTMLElement, container: HTMLElement) => {
if (isServer || !el || !container) return false

const elRect = el.getBoundingClientRect()
let containerRect: Partial<DOMRect>

if (
[window, document, document.documentElement, null, undefined].includes(
container,
)
) {
containerRect = {
top: 0,
right: window.innerWidth,
bottom: window.innerHeight,
left: 0,
}
} else {
containerRect = container.getBoundingClientRect()
}

return (
elRect.top < containerRect.bottom &&
elRect.bottom > containerRect.top &&
elRect.right > containerRect.left &&
elRect.left < containerRect.right
)
}
7 changes: 7 additions & 0 deletions packages/utils/isDef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function isDef(val) {
return val !== undefined && val !== null
}
export function isKorean(text: string) {
const reg = /([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi
return reg.test(text)
}
1 change: 1 addition & 0 deletions packages/utils/isServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default typeof window === undefined
7 changes: 7 additions & 0 deletions packages/utils/merge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function(target: Object, ...args: Array<Object>) {
target = { ...target }
for (let i = 0; i < args.length; i++) {
Object.assign(target, args[i])
}
return target
}
Loading

0 comments on commit 5b98819

Please sign in to comment.