1
0
mirror of synced 2024-11-25 14:56:03 +03:00

Initial commit of the version 2 rewrite

This commit is contained in:
Justin Schroeder 2019-10-07 10:24:30 -04:00
parent d3a1aea436
commit 27bd2bda26
57 changed files with 13735 additions and 5407 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,18 +0,0 @@
{
"presets": [
"stage-2",
["env", {
"browsers": "ie >= 11"
}]
],
"plugins": [
[
"transform-runtime", {
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
]
]
}

View File

@ -0,0 +1 @@
{"id":"FormulateInput.js","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570029289731}],"generated":{"js":"var template;\ntemplate += '<div class=\"formulate-input\"></div>';\nmodule.exports = {\n template: template\n};"},"sourceMaps":{"js":{"mappings":[{"generated":{"line":1,"column":0},"source":"FormulateInput.js","original":{"line":1,"column":0}},{"name":"template","generated":{"line":1,"column":4},"source":"FormulateInput.js","original":{"line":1,"column":4}},{"generated":{"line":1,"column":12},"source":"FormulateInput.js","original":{"line":1,"column":0}},{"name":"template","generated":{"line":2,"column":0},"source":"FormulateInput.js","original":{"line":2,"column":0}},{"generated":{"line":2,"column":8},"source":"FormulateInput.js","original":{"line":2,"column":8}},{"generated":{"line":2,"column":12},"source":"FormulateInput.js","original":{"line":2,"column":12}},{"generated":{"line":2,"column":49},"source":"FormulateInput.js","original":{"line":2,"column":0}},{"name":"module","generated":{"line":3,"column":0},"source":"FormulateInput.js","original":{"line":4,"column":0}},{"generated":{"line":3,"column":6},"source":"FormulateInput.js","original":{"line":4,"column":6}},{"name":"exports","generated":{"line":3,"column":7},"source":"FormulateInput.js","original":{"line":4,"column":7}},{"generated":{"line":3,"column":14},"source":"FormulateInput.js","original":{"line":4,"column":0}},{"generated":{"line":3,"column":17},"source":"FormulateInput.js","original":{"line":4,"column":17}},{"name":"template","generated":{"line":4,"column":0},"source":"FormulateInput.js","original":{"line":5,"column":2}},{"name":"template","generated":{"line":4,"column":2},"source":"FormulateInput.js","original":{"line":5,"column":2}},{"generated":{"line":4,"column":10},"source":"FormulateInput.js","original":{"line":5,"column":10}},{"name":"template","generated":{"line":4,"column":12},"source":"FormulateInput.js","original":{"line":5,"column":12}},{"generated":{"line":5,"column":0},"source":"FormulateInput.js","original":{"line":4,"column":17}},{"generated":{"line":5,"column":1},"source":"FormulateInput.js","original":{"line":4,"column":0}}],"sources":{"FormulateInput.js":"var template\ntemplate += '<div class=\"formulate-input\"></div>'\n\nmodule.exports = {\n template: template\n}\n"},"lineCount":null}},"error":null,"hash":"22dfb709434432cfadc29ec640064600","cacheData":{"env":{}}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":"../node_modules/isobject/index.js","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570029289731},{"name":"/Users/justinschroeder/Projects/vue-formulate/node_modules/isobject/package.json","includedInParent":true,"mtime":1570025459931}],"generated":{"js":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = isObject;\n\n/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\nfunction isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n}\n\n;"},"sourceMaps":{"js":{"mappings":[{"generated":{"line":8,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":1,"column":0}},{"generated":{"line":14,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"name":"isObject","generated":{"line":14,"column":9},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":24}},{"generated":{"line":14,"column":17},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"name":"val","generated":{"line":14,"column":18},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":33}},{"generated":{"line":14,"column":21},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"generated":{"line":14,"column":23},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":38}},{"generated":{"line":15,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":2}},{"name":"val","generated":{"line":15,"column":9},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"generated":{"line":15,"column":12},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":12}},{"generated":{"line":15,"column":16},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":16}},{"generated":{"line":15,"column":20},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"generated":{"line":15,"column":24},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":24}},{"name":"val","generated":{"line":15,"column":31},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":31}},{"generated":{"line":15,"column":34},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":24}},{"generated":{"line":15,"column":39},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":39}},{"generated":{"line":15,"column":47},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"name":"Array","generated":{"line":15,"column":51},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"generated":{"line":15,"column":56},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":56}},{"name":"isArray","generated":{"line":15,"column":57},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":57}},{"generated":{"line":15,"column":64},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"name":"val","generated":{"line":15,"column":65},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":65}},{"generated":{"line":15,"column":68},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"generated":{"line":15,"column":74},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":74}},{"generated":{"line":15,"column":79},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":2}},{"generated":{"line":16,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1}},{"generated":{"line":18,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1}}],"sources":{"../node_modules/isobject/index.js":"/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n"},"lineCount":null}},"error":null,"hash":"6f384709c2688dd9587aa95ad5f2aadc","cacheData":{"env":{}}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":"2368","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570038445966},{"name":"/Users/justinschroeder/Projects/vue-formulate/node_modules/isobject/package.json","includedInParent":true,"mtime":1570025459931}],"generated":{"js":"\"use strict\";function e(e){return null!=e&&\"object\"==typeof e&&!1===Array.isArray(e)}Object.defineProperty(exports,\"__esModule\",{value:!0}),exports.default=e;"},"sourceMaps":{"js":{"mappings":[{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":0}},{"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15},"generated":{"line":1,"column":13}},{"source":"../node_modules/isobject/index.js","name":"isObject","original":{"line":8,"column":24},"generated":{"line":1,"column":22}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":8,"column":33},"generated":{"line":1,"column":24}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":9},"generated":{"line":1,"column":27}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":16},"generated":{"line":1,"column":34}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":9},"generated":{"line":1,"column":40}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":39},"generated":{"line":1,"column":43}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":31},"generated":{"line":1,"column":60}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":74},"generated":{"line":1,"column":64}},{"source":"../node_modules/isobject/index.js","name":"Array","original":{"line":9,"column":51},"generated":{"line":1,"column":68}},{"source":"../node_modules/isobject/index.js","name":"isArray","original":{"line":9,"column":57},"generated":{"line":1,"column":74}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":65},"generated":{"line":1,"column":82}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":85}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":92}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":107}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":115}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":128}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":129}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":136}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":140}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":148}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":156}}],"sources":{"../node_modules/isobject/index.js":"/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n"},"lineCount":null}},"error":null,"hash":"f6e2f7389bba4628636f1d49740fe136","cacheData":{"env":{}}}

View File

@ -0,0 +1 @@
{"id":"vLuo","dependencies":[],"generated":{"js":"\n(function(){var t=exports.default||module.exports;\"function\"==typeof t&&(t=t.options),Object.assign(t,{render:function(){var t=this.$createElement;return(this._self._c||t)(\"div\",{staticClass:\"formulate-input\"},[this._v(\" Formulate input!\\n\")])},staticRenderFns:[],_compiled:!0,_scopeId:null,functional:void 0});})();"},"sourceMaps":null,"error":null,"hash":"376c05db7b07c17660e31aef0b2213fa","cacheData":{}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":"y3nB","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570038445966}],"generated":{"js":"var e;e+='<div class=\"formulate-input\">hi!</div>',module.exports={template:e};"},"sourceMaps":{"js":{"mappings":[{"source":"FormulateInput.js","original":{"line":1,"column":0},"generated":{"line":1,"column":0}},{"source":"FormulateInput.js","name":"template","original":{"line":1,"column":4},"generated":{"line":1,"column":4}},{"source":"FormulateInput.js","name":"template","original":{"line":2,"column":0},"generated":{"line":1,"column":6}},{"source":"FormulateInput.js","original":{"line":2,"column":12},"generated":{"line":1,"column":9}},{"source":"FormulateInput.js","name":"module","original":{"line":4,"column":0},"generated":{"line":1,"column":50}},{"source":"FormulateInput.js","name":"exports","original":{"line":4,"column":7},"generated":{"line":1,"column":57}},{"source":"FormulateInput.js","original":{"line":4,"column":17},"generated":{"line":1,"column":65}},{"source":"FormulateInput.js","name":"template","original":{"line":5,"column":2},"generated":{"line":1,"column":66}},{"source":"FormulateInput.js","name":"template","original":{"line":5,"column":12},"generated":{"line":1,"column":75}}],"sources":{"FormulateInput.js":"var template\ntemplate += '<div class=\"formulate-input\">hi!</div>'\n\nmodule.exports = {\n template: template\n}\n"},"lineCount":null}},"error":null,"hash":"fc879b8fcc134381138418e21efeb502","cacheData":{"env":{}}}

View File

@ -1 +1,2 @@
dist/*
test/*

View File

@ -1,22 +1,22 @@
module.exports = {
root: true,
env: {
browser: true,
node: true
},
parserOptions: {
parser: 'babel-eslint'
parser: 'babel-eslint',
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: [
'standard',
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/recommended',
],
// required to lint *.vue files
plugins: [
'vue'
'plugin:vue/recommended'
],
env: {
browser: true,
},
// add your custom rules here
rules: {}
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

227
README.md
View File

@ -1,228 +1,5 @@
# Vue Formulate
---------------
[![Build Status](https://travis-ci.org/wearebraid/vue-formulate.svg?branch=master)](https://travis-ci.org/wearebraid/vue-formulate)
[![Current Version](https://img.shields.io/npm/v/vue-formulate.svg)](https://www.npmjs.com/package/vue-formulate)
[![License](https://img.shields.io/github/license/wearebraid/vue-formulate.svg)](https://github.com/wearebraid/vue-formulate/blob/master/LICENSE.txt)
[![Build Status](https://travis-ci.com/wearebraid/vue-formulate-next.svg?token=4eHp5aiDcHwjrb1T8zpy&branch=version-2)](https://travis-ci.com/wearebraid/vue-formulate-next)
### What is it?
Vue Formulate is a [Vue](https://vuejs.org/) plugin that exposes an elegant
mechanism for building and validating forms with a centralized data store.
### Show and tell
You'll find an easy to use example, in [the example directory](https://github.com/wearebraid/vue-formulate/tree/master/example)
as well as a live demo available at: [demo.vueformulate.com](https://demo.vueformulate.com).
### Get Started
#### Download
First download the `vue-formulate` package from npm:
```sh
npm install vue-formulate
```
If you want to use ES6 features in your project
(and this readme assumes you do), then you'll also
need Babel:
```sh
babel-preset-env
babel-preset-stage-2
```
Many Vue/Vuex projects require Babels `stage-2` preset.
Best practice is to include a `.babelrc` in the project
root:
```sh
{
"presets": [
["env", { "modules": false }],
"stage-2"
]
}
```
#### Installation
Install `vue-formulate` like any other vue plugin:
```js
import Vue from 'vue'
import formulate from 'vue-formulate'
Vue.use(formulate)
```
#### Vuex
`vue-formulate` needs to be linked to your vuex store. Vuex can be
configured as a single root store, or as namespaced modules and `vue-formualte`
can work with either setup.
**Vuex Module**
```js
import {formulateModule} from 'vue-formulate'
export default formulateModule('namespace')
```
Using a namespaced vuex module is the recommended installation method. Just be
sure to replace `'namespace'` with the namespace of your vuex module.
Additionally, when using a vuex namespace, you _must_ also pass the namespace
in the Vue plugin installation call:
```js
Vue.use(formulate, {vuexModule: 'namespace'})
```
Alternatively, you can install `vue-formulate`'s store elements to your vuex
root store:
**Root Store**
```js
import Vue from 'vue'
import Vuex from 'vuex'
import {formulateState, formulateGetters, formulateMutations} from 'vue-formulate'
Vue.use(Vuex)
const state = () => ({
// your own state data can live next to vue-formulate's data
// Note: formulateState is a curried function.
your: 'data',
...formulateState()()
})
const getters = {
// Your own getters can live next to vue-formulate's getters
yourGetter (state) {
return state.your
},
...formulateGetters()
}
const mutations = {
// Your own mutations can live next to vue-formulate's mutations
setYour (state, payload) {
state.your = payload
},
...formulateMutations()
}
export default new Vuex.Store({
state,
getters,
mutations
})
```
### Usage
`vue-formulate` automatically registers two components `formulate` and
`formulate-element`. These two elements are able to address most of your form
building needs. Here's a simple example:
```html
<formulate name="registration">
<formulate-element
name="email"
type="email"
/>
...more formulate-elements
</formulate>
```
You can think of `<formulate>` elements a little bit like traditional
`<form>` tags. You _must_ wrap your `formulate-element` components
in a `<formulate>` component. The `formulate` component has a single
required prop `name` which creates the forms key in the vuex store.
All `formulate-element` components nested inside a `<formulate>`
component will automatically commit mutations directly to the
store. The store becomes a live representation of all your forms
values.
The `formulate-element` component is a powerful component which handles field
generation.
### Validation Rules
There are several built-in validation methods and you can easily add your own as well.
Rule | Arguments
----------|---------------
required | *none*
email | *none*
confirmed | confirmation field
number | *none*
You can add as many validation rules as you want to each `formulate-element`,
simply chain your rules with pipes `|'. Additional arguments can be passed to
validation rules by using parenthesis after the rule name:
```
validation="required|confirmed(confirmation_field)"
```
The field label used in built-in validation methods is the `validation-label`
attribute on your `formulate-element`. If no `validation-label` is found then
the `label` attribute is used, and if no `label` attribute is found it will
fall back to the fields `name` attribute (which is required).
#### Custom Validation Rules
Validation rules are easy to write! They're just simple functions that are
always passed at least one argument, an object containing the `field` name,
`value` of the field, validation `label`, `error` function to generate an error
message, and an object containing all the `values` for the entire form.
Additionally, validation rules can pass an unlimited number of extra arguments.
These arguments are passed as the 2nd-nth arguments to the validation rule.
Their values are parsed from the optional parenthesis in the validation
attribute on the `formulate-element`.
```html
<formulate-element
type="password"
name="password"
label="Password"
validation="confirmed(password_confirmation_field)"
/>
```
Validation rules should return an error message string if they failed, or
`false` if the input data is valid.
Adding your own validation rules is easy. Just pass an additional object
of rule functions in the plugins installation call:
```js
Vue.use(formulate, {
rules: {
isPizza ({field, value, error, values, label}) {
return value === 'pizza' ? false : `label is not pizza.`
}
}
})
```
### Styling
Absolutely zero styles are included so feel free to write your own! The
`form-element` components have a wrapper `div` that receives the following
classes:
```
formulate-element
formulate-element--has-value
formulate-element--has-errors
```
### Full Documentation
There are many more options available, more documentation coming soon.
Documentation development is taking place on the [vueformulate.com](https://github.com/wearebraid/vueformulate.com/tree/version-2) repository.

13
babel.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
env: {
test: {
presets: [
[ '@babel/preset-env', {
targets: {
node: 'current'
}
} ]
]
}
}
}

24
build/rollup.config.js Normal file
View File

@ -0,0 +1,24 @@
import commonjs from 'rollup-plugin-commonjs' // Convert CommonJS modules to ES6
import vue from 'rollup-plugin-vue' // Handle .vue SFC files
import buble from 'rollup-plugin-buble' // Transpile/polyfill with reasonable browser support
export default {
input: 'src/Formulate.js', // Path relative to package.json
output: {
name: 'Formulate',
exports: 'named',
globals: {
'is-plain-object': 'isPlainObject',
'nanoid': 'nanoid'
}
},
external: ['is-plain-object', 'nanoid'],
plugins: [
commonjs(),
vue({
css: true, // Dynamically inject css as a <style> tag
compileTemplate: true // Explicitly convert template to render function
}),
buble() // Transpile to ES5
]
}

1308
dist/formulate.esm.js vendored Normal file

File diff suppressed because it is too large Load Diff

1315
dist/formulate.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
dist/formulate.min.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"sources":["../node_modules/isobject/index.js","../node_modules/is-plain-object/index.js","Formulate.js"],"names":["isObject","val","Array","isArray","isObjectObject","o","Object","prototype","toString","call","isPlainObject","ctor","prot","constructor","hasOwnProperty","require","FormulateInput","Formulate","defaults","components","install","Vue","options","componentName","$formulate","extend","component","base","extendWith","merged","key","prop","module","exports"],"mappings":";AASC,aAFc,SAASA,EAASC,GACxBA,OAAO,MAAPA,GAA8B,iBAARA,IAA2C,IAAvBC,MAAMC,QAAQF,GAChE,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,QAAA;;ACyBA,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,QAAA,EA3BD,IAAA,EAAA,EAAA,QAAA,aA2BC,SAAA,EAAA,GAAA,OAAA,GAAA,EAAA,WAAA,EAAA,CAAA,QAAA,GAzBD,SAASG,EAAeC,GACf,OAAgB,KAAhB,EAASA,EAAAA,SAAAA,IAC2B,oBAAtCC,OAAOC,UAAUC,SAASC,KAAKJ,GAGvB,SAASK,EAAcL,GAChCM,IAAAA,EAAKC,EAELR,OAAsB,IAAtBA,EAAeC,KAIC,mBADpBM,EAAON,EAAEQ,gBAKoB,IAAzBT,EADJQ,EAAOD,EAAKJ,aAIiC,IAAzCK,EAAKE,eAAe;;;;;AC5B1B,IAAIJ,EAAgBK,QAAQ,mBACxBC,EAAiBD,QAAQ,wBAKzBE,EAAY,WACTC,KAAAA,SAAW,CACdC,WAAY,CAEVH,eAAgBA,KAQtBC,EAAUV,UAAUa,QAAU,SAAUC,EAAKC,GAGtC,IAAA,IAAIC,KAFTF,EAAId,UAAUiB,WAAa,KACtBF,KAAAA,QAAU,KAAKG,OAAO,KAAKP,SAAUI,GAAW,IAC3B,KAAKA,QAAQH,WACrCE,EAAIK,UAAUH,EAAe,KAAKD,QAAQH,WAAWI,KASzDN,EAAUV,UAAUkB,OAAS,SAAUE,EAAMC,GACvCC,IAAAA,EAAS,GACR,IAAA,IAAIC,KAAOH,EACVC,EAAWd,eAAegB,GAC5BD,EAAOC,GAAOpB,EAAckB,EAAWE,KAASpB,EAAciB,EAAKG,IAC/D,KAAKL,OAAOE,EAAKG,GAAMF,EAAWE,IAClCF,EAAWE,GAEfD,EAAOC,GAAOH,EAAKG,GAGlB,IAAA,IAAIC,KAAQH,EACVC,EAAOf,eAAeiB,KACzBF,EAAOE,GAAQH,EAAWG,IAGvBF,OAAAA,GAGTG,OAAOC,QAAU,IAAIhB","file":"formulate.min.js","sourceRoot":"../src","sourcesContent":["/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n","/*!\n * is-plain-object <https://github.com/jonschlinkert/is-plain-object>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nimport isObject from 'isobject';\n\nfunction isObjectObject(o) {\n return isObject(o) === true\n && Object.prototype.toString.call(o) === '[object Object]';\n}\n\nexport default function isPlainObject(o) {\n var ctor,prot;\n\n if (isObjectObject(o) === false) return false;\n\n // If has modified constructor\n ctor = o.constructor;\n if (typeof ctor !== 'function') return false;\n\n // If has modified prototype\n prot = ctor.prototype;\n if (isObjectObject(prot) === false) return false;\n\n // If constructor does not have an Object-specific method\n if (prot.hasOwnProperty('isPrototypeOf') === false) {\n return false;\n }\n\n // Most likely a plain Object\n return true;\n};\n","var isPlainObject = require('is-plain-object')\nvar FormulateInput = require('./FormulateInput.vue')\n\n/**\n * The base formulate libary.\n */\nvar Formulate = function () {\n this.defaults = {\n components: {\n // FormulateForm: FormulateForm,\n FormulateInput: FormulateInput\n }\n }\n}\n\n/**\n * Install vue formulate, and register its components.\n */\nFormulate.prototype.install = function (Vue, options) {\n Vue.prototype.$formulate = this\n this.options = this.extend(this.defaults, options || {})\n for (var componentName in this.options.components) {\n Vue.component(componentName, this.options.components[componentName])\n }\n}\n\n/**\n * Create a new object by copying properties of base and extendWith.\n * @param {Object} base\n * @param {Object} extendWith\n */\nFormulate.prototype.extend = function (base, extendWith) {\n var merged = {}\n for (var key in base) {\n if (extendWith.hasOwnProperty(key)) {\n merged[key] = isPlainObject(extendWith[key]) && isPlainObject(base[key])\n ? this.extend(base[key], extendWith[key])\n : extendWith[key]\n } else {\n merged[key] = base[key]\n }\n }\n for (var prop in extendWith) {\n if (!merged.hasOwnProperty(prop)) {\n merged[prop] = extendWith[prop]\n }\n }\n return merged\n}\n\nmodule.exports = new Formulate()\n"]}

1318
dist/formulate.umd.js vendored Normal file

File diff suppressed because it is too large Load Diff

43
dist/index.js vendored

File diff suppressed because one or more lines are too long

140
dist/snow.css vendored Normal file
View File

@ -0,0 +1,140 @@
.formulate-input {
margin-bottom: 2em; }
.formulate-input .formulate-input-label {
display: block;
line-height: 1.5;
font-size: .9em;
font-weight: 600;
margin-bottom: .1em; }
.formulate-input .formulate-input-element {
max-width: 20em;
margin-bottom: .1em; }
.formulate-input .formulate-input-help {
color: #6d6d6d;
font-size: .7em;
font-weight: 300;
line-height: 1.5;
margin-bottom: .25em; }
.formulate-input .formulate-input-group-item {
margin-bottom: .5em; }
.formulate-input:last-child {
margin-bottom: 0; }
.formulate-input[data-classification='text'] input {
appearance: none;
border-radius: .3em;
border: 1px solid #cecece;
box-sizing: border-box;
background-color: transparent;
font-size: .9em;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
padding: .75em;
display: block;
width: 100%;
font-weight: 400; }
.formulate-input[data-classification='text'] input::placeholder {
color: #a8a8a8; }
.formulate-input[data-classification='text'] input:focus {
outline: 0;
border: 1px solid #41b883; }
.formulate-input[data-classification='textarea'] textarea {
appearance: none;
border-radius: .3em;
border: 1px solid #cecece;
box-sizing: border-box;
background-color: transparent;
font-size: .9em;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
padding: .75em;
display: block;
width: 100%;
font-weight: 400; }
.formulate-input[data-classification='textarea'] textarea::placeholder {
color: #a8a8a8; }
.formulate-input[data-classification='textarea'] textarea:focus {
outline: 0;
border: 1px solid #41b883; }
.formulate-input[data-classification='select'] .formulate-input-element {
position: relative; }
.formulate-input[data-classification='select'] .formulate-input-element::before {
content: '';
width: 0;
height: 0;
border: .3em solid transparent;
border-top-color: #cecece;
border-bottom-width: 0;
top: 50%;
margin-top: -.1em;
right: 1em;
position: absolute; }
.formulate-input[data-classification='select'] select {
appearance: none;
border-radius: .3em;
border: 1px solid #cecece;
box-sizing: border-box;
background-color: transparent;
font-size: .9em;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
padding: .75em;
display: block;
width: 100%;
font-weight: 400;
padding-right: 2em; }
.formulate-input[data-classification='select'] select::placeholder {
color: #a8a8a8; }
.formulate-input[data-classification='select'] select:focus {
outline: 0;
border: 1px solid #41b883; }
.formulate-input[data-classification='select'] select[data-placeholder-selected] {
color: #a8a8a8; }
.formulate-input[data-classification='box'] .formulate-input-wrapper {
display: flex;
align-items: center; }
.formulate-input[data-classification='box'] .formulate-input-element {
overflow: hidden;
display: flex;
align-items: center; }
.formulate-input[data-classification='box'] .formulate-input-element input {
position: absolute;
left: -999px; }
.formulate-input[data-classification='box'] .formulate-input-element-decorator {
display: block;
width: 1em;
height: 1em;
border-radius: .25em;
border: 1px solid #cecece;
position: relative; }
.formulate-input[data-classification='box'] .formulate-input-element-decorator::before {
content: '';
display: block;
background-size: contain;
background-position: right;
width: calc(100% - .125em);
height: calc(100% - .125em);
box-sizing: border-box;
position: absolute;
top: .0625em;
left: .0625em; }
.formulate-input[data-classification='box'] .formulate-input-element[data-type="radio"] .formulate-input-element-decorator {
border-radius: 1em; }
.formulate-input[data-classification='box'] .formulate-input-element[data-type="radio"] .formulate-input-element-decorator::before {
border-radius: 1em;
width: calc(100% - .5em);
height: calc(100% - .5em);
top: .25em;
left: .25em; }
.formulate-input[data-classification='box'] .formulate-input-element input[type="checkbox"]:checked ~ .formulate-input-element-decorator {
border-color: #41b883; }
.formulate-input[data-classification='box'] .formulate-input-element input[type="checkbox"]:checked ~ .formulate-input-element-decorator::before {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="%2341b883"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>'); }
.formulate-input[data-classification='box'] .formulate-input-element input[type="radio"]:checked ~ .formulate-input-element-decorator {
border-color: #41b883; }
.formulate-input[data-classification='box'] .formulate-input-element input[type="radio"]:checked ~ .formulate-input-element-decorator::before {
background-color: #41b883; }
.formulate-input[data-classification='box'] .formulate-input-element input:focus ~ .formulate-input-element-decorator {
border-color: #41b883; }
.formulate-input[data-classification='box'] .formulate-input-label--after {
margin-left: .5em; }
.formulate-input[data-classification='box'] .formulate-input-label--before {
margin-right: .5em; }
.formulate-input[data-classification="group"] > .formulate-input-wrapper > .formulate-input-label {
margin-bottom: .5em; }

2
dist/snow.min.css vendored Normal file

File diff suppressed because one or more lines are too long

12905
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,21 @@
{
"name": "vue-formulate",
"version": "0.13.0",
"description": "The easiest way to build forms in Vue with validation and vuex support.",
"main": "dist/index.js",
"version": "2.0.0-alpha.0",
"description": "The easiest way to build forms in Vue.",
"main": "dist/formulate.umd.js",
"module": "dist/formulate.esm.js",
"unpkg": "dist/formulate.min.js",
"browser": {
"./sfc": "src/Formulate.js"
},
"scripts": {
"dev": "webpack --hide-modules --watch",
"build": "NODE_ENV=production && webpack -p --hide-modules",
"test": "ava",
"watch": "ava --watch"
"build": "npm run build:umd & npm run build:es & npm run build:unpkg & npm run build:css",
"build:css": "node-sass themes/snow/snow.scss dist/snow.css && postcss --use autoprefixer -b '> 2%' < dist/snow.css | postcss --use cssnano > dist/snow.min.css",
"build:umd": "rollup --config build/rollup.config.js --format umd --file dist/formulate.umd.js",
"build:es": "rollup --config build/rollup.config.js --format es --file dist/formulate.esm.js",
"build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/formulate.min.js",
"test": "NODE_ENV=test jest --config test/jest.conf.js",
"test:watch": "NODE_ENV=test jest --config test/jest.conf.js --watch"
},
"repository": {
"type": "git",
@ -26,40 +34,42 @@
"bugs": {
"url": "https://github.com/wearebraid/vue-formulate/issues"
},
"homepage": "https://github.com/wearebraid/vue-formulate#readme",
"homepage": "https://www.vueformulate.com",
"devDependencies": {
"ava": "^0.25.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-loader": "^7.1.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.24.1",
"eslint": "^4.16.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.2.0",
"vue-loader": "^13.7.0",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.10.0"
},
"ava": {
"require": [
"babel-register",
"babel-polyfill"
]
},
"babel": {
"presets": [
"env"
]
"@babel/core": "^7.6.2",
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
"@babel/preset-env": "^7.6.2",
"@vue/component-compiler-utils": "^3.0.0",
"@vue/test-utils": "^1.0.0-beta.29",
"autoprefixer": "^9.6.1",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.9.0",
"cssnano": "^4.1.10",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^5.2.3",
"jest": "^24.9.0",
"jest-vue-preprocessor": "^1.5.0",
"node-sass": "^4.12.0",
"postcss": "^7.0.18",
"postcss-cli": "^6.1.3",
"rollup": "^1.22.0",
"rollup-plugin-buble": "^0.19.8",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-vue": "^5.0.1",
"vue": "^2.6.10",
"vue-jest": "^3.0.5",
"vue-template-compiler": "^2.6.10",
"vue-template-es2015-compiler": "^1.9.1",
"watch": "^1.0.2"
},
"dependencies": {
"clone-deep": "^3.0.1",
"shortid": "^2.2.8"
"is-plain-object": "^3.0.0",
"nanoid": "^2.1.1"
}
}

5
src/FormulateForm.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<form>
<slot />
</form>
</template>

105
src/FormulateInput.vue Normal file
View File

@ -0,0 +1,105 @@
<template>
<div
class="formulate-input"
:data-classification="classification"
:data-type="type"
>
<div class="formulate-input-wrapper">
<slot
v-if="context.label && context.labelPosition === 'before'"
name="label"
v-bind="context"
>
<label
class="formulate-input-label formulate-input-label--before"
:for="id"
v-text="context.label"
/>
</slot>
<slot v-bind="context">
<component
:is="context.component"
:context="context"
/>
</slot>
<slot
v-if="context.label && context.labelPosition === 'after'"
name="label"
v-bind="context.label"
>
<label
class="formulate-input-label formulate-input-label--after"
:for="id"
v-text="context.label"
/>
</slot>
</div>
<div
v-if="help"
class="formulate-input-help"
v-text="help"
/>
</div>
</template>
<script>
import context from './libs/context'
import nanoid from 'nanoid'
export default {
name: 'FormulateInput',
inheritAttrs: false,
model: {
prop: 'formulateValue',
event: 'input'
},
props: {
type: {
type: String,
default: 'text'
},
formulateValue: {
type: [String, Number, Object, Boolean, Array],
default: false
},
value: {
type: [String, Number, Object, Boolean, Array],
default: false
},
options: {
type: [Object, Array, Boolean],
default: false
},
optionGroups: {
type: [Object, Boolean],
default: false
},
id: {
type: [String, Boolean, Number],
default: () => nanoid(9)
},
label: {
type: [String, Boolean],
default: false
},
labelPosition: {
type: [String, Boolean],
default: false
},
help: {
type: [String, Boolean],
default: false
}
},
computed: {
context,
classification () {
const classification = this.$formulate.classify(this.type)
return (classification === 'box' && this.options) ? 'group' : classification
},
component () {
return this.$formulate.component(this.type)
}
}
}
</script>

View File

@ -0,0 +1,40 @@
<template>
<div class="formulate-input-group">
<component
:is="optionContext.component"
v-for="optionContext in optionsWithContext"
:key="optionContext.id"
v-model="context.model"
v-bind="optionContext"
class="formulate-input-group-item"
/>
</div>
</template>
<script>
export default {
name: 'FormulateInputGroup',
props: {
context: {
type: Object,
required: true
}
},
computed: {
options () {
return this.context.options || []
},
optionsWithContext () {
const { options, labelPosition, attributes, ...context } = this.context
return this.options.map(option => this.groupItemContext(context, option))
}
},
methods: {
groupItemContext (...args) {
return Object.assign({}, ...args, {
component: 'FormulateInput'
})
}
}
}
</script>

View File

@ -0,0 +1,25 @@
/**
* Default base for input components.
*/
export default {
props: {
context: {
type: Object,
required: true
}
},
computed: {
type () {
return this.context.type
},
id () {
return this.context.id
},
attributes () {
return this.context.attributes || {}
},
hasValue () {
return !!this.context.model
}
}
}

View File

@ -1,115 +1,90 @@
import FormulateGroup from './components/Formulate'
import FormulateElement from './components/FormulateElement'
import DefaultRules from './rules'
import DefaultErrors from './errors'
import library from './libs/library'
import isPlainObject from 'is-plain-object'
import FormulateInput from './FormulateInput.vue'
import FormulateForm from './FormulateForm.vue'
import FormulateInputGroup from './FormulateInputGroup.vue'
import FormulateInputBox from './inputs/FormulateInputBox.vue'
import FormulateInputText from './inputs/FormulateInputText.vue'
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
import FormulateInputTextArea from './inputs/FormulateInputTextArea.vue'
/**
* The base formulate library.
*/
class Formulate {
/**
* Initialize vue-formulate.
* Instantiate our base options.
*/
constructor () {
this.defaultOptions = {
registerComponents: true,
tags: {
Formulate: 'formulate',
FormulateElement: 'formulate-element'
this.defaults = {
components: {
FormulateForm,
FormulateInput,
FormulateInputBox,
FormulateInputText,
FormulateInputGroup,
FormulateInputSelect,
FormulateInputTextArea
},
errors: {},
rules: {},
vuexModule: false
library
}
this.errors = DefaultErrors
this.rules = DefaultRules
}
/**
* Install vue-formulate as an instance of Vue.
* @param {Vue} Vue
* Install vue formulate, and register its components.
*/
install (Vue, options = {}) {
install (Vue, options) {
Vue.prototype.$formulate = this
options = Object.assign(this.defaultOptions, options)
if (options.registerComponents) {
Vue.component(options.tags.Formulate, FormulateGroup)
Vue.component(options.tags.FormulateElement, FormulateElement)
this.options = this.extend(this.defaults, options || {})
for (var componentName in this.options.components) {
Vue.component(componentName, this.options.components[componentName])
}
if (options.errors) {
this.errors = Object.assign(this.errors, options.errors)
}
if (options.rules) {
this.rules = Object.assign(this.rules, options.rules)
}
this.options = options
}
/**
* Given a string of rules parse them out to relevant pieces/parts
* @param {string} rulesString
* Create a new object by copying properties of base and extendWith.
* @param {Object} base
* @param {Object} extendWith
*/
parseRules (rulesString) {
return rulesString.split('|')
.map(rule => rule.trim())
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
args: args.split(',').map(arg => arg.trim())
} : {args: []}))
}
/**
* Return the function that generates a validation error message for a given
* validation rule.
* @param {string} rule
*/
errorFactory (rule) {
return this.errors[rule] ? this.errors[rule] : this.errors['default']
}
/**
* Given a particular field, value, validation rules, and form values
* perform asynchronous field validation.
* @param {Object} validatee
* @param {string} rulesString
* @param {Object} values
*/
async validationErrors ({field, value, label}, rulesString, values) {
return rulesString ? Promise.all(
this.parseRules(rulesString)
.map(({rule, args}) => {
if (typeof this.rules[rule] !== 'function') {
throw new Error(`Validation rule is invalid: ${rule}`)
}
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
})
).then(responses => responses.reduce((errors, error) => {
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
}, false)) : false
}
}
const formulate = new Formulate()
export default formulate
export * from './store'
/**
* Mapper to allow bindings to the vuex store for custom fields.
* @param {Object} definitions
*/
export const mapModels = (definitions) => {
const models = {}
for (let mapTo in definitions) {
let [form, field] = definitions[mapTo].split('/')
models[mapTo] = {
set (value) {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
this.$store.commit(`${m}setFieldValue`, {form, field, value})
},
get () {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
if (this.$store.getters[`${m}formValues`][form]) {
return this.$store.getters[`${m}formValues`][form][field]
}
return ''
extend (base, extendWith) {
var merged = {}
for (var key in base) {
if (extendWith.hasOwnProperty(key)) {
merged[key] = isPlainObject(extendWith[key]) && isPlainObject(base[key])
? this.extend(base[key], extendWith[key])
: extendWith[key]
} else {
merged[key] = base[key]
}
}
for (var prop in extendWith) {
if (!merged.hasOwnProperty(prop)) {
merged[prop] = extendWith[prop]
}
}
return merged
}
/**
* Determine what "class" of input this element is given the "type".
* @param {string} type
*/
classify (type) {
if (this.options.library.hasOwnProperty(type)) {
return this.options.library[type].classification
}
return 'unknown'
}
/**
* Determine what type of component to render given the "type".
* @param {string} type
*/
component (type) {
if (this.options.library.hasOwnProperty(type)) {
return this.options.library[type].component
}
return false
}
return models
}
export default new Formulate()

View File

@ -0,0 +1,26 @@
<template>
<div
class="formulate-input-element formulate-input-element--box"
:data-type="context.type"
>
<input
v-model="context.model"
:type="type"
:value="context.value"
v-bind="attributes"
>
<label
class="formulate-input-element-decorator"
:for="id"
/>
</div>
</template>
<script>
import FormulateInputMixin from '../FormulateInputMixin'
export default {
name: 'FormulateInputBox',
mixins: [FormulateInputMixin]
}
</script>

View File

@ -0,0 +1,68 @@
<template>
<div
class="formulate-input-element formulate-input-element--select"
>
<select
v-model="context.model"
v-bind="attributes"
:data-placeholder-selected="placeholderSelected"
>
<option
v-if="context.placeholder"
value=""
disabled
:selected="!hasValue"
>
{{ context.placeholder }}
</option>
<template
v-if="!optionGroups"
>
<option
v-for="option in options"
:key="option.id"
:value="option.value"
v-bind="option.attributes || {}"
v-text="option.label"
/>
</template>
<template
v-else
>
<optgroup
v-for="(subOptions, label) in optionGroups"
:key="label"
:label="label"
>
<option
v-for="option in subOptions"
:key="option.id"
:value="option.value"
v-bind="option.attributes || {}"
v-text="option.label"
/>
</optgroup>
</template>
</select>
</div>
</template>
<script>
import FormulateInputMixin from '../FormulateInputMixin'
export default {
name: 'FormulateInputSelect',
mixins: [FormulateInputMixin],
computed: {
options () {
return this.context.options || {}
},
optionGroups () {
return this.context.optionGroups || false
},
placeholderSelected () {
return !!(!this.hasValue && this.context.attributes.placeholder)
}
}
}
</script>

View File

@ -0,0 +1,20 @@
<template>
<div
class="formulate-input-element formulate-input-element--textarea"
>
<input
v-model="context.model"
:type="type"
v-bind="attributes"
>
</div>
</template>
<script>
import FormulateInputMixin from '../FormulateInputMixin'
export default {
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
}
</script>

View File

@ -0,0 +1,19 @@
<template>
<div
class="formulate-input-element formulate-input-element--textarea"
>
<textarea
v-model="context.model"
v-bind="attributes"
/>
</div>
</template>
<script>
import FormulateInputMixin from '../FormulateInputMixin'
export default {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
}
</script>

118
src/libs/context.js Normal file
View File

@ -0,0 +1,118 @@
import nanoid from 'nanoid'
import { reduce, map } from './utils'
/**
* For a single instance of an input, export all of the context needed to fully
* render that element.
* @return {object}
*/
export default function context () {
return defineModel.call(this, Object.assign({
type: this.type,
value: this.value,
classification: this.classification,
component: this.component,
id: this.id,
label: this.label,
labelPosition: labelPosition.call(this),
attributes: attributeReducer.call(this, this.$attrs)
}, typeContext.call(this)))
}
/**
* Given (this.type), return an object to merge with the context
* @return {object}
*/
function typeContext () {
switch (this.classification) {
case 'select':
return {
options: createOptionList(this.options),
optionGroups: this.optionGroups ? map(this.optionGroups, (k, v) => createOptionList(v)) : false,
placeholder: this.$attrs.placeholder || false
}
case 'group':
if (this.options) {
return {
options: createOptionList(this.options),
component: 'FormulateInputGroup'
}
}
break
default:
return {}
}
}
/**
* Reducer for attributes that will be applied to each core input element.
* @return {object}
*/
function attributeReducer (attributes = {}) {
if (this.id) {
attributes.id = this.id
}
return attributes
}
/**
* Determine the a best-guess location for the label (before or after).
* @return {string} before|after
*/
function labelPosition () {
if (this.labelPosition) {
return this.labelPosition
}
switch (this.classification) {
case 'box':
return 'after'
default:
return 'before'
}
}
/**
* Given an object or array of options, create an array of objects with label,
* value, and id.
* @param {array|object}
* @return {array}
*/
function createOptionList (options) {
if (!Array.isArray(options)) {
return reduce(options, (options, value, label) => options.concat({ value, label, id: nanoid(15) }), [])
} else if (Array.isArray(options) && !options.length) {
return [{ value: this.name, label: (this.label || this.name), id: nanoid(15) }]
}
return options
}
/**
* Create a getter/setter model factory
* @return object
*/
function defineModel (context) {
return Object.defineProperty(context, 'model', {
get: modelGetter.bind(this),
set: modelSetter.bind(this)
})
}
/**
* Get the value from a model.
**/
function modelGetter () {
if (this.type === 'checkbox' && !Array.isArray(this.formulateValue) && this.options) {
return []
}
if (this.formulateValue === false) {
return ''
}
return this.formulateValue
}
/**
* Set the value from a model.
**/
function modelSetter (value) {
this.$emit('input', value)
}

95
src/libs/library.js Normal file
View File

@ -0,0 +1,95 @@
export default {
// === SINGLE LINE TEXT STYLE INPUTS
text: {
classification: 'text',
component: 'FormulateInputText'
},
email: {
classification: 'text',
component: 'FormulateInputText'
},
number: {
classification: 'text',
component: 'FormulateInputText'
},
color: {
classification: 'text',
component: 'FormulateInputText'
},
date: {
classification: 'text',
component: 'FormulateInputText'
},
hidden: {
classification: 'text',
component: 'FormulateInputText'
},
month: {
classification: 'text',
component: 'FormulateInputText'
},
password: {
classification: 'text',
component: 'FormulateInputText'
},
range: {
classification: 'text',
component: 'FormulateInputText'
},
search: {
classification: 'text',
component: 'FormulateInputText'
},
tel: {
classification: 'text',
component: 'FormulateInputText'
},
time: {
classification: 'text',
component: 'FormulateInputText'
},
url: {
classification: 'text',
component: 'FormulateInputText'
},
week: {
classification: 'text',
component: 'FormulateInputText'
},
'datetime-local': {
classification: 'text',
component: 'FormulateInputText'
},
// === MULTI LINE TEXT INPUTS
textarea: {
classification: 'textarea',
component: 'FormulateInputTextArea'
},
// === BOX STYLE INPUTS
checkbox: {
classification: 'box',
component: 'FormulateInputBox'
},
radio: {
classification: 'box',
component: 'FormulateInputBox'
},
// === BUTTON STYLE INPUTS
submit: {
classification: 'button',
component: 'FormulateInputButton'
},
button: {
classification: 'button',
component: 'FormulateInputButton'
},
// === SELECT STYLE INPUTS
select: {
classification: 'select',
component: 'FormulateInputSelect'
}
}

41
src/libs/utils.js Normal file
View File

@ -0,0 +1,41 @@
/**
* Function to map over an object.
* @param {Object} obj An object to map over
* @param {Function} callback
*/
export function map (original, callback) {
const obj = {}
for (let key in original) {
obj[key] = callback(key, original[key])
}
return obj
}
/**
* Function to filter an object's properties
* @param {Object} original
* @param {Function} callback
*/
export function filter (original, callback) {
let obj = {}
for (let key in original) {
if (callback(key, original[key])) {
obj[key] = original[key]
}
}
return obj
}
/**
* Function to reduce an object's properties
* @param {Object} original
* @param {Function} callback
* @param {*} accumulator
*/
export function reduce (original, callback, accumulator) {
for (let key in original) {
accumulator = callback(accumulator, key, original[key])
}
return accumulator
}

115
src~v1/formulate.js Normal file
View File

@ -0,0 +1,115 @@
import FormulateGroup from './components/Formulate'
import FormulateElement from './components/FormulateElement'
import DefaultRules from './rules'
import DefaultErrors from './errors'
class Formulate {
/**
* Initialize vue-formulate.
*/
constructor () {
this.defaultOptions = {
registerComponents: true,
tags: {
Formulate: 'formulate',
FormulateElement: 'formulate-element'
},
errors: {},
rules: {},
vuexModule: false
}
this.errors = DefaultErrors
this.rules = DefaultRules
}
/**
* Install vue-formulate as an instance of Vue.
* @param {Vue} Vue
*/
install (Vue, options = {}) {
Vue.prototype.$formulate = this
options = Object.assign(this.defaultOptions, options)
if (options.registerComponents) {
Vue.component(options.tags.Formulate, FormulateGroup)
Vue.component(options.tags.FormulateElement, FormulateElement)
}
if (options.errors) {
this.errors = Object.assign(this.errors, options.errors)
}
if (options.rules) {
this.rules = Object.assign(this.rules, options.rules)
}
this.options = options
}
/**
* Given a string of rules parse them out to relevant pieces/parts
* @param {string} rulesString
*/
parseRules (rulesString) {
return rulesString.split('|')
.map(rule => rule.trim())
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
args: args.split(',').map(arg => arg.trim())
} : {args: []}))
}
/**
* Return the function that generates a validation error message for a given
* validation rule.
* @param {string} rule
*/
errorFactory (rule) {
return this.errors[rule] ? this.errors[rule] : this.errors['default']
}
/**
* Given a particular field, value, validation rules, and form values
* perform asynchronous field validation.
* @param {Object} validatee
* @param {string} rulesString
* @param {Object} values
*/
async validationErrors ({field, value, label}, rulesString, values) {
return rulesString ? Promise.all(
this.parseRules(rulesString)
.map(({rule, args}) => {
if (typeof this.rules[rule] !== 'function') {
throw new Error(`Validation rule is invalid: ${rule}`)
}
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
})
).then(responses => responses.reduce((errors, error) => {
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
}, false)) : false
}
}
const formulate = new Formulate()
export default formulate
export * from './store'
/**
* Mapper to allow bindings to the vuex store for custom fields.
* @param {Object} definitions
*/
export const mapModels = (definitions) => {
const models = {}
for (let mapTo in definitions) {
let [form, field] = definitions[mapTo].split('/')
models[mapTo] = {
set (value) {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
this.$store.commit(`${m}setFieldValue`, {form, field, value})
},
get () {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
if (this.$store.getters[`${m}formValues`][form]) {
return this.$store.getters[`${m}formValues`][form][field]
}
return ''
}
}
}
return models
}

67
test/Formulate.test.js Normal file
View File

@ -0,0 +1,67 @@
import Formulate from '../dist/formulate.esm.js'
test('can extend simple object', () => {
let a = {
optionA: true,
optionB: '1234'
}
let b = {
optionA: false
}
expect(Formulate.extend(a, b)).toEqual({
optionA: false,
optionB: '1234'
})
})
test('can extend recursively', () => {
let a = {
optionA: true,
optionC: {
first: '123',
third: {
a: 'b'
}
},
optionB: '1234'
}
let b = {
optionB: '567',
optionC: {
first: '1234',
second: '789',
}
}
expect(Formulate.extend(a, b)).toEqual({
optionA: true,
optionC: {
first: '1234',
third: {
a: 'b'
},
second: '789'
},
optionB: '567'
})
})
test('installs on vue instance', () => {
const components = [
'FormulateForm',
'FormulateInput',
'FormulateInputBox',
'FormulateInputText',
'FormulateInputGroup',
'FormulateInputSelect',
'FormulateInputTextArea'
]
const registry = []
function Vue () {}
Vue.component = function (name, instance) {
registry.push(name)
}
Formulate.install(Vue, { extended: true })
expect(Vue.prototype.$formulate).toBe(Formulate)
expect(registry).toEqual(components)
})

View File

@ -0,0 +1,44 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Formulate from '../dist/formulate.esm.js'
import FormulateInput from '../src/FormulateInput.vue'
import FormulateInputBox from '../src/inputs/FormulateInputBox.vue'
import FormulateInputGroup from '../src/FormulateInputGroup.vue'
Vue.use(Formulate)
test('type "checkbox" renders a box element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
expect(wrapper.contains(FormulateInputBox)).toBe(true)
})
test('type "radio" renders a box element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio' } })
expect(wrapper.contains(FormulateInputBox)).toBe(true)
})
test('box inputs properly process options object in context library', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
expect(Array.isArray(wrapper.vm.context.options)).toBe(true)
})
test('type "checkbox" with options renders a group', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
})
test('type "radio" with options renders a group', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
})
test('labelPosition of type "checkbox" defaults to after', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
expect(wrapper.vm.context.labelPosition).toBe('after')
})
test('labelPosition of type "checkbox" with options defaults to before', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'}}})
expect(wrapper.vm.context.labelPosition).toBe('before')
})

View File

@ -0,0 +1,106 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Formulate from '../dist/formulate.esm.js'
import FormulateInput from '../src/FormulateInput.vue'
import FormulateInputText from '../src/inputs/FormulateInputText.vue'
Vue.use(Formulate)
/**
* Test each type of text element
*/
test('type "text" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "search" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'search' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "email" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'email' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "number" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'number' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "color" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'color' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "date" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'date' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "month" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'month' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "password" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'password' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "range" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "tel" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "time" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'time' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "url" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'url' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
test('type "week" renders a text input', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'week' } })
expect(wrapper.contains(FormulateInputText)).toBe(true)
})
/**
* Test functionality to text inputs
*/
test('text inputs automatically have id assigned', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.vm.context).toHaveProperty('id')
expect(wrapper.find(`input[id="${wrapper.vm.id}"]`).exists()).toBe(true)
})
test('text inputs dont have labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('label').exists()).toBe(false)
})
test('text inputs can have labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } })
expect(wrapper.find(`label[for="${wrapper.vm.id}"]`).exists()).toBe(true)
})
test('text inputs dont have help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false)
})
test('text inputs dont have help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true)
})

17
test/jest.conf.js Normal file
View File

@ -0,0 +1,17 @@
const path = require('path')
module.exports = {
rootDir: path.resolve(__dirname, '../'),
moduleFileExtensions: [
'js',
'json',
'vue'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor'
}
}

View File

@ -1,55 +0,0 @@
import test from 'ava'
import formulate from '../dist'
import VueMock from './mocks/VueMock'
test('checks plugin registration', async t => {
let components = []
VueMock.component = (name, object) => components.push({name, object})
formulate.install(VueMock)
t.truthy(VueMock.prototype.$formulate)
t.is(2, components.length)
})
test('parses single rule', async t => {
let rules = formulate.parseRules('required')
t.deepEqual([{rule: 'required', args: []}], rules)
})
test('parses multiple rules and trims', async t => {
let rules = formulate.parseRules('email |required')
t.deepEqual([{rule: 'email', args: []}, {rule: 'required', args: []}], rules)
})
test('parses rule arguments', async t => {
let rules = formulate.parseRules('required(Name)|equals(confirm_password, Your Password)')
t.deepEqual([
{rule: 'required', args: ['Name']},
{rule: 'equals', args: ['confirm_password', 'Your Password']}
], rules)
})
test('tests single validation error', async t => {
t.is(1, (await formulate.validationErrors({field: 'email', value: ''}, 'required')).length)
})
test('tests multiple validation errors', async t => {
t.is(2, (await formulate.validationErrors({field: 'email', value: 'pastaparty'}, 'email|confirmed', {
email_confirmation: 'pizzaparty'
})).length)
})
test('tests empty validation string', async t => {
t.is(false, await formulate.validationErrors({field: 'email', value: 'pastaparty'}, false))
})
test('can extend rules and errors', async t => {
formulate.install(VueMock, {
rules: {
isPizza: ({field, value, error}, label) => value === 'pizza' ? false : error({field, value})
},
errors: {
isPizza: ({field, value}) => `${field} is not a pizza`
}
})
t.deepEqual(['pepperoni is not a pizza'], await formulate.validationErrors({field: 'pepperoni', value: 'meat'}, 'isPizza'))
})

View File

@ -1,4 +0,0 @@
class VueMock {
}
export default VueMock

View File

@ -1,90 +0,0 @@
import test from 'ava'
import f from '../dist'
const rules = f.rules
const error = ({field, value, label}) => {
return `${field}${label}`
}
test('test required rule failure', async t => {
let v = await rules.required({field: 'name', value: '', error, label: 'xyz'})
t.is('string', typeof v)
t.is('namexyz', v)
})
test('test number no failure on empty', async t => {
let v = await rules.number({field: 'name', value: '', error, label: 'xyz'})
t.is(false, v)
})
test('test number failure not number', async t => {
let v = await rules.number({field: 'name', value: 't', error, label: 'xyz'})
t.is('string', typeof v)
t.is('namexyz', v)
})
test('test number is typeof number', async t => {
let v = await rules.number({field: 'name', value: '3', error, label: 'xyz'})
t.is(false, v)
})
test('test required rule empty array failure', async t => {
let v = await rules.required({field: 'name', value: [], error, label: 'xyz'})
t.is('namexyz', v)
})
test('test required rule passes', async t => {
t.is(false, await rules.required({field: 'name', value: 'Justin'}))
})
test('test email rule with valid email', async t => {
t.is(false, await rules.email({field: 'email', value: 'valid@example.com'}))
})
test('test email rule with invalid email', async t => {
t.is('email123', await rules.email({field: 'email', value: 'invalid@example', error, label: '123'}))
})
test('test email with empty email', async t => {
t.is(false, await rules.email({field: 'email', value: '', error}))
})
test('test confirmed passes', async t => {
t.is(false, await rules.confirmed({
field: 'password',
value: 'password',
label: '123',
error,
values: {password_confirmation: 'password'}
}))
})
test('test confirmed passes custom field', async t => {
t.is(false, await rules.confirmed({
field: 'password',
value: 'password',
label: '123',
error,
values: {customfield: 'password'}
}, 'customfield'))
})
test('test confirmed fails', async t => {
t.is('password123', await rules.confirmed({
field: 'password',
value: 'password',
label: '123',
error,
values: {password_confirmation: 'pAssword'}
}))
})
test('test empty confirmed passes', async t => {
t.is(false, await rules.confirmed({
field: 'password',
value: '',
label: '123',
error,
values: {password_confirmation: ''}
}))
})

View File

@ -1,197 +0,0 @@
import test from 'ava'
import {formulateState, formulateGetters, formulateMutations} from '../dist'
test('initial store state', async t => {
t.deepEqual({
values: {},
errors: {},
validationErrors: {},
meta: {}
}, formulateState()())
})
test('extended initial store state', async t => {
t.deepEqual({
values: {},
errors: {},
validationErrors: {},
meta: {},
additionalParam: 'test'
}, formulateState({
additionalParam: 'test'
})())
})
test('validationErrors getter', async t => {
let state = {
validationErrors: {
form: {
field: ['This is an error']
}
}
}
t.is(formulateGetters().formValidationErrors(state), state.validationErrors)
})
test('errors getter', async t => {
let state = {
errors: {
form: {
field: ['This is an error', 'second error']
}
}
}
t.is(formulateGetters().formErrors(state), state.errors)
})
test('values getter', async t => {
let state = {
values: {
form: {
name: 'Johan',
field: 'Guttenberg'
}
}
}
t.is(formulateGetters().formValues(state), state.values)
})
test('formMeta getter', async t => {
let state = {
meta: {
form: {
name: {name: 'name', label: 'Name'},
flail: {name: 'email', label: 'Email'}
}
}
}
t.deepEqual(formulateGetters().formMeta(state), {
form: [
{name: 'name', label: 'Name'},
{name: 'email', label: 'Email'}
]
})
})
test('form has errors', async t => {
let state = {
errors: {
form: {
field: ['This is an error', 'second error']
}
}
}
t.is(true, formulateGetters().hasErrors(state).form)
})
test('form has empty errors', async t => {
let state = {
errors: {
form: {
field: [],
other: []
}
}
}
t.is(false, formulateGetters().hasErrors(state).form)
})
test('form has no errors', async t => {
let state = {
errors: {
form: {}
}
}
t.is(false, formulateGetters().hasErrors(state).form)
})
test('adds a new field value', async t => {
let state = {values: {}}
formulateMutations().setFieldValue(state, {
form: 'form',
field: 'name',
value: 'test name'
})
t.deepEqual(state.values, {form: {name: 'test name'}})
})
test('adds an existing field value', async t => {
let state = {values: {form: {name: 'old name'}}}
formulateMutations().setFieldValue(state, {
form: 'form',
field: 'name',
value: 'new name'
})
t.deepEqual(state.values, {form: {name: 'new name'}})
})
test('adds an error to new field', async t => {
let state = {errors: {}}
formulateMutations().setFieldErrors(state, {
form: 'form',
field: 'name',
errors: ['i dislike this field']
})
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
})
test('adds an error to existing field', async t => {
let state = {errors: {form: {name: ['i like this field']}}}
formulateMutations().setFieldErrors(state, {
form: 'form',
field: 'name',
errors: ['i dislike this field']
})
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
})
test('adds a validationError to new field', async t => {
let state = {validationErrors: {}}
formulateMutations().setFieldValidationErrors(state, {
form: 'form',
field: 'name',
errors: ['i dislike this field']
})
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
})
test('adds a validationError to existing field', async t => {
let state = {validationErrors: {form: {name: ['i like this field']}}}
formulateMutations().setFieldValidationErrors(state, {
form: 'form',
field: 'name',
errors: ['i dislike this field']
})
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
})
test('reset a form', async t => {
let state = {values: {login: {username: 'testuser', password: 'secret'}}}
formulateMutations().resetForm(state, 'login')
t.deepEqual(state.values, {login: {username: undefined, password: undefined}})
})
test('wont mutate undefined form on reset', async t => {
let state = {values: {login: {username: 'testuser', password: 'secret'}}}
formulateMutations().resetForm(state, 'register')
t.deepEqual(state.values, {login: {username: 'testuser', password: 'secret'}})
})
test('removeField deletes values from store', async t => {
let state = {
values: {
register: {
username: 'testuser',
password: 'secret'
}
}
}
formulateMutations().removeField(state, {form: 'register', field: 'password'})
t.deepEqual({
values: {
register: {
username: 'testuser'
}
}
}, state)
})

183
themes/snow/_inputs.scss Normal file
View File

@ -0,0 +1,183 @@
// Formulate inputs
// -----------------------------------------------------------------------------
.formulate-input {
margin-bottom: 2em;
.formulate-input-label {
display: block;
line-height: 1.5;
font-size: .9em;
font-weight: 600;
margin-bottom: .1em;
}
.formulate-input-element {
max-width: 20em;
margin-bottom: .1em;
}
.formulate-input-help {
color: $formulate-gray-ddd;
font-size: .7em;
font-weight: 300;
line-height: 1.5;
margin-bottom: .25em;
}
.formulate-input-group-item {
margin-bottom: .5em;
}
&:last-child {
margin-bottom: 0;
}
// Text inputs
// -----------------------------------------------------------------------------
&[data-classification='text'] {
input {
@include baseinput;
}
}
// Textarea inputs
// -----------------------------------------------------------------------------
&[data-classification='textarea'] {
textarea {
@include baseinput;
}
}
// Select lists
// -----------------------------------------------------------------------------
&[data-classification='select'] {
.formulate-input-element {
position: relative;
&::before {
@include down-arrow;
top: 50%;
margin-top: -.1em;
right: 1em;
position: absolute;
}
}
select {
@include baseinput;
padding-right: 2em;
&[data-placeholder-selected] {
color: $formulate-gray-dd;
}
}
}
// Box inputs
// -----------------------------------------------------------------------------
&[data-classification='box'] {
.formulate-input-wrapper {
display: flex;
align-items: center;
}
.formulate-input-element {
overflow: hidden;
display: flex;
align-items: center;
input {
position: absolute;
left: -999px;
}
&-decorator {
display: block;
width: 1em;
height: 1em;
border-radius: .25em;
border: 1px solid $formulate-gray-d;
position: relative;
&::before {
content: '';
display: block;
background-size: contain;
background-position: right;
width: calc(100% - .125em);
height: calc(100% - .125em);
box-sizing: border-box;
position: absolute;
top: .0625em;
left: .0625em;
}
}
&[data-type="radio"] {
.formulate-input-element-decorator {
border-radius: 1em;
&::before {
border-radius: 1em;
width: calc(100% - .5em);
height: calc(100% - .5em);
top: .25em;
left: .25em;
}
}
}
input[type="checkbox"]:checked {
& ~ .formulate-input-element-decorator {
border-color: $formulate-green;
&::before {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="%23#{str-slice("#{$formulate-green}", 2)}"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>');
}
}
}
input[type="radio"]:checked {
& ~ .formulate-input-element-decorator {
border-color: $formulate-green;
&::before {
background-color: $formulate-green;
}
}
}
input:focus {
& ~ .formulate-input-element-decorator {
border-color: $formulate-green;
}
}
}
.formulate-input-label--after {
margin-left: .5em;
}
.formulate-input-label--before {
margin-right: .5em;
}
}
&[data-classification="group"] {
& > .formulate-input-wrapper {
& > .formulate-input-label {
margin-bottom: .5em;
}
}
}
}

View File

@ -0,0 +1,56 @@
// Snow theme colors
// -----------------------------------------------------------------------------
$formulate-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
$formulate-gray: #efefef;
$formulate-gray-d: #cecece;
$formulate-gray-dd: #a8a8a8;
$formulate-gray-ddd: #6d6d6d;
$formulate-gray-dddd: #2e2e2e;
$formulate-dark: #000000;
$formulate-blue-l: #f3f4f4;
$formulate-green: #41b883;
$formulate-yellow-d: #6b5900;
$formulate-yellow: #e6c000;
$formulate-yellow-l: #fff8d2;
// Mixins
// -----------------------------------------------------------------------------
@mixin baseinput {
appearance: none;
border-radius: .3em;
border: 1px solid $formulate-gray-d;
box-sizing: border-box;
background-color: transparent;
font-size: .9em;
font-family: $formulate-font-stack;
padding: .75em;
display: block;
width: 100%;
font-weight: 400;
&::placeholder {
color: $formulate-gray-dd;
}
&:focus {
outline: 0;
border: 1px solid $formulate-green;
}
}
@mixin down-arrow {
content: '';
width: 0;
height: 0;
border: .3em solid transparent;
border-top-color: $formulate-gray-d;
border-bottom-width: 0;
}

2
themes/snow/snow.scss Normal file
View File

@ -0,0 +1,2 @@
@import 'variables';
@import 'inputs';

View File

@ -1,40 +0,0 @@
const webpack = require('webpack')
const path = require('path')
module.exports = {
entry: path.resolve(`${__dirname}/src/formulate.js`),
output: {
path: path.resolve(`${__dirname}/dist/`),
filename: 'index.js',
libraryTarget: 'umd',
library: 'vue-formulate',
umdNamedDefine: true
},
resolve: {
extensions: ['.js', '.vue']
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {}
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.resolve(__dirname, 'src')]
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
minimize: true,
sourceMap: false,
mangle: true,
compress: {
warnings: false
}
})
]
}