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 [![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) ## 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 ``` 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
``` As you can see it does nothing for now, you need to feed it a `
`: ```vue
``` Now it renders: ```html
``` 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 `` component: ```vue
{{ props.meta.error }}
``` 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 ### `` #### Props ##### submit Type: `function`
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` 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`
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 `
`. ##### 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). ### `` #### Props ##### name Type: `string`
Required: `true` The name of this field. See [name](https://github.com/final-form/final-form#name-string-1). ##### validate Type: `function` `Array` 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`
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.
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 ================================================ ================================================ FILE: example/examples/Composition.vue ================================================ ================================================ FILE: example/examples/Simple.vue ================================================ ================================================ FILE: example/index.html ================================================ Vue-Final-Form
================================================ 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 ================================================ Vue-Final-Form
================================================ 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 `` 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()], })