UNPKG

@angular/material

Version:
545 lines (539 loc) 18.7 kB
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