diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index fae3e689dd..0753a141cc 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -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'; diff --git a/packages/core/src/components/app/app.tsx b/packages/core/src/components/app/app.tsx index 8b3a4e7e98..8f52b488e9 100644 --- a/packages/core/src/components/app/app.tsx +++ b/packages/core/src/components/app/app.tsx @@ -3,6 +3,7 @@ import { Config, Nav, NavContainer } from '../../index'; import { isReady } from '../../utils/helpers'; const rootNavs = new Map(); +const ACTIVE_SCROLLING_TIME = 100; @Component({ tag: 'ion-app', @@ -16,6 +17,9 @@ const rootNavs = new Map(); }) 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 = []; + const dom = [, ]; if (this.useRouter) { + // dom.push(); } return dom; diff --git a/packages/core/src/components/app/readme.md b/packages/core/src/components/app/readme.md index b439e4c533..866b9dfbf6 100644 --- a/packages/core/src/components/app/readme.md +++ b/packages/core/src/components/app/readme.md @@ -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() diff --git a/packages/core/src/components/button/button.ios.scss b/packages/core/src/components/button/button.ios.scss index 5c4b91ccc5..4ba5404abe 100644 --- a/packages/core/src/components/button/button.ios.scss +++ b/packages/core/src/components/button/button.ios.scss @@ -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; } } diff --git a/packages/core/src/components/button/button.md.scss b/packages/core/src/components/button/button.md.scss index 04bc54f7bb..87f8839e4c 100644 --- a/packages/core/src/components/button/button.md.scss +++ b/packages/core/src/components/button/button.md.scss @@ -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; } } diff --git a/packages/core/src/components/button/button.tsx b/packages/core/src/components/button/button.tsx index 61b51c98f6..9d339e2e17 100644 --- a/packages/core/src/components/button/button.tsx +++ b/packages/core/src/components/button/button.tsx @@ -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 { - { this.mode === 'md' && } + { this.mode === 'md' && } ); } diff --git a/packages/core/src/components/chip-button/chip-button.tsx b/packages/core/src/components/chip-button/chip-button.tsx index 22b18259cd..78ffb23adc 100644 --- a/packages/core/src/components/chip-button/chip-button.tsx +++ b/packages/core/src/components/chip-button/chip-button.tsx @@ -101,7 +101,7 @@ export class ChipButton { - { this.mode === 'md' && } + { this.mode === 'md' && } ); } diff --git a/packages/core/src/components/content/content.tsx b/packages/core/src/components/content/content.tsx index 91e0cb3646..443353f809 100644 --- a/packages/core/src/components/content/content.tsx +++ b/packages/core/src/components/content/content.tsx @@ -69,11 +69,6 @@ export class Content { }; } - @Method() - enableJsScroll() { - this.scrollEl.jsScroll = true; - } - /** * Scroll to the top of the content component. * diff --git a/packages/core/src/components/content/readme.md b/packages/core/src/components/content/readme.md index dac76ace04..374ad9ed6b 100644 --- a/packages/core/src/components/content/readme.md +++ b/packages/core/src/components/content/readme.md @@ -80,9 +80,6 @@ Emitted when the scrolling first starts. ## Methods -#### enableJsScroll() - - #### scrollToBottom() Scroll to the bottom of the content component. diff --git a/packages/core/src/components/fab-button/fab-button.tsx b/packages/core/src/components/fab-button/fab-button.tsx index e4806d16b1..3b3fb6e352 100755 --- a/packages/core/src/components/fab-button/fab-button.tsx +++ b/packages/core/src/components/fab-button/fab-button.tsx @@ -139,7 +139,7 @@ export class FabButton { - { this.mode === 'md' && } + { this.mode === 'md' && } ); } diff --git a/packages/core/src/components/gesture-controller/gesture-controller.ts b/packages/core/src/components/gesture-controller/gesture-controller.ts index 2ed7185807..ea120e1904 100644 --- a/packages/core/src/components/gesture-controller/gesture-controller.ts +++ b/packages/core/src/components/gesture-controller/gesture-controller.ts @@ -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 } = {}; - private disabledScroll: Set = new Set(); - private capturedId: number = null; + private gestureId = 0; + private requestedStart = new Map(); + private disabledGestures = new Map>(); + private disabledScroll = new Set(); + 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(); - 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; } diff --git a/packages/core/src/components/item/item.tsx b/packages/core/src/components/item/item.tsx index e90c1b8f7a..8fac195f71 100644 --- a/packages/core/src/components/item/item.tsx +++ b/packages/core/src/components/item/item.tsx @@ -99,7 +99,7 @@ export class Item { - { this.href && this.mode === 'md' && } + { this.href && this.mode === 'md' && } ); } diff --git a/packages/core/src/components/ripple-effect/readme.md b/packages/core/src/components/ripple-effect/readme.md index 90d7c7a12f..b49718d6dc 100644 --- a/packages/core/src/components/ripple-effect/readme.md +++ b/packages/core/src/components/ripple-effect/readme.md @@ -5,6 +5,25 @@ +## Properties + +#### useTapClick + +boolean + + +## Attributes + +#### useTapClick + +boolean + + +## Methods + +#### addRipple() + + ---------------------------------------------- diff --git a/packages/core/src/components/ripple-effect/ripple-effect.tsx b/packages/core/src/components/ripple-effect/ripple-effect.tsx index 86fd7d5f75..0a6c77ab91 100644 --- a/packages/core/src/components/ripple-effect/ripple-effect.tsx +++ b/packages/core/src/components/ripple-effect/ripple-effect.tsx @@ -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(() => { diff --git a/packages/core/src/components/scroll/readme.md b/packages/core/src/components/scroll/readme.md index be2de69604..9e8377a9db 100644 --- a/packages/core/src/components/scroll/readme.md +++ b/packages/core/src/components/scroll/readme.md @@ -12,11 +12,6 @@ boolean -#### jsScroll - -boolean - - #### onionScroll any @@ -39,11 +34,6 @@ any boolean -#### jsScroll - -boolean - - #### onionScroll any diff --git a/packages/core/src/components/scroll/scroll.tsx b/packages/core/src/components/scroll/scroll.tsx index f57df582db..b54e63e917 100644 --- a/packages/core/src/components/scroll/scroll.tsx +++ b/packages/core/src/components/scroll/scroll.tsx @@ -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 { 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() { diff --git a/packages/core/src/components/searchbar/searchbar.ios.scss b/packages/core/src/components/searchbar/searchbar.ios.scss index f11cc4aad5..cf2935eaa5 100644 --- a/packages/core/src/components/searchbar/searchbar.ios.scss +++ b/packages/core/src/components/searchbar/searchbar.ios.scss @@ -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); } diff --git a/packages/core/src/components/tabs/tabs.ios.scss b/packages/core/src/components/tabs/tabs.ios.scss index 78d62cfc26..3c0c8714a5 100644 --- a/packages/core/src/components/tabs/tabs.ios.scss +++ b/packages/core/src/components/tabs/tabs.ios.scss @@ -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; diff --git a/packages/core/src/components/tabs/tabs.md.scss b/packages/core/src/components/tabs/tabs.md.scss index 8ed5ef6e27..f2edbbc653 100644 --- a/packages/core/src/components/tabs/tabs.md.scss +++ b/packages/core/src/components/tabs/tabs.md.scss @@ -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; diff --git a/packages/core/src/components/tap-click/readme.md b/packages/core/src/components/tap-click/readme.md new file mode 100644 index 0000000000..301a630f07 --- /dev/null +++ b/packages/core/src/components/tap-click/readme.md @@ -0,0 +1,11 @@ +# ion-tap-click + + + + + + + +---------------------------------------------- + +*Built by [StencilJS](https://stenciljs.com/)* diff --git a/packages/core/src/components/tap-click/tap-click.scss b/packages/core/src/components/tap-click/tap-click.scss new file mode 100644 index 0000000000..e21c162d17 --- /dev/null +++ b/packages/core/src/components/tap-click/tap-click.scss @@ -0,0 +1 @@ +@import "../../themes/ionic.globals"; diff --git a/packages/core/src/components/tap-click/tap-click.tsx b/packages/core/src/components/tap-click/tap-click.tsx new file mode 100644 index 0000000000..7a021be622 --- /dev/null +++ b/packages/core/src/components/tap-click/tap-click.tsx @@ -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(); + + 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; diff --git a/packages/core/src/components/tap-click/test/basic/index.html b/packages/core/src/components/tap-click/test/basic/index.html new file mode 100644 index 0000000000..1b9988a0a9 --- /dev/null +++ b/packages/core/src/components/tap-click/test/basic/index.html @@ -0,0 +1,70 @@ + + + + + Button Effect - Basic + + + + + + + + + + + Button Effect - Basic + + + + +

+ Small +

+

+ Large +

+

+ Large +

+

+ Large +

+
+ + This is just a div + effect behind + Nested button +
+
+ This is just a div + effect on top + Nested button + +
+ +
+ This is just a div + effect + +
+
+
+
+ + + diff --git a/packages/core/src/components/toolbar/toolbar.ios.scss b/packages/core/src/components/toolbar/toolbar.ios.scss index 61764d923a..0bb2bc49d5 100644 --- a/packages/core/src/components/toolbar/toolbar.ios.scss +++ b/packages/core/src/components/toolbar/toolbar.ios.scss @@ -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} { diff --git a/packages/core/src/components/toolbar/toolbar.md.scss b/packages/core/src/components/toolbar/toolbar.md.scss index 537a65c42d..7a6e4f3438 100644 --- a/packages/core/src/components/toolbar/toolbar.md.scss +++ b/packages/core/src/components/toolbar/toolbar.md.scss @@ -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} {