32 changed files with 2144 additions and 1 deletions
Unified View
Diff Options
-
383components/animation-group/index.js
-
3components/animation-group/index.json
-
5components/animation-group/index.wxml
-
206components/animation-group/index.wxss
-
62components/backdrop/index.js
-
6components/backdrop/index.json
-
1components/backdrop/index.wxml
-
15components/backdrop/index.wxss
-
24components/helpers/arrayTreeFilter.js
-
77components/helpers/baseComponent.js
-
29components/helpers/checkIPhoneX.js
-
39components/helpers/classNames.js
-
27components/helpers/colors.js
-
27components/helpers/compareVersion.js
-
48components/helpers/computedBehavior.js
-
75components/helpers/createFieldsStore.js
-
56components/helpers/debounce.js
-
53components/helpers/eventsMixin.js
-
97components/helpers/funcBehavior.js
-
50components/helpers/gestures.js
-
19components/helpers/isEmpty.js
-
17components/helpers/mergeOptionsToData.js
-
67components/helpers/relationsBehavior.js
-
46components/helpers/safeAreaBehavior.js
-
57components/helpers/safeSetDataBehavior.js
-
65components/helpers/shallowEqual.js
-
138components/helpers/styleToCssString.js
-
234components/popover/index.js
-
7components/popover/index.json
-
17components/popover/index.wxml
-
193components/popover/index.wxss
-
2pages/storage/order-info/index.js
@ -0,0 +1,383 @@ |
|||||
|
import baseComponent from '../helpers/baseComponent' |
||||
|
import styleToCssString from '../helpers/styleToCssString' |
||||
|
|
||||
|
const ENTER = 'enter' |
||||
|
const ENTERING = 'entering' |
||||
|
const ENTERED = 'entered' |
||||
|
const EXIT = 'exit' |
||||
|
const EXITING = 'exiting' |
||||
|
const EXITED = 'exited' |
||||
|
const UNMOUNTED = 'unmounted' |
||||
|
|
||||
|
const TRANSITION = 'transition' |
||||
|
const ANIMATION = 'animation' |
||||
|
|
||||
|
const TIMEOUT = 1000 / 60 |
||||
|
|
||||
|
const defaultClassNames = { |
||||
|
enter: '', // 进入过渡的开始状态,在过渡过程完成之后移除
|
||||
|
enterActive: '', // 进入过渡的结束状态,在过渡过程完成之后移除
|
||||
|
enterDone: '', // 进入过渡的完成状态
|
||||
|
exit: '', // 离开过渡的开始状态,在过渡过程完成之后移除
|
||||
|
exitActive: '', // 离开过渡的结束状态,在过渡过程完成之后移除
|
||||
|
exitDone: '', // 离开过渡的完成状态
|
||||
|
} |
||||
|
|
||||
|
baseComponent({ |
||||
|
properties: { |
||||
|
// 触发组件进入或离开过渡的状态
|
||||
|
in: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
observer(newVal) { |
||||
|
if (this.data.isMounting) { |
||||
|
this.updated(newVal) |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
// 过渡的类名
|
||||
|
classNames: { |
||||
|
type: null, |
||||
|
value: defaultClassNames, |
||||
|
}, |
||||
|
// 过渡持续时间
|
||||
|
duration: { |
||||
|
type: null, |
||||
|
value: null, |
||||
|
}, |
||||
|
// 过渡动效的类型
|
||||
|
type: { |
||||
|
type: String, |
||||
|
value: TRANSITION, |
||||
|
}, |
||||
|
// 首次挂载时是否触发进入过渡
|
||||
|
appear: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
// 是否启用进入过渡
|
||||
|
enter: { |
||||
|
type: Boolean, |
||||
|
value: true, |
||||
|
}, |
||||
|
// 是否启用离开过渡
|
||||
|
exit: { |
||||
|
type: Boolean, |
||||
|
value: true, |
||||
|
}, |
||||
|
// 首次进入过渡时是否懒挂载组件
|
||||
|
mountOnEnter: { |
||||
|
type: Boolean, |
||||
|
value: true, |
||||
|
}, |
||||
|
// 离开过渡完成时是否卸载组件
|
||||
|
unmountOnExit: { |
||||
|
type: Boolean, |
||||
|
value: true, |
||||
|
}, |
||||
|
// 自定义类名
|
||||
|
wrapCls: { |
||||
|
type: String, |
||||
|
value: '', |
||||
|
}, |
||||
|
// 自定义样式
|
||||
|
wrapStyle: { |
||||
|
type: [String, Object], |
||||
|
value: '', |
||||
|
observer(newVal) { |
||||
|
this.setData({ |
||||
|
extStyle: styleToCssString(newVal), |
||||
|
}) |
||||
|
}, |
||||
|
}, |
||||
|
disableScroll: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
}, |
||||
|
data: { |
||||
|
animateCss: '', // 动画样式
|
||||
|
animateStatus: EXITED, // 动画状态,可选值 entering、entered、exiting、exited
|
||||
|
isMounting: false, // 是否首次挂载
|
||||
|
extStyle: '', // 组件样式
|
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* 监听过渡或动画的回调函数 |
||||
|
*/ |
||||
|
addEventListener() { |
||||
|
const { animateStatus } = this.data |
||||
|
const { enter, exit } = this.getTimeouts() |
||||
|
|
||||
|
if (animateStatus === ENTERING && !enter && this.data.enter) { |
||||
|
this.performEntered() |
||||
|
} |
||||
|
|
||||
|
if (animateStatus === EXITING && !exit && this.data.exit) { |
||||
|
this.performExited() |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 |
||||
|
*/ |
||||
|
onTransitionEnd() { |
||||
|
if (this.data.type === TRANSITION) { |
||||
|
this.addEventListener() |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 会在一个 WXSS animation 动画完成时触发 |
||||
|
*/ |
||||
|
onAnimationEnd() { |
||||
|
if (this.data.type === ANIMATION) { |
||||
|
this.addEventListener() |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 更新组件状态 |
||||
|
* @param {String} nextStatus 下一状态,ENTERING 或 EXITING |
||||
|
* @param {Boolean} mounting 是否首次挂载 |
||||
|
*/ |
||||
|
updateStatus(nextStatus, mounting = false) { |
||||
|
if (nextStatus !== null) { |
||||
|
this.cancelNextCallback() |
||||
|
this.isAppearing = mounting |
||||
|
|
||||
|
if (nextStatus === ENTERING) { |
||||
|
this.performEnter() |
||||
|
} else { |
||||
|
this.performExit() |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 进入过渡 |
||||
|
*/ |
||||
|
performEnter() { |
||||
|
const { className, activeClassName } = this.getClassNames(ENTER) |
||||
|
const { enter } = this.getTimeouts() |
||||
|
const enterParams = { |
||||
|
animateStatus: ENTER, |
||||
|
animateCss: className, |
||||
|
} |
||||
|
const enteringParams = { |
||||
|
animateStatus: ENTERING, |
||||
|
animateCss: `${className} ${activeClassName}`, |
||||
|
} |
||||
|
|
||||
|
// 若已禁用进入过渡,则更新状态至 ENTERED
|
||||
|
if (!this.isAppearing && !this.data.enter) { |
||||
|
return this.performEntered() |
||||
|
} |
||||
|
|
||||
|
// 第一阶段:设置进入过渡的开始状态,并触发 ENTER 事件
|
||||
|
// 第二阶段:延迟一帧后,设置进入过渡的结束状态,并触发 ENTERING 事件
|
||||
|
// 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发进入过渡完成 performEntered,否则等待触发 onTransitionEnd 或 onAnimationEnd
|
||||
|
this.safeSetData(enterParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: ENTER }) |
||||
|
this.triggerEvent(ENTER, { isAppearing: this.isAppearing }) |
||||
|
|
||||
|
// 由于有些时候不能正确的触发动画完成的回调,具体原因未知
|
||||
|
// 所以采用延迟一帧的方式来确保可以触发回调
|
||||
|
this.delayHandler(TIMEOUT, () => { |
||||
|
this.safeSetData(enteringParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: ENTERING }) |
||||
|
this.triggerEvent(ENTERING, { isAppearing: this.isAppearing }) |
||||
|
|
||||
|
if (enter) { |
||||
|
this.delayHandler(enter, this.performEntered) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 进入过渡完成 |
||||
|
*/ |
||||
|
performEntered() { |
||||
|
const { doneClassName } = this.getClassNames(ENTER) |
||||
|
const enteredParams = { |
||||
|
animateStatus: ENTERED, |
||||
|
animateCss: doneClassName, |
||||
|
} |
||||
|
|
||||
|
// 第三阶段:设置进入过渡的完成状态,并触发 ENTERED 事件
|
||||
|
this.safeSetData(enteredParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: ENTERED }) |
||||
|
this.triggerEvent(ENTERED, { isAppearing: this.isAppearing }) |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 离开过渡 |
||||
|
*/ |
||||
|
performExit() { |
||||
|
const { className, activeClassName } = this.getClassNames(EXIT) |
||||
|
const { exit } = this.getTimeouts() |
||||
|
const exitParams = { |
||||
|
animateStatus: EXIT, |
||||
|
animateCss: className, |
||||
|
} |
||||
|
const exitingParams = { |
||||
|
animateStatus: EXITING, |
||||
|
animateCss: `${className} ${activeClassName}`, |
||||
|
} |
||||
|
|
||||
|
// 若已禁用离开过渡,则更新状态至 EXITED
|
||||
|
if (!this.data.exit) { |
||||
|
return this.performExited() |
||||
|
} |
||||
|
|
||||
|
// 第一阶段:设置离开过渡的开始状态,并触发 EXIT 事件
|
||||
|
// 第二阶段:延迟一帧后,设置离开过渡的结束状态,并触发 EXITING 事件
|
||||
|
// 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发离开过渡完成 performExited,否则等待触发 onTransitionEnd 或 onAnimationEnd
|
||||
|
this.safeSetData(exitParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: EXIT }) |
||||
|
this.triggerEvent(EXIT) |
||||
|
|
||||
|
this.delayHandler(TIMEOUT, () => { |
||||
|
this.safeSetData(exitingParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: EXITING }) |
||||
|
this.triggerEvent(EXITING) |
||||
|
|
||||
|
if (exit) { |
||||
|
this.delayHandler(exit, this.performExited) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 离开过渡完成 |
||||
|
*/ |
||||
|
performExited() { |
||||
|
const { doneClassName } = this.getClassNames(EXIT) |
||||
|
const exitedParams = { |
||||
|
animateStatus: EXITED, |
||||
|
animateCss: doneClassName, |
||||
|
} |
||||
|
|
||||
|
// 第三阶段:设置离开过渡的完成状态,并触发 EXITED 事件
|
||||
|
this.safeSetData(exitedParams, () => { |
||||
|
this.triggerEvent('change', { animateStatus: EXITED }) |
||||
|
this.triggerEvent(EXITED) |
||||
|
|
||||
|
// 判断离开过渡完成时是否卸载组件
|
||||
|
if (this.data.unmountOnExit) { |
||||
|
this.setData({ animateStatus: UNMOUNTED }, () => { |
||||
|
this.triggerEvent('change', { animateStatus: UNMOUNTED }) |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 获取指定状态下的类名 |
||||
|
* @param {String} type 过渡类型,enter 或 exit |
||||
|
*/ |
||||
|
getClassNames(type) { |
||||
|
const { classNames } = this.data |
||||
|
const className = typeof classNames !== 'string' ? classNames[type] : `${classNames}-${type}` |
||||
|
const activeClassName = typeof classNames !== 'string' ? classNames[`${type}Active`] : `${classNames}-${type}-active` |
||||
|
const doneClassName = typeof classNames !== 'string' ? classNames[`${type}Done`] : `${classNames}-${type}-done` |
||||
|
|
||||
|
return { |
||||
|
className, |
||||
|
activeClassName, |
||||
|
doneClassName, |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 获取过渡持续时间 |
||||
|
*/ |
||||
|
getTimeouts() { |
||||
|
const { duration } = this.data |
||||
|
|
||||
|
if (duration !== null && typeof duration === 'object') { |
||||
|
return { |
||||
|
enter: duration.enter, |
||||
|
exit: duration.exit, |
||||
|
} |
||||
|
} else if (typeof duration === 'number') { |
||||
|
return { |
||||
|
enter: duration, |
||||
|
exit: duration, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return {} |
||||
|
}, |
||||
|
/** |
||||
|
* 属性值 in 被更改时的响应函数 |
||||
|
* @param {Boolean} newVal 触发组件进入或离开过渡的状态 |
||||
|
*/ |
||||
|
updated(newVal) { |
||||
|
let { animateStatus } = this.pendingData || this.data |
||||
|
let nextStatus = null |
||||
|
|
||||
|
if (newVal) { |
||||
|
if (animateStatus === UNMOUNTED) { |
||||
|
animateStatus = EXITED |
||||
|
this.setData({ animateStatus: EXITED }, () => { |
||||
|
this.triggerEvent('change', { animateStatus: EXITED }) |
||||
|
}) |
||||
|
} |
||||
|
if (animateStatus !== ENTER && animateStatus !== ENTERING && animateStatus !== ENTERED) { |
||||
|
nextStatus = ENTERING |
||||
|
} |
||||
|
} else { |
||||
|
if (animateStatus === ENTER || animateStatus === ENTERING || animateStatus === ENTERED) { |
||||
|
nextStatus = EXITING |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.updateStatus(nextStatus) |
||||
|
}, |
||||
|
/** |
||||
|
* 延迟一段时间触发回调 |
||||
|
* @param {Number} timeout 延迟时间 |
||||
|
* @param {Function} handler 回调函数 |
||||
|
*/ |
||||
|
delayHandler(timeout, handler) { |
||||
|
if (timeout) { |
||||
|
this.setNextCallback(handler) |
||||
|
setTimeout(this.nextCallback, timeout) |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 点击事件 |
||||
|
*/ |
||||
|
onTap() { |
||||
|
this.triggerEvent('click') |
||||
|
}, |
||||
|
/** |
||||
|
* 阻止移动触摸 |
||||
|
*/ |
||||
|
noop() { |
||||
|
}, |
||||
|
}, |
||||
|
attached() { |
||||
|
let animateStatus = null |
||||
|
let appearStatus = null |
||||
|
|
||||
|
if (this.data.in) { |
||||
|
if (this.data.appear) { |
||||
|
animateStatus = EXITED |
||||
|
appearStatus = ENTERING |
||||
|
} else { |
||||
|
animateStatus = ENTERED |
||||
|
} |
||||
|
} else { |
||||
|
if (this.data.unmountOnExit || this.data.mountOnEnter) { |
||||
|
animateStatus = UNMOUNTED |
||||
|
} else { |
||||
|
animateStatus = EXITED |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 由于小程序组件首次挂载时 observer 事件总是优先于 attached 事件
|
||||
|
// 所以使用 isMounting 来强制优先触发 attached 事件
|
||||
|
this.safeSetData({ animateStatus, isMounting: true }, () => { |
||||
|
this.triggerEvent('change', { animateStatus }) |
||||
|
this.updateStatus(appearStatus, true) |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"component": true |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
<view class="wux-class {{ wrapCls }} {{ animateCss }}" catchtap="onTap" |
||||
|
catchtouchmove="{{ disableScroll ? 'onTap' : '' }}" bindtransitionend="onTransitionEnd" |
||||
|
bindanimationend="onAnimationEnd" wx:if="{{ animateStatus !== 'unmounted' }}" style="{{ extStyle }}"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
@ -0,0 +1,206 @@ |
|||||
|
.wux-animate--fadeIn-enter { |
||||
|
transition: opacity .3s; |
||||
|
opacity: 0 |
||||
|
} |
||||
|
.wux-animate--fadeIn-enter-active, |
||||
|
.wux-animate--fadeIn-enter-done { |
||||
|
opacity: 1 |
||||
|
} |
||||
|
.wux-animate--fadeIn-exit { |
||||
|
transition: opacity .3s; |
||||
|
opacity: 1 |
||||
|
} |
||||
|
.wux-animate--fadeIn-exit-active, |
||||
|
.wux-animate--fadeIn-exit-done { |
||||
|
opacity: 0 |
||||
|
} |
||||
|
.wux-animate--fadeInDown-enter { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 0; |
||||
|
transform: translate3d(0,-100%,0) |
||||
|
} |
||||
|
.wux-animate--fadeInDown-enter-active, |
||||
|
.wux-animate--fadeInDown-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInDown-exit { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInDown-exit-active, |
||||
|
.wux-animate--fadeInDown-exit-done { |
||||
|
opacity: 0; |
||||
|
transform: translate3d(0,-100%,0) |
||||
|
} |
||||
|
.wux-animate--fadeInLeft-enter { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 0; |
||||
|
transform: translate3d(-100%,0,0) |
||||
|
} |
||||
|
.wux-animate--fadeInLeft-enter-active, |
||||
|
.wux-animate--fadeInLeft-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInLeft-exit { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInLeft-exit-active, |
||||
|
.wux-animate--fadeInLeft-exit-done { |
||||
|
opacity: 0; |
||||
|
transform: translate3d(-100%,0,0) |
||||
|
} |
||||
|
.wux-animate--fadeInRight-enter { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 0; |
||||
|
transform: translate3d(100%,0,0) |
||||
|
} |
||||
|
.wux-animate--fadeInRight-enter-active, |
||||
|
.wux-animate--fadeInRight-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInRight-exit { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInRight-exit-active, |
||||
|
.wux-animate--fadeInRight-exit-done { |
||||
|
opacity: 0; |
||||
|
transform: translate3d(100%,0,0) |
||||
|
} |
||||
|
.wux-animate--fadeInUp-enter { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 0; |
||||
|
transform: translate3d(0,100%,0) |
||||
|
} |
||||
|
.wux-animate--fadeInUp-enter-active, |
||||
|
.wux-animate--fadeInUp-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInUp-exit { |
||||
|
transition: opacity .3s,transform .3s; |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--fadeInUp-exit-active, |
||||
|
.wux-animate--fadeInUp-exit-done { |
||||
|
opacity: 0; |
||||
|
transform: translate3d(0,100%,0) |
||||
|
} |
||||
|
.wux-animate--slideInUp-enter { |
||||
|
transition: transform .3s; |
||||
|
transform: translate3d(0,100%,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInUp-enter-active, |
||||
|
.wux-animate--slideInUp-enter-done { |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInUp-exit { |
||||
|
transition: transform .3s; |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInUp-exit-active, |
||||
|
.wux-animate--slideInUp-exit-done { |
||||
|
transform: translate3d(0,100%,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInDown-enter { |
||||
|
transition: transform .3s; |
||||
|
transform: translate3d(0,-100%,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInDown-enter-active, |
||||
|
.wux-animate--slideInDown-enter-done { |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInDown-exit { |
||||
|
transition: transform .3s; |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInDown-exit-active, |
||||
|
.wux-animate--slideInDown-exit-done { |
||||
|
transform: translate3d(0,-100%,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInLeft-enter { |
||||
|
transition: transform .3s; |
||||
|
transform: translate3d(-100%,0,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInLeft-enter-active, |
||||
|
.wux-animate--slideInLeft-enter-done { |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInLeft-exit { |
||||
|
transition: transform .3s; |
||||
|
transform: translateZ(0) |
||||
|
} |
||||
|
.wux-animate--slideInLeft-exit-active, |
||||
|
.wux-animate--slideInLeft-exit-done { |
||||
|
transform: translate3d(-100%,0,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInRight-enter { |
||||
|
transition: transform .3s; |
||||
|
transform: translate3d(100%,0,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--slideInRight-enter-active, |
||||
|
.wux-animate--slideInRight-enter-done { |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--slideInRight-exit { |
||||
|
transition: transform .3s; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--slideInRight-exit-active, |
||||
|
.wux-animate--slideInRight-exit-done { |
||||
|
transform: translate3d(100%,0,0); |
||||
|
visibility: visible |
||||
|
} |
||||
|
.wux-animate--zoom-enter { |
||||
|
transition: all .3s cubic-bezier(.215,.61,.355,1); |
||||
|
opacity: .01; |
||||
|
transform: scale(.75) |
||||
|
} |
||||
|
.wux-animate--zoom-enter-active, |
||||
|
.wux-animate--zoom-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--zoom-exit { |
||||
|
transition: all .25s linear; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--zoom-exit-active, |
||||
|
.wux-animate--zoom-exit-done { |
||||
|
opacity: .01; |
||||
|
transform: scale(.75) |
||||
|
} |
||||
|
.wux-animate--punch-enter { |
||||
|
transition: all .3s cubic-bezier(.215,.61,.355,1); |
||||
|
opacity: .01; |
||||
|
transform: scale(1.35) |
||||
|
} |
||||
|
.wux-animate--punch-enter-active, |
||||
|
.wux-animate--punch-enter-done { |
||||
|
opacity: 1; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--punch-exit { |
||||
|
transition: all .25s linear; |
||||
|
transform: none |
||||
|
} |
||||
|
.wux-animate--punch-exit-active, |
||||
|
.wux-animate--punch-exit-done { |
||||
|
opacity: .01; |
||||
|
transform: scale(1.35) |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
import baseComponent from '../helpers/baseComponent' |
||||
|
|
||||
|
baseComponent({ |
||||
|
properties: { |
||||
|
prefixCls: { |
||||
|
type: String, |
||||
|
value: 'wux-backdrop', |
||||
|
}, |
||||
|
transparent: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
zIndex: { |
||||
|
type: Number, |
||||
|
value: 1000, |
||||
|
}, |
||||
|
classNames: { |
||||
|
type: null, |
||||
|
value: 'wux-animate--fadeIn', |
||||
|
}, |
||||
|
}, |
||||
|
computed: { |
||||
|
classes: ['prefixCls, transparent', function(prefixCls, transparent) { |
||||
|
const wrap = transparent ? `${prefixCls}--transparent` : prefixCls |
||||
|
|
||||
|
return { |
||||
|
wrap, |
||||
|
} |
||||
|
}], |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* 保持锁定 |
||||
|
*/ |
||||
|
retain() { |
||||
|
if (typeof this.backdropHolds !== 'number' || !this.backdropHolds) { |
||||
|
this.backdropHolds = 0 |
||||
|
} |
||||
|
|
||||
|
this.backdropHolds = this.backdropHolds + 1 |
||||
|
|
||||
|
if (this.backdropHolds === 1) { |
||||
|
this.setData({ in: true }) |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 释放锁定 |
||||
|
*/ |
||||
|
release() { |
||||
|
if (this.backdropHolds === 1) { |
||||
|
this.setData({ in: false }) |
||||
|
} |
||||
|
this.backdropHolds = Math.max(0, this.backdropHolds - 1) |
||||
|
}, |
||||
|
/** |
||||
|
* 点击事件 |
||||
|
*/ |
||||
|
onClick() { |
||||
|
this.triggerEvent('click') |
||||
|
}, |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"usingComponents": { |
||||
|
"wux-animation-group": "../animation-group/index" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
<wux-animation-group wux-class="{{ classes.wrap }}" in="{{ in }}" classNames="{{ classNames }}" bind:click="onClick" wrapStyle="{{ { zIndex } }}" disableScroll /> |
||||
@ -0,0 +1,15 @@ |
|||||
|
.wux-backdrop { |
||||
|
background: rgba(0,0,0,.4) |
||||
|
} |
||||
|
.wux-backdrop, |
||||
|
.wux-backdrop--transparent { |
||||
|
position: fixed; |
||||
|
z-index: 1000; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
bottom: 0 |
||||
|
} |
||||
|
.wux-backdrop--transparent { |
||||
|
background: 0 0 |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
/** |
||||
|
* https://github.com/afc163/array-tree-filter
|
||||
|
*/ |
||||
|
function arrayTreeFilter(data, filterFn, options) { |
||||
|
options = options || {} |
||||
|
options.childrenKeyName = options.childrenKeyName || 'children' |
||||
|
let children = data || [] |
||||
|
const result = [] |
||||
|
let level = 0 |
||||
|
do { |
||||
|
const foundItem = children.filter(function(item) { |
||||
|
return filterFn(item, level) |
||||
|
})[0] |
||||
|
if (!foundItem) { |
||||
|
break |
||||
|
} |
||||
|
result.push(foundItem) |
||||
|
children = foundItem[options.childrenKeyName] || [] |
||||
|
level += 1 |
||||
|
} while (children.length > 0) |
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
export default arrayTreeFilter |
||||
@ -0,0 +1,77 @@ |
|||||
|
import computedBehavior from './computedBehavior' |
||||
|
import relationsBehavior from './relationsBehavior' |
||||
|
import safeAreaBehavior from './safeAreaBehavior' |
||||
|
import safeSetDataBehavior from './safeSetDataBehavior' |
||||
|
import funcBehavior from './funcBehavior' |
||||
|
import compareVersion from './compareVersion' |
||||
|
|
||||
|
const { platform, SDKVersion } = wx.getSystemInfoSync() |
||||
|
const libVersion = '2.6.6' |
||||
|
|
||||
|
// check SDKVersion
|
||||
|
if (platform === 'devtools' && compareVersion(SDKVersion, libVersion) < 0) { |
||||
|
if (wx && wx.showModal) { |
||||
|
wx.showModal({ |
||||
|
title: '提示', |
||||
|
content: `当前基础库版本(${SDKVersion})过低,无法使用 Wux Weapp 组件库,请更新基础库版本 >=${libVersion} 后重试。`, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const baseComponent = (options = {}) => { |
||||
|
// add default externalClasses
|
||||
|
options.externalClasses = [ |
||||
|
'wux-class', |
||||
|
'wux-hover-class', |
||||
|
...(options.externalClasses = options.externalClasses || []), |
||||
|
] |
||||
|
|
||||
|
// add default behaviors
|
||||
|
options.behaviors = [ |
||||
|
relationsBehavior, |
||||
|
safeSetDataBehavior, |
||||
|
...(options.behaviors = options.behaviors || []), |
||||
|
computedBehavior, // make sure it's triggered
|
||||
|
] |
||||
|
|
||||
|
// use safeArea
|
||||
|
if (options.useSafeArea) { |
||||
|
options.behaviors = [...options.behaviors, safeAreaBehavior] |
||||
|
delete options.useSafeArea |
||||
|
} |
||||
|
|
||||
|
// use func
|
||||
|
if (options.useFunc) { |
||||
|
options.behaviors = [...options.behaviors, funcBehavior] |
||||
|
delete options.useFunc |
||||
|
} |
||||
|
|
||||
|
// use field
|
||||
|
if (options.useField) { |
||||
|
options.behaviors = [...options.behaviors, 'wx://form-field'] |
||||
|
delete options.useField |
||||
|
} |
||||
|
|
||||
|
// use export
|
||||
|
if (options.useExport) { |
||||
|
options.behaviors = [...options.behaviors, 'wx://component-export'] |
||||
|
options.methods = { |
||||
|
export () { |
||||
|
return this |
||||
|
}, |
||||
|
...options.methods, |
||||
|
} |
||||
|
delete options.useExport |
||||
|
} |
||||
|
|
||||
|
// add default options
|
||||
|
options.options = { |
||||
|
multipleSlots: true, |
||||
|
addGlobalClass: true, |
||||
|
...options.options, |
||||
|
} |
||||
|
|
||||
|
return Component(options) |
||||
|
} |
||||
|
|
||||
|
export default baseComponent |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* 获取系统信息 |
||||
|
*/ |
||||
|
|
||||
|
let systemInfo = null |
||||
|
|
||||
|
export const getSystemInfo = (isForce) => { |
||||
|
if (!systemInfo || isForce) { |
||||
|
try { |
||||
|
systemInfo = wx.getSystemInfoSync() |
||||
|
} catch(e) { /* Ignore */ } |
||||
|
} |
||||
|
|
||||
|
return systemInfo |
||||
|
} |
||||
|
|
||||
|
// iPhoneX 竖屏安全区域
|
||||
|
export const safeAreaInset = { |
||||
|
top: 88, // StatusBar & NavBar
|
||||
|
left: 0, |
||||
|
right: 0, |
||||
|
bottom: 34, // Home Indicator
|
||||
|
} |
||||
|
|
||||
|
const isIPhoneX = ({ model, platform }) => { |
||||
|
return /iPhone X/.test(model) && platform === 'ios' |
||||
|
} |
||||
|
|
||||
|
export const checkIPhoneX = (isForce) => isIPhoneX(getSystemInfo(isForce)) |
||||
@ -0,0 +1,39 @@ |
|||||
|
/*! |
||||
|
Copyright (c) 2018 Jed Watson. |
||||
|
Licensed under the MIT License (MIT), see |
||||
|
http://jedwatson.github.io/classnames
|
||||
|
*/ |
||||
|
/* global define */ |
||||
|
'use strict'; |
||||
|
|
||||
|
var hasOwn = {}.hasOwnProperty; |
||||
|
|
||||
|
function classNames() { |
||||
|
var classes = []; |
||||
|
|
||||
|
for (var i = 0; i < arguments.length; i++) { |
||||
|
var arg = arguments[i]; |
||||
|
if (!arg) continue; |
||||
|
|
||||
|
var argType = typeof arg; |
||||
|
|
||||
|
if (argType === 'string' || argType === 'number') { |
||||
|
classes.push(arg); |
||||
|
} else if (Array.isArray(arg) && arg.length) { |
||||
|
var inner = classNames.apply(null, arg); |
||||
|
if (inner) { |
||||
|
classes.push(inner); |
||||
|
} |
||||
|
} else if (argType === 'object') { |
||||
|
for (var key in arg) { |
||||
|
if (hasOwn.call(arg, key) && arg[key]) { |
||||
|
classes.push(key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return classes.join(' '); |
||||
|
} |
||||
|
|
||||
|
export default classNames |
||||
@ -0,0 +1,27 @@ |
|||||
|
/** |
||||
|
* Don't modify this file! |
||||
|
* Colors generated by themes! |
||||
|
*/ |
||||
|
|
||||
|
/* eslint-disable */ |
||||
|
|
||||
|
export const colors = { |
||||
|
'light': '#ddd', |
||||
|
'stable': '#b2b2b2', |
||||
|
'positive': '#387ef5', |
||||
|
'calm': '#11c1f3', |
||||
|
'balanced': '#33cd5f', |
||||
|
'energized': '#ffc900', |
||||
|
'assertive': '#ef473a', |
||||
|
'royal': '#886aea', |
||||
|
'dark': '#444', |
||||
|
} |
||||
|
|
||||
|
export const isPresetColor = (color) => { |
||||
|
if (!color) { |
||||
|
return false |
||||
|
} |
||||
|
return colors[color] ? colors[color] : color |
||||
|
} |
||||
|
|
||||
|
/* eslint-enable */ |
||||
@ -0,0 +1,27 @@ |
|||||
|
function compareVersion(v1, v2) { |
||||
|
const $v1 = v1.split('.') |
||||
|
const $v2 = v2.split('.') |
||||
|
const len = Math.max($v1.length, $v2.length) |
||||
|
|
||||
|
while ($v1.length < len) { |
||||
|
$v1.push('0') |
||||
|
} |
||||
|
while ($v2.length < len) { |
||||
|
$v2.push('0') |
||||
|
} |
||||
|
|
||||
|
for (let i = 0; i < len; i++) { |
||||
|
const num1 = parseInt($v1[i]) |
||||
|
const num2 = parseInt($v2[i]) |
||||
|
|
||||
|
if (num1 > num2) { |
||||
|
return 1 |
||||
|
} else if (num1 < num2) { |
||||
|
return -1 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
export default compareVersion |
||||
@ -0,0 +1,48 @@ |
|||||
|
import isEmpty from './isEmpty' |
||||
|
import shallowEqual from './shallowEqual' |
||||
|
|
||||
|
const ALL_DATA_KEY = '**' |
||||
|
|
||||
|
const trim = (str = '') => str.replace(/\s/g, '') |
||||
|
|
||||
|
export default Behavior({ |
||||
|
lifetimes: { |
||||
|
attached() { |
||||
|
this.initComputed() |
||||
|
}, |
||||
|
}, |
||||
|
definitionFilter(defFields) { |
||||
|
const { computed = {} } = defFields |
||||
|
const observers = Object.keys(computed).reduce((acc, name) => { |
||||
|
const [field, getter] = Array.isArray(computed[name]) ? computed[name] : [ALL_DATA_KEY, computed[name]] |
||||
|
return { |
||||
|
...acc, |
||||
|
[field]: function(...args) { |
||||
|
if (typeof getter === 'function') { |
||||
|
const newValue = getter.apply(this, args) |
||||
|
const oldValue = this.data[name] |
||||
|
if (!isEmpty(newValue) && !shallowEqual(newValue, oldValue)) { |
||||
|
this.setData({ [name]: newValue }) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
}, {}) |
||||
|
|
||||
|
Object.assign(defFields.observers = (defFields.observers || {}), observers) |
||||
|
Object.assign(defFields.methods = (defFields.methods || {}), { |
||||
|
initComputed: function(data = {}, isForce = false) { |
||||
|
if (!this.runInitComputed || isForce) { |
||||
|
this.runInitComputed = false |
||||
|
const context = this |
||||
|
const result = { ...this.data, ...data } |
||||
|
Object.keys(observers).forEach((key) => { |
||||
|
const values = trim(key).split(',').reduce((acc, name) => ([...acc, result[name]]), []) |
||||
|
observers[key].apply(context, values) |
||||
|
}) |
||||
|
this.runInitComputed = true |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,75 @@ |
|||||
|
class FieldsStore { |
||||
|
constructor(fields = {}) { |
||||
|
this.fields = fields |
||||
|
} |
||||
|
|
||||
|
setFields(fields) { |
||||
|
Object.assign(this.fields, fields) |
||||
|
} |
||||
|
|
||||
|
updateFields(fields) { |
||||
|
this.fields = fields |
||||
|
} |
||||
|
|
||||
|
clearField(name) { |
||||
|
delete this.fields[name] |
||||
|
} |
||||
|
|
||||
|
getValueFromFields(name, fields) { |
||||
|
const field = fields[name] |
||||
|
if (field && 'value' in field) { |
||||
|
return field.value |
||||
|
} |
||||
|
return field.initialValue |
||||
|
} |
||||
|
|
||||
|
getAllFieldsName() { |
||||
|
const { fields } = this |
||||
|
return fields ? Object.keys(fields) : [] |
||||
|
} |
||||
|
|
||||
|
getField(name) { |
||||
|
return { |
||||
|
...this.fields[name], |
||||
|
name, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getFieldValuePropValue(fieldOption) { |
||||
|
const { name, valuePropName } = fieldOption |
||||
|
const field = this.getField(name) |
||||
|
const fieldValue = 'value' in field ? field.value : field.initialValue |
||||
|
|
||||
|
return { |
||||
|
[valuePropName]: fieldValue, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getFieldValue(name) { |
||||
|
return this.getValueFromFields(name, this.fields) |
||||
|
} |
||||
|
|
||||
|
getFieldsValue(names) { |
||||
|
const fields = names || this.getAllFieldsName() |
||||
|
return fields.reduce((acc, name) => { |
||||
|
acc[name] = this.getFieldValue(name) |
||||
|
return acc |
||||
|
}, {}) |
||||
|
} |
||||
|
|
||||
|
resetFields(ns) { |
||||
|
const { fields } = this |
||||
|
const names = ns || this.getAllFieldsName() |
||||
|
return names.reduce((acc, name) => { |
||||
|
const field = fields[name] |
||||
|
if (field) { |
||||
|
acc[name] = field.initialValue |
||||
|
} |
||||
|
return acc |
||||
|
}, {}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default function createFieldsStore(fields) { |
||||
|
return new FieldsStore(fields) |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
export default function debounce(func, wait, immediate) { |
||||
|
let timeout, |
||||
|
args, |
||||
|
context, |
||||
|
timestamp, |
||||
|
result |
||||
|
|
||||
|
function later() { |
||||
|
const last = +(new Date()) - timestamp |
||||
|
if (last < wait && last >= 0) { |
||||
|
timeout = setTimeout(later, wait - last) |
||||
|
} else { |
||||
|
timeout = undefined |
||||
|
if (!immediate) { |
||||
|
result = func.apply(context, args) |
||||
|
if (!timeout) { |
||||
|
context = undefined |
||||
|
args = undefined |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function debounced() { |
||||
|
context = this |
||||
|
args = arguments |
||||
|
timestamp = +(new Date()) |
||||
|
|
||||
|
const callNow = immediate && !timeout |
||||
|
if (!timeout) { |
||||
|
timeout = setTimeout(later, wait) |
||||
|
} |
||||
|
|
||||
|
if (callNow) { |
||||
|
result = func.apply(context, args) |
||||
|
context = undefined |
||||
|
args = undefined |
||||
|
} |
||||
|
|
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
function cancel() { |
||||
|
if (timeout !== undefined) { |
||||
|
clearTimeout(timeout) |
||||
|
timeout = undefined |
||||
|
} |
||||
|
|
||||
|
context = undefined |
||||
|
args = undefined |
||||
|
} |
||||
|
|
||||
|
debounced.cancel = cancel |
||||
|
|
||||
|
return debounced |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
const defaultEvents = { |
||||
|
onChange() {}, |
||||
|
} |
||||
|
|
||||
|
export default function eventsMixin(params = { defaultEvents }) { |
||||
|
return Behavior({ |
||||
|
lifetimes: { |
||||
|
created () { |
||||
|
this._oriTriggerEvent = this.triggerEvent |
||||
|
this.triggerEvent = this._triggerEvent |
||||
|
}, |
||||
|
}, |
||||
|
properties: { |
||||
|
events: { |
||||
|
type: Object, |
||||
|
value: defaultEvents, |
||||
|
}, |
||||
|
}, |
||||
|
data: { |
||||
|
inputEvents: defaultEvents, |
||||
|
}, |
||||
|
definitionFilter(defFields) { |
||||
|
// set default data
|
||||
|
Object.assign(defFields.data = (defFields.data || {}), { |
||||
|
inputEvents: Object.assign({}, defaultEvents, defFields.inputEvents), |
||||
|
}) |
||||
|
|
||||
|
// set default methods
|
||||
|
Object.assign(defFields.methods = (defFields.methods || {}), { |
||||
|
_triggerEvent(name, params, runCallbacks = true, option) { |
||||
|
const { inputEvents } = this.data |
||||
|
const method = `on${name[0].toUpperCase()}${name.slice(1)}` |
||||
|
const func = inputEvents[method] |
||||
|
|
||||
|
if (runCallbacks && typeof func === 'function') { |
||||
|
func.call(this, params) |
||||
|
} |
||||
|
|
||||
|
this._oriTriggerEvent(name, params, option) |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
// set default observers
|
||||
|
Object.assign(defFields.observers = (defFields.observers || {}), { |
||||
|
events(newVal) { |
||||
|
this.setData({ |
||||
|
inputEvents: Object.assign({}, defaultEvents, this.data.inputEvents, newVal), |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* 过滤对象的函数属性 |
||||
|
* @param {Object} opts |
||||
|
*/ |
||||
|
const mergeOptionsToData = (opts = {}) => { |
||||
|
const options = Object.assign({}, opts) |
||||
|
|
||||
|
for (const key in options) { |
||||
|
if (options.hasOwnProperty(key) && typeof options[key] === 'function') { |
||||
|
delete options[key] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return options |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Simple bind, faster than native |
||||
|
* |
||||
|
* @param {Function} fn |
||||
|
* @param {Object} ctx |
||||
|
* @return {Function} |
||||
|
*/ |
||||
|
const bind = (fn, ctx) => { |
||||
|
return (...args) => { |
||||
|
return args.length ? fn.apply(ctx, args) : fn.call(ctx) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Object assign |
||||
|
*/ |
||||
|
const assign = (...args) => Object.assign({}, ...args) |
||||
|
|
||||
|
export default Behavior({ |
||||
|
definitionFilter(defFields) { |
||||
|
defFields.data = mergeOptionsToData(defFields.data) |
||||
|
defFields.data.in = false |
||||
|
defFields.data.visible = false |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* 过滤对象的函数属性 |
||||
|
* @param {Object} opts |
||||
|
*/ |
||||
|
$$mergeOptionsToData: mergeOptionsToData, |
||||
|
/** |
||||
|
* 合并参数并绑定方法 |
||||
|
* |
||||
|
* @param {Object} opts 参数对象 |
||||
|
* @param {Object} fns 方法挂载的属性 |
||||
|
*/ |
||||
|
$$mergeOptionsAndBindMethods (opts = {}, fns = this.fns) { |
||||
|
const options = Object.assign({}, opts) |
||||
|
|
||||
|
for (const key in options) { |
||||
|
if (options.hasOwnProperty(key) && typeof options[key] === 'function') { |
||||
|
fns[key] = bind(options[key], this) |
||||
|
delete options[key] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return options |
||||
|
}, |
||||
|
/** |
||||
|
* Promise setData |
||||
|
* @param {Array} args 参数对象 |
||||
|
*/ |
||||
|
$$setData (...args) { |
||||
|
const params = assign({}, ...args) |
||||
|
|
||||
|
return new Promise((resolve) => { |
||||
|
this.setData(params, resolve) |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 延迟指定时间执行回调函数 |
||||
|
* @param {Function} callback 回调函数 |
||||
|
* @param {Number} timeout 延迟时间 |
||||
|
*/ |
||||
|
$$requestAnimationFrame (callback = () => {}, timeout = 1000 / 60) { |
||||
|
return new Promise((resolve) => setTimeout(resolve, timeout)).then(callback) |
||||
|
}, |
||||
|
}, |
||||
|
/** |
||||
|
* 组件生命周期函数,在组件实例进入页面节点树时执行 |
||||
|
*/ |
||||
|
created () { |
||||
|
this.fns = {} |
||||
|
}, |
||||
|
/** |
||||
|
* 组件生命周期函数,在组件实例被从页面节点树移除时执行 |
||||
|
*/ |
||||
|
detached () { |
||||
|
this.fns = {} |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,50 @@ |
|||||
|
/** |
||||
|
* 获取触摸点位置信息 |
||||
|
*/ |
||||
|
export const getTouchPoints = (nativeEvent, index = 0) => { |
||||
|
const touches = nativeEvent.touches |
||||
|
const changedTouches = nativeEvent.changedTouches |
||||
|
const hasTouches = touches && touches.length > 0 |
||||
|
const hasChangedTouches = changedTouches && changedTouches.length > 0 |
||||
|
const points = !hasTouches && hasChangedTouches ? changedTouches[index] : hasTouches ? touches[index] : nativeEvent |
||||
|
|
||||
|
return { |
||||
|
x: points.pageX, |
||||
|
y: points.pageY, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取触摸点个数 |
||||
|
*/ |
||||
|
export const getPointsNumber = (e) => e.touches && e.touches.length || e.changedTouches && e.changedTouches.length |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为同一点 |
||||
|
*/ |
||||
|
export const isEqualPoints = (p1, p2) => p1.x === p2.x && p1.y === p2.y |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为相近的两点 |
||||
|
*/ |
||||
|
export const isNearbyPoints = (p1, p2, DOUBLE_TAP_RADIUS = 25) => { |
||||
|
const xMove = Math.abs(p1.x - p2.x) |
||||
|
const yMove = Math.abs(p1.y - p2.y) |
||||
|
return xMove < DOUBLE_TAP_RADIUS & yMove < DOUBLE_TAP_RADIUS |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取两点之间的距离 |
||||
|
*/ |
||||
|
export const getPointsDistance = (p1, p2) => { |
||||
|
const xMove = Math.abs(p1.x - p2.x) |
||||
|
const yMove = Math.abs(p1.y - p2.y) |
||||
|
return Math.sqrt(xMove * xMove + yMove * yMove) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取触摸移动方向 |
||||
|
*/ |
||||
|
export const getSwipeDirection = (x1, x2, y1, y2) => { |
||||
|
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down') |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
/** |
||||
|
* Checks if a value is empty. |
||||
|
*/ |
||||
|
function isEmpty(value) { |
||||
|
if (Array.isArray(value)) { |
||||
|
return value.length === 0 |
||||
|
} else if (typeof value === 'object') { |
||||
|
if (value) { |
||||
|
for (const _ in value) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} else { |
||||
|
return !value |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default isEmpty |
||||
@ -0,0 +1,17 @@ |
|||||
|
/** |
||||
|
* 过滤对象的函数属性 |
||||
|
* @param {Object} opts |
||||
|
*/ |
||||
|
const mergeOptionsToData = (opts = {}) => { |
||||
|
const options = Object.assign({}, opts) |
||||
|
|
||||
|
for (const key in options) { |
||||
|
if (options.hasOwnProperty(key) && typeof options[key] === 'function') { |
||||
|
delete options[key] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return options |
||||
|
} |
||||
|
|
||||
|
export default mergeOptionsToData |
||||
@ -0,0 +1,67 @@ |
|||||
|
import isEmpty from './isEmpty' |
||||
|
import debounce from './debounce' |
||||
|
|
||||
|
/** |
||||
|
* bind func to obj |
||||
|
*/ |
||||
|
function bindFunc(obj, method, observer) { |
||||
|
const oldFn = obj[method] |
||||
|
obj[method] = function(target) { |
||||
|
if (observer) { |
||||
|
observer.call(this, target, { |
||||
|
[method]: true, |
||||
|
}) |
||||
|
} |
||||
|
if (oldFn) { |
||||
|
oldFn.call(this, target) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// default methods
|
||||
|
const methods = ['linked', 'linkChanged', 'unlinked'] |
||||
|
|
||||
|
// extra props
|
||||
|
const extProps = ['observer'] |
||||
|
|
||||
|
export default Behavior({ |
||||
|
lifetimes: { |
||||
|
created() { |
||||
|
this._debounce = null |
||||
|
}, |
||||
|
detached() { |
||||
|
if (this._debounce && this._debounce.cancel) { |
||||
|
this._debounce.cancel() |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
definitionFilter(defFields) { |
||||
|
const { relations } = defFields |
||||
|
|
||||
|
if (!isEmpty(relations)) { |
||||
|
for (const key in relations) { |
||||
|
const relation = relations[key] |
||||
|
|
||||
|
// bind func
|
||||
|
methods.forEach((method) => bindFunc(relation, method, relation.observer)) |
||||
|
|
||||
|
// delete extProps
|
||||
|
extProps.forEach((prop) => delete relation[prop]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Object.assign(defFields.methods = (defFields.methods || {}), { |
||||
|
getRelationsName: function(types = ['parent', 'child', 'ancestor', 'descendant']) { |
||||
|
return Object.keys(relations || {}).map((key) => { |
||||
|
if (relations[key] && types.includes(relations[key].type)) { |
||||
|
return key |
||||
|
} |
||||
|
return null |
||||
|
}).filter((v) => !!v) |
||||
|
}, |
||||
|
debounce: function(func, wait = 0, immediate = false) { |
||||
|
return (this._debounce = this._debounce || debounce(func.bind(this), wait, immediate)).call(this) |
||||
|
}, |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,46 @@ |
|||||
|
import { getSystemInfo, checkIPhoneX } from './checkIPhoneX' |
||||
|
|
||||
|
const defaultSafeArea = { |
||||
|
top: false, |
||||
|
bottom: false, |
||||
|
} |
||||
|
|
||||
|
const setSafeArea = (params) => { |
||||
|
if (typeof params === 'boolean') { |
||||
|
return Object.assign({}, defaultSafeArea, { |
||||
|
top: params, |
||||
|
bottom: params, |
||||
|
}) |
||||
|
} else if (params !== null && typeof params === 'object') { |
||||
|
return Object.assign({}, defaultSafeArea) |
||||
|
} else if (typeof params === 'string') { |
||||
|
return Object.assign({}, defaultSafeArea, { |
||||
|
[params]: true, |
||||
|
}) |
||||
|
} |
||||
|
return defaultSafeArea |
||||
|
} |
||||
|
|
||||
|
export default Behavior({ |
||||
|
properties: { |
||||
|
safeArea: { |
||||
|
type: [Boolean, String, Object], |
||||
|
value: false, |
||||
|
}, |
||||
|
}, |
||||
|
observers: { |
||||
|
safeArea(newVal) { |
||||
|
this.setData({ safeAreaConfig: setSafeArea(newVal) }) |
||||
|
}, |
||||
|
}, |
||||
|
definitionFilter(defFields) { |
||||
|
const { statusBarHeight } = getSystemInfo() || {} |
||||
|
const isIPhoneX = checkIPhoneX() |
||||
|
|
||||
|
Object.assign(defFields.data = (defFields.data || {}), { |
||||
|
safeAreaConfig: defaultSafeArea, |
||||
|
statusBarHeight, |
||||
|
isIPhoneX, |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,57 @@ |
|||||
|
export default Behavior({ |
||||
|
lifetimes: { |
||||
|
created() { |
||||
|
this.nextCallback = null |
||||
|
}, |
||||
|
detached() { |
||||
|
this.cancelNextCallback() |
||||
|
}, |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* safeSetData |
||||
|
* @param {Object} nextData 数据对象 |
||||
|
* @param {Function} callback 回调函数 |
||||
|
*/ |
||||
|
safeSetData(nextData, callback) { |
||||
|
this.pendingData = Object.assign({}, this.data, nextData) |
||||
|
callback = this.setNextCallback(callback) |
||||
|
|
||||
|
this.setData(nextData, () => { |
||||
|
this.pendingData = null |
||||
|
callback() |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 设置下一回调函数 |
||||
|
* @param {Function} callback 回调函数 |
||||
|
*/ |
||||
|
setNextCallback(callback) { |
||||
|
let active = true |
||||
|
|
||||
|
this.nextCallback = (event) => { |
||||
|
if (active) { |
||||
|
active = false |
||||
|
this.nextCallback = null |
||||
|
|
||||
|
callback.call(this, event) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.nextCallback.cancel = () => { |
||||
|
active = false |
||||
|
} |
||||
|
|
||||
|
return this.nextCallback |
||||
|
}, |
||||
|
/** |
||||
|
* 取消下一回调函数 |
||||
|
*/ |
||||
|
cancelNextCallback() { |
||||
|
if (this.nextCallback !== null) { |
||||
|
this.nextCallback.cancel() |
||||
|
this.nextCallback = null |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,65 @@ |
|||||
|
/** |
||||
|
* Copyright (c) 2013-present, Facebook, Inc. |
||||
|
* |
||||
|
* This source code is licensed under the MIT license found in the |
||||
|
* LICENSE file in the root directory of this source tree. |
||||
|
* |
||||
|
* @typechecks |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
/*eslint-disable no-self-compare */ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
var hasOwnProperty = Object.prototype.hasOwnProperty; |
||||
|
|
||||
|
/** |
||||
|
* inlined Object.is polyfill to avoid requiring consumers ship their own |
||||
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
||||
|
*/ |
||||
|
function is(x, y) { |
||||
|
// SameValue algorithm
|
||||
|
if (x === y) { |
||||
|
// Steps 1-5, 7-10
|
||||
|
// Steps 6.b-6.e: +0 != -0
|
||||
|
// Added the nonzero y check to make Flow happy, but it is redundant
|
||||
|
return x !== 0 || y !== 0 || 1 / x === 1 / y; |
||||
|
} else { |
||||
|
// Step 6.a: NaN == NaN
|
||||
|
return x !== x && y !== y; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Performs equality by iterating through keys on an object and returning false |
||||
|
* when any key has values which are not strictly equal between the arguments. |
||||
|
* Returns true when the values of all keys are strictly equal. |
||||
|
*/ |
||||
|
function shallowEqual(objA, objB) { |
||||
|
if (is(objA, objB)) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var keysA = Object.keys(objA); |
||||
|
var keysB = Object.keys(objB); |
||||
|
|
||||
|
if (keysA.length !== keysB.length) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Test for A's keys different from B.
|
||||
|
for (var i = 0; i < keysA.length; i++) { |
||||
|
if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
export default shallowEqual |
||||
@ -0,0 +1,138 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
/** |
||||
|
* CSS properties which accept numbers but are not in units of "px". |
||||
|
*/ |
||||
|
var isUnitlessNumber = { |
||||
|
boxFlex: true, |
||||
|
boxFlexGroup: true, |
||||
|
columnCount: true, |
||||
|
flex: true, |
||||
|
flexGrow: true, |
||||
|
flexPositive: true, |
||||
|
flexShrink: true, |
||||
|
flexNegative: true, |
||||
|
fontWeight: true, |
||||
|
lineClamp: true, |
||||
|
lineHeight: true, |
||||
|
opacity: true, |
||||
|
order: true, |
||||
|
orphans: true, |
||||
|
widows: true, |
||||
|
zIndex: true, |
||||
|
zoom: true, |
||||
|
|
||||
|
// SVG-related properties
|
||||
|
fillOpacity: true, |
||||
|
strokeDashoffset: true, |
||||
|
strokeOpacity: true, |
||||
|
strokeWidth: true |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @param {string} prefix vendor-specific prefix, eg: Webkit |
||||
|
* @param {string} key style name, eg: transitionDuration |
||||
|
* @return {string} style name prefixed with `prefix`, properly camelCased, eg: |
||||
|
* WebkitTransitionDuration |
||||
|
*/ |
||||
|
function prefixKey(prefix, key) { |
||||
|
return prefix + key.charAt(0).toUpperCase() + key.substring(1); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Support style names that may come passed in prefixed by adding permutations |
||||
|
* of vendor prefixes. |
||||
|
*/ |
||||
|
var prefixes = ['Webkit', 'ms', 'Moz', 'O']; |
||||
|
|
||||
|
// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
|
||||
|
// infinite loop, because it iterates over the newly added props too.
|
||||
|
Object.keys(isUnitlessNumber).forEach(function(prop) { |
||||
|
prefixes.forEach(function(prefix) { |
||||
|
isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
var msPattern = /^ms-/; |
||||
|
|
||||
|
var _uppercasePattern = /([A-Z])/g; |
||||
|
|
||||
|
/** |
||||
|
* Hyphenates a camelcased string, for example: |
||||
|
* |
||||
|
* > hyphenate('backgroundColor') |
||||
|
* < "background-color" |
||||
|
* |
||||
|
* For CSS style names, use `hyphenateStyleName` instead which works properly |
||||
|
* with all vendor prefixes, including `ms`. |
||||
|
* |
||||
|
* @param {string} string |
||||
|
* @return {string} |
||||
|
*/ |
||||
|
function hyphenate(string) { |
||||
|
return string.replace(_uppercasePattern, '-$1').toLowerCase(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Hyphenates a camelcased CSS property name, for example: |
||||
|
* |
||||
|
* > hyphenateStyleName('backgroundColor') |
||||
|
* < "background-color" |
||||
|
* > hyphenateStyleName('MozTransition') |
||||
|
* < "-moz-transition" |
||||
|
* > hyphenateStyleName('msTransition') |
||||
|
* < "-ms-transition" |
||||
|
* |
||||
|
* As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
|
||||
|
* is converted to `-ms-`. |
||||
|
* |
||||
|
* @param {string} string |
||||
|
* @return {string} |
||||
|
*/ |
||||
|
function hyphenateStyleName(string) { |
||||
|
return hyphenate(string).replace(msPattern, '-ms-'); |
||||
|
} |
||||
|
|
||||
|
var isArray = Array.isArray; |
||||
|
var keys = Object.keys; |
||||
|
|
||||
|
var counter = 1; |
||||
|
// Follows syntax at https://developer.mozilla.org/en-US/docs/Web/CSS/content,
|
||||
|
// including multiple space separated values.
|
||||
|
var unquotedContentValueRegex = /^(normal|none|(\b(url\([^)]*\)|chapter_counter|attr\([^)]*\)|(no-)?(open|close)-quote|inherit)((\b\s*)|$|\s+))+)$/; |
||||
|
|
||||
|
function buildRule(key, value) { |
||||
|
if (!isUnitlessNumber[key] && typeof value === 'number') { |
||||
|
value = '' + value + 'px'; |
||||
|
} else if (key === 'content' && !unquotedContentValueRegex.test(value)) { |
||||
|
value = "'" + value.replace(/'/g, "\\'") + "'"; |
||||
|
} |
||||
|
|
||||
|
return hyphenateStyleName(key) + ': ' + value + '; '; |
||||
|
} |
||||
|
|
||||
|
function styleToCssString(rules) { |
||||
|
var result = '' |
||||
|
if (typeof rules === 'string') { |
||||
|
return rules |
||||
|
} |
||||
|
if (!rules || keys(rules).length === 0) { |
||||
|
return result; |
||||
|
} |
||||
|
var styleKeys = keys(rules); |
||||
|
for (var j = 0, l = styleKeys.length; j < l; j++) { |
||||
|
var styleKey = styleKeys[j]; |
||||
|
var value = rules[styleKey]; |
||||
|
|
||||
|
if (isArray(value)) { |
||||
|
for (var i = 0, len = value.length; i < len; i++) { |
||||
|
result += buildRule(styleKey, value[i]); |
||||
|
} |
||||
|
} else { |
||||
|
result += buildRule(styleKey, value); |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
export default styleToCssString |
||||
@ -0,0 +1,234 @@ |
|||||
|
import baseComponent from '../helpers/baseComponent' |
||||
|
import classNames from '../helpers/classNames' |
||||
|
import styleToCssString from '../helpers/styleToCssString' |
||||
|
|
||||
|
const getPlacements = ([a, s, b] = rects, placement = 'top') => { |
||||
|
switch (placement) { |
||||
|
case 'topLeft': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top - b.height - 4, |
||||
|
left: s.scrollLeft + a.left, |
||||
|
} |
||||
|
case 'top': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top - b.height - 4, |
||||
|
left: s.scrollLeft + a.left + (a.width - b.width) / 2, |
||||
|
} |
||||
|
case 'topRight': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top - b.height - 4, |
||||
|
left: s.scrollLeft + a.left + a.width - b.width, |
||||
|
} |
||||
|
case 'rightTop': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top, |
||||
|
left: s.scrollLeft + a.left + a.width + 4, |
||||
|
} |
||||
|
case 'right': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + (a.height - b.height) / 2, |
||||
|
left: s.scrollLeft + a.left + a.width + 4, |
||||
|
} |
||||
|
case 'rightBottom': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + a.height - b.height, |
||||
|
left: s.scrollLeft + a.left + a.width + 4, |
||||
|
} |
||||
|
case 'bottomRight': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + a.height + 4, |
||||
|
left: s.scrollLeft + a.left + a.width - b.width, |
||||
|
} |
||||
|
case 'bottom': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + a.height + 4, |
||||
|
left: s.scrollLeft + a.left + (a.width - b.width) / 2, |
||||
|
} |
||||
|
case 'bottomLeft': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + a.height + 4, |
||||
|
left: s.scrollLeft + a.left, |
||||
|
} |
||||
|
case 'leftBottom': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + a.height - b.height, |
||||
|
left: s.scrollLeft + a.left - b.width - 4, |
||||
|
} |
||||
|
case 'left': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top + (a.height - b.height) / 2, |
||||
|
left: s.scrollLeft + a.left - b.width - 4, |
||||
|
} |
||||
|
case 'leftTop': |
||||
|
return { |
||||
|
top: s.scrollTop + a.top, |
||||
|
left: s.scrollLeft + a.left - b.width - 4, |
||||
|
} |
||||
|
default: |
||||
|
return { |
||||
|
left: 0, |
||||
|
top: 0, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
baseComponent({ |
||||
|
properties: { |
||||
|
prefixCls: { |
||||
|
type: String, |
||||
|
value: 'wux-popover', |
||||
|
}, |
||||
|
classNames: { |
||||
|
type: null, |
||||
|
value: 'wux-animate--fadeIn', |
||||
|
}, |
||||
|
theme: { |
||||
|
type: String, |
||||
|
value: 'light', |
||||
|
}, |
||||
|
title: { |
||||
|
type: String, |
||||
|
value: '', |
||||
|
}, |
||||
|
content: { |
||||
|
type: String, |
||||
|
value: '', |
||||
|
}, |
||||
|
placement: { |
||||
|
type: String, |
||||
|
value: 'top', |
||||
|
}, |
||||
|
trigger: { |
||||
|
type: String, |
||||
|
value: 'click', |
||||
|
}, |
||||
|
bodyStyle: { |
||||
|
type: [String, Object], |
||||
|
value: '', |
||||
|
observer(newVal) { |
||||
|
this.setData({ |
||||
|
extStyle: styleToCssString(newVal), |
||||
|
}) |
||||
|
}, |
||||
|
}, |
||||
|
defaultVisible: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
observer(newVal) { |
||||
|
if (this.data.controlled) { |
||||
|
this.updated(newVal) |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
controlled: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
mask: { |
||||
|
type: Boolean, |
||||
|
value: false, |
||||
|
}, |
||||
|
maskClosable: { |
||||
|
type: Boolean, |
||||
|
value: true, |
||||
|
}, |
||||
|
}, |
||||
|
data: { |
||||
|
extStyle: '', |
||||
|
popoverStyle: '', |
||||
|
popoverVisible: false, |
||||
|
}, |
||||
|
computed: { |
||||
|
classes: ['prefixCls, theme, placement', function(prefixCls, theme, placement) { |
||||
|
const wrap = classNames(prefixCls, { |
||||
|
[`${prefixCls}--theme-${theme}`]: theme, |
||||
|
[`${prefixCls}--placement-${placement}`]: placement, |
||||
|
}) |
||||
|
const content = `${prefixCls}__content` |
||||
|
const arrow = `${prefixCls}__arrow` |
||||
|
const inner = `${prefixCls}__inner` |
||||
|
const title = `${prefixCls}__title` |
||||
|
const innerContent = `${prefixCls}__inner-content` |
||||
|
const element = `${prefixCls}__element` |
||||
|
|
||||
|
return { |
||||
|
wrap, |
||||
|
content, |
||||
|
arrow, |
||||
|
inner, |
||||
|
title, |
||||
|
innerContent, |
||||
|
element, |
||||
|
} |
||||
|
}], |
||||
|
}, |
||||
|
methods: { |
||||
|
updated(popoverVisible) { |
||||
|
if (this.data.popoverVisible !== popoverVisible) { |
||||
|
this.setData({ popoverVisible }) |
||||
|
this.setBackdropVisible(popoverVisible) |
||||
|
} |
||||
|
}, |
||||
|
getPopoverStyle() { |
||||
|
const { prefixCls, placement } = this.data |
||||
|
const query = wx.createSelectorQuery().in(this) |
||||
|
query.select(`.${prefixCls}__element`).boundingClientRect() |
||||
|
query.selectViewport().scrollOffset() |
||||
|
query.select(`.${prefixCls}`).boundingClientRect() |
||||
|
query.exec((rects) => { |
||||
|
if (rects.filter((n) => !n).length) return |
||||
|
|
||||
|
const placements = getPlacements(rects, placement) |
||||
|
const popoverStyle = styleToCssString(placements) |
||||
|
|
||||
|
this.setData({ |
||||
|
popoverStyle, |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
/** |
||||
|
* 当组件进入过渡的开始状态时,设置气泡框位置信息 |
||||
|
*/ |
||||
|
onEnter() { |
||||
|
this.getPopoverStyle() |
||||
|
}, |
||||
|
onChange() { |
||||
|
const { popoverVisible, controlled } = this.data |
||||
|
const nextVisible = !popoverVisible |
||||
|
|
||||
|
if (!controlled) { |
||||
|
this.updated(nextVisible) |
||||
|
} |
||||
|
|
||||
|
this.triggerEvent('change', { visible: nextVisible }) |
||||
|
}, |
||||
|
onClick() { |
||||
|
if (this.data.trigger === 'click') { |
||||
|
this.onChange() |
||||
|
} |
||||
|
}, |
||||
|
setBackdropVisible(visible) { |
||||
|
if (this.data.mask && this.wuxBackdrop) { |
||||
|
this.wuxBackdrop[visible ? 'retain' : 'release']() |
||||
|
} |
||||
|
}, |
||||
|
onMaskClick() { |
||||
|
const { maskClosable, popoverVisible } = this.data |
||||
|
if (maskClosable && popoverVisible) { |
||||
|
this.onChange() |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
ready() { |
||||
|
const { defaultVisible, visible, controlled } = this.data |
||||
|
const popoverVisible = controlled ? visible : defaultVisible |
||||
|
if (this.data.mask) { |
||||
|
this.wuxBackdrop = this.selectComponent('#wux-backdrop') |
||||
|
} |
||||
|
this.updated(popoverVisible) |
||||
|
}, |
||||
|
}) |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"usingComponents": { |
||||
|
"wux-animation-group": "../animation-group/index", |
||||
|
"wux-backdrop": "../backdrop/index" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
<wux-backdrop id="wux-backdrop" wx:if="{{ mask }}" bind:click="onMaskClick" transparent/> |
||||
|
<view class="wux-class {{ classes.wrap }}" style="{{ extStyle + popoverStyle }}"> |
||||
|
<wux-animation-group in="{{ popoverVisible }}" classNames="{{ classNames }}" bind:enter="onEnter"> |
||||
|
<view class="{{ classes.content }}"> |
||||
|
<view class="{{ classes.arrow }}"></view> |
||||
|
<view class="{{ classes.inner }}"> |
||||
|
<view class="{{ classes.title }}" wx:if="{{ title }}">{{ title }}</view> |
||||
|
<slot name="title" wx:else></slot> |
||||
|
<view class="{{ classes.innerContent }}" wx:if="{{ content }}">{{ content }}</view> |
||||
|
<slot name="content" wx:else></slot> |
||||
|
</view> |
||||
|
</view> |
||||
|
</wux-animation-group> |
||||
|
</view> |
||||
|
<view class="{{ classes.element }}" catchtap="onClick"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
@ -0,0 +1,193 @@ |
|||||
|
.wux-popover { |
||||
|
font-family: Monospaced Number,Chinese Quote,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 1.5; |
||||
|
color: rgba(0,0,0,.65); |
||||
|
box-sizing: border-box; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
list-style: none; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
z-index: 1030; |
||||
|
cursor: auto; |
||||
|
-webkit-user-select: text; |
||||
|
-moz-user-select: text; |
||||
|
-ms-user-select: text; |
||||
|
user-select: text; |
||||
|
white-space: normal; |
||||
|
font-weight: 400; |
||||
|
text-align: left |
||||
|
} |
||||
|
.wux-popover::after { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
background: rgba(255,255,255,.01) |
||||
|
} |
||||
|
.wux-popover__container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100% |
||||
|
} |
||||
|
.wux-popover__element { |
||||
|
display: inline-block; |
||||
|
line-height: 1 |
||||
|
} |
||||
|
.wux-popover--theme-dark .wux-popover__inner { |
||||
|
background-color: #333 |
||||
|
} |
||||
|
.wux-popover--theme-dark.wux-popover--placement-top .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-topLeft .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-topRight .wux-popover__arrow { |
||||
|
border-top-color: #333 |
||||
|
} |
||||
|
.wux-popover--theme-dark.wux-popover--placement-right .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-rightBottom .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-rightTop .wux-popover__arrow { |
||||
|
border-right-color: #333 |
||||
|
} |
||||
|
.wux-popover--theme-dark.wux-popover--placement-bottom .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-bottomLeft .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-bottomRight .wux-popover__arrow { |
||||
|
border-bottom-color: #333 |
||||
|
} |
||||
|
.wux-popover--theme-dark.wux-popover--placement-left .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-leftBottom .wux-popover__arrow, |
||||
|
.wux-popover--theme-dark.wux-popover--placement-leftTop .wux-popover__arrow { |
||||
|
border-left-color: #333 |
||||
|
} |
||||
|
.wux-popover--theme-dark .wux-popover__inner, |
||||
|
.wux-popover--theme-dark .wux-popover__title { |
||||
|
color: #fff |
||||
|
} |
||||
|
.wux-popover--placement-top, |
||||
|
.wux-popover--placement-topLeft, |
||||
|
.wux-popover--placement-topRight { |
||||
|
padding-bottom: 10px |
||||
|
} |
||||
|
.wux-popover--placement-right, |
||||
|
.wux-popover--placement-rightBottom, |
||||
|
.wux-popover--placement-rightTop { |
||||
|
padding-left: 10px |
||||
|
} |
||||
|
.wux-popover--placement-bottom, |
||||
|
.wux-popover--placement-bottomLeft, |
||||
|
.wux-popover--placement-bottomRight { |
||||
|
padding-top: 10px |
||||
|
} |
||||
|
.wux-popover--placement-left, |
||||
|
.wux-popover--placement-leftBottom, |
||||
|
.wux-popover--placement-leftTop { |
||||
|
padding-right: 10px |
||||
|
} |
||||
|
.wux-popover__inner { |
||||
|
background-color: #fff; |
||||
|
background-clip: padding-box; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: 0 2px 8px rgba(0,0,0,.15); |
||||
|
color: rgba(0,0,0,.65) |
||||
|
} |
||||
|
.wux-popover__title { |
||||
|
position: relative; |
||||
|
min-width: 120px; |
||||
|
margin: 0; |
||||
|
padding: 5px 16px 4px; |
||||
|
min-height: 32px; |
||||
|
box-sizing: border-box; |
||||
|
color: rgba(0,0,0,.85); |
||||
|
font-weight: 500 |
||||
|
} |
||||
|
.wux-popover__title::after { |
||||
|
content: " "; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
height: 1PX; |
||||
|
border-bottom: 1PX solid #d9d9d9; |
||||
|
color: #d9d9d9; |
||||
|
transform-origin: 0 100%; |
||||
|
transform: scaleY(.5) |
||||
|
} |
||||
|
.wux-popover__inner-content { |
||||
|
padding: 24rpx 32rpx |
||||
|
} |
||||
|
.wux-popover__arrow { |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
position: absolute; |
||||
|
display: block; |
||||
|
border-color: transparent; |
||||
|
border-style: solid; |
||||
|
border-width: 16.97056274rpx |
||||
|
} |
||||
|
.wux-popover--placement-top .wux-popover__arrow, |
||||
|
.wux-popover--placement-topLeft .wux-popover__arrow, |
||||
|
.wux-popover--placement-topRight .wux-popover__arrow { |
||||
|
bottom: 8rpx; |
||||
|
border-bottom-width: 0; |
||||
|
border-top-color: #fff |
||||
|
} |
||||
|
.wux-popover--placement-top .wux-popover__arrow { |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%) |
||||
|
} |
||||
|
.wux-popover--placement-topLeft .wux-popover__arrow { |
||||
|
left: 32rpx |
||||
|
} |
||||
|
.wux-popover--placement-topRight .wux-popover__arrow { |
||||
|
right: 32rpx |
||||
|
} |
||||
|
.wux-popover--placement-right .wux-popover__arrow, |
||||
|
.wux-popover--placement-rightBottom .wux-popover__arrow, |
||||
|
.wux-popover--placement-rightTop .wux-popover__arrow { |
||||
|
left: 8rpx; |
||||
|
border-left-width: 0; |
||||
|
border-right-color: #fff |
||||
|
} |
||||
|
.wux-popover--placement-right .wux-popover__arrow { |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%) |
||||
|
} |
||||
|
.wux-popover--placement-rightTop .wux-popover__arrow { |
||||
|
top: 24rpx |
||||
|
} |
||||
|
.wux-popover--placement-rightBottom .wux-popover__arrow { |
||||
|
bottom: 24rpx |
||||
|
} |
||||
|
.wux-popover--placement-bottom .wux-popover__arrow, |
||||
|
.wux-popover--placement-bottomLeft .wux-popover__arrow, |
||||
|
.wux-popover--placement-bottomRight .wux-popover__arrow { |
||||
|
top: 8rpx; |
||||
|
border-top-width: 0; |
||||
|
border-bottom-color: #fff |
||||
|
} |
||||
|
.wux-popover--placement-bottom .wux-popover__arrow { |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%) |
||||
|
} |
||||
|
.wux-popover--placement-bottomLeft .wux-popover__arrow { |
||||
|
left: 32rpx |
||||
|
} |
||||
|
.wux-popover--placement-bottomRight .wux-popover__arrow { |
||||
|
right: 32rpx |
||||
|
} |
||||
|
.wux-popover--placement-left .wux-popover__arrow, |
||||
|
.wux-popover--placement-leftBottom .wux-popover__arrow, |
||||
|
.wux-popover--placement-leftTop .wux-popover__arrow { |
||||
|
right: 8rpx; |
||||
|
border-right-width: 0; |
||||
|
border-left-color: #fff |
||||
|
} |
||||
|
.wux-popover--placement-left .wux-popover__arrow { |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%) |
||||
|
} |
||||
|
.wux-popover--placement-leftTop .wux-popover__arrow { |
||||
|
top: 24rpx |
||||
|
} |
||||
|
.wux-popover--placement-leftBottom .wux-popover__arrow { |
||||
|
bottom: 24rpx |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save