UNPKG

@angular/material

Version:
640 lines (634 loc) 31.9 kB
import { normalizePassiveListenerOptions, _getEventTarget, Platform } from '@angular/cdk/platform'; import * as i0 from '@angular/core'; import { Component, ChangeDetectionStrategy, ViewEncapsulation, InjectionToken, inject, ElementRef, ANIMATION_MODULE_TYPE, 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'; /** Possible states for a ripple element. */ 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 = {})); /** * Reference to a previously launched ripple element. */ class RippleRef { _renderer; element; config; _animationForciblyDisabledThroughCss; /** Current state of the ripple. */ state = RippleState.HIDDEN; constructor(_renderer, /** Reference to the ripple HTML element. */ element, /** Ripple configuration used for the ripple. */ config, /* Whether animations are forcibly disabled for ripples through CSS. */ _animationForciblyDisabledThroughCss = false) { this._renderer = _renderer; this.element = element; this.config = config; this._animationForciblyDisabledThroughCss = _animationForciblyDisabledThroughCss; } /** Fades out the ripple element. */ fadeOut() { this._renderer.fadeOutRipple(this); } } /** Options used to bind a passive capturing event. */ const passiveCapturingEventOptions$1 = normalizePassiveListenerOptions({ passive: true, capture: true, }); /** Manages events through delegation so that as few event handlers as possible are bound. */ class RippleEventManager { _events = new Map(); /** Adds an event handler. */ 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); }); } } /** Removes an event handler. */ 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); } } /** Event handler that is bound and which dispatches the events to the different targets. */ _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)); } }); } }; } /** * Default ripple animation configuration for ripples without an explicit * animation config specified. */ const defaultRippleAnimationConfig = { enterDuration: 225, exitDuration: 150, }; /** * Timeout for ignoring mouse events. Mouse events will be temporary ignored after touch * events to avoid synthetic mouse events. */ const ignoreMouseEventsTimeout = 800; /** Options used to bind a passive capturing event. */ const passiveCapturingEventOptions = normalizePassiveListenerOptions({ passive: true, capture: true, }); /** Events that signal that the pointer is down. */ const pointerDownEvents = ['mousedown', 'touchstart']; /** Events that signal that the pointer is up. */ const pointerUpEvents = ['mouseup', 'mouseleave', 'touchend', 'touchcancel']; class _MatRippleStylesLoader { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: _MatRippleStylesLoader, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.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}"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.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}"] }] }] }); /** * Helper service that performs DOM manipulations. Not intended to be used outside this module. * The constructor takes a reference to the ripple directive's host element and a map of DOM * event handlers to be installed on the element that triggers ripple animations. * This will eventually become a custom renderer once Angular support exists. * @docs-private */ class RippleRenderer { _target; _ngZone; _platform; /** Element where the ripples are being added to. */ _containerElement; /** Element which triggers the ripple elements on mouse events. */ _triggerElement; /** Whether the pointer is currently down or not. */ _isPointerDown = false; /** * Map of currently active ripple references. * The ripple reference is mapped to its element event listeners. * The reason why `| null` is used is that event listeners are added only * when the condition is truthy (see the `_startFadeOutTransition` method). */ _activeRipples = new Map(); /** Latest non-persistent ripple that was triggered. */ _mostRecentTransientRipple; /** Time in milliseconds when the last touchstart event happened. */ _lastTouchStartEvent; /** Whether pointer-up event listeners have been registered. */ _pointerUpEventsRegistered = false; /** * Cached dimensions of the ripple container. Set when the first * ripple is shown and cleared once no more ripples are visible. */ _containerRect; static _eventManager = new RippleEventManager(); constructor(_target, _ngZone, elementOrElementRef, _platform, injector) { this._target = _target; this._ngZone = _ngZone; this._platform = _platform; // Only do anything if we're on the browser. if (_platform.isBrowser) { this._containerElement = coerceElement(elementOrElementRef); } if (injector) { injector.get(_CdkPrivateStyleLoader).load(_MatRippleStylesLoader); } } /** * Fades in a ripple at the given coordinates. * @param x Coordinate within the element, along the X axis at which to start the ripple. * @param y Coordinate within the element, along the Y axis at which to start the ripple. * @param config Extra ripple options. */ 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 a custom color has been specified, set it as inline style. If no color is // set, the default color will be applied through the ripple theme styles. if (config.color != null) { ripple.style.backgroundColor = config.color; } ripple.style.transitionDuration = `${enterDuration}ms`; this._containerElement.appendChild(ripple); // By default the browser does not recalculate the styles of dynamically created // ripple elements. This is critical to ensure that the `scale` animates properly. // We enforce a style recalculation by calling `getComputedStyle` and *accessing* a property. // See: https://gist.github.com/paulirish/5d52fb081b3570c81e3a const computedStyles = window.getComputedStyle(ripple); const userTransitionProperty = computedStyles.transitionProperty; const userTransitionDuration = computedStyles.transitionDuration; // Note: We detect whether animation is forcibly disabled through CSS (e.g. through // `transition: none` or `display: none`). This is technically unexpected since animations are // controlled through the animation config, but this exists for backwards compatibility. This // logic does not need to be super accurate since it covers some edge cases which can be easily // avoided by users. const animationForciblyDisabledThroughCss = userTransitionProperty === 'none' || // Note: The canonical unit for serialized CSS `<time>` properties is seconds. Additionally // some browsers expand the duration for every property (in our case `opacity` and `transform`). userTransitionDuration === '0s' || userTransitionDuration === '0s, 0s' || // If the container is 0x0, it's likely `display: none`. (containerRect.width === 0 && containerRect.height === 0); // Exposed reference to the ripple that will be returned. const rippleRef = new RippleRef(this, ripple, config, animationForciblyDisabledThroughCss); // Start the enter animation by setting the transform/scale to 100%. The animation will // execute as part of this statement because we forced a style recalculation before. // Note: We use a 3d transform here in order to avoid an issue in Safari where // the ripples aren't clipped when inside the shadow DOM (see #24028). ripple.style.transform = 'scale3d(1, 1, 1)'; rippleRef.state = RippleState.FADING_IN; if (!config.persistent) { this._mostRecentTransientRipple = rippleRef; } let eventListeners = null; // Do not register the `transition` event listener if fade-in and fade-out duration // are set to zero. The events won't fire anyway and we can save resources here. if (!animationForciblyDisabledThroughCss && (enterDuration || animationConfig.exitDuration)) { this._ngZone.runOutsideAngular(() => { const onTransitionEnd = () => { // Clear the fallback timer since the transition fired correctly. if (eventListeners) { eventListeners.fallbackTimer = null; } clearTimeout(fallbackTimer); this._finishRippleTransition(rippleRef); }; const onTransitionCancel = () => this._destroyRipple(rippleRef); // In some cases where there's a higher load on the browser, it can choose not to dispatch // neither `transitionend` nor `transitioncancel` (see b/227356674). This timer serves as a // fallback for such cases so that the ripple doesn't become stuck. We add a 100ms buffer // because timers aren't precise. Note that another approach can be to transition the ripple // to the `VISIBLE` state immediately above and to `FADING_IN` afterwards inside // `transitionstart`. We go with the timer because it's one less event listener and // it's less likely to break existing tests. const fallbackTimer = setTimeout(onTransitionCancel, enterDuration + 100); ripple.addEventListener('transitionend', onTransitionEnd); // If the transition is cancelled (e.g. due to DOM removal), we destroy the ripple // directly as otherwise we would keep it part of the ripple container forever. // https://www.w3.org/TR/css-transitions-1/#:~:text=no%20longer%20in%20the%20document. ripple.addEventListener('transitioncancel', onTransitionCancel); eventListeners = { onTransitionEnd, onTransitionCancel, fallbackTimer }; }); } // Add the ripple reference to the list of all active ripples. this._activeRipples.set(rippleRef, eventListeners); // In case there is no fade-in transition duration, we need to manually call the transition // end listener because `transitionend` doesn't fire if there is no transition. if (animationForciblyDisabledThroughCss || !enterDuration) { this._finishRippleTransition(rippleRef); } return rippleRef; } /** Fades out a ripple reference. */ fadeOutRipple(rippleRef) { // For ripples already fading out or hidden, this should be a noop. if (rippleRef.state === RippleState.FADING_OUT || rippleRef.state === RippleState.HIDDEN) { return; } const rippleEl = rippleRef.element; const animationConfig = { ...defaultRippleAnimationConfig, ...rippleRef.config.animation }; // This starts the fade-out transition and will fire the transition end listener that // removes the ripple element from the DOM. rippleEl.style.transitionDuration = `${animationConfig.exitDuration}ms`; rippleEl.style.opacity = '0'; rippleRef.state = RippleState.FADING_OUT; // In case there is no fade-out transition duration, we need to manually call the // transition end listener because `transitionend` doesn't fire if there is no transition. if (rippleRef._animationForciblyDisabledThroughCss || !animationConfig.exitDuration) { this._finishRippleTransition(rippleRef); } } /** Fades out all currently active ripples. */ fadeOutAll() { this._getActiveRipples().forEach(ripple => ripple.fadeOut()); } /** Fades out all currently active non-persistent ripples. */ fadeOutAllNonPersistent() { this._getActiveRipples().forEach(ripple => { if (!ripple.config.persistent) { ripple.fadeOut(); } }); } /** Sets up the trigger event listeners */ setupTriggerEvents(elementOrElementRef) { const element = coerceElement(elementOrElementRef); if (!this._platform.isBrowser || !element || element === this._triggerElement) { return; } // Remove all previously registered event listeners from the trigger element. this._removeTriggerEvents(); this._triggerElement = element; // Use event delegation for the trigger events since they're // set up during creation and are performance-sensitive. pointerDownEvents.forEach(type => { RippleRenderer._eventManager.addHandler(this._ngZone, type, element, this); }); } /** * Handles all registered events. * @docs-private */ handleEvent(event) { if (event.type === 'mousedown') { this._onMousedown(event); } else if (event.type === 'touchstart') { this._onTouchStart(event); } else { this._onPointerUp(); } // If pointer-up events haven't been registered yet, do so now. // We do this on-demand in order to reduce the total number of event listeners // registered by the ripples, which speeds up the rendering time for large UIs. if (!this._pointerUpEventsRegistered) { // The events for hiding the ripple are bound directly on the trigger, because: // 1. Some of them occur frequently (e.g. `mouseleave`) and any advantage we get from // delegation will be diminished by having to look through all the data structures often. // 2. They aren't as performance-sensitive, because they're bound only after the user // has interacted with an element. this._ngZone.runOutsideAngular(() => { pointerUpEvents.forEach(type => { this._triggerElement.addEventListener(type, this, passiveCapturingEventOptions); }); }); this._pointerUpEventsRegistered = true; } } /** Method that will be called if the fade-in or fade-in transition completed. */ _finishRippleTransition(rippleRef) { if (rippleRef.state === RippleState.FADING_IN) { this._startFadeOutTransition(rippleRef); } else if (rippleRef.state === RippleState.FADING_OUT) { this._destroyRipple(rippleRef); } } /** * Starts the fade-out transition of the given ripple if it's not persistent and the pointer * is not held down anymore. */ _startFadeOutTransition(rippleRef) { const isMostRecentTransientRipple = rippleRef === this._mostRecentTransientRipple; const { persistent } = rippleRef.config; rippleRef.state = RippleState.VISIBLE; // When the timer runs out while the user has kept their pointer down, we want to // keep only the persistent ripples and the latest transient ripple. We do this, // because we don't want stacked transient ripples to appear after their enter // animation has finished. if (!persistent && (!isMostRecentTransientRipple || !this._isPointerDown)) { rippleRef.fadeOut(); } } /** Destroys the given ripple by removing it from the DOM and updating its state. */ _destroyRipple(rippleRef) { const eventListeners = this._activeRipples.get(rippleRef) ?? null; this._activeRipples.delete(rippleRef); // Clear out the cached bounding rect if we have no more ripples. if (!this._activeRipples.size) { this._containerRect = null; } // If the current ref is the most recent transient ripple, unset it // avoid memory leaks. 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(); } /** Function being called whenever the trigger is being pressed using mouse. */ _onMousedown(event) { // Screen readers will fire fake mouse events for space/enter. Skip launching a // ripple in this case for consistency with the non-screen-reader experience. 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); } } /** Function being called whenever the trigger is being pressed using touch. */ _onTouchStart(event) { if (!this._target.rippleDisabled && !isFakeTouchstartFromScreenReader(event)) { // Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse // events will launch a second ripple if we don't ignore mouse events for a specific // time after a touchstart event. this._lastTouchStartEvent = Date.now(); this._isPointerDown = true; // Use `changedTouches` so we skip any touches where the user put // their finger down, but used another finger to tap the element again. const touches = event.changedTouches; // According to the typings the touches should always be defined, but in some cases // the browser appears to not assign them in tests which leads to flakes. if (touches) { for (let i = 0; i < touches.length; i++) { this.fadeInRipple(touches[i].clientX, touches[i].clientY, this._target.rippleConfig); } } } } /** Function being called whenever the trigger is being released. */ _onPointerUp() { if (!this._isPointerDown) { return; } this._isPointerDown = false; // Fade-out all ripples that are visible and not persistent. this._getActiveRipples().forEach(ripple => { // By default, only ripples that are completely visible will fade out on pointer release. // If the `terminateOnPointerUp` option is set, ripples that still fade in will also fade out. 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()); } /** Removes previously registered event listeners from the trigger element. */ _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; } } } } /** * Returns the distance from the point (x, y) to the furthest corner of a rectangle. */ 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); } /** Injection token that can be used to specify the global ripple options. */ const MAT_RIPPLE_GLOBAL_OPTIONS = new InjectionToken('mat-ripple-global-options'); class MatRipple { _elementRef = inject(ElementRef); _animationMode = inject(ANIMATION_MODULE_TYPE, { optional: true }); /** Custom color for all ripples. */ color; /** Whether the ripples should be visible outside the component's bounds. */ unbounded; /** * Whether the ripple always originates from the center of the host element's bounds, rather * than originating from the location of the click event. */ centered; /** * If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius * will be the distance from the center of the ripple to the furthest corner of the host element's * bounding rectangle. */ radius = 0; /** * Configuration for the ripple animation. Allows modifying the enter and exit animation * duration of the ripples. The animation durations will be overwritten if the * `NoopAnimationsModule` is being used. */ animation; /** * Whether click events will not trigger the ripple. Ripples can be still launched manually * by using the `launch()` method. */ get disabled() { return this._disabled; } set disabled(value) { if (value) { this.fadeOutAllNonPersistent(); } this._disabled = value; this._setupTriggerEventsIfEnabled(); } _disabled = false; /** * The element that triggers the ripple when click events are received. * Defaults to the directive's host element. */ get trigger() { return this._trigger || this._elementRef.nativeElement; } set trigger(trigger) { this._trigger = trigger; this._setupTriggerEventsIfEnabled(); } _trigger; /** Renderer for the ripple DOM manipulations. */ _rippleRenderer; /** Options that are set globally for all ripples. */ _globalOptions; /** @docs-private Whether ripple directive is initialized and the input bindings are set. */ _isInitialized = false; constructor() { const ngZone = inject(NgZone); const platform = inject(Platform); const globalOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, { optional: true }); const injector = inject(Injector); // Note: cannot use `inject()` here, because this class // gets instantiated manually in the ripple loader. this._globalOptions = globalOptions || {}; this._rippleRenderer = new RippleRenderer(this, ngZone, this._elementRef, platform, injector); } ngOnInit() { this._isInitialized = true; this._setupTriggerEventsIfEnabled(); } ngOnDestroy() { this._rippleRenderer._removeTriggerEvents(); } /** Fades out all currently showing ripple elements. */ fadeOutAll() { this._rippleRenderer.fadeOutAll(); } /** Fades out all currently showing non-persistent ripple elements. */ fadeOutAllNonPersistent() { this._rippleRenderer.fadeOutAllNonPersistent(); } /** * Ripple configuration from the directive's input values. * @docs-private Implemented as part of RippleTarget */ get rippleConfig() { return { centered: this.centered, radius: this.radius, color: this.color, animation: { ...this._globalOptions.animation, ...(this._animationMode === 'NoopAnimations' ? { enterDuration: 0, exitDuration: 0 } : {}), ...this.animation, }, terminateOnPointerUp: this._globalOptions.terminateOnPointerUp, }; } /** * Whether ripples on pointer-down are disabled or not. * @docs-private Implemented as part of RippleTarget */ get rippleDisabled() { return this.disabled || !!this._globalOptions.disabled; } /** Sets up the trigger event listeners if ripples are enabled. */ _setupTriggerEventsIfEnabled() { if (!this.disabled && this._isInitialized) { this._rippleRenderer.setupTriggerEvents(this.trigger); } } /** Launches a manual ripple at the specified coordinated or just by the ripple config. */ 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: "19.2.0", ngImport: i0, type: MatRipple, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.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: "19.2.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 { MatRipple as M, RippleRenderer as R, MAT_RIPPLE_GLOBAL_OPTIONS as a, RippleState as b, RippleRef as c, defaultRippleAnimationConfig as d }; //# sourceMappingURL=ripple-acd53c76.mjs.map