mdui
Version:
实现 material you 设计规范的 Web Components 组件库
258 lines (257 loc) • 10.9 kB
JavaScript
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;
};