UNPKG

mdui

Version:

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

258 lines (257 loc) 10.9 kB
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; };