fix: 本地 patch MDUI 以解决 tabindex = 0 导致的一系列玄学问题
This commit is contained in:
72
client/mdui_patched/components/range-slider/index.d.ts
vendored
Normal file
72
client/mdui_patched/components/range-slider/index.d.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import '@mdui/jq/methods/css.js';
|
||||
import { SliderBase } from '../slider/slider-base.js';
|
||||
import type { Ripple } from '../ripple/index.js';
|
||||
import type { FormControl } from '@mdui/jq/shared/form.js';
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
|
||||
/**
|
||||
* @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 - 刻度标记
|
||||
*/
|
||||
export declare class RangeSlider extends SliderBase<RangeSliderEventMap> implements FormControl {
|
||||
static styles: CSSResultGroup;
|
||||
/**
|
||||
* 默认值。在重置表单时,将重置为该默认值。此属性只能通过 JavaScript 属性设置
|
||||
*/
|
||||
defaultValue: number[];
|
||||
/**
|
||||
* 当前操作的是哪一个 handle
|
||||
*/
|
||||
private currentHandle;
|
||||
private hoverHandle?;
|
||||
private readonly rippleStartRef;
|
||||
private readonly rippleEndRef;
|
||||
private readonly handleStartRef;
|
||||
private readonly handleEndRef;
|
||||
private readonly formController;
|
||||
private _value;
|
||||
/**
|
||||
* 滑块的值,为数组格式,将于表单数据一起提交。
|
||||
*
|
||||
* **NOTE**:该属性无法通过 HTML 属性设置初始值,如果要修改该值,只能通过修改 JavaScript 属性值实现。
|
||||
*/
|
||||
get value(): number[];
|
||||
set value(_value: number[]);
|
||||
protected get rippleElement(): Ripple[];
|
||||
connectedCallback(): void;
|
||||
protected firstUpdated(changedProperties: PropertyValues): void;
|
||||
/**
|
||||
* <input /> 用于提供拖拽操作
|
||||
* <input class="invalid" /> 用于提供 html5 自带的表单错误提示
|
||||
*/
|
||||
protected render(): TemplateResult;
|
||||
protected getRippleIndex: () => 0 | 1;
|
||||
private updateStyle;
|
||||
private onInput;
|
||||
}
|
||||
export interface RangeSliderEventMap {
|
||||
focus: FocusEvent;
|
||||
blur: FocusEvent;
|
||||
change: CustomEvent<void>;
|
||||
input: Event;
|
||||
invalid: CustomEvent<void>;
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'mdui-range-slider': RangeSlider;
|
||||
}
|
||||
}
|
||||
211
client/mdui_patched/components/range-slider/index.js
Normal file
211
client/mdui_patched/components/range-slider/index.js
Normal file
@@ -0,0 +1,211 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user