Full Code of egoist/vue-final-form for AI

master 0be8ed488152 cached
23 files
25.9 KB
7.5k tokens
7 symbols
1 requests
Download .txt
Repository: egoist/vue-final-form
Branch: master
Commit: 0be8ed488152
Files: 23
Total size: 25.9 KB

Directory structure:
gitextract_6qdo1odu/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── circle.yml
├── example/
│   ├── App.vue
│   ├── examples/
│   │   ├── Composition.vue
│   │   └── Simple.vue
│   ├── index.html
│   ├── index.js
│   ├── package.json
│   └── router.js
├── index.html
├── package.json
├── src/
│   ├── Field.js
│   ├── Form.js
│   ├── index.js
│   ├── useCurrentForm.js
│   ├── useField.js
│   ├── useForm.js
│   └── utils.js
└── vite.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

================================================
FILE: .gitattributes
================================================
* text=auto


================================================
FILE: .gitignore
================================================
node_modules
dist/


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) EGOIST <0x142857@gmail.com> (https://egoist.moe)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# vue-final-form

<!-- hide-on-docup-start -->

[![NPM version](https://img.shields.io/npm/v/vue-final-form.svg?style=flat)](https://npmjs.com/package/vue-final-form) [![NPM downloads](https://img.shields.io/npm/dm/vue-final-form.svg?style=flat)](https://npmjs.com/package/vue-final-form) [![CircleCI](https://circleci.com/gh/egoist/vue-final-form/tree/master.svg?style=shield)](https://circleci.com/gh/egoist/vue-final-form/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) [![chat](https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat)](https://chat.egoist.moe)

<!-- hide-on-docup-stop -->

## Introduction

🏁 High performance subscription-based form state management for Vue.js.

## Install

```bash
yarn add final-form vue-final-form
```

## Usage

[![Edit example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/egoist/vue-final-form/tree/master/example)

This library exports two components:

```js
import { FinalForm, FinalField } from "vue-final-form";
```

The first component you'll need is the `FinalForm` component:

```vue
<FinalForm :submit="submit">
  <!-- ignore the children for now -->
</FinalForm>
```

The only required prop is `submit`, it defines how to submit the form data, maybe simply log it:

```js
function submit(values) {
  console.log(values);
}
```

The rendered output is:

```html
<div></div>
```

As you can see it does nothing for now, you need to feed it a `<form>`:

```vue
<FinalForm :submit="submit">
  <form v-slot="props" @submit="props.handleSubmit">
    <!-- ignore the children for now -->
  </form>
</FinalForm>
```

Now it renders:

```html
<div><form></form></div>
```

Here it uses the [`scoped slots`](https://vuejs.org/v2/guide/components.html#Scoped-Slots) feature from Vue.js (>=2.1.0), `props.handleSubmit` is the actual method it will run to submit data.

Next let's define a form field with `<FinalField>` component:

```vue
<FinalForm :submit="submit">
  <form v-slot="props" @submit="props.handleSubmit">
    <FinalField
      name="username"
      :validate="v => v ? null : 'this is required'">
      <div v-slot="props">
        <input v-on="props.events" :name="props.name">
        <span v-if="props.meta.error && props.meta.touched">
          {{ props.meta.error }}
        </span>
      </div>
    </FinalField>
  </form>
</FinalForm>
```

Things got a bit more complex, but it's easy if you try to understand:

The only prop that is required by `FinalField` is `name`, it tells the field where to store the field data in the form state, here we stores it as `state.username`.

The `validate` prop is used to validate the field data, it could be a function that returns an error message or `null`, `undefined` when it's considered valid.

The data that `FinalField` passed to its children contains `props.events` which is required to be bound with the `input` element in order to properly track events. And `props.name` is the `name` you gave `FinalField`, `props.meta` is some infomation about this field.

## API

### `<FinalForm>`

#### Props

##### submit

Type: `function`<br>
Default: `() => {}`

Here we allow `submit` to be optional since you may not need it when you just use `vue-final-form` as a form validation tool.

See [onSubmit](https://github.com/final-form/final-form#onsubmit-values-object-form-formapi-callback-errors-object--void--object--promiseobject--void).

##### validate

Type: `function` `Array<function>`

A whole-record validation function that takes all the values of the form and returns any validation errors.

See [validate](https://github.com/final-form/final-form#validate-values-object--object--promiseobject).

##### initialValues

Type: `object`

See [initialValues](https://github.com/final-form/final-form#initialvalues-object).

##### subscription

Type: `FormSubscription`<br>
Default: All

See [FormSubscription](https://github.com/final-form/final-form#formsubscription--string-boolean-).

#### Events

##### change

Params:

- `formState`: https://github.com/final-form/final-form#formstate

#### Scoped slot props

It basically exposes everything in [FormState](https://github.com/final-form/final-form#formstate) plus follwoings:

##### handleSubmit

Type: `function`

The function that you will invoke to submit the form data, you may use it as the `:submit` event handler on your `<form>`.

##### reset

Type: `function`

See [FormApi.reset](https://github.com/final-form/final-form#reset---void).

##### mutators

Type: `?{ [string]: Function }`

See [FormApi.mutators](https://github.com/final-form/final-form#mutators--string-function-).

##### batch

Type: `function`

See [FormApi.batch](https://github.com/final-form/final-form#batch-fn---void--void).

##### blur

Type: `function`

See [FormApi.blur](https://github.com/final-form/final-form#blur-name-string--void).

##### change

Type: `function`

See [FormApi.change](https://github.com/final-form/final-form#change-name-string-value-any--void).

##### focus

Type: `function`

See [FormApi.focus](https://github.com/final-form/final-form#focus-name-string--void)

##### initialize

Type: `function`

See [FormApi.initialize](https://github.com/final-form/final-form#initialize-values-object--void).

### `<FinalField>`

#### Props

##### name

Type: `string`<br>
Required: `true`

The name of this field.

See [name](https://github.com/final-form/final-form#name-string-1).

##### validate

Type: `function` `Array<function>`

A field-level validation function to validate a single field value. Returns an error if the value is not valid, or undefined if the value is valid.

See [validate](https://github.com/final-form/final-form#validate-value-any-allvalues-object--any).

##### subscription

Type: `FieldSubscription`<br>
Default: All

See [FieldSubcription](https://github.com/final-form/final-form#fieldsubscription--string-boolean-).

#### Events

##### change

Params:

- `fieldState`: https://github.com/final-form/final-form#fieldstate

#### Scoped slot props

It basically exposes [FieldState](https://github.com/final-form/final-form#fieldstate).

##### name

Type: `string`

The name of this field.

See [`FieldState.name`](https://github.com/final-form/final-form#name-string)

##### change

Type: `function`

See [`FieldState.change`](https://github.com/final-form/final-form#change-value-any--void)

##### value

Type: `any`.

The current value of this field. You should probably bind it to `:value` of `input` or `textarea` if you have set initial value for the field.

##### events

Type: `{ input: Function, focus: Function, blur: Function }`

Bind these event handlers to your `input` `textarea` element.

See [FieldState.change](https://github.com/final-form/final-form#change-value-any--void), [FieldState.focus](https://github.com/final-form/final-form#focus---void), [FieldState.blur](https://github.com/final-form/final-form#blur---void).

##### meta

Type: `object`

Everything in [FieldState](https://github.com/final-form/final-form#fieldstate) except for `blur` `change` `focus` `name`

## Contributing

1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D

## Author

**vue-final-form** © [EGOIST](https://github.com/egoist), Released under the [MIT](https://github.com/egoist/vue-final-form/blob/master/LICENSE) License.<br>
Authored and maintained by EGOIST with help from contributors ([list](https://github.com/egoist/vue-final-form/contributors)).

> [egoist.moe](https://egoist.moe) · GitHub [@EGOIST](https://github.com/egoist) · Twitter [@\_egoistlily](https://twitter.com/_egoistlily)


================================================
FILE: circle.yml
================================================
version: 2
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:latest
    branches:
      ignore:
        - gh-pages # list of branches to ignore
        - /release\/.*/ # or ignore regexes
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}
      - run:
          name: install dependences
          command: yarn
      - save_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules
      - run:
          name: test
          command: yarn test


================================================
FILE: example/App.vue
================================================
<template>
  <div id="app">
    <select
      class="component-list"
      v-model="current">
      <option value="">Select an example</option>
      <option
        class="component-item"
        v-for="item in list"
        :value="item"
        :key="item">
        {{ item }}
      </option>
    </select>
    <hr>
    <component
      v-if="$route.query.component"
      :is="$route.query.component" />
  </div>
</template>

<script>
import Simple from './examples/Simple.vue'
import Composition from './examples/Composition.vue'

export default {
  data() {
    return {
      list: [
        'Simple',
        'Composition',
      ],
      current: this.$route.query.component || ''
    }
  },

  watch: {
    current() {
      this.$router.push({
        query: {
          component: this.current
        }
      })
    }
  },

  components: {
    Simple,
    Composition,
  }
}
</script>

<style>
.error {
  color: red;
}
</style>

<style scoped>
.component-item {
  cursor: pointer;
}
</style>


================================================
FILE: example/examples/Composition.vue
================================================
<template>
  <div>
    <form @submit="finalForm.handleSubmit">
      <div>
        <input
          :name="emailField.name"
          :value="emailField.value"
          v-on="emailField.events"
          type="email"
        />
        <span
          class="error"
          v-if="emailField.touched && emailField.error">
          {{ emailField.error }}
        </span>
      </div>
      <div>
        <input
          :name="passwordField.name"
          :value="passwordField.value"
          v-on="passwordField.events"
          type="password"
        />
        <span
          class="error"
          v-if="passwordField.touched && passwordField.error">
          {{ passwordField.error }}
        </span>
      </div>
      <div>
        <input
          :name="confirmPasswordField.name"
          :value="confirmPasswordField.value"
          v-on="confirmPasswordField.events"
          type="password"
        />
        <span
          class="error"
          v-if="confirmPasswordField.touched && confirmPasswordField.error">
          {{ confirmPasswordField.error }}
        </span>
      </div>

      <button type="submit" :disabled="formState.submitting || null">
        {{ formState.submitting ? 'Submitting' : 'Submit' }}
      </button>
    </form>

    <pre v-if="formState"><code>form state:<br><br>{{ JSON.stringify(formState, null, 2) }}</code></pre>
  </div>
</template>

<script setup>
import { useForm, useField } from 'vue-final-form'

function sleep(timeout) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timeout)
  })
}

const required = v =>
  v ? null : 'This field is required!'

const matchedPassword = (value, values) =>
  value === values.password ? null : 'Mismatched password!'

const range = (min, max) =>
  value =>
    value && value.length >= min && value.length <= max ? null : `Password should be between length ${min} and ${max}`

const noSpecialChars = (v) =>
  /[!@#$%^&*()]/.test(v) ? 'Please do not use special chars' : null

const initialValues = {
  email: 'egoist@boring.com',
}

const handleSubmit = async state => {
  await sleep(2000)
  console.log(state)
}

const { finalForm, formState } = useForm({
  initialValues,
  onSubmit: handleSubmit,
})

const { fieldState: emailField } = useField({
  finalForm,
  name: 'email',
  validate: required,
})

const { fieldState: passwordField } = useField({
  finalForm,
  name: 'password',
  validate: [range(6, 20), noSpecialChars],
})

const { fieldState: confirmPasswordField } = useField({
  finalForm,
  name: 'confirmPassword',
  validate: matchedPassword,
})
</script>


================================================
FILE: example/examples/Simple.vue
================================================
<template>
  <div>
    <FinalForm
      :submit="handleSubmit"
      @change="updateState"
      :initialValues="initialValues">
      <template v-slot="props">
        <form @submit="props.handleSubmit">
          <FinalField name="email" :validate="required">
            <template v-slot="props">
              <div>
                <input
                  v-on="props.events"
                  :name="props.name"
                  :value="props.value"
                  type="email"
                />
                <span
                  class="error"
                  v-if="props.meta.touched && props.meta.error">
                  {{ props.meta.error }}
                </span>
              </div>
            </template>
          </FinalField>
          <FinalField name="password" :validate="[range(6, 20), noSpecialChars]">
            <template v-slot="props">
              <div>
                <input
                  v-on="props.events"
                  :name="props.name"
                  :value="props.value"
                  type="password"
                />
                <span
                  class="error"
                  v-if="props.meta.touched && props.meta.error">
                  {{ props.meta.error }}
                </span>
              </div>
            </template>
          </FinalField>
          <FinalField name="confirmPassword" :validate="matchedPassword">
            <template v-slot="props">
              <div>
                <input
                  v-on="props.events"
                  :name="props.name"
                  :value="props.value"
                  type="password"
                />
                <span
                  class="error"
                  v-if="props.meta.touched && props.meta.error">
                  {{ props.meta.error }}
                </span>
              </div>
            </template>
          </FinalField>
          <button type="submit" :disabled="props.submitting || null">
            {{ props.submitting ? 'Submitting' : 'Submit' }}
          </button>
        </form>
      </template>
    </FinalForm>

    <pre v-if="formState"><code>form state:<br><br>{{ JSON.stringify(formState, null, 2) }}</code></pre>
  </div>
</template>

<script>
import { FinalForm, FinalField } from 'vue-final-form'

function sleep(timeout) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timeout);
  })
}

export default {
  components: {
    FinalForm,
    FinalField
  },

  data() {
    return {
      formState: null,
      initialValues: {
        email: 'egoist@boring.com'
      }
    }
  },

  methods: {
    async handleSubmit(state) {
      await sleep(2000)
      console.log(state)
    },

    updateState(state) {
      this.formState = state
    },

    required(v) {
      return v ? null : 'This field is required!'
    },

    matchedPassword(value, values) {
      return value === values.password ? null : 'Mismatched password!'
    },

    range(min, max) {
      return value => {
        return value && value.length >= min && value.length <= max ? null : `Password should be between length ${min} and ${max}`
      }
    },

    noSpecialChars(v) {
      return /[!@#$%^&*()]/.test(v) ? 'Please do not use specfial chars' : null
    }
  }
}
</script>


================================================
FILE: example/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue-Final-Form</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/index.js"></script>
</body>
</html>


================================================
FILE: example/index.js
================================================
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)

app.mount('#app')


================================================
FILE: example/package.json
================================================
{
  "name": "example",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "final-form": "^4.0.0",
    "vue": "^3.0.0",
    "vue-router": "^4.0.0",
    "vue-final-form": "latest"
  }
}


================================================
FILE: example/router.js
================================================
import { h } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

export default createRouter({
  history: createWebHistory(),
  routes: [{
    path: '/',
    component: {
      name: 'Home',
      render: () => h(App)
    }
  }]
})


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue-Final-Form</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@egojump/docup/dist/docup.css">
</head>
<body>
    <a href="https://github.com/egoist/vue-final-form" class="github-corner" aria-label="View source on Github"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
  <div id="app"></div>
  <script src="https://cdn.jsdelivr.net/npm/@egojump/docup/dist/docup.js"></script>
  <script>
    var doc = new Docup()
    doc.start('#app')
  </script>
</body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "vue-final-form",
  "version": "4.0.1",
  "description": "Subscription-based form state management for Vue.js",
  "repository": {
    "url": "egoist/vue-final-form",
    "type": "git"
  },
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "unpkg": "dist/index.umd.js",
  "jsdelivr": "dist/index.umd.js",
  "files": [
    "dist"
  ],
  "scripts": {
    "test": "npm run lint && echo 'no tests!'",
    "lint": "xo",
    "prepublishOnly": "npm run build",
    "build": "bili --format esm,cjs,umd,umd-min --module-name VueFinalForm --global.final-form final-form --global.vue vue",
    "example": "vite",
    "build:example": "vite build",
    "gh": "gh-pages -d example/dist",
    "deploy": "npm run build:example && npm run gh"
  },
  "author": "egoist <0x142857@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.10.0",
    "bili": "^5.0.5",
    "eslint-config-rem": "^4.0.0",
    "final-form": "^4.0.0",
    "gh-pages": "^1.0.0",
    "vite": "^2.0.0",
    "vue": "^3.0.0",
    "vue-router": "^4.0.0",
    "xo": "^0.47.0"
  },
  "xo": {
    "extends": "rem",
    "envs": [
      "browser"
    ],
    "ignores": [
      "example/**"
    ],
    "rules": {
      "unicorn/filename-case": 0,
      "unicorn/prefer-export-from": 0,
      "import/prefer-default-export": 0
    }
  },
  "peerDependencies": {
    "final-form": "^4.0.0"
  }
}


================================================
FILE: src/Field.js
================================================
import { getChildren } from './utils.js'
import useField from './useField.js'

const FinalField = {
  name: 'final-field',

  props: {
    name: {
      required: true,
      type: String,
    },
    validate: [Function, Array],
    subscription: Object,
  },

  setup(props) {
    const { finalForm, fieldState } = useField({
      name: props.name,
      subscription: props.subscription,
      validate: props.validate,
    })

    return {
      finalForm,
      fieldState,
    }
  },

  watch: {
    fieldState(state) {
      this.$emit('change', state)
    },
  },

  render() {
    const {
      blur,
      change,
      focus,
      value,
      name,
      ...meta
    } = this.fieldState

    const children = this.$slots.default({
      events: this.fieldState.events,
      change,
      value,
      name,
      meta,
    })

    return getChildren(children)[0]
  },
}

export default FinalField


================================================
FILE: src/Form.js
================================================
import { h } from 'vue'
import { getChildren } from './utils.js'
import useForm from './useForm.js'

const FinalForm = {
  name: 'final-form',

  props: {
    initialValues: Object,
    submit: {
      type: Function,
      default: () => {},
    },
    subscription: Object,
    validate: [Function, Array],
  },

  setup(props) {
    const { finalForm, formState } = useForm({
      initialValues: props.initialValues,
      validate: props.validate,
      subscription: props.subscription,
      onSubmit: props.submit,
    })

    return {
      finalForm,
      formState,
    }
  },

  methods: {
    handleSubmit(event) {
      event && event.preventDefault()
      this.finalForm.submit()
    },
  },

  watch: {
    formState(state) {
      this.$emit('change', state)
    },
  },

  render() {
    const children = this.$slots.default
      ? this.$slots.default({
        ...this.formState,
        ...this.finalForm,
      })
      : this.$slots.default

    return h('div', null, getChildren(children))
  },
}

export default FinalForm


================================================
FILE: src/index.js
================================================
import FinalForm from './Form.js'
import FinalField from './Field.js'
import useForm from './useForm.js'
import useField from './useField.js'

export {
  FinalForm,
  FinalField,
  useForm,
  useField,
}


================================================
FILE: src/useCurrentForm.js
================================================
import { inject } from 'vue'

const useCurrentForm = () => {
  const finalForm = inject('finalForm')

  if (!finalForm) {
    throw new Error('Form was\'t found. Please provide it using `<FinalForm>` component or `useForm` hook.')
  }

  return finalForm
}

export default useCurrentForm


================================================
FILE: src/useField.js
================================================
import { fieldSubscriptionItems } from 'final-form'
import { ref, onUnmounted, computed } from 'vue'
import useCurrentForm from './useCurrentForm.js'
import { composeFieldValidators, makeSubscriptionObject } from './utils.js'

const defaultSubscription = makeSubscriptionObject(fieldSubscriptionItems)

const useField = config => {
  const finalForm = config.finalForm || useCurrentForm()

  const fieldState = ref({})

  const unregister = finalForm.value.registerField(config.name, state => {
    fieldState.value = state
  }, config.subscription || defaultSubscription, {
    getValidator: Array.isArray(config.validate) ? composeFieldValidators(config.validate) : () => config.validate,
  })

  onUnmounted(unregister)

  return {
    fieldState: computed(() => ({
      ...fieldState.value,
      events: {
        input: event => fieldState.value.change(event.target.value),
        focus: () => fieldState.value.focus(),
        blur: () => fieldState.value.blur(),
      },
    })),
    unregister,
  }
}

export default useField


================================================
FILE: src/useForm.js
================================================
import { provide, ref, onUnmounted } from 'vue'
import { createForm, formSubscriptionItems } from 'final-form'
import { composeFormValidators, makeSubscriptionObject } from './utils.js'

const defaultSubscription = makeSubscriptionObject(formSubscriptionItems)

const useForm = config => {
  const formApi = createForm({
    initialValues: config.initialValues,
    validate: Array.isArray(config.validate) ? composeFormValidators(config.validate) : config.validate,
    onSubmit: config.onSubmit,
  })

  const finalForm = ref({
    ...formApi,
    handleSubmit: event => {
      event && event.preventDefault()
      formApi.submit()
    },
  })

  const formState = ref()

  const unsubscribe = finalForm.value.subscribe(state => {
    formState.value = state
  }, config.subscription || defaultSubscription)

  onUnmounted(unsubscribe)

  provide('finalForm', finalForm)

  return {
    finalForm,
    formState,
    unsubscribe,
  }
}

export default useForm


================================================
FILE: src/utils.js
================================================
export const getChildren = children =>
  Array.isArray(children) ? children : [children]

const composeValidators = (validators, ...args) =>
  // eslint-disable-next-line unicorn/no-array-reduce
  validators.reduce((error, validator) => error || validator(...args), undefined)

export const composeFormValidators = validators => (...args) =>
  composeValidators(validators, ...args)

export const composeFieldValidators = validators => () => (...args) =>
  composeValidators(validators, ...args)

export const makeSubscriptionObject = subscriptionItems =>
  // eslint-disable-next-line no-use-extend-native/no-use-extend-native
  Object.fromEntries(subscriptionItems.map(key => [key, true]))


================================================
FILE: vite.config.js
================================================
import path from 'node:path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  root: 'example',
  resolve: {
    alias: {
      'vue-final-form': path.resolve('./src/index.js'),
    },
  },
  plugins: [vue()],
})
Download .txt
gitextract_6qdo1odu/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── circle.yml
├── example/
│   ├── App.vue
│   ├── examples/
│   │   ├── Composition.vue
│   │   └── Simple.vue
│   ├── index.html
│   ├── index.js
│   ├── package.json
│   └── router.js
├── index.html
├── package.json
├── src/
│   ├── Field.js
│   ├── Form.js
│   ├── index.js
│   ├── useCurrentForm.js
│   ├── useField.js
│   ├── useForm.js
│   └── utils.js
└── vite.config.js
Download .txt
SYMBOL INDEX (7 symbols across 2 files)

FILE: src/Field.js
  method setup (line 16) | setup(props) {
  method fieldState (line 30) | fieldState(state) {
  method render (line 35) | render() {

FILE: src/Form.js
  method setup (line 18) | setup(props) {
  method handleSubmit (line 33) | handleSubmit(event) {
  method formState (line 40) | formState(state) {
  method render (line 45) | render() {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (29K chars).
[
  {
    "path": ".editorconfig",
    "chars": 187,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".gitattributes",
    "chars": 12,
    "preview": "* text=auto\n"
  },
  {
    "path": ".gitignore",
    "chars": 19,
    "preview": "node_modules\ndist/\n"
  },
  {
    "path": "LICENSE",
    "chars": 1110,
    "preview": "The MIT License (MIT)\n\nCopyright (c) EGOIST <0x142857@gmail.com> (https://egoist.moe)\n\nPermission is hereby granted, fre"
  },
  {
    "path": "README.md",
    "chars": 7812,
    "preview": "# vue-final-form\n\n<!-- hide-on-docup-start -->\n\n[![NPM version](https://img.shields.io/npm/v/vue-final-form.svg?style=fl"
  },
  {
    "path": "circle.yml",
    "chars": 595,
    "preview": "version: 2\njobs:\n  build:\n    working_directory: ~/repo\n    docker:\n      - image: circleci/node:latest\n    branches:\n  "
  },
  {
    "path": "example/App.vue",
    "chars": 1005,
    "preview": "<template>\n  <div id=\"app\">\n    <select\n      class=\"component-list\"\n      v-model=\"current\">\n      <option value=\"\">Sel"
  },
  {
    "path": "example/examples/Composition.vue",
    "chars": 2617,
    "preview": "<template>\n  <div>\n    <form @submit=\"finalForm.handleSubmit\">\n      <div>\n        <input\n          :name=\"emailField.na"
  },
  {
    "path": "example/examples/Simple.vue",
    "chars": 3305,
    "preview": "<template>\n  <div>\n    <FinalForm\n      :submit=\"handleSubmit\"\n      @change=\"updateState\"\n      :initialValues=\"initial"
  },
  {
    "path": "example/index.html",
    "chars": 330,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "example/index.js",
    "chars": 154,
    "preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport router from './router'\n\nconst app = createApp(App)\n\na"
  },
  {
    "path": "example/package.json",
    "chars": 221,
    "preview": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"final-fo"
  },
  {
    "path": "example/router.js",
    "chars": 280,
    "preview": "import { h } from 'vue'\nimport { createRouter, createWebHistory } from 'vue-router'\nimport App from './App.vue'\n\nexport "
  },
  {
    "path": "index.html",
    "chars": 2077,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "package.json",
    "chars": 1393,
    "preview": "{\n  \"name\": \"vue-final-form\",\n  \"version\": \"4.0.1\",\n  \"description\": \"Subscription-based form state management for Vue.j"
  },
  {
    "path": "src/Field.js",
    "chars": 911,
    "preview": "import { getChildren } from './utils.js'\nimport useField from './useField.js'\n\nconst FinalField = {\n  name: 'final-field"
  },
  {
    "path": "src/Form.js",
    "chars": 1049,
    "preview": "import { h } from 'vue'\nimport { getChildren } from './utils.js'\nimport useForm from './useForm.js'\n\nconst FinalForm = {"
  },
  {
    "path": "src/index.js",
    "chars": 204,
    "preview": "import FinalForm from './Form.js'\nimport FinalField from './Field.js'\nimport useForm from './useForm.js'\nimport useField"
  },
  {
    "path": "src/useCurrentForm.js",
    "chars": 288,
    "preview": "import { inject } from 'vue'\n\nconst useCurrentForm = () => {\n  const finalForm = inject('finalForm')\n\n  if (!finalForm) "
  },
  {
    "path": "src/useField.js",
    "chars": 1038,
    "preview": "import { fieldSubscriptionItems } from 'final-form'\nimport { ref, onUnmounted, computed } from 'vue'\nimport useCurrentFo"
  },
  {
    "path": "src/useForm.js",
    "chars": 964,
    "preview": "import { provide, ref, onUnmounted } from 'vue'\nimport { createForm, formSubscriptionItems } from 'final-form'\nimport { "
  },
  {
    "path": "src/utils.js",
    "chars": 692,
    "preview": "export const getChildren = children =>\n  Array.isArray(children) ? children : [children]\n\nconst composeValidators = (val"
  },
  {
    "path": "vite.config.js",
    "chars": 299,
    "preview": "import path from 'node:path'\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vitejs"
  }
]

About this extraction

This page contains the full source code of the egoist/vue-final-form GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (25.9 KB), approximately 7.5k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!