4.22.3 • Published 5 months ago

@uiw/react-form v4.22.3

Weekly downloads
311
License
MIT
Repository
github
Last release
5 months ago

Form 表单

Buy me a coffee Open in unpkg NPM Downloads npm version

由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。

import { Form, FormItem } from 'uiw';
// or
import { Form, FormItem } from '@uiw/react-form';

基本用法

import React, { useState, useRef } from "react";
import { Form, Input, Row, Col, Slider, Button, Notify } from 'uiw';

export default function Demo() {
  const form = useRef()

  const onSubmit = () => {
    form.current.onSubmit()
  }
  const resetForm = () => {
    form.current.resetForm()
  }
  const getFieldValues = () => {
    console.log('getFieldValues', form.current.getFieldValues())
  }

  const setFieldValue=()=>{
    form.current.setFieldValue('name','UIW')
  }

  return (
    <div>
      <Form
        ref={form}
        onChange={({ initial, current }) => {
          console.log('onChange', initial, current);
        }}
        onSubmit={({ initial, current }) => {
          if (current.name === initial.name) {
            Notify.error({
              title: '提交失败!',
              description: `表单提交内容为空!`,
            });
          } else {
            Notify.success({
              title: '提交成功!',
              description: `姓名为:${current.name},提交完成,将自动填充初始化值!`,
            });
          }
        }}
        fields={{
          name: {
            label: '姓名',
            children: <Input placeholder="请输入姓名" />
          },
        }}
      >
        {({ fields, state, canSubmit }) => {
          return (
            <div>
              <Row>
                <Col style={{ maxWidth: 300 }}>{fields.name}</Col>
              </Row>
              <Row>
                <Col>
                  <Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
                </Col>
              </Row>
              <Row>
                <Col>
                  {JSON.stringify(state.current)}
                </Col>
              </Row>
            </div>
          )
        }}
      </Form>
      <Button type="primary" onClick={onSubmit} >submit</Button>
      <Button type="primary" onClick={resetForm}>resetForm</Button>
      <Button type="primary" onClick={getFieldValues}>getValues</Button>
      <Button type="primary" onClick={setFieldValue}>setFieldValue</Button>
    </div>
  )
}

自定义校验

一般校验可不需引入外部包解决,如果遇到大型工程表单比较多的地方推荐使用 jquense/yup

import React from 'react';
import { Form, Input, Notify, Checkbox, Switch, RadioGroup, Radio, Textarea, Row, Col, Button, Select, SearchSelect } from 'uiw';

export default function Demo() {
  return(
    <Form
      onSubmit={({initial, current}) => {
        const errorObj = {};
        if (current.userName.startsWith('u')) {
          errorObj.userName = `姓名 ${current.userName} 不能以 ‘u’ 开头`;
        }
        if (!current.checkboxOne) {
          errorObj.checkboxOne = '一个多选条件 为必填';
        }
        if (!current.terms) {
          errorObj.terms = '必须统一服务条款';
        }
        if(Object.keys(errorObj).length > 0) {
          const err = new Error();
          err.filed = errorObj;
          throw err;
        }
        Notify.success({
          title: '提交成功!',
          description: `姓名为:${current.userName},提交完成,将自动填充初始化值!`,
        });
      }}
      onSubmitError={(error) => {
        if (error.filed) {
          return { ...error.filed };
        }
        return null;
      }}
      fields={{
        userName: {
          initialValue: 'uiw',
          label: '姓名',
          children: <Input type="text" />,
          help: '以“u”开头的名字将在此处显示错误信息'
        },
        age: {
          initialValue: '9',
          label: '年龄',
          children: <Input type="number" />
        },
        select: {
          children: (
            <Select>
              <Select.Option value="w">Choose an item...</Select.Option>
              <Select.Option value="1">One</Select.Option>
              <Select.Option value="2">Two</Select.Option>
              <Select.Option value="3">Three</Select.Option>
              <Select.Option value="4">Four</Select.Option>
            </Select>
          )
        },
        searchSelect: {
          initialValue:[{label: 'a7', value: 7},{label: 'a8', value: 8}],
          children: (
            <SearchSelect
              allowClear
              labelInValue={true}
              showSearch={true}
              mode="multiple"
              disabled={false}
              placeholder="请选择选项"
              option={[
                { label: 'a1', value: 1 },
                { label: 'a2', value: 2 },
                { label: 'a3', value: 3 },
                { label: 'a4', value: 4 },
                { label: 'a5', value: 5 },
                { label: 'a6', value: 6 },
                { label: 'a7', value: 7 },
                { label: 'a8', value: 8 },
              ]}
            />
          )
        },
        checkbox: {
          initialValue: ['四川菜'],
          label: '选择你想吃的菜',
          children: (
            <Checkbox.Group>
              <div>菜系</div>
              <Checkbox value="四川菜">四川菜</Checkbox>
              <Checkbox value="湖北菜">湖北菜</Checkbox>
              <Checkbox value="西北菜">西北菜</Checkbox>
              <Checkbox value="新疆菜">新疆菜</Checkbox>
              <Checkbox value="东北菜">东北菜</Checkbox>
              <div style={{ marginTop: 10 }}>家常菜</div>
              <Checkbox value="红烧武昌鱼">红烧武昌鱼</Checkbox>
              <Checkbox value="麻婆豆腐">麻婆豆腐</Checkbox>
              <Checkbox value="北京烤鸭">北京烤鸭</Checkbox>
            </Checkbox.Group>
          )
        },
        checkboxOne: {
          inline: true,
          label: '一个多选条件',
          children: <Checkbox value="1">四川菜</Checkbox>
        },
        switch: {
          inline: true,
          initialValue: true,
          label: '开启',
          children: <Switch size="small" />
        },
        radioGroup: {
          inline: true,
          initialValue: '男',
          label: '单选',
          children: (
            <RadioGroup name="other">
              <Radio value="男">男</Radio>
              <Radio value="女">女</Radio>
              <Radio value="人妖" disabled>人妖</Radio>
              <Radio value="未知">未知</Radio>
            </RadioGroup>
          )
        },
        textarea: {
          initialValue: '',
          label: '多行文本输入框',
          children: <Textarea placeholder="请输入内容" />
        },
        terms: {
          validator: (currentValue) => {
            return !currentValue ? '必须统一服务条款' : null;
          },
          style: { marginBottom: 0 },
          children: <Checkbox value="1">已阅读并同意<a href="#">服务条款</a></Checkbox>
        }
      }}
    >
      {({ fields, state, canSubmit, resetForm }) => {
        console.log('fields:-->', state);
        return (
          <div style={{ maxWidth: 500 }}>
            <Row gutter={10}>
              <Col>{fields.userName}</Col>
              <Col>{fields.age}</Col>
            </Row>
            <Row gutter={10}>
              <Col>{fields.select}</Col>
              <Col>{fields.searchSelect}</Col>
            </Row>
            <Row gutter={10}>
              <Col>{fields.checkbox}</Col>
              <Col>{fields.checkboxOne}</Col>
            </Row>
            <Row gutter={10}>
              <Col>{fields.radioGroup}</Col>
            </Row>
            <Row gutter={10}>
              <Col>{fields.switch}</Col>
            </Row>
            <Row gutter={10}>
              <Col>{fields.textarea}</Col>
            </Row>
            <Row gutter={10}>
              <Col style={{ padding: '5px 0 10px 0' }}>
                {fields.terms}
              </Col>
            </Row>
            <Row gutter={10}>
              <Col fixed>
                <Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
                <Button type="light" onClick={resetForm}>重置表单</Button>
              </Col>
            </Row>
            <Row>
              <Col>
                <pre style={{ padding: '10px 0 0 10px' }}>
                  {JSON.stringify(state.current, null, 2)}
                </pre>
              </Col>
            </Row>
          </div>
        )
      }}
    </Form>
  )
}

水平登录栏

import React from 'react';
import { Form, Input, Row, Col, Notify, Button } from 'uiw';

export default function Demo() {
  return (
    <div>
      <Form
        onSubmit={({initial, current}) => {
          const errorObj = {};
          if (!current.username) {
            errorObj.username = '用户名不能为空!';
          }
          if (!current.password) {
            errorObj.password = '密码不能为空!';
          }
          if(Object.keys(errorObj).length > 0) {
            const err = new Error();
            err.filed = errorObj;
            Notify.error({ title: '提交失败!', description: '请确认提交表单是否正确!' });
            throw err;
          }
          console.log('-->>', initial, current);
          Notify.success({ title: '提交成功!', description: '恭喜你登录成功!' });
        }}
        onSubmitError={(error) => {
          if (error.filed) {
            return { ...error.filed };
          }
          return null;
        }}
        fields={{
          username: {
            labelClassName: 'fieldLabel',
            labelFor: 'username-inline',
            children: <Input preIcon="user" id="username-inline" />
          },
          password: {
            labelClassName: 'fieldLabel',
            labelFor: 'password-inline',
            children: <Input preIcon="lock" id="password-inline" type="password" />
          },
        }}
      >
        {({ fields, state, canSubmit, resetForm }) => {
          console.log('fields:', state);
          return (
            <div>
              <Row gutter={10}>
                <Col fixed>{fields.username}</Col>
                <Col fixed>{fields.password}</Col>
              </Row>
              <Row gutter={10}>
                <Col>
                  <Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
                  <Button type="danger" onClick={resetForm}>重置表单</Button>
                </Col>
              </Row>
            </div>
          )
        }}
      </Form>
    </div>
  );
}

登录

import React from 'react';
import { Form, Input, Row, Col, Checkbox, Notify, Button } from 'uiw';

export default function Demo() {
  return (
    <div>
      <Form
        onSubmit={({initial, current}) => {
          console.log('-->>', initial, current);
        }}
        fields={{
          username: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            labelFor: 'username',
            children: <Input preIcon="user" id="username" />
          },
          password: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            labelFor: 'password',
            children: <Input preIcon="lock" id="password" type="password" />
          },
          terms: {
            validator: (currentValue) => !currentValue ? '必须统一服务条款' : null,
            children: <Checkbox value="1">已阅读并同意</Checkbox>
          }
        }}
      >
        {({ fields, state, canSubmit }) => {
          console.log('fields:', state);
          return (
            <div>
              <Row>
                <Col fixed>{fields.username}</Col>
              </Row>
              <Row>
                <Col fixed>{fields.password}</Col>
              </Row>
              <Row>
                <Col fixed align="middle">{fields.terms}</Col>
                <Col fixed style={{ marginTop: -4 }}><a href="#">服务条款</a></Col>
              </Row>
              <Row>
                <Col fixed>
                  <Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
                </Col>
              </Row>
            </div>
          )
        }}
      </Form>
    </div>
  )
}

表单提交

import React from 'react';
import { Form, Input, Select, Row, Col, Button } from 'uiw';

export default function Demo() {
  return (
    <div>
      <Form
        onSubmit={({initial, current}) => {
          console.log('-->>', initial, current);
        }}
        fields={{
          firstName: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            inline: true,
            label: '姓氏',
            children: <Input />
          },
          lastName: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            initialValue: '先生',
            inline: true,
            label: '名字',
            children: <Input />
          },
          email: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            validator: (currentValue) => {
              return currentValue && currentValue.length < 2 ? 'Password must be 8+ characters' : null;
            },
            inline: true,
            label: 'Email',
            children: <Input />
          },
          select: {
            labelClassName: 'fieldLabel',
            labelStyle: { width: 60 },
            inline: true,
            label: '选择器',
            children: (
              <Select>
                <Select.Option>Choose an item...</Select.Option>
                <Select.Option value="1">One</Select.Option>
                <Select.Option value="2">Two</Select.Option>
                <Select.Option value="3">Three</Select.Option>
                <Select.Option value="4">Four</Select.Option>
              </Select>
            ),
          },
        }}
      >
        {({ fields, state, canSubmit }) => {
          console.log('fields:', state);
          return (
            <div>
              <Row gutter={10} style={{ marginBottom: 10 }}>
                <Col>{fields.firstName}</Col>
                <Col>{fields.lastName}</Col>
              </Row>
              <Row gutter={10}>
                <Col>{fields.email}</Col>
                <Col>{fields.select}</Col>
              </Row>
              <Row gutter={10}>
                <Col />
                <Col fixed align="bottom"><Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button></Col>
              </Row>
            </div>
          )
        }}
      </Form>
    </div>
  )
}

自定义控件应用

下面实例是在 <Form /> 表单组件中,应用自定义 <CustomSelect /> 控件组件。

⚠️ 注意,自定义控件需要两个必要的 props 参数,valueonChange

  • value 用于值传递,
  • onChange(value) 用于值变更需要执行的回调函数,回调函数第一个参数必须是 value
import React from 'react';
import { Form, Row, Col, Dropdown, Menu, Icon, Button, Notify } from 'uiw';

// 自定义组件
function CustomSelect(props) {
  const { option = [], onChange } = props;
  const [value, setValue] = React.useState(props.value);
  const [isOpen, setIsOpen] = React.useState(false);

  React.useEffect(() => {
    if (value !== props.value) {
      setValue(props.value);
    }
  }, [props.value]);
  const label = option.find(item => value === item.value);
  return (
    <Dropdown
      trigger="click"
      onVisibleChange={(open) => setIsOpen(open)}
      isOpen={isOpen}
      menu={
        <Menu bordered style={{ minWidth: 120 }}>
          {option.map((item, idx) => (
            <Menu.Item active={value === item.value} key={idx} text={item.label}
              onClick={() => {
                setValue(item.value);
                setIsOpen(false)
                onChange && onChange(item.value);
              }}
            />
          ))}
        </Menu>
      }
    >
      <Button
        style={{
          boxShadow: 'inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1)'
        }}
        type="link"
      >
        {label.label}<Icon type={isOpen ? 'up' : 'down'} />
      </Button>
    </Dropdown>
  );
}

// 自定义组件应用实例
export default function Demo() {
  return (
    <div>
      <Form
        onSubmitError={(error) => {
          if (error.filed) {
            return { ...error.filed };
          }
          return null;
        }}
        onSubmit={({initial, current}) => {
          console.log('~~~', current);
          const errorObj = {};
          if (!current.select) {
            errorObj.select = '内容为空,请输入内容';
          }
          if(Object.keys(errorObj).length > 0) {
            const err = new Error();
            err.filed = errorObj;
            Notify.error({ title: '提交失败!', description: '请确认提交表单是否正确!' });
            throw err;
          }
          Notify.success({
            title: '提交成功!',
            description: `表单提交成功,内容为:${current.select},将自动填充初始化值!`,
          });
        }}
        fields={{
          select: {
            initialValue: 0,
            children: (
              <CustomSelect option={[
                { label: '请选择', value: 0 },
                { label: '经济舱', value: 1 },
                { label: '豪华经济舱', value: 2 },
                { label: '商务舱', value: 3 },
                { label: '头等舱', value: 4 },
              ]} />
            )
          },
        }}
      >
        {({ fields, state, canSubmit }) => {
          return (
            <div>
              <Row>
                <Col style={{ maxWidth: 300 }}>{fields.select}</Col>
              </Row>
              <Row>
                <Col fixed>
                  <Button disabled={!canSubmit()} type="primary" htmlType="submit">提交</Button>
                </Col>
              </Row>
            </div>
          )
        }}
      </Form>
    </div>
  )
}

FormItem 竖排

对组件 FormItem 竖排展示示例。

⚠️ 注意:FormItem 组件只在 Form 组件中使用,在 @v4.10.4+ 以上版本可以当普通 form 使用。

import React from 'react';
import { Form, FormItem, Button, Input } from 'uiw';

export default function Demo() {
  const [formData, setFormData] = React.useState({});
  const handleSubmit = (_, e) => {
    e && e.preventDefault();
    const fData = new FormData(e.target);
    const data = {};
    fData.forEach((value, key) => { data[key] = value; });
    setFormData(data);
  }
  return (
    <Form onSubmit={handleSubmit} onReset={() => setFormData({})}>
      <FormItem
        label="可选字段"
        labelFor="item-basic-input"
        help={<span>在上面的字段中输入一个值</span>}
      >
        <Input id="item-basic-input" name="basic" type="text"/>
      </FormItem>
      <FormItem
        label="用户名"
        labelFor="item-username-input"
        help={(!formData.username || formData.username.length < 8) ? "用户名长度至少为8个字符串。" : "用户名正确 √ "}
        hasError={(!formData.username || formData.username.length < 8)}
      >
        <Input id="item-username-input" name="username" type="text"/>
      </FormItem>
      <FormItem>
        <Button type="success" htmlType="submit"> Submit </Button>
        <Button type="light" htmlType="reset"> Reset </Button>
      </FormItem>
      <pre>
      {JSON.stringify(formData, null, 2)}
      </pre>
    </Form>
  );
}

FormItem 横排

对组件 FormItem 横排展示示例。

⚠️ 注意:FormItem 组件只在 Form 组件中使用,在 @v4.10.4+ 以上版本可以当普通 form 使用。

import React from 'react';
import { Form, FormItem, Input } from 'uiw';

export default function Demo() {
  return (
    <Form>
      <FormItem
        inline={true}
        label="可选字段"
        labelFor="basic-input-inline"
        help={<span>在上面的字段中输入一个值</span>}
        onChange={() => {
          console.log('TEST::');
        }}
      >
        <Input id="basic-input-inline" name="basic" type="text"/>
      </FormItem>
      <FormItem
        inline={true}
        label="用户名"
        labelFor="username-input-inline"
        labelClassName="username"
        help="用户名长度至少为8个字符串。"
        hasError={true}
      >
        <Input id="username-input-inline" name="username" type="text"/>
      </FormItem>
    </Form>
  )
}

Form

参数说明类型默认值
fields设置字段object-
children回调 {fields, state, canSubmit, resetForm}function-
onSubmit提交表单时调用(state: FormSubmitProps, event: React.FormEvent) => any-
afterSubmit @3.0.0+提交回调 {initial, current}function({ initial, current })-
onChange表单发生改变回调函数 {initial, current}function({ initial, current })-
onSubmitError调用 onSubmit 抛出的任何错误。从字段名称返回对象映射。function-
resetOnSubmitonSubmit 成功后将表单重置为其初始状态。booltrue
ref返回form各种内部函数,可用于主动触发事件Ref-

fields

{
  firstName: {
    initialValue: '王',
    inline: true,
    label: '姓',
    labelClassName: 'fieldLabel',
    labelStyle: { width: 60 },
    // 验证,通过 `canSubmit()` 方法获得,提交按钮是否被禁用
    validator: (currentValue) => {},
    help: '帮助提示信息!',
    children: <Input type="number" />
  },
}

ref

const form = useRef()
render(<Form ref={form}/>)
form.current.onSubmit()                                   // 提交表单
form.current.resetForm()                                  // 重置form
const fieldValues = form.current.getFieldValues()         // 获取所有 field的 value对象
const error = form.current.getError()                     // 获取所有提交时验证错误
form.current.setFields({ /** [fieldName]: value **/  })   // 设置表单的值,覆盖 form所有 field的值
form.current.setFieldValue(fieldName, value)              // 对单个 field设置 value,如果 value为数组请自行深度拷贝后传值,以免破坏原数组

FormItem

参数说明类型默认值
label表单标题展示string-
required表单是否必填标识boolean-
labelClassName表单标题样式名称string-
labelStyle表单标题样式object-
labelFor列的宽度相对于同一网格中其他列的比率number-
help提示信息ReactNode-
hasError如果为true,则应用错误CSS。转动边框并帮助文字变红。number-
4.22.0

7 months ago

4.22.1

6 months ago

4.22.2

6 months ago

4.22.3

5 months ago

4.21.26

1 year ago

4.21.25

1 year ago

4.21.28

12 months ago

4.21.27

1 year ago

5.0.0-bate.2.1

1 year ago

5.0.0-bate.2.0

1 year ago

4.21.24

1 year ago

5.0.0-bate-19

1 year ago

4.21.22

1 year ago

4.21.21

1 year ago

4.21.23

1 year ago

5.0.0-bate-18

1 year ago

5.0.0-bate-14

1 year ago

5.0.0-bate-17

1 year ago

5.0.0-bate-10

1 year ago

5.0.0-bate-11

1 year ago

5.0.0-bate-12

1 year ago

5.0.0-bate-9

1 year ago

5.0.0-bate-7

1 year ago

5.0.0-bate-8

1 year ago

5.0.0-bate-6

1 year ago

4.21.20

1 year ago

4.21.19

2 years ago

4.21.18

2 years ago

4.21.15

2 years ago

4.21.17

2 years ago

4.21.16

2 years ago

5.0.0-bate-5

2 years ago

5.0.0-bate-3

2 years ago

5.0.0-bate-4

2 years ago

5.0.0-bate-1

2 years ago

5.0.0-bate-2

2 years ago

5.0.0-bate-0

2 years ago

4.21.13

2 years ago

4.21.14

2 years ago

4.21.11

2 years ago

4.21.12

2 years ago

4.21.10

2 years ago

4.21.1

2 years ago

4.21.2

2 years ago

4.21.3

2 years ago

4.21.4

2 years ago

4.21.0

2 years ago

4.20.0

2 years ago

4.21.9

2 years ago

4.21.5

2 years ago

4.21.6

2 years ago

4.21.7

2 years ago

4.21.8

2 years ago

4.18.1

2 years ago

4.18.0

2 years ago

4.17.0

2 years ago

4.16.0

2 years ago

4.16.1

2 years ago

4.16.2

2 years ago

4.19.0

2 years ago

4.15.1

2 years ago

4.14.1

2 years ago

4.14.2

2 years ago

4.14.0

2 years ago

4.13.7

2 years ago

4.13.8

2 years ago

4.13.9

2 years ago

4.13.11

2 years ago

4.13.12

2 years ago

4.13.10

2 years ago

4.15.0

2 years ago

4.10.5

2 years ago

4.10.6

2 years ago

4.10.7

2 years ago

4.9.9

2 years ago

4.10.1

2 years ago

4.10.2

2 years ago

4.10.3

2 years ago

4.10.4

2 years ago

4.10.0

2 years ago

4.13.6

2 years ago

4.13.2

2 years ago

4.13.3

2 years ago

4.13.4

2 years ago

4.13.5

2 years ago

4.13.0

2 years ago

4.13.1

2 years ago

4.12.0

2 years ago

4.12.1

2 years ago

4.12.2

2 years ago

4.11.4

2 years ago

4.11.5

2 years ago

4.11.6

2 years ago

4.9.11

2 years ago

4.9.10

2 years ago

4.11.0

2 years ago

4.11.1

2 years ago

4.11.2

2 years ago

4.11.3

2 years ago

4.9.7

3 years ago

4.9.6

3 years ago

4.9.4

3 years ago

4.9.3

3 years ago

4.9.2

3 years ago

4.9.1

3 years ago

4.9.0

3 years ago

4.8.9

3 years ago

4.8.8

3 years ago

4.7.16

3 years ago

4.8.5

3 years ago

4.8.4

3 years ago

4.8.7

3 years ago

4.8.6

3 years ago

4.8.1

3 years ago

4.8.0

3 years ago

4.8.3

3 years ago

4.8.2

3 years ago

4.7.13

3 years ago

4.7.14

3 years ago

4.7.15

3 years ago

4.7.12

3 years ago

4.7.10

3 years ago

4.7.11

3 years ago

4.7.9

3 years ago

4.7.8

3 years ago

4.7.7

3 years ago

4.7.6

3 years ago

4.7.5

3 years ago

4.7.4

3 years ago

4.7.3

3 years ago

4.7.2

3 years ago

4.7.0

3 years ago

4.6.19

3 years ago

4.6.15

3 years ago

4.6.16

3 years ago

4.6.17

3 years ago

4.6.18

3 years ago

4.6.14

4 years ago

4.6.13

4 years ago

4.6.12

4 years ago

4.6.11

4 years ago

4.6.10

4 years ago

4.6.9

4 years ago

4.6.8

4 years ago

4.6.7

4 years ago

4.6.6

4 years ago

4.6.3

4 years ago

4.6.4

4 years ago

4.6.2

4 years ago

4.6.1

4 years ago

4.6.0

4 years ago

4.5.0

4 years ago

4.4.1

4 years ago

4.4.0

4 years ago

4.3.1

4 years ago

4.2.3

4 years ago

4.2.5

4 years ago

4.3.0

4 years ago

4.2.14

4 years ago

4.2.1

4 years ago

4.2.0

4 years ago

4.1.2

4 years ago

4.1.1

4 years ago

4.1.0

4 years ago

4.0.0

4 years ago

4.0.0-alpha.7

4 years ago

4.0.0-alpha.8

4 years ago

4.0.0-alpha.6

4 years ago

4.0.0-alpha.3

4 years ago

4.0.0-alpha.2

4 years ago

4.0.0-alpha.1

4 years ago

4.0.0-alpha.0

4 years ago