32 changed files with 2144 additions and 1 deletions
Split 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