1.0.134 • Published 4 years ago

@vicoders/reactive-form v1.0.134

Weekly downloads
374
License
-
Repository
-
Last release
4 years ago

Vicoders Reactive Form

Introduce

  • Reactive-Form was built on Angular Reactive Form
  • Reactive-Form helps us to build a form with many types of fields faster

Install

  • Step 1: Download package form npm
yarn add @vicoders/reactive-form

or

npm install @vicoders/reactive-form
  • Step2: Import module (app.module.ts)
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AngularReactiveFormModule, UPLOAD_DIRECTIVE_HEADERS, UPLOAD_DIRECTIVE_API_URL, TINYMCE_CONFIG } from '@vicoders/reactive-form';

@NgModule({
  declarations: [AppComponent],
  imports: [...ReactiveFormsModule, AngularReactiveFormModule],
  providers: [
    {
      provide: UPLOAD_DIRECTIVE_HEADERS,
      useValue: {
        Authorization: `Bearer ${token}`
      }
    },
    {
      provide: UPLOAD_DIRECTIVE_API_URL,
      useValue: 'http://api.reflaunt.local'
    },
    {
      provide: TINYMCE_CONFIG,
      useValue: {
        apiUpload: '/api/v1/upload',
        paramName: 'files',
        resultTransformer: result => result.data[0].full_path
      }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
  • Step 3: Import style & script (angular.json)
...
 "styles": [
    ...
    "node_modules/primeng/resources/themes/nova-light/theme.css",
    "node_modules/primeng/resources/primeng.min.css",
    "node_modules/primeicons/primeicons.css",
    "node_modules/select2/dist/css/select2.min.css",
    ...
  ],
  "scripts": [
    "node_modules/jquery/dist/jquery.js",
    "node_modules/select2/dist/js/select2.min.js",
    ...
  ],
...

Use

  • Declare an array of selections (app.component.ts)
import { InputBase, TextBox } from '@vicoders/reactive-form';
...
public inputs: any;
ngOnInit() {
    let inputs: InputBase<any>[] = [
      new TextBox({
        key: 'textbox',
        label: 'TextBox',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      })
    ];
    this.inputs = inputs;
  }
...
...
<reactive-form [(inputs)]="inputs" [onSubmit]="onSubmit" [onInitReactive]="onInitReactive" [onChangeReactive]="onChange" [keysChange]="keysChange" [scrollOnErrror]="true" submitText="Save">
</reactive-form>
...
  • Use Reset Form Button (app.component.html)
...
<reactive-form ... [isReset]="true">
  ...
</reactive-form>
...
  • Use CustomSubmit & CustomButton (app.component.html)
...
<reactive-form ... [customSubmit]="customSubmit" customeSubmitText="ok" [customButton]="customButton" customButtonText="ok1">
  ...
</reactive-form>
...
  • Get form value (app.component.ts)
...
onSubmit(form) {
  if (form.valid) {
    console.log('data', form.value);
  }
}

onInitReactive(form) {
    form.get('textbox-1').valueChanges.subscribe((val) => {
      form.get('textbox-2').setValue(val);
    });
  }

onChange(form) {
  if (form.valid) {
    console.log('data', form.value);
  }
}

customSubmit = form => {
  this.onSubmit(form);
}

customButton = () => {
}
...

Selections

  • Each Selection will have its own options, but all of them have default options:
OptionTypeDesciptionDefalut Value
labelstringlabel of selector (label: 'label')' '
keystringkey of selector (key: 'key')' '
valueanyvalue of selectornull
classesarrayA array of strings, use to add class for selector (classes: ['col-6'])
groupintegerAny selectors that have the same group will be in the same groupnull
group_classesarrayAny class of group (group)null
validatorsarray (ValidatorFn)A array of object (validators: [ { label: VALIDATOR_REQUIRED, validator: Validators.required, message: 'This field is required' }])

TextBox

OptionTypeDesciptionDefalut Value
typestring'password', 'number', ...none
disabledbooleanfalse
  • Result data Type: String (example: 'This is my textBox')

CheckBox

  • Result data Type: Boolean (example: true || false)

Radio

OptionTypeDesciptionDefalut Value
optionsobjectexample: [{ value: 1, label: 'label' }]
contenthtmlexample: | null
  • Result data Type: Value of Object was choosed: 1

Dropdown

OptionTypeDesciptionDefalut Value
optionsobjectexample: [{ value: 1, label: 'label' }]
disabledbooleanfalse
  • Result data Type: Object (example: { label: 'label', value: 1 })

DateTimePicker

OptionTypeDesciptionDefalut Value
showIconBooleantrue: display icon, false: undisplay iconfalse
monthNavigatorBooleantrue: display month navigator, false: undisplay month navigatorfalse
yearNavigatorBooleantrue: display year navigator, false: undisplay year navigatorfalse
yearRangeStringexample: '1995:2050''1995:2050'
showTimeBooleantrue: display time, false: undisplay timefalse
timeOnlyBooleantrue: display only time, false: undisplay timefalse
dateFormatStringexample" 'dd/mm/yy''dd/mm/yy'
disabledbooleanfalse
minDateDatenew Date('month/date/year') // 11/05/2018null
maxDateDatenew Date('month/date/year') // 11/10/2018null
  • Result data Type: Date

Dropzone

OptionTypeDesciptionDefalut Value
messageStringDescription of selector' '
upload_pathStringName of folder save your file'folder'
acceptedFilesStringtype of files accept upload ('image/*')*
maxFilesizenumberMaximum size file upload50
maxFilesnumberMaximun number of file upload0
showPreviewBooleanDisplay preview content of uploadfalse
urlStringUrl upload file, example: /api/v1/upload' '

| paramName | String | Name of param upload file | 'file' | | resultTransformer | Function | Each Api upload will have a different result structure so you must customize it to push on dropzone, (example: resultTransformer: result => result.data[0].full_path - data: result of api upload))) |

ListCheckBox

OptionTypeDesciptionDefalut Value
optionsobjectexample: [{ value: 1, label: 'label' }]
disablebooleanfalse
  • Result data Type: Array value of options (example: ['1', '2'])

PhoneCode

  • Result data Type: Object of phone code & phone number
  • Example:
  {
    code: '+84',
    value: '123456789',
    alpha2Code: 'VN'
  }

Select2

OptionTypeDesciptionDefalut Value
optionsobjectexample: [{ value: 1, label: 'label' }]
tagsBooleanAdded a value not in optionsfalse
placeholderStringplaceholder of select2' '
isSelectAllBooleanshow select all & clear All ?false
selectBtnTextstringlabel of select all btn'Select All'
clearBtnTextBooleanlabel of clear all btn'Clear All'
  • Result data Type: Array value of options was selected
  • Example:
  ['1', '2']
  • Set default value: Each option want set default, need add selected: true for this.
  {
    value: 1,
    label: 'label'
    selected: true
  }

TexBoxMask

OptionTypeDesciptionDefalut Value
maskArrayArray of Regex. (example: Mask Date [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/])
valueWithCharacterBooleanGet value with Character or nonefalse
placeholderStringPlaceholder' '
guideBooleanShow mask when enter Charactor or nonefalse
  • Result data Type: String with mask or none
  • Example:
  "07/10/1995"
  "07101995"

TinyMce

OptionTypeDesciptionDefalut Value
heightStringHeight of selector'300'
pluginsStringName of plugins you want add to selector. (example: 'print,bold,thin...')' '

UploadFile

OptionTypeDesciptionDefalut Value
idnumberId of input control UploadFile1
uploadPathStringName of folder save your file' '
acceptStringtype of files accept upload ('png|jpg|jpeg')*
allowMaxSizenumberMaximum size file upload2
multiplebooleantrue or falsefalse
apiUploadStringUrl upload file, example: /api/v1/upload' '
paramNameStringName of param upload file'files'
resultTransformerFunctionEach Api upload will have a different result structure so you must customize it to push on dropzone, (example: resultTransformer: result => result.data[0].full_path - data: result of api upload)))
titleHtmlUpload

TextArea

OptionTypeDesciptionDefalut Value
rowsNumberNumber lines of textarea10

Province

  • 63 provinces & cities of Vietnam
  • Result data Type: String (example: 'Thành phố Hà Nội')

SelectionByApi

OptionTypeDesciptionDefalut Value
apiUploadStringApi used to search or filter 'api/v1/admin/users?search='null
resultTransformerFunctionEach Api upload will have a different result structure so you must customize it to push on SelectionByApi, (example: resultTransformer: result => result.data - data: result of api upload)))
fieldNameFunctionreturn label by data object""
multiplebooleantrue or falsefalse
lengthToSearchNumberlenght of character start to filter3

Block

OptionTypeDesciptionDefalut Value
classesarrayA array of strings, use to add class for selector (classes: ['col-6'])
groupintegerAny selectors that have the same group will be in the same groupnull
group_classesarrayAny class of group (group)
contentText`<p><strong>Buon Ngu</strong></p>`

SingleSelect2

  • Use like DropDown

SwitchInput

  • result: true or false

Captcha

OptionTypeDesciptionDefalut Value
numberOfCharNumberNumber character4
typeString'number' or 'alphabet' or nullnull
messageStringMessage of captcha'Captcha not found'
  • Use
...
<reactive-form [captcha]="'captcha'"></reactive-form>
...

('captcha' is key of Captcha input in Form)

TypeAhead

  • search by api with type ahead ng-boostraps
OptionTypeDesciptionDefalut Value
apiUploadStringApi used to search or filter 'api/v1/admin/users?search='null
resultTransformerFunctionEach Api upload will have a different result structure so you must customize it to push on SelectionByApi, (example: resultTransformer: result => result.data - data: result of api upload)))
keyNamearrayarray of key display on optional""
fieldNameFuntionreturn label in input by key""
idnumber""
searchBystring""

TypeAheadWithoutApi

  • use like dropdown & singleselect2

RangerSlider

OptionTypeDesciptionDefalut Value
minnumber
maxnumber
stepnumber
miLabelstring
idnumber
unitstring

Gutenberg

OptionTypeDesciptionDefalut Value

add css @import "./../node_modules/@vicoders/ng-gutenberg/style/ng-gutenberg.scss"

Update&fill

  • use support.UpdateInputsValue(inputs, optionsData) to update options for Dropdown, Radio, ListCheckBox, Select2

  • use supportUpdateFormValue(input, valuesData) to fill data for inputs controll

  • Full Example (app.component.ts):

...
keysChange = ['textbox'];
public optionsData = {
    dropdown: [
      { label: 'Ha Noi', value: 1 },
      { label: 'TP - HCM', value: 2 },
      { label: 'Da Nang', value: 3 }],
    radio: [
      { label: 'Nam', value: 'nam' },
      { label: 'Nu', value: 'nu' }
    ],
    listcheckbox: [
      { label: 'Football', value: 1 },
      { label: 'Volleyball', value: 2 },
      { label: 'Movies', value: 3 },
      { label: 'Camping', value: 4 }
    ],
    select2: [
      { label: 'Football', value: 1 },
      { label: 'Volleyball', value: 2 },
      { label: 'Movies', value: 3 },
      { label: 'Camping', value: 4 }
    ],
    singleselect2: [
      { label: 'Football', value: 1 },
      { label: 'Volleyball', value: 2 },
      { label: 'Movies', value: 3 },
      { label: 'Camping', value: 4 }
    ]
  };

  public valuesData: any = {
    dropdown: _.find(this.data.dropdown, item => item.value === 2),
    radio: 'nu',
    listcheckbox: [2, 3],
    select2: [4, 1],
    checkbox: true,
    datetimepicker: new Date(),
    uploadfile: [
      'http://www.tensorflownews.com/wp-content/uploads/2018/09/1_8GVucX9yhnL21KCtcyFDRQ.png',
      'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTxadr9ykSPoaet-5e7-_YZtueYaRJSvggWtEShh2EJyAjAf5-D'
    ],

    phonecode: {
      code: '84',
      value: '3684523975'
    },
    textarea: 'Content',
    tinymce: '<b>blabla</b>',
    textboxmask: '07101995',
    singleselect2: _.find(this.data.singleselect2, item => item.value === 2),
    selectionbyapi: {
      created_at: '2018-12-18T03:53:00.000Z',
      email: 'admin@reflaunt.com',
      id: 2,
      last_login: '2019-03-28T08:35:08.000Z',
      status: 0,
      updated_at: '2019-03-28T08:35:08.000Z'
    }
  };

 ngOnInit() {
   const inputs: InputBase<any>[] = [
      new TextBox({
        key: 'textbox11111',
        label: 'Textbox',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new TextBox({
        key: 'textbox',
        label: 'Textbox',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new Dropdown({
        key: 'dropdown',
        label: 'Dropdown',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new Radio({
        key: 'radio',
        label: 'Radio',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new ListCheckBox({
        key: 'listcheckbox',
        label: 'ListCheckBox',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new Select2({
        id: 1,
        key: 'select2',
        label: 'Select2',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        isSelectAll: true
      }),
      new CheckBox({
        key: 'checkbox',
        label: 'CheckBox',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new DateTimePicker({
        key: 'datetimepicker',
        label: 'DateTimePicker',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        monthNavigator: true,
        yearNavigator: true,
        yearRange: '1990:2030'
      }),
      new UploadFile({
        key: 'uploadfile',
        label: 'UploadFile',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        accept: 'png|jpg|jpeg',
        allowMaxSize: 2,
        apiUpload: '/api/v1/upload',
        multiple: true,
        resultTransformer: result => result.data[0].full_path,
        paramName: 'files'
      }),
      new PhoneCode({
        key: 'phonecode',
        label: 'PhoneCode',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new TextArea({
        key: 'textarea',
        label: 'TextArea',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        placeholder: 'placeholder of textarea'
      }),
      new TinyMce({
        key: 'tinymce',
        label: 'TinyMce',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new TextBoxMask({
        key: 'textboxmask',
        label: 'TextBoxMask',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        mask: [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/]
      }),
      new Province({
        key: 'province',
        label: 'Province',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      }),
      new SelectionByApi({
        key: 'selectionbyapi',
        label: 'SelectionByApi',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1,
        apiUpload: '/api/v1/admin/users?search=',
        resultTransformer: result => result.data,
        fieldName: result => result.email,
        multiple: false,
        lengthToSearch: 1
      }),
      new SingleSelect2({
        key: 'singleselect2',
        label: 'Single Select2',
        classes: ['col-12'],
        group_classes: ['col-12'],
        group: 1
      })
    ];
    this.inputs = inputs;
    this.inputs = support.UpdateInputsValue(inputs, this.data);
    this.inputs = support.UpdateFormValue(inputs, this.values);
  }

...

Validation

Default Validation

https://angular.io/api/forms/Validators

  • Use & Example:
...
...
 new TextBox({
        ...
        validators: [
          {
            validator: Validators.required,
            message: 'This field is required'
          },
          {
            validator: Validators.pattern('[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}'),
            message: 'This field must be a email address'
          },
          {
            validator: Validators.minLength(6),
            message: 'Minimmum alowed charactes are 6'
          },
          {
            validator: Validators.maxLength(20),
            message: 'Maximmum alowed charactes are 20'
          }
        ],
        ...
      }),
...

Custom Validation

https://angular.io/guide/form-validation#custom-validators

Reactive-Form provide us some default validator

  • isEqualValidator('input-key')
  • isDifferentValidator('input-key')
  • isExistValidator('apiUrl', 'your-message') - 'apiUrl' must return { exist: true } if item exist
...
import { DefaultValidators } from 'angular-reactive-form';
...
 new TextBox({
        ...
        validators: [
          {
            validator: DefaultValidators.isEqualValidator('input-key'),
            message: 'This field not match with input-key '
          },
          {
            validator: DefaultValidators.isDifferentValidator('input-key'),
            message: 'This field must diffrent with input-key'
          }
        ],
        asyncValidators: [DefaultValidators.isExistValidator(apiUrl, yourMessage)]
        ...
      }),
...

If you want create a custom validator:

  • Step 1: Create new file (example.validators.ts)
...
  import { AbstractControl, ValidationErrors, AsyncValidatorFn } from '@angular/forms';

  export function isEqualValidator(matchValue: any) {
    return (control: AbstractControl): ValidationErrors | null => {
      const value = control.value;
      if (value) {
        const compareValue = control.root.get(matchValue).value;
        if (value !== compareValue) {
          return { notEqual: { valid: false, value: control.value } };
        }
      }
      return null;
    };
  }

  export function isExistValidator(url, message): AsyncValidatorFn {
    return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
      /* Your code */
      return new Promise(async (resolve, reject) => {
        if (value) {
          /* Your code => result */
          if (result.exist === true) {
            resolve({ isExist: { valid: false, value: control.value, message: message } });
          }
          resolve(null);
        }
        resolve(null);
      });
    };
  }
...
  • Step 2: Use (app.component.ts)
...
import { isEqualValidator, isExistValidator } from './validators/example';
...
 new TextBox({
        ...
        validators: [
          {
            validator: isEqualValidator('input-key'),
            message: 'This field not match with input-key '
          }
        ],
        asyncValidators: [isExistValidator('/api/v1/test/check/', 'Email is Exits')]

        ...
      }),
...

uu

Theme No.1 (ThemeID: reactive-form-theme-1)

  • angular.js: add style.css:
...
 "styles": [
              ...
              "node_modules/@vicoders/reactive-form/style/styles.scss",
            ],
...
  • With Form want use new style: <reactive-form themeID="'reactive-form-theme-1'" >
1.0.134

4 years ago

1.0.133

4 years ago

1.0.132

4 years ago

1.0.131

4 years ago

1.0.130

4 years ago

1.0.129

4 years ago

1.0.128

4 years ago

1.0.127

4 years ago

1.0.126

4 years ago

1.0.125

4 years ago

1.0.124

4 years ago

1.0.123

4 years ago

1.0.122

4 years ago

1.0.121

4 years ago

1.0.120

4 years ago

1.0.118

4 years ago

1.0.119

4 years ago

1.0.117

4 years ago

1.0.116

4 years ago

1.0.115

4 years ago

1.0.114

4 years ago

1.0.112

4 years ago

1.0.113

4 years ago

1.0.109

4 years ago

1.0.108

4 years ago

1.0.110

4 years ago

1.0.111

4 years ago

1.0.107

4 years ago

1.0.106

4 years ago

1.0.105

4 years ago

1.0.104

4 years ago

1.0.103

4 years ago

1.0.102

4 years ago

1.0.101

4 years ago

1.0.99

4 years ago

1.0.98

4 years ago

1.0.97

4 years ago

1.0.96

4 years ago

1.0.100

4 years ago

1.0.95

4 years ago

1.0.94

4 years ago

1.0.93

4 years ago

1.0.92

4 years ago

1.0.91

4 years ago

1.0.90

4 years ago

1.0.89

4 years ago

1.0.88

4 years ago

1.0.87

4 years ago

1.0.86

4 years ago

1.0.85

4 years ago

1.0.84

5 years ago

1.0.83

5 years ago

1.0.82

5 years ago

1.0.81

5 years ago

1.0.80

5 years ago

1.0.79

5 years ago

1.0.78

5 years ago

1.0.77

5 years ago

1.0.76

5 years ago

1.0.75

5 years ago

1.0.74

5 years ago

1.0.72

5 years ago

1.0.71

5 years ago

1.0.70

5 years ago

1.0.69

5 years ago

1.0.68

5 years ago

1.0.67

5 years ago

1.0.66

5 years ago

1.0.65

5 years ago

1.0.64

5 years ago

1.0.63

5 years ago

1.0.62

5 years ago

1.0.61

5 years ago

1.0.60

5 years ago

1.0.59

5 years ago

1.0.58

5 years ago

1.0.57

5 years ago

1.0.56

5 years ago

1.0.55

5 years ago

1.0.54

5 years ago

1.0.53

5 years ago

1.0.52

5 years ago

1.0.51

5 years ago

1.0.50

5 years ago

1.0.49

5 years ago

1.0.48

5 years ago

1.0.47

5 years ago

1.0.46

5 years ago

1.0.45

5 years ago

1.0.44

5 years ago

1.0.43

5 years ago

1.0.42

5 years ago

1.0.41

5 years ago

1.0.40

5 years ago

1.0.39

5 years ago

1.0.38

5 years ago

1.0.36

5 years ago

1.0.35

5 years ago

1.0.34

5 years ago

1.0.33

5 years ago

1.0.32

5 years ago

1.0.31

5 years ago

1.0.30

5 years ago

1.0.29

5 years ago

1.0.28

5 years ago

1.0.27

5 years ago

1.0.26

5 years ago

1.0.25

5 years ago

1.0.24

5 years ago

1.0.23

5 years ago

1.0.22

5 years ago

1.0.21

5 years ago

1.0.20

5 years ago

1.0.19

5 years ago

1.0.18

5 years ago

1.0.17

5 years ago

1.0.16

5 years ago

1.0.15

5 years ago

1.0.14

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

0.9.67

5 years ago

0.9.66

5 years ago

0.9.65

5 years ago

0.9.64

5 years ago

0.9.63

5 years ago

0.9.62

5 years ago

0.9.61

5 years ago

0.9.60

5 years ago

0.9.59

5 years ago

0.9.58

5 years ago

0.9.57

5 years ago

0.9.56

5 years ago

0.9.55

5 years ago

0.9.54

5 years ago

0.9.53

5 years ago

0.9.52

5 years ago

0.9.51

5 years ago

0.9.50

5 years ago

0.9.49

5 years ago

0.9.48

5 years ago

0.9.47

5 years ago

0.9.46

5 years ago

0.9.45

5 years ago

0.9.44

5 years ago

0.9.43

5 years ago

0.9.42

5 years ago

0.9.41

5 years ago

0.9.40

5 years ago

0.9.39

5 years ago

0.9.38

5 years ago

0.9.37

5 years ago

0.9.36

5 years ago

0.9.35

5 years ago

0.9.34

5 years ago

0.9.33

5 years ago

0.9.32

5 years ago

0.9.31

5 years ago

0.9.30

5 years ago

0.9.29

5 years ago

0.9.28

5 years ago

0.9.27

5 years ago

0.9.26

5 years ago

0.9.25

5 years ago

0.9.24

5 years ago

0.9.23

5 years ago

0.9.22

5 years ago

0.9.21

5 years ago

0.9.20

5 years ago

0.9.19

5 years ago

0.9.18

5 years ago

0.9.17

5 years ago

0.9.16

5 years ago

0.9.15

5 years ago

0.9.14

5 years ago

0.9.13

5 years ago

0.9.12

5 years ago

0.9.11

5 years ago

0.9.10

5 years ago

0.9.9

5 years ago

0.9.8

5 years ago

0.9.7

5 years ago

0.9.6

5 years ago

0.9.5

5 years ago

0.9.4

5 years ago

0.9.3

5 years ago

0.9.2

5 years ago

0.9.1

5 years ago

0.9.0

5 years ago

0.8.9

5 years ago

0.8.8

5 years ago

0.8.7

5 years ago

0.8.6

5 years ago

0.8.5

5 years ago

0.8.4

5 years ago

0.8.3

5 years ago

0.8.2

5 years ago

0.8.1

5 years ago

0.8.0

5 years ago

0.7.9

5 years ago

0.7.8

5 years ago

0.7.7

5 years ago

0.7.6

5 years ago

0.7.5

5 years ago

0.7.4

5 years ago

0.7.1

5 years ago

0.7.0

6 years ago

0.6.9

6 years ago

0.6.8

6 years ago

0.6.7

6 years ago

0.6.6

6 years ago

0.6.5

6 years ago

0.6.4

6 years ago

0.6.3

6 years ago

0.6.2

6 years ago

0.6.1

6 years ago

0.6.0

6 years ago

0.5.9

6 years ago

0.5.8

6 years ago

0.5.7

6 years ago

0.5.6

6 years ago

0.5.5

6 years ago

0.5.4

6 years ago

0.5.3

6 years ago

0.5.2

6 years ago

0.5.1

6 years ago

0.5.0

6 years ago

0.4.9

6 years ago

0.4.8

6 years ago

0.4.7

6 years ago

0.4.6

6 years ago

0.4.5

6 years ago

0.4.4

6 years ago

0.4.3

6 years ago

0.4.2

6 years ago

0.4.1

6 years ago

0.3.9

6 years ago

0.3.8

6 years ago

0.3.7

6 years ago

0.3.6

6 years ago

0.3.5

6 years ago

0.3.4

6 years ago

0.3.3

6 years ago

0.3.2

6 years ago

0.3.1

6 years ago

0.2.9

6 years ago

0.2.8

6 years ago

0.2.7

6 years ago

0.2.6

6 years ago

0.2.5

6 years ago

0.2.4

6 years ago

0.2.3

6 years ago

0.2.2

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.1.9

6 years ago

0.0.9

6 years ago

0.0.8

6 years ago

0.0.7

6 years ago

0.0.6

6 years ago

0.0.5

6 years ago

0.0.4

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago