mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
@ -51,14 +51,14 @@ export class RefresherContent implements ComponentInterface {
|
||||
const mode = getIonMode(this);
|
||||
this.pullingIcon = config.get(
|
||||
'refreshingIcon',
|
||||
mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'arrow-down'
|
||||
mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'circular'
|
||||
);
|
||||
}
|
||||
if (this.refreshingSpinner === undefined) {
|
||||
const mode = getIonMode(this);
|
||||
this.refreshingSpinner = config.get(
|
||||
'refreshingSpinner',
|
||||
config.get('spinner', mode === 'ios' ? 'lines' : 'crescent')
|
||||
config.get('spinner', mode === 'ios' ? 'lines' : 'circular')
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -66,12 +66,21 @@ export class RefresherContent implements ComponentInterface {
|
||||
render() {
|
||||
const pullingIcon = this.pullingIcon;
|
||||
const hasSpinner = pullingIcon != null && SPINNERS[pullingIcon] as any !== undefined;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host class={getIonMode(this)}>
|
||||
<Host class={mode}>
|
||||
<div class="refresher-pulling">
|
||||
{this.pullingIcon && hasSpinner &&
|
||||
<div class="refresher-pulling-icon">
|
||||
<div class="spinner-arrow-container">
|
||||
<ion-spinner name={this.pullingIcon as SpinnerTypes} paused></ion-spinner>
|
||||
{mode === 'md' && this.pullingIcon === 'circular' &&
|
||||
<div class="arrow-container">
|
||||
<ion-icon name="caret-back-sharp"></ion-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{this.pullingIcon && !hasSpinner &&
|
||||
|
@ -10,14 +10,19 @@ refresher.
|
||||
|
||||
### Native Refreshers
|
||||
|
||||
Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel. One of the limitations of this is that the refreshers only work on their respective platform devices. For example, the iOS native `ion-refresher` works on an iPhone in iOS mode, but does not work on an Android device in iOS mode. In order for the refresher to work on an unsupported device, we provide a fallback refresher. This can also be set manually by overriding the `pullingIcon` property.
|
||||
Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel.
|
||||
|
||||
Because much of the native refreshers are based on scrolling, certain properties such as `pullMin` and `snapbackDuration` are not compatible. See [ion-refresher Properties](#properties) for more information.
|
||||
Certain properties such as `pullMin` and `snapbackDuration` are not compatible because much of the native refreshers are scroll-based. See [Refresher Properties](#properties) for more information.
|
||||
|
||||
#### iOS Usage
|
||||
|
||||
Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page. In order for the refresher to work on a device that isn't an iOS mobile device, the `pullingIcon` should be set to an icon.
|
||||
Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [Spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page.
|
||||
|
||||
The iOS native `ion-refresher` relies on rubber band scrolling in order to work properly and is only compatible with iOS devices as a result. We provide a fallback refresher for apps running in iOS mode on devices that do not support rubber band scrolling.
|
||||
|
||||
#### Android Usage
|
||||
|
||||
Using the MD native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. `pullingIcon` defaults to the `circular` spinner on MD.
|
||||
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
@ -154,7 +159,6 @@ export const RefresherExample: React.FC = () => (
|
||||
</IonContent>
|
||||
</IonContent>
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
@ -25,3 +25,74 @@
|
||||
.refresher-md .refresher-refreshing .spinner-dots circle {
|
||||
fill: $refresher-md-icon-color;
|
||||
}
|
||||
|
||||
ion-refresher.refresher-native {
|
||||
display: block;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
ion-spinner {
|
||||
@include margin(0, auto, 0, auto);
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
color: $refresher-md-native-spinner-color;
|
||||
}
|
||||
|
||||
.spinner-arrow-container {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.arrow-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
ion-icon {
|
||||
@include margin(0, auto, 0, auto);
|
||||
@include position(null, 0, -4px, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
color: $refresher-md-native-spinner-color;
|
||||
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&.refresher-pulling ion-refresher-content,
|
||||
&.refresher-ready ion-refresher-content {
|
||||
.refresher-pulling {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.refresher-refreshing ion-refresher-content,
|
||||
&.refresher-completing ion-refresher-content,
|
||||
&.refresher-cancelling ion-refresher-content {
|
||||
.refresher-refreshing {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.refresher-pulling-icon {
|
||||
transform: translateY(calc(-100% - 10px));
|
||||
}
|
||||
|
||||
.refresher-pulling-icon,
|
||||
.refresher-refreshing-icon {
|
||||
@include margin(0, auto, 0, auto);
|
||||
@include border-radius(100%);
|
||||
@include padding(8px, 8px, 8px, 8px);
|
||||
|
||||
display: flex;
|
||||
|
||||
border: $refresher-md-native-spinner-border;
|
||||
|
||||
background: $refresher-md-native-spinner-background;
|
||||
|
||||
box-shadow: $refresher-md-native-spinner-box-shadow;
|
||||
}
|
||||
}
|
@ -5,3 +5,15 @@ $refresher-md-icon-color: $text-color !default;
|
||||
|
||||
/// @prop - Text color of the refresher content
|
||||
$refresher-md-text-color: $text-color !default;
|
||||
|
||||
/// @prop - Color of the native refresher spinner
|
||||
$refresher-md-native-spinner-color: #{ion-color(primary, base)} !default;
|
||||
|
||||
/// @prop - Border of the native refresher spinner
|
||||
$refresher-md-native-spinner-border: 1px solid #ececec !default;
|
||||
|
||||
/// @prop - Background of the native refresher spinner
|
||||
$refresher-md-native-spinner-background: white !default;
|
||||
|
||||
/// @prop - Box shadow of the native refresher spinner
|
||||
$refresher-md-native-spinner-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.1) !default;
|
||||
|
@ -56,6 +56,10 @@ ion-refresher-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ion-refresher-content .arrow-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Refresher Content States
|
||||
// --------------------------------------------------
|
||||
|
||||
@ -100,3 +104,12 @@ ion-refresher-content {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresher Native
|
||||
// --------------------------------------------------
|
||||
|
||||
.refresher-native {
|
||||
.refresher-pulling-text, .refresher-refreshing-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core';
|
||||
|
||||
import { getTimeGivenProgression } from '../../';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||
import { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||
import { clamp } from '../../utils/helpers';
|
||||
import { hapticImpact } from '../../utils/native/haptic';
|
||||
|
||||
import { handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, translateElement } from './refresher.utils';
|
||||
import { createPullingAnimation, createSnapBackAnimation, getRefresherAnimationType, handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, transitionEndAsync, translateElement } from './refresher.utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-refresher',
|
||||
@ -28,6 +29,7 @@ export class Refresher implements ComponentInterface {
|
||||
private didRefresh = false;
|
||||
private lastVelocityY = 0;
|
||||
private elementToTransform?: HTMLElement;
|
||||
private animations: Animation[] = [];
|
||||
|
||||
@State() private nativeRefresher = false;
|
||||
|
||||
@ -142,26 +144,24 @@ export class Refresher implements ComponentInterface {
|
||||
private async resetNativeRefresher(el: HTMLElement | undefined, state: RefresherState) {
|
||||
this.state = state;
|
||||
|
||||
if (el !== undefined) {
|
||||
if (getIonMode(this) === 'ios') {
|
||||
await translateElement(el, undefined);
|
||||
} else {
|
||||
await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon'));
|
||||
}
|
||||
|
||||
this.didRefresh = false;
|
||||
this.needsCompletion = false;
|
||||
this.pointerDown = false;
|
||||
this.animations.forEach(ani => ani.destroy());
|
||||
this.animations = [];
|
||||
this.progress = 0;
|
||||
|
||||
this.state = RefresherState.Inactive;
|
||||
}
|
||||
|
||||
private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) {
|
||||
if (this.scrollListenerCallback || !contentEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLElement;
|
||||
const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLElement;
|
||||
|
||||
private async setupiOSNativeRefresher(pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) {
|
||||
this.elementToTransform = this.scrollEl!.querySelector(`#scroll-content`) as HTMLElement | undefined;
|
||||
this.nativeRefresher = true;
|
||||
const ticks = pullingSpinner.shadowRoot!.querySelectorAll('svg');
|
||||
const MAX_PULL = this.scrollEl!.clientHeight * 0.16;
|
||||
const NUM_TICKS = ticks.length;
|
||||
@ -198,8 +198,11 @@ export class Refresher implements ComponentInterface {
|
||||
this.ionStart.emit();
|
||||
}
|
||||
|
||||
// emit "pulling" on every move
|
||||
if (this.pointerDown) {
|
||||
this.ionPull.emit();
|
||||
}
|
||||
}
|
||||
|
||||
// delay showing the next tick marks until user has pulled 30px
|
||||
const opacity = clamp(0, Math.abs(scrollTop) / refresherHeight, 0.99);
|
||||
@ -258,9 +261,7 @@ export class Refresher implements ComponentInterface {
|
||||
this.resetNativeRefresher(this.elementToTransform, RefresherState.Completing);
|
||||
this.needsCompletion = false;
|
||||
} else if (this.didRefresh) {
|
||||
readTask(() => {
|
||||
translateElement(this.elementToTransform, `${this.el.clientHeight}px`);
|
||||
});
|
||||
readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`));
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -268,6 +269,113 @@ export class Refresher implements ComponentInterface {
|
||||
this.disabledChanged();
|
||||
}
|
||||
|
||||
private async setupMDNativeRefresher(contentEl: HTMLIonContentElement, pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) {
|
||||
const circle = pullingSpinner.shadowRoot!.querySelector('circle');
|
||||
const pullingRefresherIcon = this.el.querySelector('ion-refresher-content .refresher-pulling-icon') as HTMLElement;
|
||||
const refreshingCircle = refreshingSpinner.shadowRoot!.querySelector('circle');
|
||||
|
||||
if (circle !== null && refreshingCircle !== null) {
|
||||
writeTask(() => {
|
||||
circle.style.setProperty('animation', 'none');
|
||||
|
||||
// This lines up the animation on the refreshing spinner with the pulling spinner
|
||||
refreshingSpinner.style.setProperty('animation-delay', '-655ms');
|
||||
refreshingCircle.style.setProperty('animation-delay', '-655ms');
|
||||
});
|
||||
}
|
||||
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: this.scrollEl!,
|
||||
gestureName: 'refresher',
|
||||
gesturePriority: 10,
|
||||
direction: 'y',
|
||||
threshold: 0,
|
||||
canStart: () => this.state !== RefresherState.Refreshing && this.state !== RefresherState.Completing && this.scrollEl!.scrollTop === 0,
|
||||
onStart: (ev: GestureDetail) => {
|
||||
ev.data = { animation: undefined, didStart: false, cancelled: false };
|
||||
},
|
||||
onMove: (ev: GestureDetail) => {
|
||||
if ((ev.velocityY < 0 && this.progress === 0 && !ev.data.didStart) || ev.data.cancelled) {
|
||||
ev.data.cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ev.data.didStart) {
|
||||
ev.data.didStart = true;
|
||||
|
||||
this.state = RefresherState.Pulling;
|
||||
|
||||
writeTask(() => {
|
||||
const animationType = getRefresherAnimationType(contentEl);
|
||||
const animation = createPullingAnimation(animationType, pullingRefresherIcon);
|
||||
ev.data.animation = animation;
|
||||
|
||||
this.scrollEl!.style.setProperty('--overflow', 'hidden');
|
||||
|
||||
animation.progressStart(false, 0);
|
||||
this.ionStart.emit();
|
||||
this.animations.push(animation);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are using an easing curve, slow the gesture tracking down a bit
|
||||
this.progress = clamp(0, (ev.deltaY / 180) * 0.5, 1);
|
||||
ev.data.animation.progressStep(this.progress);
|
||||
this.ionPull.emit();
|
||||
},
|
||||
onEnd: (ev: GestureDetail) => {
|
||||
if (!ev.data.didStart) { return; }
|
||||
|
||||
writeTask(() => this.scrollEl!.style.removeProperty('--overflow'));
|
||||
if (this.progress <= 0.4) {
|
||||
this.gesture!.enable(false);
|
||||
|
||||
ev.data.animation
|
||||
.progressEnd(0, this.progress, 500)
|
||||
.onFinish(() => {
|
||||
this.animations.forEach(ani => ani.destroy());
|
||||
this.animations = [];
|
||||
this.gesture!.enable(true);
|
||||
this.state = RefresherState.Inactive;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const progress = getTimeGivenProgression([0, 0], [0, 0], [1, 1], [1, 1], this.progress)[0];
|
||||
const snapBackAnimation = createSnapBackAnimation(pullingRefresherIcon);
|
||||
this.animations.push(snapBackAnimation);
|
||||
writeTask(async () => {
|
||||
pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${(progress * 100)}px`);
|
||||
ev.data.animation.progressEnd();
|
||||
await snapBackAnimation.play();
|
||||
this.beginRefresh();
|
||||
ev.data.animation.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.disabledChanged();
|
||||
}
|
||||
|
||||
private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) {
|
||||
if (this.scrollListenerCallback || !contentEl || this.nativeRefresher) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nativeRefresher = true;
|
||||
|
||||
const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLIonSpinnerElement;
|
||||
const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLIonSpinnerElement;
|
||||
|
||||
if (getIonMode(this) === 'ios') {
|
||||
this.setupiOSNativeRefresher(pullingSpinner, refreshingSpinner);
|
||||
} else {
|
||||
this.setupMDNativeRefresher(contentEl, pullingSpinner, refreshingSpinner);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkNativeRefresher();
|
||||
}
|
||||
|
@ -1,7 +1,123 @@
|
||||
import { writeTask } from '@stencil/core';
|
||||
|
||||
import { createAnimation } from '../../';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
|
||||
// MD Native Refresher
|
||||
// -----------------------------
|
||||
type RefresherAnimationType = 'scale' | 'translate';
|
||||
|
||||
export const getRefresherAnimationType = (contentEl: HTMLIonContentElement): RefresherAnimationType => {
|
||||
const previousSibling = contentEl.previousElementSibling;
|
||||
const hasHeader = previousSibling !== null && previousSibling.tagName === 'ION-HEADER';
|
||||
|
||||
return hasHeader ? 'translate' : 'scale';
|
||||
};
|
||||
|
||||
export const createPullingAnimation = (type: RefresherAnimationType, pullingSpinner: HTMLElement) => {
|
||||
return type === 'scale' ? createScaleAnimation(pullingSpinner) : createTranslateAnimation(pullingSpinner);
|
||||
};
|
||||
|
||||
const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
const spinner = pullingRefresherIcon.querySelector('ion-spinner') as HTMLElement;
|
||||
const circle = spinner!.shadowRoot!.querySelector('circle') as any;
|
||||
const spinnerArrowContainer = pullingRefresherIcon.querySelector('.spinner-arrow-container') as HTMLElement;
|
||||
const arrowContainer = pullingRefresherIcon!.querySelector('.arrow-container');
|
||||
const arrow = (arrowContainer) ? arrowContainer!.querySelector('ion-icon') as HTMLElement : null;
|
||||
|
||||
const baseAnimation = createAnimation()
|
||||
.duration(1000)
|
||||
.easing('ease-out');
|
||||
|
||||
const spinnerArrowContainerAnimation = createAnimation()
|
||||
.addElement(spinnerArrowContainer)
|
||||
.keyframes([
|
||||
{ offset: 0, opacity: '0.3' },
|
||||
{ offset: 0.45, opacity: '0.3' },
|
||||
{ offset: 0.55, opacity: '1' },
|
||||
{ offset: 1, opacity: '1' }
|
||||
]);
|
||||
|
||||
const circleInnerAnimation = createAnimation()
|
||||
.addElement(circle)
|
||||
.keyframes([
|
||||
{ offset: 0, 'stroke-dasharray': '1px, 200px' },
|
||||
{ offset: 0.20, 'stroke-dasharray': '1px, 200px' },
|
||||
{ offset: 0.55, 'stroke-dasharray': '100px, 200px' },
|
||||
{ offset: 1, 'stroke-dasharray': '100px, 200px' }
|
||||
]);
|
||||
|
||||
const circleOuterAnimation = createAnimation()
|
||||
.addElement(spinner)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'rotate(-90deg)' },
|
||||
{ offset: 1, transform: 'rotate(210deg)' }
|
||||
]);
|
||||
|
||||
/**
|
||||
* Only add arrow animation if present
|
||||
* this allows users to customize the spinners
|
||||
* without errors being thrown
|
||||
*/
|
||||
if (arrowContainer && arrow) {
|
||||
const arrowContainerAnimation = createAnimation()
|
||||
.addElement(arrowContainer)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'rotate(0deg)' },
|
||||
{ offset: 0.30, transform: 'rotate(0deg)' },
|
||||
{ offset: 0.55, transform: 'rotate(280deg)' },
|
||||
{ offset: 1, transform: 'rotate(400deg)' }
|
||||
]);
|
||||
|
||||
const arrowAnimation = createAnimation()
|
||||
.addElement(arrow)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'translateX(2px) scale(0)' },
|
||||
{ offset: 0.30, transform: 'translateX(2px) scale(0)' },
|
||||
{ offset: 0.55, transform: 'translateX(-1.5px) scale(1)' },
|
||||
{ offset: 1, transform: 'translateX(-1.5px) scale(1)' }
|
||||
]);
|
||||
|
||||
baseAnimation.addAnimation([arrowContainerAnimation, arrowAnimation]);
|
||||
}
|
||||
|
||||
return baseAnimation.addAnimation([spinnerArrowContainerAnimation, circleInnerAnimation, circleOuterAnimation]);
|
||||
};
|
||||
|
||||
const createScaleAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
const height = pullingRefresherIcon.clientHeight;
|
||||
const spinnerAnimation = createAnimation()
|
||||
.addElement(pullingRefresherIcon)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: `scale(0) translateY(-${height + 20}px)` },
|
||||
{ offset: 1, transform: 'scale(1) translateY(100px)' }
|
||||
]);
|
||||
|
||||
return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
|
||||
};
|
||||
|
||||
const createTranslateAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
const height = pullingRefresherIcon.clientHeight;
|
||||
const spinnerAnimation = createAnimation()
|
||||
.addElement(pullingRefresherIcon)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: `translateY(-${height + 20}px)` },
|
||||
{ offset: 1, transform: 'translateY(100px)' }
|
||||
]);
|
||||
|
||||
return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
|
||||
};
|
||||
|
||||
export const createSnapBackAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
return createAnimation()
|
||||
.duration(125)
|
||||
.addElement(pullingRefresherIcon)
|
||||
.fromTo('transform', 'translateY(var(--ion-pulling-refresher-translate, 100px))', 'translateY(0px)');
|
||||
};
|
||||
|
||||
// iOS Native Refresher
|
||||
// -----------------------------
|
||||
|
||||
export const setSpinnerOpacity = (spinner: HTMLElement, opacity: number) => {
|
||||
spinner.style.setProperty('opacity', opacity.toString());
|
||||
};
|
||||
@ -29,23 +145,10 @@ export const handleScrollWhileRefreshing = (
|
||||
});
|
||||
};
|
||||
|
||||
export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, mode: string) => {
|
||||
const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
|
||||
const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
|
||||
|
||||
return (
|
||||
pullingSpinner !== null &&
|
||||
refreshingSpinner !== null &&
|
||||
mode === 'ios' &&
|
||||
isPlatform('mobile')
|
||||
);
|
||||
};
|
||||
|
||||
export const translateElement = (el?: HTMLElement, value?: string) => {
|
||||
return new Promise(resolve => {
|
||||
if (!el) { return resolve(); }
|
||||
if (!el) { return Promise.resolve(); }
|
||||
|
||||
transitionEnd(el, resolve);
|
||||
const trans = transitionEndAsync(el);
|
||||
|
||||
writeTask(() => {
|
||||
el.style.setProperty('transition', '0.2s all ease-out');
|
||||
@ -56,6 +159,31 @@ export const translateElement = (el?: HTMLElement, value?: string) => {
|
||||
el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`);
|
||||
}
|
||||
});
|
||||
|
||||
return trans;
|
||||
};
|
||||
|
||||
// Utils
|
||||
// -----------------------------
|
||||
|
||||
export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, mode: string) => {
|
||||
const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
|
||||
const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
|
||||
|
||||
return (
|
||||
pullingSpinner !== null &&
|
||||
refreshingSpinner !== null &&
|
||||
(
|
||||
(mode === 'ios' && isPlatform('mobile')) ||
|
||||
mode === 'md'
|
||||
)
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export const transitionEndAsync = (el: HTMLElement | null) => {
|
||||
return new Promise(resolve => {
|
||||
transitionEnd(el, resolve);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -44,11 +44,40 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-list id="list"></ion-list>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
||||
<ion-fab-button onclick="toggleHeader()">
|
||||
<ion-icon name="settings"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
|
||||
const toggleHeader = () => {
|
||||
const app = document.querySelector('ion-app');
|
||||
const header = app.querySelector('ion-header');
|
||||
|
||||
if (header) {
|
||||
header.parentNode.removeChild(header);
|
||||
} else {
|
||||
app.insertAdjacentHTML("afterbegin", `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button default-href="/" text="Mailboxes"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>All Inboxes</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button>Edit</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
let items = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items.push(i + 1);
|
||||
@ -94,5 +123,4 @@
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -41,5 +41,4 @@ export const RefresherExample: React.FC = () => (
|
||||
</IonContent>
|
||||
</IonContent>
|
||||
);
|
||||
|
||||
```
|
@ -44,10 +44,10 @@ const spinners = {
|
||||
fn: () => {
|
||||
return {
|
||||
r: 20,
|
||||
cx: 44,
|
||||
cy: 44,
|
||||
cx: 48,
|
||||
cy: 48,
|
||||
fill: 'none',
|
||||
viewBox: '22 22 44 44',
|
||||
viewBox: '24 24 48 48',
|
||||
transform: 'translate(0,0)',
|
||||
style: {}
|
||||
};
|
||||
|
@ -111,7 +111,7 @@ svg {
|
||||
stroke: currentColor;
|
||||
stroke-dasharray: 80px, 200px;
|
||||
stroke-dashoffset: 0px;
|
||||
stroke-width: 3.6;
|
||||
stroke-width: 5.6;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user