This commit is contained in:
Adam Bradley
2015-07-16 14:04:15 -05:00
parent a5e5789323
commit f240083535
23 changed files with 729 additions and 36 deletions

View File

@@ -11,6 +11,7 @@ import * as util from '../../util/util';
// injectables
import {ActionMenu} from '../action-menu/action-menu';
import {Modal} from '../modal/modal';
import {FocusHolder} from '../form/focus-holder';
export class IonicApp {
@@ -29,6 +30,13 @@ export class IonicApp {
this._zone = this.injector().get(NgZone);
}
focusHolder(val) {
if (arguments.length) {
this._focusHolder = val;
}
return this._focusHolder;
}
title(val) {
document.title = val;
}
@@ -206,6 +214,13 @@ export function ionicBootstrap(component, config, router) {
bootstrap(component, injectableBindings).then(appRef => {
app.load(appRef);
// append the focus holder if its needed
if (config.setting('keyboardScrollAssist')) {
app.appendComponent(FocusHolder).then(ref => {
app.focusHolder(ref.instance);
});
}
router.load(window, app, config).then(() => {
// resolve that the app has loaded
resolve(app);

View File

@@ -1,10 +1,11 @@
html,
body {
width: 100%;
height: 100%;
}
body {
position: relative;
position: fixed;
overflow: hidden;
max-width: 100%;
max-height: 100%;

View File

@@ -90,7 +90,7 @@ $content-padding: 10px !default;
left: 0;
opacity: 0;
z-index: $z-index-click-block;
transform: translate3d(-9999px, 0px, 0px);
transform: translate3d(-9999px, 0px, 0px);;
//background: red;
//opacity: .3;

View File

@@ -3,13 +3,17 @@ import {Component, View, ElementRef} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {IonicComponent} from '../../config/annotations';
import {ScrollTo} from '../../animations/scroll-to';
@Component({
selector: 'ion-content',
properties: [
'parallax'
]
],
host: {
['[class.scroll-padding]']: 'scrollPadding'
}
})
@View({
template: '<div class="scroll-content"><content></content></div>'
@@ -17,6 +21,7 @@ import {IonicComponent} from '../../config/annotations';
export class Content extends Ion {
constructor(elementRef: ElementRef, ionicConfig: IonicConfig) {
super(elementRef, ionicConfig);
this.scrollPadding = false;
}
onIonInit() {
@@ -32,4 +37,33 @@ export class Content extends Ion {
this.scrollElement.removeEventListener('scroll', handler);
}
}
addTouchMoveListener(handler) {
if(!this.scrollElement) { return; }
this.scrollElement.addEventListener('touchmove', handler);
return () => {
this.scrollElement.removeEventListener('touchmove', handler);
}
}
scrollTo(x, y, duration, tolerance) {
if (this._scrollTo) {
this._scrollTo.dispose();
}
this._scrollTo = new ScrollTo(this.scrollElement);
return this._scrollTo.start(x, y, duration, tolerance);
}
get scrollPadding() {
return this._sp;
}
set scrollPadding(val) {
this._sp = val;
}
}

View File

@@ -0,0 +1,108 @@
import {Component, Directive, View, Parent, ElementRef, forwardRef} from 'angular2/angular2';
import {IonicConfig} from '../../config/config';
import * as dom from '../../util/dom';
import {Platform} from '../../platform/platform';
import {IonInput} from './form';
@Component({
selector: 'focus-holder'
})
@View({
template: '<input><input><input>',
directives: [forwardRef(() => FocusInput)]
})
export class FocusHolder {
constructor() {
this.i = [];
}
setFocusHolder(inputType) {
IonInput.clearTabIndexes();
this.i[1].tabIndex = ACTIVE_TAB_INDEX;
this.i[1].type = inputType;
this.i[1].focus();
}
setActiveInput(input) {
IonInput.clearTabIndexes();
this.i[1].tabIndex = -1;
input.tabIndex = ACTIVE_TAB_INDEX;
}
receivedFocus(tabIndex) {
if (tabIndex === PREVIOUS_TAB_INDEX) {
// they tabbed back one input
// reset the focus to the center focus holder
this.i[1].focus();
// focus on the previous input
IonInput.focusPrevious();
} else if (tabIndex === NEXT_TAB_INDEX) {
// they tabbed to the next input
// reset the focus to the center focus holder
this.i[1].focus();
// focus on the next input
IonInput.focusNext();
}
}
register(input) {
// register each of the focus holder inputs
// assign them their correct tab indexes
input.tabIndex = PREVIOUS_TAB_INDEX + this.i.length;
this.i.push(input);
}
}
@Directive({
selector: 'input',
properties: [
'tabIndex'
],
host: {
'[tabIndex]': 'tabIndex',
'[type]': 'type',
'(focus)': 'holder.receivedFocus(tabIndex)',
'(keydown)': 'keydown($event)'
}
})
class FocusInput {
constructor(elementRef: ElementRef, @Parent() holder: FocusHolder) {
this.elementRef = elementRef;
holder.register(this);
this.holder = holder;
}
focus() {
this.elementRef.nativeElement.focus();
}
keydown(ev) {
// prevent any keyboard typing when a holder has focus
if (ev.keyCode !== 9) {
ev.preventDefault();
ev.stopPropagation();
}
}
get type() {
// default to text type if unknown
return this._t || 'text';
}
set type(val) {
this._t = val;
}
}
const PREVIOUS_TAB_INDEX = 999;
const ACTIVE_TAB_INDEX = 1000;
const NEXT_TAB_INDEX = 1001;

View File

@@ -1 +1,87 @@
// form
import {IonicConfig} from '../../config/config';
import * as dom from '../../util/dom';
let inputRegistry = [];
let activeInput = null;
let lastInput = null;
export class IonInput {
constructor(
elementRef: ElementRef,
app: IonicApp,
scrollView: Content
) {
this.elementRef = elementRef;
this.app = app;
this.scrollView = scrollView;
inputRegistry.push(this);
}
hasFocus() {
return dom.hasFocus(this.elementRef);
}
focus() {
this.setFocus();
}
setFocus() {
// TODO: How do you do this w/ NG2?
this.elementRef.nativeElement.focus();
}
setFocusHolder(type) {
let focusHolder = this.app.focusHolder();
focusHolder && focusHolder.setFocusHolder(type);
}
isActiveInput(shouldBeActive) {
if (shouldBeActive) {
if (activeInput && activeInput !== lastInput) {
lastInput = activeInput;
}
activeInput = this;
let focusHolder = this.app.focusHolder();
focusHolder && focusHolder.setActiveInput(activeInput);
} else if (activeInput === this) {
lastInput = activeInput;
activeInput = null;
}
}
sibling(inc) {
let index = inputRegistry.indexOf(this);
if (index > -1) {
return inputRegistry[index + inc];
}
}
static focusPrevious() {
this.focusMove(-1);
}
static focusNext() {
this.focusMove(1);
}
static focusMove(inc) {
let input = activeInput || lastInput;
if (input) {
let siblingInput = input.sibling(inc);
siblingInput && siblingInput.focus();
}
}
static clearTabIndexes() {
for (let i = 0; i < inputRegistry.length; i++) {
inputRegistry[i].tabIndex = -1;
}
}
}

View File

@@ -2,6 +2,28 @@ $item-input-padding: 6px 0 5px 0px;
ion-input {
display: block;
}
input.disable-focus,
textarea.disable-focus,
select.disable-focus {
//pointer-events: none;
}
focus-holder input {
position: fixed;
top: 1px;
width: 9px;
left: -9999px;
z-index: 9999;
}
.scroll-padding.scroll-padding .scroll-content {
padding-bottom: 1000px;
}
/*ion-input {
display: block;
position: relative;
@@ -28,3 +50,4 @@ ion-input {
background-color: transparent;
}
}
*/

View File

@@ -0,0 +1,198 @@
import {Directive, View, Parent, Ancestor, Optional, ElementRef, Attribute, forwardRef} from 'angular2/angular2';
import {IonicDirective} from '../../config/annotations';
import {IonicConfig} from '../../config/config';
import {IonInput} from './form';
import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {Content} from '../content/content';
import {ClickBlock} from '../../util/click-block';
import * as dom from '../../util/dom';
import {Platform} from '../../platform/platform';
@IonicDirective({
selector: 'ion-input'
})
export class Input extends Ion {
constructor(
elementRef: ElementRef,
ionicConfig: IonicConfig
) {
super(elementRef, ionicConfig);
this.id = ++inputIds;
}
onInit() {
if (this.input) {
this.input.id = 'input-' + this.id;
}
if (this.label) {
this.label.id = 'label-' + this.id;
this.input.labelledBy = this.label.id;
}
}
registerInput(directive) {
this.input = directive;
}
registerLabel(directive) {
this.label = directive;
}
}
@Directive({
selector: 'textarea,input[type=text],input[type=password],input[type=number],input[type=search],input[type=email],input[type=url]',
property: [
'tabIndex'
],
host: {
'[tabIndex]': 'tabIndex',
'(focus)': 'receivedFocus(true)',
'(blur)': 'receivedFocus(false)',
'(touchstart)': 'pointerStart($event)',
'(touchend)': 'pointerEnd($event)',
'(mousedown)': 'pointerStart($event)',
'(mouseup)': 'pointerEnd($event)',
'[attr.id]': 'id',
'[attr.aria-labelledby]': 'labelledBy',
'[class.disable-focus]': 'disableFocus'
}
})
export class TextInput extends IonInput {
constructor(
@Optional() @Parent() container: Input,
@Optional() @Ancestor() scrollView: Content,
@Attribute('type') type: string,
elementRef: ElementRef,
app: IonicApp,
config: IonicConfig
) {
super(elementRef, app, scrollView);
if (container) {
container.registerInput(this);
this.container = container;
}
this.type = type;
this.elementRef = elementRef;
this.tabIndex = this.tabIndex || '';
this.scrollAssist = config.setting('keyboardScrollAssist');
}
pointerStart(ev) {
if (this.scrollAssist) {
this.startCoord = dom.pointerCoord(ev);
this.disableFocus = true;
}
}
pointerEnd(ev) {
if (this.scrollAssist) {
let endCoord = dom.pointerCoord(ev);
if (this.startCoord && !dom.hasPointerMoved(20, this.startCoord, endCoord) && !this.hasFocus()) {
ev.preventDefault();
ev.stopPropagation();
this.focus();
} else {
this.disableFocus = false;
}
this.startCoord = null;
}
}
focus() {
let scrollView = this.scrollView;
if (scrollView && this.scrollAssist) {
this.disableFocus = true;
// this input is inside of a scroll view
// scroll the input to the top
let inputY = this.elementRef.nativeElement.offsetTop - 8;
// do not allow any clicks while it's scrolling
ClickBlock(true, SCROLL_INTO_VIEW_DURATION + 200);
// used to put a lot of padding on the bottom of the scroll view
scrollView.scrollPadding = true;
// temporarily move the focus to the focus holder so the browser
// doesn't freak out while it's trying to get the input in place
this.setFocusHolder(this.type);
// scroll the input into place
scrollView.scrollTo(0, inputY, SCROLL_INTO_VIEW_DURATION, 8).then(() => {
// the scroll view is in the correct position now
// give the native input the focus
this.setFocus();
// all good, allow clicks again
ClickBlock(false);
this.disableFocus = false;
});
} else {
// not inside of a scroll view, just focus it
this.setFocus();
this.disableFocus = false;
}
}
receivedFocus(receivedFocus) {
let self = this;
let scrollView = self.scrollView;
self.isActiveInput(receivedFocus);
function touchMove(ev) {
self.setFocusHolder(self.type);
self.deregTouchMove();
}
if (scrollView && this.scrollAssist) {
if (receivedFocus) {
// when the input has focus, then the focus holder
// should not be able to be focused
self.deregTouchMove = scrollView && scrollView.addTouchMoveListener(touchMove);
} else {
// the input no longer has focus
self.deregTouchMove && self.deregTouchMove();
}
}
}
}
@Directive({
selector: 'label',
host: {
'[attr.id]': 'id'
}
})
export class InputLabel {
constructor(@Optional() @Parent() container: Input) {
if (container) {
container.registerLabel(this);
}
}
}
const SCROLL_INTO_VIEW_DURATION = 500;
let inputIds = -1;

View File

@@ -1,12 +0,0 @@
import {Component, Directive} from 'angular2/angular2';
@Directive({
selector: 'ion-input'
})
export class Input {
constructor() {
//this.config = Button.config.invoke(this)
console.log('INPUT');
}
}

View File

@@ -0,0 +1,7 @@
import {Directive} from 'angular2/angular2';
@Directive({
selector: 'ion-label'
})
export class Label {}

View File

@@ -1,10 +0,0 @@
import {Component, Directive} from 'angular2/angular2';
@Directive({
selector: 'ion-label'
})
export class Label {
constructor() {
}
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
import {App} from 'ionic/ionic';
@App({
templateUrl: 'main.html'
})
class IonicApp {}

View File

@@ -0,0 +1,92 @@
<ion-toolbar><ion-title>Header</ion-title></ion-toolbar>
<ion-content class="padding">
<ion-input>
<input value="1" type="text">
</ion-input>
<ion-input>
<input value="2" type="text">
</ion-input>
<ion-input>
<input value="3" type="number">
</ion-input>
<ion-input>
<textarea>4</textarea>
</ion-input>
<ion-input>
<input value="5" type="text">
</ion-input>
<ion-input>
<input value="6" type="text">
</ion-input>
<ion-input>
<textarea>7</textarea>
</ion-input>
<ion-input>
<input value="8" type="text">
</ion-input>
<ion-input>
<input value="9" type="number">
</ion-input>
<ion-input>
<textarea>10</textarea>
</ion-input>
<ion-input>
<input value="11" type="text">
</ion-input>
<ion-input>
<input value="12" type="number">
</ion-input>
<ion-input>
<textarea>13</textarea>
</ion-input>
<ion-input>
<textarea>14</textarea>
</ion-input>
</ion-content>
<ion-toolbar><ion-title>Footer</ion-title></ion-toolbar>
<style>
input, textarea {
border: 1px solid red !important;
display: inline-block !important;
}
focus-holder input:focus {
background: red !important;
}
focus-holder input:nth-child(1) {
left: 200px !important;
}
focus-holder input:nth-child(2) {
left: 240px !important;
}
focus-holder input:nth-child(3) {
left: 280px !important;
}
</style>