mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
feat(tapclick): adds tap-click
This commit is contained in:
63
packages/core/src/components.d.ts
vendored
63
packages/core/src/components.d.ts
vendored
@ -923,36 +923,6 @@ declare global {
|
||||
}
|
||||
|
||||
|
||||
import {
|
||||
GestureController as IonGestureController
|
||||
} from './components/gesture-controller/gesture-controller';
|
||||
|
||||
declare global {
|
||||
interface HTMLIonGestureControllerElement extends IonGestureController, HTMLElement {
|
||||
}
|
||||
var HTMLIonGestureControllerElement: {
|
||||
prototype: HTMLIonGestureControllerElement;
|
||||
new (): HTMLIonGestureControllerElement;
|
||||
};
|
||||
interface HTMLElementTagNameMap {
|
||||
"ion-gesture-controller": HTMLIonGestureControllerElement;
|
||||
}
|
||||
interface ElementTagNameMap {
|
||||
"ion-gesture-controller": HTMLIonGestureControllerElement;
|
||||
}
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"ion-gesture-controller": JSXElements.IonGestureControllerAttributes;
|
||||
}
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonGestureControllerAttributes extends HTMLAttributes {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import {
|
||||
Gesture as IonGesture
|
||||
} from './components/gesture/gesture';
|
||||
@ -2295,7 +2265,7 @@ declare global {
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonRippleEffectAttributes extends HTMLAttributes {
|
||||
|
||||
useTapClick?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2449,7 +2419,6 @@ declare global {
|
||||
namespace JSXElements {
|
||||
export interface IonScrollAttributes extends HTMLAttributes {
|
||||
enabled?: boolean;
|
||||
jsScroll?: boolean;
|
||||
onionScroll?: ScrollCallback;
|
||||
onionScrollEnd?: ScrollCallback;
|
||||
onionScrollStart?: ScrollCallback;
|
||||
@ -2970,6 +2939,36 @@ declare global {
|
||||
}
|
||||
|
||||
|
||||
import {
|
||||
TapClick as IonTapClick
|
||||
} from './components/tap-click/tap-click';
|
||||
|
||||
declare global {
|
||||
interface HTMLIonTapClickElement extends IonTapClick, HTMLElement {
|
||||
}
|
||||
var HTMLIonTapClickElement: {
|
||||
prototype: HTMLIonTapClickElement;
|
||||
new (): HTMLIonTapClickElement;
|
||||
};
|
||||
interface HTMLElementTagNameMap {
|
||||
"ion-tap-click": HTMLIonTapClickElement;
|
||||
}
|
||||
interface ElementTagNameMap {
|
||||
"ion-tap-click": HTMLIonTapClickElement;
|
||||
}
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"ion-tap-click": JSXElements.IonTapClickAttributes;
|
||||
}
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonTapClickAttributes extends HTMLAttributes {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import {
|
||||
Text as IonText
|
||||
} from './components/text/text';
|
||||
|
@ -3,6 +3,7 @@ import { Config, Nav, NavContainer } from '../../index';
|
||||
import { isReady } from '../../utils/helpers';
|
||||
|
||||
const rootNavs = new Map<number, NavContainer>();
|
||||
const ACTIVE_SCROLLING_TIME = 100;
|
||||
|
||||
@Component({
|
||||
tag: 'ion-app',
|
||||
@ -16,6 +17,9 @@ const rootNavs = new Map<number, NavContainer>();
|
||||
})
|
||||
export class App {
|
||||
|
||||
private didScroll = false;
|
||||
private scrollTime = 0;
|
||||
|
||||
@Element() element: HTMLElement;
|
||||
|
||||
@State() modeCode: string;
|
||||
@ -27,7 +31,7 @@ export class App {
|
||||
componentWillLoad() {
|
||||
this.modeCode = this.config.get('mode');
|
||||
this.useRouter = this.config.getBoolean('useRouter', false);
|
||||
this.hoverCSS = this.config.getBoolean('hoverCSS', true);
|
||||
this.hoverCSS = this.config.getBoolean('hoverCSS', false);
|
||||
}
|
||||
|
||||
@Listen('body:navInit')
|
||||
@ -35,7 +39,6 @@ export class App {
|
||||
rootNavs.set((event.detail as Nav).navId, (event.detail as Nav));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of top level Navs
|
||||
*/
|
||||
@ -48,16 +51,36 @@ export class App {
|
||||
return navs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the app is currently scrolling
|
||||
*/
|
||||
@Method() isScrolling(): boolean {
|
||||
// TODO - sync with Manu
|
||||
return false;
|
||||
@Method()
|
||||
isEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Method() getActiveNavs(rootNavId?: number): NavContainer[] {
|
||||
/**
|
||||
* Boolean if the app is actively scrolling or not.
|
||||
* @return {boolean} returns true or false
|
||||
*/
|
||||
@Method()
|
||||
isScrolling(): boolean {
|
||||
const scrollTime = this.scrollTime;
|
||||
if (scrollTime === 0) {
|
||||
return false;
|
||||
}
|
||||
if (scrollTime < Date.now()) {
|
||||
this.scrollTime = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Method()
|
||||
setScrolling() {
|
||||
this.scrollTime = Date.now() + ACTIVE_SCROLLING_TIME;
|
||||
this.didScroll = true;
|
||||
}
|
||||
|
||||
@Method()
|
||||
getActiveNavs(rootNavId?: number): NavContainer[] {
|
||||
/*const portal = portals.get(PORTAL_MODAL);
|
||||
if (portal && portal.views && portal.views.length) {
|
||||
return findTopNavs(portal);
|
||||
@ -81,7 +104,8 @@ export class App {
|
||||
return activeNavs;
|
||||
}
|
||||
|
||||
@Method() getNavByIdOrName(nameOrId: number | string) {
|
||||
@Method()
|
||||
getNavByIdOrName(nameOrId: number | string) {
|
||||
const navs = Array.from(rootNavs.values());
|
||||
for (const navContainer of navs) {
|
||||
const match = getNavByIdOrNameImpl(navContainer, nameOrId);
|
||||
@ -102,8 +126,9 @@ export class App {
|
||||
}
|
||||
|
||||
render() {
|
||||
const dom = [<slot></slot>];
|
||||
const dom = [<ion-tap-click />, <slot></slot>];
|
||||
if (this.useRouter) {
|
||||
|
||||
// dom.push(<ion-router-controller></ion-router-controller>);
|
||||
}
|
||||
return dom;
|
||||
|
@ -18,9 +18,15 @@
|
||||
Returns an array of top level Navs
|
||||
|
||||
|
||||
#### isEnabled()
|
||||
|
||||
|
||||
#### isScrolling()
|
||||
|
||||
Check if the app is currently scrolling
|
||||
Boolean if the app is actively scrolling or not.
|
||||
|
||||
|
||||
#### setScrolling()
|
||||
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
background-color: $button-ios-background-color-focused;
|
||||
}
|
||||
|
||||
.button-ios:hover:not(.disable-hover) {
|
||||
.enable-hover .button-ios:hover {
|
||||
opacity: $button-ios-opacity-hover;
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@
|
||||
background-color: $button-ios-clear-background-color-focused;
|
||||
}
|
||||
|
||||
.button-clear-ios:hover:not(.disable-hover) {
|
||||
.enable-hover .button-clear-ios:hover {
|
||||
color: $button-ios-clear-text-color-hover;
|
||||
opacity: $button-ios-clear-opacity-hover;
|
||||
}
|
||||
@ -196,7 +196,7 @@
|
||||
background-color: $bg-color-focused;
|
||||
}
|
||||
|
||||
.button-clear-ios-#{$color-name}:hover:not(.disable-hover) {
|
||||
.enable-hover .button-clear-ios-#{$color-name}:hover {
|
||||
color: $fg-color;
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
color $button-md-transition-duration $button-md-transition-timing-function;
|
||||
}
|
||||
|
||||
.button-md:hover:not(.disable-hover) {
|
||||
.enable-hover .button-md:hover {
|
||||
background-color: $button-md-background-color-hover;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
.button-md-#{$color-name}:hover:not(.disable-hover) {
|
||||
.enable-hover .button-md-#{$color-name}:hover {
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@
|
||||
box-shadow: $button-md-outline-box-shadow;
|
||||
}
|
||||
|
||||
.button-outline-md:hover:not(.disable-hover) {
|
||||
.enable-hover .button-outline-md:hover {
|
||||
background-color: $button-md-outline-background-color-hover;
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@
|
||||
background-color: $button-md-outline-background-color;
|
||||
}
|
||||
|
||||
.button-outline-md-#{$color-name}:hover:not(.disable-hover) {
|
||||
.enable-hover .button-outline-md-#{$color-name}:hover {
|
||||
background-color: $button-md-outline-background-color-hover;
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@
|
||||
background-color: $button-md-clear-background-color-focused;
|
||||
}
|
||||
|
||||
.button-clear-md:hover:not(.disable-hover) {
|
||||
.enable-hover .button-clear-md:hover {
|
||||
background-color: $button-md-clear-background-color-hover;
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@
|
||||
background-color: $bg-color-focused;
|
||||
}
|
||||
|
||||
.button-clear-md-#{$color-name}:hover:not(.disable-hover) {
|
||||
.enable-hover .button-clear-md-#{$color-name}:hover {
|
||||
color: $fg-color;
|
||||
}
|
||||
}
|
||||
|
@ -111,18 +111,16 @@ export class Button {
|
||||
strong
|
||||
} = this;
|
||||
|
||||
const elementClasses: string[] = []
|
||||
.concat(
|
||||
getButtonClassList(buttonType, mode),
|
||||
getClassList(buttonType, expand, mode),
|
||||
getClassList(buttonType, size, mode),
|
||||
getClassList(buttonType, round ? 'round' : null, mode),
|
||||
getClassList(buttonType, strong ? 'strong' : null, mode),
|
||||
getColorClassList(buttonType, color, fill, mode),
|
||||
);
|
||||
const elementClasses = [
|
||||
...getButtonClassList(buttonType, mode),
|
||||
...getClassList(buttonType, expand, mode),
|
||||
...getClassList(buttonType, size, mode),
|
||||
...getClassList(buttonType, round ? 'round' : null, mode),
|
||||
...getClassList(buttonType, strong ? 'strong' : null, mode),
|
||||
...getColorClassList(buttonType, color, fill, mode),
|
||||
];
|
||||
|
||||
const TagType = this.href ? 'a' : 'button';
|
||||
|
||||
const buttonClasses = {
|
||||
...getElementClassObject(this.el.classList),
|
||||
...getElementClassObject(elementClasses),
|
||||
@ -143,7 +141,7 @@ export class Button {
|
||||
<slot></slot>
|
||||
<slot name='end'></slot>
|
||||
</span>
|
||||
{ this.mode === 'md' && <ion-ripple-effect /> }
|
||||
{ this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> }
|
||||
</TagType>
|
||||
);
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export class ChipButton {
|
||||
<span class='button-inner'>
|
||||
<slot></slot>
|
||||
</span>
|
||||
{ this.mode === 'md' && <ion-ripple-effect /> }
|
||||
{ this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> }
|
||||
</TagType>
|
||||
);
|
||||
}
|
||||
|
@ -69,11 +69,6 @@ export class Content {
|
||||
};
|
||||
}
|
||||
|
||||
@Method()
|
||||
enableJsScroll() {
|
||||
this.scrollEl.jsScroll = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the top of the content component.
|
||||
*
|
||||
|
@ -80,9 +80,6 @@ Emitted when the scrolling first starts.
|
||||
|
||||
## Methods
|
||||
|
||||
#### enableJsScroll()
|
||||
|
||||
|
||||
#### scrollToBottom()
|
||||
|
||||
Scroll to the bottom of the content component.
|
||||
|
@ -139,7 +139,7 @@ export class FabButton {
|
||||
<span class='button-inner'>
|
||||
<slot></slot>
|
||||
</span>
|
||||
{ this.mode === 'md' && <ion-ripple-effect /> }
|
||||
{ this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> }
|
||||
</TagType>
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { Component } from '@stencil/core';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-gesture-controller'
|
||||
})
|
||||
export class GestureController {
|
||||
private gestureId = 0;
|
||||
private requestedStart: { [eventId: number]: number } = {};
|
||||
private disabledGestures: { [eventName: string]: Set<number> } = {};
|
||||
private disabledScroll: Set<number> = new Set<number>();
|
||||
private capturedId: number = null;
|
||||
|
||||
private gestureId = 0;
|
||||
private requestedStart = new Map<number, number>();
|
||||
private disabledGestures = new Map<string, Set<number>>();
|
||||
private disabledScroll = new Set<number>();
|
||||
private capturedId: number = null;
|
||||
|
||||
createGesture(gestureName: string, gesturePriority: number, disableScroll: boolean): GestureDelegate {
|
||||
return new GestureDelegate(this, this.newID(), gestureName, gesturePriority, disableScroll);
|
||||
@ -29,11 +24,10 @@ export class GestureController {
|
||||
|
||||
start(gestureName: string, id: number, priority: number): boolean {
|
||||
if (!this.canStart(gestureName)) {
|
||||
delete this.requestedStart[id];
|
||||
this.requestedStart.delete(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.requestedStart[id] = priority;
|
||||
this.requestedStart.set(id, priority);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -43,22 +37,22 @@ export class GestureController {
|
||||
}
|
||||
const requestedStart = this.requestedStart;
|
||||
let maxPriority = -10000;
|
||||
for (const gestureID in requestedStart) {
|
||||
maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
|
||||
for (const value of requestedStart.values()) {
|
||||
maxPriority = Math.max(maxPriority, value);
|
||||
}
|
||||
|
||||
if (maxPriority === priority) {
|
||||
this.capturedId = id;
|
||||
this.requestedStart = {};
|
||||
this.requestedStart.clear();
|
||||
return true;
|
||||
}
|
||||
delete requestedStart[id];
|
||||
requestedStart.delete(id);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
release(id: number) {
|
||||
delete this.requestedStart[id];
|
||||
this.requestedStart.delete(id);
|
||||
|
||||
if (this.capturedId && id === this.capturedId) {
|
||||
this.capturedId = null;
|
||||
@ -66,16 +60,16 @@ export class GestureController {
|
||||
}
|
||||
|
||||
disableGesture(gestureName: string, id: number) {
|
||||
let set = this.disabledGestures[gestureName];
|
||||
let set = this.disabledGestures.get(gestureName);
|
||||
if (!set) {
|
||||
set = new Set<number>();
|
||||
this.disabledGestures[gestureName] = set;
|
||||
this.disabledGestures.set(gestureName, set);
|
||||
}
|
||||
set.add(id);
|
||||
}
|
||||
|
||||
enableGesture(gestureName: string, id: number) {
|
||||
const set = this.disabledGestures[gestureName];
|
||||
const set = this.disabledGestures.get(gestureName);
|
||||
if (set) {
|
||||
set.delete(id);
|
||||
}
|
||||
@ -121,7 +115,7 @@ export class GestureController {
|
||||
}
|
||||
|
||||
isDisabled(gestureName: string): boolean {
|
||||
const disabled = this.disabledGestures[gestureName];
|
||||
const disabled = this.disabledGestures.get(gestureName);
|
||||
if (disabled && disabled.size > 0) {
|
||||
return true;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export class Item {
|
||||
</div>
|
||||
<slot name='end'></slot>
|
||||
</div>
|
||||
{ this.href && this.mode === 'md' && <ion-ripple-effect /> }
|
||||
{ this.href && this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> }
|
||||
</TagType>
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,25 @@
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
#### useTapClick
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
#### useTapClick
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
#### addRipple()
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Element, Listen, Prop } from '@stencil/core';
|
||||
import { Component, Element, EventListenerEnable, Listen, Method, Prop } from '@stencil/core';
|
||||
import { now } from '../../utils/helpers';
|
||||
import { DomController } from '../../global/dom-controller';
|
||||
|
||||
@ -12,15 +12,23 @@ export class RippleEffect {
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
@Prop({context: 'dom'}) dom: DomController;
|
||||
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
|
||||
|
||||
@Listen('touchstart')
|
||||
@Prop() useTapClick = false;
|
||||
|
||||
@Listen('parent:ionActivated', {enabled: false})
|
||||
ionActivated(ev: CustomEvent) {
|
||||
this.addRipple(ev.detail.x, ev.detail.y);
|
||||
}
|
||||
|
||||
@Listen('touchstart', {passive: true, enabled: false})
|
||||
touchStart(ev: TouchEvent) {
|
||||
this.lastClick = now(ev);
|
||||
const touches = ev.touches[0];
|
||||
this.addRipple(touches.clientX, touches.clientY);
|
||||
}
|
||||
|
||||
@Listen('mousedown')
|
||||
@Listen('mousedown', {passive: true, enabled: false})
|
||||
mouseDown(ev: MouseEvent) {
|
||||
const timeStamp = now(ev);
|
||||
if (this.lastClick < (timeStamp - 1000)) {
|
||||
@ -28,7 +36,17 @@ export class RippleEffect {
|
||||
}
|
||||
}
|
||||
|
||||
private addRipple(pageX: number, pageY: number) {
|
||||
componentDidLoad() {
|
||||
if (this.useTapClick) {
|
||||
this.enableListener(this, 'parent:ionActivated', true);
|
||||
} else {
|
||||
this.enableListener(this, 'touchstart', true);
|
||||
this.enableListener(this, 'mousedown', true);
|
||||
}
|
||||
}
|
||||
|
||||
@Method()
|
||||
addRipple(pageX: number, pageY: number) {
|
||||
let x: number, y: number, size: number;
|
||||
|
||||
this.dom.read(() => {
|
||||
|
@ -12,11 +12,6 @@
|
||||
boolean
|
||||
|
||||
|
||||
#### jsScroll
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### onionScroll
|
||||
|
||||
any
|
||||
@ -39,11 +34,6 @@ any
|
||||
boolean
|
||||
|
||||
|
||||
#### jsScroll
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### onionScroll
|
||||
|
||||
any
|
||||
|
@ -12,10 +12,9 @@ export class Scroll {
|
||||
|
||||
private gesture: GestureDelegate;
|
||||
private positions: number[] = [];
|
||||
private _l: number;
|
||||
private _t: number;
|
||||
private tmr: any;
|
||||
private queued = false;
|
||||
private app: HTMLIonAppElement;
|
||||
|
||||
isScrolling = false;
|
||||
detail: ScrollDetail = {};
|
||||
@ -27,14 +26,6 @@ export class Scroll {
|
||||
@Prop({ context: 'isServer' }) isServer: boolean;
|
||||
|
||||
@Prop() enabled = true;
|
||||
@Prop() jsScroll = false;
|
||||
|
||||
@Watch('jsScroll')
|
||||
jsScrollChanged(js: boolean) {
|
||||
if (js) {
|
||||
throw new Error('jsScroll: TODO!');
|
||||
}
|
||||
}
|
||||
|
||||
@Prop() onionScrollStart: ScrollCallback;
|
||||
@Prop() onionScroll: ScrollCallback;
|
||||
@ -48,7 +39,7 @@ export class Scroll {
|
||||
/**
|
||||
* @output {ScrollEvent} Emitted while scrolling.
|
||||
*/
|
||||
@Event() ionScroll: EventEmitter;
|
||||
@Event({bubbles: false}) ionScroll: EventEmitter;
|
||||
|
||||
/**
|
||||
* @output {ScrollEvent} Emitted when the scroll has ended.
|
||||
@ -62,6 +53,7 @@ export class Scroll {
|
||||
|
||||
const gestureCtrl = Ionic.gesture = Ionic.gesture || new GestureController();
|
||||
this.gesture = gestureCtrl.createGesture('scroll', 100, false);
|
||||
this.app = this.el.closest('ion-app') as HTMLIonAppElement;
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
@ -69,6 +61,20 @@ export class Scroll {
|
||||
this.gesture = this.detail = this.detail.event = null;
|
||||
}
|
||||
|
||||
// Native Scroll *************************
|
||||
|
||||
@Listen('scroll', { passive: true })
|
||||
onNativeScroll() {
|
||||
if (!this.queued) {
|
||||
this.queued = true;
|
||||
|
||||
this.dom.read(timeStamp => {
|
||||
this.queued = false;
|
||||
this.onScroll(timeStamp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Method()
|
||||
scrollToTop(duration: number): Promise<void> {
|
||||
return this.scrollToPoint(0, 0, duration);
|
||||
@ -106,8 +112,8 @@ export class Scroll {
|
||||
}
|
||||
|
||||
if (duration < 32) {
|
||||
self.setTop(y);
|
||||
self.setLeft(x);
|
||||
el.scrollTop = y;
|
||||
el.scrollLeft = x;
|
||||
done();
|
||||
return promise;
|
||||
}
|
||||
@ -139,11 +145,11 @@ export class Scroll {
|
||||
const easedT = (--time) * time * time + 1;
|
||||
|
||||
if (fromY !== y) {
|
||||
self.setTop((easedT * (y - fromY)) + fromY);
|
||||
el.scrollTop = (easedT * (y - fromY)) + fromY;
|
||||
}
|
||||
|
||||
if (fromX !== x) {
|
||||
self.setLeft(Math.floor((easedT * (x - fromX)) + fromX));
|
||||
el.scrollLeft = Math.floor((easedT * (x - fromX)) + fromX);
|
||||
}
|
||||
|
||||
if (easedT < 1) {
|
||||
@ -173,33 +179,23 @@ export class Scroll {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Native Scroll *************************
|
||||
|
||||
@Listen('scroll', { passive: true })
|
||||
protected onNativeScroll() {
|
||||
if (!this.queued) {
|
||||
this.queued = true;
|
||||
|
||||
this.dom.read(timeStamp => {
|
||||
this.queued = false;
|
||||
this.onScroll(timeStamp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onScroll(timeStamp: number) {
|
||||
const detail = this.detail;
|
||||
const positions = this.positions;
|
||||
const el = this.el;
|
||||
if (this.app) {
|
||||
this.app.setScrolling();
|
||||
}
|
||||
|
||||
detail.timeStamp = timeStamp;
|
||||
|
||||
// get the current scrollTop
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollTop = this.getTop();
|
||||
detail.scrollTop = el.scrollTop;
|
||||
|
||||
// get the current scrollLeft
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollLeft = this.getLeft();
|
||||
detail.scrollLeft = el.scrollLeft;
|
||||
|
||||
if (!this.isScrolling) {
|
||||
// currently not scrolling, so this is a scroll start
|
||||
@ -215,9 +211,8 @@ export class Scroll {
|
||||
// emit only on the first scroll event
|
||||
if (this.onionScrollStart) {
|
||||
this.onionScrollStart(detail);
|
||||
} else {
|
||||
this.ionScrollStart.emit(detail);
|
||||
}
|
||||
this.ionScrollStart.emit(detail);
|
||||
}
|
||||
|
||||
// actively scrolling
|
||||
@ -270,7 +265,6 @@ export class Scroll {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private onEnd(timeStamp: number) {
|
||||
const detail = this.detail;
|
||||
|
||||
@ -279,49 +273,8 @@ export class Scroll {
|
||||
// emit that the scroll has ended
|
||||
if (this.onionScrollEnd) {
|
||||
this.onionScrollEnd(detail);
|
||||
} else {
|
||||
this.ionScrollEnd.emit(detail);
|
||||
}
|
||||
}
|
||||
|
||||
/** DOM READ */
|
||||
private getTop() {
|
||||
if (this.jsScroll) {
|
||||
return this._t;
|
||||
}
|
||||
return this._t = this.el.scrollTop;
|
||||
}
|
||||
|
||||
/** DOM READ */
|
||||
private getLeft() {
|
||||
if (this.jsScroll) {
|
||||
return 0;
|
||||
}
|
||||
return this._l = this.el.scrollLeft;
|
||||
}
|
||||
|
||||
/** DOM WRITE */
|
||||
private setTop(top: number) {
|
||||
this._t = top;
|
||||
|
||||
if (this.jsScroll) {
|
||||
this.el.style.transform = this.el.style.webkitTransform = `translate3d(${this._l * -1}px,${top * -1}px,0px)`;
|
||||
|
||||
} else {
|
||||
this.el.scrollTop = top;
|
||||
}
|
||||
}
|
||||
|
||||
/** DOM WRITE */
|
||||
private setLeft(left: number) {
|
||||
this._l = left;
|
||||
|
||||
if (this.jsScroll) {
|
||||
this.el.style.transform = this.el.style.webkitTransform = `translate3d(${left * -1}px,${this._t * -1}px,0px)`;
|
||||
|
||||
} else {
|
||||
this.el.scrollLeft = left;
|
||||
}
|
||||
this.ionScrollEnd.emit(detail);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -149,7 +149,7 @@
|
||||
color: $color-base;
|
||||
}
|
||||
|
||||
.searchbar-ios-#{$color-name} .searchbar-ios-cancel:hover:not(.disable-hover) {
|
||||
.enable-hover .searchbar-ios-#{$color-name} .searchbar-ios-cancel:hover {
|
||||
color: color-shade($color-base);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
fill: $tabs-ios-tab-icon-color;
|
||||
}
|
||||
|
||||
.tabbar-ios ion-tab-button:hover:not(.disable-hover),
|
||||
.enable-hover .tabbar-ios ion-tab-button:hover,
|
||||
.tabbar-ios .tab-selected {
|
||||
color: $tabs-ios-tab-text-color-active;
|
||||
|
||||
|
@ -145,7 +145,7 @@
|
||||
fill: rgba($color-contrast, $tabs-md-tab-opacity);
|
||||
}
|
||||
|
||||
.tabbar-md-#{$color-name} ion-tab-button:hover:not(.disable-hover),
|
||||
.enable-hover .tabbar-md-#{$color-name} ion-tab-button:hover,
|
||||
.tabbar-md-#{$color-name} ion-tab-button.tab-selected {
|
||||
color: $color-contrast;
|
||||
|
||||
|
11
packages/core/src/components/tap-click/readme.md
Normal file
11
packages/core/src/components/tap-click/readme.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ion-tap-click
|
||||
|
||||
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built by [StencilJS](https://stenciljs.com/)*
|
1
packages/core/src/components/tap-click/tap-click.scss
Normal file
1
packages/core/src/components/tap-click/tap-click.scss
Normal file
@ -0,0 +1 @@
|
||||
@import "../../themes/ionic.globals";
|
190
packages/core/src/components/tap-click/tap-click.tsx
Normal file
190
packages/core/src/components/tap-click/tap-click.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import { Component, Element, EventListenerEnable, Listen, Prop } from '@stencil/core';
|
||||
import { now, pointerCoordX, pointerCoordY } from '../../utils/helpers';
|
||||
import { GestureController } from '../gesture-controller/gesture-controller';
|
||||
|
||||
declare const Ionic: { gesture: GestureController };
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-tap-click',
|
||||
styleUrl: 'tap-click.scss'
|
||||
})
|
||||
export class TapClick {
|
||||
|
||||
private app: HTMLIonAppElement;
|
||||
private lastTouch = 0;
|
||||
private lastActivated = 0;
|
||||
|
||||
private gestureCtrl: GestureController;
|
||||
|
||||
private activatableEle: HTMLElement;
|
||||
private activeDefer: any;
|
||||
|
||||
private clearDefers = new WeakMap<HTMLElement, any>();
|
||||
|
||||
passive = true;
|
||||
attachTo = 'document';
|
||||
|
||||
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
|
||||
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
componentDidLoad() {
|
||||
this.gestureCtrl = Ionic.gesture = Ionic.gesture || new GestureController();
|
||||
|
||||
this.app = this.el.closest('ion-app') as HTMLIonAppElement;
|
||||
}
|
||||
|
||||
@Listen('document:click', {passive: false, capture: true})
|
||||
onBodyClick(ev: any) {
|
||||
if (this.shouldCancel()) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
// Touch Events
|
||||
@Listen('document:touchstart', { passive: true })
|
||||
onTouchStart(ev: TouchEvent) {
|
||||
this.lastTouch = now(ev);
|
||||
this.pointerDown(ev);
|
||||
}
|
||||
|
||||
@Listen('document:touchcancel', { passive: true })
|
||||
onTouchCancel(ev: TouchEvent) {
|
||||
this.lastTouch = now(ev);
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
|
||||
@Listen('document:touchend', { passive: true })
|
||||
onTouchEnd(ev: TouchEvent) {
|
||||
this.lastTouch = now(ev);
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
|
||||
@Listen('document:mousedown', { passive: true })
|
||||
onMouseDown(ev: MouseEvent) {
|
||||
const t = now(ev);
|
||||
if (this.lastTouch < t - MOUSE_WAIT) {
|
||||
this.pointerDown(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('document:mouseup', { passive: true })
|
||||
onMouseUp(ev: TouchEvent) {
|
||||
const t = now(ev);
|
||||
if (this.lastTouch < t - MOUSE_WAIT) {
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('body:ionScrollStart')
|
||||
scrollStarted() {
|
||||
clearTimeout(this.activeDefer);
|
||||
if (this.activatableEle) {
|
||||
this.removeActivated(false);
|
||||
this.activatableEle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private pointerDown(ev: any) {
|
||||
if (this.activatableEle) {
|
||||
return;
|
||||
}
|
||||
if (!this.shouldCancel()) {
|
||||
this.setActivatedElement(getActivatableTarget(ev.target), ev);
|
||||
}
|
||||
}
|
||||
|
||||
private pointerUp(ev: UIEvent) {
|
||||
this.setActivatedElement(null, ev);
|
||||
}
|
||||
|
||||
private setActivatedElement(el: HTMLElement, ev: UIEvent) {
|
||||
// do nothing
|
||||
const activatableEle = this.activatableEle;
|
||||
if (el && el === activatableEle) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.activeDefer);
|
||||
this.activeDefer = null;
|
||||
|
||||
const eventX = pointerCoordX(ev);
|
||||
const eventY = pointerCoordY(ev);
|
||||
|
||||
// unactivate selected
|
||||
if (activatableEle) {
|
||||
if (this.clearDefers.has(activatableEle)) {
|
||||
throw new Error('internal error');
|
||||
}
|
||||
if (!activatableEle.classList.contains(ACTIVATED)) {
|
||||
this.addActivated(activatableEle, eventX, eventY);
|
||||
}
|
||||
this.removeActivated(true);
|
||||
}
|
||||
|
||||
// activate
|
||||
if (el) {
|
||||
const deferId = this.clearDefers.get(el);
|
||||
if (deferId) {
|
||||
clearTimeout(deferId);
|
||||
this.clearDefers.delete(el);
|
||||
}
|
||||
|
||||
el.classList.remove(ACTIVATED);
|
||||
this.activeDefer = setTimeout(() => {
|
||||
this.addActivated(el, eventX, eventY);
|
||||
this.activeDefer = null;
|
||||
}, ADD_ACTIVATED_DEFERS);
|
||||
}
|
||||
this.activatableEle = el;
|
||||
}
|
||||
|
||||
private addActivated(el: HTMLElement, x: number, y: number) {
|
||||
this.lastActivated = Date.now();
|
||||
el.classList.add(ACTIVATED);
|
||||
|
||||
const event = new CustomEvent('ionActivated', {
|
||||
bubbles: false,
|
||||
detail: {x, y}
|
||||
});
|
||||
el.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private removeActivated(smooth: boolean) {
|
||||
const activatableEle = this.activatableEle;
|
||||
|
||||
const time = CLEAR_STATE_DEFERS - Date.now() + this.lastActivated;
|
||||
if (smooth && time > 0) {
|
||||
const deferId = setTimeout(() => {
|
||||
activatableEle.classList.remove(ACTIVATED);
|
||||
this.clearDefers.delete(activatableEle);
|
||||
}, CLEAR_STATE_DEFERS);
|
||||
this.clearDefers.set(activatableEle, deferId);
|
||||
} else {
|
||||
activatableEle.classList.remove(ACTIVATED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private shouldCancel(): boolean {
|
||||
if (!this.app.isEnabled()) {
|
||||
console.debug('click prevent: appDisabled');
|
||||
return true;
|
||||
}
|
||||
if (this.gestureCtrl.isCaptured()) {
|
||||
console.debug('click prevent: tap-click (gesture is captured)');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getActivatableTarget(el: HTMLElement): any {
|
||||
return el.closest('a,button,[tappable]');
|
||||
}
|
||||
|
||||
const ACTIVATED = 'activated';
|
||||
const ADD_ACTIVATED_DEFERS = 200;
|
||||
const CLEAR_STATE_DEFERS = 200;
|
||||
const MOUSE_WAIT = 2500;
|
70
packages/core/src/components/tap-click/test/basic/index.html
Normal file
70
packages/core/src/components/tap-click/test/basic/index.html
Normal file
@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Button Effect - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
|
||||
<style>
|
||||
.my-block {
|
||||
position: relative;
|
||||
background: blue;
|
||||
color: white;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
margin: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Button Effect - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding no-bounce>
|
||||
<p>
|
||||
<ion-button size="small">Small</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="outline">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="clear">Large</ion-button>
|
||||
</p>
|
||||
<div class="my-block">
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
This is just a div + effect behind
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
</div>
|
||||
<div class="my-block">
|
||||
This is just a div + effect on top
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
|
||||
<div class="my-block">
|
||||
This is just a div + effect
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</ion-app>
|
||||
<script>
|
||||
function blockClicked() {
|
||||
console.log('block clicked');
|
||||
return true;
|
||||
}
|
||||
function buttonClicked() {
|
||||
console.log('button clicked');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -159,15 +159,17 @@
|
||||
color: $color-base;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
color: $color-base;
|
||||
}
|
||||
|
||||
&.activated {
|
||||
opacity: .4;
|
||||
}
|
||||
}
|
||||
|
||||
.enable-hover .bar-button-#{$color-name}-ios:hover,
|
||||
.enable-hover .bar-button-clear-ios-#{$color-name}:hover,
|
||||
.enable-hover .bar-button-ios-#{$color-name}:hover {
|
||||
color: $color-base;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// iOS Toolbar Button Icon
|
||||
@ -188,16 +190,16 @@
|
||||
color: $toolbar-ios-button-color;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.activated {
|
||||
color: color-contrast($colors-ios, $toolbar-ios-button-color);
|
||||
background-color: $toolbar-ios-button-color;
|
||||
}
|
||||
}
|
||||
|
||||
.enable-hover .bar-button-outline-ios:hover {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@mixin ios-bar-button-outline($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.bar-button-outline-ios-#{$color-name} {
|
||||
@ -222,11 +224,6 @@
|
||||
color: color-contrast($colors-ios, $toolbar-ios-button-color);
|
||||
background-color: $toolbar-ios-button-color;
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
color: color-contrast($colors-ios, $toolbar-ios-button-color);
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.activated {
|
||||
color: color-contrast($colors-ios, $toolbar-ios-button-color);
|
||||
background-color: color-shade($toolbar-ios-button-color);
|
||||
@ -234,6 +231,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.enable-hover .bar-button-solid-ios:hover {
|
||||
color: color-contrast($colors-ios, $toolbar-ios-button-color);
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@mixin ios-bar-button-solid($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.bar-button-solid-ios-#{$color-name} {
|
||||
|
@ -167,10 +167,12 @@
|
||||
.bar-button-md-#{$color-name} {
|
||||
color: $color-base;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
color: $color-base;
|
||||
}
|
||||
.enable-hover .bar-button-#{$color-name}-md:hover,
|
||||
.enable-hover .bar-button-clear-md-#{$color-name}:hover,
|
||||
.enable-hover .bar-button-md-#{$color-name}:hover {
|
||||
color: $color-base;
|
||||
}
|
||||
|
||||
}
|
||||
@ -194,10 +196,6 @@
|
||||
color: $toolbar-md-button-color;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.activated {
|
||||
background-color: transparent;
|
||||
}
|
||||
@ -207,6 +205,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.enable-hover .bar-button-outline-md:hover {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@mixin md-bar-button-outline($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.bar-button-outline-md-#{$color-name} {
|
||||
@ -234,16 +236,18 @@
|
||||
color: color-contrast($colors-md, $toolbar-md-button-color, md);
|
||||
background-color: $toolbar-md-button-color;
|
||||
|
||||
&:hover:not(.disable-hover) {
|
||||
color: color-contrast($colors-md, $toolbar-md-button-color, md);
|
||||
}
|
||||
|
||||
&.activated {
|
||||
color: color-contrast($colors-md, $toolbar-md-button-color, md);
|
||||
background-color: color-shade($toolbar-md-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
.enable-hover .bar-button-solid-md:hover {
|
||||
color: color-contrast($colors-md, $toolbar-md-button-color, md);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@mixin md-bar-button-solid($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.bar-button-solid-md-#{$color-name} {
|
||||
|
Reference in New Issue
Block a user