mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
fix(input): device support
This commit is contained in:
37
packages/core/src/components.d.ts
vendored
37
packages/core/src/components.d.ts
vendored
@ -20,6 +20,10 @@ import {
|
|||||||
AlertButton,
|
AlertButton,
|
||||||
AlertInput,
|
AlertInput,
|
||||||
} from './components/alert/alert';
|
} from './components/alert/alert';
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
FrameworkDelegate as FrameworkDelegate2,
|
||||||
|
} from '.';
|
||||||
import {
|
import {
|
||||||
ElementRef,
|
ElementRef,
|
||||||
Side,
|
Side,
|
||||||
@ -38,9 +42,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
SelectPopoverOption,
|
SelectPopoverOption,
|
||||||
} from './components/select-popover/select-popover';
|
} from './components/select-popover/select-popover';
|
||||||
import {
|
|
||||||
FrameworkDelegate as FrameworkDelegate2,
|
|
||||||
} from '.';
|
|
||||||
import {
|
import {
|
||||||
DomRenderFn,
|
DomRenderFn,
|
||||||
HeaderFn,
|
HeaderFn,
|
||||||
@ -847,6 +848,36 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
DeviceHacks as IonDeviceHacks
|
||||||
|
} from './components/device-hacks/device-hacks';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLIonDeviceHacksElement extends IonDeviceHacks, HTMLStencilElement {
|
||||||
|
}
|
||||||
|
var HTMLIonDeviceHacksElement: {
|
||||||
|
prototype: HTMLIonDeviceHacksElement;
|
||||||
|
new (): HTMLIonDeviceHacksElement;
|
||||||
|
};
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ion-device-hacks": HTMLIonDeviceHacksElement;
|
||||||
|
}
|
||||||
|
interface ElementTagNameMap {
|
||||||
|
"ion-device-hacks": HTMLIonDeviceHacksElement;
|
||||||
|
}
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
"ion-device-hacks": JSXElements.IonDeviceHacksAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace JSXElements {
|
||||||
|
export interface IonDeviceHacksAttributes extends HTMLAttributes {
|
||||||
|
app?: App;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Events as IonEvents
|
Events as IonEvents
|
||||||
} from './components/events/events';
|
} from './components/events/events';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
|
||||||
import { Config, NavEvent, OverlayController, PublicNav, PublicViewController } from '../../index';
|
import { Config, NavEvent, OverlayController, PublicNav, PublicViewController } from '../../index';
|
||||||
|
|
||||||
import { getOrAppendElement } from '../../utils/helpers';
|
import { getOrAppendElement } from '../../utils/helpers';
|
||||||
@ -19,20 +19,23 @@ let backButtonActions: BackButtonAction[] = [];
|
|||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
|
|
||||||
|
private isDevice = false;
|
||||||
|
private deviceHacks = false;
|
||||||
private scrollTime = 0;
|
private scrollTime = 0;
|
||||||
|
|
||||||
@Element() element: HTMLElement;
|
|
||||||
@Event() exitApp: EventEmitter<ExitAppEventDetail>;
|
|
||||||
|
|
||||||
@State() modeCode: string;
|
|
||||||
@State() hoverCSS = false;
|
|
||||||
|
|
||||||
@Prop({ context: 'config' }) config: Config;
|
|
||||||
|
|
||||||
externalNavPromise: void | Promise<any> = null;
|
externalNavPromise: void | Promise<any> = null;
|
||||||
externalNavOccuring = false;
|
externalNavOccuring = false;
|
||||||
didScroll = false;
|
didScroll = false;
|
||||||
|
|
||||||
|
@Element() element: HTMLElement;
|
||||||
|
@Event() exitApp: EventEmitter<ExitAppEventDetail>;
|
||||||
|
|
||||||
|
@Prop({ context: 'config' }) config: Config;
|
||||||
|
|
||||||
|
componentWillLoad() {
|
||||||
|
this.isDevice = this.config.getBoolean('isDevice', false);
|
||||||
|
this.deviceHacks = this.config.getBoolean('deviceHacks', false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the promise set by an external navigation system
|
* Returns the promise set by an external navigation system
|
||||||
@ -74,11 +77,6 @@ export class App {
|
|||||||
this.externalNavOccuring = status;
|
this.externalNavOccuring = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillLoad() {
|
|
||||||
this.modeCode = this.config.get('mode');
|
|
||||||
this.hoverCSS = this.config.getBoolean('hoverCSS', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listen('body:navInit')
|
@Listen('body:navInit')
|
||||||
protected registerRootNav(event: NavEvent) {
|
protected registerRootNav(event: NavEvent) {
|
||||||
rootNavs.set(event.target.getId(), event.target);
|
rootNavs.set(event.target.getId(), event.target);
|
||||||
@ -232,20 +230,23 @@ export class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
|
const mode = this.config.get('mode');
|
||||||
|
const hoverCSS = this.config.getBoolean('hoverCSS', false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
class: {
|
class: {
|
||||||
[this.modeCode]: true,
|
[mode]: true,
|
||||||
'enable-hover': this.hoverCSS
|
'enable-hover': hoverCSS
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const isDevice = true;
|
|
||||||
return [
|
return [
|
||||||
isDevice && <ion-tap-click />,
|
|
||||||
isDevice && <ion-status-tap />,
|
|
||||||
<ion-platform />,
|
<ion-platform />,
|
||||||
|
this.deviceHacks && <ion-device-hacks app={this} />,
|
||||||
|
this.isDevice && <ion-tap-click />,
|
||||||
|
this.isDevice && <ion-status-tap />,
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
71
packages/core/src/components/device-hacks/device-hacks.tsx
Normal file
71
packages/core/src/components/device-hacks/device-hacks.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Component, Listen, Prop } from "@stencil/core";
|
||||||
|
import { App, Config } from "../..";
|
||||||
|
|
||||||
|
import enableHideCaretOnScroll from "./hacks/hide-caret";
|
||||||
|
import enableInputBlurring from "./hacks/input-blurring";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-device-hacks',
|
||||||
|
})
|
||||||
|
export class DeviceHacks {
|
||||||
|
|
||||||
|
private didLoad = false;
|
||||||
|
private hideCaret = false;
|
||||||
|
private keyboardHeight = 0;
|
||||||
|
private hideCaretMap = new WeakMap<HTMLElement, Function>();
|
||||||
|
|
||||||
|
@Prop({context: 'config'}) config: Config;
|
||||||
|
@Prop() app: App;
|
||||||
|
|
||||||
|
componentDidLoad() {
|
||||||
|
this.keyboardHeight = this.config.getNumber('keyboardHeight', 200);
|
||||||
|
this.hideCaret = this.config.getBoolean('hideCaretOnScroll', true);
|
||||||
|
|
||||||
|
const inputBlurring = this.config.getBoolean('inputBlurring', true);
|
||||||
|
if (inputBlurring) {
|
||||||
|
enableInputBlurring(this.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input might be already loaded in the DOM before ion-device-hacks did.
|
||||||
|
// At this point we need to look for all the ion-inputs not registered yet
|
||||||
|
// and register them.
|
||||||
|
const inputs = Array.from(document.querySelectorAll('ion-input'));
|
||||||
|
for (let input of inputs) {
|
||||||
|
this.registerInput(input);
|
||||||
|
}
|
||||||
|
this.didLoad = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('body:ionInputDidLoad')
|
||||||
|
protected onInputDidLoad(event: CustomEvent<HTMLElement>) {
|
||||||
|
if (this.didLoad) {
|
||||||
|
this.registerInput(event.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('body:ionInputDidUnload')
|
||||||
|
protected onInputDidUnload(event: CustomEvent<HTMLElement>) {
|
||||||
|
if (this.didLoad) {
|
||||||
|
this.unregisterInput(event.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerInput(componentEl: HTMLElement) {
|
||||||
|
if (this.hideCaret && !this.hideCaretMap.has(componentEl)) {
|
||||||
|
const rmFn = enableHideCaretOnScroll(
|
||||||
|
componentEl,
|
||||||
|
componentEl.querySelector('input'),
|
||||||
|
componentEl.closest('ion-scroll'),
|
||||||
|
this.keyboardHeight
|
||||||
|
);
|
||||||
|
this.hideCaretMap.set(componentEl, rmFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterInput(componentEl: HTMLElement) {
|
||||||
|
if (this.hideCaret) {
|
||||||
|
const fn = this.hideCaretMap.get(componentEl);
|
||||||
|
fn && fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
packages/core/src/components/device-hacks/hacks/hide-caret.ts
Normal file
129
packages/core/src/components/device-hacks/hacks/hide-caret.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
const RELOCATED_KEY= '$ionRelocated';
|
||||||
|
|
||||||
|
export default function enableHideCaretOnScroll(componentEl: HTMLElement, inputEl: HTMLInputElement, scrollEl: HTMLIonScrollElement, keyboardHeight: number) {
|
||||||
|
if(!scrollEl || !inputEl) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
console.debug('Input: enableHideCaretOnScroll');
|
||||||
|
|
||||||
|
const scrollHideCaret = (shouldHideCaret: boolean) => {
|
||||||
|
// console.log('scrollHideCaret', shouldHideCaret)
|
||||||
|
if (isFocused(inputEl)) {
|
||||||
|
relocateInput(componentEl, inputEl, keyboardHeight, shouldHideCaret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBlur = () => relocateInput(componentEl, inputEl, keyboardHeight, false);
|
||||||
|
const hideCaret = () => scrollHideCaret(true);
|
||||||
|
const showCaret = () => scrollHideCaret(false);
|
||||||
|
|
||||||
|
scrollEl && scrollEl.addEventListener('ionScrollStart', hideCaret);
|
||||||
|
scrollEl && scrollEl.addEventListener('ionScrollEnd', showCaret);
|
||||||
|
inputEl.addEventListener('blur', onBlur);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scrollEl.removeEventListener('ionScrollStart', hideCaret);
|
||||||
|
scrollEl.removeEventListener('ionScrollEnd', showCaret);
|
||||||
|
inputEl.addEventListener('ionBlur', onBlur);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function removeClone(componentEl: HTMLElement, inputEl: HTMLElement) {
|
||||||
|
if (componentEl && componentEl.parentElement) {
|
||||||
|
const clonedInputEles = componentEl.parentElement.querySelectorAll('.cloned-input');
|
||||||
|
for (let i = 0; i < clonedInputEles.length; i++) {
|
||||||
|
clonedInputEles[i].parentNode.removeChild(clonedInputEles[i]);
|
||||||
|
}
|
||||||
|
componentEl.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
(<any>inputEl.style)['transform'] = '';
|
||||||
|
inputEl.style.opacity = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement) {
|
||||||
|
// Make sure we kill all the clones before creating new ones
|
||||||
|
// It is a defensive, removeClone() should do nothing
|
||||||
|
// removeClone(plt, srcComponentEle, srcNativeInputEle);
|
||||||
|
// given a native <input> or <textarea> element
|
||||||
|
// find its parent wrapping component like <ion-input> or <ion-textarea>
|
||||||
|
// then clone the entire component
|
||||||
|
const parentElement = componentEl.parentElement;
|
||||||
|
if (componentEl && parentElement) {
|
||||||
|
// DOM READ
|
||||||
|
const srcTop = componentEl.offsetTop;
|
||||||
|
const srcLeft = componentEl.offsetLeft;
|
||||||
|
const srcWidth = componentEl.offsetWidth;
|
||||||
|
const srcHeight = componentEl.offsetHeight;
|
||||||
|
|
||||||
|
// DOM WRITE
|
||||||
|
// not using deep clone so we don't pull in unnecessary nodes
|
||||||
|
const clonedComponentEle = document.createElement('div');
|
||||||
|
const clonedStyle = clonedComponentEle.style;
|
||||||
|
clonedComponentEle.classList.add(...Array.from(componentEl.classList));
|
||||||
|
clonedComponentEle.classList.add('cloned-input');
|
||||||
|
clonedComponentEle.setAttribute('aria-hidden', 'true');
|
||||||
|
clonedStyle.pointerEvents = 'none';
|
||||||
|
clonedStyle.position = 'absolute';
|
||||||
|
clonedStyle.top = srcTop + 'px';
|
||||||
|
clonedStyle.left = srcLeft + 'px';
|
||||||
|
clonedStyle.width = srcWidth + 'px';
|
||||||
|
clonedStyle.height = srcHeight + 'px';
|
||||||
|
|
||||||
|
const clonedInputEl = document.createElement('input');
|
||||||
|
clonedInputEl.classList.add(...Array.from(inputEl.classList));
|
||||||
|
// Object.assign(clonedInputEl, input);
|
||||||
|
//const clonedInputEl = <HTMLInputElement>inputEl.cloneNode(false);
|
||||||
|
clonedInputEl.value = inputEl.value;
|
||||||
|
clonedInputEl.type = inputEl.type;
|
||||||
|
|
||||||
|
clonedInputEl.tabIndex = -1;
|
||||||
|
|
||||||
|
clonedComponentEle.appendChild(clonedInputEl);
|
||||||
|
parentElement.appendChild(clonedComponentEle);
|
||||||
|
|
||||||
|
componentEl.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
inputEl.style.transform = 'scale(0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
function relocateInput(
|
||||||
|
componentEl: HTMLElement,
|
||||||
|
inputEle: HTMLInputElement,
|
||||||
|
_keyboardHeight: number,
|
||||||
|
shouldRelocate: boolean
|
||||||
|
) {
|
||||||
|
console.log('relocateInput',shouldRelocate );
|
||||||
|
if ((componentEl as any)[RELOCATED_KEY] === shouldRelocate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug(`native-input, hideCaret, shouldHideCaret: ${shouldRelocate}, input value: ${inputEle.value}`);
|
||||||
|
if (shouldRelocate) {
|
||||||
|
// this allows for the actual input to receive the focus from
|
||||||
|
// the user's touch event, but before it receives focus, it
|
||||||
|
// moves the actual input to a location that will not screw
|
||||||
|
// up the app's layout, and does not allow the native browser
|
||||||
|
// to attempt to scroll the input into place (messing up headers/footers)
|
||||||
|
// the cloned input fills the area of where native input should be
|
||||||
|
// while the native input fakes out the browser by relocating itself
|
||||||
|
// before it receives the actual focus event
|
||||||
|
// We hide the focused input (with the visible caret) invisiable by making it scale(0),
|
||||||
|
cloneInputComponent(componentEl, inputEle);
|
||||||
|
// TODO
|
||||||
|
// const inputRelativeY = getScrollData(componentEl, inputEle, keyboardHeight).scrollAmount;
|
||||||
|
// const inputRelativeY = 9999;
|
||||||
|
// fix for #11817
|
||||||
|
const tx = document.dir === 'rtl' ? 9999 : -9999;
|
||||||
|
inputEle.style.transform= `translate3d(${tx}px,${0}px,0)`;
|
||||||
|
// inputEle.style.opacity = '0';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
removeClone(componentEl, inputEle);
|
||||||
|
}
|
||||||
|
(componentEl as any)[RELOCATED_KEY] = shouldRelocate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFocused(input: HTMLInputElement): boolean {
|
||||||
|
return input === document.activeElement;
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
import { App } from "../../..";
|
||||||
|
|
||||||
|
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
|
||||||
|
|
||||||
|
export default function enableInputBlurring(app: App) {
|
||||||
|
console.debug('Input: enableInputBlurring');
|
||||||
|
|
||||||
|
let focused = true;
|
||||||
|
|
||||||
|
function onFocusin() {
|
||||||
|
focused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('focusin', onFocusin, true);
|
||||||
|
document.addEventListener('touchend', onTouchend, false);
|
||||||
|
|
||||||
|
function onTouchend(ev: any) {
|
||||||
|
// if app did scroll return early
|
||||||
|
if (app.didScroll) {
|
||||||
|
app.didScroll = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const active = document.activeElement as HTMLElement;
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// only blur if the active element is a text-input or a textarea
|
||||||
|
if (SKIP_BLURRING.indexOf(active.tagName) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the selected target is the active element, do not blur
|
||||||
|
const tapped = ev.target;
|
||||||
|
if (tapped === active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SKIP_BLURRING.indexOf(tapped.tagName) >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if div is a cover
|
||||||
|
if (tapped.classList.contains('input-cover')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused = false;
|
||||||
|
// TODO: find a better way, why 50ms?
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!focused) {
|
||||||
|
active.blur();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('focusin', onFocusin, true);
|
||||||
|
document.removeEventListener('touchend', onTouchend, false);
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
const SCROLL_ASSIST_SPEED = 0.3;
|
||||||
|
|
||||||
|
export interface ScrollData {
|
||||||
|
scrollAmount: number;
|
||||||
|
scrollPadding: number;
|
||||||
|
scrollDuration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcScrollData(
|
||||||
|
inputRect: ClientRect,
|
||||||
|
contentRect: ClientRect,
|
||||||
|
keyboardHeight: number,
|
||||||
|
plaformHeight: number
|
||||||
|
): ScrollData {
|
||||||
|
// compute input's Y values relative to the body
|
||||||
|
const inputTop = inputRect.top;
|
||||||
|
const inputBottom = inputRect.bottom;
|
||||||
|
|
||||||
|
// compute safe area
|
||||||
|
const safeAreaTop = contentRect.top;
|
||||||
|
const safeAreaBottom = Math.min(contentRect.bottom, plaformHeight - keyboardHeight);
|
||||||
|
|
||||||
|
// figure out if each edge of teh input is within the safe area
|
||||||
|
const distanceToBottom = safeAreaBottom - inputBottom;
|
||||||
|
const distanceToTop = safeAreaTop - inputTop;
|
||||||
|
|
||||||
|
const scrollAmount = Math.round((distanceToBottom < 0 )
|
||||||
|
? distanceToBottom
|
||||||
|
: (distanceToTop < 0 )
|
||||||
|
? distanceToTop
|
||||||
|
: 0);
|
||||||
|
|
||||||
|
const distance = Math.abs(scrollAmount);
|
||||||
|
const duration = distance / SCROLL_ASSIST_SPEED;
|
||||||
|
const scrollDuration = Math.min(400, Math.max(150, duration));
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollAmount,
|
||||||
|
scrollDuration,
|
||||||
|
scrollPadding: keyboardHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getScrollData(componentEl: HTMLElement, contentEl: HTMLElement, keyboardHeight: number): ScrollData {
|
||||||
|
if (!contentEl) {
|
||||||
|
return {
|
||||||
|
scrollAmount: 0,
|
||||||
|
scrollPadding: 0,
|
||||||
|
scrollDuration: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// const scrollData = (componentEl as any)[SCROLL_DATA_KEY];
|
||||||
|
// if (scrollData) {
|
||||||
|
// return scrollData;
|
||||||
|
// }
|
||||||
|
const itemEl = <HTMLElement>componentEl.closest('ion-item,[ion-item]') || componentEl;
|
||||||
|
const newScrollData = calcScrollData(
|
||||||
|
itemEl.getBoundingClientRect(),
|
||||||
|
contentEl.getBoundingClientRect(),
|
||||||
|
keyboardHeight,
|
||||||
|
window.innerHeight
|
||||||
|
);
|
||||||
|
// (componentEl as any)[SCROLL_DATA_KEY] = newScrollData;
|
||||||
|
return newScrollData;
|
||||||
|
}
|
35
packages/core/src/components/device-hacks/hacks/wip.ts
Normal file
35
packages/core/src/components/device-hacks/hacks/wip.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
// export function enableScrollPadding(componentEl: HTMLElement, inputEl: HTMLElement, contentEl: HTMLElement, keyboardHeight: number) {
|
||||||
|
// console.debug('Input: enableScrollPadding');
|
||||||
|
// debugger;
|
||||||
|
// const onFocus = () => {
|
||||||
|
// const scrollPadding = getScrollData(componentEl, contentEl, keyboardHeight).scrollPadding;
|
||||||
|
// contentEl.addScrollPadding(scrollPadding);
|
||||||
|
// content.clearScrollPaddingFocusOut();
|
||||||
|
// };
|
||||||
|
// inputEl.addEventListener('focus', onFocus);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// inputEl.removeEventListener('focus', onFocus);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function enableScrollMove(
|
||||||
|
// componentEl: HTMLElement,
|
||||||
|
// inputEl: HTMLElement,
|
||||||
|
// contentEl: HTMLIonContentElement,
|
||||||
|
// keyboardHeight: number
|
||||||
|
// ) {
|
||||||
|
// console.debug('Input: enableAutoScroll');
|
||||||
|
// const onFocus = () => {
|
||||||
|
// const scrollData = getScrollData(componentEl, contentEl, keyboardHeight);
|
||||||
|
// if (Math.abs(scrollData.scrollAmount) > 4) {
|
||||||
|
// contentEl.scrollBy(0, scrollData.scrollAmount);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// inputEl.addEventListener('focus', onFocus);
|
||||||
|
// return () => {
|
||||||
|
// inputEl.removeEventListener('focus', onFocus);
|
||||||
|
// };
|
||||||
|
// }
|
25
packages/core/src/components/device-hacks/readme.md
Normal file
25
packages/core/src/components/device-hacks/readme.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# ion-app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auto Generated Below -->
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
#### app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes
|
||||||
|
|
||||||
|
#### app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
*Built with [StencilJS](https://stenciljs.com/)*
|
@ -1,278 +0,0 @@
|
|||||||
import { assert } from '../../utils/helpers';
|
|
||||||
import { CSS_PROP } from '../animation-controller/constants';
|
|
||||||
import { App } from '../..';
|
|
||||||
|
|
||||||
const SCROLL_DATA_MAP = new WeakMap<HTMLElement, ScrollData>();
|
|
||||||
const SCROLL_ASSIST_SPEED = 0.3;
|
|
||||||
|
|
||||||
export interface ScrollData {
|
|
||||||
scrollAmount: number;
|
|
||||||
scrollPadding: number;
|
|
||||||
scrollDuration: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calcScrollData(
|
|
||||||
inputRect: ClientRect,
|
|
||||||
contentRect: ClientRect,
|
|
||||||
keyboardHeight: number,
|
|
||||||
plaformHeight: number
|
|
||||||
): ScrollData {
|
|
||||||
// compute input's Y values relative to the body
|
|
||||||
const inputTop = inputRect.top;
|
|
||||||
const inputBottom = inputRect.bottom;
|
|
||||||
|
|
||||||
// compute safe area
|
|
||||||
const safeAreaTop = contentRect.top;
|
|
||||||
const safeAreaBottom = Math.min(contentRect.bottom, plaformHeight - keyboardHeight);
|
|
||||||
|
|
||||||
// figure out if each edge of teh input is within the safe area
|
|
||||||
const distanceToBottom = safeAreaBottom - inputBottom;
|
|
||||||
const distanceToTop = safeAreaTop - inputTop;
|
|
||||||
|
|
||||||
const scrollAmount = Math.round((distanceToBottom < 0 )
|
|
||||||
? distanceToBottom
|
|
||||||
: (distanceToTop < 0 )
|
|
||||||
? distanceToTop
|
|
||||||
: 0);
|
|
||||||
|
|
||||||
const distance = Math.abs(scrollAmount);
|
|
||||||
const duration = distance / SCROLL_ASSIST_SPEED;
|
|
||||||
const scrollDuration = Math.min(400, Math.max(150, duration));
|
|
||||||
|
|
||||||
return {
|
|
||||||
scrollAmount,
|
|
||||||
scrollDuration,
|
|
||||||
scrollPadding: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScrollData(componentEl: HTMLElement, contentEl: HTMLElement, keyboardHeight: number): ScrollData {
|
|
||||||
if (!contentEl) {
|
|
||||||
return {
|
|
||||||
scrollAmount: 0,
|
|
||||||
scrollPadding: 0,
|
|
||||||
scrollDuration: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const scrollData = SCROLL_DATA_MAP.get(componentEl);
|
|
||||||
if (scrollData) {
|
|
||||||
return scrollData;
|
|
||||||
}
|
|
||||||
const ele = <HTMLElement>componentEl.closest('ion-item,[ion-item]') || componentEl;
|
|
||||||
const newScrollData = calcScrollData(
|
|
||||||
ele.getBoundingClientRect(),
|
|
||||||
contentEl.getBoundingClientRect(),
|
|
||||||
keyboardHeight,
|
|
||||||
window.innerHeight
|
|
||||||
);
|
|
||||||
SCROLL_DATA_MAP.set(componentEl, newScrollData);
|
|
||||||
return newScrollData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableScrollPadding(_componentEl: HTMLElement, inputEl: HTMLElement, _contentEl: HTMLElement, _keyboardHeight: number) {
|
|
||||||
console.debug('Input: enableScrollPadding');
|
|
||||||
|
|
||||||
const onFocus = () => {
|
|
||||||
// const scrollPadding = getScrollData(componentEl, contentEl, keyboardHeight).scrollPadding;
|
|
||||||
// content.addScrollPadding(scrollPadding);
|
|
||||||
// content.clearScrollPaddingFocusOut();
|
|
||||||
};
|
|
||||||
inputEl.addEventListener('focus', onFocus);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
inputEl.removeEventListener('focus', onFocus);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableScrollMove(
|
|
||||||
componentEl: HTMLElement,
|
|
||||||
inputEl: HTMLElement,
|
|
||||||
contentEl: HTMLIonContentElement,
|
|
||||||
keyboardHeight: number
|
|
||||||
) {
|
|
||||||
console.debug('Input: enableAutoScroll');
|
|
||||||
const onFocus = () => {
|
|
||||||
const scrollData = getScrollData(componentEl, contentEl, keyboardHeight);
|
|
||||||
if (Math.abs(scrollData.scrollAmount) > 4) {
|
|
||||||
contentEl.scrollBy(0, scrollData.scrollAmount);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inputEl.addEventListener('focus', onFocus);
|
|
||||||
return () => {
|
|
||||||
inputEl.removeEventListener('focus', onFocus);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
|
|
||||||
|
|
||||||
export function enableInputBlurring(app: App) {
|
|
||||||
let focused = true;
|
|
||||||
|
|
||||||
function onFocusin() {
|
|
||||||
focused = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('focusin', onFocusin, true);
|
|
||||||
document.addEventListener('touchend', onTouchend, false);
|
|
||||||
|
|
||||||
function onTouchend(ev: any) {
|
|
||||||
// if app did scroll return early
|
|
||||||
if (app.didScroll) {
|
|
||||||
app.didScroll = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const active = document.activeElement as HTMLElement;
|
|
||||||
if (!active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// only blur if the active element is a text-input or a textarea
|
|
||||||
if (SKIP_BLURRING.indexOf(active.tagName) === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the selected target is the active element, do not blur
|
|
||||||
const tapped = ev.target;
|
|
||||||
if (tapped === active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SKIP_BLURRING.indexOf(tapped.tagName) >= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip if div is a cover
|
|
||||||
if (tapped.classList.contains('input-cover')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
focused = false;
|
|
||||||
// TODO: find a better way, why 50ms?
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!focused) {
|
|
||||||
active.blur();
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('focusin', onFocusin, true);
|
|
||||||
document.removeEventListener('touchend', onTouchend, false);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableHideCaretOnScroll(componentEl: HTMLElement, inputEl: HTMLInputElement, scrollEl: HTMLIonScrollElement) {
|
|
||||||
|
|
||||||
console.debug('Input: enableHideCaretOnScroll');
|
|
||||||
|
|
||||||
function scrollHideCaret(shouldHideCaret: boolean) {
|
|
||||||
if (isFocused(inputEl)) {
|
|
||||||
relocateInput(componentEl, inputEl, shouldHideCaret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onBlur = () => relocateInput(componentEl, inputEl, false);
|
|
||||||
const hideCaret = () => scrollHideCaret(true);
|
|
||||||
const showCaret = () => scrollHideCaret(false);
|
|
||||||
|
|
||||||
scrollEl.addEventListener('ionScrollStart', hideCaret);
|
|
||||||
scrollEl.addEventListener('ionScrollEnd', showCaret);
|
|
||||||
inputEl.addEventListener('blur', onBlur);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
scrollEl.removeEventListener('ionScrollStart', hideCaret);
|
|
||||||
scrollEl.removeEventListener('ionScrollEnd', showCaret);
|
|
||||||
inputEl.addEventListener('ionBlur', onBlur);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function removeClone(componentEl: HTMLElement, nativeInputEl: HTMLElement) {
|
|
||||||
if (componentEl && componentEl.parentElement) {
|
|
||||||
const clonedInputEles = componentEl.parentElement.querySelectorAll('.cloned-input');
|
|
||||||
for (let i = 0; i < clonedInputEles.length; i++) {
|
|
||||||
clonedInputEles[i].parentNode.removeChild(clonedInputEles[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentEl.style.pointerEvents = '';
|
|
||||||
}
|
|
||||||
(<any>nativeInputEl.style)[CSS_PROP.transformProp] = '';
|
|
||||||
nativeInputEl.style.opacity = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneInputComponent(componentEle: HTMLElement, nativeInputEle: HTMLInputElement) {
|
|
||||||
// Make sure we kill all the clones before creating new ones
|
|
||||||
// It is a defensive, removeClone() should do nothing
|
|
||||||
// removeClone(plt, srcComponentEle, srcNativeInputEle);
|
|
||||||
// given a native <input> or <textarea> element
|
|
||||||
// find its parent wrapping component like <ion-input> or <ion-textarea>
|
|
||||||
// then clone the entire component
|
|
||||||
const parentElement = componentEle.parentElement;
|
|
||||||
if (componentEle && parentElement) {
|
|
||||||
assert(parentElement.querySelector('.cloned-input') === null, 'leaked cloned input');
|
|
||||||
|
|
||||||
// DOM READ
|
|
||||||
const srcTop = componentEle.offsetTop;
|
|
||||||
const srcLeft = componentEle.offsetLeft;
|
|
||||||
const srcWidth = componentEle.offsetWidth;
|
|
||||||
const srcHeight = componentEle.offsetHeight;
|
|
||||||
|
|
||||||
// DOM WRITE
|
|
||||||
// not using deep clone so we don't pull in unnecessary nodes
|
|
||||||
const clonedComponentEle = <HTMLElement>componentEle.cloneNode(false);
|
|
||||||
const clonedStyle = clonedComponentEle.style;
|
|
||||||
clonedComponentEle.classList.add('cloned-input');
|
|
||||||
clonedComponentEle.setAttribute('aria-hidden', 'true');
|
|
||||||
clonedStyle.pointerEvents = 'none';
|
|
||||||
clonedStyle.position = 'absolute';
|
|
||||||
clonedStyle.top = srcTop + 'px';
|
|
||||||
clonedStyle.left = srcLeft + 'px';
|
|
||||||
clonedStyle.width = srcWidth + 'px';
|
|
||||||
clonedStyle.height = srcHeight + 'px';
|
|
||||||
|
|
||||||
const clonedNativeInputEle = <HTMLInputElement>nativeInputEle.cloneNode(false);
|
|
||||||
clonedNativeInputEle.value = nativeInputEle.value;
|
|
||||||
clonedNativeInputEle.tabIndex = -1;
|
|
||||||
|
|
||||||
clonedComponentEle.appendChild(clonedNativeInputEle);
|
|
||||||
parentElement.appendChild(clonedComponentEle);
|
|
||||||
|
|
||||||
clonedComponentEle.style.pointerEvents = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
(<any>nativeInputEle.style)[CSS_PROP.transformProp] = 'scale(0)';
|
|
||||||
}
|
|
||||||
|
|
||||||
function relocateInput(componentEl: HTMLElement, inputEle: HTMLInputElement, shouldRelocate: boolean) {
|
|
||||||
|
|
||||||
if ((componentEl as any)['$ionRelocated'] === shouldRelocate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug(`native-input, hideCaret, shouldHideCaret: ${shouldRelocate}, input value: ${inputEle.value}`);
|
|
||||||
if (shouldRelocate) {
|
|
||||||
// this allows for the actual input to receive the focus from
|
|
||||||
// the user's touch event, but before it receives focus, it
|
|
||||||
// moves the actual input to a location that will not screw
|
|
||||||
// up the app's layout, and does not allow the native browser
|
|
||||||
// to attempt to scroll the input into place (messing up headers/footers)
|
|
||||||
// the cloned input fills the area of where native input should be
|
|
||||||
// while the native input fakes out the browser by relocating itself
|
|
||||||
// before it receives the actual focus event
|
|
||||||
// We hide the focused input (with the visible caret) invisiable by making it scale(0),
|
|
||||||
cloneInputComponent(componentEl, inputEle);
|
|
||||||
// TODO
|
|
||||||
// const inputRelativeY = this._getScrollData().inputSafeY;
|
|
||||||
const inputRelativeY = 0;
|
|
||||||
|
|
||||||
// fix for #11817
|
|
||||||
const tx = document.dir === 'rtl' ? 9999 : -9999;
|
|
||||||
(inputEle.style as any)[CSS_PROP.transformProp] = `translate3d(${tx}px,${inputRelativeY}px,0)`;
|
|
||||||
inputEle.style.opacity = '0';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
removeClone(componentEl, inputEle);
|
|
||||||
}
|
|
||||||
(componentEl as any)['$ionRelocated'] = shouldRelocate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFocused(input: HTMLInputElement): boolean {
|
|
||||||
return input === document.activeElement;
|
|
||||||
}
|
|
@ -85,6 +85,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iOS Input After Label
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.label-ios + .input .native-input,
|
||||||
|
.label-ios + .input + .cloned-input {
|
||||||
|
@include margin-horizontal($input-ios-by-label-margin-start, null);
|
||||||
|
}
|
||||||
|
|
||||||
// iOS Stacked & Floating Inputs
|
// iOS Stacked & Floating Inputs
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@ -102,14 +109,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// iOS Input After Label
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
.label-ios + ion-input .native-input,
|
|
||||||
.label-ios + .input + .cloned-input {
|
|
||||||
@include margin-horizontal($input-ios-by-label-margin-start, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// iOS Clear Input Icon
|
// iOS Clear Input Icon
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// Input
|
// Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
ion-input {
|
.input {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ ion-input {
|
|||||||
// Input Within An Item
|
// Input Within An Item
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
.item-input ion-input {
|
.item-input .input {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Prop, Watch } from '@stencil/core';
|
import { Component, Event, EventEmitter, Prop, Watch, Element } from '@stencil/core';
|
||||||
|
|
||||||
import { debounceEvent } from '../../utils/helpers';
|
import { debounceEvent } from '../../utils/helpers';
|
||||||
import { createThemedClasses } from '../../utils/theme';
|
import { createThemedClasses } from '../../utils/theme';
|
||||||
@ -16,6 +16,8 @@ import { InputComponent } from './input-base';
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class Input implements InputComponent {
|
export class Input implements InputComponent {
|
||||||
|
|
||||||
|
private nativeInput: HTMLInputElement;
|
||||||
mode: string;
|
mode: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
|
||||||
@ -44,6 +46,16 @@ export class Input implements InputComponent {
|
|||||||
*/
|
*/
|
||||||
@Event() ionFocus: EventEmitter;
|
@Event() ionFocus: EventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the input has been created.
|
||||||
|
*/
|
||||||
|
@Event() ionInputDidLoad: EventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the input has been removed.
|
||||||
|
*/
|
||||||
|
@Event() ionInputDidUnload: EventEmitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the value of the type attribute is `"file"`, then this attribute will indicate the types of files that the server accepts, otherwise it will be ignored. The value must be a comma-separated list of unique content type specifiers.
|
* If the value of the type attribute is `"file"`, then this attribute will indicate the types of files that the server accepts, otherwise it will be ignored. The value must be a comma-separated list of unique content type specifiers.
|
||||||
*/
|
*/
|
||||||
@ -200,7 +212,7 @@ export class Input implements InputComponent {
|
|||||||
*/
|
*/
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
protected valueChanged() {
|
protected valueChanged() {
|
||||||
const inputEl = this.el.querySelector('input');
|
const inputEl = this.nativeInput;
|
||||||
if (inputEl && inputEl.value !== this.value) {
|
if (inputEl && inputEl.value !== this.value) {
|
||||||
inputEl.value = this.value;
|
inputEl.value = this.value;
|
||||||
}
|
}
|
||||||
@ -214,6 +226,12 @@ export class Input implements InputComponent {
|
|||||||
if (this.type === 'password' && this.clearOnEdit !== false) {
|
if (this.type === 'password' && this.clearOnEdit !== false) {
|
||||||
this.clearOnEdit = true;
|
this.clearOnEdit = true;
|
||||||
}
|
}
|
||||||
|
this.ionInputDidLoad.emit(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUnload() {
|
||||||
|
this.nativeInput = null;
|
||||||
|
this.ionInputDidUnload.emit(this.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitStyle() {
|
private emitStyle() {
|
||||||
@ -288,7 +306,7 @@ export class Input implements InputComponent {
|
|||||||
|
|
||||||
hasFocus(): boolean {
|
hasFocus(): boolean {
|
||||||
// check if an input has focus or not
|
// check if an input has focus or not
|
||||||
return this.el && (this.el.querySelector(':focus') === this.el.querySelector('input'));
|
return this.nativeInput === document.activeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasValue(): boolean {
|
hasValue(): boolean {
|
||||||
@ -301,6 +319,7 @@ export class Input implements InputComponent {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
<input
|
<input
|
||||||
|
ref={input => this.nativeInput = input as any}
|
||||||
aria-disabled={this.disabled ? 'true' : false}
|
aria-disabled={this.disabled ? 'true' : false}
|
||||||
accept={this.accept}
|
accept={this.accept}
|
||||||
autoCapitalize={this.autocapitalize}
|
autoCapitalize={this.autocapitalize}
|
||||||
|
@ -404,6 +404,16 @@ Emitted when the input has focus.
|
|||||||
Emitted when the input value has changed.
|
Emitted when the input value has changed.
|
||||||
|
|
||||||
|
|
||||||
|
#### ionInputDidLoad
|
||||||
|
|
||||||
|
Emitted when the input has been created.
|
||||||
|
|
||||||
|
|
||||||
|
#### ionInputDidUnload
|
||||||
|
|
||||||
|
Emitted when the input has been removed.
|
||||||
|
|
||||||
|
|
||||||
#### ionStyle
|
#### ionStyle
|
||||||
|
|
||||||
Emitted when the styles change.
|
Emitted when the styles change.
|
||||||
|
@ -70,6 +70,11 @@
|
|||||||
<ion-label>Toggle</ion-label>
|
<ion-label>Toggle</ion-label>
|
||||||
<ion-toggle checked slot="end"></ion-toggle>
|
<ion-toggle checked slot="end"></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label fixed>Type #</ion-label>
|
||||||
|
<div type="number" value="333" class="input input-md hydrated"><!----><input aria-disabled="false" autocapitalize="none" autocomplete="off" autocorrect="off" autofocus="false" class="native-input native-input-md" spellcheck="false" type="number"><button type="button" class="input-clear-icon" hidden=""></button></div>
|
||||||
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<div text-center>
|
<div text-center>
|
||||||
|
@ -69,7 +69,7 @@ ion-item-group {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[vertical-align-top],
|
[vertical-align-top],
|
||||||
ion-input.item {
|
.input.item {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
@include margin(-$list-md-margin-top, null, null, null);
|
@include margin(-$list-md-margin-top, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-md > ion-input:last-child::after {
|
.list-md > .input:last-child::after {
|
||||||
@include position-horizontal(0, null);
|
@include position-horizontal(0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +33,11 @@ export const PLATFORM_CONFIGS: PlatformConfig[] = [
|
|||||||
{
|
{
|
||||||
name: IOS,
|
name: IOS,
|
||||||
settings: {
|
settings: {
|
||||||
mode: IOS,
|
mode: "ios",
|
||||||
tabsHighlight: false,
|
tabsHighlight: false,
|
||||||
statusbarPadding: isCordova(),
|
statusbarPadding: isCordova(),
|
||||||
|
isDevice: true,
|
||||||
|
deviceHacks: true,
|
||||||
},
|
},
|
||||||
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IOS, [IPHONE, IPAD, 'ipod'], WINDOWS_PHONE)
|
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IOS, [IPHONE, IPAD, 'ipod'], WINDOWS_PHONE)
|
||||||
},
|
},
|
||||||
@ -43,7 +45,7 @@ export const PLATFORM_CONFIGS: PlatformConfig[] = [
|
|||||||
{
|
{
|
||||||
name: ANDROID,
|
name: ANDROID,
|
||||||
settings: {
|
settings: {
|
||||||
activator: 'ripple',
|
isDevice: true,
|
||||||
mode: 'md',
|
mode: 'md',
|
||||||
},
|
},
|
||||||
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, ANDROID, [ANDROID, 'silk'], WINDOWS_PHONE)
|
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, ANDROID, [ANDROID, 'silk'], WINDOWS_PHONE)
|
||||||
|
@ -46,6 +46,7 @@ exports.config = {
|
|||||||
{ components: ['ion-toggle'] },
|
{ components: ['ion-toggle'] },
|
||||||
{ components: ['ion-toast', 'ion-toast-controller'] },
|
{ components: ['ion-toast', 'ion-toast-controller'] },
|
||||||
{ components: ['ion-tap-click', 'ion-status-tap'] },
|
{ components: ['ion-tap-click', 'ion-status-tap'] },
|
||||||
|
{ components: ['ion-device-hacks'] },
|
||||||
{ components: ['ion-platform', 'ion-cordova-platform'] },
|
{ components: ['ion-platform', 'ion-cordova-platform'] },
|
||||||
{ components: ['ion-nav-pop'] },
|
{ components: ['ion-nav-pop'] },
|
||||||
{ components: ['ion-hide-when', 'ion-show-when'] },
|
{ components: ['ion-hide-when', 'ion-show-when'] },
|
||||||
|
Reference in New Issue
Block a user