diff --git a/src/progress/index.js b/src/progress/index.js index a0fd6b9..b072768 100644 --- a/src/progress/index.js +++ b/src/progress/index.js @@ -185,11 +185,12 @@ class Progress extends Component { } #drawLine(v) { + let l = v < 2 ? 2 : v > 98 ? 98 : v return html`
-
+
${v}%
` diff --git a/src/slider/index.js b/src/slider/index.js index 6b3da72..654030d 100644 --- a/src/slider/index.js +++ b/src/slider/index.js @@ -1,10 +1,10 @@ /** - * {} - * @author yutent - * @date 2023/03/21 16:14:10 + * {进度条} + * @author chensbox + * @date 2023/04/28 16:14:10 */ -import { css, bind, unbind, html, Component } from 'wkit' +import { css, html, Component, range } from 'wkit' class Slider extends Component { static props = { @@ -12,325 +12,267 @@ class Slider extends Component { type: Number, default: 0, observer(val) { - this.$bar && this.initValue(val) + this.value = this.#fix(val) } }, - type: 'info', - max: 100, - min: 0, - step: 1, - disabled: false, - readonly: false, - vertical: false + step: { + type: Number, + default: 1, + observer(v) { + v += '' + this.#decimal = v.split('.').pop().length + } + }, + min: { + type: Number, + default: 0, + observer(v) { + this.#updateRange() + } + }, + max: { + type: Number, + default: 100, + observer(v) { + this.#updateRange() + } + }, + vertical: false, + disabled: false } + #len = 100 + #stops = 10 // 断点数, 最大只显示10个断点 + #point = 10 // 每个断点的百分比 + #value = 0 // 内部的值, 固定使用 0-100范围的值 + #decimal = 0 //小数位 + static styles = [ css` :host { - display: inline-block; + display: flex; + align-items: center; width: 100%; - height: 38px; + min-height: 32px; } - .slider { + + .container { + --line-width: var(--wc-slider-line-width, 6px); + --base-color: var(--wc-slider-inactive-color, var(--color-plain-2)); + --active-color: var(--wc-slider-active-color, var(--color-teal-1)); position: relative; - display: flex; - align-items: center; - height: 100%; - } - .runway { - flex: 1; - position: relative; - height: 6px; - // width: 100%; - background: #e4e7ed; - border-radius: 99rem; - cursor: pointer; - .bar { - flex: 1; - pointer-events: none; - position: absolute; - height: 100%; - width: 0%; - border-radius: 99rem; - background: var(--color-blue-2, #409eff); - } - } - .dot-wrapper { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - height: 36px; - width: 36px; - top: 50%; - left: 0%; - cursor: grab; - transform: translate(-50%, -50%); - .dot { - height: 20px; - width: 20px; - border-radius: 50%; - border: 2px solid var(--color-blue-2, #409eff); - background: #fff; - transition: transform 0.2s ease-in-out; - &:hover { - transform: scale(1.2); - } - } - } - .tips { - opacity: 0; - pointer-events: none; + width: 100%; + height: var(--line-width); + -webkit-user-select: none; user-select: none; + } + + .slider-bar { position: absolute; - top: -100%; - border-radius: 4px; - padding: 10px; - z-index: 2000; - font-size: 12px; - line-height: 1.2; - min-width: 10px; - white-space: nowrap; - color: #fff; - background: #303133; - transform: translateX(-50%); - transition: opacity 0.2s ease-in-out; - &.show { - opacity: 1; - } - &:after { - position: absolute; - border: 6px solid transparent; - bottom: -10px; - left: 50%; - transform: translateX(-50%); - content: ''; - border-top-color: #303133; + left: 0; + top: 0; + width: 100%; + height: var(--line-width); + border-radius: 32px; + background: var(--base-color); + i { + display: none; } } `, - //状态 + // vertical css` :host([vertical]) { - display: inline-block; - width: 38px; - height: 250px; - .slider { - justify-content: center; - .runway { - flex: 0 0 auto; - display: flex; - flex-direction: column-reverse; - width: 6px; - height: 100%; + width: auto; + height: 100%; + align-items: flex-end; - cursor: pointer; - .bar { - height: 0%; - width: 100%; - } - .dot-wrapper { - left: 50%; - top: 100%; - transform: translate(-50%, -50%); - } - } + .container { + width: var(--line-width); + height: 100%; + min-height: 32px; } - .tips { - top: 100%; - left: 50%; - transform: translate(-50%, -180%); - } - } - :host([hide-tooltip]) .tips { - visibility: hidden; - } - :host([loading]), - :host([disabled]) { - pointer-events: none; - cursor: not-allowed; - opacity: 0.6; + .slider-bar { + width: var(--line-width); + height: 100%; + } + + .thumb { + top: auto; + transform: translate(calc(var(--line-width) / 2 - 16px), 16px); + } } `, - //配色 - css` - $colors: ( - primary: 'teal', - info: 'blue', - success: 'green', - warning: 'orange', - danger: 'red', - secondary: 'dark', - help: 'grey' - ); - @loop $t, $c in $colors { - :host([type='#{$t}']) { - .bar { - background-color: var(--color-#{$c}-2); + // stops + css` + :host([show-stops]) { + .slider-bar { + i { + position: absolute; + display: block; + width: var(--line-width); + height: var(--line-width); + border-radius: 50%; + background: #fff; + transform: translateX(-50%); } - .dot { - border-color: var(--color-#{$c}-2); + } + } + `, + // thumb + css` + .thumb { + position: absolute; + left: 0; + top: calc((32px - var(--line-width)) / -2); + z-index: 2; + width: 32px; + height: 32px; + padding: 7px; + transform: translateX(-16px); + + &::before { + display: block; + width: 18px; + height: 18px; + border: 2px solid var(--active-color); + border-radius: 50%; + content: ''; + background: #fff; + cursor: grab; + transition: transform 0.1s ease-in-out; + } + + .value { + display: none; + position: absolute; + left: 50%; + bottom: 32px; + padding: 4px 8px; + border-radius: 4px; + font-size: 14px; + text-align: center; + background: var(--color-dark-2); + color: #fff; + transform: translateX(-50%); + + &::after { + display: block; + position: absolute; + left: 50%; + margin-left: -3px; + width: 6px; + height: 6px; + background: var(--color-dark-2); + content: ''; + transform: rotate(45deg); + } + } + + &:hover { + &::before { + transform: scale(1.15); + } + + .value { + display: block; + } + } + + &:active { + &::before { + cursor: grabbing; } } } ` ] - progress = 0 - mounted() { - this.$bar = this.$refs.bar - this.$dotWrap = this.$refs.dotWrap - this.$runway = this.$refs.runway - this.$tips = this.$refs.tips - this.accuracy = this.step >= 1 ? 0 : String(this.step).split('.')[1].length - this.initValue(this.value) + + #updateRange() { + let { min, max, step } = this + if (min >= max) { + throw new Error('Slider props "min >= max"') + } + let len = max - min + this.#stops = ~~(len / step) + if (this.#stops > 10) { + this.#stops = 10 + } + this.#len = len + this.#point = +(100 / this.#stops).toFixed(2) } - onMousedown(e) { - let { - value: preValue, - step, - max, - min, - progress, - vertical, - disabled, - readOnly, - accuracy - } = this - if (disabled || readOnly) { - return - } - this.$tips.classList.toggle('show') - preValue = +preValue || min - let start = vertical ? e.clientY : e.clientX - let onMousemove = bind(document, 'mousemove', e => { - e.preventDefault() - let distance = (vertical ? e.clientY : e.clientX) - start - let scale = - (distance / - (vertical ? this.$runway.clientHeight : this.$runway.clientWidth)) * - (vertical ? -1 : 1) + #fix(val) { + let { min, max, step } = this + let fix + val = Math.max(min, Math.min(val, max)) + val = +val.toFixed(this.#decimal) - let diff = - accuracy === 0 ? Math.round(scale * (max - min)) : scale * (max - min) - let newProgress = progress + Math.floor(scale * 100) - let newValue = - accuracy === 0 ? Math.floor(preValue + diff) : preValue + diff - newValue = Math.max(newValue, min) - newValue = Math.min(newValue, max) - - if (accuracy === 0) { - if (newValue % step) { - return - } + fix = +(val % step).toFixed(this.#decimal) + if (fix !== step) { + if (fix < step / 2) { + val -= fix } else { - let _newValue = Math.round(newValue * Math.pow(10, accuracy)) - let _step = step * Math.pow(10, accuracy) - if (_newValue % _step) { - return - } - } - this.value = newValue.toFixed(accuracy) - requestAnimationFrame(() => { - this.setProgress(vertical ? 100 - newProgress : newProgress) - }) - this.$emit('input') - }) - - let onMouseup = bind(document, 'mouseup', () => { - unbind(document, 'mousemove', onMousemove) - unbind(document, 'mouseup', onMouseup) - this.$tips.classList.toggle('show') - this.$emit('change') - }) - } - onClick(e) { - if (e.target !== this.$refs.runway) { - return - } - let { max, min, step, vertical, disabled, readOnly, accuracy } = this - if (disabled || readOnly) { - return - } - let { clientWidth, clientHeight } = e.target - let { offsetX, offsetY } = e - let range = max - min - let scale = - (vertical ? offsetY : offsetX) / (vertical ? clientHeight : clientWidth) - - step *= Math.pow(10, accuracy) - - let newValue = - +(scale * range + min).toFixed(accuracy) * Math.pow(10, accuracy) - let mod = +(newValue % step).toFixed(accuracy) - if (mod) { - let half = step / 2 - if (mod > half) { - newValue += step - mod - } else { - newValue -= mod + val = val - fix + step } } - newValue *= Math.pow(10, -accuracy) - this.value = (vertical ? range - newValue : newValue).toFixed(accuracy) - let progress = Math.floor(((newValue - min) / range) * 100) - this.setProgress(progress) - this.$emit('change') - this.$emit('input') - if (!this.timeout) { - this.$tips.classList.toggle('show') + this.#value = Math.round(((val - min) * 100) / (max - min)) + return val + } + + #seek(ev) { + let { clientWidth, clientHeight } = ev.target + let { min, max, step } = this + let val = 0 + if (this.vertical) { + val = (clientHeight - ev.offsetY) / clientHeight } else { - clearTimeout(this.timeout) - this.timeout = null + val = ev.offsetX / clientWidth } - this.timeout = setTimeout(() => { - this.$tips.classList.toggle('show') - this.timeout = null - }, 1000) + val *= this.#len + val += min + + this.value = this.#fix(val) } - initValue(val) { - let { max, min, vertical, disabled, readOnly } = this - if (disabled || readOnly) { - return - } - let range = max - min - val = Math.max(val, min) - val = Math.min(val, max) - this.value = val - let progress = Math.floor(((val - min) / range) * 100) - progress = vertical ? 100 - progress : progress - this.setProgress(progress) - } - setProgress(val) { - let { vertical } = this - val = Math.floor(val) - val = Math.min(val, 100) - val = Math.max(val, 0) - if (vertical) { - this.$bar.style.height = `${100 - val}%` - this.$dotWrap.style.top = `${val}%` - this.$tips.style.top = `${val}%` - this.progress = 100 - val - } else { - this.$bar.style.width = `${val}%` - this.$dotWrap.style.left = `${val}%` - this.$tips.style.left = `${val}%` - this.progress = val + + get #activeStyle() { + let v = this.#value + let deg = this.vertical ? 0 : '90deg' + return `background:linear-gradient(${deg}, var(--active-color) ${v}%, transparent ${v}%)` + } + + get #thumbStyle() { + let v = this.#value + if (this.vertical) { + return `bottom:${v}%` } + return `left:${v}%` } + render() { - return html`
-
-
-
-
+ let n = this.hasAttribute('show-stops') ? this.#stops : 1 + + return html` +
+
+ ${range(1, n).map(i => { + return html`` + })}
-
-
${this.value}
-
` +
+ +
+ ${this.value} +
+ + ` } }