Files
LingChair/mdui_patched/components/ripple/ripple-mixin.js
CrescentLeaf 1cb8ac3fff 移动目录
2025-11-23 13:27:15 +08:00

258 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { __decorate } from "tslib";
import { property } from 'lit/decorators.js';
import { $ } from '@mdui/jq/$.js';
import '@mdui/jq/methods/index.js';
import { isArrayLike } from '@mdui/jq/shared/helper.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import './index.js';
/**
* hover, pressed, dragged 三个属性用于添加到 rippleTarget 属性指定的元素上,供 CSS 选择题添加样式
*
* TODO: dragged 功能
*
* NOTE:
* 不支持触控的屏幕上事件顺序为pointerdown -> (8ms) -> mousedown -> pointerup -> (1ms) -> mouseup -> (1ms) -> click
*
* 支持触控的屏幕上事件顺序为pointerdown -> (8ms) -> touchstart -> pointerup -> (1ms) -> touchend -> (5ms) -> mousedown -> mouseup -> click
* pointermove 比较灵敏,有可能触发了 pointermove 但没有触发 touchmove
*
* 支持触摸笔的屏幕上事件顺序为todo
*/
export const RippleMixin = (superclass) => {
class Mixin extends superclass {
constructor() {
super(...arguments);
/**
* 是否禁用涟漪动画
*/
this.noRipple = false;
/**
* 当前激活的是第几个 <mdui-ripple>。仅一个组件中有多个 <mdui-ripple> 时可以使用该属性
* 若值为 undefined则组件中所有 <mdui-ripple> 都激活
*/
this.rippleIndex = undefined;
/**
* 获取当前激活的是第几个 <mdui-ripple>。仅一个组件中有多个 <mdui-ripple> 时可以使用该属性
* 若值为 undefined则组件中所有 <mdui-ripple> 都激活
* 可在子类中手动指定该方法,指定需要激活的 ripple
*/
this.getRippleIndex = () => this.rippleIndex;
}
/**
* 子类要添加该属性,指向 <mdui-ripple> 元素
* 如果一个组件中包含多个 <mdui-ripple> 元素,则这里可以是一个数组或 NodeList
*/
get rippleElement() {
throw new Error('Must implement rippleElement getter!');
}
/**
* 子类要实现该属性,表示是否禁用 ripple
* 如果一个组件中包含多个 <mdui-ripple> 元素,则这里可以是一个数组;也可以是单个值,同时控制多个 <mdui-ripple> 元素
*/
get rippleDisabled() {
throw new Error('Must implement rippleDisabled getter!');
}
/**
* 当前 <mdui-ripple> 元素相对于哪个元素存在,即 hover、pressed、dragged 属性要添加到哪个元素上,默认为 :host
* 如果需要修改该属性,则子类可以实现该属性
* 如果一个组件中包含多个 <mdui-ripple> 元素,则这里可以是一个数组;也可以是单个值,同时控制多个 <mdui-ripple> 元素
*/
get rippleTarget() {
return this;
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
const $rippleTarget = $(this.rippleTarget);
// 监听到事件时,是在第几个 <mdui-ripple> 上触发的事件,记录到 this.rippleIndex 中
const setRippleIndex = (event) => {
if (isArrayLike(this.rippleTarget)) {
this.rippleIndex = $rippleTarget.index(event.target);
}
};
const rippleTargetArr = isArrayLike(this.rippleTarget)
? this.rippleTarget
: [this.rippleTarget];
rippleTargetArr.forEach((rippleTarget) => {
rippleTarget.addEventListener('pointerdown', (event) => {
setRippleIndex(event);
this.startPress(event);
});
rippleTarget.addEventListener('pointerenter', (event) => {
setRippleIndex(event);
this.startHover(event);
});
rippleTarget.addEventListener('pointerleave', (event) => {
setRippleIndex(event);
this.endHover(event);
});
rippleTarget.addEventListener('focus', (event) => {
setRippleIndex(event);
this.startFocus();
});
rippleTarget.addEventListener('blur', (event) => {
setRippleIndex(event);
this.endFocus();
});
});
}
/**
* 若存在多个 <mdui-ripple>,但 rippleTarget 为同一个,则 hover 状态无法在多个 <mdui-ripple> 之间切换
* 所以把 startHover 和 endHover 设置为 protected供子类调用
* 子类中,在 getRippleIndex() 的返回值变更前调用 endHover(event),变更后调用 startHover(event)
*/
startHover(event) {
if (event.pointerType !== 'mouse' || this.isRippleDisabled()) {
return;
}
this.getRippleTarget().setAttribute('hover', '');
this.getRippleElement().startHover();
}
endHover(event) {
if (event.pointerType !== 'mouse' || this.isRippleDisabled()) {
return;
}
this.getRippleTarget().removeAttribute('hover');
this.getRippleElement().endHover();
}
/**
* 当前激活的 <mdui-ripple> 元素是否被禁用
*/
isRippleDisabled() {
const disabled = this.rippleDisabled;
if (!Array.isArray(disabled)) {
return disabled;
}
const rippleIndex = this.getRippleIndex();
if (rippleIndex !== undefined) {
return disabled[rippleIndex];
}
return disabled.length ? disabled[0] : false;
}
/**
* 获取当前激活的 <mdui-ripple> 元素实例
*/
getRippleElement() {
const ripple = this.rippleElement;
if (!isArrayLike(ripple)) {
return ripple;
}
const rippleIndex = this.getRippleIndex();
if (rippleIndex !== undefined) {
return ripple[rippleIndex];
}
return ripple[0];
}
/**
* 获取当前激活的 <mdui-ripple> 元素相对于哪个元素存在
*/
getRippleTarget() {
const target = this.rippleTarget;
if (!isArrayLike(target)) {
return target;
}
const rippleIndex = this.getRippleIndex();
if (rippleIndex !== undefined) {
return target[rippleIndex];
}
return target[0];
}
startFocus() {
if (this.isRippleDisabled()) {
return;
}
this.getRippleElement().startFocus();
}
endFocus() {
if (this.isRippleDisabled()) {
return;
}
this.getRippleElement().endFocus();
}
startPress(event) {
// 为鼠标时操作,仅响应鼠标左键点击
if (this.isRippleDisabled() || event.button) {
return;
}
const target = this.getRippleTarget();
target.setAttribute('pressed', '');
// 手指触摸触发涟漪
if (['touch', 'pen'].includes(event.pointerType)) {
let hidden = false;
// 手指触摸后,延迟一段时间触发涟漪,避免手指滑动时也触发涟漪
let timer = setTimeout(() => {
timer = 0;
this.getRippleElement().startPress(event);
}, 70);
const hideRipple = () => {
// 如果手指没有移动,且涟漪动画还没有开始,则开始涟漪动画
if (timer) {
clearTimeout(timer);
timer = 0;
this.getRippleElement().startPress(event);
}
if (!hidden) {
hidden = true;
this.endPress();
}
target.removeEventListener('pointerup', hideRipple);
target.removeEventListener('pointercancel', hideRipple);
};
// 手指移动后,移除涟漪动画
const touchMove = () => {
if (timer) {
clearTimeout(timer);
timer = 0;
}
target.removeEventListener('touchmove', touchMove);
};
// pointermove 事件过于灵敏,可能在未触发 touchmove 的情况下,触发了 pointermove 事件,导致正常的点击操作没有显示涟漪
// 因此这里监听 touchmove 事件
target.addEventListener('touchmove', touchMove);
target.addEventListener('pointerup', hideRipple);
target.addEventListener('pointercancel', hideRipple);
}
// 鼠标点击触发涟漪,点击后立即触发涟漪(仅鼠标左键能触发涟漪)
if (event.pointerType === 'mouse' && event.button === 0) {
const hideRipple = () => {
this.endPress();
target.removeEventListener('pointerup', hideRipple);
target.removeEventListener('pointercancel', hideRipple);
target.removeEventListener('pointerleave', hideRipple);
};
this.getRippleElement().startPress(event);
target.addEventListener('pointerup', hideRipple);
target.addEventListener('pointercancel', hideRipple);
target.addEventListener('pointerleave', hideRipple);
}
}
endPress() {
if (this.isRippleDisabled()) {
return;
}
this.getRippleTarget().removeAttribute('pressed');
this.getRippleElement().endPress();
}
startDrag() {
if (this.isRippleDisabled()) {
return;
}
this.getRippleElement().startDrag();
}
endDrag() {
if (this.isRippleDisabled()) {
return;
}
this.getRippleElement().endDrag();
}
}
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'no-ripple',
})
], Mixin.prototype, "noRipple", void 0);
return Mixin;
};