Merge branch 'master' of ssh://github.com/bd-js/wcui

master
chenjiajian 2023-03-23 17:20:34 +08:00
commit a01a8e5d4e
4 changed files with 380 additions and 202 deletions

View File

@ -43,7 +43,7 @@
- [ ] `wc-datepicker`日期选择器
- [ ] `wc-timepicker`时间选择器
- [x] `wc-code`代码高亮插件
- [ ] `wc-scroll`滚动组件
- [x] `wc-scroll`滚动组件
- [ ] `wc-silder`滑块组件
- [ ] `wc-progress`进度条组件
- [ ] `wc-tree`树形菜单组件

View File

@ -17,6 +17,6 @@
"author": "yutent",
"license": "MIT",
"devDependencies": {
"@bd/wcui-cli": "^1.1.0"
"@bd/wcui-cli": "^1.2.0"
}
}

22
src/form/dropdown.js Normal file
View File

@ -0,0 +1,22 @@
import { nextTick, css, html, Component } from '@bd/core'
class Dropdown extends Component {
mounted() {
console.log('Dropdown: ', this.$refs)
}
foo() {}
render() {
return html`
<div class="aa" ref="aa" @click=${this.foo}>
<div class="bb" ref="bb">
<slot ref="dd"></slot>
</div>
</div>
<div class="cc" ref="cc">${this.foo}</div>
`
}
}
Dropdown.reg('dropdown')

View File

@ -4,258 +4,414 @@
* @date 2023/03/20 15:17:25
*/
import { css, bind, html, unbind, Component } from '@bd/core'
import { css, html, bind, unbind, Component } from '@bd/core'
class Scroll extends Component {
static props = {
axis: 'xy',
distance: 0
axis: 'xy', // 滚动方向, 默认x轴和y轴都可以滚动
delay: 1000, // 节流防抖延迟
distance: 1 // 触发距离阀值, 单位像素
}
dragging = false
hovering = false
static styles = [
css`
:host {
position: relative;
overflow: hidden;
display: block;
}
.scroller {
position: relative;
height: 100%;
width: 100%;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none; //火狐专属
}
.scroll-bar {
position: absolute;
background: #909399;
width: 0;
height: 0;
opacity: 0;
border-radius: 4px;
cursor: pointer;
transition: opacity ease-in-out 0.2s;
height: 100%;
&.vertical {
width: 8px;
top: 0;
right: 0;
.container {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
}
&.horizon {
height: 8px;
bottom: 0;
left: 0;
.wrapper {
overflow: auto;
scrollbar-width: none;
width: 100%;
height: 100%;
scrollbar-width: 0;
&::-webkit-scrollbar {
display: none;
}
}
&:hover {
opacity: 0.5 !important;
.content {
width: fit-content;
height: fit-content;
}
}
:host([disabled]) {
overflow: hidden !important;
.scroll-bar {
visibility: hidden;
`,
css`
/* 横向 */
.is-horizontal,
.is-vertical {
visibility: hidden;
position: absolute;
display: flex;
justify-content: flex-end;
z-index: 10240;
opacity: 0;
user-select: none;
transition: opacity 0.3s linear, visibility 0.3s linear;
.thumb {
display: block;
border-radius: 5px;
background: rgba(44, 47, 53, 0.25);
cursor: default;
transition: width 0.1s linear, height 0.1s linear;
&:hover {
background: rgba(44, 47, 53, 0.5);
}
}
}
.is-horizontal {
flex-direction: column;
left: 0;
bottom: 0;
width: 100%;
height: 10px;
.thumb {
width: 0;
height: 6px;
&:hover {
height: 10px;
}
}
}
/* 纵向 */
.is-vertical {
top: 0;
right: 0;
width: 10px;
height: 100%;
.thumb {
width: 6px;
height: 0;
&:hover {
width: 10px;
}
}
}
`,
css`
:host(:hover) {
.is-horizontal,
.is-vertical {
visibility: visible;
opacity: 1;
}
}
:host([axis='x']) {
overflow-x: auto;
overflow-y: hidden;
.vertical {
.wrapper {
overflow-y: hidden;
}
.is-vertical {
display: none;
}
}
:host([axis='y']) {
overflow-y: auto;
overflow-x: hidden;
.horizon {
.wrapper {
overflow-x: hidden;
}
.is-horizontal {
display: none;
}
}
:host([disabled]) {
.wrapper {
overflow: hidden;
}
.is-vertical,
.is-horizontal {
display: none;
}
}
`
]
stamp = 0
cache = {
xBar: 0, // 横轴长度
yBar: 0, // 纵轴长度
thumbX: 0, //横向条滚动距离
thumbY: 0 // 纵向条滚动距离
}
get scrollTop() {
return this.$refs.scroller.scrollTop
return this.$refs.box?.scrollTop || 0
}
set scrollTop(val) {
this.$refs.scroller.scrollTop = val
}
get scrollHeight() {
return this.$refs.scroller.scrollHeight
}
set scrollHeight(val) {
this.$refs.scroller.scrollHeight = val
set scrollTop(n) {
n = +n
if (n === n) {
this.$refs.box.scrollTop = n
}
}
get scrollLeft() {
return this.$refs.scroller.scrollLeft
return this.$refs.box?.scrollLeft || 0
}
set scrollLeft(val) {
this.$refs.scroller.scrollLeft = val
set scrollLeft(n) {
n = +n
if (n === n) {
this.$refs.box.scrollLeft = n
}
}
get scrollHeight() {
return this.$refs.box?.scrollHeight
}
get scrollWidth() {
return this.$refs.scroller.scrollWidth
}
set scrollWidth(val) {
this.$refs.scroller.scrollWidth = val
}
render() {
return html`
<div ref="scroller" class="scroller">
<slot ref="slot"></slot>
</div>
<div ref="vertical" class="scroll-bar vertical"></div>
<div ref="horizon" class="scroll-bar horizon"></div>
`
return this.$refs.box?.scrollWidth
}
onmouseenter() {
this.hovering = true
this.$refs.vertical.style.opacity = 0.3
this.$refs.horizon.style.opacity = 0.3
}
onmouseleave() {
this.hovering = false
if (!this.dragging) {
this.$refs.vertical.style.opacity = 0
this.$refs.horizon.style.opacity = 0
__init__(ev) {
//
let width = this.offsetWidth
let height = this.offsetHeight
let scrollWidth = this.scrollWidth
let scrollHeight = this.scrollHeight
let yBar = 50 // 滚动条的高度
let xBar = 50 // 滚动条的宽度
this.scrollLeft = 0
this.scrollTop = 0
// 修正由于未知原因,导致父容器产生滚动距离
// 导致的内容被遮挡的bug
// 重构后待观察, 暂时注释掉这行(2023/3/23)
// this.$refs.box.parentNode.scrollTop = 0
yBar = (height * (height / scrollHeight)) >> 0
xBar = (width * (width / scrollWidth)) >> 0
if (yBar < 50) {
yBar = 50
}
}
onmousedown(e) {
this.dragging = true
const {
clientHeight,
scrollHeight,
clientWidth,
scrollWidth,
verticalHeight,
horizonWidth
} = this
let start, cur
const isVerticalScroll = e.target === this.$refs.vertical
if (isVerticalScroll) {
start = e.clientY
cur = this.scrollTop
} else {
start = e.clientX
cur = this.scrollLeft
if (xBar < 50) {
xBar = 50
}
const onmousemove = bind(document, 'mousemove', e => {
let dif, rang, progress
if (isVerticalScroll) {
dif = e.clientY - start
rang = clientHeight - verticalHeight
progress = dif / rang
this.scrollTop = cur + progress * (scrollHeight - clientHeight)
} else {
dif = e.clientX - start
rang = clientWidth - horizonWidth
progress = dif / rang
this.scrollLeft = cur + progress * (scrollWidth - clientWidth)
}
})
// 100%或主体高度比滚动条还短时不显示
if (xBar >= width) {
xBar = 0
}
if (yBar >= height) {
yBar = 0
}
this.onmouseup = bind(document, 'mouseup', () => {
this.dragging = false
if (!this.hovering) {
this.$refs.vertical.style.opacity = 0
this.$refs.horizon.style.opacity = 0
}
unbind(document, 'mousemove', onmousemove)
})
}
onscroll(ev) {
const {
distance,
scrollTop,
scrollLeft,
clientHeight,
scrollHeight,
clientWidth,
scrollWidth,
verticalHeight,
horizonWidth
} = this
const { vertical, horizon } = this.$refs
const verticalProgress = scrollTop / (scrollHeight - clientHeight) || 0
const horizonProgress = scrollLeft / (scrollWidth - clientWidth) || 0
vertical.style.transform = `translateY(${
(clientHeight - verticalHeight) * verticalProgress
}px)`
horizon.style.transform = `translateX(${
(clientWidth - horizonWidth) * horizonProgress
}px)`
if (!ev) {
//事件对象不存在,则是手动调用的 不触发事件。
return
}
if (this.verticalHeight) {
if (scrollHeight - scrollTop - clientHeight <= distance) {
this.$emit('reach-bottom')
}
if (scrollTop === 0) {
this.$emit('reach-top')
}
}
}
initScrollBar() {
const { axis, clientHeight, scrollHeight, clientWidth, scrollWidth } = this
const { vertical, horizon } = this.$refs
// console.log(this.$refs)
if (clientHeight !== scrollHeight) {
this.verticalHeight = (clientHeight / scrollHeight) * clientHeight
this.cache.yBar = yBar
this.cache.xBar = xBar
if (xBar > 0) {
this.$refs.x.parentNode.style.display = 'flex'
this.$refs.x.style.width = xBar + 'px'
} else {
this.verticalHeight = 0
this.$refs.x.parentNode.style.display = 'none'
}
if (clientWidth !== scrollWidth) {
this.horizonWidth = (clientWidth / scrollWidth) * clientWidth
if (yBar > 0) {
this.$refs.y.parentNode.style.display = 'flex'
this.$refs.y.style.height = yBar + 'px'
} else {
this.horizonWidth = 0
}
if (axis.includes('x')) {
horizon.style.width = this.horizonWidth + 'px'
bind(horizon, 'mousedown', this.onmousedown.bind(this))
}
if (axis.includes('y')) {
vertical.style.height = this.verticalHeight + 'px'
bind(vertical, 'mousedown', this.onmousedown.bind(this))
this.$refs.y.parentNode.style.display = 'none'
}
}
#watchResize() {
const slotList = this.$refs.slot.assignedNodes()
if (slotList) {
slotList.forEach(element => {
if (element.nodeType === 1) {
this.resizeObserver.observe(element)
_fetchScrollX(moveX) {
let { xBar } = this.cache
let { scrollWidth, offsetWidth: width } = this
if (moveX < 0) {
moveX = 0
} else if (moveX > width - xBar) {
moveX = width - xBar
}
this.scrollLeft = (scrollWidth - width) * (moveX / (width - xBar))
this.$refs.x.style.transform = `translateX(${moveX}px)`
return moveX
}
_fetchScrollY(moveY) {
let { yBar } = this.cache
let { scrollHeight, offsetHeight: height } = this
if (moveY < 0) {
moveY = 0
} else if (moveY > height - yBar) {
moveY = height - yBar
}
this.scrollTop = (scrollHeight - height) * (moveY / (height - yBar))
this.$refs.y.style.transform = `translateY(${moveY}px)`
return moveY
}
_fireReachEnd(action = 'reach-bottom') {
let delay = this.delay
let { scrollHeight, offsetHeight: height } = this
let top = this.scrollTop
let now = Date.now()
if (now - this.stamp > delay) {
if (action === 'reach-bottom') {
if (height + top < scrollHeight) {
return
}
})
} else {
if (top > 0) {
return
}
}
this.stamp = now
this.$emit(action)
}
}
mounted() {
this.$on('mouseenter', this.onmouseenter)
this.$on('mouseleave', this.onmouseleave)
this.onscroll = bind(
this.$refs.scroller,
'scroll',
this.onscroll.bind(this)
)
this.resizeObserver = new ResizeObserver(() => {
this.initScrollBar()
this.onscroll()
let startX,
startY,
moveX,
moveY,
mousemoveFn = ev => {
let { thumbY, thumbX } = this.cache
if (startX !== undefined) {
moveX = this._fetchScrollX(thumbX + ev.pageX - startX)
}
if (startY !== undefined) {
moveY = this._fetchScrollY(thumbY + ev.pageY - startY)
}
},
mouseupFn = ev => {
if (Math.abs(ev.pageY - startY) > this.distance) {
this._fireReachEnd(ev.pageY > startY ? 'reach-bottom' : 'reach-top')
}
startX = undefined
startY = undefined
this.cache.thumbX = moveX || 0
this.cache.thumbY = moveY || 0
delete this._active
unbind(document, 'mousemove', mousemoveFn)
unbind(document, 'mouseup', mouseupFn)
}
// 鼠标滚动事件
this._scrollFn = bind(this.$refs.box, 'scroll', ev => {
ev.stopPropagation()
// 拖拽时忽略滚动事件
if (this._active) {
return
}
let { xBar, yBar, thumbX, thumbY } = this.cache
let {
axis,
scrollHeight,
scrollWidth,
offsetHeight: height,
offsetWidth: width,
scrollTop,
scrollLeft
} = this
// x轴 y轴 都为0时, 不作任何处理
if (xBar === 0 && yBar === 0) {
return
}
//
if (axis === 'y' || axis === 'xy') {
if (yBar) {
// 修正滚动条的位置
// 滚动比例 y 滚动条的可移动距离
let fixedY = ~~(
(scrollTop / (scrollHeight - height)) *
(height - yBar)
)
if (fixedY !== thumbY) {
this.cache.thumbY = fixedY
this.$refs.y.style.transform = `translateY(${fixedY}px)`
if (Math.abs(fixedY - thumbY) > this.distance) {
this._fireReachEnd(fixedY > thumbY ? 'reach-bottom' : 'reach-top')
}
}
}
}
if (axis === 'x' || axis === 'xy') {
if (xBar) {
// 修正滚动条的位置
// 滚动比例 x 滚动条的可移动距离
let fixedX = ~~((scrollLeft / (scrollWidth - width)) * (width - xBar))
if (fixedX !== thumbX) {
this.cache.thumbX = fixedX
this.$refs.x.style.transform = `translateX(${fixedX}px)`
}
}
}
this.$emit('scroll')
})
this.#watchResize()
this.$refs.slot.addEventListener('slotchange', () => this.#watchResize())
this._yBarFn = bind(this.$refs.y, 'mousedown', ev => {
startY = ev.pageY
this._active = true
bind(document, 'mousemove', mousemoveFn)
bind(document, 'mouseup', mouseupFn)
})
this._xBarFn = bind(this.$refs.x, 'mousedown', ev => {
startX = ev.pageX
this._active = true
bind(document, 'mousemove', mousemoveFn)
bind(document, 'mouseup', mouseupFn)
})
this.__observer = new ResizeObserver(this.__init__.bind(this))
this.__observer.observe(this.$refs.cont)
}
unmounted() {
this.resizeObserver.disconnect()
unbind(document, 'mouseup', this.onmouseup)
this.$of('mouseenter', this.onmouseenter)
this.$of('mouseleave', this.onmouseleave)
this.$of('scroll', this.onscroll)
unbind(this.$refs.vertical, 'mousedown', this.onmousedown)
unbind(this.$refs.horizon, 'mousedown', this.onmousedown)
this.__observer.disconnect()
}
render() {
return html`
<div class="container">
<div class="wrapper" ref="box">
<div class="content" ref="cont"><slot></slot></div>
</div>
</div>
<div class="is-horizontal">
<span ref="x" class="thumb"></span>
</div>
<div class="is-vertical">
<span ref="y" class="thumb"></span>
</div>
`
}
}