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}
+
+
+ `
}
}