UNPKG

mdui

Version:

实现 material you 设计规范的 Web Components 组件库

212 lines (211 loc) 9.25 kB
import { __decorate } from "tslib"; import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { map } from 'lit/directives/map.js'; import { createRef, ref } from 'lit/directives/ref.js'; import { styleMap } from 'lit/directives/style-map.js'; import { when } from 'lit/directives/when.js'; import { $ } from '@mdui/jq/$.js'; import '@mdui/jq/methods/css.js'; import { FormController, formResets } from '@mdui/shared/controllers/form.js'; import { defaultValue } from '@mdui/shared/decorators/default-value.js'; import { SliderBase } from '../slider/slider-base.js'; /** * @summary 范围滑块组件 * * ```html * <mdui-range-slider></mdui-range-slider> * ``` * * @event focus - 获得焦点时触发 * @event blur - 失去焦点时触发 * @event change - 值发生变更,且失去焦点时,将触发该事件 * @event input - 值变更时触发 * @event invalid - 表单字段验证未通过时触发 * * @csspart track-inactive - 未激活状态的轨道 * @csspart track-active - 已激活状态的轨道 * @csspart handle - 操作杆 * @csspart label - 提示文本 * @csspart tickmark - 刻度标记 */ let RangeSlider = class RangeSlider extends SliderBase { constructor() { super(...arguments); /** * 默认值。在重置表单时,将重置为该默认值。此属性只能通过 JavaScript 属性设置 */ this.defaultValue = []; /** * 当前操作的是哪一个 handle */ this.currentHandle = 'start'; this.rippleStartRef = createRef(); this.rippleEndRef = createRef(); this.handleStartRef = createRef(); this.handleEndRef = createRef(); this.formController = new FormController(this); this._value = []; this.getRippleIndex = () => { if (this.hoverHandle) { return this.hoverHandle === 'start' ? 0 : 1; } return this.currentHandle === 'start' ? 0 : 1; }; } /** * 滑块的值,为数组格式,将于表单数据一起提交。 * * **NOTE**:该属性无法通过 HTML 属性设置初始值,如果要修改该值,只能通过修改 JavaScript 属性值实现。 */ get value() { return this._value; } set value(_value) { const oldValue = [...this._value]; this._value = [this.fixValue(_value[0]), this.fixValue(_value[1])]; this.requestUpdate('value', oldValue); this.updateComplete.then(() => { this.updateStyle(); // reset 引起的值变更,不执行验证;直接修改值引起的变更,需要进行验证 const form = this.formController.getForm(); if (form && formResets.get(form)?.has(this)) { this.invalid = false; formResets.get(form).delete(this); } else { this.invalid = !this.inputRef.value.checkValidity(); } }); } get rippleElement() { return [this.rippleStartRef.value, this.rippleEndRef.value]; } connectedCallback() { super.connectedCallback(); if (!this.value.length) { this.value = [this.min, this.max]; } this.value[0] = this.fixValue(this.value[0]); this.value[1] = this.fixValue(this.value[1]); if (!this.defaultValue.length) { this.defaultValue = [...this.value]; } } firstUpdated(changedProperties) { super.firstUpdated(changedProperties); // 在轨道上点击时,计算出点击位置在 <input type="range"> 元素上的值 // 若该值在 this.value 的两个值中间位置的左侧,则表示操作的是左侧的值,否则操作的是右侧的值 const getCurrentHandle = (event) => { const $this = $(this); // 计算出鼠标悬浮位置的值,<mdui-range-slider> 元素的左右两侧有内边距,计算时要去除内边距 const paddingLeft = parseFloat($this.css('padding-left')); const paddingRight = parseFloat($this.css('padding-right')); const percent = (event.offsetX - paddingLeft) / (this.clientWidth - paddingLeft - paddingRight); const pointerValue = (this.max - this.min) * percent + this.min; // 计算 this.value 两个值中间位置的值 const middleValue = (this.value[1] - this.value[0]) / 2 + this.value[0]; return pointerValue > middleValue ? 'end' : 'start'; }; const onTouchStart = () => { if (!this.disabled) { this.labelVisible = true; } }; const onTouchEnd = () => { if (!this.disabled) { this.labelVisible = false; } }; this.addEventListener('touchstart', onTouchStart); this.addEventListener('mousedown', onTouchStart); this.addEventListener('touchend', onTouchEnd); this.addEventListener('mouseup', onTouchEnd); // 按下鼠标时,计算当前操作的是起始值还是结束值 this.addEventListener('pointerdown', (event) => { this.currentHandle = getCurrentHandle(event); }); // 移动鼠标时,修改 mdui-ripple 的 hover 状态 this.addEventListener('pointermove', (event) => { const currentHandle = getCurrentHandle(event); if (this.hoverHandle !== currentHandle) { this.endHover(event); this.hoverHandle = currentHandle; this.startHover(event); } }); this.updateStyle(); } /** * <input /> 用于提供拖拽操作 * <input class="invalid" /> 用于提供 html5 自带的表单错误提示 */ render() { return html `<label class="${classMap({ invalid: this.invalid })}"><input ${ref(this.inputRef)} type="range" step="${this.step}" min="${this.min}" max="${this.max}" ?disabled="${this.disabled}" @input="${this.onInput}" @change="${this.onChange}"><div part="track-inactive" class="track-inactive"></div><div ${ref(this.trackActiveRef)} part="track-active" class="track-active"></div><div ${ref(this.handleStartRef)} part="handle" class="handle start" style="${styleMap({ 'z-index': this.currentHandle === 'start' ? '2' : '1', })}"><div class="elevation"></div><mdui-ripple ${ref(this.rippleStartRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.renderLabel(this.value[0])}</div><div ${ref(this.handleEndRef)} part="handle" class="handle end" style="${styleMap({ 'z-index': this.currentHandle === 'end' ? '2' : '1', })}"><div class="elevation"></div><mdui-ripple ${ref(this.rippleEndRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.renderLabel(this.value[1])}</div>${when(this.tickmarks, () => map(this.getCandidateValues(), (value) => html `<div part="tickmark" class="tickmark ${classMap({ active: value > this.value[0] && value < this.value[1], })}" style="${styleMap({ left: `${((value - this.min) / this.max) * 100}%`, display: value === this.value[0] || value === this.value[1] ? 'none' : 'block', })}"></div>`))}</label>`; } updateStyle() { const getPercent = (value) => ((value - this.min) / (this.max - this.min)) * 100; const startPercent = getPercent(this.value[0]); const endPercent = getPercent(this.value[1]); this.trackActiveRef.value.style.width = `${endPercent - startPercent}%`; this.trackActiveRef.value.style.left = `${startPercent}%`; this.handleStartRef.value.style.left = `${startPercent}%`; this.handleEndRef.value.style.left = `${endPercent}%`; } onInput() { const isStart = this.currentHandle === 'start'; const value = parseFloat(this.inputRef.value.value); const startValue = this.value[0]; const endValue = this.value[1]; const doInput = () => { this.updateStyle(); }; if (isStart) { if (value <= endValue) { this.value = [value, endValue]; doInput(); } else if (startValue !== endValue) { this.value = [endValue, endValue]; doInput(); } } else { if (value >= startValue) { this.value = [startValue, value]; doInput(); } else if (startValue !== endValue) { this.value = [startValue, startValue]; doInput(); } } } }; RangeSlider.styles = [SliderBase.styles]; __decorate([ defaultValue() ], RangeSlider.prototype, "defaultValue", void 0); __decorate([ state() ], RangeSlider.prototype, "currentHandle", void 0); __decorate([ property({ type: Array, attribute: false }) ], RangeSlider.prototype, "value", null); RangeSlider = __decorate([ customElement('mdui-range-slider') ], RangeSlider); export { RangeSlider };