@angular/material
Version:
Angular Material
545 lines (539 loc) • 18.7 kB
JavaScript
import { normalizePassiveListenerOptions, _getEventTarget, Platform } from '@angular/cdk/platform';
import * as i0 from '@angular/core';
import { Component, ChangeDetectionStrategy, ViewEncapsulation, InjectionToken, inject, ElementRef, NgZone, Injector, Directive, Input } from '@angular/core';
import { isFakeMousedownFromScreenReader, isFakeTouchstartFromScreenReader } from '@angular/cdk/a11y';
import { coerceElement } from '@angular/cdk/coercion';
import { _CdkPrivateStyleLoader } from '@angular/cdk/private';
import { _animationsDisabled } from './_animation-chunk.mjs';
var RippleState;
(function (RippleState) {
RippleState[RippleState["FADING_IN"] = 0] = "FADING_IN";
RippleState[RippleState["VISIBLE"] = 1] = "VISIBLE";
RippleState[RippleState["FADING_OUT"] = 2] = "FADING_OUT";
RippleState[RippleState["HIDDEN"] = 3] = "HIDDEN";
})(RippleState || (RippleState = {}));
class RippleRef {
_renderer;
element;
config;
_animationForciblyDisabledThroughCss;
state = RippleState.HIDDEN;
constructor(_renderer, element, config, _animationForciblyDisabledThroughCss = false) {
this._renderer = _renderer;
this.element = element;
this.config = config;
this._animationForciblyDisabledThroughCss = _animationForciblyDisabledThroughCss;
}
fadeOut() {
this._renderer.fadeOutRipple(this);
}
}
const passiveCapturingEventOptions$1 = normalizePassiveListenerOptions({
passive: true,
capture: true
});
class RippleEventManager {
_events = new Map();
addHandler(ngZone, name, element, handler) {
const handlersForEvent = this._events.get(name);
if (handlersForEvent) {
const handlersForElement = handlersForEvent.get(element);
if (handlersForElement) {
handlersForElement.add(handler);
} else {
handlersForEvent.set(element, new Set([handler]));
}
} else {
this._events.set(name, new Map([[element, new Set([handler])]]));
ngZone.runOutsideAngular(() => {
document.addEventListener(name, this._delegateEventHandler, passiveCapturingEventOptions$1);
});
}
}
removeHandler(name, element, handler) {
const handlersForEvent = this._events.get(name);
if (!handlersForEvent) {
return;
}
const handlersForElement = handlersForEvent.get(element);
if (!handlersForElement) {
return;
}
handlersForElement.delete(handler);
if (handlersForElement.size === 0) {
handlersForEvent.delete(element);
}
if (handlersForEvent.size === 0) {
this._events.delete(name);
document.removeEventListener(name, this._delegateEventHandler, passiveCapturingEventOptions$1);
}
}
_delegateEventHandler = event => {
const target = _getEventTarget(event);
if (target) {
this._events.get(event.type)?.forEach((handlers, element) => {
if (element === target || element.contains(target)) {
handlers.forEach(handler => handler.handleEvent(event));
}
});
}
};
}
const defaultRippleAnimationConfig = {
enterDuration: 225,
exitDuration: 150
};
const ignoreMouseEventsTimeout = 800;
const passiveCapturingEventOptions = normalizePassiveListenerOptions({
passive: true,
capture: true
});
const pointerDownEvents = ['mousedown', 'touchstart'];
const pointerUpEvents = ['mouseup', 'mouseleave', 'touchend', 'touchcancel'];
class _MatRippleStylesLoader {
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.0",
ngImport: i0,
type: _MatRippleStylesLoader,
deps: [],
target: i0.ɵɵFactoryTarget.Component
});
static ɵcmp = i0.ɵɵngDeclareComponent({
minVersion: "14.0.0",
version: "21.0.0",
type: _MatRippleStylesLoader,
isStandalone: true,
selector: "ng-component",
host: {
attributes: {
"mat-ripple-style-loader": ""
}
},
ngImport: i0,
template: '',
isInline: true,
styles: [".mat-ripple{overflow:hidden;position:relative}.mat-ripple:not(:empty){transform:translateZ(0)}.mat-ripple.mat-ripple-unbounded{overflow:visible}.mat-ripple-element{position:absolute;border-radius:50%;pointer-events:none;transition:opacity,transform 0ms cubic-bezier(0, 0, 0.2, 1);transform:scale3d(0, 0, 0);background-color:var(--mat-ripple-color, color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent))}@media(forced-colors: active){.mat-ripple-element{display:none}}.cdk-drag-preview .mat-ripple-element,.cdk-drag-placeholder .mat-ripple-element{display:none}\n"],
changeDetection: i0.ChangeDetectionStrategy.OnPush,
encapsulation: i0.ViewEncapsulation.None
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.0",
ngImport: i0,
type: _MatRippleStylesLoader,
decorators: [{
type: Component,
args: [{
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'mat-ripple-style-loader': ''
},
styles: [".mat-ripple{overflow:hidden;position:relative}.mat-ripple:not(:empty){transform:translateZ(0)}.mat-ripple.mat-ripple-unbounded{overflow:visible}.mat-ripple-element{position:absolute;border-radius:50%;pointer-events:none;transition:opacity,transform 0ms cubic-bezier(0, 0, 0.2, 1);transform:scale3d(0, 0, 0);background-color:var(--mat-ripple-color, color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent))}@media(forced-colors: active){.mat-ripple-element{display:none}}.cdk-drag-preview .mat-ripple-element,.cdk-drag-placeholder .mat-ripple-element{display:none}\n"]
}]
}]
});
class RippleRenderer {
_target;
_ngZone;
_platform;
_containerElement;
_triggerElement;
_isPointerDown = false;
_activeRipples = new Map();
_mostRecentTransientRipple;
_lastTouchStartEvent;
_pointerUpEventsRegistered = false;
_containerRect;
static _eventManager = new RippleEventManager();
constructor(_target, _ngZone, elementOrElementRef, _platform, injector) {
this._target = _target;
this._ngZone = _ngZone;
this._platform = _platform;
if (_platform.isBrowser) {
this._containerElement = coerceElement(elementOrElementRef);
}
if (injector) {
injector.get(_CdkPrivateStyleLoader).load(_MatRippleStylesLoader);
}
}
fadeInRipple(x, y, config = {}) {
const containerRect = this._containerRect = this._containerRect || this._containerElement.getBoundingClientRect();
const animationConfig = {
...defaultRippleAnimationConfig,
...config.animation
};
if (config.centered) {
x = containerRect.left + containerRect.width / 2;
y = containerRect.top + containerRect.height / 2;
}
const radius = config.radius || distanceToFurthestCorner(x, y, containerRect);
const offsetX = x - containerRect.left;
const offsetY = y - containerRect.top;
const enterDuration = animationConfig.enterDuration;
const ripple = document.createElement('div');
ripple.classList.add('mat-ripple-element');
ripple.style.left = `${offsetX - radius}px`;
ripple.style.top = `${offsetY - radius}px`;
ripple.style.height = `${radius * 2}px`;
ripple.style.width = `${radius * 2}px`;
if (config.color != null) {
ripple.style.backgroundColor = config.color;
}
ripple.style.transitionDuration = `${enterDuration}ms`;
this._containerElement.appendChild(ripple);
const computedStyles = window.getComputedStyle(ripple);
const userTransitionProperty = computedStyles.transitionProperty;
const userTransitionDuration = computedStyles.transitionDuration;
const animationForciblyDisabledThroughCss = userTransitionProperty === 'none' || userTransitionDuration === '0s' || userTransitionDuration === '0s, 0s' || containerRect.width === 0 && containerRect.height === 0;
const rippleRef = new RippleRef(this, ripple, config, animationForciblyDisabledThroughCss);
ripple.style.transform = 'scale3d(1, 1, 1)';
rippleRef.state = RippleState.FADING_IN;
if (!config.persistent) {
this._mostRecentTransientRipple = rippleRef;
}
let eventListeners = null;
if (!animationForciblyDisabledThroughCss && (enterDuration || animationConfig.exitDuration)) {
this._ngZone.runOutsideAngular(() => {
const onTransitionEnd = () => {
if (eventListeners) {
eventListeners.fallbackTimer = null;
}
clearTimeout(fallbackTimer);
this._finishRippleTransition(rippleRef);
};
const onTransitionCancel = () => this._destroyRipple(rippleRef);
const fallbackTimer = setTimeout(onTransitionCancel, enterDuration + 100);
ripple.addEventListener('transitionend', onTransitionEnd);
ripple.addEventListener('transitioncancel', onTransitionCancel);
eventListeners = {
onTransitionEnd,
onTransitionCancel,
fallbackTimer
};
});
}
this._activeRipples.set(rippleRef, eventListeners);
if (animationForciblyDisabledThroughCss || !enterDuration) {
this._finishRippleTransition(rippleRef);
}
return rippleRef;
}
fadeOutRipple(rippleRef) {
if (rippleRef.state === RippleState.FADING_OUT || rippleRef.state === RippleState.HIDDEN) {
return;
}
const rippleEl = rippleRef.element;
const animationConfig = {
...defaultRippleAnimationConfig,
...rippleRef.config.animation
};
rippleEl.style.transitionDuration = `${animationConfig.exitDuration}ms`;
rippleEl.style.opacity = '0';
rippleRef.state = RippleState.FADING_OUT;
if (rippleRef._animationForciblyDisabledThroughCss || !animationConfig.exitDuration) {
this._finishRippleTransition(rippleRef);
}
}
fadeOutAll() {
this._getActiveRipples().forEach(ripple => ripple.fadeOut());
}
fadeOutAllNonPersistent() {
this._getActiveRipples().forEach(ripple => {
if (!ripple.config.persistent) {
ripple.fadeOut();
}
});
}
setupTriggerEvents(elementOrElementRef) {
const element = coerceElement(elementOrElementRef);
if (!this._platform.isBrowser || !element || element === this._triggerElement) {
return;
}
this._removeTriggerEvents();
this._triggerElement = element;
pointerDownEvents.forEach(type => {
RippleRenderer._eventManager.addHandler(this._ngZone, type, element, this);
});
}
handleEvent(event) {
if (event.type === 'mousedown') {
this._onMousedown(event);
} else if (event.type === 'touchstart') {
this._onTouchStart(event);
} else {
this._onPointerUp();
}
if (!this._pointerUpEventsRegistered) {
this._ngZone.runOutsideAngular(() => {
pointerUpEvents.forEach(type => {
this._triggerElement.addEventListener(type, this, passiveCapturingEventOptions);
});
});
this._pointerUpEventsRegistered = true;
}
}
_finishRippleTransition(rippleRef) {
if (rippleRef.state === RippleState.FADING_IN) {
this._startFadeOutTransition(rippleRef);
} else if (rippleRef.state === RippleState.FADING_OUT) {
this._destroyRipple(rippleRef);
}
}
_startFadeOutTransition(rippleRef) {
const isMostRecentTransientRipple = rippleRef === this._mostRecentTransientRipple;
const {
persistent
} = rippleRef.config;
rippleRef.state = RippleState.VISIBLE;
if (!persistent && (!isMostRecentTransientRipple || !this._isPointerDown)) {
rippleRef.fadeOut();
}
}
_destroyRipple(rippleRef) {
const eventListeners = this._activeRipples.get(rippleRef) ?? null;
this._activeRipples.delete(rippleRef);
if (!this._activeRipples.size) {
this._containerRect = null;
}
if (rippleRef === this._mostRecentTransientRipple) {
this._mostRecentTransientRipple = null;
}
rippleRef.state = RippleState.HIDDEN;
if (eventListeners !== null) {
rippleRef.element.removeEventListener('transitionend', eventListeners.onTransitionEnd);
rippleRef.element.removeEventListener('transitioncancel', eventListeners.onTransitionCancel);
if (eventListeners.fallbackTimer !== null) {
clearTimeout(eventListeners.fallbackTimer);
}
}
rippleRef.element.remove();
}
_onMousedown(event) {
const isFakeMousedown = isFakeMousedownFromScreenReader(event);
const isSyntheticEvent = this._lastTouchStartEvent && Date.now() < this._lastTouchStartEvent + ignoreMouseEventsTimeout;
if (!this._target.rippleDisabled && !isFakeMousedown && !isSyntheticEvent) {
this._isPointerDown = true;
this.fadeInRipple(event.clientX, event.clientY, this._target.rippleConfig);
}
}
_onTouchStart(event) {
if (!this._target.rippleDisabled && !isFakeTouchstartFromScreenReader(event)) {
this._lastTouchStartEvent = Date.now();
this._isPointerDown = true;
const touches = event.changedTouches;
if (touches) {
for (let i = 0; i < touches.length; i++) {
this.fadeInRipple(touches[i].clientX, touches[i].clientY, this._target.rippleConfig);
}
}
}
}
_onPointerUp() {
if (!this._isPointerDown) {
return;
}
this._isPointerDown = false;
this._getActiveRipples().forEach(ripple => {
const isVisible = ripple.state === RippleState.VISIBLE || ripple.config.terminateOnPointerUp && ripple.state === RippleState.FADING_IN;
if (!ripple.config.persistent && isVisible) {
ripple.fadeOut();
}
});
}
_getActiveRipples() {
return Array.from(this._activeRipples.keys());
}
_removeTriggerEvents() {
const trigger = this._triggerElement;
if (trigger) {
pointerDownEvents.forEach(type => RippleRenderer._eventManager.removeHandler(type, trigger, this));
if (this._pointerUpEventsRegistered) {
pointerUpEvents.forEach(type => trigger.removeEventListener(type, this, passiveCapturingEventOptions));
this._pointerUpEventsRegistered = false;
}
}
}
}
function distanceToFurthestCorner(x, y, rect) {
const distX = Math.max(Math.abs(x - rect.left), Math.abs(x - rect.right));
const distY = Math.max(Math.abs(y - rect.top), Math.abs(y - rect.bottom));
return Math.sqrt(distX * distX + distY * distY);
}
const MAT_RIPPLE_GLOBAL_OPTIONS = new InjectionToken('mat-ripple-global-options');
class MatRipple {
_elementRef = inject(ElementRef);
_animationsDisabled = _animationsDisabled();
color;
unbounded;
centered;
radius = 0;
animation;
get disabled() {
return this._disabled;
}
set disabled(value) {
if (value) {
this.fadeOutAllNonPersistent();
}
this._disabled = value;
this._setupTriggerEventsIfEnabled();
}
_disabled = false;
get trigger() {
return this._trigger || this._elementRef.nativeElement;
}
set trigger(trigger) {
this._trigger = trigger;
this._setupTriggerEventsIfEnabled();
}
_trigger;
_rippleRenderer;
_globalOptions;
_isInitialized = false;
constructor() {
const ngZone = inject(NgZone);
const platform = inject(Platform);
const globalOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, {
optional: true
});
const injector = inject(Injector);
this._globalOptions = globalOptions || {};
this._rippleRenderer = new RippleRenderer(this, ngZone, this._elementRef, platform, injector);
}
ngOnInit() {
this._isInitialized = true;
this._setupTriggerEventsIfEnabled();
}
ngOnDestroy() {
this._rippleRenderer._removeTriggerEvents();
}
fadeOutAll() {
this._rippleRenderer.fadeOutAll();
}
fadeOutAllNonPersistent() {
this._rippleRenderer.fadeOutAllNonPersistent();
}
get rippleConfig() {
return {
centered: this.centered,
radius: this.radius,
color: this.color,
animation: {
...this._globalOptions.animation,
...(this._animationsDisabled ? {
enterDuration: 0,
exitDuration: 0
} : {}),
...this.animation
},
terminateOnPointerUp: this._globalOptions.terminateOnPointerUp
};
}
get rippleDisabled() {
return this.disabled || !!this._globalOptions.disabled;
}
_setupTriggerEventsIfEnabled() {
if (!this.disabled && this._isInitialized) {
this._rippleRenderer.setupTriggerEvents(this.trigger);
}
}
launch(configOrX, y = 0, config) {
if (typeof configOrX === 'number') {
return this._rippleRenderer.fadeInRipple(configOrX, y, {
...this.rippleConfig,
...config
});
} else {
return this._rippleRenderer.fadeInRipple(0, 0, {
...this.rippleConfig,
...configOrX
});
}
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.0",
ngImport: i0,
type: MatRipple,
deps: [],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.0",
type: MatRipple,
isStandalone: true,
selector: "[mat-ripple], [matRipple]",
inputs: {
color: ["matRippleColor", "color"],
unbounded: ["matRippleUnbounded", "unbounded"],
centered: ["matRippleCentered", "centered"],
radius: ["matRippleRadius", "radius"],
animation: ["matRippleAnimation", "animation"],
disabled: ["matRippleDisabled", "disabled"],
trigger: ["matRippleTrigger", "trigger"]
},
host: {
properties: {
"class.mat-ripple-unbounded": "unbounded"
},
classAttribute: "mat-ripple"
},
exportAs: ["matRipple"],
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.0",
ngImport: i0,
type: MatRipple,
decorators: [{
type: Directive,
args: [{
selector: '[mat-ripple], [matRipple]',
exportAs: 'matRipple',
host: {
'class': 'mat-ripple',
'[class.mat-ripple-unbounded]': 'unbounded'
}
}]
}],
ctorParameters: () => [],
propDecorators: {
color: [{
type: Input,
args: ['matRippleColor']
}],
unbounded: [{
type: Input,
args: ['matRippleUnbounded']
}],
centered: [{
type: Input,
args: ['matRippleCentered']
}],
radius: [{
type: Input,
args: ['matRippleRadius']
}],
animation: [{
type: Input,
args: ['matRippleAnimation']
}],
disabled: [{
type: Input,
args: ['matRippleDisabled']
}],
trigger: [{
type: Input,
args: ['matRippleTrigger']
}]
}
});
export { MAT_RIPPLE_GLOBAL_OPTIONS, MatRipple, RippleRef, RippleRenderer, RippleState, defaultRippleAnimationConfig };
//# sourceMappingURL=_ripple-chunk.mjs.map