# Svelte Material UI - Ripple
Ripples are an interaction feedback mechanism.
# Installation
```sh
npm install --save-dev @smui/ripple
```
# Examples and Usage Information
https://sveltematerialui.com/demo/ripple
# Design
https://m2.material.io/design/interaction/states.html
# Exports
## (default)
A ripple Svelte action.
### Props / Defaults
The action accepts an array, with two entries. The first is a boolean, whether the ripple is enabled. The second is an object with the props:
- `ripple`: `true` - Whether to enable the ripple.
- `surface`: `false` - Whether the ripple surface classes should be added.
- `unbounded`: `false` - Whether the ripple is unbounded.
- `disabled`: `false` - Whether the node is disabled.
- `color`: `undefined` - The ripple color. ('surface', 'primary', or 'secondary')
- `active`: `undefined` - Used to determine active status of the ripple. If it's undefined, the ":active" pseudo class will be checked on `activeTarget` or the node.
- `eventTarget`: `undefined` - An alternate element where ripple triggering event listeners will be added.
- `activeTarget`: `undefined` - An alternate element where active status will be checked.
- `addClass`: `(className) => node.classList.add(className)` - A function to add a class to the node.
- `removeClass`: `(className) => node.classList.remove(className)` - A function to remove a class from the node.
- `addStyle`: `(name, value) => node.style.setProperty(name, value)` - A function to add a style property to the node. If given a value of `''` or `null`, it should remove the property.
- `initPromise`: `Promise.resolve()` - A promise to wait for before initializing the ripple.
# Usage
A ripple can be applied to a variety of elements to represent interactive surfaces. Several SMUI components, such as Button, FAB, Checkbox and Radio, also use ripples.
A ripple can be added to an element through either a JavaScript or CSS-only implementation. When a ripple is initialized on an element using JS, it dynamically adds a `mdc-ripple-upgraded` class to that element. If ripple JS is not initialized but Sass mixins are included on the element, the ripple uses a simpler CSS-only implementation which relies on the `:hover`, `:focus`, and `:active` pseudo-classes.
## CSS Classes
| CSS Class | Description |
| ----------------------------- | -------------------------------------------------- |
| `mdc-ripple-surface` | Adds a ripple to the element |
| `mdc-ripple-surface--primary` | Sets the ripple color to the theme primary color |
| `mdc-ripple-surface--accent` | Sets the ripple color to the theme secondary color |
## Sass APIs
In order to fully style the ripple effect for different states (hover/focus/pressed), the following mixins must be included:
- `surface`, for base styles
- Either `radius-bounded` or `radius-unbounded`, to appropriately size the ripple on the surface
- Either the basic or advanced `states` mixins, as explained below
#### Using basic states mixins
```css
@use '@smui/ripple';
.my-surface {
@include ripple.surface;
@include ripple.radius-bounded;
@include ripple.states;
}
```
#### Using advanced states mixins
```css
.my-surface {
@include ripple.surface;
@include ripple.radius-bounded;
@include ripple.states-base-color(black);
@include ripple.states-opacities((hover: 0.1, focus: 0.3, press: 0.4));
}
```
These APIs use pseudo-elements for the ripple effect: `::before` for the background, and `::after` for the foreground.
### Ripple Mixins
| Mixin | Description |
| --------------------------- | --------------------------------------------------------------------------------- |
| `surface` | Mandatory. Adds base styles for a ripple surface |
| `radius-bounded($radius)` | Adds styles for the radius of the ripple effect,
for bounded ripple surfaces |
| `radius-unbounded($radius)` | Adds styles for the radius of the ripple effect,
for unbounded ripple surfaces |
> _NOTE_: It is mandatory to include _either_ `radius-bounded` or `radius-unbounded`. In both cases, `$radius` is optional and defaults to `100%`.
### Basic States Mixins
| Mixin | Description |
| --------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `states($color, $has-nested-focusable-element)` | Mandatory. Adds state and ripple styles in the given color |
| `states-activated($color, $has-nested-focusable-element)` | Optional. Adds state and ripple styles for activated states in the given color |
| `states-selected($color, $has-nested-focusable-element)` | Optional. Adds state and ripple styles for selected states in the given color |
> _NOTE_: Each of the mixins above adds ripple styles using the indicated color, deciding opacity values based on whether the passed color is light or dark.
> _NOTE_: The `states-activated` and `states-selected` mixins add the appropriate state styles to the root element containing `&--activated` or `&--selected` modifier classes respectively.
> _NOTE_: `$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) inside the root element.
### Advanced States Mixins
When using the advanced states mixins instead of the basic states mixins, every one of the mixins below should be included at least once.
These mixins can also be used to emit activated or selected styles, by applying them within a selector for
`&--activated` or `&--selected` modifier classes.
| Mixin | Description |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `states-base-color($color)` | Mandatory. Sets up base state styles using the provided color |
| `states-opacities($opacity-map, $has-nested-focusable-element)` | Sets the opacity of the ripple in any of the `hover`, `focus`, or `press` states. The `opacity-map` can specify one or more of these states as keys. States not specified in the map resort to default opacity values. |
> _NOTE_: `$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) inside the root element.
> _DEPRECATED_: The individual mixins `states-hover-opacity($opacity)`, `states-focus-opacity($opacity, $has-nested-focusable-element)`, and `states-press-opacity($opacity)` are deprecated in favor of the unified `states-opacities($opacity-map, $has-nested-focusable-element)` mixin above.
### Sass Functions
| Function | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `states-opacity($color, $state)` | Returns the appropriate default opacity to apply to the given color in the given state (hover, focus, press, selected, or activated) |
## `MDCRipple`
The `MDCRipple` JavaScript component allows for programmatic activation / deactivation of the ripple, for interdependent interaction between
components. For example, this is used for making form field labels trigger the ripples in their corresponding input elements.
To use the `MDCRipple` component, first [import the `MDCRipple` JS](../../docs/importing-js.md). Then, initialize the ripple with the correct DOM element.
```javascript
const surface = document.querySelector('.my-surface');
const ripple = new MDCRipple(surface);
```
You can also use `attachTo()` as an alias if you don't care about retaining a reference to the
ripple.
```javascript
MDCRipple.attachTo(document.querySelector('.my-surface'));
```
| Property | Value Type | Description |
| ----------- | ---------- | -------------------------------------- |
| `unbounded` | Boolean | Whether or not the ripple is unbounded |
> _NOTE_: Surfaces for bounded ripples should have the `overflow` property set to `hidden`, while surfaces for unbounded ripples should have it set to `visible`.
| Method Signature | Description |
| ----------------------- | ----------------------------------------------- |
| `activate() => void` | Proxies to the foundation's `activate` method |
| `deactivate() => void` | Proxies to the foundation's `deactivate` method |
| `layout() => void` | Proxies to the foundation's `layout` method |
| `handleFocus() => void` | Handles focus event on the ripple surface |
| `handleBlur() => void` | Handles blur event on the ripple surface |
## `MDCRippleAdapter`
| Method Signature | Description |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `browserSupportsCssVars() => boolean` | Whether or not the given browser supports CSS Variables. |
| `isUnbounded() => boolean` | Whether or not the ripple should be considered unbounded. |
| `isSurfaceActive() => boolean` | Whether or not the surface the ripple is acting upon is [active](https://www.w3.org/TR/css3-selectors/#useraction-pseudos) |
| `isSurfaceDisabled() => boolean` | Whether or not the ripple is attached to a disabled component |
| `addClass(className: string) => void` | Adds a class to the ripple surface |
| `removeClass(className: string) => void` | Removes a class from the ripple surface |
| `containsEventTarget(target: EventTarget) => boolean` | Whether or not the ripple surface contains the given event target |
| `registerInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event handler on the ripple surface |
| `deregisterInteractionHandler(evtType: string, handler: EventListener) => void` | Unregisters an event handler on the ripple surface |
| `registerDocumentInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event handler on the documentElement |
| `deregisterDocumentInteractionHandler(evtType: string, handler: EventListener) => void` | Unregisters an event handler on the documentElement |
| `registerResizeHandler(handler: Function) => void` | Registers a handler to be called when the ripple surface (or its viewport) resizes |
| `deregisterResizeHandler(handler: Function) => void` | Unregisters a handler to be called when the ripple surface (or its viewport) resizes |
| `updateCssVariable(varName: string, value: (string or null)) => void` | Sets the CSS property `varName` on the ripple surface to the value specified |
| `computeBoundingRect() => ClientRect` | Returns the ClientRect for the surface |
| `getWindowPageOffset() => {x: number, y: number}` | Returns the `page{X,Y}Offset` values for the window object |
> _NOTE_: When implementing `browserSupportsCssVars`, please take the [Safari 9](#caveat-safari) considerations into account. We provide a `supportsCssVariables` function within the `util.js` which we recommend using, as it handles this for you.
## `MDCRippleFoundation`
| Method Signature | Description |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `activate() => void` | Triggers an activation of the ripple (the first stage, which happens when the ripple surface is engaged via interaction, such as a `mousedown` or a `pointerdown` event). It expands from the center. |
| `deactivate() => void` | Triggers a deactivation of the ripple (the second stage, which happens when the ripple surface is engaged via interaction, such as a `mouseup` or a `pointerup` event). It expands from the center. |
| `layout() => void` | Recomputes all dimensions and positions for the ripple element. Useful if a ripple surface's position or dimension is changed programmatically. |
| `setUnbounded(unbounded: boolean) => void` | Sets the ripple to be unbounded or not, based on the given boolean. |
# Tips/Tricks
## Using a sentinel element for a ripple
Usually, you'll want to leverage `::before` and `::after` pseudo-elements when integrating the ripple into SMUI components. If you can't use pseudo-elements, create a sentinel element inside your root element. The sentinel element covers the root element's surface.
```html
A surface