mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Merge branch 'master' into v4
This commit is contained in:
@@ -51,7 +51,7 @@ export class ItemOptions {
|
||||
* @hidden
|
||||
*/
|
||||
isRightSide(): boolean {
|
||||
return isRightSide(this.side, this._plt.isRTL);
|
||||
return isRightSide(this.side, this._plt.isRTL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,10 @@ $label-ios-margin: $item-ios-padding-top ($item-ios-padding-right /
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
|
||||
[dir="rtl"] .label-ios[floating] {
|
||||
transform-origin: right top;
|
||||
}
|
||||
|
||||
.input-has-focus .label-ios[floating],
|
||||
.input-has-value .label-ios[floating] {
|
||||
transform: translate3d(0, 0, 0) scale(.8);
|
||||
|
||||
@@ -48,6 +48,10 @@ $label-md-margin: $item-md-padding-top ($item-md-padding-rig
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
|
||||
[dir="rtl"] .label-md[floating] {
|
||||
transform-origin: right top;
|
||||
}
|
||||
|
||||
.label-md[stacked],
|
||||
.label-md[floating] {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -40,6 +40,11 @@ $label-wp-text-color-focused: color($colors-wp, primary) !default;
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
[dir="rtl"] .label-wp[floating] {
|
||||
transform: translate3d(-8px, 34px, 0);
|
||||
transform-origin: right top;
|
||||
}
|
||||
|
||||
.label-wp[stacked],
|
||||
.label-wp[floating] {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -182,6 +182,10 @@ $list-ios-header-background-color: transparent !default;
|
||||
background: $list-ios-header-background-color;
|
||||
}
|
||||
|
||||
[dir="rtl"] .list-header-ios {
|
||||
padding-right: $list-ios-header-padding-left;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
// Generate iOS List Header Colors
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -168,6 +168,11 @@ $list-md-header-color: #757575 !default;
|
||||
color: $list-md-header-color;
|
||||
}
|
||||
|
||||
[dir="rtl"] .list-header-md {
|
||||
padding-right: $list-md-header-padding-left;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
|
||||
// Generate Material Design List Header Colors
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -165,6 +165,11 @@ $list-wp-header-color: $list-wp-text-color !default;
|
||||
color: $list-wp-header-color;
|
||||
}
|
||||
|
||||
[dir="rtl"] .list-header-wp {
|
||||
padding-right: $list-wp-header-padding-left;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
|
||||
// Generate Windows List Header Colors
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -4,6 +4,7 @@ import { NgControl } from '@angular/forms';
|
||||
import { Config } from '../../config/config';
|
||||
import { BaseInput } from '../../util/base-input';
|
||||
import { isPresent, isTrueProperty } from '../../util/util';
|
||||
import { TimeoutDebouncer } from '../../util/debouncer';
|
||||
import { Platform } from '../../platform/platform';
|
||||
|
||||
/**
|
||||
@@ -63,6 +64,7 @@ export class Searchbar extends BaseInput<string> {
|
||||
_isActive: boolean = false;
|
||||
_showCancelButton: boolean = false;
|
||||
_animated: boolean = false;
|
||||
_inputDebouncer: TimeoutDebouncer = new TimeoutDebouncer(0);
|
||||
|
||||
/**
|
||||
* @input {string} Set the the cancel button text. Default: `"Cancel"`.
|
||||
@@ -89,6 +91,7 @@ export class Searchbar extends BaseInput<string> {
|
||||
}
|
||||
set debounce(val: number) {
|
||||
this._debouncer.wait = val;
|
||||
this._inputDebouncer.wait = val;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,7 +291,9 @@ export class Searchbar extends BaseInput<string> {
|
||||
*/
|
||||
inputChanged(ev: any) {
|
||||
this.value = ev.target.value;
|
||||
this.ionInput.emit(ev);
|
||||
this._inputDebouncer.debounce(() => {
|
||||
this.ionInput.emit(ev);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -147,12 +147,13 @@ import { SelectPopover, SelectPopoverOption } from './select-popover-component';
|
||||
providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: Select, multi: true } ],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Select extends BaseInput<string[]> implements AfterViewInit, OnDestroy {
|
||||
export class Select extends BaseInput<string[]|string> implements AfterViewInit, OnDestroy {
|
||||
|
||||
_multi: boolean = false;
|
||||
_options: QueryList<Option>;
|
||||
_texts: string[] = [];
|
||||
_text: string = '';
|
||||
_values: string[] = [];
|
||||
|
||||
/**
|
||||
* @input {string} The text to display on the cancel button. Default: `Cancel`.
|
||||
@@ -394,7 +395,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
||||
set options(val: QueryList<Option>) {
|
||||
this._options = val;
|
||||
|
||||
if (this._value.length === 0) {
|
||||
if (this._values.length === 0) {
|
||||
// there are no values set at this point
|
||||
// so check to see who should be selected
|
||||
// we use writeValue() because we don't want to update ngModel
|
||||
@@ -404,14 +405,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
||||
}
|
||||
}
|
||||
|
||||
_inputNormalize(val: any): string[] {
|
||||
if (Array.isArray(val)) {
|
||||
return val;
|
||||
}
|
||||
return [val + ''];
|
||||
}
|
||||
|
||||
_inputShouldChange(val: string[]): boolean {
|
||||
_inputShouldChange(val: string[]|string): boolean {
|
||||
return !deepEqual(this._value, val);
|
||||
}
|
||||
|
||||
@@ -420,11 +414,12 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
||||
*/
|
||||
_inputUpdated() {
|
||||
this._texts.length = 0;
|
||||
this._values = Array.isArray(this._value) ? this._value : [this._value + ''];
|
||||
|
||||
if (this._options) {
|
||||
this._options.forEach(option => {
|
||||
// check this option if the option's value is in the values array
|
||||
option.selected = this._value.some(selectValue => {
|
||||
option.selected = this._values.some(selectValue => {
|
||||
return isCheckedProperty(selectValue, option.value);
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Select', () => {
|
||||
corpus: [
|
||||
[['hola'], ['hola']],
|
||||
[null, []],
|
||||
['hola', ['hola']],
|
||||
['hola', 'hola'],
|
||||
[['hola', 'adios'], ['hola', 'adios']]
|
||||
]
|
||||
});
|
||||
|
||||
@@ -891,6 +891,11 @@ export function enableTouchControl(s: Slides) {
|
||||
|
||||
// Cleanup dynamic styles
|
||||
function cleanupStyles(s: Slides) {
|
||||
if (!s.container || !s._wrapper) {
|
||||
// fix #10830
|
||||
return;
|
||||
}
|
||||
|
||||
// Container
|
||||
removeClass(s.container, s._classNames);
|
||||
s.container.removeAttribute('style');
|
||||
|
||||
@@ -21,7 +21,7 @@ export function processRecords(stopAtHeight: number,
|
||||
let startRecordIndex: number;
|
||||
let previousCell: VirtualCell;
|
||||
let tmpData: any;
|
||||
let lastRecordIndex = (records.length - 1);
|
||||
let lastRecordIndex = records ? (records.length - 1) : -1;
|
||||
|
||||
if (cells.length) {
|
||||
// we already have cells
|
||||
@@ -131,11 +131,11 @@ export function populateNodeData(startCellIndex: number, endCellIndex: number, v
|
||||
cells: VirtualCell[], records: any[], nodes: VirtualNode[], viewContainer: ViewContainerRef,
|
||||
itmTmp: TemplateRef<VirtualContext>, hdrTmp: TemplateRef<VirtualContext>, ftrTmp: TemplateRef<VirtualContext>,
|
||||
initialLoad: boolean): boolean {
|
||||
const recordsLength = records.length;
|
||||
if (!recordsLength) {
|
||||
if (!records || records.length === 0) {
|
||||
nodes.length = 0;
|
||||
return true;
|
||||
}
|
||||
const recordsLength = records.length;
|
||||
|
||||
let hasChanges = false;
|
||||
let node: VirtualNode;
|
||||
|
||||
@@ -14,7 +14,7 @@ export class SlideEdgeGesture extends SlideGesture {
|
||||
|
||||
constructor(plt: Platform, element: HTMLElement, opts: any = {}) {
|
||||
defaults(opts, {
|
||||
edge: 'left',
|
||||
edge: 'start',
|
||||
maxEdgeStart: 50
|
||||
});
|
||||
super(plt, element, opts);
|
||||
@@ -29,7 +29,7 @@ export class SlideEdgeGesture extends SlideGesture {
|
||||
switch (value) {
|
||||
case 'start': return isRTL ? 'right' : 'left';
|
||||
case 'end': return isRTL ? 'left' : 'right';
|
||||
default: value;
|
||||
default: return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,6 +216,10 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
}
|
||||
|
||||
_success(result: NavResult, ti: TransitionInstruction) {
|
||||
if (this._queue === null) {
|
||||
this._fireError('nav controller was destroyed', ti);
|
||||
return;
|
||||
}
|
||||
this._init = true;
|
||||
this._trnsId = null;
|
||||
|
||||
@@ -237,6 +241,10 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
}
|
||||
|
||||
_failed(rejectReason: any, ti: TransitionInstruction) {
|
||||
if (this._queue === null) {
|
||||
this._fireError('nav controller was destroyed', ti);
|
||||
return;
|
||||
}
|
||||
this._trnsId = null;
|
||||
this._queue.length = 0;
|
||||
|
||||
@@ -245,6 +253,10 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
this._swipeBackCheck();
|
||||
this._nextTrns();
|
||||
|
||||
this._fireError(rejectReason, ti);
|
||||
}
|
||||
|
||||
_fireError(rejectReason: any, ti: TransitionInstruction) {
|
||||
if (ti.done) {
|
||||
ti.done(false, false, rejectReason);
|
||||
}
|
||||
|
||||
@@ -1068,6 +1068,23 @@ describe('NavController', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
|
||||
it('should not crash when destroyed while transitioning', (done) => {
|
||||
let view1 = mockView(MockView1);
|
||||
nav.push(view1).then(() => {
|
||||
fail('it should not succeed');
|
||||
done();
|
||||
}).catch((err: any) => {
|
||||
expect(err).toEqual('nav controller was destroyed');
|
||||
done();
|
||||
});
|
||||
nav.destroy();
|
||||
}, 10000);
|
||||
|
||||
});
|
||||
|
||||
|
||||
let nav: NavControllerBase;
|
||||
let trnsDone: jasmine.Spy;
|
||||
|
||||
|
||||
@@ -238,6 +238,16 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
|
||||
return this._isFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
hasValue(): boolean {
|
||||
const val = this._value;
|
||||
return isArray(val)
|
||||
? val.length > 0
|
||||
: isPresent(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
@@ -260,12 +270,7 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasValue = isArray(val)
|
||||
? val.length > 0
|
||||
: isPresent(val);
|
||||
|
||||
this._item.setElementClass('input-has-value', hasValue);
|
||||
this._item.setElementClass('input-has-value', this.hasValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -128,15 +128,24 @@ export function isCheckedProperty(a: any, b: any): boolean {
|
||||
return (a == b); // tslint:disable-line
|
||||
};
|
||||
|
||||
/** @hidden */
|
||||
export type Side = 'left' | 'right' | 'start' | 'end';
|
||||
|
||||
export function isRightSide(side: Side, isRTL: boolean): boolean {
|
||||
/**
|
||||
* @hidden
|
||||
* Given a side, return if it should be on the right
|
||||
* based on the value of dir
|
||||
* @param side the side
|
||||
* @param isRTL whether the application dir is rtl
|
||||
* @param defaultRight whether the default side is right
|
||||
*/
|
||||
export function isRightSide(side: Side, isRTL: boolean, defaultRight: boolean = false): boolean {
|
||||
switch (side) {
|
||||
case 'right': return true;
|
||||
case 'left': return false;
|
||||
case 'end': return !isRTL;
|
||||
// 'start' by default
|
||||
default: return isRTL;
|
||||
case 'start': return isRTL;
|
||||
default: return defaultRight ? !isRTL : isRTL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user