Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/src/components/action-sheet/action-sheet.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
@include margin-horizontal(null, $action-sheet-ios-button-icon-padding-right);

font-size: $action-sheet-ios-button-icon-font-size;

pointer-events: none;
}

.action-sheet-button:last-child {
Expand Down
3 changes: 2 additions & 1 deletion core/src/components/action-sheet/action-sheet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
flex-shrink: 0;
align-items: center;
justify-content: center;
pointer-events: none;

width: 100%;
height: 100%;
Expand Down Expand Up @@ -210,4 +211,4 @@
opacity: var(--button-background-hover-opacity);
}
}
}
}
40 changes: 37 additions & 3 deletions core/src/components/action-sheet/action-sheet.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, h } from '@stencil/core';
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, h, readTask } from '@stencil/core';

import { getIonMode } from '../../global/ionic-global';
import { ActionSheetButton, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
import { Gesture } from '../../utils/gesture';
import { createButtonActiveGesture } from '../../utils/gesture/button-active';
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { getClassMap } from '../../utils/theme';

Expand All @@ -25,6 +27,9 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {

presented = false;
animation?: any;
private wrapperEl?: HTMLElement;
private groupEl?: HTMLElement;
private gesture?: Gesture;

@Element() el!: HTMLIonActionSheetElement;

Expand Down Expand Up @@ -192,6 +197,35 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
}
}

componentDidUnload() {
if (this.gesture) {
this.gesture.destroy();
this.gesture = undefined;
}
}

componentDidLoad() {
/**
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
*/
const { groupEl, wrapperEl } = this;
if (this.gesture || getIonMode(this) === 'md' || !wrapperEl || !groupEl) { return; }

readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
this.gesture = createButtonActiveGesture(
wrapperEl,
(refEl: HTMLElement) => refEl.classList.contains('action-sheet-button')
);
this.gesture.enable(true);
}
});
}

render() {
const mode = getIonMode(this);
const allButtons = this.getButtons();
Expand All @@ -215,9 +249,9 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
onIonBackdropTap={this.onBackdropTap}
>
<ion-backdrop tappable={this.backdropDismiss}/>
<div class="action-sheet-wrapper" role="dialog">
<div class="action-sheet-wrapper" role="dialog" ref={el => this.wrapperEl = el}>
<div class="action-sheet-container">
<div class="action-sheet-group">
<div class="action-sheet-group" ref={el => this.groupEl = el}>
{this.header !== undefined &&
<div class="action-sheet-title">
{this.header}
Expand Down
4 changes: 4 additions & 0 deletions core/src/components/alert/alert.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
overflow: hidden;
}

.alert-button .alert-button-inner {
pointer-events: none;
}


// iOS Translucent Alert
// -----------------------------------------
Expand Down
29 changes: 28 additions & 1 deletion core/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth

import { getIonMode } from '../../global/ionic-global';
import { AlertButton, AlertInput, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
import { Gesture } from '../../utils/gesture';
import { createButtonActiveGesture } from '../../utils/gesture/button-active';
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { sanitizeDOMString } from '../../utils/sanitization';
import { getClassMap } from '../../utils/theme';
Expand All @@ -28,6 +30,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
private inputType?: string;
private processedInputs: AlertInput[] = [];
private processedButtons: AlertButton[] = [];
private wrapperEl?: HTMLElement;
private gesture?: Gesture;

presented = false;

Expand Down Expand Up @@ -170,6 +174,29 @@ export class Alert implements ComponentInterface, OverlayInterface {
this.buttonsChanged();
}

componentDidUnload() {
if (this.gesture) {
this.gesture.destroy();
this.gesture = undefined;
}
}

componentDidLoad() {
/**
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
*/
if (this.gesture || getIonMode(this) === 'md' || !this.wrapperEl) { return; }

this.gesture = createButtonActiveGesture(
this.wrapperEl,
(refEl: HTMLElement) => refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
}

/**
* Present the alert overlay after it has been created.
*/
Expand Down Expand Up @@ -480,7 +507,7 @@ export class Alert implements ComponentInterface, OverlayInterface {

<ion-backdrop tappable={this.backdropDismiss}/>

<div class="alert-wrapper">
<div class="alert-wrapper" ref={el => this.wrapperEl = el}>

<div class="alert-head">
{header && <h2 id={hdrId} class="alert-title">{header}</h2>}
Expand Down
58 changes: 58 additions & 0 deletions core/src/utils/gesture/button-active.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { writeTask } from '@stencil/core';

import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '../native/haptic';

import { Gesture, createGesture } from './index';

export const createButtonActiveGesture = (
el: HTMLElement,
isButton: (refEl: HTMLElement) => boolean
): Gesture => {
let touchedButton: HTMLElement | undefined;

const activateButtonAtPoint = (x: number, y: number, hapticFeedbackFn: () => void) => {
if (typeof (document as any) === 'undefined') { return; }
const target = document.elementFromPoint(x, y) as HTMLElement | null;
if (!target || !isButton(target)) {
clearActiveButton();
return;
}

if (target !== touchedButton) {
clearActiveButton();
setActiveButton(target, hapticFeedbackFn);
}
};

const setActiveButton = (button: HTMLElement, hapticFeedbackFn: () => void) => {
touchedButton = button;
const buttonToModify = touchedButton;
writeTask(() => buttonToModify.classList.add('ion-activated'));
hapticFeedbackFn();
};

const clearActiveButton = (dispatchClick = false) => {
if (!touchedButton) { return; }

const buttonToModify = touchedButton;
writeTask(() => buttonToModify.classList.remove('ion-activated'));

if (dispatchClick) {
touchedButton.click();
}

touchedButton = undefined;
};

return createGesture({
el,
gestureName: 'buttonActiveDrag',
threshold: 0,
onStart: ev => activateButtonAtPoint(ev.currentX, ev.currentY, hapticSelectionStart),
onMove: ev => activateButtonAtPoint(ev.currentX, ev.currentY, hapticSelectionChanged),
onEnd: () => {
clearActiveButton(true);
hapticSelectionEnd();
}
});
};
4 changes: 2 additions & 2 deletions core/src/utils/native/haptic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ const HapticEngine = {
return;
}
if (this.isCapacitor()) {
engine.selectionChanged();
engine.selectionEnd();
} else {
engine.gestureSelectionChanged();
engine.gestureSelectionEnd();
}
}
};
Expand Down