From 3559eb04fbb27f55a831075c9e51531461e4d3c5 Mon Sep 17 00:00:00 2001 From: Drew Rygh Date: Thu, 8 Oct 2015 14:28:55 -0500 Subject: [PATCH 01/26] fix(demos): fix icon, modals --- demos/component-docs/icons/icons.html | 2 +- demos/component-docs/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/component-docs/icons/icons.html b/demos/component-docs/icons/icons.html index bb034e3479..67b4570a41 100644 --- a/demos/component-docs/icons/icons.html +++ b/demos/component-docs/icons/icons.html @@ -67,7 +67,7 @@ - + diff --git a/demos/component-docs/index.ts b/demos/component-docs/index.ts index 328ce94004..a4a054f8b9 100644 --- a/demos/component-docs/index.ts +++ b/demos/component-docs/index.ts @@ -5,7 +5,7 @@ import * as helpers from 'helpers'; @App({ - template: '' + template: '' }) class DemoApp { From 52bcc24901cbf567a80c3dbbd6a1ff01acc6d19c Mon Sep 17 00:00:00 2001 From: Drew Rygh Date: Thu, 8 Oct 2015 14:55:36 -0500 Subject: [PATCH 02/26] docs(demos): actionsheet style and content --- demos/component-docs/actionSheet/actionSheet.ts | 9 +++++---- demos/component-docs/app.css | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/demos/component-docs/actionSheet/actionSheet.ts b/demos/component-docs/actionSheet/actionSheet.ts index 83ef677164..be3b284c63 100644 --- a/demos/component-docs/actionSheet/actionSheet.ts +++ b/demos/component-docs/actionSheet/actionSheet.ts @@ -20,7 +20,7 @@ export class ActionSheetPage { { text: 'Favorite', icon: 'ion-md-heart-outline'} ], destructiveText: 'Delete', - titleText: 'Purchased', + titleText: 'Albums', cancelText: 'Cancel', cancel: function() { console.log('Canceled'); @@ -38,11 +38,12 @@ export class ActionSheetPage { this.actionSheet.open(androidSheet || { buttons: [ - { text: 'Share This' }, - { text: 'Move' } + { text: 'Share'}, + { text: 'Play'}, + { text: 'Favorite'} ], destructiveText: 'Delete', - titleText: 'You Opened Action Sheet', + titleText: 'Albums', cancelText: 'Cancel', cancel: function() { console.log('Canceled'); diff --git a/demos/component-docs/app.css b/demos/component-docs/app.css index 0c961e226d..1768e2be98 100644 --- a/demos/component-docs/app.css +++ b/demos/component-docs/app.css @@ -83,3 +83,13 @@ scroll-content { color: #31D55F; } +.md .action-sheet-destructive { + border-top: 1px solid rgba(204, 204, 204, 0.32); + margin-top: 8px; + padding-top: 4px; +} + +.md .action-sheet-cancel icon, .md .action-sheet-destructive icon { + color: #757575; +} + From b9ae06d442f3923e344ae1bc5e63558cd0f43b41 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 8 Oct 2015 15:50:27 -0500 Subject: [PATCH 03/26] refactor(input): improve focus handling, alpha39 fixes --- ionic/components.ts | 2 +- ionic/components/checkbox/checkbox.ts | 29 ++- ionic/components/form/focus-holder.ts | 96 --------- ionic/components/form/form.ts | 235 ++++++++++++++++++++++ ionic/components/form/input.ts | 87 -------- ionic/components/switch/switch.ts | 33 ++- ionic/components/text-input/text-input.ts | 113 ++++++----- ionic/config/bootstrap.ts | 6 +- ionic/ionic.ts | 1 - ionic/util/dom.ts | 6 +- ionic/util/focus.ts | 43 ---- 11 files changed, 329 insertions(+), 322 deletions(-) delete mode 100644 ionic/components/form/focus-holder.ts create mode 100644 ionic/components/form/form.ts delete mode 100644 ionic/components/form/input.ts delete mode 100644 ionic/util/focus.ts diff --git a/ionic/components.ts b/ionic/components.ts index a4ff6ecc97..cde02aa715 100644 --- a/ionic/components.ts +++ b/ionic/components.ts @@ -6,7 +6,7 @@ export * from 'ionic/components/blur/blur' export * from 'ionic/components/button/button' export * from 'ionic/components/checkbox/checkbox' export * from 'ionic/components/content/content' -export * from 'ionic/components/form/input' +export * from 'ionic/components/form/form' export * from 'ionic/components/icon/icon' export * from 'ionic/components/item/item' export * from 'ionic/components/item/item-group' diff --git a/ionic/components/checkbox/checkbox.ts b/ionic/components/checkbox/checkbox.ts index d9814214d5..de763f43de 100644 --- a/ionic/components/checkbox/checkbox.ts +++ b/ionic/components/checkbox/checkbox.ts @@ -1,8 +1,7 @@ -import {Component, View, Directive, ElementRef, Optional, NgControl} from 'angular2/angular2'; +import {Component, View, Directive, Optional, NgControl} from 'angular2/angular2'; import {Ion} from '../ion'; -import {IonInput} from '../form/input'; -import {IonicConfig} from '../../config/config'; +import {IonicForm} from '../form/form'; /** * The checkbox is no different than the HTML checkbox input, except it's styled differently @@ -44,21 +43,14 @@ import {IonicConfig} from '../../config/config'; '' + '' }) -export class Checkbox extends Ion { - /** - * TODO - * @param {ElementRef} elementRef TODO - * @param {IonicConfig} ionicConfig TODO - * @param {NgControl=} ngControl TODO - */ +export class Checkbox { + constructor( - elementRef: ElementRef, - config: IonicConfig, + form: IonicForm, @Optional() ngControl: NgControl ) { - super(elementRef, config); - this.tabIndex = 0; - this.id = IonInput.nextId(); + this.form = form; + form.register(this); this.onChange = (_) => {}; this.onTouched = (_) => {}; @@ -72,8 +64,7 @@ export class Checkbox extends Ion { * TODO */ onInit() { - super.onInit(); - this.labelId = 'label-' + this.id; + this.labelId = 'label-' + this.inputId; } /** @@ -121,4 +112,8 @@ export class Checkbox extends Ion { * @param {Function} fn onTouched event handler. */ registerOnTouched(fn) { this.onTouched = fn; } + + onDestroy() { + this.form.deregister(this); + } } diff --git a/ionic/components/form/focus-holder.ts b/ionic/components/form/focus-holder.ts deleted file mode 100644 index e93124adc1..0000000000 --- a/ionic/components/form/focus-holder.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {Component, Directive, View, Host, Attribute, ElementRef, forwardRef} from 'angular2/angular2'; - -import {IonicConfig} from '../../config/config'; -import {IonInput} from './input'; - -/** - * TODO - */ -@Component({ - selector: 'focus-holder' -}) -@View({ - template: '', - directives: [forwardRef(() => FocusInput)] -}) -export class FocusHolder { - /** - * TODO - */ - constructor() { - this.i = []; - } - - /** - * TODO - * @param {TODO} inputType TODO - */ - setFocusHolder(inputType) { - this.i[2].type = inputType; - this.i[2].setFocus(); - } - - /** - * TODO - * @param {TODO} tabIndex TODO - */ - receivedFocus(tabIndex) { - if (tabIndex === '999') { - // focus on the previous input - IonInput.focusPrevious(); - - } else if (tabIndex === '1001') { - // focus on the next input - IonInput.focusNext(); - } - } - - /** - * TODO - * @param {TODO} input TODO - */ - register(input) { - this.i.push(input); - } -} - - -@Directive({ - selector: 'input', - host: { - '[type]': 'type', - '(focus)': 'holder.receivedFocus(tabindex)', - '(keydown)': 'keydown($event)' - } -}) -class FocusInput { - constructor( - elementRef: ElementRef, - @Host() holder: FocusHolder, - @Attribute('tabindex') tabindex: string - ) { - this.elementRef = elementRef; - this.holder = holder; - this.tabindex = tabindex; - this.holder.register(this); - } - - setFocus() { - this.elementRef.nativeElement.focus(); - } - - keydown(ev) { - // prevent any keyboard typing when a holder has focus - ev.preventDefault(); - ev.stopPropagation(); - } - - get type() { - // default to text type if unknown - return this._t || 'text'; - } - - set type(val) { - this._t = val; - } -} diff --git a/ionic/components/form/form.ts b/ionic/components/form/form.ts new file mode 100644 index 0000000000..98f6ff648c --- /dev/null +++ b/ionic/components/form/form.ts @@ -0,0 +1,235 @@ +import {Injectable, NgZone} from 'angular2/angular2'; + +import {IonicConfig} from '../../config/config'; +import {raf} from '../../util/dom'; + +/** + * The Input component is used to focus text input elements. + * + * The `focusNext()` and `focusPrevious()` methods make it easy to focus input elements across all devices. + * + * @usage + * ```html + * + * Name + * + * + * ``` + */ + @Injectable() +export class IonicForm { + + constructor(config: IonicConfig, zone: NgZone) { + this._config = config; + this._zone = zone; + + this._inputs = []; + this._ids = -1; + this._focused = null; + + zone.runOutsideAngular(() => { + if (this._config.get('keyboardScrollAssist')) { + this.initHolders(document); + } + + if (this._config.get('keyboardInputListener') !== false) { + this.initKeyInput(document); + } + }); + + } + + initKeyInput(document) { + /* Focus Outline + * -------------------------------------------------- + * When a keydown event happens, from a tab key, then the + * 'key-input' class is added to the body element so focusable + * elements have an outline. On a mousedown or touchstart + * event then the 'key-input' class is removed. + */ + + let isKeyInputEnabled = false; + + function keyDown(ev) { + if (!isKeyInputEnabled && ev.keyCode == 9) { + isKeyInputEnabled = true; + raf(enableKeyInput); + } + } + + function pointerDown() { + isKeyInputEnabled = false; + raf(enableKeyInput); + } + + + function enableKeyInput() { + document.body.classList[isKeyInputEnabled ? 'add' : 'remove']('key-input'); + + document.removeEventListener('mousedown', pointerDown); + document.removeEventListener('touchstart', pointerDown); + + if (isKeyInputEnabled) { + document.addEventListener('mousedown', pointerDown); + document.addEventListener('touchstart', pointerDown); + } + } + + document.addEventListener('keydown', keyDown); + } + + initHolders(document) { + // raw DOM fun + this._holder = document.createElement('focus-holder'); + + this._prev = document.createElement('input'); + this._prev.tabIndex = NO_FOCUS_TAB_INDEX; + this._holder.appendChild(this._prev); + + this._next = document.createElement('input'); + this._next.tabIndex = NO_FOCUS_TAB_INDEX; + this._holder.appendChild(this._next); + + this._temp = document.createElement('input'); + this._temp.tabIndex = NO_FOCUS_TAB_INDEX; + this._holder.appendChild(this._temp); + + document.body.appendChild(this._holder); + + function preventDefault(ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + this._prev.addEventListener('keydown', preventDefault); + this._next.addEventListener('keydown', preventDefault); + this._temp.addEventListener('keydown', preventDefault); + + this._prev.addEventListener('focus', () => { + this.focusPrevious(); + }); + + this._next.addEventListener('focus', () => { + this.focusNext(); + }); + + let self = this; + let resetTimer; + function queueReset() { + clearTimeout(resetTimer); + + resetTimer = setTimeout(function() { + self._zone.run(() => { + self.resetInputs(); + }); + }, 100); + } + + document.addEventListener('focusin', queueReset); + document.addEventListener('focusout', queueReset); + } + + setFocusHolder(type) { + if (this._temp) { + this._temp.tabIndex = TEMP_TAB_INDEX; + + this._temp.type = type || 'text'; + console.debug('setFocusHolder', this._temp.type); + this._temp.focus(); + } + } + + /** + * @param {TODO} input TODO + */ + register(input) { + console.debug('register input', input); + + input.inputId = ++this._ids; + input.tabIndex = NORMAL_FOCUS_TAB_INDEX; + this._inputs.push(input); + } + + deregister(input) { + console.debug('deregister input', input); + + let index = this._inputs.indexOf(input); + if (index > -1) { + this._inputs.splice(index, 1); + } + if (input === this._focused) { + this._focused = null; + } + } + + resetInputs() { + this._focused = null; + + for (let i = 0, ii = this._inputs.length; i < ii; i++) { + if (!this._focused && this._inputs[i].hasFocus) { + this._focused = this._inputs[i]; + this._focused.tabIndex = ACTIVE_FOCUS_TAB_INDEX; + + } else { + this._inputs[i].tabIndex = NORMAL_FOCUS_TAB_INDEX; + } + } + + if (this._temp) { + this._temp.tabIndex = NO_FOCUS_TAB_INDEX; + + if (this._focused) { + // there's a focused input + this._prev.tabIndex = PREV_TAB_INDEX; + this._next.tabIndex = NEXT_TAB_INDEX; + + } else { + this._prev.tabIndex = this._next.tabIndex = NO_FOCUS_TAB_INDEX; + } + } + + } + + /** + * Focuses the previous input element, if it exists. + */ + focusPrevious() { + console.debug('focusPrevious'); + this.focusMove(-1); + } + + /** + * Focuses the next input element, if it exists. + */ + focusNext() { + console.debug('focusNext'); + this.focusMove(1); + } + + /** + * @param {Number} inc TODO + */ + focusMove(inc) { + let input = this._focused; + if (input) { + let index = this._inputs.indexOf(input); + if (index > -1 && (index + inc) < this._inputs.length) { + let siblingInput = this._inputs[index + inc]; + if (siblingInput) { + siblingInput.initFocus(); + return; + } + } + this._focused.initFocus(); + } + } + +} + +const NO_FOCUS_TAB_INDEX = -1; +const NORMAL_FOCUS_TAB_INDEX = 0; +const PREV_TAB_INDEX = 999; +const ACTIVE_FOCUS_TAB_INDEX = 1000; +const NEXT_TAB_INDEX = 1001; +const TEMP_TAB_INDEX = 2000; + diff --git a/ionic/components/form/input.ts b/ionic/components/form/input.ts deleted file mode 100644 index 6449beabb6..0000000000 --- a/ionic/components/form/input.ts +++ /dev/null @@ -1,87 +0,0 @@ -import {Directive, ElementRef, Query, QueryList} from 'angular2/angular2'; - -import {Ion} from '../ion'; -import {IonicApp} from '../app/app'; -import {IonicConfig} from '../../config/config'; - - -let inputRegistry = []; -let inputItemIds = -1; -let activeInput = null; -let lastInput = null; - -/** - * The Input component is used to focus text input elements. - * - * The `focusNext()` and `focusPrevious()` methods make it easy to focus input elements across all devices. - * - * @usage - * ```html - * - * Name - * - * - * ``` - */ -export class IonInput extends Ion { - /** - * @param {TODO} input TODO - */ - static registerInput(input) { - inputRegistry.push(input); - } - - /** - * TODO - * @param {TODO} input TODO - */ - static setAsLastInput(input) { - lastInput = input; - } - - /** - * Focuses the previous input element, if it exists. - */ - static focusPrevious() { - this.focusMove(-1); - } - - /** - * Focuses the next input element, if it exists. - */ - static focusNext() { - this.focusMove(1); - } - - /** - * @param {Number} inc TODO - */ - static focusMove(inc) { - let input = activeInput || lastInput; - if (input) { - - let index = inputRegistry.indexOf(input); - if (index > -1 && (index + inc) < inputRegistry.length) { - let siblingInput = inputRegistry[index + inc]; - siblingInput && siblingInput.initFocus(); - } - } - } - - /** - * @returns {Number} The ID of the next input element. - */ - static nextId() { - return ++inputItemIds; - } - - /** - * TODO - */ - static clearTabIndexes() { - for (let i = 0; i < inputRegistry.length; i++) { - inputRegistry[i].tabIndex = -1; - } - } - -} diff --git a/ionic/components/switch/switch.ts b/ionic/components/switch/switch.ts index 306f57622a..a5da0d74cf 100644 --- a/ionic/components/switch/switch.ts +++ b/ionic/components/switch/switch.ts @@ -10,8 +10,7 @@ import { forwardRef } from 'angular2/angular2'; -import {Ion} from '../ion'; -import {IonInput} from '../form/input'; +import {IonicForm} from '../form/form'; import {IonicConfig} from '../../config/config'; import {pointerCoord} from '../../util/dom'; @@ -109,7 +108,7 @@ class MediaSwitch { '', directives: [MediaSwitch] }) -export class Switch extends Ion { +export class Switch { /** * TODO * @param {ElementRef} elementRef TODO @@ -117,24 +116,24 @@ export class Switch extends Ion { * @param {NgControl=} ngControl TODO */ constructor( + form: IonicForm, elementRef: ElementRef, config: IonicConfig, @Optional() private ngControl: NgControl ) { - super(elementRef, config); - let self = this; + this.form = form; + form.register(this); - self.id = IonInput.nextId(); - self.tabIndex = 0; - self.lastTouch = 0; - self.mode = config.get('mode'); + this.lastTouch = 0; + this.mode = config.get('mode'); - self.onChange = (_) => {}; - self.onTouched = (_) => {}; + this.onChange = (_) => {}; + this.onTouched = (_) => {}; if (ngControl) ngControl.valueAccessor = this; + let self = this; function pointerMove(ev) { let currentX = pointerCoord(ev).x; @@ -156,21 +155,20 @@ export class Switch extends Ion { } this.addMoveListener = function() { - this.switchEle.addEventListener('touchmove', pointerMove); - this.switchEle.addEventListener('mousemove', pointerMove); + self.switchEle.addEventListener('touchmove', pointerMove); + self.switchEle.addEventListener('mousemove', pointerMove); elementRef.nativeElement.addEventListener('mouseout', pointerOut); }; this.removeMoveListener = function() { - this.switchEle.removeEventListener('touchmove', pointerMove); - this.switchEle.removeEventListener('mousemove', pointerMove); + self.switchEle.removeEventListener('touchmove', pointerMove); + self.switchEle.removeEventListener('mousemove', pointerMove); elementRef.nativeElement.removeEventListener('mouseout', pointerOut); }; } onInit() { - super.onInit(); - this.labelId = 'label-' + this.id; + this.labelId = 'label-' + this.inputId; } /** @@ -234,6 +232,7 @@ export class Switch extends Ion { onDestroy() { this.removeMoveListener(); this.switchEle = this.addMoveListener = this.removeMoveListener = null; + this.form.deregister(this); } isDisabled(ev) { diff --git a/ionic/components/text-input/text-input.ts b/ionic/components/text-input/text-input.ts index 3db3e6bfb4..343552f943 100644 --- a/ionic/components/text-input/text-input.ts +++ b/ionic/components/text-input/text-input.ts @@ -1,9 +1,8 @@ import {Directive, View, Host, Optional, ElementRef, Attribute, Query, QueryList, NgZone} from 'angular2/angular2'; import {IonicConfig} from '../../config/config'; -import {IonInput} from '../form/input'; +import {IonicForm} from '../form/form'; import {Label} from './label'; -import {Ion} from '../ion'; import {IonicApp} from '../app/app'; import {Content} from '../content/content'; import * as dom from '../../util/dom'; @@ -15,22 +14,18 @@ import {IonicPlatform} from '../../platform/platform'; */ @Directive({ selector: 'ion-input', - inputs: [ - 'tabIndex' - ], host: { '(focus)': 'receivedFocus(true)', '(blur)': 'receivedFocus(false)', '(touchstart)': 'pointerStart($event)', '(touchend)': 'pointerEnd($event)', '(mouseup)': 'pointerEnd($event)', - '[class.has-focus]': 'inputHasFocus', - '[class.has-value]': 'inputHasValue', - '[tabIndex]': 'activeTabIndex', + '[class.has-focus]': 'hasFocus', + '[class.has-value]': 'hasValue', 'class': 'item' } }) -export class TextInput extends Ion { +export class TextInput { /** * TODO * @param {ElementRef} elementRef TODO @@ -42,31 +37,30 @@ export class TextInput extends Ion { * @param {QueryList