mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(ripple): adds unbounded ripple-effect (#16399)
This commit is contained in:
@@ -652,13 +652,14 @@ export class ReorderGroup {
|
||||
}
|
||||
|
||||
export declare interface RippleEffect extends StencilComponents<'IonRippleEffect'> {}
|
||||
@Component({ selector: 'ion-ripple-effect', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>' })
|
||||
@Component({ selector: 'ion-ripple-effect', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['type'] })
|
||||
export class RippleEffect {
|
||||
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
||||
c.detach();
|
||||
const el = r.nativeElement;
|
||||
proxyMethods(this, el, ['addRipple']);
|
||||
proxyInputs(this, el, ['type']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -740,6 +740,7 @@ ion-reorder-group,method,complete,complete(listOrReorder?: boolean | any[] | und
|
||||
ion-reorder-group,event,ionItemReorder,ItemReorderDetail,true
|
||||
|
||||
|
||||
ion-ripple-effect,prop,type,"bounded" | "unbounded",'bounded',false
|
||||
ion-ripple-effect,method,addRipple,addRipple(pageX: number, pageY: number) => Promise<() => void>
|
||||
|
||||
ion-route-redirect,prop,from,string,undefined,false
|
||||
@@ -943,6 +944,7 @@ ion-tab-button,style,--padding-bottom
|
||||
ion-tab-button,style,--padding-end
|
||||
ion-tab-button,style,--padding-start
|
||||
ion-tab-button,style,--padding-top
|
||||
ion-tab-button,style,--ripple-color
|
||||
|
||||
ion-tab,prop,component,Function | HTMLElement | null | string | undefined,undefined,false
|
||||
ion-tab,prop,tab,string,undefined,false
|
||||
|
||||
11
core/src/components.d.ts
vendored
11
core/src/components.d.ts
vendored
@@ -3543,8 +3543,17 @@ export namespace Components {
|
||||
* Adds the ripple effect to the parent element
|
||||
*/
|
||||
'addRipple': (pageX: number, pageY: number) => Promise<() => void>;
|
||||
/**
|
||||
* Sets the type of ripple-effect: - `bounded`: the ripple effect expands from the user's click position - `unbounded`: the ripple effect expands from the center of the button and overflows the container. NOTE: Surfaces for bounded ripples should have the overflow property set to hidden, while surfaces for unbounded ripples should have it set to visible.
|
||||
*/
|
||||
'type': 'bounded' | 'unbounded';
|
||||
}
|
||||
interface IonRippleEffectAttributes extends StencilHTMLAttributes {
|
||||
/**
|
||||
* Sets the type of ripple-effect: - `bounded`: the ripple effect expands from the user's click position - `unbounded`: the ripple effect expands from the center of the button and overflows the container. NOTE: Surfaces for bounded ripples should have the overflow property set to hidden, while surfaces for unbounded ripples should have it set to visible.
|
||||
*/
|
||||
'type'?: 'bounded' | 'unbounded';
|
||||
}
|
||||
interface IonRippleEffectAttributes extends StencilHTMLAttributes {}
|
||||
|
||||
interface IonRouteRedirect {
|
||||
/**
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
line-height: 1;
|
||||
|
||||
box-shadow: var(--box-shadow);
|
||||
contain: content;
|
||||
contain: layout style;
|
||||
cursor: pointer;
|
||||
opacity: var(--opacity);
|
||||
overflow: var(--overflow);
|
||||
|
||||
@@ -13,6 +13,9 @@ import { createColorClasses, openURL } from '../../utils/theme';
|
||||
shadow: true,
|
||||
})
|
||||
export class Button implements ComponentInterface {
|
||||
|
||||
private inToolbar = false;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
@@ -97,9 +100,7 @@ export class Button implements ComponentInterface {
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
componentWillLoad() {
|
||||
if (this.fill === undefined) {
|
||||
this.fill = this.el.closest('ion-buttons') ? 'clear' : 'solid';
|
||||
}
|
||||
this.inToolbar = !!this.el.closest('ion-buttons');
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
@@ -139,8 +140,11 @@ export class Button implements ComponentInterface {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const { buttonType, keyFocus, disabled, color, expand, fill, shape, size, strong } = this;
|
||||
|
||||
const { buttonType, keyFocus, disabled, color, expand, shape, size, strong } = this;
|
||||
let fill = this.fill;
|
||||
if (fill === undefined) {
|
||||
fill = this.inToolbar ? 'clear' : 'solid';
|
||||
}
|
||||
return {
|
||||
'ion-activatable': true,
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
@@ -181,7 +185,7 @@ export class Button implements ComponentInterface {
|
||||
<slot></slot>
|
||||
<slot name="end"></slot>
|
||||
</span>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
{this.mode === 'md' && <ion-ripple-effect type={this.inToolbar ? 'unbounded' : 'bounded'}></ion-ripple-effect>}
|
||||
</TagType>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
--padding-start: 0;
|
||||
--padding-end: 0;
|
||||
--box-shadow: none;
|
||||
--overflow: visible;
|
||||
|
||||
@include margin-horizontal(2px, 2px);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,13 @@ The ripple effect component adds the [Material Design ink ripple interaction eff
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----------- |
|
||||
| `type` | `type` | Sets the type of ripple-effect: - `bounded`: the ripple effect expands from the user's click position - `unbounded`: the ripple effect expands from the center of the button and overflows the container. NOTE: Surfaces for bounded ripples should have the overflow property set to hidden, while surfaces for unbounded ripples should have it set to visible. | `"bounded" \| "unbounded"` | `'bounded'` |
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
### `addRipple(pageX: number, pageY: number) => Promise<() => void>`
|
||||
|
||||
@@ -17,6 +17,10 @@ $opacity-duration: $fade-in-duration + $fade-out-duration;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
:host(.unbounded) {
|
||||
contain: layout size style;
|
||||
}
|
||||
|
||||
.ripple-effect {
|
||||
@include border-radius(50%);
|
||||
|
||||
|
||||
@@ -12,6 +12,17 @@ export class RippleEffect implements ComponentInterface {
|
||||
@Prop({ context: 'queue' }) queue!: QueueApi;
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
|
||||
/**
|
||||
* Sets the type of ripple-effect:
|
||||
*
|
||||
* - `bounded`: the ripple effect expands from the user's click position
|
||||
* - `unbounded`: the ripple effect expands from the center of the button and overflows the container.
|
||||
*
|
||||
* NOTE: Surfaces for bounded ripples should have the overflow property set to hidden,
|
||||
* while surfaces for unbounded ripples should have it set to visible.
|
||||
*/
|
||||
@Prop() type: 'bounded' | 'unbounded' = 'bounded';
|
||||
|
||||
/**
|
||||
* Adds the ripple effect to the parent element
|
||||
*/
|
||||
@@ -23,13 +34,17 @@ export class RippleEffect implements ComponentInterface {
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
const hypotenuse = Math.sqrt(width * width + height * height);
|
||||
const maxRadius = hypotenuse + PADDING;
|
||||
const maxDim = Math.max(height, width);
|
||||
const posX = pageX - rect.left;
|
||||
const posY = pageY - rect.top;
|
||||
|
||||
const maxRadius = this.unbounded ? maxDim : hypotenuse + PADDING;
|
||||
const initialSize = Math.floor(maxDim * INITIAL_ORIGIN_SCALE);
|
||||
const finalScale = maxRadius / initialSize;
|
||||
let posX = pageX - rect.left;
|
||||
let posY = pageY - rect.top;
|
||||
if (this.type) {
|
||||
posX = width * 0.5;
|
||||
posY = height * 0.5;
|
||||
}
|
||||
|
||||
const x = posX - initialSize * 0.5;
|
||||
const y = posY - initialSize * 0.5;
|
||||
const moveX = width * 0.5 - posX;
|
||||
@@ -57,9 +72,16 @@ export class RippleEffect implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private get unbounded() {
|
||||
return this.type === 'unbounded';
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
role: 'presentation'
|
||||
role: 'presentation',
|
||||
class: {
|
||||
'unbounded': this.unbounded
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ See the [Tabs API Docs](../Tabs/) for more details on configuring Tabs.
|
||||
| `--padding-end` | End padding of the tab button |
|
||||
| `--padding-start` | Start padding of the tab button |
|
||||
| `--padding-top` | Top padding of the tab button |
|
||||
| `--ripple-color` | Color of the button ripple effect |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
* @prop --padding-end: End padding of the tab button
|
||||
* @prop --padding-bottom: Bottom padding of the tab button
|
||||
* @prop --padding-start: Start padding of the tab button
|
||||
* @prop --ripple-color: Color of the button ripple effect
|
||||
*/
|
||||
--badge-end: 4%;
|
||||
--ripple-color: var(--color-selected);
|
||||
|
||||
flex: 1;
|
||||
|
||||
@@ -177,3 +179,7 @@ a:focus-visible {
|
||||
:host(.tab-has-label-only) {
|
||||
--badge-end: #{calc(50% - 50px)};
|
||||
}
|
||||
|
||||
ion-ripple-effect {
|
||||
color: var(--ripple-color);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class TabButton implements ComponentInterface {
|
||||
return (
|
||||
<a href={href || '#'}>
|
||||
<slot></slot>
|
||||
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
{mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user