-
Notifications
You must be signed in to change notification settings - Fork 5
/
RippleMixin.js
127 lines (120 loc) · 3.46 KB
/
RippleMixin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import Ripple from '../components/Ripple.js';
/** @typedef {import('../components/Ripple.js').default} Ripple */
/**
* @param {ReturnType<import('./StateMixin.js').default>} Base
*/
export default function RippleMixin(Base) {
return Base
.set({
/** @type {WeakRef<InstanceType<Ripple>>} */
_lastRippleWeakRef: null,
/** Flag set if ripple was added this event loop. */
_rippleAdded: false,
})
.define({
_lastRipple: {
get() {
const element = this._lastRippleWeakRef?.deref();
if (element?.isConnected) return element;
return null;
},
set(ripple) {
this._lastRippleWeakRef = ripple ? new WeakRef(ripple) : null;
},
},
})
.methods({
/**
* @param {number} [x]
* @param {number} [y]
* @param {boolean} [hold]
* @return {InstanceType<Ripple>}
*/
addRipple(x, y, hold) {
const { rippleContainer } = this.refs;
if (!rippleContainer.isConnected) return null; // Detached?
const ripple = new Ripple();
this._rippleAdded = true;
queueMicrotask(() => {
// Reset before next event loop;
this._rippleAdded = false;
});
rippleContainer.append(ripple);
if (hold) {
ripple.holdRipple = true;
}
ripple.updatePosition(x, y);
this._lastRipple = ripple;
return ripple;
},
})
.html`
<div id=ripple-container mdw-if={!disabledState} aria-hidden=true></div>
`
.events({
'~pointerdown'(event) {
if (event.button) return;
if (this.disabledState) return;
const { rippleContainer } = this.refs;
if (!rippleContainer.isConnected) return; // Detached?
const rect = rippleContainer.getBoundingClientRect();
const x = event.pageX - rect.left - window.pageXOffset;
const y = event.pageY - rect.top - window.pageYOffset;
const lastRipple = this._lastRipple;
if (lastRipple) {
lastRipple.holdRipple = false;
}
// Ripple from pointerdown
this.addRipple(x, y, true);
},
'~click'(e) {
if (this._rippleAdded) {
// Avoid double event
return;
}
if (e.pointerType || e.detail) return;
if (this.disabledState) return;
if (this.pressedState || this._keyReleased) return;
const lastRipple = this._lastRipple;
if (lastRipple) {
lastRipple.holdRipple = false;
}
// Ripple from programmatic click
this.addRipple();
},
})
.on({
pressedStateChanged(oldValue, pressed) {
const ripple = this._lastRipple;
if (!pressed) {
if (ripple) {
ripple.holdRipple = false;
}
return;
}
if (!ripple || ripple.hadRippleReleased) {
if (this._lastInteraction !== 'key') {
// Sometimes pointer events may be out of order
return;
}
// Ripple from pressed state
this.addRipple(null, null, true);
return;
}
if (ripple.hadRippleHeld) return;
ripple.holdRipple = true;
},
})
.css`
:host {
--mdw-state__pressed-opacity: 0;
}
#ripple-container {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
border-radius: inherit;
}
`;
}