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-datepicker`日期选择器
- [ ] `wc-timepicker`时间选择器 - [ ] `wc-timepicker`时间选择器
- [x] `wc-code`代码高亮插件 - [x] `wc-code`代码高亮插件
- [ ] `wc-scroll`滚动组件 - [x] `wc-scroll`滚动组件
- [ ] `wc-silder`滑块组件 - [ ] `wc-silder`滑块组件
- [ ] `wc-progress`进度条组件 - [ ] `wc-progress`进度条组件
- [ ] `wc-tree`树形菜单组件 - [ ] `wc-tree`树形菜单组件

View File

@ -17,6 +17,6 @@
"author": "yutent", "author": "yutent",
"license": "MIT", "license": "MIT",
"devDependencies": { "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 * @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 { class Scroll extends Component {
static props = { static props = {
axis: 'xy', axis: 'xy', // 滚动方向, 默认x轴和y轴都可以滚动
distance: 0 delay: 1000, // 节流防抖延迟
distance: 1 // 触发距离阀值, 单位像素
} }
dragging = false
hovering = false
static styles = [ static styles = [
css` css`
:host { :host {
position: relative; position: relative;
overflow: hidden;
display: block; display: block;
}
.scroller {
position: relative;
height: 100%;
width: 100%; width: 100%;
height: 100%;
.container {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
}
.wrapper {
overflow: auto; overflow: auto;
scrollbar-width: none;
width: 100%;
height: 100%;
scrollbar-width: 0;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
scrollbar-width: none; //火狐专属
} }
.scroll-bar { .content {
position: absolute; width: fit-content;
background: #909399; height: fit-content;
width: 0; }
height: 0; }
opacity: 0; `,
border-radius: 4px; css`
cursor: pointer; /* 横向 */
transition: opacity ease-in-out 0.2s;
&.vertical { .is-horizontal,
width: 8px; .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; top: 0;
right: 0; right: 0;
} width: 10px;
&.horizon { height: 100%;
height: 8px;
bottom: 0; .thumb {
left: 0; width: 6px;
} height: 0;
&:hover { &:hover {
opacity: 0.5 !important; width: 10px;
} }
} }
:host([disabled]) { }
overflow: hidden !important; `,
.scroll-bar { css`
visibility: hidden; :host(:hover) {
.is-horizontal,
.is-vertical {
visibility: visible;
opacity: 1;
} }
} }
:host([axis='x']) { :host([axis='x']) {
overflow-x: auto; .wrapper {
overflow-y: hidden; overflow-y: hidden;
.vertical { }
.is-vertical {
display: none; display: none;
} }
} }
:host([axis='y']) { :host([axis='y']) {
overflow-y: auto; .wrapper {
overflow-x: hidden; overflow-x: hidden;
.horizon { }
.is-horizontal {
display: none;
}
}
:host([disabled]) {
.wrapper {
overflow: hidden;
}
.is-vertical,
.is-horizontal {
display: none; display: none;
} }
} }
` `
] ]
stamp = 0
cache = {
xBar: 0, // 横轴长度
yBar: 0, // 纵轴长度
thumbX: 0, //横向条滚动距离
thumbY: 0 // 纵向条滚动距离
}
get scrollTop() { get scrollTop() {
return this.$refs.scroller.scrollTop return this.$refs.box?.scrollTop || 0
} }
set scrollTop(val) {
this.$refs.scroller.scrollTop = val set scrollTop(n) {
n = +n
if (n === n) {
this.$refs.box.scrollTop = n
} }
get scrollHeight() {
return this.$refs.scroller.scrollHeight
}
set scrollHeight(val) {
this.$refs.scroller.scrollHeight = val
} }
get scrollLeft() { 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() { get scrollWidth() {
return this.$refs.scroller.scrollWidth return this.$refs.box?.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>
`
} }
onmouseenter() { __init__(ev) {
this.hovering = true //
this.$refs.vertical.style.opacity = 0.3 let width = this.offsetWidth
this.$refs.horizon.style.opacity = 0.3 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
} }
onmouseleave() { if (xBar < 50) {
this.hovering = false xBar = 50
if (!this.dragging) {
this.$refs.vertical.style.opacity = 0
this.$refs.horizon.style.opacity = 0
} }
// 100%或主体高度比滚动条还短时不显示
if (xBar >= width) {
xBar = 0
} }
onmousedown(e) { if (yBar >= height) {
this.dragging = true yBar = 0
const { }
clientHeight,
scrollHeight, this.cache.yBar = yBar
clientWidth, this.cache.xBar = xBar
scrollWidth,
verticalHeight, if (xBar > 0) {
horizonWidth this.$refs.x.parentNode.style.display = 'flex'
} = this this.$refs.x.style.width = xBar + 'px'
let start, cur
const isVerticalScroll = e.target === this.$refs.vertical
if (isVerticalScroll) {
start = e.clientY
cur = this.scrollTop
} else { } else {
start = e.clientX this.$refs.x.parentNode.style.display = 'none'
cur = this.scrollLeft
} }
if (yBar > 0) {
const onmousemove = bind(document, 'mousemove', e => { this.$refs.y.parentNode.style.display = 'flex'
let dif, rang, progress this.$refs.y.style.height = yBar + 'px'
if (isVerticalScroll) {
dif = e.clientY - start
rang = clientHeight - verticalHeight
progress = dif / rang
this.scrollTop = cur + progress * (scrollHeight - clientHeight)
} else { } else {
dif = e.clientX - start this.$refs.y.parentNode.style.display = 'none'
rang = clientWidth - horizonWidth }
progress = dif / rang
this.scrollLeft = cur + progress * (scrollWidth - clientWidth)
} }
})
this.onmouseup = bind(document, 'mouseup', () => { _fetchScrollX(moveX) {
this.dragging = false let { xBar } = this.cache
if (!this.hovering) { let { scrollWidth, offsetWidth: width } = this
this.$refs.vertical.style.opacity = 0
this.$refs.horizon.style.opacity = 0 if (moveX < 0) {
moveX = 0
} else if (moveX > width - xBar) {
moveX = width - xBar
} }
unbind(document, 'mousemove', onmousemove) this.scrollLeft = (scrollWidth - width) * (moveX / (width - xBar))
}) this.$refs.x.style.transform = `translateX(${moveX}px)`
return moveX
} }
onscroll(ev) {
const { _fetchScrollY(moveY) {
distance, let { yBar } = this.cache
scrollTop, let { scrollHeight, offsetHeight: height } = this
scrollLeft,
clientHeight, if (moveY < 0) {
scrollHeight, moveY = 0
clientWidth, } else if (moveY > height - yBar) {
scrollWidth, moveY = height - yBar
verticalHeight, }
horizonWidth
} = this this.scrollTop = (scrollHeight - height) * (moveY / (height - yBar))
const { vertical, horizon } = this.$refs this.$refs.y.style.transform = `translateY(${moveY}px)`
const verticalProgress = scrollTop / (scrollHeight - clientHeight) || 0 return moveY
const horizonProgress = scrollLeft / (scrollWidth - clientWidth) || 0 }
vertical.style.transform = `translateY(${
(clientHeight - verticalHeight) * verticalProgress _fireReachEnd(action = 'reach-bottom') {
}px)` let delay = this.delay
horizon.style.transform = `translateX(${ let { scrollHeight, offsetHeight: height } = this
(clientWidth - horizonWidth) * horizonProgress let top = this.scrollTop
}px)` let now = Date.now()
if (!ev) {
//事件对象不存在,则是手动调用的 不触发事件。 if (now - this.stamp > delay) {
if (action === 'reach-bottom') {
if (height + top < scrollHeight) {
return 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
} else { } else {
this.verticalHeight = 0 if (top > 0) {
} return
if (clientWidth !== scrollWidth) {
this.horizonWidth = (clientWidth / scrollWidth) * clientWidth
} 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))
} }
} }
#watchResize() {
const slotList = this.$refs.slot.assignedNodes() this.stamp = now
if (slotList) { this.$emit(action)
slotList.forEach(element => {
if (element.nodeType === 1) {
this.resizeObserver.observe(element)
}
})
} }
} }
mounted() { mounted() {
this.$on('mouseenter', this.onmouseenter) let startX,
this.$on('mouseleave', this.onmouseleave) startY,
this.onscroll = bind( moveX,
this.$refs.scroller, moveY,
'scroll', mousemoveFn = ev => {
this.onscroll.bind(this) 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)
) )
this.resizeObserver = new ResizeObserver(() => {
this.initScrollBar() if (fixedY !== thumbY) {
this.onscroll() 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() { unmounted() {
this.resizeObserver.disconnect() this.__observer.disconnect()
unbind(document, 'mouseup', this.onmouseup) }
this.$of('mouseenter', this.onmouseenter)
this.$of('mouseleave', this.onmouseleave) render() {
this.$of('scroll', this.onscroll) return html`
unbind(this.$refs.vertical, 'mousedown', this.onmousedown) <div class="container">
unbind(this.$refs.horizon, 'mousedown', this.onmousedown) <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>
`
} }
} }