@fluid-topics/ft-ripple
Version:
A custom Fluid Topics ripple component
267 lines (266 loc) • 10.3 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { html, } from "lit";
import { property, query, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { Debouncer, FtLitElement } from "@fluid-topics/ft-wc-utils";
import { styles } from "./ft-ripple.styles";
class FtRipple extends FtLitElement {
constructor() {
super(...arguments);
this.primary = false;
this.secondary = false;
this.unbounded = false;
this.activated = false;
this.selected = false;
this.disabled = false;
this.hovered = false;
this.focused = false;
this.pressed = false;
this.rippling = false;
this.originX = 0;
this.originY = 0;
this.debouncer = new Debouncer(1000);
this.onTransitionStart = (e) => {
if (e.propertyName === "transform") {
this.rippling = this.pressed;
this.debouncer.run(() => this.rippling = false);
}
};
this.onTransitionEnd = (e) => {
if (e.propertyName === "transform") {
this.rippling = false;
}
};
this.setupDebouncer = new Debouncer(10);
this.moveRipple = (e) => {
var _a, _b;
let { x, y } = this.getCoordinates(e);
let rect = (_b = (_a = this.ripple) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) !== null && _b !== void 0 ? _b : { x: 0, y: 0, width: 0, height: 0 };
this.originX = Math.round(x != null ? x - rect.x : rect.width / 2);
this.originY = Math.round(y != null ? y - rect.y : rect.height / 2);
};
this.startPress = (e) => {
this.moveRipple(e);
this.pressed = !this.isIgnored(e);
};
this.endPress = () => {
this.pressed = false;
};
this.startHover = (e) => {
this.hovered = !this.isIgnored(e);
};
this.endHover = () => {
this.hovered = false;
};
this.startFocus = (e) => {
this.focused = this.isFocusVisible(e === null || e === void 0 ? void 0 : e.target) && !this.isIgnored(e);
};
this.endFocus = () => {
this.focused = false;
};
}
render() {
let classes = {
"ft-ripple": true,
"ft-ripple--primary": this.primary,
"ft-ripple--secondary": this.secondary,
"ft-ripple--unbounded": this.unbounded,
"ft-ripple--selected": (this.selected || this.activated) && !this.disabled,
"ft-ripple--pressed": (this.pressed || this.rippling) && !this.disabled,
"ft-ripple--hovered": this.hovered && !this.disabled,
"ft-ripple--focused": this.focused && !this.disabled,
};
return html `
<style>
.ft-ripple .ft-ripple--effect {
left: ${this.originX}px;
top: ${this.originY}px;
}
</style>
<div class="${classMap(classes)}">
<div class="ft-ripple--background"></div>
<div class="ft-ripple--effect"></div>
</div>
`;
}
contentAvailableCallback(props) {
super.contentAvailableCallback(props);
if (this.rippleEffect && this.rippleEffect.ontransitionstart !== this.onTransitionStart) {
this.rippleEffect.ontransitionstart = this.onTransitionStart;
this.rippleEffect.ontransitionend = this.onTransitionEnd;
}
}
update(props) {
var _a, _b;
super.update(props);
if (props.has("disabled")) {
if (this.disabled) {
this.endRipple();
(_a = this.target) === null || _a === void 0 ? void 0 : _a.removeAttribute("data-is-ft-ripple-target");
}
else {
(_b = this.target) === null || _b === void 0 ? void 0 : _b.setAttribute("data-is-ft-ripple-target", "true");
}
}
}
endRipple() {
this.endHover();
this.endFocus();
this.endPress();
this.rippling = false;
}
connectedCallback() {
super.connectedCallback();
this.setupDebouncer.run(() => this.defaultSetup());
}
defaultSetup() {
var _a, _b;
const parent = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.host.parentElement;
if (parent) {
this.setupFor((_b = this.target) !== null && _b !== void 0 ? _b : parent);
}
}
setupFor(target) {
this.setupDebouncer.cancel();
if (this.target === target) {
return;
}
this.onDisconnect && this.onDisconnect();
this.target = target;
target.setAttribute("data-is-ft-ripple-target", "true");
const startPress = (...endEvents) => {
return (startEvent) => {
endEvents.forEach(endEvent => window.addEventListener(endEvent, this.endPress, { once: true }));
this.startPress(startEvent);
};
};
const mouseDownHandler = startPress("mouseup", "contextmenu");
const touchStartHandler = startPress("touchend", "touchcancel");
const keyDownHandler = (e) => {
if (["Enter", " "].includes(e.key)) {
startPress("keyup")(e);
}
};
const options = { passive: true };
target.addEventListener("mouseover", this.startHover, options);
target.addEventListener("mousemove", this.moveRipple, options);
target.addEventListener("mouseleave", this.endHover, options);
target.addEventListener("mousedown", mouseDownHandler, options);
target.addEventListener("touchstart", touchStartHandler, options);
target.addEventListener("touchmove", this.moveRipple, options);
target.addEventListener("keydown", keyDownHandler, options);
target.addEventListener("focus", this.startFocus, options);
target.addEventListener("blur", this.endFocus, options);
target.addEventListener("focusin", this.startFocus, options);
target.addEventListener("focusout", this.endFocus, options);
this.onDisconnect = () => {
target.removeAttribute("data-is-ft-ripple-target");
target.removeEventListener("mouseover", this.startHover, options);
target.removeEventListener("mousemove", this.moveRipple, options);
target.removeEventListener("mouseleave", this.endHover, options);
target.removeEventListener("mousedown", mouseDownHandler, options);
target.removeEventListener("touchstart", touchStartHandler, options);
target.removeEventListener("touchmove", this.moveRipple, options);
target.removeEventListener("keydown", keyDownHandler, options);
target.removeEventListener("focus", this.startFocus, options);
target.removeEventListener("blur", this.endFocus, options);
target.removeEventListener("focusin", this.startFocus, options);
target.removeEventListener("focusout", this.endFocus, options);
this.onDisconnect = undefined;
this.target = undefined;
};
}
getCoordinates(e) {
const mouseEvent = e;
const touchEvent = e;
let x, y;
if (mouseEvent.x != null) {
({ x, y } = mouseEvent);
}
else if (touchEvent.touches != null) {
x = touchEvent.touches[0].clientX;
y = touchEvent.touches[0].clientY;
}
return { x, y };
}
isFocusVisible(target) {
if (target instanceof HTMLElement) {
return target.matches(":focus-visible");
}
else {
return true;
}
}
isIgnored(e) {
if (this.disabled) {
return true;
}
if (e != null) {
for (let t of e.composedPath()) {
if (t === this.target) {
break;
}
const anotherRippleExists = "hasAttribute" in t && t.hasAttribute("data-is-ft-ripple-target");
if (anotherRippleExists) {
return true;
}
}
}
return false;
}
disconnectedCallback() {
super.disconnectedCallback();
this.onDisconnect && this.onDisconnect();
this.endRipple();
}
}
FtRipple.elementDefinitions = {};
FtRipple.styles = styles;
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "primary", void 0);
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "secondary", void 0);
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "unbounded", void 0);
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "activated", void 0);
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "selected", void 0);
__decorate([
property({ type: Boolean })
], FtRipple.prototype, "disabled", void 0);
__decorate([
state()
], FtRipple.prototype, "hovered", void 0);
__decorate([
state()
], FtRipple.prototype, "focused", void 0);
__decorate([
state()
], FtRipple.prototype, "pressed", void 0);
__decorate([
state()
], FtRipple.prototype, "rippling", void 0);
__decorate([
state()
], FtRipple.prototype, "originX", void 0);
__decorate([
state()
], FtRipple.prototype, "originY", void 0);
__decorate([
query(".ft-ripple")
], FtRipple.prototype, "ripple", void 0);
__decorate([
query(".ft-ripple--effect")
], FtRipple.prototype, "rippleEffect", void 0);
export { FtRipple };