Skip to content
Snippets Groups Projects
Commit 377bacb3 authored by Tom Robinson's avatar Tom Robinson Committed by GitHub
Browse files

Merge pull request #4207 from metabase/more-flow-annotations

More Flow type annotations
parents 8b78e483 53e40a43
Branches
Tags
No related merge requests found
Showing
with 388 additions and 127 deletions
/* @flow */
// Legacy "tableMetadata" etc
import type { Table } from "metabase/meta/types/Table";
import type { Field } from "metabase/meta/types/Field";
import type { Segment } from "metabase/meta/types/Segment";
export type FieldValue = {
name: string,
key: string
}
export type OperatorName = string;
export type Operator = {
name: OperatorName,
verboseName: string,
moreVerboseName: string,
fields: OperatorField[],
multi: bool,
advanced: bool,
placeholders?: string[],
validArgumentsFilters: ValidArgumentsFilter[],
}
export type OperatorField = {
type: string,
values: FieldValue[]
}
export type ValidArgumentsFilter = (field: Field, table: Table) => bool;
export type FieldMetadata = Field & {
operators_lookup: { [name: string]: Operator }
}
export type TableMetadata = Table & {
segments: Segment[],
fields: FieldMetadata[]
}
......@@ -2,8 +2,8 @@
import type { TableId } from "./Table";
import type { FieldId } from "./Field";
import type { SegmentId } from "./Segment";
export type SegmentId = number;
export type MetricId = number;
export type ExpressionName = string;
......@@ -76,47 +76,38 @@ export type Breakout =
ConcreteField;
export type FilterClause = Filter;
export type Filter =
export type Filter = FieldFilter | CompoundFilter | NotFilter | SegmentFilter;
export type CompoundFilter =
AndFilter |
OrFilter |
NotFilter |
EqualFilter |
NEFilter |
LTFilter |
LTEFilter |
GTFilter |
GTEFilter |
OrFilter;
export type FieldFilter =
EqualityFilter |
ComparisonFilter |
BetweenFilter |
StringFilter |
NullFilter |
NotNullFilter |
NotNullFilter |
BetweenFilter |
InsideFilter |
StartsWithFilter |
ContainsFilter |
NotContainsFilter |
EndsWithFilter |
TimeIntervalFilter |
SegmentFilter;
type AndFilter = ["and", Filter, Filter];
type OrFilter = ["or", Filter, Filter];
type NotFilter = ["not", Filter];
type EqualFilter = ["=", ConcreteField, Value];
type NEFilter = ["!=", ConcreteField, Value];
type LTFilter = ["<", ConcreteField, OrderableValue];
type LTEFilter = ["<=", ConcreteField, OrderableValue];
type GTFilter = [">", ConcreteField, OrderableValue];
type GTEFilter = [">=", ConcreteField, OrderableValue];
type NullFilter = ["is-null", ConcreteField];
type NotNullFilter = ["not-null", ConcreteField];
type BetweenFilter = ["between", ConcreteField, OrderableValue, OrderableValue];
type InsideFilter = ["inside", ConcreteField, ConcreteField, NumericLiteral, NumericLiteral, NumericLiteral, NumericLiteral];
type StartsWithFilter = ["starts-with", ConcreteField, StringLiteral];
type ContainsFilter = ["contains", ConcreteField, StringLiteral];
type NotContainsFilter = ["does-not-contain", ConcreteField, StringLiteral];
type EndsWithFilter = ["ends-with", ConcreteField, StringLiteral];
type TimeIntervalFilter = ["time-interval", ConcreteField, RelativeDatetimePeriod, RelativeDatetimeUnit];
type SegmentFilter = ["segment", SegmentId];
TimeIntervalFilter;
export type AndFilter = ["and", Filter, Filter];
export type OrFilter = ["or", Filter, Filter];
export type NotFilter = ["not", Filter];
export type EqualityFilter = ["="|"!=", ConcreteField, Value];
export type ComparisonFilter = ["<"|"<="|">="|">", ConcreteField, OrderableValue];
export type BetweenFilter = ["between", ConcreteField, OrderableValue, OrderableValue];
export type StringFilter = ["starts-with"|"contains"|"does-not-contain"|"ends-with", ConcreteField, StringLiteral];
export type NullFilter = ["is-null", ConcreteField];
export type NotNullFilter = ["not-null", ConcreteField];
export type InsideFilter = ["inside", ConcreteField, ConcreteField, NumericLiteral, NumericLiteral, NumericLiteral, NumericLiteral];
export type TimeIntervalFilter = ["time-interval", ConcreteField, RelativeDatetimePeriod, RelativeDatetimeUnit];
export type SegmentFilter = ["segment", SegmentId];
export type OrderByClause = Array<OrderBy>;
export type OrderBy = ["asc"|"desc", Field];
......
/* @flow */
export type SegmentId = number;
// TODO: incomplete
export type Segment = {
name: string,
id: SegmentId,
is_active: bool
};
/* @flow */
import React, { Component, PropTypes } from "react";
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
import cx from "classnames";
......@@ -5,12 +7,29 @@ import { titleCase } from "humanize-plus";
import Icon from "metabase/components/Icon";
export default class DateOperatorSelector extends Component {
type Operator = {
name: string
}
type Props = {
operator: string,
operators: Operator[],
onOperatorChange: (o: Operator) => void
}
type State = {
expanded: bool
};
export default class DateOperatorSelector extends Component<*, Props, State> {
props: Props;
state: State;
constructor() {
super();
this.state = { expanded: false };
this.toggleExpanded = this.toggleExpanded.bind(this);
this.state = {
expanded: false
};
}
static propTypes = {
......@@ -19,7 +38,7 @@ export default class DateOperatorSelector extends Component {
onOperatorChange: PropTypes.func.isRequired
};
toggleExpanded () {
toggleExpanded = () => {
this.setState({ expanded: !this.state.expanded });
}
......
/* @flow */
import React, { Component, PropTypes } from "react";
import { findDOMNode } from 'react-dom';
import FilterWidget from './FilterWidget.jsx';
export default class FilterList extends Component {
constructor(props, context) {
super(props, context);
import type { Filter } from "metabase/meta/types/Query";
import type { Table } from "metabase/meta/types/Table";
type Props = {
filters: Array<Filter>,
tableMetadata: Table,
removeFilter: (index: number) => void,
updateFilter: (index: number, filter: Filter) => void,
maxDisplayValues?: bool
};
type State = {
shouldScroll: bool
};
export default class FilterList extends Component<*, Props, State> {
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
shouldScroll: false
};
}
static propTypes = {};
static defaultProps = {};
componentDidUpdate () {
this.state.shouldScroll ? (findDOMNode(this).scrollLeft = findDOMNode(this).scrollWidth) : null;
}
componentWillReceiveProps (nextProps) {
componentWillReceiveProps (nextProps: Props) {
// only scroll when a filter is added
if(nextProps.filters.length > this.props.filters.length) {
this.setState({ shouldScroll: true })
......
/* @flow */
import React, { Component, PropTypes } from "react";
import FieldList from "../FieldList.jsx";
......@@ -15,17 +17,34 @@ import { isDate } from "metabase/lib/schema_metadata";
import { singularize } from "metabase/lib/formatting";
import cx from "classnames";
import _ from "underscore";
export default class FilterPopover extends Component {
constructor(props, context) {
super(props, context);
import type { FieldFilter, ConcreteField, ExpressionClause } from "metabase/meta/types/Query";
import type { TableMetadata, FieldMetadata, Operator } from "metabase/meta/types/Metadata";
type Props = {
isNew?: bool,
filter?: FieldFilter,
onCommitFilter: () => void,
onClose: () => void,
tableMetadata: TableMetadata,
customFields: ExpressionClause
}
type State = {
filter: FieldFilter
}
export default class FilterPopover extends Component<*, Props, State> {
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
// $FlowFixMe
filter: (props.isNew ? [] : props.filter)
};
_.bindAll(this, "setField", "clearField", "setOperator", "setValues", "setFilter", "commitFilter");
}
static propTypes = {
......@@ -36,12 +55,12 @@ export default class FilterPopover extends Component {
tableMetadata: PropTypes.object.isRequired
};
commitFilter(filter) {
commitFilter = (filter: FieldFilter) => {
this.props.onCommitFilter(filter);
this.props.onClose();
}
setField(fieldId) {
setField = (fieldId: ConcreteField) => {
let { filter } = this.state;
if (filter[1] !== fieldId) {
// different field, reset the filter
......@@ -56,16 +75,17 @@ export default class FilterPopover extends Component {
// let the DatePicker choose the default operator, otherwise use the first one
let operator = isDate(field) ? null : field.valid_operators[0].name;
// $FlowFixMe
filter = this._updateOperator(filter, operator);
}
this.setState({ filter });
}
setFilter(filter) {
setFilter = (filter: FieldFilter) => {
this.setState({ filter });
}
setOperator(operator) {
setOperator = (operator: string) => {
let { filter } = this.state;
if (filter[0] !== operator) {
filter = this._updateOperator(filter, operator);
......@@ -73,24 +93,26 @@ export default class FilterPopover extends Component {
}
}
setValue(index, value) {
setValue(index: number, value: any) {
let { filter } = this.state;
filter[index + 2] = value;
this.setState({ filter: filter });
}
setValues(values) {
setValues = (values: any[]) => {
let { filter } = this.state;
// $FlowFixMe
this.setState({ filter: filter.slice(0,2).concat(values) });
}
_updateOperator(oldFilter, operatorName) {
_updateOperator(oldFilter: FieldFilter, operatorName: ?string): FieldFilter {
let { field } = Query.getFieldTarget(oldFilter[1], this.props.tableMetadata);
let operator = field.operators_lookup[operatorName];
let oldOperator = field.operators_lookup[oldFilter[0]];
// update the operator
let filter = [operatorName, oldFilter[1]];
// $FlowFixMe
let filter: FieldFilter = [operatorName, oldFilter[1]];
if (operator) {
for (let i = 0; i < operator.fields.length; i++) {
......@@ -139,16 +161,20 @@ export default class FilterPopover extends Component {
return true;
}
clearField() {
clearField = () => {
let { filter } = this.state;
// $FlowFixMe
this.setState({ filter: [...filter.slice(0, 1), null, ...filter.slice(2)] });
}
renderPicker(filter, field) {
let operator = field.operators_lookup[filter[0]];
renderPicker(filter: FieldFilter, field: FieldMetadata) {
let operator: ?Operator = field.operators_lookup[filter[0]];
return operator && operator.fields.map((operatorField, index) => {
if (!operator) {
return;
}
let values, onValuesChange;
let placeholder = operator.placeholders && operator.placeholders[index] || undefined;
let placeholder = operator && operator.placeholders && operator.placeholders[index] || undefined;
if (operator.multi) {
values = this.state.filter.slice(2);
onValuesChange = (values) => this.setValues(values);
......
/* @flow */
import React, { Component, PropTypes } from "react";
import Icon from "metabase/components/Icon.jsx";
......@@ -12,15 +14,30 @@ import { isDate } from "metabase/lib/schema_metadata";
import cx from "classnames";
import _ from "underscore";
export default class FilterWidget extends Component {
constructor(props, context) {
super(props, context);
import type { FieldFilter } from "metabase/meta/types/Query";
import type { TableMetadata } from "metabase/meta/types/Metadata";
type Props = {
filter: FieldFilter,
tableMetadata: TableMetadata,
index: number,
updateFilter: (index: number, field: FieldFilter) => void,
removeFilter: (index: number) => void,
maxDisplayValues?: number
}
type State = {
isOpen: bool
}
export default class FilterWidget extends Component<*, Props, State> {
state: State;
constructor(props: Props) {
super(props);
this.state = {
isOpen: this.props.filter[0] == undefined
};
_.bindAll(this, "open", "close", "removeFilter");
}
static propTypes = {
......@@ -35,15 +52,11 @@ export default class FilterWidget extends Component {
maxDisplayValues: 1
};
removeFilter() {
this.props.removeFilter(this.props.index);
}
open() {
open = () => {
this.setState({ isOpen: true });
}
close() {
close = () => {
this.setState({ isOpen: false });
}
......@@ -55,6 +68,7 @@ export default class FilterWidget extends Component {
let fieldDef = target && target.field;
let operatorDef = fieldDef && fieldDef.operators_lookup[operator];
// $FlowFixMe: not understanding maxDisplayValues is provided by defaultProps
if (operatorDef && operatorDef.multi && values.length > maxDisplayValues) {
values = [values.length + " selections"];
}
......@@ -133,7 +147,7 @@ export default class FilterWidget extends Component {
}
render() {
const { filter } = this.props;
const { filter, index, removeFilter } = this.props;
return (
<div className={cx("Query-filter p1 pl2", { "selected": this.state.isOpen })}>
<div>
......@@ -144,8 +158,8 @@ export default class FilterWidget extends Component {
}
{this.renderPopover()}
</div>
{ this.props.removeFilter &&
<a className="text-grey-2 no-decoration px1 flex align-center" onClick={this.removeFilter}>
{ removeFilter &&
<a className="text-grey-2 no-decoration px1 flex align-center" onClick={() => removeFilter(index)}>
<Icon name='close' size={14} />
</a>
}
......
/* @flow */
import React, { Component, PropTypes } from "react";
import Icon from "metabase/components/Icon.jsx";
......@@ -5,13 +7,28 @@ import Icon from "metabase/components/Icon.jsx";
import cx from "classnames";
import _ from "underscore";
export default class OperatorSelector extends Component {
constructor(props, context) {
super(props, context);
import type { Operator, OperatorName } from "metabase/meta/types/Metadata"
type Props = {
operator: string,
operators: Operator[],
onOperatorChange: (name: OperatorName) => void
};
type State = {
expanded: bool
};
export default class OperatorSelector extends Component<*, Props, State> {
props: Props;
state: State;
constructor(props: Props) {
super(props);
// if the initial operator is "advanced" expand the list
let operator = _.find(props.operators, o => o.name === props.operator);
this.state = {
expanded: operator && operator.advanced
expanded: !!(operator && operator.advanced)
};
}
......
/* @flow */
import React, { Component, PropTypes } from "react";
import SpecificDatePicker from "./SpecificDatePicker";
......@@ -9,6 +11,8 @@ import moment from "moment";
import _ from "underscore";
import type { FieldFilter, TimeIntervalFilter } from "metabase/meta/types/Query";
const SingleDatePicker = ({ filter: [op, field, value], onFilterChange }) =>
<SpecificDatePicker value={value} onChange={(value) => onFilterChange([op, field, value])} calendar />
......@@ -36,9 +40,22 @@ const PreviousPicker = (props) =>
const NextPicker = (props) =>
<RelativeDatePicker {...props} />
class CurrentPicker extends Component {
constructor() {
super();
type CurentPickerProps = {
filter: TimeIntervalFilter,
onFilterChange: (filter: TimeIntervalFilter) => void
};
type CurrentPickerState = {
showUnits: boolean
};
class CurrentPicker extends Component<*, CurentPickerProps, CurrentPickerState> {
props: CurentPickerProps;
state: CurrentPickerState;
constructor(props) {
super(props);
this.state = { showUnits: false };
}
......@@ -62,27 +79,37 @@ class CurrentPicker extends Component {
}
const getIntervals = ([op, field, value, unit]) => op === "TIME_INTERVAL" && typeof value === "number" ? Math.abs(value) : 30;
const getUnit = ([op, field, value, unit]) => op === "TIME_INTERVAL" && unit ? unit : "day";
const getIntervals = ([op, field, value, unit]) => op === "time-interval" && typeof value === "number" ? Math.abs(value) : 30;
const getUnit = ([op, field, value, unit]) => op === "time-interval" && unit ? unit : "day";
const getDate = (value) => typeof value === "string" && moment(value).isValid() ? value : moment().format("YYYY-MM-DD");
const OPERATORS = [
export type Operator = {
name: string,
widget?: any,
init: (filter: FieldFilter) => any,
test: (filter: FieldFilter) => boolean
}
const OPERATORS: Operator[] = [
{
name: "Previous",
init: (filter) => ["TIME_INTERVAL", filter[1], -getIntervals(filter), getUnit(filter)],
test: ([op, field, value]) => op === "TIME_INTERVAL" && value < 0 || Object.is(value, -0),
init: (filter) => ["time-interval", filter[1], -getIntervals(filter), getUnit(filter)],
// $FlowFixMe
test: ([op, field, value]) => op === "time-interval" && value < 0 || Object.is(value, -0),
widget: PreviousPicker,
},
{
name: "Next",
init: (filter) => ["TIME_INTERVAL", filter[1], getIntervals(filter), getUnit(filter)],
test: ([op, field, value]) => op === "TIME_INTERVAL" && value >= 0,
init: (filter) => ["time-interval", filter[1], getIntervals(filter), getUnit(filter)],
// $FlowFixMe
test: ([op, field, value]) => op === "time-interval" && value >= 0,
widget: NextPicker,
},
{
name: "Current",
init: (filter) => ["TIME_INTERVAL", filter[1], "current", getUnit(filter)],
test: ([op, field, value]) => op === "TIME_INTERVAL" && value === "current",
init: (filter) => ["time-interval", filter[1], "current", getUnit(filter)],
test: ([op, field, value]) => op === "time-interval" && value === "current",
widget: CurrentPicker,
},
{
......@@ -121,7 +148,13 @@ const OPERATORS = [
}
];
export default class DatePicker extends Component {
type Props = {
filter: FieldFilter,
onFilterChange: (filter: FieldFilter) => void,
tableMetadata: any
}
export default class DatePicker extends Component<*, Props, *> {
static propTypes = {
filter: PropTypes.array.isRequired,
onFilterChange: PropTypes.func.isRequired,
......
/* @flow */
import React, { Component, PropTypes } from "react";
import TextPicker from "./TextPicker.jsx";
export default class NumberPicker extends Component {
constructor(props, context) {
super(props, context);
type Props = {
values: Array<number|null>,
onValuesChange: (values: Array<number|null>) => void,
placeholder?: string,
multi?: bool,
}
type State = {
stringValues: Array<string>,
validations: bool[]
}
export default class NumberPicker extends Component<*, Props, State> {
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
values: props.values,
stringValues: props.values.map(v => String(v || "")),
validations: this._validate(props.values)
}
}
......@@ -15,7 +32,6 @@ export default class NumberPicker extends Component {
values: PropTypes.array.isRequired,
onValuesChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
validations: PropTypes.array,
multi: PropTypes.bool
};
......@@ -23,15 +39,15 @@ export default class NumberPicker extends Component {
placeholder: "Enter desired number"
};
_validate(values) {
_validate(values: Array<number|null>) {
return values.map(v => v === undefined || !isNaN(v));
}
onValuesChange(stringValues) {
onValuesChange(stringValues: string[]) {
let values = stringValues.map(v => parseFloat(v))
this.props.onValuesChange(values.map(v => isNaN(v) ? null : v));
this.setState({
values: stringValues,
stringValues: stringValues,
validations: this._validate(values)
});
}
......@@ -40,7 +56,7 @@ export default class NumberPicker extends Component {
return (
<TextPicker
{...this.props}
values={this.state.values.slice(0, this.props.values.length)}
values={this.state.stringValues.slice(0, this.props.values.length)}
validations={this.state.validations}
onValuesChange={(values) => this.onValuesChange(values)}
/>
......
/* @flow */
import React from "react";
import Input from "metabase/components/Input.jsx";
const NumericInput = ({ value, onChange, ...props }) =>
type Props = {
value: ?(number|string);
onChange: (value: ?number) => void
}
const NumericInput = ({ value, onChange, ...props }: Props) =>
<Input
value={value == null ? "" : String(value)}
onBlurChange={({ target: { value }}) => {
......
/* @flow */
import React, { Component, PropTypes } from "react";
import { pluralize, titleCase, capitalize } from "humanize-plus";
import cx from "classnames";
......@@ -5,7 +7,9 @@ import cx from "classnames";
import Icon from "metabase/components/Icon";
import NumericInput from "./NumericInput.jsx";
const PERIODS = [
import type { TimeIntervalFilter, RelativeDatetimeUnit } from "metabase/meta/types/Query";
const PERIODS: RelativeDatetimeUnit[] = [
"minute",
"hour",
"day",
......@@ -14,10 +18,26 @@ const PERIODS = [
"year"
];
export default class RelativeDatePicker extends Component {
constructor () {
super();
this.state = { showUnits: false };
type Props = {
filter: TimeIntervalFilter,
onFilterChange: (filter: TimeIntervalFilter) => void,
formatter: (value: any) => any
}
type State = {
showUnits: bool
}
export default class RelativeDatePicker extends Component<*, Props, State> {
props: Props;
state: State;
constructor (props: Props) {
super(props);
this.state = {
showUnits: false
};
}
static propTypes = {
......@@ -51,6 +71,7 @@ export default class RelativeDatePicker extends Component {
this.setState({ showUnits: false });
}}
togglePicker={() => this.setState({ showUnits: !this.state.showUnits})}
// $FlowFixMe: intervals could be a string like "current" "next"
intervals={intervals}
formatter={formatter}
/>
......@@ -59,7 +80,16 @@ export default class RelativeDatePicker extends Component {
}
}
export const UnitPicker = ({ open, value, onChange, togglePicker, intervals, formatter }) =>
type UnitPickerProps = {
value: RelativeDatetimeUnit,
onChange: (value: RelativeDatetimeUnit) => void,
open: bool,
intervals?: number,
togglePicker: () => void,
formatter: (value: ?number) => ?number
}
export const UnitPicker = ({ open, value, onChange, togglePicker, intervals, formatter }: UnitPickerProps) =>
<div>
<div
onClick={() => togglePicker()}
......
/* @flow */
import React, { Component, PropTypes } from "react";
import CheckBox from 'metabase/components/CheckBox.jsx';
......@@ -6,7 +8,22 @@ import { capitalize } from "metabase/lib/formatting";
import cx from "classnames";
export default class SelectPicker extends Component {
type SelectOption = {
name: string,
key: string
};
type Props = {
options: Array<SelectOption>,
values: Array<string>,
onValuesChange: (values: any) => void,
placeholder?: string,
multi?: bool
}
export default class SelectPicker extends Component<*, Props, *> {
props: Props;
static propTypes = {
options: PropTypes.object.isRequired,
values: PropTypes.array.isRequired,
......@@ -15,7 +32,7 @@ export default class SelectPicker extends Component {
multi: PropTypes.bool
};
selectValue(key, selected) {
selectValue(key: string, selected: bool) {
let values;
if (this.props.multi) {
values = this.props.values.slice().filter(v => v != null);
......@@ -30,7 +47,7 @@ export default class SelectPicker extends Component {
this.props.onValuesChange(values);
}
nameForOption(option) {
nameForOption(option: SelectOption) {
if (option.name === "") {
return "Empty";
} else if (typeof option.name === "string") {
......
/* @flow */
import React, { Component, PropTypes } from 'react';
import Calendar from "metabase/components/Calendar";
......@@ -13,15 +15,26 @@ import cx from "classnames";
const DATE_FORMAT = "YYYY-MM-DD";
const DATE_TIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";
export default class SpecificDatePicker extends Component {
constructor() {
super();
type Props = {
value: ?string,
onChange: (value: ?string) => void,
calendar?: bool
}
type State = {
showCalendar: bool
}
export default class SpecificDatePicker extends Component<*, Props, State> {
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
showCalendar: true
}
this.onChange = this.onChange.bind(this);
}
static propTypes = {
......@@ -29,7 +42,7 @@ export default class SpecificDatePicker extends Component {
onChange: PropTypes.func.isRequired,
};
onChange(date, hours, minutes) {
onChange = (date: ?string, hours: ?number, minutes: ?number) => {
let m = moment(date);
if (!m.isValid()) {
this.props.onChange(null);
......
/* @flow */
import React, { Component, PropTypes } from "react";
import Icon from "metabase/components/Icon.jsx";
import cx from "classnames";
export default class TextPicker extends Component {
type Props = {
values: Array<string|null>,
onValuesChange: (values: Array<string|null>) => void,
validations: bool[],
placeholder?: string,
multi?: bool
};
export default class TextPicker extends Component<*, Props, *> {
static propTypes = {
values: PropTypes.array.isRequired,
onValuesChange: PropTypes.func.isRequired,
......@@ -23,13 +33,13 @@ export default class TextPicker extends Component {
this.props.onValuesChange(values);
}
removeValue(index) {
removeValue(index: number) {
let values = this.props.values.slice();
values.splice(index, 1);
this.props.onValuesChange(values);
}
setValue(index, value) {
setValue(index: number, value: string|null) {
let values = this.props.values.slice();
values[index] = value;
this.props.onValuesChange(values);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment