fix(datetime): ionChange event

This commit is contained in:
Manu Mtz.-Almeida
2018-08-07 01:08:30 +02:00
parent efd77c93a4
commit 8b35e37a43
6 changed files with 3868 additions and 5398 deletions

View File

@ -27,7 +27,7 @@
"ionicons": "4.3.0"
},
"devDependencies": {
"@stencil/core": "0.11.0",
"@stencil/core": "0.11.1",
"@stencil/dev-server": "latest",
"@stencil/sass": "0.1.0",
"@stencil/utils": "latest",

8845
core/src/components.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -173,7 +173,7 @@ export function isLeapYear(year: number): boolean {
const ISO_8601_REGEXP = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
const TIME_REGEXP = /^((\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
export function parseDate(val: any): DatetimeData | null {
export function parseDate(val: string | undefined): DatetimeData | null {
// manually parse IS0 cuz Date.parse cannot be trusted
// ISO 8601 format: 1994-12-15T13:47:20Z
let parse: any[] | null = null;
@ -254,8 +254,9 @@ export function updateDate(existingData: DatetimeData, newData: any): boolean {
// merge new values from the picker's selection
// to the existing DatetimeData values
Object.assign(existingData, newData);
for (const key of Object.keys(newData)) {
existingData[key] = newData[key].value;
}
return true;
}

View File

@ -163,10 +163,7 @@ export class Datetime {
* Any additional options that the picker interface can accept.
* See the [Picker API docs](../../picker/Picker) for the picker options.
*/
@Prop() pickerOptions: PickerOptions = {
buttons: [],
columns: []
};
@Prop() pickerOptions?: PickerOptions;
/**
* The text to display when there's no date selected yet.
@ -186,6 +183,7 @@ export class Datetime {
protected valueChanged() {
this.updateValue();
this.emitStyle();
this.ionChange.emit();
}
/**
@ -193,6 +191,11 @@ export class Datetime {
*/
@Event() ionCancel!: EventEmitter<void>;
/**
* Emitted when the checked property has changed.
*/
@Event() ionChange!: EventEmitter<void>;
/**
* Emitted when the styles change.
*/
@ -232,7 +235,23 @@ export class Datetime {
this.updateText();
}
private buildPicker(pickerOptions: PickerOptions) {
private async open() {
if (this.disabled) {
return;
}
const pickerOptions = this.generatePicketOptions();
this.picker = await this.pickerCtrl.create(pickerOptions);
this.validate();
await this.picker!.present();
}
private generatePicketOptions(): PickerOptions {
const pickerOptions: PickerOptions = {
...this.pickerOptions,
columns: this.generateColumns()
};
// If the user has not passed in picker buttons,
// add a cancel and ok button to the picker
const buttons = pickerOptions.buttons;
@ -245,101 +264,77 @@ export class Datetime {
},
{
text: this.doneText,
handler: (data: any) => this.value = data,
}];
handler: (data: any) => this.value = data
}
];
}
pickerOptions.columns = this.generateColumns();
const picker = this.pickerCtrl.create(pickerOptions);
return picker;
}
private async open() {
// TODO check this.isFocus() || this.disabled
if (this.disabled) {
return;
}
const pickerOptions = { ...this.pickerOptions };
this.picker = await this.buildPicker(pickerOptions);
this.validate();
await this.picker!.present();
return pickerOptions;
}
private generateColumns(): PickerColumn[] {
let columns: PickerColumn[] = [];
// if a picker format wasn't provided, then fallback
// to use the display format
let template = this.pickerFormat || this.displayFormat || DEFAULT_FORMAT;
if (!template) {
return [];
}
// make sure we've got up to date sizing information
this.calcMinMax();
if (template) {
// make sure we've got up to date sizing information
this.calcMinMax();
// does not support selecting by day name
// automaticallly remove any day name formats
template = template.replace('DDDD', '{~}').replace('DDD', '{~}');
if (template.indexOf('D') === -1) {
// there is not a day in the template
// replace the day name with a numeric one if it exists
template = template.replace('{~}', 'D');
}
// make sure no day name replacer is left in the string
template = template.replace(/{~}/g, '');
// does not support selecting by day name
// automaticallly remove any day name formats
template = template.replace('DDDD', '{~}').replace('DDD', '{~}');
if (template.indexOf('D') === -1) {
// there is not a day in the template
// replace the day name with a numeric one if it exists
template = template.replace('{~}', 'D');
}
// make sure no day name replacer is left in the string
template = template.replace(/{~}/g, '');
// parse apart the given template into an array of "formats"
const columns = parseTemplate(template).map((format: any) => {
// loop through each format in the template
// create a new picker column to build up with data
const key = convertFormatToKey(format)!;
let values: any[];
// parse apart the given template into an array of "formats"
parseTemplate(template).forEach((format: any) => {
// loop through each format in the template
// create a new picker column to build up with data
const key = convertFormatToKey(format)!;
let values: any[];
// check if they have exact values to use for this date part
// otherwise use the default date part values
values = this[key + 'Values']
? convertToArrayOfNumbers(this[key + 'Values'], key)
: dateValueRange(format, this.datetimeMin, this.datetimeMax);
// check if they have exact values to use for this date part
// otherwise use the default date part values
values = this[key + 'Values']
? convertToArrayOfNumbers(this[key + 'Values'], key)
: dateValueRange(format, this.datetimeMin, this.datetimeMax);
const column: PickerColumn = {
name: key,
selectedIndex: 0,
options: values.map(val => {
return {
value: val,
text: renderTextFormat(format, val, null, this.locale),
};
})
const colOptions = values.map(val => {
return {
value: val,
text: renderTextFormat(format, val, null, this.locale),
};
// cool, we've loaded up the columns with options
// preselect the option for this column
const optValue = getValueFromFormat(this.datetimeValue, format);
const selectedIndex = column.options.findIndex(opt => opt.value === optValue);
if (selectedIndex >= 0) {
// set the select index for this column's options
column.selectedIndex = selectedIndex;
}
// add our newly created column to the picker
columns.push(column);
});
// Normalize min/max
const min = this.datetimeMin;
const max = this.datetimeMax;
['month', 'day', 'hour', 'minute']
.filter(name => !columns.find(column => column.name === name))
.forEach(name => {
min[name] = 0;
max[name] = 0;
});
// cool, we've loaded up the columns with options
// preselect the option for this column
const optValue = getValueFromFormat(this.datetimeValue, format);
const selectedIndex = colOptions.findIndex(opt => opt.value === optValue);
columns = this.divyColumns(columns);
}
return {
name: key,
selectedIndex: selectedIndex >= 0 ? selectedIndex : 0,
options: colOptions
};
});
return columns;
// Normalize min/max
const min = this.datetimeMin;
const max = this.datetimeMax;
['month', 'day', 'hour', 'minute']
.filter(name => !columns.find(column => column.name === name))
.forEach(name => {
min[name] = 0;
max[name] = 0;
});
return divyColumns(columns);
}
private validate() {
@ -484,42 +479,6 @@ export class Datetime {
return 0;
}
private divyColumns(columns: PickerColumn[]): PickerColumn[] {
const pickerColumns = columns;
const columnsWidth: number[] = [];
let col: PickerColumn;
let width: number;
for (let i = 0; i < pickerColumns.length; i++) {
col = pickerColumns[i];
columnsWidth.push(0);
for (const option of col.options) {
width = option.text!.length;
if (width > columnsWidth[i]) {
columnsWidth[i] = width;
}
}
}
if (columnsWidth.length === 2) {
width = Math.max(columnsWidth[0], columnsWidth[1]);
pickerColumns[0].align = 'right';
pickerColumns[1].align = 'left';
pickerColumns[0].optionsWidth = pickerColumns[1].optionsWidth = `${width * 17}px`;
} else if (columnsWidth.length === 3) {
width = Math.max(columnsWidth[0], columnsWidth[2]);
pickerColumns[0].align = 'right';
pickerColumns[1].columnWidth = `${columnsWidth[1] * 17}px`;
pickerColumns[0].optionsWidth = pickerColumns[2].optionsWidth = `${width * 17}px`;
pickerColumns[2].align = 'left';
}
return columns;
}
/**
*/
private updateText() {
// create the text of the formatted data
const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT;
@ -527,8 +486,6 @@ export class Datetime {
this.text = renderDatetime(template, this.datetimeValue, this.locale);
}
/**
*/
hasValue(): boolean {
const val = this.datetimeValue;
return val
@ -580,6 +537,38 @@ export class Datetime {
}
}
function divyColumns(columns: PickerColumn[]): PickerColumn[] {
const columnsWidth: number[] = [];
let col: PickerColumn;
let width: number;
for (let i = 0; i < columns.length; i++) {
col = columns[i];
columnsWidth.push(0);
for (const option of col.options) {
width = option.text!.length;
if (width > columnsWidth[i]) {
columnsWidth[i] = width;
}
}
}
if (columnsWidth.length === 2) {
width = Math.max(columnsWidth[0], columnsWidth[1]);
columns[0].align = 'right';
columns[1].align = 'left';
columns[0].optionsWidth = columns[1].optionsWidth = `${width * 17}px`;
} else if (columnsWidth.length === 3) {
width = Math.max(columnsWidth[0], columnsWidth[2]);
columns[0].align = 'right';
columns[1].columnWidth = `${columnsWidth[1] * 17}px`;
columns[0].optionsWidth = columns[2].optionsWidth = `${width * 17}px`;
columns[2].align = 'left';
}
return columns;
}
let datetimeIds = 0;
const DEFAULT_FORMAT = 'MMM D, YYYY';

View File

@ -1,9 +1,8 @@
import { Component, Element, Prop, QueueApi } from '@stencil/core';
import { Component, Element, Event, EventEmitter, Prop, QueueApi } from '@stencil/core';
import { Gesture, GestureDetail, Mode, PickerColumn } from '../../interface';
import { hapticSelectionChanged } from '../../utils';
import { clamp } from '../../utils/helpers';
import { createThemedClasses } from '../../utils/theme';
/** @hidden */
@Component({
@ -14,7 +13,6 @@ export class PickerColumnCmp {
private bounceFrom!: number;
private lastIndex?: number;
private lastTempIndex?: number;
private minY!: number;
private maxY!: number;
private optHeight = 0;
@ -22,6 +20,7 @@ export class PickerColumnCmp {
private scaleFactor = 1;
private velocity = 0;
private y = 0;
private optsEl?: HTMLElement;
private gesture?: Gesture;
private rafId: any;
@ -31,6 +30,8 @@ export class PickerColumnCmp {
@Prop() col!: PickerColumn;
@Event() ionChange!: EventEmitter<void>;
componentWillLoad() {
let pickerRotateFactor = 0;
let pickerScaleFactor = 0.81;
@ -46,7 +47,7 @@ export class PickerColumnCmp {
async componentDidLoad() {
// get the scrollable element within the column
const colEl = this.el.querySelector('.picker-opts')!;
const colEl = this.optsEl!;
// get the height of one option
this.optHeight = (colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0);
@ -69,39 +70,35 @@ export class PickerColumnCmp {
private setSelected(selectedIndex: number, duration: number) {
// if there is a selected index, then figure out it's y position
// if there isn't a selected index, then just use the top y position
const y = (selectedIndex > -1) ? ((selectedIndex * this.optHeight) * -1) : 0;
const y = (selectedIndex > -1) ? -(selectedIndex * this.optHeight) : 0;
this.velocity = 0;
// set what y position we're at
cancelAnimationFrame(this.rafId);
this.update(y, duration, true, true);
this.update(y, duration, true);
this.ionChange.emit();
}
private update(y: number, duration: number, saveY: boolean, emitChange: boolean) {
private update(y: number, duration: number, saveY: boolean) {
// ensure we've got a good round number :)
y = Math.round(y);
let translateY = 0;
let translateZ = 0;
const parent = this.el.querySelector('.picker-opts')!;
const children = parent.children;
const length = children.length;
const selectedIndex = this.col.selectedIndex = Math.min(Math.max(Math.round(-y / this.optHeight), 0), length - 1);
const { col, rotateFactor } = this;
const selectedIndex = col.selectedIndex = this.indexForY(-y);
const durationStr = (duration === 0) ? null : duration + 'ms';
const scaleStr = `scale(${this.scaleFactor})`;
for (let i = 0; i < length; i++) {
const children = this.optsEl!.children;
for (let i = 0; i < children.length; i++) {
const button = children[i] as HTMLElement;
const opt = this.col.options[i];
const opt = col.options[i];
const optOffset = (i * this.optHeight) + y;
let visible = true;
let transform = '';
if (this.rotateFactor !== 0) {
const rotateX = optOffset * this.rotateFactor;
if (rotateFactor !== 0) {
const rotateX = optOffset * rotateFactor;
if (Math.abs(rotateX) > 90) {
visible = false;
} else {
@ -152,32 +149,15 @@ export class PickerColumnCmp {
this.y = y;
}
if (emitChange) {
if (this.lastIndex === undefined) {
// have not set a last index yet
this.lastIndex = this.col.selectedIndex;
} else if (this.lastIndex !== this.col.selectedIndex) {
// new selected index has changed from the last index
// update the lastIndex and emit that it has changed
this.lastIndex = this.col.selectedIndex;
// TODO ionChange event
// var ionChange = this.ionChange;
// if (ionChange.observers.length > 0) {
// this._zone.run(ionChange.emit.bind(ionChange, this.col.options[this.col.selectedIndex]));
// }
}
if (this.lastIndex !== selectedIndex) {
// have not set a last index yet
hapticSelectionChanged();
this.lastIndex = selectedIndex;
}
}
private decelerate() {
let y = 0;
if (isNaN(this.y) || !this.optHeight) {
// fallback in case numbers get outta wack
this.update(y, 0, true, true);
} else if (Math.abs(this.velocity) > 0) {
if (this.velocity !== 0) {
// still decelerating
this.velocity *= DECELERATION_FRICTION;
@ -186,7 +166,7 @@ export class PickerColumnCmp {
? Math.max(this.velocity, 1)
: Math.min(this.velocity, -1);
y = Math.round(this.y + this.velocity);
let y = this.y + this.velocity;
if (y > this.minY) {
// whoops, it's trying to scroll up farther than the options we have!
@ -199,13 +179,13 @@ export class PickerColumnCmp {
this.velocity = 0;
}
const notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1);
this.update(y, 0, true, !notLockedIn);
this.update(y, 0, true);
const notLockedIn = (Math.round(y) % this.optHeight !== 0) || (Math.abs(this.velocity) > 1);
if (notLockedIn) {
// isn't locked in yet, keep decelerating until it is
this.rafId = requestAnimationFrame(() => this.decelerate());
} else {
this.ionChange.emit();
}
} else if (this.y % this.optHeight !== 0) {
@ -217,14 +197,10 @@ export class PickerColumnCmp {
this.decelerate();
}
}
const currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
if (currentIndex !== this.lastTempIndex) {
// Trigger a haptic event for physical feedback that the index has changed
hapticSelectionChanged();
}
this.lastTempIndex = currentIndex;
private indexForY(y: number) {
return Math.min(Math.max(Math.abs(Math.round(y / this.optHeight)), 0), this.col.options.length - 1);
}
// TODO should this check disabled?
@ -250,8 +226,8 @@ export class PickerColumnCmp {
}
}
this.minY = (minY * this.optHeight * -1);
this.maxY = (maxY * this.optHeight * -1);
this.minY = -(minY * this.optHeight);
this.maxY = -(maxY * this.optHeight);
}
private onDragMove(detail: GestureDetail) {
@ -277,22 +253,17 @@ export class PickerColumnCmp {
this.bounceFrom = 0;
}
this.update(y, 0, false, false);
const currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
if (currentIndex !== this.lastTempIndex) {
this.lastTempIndex = currentIndex;
}
this.update(y, 0, false);
}
private onDragEnd(detail: GestureDetail) {
if (this.bounceFrom > 0) {
// bounce back up
this.update(this.minY, 100, true, true);
this.update(this.minY, 100, true);
return;
} else if (this.bounceFrom < 0) {
// bounce back down
this.update(this.maxY, 100, true, true);
this.update(this.maxY, 100, true);
return;
}
@ -304,10 +275,7 @@ export class PickerColumnCmp {
}
} else {
if (Math.abs(detail.deltaY) > 3) {
const y = this.y + detail.deltaY;
this.update(y, 0, true, true);
}
this.y += detail.deltaY;
this.decelerate();
}
}
@ -327,15 +295,14 @@ export class PickerColumnCmp {
if (this.col.prevSelected !== selectedIndex) {
const y = (selectedIndex * this.optHeight) * -1;
this.velocity = 0;
this.update(y, 150, true, false);
this.update(y, 150, true);
}
}
hostData() {
return {
class: {
...createThemedClasses(this.mode, 'picker-col'),
'picker-col': true,
'picker-opts-left': this.col.align === 'left',
'picker-opts-right': this.col.align === 'right'
},
@ -347,14 +314,6 @@ export class PickerColumnCmp {
render() {
const col = this.col;
const options = col.options.map(o => {
return (typeof o === 'string')
? { text: o }
: o;
})
.filter(o => o !== null);
const Button = 'button' as any;
return [
col.prefix && (
@ -362,8 +321,11 @@ export class PickerColumnCmp {
{col.prefix}
</div>
),
<div class="picker-opts" style={{ maxWidth: col.optionsWidth! }}>
{options.map((o, index) =>
<div
class="picker-opts"
style={{ maxWidth: col.optionsWidth! }}
ref={ el => this.optsEl = el }>
{ col.options.map((o, index) =>
<Button
class={{ 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }}
disable-activated

View File

@ -205,22 +205,6 @@ export class Picker implements OverlayInterface {
return eventMethod(this.el, 'ionPickerWillDismiss', callback);
}
/**
* Add a new PickerButton to the picker
*/
@Method()
addButton(button: PickerButton) {
this.buttons.push(button);
}
/**
* Add a new PickerColumn to the picker
*/
@Method()
addColumn(column: PickerColumn) {
this.columns.push(column);
}
/**
* Returns the column the matches the specified name
*/
@ -229,14 +213,6 @@ export class Picker implements OverlayInterface {
return this.columns.find(column => column.name === name);
}
/**
* Returns all the PickerColumns
*/
@Method()
getColumns(): PickerColumn[] {
return this.columns;
}
private buttonClick(button: PickerButton) {
// if (this.disabled) {
// return;
@ -295,41 +271,14 @@ export class Picker implements OverlayInterface {
const columns = this.columns;
// // clean up dat data
// data.columns = data.columns.map(column => {
// if (!isPresent(column.options)) {
// column.options = [];
// }
// column.selectedIndex = column.selectedIndex || 0;
// column.options = column.options.map(inputOpt => {
// let opt: PickerColumnOption = {
// text: '',
// value: '',
// disabled: inputOpt.disabled,
// };
// if (isPresent(inputOpt)) {
// if (isString(inputOpt) || isNumber(inputOpt)) {
// opt.text = inputOpt.toString();
// opt.value = inputOpt;
// } else {
// opt.text = isPresent(inputOpt.text) ? inputOpt.text : inputOpt.value;
// opt.value = isPresent(inputOpt.value) ? inputOpt.value : inputOpt.text;
// }
// }
// return opt;
// });
// return column;
// });
return [
<ion-backdrop
visible={this.showBackdrop}
tappable={this.enableBackdropDismiss}
/>,
<div class="picker-wrapper" role="dialog">
<div class="picker-toolbar">
{buttons.map(b => (
<div class={buttonWrapperClass(b)}>
@ -342,11 +291,13 @@ export class Picker implements OverlayInterface {
</div>
))}
</div>
<div class="picker-columns">
<div class="picker-above-highlight" />
{columns.map(c => <ion-picker-column col={c} />)}
<div class="picker-below-highlight" />
</div>
</div>
];
}