UNPKG

@fluid-topics/ft-ripple

Version:

A custom Fluid Topics ripple component

275 lines (274 loc) 10.5 kB
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"; export 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); } } forceFocusUpdate() { var _a; if ((_a = this.target) === null || _a === void 0 ? void 0 : _a.matches(":focus-within")) { this.startFocus(); } else { this.endFocus(); } } 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);